Skip to content

Commit

Permalink
refactor(core): 💡 lazy build widget
Browse files Browse the repository at this point in the history
  • Loading branch information
M-Adoo authored and rchangelog[bot] committed Aug 1, 2024
1 parent bc04965 commit 194920e
Show file tree
Hide file tree
Showing 103 changed files with 2,070 additions and 2,059 deletions.
21 changes: 12 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,31 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he

### Features

- **core**: Introduced `IntoWidget` and `IntoChild`. (@M-Adoo #pr)
- **core**: Introduced `IntoWidget` and `IntoChild`. (@M-Adoo #612)

The `IntoWidget` trait allows for the conversion of any widget to the type `Widget`.
The `IntoChild` trait provides a way to convert a more general widget into a child of `ComposeChild`.
The `IntoChild` trait provides a way to convert a more general type into a child of `ComposeChild`.

### Fixed

**core**: The generation of a pipe widget from another pipe widget may potentially result in a crash. (#pr, @M-Adoo)
**core**: The generation of a pipe widget from another pipe widget may potentially result in a crash. (#612, @M-Adoo)

### Changed

- **core**: Simplify the implementation of parent composition with child widgets. (#pr, @M-Adoo)
- **core**: Lazy build the widget tree. (#612, @M-Adoo)
- **core**: Simplify the implementation of parent composition with child widgets. (#612, @M-Adoo)

Merge `SingleWithChild`, `MultiWithChild`, and `ComposeWithChild` into a single trait called WithChild.

### Breaking

- Removed `ChildFrom` and `FromAnother` traits (#pr @M-Adoo)
- Removed `SingleParent` and `MultiParent` traits. (#pr @M-Adoo)
- Removed `PairChild` and `PairWithChild` traits. User can use a generic type instead. (#pr @M-Adoo)
- Allow only the child to be converted to a widget or a type that implements the Into trait. (#pr @M-Adoo)
- Removed the all builder traits such as WidgetBuilder and ComposeBuilder and so on. (#pr @M-Adoo)
- Removed `ChildFrom` and `FromAnother` traits (#612 @M-Adoo)
- Removed `SingleParent` and `MultiParent` traits. (#612 @M-Adoo)
- Removed `PairChild` and `PairWithChild` traits. User can use a generic type instead. (#612 @M-Adoo)
- Allow only the child to be converted to a widget or a type that implements the Into trait. (#612 @M-Adoo)
- Removed the all builder traits such as WidgetBuilder and ComposeBuilder and so on. (#612 @M-Adoo)
- All implicit child conversions have been removed, except for conversions to Widget. (#612 @M-Adoo)


## [0.4.0-alpha.3] - 2024-06-26

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,17 @@ fn main() {
let inc_btn = FilledButton::declarer()
.on_tap(move |_| *c_cnt.write() += 1)
.finish(ctx)
.with_child(Label::new("Inc"), ctx);
.with_child(Label::new("Inc"));

let counter = H1::declarer()
.text(pipe!($cnt.to_string()))
.finish(ctx);

Row::declarer()
.finish(ctx)
.with_child(inc_btn, ctx)
.with_child(counter, ctx)
.build(ctx)
.with_child(inc_btn)
.with_child(counter)
.into_widget()
};

App::run(counter);
Expand Down
3 changes: 2 additions & 1 deletion core/src/animation/stagger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ mod tests {
use super::*;
use crate::{reset_test_env, test_helper::*};

fn stagger_run_and_stop() -> impl IntoWidgetStrict<FN> {
fn stagger_run_and_stop() -> Widget<'static> {
fn_widget! {
let stagger = Stagger::new(Duration::from_millis(100), transitions::EASE_IN.of(ctx!()));
let mut mock_box = @MockBox { size: Size::new(100., 100.) };
Expand Down Expand Up @@ -253,6 +253,7 @@ mod tests {

mock_box
}
.into_widget()
}
widget_layout_test!(stagger_run_and_stop, width == 100., height == 100.,);

Expand Down
104 changes: 59 additions & 45 deletions core/src/builtin_widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ pub struct LazyWidgetId(Sc<Cell<Option<WidgetId>>>);
/// let w = multi.get_margin_widget().clone_writer();
/// multi
/// .on_tap(move |_| w.write().margin = EdgeInsets::all(20.))
/// .into_widget(ctx)
/// .into_widget()
/// };
/// ```
pub struct FatObj<T> {
Expand Down Expand Up @@ -125,11 +125,34 @@ pub struct FatObj<T> {
}

impl LazyWidgetId {
pub fn id(&self) -> Option<WidgetId> { self.0.get() }
/// Creates a new `LazyWidgetId` associated with a widget. You can retrieve
/// the widget's ID after the build process using this `LazyWidgetId`.
pub fn new(widget: Widget) -> (Widget, Self) {
let lazy_id = Self(<_>::default());
let w = lazy_id.clone().bind(widget);
(w, lazy_id)
}

/// Bind a widget to the LazyWidgetId, and return a widget that will set the
/// id to the LazyWidgetId after build.
pub fn bind(self, widget: Widget) -> Widget {
let f = move |ctx: &BuildCtx| {
let id = widget.build(ctx);
assert!(self.id().is_none(), "The LazyWidgetID only allows binding to one widget.");
self.0.set(Some(id));
id
};

pub fn assert_id(&self) -> WidgetId { self.0.get().unwrap() }
InnerWidget::LazyBuild(Box::new(f)).into()
}

fn set(&self, wid: WidgetId) { self.0.set(Some(wid)); }
pub fn id(&self) -> Option<WidgetId> { self.0.get() }

pub fn assert_id(&self) -> WidgetId {
self.0.get().expect(
"The binding is not associated with a widget, or the bound widget has not been built yet.",
)
}

fn ref_count(&self) -> usize { self.0.ref_count() }
}
Expand Down Expand Up @@ -845,97 +868,88 @@ impl<T> ObjDeclarer for FatObj<T> {
fn finish(self, _: &BuildCtx) -> Self::Target { self }
}

impl<T, const M: usize> IntoWidgetStrict<M> for FatObj<T>
impl<'w, T, const M: usize> IntoWidgetStrict<'w, M> for FatObj<T>
where
T: IntoWidget<M>,
T: IntoWidget<'w, M>,
{
#[inline]
fn into_widget_strict(self, ctx: &BuildCtx) -> Widget {
let mut host = self.host.into_widget(ctx);
self.host_id.set(host.id());
fn into_widget_strict(self) -> Widget<'w> { self.map(|w| w.into_widget()).compose() }
}

impl<'a> FatObj<Widget<'a>> {
fn compose(self) -> Widget<'a> {
let mut host = self.host;
host = self.host_id.clone().bind(host);
if let Some(mix_builtin) = self.mix_builtin {
host = mix_builtin.with_child(host, ctx).into_widget(ctx)
host = mix_builtin.with_child(host).into_widget()
}
if let Some(request_focus) = self.request_focus {
host = request_focus
.with_child(host, ctx)
.into_widget(ctx);
host = request_focus.with_child(host).into_widget();
}
if let Some(has_focus) = self.has_focus {
host = has_focus.with_child(host, ctx).into_widget(ctx);
host = has_focus.with_child(host).into_widget();
}
if let Some(mouse_hover) = self.mouse_hover {
host = mouse_hover.with_child(host, ctx).into_widget(ctx);
host = mouse_hover.with_child(host).into_widget();
}
if let Some(pointer_pressed) = self.pointer_pressed {
host = pointer_pressed
.with_child(host, ctx)
.into_widget(ctx);
host = pointer_pressed.with_child(host).into_widget();
}
if let Some(fitted_box) = self.fitted_box {
host = fitted_box.with_child(host, ctx).into_widget(ctx);
host = fitted_box.with_child(host).into_widget();
}
if let Some(box_decoration) = self.box_decoration {
host = box_decoration
.with_child(host, ctx)
.into_widget(ctx);
host = box_decoration.with_child(host).into_widget();
}
if let Some(padding) = self.padding {
host = padding.with_child(host, ctx).into_widget(ctx);
host = padding.with_child(host).into_widget();
}
if let Some(layout_box) = self.layout_box {
host = layout_box.with_child(host, ctx).into_widget(ctx);
host = layout_box.with_child(host).into_widget();
}
if let Some(cursor) = self.cursor {
host = cursor.with_child(host, ctx).into_widget(ctx);
host = cursor.with_child(host).into_widget();
}
if let Some(margin) = self.margin {
host = margin.with_child(host, ctx).into_widget(ctx);
host = margin.with_child(host).into_widget();
}
if let Some(scrollable) = self.scrollable {
host = scrollable.with_child(host, ctx).into_widget(ctx);
host = scrollable.with_child(host).into_widget();
}
if let Some(transform) = self.transform {
host = transform.with_child(host, ctx).into_widget(ctx);
host = transform.with_child(host).into_widget();
}
if let Some(h_align) = self.h_align {
host = h_align.with_child(host, ctx).into_widget(ctx);
host = h_align.with_child(host).into_widget();
}
if let Some(v_align) = self.v_align {
host = v_align.with_child(host, ctx).into_widget(ctx);
host = v_align.with_child(host).into_widget();
}
if let Some(relative_anchor) = self.relative_anchor {
host = relative_anchor
.with_child(host, ctx)
.into_widget(ctx);
host = relative_anchor.with_child(host).into_widget();
}
if let Some(global_anchor) = self.global_anchor {
host = global_anchor
.with_child(host, ctx)
.into_widget(ctx);
host = global_anchor.with_child(host).into_widget();
}
if let Some(visibility) = self.visibility {
host = visibility.with_child(host, ctx).into_widget(ctx);
host = visibility.with_child(host).into_widget();
}
if let Some(opacity) = self.opacity {
host = opacity.with_child(host, ctx).into_widget(ctx);
host = opacity.with_child(host).into_widget();
}
if let Some(keep_alive) = self.keep_alive {
host = keep_alive.with_child(host, ctx).into_widget(ctx);
host = keep_alive.with_child(host).into_widget();
}
if let Some(h) = self.keep_alive_unsubscribe_handle {
let arena = &mut ctx.tree.borrow_mut().arena;
host.id().attach_anonymous_data(h, arena);
host = host.attach_anonymous_data(h);
}
self.id.set(host.id());
let host = self.id.clone().bind(host);
host
}
}

impl FatObj<()> {
#[inline]
#[track_caller]
pub fn with_child<C>(self, child: C, _: &BuildCtx) -> FatObj<C> { self.map(move |_| child) }
pub fn with_child<C>(self, child: C) -> FatObj<C> { self.map(move |_| child) }
}

impl<T> std::ops::Deref for FatObj<T> {
Expand Down
20 changes: 10 additions & 10 deletions core/src/builtin_widgets/align.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,47 +168,47 @@ mod tests {
const CHILD_SIZE: Size = Size::new(10., 10.);
const WND_SIZE: Size = Size::new(100., 100.);

fn h_align(h_align: HAlign) -> impl IntoWidgetStrict<FN> {
fn h_align(h_align: HAlign) -> impl IntoWidget<'static, FN> {
fn_widget! {
@HAlignWidget {
h_align,
@MockBox { size: CHILD_SIZE }
}
}
}
fn left_align() -> impl IntoWidgetStrict<FN> { h_align(HAlign::Left) }
fn left_align() -> impl IntoWidget<'static, FN> { h_align(HAlign::Left) }
widget_layout_test!(
left_align,
wnd_size = WND_SIZE,
{ path = [0], width == 100., height == 10.,}
{ path = [0, 0], size == CHILD_SIZE, }
);

fn h_center_align() -> impl IntoWidgetStrict<FN> { h_align(HAlign::Center) }
fn h_center_align() -> impl IntoWidget<'static, FN> { h_align(HAlign::Center) }
widget_layout_test!(
h_center_align,
wnd_size = WND_SIZE,
{ path = [0], width == 100., height == 10.,}
{ path = [0, 0], x == 45., size == CHILD_SIZE,}
);

fn right_align() -> impl IntoWidgetStrict<FN> { h_align(HAlign::Right) }
fn right_align() -> impl IntoWidget<'static, FN> { h_align(HAlign::Right) }
widget_layout_test!(
right_align,
wnd_size = WND_SIZE,
{ path = [0], width == 100., height == 10.,}
{ path = [0, 0], x == 90., size == CHILD_SIZE,}
);

fn h_stretch_algin() -> impl IntoWidgetStrict<FN> { h_align(HAlign::Stretch) }
fn h_stretch_algin() -> impl IntoWidget<'static, FN> { h_align(HAlign::Stretch) }
widget_layout_test!(
h_stretch_algin,
wnd_size = WND_SIZE,
{ path = [0], width == 100., height == 10.,}
{ path = [0, 0], x == 0., width == 100., height == 10.,}
);

fn v_align(v_align: VAlign) -> impl IntoWidgetStrict<FN> {
fn v_align(v_align: VAlign) -> impl IntoWidget<'static, FN> {
fn_widget! {
@VAlignWidget {
v_align,
Expand All @@ -217,31 +217,31 @@ mod tests {
}
}

fn top_align() -> impl IntoWidgetStrict<FN> { v_align(VAlign::Top) }
fn top_align() -> impl IntoWidget<'static, FN> { v_align(VAlign::Top) }
widget_layout_test!(
top_align,
wnd_size = WND_SIZE,
{ path = [0], width == 10., height == 100.,}
{ path = [0, 0], size == CHILD_SIZE,}
);

fn v_center_align() -> impl IntoWidgetStrict<FN> { v_align(VAlign::Center) }
fn v_center_align() -> impl IntoWidget<'static, FN> { v_align(VAlign::Center) }
widget_layout_test!(
v_center_align,
wnd_size = WND_SIZE,
{ path = [0], width == 10., height == 100.,}
{ path = [0, 0], y == 45., size == CHILD_SIZE,}
);

fn bottom_align() -> impl IntoWidgetStrict<FN> { v_align(VAlign::Bottom) }
fn bottom_align() -> impl IntoWidget<'static, FN> { v_align(VAlign::Bottom) }
widget_layout_test!(
bottom_align,
wnd_size = WND_SIZE,
{ path = [0], width == 10., height == 100.,}
{ path = [0, 0], y == 90., size == CHILD_SIZE,}
);

fn v_stretch_align() -> impl IntoWidgetStrict<FN> { v_align(VAlign::Stretch) }
fn v_stretch_align() -> impl IntoWidget<'static, FN> { v_align(VAlign::Stretch) }
widget_layout_test!(
v_stretch_align,
wnd_size = WND_SIZE,
Expand Down
8 changes: 4 additions & 4 deletions core/src/builtin_widgets/anchor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ mod test {
const CHILD_SIZE: Size = Size::new(50., 50.);
const WND_SIZE: Size = Size::new(100., 100.);

fn pixel_left_top() -> impl IntoWidgetStrict<FN> {
fn pixel_left_top() -> impl IntoWidget<'static, FN> {
fn_widget! {
@MockBox {
size: CHILD_SIZE,
Expand All @@ -220,7 +220,7 @@ mod test {
{ path = [0, 0], x == 1., }
);

fn pixel_left_bottom() -> impl IntoWidgetStrict<FN> {
fn pixel_left_bottom() -> impl IntoWidget<'static, FN> {
fn_widget! {
@MockBox {
size: CHILD_SIZE,
Expand All @@ -235,7 +235,7 @@ mod test {
{ path = [0, 0], x == 1., }
);

fn pixel_top_right() -> impl IntoWidgetStrict<FN> {
fn pixel_top_right() -> impl IntoWidget<'static, FN> {
fn_widget! {
@MockBox {
size: CHILD_SIZE,
Expand All @@ -250,7 +250,7 @@ mod test {
{ path = [0, 0], x == 49.,}
);

fn pixel_bottom_right() -> impl IntoWidgetStrict<FN> {
fn pixel_bottom_right() -> impl IntoWidget<'static, FN> {
fn_widget! {
@MockBox {
size: CHILD_SIZE,
Expand Down
2 changes: 1 addition & 1 deletion core/src/builtin_widgets/box_decoration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ mod tests {
}

const SIZE: Size = Size::new(100., 100.);
fn with_border() -> impl IntoWidgetStrict<FN> {
fn with_border() -> impl IntoWidget<'static, FN> {
fn_widget! {
@MockBox {
size: SIZE,
Expand Down
Loading

0 comments on commit 194920e

Please sign in to comment.