Add support for a measure function to the layout engine facade

Nathan Sobo created

Change summary

crates/gpui/playground/src/element.rs    |   7 
crates/gpui/playground/src/playground.rs |   3 
crates/gpui/playground/src/style.rs      | 175 ----------------------
crates/gpui/src/app/window.rs            |  59 +++++++
crates/gpui/src/geometry.rs              | 198 ++++++++++++++++++++++++++
5 files changed, 261 insertions(+), 181 deletions(-)

Detailed changes

crates/gpui/playground/src/element.rs 🔗

@@ -2,13 +2,14 @@ use std::{any::Any, rc::Rc};
 
 use crate::{
     adapter::Adapter,
-    style::{DefinedLength, Display, ElementStyle, Fill, Length, Overflow, Position},
+    style::{Display, ElementStyle, Fill, Overflow, Position},
 };
 use anyhow::Result;
 use derive_more::{Deref, DerefMut};
 use gpui::{
-    scene::MouseClick, EngineLayout, LayoutContext as LegacyLayoutContext,
-    PaintContext as LegacyPaintContext,
+    geometry::{DefinedLength, Length},
+    scene::MouseClick,
+    EngineLayout, LayoutContext as LegacyLayoutContext, PaintContext as LegacyPaintContext,
 };
 use playground_macros::tailwind_lengths;
 pub use taffy::tree::NodeId;

crates/gpui/playground/src/playground.rs 🔗

@@ -3,13 +3,12 @@ use components::button;
 use element::Element;
 use frame::frame;
 use gpui::{
-    geometry::{rect::RectF, vector::vec2f},
+    geometry::{percent, rect::RectF, vector::vec2f},
     platform::WindowOptions,
 };
 use log::LevelFilter;
 use simplelog::SimpleLogger;
 
-use style::percent;
 use themes::{rose_pine, ThemeColors};
 use view::view;
 

crates/gpui/playground/src/style.rs 🔗

@@ -1,4 +1,5 @@
 use crate::color::Hsla;
+use gpui::geometry::{DefinedLength, Edges, Length, Point, Size};
 pub use taffy::style::{
     AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
     Overflow, Position,
@@ -143,180 +144,6 @@ impl Default for ElementStyle {
     }
 }
 
