diff --git a/CHANGELOG.md b/CHANGELOG.md index 031e5e62c..a069d6de6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he ### Features - **core**: Added the smooth widgets for transitioning the layout position and size. (#pr @M-Adoo) +- **widgets**: Added three widgets `FractionallyWidthBox`, `FractionallyHeightBox`, and `FractionallySizedBox` to enable fractional sizing of widgets. (#pr @M-Adoo) ## [0.4.0-alpha.14] - 2024-10-30 diff --git a/core/src/test_helper.rs b/core/src/test_helper.rs index 836241a35..03cc75e17 100644 --- a/core/src/test_helper.rs +++ b/core/src/test_helper.rs @@ -338,16 +338,16 @@ impl LayoutCase { let info = wnd.layout_info_by_path(path).unwrap(); if let Some(x) = x { - assert_eq!(*x, info.pos.x, "unexpected x"); + assert_eq!(info.pos.x, *x, "unexpected x"); } if let Some(y) = y { - assert_eq!(*y, info.pos.y, "unexpected y"); + assert_eq!(info.pos.y, *y, "unexpected y"); } if let Some(w) = width { - assert_eq!(*w, info.size.unwrap().width, "unexpected width"); + assert_eq!(info.size.unwrap().width, *w, "unexpected width"); } if let Some(h) = height { - assert_eq!(*h, info.size.unwrap().height, "unexpected height"); + assert_eq!(info.size.unwrap().height, *h, "unexpected height"); } } } diff --git a/core/src/wrap_render.rs b/core/src/wrap_render.rs index 2d6c3a50d..4c3ef242a 100644 --- a/core/src/wrap_render.rs +++ b/core/src/wrap_render.rs @@ -122,6 +122,7 @@ where } } +#[macro_export] macro_rules! impl_compose_child_for_wrap_render { ($name:ty) => { impl<'c> ComposeChild<'c> for $name { diff --git a/widgets/src/layout.rs b/widgets/src/layout.rs index f214d6182..0ecc4e7f8 100644 --- a/widgets/src/layout.rs +++ b/widgets/src/layout.rs @@ -32,3 +32,5 @@ pub use stack::*; pub mod only_sized_by_parent; pub use only_sized_by_parent::OnlySizedByParent; pub use ribir_core::builtin_widgets::container::Container; +mod fractionally; +pub use fractionally::*; diff --git a/widgets/src/layout/fractionally.rs b/widgets/src/layout/fractionally.rs new file mode 100644 index 000000000..6732ebca4 --- /dev/null +++ b/widgets/src/layout/fractionally.rs @@ -0,0 +1,177 @@ +use ribir_core::{prelude::*, wrap_render::WrapRender}; + +/// This widget resizes its child to occupy a fraction of the total available +/// space. +/// Alternatively, it can function as an empty box, occupying a fraction +/// of the total available space. +#[derive(Declare)] +pub struct FractionallySizedBox { + pub width_factor: f32, + pub height_factor: f32, +} + +/// This widget sizes its child to occupy a fraction of the total available +/// space along the x-axis. +/// +/// Alternatively, it can act as an empty box, occupying a fraction of the +/// x-axis space while extending along the y-axis. +#[derive(Declare)] +pub struct FractionallyWidthBox { + pub factor: f32, +} + +/// This widget sizes its child to occupy a fraction of the total available +/// space along the y-axis. +/// +/// Alternatively, it can act as an empty box, occupying a fraction of the +/// y-axis space while extending along the x-axis. +#[derive(Declare)] +pub struct FractionallyHeightBox { + pub factor: f32, +} + +// implementation for FractionallySizedBox +impl Render for FractionallySizedBox { + fn perform_layout(&self, clamp: BoxClamp, _: &mut LayoutCtx) -> Size { self.size(clamp) } +} + +ribir_core::impl_compose_child_for_wrap_render!(FractionallySizedBox); + +impl WrapRender for FractionallySizedBox { + fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { + let size = self.size(clamp); + host.perform_layout(BoxClamp::fixed_size(size), ctx) + } +} + +impl FractionallySizedBox { + fn size(&self, clamp: BoxClamp) -> Size { + let w_factor = self.width_factor.clamp(0., 1.); + let h_factor = self.height_factor.clamp(0., 1.); + let size = Size::new(clamp.max.width * w_factor, clamp.max.height * h_factor); + clamp.clamp(size) + } +} + +// implementation for FractionallyWidthBox +impl Render for FractionallyWidthBox { + fn perform_layout(&self, clamp: BoxClamp, _: &mut LayoutCtx) -> Size { + let width = self.width(clamp); + Size::new(width, clamp.max.height) + } +} + +ribir_core::impl_compose_child_for_wrap_render!(FractionallyWidthBox); + +impl WrapRender for FractionallyWidthBox { + fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { + let width = self.width(clamp); + host.perform_layout(clamp.with_fixed_width(width), ctx) + } +} + +impl FractionallyWidthBox { + fn width(&self, clamp: BoxClamp) -> f32 { + let factor = self.factor.clamp(0., 1.); + let width = clamp.max.width * factor; + width.clamp(clamp.min.width, clamp.max.width) + } +} +// implementation for FractionallyHeightBox + +impl Render for FractionallyHeightBox { + fn perform_layout(&self, clamp: BoxClamp, _: &mut LayoutCtx) -> Size { + let height = self.height(clamp); + Size::new(clamp.max.width, height) + } +} + +ribir_core::impl_compose_child_for_wrap_render!(FractionallyHeightBox); + +impl WrapRender for FractionallyHeightBox { + fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { + let height = self.height(clamp); + host.perform_layout(clamp.with_fixed_height(height), ctx) + } +} + +impl FractionallyHeightBox { + fn height(&self, clamp: BoxClamp) -> f32 { + let factor = self.factor.clamp(0., 1.); + let height = clamp.max.height * factor; + height.clamp(clamp.min.height, clamp.max.height) + } +} + +#[cfg(test)] +mod tests { + use ribir_core::test_helper::*; + use ribir_dev_helper::*; + + use super::*; + + widget_layout_test! { + fractionally_sized_box, + WidgetTester::new(fn_widget! { + @FractionallySizedBox { + width_factor: 0.5, height_factor: 0.5, + } + }) + .with_wnd_size(Size::new(100., 100.)), + LayoutCase::default().with_size(Size::new(50., 50.)) + } + + widget_layout_test! { + fractionally_sized_box_with_child, + WidgetTester::new(fn_widget! { + @FractionallySizedBox { + width_factor: 0., height_factor: 1.2, + @ { Void } + } + }) + .with_wnd_size(Size::new(100., 100.)), + LayoutCase::default().with_size(Size::new(0., 100.)) + } + + widget_layout_test! { + fractionally_width_box, + WidgetTester::new(fn_widget! { + @FractionallyWidthBox { factor: 0.5 } + }) + .with_wnd_size(Size::new(100., 100.)), + LayoutCase::default().with_size(Size::new(50., 100.)) + } + + widget_layout_test! { + fractionally_width_box_with_child, + WidgetTester::new(fn_widget! { + @FractionallyWidthBox { + factor: 0.5, + @ { Void } + } + }) + .with_wnd_size(Size::new(100., 100.)), + LayoutCase::default().with_size(Size::new(50., 0.)) + } + + widget_layout_test! { + fractionally_height_box, + WidgetTester::new(fn_widget! { + @FractionallyHeightBox { factor: 0.5 } + }) + .with_wnd_size(Size::new(100., 100.)), + LayoutCase::default().with_size(Size::new(100., 50.)) + } + + widget_layout_test! { + fractionally_height_box_with_child, + WidgetTester::new(fn_widget! { + @FractionallyHeightBox { + factor: 0.5, + @ { Void } + } + }) + .with_wnd_size(Size::new(100., 100.)), + LayoutCase::default().with_size(Size::new(0., 50.)) + } +}