diff --git a/crates/yakui-core/src/layout/mod.rs b/crates/yakui-core/src/layout/mod.rs index 30e4df14..7d4b415e 100644 --- a/crates/yakui-core/src/layout/mod.rs +++ b/crates/yakui-core/src/layout/mod.rs @@ -10,7 +10,8 @@ use crate::event::EventInterest; use crate::geometry::{Constraints, Rect}; use crate::id::WidgetId; use crate::input::{InputState, MouseInterest}; -use crate::widget::LayoutContext; +use crate::types::Axis; +use crate::widget::{IntrinsicSizeContext, LayoutContext}; /// Contains information on how each widget in the DOM is laid out and what /// events they're interested in. @@ -199,6 +200,22 @@ impl LayoutDom { size } + /// Calculates the intrinsic size of the given widget along the given axis. + pub fn intrinsic_size(&self, dom: &Dom, id: WidgetId, axis: Axis) -> f32 { + dom.enter(id); + let dom_node = dom.get(id).unwrap(); + + let context = IntrinsicSizeContext { dom, layout: self }; + + let size = match axis { + Axis::X => dom_node.widget.intrinsic_width(context), + Axis::Y => dom_node.widget.intrinsic_height(context), + }; + + dom.exit(id); + size + } + /// Enables clipping for the currently active widget. pub fn enable_clipping(&mut self, dom: &Dom) { self.clip_stack.push(dom.current()); diff --git a/crates/yakui-core/src/types.rs b/crates/yakui-core/src/types.rs index c6cd9591..e5ce3c8a 100644 --- a/crates/yakui-core/src/types.rs +++ b/crates/yakui-core/src/types.rs @@ -256,3 +256,11 @@ impl Pivot { pub const BOTTOM_CENTER: Self = Self::new(0.5, 1.0); pub const BOTTOM_RIGHT: Self = Self::new(1.0, 1.0); } + +/// Defines an axis usable by the UI. +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Axis { + X, + Y, +} diff --git a/crates/yakui-core/src/widget.rs b/crates/yakui-core/src/widget.rs index a601e692..07da437d 100644 --- a/crates/yakui-core/src/widget.rs +++ b/crates/yakui-core/src/widget.rs @@ -9,10 +9,11 @@ use crate::dom::Dom; use crate::event::EventResponse; use crate::event::{EventInterest, WidgetEvent}; use crate::geometry::{Constraints, FlexFit}; +use crate::id::WidgetId; use crate::input::{InputState, NavDirection}; use crate::layout::LayoutDom; use crate::paint::PaintDom; -use crate::{Flow, WidgetId}; +use crate::types::{Axis, Flow}; /// Trait that's automatically implemented for all widget props. /// @@ -38,6 +39,36 @@ impl<'dom> LayoutContext<'dom> { self.layout .calculate(self.dom, self.input, widget, constraints) } + + /// Calculate the intrinsic width for the given widget. + pub fn intrinsic_width(&mut self, widget: WidgetId) -> f32 { + self.layout.intrinsic_size(self.dom, widget, Axis::X) + } + + /// Calculate the intrinsic height for the given widget. + pub fn intrinsic_height(&mut self, widget: WidgetId) -> f32 { + self.layout.intrinsic_size(self.dom, widget, Axis::Y) + } +} + +/// Information available to a widget during the layout phase. +#[non_exhaustive] +#[allow(missing_docs)] +pub struct IntrinsicSizeContext<'dom> { + pub dom: &'dom Dom, + pub layout: &'dom LayoutDom, +} + +impl<'dom> IntrinsicSizeContext<'dom> { + /// Calculate the intrinsic width for the given widget. + pub fn intrinsic_width(&self, widget: WidgetId) -> f32 { + self.layout.intrinsic_size(self.dom, widget, Axis::X) + } + + /// Calculate the intrinsic height for the given widget. + pub fn intrinsic_height(&self, widget: WidgetId) -> f32 { + self.layout.intrinsic_size(self.dom, widget, Axis::Y) + } } /// Information available to a widget during the paint phase. @@ -134,6 +165,34 @@ pub trait Widget: 'static + fmt::Debug { constraints.constrain_min(size) } + /// Tells the intrinsic width of the object, which is its width if the + /// widget were given unbounded constraints. + fn intrinsic_width(&self, ctx: IntrinsicSizeContext<'_>) -> f32 { + let node = ctx.dom.get_current(); + let mut width: f32 = 0.0; + + for &child in &node.children { + let child_width = ctx.intrinsic_width(child); + width = width.max(child_width); + } + + width + } + + /// Tells the intrinsic height of the object, which is its height if the + /// widget were given unbounded constraints. + fn intrinsic_height(&self, ctx: IntrinsicSizeContext<'_>) -> f32 { + let node = ctx.dom.get_current(); + let mut height: f32 = 0.0; + + for &child in &node.children { + let child_height = ctx.intrinsic_height(child); + height = height.max(child_height); + } + + height + } + /// Paint the widget based on its current state. /// /// The default implementation will paint all of the widget's children. @@ -180,6 +239,12 @@ pub trait ErasedWidget: Any + fmt::Debug { /// See [`Widget::layout`]. fn layout(&self, ctx: LayoutContext<'_>, constraints: Constraints) -> Vec2; + /// See [`Widget::intrinsic_width`]. + fn intrinsic_width(&self, ctx: IntrinsicSizeContext<'_>) -> f32; + + /// See [`Widget::intrinsic_height`]. + fn intrinsic_height(&self, ctx: IntrinsicSizeContext<'_>) -> f32; + /// See [`Widget::flex`]. fn flex(&self) -> (u32, FlexFit); @@ -207,6 +272,14 @@ where ::layout(self, ctx, constraints) } + fn intrinsic_width(&self, ctx: IntrinsicSizeContext<'_>) -> f32 { + ::intrinsic_width(self, ctx) + } + + fn intrinsic_height(&self, ctx: IntrinsicSizeContext<'_>) -> f32 { + ::intrinsic_height(self, ctx) + } + fn flex(&self) -> (u32, FlexFit) { ::flex(self) }