Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Intrinsic Sizing #192

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion crates/yakui-core/src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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());
Expand Down
29 changes: 29 additions & 0 deletions crates/yakui-core/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
}
45 changes: 44 additions & 1 deletion crates/yakui-core/src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -207,6 +246,10 @@ where
<T as Widget>::layout(self, ctx, constraints)
}

fn intrinsic_size(&self, ctx: IntrinsicSizeContext<'_>, axis: Axis, extent: f32) -> f32 {
<T as Widget>::intrinsic_size(self, ctx, axis, extent)
}

fn flex(&self) -> (u32, FlexFit) {
<T as Widget>::flex(self)
}
Expand Down
15 changes: 13 additions & 2 deletions crates/yakui-widgets/src/widgets/colored_box.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -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();
Expand Down
41 changes: 39 additions & 2 deletions crates/yakui-widgets/src/widgets/list.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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
}
}
}
Loading