Skip to content

Commit

Permalink
Fix text wrapping when a child of a v_stack() (#3362)
Browse files Browse the repository at this point in the history
Previously text that was rendered in a flex-column would reserve the
correct
amount of space during layout, and then paint itself incorrectly.

Release Notes:

- N/A
  • Loading branch information
ConradIrwin authored Nov 19, 2023
2 parents b67193e + c5738a5 commit c0d85dc
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 41 deletions.
23 changes: 20 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/gpui2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ serde_derive.workspace = true
serde_json.workspace = true
smallvec.workspace = true
smol.workspace = true
taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e" }
taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "1876f72bee5e376023eaa518aa7b8a34c769bd1b" }
thiserror.workspace = true
time.workspace = true
tiny-skia = "0.5"
Expand Down
30 changes: 25 additions & 5 deletions crates/gpui2/src/elements/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,34 @@ impl<V: 'static> Element<V> for Text {

let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
let element_state = element_state.clone();
move |known_dimensions, _| {
move |known_dimensions, available_space| {
let wrap_width = known_dimensions.width.or(match available_space.width {
crate::AvailableSpace::Definite(x) => Some(x),
_ => None,
});

if let Some(text_state) = element_state.0.lock().as_ref() {
if text_state.size.is_some()
&& (wrap_width.is_none() || wrap_width == text_state.wrap_width)
{
return text_state.size.unwrap();
}
}

let Some(lines) = text_system
.shape_text(
&text,
font_size,
&runs[..],
known_dimensions.width, // Wrap if we know the width.
wrap_width, // Wrap if we know the width.
)
.log_err()
else {
element_state.lock().replace(TextStateInner {
lines: Default::default(),
line_height,
wrap_width,
size: Some(Size::default()),
});
return Size::default();
};
Expand All @@ -88,9 +103,12 @@ impl<V: 'static> Element<V> for Text {
size.width = size.width.max(line_size.width);
}

element_state
.lock()
.replace(TextStateInner { lines, line_height });
element_state.lock().replace(TextStateInner {
lines,
line_height,
wrap_width,
size: Some(size),
});

size
}
Expand Down Expand Up @@ -133,6 +151,8 @@ impl TextState {
struct TextStateInner {
lines: SmallVec<[WrappedLine; 1]>,
line_height: Pixels,
wrap_width: Option<Pixels>,
size: Option<Size<Pixels>>,
}

struct InteractiveText {
Expand Down
46 changes: 22 additions & 24 deletions crates/gpui2/src/taffy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ use std::fmt::Debug;
use taffy::{
geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize},
style::AvailableSpace as TaffyAvailableSpace,
tree::{Measurable, MeasureFunc, NodeId},
tree::NodeId,
Taffy,
};

type Measureable = dyn Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels> + Send + Sync;

pub struct TaffyLayoutEngine {
taffy: Taffy,
taffy: Taffy<Box<Measureable>>,
children_to_parents: HashMap<LayoutId, LayoutId>,
absolute_layout_bounds: HashMap<LayoutId, Bounds<Pixels>>,
computed_layouts: HashSet<LayoutId>,
Expand Down Expand Up @@ -70,9 +72,9 @@ impl TaffyLayoutEngine {
) -> LayoutId {
let style = style.to_taffy(rem_size);

let measurable = Box::new(Measureable(measure)) as Box<dyn Measurable>;
let measurable = Box::new(measure);
self.taffy
.new_leaf_with_measure(style, MeasureFunc::Boxed(measurable))
.new_leaf_with_context(style, measurable)
.expect(EXPECT_MESSAGE)
.into()
}
Expand Down Expand Up @@ -154,7 +156,22 @@ impl TaffyLayoutEngine {

// let started_at = std::time::Instant::now();
self.taffy
.compute_layout(id.into(), available_space.into())
.compute_layout_with_measure(
id.into(),
available_space.into(),
|known_dimensions, available_space, _node_id, context| {
let Some(measure) = context else {
return taffy::geometry::Size::default();
};

let known_dimensions = Size {
width: known_dimensions.width.map(Pixels),
height: known_dimensions.height.map(Pixels),
};

measure(known_dimensions, available_space.into()).into()
},
)
.expect(EXPECT_MESSAGE);
// println!("compute_layout took {:?}", started_at.elapsed());
}
Expand Down Expand Up @@ -202,25 +219,6 @@ impl From<LayoutId> for NodeId {
}
}

struct Measureable<F>(F);

impl<F> taffy::tree::Measurable for Measureable<F>
where
F: Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels> + Send + Sync,
{
fn measure(
&self,
known_dimensions: TaffySize<Option<f32>>,
available_space: TaffySize<TaffyAvailableSpace>,
) -> TaffySize<f32> {
let known_dimensions: Size<Option<f32>> = known_dimensions.into();
let known_dimensions: Size<Option<Pixels>> = known_dimensions.map(|d| d.map(Into::into));
let available_space = available_space.into();
let size = (self.0)(known_dimensions, available_space);
size.into()
}
}

trait ToTaffy<Output> {
fn to_taffy(&self, rem_size: Pixels) -> Output;
}
Expand Down
51 changes: 45 additions & 6 deletions crates/storybook2/src/stories/text.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use gpui::{div, white, Div, ParentComponent, Render, Styled, View, VisualContext, WindowContext};
use gpui::{
blue, div, red, white, Div, ParentComponent, Render, Styled, View, VisualContext, WindowContext,
};
use ui::v_stack;

pub struct TextStory;

Expand All @@ -12,10 +15,46 @@ impl Render for TextStory {
type Element = Div<Self>;

fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
div().size_full().bg(white()).child(concat!(
"The quick brown fox jumps over the lazy dog. ",
"Meanwhile, the lazy dog decided it was time for a change. ",
"He started daily workout routines, ate healthier and became the fastest dog in town.",
))
v_stack()
.bg(blue())
.child(
div()
.flex()
.child(div().max_w_96().bg(white()).child(concat!(
"max-width: 96. The quick brown fox jumps over the lazy dog. ",
"Meanwhile, the lazy dog decided it was time for a change. ",
"He started daily workout routines, ate healthier and became the fastest dog in town.",
))),
)
.child(div().h_5())
.child(div().flex().flex_col().w_96().bg(white()).child(concat!(
"flex-col. width: 96; The quick brown fox jumps over the lazy dog. ",
"Meanwhile, the lazy dog decided it was time for a change. ",
"He started daily workout routines, ate healthier and became the fastest dog in town.",
)))
.child(div().h_5())
.child(
div()
.flex()
.child(div().min_w_96().bg(white()).child(concat!(
"min-width: 96. The quick brown fox jumps over the lazy dog. ",
"Meanwhile, the lazy dog decided it was time for a change. ",
"He started daily workout routines, ate healthier and became the fastest dog in town.",
))))
.child(div().h_5())
.child(div().flex().w_96().bg(white()).child(div().overflow_hidden().child(concat!(
"flex-row. width 96. overflow-hidden. The quick brown fox jumps over the lazy dog. ",
"Meanwhile, the lazy dog decided it was time for a change. ",
"He started daily workout routines, ate healthier and became the fastest dog in town.",
))))
// NOTE: When rendering text in a horizonal flex container,
// Taffy will not pass width constraints down from the parent.
// To fix this, render text in a praent with overflow: hidden, which
.child(div().h_5())
.child(div().flex().w_96().bg(red()).child(concat!(
"flex-row. width 96. The quick brown fox jumps over the lazy dog. ",
"Meanwhile, the lazy dog decided it was time for a change. ",
"He started daily workout routines, ate healthier and became the fastest dog in town.",
)))
}
}
18 changes: 16 additions & 2 deletions crates/storybook3/src/storybook3.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use anyhow::Result;
use gpui::AssetSource;
use gpui::{
div, px, size, AnyView, Bounds, Div, Render, ViewContext, VisualContext, WindowBounds,
WindowOptions,
};
use gpui::{white, AssetSource};
use settings::{default_settings, Settings, SettingsStore};
use std::borrow::Cow;
use std::sync::Arc;
Expand Down Expand Up @@ -56,6 +56,7 @@ fn main() {
}

struct TestView {
#[allow(unused)]
story: AnyView,
}

Expand All @@ -65,9 +66,22 @@ impl Render for TestView {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
div()
.flex()
.bg(gpui::blue())
.flex_col()
.size_full()
.font("Helvetica")
.child(self.story.clone())
.child(div().h_5())
.child(
div()
.flex()
.w_96()
.bg(white())
.relative()
.child(div().child(concat!(
"The quick brown fox jumps over the lazy dog. ",
"Meanwhile, the lazy dog decided it was time for a change. ",
"He started daily workout routines, ate healthier and became the fastest dog in town.",
))),
)
}
}

0 comments on commit c0d85dc

Please sign in to comment.