From fc1da9be06cd25e3e77a8237a069519e9dd33ec0 Mon Sep 17 00:00:00 2001 From: Adoo Date: Thu, 12 Sep 2024 18:25:43 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(core):=20=F0=9F=8E=B8=20Added=20`State?= =?UTF-8?q?Writer::into=5Frender`=20to=20covert=20writer=20to=20reader?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The writer of the render widget may not be flagged as dirty. --- CHANGELOG.md | 9 +++++ core/src/state.rs | 61 ++++++++++++++++++++++++++++---- core/src/state/map_state.rs | 14 +++++--- core/src/state/splitted_state.rs | 9 +++-- core/src/state/stateful.rs | 22 +++++------- core/src/widget.rs | 10 +++--- 6 files changed, 94 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f1492804..54125b99f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,15 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he ## [@Unreleased] - @ReleaseDate +### Features + +- **core:: Added `StateWriter::into_render` to covert writer to reader if no other writer exist. (#pr @M-Adoo) + +### Fixed + +- **core**: The `SplitWriter` and `MapWriter` of the render widget may not be flagged as dirty. (#626, @M-Adoo) + + ## [0.4.0-alpha.9] - 2024-09-18 ### Changed diff --git a/core/src/state.rs b/core/src/state.rs index 46f7d2ea9..baf342627 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -16,7 +16,7 @@ use state_cell::{StateCell, ValueMutRef}; pub use stateful::*; pub use watcher::*; -use crate::prelude::*; +use crate::{prelude::*, render_helper::RenderProxy}; /// The `StateReader` trait allows for reading, clone and map the state. pub trait StateReader: 'static { @@ -82,6 +82,11 @@ pub trait StateWriter: StateWatcher { type Writer: StateWriter; type OriginWriter: StateWriter; + /// Convert the writer to a reader if no other writers exist. + fn into_reader(self) -> Result + where + Self: Sized; + /// Return a write reference of this state. fn write(&self) -> WriteRef; /// Return a silent write reference which notifies will be ignored by the @@ -196,6 +201,16 @@ impl StateWriter for State { type Writer = Stateful; type OriginWriter = Self; + fn into_reader(self) -> Result + where + Self: Sized, + { + match self.0.into_inner() { + InnerState::Data(d) => Ok(Reader(Sc::new(d))), + InnerState::Stateful(s) => s.into_reader().map_err(State::stateful), + } + } + #[inline] fn write(&self) -> WriteRef { self.as_stateful().write() } @@ -365,15 +380,49 @@ impl<'a, W: ?Sized> Drop for WriteRef<'a, W> { } } -impl IntoWidgetStrict<'static, RENDER> for State { - fn into_widget_strict(self) -> Widget<'static> { - match self.0.into_inner() { - InnerState::Data(w) => w.into_inner().into_widget(), - InnerState::Stateful(w) => w.into_widget(), +pub struct WriterRender(pub(crate) T); + +pub struct ReaderRender(pub(crate) T); + +impl WriterRender +where + T: StateWriter, + T::Value: Render + Sized, +{ + pub fn into_widget(self) -> Widget<'static> { + match self.0.try_into_value() { + Ok(r) => r.into_widget(), + Err(this) => match this.into_reader() { + Ok(r) => ReaderRender(r).into_widget(), + Err(s) => { + let modifies = s.raw_modifies(); + let w = ReaderRender(s.clone_reader()).into_widget(); + w.on_build(move |id, ctx| { + id.dirty_subscribe(modifies, ctx); + }) + } + }, } } } +impl RenderProxy for ReaderRender +where + R: StateReader, + R::Value: Render, +{ + type Target<'r> = ReadRef<'r, R::Value> + where + Self: 'r; + + #[inline(always)] + fn proxy(&self) -> Self::Target<'_> { self.0.read() } +} + +impl IntoWidgetStrict<'static, RENDER> for State { + fn into_widget_strict(self) -> Widget<'static> { WriterRender(self).into_widget() } +} + impl IntoWidgetStrict<'static, COMPOSE> for State { #[inline] fn into_widget_strict(self) -> Widget<'static> { Compose::compose(self) } diff --git a/core/src/state/map_state.rs b/core/src/state/map_state.rs index 80db93853..3ca12956b 100644 --- a/core/src/state/map_state.rs +++ b/core/src/state/map_state.rs @@ -133,6 +133,14 @@ where type Writer = MapWriter; type OriginWriter = W; + fn into_reader(self) -> Result { + let Self { origin, part_map } = self; + match origin.into_reader() { + Ok(origin) => Ok(MapWriterAsReader { origin, part_map }), + Err(origin) => Err(Self { origin, part_map }), + } + } + #[inline] fn write(&self) -> WriteRef { WriteRef::map(self.origin.write(), &self.part_map) } @@ -185,11 +193,9 @@ where impl<'w, S, F> IntoWidgetStrict<'w, RENDER> for MapWriter where - Self: 'static, - Self: StateReader + 'w, - ::Reader: IntoWidget<'w, RENDER>, + Self: StateWriter, { - fn into_widget_strict(self) -> Widget<'w> { self.clone_reader().into_widget() } + fn into_widget_strict(self) -> Widget<'w> { WriterRender(self).into_widget() } } impl IntoWidgetStrict<'static, COMPOSE> for MapWriter diff --git a/core/src/state/splitted_state.rs b/core/src/state/splitted_state.rs index bd44fa9fc..09d22dd9b 100644 --- a/core/src/state/splitted_state.rs +++ b/core/src/state/splitted_state.rs @@ -64,6 +64,10 @@ where type Writer = SplittedWriter; type OriginWriter = O; + fn into_reader(self) -> Result { + if self.info.writer_count.get() == 1 { Ok(self.clone_reader()) } else { Err(self) } + } + #[inline] fn write(&self) -> WriteRef { self.split_ref(self.origin.write()) } @@ -113,10 +117,9 @@ where impl<'w, S, F> IntoWidgetStrict<'w, RENDER> for SplittedWriter where - Self: StateWriter + 'w, - ::Reader: IntoWidget<'w, RENDER>, + Self: StateWriter + 'w, { - fn into_widget_strict(self) -> Widget<'w> { self.clone_reader().into_widget() } + fn into_widget_strict(self) -> Widget<'w> { WriterRender(self).into_widget() } } impl IntoWidgetStrict<'static, COMPOSE> for SplittedWriter diff --git a/core/src/state/stateful.rs b/core/src/state/stateful.rs index c94f69835..21f992111 100644 --- a/core/src/state/stateful.rs +++ b/core/src/state/stateful.rs @@ -12,7 +12,7 @@ pub struct Stateful { info: Sc, } -pub struct Reader(Sc>); +pub struct Reader(pub(crate) Sc>); /// The notifier is a `RxRust` stream that emit notification when the state /// changed. @@ -81,6 +81,13 @@ impl StateWriter for Stateful { type Writer = Self; type OriginWriter = Self; + fn into_reader(self) -> Result + where + Self: Sized, + { + if self.info.writer_count.get() == 1 { Ok(self.clone_reader()) } else { Err(self) } + } + #[inline] fn write(&self) -> WriteRef { self.write_ref(ModifyScope::BOTH) } @@ -168,18 +175,7 @@ impl WriterInfo { } impl IntoWidgetStrict<'static, RENDER> for Stateful { - fn into_widget_strict(self) -> Widget<'static> { - match self.try_into_value() { - Ok(r) => r.into_widget(), - Err(s) => { - let modifies = s.raw_modifies(); - let w = s.data.clone().into_widget(); - w.on_build(move |id, ctx| { - id.dirty_subscribe(modifies, ctx); - }) - } - } - } + fn into_widget_strict(self) -> Widget<'static> { WriterRender(self).into_widget() } } impl IntoWidgetStrict<'static, COMPOSE> for Stateful { diff --git a/core/src/widget.rs b/core/src/widget.rs index 71e8e705b..5670de0ce 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -157,11 +157,7 @@ impl IntoWidgetStrict<'static, COMPOSE> for C { } impl IntoWidgetStrict<'static, RENDER> for R { - fn into_widget_strict(self) -> Widget<'static> { - let n = PureNode::Render(Box::new(PureRender(self))); - let node = Node::Leaf(n); - Widget(InnerWidget::Node(node)) - } + fn into_widget_strict(self) -> Widget<'static> { Widget::from_render(Box::new(PureRender(self))) } } impl>, C> Compose for W { @@ -212,6 +208,10 @@ impl<'w> Widget<'w> { (Widget(InnerWidget::Node(node)), root_id.unwrap()) } + pub(crate) fn from_render(r: Box) -> Widget<'static> { + Widget(InnerWidget::Node(Node::Leaf(PureNode::Render(r)))) + } + /// Attach anonymous data to a widget and user can't query it. pub fn attach_anonymous_data(self, data: impl Any) -> Self { self.on_build(|id, ctx| id.attach_anonymous_data(data, ctx.tree_mut())) From 3332e21b7f1b0490313254a88e3461ed05d77c1a Mon Sep 17 00:00:00 2001 From: Adoo Date: Mon, 16 Sep 2024 22:08:31 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat(core):=20=F0=9F=8E=B8=20Added=20`WrapR?= =?UTF-8?q?ender`=20to=20combines=20render=20widget=20with=20its=20child?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reimplemented `HAlignWidget`, `VAlignWidget`, `RelativeAnchor`, `BoxDecoration`, `ConstrainedBox`, `IgnorePoint`, `Opacity`, `Padding`, `TransformWidget`, and `VisibilityRender` as `WrapRender`. --- CHANGELOG.md | 8 +- core/src/builtin_widgets/align.rs | 91 ++++++------- core/src/builtin_widgets/anchor.rs | 42 +++--- core/src/builtin_widgets/box_decoration.rs | 23 +++- core/src/builtin_widgets/constrained_box.rs | 26 ++-- core/src/builtin_widgets/container.rs | 6 +- core/src/builtin_widgets/global_anchor.rs | 15 ++- core/src/builtin_widgets/ignore_pointer.rs | 23 ++-- core/src/builtin_widgets/image_widget.rs | 5 +- core/src/builtin_widgets/layout_box.rs | 2 +- core/src/builtin_widgets/opacity.rs | 26 ++-- core/src/builtin_widgets/padding.rs | 60 ++++----- core/src/builtin_widgets/scrollable.rs | 5 +- core/src/builtin_widgets/svg.rs | 3 +- core/src/builtin_widgets/transform_widget.rs | 45 ++++--- core/src/builtin_widgets/unconstrained_box.rs | 19 +-- core/src/builtin_widgets/visibility.rs | 33 +++-- core/src/builtin_widgets/void.rs | 4 +- core/src/context/widget_ctx.rs | 13 +- core/src/events/dispatcher.rs | 34 +++-- core/src/lib.rs | 2 +- core/src/overlay.rs | 12 +- core/src/pipe.rs | 26 ++-- core/src/state.rs | 4 +- core/src/test_helper.rs | 22 +--- core/src/widget.rs | 22 +--- .../src/widget_children/compose_child_impl.rs | 25 ++++ core/src/widget_tree.rs | 8 +- core/src/widget_tree/layout_info.rs | 6 +- core/src/widget_tree/widget_id.rs | 8 +- core/src/window.rs | 8 +- core/src/wrap_render.rs | 123 ++++++++++++++++++ examples/storybook/src/storybook.rs | 32 ++--- macros/src/declare_derive.rs | 5 +- .../tests/todos_with_default_by_wgpu.png | Bin 12943 -> 12943 bytes .../tests/todos_with_material_by_wgpu.png | Bin 13447 -> 13446 bytes tests/rdl_macro_test.rs | 2 +- themes/material/src/classes/scrollbar_cls.rs | 1 - widgets/src/buttons.rs | 2 +- widgets/src/input.rs | 6 +- widgets/src/layout/only_sized_by_parent.rs | 10 +- widgets/src/layout/sized_box.rs | 13 +- widgets/src/layout/stack.rs | 7 +- widgets/src/lists.rs | 4 +- widgets/src/text.rs | 5 +- 45 files changed, 513 insertions(+), 323 deletions(-) create mode 100644 core/src/wrap_render.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 54125b99f..b215cae89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,13 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he ### Features -- **core:: Added `StateWriter::into_render` to covert writer to reader if no other writer exist. (#pr @M-Adoo) +- **core**: Added `WrapRender` for a render widget that combines with its child as a single widget tree node. (#626 @M-Adoo) +- **core:: Added `StateWriter::into_render` to covert writer to reader if no other writer exist. (#626 @M-Adoo) + +### Changed + +- **core**: Reimplemented `HAlignWidget`, `VAlignWidget`, `RelativeAnchor`, `BoxDecoration`, `ConstrainedBox`, `IgnorePoint`, `Opacity`, `Padding`, `TransformWidget`, and `VisibilityRender` as `WrapRender`. (#626 @M-Adoo) + ### Fixed diff --git a/core/src/builtin_widgets/align.rs b/core/src/builtin_widgets/align.rs index fee29c3fa..1c1a1151e 100644 --- a/core/src/builtin_widgets/align.rs +++ b/core/src/builtin_widgets/align.rs @@ -1,4 +1,4 @@ -use crate::prelude::*; +use crate::{prelude::*, wrap_render::WrapRender}; /// A enum that describe how widget align to its box. #[derive(Default, Clone, Copy, PartialEq, Eq, Hash)] @@ -54,13 +54,13 @@ pub enum VAlign { } /// A widget that align its child in x-axis, base on child's width. -#[derive(SingleChild, Default)] +#[derive(Default)] pub struct HAlignWidget { pub h_align: HAlign, } /// A widget that align its child in y-axis, base on child's height. -#[derive(SingleChild, Default)] +#[derive(Default)] pub struct VAlignWidget { pub v_align: VAlign, } @@ -77,51 +77,50 @@ impl Declare for VAlignWidget { fn declarer() -> Self::Builder { FatObj::new(()) } } -impl Render for HAlignWidget { - fn perform_layout(&self, mut clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { +impl<'c> ComposeChild<'c> for HAlignWidget { + type Child = Widget<'c>; + + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { + WrapRender::combine_child(this, child) + } +} + +impl<'c> ComposeChild<'c> for VAlignWidget { + type Child = Widget<'c>; + + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { + WrapRender::combine_child(this, child) + } +} + +impl WrapRender for HAlignWidget { + fn perform_layout(&self, mut clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { let align: Align = self.h_align.into(); if align == Align::Stretch { clamp.min.width = clamp.max.width; } else { clamp.min.width = 0.; } - let child = ctx.assert_single_child(); - let child_size = ctx.perform_child_layout(child, clamp); - let box_width = clamp.max.width; - let x = align.align_value(child_size.width, box_width); - ctx.update_position(child, Point::new(x, 0.)); - Size::new(box_width, child_size.height) - } - fn paint(&self, _: &mut PaintingCtx) {} - - #[inline] - fn hit_test(&self, _: &HitTestCtx, _: Point) -> HitTest { - HitTest { hit: false, can_hit_child: true } + let child_size = host.perform_layout(clamp, ctx); + let x = align.align_value(child_size.width, clamp.max.width); + ctx.update_position(ctx.widget_id(), Point::new(x, 0.)); + clamp.clamp(child_size) } } -impl Render for VAlignWidget { - fn perform_layout(&self, mut clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { +impl WrapRender for VAlignWidget { + fn perform_layout(&self, mut clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { let align: Align = self.v_align.into(); if align == Align::Stretch { clamp.min.height = clamp.max.height; } else { clamp.min.height = 0.; } - let child = ctx.assert_single_child(); - let child_size = ctx.perform_child_layout(child, clamp); - let box_height = clamp.max.height; - let y = align.align_value(child_size.height, box_height); - ctx.update_position(child, Point::new(0., y)); - Size::new(child_size.width, box_height) - } - - fn paint(&self, _: &mut PaintingCtx) {} - - #[inline] - fn hit_test(&self, _: &HitTestCtx, _: Point) -> HitTest { - HitTest { hit: false, can_hit_child: true } + let child_size = host.perform_layout(clamp, ctx); + let y = align.align_value(child_size.height, clamp.max.height); + ctx.update_position(ctx.widget_id(), Point::new(0., y)); + clamp.clamp(child_size) } } @@ -181,15 +180,15 @@ mod tests { widget_layout_test!( left_align, WidgetTester::new(h_align(HAlign::Left)).with_wnd_size(WND_SIZE), - LayoutCase::default().with_size(Size::new(100., 10.)), - LayoutCase::new(&[0, 0]).with_size(CHILD_SIZE) + LayoutCase::default() + .with_size(CHILD_SIZE) + .with_x(0.) ); widget_layout_test!( h_center_align, WidgetTester::new(h_align(HAlign::Center)).with_wnd_size(WND_SIZE), - LayoutCase::default().with_size(Size::new(100., 10.)), - LayoutCase::new(&[0, 0]) + LayoutCase::default() .with_size(CHILD_SIZE) .with_x(45.) ); @@ -197,8 +196,7 @@ mod tests { widget_layout_test!( right_align, WidgetTester::new(h_align(HAlign::Right)).with_wnd_size(WND_SIZE), - LayoutCase::default().with_size(Size::new(100., 10.)), - LayoutCase::new(&[0, 0]) + LayoutCase::default() .with_size(CHILD_SIZE) .with_x(90.) ); @@ -206,9 +204,8 @@ mod tests { widget_layout_test!( h_stretch_algin, WidgetTester::new(h_align(HAlign::Stretch)).with_wnd_size(WND_SIZE), - LayoutCase::default().with_size(Size::new(100., 10.)), - LayoutCase::new(&[0, 0]) - .with_size(Size::new(100., 10.)) + LayoutCase::default() + .with_size(Size::new(WND_SIZE.width, 10.)) .with_x(0.) ); @@ -225,8 +222,7 @@ mod tests { widget_layout_test!( top_align, WidgetTester::new(v_align(VAlign::Top)).with_wnd_size(WND_SIZE), - LayoutCase::default().with_size(Size::new(10., 100.)), - LayoutCase::new(&[0, 0]) + LayoutCase::default() .with_size(CHILD_SIZE) .with_y(0.) ); @@ -234,8 +230,7 @@ mod tests { widget_layout_test!( v_center_align, WidgetTester::new(v_align(VAlign::Center)).with_wnd_size(WND_SIZE), - LayoutCase::default().with_size(Size::new(10., 100.)), - LayoutCase::new(&[0, 0]) + LayoutCase::default() .with_size(CHILD_SIZE) .with_y(45.) ); @@ -243,8 +238,7 @@ mod tests { widget_layout_test!( bottom_align, WidgetTester::new(v_align(VAlign::Bottom)).with_wnd_size(WND_SIZE), - LayoutCase::default().with_size(Size::new(10., 100.)), - LayoutCase::new(&[0, 0]) + LayoutCase::default() .with_size(CHILD_SIZE) .with_y(90.) ); @@ -252,7 +246,6 @@ mod tests { widget_layout_test!( v_stretch_align, WidgetTester::new(v_align(VAlign::Stretch)).with_wnd_size(WND_SIZE), - LayoutCase::default().with_size(Size::new(10., 100.)), - LayoutCase::new(&[0, 0]).with_size(Size::new(10., 100.)) + LayoutCase::default().with_size(Size::new(10., WND_SIZE.height)) ); } diff --git a/core/src/builtin_widgets/anchor.rs b/core/src/builtin_widgets/anchor.rs index 147f46475..7d98922d7 100644 --- a/core/src/builtin_widgets/anchor.rs +++ b/core/src/builtin_widgets/anchor.rs @@ -1,4 +1,4 @@ -use crate::prelude::*; +use crate::{prelude::*, wrap_render::WrapRender}; /// Specifies the horizontal position you want to anchor the widget. #[derive(Debug, Clone, Copy)] @@ -154,8 +154,11 @@ impl Anchor { pub fn right_bottom(x: f32, y: f32) -> Self { Self::new(HAnchor::Right(x), VAnchor::Bottom(y)) } } -/// Widget use to anchor child constraints relative to parent widget. -#[derive(SingleChild, Default)] +/// This widget is used to anchor child constraints relative to the parent +/// widget. It's important to note that if you anchor the child widget outside +/// of its parent, it may become unable to click, so ensure there is ample space +/// within the parent. +#[derive(Default)] pub struct RelativeAnchor { pub anchor: Anchor, } @@ -166,10 +169,17 @@ impl Declare for RelativeAnchor { fn declarer() -> Self::Builder { FatObj::new(()) } } -impl Render for RelativeAnchor { - fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { - let child = ctx.assert_single_child(); - let child_size = ctx.perform_child_layout(child, clamp); +impl<'c> ComposeChild<'c> for RelativeAnchor { + type Child = Widget<'c>; + + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { + WrapRender::combine_child(this, child) + } +} + +impl WrapRender for RelativeAnchor { + fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { + let child_size = host.perform_layout(clamp, ctx); let Anchor { x, y } = self.anchor; let x = x @@ -185,14 +195,14 @@ impl Render for RelativeAnchor { }) .unwrap_or_default(); - ctx.update_position(child, Point::new(x, y)); + ctx.update_position(ctx.widget_id(), Point::new(x, y)); child_size } +} - fn paint(&self, _: &mut PaintingCtx) {} - - fn hit_test(&self, _: &HitTestCtx, _: Point) -> HitTest { - HitTest { hit: false, can_hit_child: true } +impl From for Anchor { + fn from(value: Point) -> Self { + Anchor { x: Some(HAnchor::Left(value.x)), y: Some(VAnchor::Top(value.y)) } } } @@ -214,24 +224,24 @@ mod test { widget_layout_test!( pixel_left_top, widget_tester(Anchor::left_top(1., 1.)), - LayoutCase::new(&[0, 0]).with_pos(Point::new(1., 1.)) + LayoutCase::default().with_pos(Point::new(1., 1.)) ); widget_layout_test!( pixel_left_bottom, widget_tester(Anchor::left_bottom(1., 1.)), - LayoutCase::new(&[0, 0]).with_pos((1., 49.).into()) + LayoutCase::default().with_pos((1., 49.).into()) ); widget_layout_test!( pixel_top_right, widget_tester(Anchor::right_top(1., 1.)), - LayoutCase::new(&[0, 0]).with_pos((49., 1.).into()) + LayoutCase::default().with_pos((49., 1.).into()) ); widget_layout_test!( pixel_bottom_right, widget_tester(Anchor::right_bottom(1., 1.)), - LayoutCase::new(&[0, 0]).with_pos((49., 49.).into()) + LayoutCase::default().with_pos((49., 49.).into()) ); } diff --git a/core/src/builtin_widgets/box_decoration.rs b/core/src/builtin_widgets/box_decoration.rs index 2b63a452e..7f42df11d 100644 --- a/core/src/builtin_widgets/box_decoration.rs +++ b/core/src/builtin_widgets/box_decoration.rs @@ -1,7 +1,7 @@ -use crate::prelude::*; +use crate::{prelude::*, wrap_render::WrapRender}; /// The BoxDecoration provides a variety of ways to draw a box. -#[derive(SingleChild, Default, Clone)] +#[derive(Default, Clone)] pub struct BoxDecoration { /// The background of the box. pub background: Option, @@ -32,18 +32,26 @@ pub struct BorderSide { pub width: f32, } +impl<'c> ComposeChild<'c> for BoxDecoration { + type Child = Widget<'c>; + + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { + WrapRender::combine_child(this, child) + } +} + impl BorderSide { #[inline] pub fn new(width: f32, color: Brush) -> Self { Self { width, color } } } -impl Render for BoxDecoration { +impl WrapRender for BoxDecoration { #[inline] - fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { - ctx.assert_perform_single_child_layout(clamp) + fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { + host.perform_layout(clamp, ctx) } - fn paint(&self, ctx: &mut PaintingCtx) { + fn paint(&self, host: &dyn Render, ctx: &mut PaintingCtx) { let size = ctx.box_size().unwrap(); if !size.is_empty() { let rect = Rect::from_size(size); @@ -58,6 +66,7 @@ impl Render for BoxDecoration { painter.fill(); } self.paint_border(painter, &rect); + host.paint(ctx) } } } @@ -211,6 +220,6 @@ mod tests { } }), LayoutCase::default().with_size(Size::new(100., 100.)), - LayoutCase::new(&[0, 0]).with_rect(ribir_geom::rect(0., 0., 100., 100.)) + LayoutCase::new(&[0]).with_rect(ribir_geom::rect(0., 0., 100., 100.)) ); } diff --git a/core/src/builtin_widgets/constrained_box.rs b/core/src/builtin_widgets/constrained_box.rs index 1306b35be..6845358c7 100644 --- a/core/src/builtin_widgets/constrained_box.rs +++ b/core/src/builtin_widgets/constrained_box.rs @@ -1,7 +1,7 @@ -use crate::prelude::*; +use crate::{prelude::*, wrap_render::WrapRender}; /// a widget that imposes additional constraints clamp on its child. -#[derive(SingleChild, Clone, Default)] +#[derive(Clone, Default)] pub struct ConstrainedBox { pub clamp: BoxClamp, } @@ -12,18 +12,20 @@ impl Declare for ConstrainedBox { fn declarer() -> Self::Builder { FatObj::new(()) } } -impl Render for ConstrainedBox { - fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { +impl<'c> ComposeChild<'c> for ConstrainedBox { + type Child = Widget<'c>; + + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { + WrapRender::combine_child(this, child) + } +} + +impl WrapRender for ConstrainedBox { + fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { let max = clamp.clamp(self.clamp.max); let min = clamp.clamp(self.clamp.min); - ctx.assert_perform_single_child_layout(BoxClamp { min, max }) + host.perform_layout(BoxClamp { min, max }, ctx) } - - #[inline] - fn only_sized_by_parent(&self) -> bool { false } - - #[inline] - fn paint(&self, _: &mut PaintingCtx) {} } #[cfg(test)] @@ -43,7 +45,7 @@ mod tests { } } }), - LayoutCase::new(&[0, 0, 0]).with_size(Size::new(50., 50.)) + LayoutCase::new(&[0]).with_size(Size::new(50., 50.)) ); widget_layout_test!( diff --git a/core/src/builtin_widgets/container.rs b/core/src/builtin_widgets/container.rs index 8c8fac4c9..b8db5412f 100644 --- a/core/src/builtin_widgets/container.rs +++ b/core/src/builtin_widgets/container.rs @@ -8,14 +8,12 @@ pub struct Container { impl Render for Container { fn perform_layout(&self, mut clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { + let size = clamp.clamp(self.size); clamp.max = clamp.max.min(self.size); ctx.perform_single_child_layout(clamp); - self.size + size } - #[inline] - fn paint(&self, _: &mut PaintingCtx) {} - #[inline] fn only_sized_by_parent(&self) -> bool { true } } diff --git a/core/src/builtin_widgets/global_anchor.rs b/core/src/builtin_widgets/global_anchor.rs index 1e71220c2..718b4a64c 100644 --- a/core/src/builtin_widgets/global_anchor.rs +++ b/core/src/builtin_widgets/global_anchor.rs @@ -28,9 +28,11 @@ impl<'c> ComposeChild<'c> for GlobalAnchor { .sample(tick_of_layout_ready) .subscribe(move |(_, size)| { let wnd_size = wnd.size(); - let base = wnd.map_to_global(Point::zero(), wid.assert_id()); - // The global anchor may change during sampling, so we need to retrieve it again. let Anchor {x, y} = $this.get_global_anchor(); + // The global anchor may change during sampling, so we need to retrieve it again. + let cid = wid.assert_id(); + let offset = wnd.widget_pos(cid).unwrap_or_else(Point::zero); + let base = wnd.map_to_global(Point::zero(), cid) - offset; let anchor = Anchor { x: x.map(|x| match x { HAnchor::Left(x) => x - base.x, @@ -121,7 +123,7 @@ impl FatObj { .map_to_global(Point::zero(), wid.assert_id()) .x; let size = wnd - .layout_size(wid.assert_id()) + .widget_size(wid.assert_id()) .unwrap_or_default(); bind_h_anchor(&this, &wnd, HAnchor::Right(relative), base + size.width); }) @@ -164,7 +166,7 @@ impl FatObj { .map_to_global(Point::zero(), wid.assert_id()) .y; let size = wnd - .layout_size(wid.assert_id()) + .widget_size(wid.assert_id()) .unwrap_or_default(); bind_v_anchor(&this, &wnd, VAnchor::Bottom(relative), base + size.height); }) @@ -200,14 +202,13 @@ mod tests { bottom_right.bottom_align_to(&wid, 20., ctx!()); @ $parent { @MockStack { - child_pos: vec![Point::new(0., 0.), Point::new(0., 0.)], @ { top_left } @ { bottom_right } } } }) .with_wnd_size(WND_SIZE), - LayoutCase::new(&[0, 0, 0, 0, 0]).with_pos(Point::new(20., 10.)), - LayoutCase::new(&[0, 0, 0, 1, 0]).with_pos(Point::new(30., 20.)) + LayoutCase::new(&[0, 0, 0]).with_pos(Point::new(20., 10.)), + LayoutCase::new(&[0, 0, 1]).with_pos(Point::new(30., 20.)) ); } diff --git a/core/src/builtin_widgets/ignore_pointer.rs b/core/src/builtin_widgets/ignore_pointer.rs index 305fdd8ed..e090b4639 100644 --- a/core/src/builtin_widgets/ignore_pointer.rs +++ b/core/src/builtin_widgets/ignore_pointer.rs @@ -1,22 +1,27 @@ -use crate::prelude::*; +use crate::{prelude::*, wrap_render::WrapRender}; -#[derive(Declare, SingleChild, Clone)] +#[derive(Declare, Clone)] pub struct IgnorePointer { #[declare(default = true)] pub ignore: bool, } -impl Render for IgnorePointer { - #[inline] - fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { - ctx.assert_perform_single_child_layout(clamp) +impl<'c> ComposeChild<'c> for IgnorePointer { + type Child = Widget<'c>; + + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { + WrapRender::combine_child(this, child) } +} +impl WrapRender for IgnorePointer { #[inline] - fn paint(&self, _: &mut PaintingCtx) {} + fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { + host.perform_layout(clamp, ctx) + } - fn hit_test(&self, _: &HitTestCtx, _: Point) -> HitTest { - HitTest { hit: false, can_hit_child: !self.ignore } + fn hit_test(&self, host: &dyn Render, ctx: &HitTestCtx, pos: Point) -> HitTest { + if self.ignore { HitTest { hit: false, can_hit_child: false } } else { host.hit_test(ctx, pos) } } } diff --git a/core/src/builtin_widgets/image_widget.rs b/core/src/builtin_widgets/image_widget.rs index 98762897e..39b5d1681 100644 --- a/core/src/builtin_widgets/image_widget.rs +++ b/core/src/builtin_widgets/image_widget.rs @@ -1,8 +1,9 @@ use crate::prelude::*; impl Render for Resource { - fn perform_layout(&self, _: BoxClamp, _: &mut LayoutCtx) -> Size { - Size::new(self.width() as f32, self.height() as f32) + fn perform_layout(&self, clamp: BoxClamp, _: &mut LayoutCtx) -> Size { + let size = Size::new(self.width() as f32, self.height() as f32); + clamp.clamp(size) } fn paint(&self, ctx: &mut PaintingCtx) { diff --git a/core/src/builtin_widgets/layout_box.rs b/core/src/builtin_widgets/layout_box.rs index 362f59eaa..52a6c7dba 100644 --- a/core/src/builtin_widgets/layout_box.rs +++ b/core/src/builtin_widgets/layout_box.rs @@ -73,7 +73,7 @@ mod tests { let mut first_box = @MockBox { size: Size::new(100., 200.) }; let second_box = @MockBox { size: pipe!($first_box.layout_size()) }; @MockMulti { - @ { [first_box, second_box ] } + @ { [first_box, second_box ] } } }), LayoutCase::default().with_size(Size::new(200., 200.)), diff --git a/core/src/builtin_widgets/opacity.rs b/core/src/builtin_widgets/opacity.rs index 0ceb9b156..1161715a8 100644 --- a/core/src/builtin_widgets/opacity.rs +++ b/core/src/builtin_widgets/opacity.rs @@ -1,6 +1,6 @@ -use crate::prelude::*; +use crate::{prelude::*, wrap_render::WrapRender}; -#[derive(Clone, SingleChild)] +#[derive(Clone)] pub struct Opacity { pub opacity: f32, } @@ -16,17 +16,21 @@ impl Default for Opacity { fn default() -> Self { Self { opacity: 1.0 } } } -impl Render for Opacity { - #[inline] - fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { - ctx.assert_perform_single_child_layout(clamp) - } +impl<'c> ComposeChild<'c> for Opacity { + type Child = Widget<'c>; - fn paint(&self, ctx: &mut PaintingCtx) { ctx.painter().apply_alpha(self.opacity); } + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { + WrapRender::combine_child(this, child) + } +} - fn only_sized_by_parent(&self) -> bool { false } +impl WrapRender for Opacity { + fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { + host.perform_layout(clamp, ctx) + } - fn hit_test(&self, _: &HitTestCtx, _: Point) -> HitTest { - HitTest { hit: false, can_hit_child: true } + fn paint(&self, host: &dyn Render, ctx: &mut PaintingCtx) { + ctx.painter().apply_alpha(self.opacity); + host.paint(ctx) } } diff --git a/core/src/builtin_widgets/padding.rs b/core/src/builtin_widgets/padding.rs index c1699f40b..1aa4f7d3d 100644 --- a/core/src/builtin_widgets/padding.rs +++ b/core/src/builtin_widgets/padding.rs @@ -1,7 +1,7 @@ -use crate::prelude::*; +use crate::{prelude::*, wrap_render::WrapRender}; /// A widget that insets its child by the given padding. -#[derive(SingleChild, Clone, Default)] +#[derive(Default)] pub struct Padding { pub padding: EdgeInsets, } @@ -12,48 +12,44 @@ impl Declare for Padding { fn declarer() -> Self::Builder { FatObj::new(()) } } -impl Render for Padding { - fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { - let child = match ctx.single_child() { - Some(c) => c, - None => return Size::zero(), - }; +impl<'c> ComposeChild<'c> for Padding { + type Child = Widget<'c>; + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { + WrapRender::combine_child(this, child) + } +} + +impl WrapRender for Padding { + fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { let thickness = self.padding.thickness(); let zero = Size::zero(); + // Reset children position before layout + let (ctx, children) = ctx.split_children(); + for c in children { + ctx.update_position(c, Point::zero()); + } + let min = (clamp.min - thickness).max(zero); let max = (clamp.max - thickness).max(zero); // Shrink the clamp of child. let child_clamp = BoxClamp { min, max }; - ctx.force_child_relayout(child); - let child = ctx.assert_single_child(); + let mut size = host.perform_layout(child_clamp, ctx); - let mut size = ctx.perform_child_layout(child, child_clamp); - if child.first_child(ctx.tree).is_some() { - // Expand the size, so the child have padding. - size = clamp.clamp(size + thickness); - ctx.update_size(child, size); + size = clamp.clamp(size + thickness); - // Update child's children position, let they have a correct position after - // expanded with padding. padding. - let mut ctx = LayoutCtx { id: child, tree: ctx.tree }; - let (ctx, grandson) = ctx.split_children(); - for g in grandson { - if let Some(pos) = ctx.widget_box_pos(g) { - let pos = pos + Vector::new(self.padding.left, self.padding.top); - ctx.update_position(g, pos); - } + let (ctx, children) = ctx.split_children(); + // Update the children's positions to ensure they are correctly positioned after + // expansion with padding. + for c in children { + if let Some(pos) = ctx.widget_box_pos(c) { + let pos = pos + Vector::new(self.padding.left, self.padding.top); + ctx.update_position(c, pos); } } size } - - #[inline] - fn only_sized_by_parent(&self) -> bool { false } - - #[inline] - fn paint(&self, _: &mut PaintingCtx) {} } impl Padding { @@ -80,10 +76,8 @@ mod tests { }), // padding widget LayoutCase::default().with_size(Size::new(101., 100.)), - // MockMulti widget - LayoutCase::new(&[0, 0]).with_size(Size::new(101., 100.)), // MockBox - LayoutCase::new(&[0, 0, 0]) + LayoutCase::new(&[0, 0]) .with_size(Size::new(100., 100.)) .with_x(1.) ); diff --git a/core/src/builtin_widgets/scrollable.rs b/core/src/builtin_widgets/scrollable.rs index be568561e..5d0bd46b7 100644 --- a/core/src/builtin_widgets/scrollable.rs +++ b/core/src/builtin_widgets/scrollable.rs @@ -161,10 +161,7 @@ mod tests { }); wnd.draw_frame(); - let pos = wnd - .layout_info_by_path(&[0, 0, 0, 0]) - .unwrap() - .pos; + let pos = wnd.layout_info_by_path(&[0, 0, 0]).unwrap().pos; assert_eq!(pos, Point::new(expect_x, expect_y)); } diff --git a/core/src/builtin_widgets/svg.rs b/core/src/builtin_widgets/svg.rs index 7d22514bf..78f3c7dbb 100644 --- a/core/src/builtin_widgets/svg.rs +++ b/core/src/builtin_widgets/svg.rs @@ -2,9 +2,8 @@ use crate::prelude::*; impl Render for Svg { #[inline] - fn perform_layout(&self, _: BoxClamp, _: &mut LayoutCtx) -> Size { self.size } + fn perform_layout(&self, clamp: BoxClamp, _: &mut LayoutCtx) -> Size { clamp.clamp(self.size) } - #[inline] fn paint(&self, ctx: &mut PaintingCtx) { let painter = ctx.painter(); painter.draw_svg(self); diff --git a/core/src/builtin_widgets/transform_widget.rs b/core/src/builtin_widgets/transform_widget.rs index 631fd563d..34f8d2e7d 100644 --- a/core/src/builtin_widgets/transform_widget.rs +++ b/core/src/builtin_widgets/transform_widget.rs @@ -1,6 +1,6 @@ -use crate::prelude::*; +use crate::{prelude::*, wrap_render::WrapRender}; -#[derive(SingleChild, Clone, Default)] +#[derive(Clone, Default)] pub struct TransformWidget { pub transform: Transform, } @@ -11,26 +11,41 @@ impl Declare for TransformWidget { fn declarer() -> Self::Builder { FatObj::new(()) } } -impl Render for TransformWidget { - #[inline] - fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { - ctx.assert_perform_single_child_layout(clamp) +impl<'c> ComposeChild<'c> for TransformWidget { + type Child = Widget<'c>; + + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { + WrapRender::combine_child(this, child) } +} +impl WrapRender for TransformWidget { #[inline] - fn paint(&self, ctx: &mut PaintingCtx) { ctx.painter().apply_transform(&self.transform); } + fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { + host.perform_layout(clamp, ctx) + } - #[inline] - fn hit_test(&self, ctx: &HitTestCtx, pos: Point) -> HitTest { - let is_hit = self - .transform - .inverse() - .map_or(false, |transform| hit_test_impl(ctx, transform.transform_point(pos))); + fn paint(&self, host: &dyn Render, ctx: &mut PaintingCtx) { + ctx.painter().apply_transform(&self.transform); + host.paint(ctx) + } - HitTest { hit: is_hit, can_hit_child: is_hit } + fn hit_test(&self, host: &dyn Render, ctx: &HitTestCtx, pos: Point) -> HitTest { + if let Some(t) = self.transform.inverse() { + let pos = t.transform_point(pos); + host.hit_test(ctx, pos) + } else { + HitTest { hit: false, can_hit_child: false } + } } - fn get_transform(&self) -> Option { Some(self.transform) } + fn get_transform(&self, host: &dyn Render) -> Option { + if let Some(t) = host.get_transform() { + Some(self.transform.then(&t)) + } else { + Some(self.transform) + } + } } impl TransformWidget { diff --git a/core/src/builtin_widgets/unconstrained_box.rs b/core/src/builtin_widgets/unconstrained_box.rs index 785b58893..f5c3658d2 100644 --- a/core/src/builtin_widgets/unconstrained_box.rs +++ b/core/src/builtin_widgets/unconstrained_box.rs @@ -40,23 +40,24 @@ impl Default for ClampDim { impl Render for UnconstrainedBox { #[inline] - fn perform_layout(&self, mut clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { + fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { + let mut child_clamp = clamp; if self.clamp_dim.contains(ClampDim::MIN_SIZE) { match self.dir { - UnconstrainedDir::X => clamp.min.width = 0., - UnconstrainedDir::Y => clamp.min.height = 0., - UnconstrainedDir::Both => clamp = clamp.loose(), + UnconstrainedDir::X => child_clamp.min.width = 0., + UnconstrainedDir::Y => child_clamp.min.height = 0., + UnconstrainedDir::Both => child_clamp = child_clamp.loose(), }; } if self.clamp_dim.contains(ClampDim::MAX_SIZE) { match self.dir { - UnconstrainedDir::X => clamp.max.width = f32::INFINITY, - UnconstrainedDir::Y => clamp.max.height = f32::INFINITY, - UnconstrainedDir::Both => clamp = clamp.expand(), + UnconstrainedDir::X => child_clamp.max.width = f32::INFINITY, + UnconstrainedDir::Y => child_clamp.max.height = f32::INFINITY, + UnconstrainedDir::Both => child_clamp = child_clamp.expand(), }; } - - ctx.assert_perform_single_child_layout(clamp) + let size = ctx.assert_perform_single_child_layout(child_clamp); + clamp.clamp(size) } #[inline] diff --git a/core/src/builtin_widgets/visibility.rs b/core/src/builtin_widgets/visibility.rs index ff24bfbc2..f4510666f 100644 --- a/core/src/builtin_widgets/visibility.rs +++ b/core/src/builtin_widgets/visibility.rs @@ -1,4 +1,4 @@ -use crate::prelude::*; +use crate::{prelude::*, wrap_render::WrapRender}; #[derive(Default)] pub struct Visibility { @@ -28,26 +28,37 @@ impl<'c> ComposeChild<'c> for Visibility { } } -#[derive(SingleChild, Declare, Clone)] +#[derive(Declare, Clone)] struct VisibilityRender { display: bool, } -impl Render for VisibilityRender { - #[inline] - fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { - if self.display { ctx.assert_perform_single_child_layout(clamp) } else { ZERO_SIZE } +impl<'c> ComposeChild<'c> for VisibilityRender { + type Child = Widget<'c>; + + fn compose_child(this: impl StateWriter, child: Self::Child) -> Widget<'c> { + WrapRender::combine_child(this, child) } +} +impl WrapRender for VisibilityRender { #[inline] - fn paint(&self, ctx: &mut PaintingCtx) { - if !self.display { - ctx.painter().apply_alpha(0.); + fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { + if self.display { host.perform_layout(clamp, ctx) } else { clamp.min } + } + + fn paint(&self, host: &dyn Render, ctx: &mut PaintingCtx) { + if self.display { + host.paint(ctx) } } - fn hit_test(&self, _: &HitTestCtx, _: Point) -> HitTest { - HitTest { hit: false, can_hit_child: self.display } + fn hit_test(&self, host: &dyn Render, ctx: &HitTestCtx, pos: Point) -> HitTest { + if self.display { + host.hit_test(ctx, pos) + } else { + HitTest { hit: false, can_hit_child: false } + } } } diff --git a/core/src/builtin_widgets/void.rs b/core/src/builtin_widgets/void.rs index 77b172f0c..1b5b7ded0 100644 --- a/core/src/builtin_widgets/void.rs +++ b/core/src/builtin_widgets/void.rs @@ -10,8 +10,8 @@ pub struct Void; impl Render for Void { fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { ctx - .single_child() - .map_or_else(Size::zero, |c| ctx.perform_child_layout(c, clamp)) + .perform_single_child_layout(clamp) + .unwrap_or(clamp.min) } fn paint(&self, _: &mut PaintingCtx) {} diff --git a/core/src/context/widget_ctx.rs b/core/src/context/widget_ctx.rs index dcd772aa5..c1900ea2a 100644 --- a/core/src/context/widget_ctx.rs +++ b/core/src/context/widget_ctx.rs @@ -5,7 +5,7 @@ use ribir_geom::{Point, Rect, Size}; use crate::{ query::QueryRef, state::WriteRef, - widget::{BoxClamp, WidgetTree}, + widget::{BoxClamp, HitTest, WidgetTree}, widget_tree::WidgetId, window::Window, }; @@ -229,6 +229,15 @@ pub(crate) use define_widget_context; define_widget_context!(HitTestCtx); +impl HitTestCtx { + pub fn box_hit_test(&self, pos: Point) -> HitTest { + let is_hit = self + .box_rect() + .map_or(false, |rect| rect.contains(pos)); + HitTest { hit: is_hit, can_hit_child: is_hit } + } +} + #[cfg(test)] mod tests { use super::*; @@ -281,7 +290,7 @@ mod tests { wnd.draw_frame(); let root = wnd.tree().root(); - let child = get_single_child_by_depth(root, wnd.tree(), 3); + let child = get_single_child_by_depth(root, wnd.tree(), 2); let w_ctx = TestCtx { id: root, tree: wnd.tree }; let from_pos = Point::new(30., 30.); assert_eq!(w_ctx.map_from(from_pos, child), Point::new(45., 45.)); diff --git a/core/src/events/dispatcher.rs b/core/src/events/dispatcher.rs index 40e212432..34415db09 100644 --- a/core/src/events/dispatcher.rs +++ b/core/src/events/dispatcher.rs @@ -650,25 +650,21 @@ mod tests { let (data, writer) = split_value(T { wid1: None, wid2: None }); let w = fn_widget! { - @MockBox { - size: Size::new(200., 200.), - @MockStack { - child_pos: vec![ - Point::new(50., 50.), - Point::new(100., 100.), - ], - @MockBox { - on_mounted: move |ctx| { - $writer.write().wid1 = Some(ctx.id); - }, - size: Size::new(100., 100.), - } - @MockBox { - on_mounted: move |ctx| { - $writer.write().wid2 = Some(ctx.id); - }, - size: Size::new(50., 150.), - } + @MockStack { + clamp: BoxClamp::EXPAND_BOTH, + @MockBox { + anchor: Point::new(50., 50.), + on_mounted: move |ctx| { + $writer.write().wid1 = Some(ctx.id); + }, + size: Size::new(100., 100.), + } + @MockBox { + on_mounted: move |ctx| { + $writer.write().wid2 = Some(ctx.id); + }, + size: Size::new(50., 150.), + anchor: Point::new(100., 100.), } } }; diff --git a/core/src/lib.rs b/core/src/lib.rs index e78c1d5b9..56c0bace9 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -23,7 +23,7 @@ pub mod window; pub use rxrust; pub mod overlay; pub mod query; - +pub mod wrap_render; pub mod prelude { pub use log; #[doc(no_inline)] diff --git a/core/src/overlay.rs b/core/src/overlay.rs index 3ab4aeff6..b997dca3a 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -319,16 +319,8 @@ mod tests { overlay.show_at(Point::new(50., 30.), wnd.0.clone()); wnd.draw_frame(); assert_eq!(*r_log.borrow(), &["mounted"]); - // the path [1, 0, 0, 0] is from root to anchor, - // Root -> BoxDecoration-> Container -> Anchor - - assert_eq!( - wnd - .layout_info_by_path(&[1, 0, 0, 0]) - .unwrap() - .pos, - Point::new(50., 30.) - ); + + assert_eq!(wnd.layout_info_by_path(&[1, 0]).unwrap().pos, Point::new(50., 30.)); overlay.close(); wnd.draw_frame(); diff --git a/core/src/pipe.rs b/core/src/pipe.rs index 1be6d1ec4..513f37265 100644 --- a/core/src/pipe.rs +++ b/core/src/pipe.rs @@ -10,10 +10,7 @@ use smallvec::SmallVec; use widget_id::RenderQueryable; use crate::{ - builtin_widgets::key::AnyKey, - prelude::*, - render_helper::{PureRender, RenderProxy}, - window::WindowId, + builtin_widgets::key::AnyKey, prelude::*, render_helper::PureRender, window::WindowId, }; pub type ValueStream = BoxOp<'static, (ModifyScope, V), Infallible>; @@ -832,12 +829,23 @@ impl Query for PipeNode { fn queryable(&self) -> bool { true } } -impl RenderProxy for PipeNode { - type Target<'r> = &'r dyn RenderQueryable - where - Self: 'r; +impl Render for PipeNode { + fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { + self.as_ref().data.perform_layout(clamp, ctx) + } + + fn paint(&self, ctx: &mut PaintingCtx) { self.as_ref().data.paint(ctx) } + + fn only_sized_by_parent(&self) -> bool { + // A pipe node is always sized by its parent because it can generate any widget. + false + } + + fn hit_test(&self, ctx: &HitTestCtx, pos: Point) -> HitTest { + self.as_ref().data.hit_test(ctx, pos) + } - fn proxy(&self) -> Self::Target<'_> { &*self.as_ref().data } + fn get_transform(&self) -> Option { self.as_ref().data.get_transform() } } #[derive(Clone)] diff --git a/core/src/state.rs b/core/src/state.rs index baf342627..155fe1840 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -380,9 +380,9 @@ impl<'a, W: ?Sized> Drop for WriteRef<'a, W> { } } -pub struct WriterRender(pub(crate) T); +pub(crate) struct WriterRender(pub(crate) T); -pub struct ReaderRender(pub(crate) T); +struct ReaderRender(pub(crate) T); impl WriterRender where diff --git a/core/src/test_helper.rs b/core/src/test_helper.rs index a5ed9bc36..02016bf51 100644 --- a/core/src/test_helper.rs +++ b/core/src/test_helper.rs @@ -198,22 +198,14 @@ impl TestShellWindow { } #[derive(Declare, MultiChild)] -pub struct MockStack { - child_pos: Vec, -} +pub struct MockStack {} impl Render for MockStack { fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { let mut size = ZERO_SIZE; let (ctx, children) = ctx.split_children(); - for (i, c) in children.enumerate() { - let mut child_size = ctx.perform_child_layout(c, clamp); - if let Some(offset) = self.child_pos.get(i) { - ctx.update_position(c, *offset); - child_size = Size::new(offset.x + child_size.width, offset.y + child_size.height); - } else { - ctx.update_position(c, Point::zero()); - } + for c in children { + let child_size = ctx.perform_child_layout(c, clamp); size = size.max(child_size); } @@ -347,16 +339,16 @@ impl LayoutCase { let info = wnd.layout_info_by_path(path).unwrap(); if let Some(x) = x { - assert_eq!(info.pos.x, *x, "unexpected x"); + assert_eq!(*x, info.pos.x, "unexpected x"); } if let Some(y) = y { - assert_eq!(info.pos.y, *y, "unexpected y"); + assert_eq!(*y, info.pos.y, "unexpected y"); } if let Some(w) = width { - assert_eq!(info.size.unwrap().width, *w, "unexpected width"); + assert_eq!(*w, info.size.unwrap().width, "unexpected width"); } if let Some(h) = height { - assert_eq!(info.size.unwrap().height, *h, "unexpected height"); + assert_eq!(*h, info.size.unwrap().height, "unexpected height"); } } } diff --git a/core/src/widget.rs b/core/src/widget.rs index 5670de0ce..0c4b56962 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -33,22 +33,18 @@ pub trait Render: 'static { /// children's perform_layout across the `LayoutCtx` fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size; - /// `paint` is a low level trait to help you draw your widget to paint device - /// across `PaintingCtx::painter` by itself coordinate system. Not care - /// about children's paint in this method, framework will call children's - /// paint individual. And framework guarantee always paint parent before - /// children. - fn paint(&self, ctx: &mut PaintingCtx); + /// Draw the widget on the paint device using `PaintingCtx::painter` within + /// its own coordinate system. This method should not handle painting of + /// children; the framework will handle painting of children individually. The + /// framework ensures that the parent is always painted before its children. + fn paint(&self, _: &mut PaintingCtx) {} /// Whether the constraints from parent are the only input to detect the /// widget size, and child nodes' size not affect its size. fn only_sized_by_parent(&self) -> bool { false } /// Determines the set of render widgets located at the given position. - fn hit_test(&self, ctx: &HitTestCtx, pos: Point) -> HitTest { - let is_hit = hit_test_impl(ctx, pos); - HitTest { hit: is_hit, can_hit_child: is_hit } - } + fn hit_test(&self, ctx: &HitTestCtx, pos: Point) -> HitTest { ctx.box_hit_test(pos) } fn get_transform(&self) -> Option { None } } @@ -344,9 +340,3 @@ impl Widget<'static> + 'static> From for GenWidget #[inline] fn from(f: F) -> Self { Self::new(f) } } - -pub(crate) fn hit_test_impl(ctx: &HitTestCtx, pos: Point) -> bool { - ctx - .box_rect() - .map_or(false, |rect| rect.contains(pos)) -} diff --git a/core/src/widget_children/compose_child_impl.rs b/core/src/widget_children/compose_child_impl.rs index c8bef4397..147cd70f1 100644 --- a/core/src/widget_children/compose_child_impl.rs +++ b/core/src/widget_children/compose_child_impl.rs @@ -24,6 +24,31 @@ where fn with_child(self, c: C) -> Self::Target { Pair { parent: self, child: c.into_child() } } } +impl<'w, T, C, const M: usize> WithChild<'w, C, 2, M> for Option +where + T: StateWriter + 'static, + T::Value: ComposeChild<'w, Child = Widget<'w>>, + C: IntoChild, M> + 'w, +{ + type Target = Widget<'w>; + + fn with_child(self, c: C) -> Self::Target { + if let Some(p) = self { p.with_child(c).into_widget() } else { c.into_child() } + } +} + +impl<'w, T, C, const M: usize> WithChild<'w, C, 3, M> for Option +where + T: ComposeChild<'w, Child = Widget<'w>> + 'static, + C: IntoChild, M> + 'w, +{ + type Target = Widget<'w>; + + fn with_child(self, c: C) -> Self::Target { + if let Some(p) = self { p.with_child(c).into_widget() } else { c.into_child() } + } +} + stateless_with_child!(3); impl<'w, P, T, C, const M: usize> WithChild<'w, C, 4, M> for P diff --git a/core/src/widget_tree.rs b/core/src/widget_tree.rs index c8f8a90db..7a15c5dcd 100644 --- a/core/src/widget_tree.rs +++ b/core/src/widget_tree.rs @@ -149,14 +149,17 @@ impl WidgetTree { continue; } - let mut relayout_root = *id; if let Some(info) = self.store.get_mut(id) { info.size.take(); } + let mut relayout_root = *id; // All ancestors of this render widget should relayout until the one which only // sized by parent. for p in id.0.ancestors(&self.arena).skip(1).map(WidgetId) { + // The first one may be a pipe that is newly generated. Otherwise, if there + // isn't layout information, it indicates that the ancestor marked for relayout + // already. if self.store.layout_box_size(p).is_none() { break; } @@ -166,8 +169,7 @@ impl WidgetTree { info.size.take(); } - let r = p.assert_get(self); - if r.only_sized_by_parent() { + if p.assert_get(self).only_sized_by_parent() { break; } } diff --git a/core/src/widget_tree/layout_info.rs b/core/src/widget_tree/layout_info.rs index 6a882b28b..bda30ee67 100644 --- a/core/src/widget_tree/layout_info.rs +++ b/core/src/widget_tree/layout_info.rs @@ -96,7 +96,7 @@ impl LayoutStore { self.layout_info(id).and_then(|info| info.size) } - pub(crate) fn layout_box_position(&self, id: WidgetId) -> Option { + pub(crate) fn layout_box_pos(&self, id: WidgetId) -> Option { self.layout_info(id).map(|info| info.pos) } @@ -113,7 +113,7 @@ impl WidgetTree { pub(crate) fn map_to_parent(&self, id: WidgetId, pos: Point) -> Point { self .store - .layout_box_position(id) + .layout_box_pos(id) .map_or(pos, |offset| { let pos = id .assert_get(self) @@ -126,7 +126,7 @@ impl WidgetTree { pub(crate) fn map_from_parent(&self, id: WidgetId, pos: Point) -> Point { self .store - .layout_box_position(id) + .layout_box_pos(id) .map_or(pos, |offset| { let pos = pos - offset.to_vector(); id.assert_get(self) diff --git a/core/src/widget_tree/widget_id.rs b/core/src/widget_tree/widget_id.rs index 3dae8de74..f528718f5 100644 --- a/core/src/widget_tree/widget_id.rs +++ b/core/src/widget_tree/widget_id.rs @@ -14,9 +14,13 @@ use crate::{ pub struct WidgetId(pub(crate) NodeId); -pub trait RenderQueryable: Render + Query {} +pub trait RenderQueryable: Render + Query { + fn as_render(&self) -> &dyn Render; +} -impl RenderQueryable for T {} +impl RenderQueryable for T { + fn as_render(&self) -> &dyn Render { self } +} impl WidgetId { /// Returns a reference to the node data. diff --git a/core/src/window.rs b/core/src/window.rs index 288162b8e..fa0676f56 100644 --- a/core/src/window.rs +++ b/core/src/window.rs @@ -602,7 +602,13 @@ impl Window { self.tree().map_to_global(point, id) } - pub fn layout_size(&self, id: WidgetId) -> Option { self.tree().store.layout_box_size(id) } + pub fn map_from_global(&self, point: Point, id: WidgetId) -> Point { + self.tree().map_from_global(point, id) + } + + pub fn widget_size(&self, id: WidgetId) -> Option { self.tree().store.layout_box_size(id) } + + pub fn widget_pos(&self, id: WidgetId) -> Option { self.tree().store.layout_box_pos(id) } pub(crate) fn tree(&self) -> &WidgetTree { // Safety: Please refer to the comments in `WidgetTree::tree_mut` for more diff --git a/core/src/wrap_render.rs b/core/src/wrap_render.rs new file mode 100644 index 000000000..6d261270b --- /dev/null +++ b/core/src/wrap_render.rs @@ -0,0 +1,123 @@ +use std::any::TypeId; + +use ribir_geom::{Point, Size, Transform}; +use smallvec::SmallVec; +use widget_id::RenderQueryable; + +use crate::prelude::*; + +/// This trait is for a render widget that does not need to be an independent +/// node in the widget tree. It can serve as a wrapper for another render +/// widget. +/// +/// # Which widgets should implement this trait? +/// +/// If a render widget accepts a single child and its layout size matches its +/// child size, it can be implemented as a `WrapRender` instead of `Render`, +/// eliminating the need to allocate a node in the widget tree. +pub trait WrapRender { + fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size; + + fn paint(&self, host: &dyn Render, ctx: &mut PaintingCtx) { host.paint(ctx) } + + fn only_sized_by_parent(&self, host: &dyn Render) -> bool { + // Detected by its host by default, so we return true here. + host.only_sized_by_parent() + } + + fn hit_test(&self, host: &dyn Render, ctx: &HitTestCtx, pos: Point) -> HitTest { + host.hit_test(ctx, pos) + } + + fn get_transform(&self, host: &dyn Render) -> Option { host.get_transform() } + + fn combine_child(this: impl StateWriter, child: Widget) -> Widget + where + Self: Sized + 'static, + { + child.on_build(move |id, ctx| { + let mut modifies = None; + id.wrap_node(ctx.tree_mut(), |r| match this.try_into_value() { + Ok(this) => Box::new(RenderPair { wrapper: Box::new(this), host: r }), + Err(this) => { + let reader = match this.into_reader() { + Ok(r) => r, + Err(s) => { + modifies = Some(s.raw_modifies()); + s.clone_reader() + } + }; + Box::new(RenderPair { wrapper: Box::new(reader), host: r }) + } + }); + if let Some(modifies) = modifies { + id.dirty_subscribe(modifies, ctx); + } + }) + } +} + +struct RenderPair { + wrapper: Box, + host: Box, +} + +impl Query for RenderPair { + fn query_all<'q>(&'q self, type_id: TypeId, out: &mut SmallVec<[QueryHandle<'q>; 1]>) { + self.host.query_all(type_id, out) + } + + fn query(&self, type_id: TypeId) -> Option { self.host.query(type_id) } + + fn query_write(&self, type_id: TypeId) -> Option { self.host.query_write(type_id) } + + fn queryable(&self) -> bool { self.host.queryable() } +} + +impl Render for RenderPair { + fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { + self + .wrapper + .perform_layout(clamp, self.host.as_render(), ctx) + } + + fn paint(&self, ctx: &mut PaintingCtx) { self.wrapper.paint(self.host.as_render(), ctx); } + + fn only_sized_by_parent(&self) -> bool { + self + .wrapper + .only_sized_by_parent(self.host.as_render()) + } + + fn hit_test(&self, ctx: &HitTestCtx, pos: Point) -> HitTest { + self + .wrapper + .hit_test(self.host.as_render(), ctx, pos) + } + + fn get_transform(&self) -> Option { self.wrapper.get_transform(self.host.as_render()) } +} + +impl WrapRender for R +where + R: StateReader, + R::Value: WrapRender, +{ + fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size { + self.read().perform_layout(clamp, host, ctx) + } + + fn paint(&self, host: &dyn Render, ctx: &mut PaintingCtx) { self.read().paint(host, ctx) } + + fn only_sized_by_parent(&self, host: &dyn Render) -> bool { + self.read().only_sized_by_parent(host) + } + + fn hit_test(&self, host: &dyn Render, ctx: &HitTestCtx, pos: Point) -> HitTest { + self.read().hit_test(host, ctx, pos) + } + + fn get_transform(&self, host: &dyn Render) -> Option { + self.read().get_transform(host) + } +} diff --git a/examples/storybook/src/storybook.rs b/examples/storybook/src/storybook.rs index dd7e7d0d2..a7719f766 100644 --- a/examples/storybook/src/storybook.rs +++ b/examples/storybook/src/storybook.rs @@ -3,18 +3,11 @@ use ribir::{material::material_svgs, prelude::*}; static NORMAL_BUTTON_SIZE: Size = Size::new(120., 40.); fn header() -> Widget<'static> { - static HEADER_HEIGHT: f32 = 64.; static TITLE: &str = "Material Design"; fn_widget! { - @ConstrainedBox { - clamp: BoxClamp::fixed_height(HEADER_HEIGHT), - @Row { - v_align: VAlign::Center, - justify_content: JustifyContent::SpaceAround, - @Text { - text: TITLE, - } - } + @Text { + margin: EdgeInsets::vertical(22.), + text: TITLE } } .into_widget() @@ -25,17 +18,16 @@ fn content() -> Widget<'static> { fn_widget! { @Scrollbar { @Column { + clamp: BoxClamp::EXPAND_X, + align_items: Align::Center, @Column { align_items: Align::Center, - @ConstrainedBox { + @Row { clamp: BoxClamp::fixed_height(30.), - @Row { - h_align: HAlign::Center, - @Text { text: "Common buttons" } - @Icon { - size: Size::splat(16.), - @ { material_svgs::INFO } - } + @Text { text: "Common buttons" } + @Icon { + size: Size::splat(16.), + @ { material_svgs::INFO } } } @Column { @@ -102,7 +94,6 @@ fn content() -> Widget<'static> { @ConstrainedBox { clamp: BoxClamp::fixed_height(30.), @Row { - h_align: HAlign::Center, @Text { text: "Floating action buttons" } @Icon { size: Size::splat(16.), @@ -136,7 +127,6 @@ fn content() -> Widget<'static> { @ConstrainedBox { clamp: BoxClamp::fixed_height(30.), @Row { - h_align: HAlign::Center, @Text { text: "Icon buttons" } @Icon { size: Size::splat(16.), @@ -338,6 +328,8 @@ fn content() -> Widget<'static> { pub fn storybook(ctx: &mut BuildCtx) -> Widget<'static> { let f = fn_widget! { @Column { + clamp: BoxClamp::EXPAND_X, + align_items: Align::Center, background: Palette::of(ctx!()).surface_container_low(), @ { header() } @Expanded { diff --git a/macros/src/declare_derive.rs b/macros/src/declare_derive.rs index 9be5bac3f..4e8925010 100644 --- a/macros/src/declare_derive.rs +++ b/macros/src/declare_derive.rs @@ -483,7 +483,10 @@ pub(crate) fn declare_derive(input: &mut syn::DeriveInput) -> syn::Result(mut self, v: impl DeclareInto) -> Self { self.fat_obj = self.fat_obj.anchor(v); self diff --git a/test_cases/todos/tests/todos_with_default_by_wgpu.png b/test_cases/todos/tests/todos_with_default_by_wgpu.png index e5b8369fc133a36c8262edbab443d5c55bb2d267..942b5d20437116f665f5e13bb8e23afa49de1c5a 100644 GIT binary patch delta 236 zcmeBA?N8lsgj3OV-{Q2!iSKqkk4xJunSO50Gq8jHb83bEZO>f$@$LP8yc4^cy$WWKD?|UE j518yIsbP0l+XkKRTXYH delta 236 zcmV0^50;y`wE}|1cu6ggR`Fux&Z_b{5QC>T@D`s1z(Eb zzrmA%55xs>RQ{X1lS&ZN1MHSp_7UeBRfBg z%2og2?z!h)X0sV*&Yb3|tFF50|2QgF{fBF<@$KLKja+rrRagB#K;^3ca5vs~16N&j m)m8sbP?JFwDzg$4X&w|f2Y_kgweGV30000=` diff --git a/test_cases/todos/tests/todos_with_material_by_wgpu.png b/test_cases/todos/tests/todos_with_material_by_wgpu.png index 809183d6e7dea1c8dd2d4bd8211132a870fc6a92..0ca1ac1b91af7cd86ccbe1efdf8f417207b7ee9a 100644 GIT binary patch delta 4788 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 P+xW(o*RQ>{v-p1jF)L0X delta 4785 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 Y!=Ha#u;J_(2>fhVx9P3=H$Oc1FLzH_jQ{`u diff --git a/tests/rdl_macro_test.rs b/tests/rdl_macro_test.rs index c9bc7d746..99ec64046 100644 --- a/tests/rdl_macro_test.rs +++ b/tests/rdl_macro_test.rs @@ -211,7 +211,7 @@ fn pipe_single_parent() { if *$outside_blank { BoxedSingleChild::new(Margin { margin: edges }) } else { - BoxedSingleChild::new(Padding { padding: edges }) + BoxedSingleChild::new(Void { }) } }; rdl!{ diff --git a/themes/material/src/classes/scrollbar_cls.rs b/themes/material/src/classes/scrollbar_cls.rs index 6cfe3614a..e8c3c03e4 100644 --- a/themes/material/src/classes/scrollbar_cls.rs +++ b/themes/material/src/classes/scrollbar_cls.rs @@ -80,7 +80,6 @@ fn base_track(w: Widget) -> Widget { let color = Palette::of(ctx!()).primary_container(); pipe!(if $w.mouse_hover() { color } else { color.with_alpha(0.)}) }, - h_align: HAlign::Right, on_disposed: move |_| u.unsubscribe(), }; // Smoothly display the background. diff --git a/widgets/src/buttons.rs b/widgets/src/buttons.rs index 4754ab999..ae36edae6 100644 --- a/widgets/src/buttons.rs +++ b/widgets/src/buttons.rs @@ -57,7 +57,7 @@ impl ComposeChild<'static> for ButtonImpl { clamp: pipe!(BoxClamp::min_width($this.min_width) .with_fixed_height($this.height)), @{ - let padding = pipe!($this.padding_style.map(|padding| Padding { padding })); + let padding = $this.padding_style.map(Padding::new); let icon = icon.map(|icon| @Icon { size: pipe!($this.icon_size), @{ icon } diff --git a/widgets/src/input.rs b/widgets/src/input.rs index 84cec59cd..cf7b264e7 100644 --- a/widgets/src/input.rs +++ b/widgets/src/input.rs @@ -234,7 +234,7 @@ where let pos = window.map_to_global(Point::zero(), wid.assert_id()); let size = window - .layout_size(wid.assert_id()) + .widget_size(wid.assert_id()) .unwrap_or_default(); window.set_ime_cursor_area(&Rect::new(pos, size)); @@ -250,7 +250,7 @@ where .subscribe(move |_| { let pos = window.map_to_global(Point::zero(), wid.assert_id()); let size = window - .layout_size(wid.assert_id()) + .widget_size(wid.assert_id()) .unwrap_or_default(); window.set_ime_cursor_area(&Rect::new(pos, size)); }) @@ -270,6 +270,7 @@ impl ComposeChild<'static> for Input { text_style: pipe!($this.style.clone()), }; @FocusScope { + can_focus: true, @ConstrainedBox { clamp: pipe!(size_clamp(&$this.style, Some(1.), $this.size)), @ { @@ -307,6 +308,7 @@ impl ComposeChild<'static> for TextArea { false => Scrollable::Both, }); @FocusScope { + can_focus: true, @ConstrainedBox { clamp: pipe!(size_clamp(&$this.style, $this.rows, $this.cols)), @EditableTextExtraWidget::edit_area( diff --git a/widgets/src/layout/only_sized_by_parent.rs b/widgets/src/layout/only_sized_by_parent.rs index 98ab6029b..fb5b08d5c 100644 --- a/widgets/src/layout/only_sized_by_parent.rs +++ b/widgets/src/layout/only_sized_by_parent.rs @@ -6,6 +6,12 @@ use ribir_core::prelude::*; #[derive(SingleChild, Declare)] pub struct OnlySizedByParent {} +// `OnlySizedByParent` must be an independent node in the widget tree. +// Therefore, any modifications to its child should terminate at +// `OnlySizedByParent`. Otherwise, if its host is dirty, it implies that the +// `OnlySizedByParent` node is also dirty, and its parent must be marked as +// dirty. For instance, if `w2` in a Row[w1, OnlySizedByParent] is dirty, +// the Row requires a relayout. impl Render for OnlySizedByParent { fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { ctx @@ -14,10 +20,6 @@ impl Render for OnlySizedByParent { } fn only_sized_by_parent(&self) -> bool { true } - - fn paint(&self, _: &mut PaintingCtx) { - // nothing to paint. - } } #[cfg(test)] diff --git a/widgets/src/layout/sized_box.rs b/widgets/src/layout/sized_box.rs index c0c9fb810..df03c7529 100644 --- a/widgets/src/layout/sized_box.rs +++ b/widgets/src/layout/sized_box.rs @@ -12,15 +12,14 @@ pub struct SizedBox { impl Render for SizedBox { #[inline] - fn perform_layout(&self, _: BoxClamp, ctx: &mut LayoutCtx) -> Size { - ctx.perform_single_child_layout(BoxClamp { min: self.size, max: self.size }); - self.size + fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { + let size = clamp.clamp(self.size); + ctx.perform_single_child_layout(BoxClamp { min: size, max: size }); + size } - #[inline] - fn only_sized_by_parent(&self) -> bool { true } #[inline] - fn paint(&self, _: &mut PaintingCtx) {} + fn only_sized_by_parent(&self) -> bool { true } } #[cfg(test)] @@ -65,7 +64,7 @@ mod tests { }) .with_wnd_size(Size::new(500., 500.)), LayoutCase::default().with_size(Size::new(500., 500.)), - LayoutCase::new(&[0, 0]).with_size(INFINITY_SIZE) + LayoutCase::new(&[0, 0]).with_size(Size::new(500., 500.)) ); widget_layout_test!( diff --git a/widgets/src/layout/stack.rs b/widgets/src/layout/stack.rs index 88cc14b15..3325a5153 100644 --- a/widgets/src/layout/stack.rs +++ b/widgets/src/layout/stack.rs @@ -37,7 +37,7 @@ pub enum StackFit { impl Render for Stack { fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size { - let clamp = match self.fit { + let stack_clamp = match self.fit { StackFit::Loose => clamp.loose(), StackFit::Expand => BoxClamp { min: clamp.max, max: clamp.max }, StackFit::Passthrough => clamp, @@ -46,11 +46,10 @@ impl Render for Stack { let mut size = ZERO_SIZE; let (ctx, children) = ctx.split_children(); for c in children { - let child_size = ctx.perform_child_layout(c, clamp); + let child_size = ctx.perform_child_layout(c, stack_clamp); size = size.max(child_size); } - - size + clamp.clamp(size) } fn paint(&self, _: &mut PaintingCtx) { diff --git a/widgets/src/lists.rs b/widgets/src/lists.rs index 08bad90c3..752b9eb4b 100644 --- a/widgets/src/lists.rs +++ b/widgets/src/lists.rs @@ -270,8 +270,8 @@ impl<'c> ComposeChild<'c> for ListItem { item_align, } = ListItemStyle::of(ctx!()); - let padding = padding_style.map(|padding| Padding { padding }); - let label_gap = label_gap.map(|padding| Padding { padding }); + let padding = padding_style.map(Padding::new); + let label_gap = label_gap.map(Padding::new ); @ListItemDecorator { color: pipe!($this.active_background), diff --git a/widgets/src/text.rs b/widgets/src/text.rs index b5c56a6c2..14d8e4733 100644 --- a/widgets/src/text.rs +++ b/widgets/src/text.rs @@ -25,11 +25,12 @@ impl VisualText for Text { impl Render for Text { fn perform_layout(&self, clamp: BoxClamp, _: &mut LayoutCtx) -> Size { - self + let size = self .text_layout(AppCtx::typography_store(), clamp.max) .visual_rect() .size - .cast_unit() + .cast_unit(); + clamp.clamp(size) } #[inline]