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

Make all lines and rectangles crisp #5518

Merged
merged 24 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0010e90
Fix bug in `Painter::with_layer_id`
emilk Dec 26, 2024
ca1e416
Make more function of `Painter` public
emilk Dec 26, 2024
7a99414
Cache `pixels_per_point` inside `Painter` for quick access
emilk Dec 26, 2024
9d9910e
`LineSegment::stroke` is now a normal `Stroke` again
emilk Dec 26, 2024
8ca2b4a
Fix bug in `RectShape::visual_bounding_rect`
emilk Dec 26, 2024
3780de2
Document how `RectShape::stroke` works
emilk Dec 26, 2024
f29de94
Add `Shape::round_to_pixels`
emilk Dec 26, 2024
e148e21
Remove dead code
emilk Dec 26, 2024
9748cab
Fix typo
emilk Dec 26, 2024
39f6302
Add `ui.pixels_per_point()`
emilk Dec 26, 2024
1f71baa
Round all shapes by default
emilk Dec 26, 2024
e925fed
Rename `Tessellator::tessellate_line` to `tessellate_line_segment`
emilk Dec 26, 2024
2028db0
Round the shapes in the tessellator instead
emilk Dec 26, 2024
95aad60
Fix look of very tiny rectangles
emilk Dec 26, 2024
e6bed36
Remove extra rounding of panel separator line
emilk Dec 26, 2024
0882672
Remove egui-side pixel-rounding
emilk Dec 26, 2024
cb2fe60
Mark `Painter::with_layer_id` as `#[inline]`
emilk Dec 26, 2024
4b6c8f1
Fix some doclinks
emilk Dec 26, 2024
6b833ac
Fix doc-link
emilk Dec 26, 2024
07c1726
Fix old rust-version in hello_android example
emilk Dec 26, 2024
c5ed20c
Avoid expensive bbox calculation unless needed
emilk Dec 26, 2024
3925535
Fix bug in tesselator rect rounding
emilk Dec 26, 2024
4115bf8
Better positioning of panel line
emilk Dec 26, 2024
9a22b0c
Update snapshot screenshots
emilk Dec 26, 2024
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
30 changes: 18 additions & 12 deletions crates/egui/src/containers/panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}

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

Expand Down
4 changes: 3 additions & 1 deletion crates/egui/src/containers/resize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/containers/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
45 changes: 0 additions & 45 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use emath::GuiRounding as _;
use epaint::{
emath::{self, TSTransform},
mutex::RwLock,
pos2,
stats::PaintStats,
tessellator,
text::{FontInsert, FontPriority, Fonts},
Expand Down Expand Up @@ -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.
Expand Down
15 changes: 13 additions & 2 deletions crates/egui/src/introspection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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");
Expand Down
83 changes: 47 additions & 36 deletions crates/egui/src/painter.rs
Original file line number Diff line number Diff line change
@@ -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,

Expand All @@ -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,
Expand All @@ -49,28 +58,20 @@ 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`].
///
/// 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.
Expand All @@ -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<Color32>) {
pub fn set_fade_to_color(&mut self, fade_to_color: Option<Color32>) {
self.fade_to_color = fade_to_color;
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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())
}
}

Expand Down Expand Up @@ -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<PathStroke>) -> ShapeIdx {
pub fn line_segment(&self, points: [Pos2; 2], stroke: impl Into<Stroke>) -> ShapeIdx {
self.add(Shape::LineSegment {
points,
stroke: stroke.into(),
Expand All @@ -351,13 +360,13 @@ impl Painter {
}

/// Paints a horizontal line.
pub fn hline(&self, x: impl Into<Rangef>, y: f32, stroke: impl Into<PathStroke>) -> ShapeIdx {
self.add(Shape::hline(x, y, stroke.into()))
pub fn hline(&self, x: impl Into<Rangef>, y: f32, stroke: impl Into<Stroke>) -> ShapeIdx {
self.add(Shape::hline(x, y, stroke))
}

/// Paints a vertical line.
pub fn vline(&self, x: f32, y: impl Into<Rangef>, stroke: impl Into<PathStroke>) -> ShapeIdx {
self.add(Shape::vline(x, y, stroke.into()))
pub fn vline(&self, x: f32, y: impl Into<Rangef>, stroke: impl Into<Stroke>) -> ShapeIdx {
self.add(Shape::vline(x, y, stroke))
}

pub fn circle(
Expand Down Expand Up @@ -398,6 +407,7 @@ impl Painter {
})
}

/// The stroke extends _outside_ the [`Rect`].
pub fn rect(
&self,
rect: Rect,
Expand All @@ -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,
Expand Down
Loading
Loading