Merge pull request #9562 from penpot/elenatorro-fix-layout-freeze

🐛 Reflow only when needed
This commit is contained in:
Alejandro Alonso 2026-05-13 16:26:05 +02:00 committed by GitHub
commit 2685389aad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 69 additions and 22 deletions

View File

@ -167,6 +167,7 @@ fn set_pixel_precision(transform: &mut Matrix, bounds: &mut Bounds) {
}
}
#[allow(clippy::too_many_arguments)]
fn propagate_transform(
entry: TransformEntry,
pixel_precision: bool,
@ -175,6 +176,7 @@ fn propagate_transform(
bounds: &mut HashMap<Uuid, Bounds>,
modifiers: &mut HashMap<Uuid, Matrix>,
reflown: &mut HashSet<Uuid>,
reflowed_shapes: &mut HashSet<Uuid>,
) -> Result<()> {
let Some(shape) = state.shapes.get(&entry.id) else {
return Ok(());
@ -190,7 +192,11 @@ fn propagate_transform(
if !is_close_to(shape_bounds_before.width(), shape_bounds_after.width())
|| !is_close_to(shape_bounds_before.height(), shape_bounds_after.height())
{
if let Type::Text(text_content) = &mut shape.shape_type.clone() {
if let Type::Text(text_content) = &shape.shape_type {
let width_changed =
!is_close_to(shape_bounds_before.width(), shape_bounds_after.width());
let height_changed =
!is_close_to(shape_bounds_before.height(), shape_bounds_after.height());
let resized_selrect = math::Rect::from_xywh(
shape.selrect.left(),
shape.selrect.top(),
@ -199,12 +205,15 @@ fn propagate_transform(
);
match text_content.grow_type() {
GrowType::AutoHeight => {
// For auto-height, always update layout when width changes
// because the new width affects how text wraps
let width_changed =
!is_close_to(shape_bounds_before.width(), shape_bounds_after.width());
if width_changed || text_content.needs_update_layout() {
text_content.update_layout(resized_selrect);
let height_before = text_content.size.height;
let new_height = if width_changed {
let mut clone = text_content.clone();
clone.update_layout(resized_selrect);
clone.size.height
} else {
height_before
};
if !is_close_to(height_before, new_height) && reflowed_shapes.insert(shape.id) {
entries.push_back(Modifier::reflow(shape.id, false));
if let Some(parent_id) = shape.parent_id {
@ -215,31 +224,28 @@ fn propagate_transform(
}
}
}
let height = text_content.size.height;
let resize_transform = math::resize_matrix(
&shape_bounds_after,
&shape_bounds_after,
shape_bounds_after.width(),
height,
new_height,
);
shape_bounds_after = shape_bounds_after.transform(&resize_transform);
transform.post_concat(&resize_transform);
}
GrowType::AutoWidth => {
// For auto-width, always update layout when height changes
// because the new height affects how text flows
let height_changed =
!is_close_to(shape_bounds_before.height(), shape_bounds_after.height());
if height_changed || text_content.needs_update_layout() {
text_content.update_layout(resized_selrect);
}
let width = text_content.width();
let height = text_content.size.height;
let (new_width, new_height) = if height_changed {
let mut clone = text_content.clone();
clone.update_layout(resized_selrect);
(clone.width(), clone.size.height)
} else {
(text_content.width(), text_content.size.height)
};
let resize_transform = math::resize_matrix(
&shape_bounds_after,
&shape_bounds_after,
width,
height,
new_width,
new_height,
);
shape_bounds_after = shape_bounds_after.transform(&resize_transform);
transform.post_concat(&resize_transform);
@ -404,6 +410,7 @@ pub fn propagate_modifiers(
// In order for loop to eventualy finish, we limit the flex reflow to just
// one (the reflown set).
while !entries.is_empty() {
let mut reflowed_shapes = HashSet::<Uuid>::new();
while let Some(modifier) = entries.pop_front() {
match modifier {
Modifier::Transform(entry, pixel) => propagate_transform(
@ -414,6 +421,7 @@ pub fn propagate_modifiers(
&mut bounds,
&mut modifiers,
&mut reflown,
&mut reflowed_shapes,
)?,
Modifier::Reflow(id, force_reflow) => {
if force_reflow {

View File

@ -333,13 +333,26 @@ pub fn calculate_normalized_line_height(
normalized_line_height
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub struct TextContent {
pub paragraphs: Vec<Paragraph>,
pub bounds: Rect,
pub grow_type: GrowType,
pub size: TextContentSize,
pub layout: TextContentLayout,
content_version: u64,
layout_version: u64,
layout_width: Option<f32>,
}
impl PartialEq for TextContent {
fn eq(&self, other: &Self) -> bool {
self.paragraphs == other.paragraphs
&& self.bounds == other.bounds
&& self.grow_type == other.grow_type
&& self.size == other.size
&& self.layout == other.layout
}
}
impl TextContent {
@ -350,6 +363,9 @@ impl TextContent {
grow_type,
size: TextContentSize::default(),
layout: TextContentLayout::new(),
content_version: 0,
layout_version: 0,
layout_width: None,
}
}
@ -362,6 +378,9 @@ impl TextContent {
grow_type,
size: TextContentSize::new_with_size(bounds.width(), bounds.height()),
layout: TextContentLayout::new(),
content_version: 0,
layout_version: 0,
layout_width: None,
}
}
@ -385,6 +404,7 @@ impl TextContent {
pub fn add_paragraph(&mut self, paragraph: Paragraph) {
self.paragraphs.push(paragraph);
self.content_version = self.content_version.wrapping_add(1);
}
pub fn paragraphs(&self) -> &[Paragraph] {
@ -392,6 +412,7 @@ impl TextContent {
}
pub fn paragraphs_mut(&mut self) -> &mut Vec<Paragraph> {
self.content_version = self.content_version.wrapping_add(1);
&mut self.paragraphs
}
@ -408,7 +429,10 @@ impl TextContent {
}
pub fn set_grow_type(&mut self, grow_type: GrowType) {
self.grow_type = grow_type;
if self.grow_type != grow_type {
self.grow_type = grow_type;
self.content_version = self.content_version.wrapping_add(1);
}
}
/// Compute a tight text rect from laid-out Skia paragraphs using glyph
@ -891,6 +915,15 @@ impl TextContent {
}
pub fn update_layout(&mut self, selrect: Rect) -> TextContentSize {
if !self.layout.needs_update()
&& self.layout_version == self.content_version
&& self
.layout_width
.is_some_and(|w| (w - selrect.width()).abs() < f32::EPSILON)
{
return self.size;
}
self.size.set_size(selrect.width(), selrect.height());
match self.grow_type() {
@ -915,6 +948,9 @@ impl TextContent {
self.size.max_width = placeholder_width;
}
self.layout_version = self.content_version;
self.layout_width = Some(selrect.width());
self.size
}
@ -1048,6 +1084,9 @@ impl Default for TextContent {
grow_type: GrowType::Fixed,
size: TextContentSize::default(),
layout: TextContentLayout::new(),
content_version: 0,
layout_version: 0,
layout_width: None,
}
}
}