mirror of
https://github.com/emilk/egui.git
synced 2026-09-02 06:40:06 -04:00
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`
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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<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(
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user