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

Combine render widget #626

Merged
merged 2 commits into from
Sep 19, 2024
Merged
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: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,21 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he

## [@Unreleased] - @ReleaseDate

### Features

- **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

- **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
Expand Down
91 changes: 42 additions & 49 deletions core/src/builtin_widgets/align.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -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,
}
Expand All @@ -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<Value = Self>, 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<Value = Self>, 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)
}
}

Expand Down Expand Up @@ -181,34 +180,32 @@ 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.)
);

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.)
);

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.)
);

Expand All @@ -225,34 +222,30 @@ 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.)
);

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.)
);

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.)
);

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))
);
}
42 changes: 26 additions & 16 deletions core/src/builtin_widgets/anchor.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -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,
}
Expand All @@ -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<Value = Self>, 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
Expand All @@ -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<Point> for Anchor {
fn from(value: Point) -> Self {
Anchor { x: Some(HAnchor::Left(value.x)), y: Some(VAnchor::Top(value.y)) }
}
}

Expand All @@ -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())
);
}
23 changes: 16 additions & 7 deletions core/src/builtin_widgets/box_decoration.rs
Original file line number Diff line number Diff line change
@@ -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<Brush>,
Expand Down Expand Up @@ -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<Value = Self>, 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);
Expand All @@ -58,6 +66,7 @@ impl Render for BoxDecoration {
painter.fill();
}
self.paint_border(painter, &rect);
host.paint(ctx)
}
}
}
Expand Down Expand Up @@ -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.))
);
}
Loading
Loading