From 6707b1b95610c1d4d0d6c88c8b7ef0c63f0e255d Mon Sep 17 00:00:00 2001 From: Adoo Date: Fri, 18 Aug 2023 19:49:07 +0800 Subject: [PATCH] =?UTF-8?q?refactor(ribir):=20=F0=9F=92=A1=20todos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/src/animation/transition.rs | 19 +-- core/src/builtin_widgets/cursor.rs | 72 ++------- core/src/builtin_widgets/key.rs | 10 +- core/src/declare.rs | 12 -- core/src/state.rs | 36 +++-- core/src/state/partial_state.rs | 73 +++++---- core/src/state/route_state.rs | 85 ++++++----- examples/greet/Cargo.toml | 21 --- examples/greet/README.md | 9 -- examples/greet/src/greet.rs | 68 --------- examples/greet/src/main.rs | 8 - examples/todos/src/todos.rs | 237 ++++++++++++++--------------- macros/src/declare_derive2.rs | 26 ++-- macros/src/declare_obj.rs | 8 +- macros/src/lib.rs | 14 +- macros/src/rdl_macro.rs | 4 +- macros/src/route_state_macro.rs | 72 +++++++++ macros/src/split_state_macro.rs | 97 ------------ painter/src/style.rs | 5 + themes/material/src/lib.rs | 8 +- themes/material/src/ripple.rs | 4 +- widgets/src/checkbox.rs | 2 +- widgets/src/text_field.rs | 6 +- 23 files changed, 364 insertions(+), 532 deletions(-) delete mode 100644 examples/greet/Cargo.toml delete mode 100644 examples/greet/README.md delete mode 100644 examples/greet/src/greet.rs delete mode 100644 examples/greet/src/main.rs create mode 100644 macros/src/route_state_macro.rs delete mode 100644 macros/src/split_state_macro.rs diff --git a/core/src/animation/transition.rs b/core/src/animation/transition.rs index a8d9561be..150fbf643 100644 --- a/core/src/animation/transition.rs +++ b/core/src/animation/transition.rs @@ -4,11 +4,12 @@ use std::{ops::Deref, rc::Rc, time::Duration}; /// Transition use rate to describe how the state change form init to final /// smoothly. -#[derive(Declare, Clone, Debug, PartialEq)] -pub struct Transition { +#[derive(Declare2, Clone, Debug, PartialEq)] +pub struct Transition { #[declare(default, convert=strip_option)] pub delay: Option, pub duration: Duration, + #[declare(strict)] pub easing: E, #[declare(default, convert=strip_option)] pub repeat: Option, @@ -86,6 +87,13 @@ impl Roc for Transition { } } +impl Roc for T +where + T::V: Roc, +{ + fn rate_of_change(&self, dur: Duration) -> AnimateProgress { self.as_ref().rate_of_change(dur) } +} + impl TransitionState for S {} impl TransitionState for LerpFnState where @@ -94,13 +102,6 @@ where { } -impl Roc for Stateful { - #[inline] - fn rate_of_change(&self, dur: Duration) -> AnimateProgress { - self.state_ref().rate_of_change(dur) - } -} - impl Roc for Box { #[inline] fn rate_of_change(&self, dur: Duration) -> AnimateProgress { self.deref().rate_of_change(dur) } diff --git a/core/src/builtin_widgets/cursor.rs b/core/src/builtin_widgets/cursor.rs index 3dc4d37f4..17e4b6f42 100644 --- a/core/src/builtin_widgets/cursor.rs +++ b/core/src/builtin_widgets/cursor.rs @@ -1,36 +1,31 @@ use crate::prelude::*; -use std::{cell::Cell, rc::Rc}; use winit::window::CursorIcon; /// `Cursor` is an attribute to assign an `cursor` to a widget. -#[derive(Declare, Debug, Declare2)] +#[derive(Declare, Default, Debug, Declare2)] pub struct Cursor { - #[declare(convert=custom, builtin, default)] - pub cursor: Rc>, + #[declare(builtin, default)] + pub cursor: CursorIcon, } impl ComposeChild for Cursor { type Child = Widget; - fn compose_child(this: State, child: Self::Child) -> Widget { - widget! { - states { - save_cursor: Stateful::new(CursorIcon::Default), - this: this.into_readonly() - } - DynWidget { - dyns: child, + fn compose_child(mut this: State, child: Self::Child) -> Widget { + fn_widget! { + let mut save_cursor = Stateful::new(CursorIcon::Default); + @$child { on_pointer_enter: move |e: &mut PointerEvent| { if e.point_type == PointerType::Mouse && e.mouse_buttons() == MouseButtons::empty() { let wnd = e.window(); - *save_cursor = wnd.get_cursor(); - wnd.set_cursor(this.cursor.get()); + *$save_cursor = wnd.get_cursor(); + wnd.set_cursor($this.get_cursor()); } }, on_pointer_leave: move |e: &mut PointerEvent| { - e.window().set_cursor(*save_cursor); + e.window().set_cursor(*$save_cursor); } } } @@ -38,53 +33,8 @@ impl ComposeChild for Cursor { } } -pub trait IntoCursorIcon { - fn into_cursor_icon(self) -> Rc>; -} - -impl IntoCursorIcon for Rc> { - #[inline] - fn into_cursor_icon(self) -> Rc> { self } -} - -impl IntoCursorIcon for CursorIcon { - #[inline] - fn into_cursor_icon(self) -> Rc> { Rc::new(Cell::new(self)) } -} - -impl CursorDeclarer { - #[inline] - pub fn cursor(mut self, icon: C) -> Self { - self.cursor = Some(icon.into_cursor_icon()); - self - } -} - impl Cursor { - #[inline] - pub fn set_declare_cursor(&mut self, icon: C) { - self.cursor = icon.into_cursor_icon(); - } -} - -impl Cursor { - #[inline] - pub fn icon(&self) -> CursorIcon { self.cursor.get() } - - #[inline] - pub fn set_icon(&self, icon: CursorIcon) { self.cursor.set(icon) } - - #[inline] - pub fn new_icon(icon: CursorIcon) -> Rc> { Rc::new(Cell::new(icon)) } -} - -impl Default for Cursor { - #[inline] - fn default() -> Self { - Cursor { - cursor: Rc::new(Cell::new(CursorIcon::Default)), - } - } + fn get_cursor(&self) -> CursorIcon { self.cursor } } #[cfg(test)] diff --git a/core/src/builtin_widgets/key.rs b/core/src/builtin_widgets/key.rs index 520b1e315..5e520e40d 100644 --- a/core/src/builtin_widgets/key.rs +++ b/core/src/builtin_widgets/key.rs @@ -66,15 +66,15 @@ pub struct KeyChange(pub Option, pub Option); impl Default for KeyChange { fn default() -> Self { KeyChange(None, None) } } -#[derive(Declare)] -pub struct KeyWidget { +#[derive(Declare, Declare2)] +pub struct KeyWidget { #[declare(convert=into)] pub key: Key, - pub value: Option, - #[declare(default)] + pub value: Option, + #[declare(skip)] before_value: Option, - #[declare(default)] + #[declare(skip)] status: KeyStatus, } diff --git a/core/src/declare.rs b/core/src/declare.rs index d36d77d6f..36f15a76f 100644 --- a/core/src/declare.rs +++ b/core/src/declare.rs @@ -67,23 +67,11 @@ impl> DeclareFrom for DeclareInit { fn declare_from(value: V) -> Self { Self::Value(value.into()) } } -impl> DeclareFrom> for DeclareInit> { - #[inline] - fn declare_from(value: V) -> Self { Self::Value(Some(value.into())) } -} - impl + 'static> DeclareFrom, Pipe<()>> for DeclareInit { #[inline] fn declare_from(value: Pipe) -> Self { Self::Pipe(value.map(U::from)) } } -impl + 'static> DeclareFrom, Option>> - for DeclareInit> -{ - #[inline] - fn declare_from(value: Pipe) -> Self { Self::Pipe(value.map(|v| Some(v.into()))) } -} - #[derive(Debug, PartialEq, Hash)] pub struct DeclareStripOption(O); diff --git a/core/src/state.rs b/core/src/state.rs index bf00bfe93..f38bcc10c 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -54,22 +54,31 @@ pub trait Share: Sized { /// origin state the return state will be notified. But when modifies the /// return state the origin state will not be notified. #[inline] - fn partial_state(&mut self, router: R) -> PartialState + fn partial_state( + &mut self, + router: R1, + router_mut: R2, + ) -> PartialState where - R: StateRouter, + R1: FnOnce(&Self::V) -> &Target + Copy, + R2: FnOnce(&mut Self::V) -> &mut Target + Copy, { - PartialState::new(self.clone_state(), router) + PartialState::new(self.clone_state(), router, router_mut) } /// Create a state that help you quick route to the part of the origin state /// before you access it. The return state and the origin state are the same /// state. So when one of them is modified, they will both be notified. - #[inline] - fn route_state(&mut self, router: R) -> RouteState + fn route_state( + &mut self, + router: R1, + router_mut: R2, + ) -> RouteState where - R: StateRouter, + R1: FnOnce(&Self::V) -> &Target + Copy, + R2: FnOnce(&mut Self::V) -> &mut Target + Copy, { - RouteState::new(self.clone_state(), router) + RouteState::new(self.clone_state(), router, router_mut) } /// Return the origin state of the state if this state is a partial state. @@ -103,15 +112,6 @@ pub trait RefShare: DerefMut { fn forget_modifies(&self) -> ModifyScope; } -/// A trait that to split part from a state. -pub trait StateRouter: Copy { - type Origin; - type Target; - - fn route(self, origin: &Self::Origin) -> &Self::Target; - fn route_mut(self, origin: &mut Self::Origin) -> &mut Self::Target; -} - /// Enum to store both stateless and stateful object. pub enum State { Stateless(W), @@ -224,9 +224,7 @@ impl State { pub fn clone_stateful(&mut self) -> Stateful { self.to_stateful().clone() } - pub fn stateful_ref(&mut self) -> TrackRef { self.to_stateful().state_ref() } - - pub fn to_stateful(&mut self) -> &mut Stateful { + fn to_stateful(&mut self) -> &mut Stateful { match self { State::Stateless(w) => { // convert the stateless value to stateful first. diff --git a/core/src/state/partial_state.rs b/core/src/state/partial_state.rs index fe8268f9c..6e18e5772 100644 --- a/core/src/state/partial_state.rs +++ b/core/src/state/partial_state.rs @@ -1,4 +1,4 @@ -use super::{route_state, Modifier, ModifyScope, RefShare, Share, StateRouter}; +use super::{route_state, Modifier, ModifyScope, RefShare, Share}; use rxrust::{ prelude::{ObservableItem, Observer}, subject::Subject, @@ -14,13 +14,15 @@ use std::{any::Any, rc::Rc}; // Keep the `S` as the first generic, so the user know the actual state type // when ide hints. -pub struct PartialState +pub struct PartialState where T: Share, - R: StateRouter, + R1: FnOnce(&T::V) -> &S + Copy, + R2: FnOnce(&mut T::V) -> &mut S + Copy, { origin_state: T, - router: R, + router: R1, + router_mut: R2, modifier: Modifier, connect_guard: Rc>, } @@ -29,35 +31,39 @@ where // Keep the `S` as the first generic, so the user know the actual state type // when ide hints. -pub struct PartialRef +pub struct PartialRef where O: RefShare, - R: StateRouter, + R1: FnOnce(&O::Target) -> &S + Copy, + R2: FnOnce(&mut O::Target) -> &mut S + Copy, { origin_ref: O, - router: R, + router: R1, + router_mut: R2, modifier: Modifier, } -impl Share for PartialState +impl Share for PartialState where - T: Share, - R: StateRouter, + T: Share, + R1: FnOnce(&T::V) -> &S + Copy, + R2: FnOnce(&mut T::V) -> &mut S + Copy, { type V = S; type Origin = T::S; - type S = Self; - type RefTrack<'a> = PartialRef, R> where Self: 'a; - type Ref<'a> = route_state:: Ref, R> where Self: 'a; + type S = PartialState; + type RefTrack<'a> = PartialRef, R1, R2> where Self: 'a; + type Ref<'a> = route_state:: Ref, R1> where Self: 'a; fn as_ref(&self) -> Self::Ref<'_> { route_state::Ref::new(self.origin_state.as_ref(), self.router) } - fn clone_state(&mut self) -> Self { - Self { + fn clone_state(&mut self) -> Self::S { + PartialState { origin_state: self.origin_state.clone_state(), router: self.router, + router_mut: self.router_mut, modifier: self.modifier.clone(), connect_guard: self.connect_guard.clone(), } @@ -78,7 +84,8 @@ where fn state_ref(&'_ mut self) -> Self::RefTrack<'_> { PartialRef { origin_ref: self.origin_state.state_ref(), - router: self.router.clone(), + router: self.router, + router_mut: self.router_mut, modifier: self.modifier.clone(), } } @@ -87,12 +94,13 @@ where fn own_data(&mut self, data: impl std::any::Any) { self.origin_state.own_data(data); } } -impl PartialState +impl PartialState where T: Share, - R: StateRouter, + R1: FnOnce(&T::V) -> &S + Copy, + R2: FnOnce(&mut T::V) -> &mut S + Copy, { - pub(super) fn new(mut state: T, router: R) -> Self { + pub(super) fn new(mut state: T, router: R1, router_mut: R2) -> Self { let modifier = Modifier::default(); let c_modifier = modifier.clone(); let h = state @@ -103,16 +111,18 @@ where Self { origin_state: state, router, + router_mut, modifier, connect_guard: Rc::new(Box::new(h)), } } } -impl RefShare for PartialRef +impl RefShare for PartialRef where T: RefShare, - P: StateRouter, + R1: FnOnce(&T::Target) -> &S + Copy, + R2: FnOnce(&mut T::Target) -> &mut S + Copy, { #[inline] fn silent(&mut self) -> &mut Self { @@ -130,31 +140,32 @@ where fn forget_modifies(&self) -> ModifyScope { self.origin_ref.forget_modifies() } } -impl std::ops::Deref for PartialRef +impl std::ops::Deref for PartialRef where T: RefShare, - P: StateRouter, + R1: FnOnce(&T::Target) -> &S + Copy, + R2: FnOnce(&mut T::Target) -> &mut S + Copy, { type Target = S; #[inline] - fn deref(&self) -> &Self::Target { self.router.route(self.origin_ref.deref()) } + fn deref(&self) -> &Self::Target { (self.router)(self.origin_ref.deref()) } } -impl std::ops::DerefMut for PartialRef +impl std::ops::DerefMut for PartialRef where T: RefShare, - P: StateRouter, + R1: FnOnce(&T::Target) -> &S + Copy, + R2: FnOnce(&mut T::Target) -> &mut S + Copy, { #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - self.router.route_mut(self.origin_ref.deref_mut()) - } + fn deref_mut(&mut self) -> &mut Self::Target { (self.router_mut)(self.origin_ref.deref_mut()) } } -impl Drop for PartialRef +impl Drop for PartialRef where T: RefShare, - P: StateRouter, + R1: FnOnce(&T::Target) -> &S + Copy, + R2: FnOnce(&mut T::Target) -> &mut S + Copy, { fn drop(&mut self) { let scope = self.origin_ref.forget_modifies(); diff --git a/core/src/state/route_state.rs b/core/src/state/route_state.rs index 87c093b80..431955571 100644 --- a/core/src/state/route_state.rs +++ b/core/src/state/route_state.rs @@ -1,4 +1,4 @@ -use super::{ModifyScope, RefShare, Share, StateRouter}; +use super::{ModifyScope, RefShare, Share}; use rxrust::{ops::box_it::BoxOp, subject::Subject}; use std::{convert::Infallible, ops::Deref}; @@ -7,36 +7,40 @@ use std::{convert::Infallible, ops::Deref}; /// try to access. It's also have the same modifier with the origin state. // Keep the `S` as the first generic, so the user know the actual state type // when ide hints. -pub struct RouteState +pub struct RouteState where T: Share, - R: StateRouter, + R1: FnOnce(&T::V) -> &S + Copy, + R2: FnOnce(&mut T::V) -> &mut S + Copy, { origin_state: T, - router: R, + router: R1, + router_mut: R2, } -impl Share for RouteState +impl Share for RouteState where - T: Share, - R: StateRouter, + T: Share, + R1: FnOnce(&T::V) -> &S + Copy, + R2: FnOnce(&mut T::V) -> &mut S + Copy, { type V = S; - type Origin = T; - type S = Self; - type RefTrack<'a> = RouteRef, R> + type Origin = T::S; + type S = RouteState; + type RefTrack<'a> = RouteRef, R1, R2> where Self: 'a; - type Ref<'a> = Ref, R> where Self: 'a; + type Ref<'a> = Ref, R1> where Self: 'a; #[inline] fn as_ref(&self) -> Self::Ref<'_> { Ref::new(self.origin_state.as_ref(), self.router) } #[inline] - fn clone_state(&mut self) -> Self { - Self { + fn clone_state(&mut self) -> Self::S { + RouteState { origin_state: self.origin_state.clone_state(), router: self.router, + router_mut: self.router_mut, } } @@ -56,6 +60,7 @@ where RouteRef { origin_ref: self.origin_state.state_ref(), router: self.router, + router_mut: self.router_mut, } } @@ -63,74 +68,84 @@ where fn own_data(&mut self, data: impl std::any::Any) { self.origin_state.own_data(data); } } -impl RouteState +impl RouteState where T: Share, - R: StateRouter, + R1: FnOnce(&T::V) -> &S + Copy, + R2: FnOnce(&mut T::V) -> &mut S + Copy, { #[inline] - pub fn new(state: T, router: R) -> Self { Self { origin_state: state, router } } + pub fn new(state: T, router: R1, router_mut: R2) -> Self { + Self { + origin_state: state, + router, + router_mut, + } + } } /// The reference of `PartialState`. // Keep the `S` as the first generic, so the user know the actual state type // when ide hints. -pub struct RouteRef +pub struct RouteRef where T: RefShare, - R: StateRouter, + R1: FnOnce(&T::Target) -> &S + Copy, + R2: FnOnce(&mut T::Target) -> &mut S + Copy, { origin_ref: T, - router: R, + router: R1, + router_mut: R2, } -pub struct Ref> { +pub struct Ref { origin_ref: O, router: R, } -impl> Ref { +impl &S + Copy> Ref { #[inline] pub fn new(origin_ref: O, router: R) -> Self { Self { origin_ref, router } } } -impl Deref for Ref +impl Deref for Ref where O: Deref, - R: StateRouter, + R: FnOnce(&O::Target) -> &S + Copy, { - type Target = R::Target; + type Target = S; #[inline] - fn deref(&self) -> &Self::Target { self.router.route(self.origin_ref.deref()) } + fn deref(&self) -> &Self::Target { (self.router)(self.origin_ref.deref()) } } -impl Deref for RouteRef +impl Deref for RouteRef where T: RefShare, - P: StateRouter, + R1: FnOnce(&T::Target) -> &S + Copy, + R2: FnOnce(&mut T::Target) -> &mut S + Copy, { type Target = S; #[inline] - fn deref(&self) -> &Self::Target { self.router.route(self.origin_ref.deref()) } + fn deref(&self) -> &Self::Target { (self.router)(self.origin_ref.deref()) } } -impl std::ops::DerefMut for RouteRef +impl std::ops::DerefMut for RouteRef where T: RefShare, - P: StateRouter, + R1: FnOnce(&T::Target) -> &S + Copy, + R2: FnOnce(&mut T::Target) -> &mut S + Copy, { #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - self.router.route_mut(self.origin_ref.deref_mut()) - } + fn deref_mut(&mut self) -> &mut Self::Target { (self.router_mut)(self.origin_ref.deref_mut()) } } -impl RefShare for RouteRef +impl RefShare for RouteRef where T: RefShare, - P: StateRouter, + R1: FnOnce(&T::Target) -> &S + Copy, + R2: FnOnce(&mut T::Target) -> &mut S + Copy, { #[inline] fn silent(&mut self) -> &mut Self { diff --git a/examples/greet/Cargo.toml b/examples/greet/Cargo.toml deleted file mode 100644 index 572ffc178..000000000 --- a/examples/greet/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -authors.workspace = true -categories.workspace = true -description.workspace = true -documentation.workspace = true -edition.workspace = true -homepage.workspace = true -keywords.workspace = true -license.workspace = true -name = "greet" -publish = false -version.workspace = true - -[dependencies] -paste.workspace = true -# we disable `default-features`, because we want more control over testing. -ribir = {path = "../../ribir", features = ["material", "widgets"]} -ribir_dev_helper = {path = "../../dev-helper"} - -[features] -wgpu = ["ribir/wgpu"] diff --git a/examples/greet/README.md b/examples/greet/README.md deleted file mode 100644 index 9e9757f49..000000000 --- a/examples/greet/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Greet - -This is just a `widget!` syntax teaching, demo No consideration to its completeness and reasonableness. - - -You can run with: -``` -cargo run --p greet -``` diff --git a/examples/greet/src/greet.rs b/examples/greet/src/greet.rs deleted file mode 100644 index b8cc10a68..000000000 --- a/examples/greet/src/greet.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! This is just a `widget!` syntax teaching demo No consideration to its -//! completeness and reasonableness. - -use ribir::prelude::*; - -pub fn greet() -> Widget { - widget! { - states { counter: Stateful::new(0) } - init ctx => { - let style = TypographyTheme::of(ctx).display_large.text.clone(); - } - Column { - Row { - align_items: Align::Center, - Input { - id: input, - Placeholder::new("Enter the name you want to greet.") - } - FilledButton { - on_tap: move |_| *counter += 1, - Label::new({ - let counter = counter.to_string(); - format!("Greet!({counter})") - }) - } - } - DynWidget { - dyns := assign_watch!(*counter > 0) - .stream_map(|o| o.distinct_until_changed()) - .map(move |need_greet| { - let style = style.clone(); - need_greet.then(move || { - widget! { - init ctx => { - let ease_in = transitions::EASE_IN.of(ctx); - } - Row { - Text { text: "Hello ", text_style: style.clone() } - Text { - id: greet, - text: no_watch!(input.text()), - text_style: style.clone() - } - Text { text: "!" , text_style: style } - } - Animate { - id: greet_new, - transition: ease_in, - prop: prop!(greet.transform), - from: Transform::translation(0., greet.layout_height() * 2.) - } - finally { - let_watch!(*counter) - .subscribe(move |_| { - greet.text = input.text(); - input.set_text(""); - }); - let_watch!(greet.text.clone()) - .subscribe(move |_| greet_new.run()); - } - } - }) - }) - } - } - } - .into() -} diff --git a/examples/greet/src/main.rs b/examples/greet/src/main.rs deleted file mode 100644 index 95b602930..000000000 --- a/examples/greet/src/main.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![feature(test)] - -mod greet; -use greet::greet; -use ribir::prelude::*; -use ribir_dev_helper::*; - -example_framework!(greet, wnd_size = Size::new(640., 400.)); diff --git a/examples/todos/src/todos.rs b/examples/todos/src/todos.rs index 9870568ed..13f2e9c57 100644 --- a/examples/todos/src/todos.rs +++ b/examples/todos/src/todos.rs @@ -8,65 +8,71 @@ struct Task { label: String, } #[derive(Debug)] -struct TodoMVP { +struct Todos { tasks: Vec, } -impl Compose for TodoMVP { - fn compose(this: State) -> Widget { - widget! { - states { this: this.into_writable(), id_gen: Stateful::new(this.state_ref().tasks.len()) } - init ctx => { - let surface_variant = Palette::of(ctx).surface_variant(); - let text_style = TypographyTheme::of(ctx).display_large.text.clone(); - } - Column { +impl Compose for Todos { + fn compose(mut this: State) -> Widget { + fn_widget! { + @Column { padding: EdgeInsets::all(10.), - Text { + @H1 { margin: EdgeInsets::only_bottom(10.), text: "Todo", - text_style, } - Row { - Container { - size: Size::new(240., 30.), - border: Border::only_bottom(BorderSide { width:1., color: surface_variant.into() }), - Input { - id: input, - Placeholder::new("What do you want to do ?") + @{ + let mut input = @Input { }; + let mut id_gen = Stateful::new($this.tasks.len()); + let add_task = move |_| { + if !$input.text().is_empty() { + $this.tasks.push(Task { + id: *$id_gen, + label: $input.text().to_string(), + finished: false, + }); + *$id_gen += 1; + $input.set_text(""); + } + }; + @Row { + @Container { + size: Size::new(240., 30.), + border: { + let color = Palette::of(ctx!()).surface_variant().into(); + Border::only_bottom(BorderSide { width:1., color }) + }, + @ $input { @{ Placeholder::new("What do you want to do ?") } } + } + @FilledButton { + margin: EdgeInsets::only_left(20.), + on_tap: add_task, + @{ Label::new("ADD") } } - } - FilledButton { - margin: EdgeInsets::only_left(20.), - on_tap: move |_| { - if !input.text().is_empty() { - let label = input.text().to_string(); - this.tasks.push(Task { - id: *id_gen, - label, - finished: false, - }); - *id_gen += 1; - input.set_text(""); - } - }, - Label::new("ADD") } } - - Tabs { + @Tabs { pos: Position::Top, - Tab { - TabItem { Label::new("ALL") } - TabPane { Self::pane(no_watch!(this), |_| true) } + @Tab { + @TabItem { @{ Label::new("ALL") } } + @TabPane { @{ + let tasks = partial_state!($this.tasks); + Self::pane(tasks, |_| true) + } } } - Tab { - TabItem { Label::new("ACTIVE") } - TabPane { Self::pane(no_watch!(this), |task| !task.finished) } + @Tab { + @TabItem { @{ Label::new("ACTIVE") } } + @TabPane { @{ + let tasks = partial_state!($this.tasks); + Self::pane(tasks, |task| !task.finished) + } } } - Tab { - TabItem { Label::new("DONE") } - TabPane { Self::pane(no_watch!(this), |task| task.finished) } + @Tab { + @TabItem { @{ Label::new("DONE") } } + @TabPane { @{ + let tasks = partial_state!($this.tasks); + Self::pane(tasks, |task| task.finished) + } } } } } @@ -75,95 +81,78 @@ impl Compose for TodoMVP { } } -impl TodoMVP { - fn pane(this: TrackRef, cond: fn(&Task) -> bool) -> Widget { - let this = this.clone_stateful(); - widget! { - states { this, mount_task_cnt: Stateful::new(0) } - VScrollBar { - Lists { +impl Todos { + fn pane( + mut tasks: impl Share, Origin = impl Share> + 'static, + cond: fn(&Task) -> bool, + ) -> Widget { + fn_widget! { + let mut mount_task_cnt = Stateful::new(0); + @VScrollBar { @ { pipe! { + @Lists { // when performed layout, means all task are mounted, we reset the mount count. - on_performed_layout: move |_| *mount_task_cnt = 0, + on_performed_layout: move |_| *$mount_task_cnt = 0, padding: EdgeInsets::vertical(8.), - DynWidget { - dyns: { - let tasks = this.tasks.clone(); - Multi::new( - tasks - .into_iter() - .enumerate() - .filter(move |(_, task)| { cond(task) }) - .map(move |(idx, task)| { - no_watch!(Self::task(this, task, idx, mount_task_cnt)) - }) - ) - } - } - } - } - } - .into() - } + @ { + let mut mount_idx = Stateful::new(0); - fn task(this: TrackRef, task: Task, idx: usize, mount_task_cnt: TrackRef) -> Widget { - let this = this.clone_stateful(); - let mount_task_cnt = mount_task_cnt.clone_stateful(); - widget! { - states { this, mount_task_cnt, mount_idx: Stateful::new(0) } - KeyWidget { - id: key, - key: Key::from(task.id), - value: Some(task.label.clone()), - ListItem { - id: item, - transform: Transform::default(), - on_mounted: move |_| { - if key.is_enter() { - *mount_idx = *mount_task_cnt; - *mount_task_cnt += 1; - mount_animate.run(); - } - }, - HeadlineText(Label::new(task.label.clone())) - Leading { - CustomEdgeWidget(widget! { - Checkbox { - id: checkbox, - checked: task.finished, - margin: EdgeInsets::vertical(4.), - } - finally { - let_watch!(checkbox.checked) - .subscribe(move |v| this.tasks[idx].finished = v); - } - }.into()) - } - Trailing { - cursor: CursorIcon::Hand, - visible: item.mouse_hover(), - on_tap: move |_| { this.tasks.remove(idx); }, - svgs::CLOSE + let task_widget_iter = $tasks + .iter() + .enumerate() + .filter_map(move |(idx, task)| { cond(task).then_some(idx) }) + .map(move |idx| { + let mut task = partial_state!($tasks[idx]); + let mut key = @KeyWidget { key: $task.id, value: () }; + let mut mount_animate = @Animate { + transition: @Transition { + delay: Some(Duration::from_millis(100).mul_f32((*$mount_idx + 1) as f32)), + duration: Duration::from_millis(150), + easing: easing::EASE_IN, + }.unwrap(), + state: partial_state!($key.transform), + from: Transform::translation(-400., 0. ), + }; + @ $key { + @ListItem { + on_mounted: move |_| { + if $key.is_enter() { + *$mount_idx = *$mount_task_cnt; + *$mount_task_cnt += 1; + mount_animate.run(); + } + }, + @{ HeadlineText(Label::new($task.label.clone())) } + @Leading { + @{ + let mut checkbox = @Checkbox { + checked: pipe!($task.finished), + margin: EdgeInsets::vertical(4.), + }; + watch!($checkbox.checked).subscribe(move |v| $task.finished = v); + CustomEdgeWidget(checkbox.into()) + } + } + @Trailing { + cursor: CursorIcon::Hand, + visible: $key.mouse_hover(), + on_tap: move |_| { $tasks.remove(idx); }, + @{ svgs::CLOSE } + } + } + } + }); + Multi::new(task_widget_iter) } + } - } - Animate { - id: mount_animate, - transition: Transition { - delay: Some(Duration::from_millis(100).mul_f32((*mount_idx + 1) as f32)), - duration: Duration::from_millis(150), - easing: easing::EASE_IN, - repeat: None, - }, - prop: prop!(item.transform), - from: Transform::translation(-400., 0. ), - } + }}} } .into() } } pub fn todos() -> Widget { - TodoMVP { + Todos { tasks: vec![ Task { id: 0, diff --git a/macros/src/declare_derive2.rs b/macros/src/declare_derive2.rs index e4dc4b88b..ce4ef488b 100644 --- a/macros/src/declare_derive2.rs +++ b/macros/src/declare_derive2.rs @@ -147,8 +147,6 @@ fn struct_with_fields_gen( generics: &syn::Generics, name: &syn::Ident, ) -> syn::Result { - let (g_impl, g_ty, g_where) = generics.split_for_impl(); - let mut builder_fields = collect_filed_and_attrs(stt)?; // reverse name check. @@ -229,11 +227,14 @@ fn struct_with_fields_gen( let unzip_fields = builder_fields.iter().map(|df| { let field_name = df.field.ident.as_ref().unwrap(); let method = df.set_method_name(); + let err = format!( + "Required field `{}::{}` not set, use method `{}` init it", + name.to_string(), + field_name.to_string(), + method.to_string() + ); quote_spanned! { field_name.span() => - let #field_name = self.#field_name.expect(&format!( - "Required field `{}::{}` not set, use method `{}` init it", - stringify!(#name), stringify!(#field_name), stringify!(#method) - )).unzip(); + let #field_name = self.#field_name.expect(#err).unzip(); } }); @@ -241,8 +242,15 @@ fn struct_with_fields_gen( let field_names2 = field_names.clone(); let field_names3 = field_names.clone(); + let (g_impl, g_ty, g_where) = generics.split_for_impl(); + let syn::Generics { + lt_token, + params, + gt_token, + where_clause, + } = generics; let tokens = quote! { - #vis struct #declarer #g_impl #g_where { + #vis struct #declarer #lt_token #params #gt_token #where_clause { #(#def_fields)* } @@ -270,10 +278,10 @@ fn struct_with_fields_gen( }); #( if let Some(u) = #field_names3.1 { - let mut _ribir2 = _ribir.clone_stateful(); + let mut _ribir2 = _ribir.clone_state(); let h = u.subscribe(move |v| _ribir2.state_ref().#field_names3 = v) .unsubscribe_when_dropped(); - _ribir.to_stateful().own_data(h); + _ribir.own_data(h); } );* diff --git a/macros/src/declare_obj.rs b/macros/src/declare_obj.rs index 2cf5dee62..c04932418 100644 --- a/macros/src/declare_obj.rs +++ b/macros/src/declare_obj.rs @@ -4,13 +4,12 @@ use crate::{ }; use inflector::Inflector; use proc_macro2::{Span, TokenStream}; -use quote::quote; use quote::{quote_spanned, ToTokens}; use smallvec::SmallVec; use syn::{ parse_str, spanned::Spanned, - token::{Brace, Comma, Paren}, + token::{Brace, Paren}, Ident, Macro, Path, }; @@ -95,7 +94,7 @@ impl<'a> ToTokens for DeclareObj<'a> { if children.is_empty() && builtin.is_empty() { quote_spanned! { *span => FatObj::new(#this) }.to_tokens(tokens) } else { - Brace::default().surround(tokens, |tokens| { + Brace(*span).surround(tokens, |tokens| { let mut builtin_names = vec![]; for (ty_str, fields) in builtin { // 'b is live longer than 'a, safe convert, but we can't directly convert @@ -180,8 +179,7 @@ fn recursive_compose_with( } else { leaf(tokens) } - Comma::default().to_tokens(tokens); - quote! { ctx!() }.to_tokens(tokens); + quote_spanned!(p.span() => , ctx!()).to_tokens(tokens); }); } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 19fd49b47..471af6712 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -17,13 +17,13 @@ mod child_template; mod fn_widget_macro; mod pipe_macro; mod rdl_macro; -mod split_state_macro; +mod route_state_macro; mod watch_macro; pub(crate) use rdl_macro::*; use crate::pipe_macro::PipeMacro; use crate::watch_macro::WatchMacro; -use split_state_macro::SplitStateMacro; +use route_state_macro::RouteStateMacro; pub(crate) mod declare_obj; pub(crate) mod symbol_process; @@ -198,8 +198,8 @@ pub fn watch(input: TokenStream) -> TokenStream { WatchMacro::gen_code(input.int #[proc_macro] pub fn partial_state(input: TokenStream) -> TokenStream { let input = ok!(symbol_to_macro(input)); - let split_state = parse_macro_input! { input as SplitStateMacro }; - split_state.to_token_stream().into() + let route_state = parse_macro_input! { input as RouteStateMacro }; + route_state.to_token_stream().into() } /// macro split a state from another state as a new state. For example, @@ -212,9 +212,9 @@ pub fn partial_state(input: TokenStream) -> TokenStream { #[proc_macro] pub fn route_state(input: TokenStream) -> TokenStream { let input = ok!(symbol_to_macro(input)); - let mut split_state = parse_macro_input! { input as SplitStateMacro }; - split_state.only_route = true; - split_state.to_token_stream().into() + let mut route_state = parse_macro_input! { input as RouteStateMacro }; + route_state.only_route = true; + route_state.to_token_stream().into() } /// The macro to use a state as its StateRef. Transplanted from the `$`. diff --git a/macros/src/rdl_macro.rs b/macros/src/rdl_macro.rs index 025baad35..60b3e8b9f 100644 --- a/macros/src/rdl_macro.rs +++ b/macros/src/rdl_macro.rs @@ -5,7 +5,7 @@ use crate::{ }; use proc_macro::TokenStream as TokenStream1; use proc_macro2::{Span, TokenStream}; -use quote::{quote, quote_spanned, ToTokens}; +use quote::{quote_spanned, ToTokens}; use std::collections::HashSet; use syn::{ braced, @@ -77,7 +77,7 @@ impl RdlMacro { if stmts.len() > 1 { quote_spanned! { span => { #(#stmts)* }}.into() } else { - quote! { #(#stmts)* }.into() + quote_spanned! { span => #(#stmts)* }.into() } } } diff --git a/macros/src/route_state_macro.rs b/macros/src/route_state_macro.rs new file mode 100644 index 000000000..cb9353aea --- /dev/null +++ b/macros/src/route_state_macro.rs @@ -0,0 +1,72 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + fold::Fold, + parse::{Parse, ParseStream}, + parse_quote, + token::Brace, + ExprMacro, Result, +}; + +use crate::symbol_process::{DollarRefs, KW_DOLLAR_STR}; + +pub(crate) struct RouteStateMacro { + part_state: PartStateExpr, + pub only_route: bool, +} + +struct PartStateExpr { + host: TokenStream, + path: TokenStream, +} + +impl Parse for PartStateExpr { + fn parse(input: ParseStream) -> Result { + let mac = input.parse::()?; + if !mac.mac.path.is_ident(KW_DOLLAR_STR) { + return Err(syn::Error::new_spanned( + mac, + "expected `$` as the first token", + )); + } + + Ok(Self { + host: mac.mac.tokens, + path: input.parse()?, + }) + } +} + +impl Parse for RouteStateMacro { + fn parse(input: ParseStream) -> Result { + let mut refs = DollarRefs::default(); + let expr = refs.fold_expr(input.parse()?); + let part_state = parse_quote!(#expr); + + Ok(Self { part_state, only_route: false }) + } +} + +impl ToTokens for RouteStateMacro { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + part_state: PartStateExpr { host, path }, + only_route, + } = self; + + Brace::default().surround(tokens, |tokens| { + let route_method = if *only_route { + quote!(route_state) + } else { + quote!(partial_state) + }; + quote!( + #host.#route_method( + move |origin: &_| &origin #path, + move |origin: &mut _| &mut origin #path + ) + ) + .to_tokens(tokens) + }) + } +} diff --git a/macros/src/split_state_macro.rs b/macros/src/split_state_macro.rs deleted file mode 100644 index b343d7f6e..000000000 --- a/macros/src/split_state_macro.rs +++ /dev/null @@ -1,97 +0,0 @@ -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; -use syn::{ - fold::Fold, - parse::{Parse, ParseStream}, - parse_quote, - token::{Brace, Comma}, - ExprMacro, Result, Type, -}; - -use crate::symbol_process::{DollarRefs, KW_DOLLAR_STR}; - -pub(crate) struct SplitStateMacro { - part_state: PartStateExpr, - origin_ty: Type, - _arrow: syn::Token![->], - target_ty: Type, - pub only_route: bool, -} - -struct PartStateExpr { - host: TokenStream, - path: TokenStream, -} - -impl Parse for PartStateExpr { - fn parse(input: ParseStream) -> Result { - let mac = input.parse::()?; - if !mac.mac.path.is_ident(KW_DOLLAR_STR) { - return Err(syn::Error::new_spanned( - mac, - "expected `$` as the first token", - )); - } - - Ok(Self { - host: mac.mac.tokens, - path: input.parse()?, - }) - } -} - -impl Parse for SplitStateMacro { - fn parse(input: ParseStream) -> Result { - let mut refs = DollarRefs::default(); - let expr = refs.fold_expr_field(input.parse()?); - input.parse::()?; - let part_state = parse_quote!(#expr); - - Ok(Self { - part_state, - origin_ty: input.parse()?, - _arrow: input.parse()?, - target_ty: input.parse()?, - only_route: false, - }) - } -} - -impl ToTokens for SplitStateMacro { - fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { - part_state: PartStateExpr { host, path }, - origin_ty, - target_ty, - .. - } = self; - - Brace::default().surround(tokens, |tokens| { - // implement the splitter - quote! { - #[derive(Clone, Copy)] - struct Router; - - impl StateRouter for Router { - type Origin = #origin_ty; - type Target = #target_ty; - - fn route(self, origin: &Self::Origin) -> &Self::Target { - &origin #path - } - - fn route_mut(self, origin: &mut Self::Origin) -> &mut Self::Target { - &mut origin #path - } - } - } - .to_tokens(tokens); - - if self.only_route { - quote!(#host.route_state(Router)).to_tokens(tokens) - } else { - quote!(#host.partial_state(Router)).to_tokens(tokens) - } - }) - } -} diff --git a/painter/src/style.rs b/painter/src/style.rs index bc5d08d97..d95641b8c 100644 --- a/painter/src/style.rs +++ b/painter/src/style.rs @@ -24,6 +24,11 @@ impl From for Brush { fn from(c: Color) -> Self { Brush::Color(c) } } +impl From for Option { + #[inline] + fn from(c: Color) -> Self { Some(c.into()) } +} + impl From> for Brush { #[inline] fn from(img: ShareResource) -> Self { Brush::Image(img) } diff --git a/themes/material/src/lib.rs b/themes/material/src/lib.rs index 6d27b46dd..66507e3eb 100644 --- a/themes/material/src/lib.rs +++ b/themes/material/src/lib.rs @@ -272,7 +272,7 @@ fn override_compose_decorator(theme: &mut FullTheme) { let host = scrollbar_thumb(host, EdgeInsets::vertical(1.)); let mut thumb = @ $host { left_anchor: pipe!($this.offset) }; - let left_trans = partial_state!($thumb.left_anchor, LeftAnchor -> PositionUnit); + let left_trans = partial_state!($thumb.left_anchor); left_trans.transition_with( transitions::LINEAR.of(ctx!()), move |from, to, rate| PositionUnit::lerp_fn(from, to, rate, $thumb.layout_width()), @@ -288,7 +288,7 @@ fn override_compose_decorator(theme: &mut FullTheme) { let host = scrollbar_thumb(host, EdgeInsets::vertical(1.)); let mut thumb = @ $host { top_anchor: pipe!($this.offset) }; - let top_trans = partial_state!($thumb.top_anchor, TopAnchor -> PositionUnit); + let top_trans = partial_state!($thumb.top_anchor); top_trans.transition_with( transitions::LINEAR.of(ctx!()), move |from, to, rate| PositionUnit::lerp_fn(from, to, rate, $thumb.layout_height()), @@ -323,14 +323,14 @@ fn override_compose_decorator(theme: &mut FullTheme) { }; let ease_in = transitions::EASE_IN.of(ctx!()); - let left = partial_state!($indicator.left_anchor, LeftAnchor -> PositionUnit); + let left = partial_state!($indicator.left_anchor); left.transition_with( ease_in.clone(), move |from, to, rate| PositionUnit::lerp_fn(from, to, rate, $style.rect.width()), ctx!() ); - let top = partial_state!($indicator.top_anchor, TopAnchor -> PositionUnit); + let top = partial_state!($indicator.top_anchor); top.transition_with( ease_in, move |from, to, rate| PositionUnit::lerp_fn(from, to, rate, $style.rect.height()), diff --git a/themes/material/src/ripple.rs b/themes/material/src/ripple.rs index 01122b69f..fccc696af 100644 --- a/themes/material/src/ripple.rs +++ b/themes/material/src/ripple.rs @@ -62,7 +62,7 @@ impl ComposeChild for Ripple { let mut ripper_enter = @Animate { transition: transitions::LINEAR.of(ctx!()), state: LerpFnState::new( - partial_state!($ripple_path.path, PathPaintKit -> Path), + partial_state!($ripple_path.path), move |_, _, rate| { let radius = Lerp::lerp(&0., &radius, rate); let center = $this.launch_pos.clone().unwrap(); @@ -80,7 +80,7 @@ impl ComposeChild for Ripple { let mut ripper_fade_out = @Animate { from: 0., - state: partial_state!($ripple.opacity, Opacity -> f32), + state: partial_state!($ripple.opacity), transition: transitions::EASE_OUT.of(ctx!()), }; diff --git a/widgets/src/checkbox.rs b/widgets/src/checkbox.rs index f98b52dbd..157bf4fbe 100644 --- a/widgets/src/checkbox.rs +++ b/widgets/src/checkbox.rs @@ -5,7 +5,7 @@ use crate::{ use ribir_core::prelude::*; /// Represents a control that a user can select and clear. -#[derive(Clone, Declare)] +#[derive(Clone, Declare, Declare2)] pub struct Checkbox { #[declare(default)] pub checked: bool, diff --git a/widgets/src/text_field.rs b/widgets/src/text_field.rs index bdae45bcf..33d5f845a 100644 --- a/widgets/src/text_field.rs +++ b/widgets/src/text_field.rs @@ -339,7 +339,7 @@ fn build_input_area( visible: pipe!(!$this.text.is_empty() || $theme.state == TextFieldState::Focused), }; - let mut visible = partial_state!($input_area.visible, Visibility -> bool); + let mut visible = partial_state!($input_area.visible); visible.transition(transitions::LINEAR.of(ctx!()), ctx!()); let mut input = @Input{ style: pipe!($theme.text.clone()) }; @@ -400,7 +400,7 @@ impl Compose for TextFieldLabel { text_style: pipe!($this.style.clone()), }; - let mut font_size = partial_state!($this.style.font_size, TextFieldLabel -> FontSize); + let mut font_size = partial_state!($this.style.font_size); font_size.transition(transitions::LINEAR.of(ctx!()), ctx!()); label @@ -420,7 +420,7 @@ fn build_content_area( padding: pipe!($theme.input_padding($this.text.is_empty())), }; - let mut padding = partial_state!($content_area.padding, Padding -> EdgeInsets); + let mut padding = partial_state!($content_area.padding); padding.transition(transitions::LINEAR.of(ctx!()), ctx!()); @ $content_area {