Add `Galley::intrinsic_size` and use it in `AtomLayout` (#7146)
- part of https://github.com/emilk/egui/issues/5762 - also allows me to simplify sizing logic in egui_flex
This commit is contained in:
parent
f46926aaf1
commit
508c60b2e2
|
|
@ -81,7 +81,7 @@ impl<'a> Atom<'a> {
|
|||
wrap_mode = Some(TextWrapMode::Truncate);
|
||||
}
|
||||
|
||||
let (preferred, kind) = self.kind.into_sized(ui, available_size, wrap_mode);
|
||||
let (intrinsic, kind) = self.kind.into_sized(ui, available_size, wrap_mode);
|
||||
|
||||
let size = self
|
||||
.size
|
||||
|
|
@ -89,7 +89,7 @@ impl<'a> Atom<'a> {
|
|||
|
||||
SizedAtom {
|
||||
size,
|
||||
preferred_size: preferred,
|
||||
intrinsic_size: intrinsic.at_least(self.size.unwrap_or_default()),
|
||||
grow: self.grow,
|
||||
kind,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,11 +81,10 @@ impl<'a> AtomKind<'a> {
|
|||
) -> (Vec2, SizedAtomKind<'a>) {
|
||||
match self {
|
||||
AtomKind::Text(text) => {
|
||||
let galley = text.into_galley(ui, wrap_mode, available_size.x, TextStyle::Button);
|
||||
(
|
||||
galley.size(), // TODO(#5762): calculate the preferred size
|
||||
SizedAtomKind::Text(galley),
|
||||
)
|
||||
let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode());
|
||||
let galley =
|
||||
text.into_galley(ui, Some(wrap_mode), available_size.x, TextStyle::Button);
|
||||
(galley.intrinsic_size, SizedAtomKind::Text(galley))
|
||||
}
|
||||
AtomKind::Image(image) => {
|
||||
let size = image.load_and_calc_size(ui, available_size);
|
||||
|
|
|
|||
|
|
@ -183,10 +183,10 @@ impl<'a> AtomLayout<'a> {
|
|||
|
||||
let mut desired_width = 0.0;
|
||||
|
||||
// Preferred width / height is the ideal size of the widget, e.g. the size where the
|
||||
// intrinsic width / height is the ideal size of the widget, e.g. the size where the
|
||||
// text is not wrapped. Used to set Response::intrinsic_size.
|
||||
let mut preferred_width = 0.0;
|
||||
let mut preferred_height = 0.0;
|
||||
let mut intrinsic_width = 0.0;
|
||||
let mut intrinsic_height = 0.0;
|
||||
|
||||
let mut height: f32 = 0.0;
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ impl<'a> AtomLayout<'a> {
|
|||
if atoms.len() > 1 {
|
||||
let gap_space = gap * (atoms.len() as f32 - 1.0);
|
||||
desired_width += gap_space;
|
||||
preferred_width += gap_space;
|
||||
intrinsic_width += gap_space;
|
||||
}
|
||||
|
||||
for (idx, item) in atoms.into_iter().enumerate() {
|
||||
|
|
@ -224,10 +224,10 @@ impl<'a> AtomLayout<'a> {
|
|||
let size = sized.size;
|
||||
|
||||
desired_width += size.x;
|
||||
preferred_width += sized.preferred_size.x;
|
||||
intrinsic_width += sized.intrinsic_size.x;
|
||||
|
||||
height = height.at_least(size.y);
|
||||
preferred_height = preferred_height.at_least(sized.preferred_size.y);
|
||||
intrinsic_height = intrinsic_height.at_least(sized.intrinsic_size.y);
|
||||
|
||||
sized_items.push(sized);
|
||||
}
|
||||
|
|
@ -243,10 +243,10 @@ impl<'a> AtomLayout<'a> {
|
|||
let size = sized.size;
|
||||
|
||||
desired_width += size.x;
|
||||
preferred_width += sized.preferred_size.x;
|
||||
intrinsic_width += sized.intrinsic_size.x;
|
||||
|
||||
height = height.at_least(size.y);
|
||||
preferred_height = preferred_height.at_least(sized.preferred_size.y);
|
||||
intrinsic_height = intrinsic_height.at_least(sized.intrinsic_size.y);
|
||||
|
||||
sized_items.insert(index, sized);
|
||||
}
|
||||
|
|
@ -259,7 +259,7 @@ impl<'a> AtomLayout<'a> {
|
|||
let mut response = ui.interact(rect, id, sense);
|
||||
|
||||
response.intrinsic_size =
|
||||
Some((Vec2::new(preferred_width, preferred_height) + margin.sum()).at_least(min_size));
|
||||
Some((Vec2::new(intrinsic_width, intrinsic_height) + margin.sum()).at_least(min_size));
|
||||
|
||||
AllocatedAtomLayout {
|
||||
sized_atoms: sized_items,
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ pub struct SizedAtom<'a> {
|
|||
/// size.x + gap.
|
||||
pub size: Vec2,
|
||||
|
||||
/// Preferred size of the atom. This is used to calculate `Response::intrinsic_size`.
|
||||
pub preferred_size: Vec2,
|
||||
/// Intrinsic size of the atom. This is used to calculate `Response::intrinsic_size`.
|
||||
pub intrinsic_size: Vec2,
|
||||
|
||||
pub kind: SizedAtomKind<'a>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,10 +130,12 @@ impl TextShape {
|
|||
num_vertices: _,
|
||||
num_indices: _,
|
||||
pixels_per_point: _,
|
||||
intrinsic_size,
|
||||
} = Arc::make_mut(galley);
|
||||
|
||||
*rect = transform.scaling * *rect;
|
||||
*mesh_bounds = transform.scaling * *mesh_bounds;
|
||||
*intrinsic_size = transform.scaling * *intrinsic_size;
|
||||
|
||||
for text::PlacedRow { pos, row } in rows {
|
||||
*pos *= transform.scaling;
|
||||
|
|
|
|||
|
|
@ -1072,6 +1072,7 @@ mod tests {
|
|||
use core::f32;
|
||||
|
||||
use super::*;
|
||||
use crate::text::{TextWrapping, layout};
|
||||
use crate::{Stroke, text::TextFormat};
|
||||
use ecolor::Color32;
|
||||
use emath::Align;
|
||||
|
|
@ -1183,4 +1184,60 @@ mod tests {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_intrinsic_size() {
|
||||
let pixels_per_point = [1.0, 1.3, 2.0, 0.867];
|
||||
let max_widths = [40.0, 80.0, 133.0, 200.0];
|
||||
let rounded_output_to_gui = [false, true];
|
||||
|
||||
for pixels_per_point in pixels_per_point {
|
||||
let mut fonts = FontsImpl::new(
|
||||
pixels_per_point,
|
||||
1024,
|
||||
AlphaFromCoverage::default(),
|
||||
FontDefinitions::default(),
|
||||
);
|
||||
|
||||
for &max_width in &max_widths {
|
||||
for round_output_to_gui in rounded_output_to_gui {
|
||||
for mut job in jobs() {
|
||||
job.wrap = TextWrapping::wrap_at_width(max_width);
|
||||
|
||||
job.round_output_to_gui = round_output_to_gui;
|
||||
|
||||
let galley_wrapped = layout(&mut fonts, job.clone().into());
|
||||
|
||||
job.wrap = TextWrapping::no_max_width();
|
||||
|
||||
let text = job.text.clone();
|
||||
let galley_unwrapped = layout(&mut fonts, job.into());
|
||||
|
||||
let intrinsic_size = galley_wrapped.intrinsic_size;
|
||||
let unwrapped_size = galley_unwrapped.size();
|
||||
|
||||
let difference = (intrinsic_size - unwrapped_size).length().abs();
|
||||
similar_asserts::assert_eq!(
|
||||
format!("{intrinsic_size:.4?}"),
|
||||
format!("{unwrapped_size:.4?}"),
|
||||
"Wrapped intrinsic size should almost match unwrapped size. Intrinsic: {intrinsic_size:.8?} vs unwrapped: {unwrapped_size:.8?}
|
||||
Difference: {difference:.8?}
|
||||
wrapped rows: {}, unwrapped rows: {}
|
||||
pixels_per_point: {pixels_per_point}, text: {text:?}, max_width: {max_width}, round_output_to_gui: {round_output_to_gui}",
|
||||
galley_wrapped.rows.len(),
|
||||
galley_unwrapped.rows.len()
|
||||
);
|
||||
similar_asserts::assert_eq!(
|
||||
format!("{intrinsic_size:.4?}"),
|
||||
format!("{unwrapped_size:.4?}"),
|
||||
"Unwrapped galley intrinsic size should exactly match its size. \
|
||||
{:.8?} vs {:8?}",
|
||||
galley_unwrapped.intrinsic_size,
|
||||
galley_unwrapped.size(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc<LayoutJob>) -> Galley {
|
|||
num_indices: 0,
|
||||
pixels_per_point: fonts.pixels_per_point(),
|
||||
elided: true,
|
||||
intrinsic_size: Vec2::ZERO,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -94,6 +95,8 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc<LayoutJob>) -> Galley {
|
|||
|
||||
let point_scale = PointScale::new(fonts.pixels_per_point());
|
||||
|
||||
let intrinsic_size = calculate_intrinsic_size(point_scale, &job, ¶graphs);
|
||||
|
||||
let mut elided = false;
|
||||
let mut rows = rows_from_paragraphs(paragraphs, &job, &mut elided);
|
||||
if elided {
|
||||
|
|
@ -124,7 +127,7 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc<LayoutJob>) -> Galley {
|
|||
}
|
||||
|
||||
// Calculate the Y positions and tessellate the text:
|
||||
galley_from_rows(point_scale, job, rows, elided)
|
||||
galley_from_rows(point_scale, job, rows, elided, intrinsic_size)
|
||||
}
|
||||
|
||||
// Ignores the Y coordinate.
|
||||
|
|
@ -190,6 +193,46 @@ fn layout_section(
|
|||
}
|
||||
}
|
||||
|
||||
/// Calculate the intrinsic size of the text.
|
||||
///
|
||||
/// The result is eventually passed to `Response::intrinsic_size`.
|
||||
/// This works by calculating the size of each `Paragraph` (instead of each `Row`).
|
||||
fn calculate_intrinsic_size(
|
||||
point_scale: PointScale,
|
||||
job: &LayoutJob,
|
||||
paragraphs: &[Paragraph],
|
||||
) -> Vec2 {
|
||||
let mut intrinsic_size = Vec2::ZERO;
|
||||
for (idx, paragraph) in paragraphs.iter().enumerate() {
|
||||
if paragraph.glyphs.is_empty() {
|
||||
if idx == 0 {
|
||||
intrinsic_size.y += point_scale.round_to_pixel(paragraph.empty_paragraph_height);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
intrinsic_size.x = f32::max(
|
||||
paragraph
|
||||
.glyphs
|
||||
.last()
|
||||
.map(|l| l.max_x())
|
||||
.unwrap_or_default(),
|
||||
intrinsic_size.x,
|
||||
);
|
||||
|
||||
let mut height = paragraph
|
||||
.glyphs
|
||||
.iter()
|
||||
.map(|g| g.line_height)
|
||||
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
|
||||
.unwrap_or(paragraph.empty_paragraph_height);
|
||||
if idx == 0 {
|
||||
height = f32::max(height, job.first_row_min_height);
|
||||
}
|
||||
intrinsic_size.y += point_scale.round_to_pixel(height);
|
||||
}
|
||||
intrinsic_size
|
||||
}
|
||||
|
||||
// Ignores the Y coordinate.
|
||||
fn rows_from_paragraphs(
|
||||
paragraphs: Vec<Paragraph>,
|
||||
|
|
@ -610,6 +653,7 @@ fn galley_from_rows(
|
|||
job: Arc<LayoutJob>,
|
||||
mut rows: Vec<PlacedRow>,
|
||||
elided: bool,
|
||||
intrinsic_size: Vec2,
|
||||
) -> Galley {
|
||||
let mut first_row_min_height = job.first_row_min_height;
|
||||
let mut cursor_y = 0.0;
|
||||
|
|
@ -680,6 +724,7 @@ fn galley_from_rows(
|
|||
num_vertices,
|
||||
num_indices,
|
||||
pixels_per_point: point_scale.pixels_per_point,
|
||||
intrinsic_size,
|
||||
};
|
||||
|
||||
if galley.job.round_output_to_gui {
|
||||
|
|
|
|||
|
|
@ -560,6 +560,12 @@ pub struct Galley {
|
|||
/// so that we can warn if this has changed once we get to
|
||||
/// tessellation.
|
||||
pub pixels_per_point: f32,
|
||||
|
||||
/// This is the size that a non-wrapped, non-truncated, non-justified version of the text
|
||||
/// would have.
|
||||
///
|
||||
/// Useful for advanced layouting.
|
||||
pub intrinsic_size: Vec2,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
|
@ -821,6 +827,8 @@ impl Galley {
|
|||
.at_most(rect.min.x + self.job.wrap.max_width)
|
||||
.floor_ui();
|
||||
}
|
||||
|
||||
self.intrinsic_size = self.intrinsic_size.round_ui();
|
||||
}
|
||||
|
||||
/// Append each galley under the previous one.
|
||||
|
|
@ -836,6 +844,7 @@ impl Galley {
|
|||
num_vertices: 0,
|
||||
num_indices: 0,
|
||||
pixels_per_point,
|
||||
intrinsic_size: Vec2::ZERO,
|
||||
};
|
||||
|
||||
for (i, galley) in galleys.iter().enumerate() {
|
||||
|
|
@ -872,6 +881,9 @@ impl Galley {
|
|||
// Note that if `galley.elided` is true this will be the last `Galley` in
|
||||
// the vector and the loop will end.
|
||||
merged_galley.elided |= galley.elided;
|
||||
merged_galley.intrinsic_size.x =
|
||||
f32::max(merged_galley.intrinsic_size.x, galley.intrinsic_size.x);
|
||||
merged_galley.intrinsic_size.y += galley.intrinsic_size.y;
|
||||
}
|
||||
|
||||
if merged_galley.job.round_output_to_gui {
|
||||
|
|
|
|||
|
|
@ -69,3 +69,35 @@ fn single_test(name: &str, mut f: impl FnMut(&mut Ui)) -> SnapshotResult {
|
|||
|
||||
harness.try_snapshot(name)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_intrinsic_size() {
|
||||
let mut intrinsic_size = None;
|
||||
for wrapping in [
|
||||
TextWrapMode::Extend,
|
||||
TextWrapMode::Wrap,
|
||||
TextWrapMode::Truncate,
|
||||
] {
|
||||
_ = HarnessBuilder::default()
|
||||
.with_size(Vec2::new(100.0, 100.0))
|
||||
.build_ui(|ui| {
|
||||
ui.style_mut().wrap_mode = Some(wrapping);
|
||||
let response = ui.add(Button::new(
|
||||
"Hello world this is a long text that should be wrapped.",
|
||||
));
|
||||
if let Some(current_intrinsic_size) = intrinsic_size {
|
||||
assert_eq!(
|
||||
Some(current_intrinsic_size),
|
||||
response.intrinsic_size,
|
||||
"For wrapping: {wrapping:?}"
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
response.intrinsic_size.is_some(),
|
||||
"intrinsic_size should be set for `Button`"
|
||||
);
|
||||
intrinsic_size = response.intrinsic_size;
|
||||
});
|
||||
}
|
||||
assert_eq!(intrinsic_size.unwrap().round(), Vec2::new(305.0, 18.0));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue