mirror of
https://github.com/emilk/egui.git
synced 2026-06-28 07:23:13 -04:00
Merge branch 'master' into cache_galley_lines
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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<PathStroke>) -> Self {
|
||||
pub fn line_segment(points: [Pos2; 2], stroke: impl Into<Stroke>) -> Self {
|
||||
Self::LineSegment {
|
||||
points,
|
||||
stroke: stroke.into(),
|
||||
@@ -100,7 +97,7 @@ impl Shape {
|
||||
}
|
||||
|
||||
/// A horizontal line.
|
||||
pub fn hline(x: impl Into<Rangef>, y: f32, stroke: impl Into<PathStroke>) -> Self {
|
||||
pub fn hline(x: impl Into<Rangef>, y: f32, stroke: impl Into<Stroke>) -> 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<Rangef>, stroke: impl Into<PathStroke>) -> Self {
|
||||
pub fn vline(x: f32, y: impl Into<Rangef>, stroke: impl Into<Stroke>) -> 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,
|
||||
@@ -671,6 +669,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
|
||||
@@ -696,6 +699,7 @@ pub struct RectShape {
|
||||
}
|
||||
|
||||
impl RectShape {
|
||||
/// The stroke extends _outside_ the [`Rect`].
|
||||
#[inline]
|
||||
pub fn new(
|
||||
rect: Rect,
|
||||
@@ -731,6 +735,7 @@ impl RectShape {
|
||||
}
|
||||
}
|
||||
|
||||
/// The stroke extends _outside_ the [`Rect`].
|
||||
#[inline]
|
||||
pub fn stroke(rect: Rect, rounding: impl Into<Rounding>, stroke: impl Into<Stroke>) -> Self {
|
||||
Self {
|
||||
@@ -762,8 +767,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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::<Vec<Pos2>>())
|
||||
.expand((stroke.width / 2.0) + feathering);
|
||||
let bbox = if matches!(stroke.color, ColorMode::UV(_)) {
|
||||
Rect::from_points(&path.iter().map(|p| p.pos).collect::<Vec<Pos2>>()).expand(feathering)
|
||||
} else {
|
||||
Rect::NAN
|
||||
};
|
||||
|
||||
let stroke_color = &stroke.color;
|
||||
let get_stroke_color: Box<dyn Fn(Pos2) -> 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::<Vec<Pos2>>())
|
||||
.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::<Vec<Pos2>>())
|
||||
.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<PathStroke>,
|
||||
mut points: [Pos2; 2],
|
||||
stroke: impl Into<Stroke>,
|
||||
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<Stroke>,
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1970,6 +2016,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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -817,7 +821,7 @@ impl GalleyCache {
|
||||
halign: job.halign,
|
||||
justify: job.justify,
|
||||
first_row_min_height,
|
||||
round_output_size_to_nearest_ui_point: job.round_output_size_to_nearest_ui_point,
|
||||
round_output_to_gui: job.round_output_to_gui,
|
||||
};
|
||||
first_row_min_height = 0.0;
|
||||
|
||||
@@ -910,11 +914,8 @@ impl GalleyCache {
|
||||
merged_galley.elided |= galley.elided;
|
||||
}
|
||||
|
||||
if merged_galley.job.round_output_size_to_nearest_ui_point {
|
||||
super::round_output_size_to_nearest_ui_point(
|
||||
&mut merged_galley.rect,
|
||||
&merged_galley.job,
|
||||
);
|
||||
if merged_galley.job.round_output_to_gui {
|
||||
super::round_output_to_gui(&mut merged_galley.rect, &merged_galley.job);
|
||||
}
|
||||
|
||||
merged_galley
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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};
|
||||
|
||||
@@ -643,7 +643,7 @@ fn galley_from_rows(
|
||||
min_x = min_x.min(placed_row.rect().min.x);
|
||||
max_x = max_x.max(placed_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);
|
||||
@@ -662,8 +662,13 @@ 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 {
|
||||
round_output_size_to_nearest_ui_point(&mut rect, &job);
|
||||
if job.round_output_to_gui {
|
||||
for placed_row in &mut rows {
|
||||
placed_row.pos = placed_row.pos.round_ui();
|
||||
let row = Arc::get_mut(&mut placed_row.row).unwrap();
|
||||
row.size = row.size.round_ui();
|
||||
}
|
||||
round_output_to_gui(&mut rect, &job);
|
||||
}
|
||||
|
||||
Galley {
|
||||
@@ -678,20 +683,21 @@ fn galley_from_rows(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn round_output_size_to_nearest_ui_point(rect: &mut Rect, job: &LayoutJob) {
|
||||
pub(crate) fn round_output_to_gui(rect: &mut Rect, job: &LayoutJob) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1151,6 +1157,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!(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user