Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Round widget coordinates to even multiple of 1/32 #5517

Merged
merged 16 commits into from
Dec 26, 2024
10 changes: 6 additions & 4 deletions crates/egui/src/containers/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand All @@ -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()
}
}

Expand Down Expand Up @@ -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();
Expand Down
9 changes: 7 additions & 2 deletions crates/egui/src/containers/panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -264,6 +266,8 @@ impl SidePanel {
}
}

panel_rect = panel_rect.round_ui();

let mut panel_ui = ui.new_child(
UiBuilder::new()
.id_salt(id)
Expand Down Expand Up @@ -756,6 +760,8 @@ impl TopBottomPanel {
}
}

panel_rect = panel_rect.round_ui();

let mut panel_ui = ui.new_child(
UiBuilder::new()
.id_salt(id)
Expand Down Expand Up @@ -1130,15 +1136,14 @@ impl CentralPanel {
ctx: &Context,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
let available_rect = ctx.available_rect();
let id = Id::new((ctx.viewport_id(), "central_panel"));

let mut panel_ui = Ui::new(
ctx.clone(),
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());

Expand Down
7 changes: 5 additions & 2 deletions crates/egui/src/containers/resize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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();

Expand Down Expand Up @@ -383,6 +385,7 @@ impl Resize {
}
}

use emath::GuiRounding as _;
use epaint::Stroke;

pub fn paint_resize_corner(ui: &Ui, response: &Response) {
Expand Down
18 changes: 9 additions & 9 deletions crates/egui/src/containers/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"
Expand All @@ -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(
Expand Down Expand Up @@ -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);
Expand Down
15 changes: 7 additions & 8 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand All @@ -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()
}
}

Expand Down Expand Up @@ -2568,15 +2567,15 @@ 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.
///
/// 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.
Expand All @@ -2586,15 +2585,15 @@ impl Context {
for (_id, window) in ctx.memory.areas().visible_windows() {
used = used.union(window.rect());
}
used
used.round_ui()
})
}

/// How much space is used by panels and windows.
///
/// 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()
}

// ---------------------------------------------------------------------
Expand Down
8 changes: 6 additions & 2 deletions crates/egui/src/grid.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use emath::GuiRounding as _;

use crate::{
vec2, Align2, Color32, Context, Id, InnerResponse, NumExt, Painter, Rect, Region, Style, Ui,
UiBuilder, Vec2,
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 4 additions & 2 deletions crates/egui/src/layout.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use emath::GuiRounding as _;

use crate::{
emath::{pos2, vec2, Align2, NumExt, Pos2, Rect, Vec2},
Align,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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`.
Expand Down
8 changes: 3 additions & 5 deletions crates/egui/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://github.com/emilk/egui/issues/5163> for more.
/// See [`emath::GuiRounding`] for more.
pub show_unaligned: bool,
}

Expand All @@ -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),
}
}
}
Expand Down
18 changes: 12 additions & 6 deletions crates/egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use std::{any::Any, hash::Hash, sync::Arc};

use emath::GuiRounding as _;
use epaint::mutex::RwLock;

use crate::{
Expand Down Expand Up @@ -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())))
}
Expand Down Expand Up @@ -1295,13 +1298,16 @@ 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)
}

/// 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);
Expand Down Expand Up @@ -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;

Expand All @@ -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());
}
}
Expand Down
Loading
Loading