-#[derive(Clone)]
-pub struct Point<T> {
-    pub x: T,
-    pub y: T,
-}
-
-impl<T> Into<taffy::geometry::Point<T>> for Point<T> {
-    fn into(self) -> taffy::geometry::Point<T> {
-        taffy::geometry::Point {
-            x: self.x,
-            y: self.y,
-        }
-    }
-}
-
-#[derive(Clone)]
-pub struct Size<T> {
-    pub width: T,
-    pub height: T,
-}
-
-impl Size<DefinedLength> {
-    pub const fn zero() -> Self {
-        Self {
-            width: DefinedLength::Pixels(0.),
-            height: DefinedLength::Pixels(0.),
-        }
-    }
-
-    pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Size<taffy::style::LengthPercentage> {
-        taffy::geometry::Size {
-            width: self.width.to_taffy(rem_size),
-            height: self.height.to_taffy(rem_size),
-        }
-    }
-}
-
-impl Size<Length> {
-    pub const fn auto() -> Self {
-        Self {
-            width: Length::Auto,
-            height: Length::Auto,
-        }
-    }
-
-    pub fn to_taffy<T: From<taffy::prelude::LengthPercentageAuto>>(
-        &self,
-        rem_size: f32,
-    ) -> taffy::geometry::Size<T> {
-        taffy::geometry::Size {
-            width: self.width.to_taffy(rem_size).into(),
-            height: self.height.to_taffy(rem_size).into(),
-        }
-    }
-}
-
-#[derive(Clone)]
-pub struct Edges<T> {
-    pub top: T,
-    pub right: T,
-    pub bottom: T,
-    pub left: T,
-}
-
-impl Edges<DefinedLength> {
-    pub const fn zero() -> Self {
-        Self {
-            top: DefinedLength::Pixels(0.0),
-            right: DefinedLength::Pixels(0.0),
-            bottom: DefinedLength::Pixels(0.0),
-            left: DefinedLength::Pixels(0.0),
-        }
-    }
-
-    pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
-        taffy::geometry::Rect {
-            top: self.top.to_taffy(rem_size),
-            right: self.right.to_taffy(rem_size),
-            bottom: self.bottom.to_taffy(rem_size),
-            left: self.left.to_taffy(rem_size),
-        }
-    }
-}
-
-impl Edges<Length> {
-    pub const fn auto() -> Self {
-        Self {
-            top: Length::Auto,
-            right: Length::Auto,
-            bottom: Length::Auto,
-            left: Length::Auto,
-        }
-    }
-
-    pub const fn zero() -> Self {
-        Self {
-            top: Length::Defined(DefinedLength::Pixels(0.0)),
-            right: Length::Defined(DefinedLength::Pixels(0.0)),
-            bottom: Length::Defined(DefinedLength::Pixels(0.0)),
-            left: Length::Defined(DefinedLength::Pixels(0.0)),
-        }
-    }
-
-    pub fn to_taffy(
-        &self,
-        rem_size: f32,
-    ) -> taffy::geometry::Rect<taffy::style::LengthPercentageAuto> {
-        taffy::geometry::Rect {
-            top: self.top.to_taffy(rem_size),
-            right: self.right.to_taffy(rem_size),
-            bottom: self.bottom.to_taffy(rem_size),
-            left: self.left.to_taffy(rem_size),
-        }
-    }
-}
-
-/// A non-auto length that can be defined in pixels, rems, or percent of parent.
-#[derive(Clone, Copy)]
-pub enum DefinedLength {
-    Pixels(f32),
-    Rems(f32),
-    Percent(f32), // 0. - 100.
-}
-
-impl DefinedLength {
-    fn to_taffy(&self, rem_size: f32) -> taffy::style::LengthPercentage {
-        match self {
-            DefinedLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(*pixels),
-            DefinedLength::Rems(rems) => taffy::style::LengthPercentage::Length(rems * rem_size),
-            DefinedLength::Percent(percent) => {
-                taffy::style::LengthPercentage::Percent(*percent / 100.)
-            }
-        }
-    }
-}
-
-/// A length that can be defined in pixels, rems, percent of parent, or auto.
-#[derive(Clone, Copy)]
-pub enum Length {
-    Defined(DefinedLength),
-    Auto,
-}
-
-pub fn auto() -> Length {
-    Length::Auto
-}
-
-pub fn percent(percent: f32) -> DefinedLength {
-    DefinedLength::Percent(percent)
-}
-
-pub fn rems(rems: f32) -> DefinedLength {
-    DefinedLength::Rems(rems)
-}
-
-pub fn pixels(pixels: f32) -> DefinedLength {
-    DefinedLength::Pixels(pixels)
-}
-
-impl Length {
-    fn to_taffy(&self, rem_size: f32) -> taffy::prelude::LengthPercentageAuto {
-        match self {
-            Length::Defined(length) => length.to_taffy(rem_size).into(),
-            Length::Auto => taffy::prelude::LengthPercentageAuto::Auto,
-        }
-    }
-}
-
-impl From<DefinedLength> for Length {
-    fn from(value: DefinedLength) -> Self {
-        Length::Defined(value)
-    }
-}
-
 #[derive(Clone)]
 pub enum Fill {
     Color(Hsla),

crates/gpui/src/app/window.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
     elements::AnyRootElement,
-    geometry::rect::RectF,
+    geometry::{rect::RectF, Size},
     json::ToJson,
     keymap_matcher::{Binding, KeymapContext, Keystroke, MatchResult},
     platform::{
@@ -33,7 +33,10 @@ use std::{
     mem,
     ops::{Deref, DerefMut, Range, Sub},
 };
-use taffy::Taffy;
+use taffy::{
+    tree::{Measurable, MeasureFunc},
+    Taffy,
+};
 use util::ResultExt;
 use uuid::Uuid;
 
@@ -1253,6 +1256,15 @@ impl LayoutEngine {
             .new_with_children(style, &children.into_iter().collect::<Vec<_>>())?)
     }
 
+    pub fn add_measured_node<F>(&mut self, style: LayoutStyle, measure: F) -> Result<LayoutNodeId>
+    where
+        F: Fn(MeasureParams) -> Size<f32> + Sync + Send + 'static,
+    {
+        Ok(self
+            .0
+            .new_leaf_with_measure(style, MeasureFunc::Boxed(Box::new(MeasureFn(measure))))?)
+    }
+
     pub fn compute_layout(&mut self, root: LayoutNodeId, available_space: Vector2F) -> Result<()> {
         self.0.compute_layout(
             root,
@@ -1269,11 +1281,54 @@ impl LayoutEngine {
     }
 }
 
+pub struct MeasureFn<F>(F);
+
+impl<F: Send + Sync> Measurable for MeasureFn<F>
+where
+    F: Fn(MeasureParams) -> Size<f32>,
+{
+    fn measure(
+        &self,
+        known_dimensions: taffy::prelude::Size<Option<f32>>,
+        available_space: taffy::prelude::Size<taffy::style::AvailableSpace>,
+    ) -> taffy::prelude::Size<f32> {
+        (self.0)(MeasureParams {
+            known_dimensions: known_dimensions.into(),
+            available_space: available_space.into(),
+        })
+        .into()
+    }
+}
+
 pub struct EngineLayout {
     pub bounds: RectF,
     pub order: u32,
 }
 
+pub struct MeasureParams {
+    pub known_dimensions: Size<Option<f32>>,
+    pub available_space: Size<AvailableSpace>,
+}
+
+pub enum AvailableSpace {
+    /// The amount of space available is the specified number of pixels
+    Pixels(f32),
+    /// The amount of space available is indefinite and the node should be laid out under a min-content constraint
+    MinContent,
+    /// The amount of space available is indefinite and the node should be laid out under a max-content constraint
+    MaxContent,
+}
+
+impl From<taffy::prelude::AvailableSpace> for AvailableSpace {
+    fn from(value: taffy::prelude::AvailableSpace) -> Self {
+        match value {
+            taffy::prelude::AvailableSpace::Definite(pixels) => Self::Pixels(pixels),
+            taffy::prelude::AvailableSpace::MinContent => Self::MinContent,
+            taffy::prelude::AvailableSpace::MaxContent => Self::MaxContent,
+        }
+    }
+}
+
 impl From<&taffy::tree::Layout> for EngineLayout {
     fn from(value: &taffy::tree::Layout) -> Self {
         Self {

crates/gpui/src/geometry.rs 🔗

@@ -131,3 +131,201 @@ impl ToJson for RectF {
         json!({"origin": self.origin().to_json(), "size": self.size().to_json()})
     }
 }
+
+#[derive(Clone)]
+pub struct Point<T> {
+    pub x: T,
+    pub y: T,
+}
+
+impl<T> Into<taffy::geometry::Point<T>> for Point<T> {
+    fn into(self) -> taffy::geometry::Point<T> {
+        taffy::geometry::Point {
+            x: self.x,
+            y: self.y,
+        }
+    }
+}
+
+#[derive(Clone)]
+pub struct Size<T> {
+    pub width: T,
+    pub height: T,
+}
+
+impl<S, T> From<taffy::geometry::Size<S>> for Size<T>
+where
+    S: Into<T>,
+{
+    fn from(value: taffy::geometry::Size<S>) -> Self {
+        Self {
+            width: value.width.into(),
+            height: value.height.into(),
+        }
+    }
+}
+
+impl<S, T> Into<taffy::geometry::Size<S>> for Size<T>
+where
+    T: Into<S>,
+{
+    fn into(self) -> taffy::geometry::Size<S> {
+        taffy::geometry::Size {
+            width: self.width.into(),
+            height: self.height.into(),
+        }
+    }
+}
+
+impl Size<DefinedLength> {
+    pub const fn zero() -> Self {
+        Self {
+            width: DefinedLength::Pixels(0.),
+            height: DefinedLength::Pixels(0.),
+        }
+    }
+
+    pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Size<taffy::style::LengthPercentage> {
+        taffy::geometry::Size {
+            width: self.width.to_taffy(rem_size),
+            height: self.height.to_taffy(rem_size),
+        }
+    }
+}
+
+impl Size<Length> {
+    pub const fn auto() -> Self {
+        Self {
+            width: Length::Auto,
+            height: Length::Auto,
+        }
+    }
+
+    pub fn to_taffy<T: From<taffy::prelude::LengthPercentageAuto>>(
+        &self,
+        rem_size: f32,
+    ) -> taffy::geometry::Size<T> {
+        taffy::geometry::Size {
+            width: self.width.to_taffy(rem_size).into(),
+            height: self.height.to_taffy(rem_size).into(),
+        }
+    }
+}
+
+#[derive(Clone)]
+pub struct Edges<T> {
+    pub top: T,
+    pub right: T,
+    pub bottom: T,
+    pub left: T,
+}
+
+impl Edges<DefinedLength> {
+    pub const fn zero() -> Self {
+        Self {
+            top: DefinedLength::Pixels(0.0),
+            right: DefinedLength::Pixels(0.0),
+            bottom: DefinedLength::Pixels(0.0),
+            left: DefinedLength::Pixels(0.0),
+        }
+    }
+
+    pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
+        taffy::geometry::Rect {
+            top: self.top.to_taffy(rem_size),
+            right: self.right.to_taffy(rem_size),
+            bottom: self.bottom.to_taffy(rem_size),
+            left: self.left.to_taffy(rem_size),
+        }
+    }
+}
+
+impl Edges<Length> {
+    pub const fn auto() -> Self {
+        Self {
+            top: Length::Auto,
+            right: Length::Auto,
+            bottom: Length::Auto,
+            left: Length::Auto,
+        }
+    }
+
+    pub const fn zero() -> Self {
+        Self {
+            top: Length::Defined(DefinedLength::Pixels(0.0)),
+            right: Length::Defined(DefinedLength::Pixels(0.0)),
+            bottom: Length::Defined(DefinedLength::Pixels(0.0)),
+            left: Length::Defined(DefinedLength::Pixels(0.0)),
+        }
+    }
+
+    pub fn to_taffy(
+        &self,
+        rem_size: f32,
+    ) -> taffy::geometry::Rect<taffy::style::LengthPercentageAuto> {
+        taffy::geometry::Rect {
+            top: self.top.to_taffy(rem_size),
+            right: self.right.to_taffy(rem_size),
+            bottom: self.bottom.to_taffy(rem_size),
+            left: self.left.to_taffy(rem_size),
+        }
+    }
+}
+
+/// A non-auto length that can be defined in pixels, rems, or percent of parent.
+#[derive(Clone, Copy)]
+pub enum DefinedLength {
+    Pixels(f32),
+    Rems(f32),
+    Percent(f32), // 0. - 100.
+}
+
+impl DefinedLength {
+    fn to_taffy(&self, rem_size: f32) -> taffy::style::LengthPercentage {
+        match self {
+            DefinedLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(*pixels),
+            DefinedLength::Rems(rems) => taffy::style::LengthPercentage::Length(rems * rem_size),
+            DefinedLength::Percent(percent) => {
+                taffy::style::LengthPercentage::Percent(*percent / 100.)
+            }
+        }
+    }
+}
+
+/// A length that can be defined in pixels, rems, percent of parent, or auto.
+#[derive(Clone, Copy)]
+pub enum Length {
+    Defined(DefinedLength),
+    Auto,
+}
+
+pub fn auto() -> Length {
+    Length::Auto
+}
+
+pub fn percent(percent: f32) -> DefinedLength {
+    DefinedLength::Percent(percent)
+}
+
+pub fn rems(rems: f32) -> DefinedLength {
+    DefinedLength::Rems(rems)
+}
+
+pub fn pixels(pixels: f32) -> DefinedLength {
+    DefinedLength::Pixels(pixels)
+}
+
+impl Length {
+    pub fn to_taffy(&self, rem_size: f32) -> taffy::prelude::LengthPercentageAuto {
+        match self {
+            Length::Defined(length) => length.to_taffy(rem_size).into(),
+            Length::Auto => taffy::prelude::LengthPercentageAuto::Auto,
+        }
+    }
+}
+
+impl From<DefinedLength> for Length {
+    fn from(value: DefinedLength) -> Self {
+        Length::Defined(value)
+    }
+}