diff --git a/crates/yakui-core/src/layout/mod.rs b/crates/yakui-core/src/layout/mod.rs index 30e4df14..caa19863 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,18 @@ 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, extent: f32) -> f32 { + dom.enter(id); + let dom_node = dom.get(id).unwrap(); + + let context = IntrinsicSizeContext { dom, layout: self }; + let size = dom_node.widget.intrinsic_size(context, axis, extent); + + 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..1bfda033 100644 --- a/crates/yakui-core/src/types.rs +++ b/crates/yakui-core/src/types.rs @@ -256,3 +256,32 @@ 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, +} + +impl Axis { + /// Return the component of a [`Vec2`] that matches this axis. + pub fn select(self, v: Vec2) -> f32 { + match self { + Axis::X => v.x, + Axis::Y => v.y, + } + } + + /// Tells whether this axis indicates the same axis as a given [`Direction`]. + pub fn is_direction(self, dir: Direction) -> bool { + match (self, dir) { + (Axis::X, Direction::Right) => true, + (Axis::Y, Direction::Down) => true, + + (Axis::X, Direction::Down) => false, + (Axis::Y, Direction::Right) => false, + } + } +} diff --git a/crates/yakui-core/src/widget.rs b/crates/yakui-core/src/widget.rs index a601e692..6da40d73 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,26 @@ impl<'dom> LayoutContext<'dom> { self.layout .calculate(self.dom, self.input, widget, constraints) } + + /// Calculates the intrinsic size for the given widget on the given axis. + pub fn intrinsic_size(&self, widget: WidgetId, axis: Axis, extent: f32) -> f32 { + self.layout.intrinsic_size(self.dom, widget, axis, extent) + } +} + +/// 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> { + /// Calculates the intrinsic size for the given widget on the given axis. + pub fn intrinsic_size(&self, widget: WidgetId, axis: Axis, extent: f32) -> f32 { + self.layout.intrinsic_size(self.dom, widget, axis, extent) + } } /// Information available to a widget during the paint phase. @@ -134,6 +155,21 @@ pub trait Widget: 'static + fmt::Debug { constraints.constrain_min(size) } + /// Tells the intrinsic size on one axis of the object, which is its size + /// along that axis if the widget is provided the given `extent` as the max + /// size along the other axis. + fn intrinsic_size(&self, ctx: IntrinsicSizeContext<'_>, axis: Axis, extent: f32) -> f32 { + let node = ctx.dom.get_current(); + let mut size: f32 = 0.0; + + for &child in &node.children { + let child_size = ctx.intrinsic_size(child, axis, extent); + size = size.max(child_size); + } + + size + } + /// Paint the widget based on its current state. /// /// The default implementation will paint all of the widget's children. @@ -180,6 +216,9 @@ pub trait ErasedWidget: Any + fmt::Debug { /// See [`Widget::layout`]. fn layout(&self, ctx: LayoutContext<'_>, constraints: Constraints) -> Vec2; + /// See [`Widget::intrinsic_size`]. + fn intrinsic_size(&self, ctx: IntrinsicSizeContext<'_>, axis: Axis, extent: f32) -> f32; + /// See [`Widget::flex`]. fn flex(&self) -> (u32, FlexFit); @@ -207,6 +246,10 @@ where ::layout(self, ctx, constraints) } + fn intrinsic_size(&self, ctx: IntrinsicSizeContext<'_>, axis: Axis, extent: f32) -> f32 { + ::intrinsic_size(self, ctx, axis, extent) + } + fn flex(&self) -> (u32, FlexFit) { ::flex(self) } diff --git a/crates/yakui-widgets/src/widgets/colored_box.rs b/crates/yakui-widgets/src/widgets/colored_box.rs index efd65cee..69ff423b 100644 --- a/crates/yakui-widgets/src/widgets/colored_box.rs +++ b/crates/yakui-widgets/src/widgets/colored_box.rs @@ -1,7 +1,7 @@ use yakui_core::geometry::{Color, Constraints, Vec2}; use yakui_core::paint::PaintRect; -use yakui_core::widget::{LayoutContext, PaintContext, Widget}; -use yakui_core::Response; +use yakui_core::widget::{IntrinsicSizeContext, LayoutContext, PaintContext, Widget}; +use yakui_core::{Axis, Response}; use crate::util::{widget, widget_children}; @@ -82,6 +82,17 @@ impl Widget for ColoredBoxWidget { input.constrain_min(size) } + fn intrinsic_size(&self, ctx: IntrinsicSizeContext<'_>, axis: Axis, extent: f32) -> f32 { + let node = ctx.dom.get_current(); + let mut size = axis.select(self.props.min_size); + + for &child in &node.children { + size = size.max(ctx.intrinsic_size(child, axis, extent)); + } + + size + } + fn paint(&self, mut ctx: PaintContext<'_>) { let node = ctx.dom.get_current(); let layout_node = ctx.layout.get(ctx.dom.current()).unwrap(); diff --git a/crates/yakui-widgets/src/widgets/list.rs b/crates/yakui-widgets/src/widgets/list.rs index 8cb9b935..7181e6e3 100644 --- a/crates/yakui-widgets/src/widgets/list.rs +++ b/crates/yakui-widgets/src/widgets/list.rs @@ -1,6 +1,8 @@ use yakui_core::geometry::{Constraints, FlexFit, Vec2}; -use yakui_core::widget::{LayoutContext, Widget}; -use yakui_core::{CrossAxisAlignment, Direction, Flow, MainAxisAlignment, MainAxisSize, Response}; +use yakui_core::widget::{IntrinsicSizeContext, LayoutContext, Widget}; +use yakui_core::{ + Axis, CrossAxisAlignment, Direction, Flow, MainAxisAlignment, MainAxisSize, Response, +}; use crate::util::widget_children; @@ -285,4 +287,39 @@ impl Widget for ListWidget { container_size } + + fn intrinsic_size(&self, ctx: IntrinsicSizeContext<'_>, axis: Axis, extent: f32) -> f32 { + let node = ctx.dom.get_current(); + + if axis.is_direction(self.props.direction) { + // main axis layout + let mut total_flex = 0.0; + let mut inflexible_space = 0.0; + + for &child_id in &node.children { + let child_size = ctx.intrinsic_size(child_id, axis, extent); + let child = ctx.dom.get(child_id).unwrap(); + + let (flex, _) = child.widget.flex(); + + if flex > 0 { + // TODO + } else { + inflexible_space += child_size; + } + } + + inflexible_space + } else { + // cross axis layout + let mut max_size: f32 = 0.0; + + for &child_id in &node.children { + let child_size = ctx.intrinsic_size(child_id, axis, extent); + max_size = max_size.max(child_size); + } + + max_size + } + } }