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 widgets to integer coordinates #5197

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions crates/egui/src/containers/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,25 @@ impl AreaState {
pivot_pos.x - self.pivot.x().to_factor() * size.x,
pivot_pos.y - self.pivot.y().to_factor() * size.y,
)
.round()
}

/// Move the left top positions of the area.
pub fn set_left_top_pos(&mut self, pos: Pos2) {
let size = self.size.unwrap_or_default();
self.pivot_pos = Some(pos2(
pos.x + self.pivot.x().to_factor() * size.x,
pos.y + self.pivot.y().to_factor() * size.y,
));
self.pivot_pos = Some(
pos2(
pos.x + self.pivot.x().to_factor() * size.x,
pos.y + self.pivot.y().to_factor() * size.y,
)
.round(),
);
}

/// 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()
}
}

Expand Down Expand Up @@ -493,12 +497,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().round());

// Update response with possibly moved/constrained rect:
move_response.rect = state.rect();
Expand Down
3 changes: 2 additions & 1 deletion crates/egui/src/containers/resize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,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();

// ------------------------------

Expand Down
22 changes: 11 additions & 11 deletions crates/egui/src/containers/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ impl<'open> Window<'open> {
let (title_bar_height, title_content_spacing) = if with_title_bar {
let style = ctx.style();
let spacing = window_margin.top + window_margin.bottom;
let height = ctx.fonts(|f| title.font_height(f, &style)) + spacing;
let height = ctx.fonts(|f| title.font_height(f, &style)).round() + spacing;
window_frame.rounding.ne = window_frame.rounding.ne.clamp(0.0, height / 2.0);
window_frame.rounding.nw = window_frame.rounding.nw.clamp(0.0, height / 2.0);
(height, spacing)
Expand Down Expand Up @@ -771,13 +771,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 @@ -802,18 +801,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())
}

fn resize_interaction(
Expand Down Expand Up @@ -1047,13 +1046,14 @@ impl TitleBar {
let inner_response = ui.horizontal(|ui| {
let height = ui
.fonts(|fonts| title.font_height(fonts, ui.style()))
.max(ui.spacing().interact_size.y);
.max(ui.spacing().interact_size.y)
.round();
ui.set_min_height(height);

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(); // calculated so that the icon is on the diagonal (if window padding is symmetrical)

if collapsible {
ui.add_space(pad);
Expand Down
6 changes: 2 additions & 4 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2065,7 +2065,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 @@ -2077,9 +2077,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()
}
}

Expand Down
6 changes: 4 additions & 2 deletions crates/egui/src/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,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()
}

#[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()
}

pub(crate) fn justify_and_align(&self, frame: Rect, size: Vec2) -> Rect {
Expand Down
4 changes: 2 additions & 2 deletions crates/egui/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,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()
}

fn initial_cursor(&self, max_rect: Rect) -> Rect {
Expand Down Expand Up @@ -634,7 +634,7 @@ impl Layout {
debug_assert!(!frame_rect.any_nan());
debug_assert!(!frame_rect.is_negative());

frame_rect
frame_rect.round()
}

/// Apply justify (fill width/height) and/or alignment after calling `next_space`.
Expand Down
1 change: 1 addition & 0 deletions crates/egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,7 @@ impl Ui {
/// The height of text of this text style
pub fn text_style_height(&self, style: &TextStyle) -> f32 {
self.fonts(|f| f.row_height(&style.resolve(self.style())))
.round()
}

/// Screen-space rectangle for clipping what we paint in this ui.
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/widget_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ impl RichText {
if let Some(family) = &self.family {
font_id.family = family.clone();
}
fonts.row_height(&font_id)
fonts.row_height(&font_id).round()
}

/// Append to an existing [`LayoutJob`]
Expand Down
4 changes: 3 additions & 1 deletion crates/egui/src/widgets/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,9 @@ impl Widget for Button<'_> {
}

let space_available_for_image = if let Some(text) = &text {
let font_height = ui.fonts(|fonts| text.font_height(fonts, ui.style()));
let font_height = ui
.fonts(|fonts| text.font_height(fonts, ui.style()))
.round();
Vec2::splat(font_height) // Reasonable?
} else {
ui.available_size() - 2.0 * button_padding
Expand Down
4 changes: 2 additions & 2 deletions crates/egui/src/widgets/label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,10 @@ impl Label {
assert!(!galley.rows.is_empty(), "Galleys are never empty");
// collect a response from many rows:
let rect = galley.rows[0].rect.translate(vec2(pos.x, pos.y));
let mut response = ui.allocate_rect(rect, sense);
let mut response = ui.allocate_rect(rect.round(), sense);
for row in galley.rows.iter().skip(1) {
let rect = row.rect.translate(vec2(pos.x, pos.y));
response |= ui.allocate_rect(rect, sense);
response |= ui.allocate_rect(rect.round(), sense);
}
(pos, galley, response)
} else {
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/widgets/text_edit/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ impl<'t> TextEdit<'t> {
let prev_text = text.as_str().to_owned();

let font_id = font_selection.resolve(ui.style());
let row_height = ui.fonts(|f| f.row_height(&font_id));
let row_height = ui.fonts(|f| f.row_height(&font_id)).round();
const MIN_WIDTH: f32 = 24.0; // Never make a [`TextEdit`] more narrow than this.
let available_width = (ui.available_width() - margin.sum().x).at_least(MIN_WIDTH);
let desired_width = desired_width.unwrap_or_else(|| ui.spacing().text_edit_width);
Expand Down
2 changes: 1 addition & 1 deletion crates/egui_demo_lib/src/demo/scrolling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ fn huge_content_painter(ui: &mut egui::Ui) {
ui.add_space(4.0);

let font_id = TextStyle::Body.resolve(ui.style());
let row_height = ui.fonts(|f| f.row_height(&font_id)) + ui.spacing().item_spacing.y;
let row_height = ui.fonts(|f| f.row_height(&font_id)).round() + ui.spacing().item_spacing.y;
let num_rows = 10_000;

ScrollArea::vertical()
Expand Down
2 changes: 1 addition & 1 deletion crates/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ fn bullet_point(ui: &mut Ui, width: f32) -> Response {

fn numbered_point(ui: &mut Ui, width: f32, number: &str) -> Response {
let font_id = TextStyle::Body.resolve(ui.style());
let row_height = ui.fonts(|f| f.row_height(&font_id));
let row_height = ui.fonts(|f| f.row_height(&font_id)).round();
let (rect, response) = ui.allocate_exact_size(vec2(width, row_height), Sense::hover());
let text = format!("{number}.");
let text_color = ui.visuals().strong_text_color();
Expand Down
2 changes: 1 addition & 1 deletion crates/egui_extras/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

if flags.striped {
self.ui.painter().rect_filled(
Expand Down
10 changes: 10 additions & 0 deletions crates/emath/src/rect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,16 @@ impl Rect {
)
}

/// Round to integer
#[must_use]
#[inline]
pub fn round(self) -> Self {
Self {
min: self.min.round(),
max: self.max.round(),
}
}

#[must_use]
#[inline]
pub fn intersects(self, other: Self) -> bool {
Expand Down
Loading