diff --git a/crates/yakui-core/src/types.rs b/crates/yakui-core/src/types.rs index c91ec574..c6cd9591 100644 --- a/crates/yakui-core/src/types.rs +++ b/crates/yakui-core/src/types.rs @@ -219,3 +219,40 @@ impl Alignment { pub const BOTTOM_CENTER: Self = Self::new(0.5, 1.0); pub const BOTTOM_RIGHT: Self = Self::new(1.0, 1.0); } + +/// Defines a reference point for a widget. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Pivot { + x: f32, + y: f32, +} + +impl Pivot { + /// Create a new `Pivot` given an anchor point. + /// + /// `0.0, 0.0` is the top left corner of the widget, while `1.0, 1.0` is + /// the bottom right corner. + pub const fn new(x: f32, y: f32) -> Self { + Self { x, y } + } + + /// Returns the point for a pivot value. + pub const fn as_vec2(&self) -> Vec2 { + Vec2::new(self.x, self.y) + } +} + +#[allow(missing_docs)] +impl Pivot { + pub const TOP_LEFT: Self = Self::new(0.0, 0.0); + pub const TOP_CENTER: Self = Self::new(0.5, 0.0); + pub const TOP_RIGHT: Self = Self::new(1.0, 0.0); + + pub const CENTER_LEFT: Self = Self::new(0.0, 0.5); + pub const CENTER: Self = Self::new(0.5, 0.5); + pub const CENTER_RIGHT: Self = Self::new(1.0, 0.5); + + pub const BOTTOM_LEFT: Self = Self::new(0.0, 1.0); + pub const BOTTOM_CENTER: Self = Self::new(0.5, 1.0); + pub const BOTTOM_RIGHT: Self = Self::new(1.0, 1.0); +} diff --git a/crates/yakui-widgets/src/shorthand.rs b/crates/yakui-widgets/src/shorthand.rs index 03466d55..acc1ccc1 100644 --- a/crates/yakui-widgets/src/shorthand.rs +++ b/crates/yakui-widgets/src/shorthand.rs @@ -7,7 +7,7 @@ use std::borrow::Cow; use yakui_core::geometry::{Color, Constraints, Dim2, Vec2}; use yakui_core::widget::PaintContext; -use yakui_core::{Alignment, ManagedTextureId, Response, TextureId}; +use yakui_core::{Alignment, ManagedTextureId, Pivot, Response, TextureId}; use crate::widgets::{ Align, AlignResponse, Button, ButtonResponse, Canvas, CanvasResponse, Checkbox, @@ -170,10 +170,11 @@ pub fn slider(value: f64, min: f64, max: f64) -> Response { /// See [Reflow]. pub fn reflow( anchor: Alignment, + pivot: Pivot, offset: Dim2, children: impl FnOnce(), ) -> Response { - Reflow::new(anchor, offset).show(children) + Reflow::new(anchor, pivot, offset).show(children) } /// See [Opaque]. diff --git a/crates/yakui-widgets/src/widgets/reflow.rs b/crates/yakui-widgets/src/widgets/reflow.rs index d03ffb39..f29af5fd 100644 --- a/crates/yakui-widgets/src/widgets/reflow.rs +++ b/crates/yakui-widgets/src/widgets/reflow.rs @@ -1,6 +1,6 @@ use yakui_core::geometry::{Constraints, Dim2, Vec2}; use yakui_core::widget::{LayoutContext, Widget}; -use yakui_core::{Alignment, Flow, Response}; +use yakui_core::{Alignment, Flow, Pivot, Response}; use crate::util::widget_children; @@ -12,12 +12,17 @@ or table layouts. #[non_exhaustive] pub struct Reflow { pub anchor: Alignment, + pub pivot: Pivot, pub offset: Dim2, } impl Reflow { - pub fn new(anchor: Alignment, offset: Dim2) -> Self { - Self { anchor, offset } + pub fn new(anchor: Alignment, pivot: Pivot, offset: Dim2) -> Self { + Self { + anchor, + pivot, + offset, + } } pub fn show(self, children: F) -> Response { @@ -40,6 +45,7 @@ impl Widget for ReflowWidget { Self { props: Reflow { anchor: Alignment::TOP_LEFT, + pivot: Pivot::TOP_LEFT, offset: Dim2::ZERO, }, } @@ -58,8 +64,14 @@ impl Widget for ReflowWidget { fn layout(&self, mut ctx: LayoutContext<'_>, _constraints: Constraints) -> Vec2 { let node = ctx.dom.get_current(); + let mut size = Vec2::ZERO; + for &child in &node.children { + size = size.max(ctx.calculate_layout(child, Constraints::none())); + } + + let pivot_offset = -size * self.props.pivot.as_vec2(); for &child in &node.children { - ctx.calculate_layout(child, Constraints::none()); + ctx.layout.set_pos(child, pivot_offset); } Vec2::ZERO diff --git a/crates/yakui-widgets/tests/snapshot.rs b/crates/yakui-widgets/tests/snapshot.rs index 256f857d..c23ed3de 100644 --- a/crates/yakui-widgets/tests/snapshot.rs +++ b/crates/yakui-widgets/tests/snapshot.rs @@ -1,6 +1,6 @@ use yakui::{Constraints, CrossAxisAlignment, Dim2, MainAxisAlignment, MainAxisSize, Vec2}; use yakui_core::geometry::Color; -use yakui_core::Alignment; +use yakui_core::{Alignment, Pivot}; use yakui_test::{run, Test}; use yakui_widgets::widgets::{Button, List, Pad, UnconstrainedBox}; use yakui_widgets::{ @@ -351,13 +351,18 @@ fn row_reflow() { colored_box(Color::RED, [50.0, 50.0]); colored_box(Color::GREEN, [50.0, 50.0]); - reflow(Alignment::BOTTOM_RIGHT, Dim2::ZERO, || { + reflow(Alignment::BOTTOM_RIGHT, Pivot::TOP_LEFT, Dim2::ZERO, || { colored_box(Color::BLUE, [100.0, 50.0]); }); - reflow(Alignment::BOTTOM_RIGHT, Dim2::pixels(0.0, 50.0), || { - colored_box(Color::WHITE, [100.0, 100.0]); - }); + reflow( + Alignment::BOTTOM_RIGHT, + Pivot::TOP_LEFT, + Dim2::pixels(0.0, 50.0), + || { + colored_box(Color::WHITE, [100.0, 100.0]); + }, + ); }); }); }); diff --git a/crates/yakui/examples/dropdown.rs b/crates/yakui/examples/dropdown.rs index 80d69338..09ef7ad1 100644 --- a/crates/yakui/examples/dropdown.rs +++ b/crates/yakui/examples/dropdown.rs @@ -5,6 +5,7 @@ use yakui::widgets::Layer; use yakui::{align, button, column, reflow, use_state, widgets::Pad, Alignment, Dim2}; +use yakui_core::Pivot; pub fn run() { let open = use_state(|| false); @@ -25,7 +26,7 @@ pub fn run() { if open.get() { Pad::ZERO.show(|| { Layer::new().show(|| { - reflow(Alignment::BOTTOM_LEFT, Dim2::ZERO, || { + reflow(Alignment::BOTTOM_LEFT, Pivot::TOP_LEFT, Dim2::ZERO, || { column(|| { let current = selected.get(); for (i, option) in options.iter().enumerate() {