From dfcc679d5afaba3130356a2c6c922f7105f5a302 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 26 Dec 2024 20:54:24 +0100 Subject: [PATCH 1/2] Round widget coordinates to even multiple of 1/32 (#5517) * Closes https://github.com/emilk/egui/pull/5197 * Closes https://github.com/emilk/egui/issues/5163 This should help prevent rounding errors in layout code. @lucasmerlin you may wanna test this with `egui_flex` --- crates/egui/src/containers/area.rs | 10 +- crates/egui/src/containers/panel.rs | 9 +- crates/egui/src/containers/resize.rs | 7 +- crates/egui/src/containers/window.rs | 18 +- crates/egui/src/context.rs | 15 +- crates/egui/src/grid.rs | 8 +- crates/egui/src/layout.rs | 6 +- crates/egui/src/style.rs | 8 +- crates/egui/src/ui.rs | 18 +- crates/egui/src/widget_text.rs | 9 +- crates/egui_demo_lib/src/rendering_test.rs | 9 +- .../tests/snapshots/demos/Code Example.png | 4 +- .../tests/snapshots/demos/Context Menus.png | 4 +- .../tests/snapshots/demos/Dancing Strings.png | 4 +- .../tests/snapshots/demos/Drag and Drop.png | 4 +- .../tests/snapshots/demos/Extra Viewport.png | 4 +- .../tests/snapshots/demos/Font Book.png | 4 +- .../tests/snapshots/demos/Frame.png | 4 +- .../tests/snapshots/demos/Highlighting.png | 4 +- .../tests/snapshots/demos/Misc Demos.png | 4 +- .../tests/snapshots/demos/Multi Touch.png | 4 +- .../tests/snapshots/demos/Painting.png | 4 +- .../tests/snapshots/demos/Pan Zoom.png | 4 +- .../tests/snapshots/demos/Scrolling.png | 4 +- .../tests/snapshots/demos/Strip.png | 4 +- .../tests/snapshots/demos/Table.png | 4 +- .../tests/snapshots/demos/Text Layout.png | 4 +- .../tests/snapshots/demos/TextEdit.png | 4 +- .../tests/snapshots/demos/Undo Redo.png | 4 +- .../tests/snapshots/demos/Window Options.png | 4 +- .../tests/snapshots/modals_1.png | 4 +- .../tests/snapshots/modals_2.png | 4 +- .../tests/snapshots/modals_3.png | 4 +- ...rop_should_prevent_focusing_lower_area.png | 4 +- .../snapshots/rendering_test/dpi_1.00.png | 4 +- .../snapshots/rendering_test/dpi_1.25.png | 4 +- .../snapshots/rendering_test/dpi_1.50.png | 4 +- .../snapshots/rendering_test/dpi_1.67.png | 4 +- .../snapshots/rendering_test/dpi_1.75.png | 4 +- .../snapshots/rendering_test/dpi_2.00.png | 4 +- .../tests/snapshots/widget_gallery.png | 4 +- crates/egui_extras/src/layout.rs | 4 +- crates/emath/src/gui_rounding.rs | 201 ++++++++++++++++++ crates/emath/src/lib.rs | 2 + crates/epaint/src/text/font.rs | 26 +-- crates/epaint/src/text/fonts.rs | 6 +- crates/epaint/src/text/text_layout.rs | 22 +- crates/epaint/src/text/text_layout_types.rs | 15 +- 48 files changed, 377 insertions(+), 136 deletions(-) create mode 100644 crates/emath/src/gui_rounding.rs diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index a2bfbe1905f..6af762561fc 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -2,6 +2,8 @@ //! It has no frame or own size. It is potentially movable. //! It is the foundation for windows and popups. +use emath::GuiRounding as _; + use crate::{ emath, pos2, Align2, Context, Id, InnerResponse, LayerId, NumExt, Order, Pos2, Rect, Response, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetRect, WidgetWithState, @@ -66,6 +68,7 @@ impl AreaState { pivot_pos.x - self.pivot.x().to_factor() * size.x, pivot_pos.y - self.pivot.y().to_factor() * size.y, ) + .round_ui() } /// Move the left top positions of the area. @@ -80,7 +83,7 @@ impl AreaState { /// Where the area is on screen. pub fn rect(&self) -> Rect { let size = self.size.unwrap_or_default(); - Rect::from_min_size(self.left_top_pos(), size) + Rect::from_min_size(self.left_top_pos(), size).round_ui() } } @@ -493,12 +496,11 @@ impl Area { if constrain { state.set_left_top_pos( - ctx.constrain_window_rect_to_area(state.rect(), constrain_rect) - .min, + Context::constrain_window_rect_to_area(state.rect(), constrain_rect).min, ); } - state.set_left_top_pos(ctx.round_pos_to_pixels(state.left_top_pos())); + state.set_left_top_pos(state.left_top_pos()); // Update response with possibly moved/constrained rect: move_response.rect = state.rect(); diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 7cf22a693db..f659a8c4efa 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -15,6 +15,8 @@ //! //! Add your [`crate::Window`]:s after any top-level panels. +use emath::GuiRounding as _; + use crate::{ lerp, vec2, Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt, Rangef, Rect, Sense, Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, @@ -264,6 +266,8 @@ impl SidePanel { } } + panel_rect = panel_rect.round_ui(); + let mut panel_ui = ui.new_child( UiBuilder::new() .id_salt(id) @@ -756,6 +760,8 @@ impl TopBottomPanel { } } + panel_rect = panel_rect.round_ui(); + let mut panel_ui = ui.new_child( UiBuilder::new() .id_salt(id) @@ -1130,7 +1136,6 @@ impl CentralPanel { ctx: &Context, add_contents: Box R + 'c>, ) -> InnerResponse { - let available_rect = ctx.available_rect(); let id = Id::new((ctx.viewport_id(), "central_panel")); let mut panel_ui = Ui::new( @@ -1138,7 +1143,7 @@ impl CentralPanel { id, UiBuilder::new() .layer_id(LayerId::background()) - .max_rect(available_rect), + .max_rect(ctx.available_rect().round_ui()), ); panel_ui.set_clip_rect(ctx.screen_rect()); diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index 84f687783aa..0165eba996a 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -221,7 +221,8 @@ impl Resize { .at_most(self.max_size) .at_most( ui.ctx().screen_rect().size() - ui.spacing().window_margin.sum(), // hack for windows - ); + ) + .round_ui(); State { desired_size: default_size, @@ -233,7 +234,8 @@ impl Resize { state.desired_size = state .desired_size .at_least(self.min_size) - .at_most(self.max_size); + .at_most(self.max_size) + .round_ui(); let mut user_requested_size = state.requested_size.take(); @@ -383,6 +385,7 @@ impl Resize { } } +use emath::GuiRounding as _; use epaint::Stroke; pub fn paint_resize_corner(ui: &Ui, response: &Response) { diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index e2d133f39ad..44c3dda58c1 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -7,6 +7,7 @@ use crate::{ Align, Align2, Context, CursorIcon, Id, InnerResponse, LayerId, NumExt, Order, Response, Sense, TextStyle, Ui, UiKind, Vec2b, WidgetInfo, WidgetRect, WidgetText, WidgetType, }; +use emath::GuiRounding as _; use epaint::{emath, pos2, vec2, Galley, Pos2, Rect, RectShape, Rounding, Shape, Stroke, Vec2}; use super::scroll_area::ScrollBarVisibility; @@ -788,13 +789,12 @@ fn resize_response( area: &mut area::Prepared, resize_id: Id, ) { - let Some(new_rect) = move_and_resize_window(ctx, &resize_interaction) else { + let Some(mut new_rect) = move_and_resize_window(ctx, &resize_interaction) else { return; }; - let mut new_rect = ctx.round_rect_to_pixels(new_rect); if area.constrain() { - new_rect = ctx.constrain_window_rect_to_area(new_rect, area.constrain_rect()); + new_rect = Context::constrain_window_rect_to_area(new_rect, area.constrain_rect()); } // TODO(emilk): add this to a Window state instead as a command "move here next frame" @@ -819,18 +819,18 @@ fn move_and_resize_window(ctx: &Context, interaction: &ResizeInteraction) -> Opt let mut rect = interaction.start_rect; // prevent drift if interaction.left.drag { - rect.min.x = ctx.round_to_pixel(pointer_pos.x); + rect.min.x = pointer_pos.x; } else if interaction.right.drag { - rect.max.x = ctx.round_to_pixel(pointer_pos.x); + rect.max.x = pointer_pos.x; } if interaction.top.drag { - rect.min.y = ctx.round_to_pixel(pointer_pos.y); + rect.min.y = pointer_pos.y; } else if interaction.bottom.drag { - rect.max.y = ctx.round_to_pixel(pointer_pos.y); + rect.max.y = pointer_pos.y; } - Some(rect) + Some(rect.round_ui()) } fn resize_interaction( @@ -1070,7 +1070,7 @@ impl TitleBar { let item_spacing = ui.spacing().item_spacing; let button_size = Vec2::splat(ui.spacing().icon_width); - let pad = (height - button_size.y) / 2.0; // calculated so that the icon is on the diagonal (if window padding is symmetrical) + let pad = ((height - button_size.y) / 2.0).round_ui(); // calculated so that the icon is on the diagonal (if window padding is symmetrical) if collapsible { ui.add_space(pad); diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 322007113ba..eaca8d36f52 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -3,6 +3,7 @@ use std::{borrow::Cow, cell::RefCell, panic::Location, sync::Arc, time::Duration}; use containers::area::AreaState; +use emath::GuiRounding as _; use epaint::{ emath::{self, TSTransform}, mutex::RwLock, @@ -2121,7 +2122,7 @@ impl Context { // --------------------------------------------------------------------- /// Constrain the position of a window/area so it fits within the provided boundary. - pub(crate) fn constrain_window_rect_to_area(&self, window: Rect, area: Rect) -> Rect { + pub(crate) fn constrain_window_rect_to_area(window: Rect, area: Rect) -> Rect { let mut pos = window.min; // Constrain to screen, unless window is too large to fit: @@ -2133,9 +2134,7 @@ impl Context { pos.y = pos.y.at_most(area.bottom() + margin_y - window.height()); // move right if needed pos.y = pos.y.at_least(area.top() - margin_y); // move down if needed - pos = self.round_pos_to_pixels(pos); - - Rect::from_min_size(pos, window.size()) + Rect::from_min_size(pos, window.size()).round_ui() } } @@ -2568,7 +2567,7 @@ impl Context { /// Position and size of the egui area. pub fn screen_rect(&self) -> Rect { - self.input(|i| i.screen_rect()) + self.input(|i| i.screen_rect()).round_ui() } /// How much space is still available after panels has been added. @@ -2576,7 +2575,7 @@ impl Context { /// This is the "background" area, what egui doesn't cover with panels (but may cover with windows). /// This is also the area to which windows are constrained. pub fn available_rect(&self) -> Rect { - self.pass_state(|s| s.available_rect()) + self.pass_state(|s| s.available_rect()).round_ui() } /// How much space is used by panels and windows. @@ -2586,7 +2585,7 @@ impl Context { for (_id, window) in ctx.memory.areas().visible_windows() { used = used.union(window.rect()); } - used + used.round_ui() }) } @@ -2594,7 +2593,7 @@ impl Context { /// /// You can shrink your egui area to this size and still fit all egui components. pub fn used_size(&self) -> Vec2 { - self.used_rect().max - Pos2::ZERO + (self.used_rect().max - Pos2::ZERO).round_ui() } // --------------------------------------------------------------------- diff --git a/crates/egui/src/grid.rs b/crates/egui/src/grid.rs index 0342f6f5227..102027dd8e4 100644 --- a/crates/egui/src/grid.rs +++ b/crates/egui/src/grid.rs @@ -1,3 +1,5 @@ +use emath::GuiRounding as _; + use crate::{ vec2, Align2, Color32, Context, Id, InnerResponse, NumExt, Painter, Rect, Region, Style, Ui, UiBuilder, Vec2, @@ -179,13 +181,15 @@ impl GridLayout { let width = self.prev_state.col_width(self.col).unwrap_or(0.0); let height = self.prev_row_height(self.row); let size = child_size.max(vec2(width, height)); - Rect::from_min_size(cursor.min, size) + Rect::from_min_size(cursor.min, size).round_ui() } #[allow(clippy::unused_self)] pub(crate) fn align_size_within_rect(&self, size: Vec2, frame: Rect) -> Rect { // TODO(emilk): allow this alignment to be customized - Align2::LEFT_CENTER.align_size_within_rect(size, frame) + Align2::LEFT_CENTER + .align_size_within_rect(size, frame) + .round_ui() } pub(crate) fn justify_and_align(&self, frame: Rect, size: Vec2) -> Rect { diff --git a/crates/egui/src/layout.rs b/crates/egui/src/layout.rs index 0c9bb494133..e967aaa6d57 100644 --- a/crates/egui/src/layout.rs +++ b/crates/egui/src/layout.rs @@ -1,3 +1,5 @@ +use emath::GuiRounding as _; + use crate::{ emath::{pos2, vec2, Align2, NumExt, Pos2, Rect, Vec2}, Align, @@ -394,7 +396,7 @@ impl Layout { pub fn align_size_within_rect(&self, size: Vec2, outer: Rect) -> Rect { debug_assert!(size.x >= 0.0 && size.y >= 0.0); debug_assert!(!outer.is_negative()); - self.align2().align_size_within_rect(size, outer) + self.align2().align_size_within_rect(size, outer).round_ui() } fn initial_cursor(&self, max_rect: Rect) -> Rect { @@ -634,7 +636,7 @@ impl Layout { debug_assert!(!frame_rect.any_nan()); debug_assert!(!frame_rect.is_negative()); - frame_rect + frame_rect.round_ui() } /// Apply justify (fill width/height) and/or alignment after calling `next_space`. diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 14b5aecd6ee..91c0e09dbfa 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -1168,11 +1168,9 @@ pub struct DebugOptions { /// Show interesting widgets under the mouse cursor. pub show_widget_hits: bool, - /// If true, highlight widgets that are not aligned to integer point coordinates. + /// If true, highlight widgets that are not aligned to [`emath::GUI_ROUNDING`]. /// - /// It's usually a good idea to keep to integer coordinates to avoid rounding issues. - /// - /// See for more. + /// See [`emath::GuiRounding`] for more. pub show_unaligned: bool, } @@ -1189,7 +1187,7 @@ impl Default for DebugOptions { show_resize: false, show_interactive_widgets: false, show_widget_hits: false, - show_unaligned: false, + show_unaligned: cfg!(debug_assertions), } } } diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 6cc474e141b..9cf2d4b5427 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -3,6 +3,7 @@ use std::{any::Any, hash::Hash, sync::Arc}; +use emath::GuiRounding as _; use epaint::mutex::RwLock; use crate::{ @@ -716,7 +717,9 @@ impl Ui { self.painter().layer_id() } - /// The height of text of this text style + /// The height of text of this text style. + /// + /// Returns a value rounded to [`emath::GUI_ROUNDING`]. pub fn text_style_height(&self, style: &TextStyle) -> f32 { self.fonts(|f| f.row_height(&style.resolve(self.style()))) } @@ -1295,6 +1298,7 @@ impl Ui { /// Ignore the layout of the [`Ui`]: just put my widget here! /// The layout cursor will advance to past this `rect`. pub fn allocate_rect(&mut self, rect: Rect, sense: Sense) -> Response { + let rect = rect.round_ui(); let id = self.advance_cursor_after_rect(rect); self.interact(rect, id, sense) } @@ -1302,6 +1306,8 @@ impl Ui { /// Allocate a rect without interacting with it. pub fn advance_cursor_after_rect(&mut self, rect: Rect) -> Id { debug_assert!(!rect.any_nan()); + let rect = rect.round_ui(); + let item_spacing = self.spacing().item_spacing; self.placer.advance_after_rects(rect, rect, item_spacing); register_rect(self, rect); @@ -3018,7 +3024,7 @@ impl Drop for Ui { /// Show this rectangle to the user if certain debug options are set. #[cfg(debug_assertions)] fn register_rect(ui: &Ui, rect: Rect) { - use emath::Align2; + use emath::{Align2, GuiRounding}; let debug = ui.style().debug; @@ -3031,16 +3037,16 @@ fn register_rect(ui: &Ui, rect: Rect) { .text(p0, Align2::LEFT_TOP, "Unaligned", font_id, color); }; - if rect.left().fract() != 0.0 { + if rect.left() != rect.left().round_ui() { unaligned_line(rect.left_top(), rect.left_bottom()); } - if rect.right().fract() != 0.0 { + if rect.right() != rect.right().round_ui() { unaligned_line(rect.right_top(), rect.right_bottom()); } - if rect.top().fract() != 0.0 { + if rect.top() != rect.top().round_ui() { unaligned_line(rect.left_top(), rect.right_top()); } - if rect.bottom().fract() != 0.0 { + if rect.bottom() != rect.bottom().round_ui() { unaligned_line(rect.left_bottom(), rect.right_bottom()); } } diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index 011a4adcbb0..d5cd16f09fd 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -1,5 +1,7 @@ use std::{borrow::Cow, sync::Arc}; +use emath::GuiRounding as _; + use crate::{ text::{LayoutJob, TextWrapping}, Align, Color32, FontFamily, FontSelection, Galley, Style, TextStyle, TextWrapMode, Ui, Visuals, @@ -278,6 +280,8 @@ impl RichText { } /// Read the font height of the selected text style. + /// + /// Returns a value rounded to [`emath::GUI_ROUNDING`]. pub fn font_height(&self, fonts: &epaint::Fonts, style: &Style) -> f32 { let mut font_id = self.text_style.as_ref().map_or_else( || FontSelection::Default.resolve(style), @@ -635,15 +639,16 @@ impl WidgetText { } } + /// Returns a value rounded to [`emath::GUI_ROUNDING`]. pub(crate) fn font_height(&self, fonts: &epaint::Fonts, style: &Style) -> f32 { match self { Self::RichText(text) => text.font_height(fonts, style), Self::LayoutJob(job) => job.font_height(fonts), Self::Galley(galley) => { if let Some(row) = galley.rows.first() { - row.height() + row.height().round_ui() } else { - galley.size().y + galley.size().y.round_ui() } } } diff --git a/crates/egui_demo_lib/src/rendering_test.rs b/crates/egui_demo_lib/src/rendering_test.rs index f9ead5c584c..df3add3776e 100644 --- a/crates/egui_demo_lib/src/rendering_test.rs +++ b/crates/egui_demo_lib/src/rendering_test.rs @@ -681,7 +681,7 @@ fn mul_color_gamma(left: Color32, right: Color32) -> Color32 { #[cfg(test)] mod tests { use crate::ColorTest; - use egui::vec2; + use egui_kittest::kittest::Queryable as _; #[test] pub fn rendering_test() { @@ -689,13 +689,16 @@ mod tests { for dpi in [1.0, 1.25, 1.5, 1.75, 1.6666667, 2.0] { let mut color_test = ColorTest::default(); let mut harness = egui_kittest::Harness::builder() - .with_size(vec2(2000.0, 2000.0)) .with_pixels_per_point(dpi) .build_ui(|ui| { color_test.ui(ui); }); - //harness.set_size(harness.ctx.used_size()); + { + // Expand color-test collapsing header + harness.get_by_label("Color test").click(); + harness.run(); + } harness.fit_contents(); diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png index 162fc51a1df..33121fe85fa 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01aaa4ef1a167a94fa1e5163550aabe4fa5e9f3a012b26170fe3088a6ca32d94 -size 81064 +oid sha256:f93cbf4968e69e2b51256631ab8703d1af2274147924f8d802d9da0f9767aa1b +size 81162 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Context Menus.png b/crates/egui_demo_lib/tests/snapshots/demos/Context Menus.png index c29267377b7..9191ade8504 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Context Menus.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Context Menus.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:684648bea4ef5ce138fc25dbe7576e3937a797e87f2244cb3656ff8b4c2777f5 -size 11574 +oid sha256:b7157cc3a1c87b63a1af41e9c3493beb426f9a2c7e3ce1adf228a8d1aee23818 +size 11549 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png b/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png index bdc4739c77f..ac28a61122d 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad38bff7cc5661be43e730e1b34c444b571b24b9f50791209496a1687610dd3d -size 20543 +oid sha256:7fcc5ac195fdf455836cce7503c530d56a5c9e053cf743f8165c2b33212bfaae +size 20645 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png b/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png index 035d4c13f7b..c25087b71fe 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff78748f2571c49638d8fe8fdc859aaa5181758aad65498b7217551350fb9138 -size 20672 +oid sha256:eee96c06b54b0c9779a84bce1caf94f907593d675625a51791b18925881bd619 +size 20883 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png b/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png index 2d48a7e085f..aa6372175d0 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9dee66004cc47f5e27aaac34d137ff005eedf70cbfa3fbe43153dfd5c09d5e18 -size 10610 +oid sha256:cced772c3830cf1d8d5f946cb457f1a0a059ac3b08b791899db5f5fe8132fc85 +size 10711 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png b/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png index 69201f86154..f8c028fb0cc 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d1086b789f1fe0a8085c86f5b6a5ae7ecb53020f385b84775d6812ebc9d74a3 -size 132349 +oid sha256:cce41bc2887462a0a3aebc4148faf769d3666ff4a0e4c7db6ffbddb174be0ed3 +size 135734 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Frame.png b/crates/egui_demo_lib/tests/snapshots/demos/Frame.png index ff4d08bafe8..28a855fa38c 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Frame.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Frame.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08be378c01e376aab6e99ba3158519bbd7b301e815dc3447b57c9abab558977f -size 24237 +oid sha256:194c07bce8370b886b1bcb58936044e71f4725b2034dff3bbb7e3f710cf82f17 +size 24701 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png b/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png index a3cab2a3097..edc26309f9f 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53097b2c26ebcba8b8ad657ed8e52ca40261155e96dbbfca1e8eb01fce25d290 -size 17586 +oid sha256:1b5a71ea8cd6b3e2b267d668c1f149689e938eef3b4038a0935823d37aba348f +size 17806 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png index 80cb5b5a177..9eea2ac7b92 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:38d21b6f8c364f86ad759e88ea1068649c23c58ded5d2953ba8ff1c83b46112f -size 63884 +oid sha256:17901f3e235cfbac03fb2334f36f614ed152a25b3a212e78beba9fb342039844 +size 65339 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png b/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png index 23bad456c8c..af91ac8e61f 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:83162f8c496a55230375dbc4cc636cfacf63049c913904bea9d06bdb56e63da6 -size 36282 +oid sha256:950c825c8f6eadc682c57fe8843ded2ceb6981b6d4086d427dcf0d27740df8ef +size 36795 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Painting.png b/crates/egui_demo_lib/tests/snapshots/demos/Painting.png index 89a17e67406..8ad735d6c6f 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Painting.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Painting.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2537c681d1ffceb5cf4bf19d11295891525c96aea0b1422ab28f133021185be0 -size 17451 +oid sha256:e68f145f57812a1f6a81e1a8ab41bcb134320dcaaee3f462d1aa00d6c331752a +size 17579 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Pan Zoom.png b/crates/egui_demo_lib/tests/snapshots/demos/Pan Zoom.png index 384840b7101..6a1b79e3d78 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Pan Zoom.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Pan Zoom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ccfda16ef7cdf94f7fbbd2c0f8df6f6de7904969e2a66337920c32608a6f9f05 -size 25357 +oid sha256:6aec5a6291ad2796457d1c4fc44228c75bf075262aa672749a3cceb4baef1e08 +size 25271 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png index 440a51f3871..eef59e1b79c 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be2ac005fd5aafa293e21b162c22a09078e46d2d45b6208ce0f7841eeb05314a -size 183934 +oid sha256:4c50f7cbac65ff98b4bdb11034dd18bf19836ea22f27e0ed04415ac763d86d09 +size 188067 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Strip.png b/crates/egui_demo_lib/tests/snapshots/demos/Strip.png index 69ec9e88230..24e613b1c91 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Strip.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Strip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df7dabf726620ab5205ce153f692d1ba02365848ead7b79c95b873d5121d52a6 -size 25850 +oid sha256:764fc4f5fc60cc72d9731c7073e05abda70c1b5caf2992aabad63772e0cd5e33 +size 25905 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Table.png b/crates/egui_demo_lib/tests/snapshots/demos/Table.png index 9f3618ba039..fd796f69561 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Table.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Table.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae6c2e3aad43cfad3322340ff7045ec50ba01d58feb7b8acc5ba062a8a5c9ab8 -size 70230 +oid sha256:cce44cee74da69ce065e79f92919928bbdca6b1e57b44f0165666508b3cddce3 +size 72240 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png b/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png index ff972ae484c..dfef3f17542 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec0c2efff75cb8d621f5a4ea59f9fa8d3076521ca34f4499e07fb9dc8681d7ba -size 65916 +oid sha256:10d278417ae66a788a88676874381b2d7c026b3130ca1c9178e2027595bb8e74 +size 67383 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png index 7af3611b924..eb234e7fb7f 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c04aee0a3a77a3691bb601a93871117500be917e0896138fda43251454ec04c2 -size 20988 +oid sha256:6f13c687e27870df62488563de07b02711eb508ec7eee91470a44f2669610889 +size 21342 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Undo Redo.png b/crates/egui_demo_lib/tests/snapshots/demos/Undo Redo.png index a635cdfabd0..a5d82e4378c 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Undo Redo.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Undo Redo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e682f5cb9ecb1bdf89281c2ba1612078e70e97f28c76facc64d717e4015ced6a -size 12977 +oid sha256:65674a0da6ee80eff735a6404ad328332ad0ab48766de9b1d358e38ccb3ecc29 +size 13059 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png b/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png index f28774030aa..0dc3784a003 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15acfb041cc53ef9bd966d6edd53a6b692cdb645ae5cf34bc20e70d403371c30 -size 34809 +oid sha256:450ede959fac122e6b045d18a6ab62952fa03371508704a1f2303d6379e41593 +size 35523 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_1.png b/crates/egui_demo_lib/tests/snapshots/modals_1.png index 461bef728bc..87d1bd47bb0 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_1.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8348ff582e11fdc9baf008b5434f81f8d77b834479cb3765c87d1f4fd695e30f -size 48212 +oid sha256:bd53d25bcecc4f22cb9226237d69557696e8aa08b969157c0cdb015cfacf206d +size 48244 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_2.png b/crates/egui_demo_lib/tests/snapshots/modals_2.png index 0f1273f41df..bfac37e75a9 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_2.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23482b77cbd817c66421a630e409ac3d8c5d24de00aa91e476e8d42b607c24b1 -size 48104 +oid sha256:d9d9226996b0eecf516b9b89f09c57e5f7ad43cc154a2c8937779421a6580113 +size 48157 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_3.png b/crates/egui_demo_lib/tests/snapshots/modals_3.png index ba8cca6228b..ab2e1f12553 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_3.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d94aa33d72c32f6f1aafab92c9753dc07bc5224c701003ac7fe8a01ae8c701a -size 44011 +oid sha256:49401fcd6e348c75d81ad7419e1fa1a77500ca3d2360b418e8304ad5843e5b3a +size 44080 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png index 14d6fb9cbfc..8042b02babb 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1a5d265470c36e64340ccceea4ade464b3c4a1177d60630b02ae8287934748f -size 44026 +oid sha256:6cc1363890eb878ec7f37bf5350dd5a81ed39e16655b2e8b7fee0937e605bcae +size 44044 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.00.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.00.png index ae442978324..b4c96d8397d 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.00.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:023eaa363b42ec24ae845dc2ca9ff271a0bd47217e625785d3716044ecfa7a64 -size 278444 +oid sha256:dc83a67ac7ff574f981dae02c05ecfba934df4fb181bba12fd7607ef9cba5aa6 +size 523650 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.25.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.25.png index 428160c867d..82426652255 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.25.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d81f618e54176b1c43b710121f249e13ce29827fbea3451827ab62229006677e -size 378603 +oid sha256:a0fe7626d08a4d28a66d39824cdcd8fef140c7c818e5fc07a7e86756c249875d +size 737802 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.50.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.50.png index 003a08bd6e3..29514c4e7ff 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.50.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d8eca6d5555ef779233175615b877fb91318b4a09a37e5cfbe71973d56f4caf -size 465907 +oid sha256:477437c5c8766beac88a96f531d8dfe5fac1fe2cdd4bad02fbc53610eb8925fb +size 887154 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.67.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.67.png index 629edf05c1e..3d412b56667 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.67.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.67.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4768804f57dfc54c5f6b84a2686038b8d630a28c7e928ae044d5b2ce8377e2cd -size 538775 +oid sha256:8aa0503d6e5dc82953000c7e68be826b0bf4f288aef82c2d5d832189538eb3b2 +size 1002104 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.75.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.75.png index 98ffb99034b..f10d189a56e 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.75.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fcee0e0302f33d681348d62bee3b548beb494c6dd1fa3454586986e0b699e162 -size 572403 +oid sha256:bef9f6dde686733e04a447be280fd50704db163d330fd8c663357b057cd50e27 +size 1080661 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_2.00.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_2.00.png index 04d581799bd..6890b9d4611 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_2.00.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_2.00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:254a8dff0b1d4b74971fd3bd4044c4ec0ce49412a95e98419a14dc55b32a4fc9 -size 663272 +oid sha256:136c60283286df17e6751220e0c849028bc1379910995cf9e4930c5d63d0a90f +size 1235057 diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery.png index 914b3da9ead..a2adca3db43 100644 --- a/crates/egui_demo_lib/tests/snapshots/widget_gallery.png +++ b/crates/egui_demo_lib/tests/snapshots/widget_gallery.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c069ef4f86beeeafd8686f30fc914bedd7e7e7ec38fd96e9a46ac6b31308c43f -size 160883 +oid sha256:9da93a7c27e17d370a3df73930d71694d436c300656b054722646ea57db45810 +size 160803 diff --git a/crates/egui_extras/src/layout.rs b/crates/egui_extras/src/layout.rs index 239e7b1d29b..0121f83e0f2 100644 --- a/crates/egui_extras/src/layout.rs +++ b/crates/egui_extras/src/layout.rs @@ -1,4 +1,4 @@ -use egui::{Id, Pos2, Rect, Response, Sense, Ui, UiBuilder}; +use egui::{emath::GuiRounding, Id, Pos2, Rect, Response, Sense, Ui, UiBuilder}; #[derive(Clone, Copy)] pub(crate) enum CellSize { @@ -123,7 +123,7 @@ impl<'l> StripLayout<'l> { // Make sure we don't have a gap in the stripe/frame/selection background: let item_spacing = self.ui.spacing().item_spacing; - let gapless_rect = max_rect.expand2(0.5 * item_spacing); + let gapless_rect = max_rect.expand2(0.5 * item_spacing).round_ui(); if flags.striped { self.ui.painter().rect_filled( diff --git a/crates/emath/src/gui_rounding.rs b/crates/emath/src/gui_rounding.rs new file mode 100644 index 00000000000..3c2cf92411b --- /dev/null +++ b/crates/emath/src/gui_rounding.rs @@ -0,0 +1,201 @@ +/// We (sometimes) round sizes and coordinates to an even multiple of this value. +/// +/// This is only used for rounding _logical UI points_, used for widget coordinates and sizes. +/// When rendering, you may want to round to an integer multiple of the physical _pixels_ instead, +/// using [`GuiRounding::round_to_pixels`]. +/// +/// See [`GuiRounding::round_ui`] for more information. +/// +/// This constant has to be a (negative) power of two so that it can be represented exactly +/// by a floating point number. +/// +/// If we pick too large a value (e.g. 1 or 1/2), then we get judder during scrolling and animations. +/// If we pick too small a value (e.g. 1/4096), we run the risk of rounding errors again. +/// +/// `f32` has 23 bits of mantissa, so if we use e.g. 1/8 as the rounding factor, +/// we can represent all numbers up to 2^20 exactly, which is plenty +/// (to my knowledge there are no displays that are a million pixels wide). +pub const GUI_ROUNDING: f32 = 1.0 / 32.0; + +/// Trait for rounding coordinates and sizes to align with either . +/// +/// See [`GuiRounding::round_ui`] for more information. +pub trait GuiRounding { + /// Rounds floating point numbers to an even multiple of the GUI rounding factor, [`crate::GUI_ROUNDING`]. + /// + /// Use this for widget coordinates and sizes. + /// + /// Rounding sizes and positions prevent rounding errors when doing sizing calculations. + /// We don't round to integers, because that would be too coarse (causing visible juddering when scrolling, for instance). + /// Instead we round to an even multiple of [`GUI_ROUNDING`]. + fn round_ui(self) -> Self; + + /// Like [`Self::round_ui`], but always rounds towards negative infinity. + fn floor_ui(self) -> Self; + + /// Round a size or position to an even multiple of the physical pixel size. + /// + /// This can be useful for crisp rendering. + /// + /// The `self` should be in coordinates of _logical UI points_. + /// The argument `pixels_per_point` is the number of _physical pixels_ per logical UI point. + /// For instance, on a high-DPI screen, `pixels_per_point` could be `2.0`. + fn round_to_pixels(self, pixels_per_point: f32) -> Self; + + /// Will round the position to be in the center of a pixel. + /// + /// The pixel size is `1.0 / pixels_per_point`. + /// + /// So if `pixels_per_point = 2` (i.e. `pixel size = 0.5`), + /// then the position will be rounded to the closest of `…, 0.25, 0.75, 1.25, …`. + /// + /// This is useful, for instance, when picking the center of a line that is one pixel wide. + fn round_to_pixel_center(self, pixels_per_point: f32) -> Self; +} + +impl GuiRounding for f32 { + #[inline] + fn round_ui(self) -> Self { + (self / GUI_ROUNDING).round() * GUI_ROUNDING + } + + #[inline] + fn floor_ui(self) -> Self { + (self / GUI_ROUNDING).floor() * GUI_ROUNDING + } + + #[inline] + fn round_to_pixels(self, pixels_per_point: f32) -> Self { + (self * pixels_per_point).round() / pixels_per_point + } + + #[inline] + fn round_to_pixel_center(self, pixels_per_point: f32) -> Self { + ((self * pixels_per_point - 0.5).round() + 0.5) / pixels_per_point + } +} + +impl GuiRounding for f64 { + #[inline] + fn round_ui(self) -> Self { + (self / GUI_ROUNDING as Self).round() * GUI_ROUNDING as Self + } + + #[inline] + fn floor_ui(self) -> Self { + (self / GUI_ROUNDING as Self).floor() * GUI_ROUNDING as Self + } + + #[inline] + fn round_to_pixels(self, pixels_per_point: f32) -> Self { + (self * pixels_per_point as Self).round() / pixels_per_point as Self + } + + #[inline] + fn round_to_pixel_center(self, pixels_per_point: f32) -> Self { + ((self * pixels_per_point as Self - 0.5).round() + 0.5) / pixels_per_point as Self + } +} + +impl GuiRounding for crate::Vec2 { + #[inline] + fn round_ui(self) -> Self { + Self::new(self.x.round_ui(), self.y.round_ui()) + } + + #[inline] + fn floor_ui(self) -> Self { + Self::new(self.x.floor_ui(), self.y.floor_ui()) + } + + #[inline] + fn round_to_pixels(self, pixels_per_point: f32) -> Self { + Self::new( + self.x.round_to_pixels(pixels_per_point), + self.y.round_to_pixels(pixels_per_point), + ) + } + + #[inline] + fn round_to_pixel_center(self, pixels_per_point: f32) -> Self { + Self::new( + self.x.round_to_pixel_center(pixels_per_point), + self.y.round_to_pixel_center(pixels_per_point), + ) + } +} + +impl GuiRounding for crate::Pos2 { + #[inline] + fn round_ui(self) -> Self { + Self::new(self.x.round_ui(), self.y.round_ui()) + } + + #[inline] + fn floor_ui(self) -> Self { + Self::new(self.x.floor_ui(), self.y.floor_ui()) + } + + #[inline] + fn round_to_pixels(self, pixels_per_point: f32) -> Self { + Self::new( + self.x.round_to_pixels(pixels_per_point), + self.y.round_to_pixels(pixels_per_point), + ) + } + + #[inline] + fn round_to_pixel_center(self, pixels_per_point: f32) -> Self { + Self::new( + self.x.round_to_pixel_center(pixels_per_point), + self.y.round_to_pixel_center(pixels_per_point), + ) + } +} + +impl GuiRounding for crate::Rect { + /// Rounded so that two adjacent rects that tile perfectly + /// will continue to tile perfectly. + #[inline] + fn round_ui(self) -> Self { + Self::from_min_max(self.min.round_ui(), self.max.round_ui()) + } + + /// Rounded so that two adjacent rects that tile perfectly + /// will continue to tile perfectly. + #[inline] + fn floor_ui(self) -> Self { + Self::from_min_max(self.min.floor_ui(), self.max.floor_ui()) + } + + /// Rounded so that two adjacent rects that tile perfectly + /// will continue to tile perfectly. + #[inline] + fn round_to_pixels(self, pixels_per_point: f32) -> Self { + Self::from_min_max( + self.min.round_to_pixels(pixels_per_point), + self.max.round_to_pixels(pixels_per_point), + ) + } + + /// Rounded so that two adjacent rects that tile perfectly + /// will continue to tile perfectly. + #[inline] + fn round_to_pixel_center(self, pixels_per_point: f32) -> Self { + Self::from_min_max( + self.min.round_to_pixel_center(pixels_per_point), + self.max.round_to_pixel_center(pixels_per_point), + ) + } +} + +#[test] +fn test_gui_rounding() { + assert_eq!(0.0_f32.round_ui(), 0.0); + assert_eq!((GUI_ROUNDING * 1.11).round_ui(), GUI_ROUNDING); + assert_eq!((-GUI_ROUNDING * 1.11).round_ui(), -GUI_ROUNDING); + assert_eq!(f32::NEG_INFINITY.round_ui(), f32::NEG_INFINITY); + assert_eq!(f32::INFINITY.round_ui(), f32::INFINITY); + + assert_eq!(0.17_f32.round_to_pixel_center(2.0), 0.25); +} diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index 2f30a55ca98..05210fbb298 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -27,6 +27,7 @@ use std::ops::{Add, Div, Mul, RangeInclusive, Sub}; pub mod align; pub mod easing; +mod gui_rounding; mod history; mod numeric; mod ordered_float; @@ -42,6 +43,7 @@ mod vec2b; pub use self::{ align::{Align, Align2}, + gui_rounding::{GuiRounding, GUI_ROUNDING}, history::History, numeric::*, ordered_float::*, diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index d717c3e7355..a3994ca4c3b 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use std::sync::Arc; -use emath::{vec2, Vec2}; +use emath::{vec2, GuiRounding, Vec2}; use crate::{ mutex::{Mutex, RwLock}, @@ -96,22 +96,18 @@ impl FontImpl { use ab_glyph::{Font, ScaleFont}; let scaled = ab_glyph_font.as_scaled(scale_in_pixels); - let ascent = scaled.ascent() / pixels_per_point; - let descent = scaled.descent() / pixels_per_point; - let line_gap = scaled.line_gap() / pixels_per_point; + let ascent = (scaled.ascent() / pixels_per_point).round_ui(); + let descent = (scaled.descent() / pixels_per_point).round_ui(); + let line_gap = (scaled.line_gap() / pixels_per_point).round_ui(); // Tweak the scale as the user desired let scale_in_pixels = scale_in_pixels * tweak.scale; + let scale_in_points = scale_in_pixels / pixels_per_point; - let baseline_offset = { - let scale_in_points = scale_in_pixels / pixels_per_point; - scale_in_points * tweak.baseline_offset_factor - }; + let baseline_offset = (scale_in_points * tweak.baseline_offset_factor).round_ui(); - let y_offset_points = { - let scale_in_points = scale_in_pixels / pixels_per_point; - scale_in_points * tweak.y_offset_factor - } + tweak.y_offset; + let y_offset_points = + ((scale_in_points * tweak.y_offset_factor) + tweak.y_offset).round_ui(); // Center scaled glyphs properly: let height = ascent + descent; @@ -247,6 +243,8 @@ impl FontImpl { } /// Height of one row of text in points. + /// + /// Returns a value rounded to [`emath::GUI_ROUNDING`]. #[inline(always)] pub fn row_height(&self) -> f32 { self.height_in_points @@ -418,7 +416,9 @@ impl Font { (point * self.pixels_per_point).round() / self.pixels_per_point } - /// Height of one row of text. In points + /// Height of one row of text. In points. + /// + /// Returns a value rounded to [`emath::GUI_ROUNDING`]. #[inline(always)] pub fn row_height(&self) -> f32 { self.row_height diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 4be2fdfe8d0..b952b2817d1 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -519,7 +519,9 @@ impl Fonts { self.lock().fonts.has_glyphs(font_id, s) } - /// Height of one row of text in points + /// Height of one row of text in points. + /// + /// Returns a value rounded to [`emath::GUI_ROUNDING`]. #[inline] pub fn row_height(&self, font_id: &FontId) -> f32 { self.lock().fonts.row_height(font_id) @@ -706,6 +708,8 @@ impl FontsImpl { } /// Height of one row of text in points. + /// + /// Returns a value rounded to [`emath::GUI_ROUNDING`]. fn row_height(&mut self, font_id: &FontId) -> f32 { self.font(font_id).row_height() } diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 9db77f888af..2f19e538f60 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -1,7 +1,7 @@ use std::ops::RangeInclusive; use std::sync::Arc; -use emath::{pos2, vec2, Align, NumExt, Pos2, Rect, Vec2}; +use emath::{pos2, vec2, Align, GuiRounding as _, NumExt, Pos2, Rect, Vec2}; use crate::{stroke::PathStroke, text::font::Font, Color32, Mesh, Stroke, Vertex}; @@ -630,7 +630,7 @@ fn galley_from_rows( min_x = min_x.min(row.rect.min.x); max_x = max_x.max(row.rect.max.x); cursor_y += max_row_height; - cursor_y = point_scale.round_to_pixel(cursor_y); + cursor_y = point_scale.round_to_pixel(cursor_y); // TODO(emilk): it would be better to do the calculations in pixels instead. } let format_summary = format_summary(&job); @@ -648,20 +648,25 @@ fn galley_from_rows( let mut rect = Rect::from_min_max(pos2(min_x, 0.0), pos2(max_x, cursor_y)); - if job.round_output_size_to_nearest_ui_point { + if job.round_output_to_gui { + for row in &mut rows { + row.rect = row.rect.round_ui(); + } + let did_exceed_wrap_width_by_a_lot = rect.width() > job.wrap.max_width + 1.0; - // We round the size to whole ui points here (not pixels!) so that the egui layout code - // can have the advantage of working in integer units, avoiding rounding errors. - rect.min = rect.min.round(); - rect.max = rect.max.round(); + rect = rect.round_ui(); if did_exceed_wrap_width_by_a_lot { // If the user picked a too aggressive wrap width (e.g. more narrow than any individual glyph), // we should let the user know by reporting that our width is wider than the wrap width. } else { // Make sure we don't report being wider than the wrap width the user picked: - rect.max.x = rect.max.x.at_most(rect.min.x + job.wrap.max_width).floor(); + rect.max.x = rect + .max + .x + .at_most(rect.min.x + job.wrap.max_width) + .floor_ui(); } } @@ -1133,6 +1138,7 @@ mod tests { LayoutJob::single_section("# DNA\nMore text".into(), TextFormat::default()); layout_job.wrap.max_width = f32::INFINITY; layout_job.wrap.max_rows = 1; + layout_job.round_output_to_gui = false; let galley = layout(&mut fonts, layout_job.into()); assert!(galley.elided); assert_eq!( diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 64dd827148c..b228d023e66 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -78,9 +78,8 @@ pub struct LayoutJob { /// Justify text so that word-wrapped rows fill the whole [`TextWrapping::max_width`]. pub justify: bool, - /// Rounding to the closest ui point (not pixel!) allows the rest of the - /// layout code to run on perfect integers, avoiding rounding errors. - pub round_output_size_to_nearest_ui_point: bool, + /// Round output sizes using [`emath::GuiRounding`], to avoid rounding errors in layout code. + pub round_output_to_gui: bool, } impl Default for LayoutJob { @@ -94,7 +93,7 @@ impl Default for LayoutJob { break_on_newline: true, halign: Align::LEFT, justify: false, - round_output_size_to_nearest_ui_point: true, + round_output_to_gui: true, } } } @@ -168,6 +167,8 @@ impl LayoutJob { } /// The height of the tallest font used in the job. + /// + /// Returns a value rounded to [`emath::GUI_ROUNDING`]. pub fn font_height(&self, fonts: &crate::Fonts) -> f32 { let mut max_height = 0.0_f32; for section in &self.sections { @@ -178,7 +179,7 @@ impl LayoutJob { /// The wrap with, with a small margin in some cases. pub fn effective_wrap_width(&self) -> f32 { - if self.round_output_size_to_nearest_ui_point { + if self.round_output_to_gui { // On a previous pass we may have rounded down by at most 0.5 and reported that as a width. // egui may then set that width as the max width for subsequent frames, and it is important // that we then don't wrap earlier. @@ -200,7 +201,7 @@ impl std::hash::Hash for LayoutJob { break_on_newline, halign, justify, - round_output_size_to_nearest_ui_point, + round_output_to_gui, } = self; text.hash(state); @@ -210,7 +211,7 @@ impl std::hash::Hash for LayoutJob { break_on_newline.hash(state); halign.hash(state); justify.hash(state); - round_output_size_to_nearest_ui_point.hash(state); + round_output_to_gui.hash(state); } } From d20f93e9bf1e2fe52c1b11aff8cf741d10b2cb85 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 26 Dec 2024 21:02:27 +0100 Subject: [PATCH 2/2] Make all lines and rectangles crisp (#5518) * Merge this first: https://github.com/emilk/egui/pull/5517 This aligns all rectangles and (horizontal or vertical) line segments to the physical pixel grid in the `epaint::Tessellator`, making these shapes appear crisp everywhere. * Closes https://github.com/emilk/egui/issues/5164 * Closes https://github.com/emilk/egui/issues/3667 This undoes a lot of the explicit, egui-side aligning added in: * https://github.com/emilk/egui/pull/4943 The new approach has several benefits over the old one: * It is done automatically by epaint, so it is applied to everything (no longer opt-in) * It is applied after any layer transforms (so it always works) * It makes line segments crisper on high-DPI screens * All filled rectangles now has sides that end on pixel boundaries --- crates/egui/src/containers/panel.rs | 30 ++-- crates/egui/src/containers/resize.rs | 4 +- crates/egui/src/containers/window.rs | 2 +- crates/egui/src/context.rs | 45 ----- crates/egui/src/introspection.rs | 15 +- crates/egui/src/painter.rs | 83 ++++++---- crates/egui/src/style.rs | 8 +- crates/egui/src/text_selection/visuals.rs | 15 +- crates/egui/src/ui.rs | 8 +- crates/egui/src/widgets/separator.rs | 4 +- crates/egui_demo_lib/src/rendering_test.rs | 7 +- .../snapshots/demos/B\303\251zier Curve.png" | 4 +- .../tests/snapshots/demos/Code Editor.png | 4 +- .../tests/snapshots/demos/Code Example.png | 4 +- .../tests/snapshots/demos/Context Menus.png | 4 +- .../tests/snapshots/demos/Dancing Strings.png | 4 +- .../tests/snapshots/demos/Drag and Drop.png | 4 +- .../tests/snapshots/demos/Extra Viewport.png | 4 +- .../tests/snapshots/demos/Font Book.png | 4 +- .../tests/snapshots/demos/Frame.png | 4 +- .../tests/snapshots/demos/Highlighting.png | 4 +- .../snapshots/demos/Interactive Container.png | 4 +- .../tests/snapshots/demos/Misc Demos.png | 4 +- .../tests/snapshots/demos/Modals.png | 4 +- .../tests/snapshots/demos/Multi Touch.png | 4 +- .../tests/snapshots/demos/Painting.png | 4 +- .../tests/snapshots/demos/Pan Zoom.png | 4 +- .../tests/snapshots/demos/Panels.png | 4 +- .../tests/snapshots/demos/Screenshot.png | 4 +- .../tests/snapshots/demos/Scrolling.png | 4 +- .../tests/snapshots/demos/Sliders.png | 4 +- .../tests/snapshots/demos/Strip.png | 4 +- .../tests/snapshots/demos/Table.png | 4 +- .../tests/snapshots/demos/Text Layout.png | 4 +- .../tests/snapshots/demos/TextEdit.png | 4 +- .../tests/snapshots/demos/Tooltips.png | 4 +- .../tests/snapshots/demos/Undo Redo.png | 4 +- .../tests/snapshots/demos/Window Options.png | 4 +- .../tests/snapshots/modals_1.png | 4 +- .../tests/snapshots/modals_2.png | 4 +- .../tests/snapshots/modals_3.png | 4 +- ...rop_should_prevent_focusing_lower_area.png | 4 +- .../snapshots/rendering_test/dpi_1.00.png | 4 +- .../snapshots/rendering_test/dpi_1.25.png | 4 +- .../snapshots/rendering_test/dpi_1.50.png | 4 +- .../snapshots/rendering_test/dpi_1.67.png | 4 +- .../snapshots/rendering_test/dpi_1.75.png | 4 +- .../snapshots/rendering_test/dpi_2.00.png | 4 +- .../tests/snapshots/widget_gallery.png | 4 +- crates/emath/src/gui_rounding.rs | 1 + crates/epaint/src/lib.rs | 2 +- crates/epaint/src/shape.rs | 23 ++- crates/epaint/src/shape_transform.rs | 2 +- crates/epaint/src/tessellator.rs | 155 ++++++++++++++---- examples/hello_android/Cargo.toml | 9 +- 55 files changed, 314 insertions(+), 251 deletions(-) diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index f659a8c4efa..6fd19e18688 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -78,6 +78,13 @@ impl Side { Self::Right => rect.right(), } } + + fn sign(self) -> f32 { + match self { + Self::Left => -1.0, + Self::Right => 1.0, + } + } } /// A panel that covers the entire left or right side of a [`Ui`] or screen. @@ -349,12 +356,8 @@ impl SidePanel { // TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done let resize_x = side.opposite().side_x(rect); - // This makes it pixel-perfect for odd-sized strokes (width=1.0, width=3.0, etc) - let resize_x = ui.painter().round_to_pixel_center(resize_x); - - // We want the line exactly on the last pixel but rust rounds away from zero so we bring it back a bit for - // left-side panels - let resize_x = resize_x - if side == Side::Left { 1.0 } else { 0.0 }; + // Make sure the line is on the inside of the panel: + let resize_x = resize_x + 0.5 * side.sign() * stroke.width; ui.painter().vline(resize_x, panel_rect.y_range(), stroke); } @@ -562,6 +565,13 @@ impl TopBottomSide { Self::Bottom => rect.bottom(), } } + + fn sign(self) -> f32 { + match self { + Self::Top => -1.0, + Self::Bottom => 1.0, + } + } } /// A panel that covers the entire top or bottom of a [`Ui`] or screen. @@ -843,12 +853,8 @@ impl TopBottomPanel { // TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done let resize_y = side.opposite().side_y(rect); - // This makes it pixel-perfect for odd-sized strokes (width=1.0, width=3.0, etc) - let resize_y = ui.painter().round_to_pixel_center(resize_y); - - // We want the line exactly on the last pixel but rust rounds away from zero so we bring it back a bit for - // top-side panels - let resize_y = resize_y - if side == TopBottomSide::Top { 1.0 } else { 0.0 }; + // Make sure the line is on the inside of the panel: + let resize_y = resize_y + 0.5 * side.sign() * stroke.width; ui.painter().hline(panel_rect.x_range(), resize_y, stroke); } diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index 0165eba996a..bbb86dfbf36 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -400,7 +400,9 @@ pub fn paint_resize_corner_with_style( corner: Align2, ) { let painter = ui.painter(); - let cp = painter.round_pos_to_pixels(corner.pos_in_rect(rect)); + let cp = corner + .pos_in_rect(rect) + .round_to_pixels(ui.pixels_per_point()); let mut w = 2.0; let stroke = Stroke { width: 1.0, // Set width to 1.0 to prevent overlapping diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 44c3dda58c1..414c58cfae7 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -596,7 +596,7 @@ impl<'open> Window<'open> { }, ); - title_rect = area_content_ui.painter().round_rect_to_pixels(title_rect); + title_rect = title_rect.round_to_pixels(area_content_ui.pixels_per_point()); if on_top && area_content_ui.visuals().window_highlight_topmost { let mut round = window_frame.rounding; diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index eaca8d36f52..118cddb31fa 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -7,7 +7,6 @@ use emath::GuiRounding as _; use epaint::{ emath::{self, TSTransform}, mutex::RwLock, - pos2, stats::PaintStats, tessellator, text::{FontInsert, FontPriority, Fonts}, @@ -2004,50 +2003,6 @@ impl Context { }); } - /// Useful for pixel-perfect rendering of lines that are one pixel wide (or any odd number of pixels). - #[inline] - pub(crate) fn round_to_pixel_center(&self, point: f32) -> f32 { - let pixels_per_point = self.pixels_per_point(); - ((point * pixels_per_point - 0.5).round() + 0.5) / pixels_per_point - } - - /// Useful for pixel-perfect rendering of lines that are one pixel wide (or any odd number of pixels). - #[inline] - pub(crate) fn round_pos_to_pixel_center(&self, point: Pos2) -> Pos2 { - pos2( - self.round_to_pixel_center(point.x), - self.round_to_pixel_center(point.y), - ) - } - - /// Useful for pixel-perfect rendering of filled shapes - #[inline] - pub(crate) fn round_to_pixel(&self, point: f32) -> f32 { - let pixels_per_point = self.pixels_per_point(); - (point * pixels_per_point).round() / pixels_per_point - } - - /// Useful for pixel-perfect rendering of filled shapes - #[inline] - pub(crate) fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 { - pos2(self.round_to_pixel(pos.x), self.round_to_pixel(pos.y)) - } - - /// Useful for pixel-perfect rendering of filled shapes - #[inline] - pub(crate) fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 { - vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y)) - } - - /// Useful for pixel-perfect rendering of filled shapes - #[inline] - pub(crate) fn round_rect_to_pixels(&self, rect: Rect) -> Rect { - Rect { - min: self.round_pos_to_pixels(rect.min), - max: self.round_pos_to_pixels(rect.max), - } - } - /// Allocate a texture. /// /// This is for advanced users. diff --git a/crates/egui/src/introspection.rs b/crates/egui/src/introspection.rs index 7f0ced6ba93..85e1ee6c62f 100644 --- a/crates/egui/src/introspection.rs +++ b/crates/egui/src/introspection.rs @@ -144,6 +144,8 @@ impl Widget for &mut epaint::TessellationOptions { coarse_tessellation_culling, prerasterized_discs, round_text_to_pixels, + round_line_segments_to_pixels, + round_rects_to_pixels, debug_paint_clip_rects, debug_paint_text_rects, debug_ignore_clip_rects, @@ -179,13 +181,22 @@ impl Widget for &mut epaint::TessellationOptions { ui.checkbox(validate_meshes, "Validate meshes").on_hover_text("Check that incoming meshes are valid, i.e. that all indices are in range, etc."); + ui.collapsing("Align to pixel grid", |ui| { + ui.checkbox(round_text_to_pixels, "Text") + .on_hover_text("Most text already is, so don't expect to see a large change."); + + ui.checkbox(round_line_segments_to_pixels, "Line segments") + .on_hover_text("Makes line segments appear crisp on any display."); + + ui.checkbox(round_rects_to_pixels, "Rectangles") + .on_hover_text("Makes line segments appear crisp on any display."); + }); + ui.collapsing("Debug", |ui| { ui.checkbox( coarse_tessellation_culling, "Do coarse culling in the tessellator", ); - ui.checkbox(round_text_to_pixels, "Align text positions to pixel grid") - .on_hover_text("Most text already is, so don't expect to see a large change."); ui.checkbox(debug_ignore_clip_rects, "Ignore clip rectangles"); ui.checkbox(debug_paint_clip_rects, "Paint clip rectangles"); diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index 232450e3862..e30a148d979 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -1,23 +1,30 @@ use std::sync::Arc; +use emath::GuiRounding as _; +use epaint::{ + text::{Fonts, Galley, LayoutJob}, + CircleShape, ClippedShape, PathStroke, RectShape, Rounding, Shape, Stroke, +}; + use crate::{ emath::{Align2, Pos2, Rangef, Rect, Vec2}, layers::{LayerId, PaintList, ShapeIdx}, Color32, Context, FontId, }; -use epaint::{ - text::{Fonts, Galley, LayoutJob}, - CircleShape, ClippedShape, PathStroke, RectShape, Rounding, Shape, Stroke, -}; /// Helper to paint shapes and text to a specific region on a specific layer. /// /// All coordinates are screen coordinates in the unit points (one point can consist of many physical pixels). +/// +/// A [`Painter`] never outlive a single frame/pass. #[derive(Clone)] pub struct Painter { /// Source of fonts and destination of shapes ctx: Context, + /// For quick access, without having to go via [`Context`]. + pixels_per_point: f32, + /// Where we paint layer_id: LayerId, @@ -38,8 +45,10 @@ pub struct Painter { impl Painter { /// Create a painter to a specific layer within a certain clip rectangle. pub fn new(ctx: Context, layer_id: LayerId, clip_rect: Rect) -> Self { + let pixels_per_point = ctx.pixels_per_point(); Self { ctx, + pixels_per_point, layer_id, clip_rect, fade_to_color: None, @@ -49,14 +58,10 @@ impl Painter { /// Redirect where you are painting. #[must_use] - pub fn with_layer_id(self, layer_id: LayerId) -> Self { - Self { - ctx: self.ctx, - layer_id, - clip_rect: self.clip_rect, - fade_to_color: None, - opacity_factor: 1.0, - } + #[inline] + pub fn with_layer_id(mut self, layer_id: LayerId) -> Self { + self.layer_id = layer_id; + self } /// Create a painter for a sub-region of this [`Painter`]. @@ -64,13 +69,9 @@ impl Painter { /// The clip-rect of the returned [`Painter`] will be the intersection /// of the given rectangle and the `clip_rect()` of the parent [`Painter`]. pub fn with_clip_rect(&self, rect: Rect) -> Self { - Self { - ctx: self.ctx.clone(), - layer_id: self.layer_id, - clip_rect: rect.intersect(self.clip_rect), - fade_to_color: self.fade_to_color, - opacity_factor: self.opacity_factor, - } + let mut new_self = self.clone(); + new_self.clip_rect = rect.intersect(self.clip_rect); + new_self } /// Redirect where you are painting. @@ -82,7 +83,7 @@ impl Painter { } /// If set, colors will be modified to look like this - pub(crate) fn set_fade_to_color(&mut self, fade_to_color: Option) { + pub fn set_fade_to_color(&mut self, fade_to_color: Option) { self.fade_to_color = fade_to_color; } @@ -118,24 +119,27 @@ impl Painter { /// If `false`, nothing you paint will show up. /// /// Also checks [`Context::will_discard`]. - pub(crate) fn is_visible(&self) -> bool { + pub fn is_visible(&self) -> bool { self.fade_to_color != Some(Color32::TRANSPARENT) && !self.ctx.will_discard() } /// If `false`, nothing added to the painter will be visible - pub(crate) fn set_invisible(&mut self) { + pub fn set_invisible(&mut self) { self.fade_to_color = Some(Color32::TRANSPARENT); } -} -/// ## Accessors etc -impl Painter { /// Get a reference to the parent [`Context`]. #[inline] pub fn ctx(&self) -> &Context { &self.ctx } + /// Number of physical pixels for each logical UI point. + #[inline] + pub fn pixels_per_point(&self) -> f32 { + self.pixels_per_point + } + /// Read-only access to the shared [`Fonts`]. /// /// See [`Context`] documentation for how locks work. @@ -180,37 +184,42 @@ impl Painter { /// Useful for pixel-perfect rendering of lines that are one pixel wide (or any odd number of pixels). #[inline] pub fn round_to_pixel_center(&self, point: f32) -> f32 { - self.ctx().round_to_pixel_center(point) + point.round_to_pixel_center(self.pixels_per_point()) } /// Useful for pixel-perfect rendering of lines that are one pixel wide (or any odd number of pixels). + #[deprecated = "Use `emath::GuiRounding` with `painter.pixels_per_point()` instead"] #[inline] pub fn round_pos_to_pixel_center(&self, pos: Pos2) -> Pos2 { - self.ctx().round_pos_to_pixel_center(pos) + pos.round_to_pixel_center(self.pixels_per_point()) } /// Useful for pixel-perfect rendering of filled shapes. + #[deprecated = "Use `emath::GuiRounding` with `painter.pixels_per_point()` instead"] #[inline] pub fn round_to_pixel(&self, point: f32) -> f32 { - self.ctx().round_to_pixel(point) + point.round_to_pixels(self.pixels_per_point()) } /// Useful for pixel-perfect rendering. + #[deprecated = "Use `emath::GuiRounding` with `painter.pixels_per_point()` instead"] #[inline] pub fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 { - self.ctx().round_vec_to_pixels(vec) + vec.round_to_pixels(self.pixels_per_point()) } /// Useful for pixel-perfect rendering. + #[deprecated = "Use `emath::GuiRounding` with `painter.pixels_per_point()` instead"] #[inline] pub fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 { - self.ctx().round_pos_to_pixels(pos) + pos.round_to_pixels(self.pixels_per_point()) } /// Useful for pixel-perfect rendering. + #[deprecated = "Use `emath::GuiRounding` with `painter.pixels_per_point()` instead"] #[inline] pub fn round_rect_to_pixels(&self, rect: Rect) -> Rect { - self.ctx().round_rect_to_pixels(rect) + rect.round_to_pixels(self.pixels_per_point()) } } @@ -337,7 +346,7 @@ impl Painter { /// # Paint different primitives impl Painter { /// Paints a line from the first point to the second. - pub fn line_segment(&self, points: [Pos2; 2], stroke: impl Into) -> ShapeIdx { + pub fn line_segment(&self, points: [Pos2; 2], stroke: impl Into) -> ShapeIdx { self.add(Shape::LineSegment { points, stroke: stroke.into(), @@ -351,13 +360,13 @@ impl Painter { } /// Paints a horizontal line. - pub fn hline(&self, x: impl Into, y: f32, stroke: impl Into) -> ShapeIdx { - self.add(Shape::hline(x, y, stroke.into())) + pub fn hline(&self, x: impl Into, y: f32, stroke: impl Into) -> ShapeIdx { + self.add(Shape::hline(x, y, stroke)) } /// Paints a vertical line. - pub fn vline(&self, x: f32, y: impl Into, stroke: impl Into) -> ShapeIdx { - self.add(Shape::vline(x, y, stroke.into())) + pub fn vline(&self, x: f32, y: impl Into, stroke: impl Into) -> ShapeIdx { + self.add(Shape::vline(x, y, stroke)) } pub fn circle( @@ -398,6 +407,7 @@ impl Painter { }) } + /// The stroke extends _outside_ the [`Rect`]. pub fn rect( &self, rect: Rect, @@ -417,6 +427,7 @@ impl Painter { self.add(RectShape::filled(rect, rounding, fill_color)) } + /// The stroke extends _outside_ the [`Rect`]. pub fn rect_stroke( &self, rect: Rect, diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 91c0e09dbfa..33ab21a6837 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -2485,12 +2485,8 @@ impl Widget for &mut Stroke { // stroke preview: let (_id, stroke_rect) = ui.allocate_space(ui.spacing().interact_size); - let left = ui - .painter() - .round_pos_to_pixel_center(stroke_rect.left_center()); - let right = ui - .painter() - .round_pos_to_pixel_center(stroke_rect.right_center()); + let left = stroke_rect.left_center(); + let right = stroke_rect.right_center(); ui.painter().line_segment([left, right], (*width, *color)); }) .response diff --git a/crates/egui/src/text_selection/visuals.rs b/crates/egui/src/text_selection/visuals.rs index 2a31d1e1270..dd7c867a222 100644 --- a/crates/egui/src/text_selection/visuals.rs +++ b/crates/egui/src/text_selection/visuals.rs @@ -96,19 +96,8 @@ pub fn paint_text_selection( pub fn paint_cursor_end(painter: &Painter, visuals: &Visuals, cursor_rect: Rect) { let stroke = visuals.text_cursor.stroke; - // Ensure the cursor is aligned to the pixel grid for whole number widths. - // See https://github.com/emilk/egui/issues/5164 - let (top, bottom) = if (stroke.width as usize) % 2 == 0 { - ( - painter.round_pos_to_pixels(cursor_rect.center_top()), - painter.round_pos_to_pixels(cursor_rect.center_bottom()), - ) - } else { - ( - painter.round_pos_to_pixel_center(cursor_rect.center_top()), - painter.round_pos_to_pixel_center(cursor_rect.center_bottom()), - ) - }; + let top = cursor_rect.center_top(); + let bottom = cursor_rect.center_bottom(); painter.line_segment([top, bottom], (stroke.width, stroke.color)); diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 9cf2d4b5427..19fda5d0fee 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -483,6 +483,12 @@ impl Ui { &self.painter } + /// Number of physical pixels for each logical UI point. + #[inline] + pub fn pixels_per_point(&self) -> f32 { + self.painter.pixels_per_point() + } + /// If `false`, the [`Ui`] does not allow any interaction and /// the widgets in it will draw with a gray look. #[inline] @@ -2385,9 +2391,7 @@ impl Ui { let stroke = self.visuals().widgets.noninteractive.bg_stroke; let left_top = child_rect.min - 0.5 * indent * Vec2::X; - let left_top = self.painter().round_pos_to_pixel_center(left_top); let left_bottom = pos2(left_top.x, child_ui.min_rect().bottom() - 2.0); - let left_bottom = self.painter().round_pos_to_pixel_center(left_bottom); if left_vline { // draw a faint line on the left to mark the indented section diff --git a/crates/egui/src/widgets/separator.rs b/crates/egui/src/widgets/separator.rs index 2cc3a857ce6..d018cfa4d0d 100644 --- a/crates/egui/src/widgets/separator.rs +++ b/crates/egui/src/widgets/separator.rs @@ -116,12 +116,12 @@ impl Widget for Separator { if is_horizontal_line { painter.hline( (rect.left() - grow)..=(rect.right() + grow), - painter.round_to_pixel_center(rect.center().y), + rect.center().y, stroke, ); } else { painter.vline( - painter.round_to_pixel_center(rect.center().x), + rect.center().x, (rect.top() - grow)..=(rect.bottom() + grow), stroke, ); diff --git a/crates/egui_demo_lib/src/rendering_test.rs b/crates/egui_demo_lib/src/rendering_test.rs index df3add3776e..bd454267e7a 100644 --- a/crates/egui_demo_lib/src/rendering_test.rs +++ b/crates/egui_demo_lib/src/rendering_test.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; use egui::{ - epaint, lerp, pos2, vec2, widgets::color_picker::show_color, Align2, Color32, FontId, Image, - Mesh, Pos2, Rect, Response, Rgba, RichText, Sense, Shape, Stroke, TextureHandle, - TextureOptions, Ui, Vec2, + emath::GuiRounding, epaint, lerp, pos2, vec2, widgets::color_picker::show_color, Align2, + Color32, FontId, Image, Mesh, Pos2, Rect, Response, Rgba, RichText, Sense, Shape, Stroke, + TextureHandle, TextureOptions, Ui, Vec2, }; const GRADIENT_SIZE: Vec2 = vec2(256.0, 18.0); @@ -270,6 +270,7 @@ impl ColorTest { fn vertex_gradient(ui: &mut Ui, bg_fill: Color32, gradient: &Gradient) -> Response { let (rect, response) = ui.allocate_at_least(GRADIENT_SIZE, Sense::hover()); + let rect = rect.round_to_pixels(ui.pixels_per_point()); if bg_fill != Default::default() { let mut mesh = Mesh::default(); mesh.add_colored_rect(rect, bg_fill); diff --git "a/crates/egui_demo_lib/tests/snapshots/demos/B\303\251zier Curve.png" "b/crates/egui_demo_lib/tests/snapshots/demos/B\303\251zier Curve.png" index ad7d9becfaa..c4cd0bd1099 100644 --- "a/crates/egui_demo_lib/tests/snapshots/demos/B\303\251zier Curve.png" +++ "b/crates/egui_demo_lib/tests/snapshots/demos/B\303\251zier Curve.png" @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a725aa81433f301fda4ff8a28be869366332964995d1ae4ed996591596eb7e2 -size 31461 +oid sha256:cf83bead834ec8f88d74b32ae6331715e8c6df183e007e2a16004c019534a30f +size 31810 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png b/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png index 252c7c8f205..77ebb00947f 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Code Editor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36028d85f49ee77562250214237def2b676ecc9ed413d2fd8afc473d61289ca1 -size 32761 +oid sha256:b8ca5a27491c0589a97e43a70bc10dc52778d25ca3f7e7c895dbbbb784adfcfa +size 33245 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png index 33121fe85fa..04fa9ba3c55 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f93cbf4968e69e2b51256631ab8703d1af2274147924f8d802d9da0f9767aa1b -size 81162 +oid sha256:e640606207265b4f040f793b0ffb989504b6a98b89e95e77a9a9d3e3abc9327a +size 80933 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Context Menus.png b/crates/egui_demo_lib/tests/snapshots/demos/Context Menus.png index 9191ade8504..caa3c3a5d96 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Context Menus.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Context Menus.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7157cc3a1c87b63a1af41e9c3493beb426f9a2c7e3ce1adf228a8d1aee23818 -size 11549 +oid sha256:332c2af36873d8ccccb36c08fd2e475dc1f18454a3090a851c0889395d4f364f +size 11518 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png b/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png index ac28a61122d..e86d10772ef 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Dancing Strings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7fcc5ac195fdf455836cce7503c530d56a5c9e053cf743f8165c2b33212bfaae -size 20645 +oid sha256:d0d0b1b4d2c4b624904250bc8d6870559f0179e3f7f2d6dc4a4ff256df356237 +size 20626 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png b/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png index c25087b71fe..38199566d27 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Drag and Drop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eee96c06b54b0c9779a84bce1caf94f907593d675625a51791b18925881bd619 -size 20883 +oid sha256:d5fe6166bb8cd5fae0899957e968312b9229a79e423a5d154eda483a149b264d +size 20831 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png b/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png index aa6372175d0..96adb69423d 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cced772c3830cf1d8d5f946cb457f1a0a059ac3b08b791899db5f5fe8132fc85 -size 10711 +oid sha256:b71e1d109f90e017644dd20b9d84d81e3a6d5195afbd01ba86c85fa248c8b5c5 +size 10703 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png b/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png index f8c028fb0cc..10f5d0b40a0 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Font Book.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cce41bc2887462a0a3aebc4148faf769d3666ff4a0e4c7db6ffbddb174be0ed3 -size 135734 +oid sha256:e3c9ba9064f44a4a14405f53316c1c602184caf16cb584d7c1f1912fe59f85ab +size 135712 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Frame.png b/crates/egui_demo_lib/tests/snapshots/demos/Frame.png index 28a855fa38c..e896f366e31 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Frame.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Frame.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:194c07bce8370b886b1bcb58936044e71f4725b2034dff3bbb7e3f710cf82f17 -size 24701 +oid sha256:097bd512dd71c17370f6e374312c19e7ab6845f151b3c3565f2a0790b61ee7ba +size 24413 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png b/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png index edc26309f9f..ea317322420 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Highlighting.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b5a71ea8cd6b3e2b267d668c1f149689e938eef3b4038a0935823d37aba348f -size 17806 +oid sha256:2bdf54573a6b0d2fedd90314f91dd7de22dd13709e8dd699b37ef8935b6adda5 +size 17785 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png b/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png index 2bfbf20f91e..68d03d9fd07 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Interactive Container.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d9c8395e6b4287b92d85a52ca2d47750f67abeb0ad88c6b42264bfe2e62fd09d -size 22283 +oid sha256:d6328c86b70f3e24aaf87db200d13dfd0baa787dd8431e47621457937f8f5021 +size 22552 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png index 9eea2ac7b92..dcd12b097f2 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17901f3e235cfbac03fb2334f36f614ed152a25b3a212e78beba9fb342039844 -size 65339 +oid sha256:afb57dc0bb8ff839138e36b8780136e0c8da55ff380832538fae0394143807c0 +size 65321 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Modals.png b/crates/egui_demo_lib/tests/snapshots/demos/Modals.png index 274b4b57686..3341eebff84 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Modals.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Modals.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:026723cb5d89b32386a849328c34420ee9e3ae1f97cbf6fa3c4543141123549e -size 32890 +oid sha256:403fc1d08d79168bc9f7a08ac7f363f2df916547f08311838edfda8a499a9b2d +size 32879 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png b/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png index af91ac8e61f..882279e03b7 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Multi Touch.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:950c825c8f6eadc682c57fe8843ded2ceb6981b6d4086d427dcf0d27740df8ef -size 36795 +oid sha256:cbd490a15377bdd4fd3272f3cd126cbc4fb9ed2f6f84edcbba03dd30dc3f3d99 +size 36780 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Painting.png b/crates/egui_demo_lib/tests/snapshots/demos/Painting.png index 8ad735d6c6f..40cb73fe06a 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Painting.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Painting.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e68f145f57812a1f6a81e1a8ab41bcb134320dcaaee3f462d1aa00d6c331752a -size 17579 +oid sha256:c31b3998817e345b12b037d7f8bec7641f77d0c7eab7da9a746b7b51c9efc8fb +size 17531 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Pan Zoom.png b/crates/egui_demo_lib/tests/snapshots/demos/Pan Zoom.png index 6a1b79e3d78..cecfb843283 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Pan Zoom.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Pan Zoom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6aec5a6291ad2796457d1c4fc44228c75bf075262aa672749a3cceb4baef1e08 -size 25271 +oid sha256:4e8963c3ecd0e74fe9641d87101742a0d45c82a582d70e30eb36bc835f5aac06 +size 25330 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Panels.png b/crates/egui_demo_lib/tests/snapshots/demos/Panels.png index 585c126360c..6576f9ad5dd 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Panels.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Panels.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5068df8549ffc91028addfec6f851f12a4de80e208b50b39e4d44b6aa2c7240e -size 261946 +oid sha256:88b3a50b481942630b5804c60227f23b719dc7e3eb6dbe432c2448cb69332def +size 262141 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png b/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png index 56978de2001..4c97bde2061 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Screenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:579a7a66f86ade628e9f469b0014e9010aa56312ad5bd1e8de2faaae7e0d1af6 -size 23770 +oid sha256:4d5d628b54b28eccac0d9ef21bbdabace0cdf507898707956943e2259df846ca +size 23741 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png index eef59e1b79c..98b10f30d6f 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Scrolling.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4c50f7cbac65ff98b4bdb11034dd18bf19836ea22f27e0ed04415ac763d86d09 -size 188067 +oid sha256:8763a8f8b4242cbe589cd3282dc5f4b32a84b4e0416fb8072dfefb7879a5d4f6 +size 187982 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png b/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png index e3a213176e9..19b7cf0ff35 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Sliders.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e3436906f7ac459b7f4330a286937722e78ad885ae1e90f75be566e970a8ca7 -size 116899 +oid sha256:3d61088bf1f992467e8396ac89407fa158d6f44e2d2d196778247f3ff18cb893 +size 119759 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Strip.png b/crates/egui_demo_lib/tests/snapshots/demos/Strip.png index 24e613b1c91..16a54ae0fa8 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Strip.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Strip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:764fc4f5fc60cc72d9731c7073e05abda70c1b5caf2992aabad63772e0cd5e33 -size 25905 +oid sha256:79ebaf9cccd19da2923507b5a553a23edc67382ef59e4b71f01d3bd6cc913539 +size 25829 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Table.png b/crates/egui_demo_lib/tests/snapshots/demos/Table.png index fd796f69561..232a5ec327c 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Table.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Table.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cce44cee74da69ce065e79f92919928bbdca6b1e57b44f0165666508b3cddce3 -size 72240 +oid sha256:06cbf13841e6ac5fbc57bdae6f5ad9d19718193c344420dedcc0e9d7ed2b8ba9 +size 71590 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png b/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png index dfef3f17542..cc2da3a5563 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Text Layout.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:10d278417ae66a788a88676874381b2d7c026b3130ca1c9178e2027595bb8e74 -size 67383 +oid sha256:35e66f211c0b30a684371b520c46dbe4f9d5b6835e053a4eb65f492dd66a9e6c +size 67288 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png index eb234e7fb7f..4704ff75dd7 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/TextEdit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6f13c687e27870df62488563de07b02711eb508ec7eee91470a44f2669610889 -size 21342 +oid sha256:fa9ee8631bfe433ee6fad1fb1651fd6b63e2fb3fbc5f321a5410f7266dc16d09 +size 21296 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png b/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png index 4e7d8a9230b..3e8a3ba3e93 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Tooltips.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:814d863deaa4fa029044da1783db87744f0d82e874edd6cbab16e712ed8715aa -size 59881 +oid sha256:6475702b1bf2c65fb5196928a8934ade647a6053d6902a836e3d69cb7920091e +size 59874 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Undo Redo.png b/crates/egui_demo_lib/tests/snapshots/demos/Undo Redo.png index a5d82e4378c..c39aa9f8571 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Undo Redo.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Undo Redo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65674a0da6ee80eff735a6404ad328332ad0ab48766de9b1d358e38ccb3ecc29 -size 13059 +oid sha256:9d8daaec0c58709298a4594b7b2aa935aa2de327e6b71bd7417c2ba3a6eb060c +size 13020 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png b/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png index 0dc3784a003..5670cef5834 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:450ede959fac122e6b045d18a6ab62952fa03371508704a1f2303d6379e41593 -size 35523 +oid sha256:29d8d859a8cb11e4b390d45c658ed8ff191c2e542909e12f2385c0cba62baa2d +size 35109 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_1.png b/crates/egui_demo_lib/tests/snapshots/modals_1.png index 87d1bd47bb0..0bd63cd50e9 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_1.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd53d25bcecc4f22cb9226237d69557696e8aa08b969157c0cdb015cfacf206d -size 48244 +oid sha256:17217e600d8a85ec00ecb11f0e5fe69496b30bbf715cc86785cec6e18b8c2fa1 +size 48158 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_2.png b/crates/egui_demo_lib/tests/snapshots/modals_2.png index bfac37e75a9..8c496d81dd1 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_2.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d9d9226996b0eecf516b9b89f09c57e5f7ad43cc154a2c8937779421a6580113 -size 48157 +oid sha256:fac50d2327a9e5e99989dd81d5644db86031b91b9c5c68fc17b5ef53ae655048 +size 47970 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_3.png b/crates/egui_demo_lib/tests/snapshots/modals_3.png index ab2e1f12553..1cc989ddce5 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_3.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:49401fcd6e348c75d81ad7419e1fa1a77500ca3d2360b418e8304ad5843e5b3a -size 44080 +oid sha256:e5e829257b742194742429be0efd326be17ff7f5b9b16f9df58df21e899320bd +size 43963 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png index 8042b02babb..0246090d4a6 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6cc1363890eb878ec7f37bf5350dd5a81ed39e16655b2e8b7fee0937e605bcae -size 44044 +oid sha256:5da064332c669a860a92a34b101f23e28026d4f07948f7c3e9a40e611f5e284f +size 43986 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.00.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.00.png index b4c96d8397d..4bb0582ae12 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.00.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc83a67ac7ff574f981dae02c05ecfba934df4fb181bba12fd7607ef9cba5aa6 -size 523650 +oid sha256:ed2a356452d792e32bea57f044da9d86da27fd8504826dd6b87618a53519ea6a +size 522556 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.25.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.25.png index 82426652255..1abf12ef16a 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.25.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0fe7626d08a4d28a66d39824cdcd8fef140c7c818e5fc07a7e86756c249875d -size 737802 +oid sha256:0d04a5854528c6141f7def6f9229c51c6d2d4c87e2f656be4d149e7b2b852976 +size 729056 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.50.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.50.png index 29514c4e7ff..c3bd68ac263 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.50.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:477437c5c8766beac88a96f531d8dfe5fac1fe2cdd4bad02fbc53610eb8925fb -size 887154 +oid sha256:e6e5c1a745e357faa7b98f7a2cd1ca139c4a14be154b9d21feb8030933acfdb7 +size 867552 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.67.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.67.png index 3d412b56667..4564e587106 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.67.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.67.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8aa0503d6e5dc82953000c7e68be826b0bf4f288aef82c2d5d832189538eb3b2 -size 1002104 +oid sha256:7d9f4a37541fd1a0754c1cb1f3a2d4a76f03d67ca4e5596c8e6982d691d29dea +size 980286 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.75.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.75.png index f10d189a56e..93931ef3889 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.75.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_1.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bef9f6dde686733e04a447be280fd50704db163d330fd8c663357b057cd50e27 -size 1080661 +oid sha256:4d1c99867202e16500146b7146a32fd83d70d60f5ac94aae4ca405a6377e4625 +size 1066559 diff --git a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_2.00.png b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_2.00.png index 6890b9d4611..4d76639461c 100644 --- a/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_2.00.png +++ b/crates/egui_demo_lib/tests/snapshots/rendering_test/dpi_2.00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:136c60283286df17e6751220e0c849028bc1379910995cf9e4930c5d63d0a90f -size 1235057 +oid sha256:08fc1c89fd2d04aa12c75a1829dacfff090e322c65ad969648799833e1b072eb +size 1235574 diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery.png index a2adca3db43..51596247499 100644 --- a/crates/egui_demo_lib/tests/snapshots/widget_gallery.png +++ b/crates/egui_demo_lib/tests/snapshots/widget_gallery.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9da93a7c27e17d370a3df73930d71694d436c300656b054722646ea57db45810 -size 160803 +oid sha256:d122b1a995e691b5049c57d65c9f222a5f1639b1e4f6f96f91823444339693cc +size 160540 diff --git a/crates/emath/src/gui_rounding.rs b/crates/emath/src/gui_rounding.rs index 3c2cf92411b..ac624792a27 100644 --- a/crates/emath/src/gui_rounding.rs +++ b/crates/emath/src/gui_rounding.rs @@ -116,6 +116,7 @@ impl GuiRounding for crate::Vec2 { ) } + // This doesn't really make sense for a Vec2, but 🤷‍♂️ #[inline] fn round_to_pixel_center(self, pixels_per_point: f32) -> Self { Self::new( diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index 89664cdb844..b769275cc2d 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -53,7 +53,7 @@ pub use self::{ Rounding, Shape, TextShape, }, stats::PaintStats, - stroke::{PathStroke, Stroke}, + stroke::{PathStroke, Stroke, StrokeKind}, tessellator::{TessellationOptions, Tessellator}, text::{FontFamily, FontId, Fonts, Galley}, texture_atlas::TextureAtlas, diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 6f67a2bc6a6..56af703b669 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -35,10 +35,7 @@ pub enum Shape { Ellipse(EllipseShape), /// A line between two points. - LineSegment { - points: [Pos2; 2], - stroke: PathStroke, - }, + LineSegment { points: [Pos2; 2], stroke: Stroke }, /// A series of lines between points. /// The path can have a stroke and/or fill (if closed). @@ -92,7 +89,7 @@ impl Shape { /// A line between two points. /// More efficient than calling [`Self::line`]. #[inline] - pub fn line_segment(points: [Pos2; 2], stroke: impl Into) -> Self { + pub fn line_segment(points: [Pos2; 2], stroke: impl Into) -> Self { Self::LineSegment { points, stroke: stroke.into(), @@ -100,7 +97,7 @@ impl Shape { } /// A horizontal line. - pub fn hline(x: impl Into, y: f32, stroke: impl Into) -> Self { + pub fn hline(x: impl Into, y: f32, stroke: impl Into) -> Self { let x = x.into(); Self::LineSegment { points: [pos2(x.min, y), pos2(x.max, y)], @@ -109,7 +106,7 @@ impl Shape { } /// A vertical line. - pub fn vline(x: f32, y: impl Into, stroke: impl Into) -> Self { + pub fn vline(x: f32, y: impl Into, stroke: impl Into) -> Self { let y = y.into(); Self::LineSegment { points: [pos2(x, y.min), pos2(x, y.max)], @@ -262,6 +259,7 @@ impl Shape { Self::Rect(RectShape::filled(rect, rounding, fill_color)) } + /// The stroke extends _outside_ the [`Rect`]. #[inline] pub fn rect_stroke( rect: Rect, @@ -670,6 +668,11 @@ pub struct RectShape { pub fill: Color32, /// The thickness and color of the outline. + /// + /// The stroke extends _outside_ the edge of [`Self::rect`], + /// i.e. using [`crate::StrokeKind::Outside`]. + /// + /// This means the [`Self::visual_bounding_rect`] is `rect.size() + 2.0 * stroke.width`. pub stroke: Stroke, /// If larger than zero, the edges of the rectangle @@ -695,6 +698,7 @@ pub struct RectShape { } impl RectShape { + /// The stroke extends _outside_ the [`Rect`]. #[inline] pub fn new( rect: Rect, @@ -730,6 +734,7 @@ impl RectShape { } } + /// The stroke extends _outside_ the [`Rect`]. #[inline] pub fn stroke(rect: Rect, rounding: impl Into, stroke: impl Into) -> Self { Self { @@ -761,8 +766,8 @@ impl RectShape { if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() { Rect::NOTHING } else { - self.rect - .expand((self.stroke.width + self.blur_width) / 2.0) + let Stroke { width, .. } = self.stroke; // Make sure we remember to update this if we change `stroke` to `PathStroke` + self.rect.expand(width + self.blur_width / 2.0) } } } diff --git a/crates/epaint/src/shape_transform.rs b/crates/epaint/src/shape_transform.rs index f072393f557..263f9cf07a6 100644 --- a/crates/epaint/src/shape_transform.rs +++ b/crates/epaint/src/shape_transform.rs @@ -21,7 +21,7 @@ pub fn adjust_colors( } Shape::LineSegment { stroke, points: _ } => { - adjust_color_mode(&mut stroke.color, adjust_color); + adjust_color(&mut stroke.color); } Shape::Path(PathShape { diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index ebd22f55303..e04eb03bee8 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -11,7 +11,7 @@ use crate::{ EllipseShape, Mesh, PathShape, Primitive, QuadraticBezierShape, RectShape, Rounding, Shape, Stroke, TextShape, TextureId, Vertex, WHITE_UV, }; -use emath::{pos2, remap, vec2, NumExt, Pos2, Rect, Rot2, Vec2}; +use emath::{pos2, remap, vec2, GuiRounding as _, NumExt, Pos2, Rect, Rot2, Vec2}; use self::color::ColorMode; use self::stroke::PathStroke; @@ -671,10 +671,21 @@ pub struct TessellationOptions { /// from the font atlas. pub prerasterized_discs: bool, - /// If `true` (default) align text to mesh grid. + /// If `true` (default) align text to the physical pixel grid. /// This makes the text sharper on most platforms. pub round_text_to_pixels: bool, + /// If `true` (default), align right-angled line segments to the physical pixel grid. + /// + /// This makes the line segments appear crisp on any display. + pub round_line_segments_to_pixels: bool, + + /// If `true` (default), align rectangles to the physical pixel grid. + /// + /// This makes the rectangle strokes more crisp, + /// and makes filled rectangles tile perfectly (without feathering). + pub round_rects_to_pixels: bool, + /// Output the clip rectangles to be painted. pub debug_paint_clip_rects: bool, @@ -708,6 +719,8 @@ impl Default for TessellationOptions { coarse_tessellation_culling: true, prerasterized_discs: true, round_text_to_pixels: true, + round_line_segments_to_pixels: true, + round_rects_to_pixels: true, debug_paint_text_rects: false, debug_paint_clip_rects: false, debug_ignore_clip_rects: false, @@ -754,8 +767,11 @@ fn fill_closed_path( // TODO(juancampa): This bounding box is computed twice per shape: once here and another when tessellating the // stroke, consider hoisting that logic to the tessellator/scratchpad. - let bbox = Rect::from_points(&path.iter().map(|p| p.pos).collect::>()) - .expand((stroke.width / 2.0) + feathering); + let bbox = if matches!(stroke.color, ColorMode::UV(_)) { + Rect::from_points(&path.iter().map(|p| p.pos).collect::>()).expand(feathering) + } else { + Rect::NAN + }; let stroke_color = &stroke.color; let get_stroke_color: Box Color32> = match stroke_color { @@ -900,7 +916,7 @@ fn fill_closed_path_with_uv( #[inline(always)] fn translate_stroke_point(p: &mut PathPoint, stroke: &PathStroke) { match stroke.kind { - stroke::StrokeKind::Middle => { /* Nothingn to do */ } + stroke::StrokeKind::Middle => { /* Nothing to do */ } stroke::StrokeKind::Outside => { p.pos += p.normal * stroke.width * 0.5; } @@ -932,9 +948,13 @@ fn stroke_path( .for_each(|p| translate_stroke_point(p, stroke)); } - // expand the bounding box to include the thickness of the path - let bbox = Rect::from_points(&path.iter().map(|p| p.pos).collect::>()) - .expand((stroke.width / 2.0) + feathering); + // Expand the bounding box to include the thickness of the path + let bbox = if matches!(stroke.color, ColorMode::UV(_)) { + Rect::from_points(&path.iter().map(|p| p.pos).collect::>()) + .expand((stroke.width / 2.0) + feathering) + } else { + Rect::NAN + }; let get_color = |col: &ColorMode, pos: Pos2| match col { ColorMode::Solid(col) => *col, @@ -1386,7 +1406,9 @@ impl Tessellator { out.append(mesh); } - Shape::LineSegment { points, stroke } => self.tessellate_line(points, stroke, out), + Shape::LineSegment { points, stroke } => { + self.tessellate_line_segment(points, stroke, out); + } Shape::Path(path_shape) => { self.tessellate_path(&path_shape, out); } @@ -1563,10 +1585,10 @@ impl Tessellator { /// /// * `shape`: the mesh to tessellate. /// * `out`: triangles are appended to this. - pub fn tessellate_line( + pub fn tessellate_line_segment( &mut self, - points: [Pos2; 2], - stroke: impl Into, + mut points: [Pos2; 2], + stroke: impl Into, out: &mut Mesh, ) { let stroke = stroke.into(); @@ -1582,10 +1604,38 @@ impl Tessellator { return; } + if self.options.round_line_segments_to_pixels { + let [a, b] = &mut points; + if a.x == b.x { + // Vertical line + let mut x = a.x; + round_line_segment(&mut x, &stroke, self.pixels_per_point); + a.x = x; + b.x = x; + } + if a.y == b.y { + // Horizontal line + let mut y = a.y; + round_line_segment(&mut y, &stroke, self.pixels_per_point); + a.y = y; + b.y = y; + } + } + self.scratchpad_path.clear(); self.scratchpad_path.add_line_segment(points); self.scratchpad_path - .stroke_open(self.feathering, &stroke, out); + .stroke_open(self.feathering, &stroke.into(), out); + } + + #[deprecated = "Use `tessellate_line_segment` instead"] + pub fn tessellate_line( + &mut self, + points: [Pos2; 2], + stroke: impl Into, + out: &mut Mesh, + ) { + self.tessellate_line_segment(points, stroke, out); } /// Tessellate a single [`PathShape`] into a [`Mesh`]. @@ -1660,6 +1710,14 @@ impl Tessellator { return; } + if self.options.round_rects_to_pixels { + // Since the stroke extends outside of the rectangle, + // we can round the rectangle sides to the physical pixel edges, + // and the filled rect will appear crisp, as will the inside of the stroke. + let Stroke { .. } = stroke; // Make sure we remember to update this if we change `stroke` to `PathStroke` + rect = rect.round_to_pixels(self.pixels_per_point); + } + // It is common to (sometimes accidentally) create an infinitely sized rectangle. // Make sure we can handle that: rect.min = rect.min.at_least(pos2(-1e7, -1e7)); @@ -1688,46 +1746,33 @@ impl Tessellator { self.feathering = self.feathering.max(blur_width); } - if rect.width() < self.feathering { + if rect.width() < 0.5 * self.feathering { // Very thin - approximate by a vertical line-segment: let line = [rect.center_top(), rect.center_bottom()]; if fill != Color32::TRANSPARENT { - self.tessellate_line(line, Stroke::new(rect.width(), fill), out); + self.tessellate_line_segment(line, Stroke::new(rect.width(), fill), out); } if !stroke.is_empty() { - self.tessellate_line(line, stroke, out); // back… - self.tessellate_line(line, stroke, out); // …and forth + self.tessellate_line_segment(line, stroke, out); // back… + self.tessellate_line_segment(line, stroke, out); // …and forth } - } else if rect.height() < self.feathering { + } else if rect.height() < 0.5 * self.feathering { // Very thin - approximate by a horizontal line-segment: let line = [rect.left_center(), rect.right_center()]; if fill != Color32::TRANSPARENT { - self.tessellate_line(line, Stroke::new(rect.height(), fill), out); + self.tessellate_line_segment(line, Stroke::new(rect.height(), fill), out); } if !stroke.is_empty() { - self.tessellate_line(line, stroke, out); // back… - self.tessellate_line(line, stroke, out); // …and forth + self.tessellate_line_segment(line, stroke, out); // back… + self.tessellate_line_segment(line, stroke, out); // …and forth } } else { - let rect = if !stroke.is_empty() && stroke.width < self.feathering { - // Very thin rectangle strokes create extreme aliasing when they move around. - // We can fix that by rounding the rectangle corners to pixel centers. - // TODO(#5164): maybe do this for all shapes and stroke sizes - // TODO(emilk): since we use StrokeKind::Outside, we should probably round the - // corners after offsetting them with half the stroke width (see `translate_stroke_point`). - Rect { - min: self.round_pos_to_pixel_center(rect.min), - max: self.round_pos_to_pixel_center(rect.max), - } - } else { - rect - }; - let path = &mut self.scratchpad_path; path.clear(); path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding); path.add_line_loop(&self.scratchpad_points); let path_stroke = PathStroke::from(stroke).outside(); + if uv.is_positive() { // Textured let uv_from_pos = |p: Pos2| { @@ -1741,6 +1786,7 @@ impl Tessellator { // Untextured path.fill(self.feathering, fill, &path_stroke, out); } + path.stroke_closed(self.feathering, &path_stroke, out); } @@ -1968,6 +2014,45 @@ impl Tessellator { } } +fn round_line_segment(coord: &mut f32, stroke: &Stroke, pixels_per_point: f32) { + // If the stroke is an odd number of pixels wide, + // we want to round the center of it to the center of a pixel. + // + // If however it is an even number of pixels wide, + // we want to round the center to be between two pixels. + // + // We also want to treat strokes that are _almost_ odd as it it was odd, + // to make it symmetric. Same for strokes that are _almost_ even. + // + // For strokes less than a pixel wide we also round to the center, + // because it will rendered as a single row of pixels by the tessellator. + + let pixel_size = 1.0 / pixels_per_point; + + if stroke.width <= pixel_size || is_nearest_integer_odd(pixels_per_point * stroke.width) { + *coord = coord.round_to_pixel_center(pixels_per_point); + } else { + *coord = coord.round_to_pixels(pixels_per_point); + } +} + +fn is_nearest_integer_odd(width: f32) -> bool { + (width * 0.5 + 0.25).fract() > 0.5 +} + +#[test] +fn test_is_nearest_integer_odd() { + assert!(is_nearest_integer_odd(0.6)); + assert!(is_nearest_integer_odd(1.0)); + assert!(is_nearest_integer_odd(1.4)); + assert!(!is_nearest_integer_odd(1.6)); + assert!(!is_nearest_integer_odd(2.0)); + assert!(!is_nearest_integer_odd(2.4)); + assert!(is_nearest_integer_odd(2.6)); + assert!(is_nearest_integer_odd(3.0)); + assert!(is_nearest_integer_odd(3.4)); +} + #[deprecated = "Use `Tessellator::new(…).tessellate_shapes(…)` instead"] pub fn tessellate_shapes( pixels_per_point: f32, diff --git a/examples/hello_android/Cargo.toml b/examples/hello_android/Cargo.toml index dcb0a5a5c01..a7d27277359 100644 --- a/examples/hello_android/Cargo.toml +++ b/examples/hello_android/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.76" +rust-version = "1.80" publish = false # `unsafe_code` is required for `#[no_mangle]`, disable workspace lints to workaround lint error. @@ -16,10 +16,7 @@ crate-type = ["cdylib"] [dependencies] -eframe = { workspace = true, features = [ - "default", - "android-native-activity", -] } +eframe = { workspace = true, features = ["default", "android-native-activity"] } # For image support: egui_extras = { workspace = true, features = ["default", "image"] } @@ -29,4 +26,4 @@ winit = { workspace = true } android_logger = "0.14" [package.metadata.android] -build_targets = [ "armv7-linux-androideabi", "aarch64-linux-android" ] +build_targets = ["armv7-linux-androideabi", "aarch64-linux-android"]