From 231b78cd0b9dcb67da32e203a2154298a3dea4d5 Mon Sep 17 00:00:00 2001 From: Adoo Date: Mon, 14 Aug 2023 16:12:28 +0800 Subject: [PATCH] =?UTF-8?q?refactor(core):=20=F0=9F=92=A1=20use=20`Partial?= =?UTF-8?q?State`=20to=20implement=20animate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- algo/src/cow_rc.rs | 9 +- core/src/animation.rs | 2 - core/src/animation/animate.rs | 242 ++++++++----- core/src/animation/lerp.rs | 27 ++ core/src/animation/property.rs | 222 ------------ core/src/animation/transition.rs | 50 ++- core/src/builtin_widgets.rs | 22 ++ core/src/builtin_widgets/anchor.rs | 10 +- core/src/builtin_widgets/clip.rs | 4 +- core/src/builtin_widgets/fitted_box.rs | 2 +- core/src/builtin_widgets/ignore_pointer.rs | 2 +- .../builtin_widgets/theme/transition_theme.rs | 7 - core/src/pipe.rs | 9 +- core/src/state.rs | 3 + core/src/state/stateful.rs | 11 - core/src/window.rs | 18 +- macros/src/declare_derive2.rs | 42 ++- themes/material/src/lib.rs | 112 +++--- themes/material/src/ripple.rs | 144 ++++---- widgets/src/input.rs | 2 +- widgets/src/layout/container.rs | 2 +- widgets/src/layout/expanded.rs | 2 +- widgets/src/path.rs | 2 +- widgets/src/text_field.rs | 321 +++++++----------- 24 files changed, 571 insertions(+), 696 deletions(-) delete mode 100644 core/src/animation/property.rs diff --git a/algo/src/cow_rc.rs b/algo/src/cow_rc.rs index f68d81c8f..9f86e09e6 100644 --- a/algo/src/cow_rc.rs +++ b/algo/src/cow_rc.rs @@ -1,5 +1,5 @@ use std::{ - borrow::Borrow, + borrow::{Borrow, BorrowMut}, fmt::Debug, hash::Hash, ops::{Bound, Range}, @@ -88,6 +88,13 @@ where } } +impl std::ops::DerefMut for CowArc +where + B::Owned: Clone + std::borrow::BorrowMut, +{ + fn deref_mut(&mut self) -> &mut B { self.to_mut().borrow_mut() } +} + impl Clone for CowArc { #[inline] fn clone(&self) -> Self { diff --git a/core/src/animation.rs b/core/src/animation.rs index fdf4ed031..e6e7466f3 100644 --- a/core/src/animation.rs +++ b/core/src/animation.rs @@ -11,5 +11,3 @@ mod lerp; pub use lerp::Lerp; mod repeat; pub use repeat::*; -mod property; -pub use property::{LerpProp, Prop, Property}; diff --git a/core/src/animation/animate.rs b/core/src/animation/animate.rs index db9d1d93e..6f4a84afd 100644 --- a/core/src/animation/animate.rs +++ b/core/src/animation/animate.rs @@ -1,32 +1,47 @@ -use crate::{ - prelude::*, - ticker::{FrameMsg, FrameTicker}, -}; -use std::{ - cell::RefCell, - ops::DerefMut, - rc::Rc, - time::{Duration, Instant}, -}; - -use super::property::AnimateProperty; - -#[derive(Declare)] -pub struct Animate { +use crate::{prelude::*, ticker::FrameMsg, window::WindowId}; +use std::time::{Duration, Instant}; + +#[derive(Declare2)] +pub struct Animate +where + T: Roc + 'static, + S: AnimateState + 'static, +{ + #[declare(strict)] pub transition: T, - pub prop: P, - pub from: P::Value, + #[declare(strict)] + pub state: S, + pub from: ::S, #[declare(skip)] - running_info: Option>, - #[declare(skip, default = ctx.window().frame_ticker.clone())] - frame_ticker: FrameTicker, - #[declare(skip, default = ctx.window().animate_track())] - animate_track: AnimateTrack, - #[declare(skip, default = ctx.window().frame_scheduler())] - frame_scheduler: FuturesLocalScheduler, + running_info: Option::S>>, + #[declare(skip, default = ctx.window().id())] + window_id: WindowId, +} + +pub trait AnimateState { + type State: Share; + fn state(&mut self) -> &mut Self::State; + + fn calc_lerp_value( + &mut self, + from: &::S, + to: &::S, + rate: f32, + ) -> ::S; } -pub struct AnimateInfo { +/// A state with a lerp function as an animation state that use the `lerp_fn` +/// function to calc the linearly lerp value by rate, and not require the value +/// type of the state to implement the `Lerp` trait. +/// +/// User can use it if the value type of the state is not implement the `Lerp` +/// or override the lerp algorithm of the value type of state. +pub struct LerpFnState { + lerp_fn: F, + state: S, +} + +pub(crate) struct AnimateInfo { from: V, to: V, start_at: Instant, @@ -36,53 +51,73 @@ pub struct AnimateInfo { _tick_msg_guard: Option>>, } -impl<'a, T: Roc, P: AnimateProperty> StatefulRef<'a, Animate> +impl State> where - Animate: 'static, + T: Roc + 'static, + S: AnimateState + 'static, + ::S: Clone, { pub fn run(&mut self) { - let new_to = self.prop.get(); - // if animate is running, animate start from current value. - let Animate { prop, running_info, .. } = self.deref_mut(); - if let Some(AnimateInfo { from, to, last_progress, .. }) = running_info { - *from = prop.calc_lerp_value(from, to, last_progress.value()); + let mut animate_ref = self.state_ref(); + let this = &mut *animate_ref; + let wnd_id = this.window_id; + let new_to = this.state.state().state_ref().clone(); + + if let Some(AnimateInfo { from, to, last_progress, .. }) = &mut this.running_info { + *from = this.state.calc_lerp_value(from, to, last_progress.value()); *to = new_to; - } else { - let animate = self.clone_stateful(); - let ticker = self.frame_ticker.frame_tick_stream(); - let unsub = ticker.subscribe(move |msg| match msg { - FrameMsg::NewFrame(_) => {} - FrameMsg::LayoutReady(time) => { - let p = animate.shallow_ref().lerp(time); - if matches!(p, AnimateProgress::Finish) { - let scheduler = animate.silent_ref().frame_scheduler.clone(); - let animate = animate.clone(); - observable::of(()) - .delay(Duration::ZERO, scheduler) - .subscribe(move |_| { - animate.silent_ref().stop(); + } else if let Some(wnd) = AppCtx::get_window(wnd_id) { + drop(animate_ref); + + let mut animate = self.clone(); + let ticker = wnd.frame_ticker.frame_tick_stream(); + let unsub = ticker.subscribe(move |msg| { + match msg { + FrameMsg::NewFrame(_) => {} + FrameMsg::LayoutReady(time) => { + let p = animate.state_ref().shallow().rate_at_instant(time); + if matches!(p, AnimateProgress::Finish) { + let wnd = AppCtx::get_window(wnd_id).unwrap(); + let mut animate = animate.clone(); + observable::timer((), Duration::ZERO, wnd.frame_scheduler()).subscribe(move |_| { + animate.state_ref().silent().stop(); }); + } + } + // use silent_ref because the state of animate change, bu no need to effect the framework. + FrameMsg::Finish(_) => { + let animate = &mut *animate.state_ref(); + let info = animate.running_info.as_mut().unwrap(); + if !matches!(info.last_progress, AnimateProgress::Finish) { + **animate.state.state().state_ref().shallow() = info.to.clone() + } + info.already_lerp = false; } } - // use silent_ref because the state of animate change, bu no need to effect the framework. - FrameMsg::Finish(_) => animate.silent_ref().frame_finished(), }); let guard = BoxSubscription::new(unsub).unsubscribe_when_dropped(); - self.running_info = Some(AnimateInfo { - from: self.from.clone(), + let animate = &mut *self.state_ref(); + animate.running_info = Some(AnimateInfo { + from: animate.from.clone(), to: new_to, start_at: Instant::now(), last_progress: AnimateProgress::Dismissed, _tick_msg_guard: Some(guard), already_lerp: false, }); - self.animate_track.set_actived(true); + wnd.inc_running_animate(); } } } -impl Animate { - fn lerp(&mut self, now: Instant) -> AnimateProgress { +impl Animate +where + S: AnimateState + 'static, +{ + fn rate_at_instant(&mut self, now: Instant) -> AnimateProgress + where + ::S: Clone, + { let AnimateInfo { from, to, @@ -102,15 +137,15 @@ impl Animate { let elapsed = now - *start_at; let progress = self.transition.rate_of_change(elapsed); - let prop = &mut self.prop; match progress { AnimateProgress::Between(rate) => { + let value = self.state.calc_lerp_value(from, to, rate); + let state = &mut self.state.state().state_ref(); // the state may change during animate. - *to = prop.get(); - let value = prop.calc_lerp_value(from, to, rate); - prop.shallow_set(value); + *to = state.clone(); + **state.shallow() = value; } - AnimateProgress::Dismissed => prop.set(from.clone()), + AnimateProgress::Dismissed => **self.state.state().state_ref().shallow() = from.clone(), AnimateProgress::Finish => {} } @@ -120,54 +155,79 @@ impl Animate { progress } - fn frame_finished(&mut self) { - let info = self - .running_info - .as_mut() - .expect("This animation is not running."); - - if !matches!(info.last_progress, AnimateProgress::Finish) { - self.prop.set(info.to.clone()) - } - info.already_lerp = false; - } - pub fn stop(&mut self) { - self.animate_track.set_actived(false); - self.running_info.take(); + if self.is_running() { + if let Some(wnd) = AppCtx::get_window(self.window_id) { + wnd.dec_running_animate(); + self.running_info.take(); + } + } } #[inline] pub fn is_running(&self) -> bool { self.running_info.is_some() } } -pub struct AnimateTrack { - pub(crate) actived: bool, - pub(crate) actived_cnt: Rc>, -} - -impl Drop for AnimateTrack { +impl Drop for Animate +where + P: AnimateState + 'static, +{ fn drop(&mut self) { - if self.actived { - *self.actived_cnt.borrow_mut() -= 1; + if self.is_running() { + if let Some(wnd) = AppCtx::get_window(self.window_id).filter(|_| self.is_running()) { + wnd.dec_running_animate(); + } } - self.actived = false; } } -impl AnimateTrack { - fn set_actived(&mut self, actived: bool) { - if self.actived == actived { - return; - } - self.actived = actived; - match actived { - true => *self.actived_cnt.borrow_mut() += 1, - false => *self.actived_cnt.borrow_mut() -= 1, - }; +impl AnimateState for S +where + S: Share, + S::S: Lerp, +{ + type State = S; + + fn state(&mut self) -> &mut Self::State { self } + + fn calc_lerp_value( + &mut self, + from: &::S, + to: &::S, + rate: f32, + ) -> ::S { + from.lerp(to, rate) + } +} + +impl AnimateState for LerpFnState +where + S: Share, + F: FnMut(&::S, &::S, f32) -> ::S, +{ + type State = S; + + fn state(&mut self) -> &mut Self::State { &mut self.state } + + fn calc_lerp_value( + &mut self, + from: &::S, + to: &::S, + rate: f32, + ) -> ::S { + (self.lerp_fn)(from, to, rate) } } +impl LerpFnState +where + S: Share, + F: FnMut(&::S, &::S, f32) -> ::S, +{ + #[inline] + pub fn new(state: S, lerp_fn: F) -> Self { Self { state, lerp_fn } } +} + #[cfg(test)] mod tests { use super::*; diff --git a/core/src/animation/lerp.rs b/core/src/animation/lerp.rs index fa3cb5b89..a2801829c 100644 --- a/core/src/animation/lerp.rs +++ b/core/src/animation/lerp.rs @@ -1,3 +1,5 @@ +use ribir_text::{Em, FontSize, Pixel, PIXELS_PER_EM}; + use crate::prelude::{ Angle, Box2D, Brush, Color, DevicePoint, DeviceRect, DeviceSize, DeviceVector, Point, Radius, Rect, Size, Transform, Vector, @@ -169,6 +171,31 @@ impl Lerp for Transform { } } +impl Lerp for Pixel { + #[inline] + fn lerp(&self, to: &Self, factor: f32) -> Self { + let v = self.0.lerp(&to.0, factor); + Pixel(v.into()) + } +} + +impl Lerp for Em { + #[inline] + fn lerp(&self, to: &Self, factor: f32) -> Self { + let v = self.value().lerp(&to.value(), factor); + Em::relative_to(v, FontSize::Pixel(PIXELS_PER_EM.into())) + } +} + +impl Lerp for FontSize { + fn lerp(&self, to: &Self, factor: f32) -> Self { + let from = self.into_pixel().value(); + let to = to.into_pixel().value(); + let v = from.lerp(&to, factor); + FontSize::Pixel(v.into()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/core/src/animation/property.rs b/core/src/animation/property.rs deleted file mode 100644 index 928a92fcb..000000000 --- a/core/src/animation/property.rs +++ /dev/null @@ -1,222 +0,0 @@ -use crate::state::Stateful; -use std::convert::Infallible; - -use super::Lerp; -use rxrust::{ - observable, - ops::box_it::{BoxIt, BoxOp}, - prelude::ObservableExt, -}; - -/// Property is a value with can be accessed and watch its changes. -pub trait Property { - type Value: Clone; - fn get(&self) -> Self::Value; - fn set(&mut self, v: Self::Value); - fn shallow_set(&mut self, v: Self::Value); - fn modifies(&self) -> BoxOp<'static, (), Infallible>; -} - -pub trait AnimateProperty: Property { - fn calc_lerp_value(&self, from: &Self::Value, to: &Self::Value, rate: f32) -> Self::Value; -} - -pub struct Prop { - target: Stateful, - getter: G, - setter: S, -} - -#[derive(Clone)] -pub struct LerpProp { - prop: P, - /// function calc the linearly lerp value by rate, three arguments are - /// `from` `to` and `rate`, specify `lerp_fn` when the animate state not - /// implement `Lerp` trait or you want to specify a custom lerp function. - lerp_fn: F, -} - -impl Prop -where - G: Fn(&T) -> V, - S: FnMut(&mut T, V), - V: Clone, -{ - #[inline] - pub fn new(target: Stateful, getter: G, setter: S) -> Self { Self { target, getter, setter } } -} - -impl LerpProp -where - P: Property, - F: Fn(&P::Value, &P::Value, f32) -> P::Value, -{ - #[inline] - pub fn new(prop: P, lerp_fn: F) -> Self { Self { prop, lerp_fn } } -} - -impl Property for Prop -where - G: Fn(&T) -> V, - S: FnMut(&mut T, V), - V: Clone, -{ - type Value = V; - - #[inline] - fn get(&self) -> V { (self.getter)(&*self.target.state_ref()) } - - #[inline] - fn set(&mut self, v: V) { (self.setter)(&mut *self.target.state_ref(), v); } - - #[inline] - fn shallow_set(&mut self, v: Self::Value) { (self.setter)(&mut *self.target.shallow_ref(), v); } - - #[inline] - fn modifies(&self) -> BoxOp<'static, (), Infallible> { self.target.modifies().box_it() } -} - -impl AnimateProperty for Prop -where - Self: Property, - Self::Value: Lerp, -{ - #[inline] - - fn calc_lerp_value(&self, from: &Self::Value, to: &Self::Value, rate: f32) -> Self::Value { - from.lerp(to, rate) - } -} - -impl Prop -where - Self: Property, - G: Fn(&T) -> V + Clone + 'static, - V: PartialEq + Clone + 'static, - T: 'static, -{ - pub fn changes(&self) -> BoxOp<'static, V, Infallible> { - let target = self.target.clone(); - let getter = self.getter.clone(); - self - .modifies() - .map(move |_| getter(&*target.state_ref())) - .distinct_until_changed() - .box_it() - } -} - -impl Property for LerpProp -where - P: Property, -{ - type Value = P::Value; - - #[inline] - fn get(&self) -> Self::Value { self.prop.get() } - - #[inline] - fn set(&mut self, v: Self::Value) { self.prop.set(v) } - - #[inline] - fn shallow_set(&mut self, v: Self::Value) { self.prop.shallow_set(v) } - - #[inline] - fn modifies(&self) -> BoxOp<'static, (), Infallible> { self.prop.modifies() } -} - -impl AnimateProperty for LerpProp -where - P: Property, - F: Fn(&P::Value, &P::Value, f32) -> P::Value + Clone, -{ - #[inline] - fn calc_lerp_value(&self, from: &Self::Value, to: &Self::Value, rate: f32) -> Self::Value { - (self.lerp_fn)(from, to, rate) - } -} - -impl LerpProp, F> -where - Prop: Property, - V: PartialEq + Clone + 'static, - G: Fn(&T) -> V + Clone + 'static, - T: 'static, -{ - #[inline] - pub fn changes(&self) -> BoxOp<'static, V, Infallible> { self.prop.changes() } -} - -macro_rules! impl_tuple_property { - ($ty: ident, $idx: tt $(,$other_ty: ident, $other_idx: tt)*) => { - impl_tuple_property!({$ty, $idx} $($other_ty, $other_idx),*); - }; - ( - {$($ty: ident, $idx: tt),+} - $next_ty: ident, $next_idx: tt - $(,$other_ty: ident, $other_idx: tt)* - ) => { - impl_tuple_property!({$($ty, $idx),+}); - impl_tuple_property!( - {$($ty, $idx,)+ $next_ty, $next_idx } - $($other_ty, $other_idx),* - ); - }; - ({ $($ty: ident, $idx:tt),+}) => { - impl<$($ty),+> Property for ($($ty,)+) - where - $($ty: Property + 'static,)+ - $($ty::Value: 'static),+ - { - type Value = ($($ty::Value,)+); - - #[inline] - fn get(&self) -> Self::Value { - ($(self.$idx.get(),)+) - } - - #[inline] - fn set(&mut self, v: Self::Value) { - $(self.$idx.set(v.$idx);)+ - } - - #[inline] - fn shallow_set(&mut self, v: Self::Value) { - $(self.$idx.shallow_set(v.$idx);)+ - } - - #[inline] - fn modifies(&self) -> BoxOp<'static, (), Infallible> { - observable::from_iter([$(self.$idx.modifies()),+]) - .merge_all(usize::MAX) - .box_it() - } - } - - impl<$($ty),+> AnimateProperty for ($($ty,)+) - where - Self: Property, - $($ty: AnimateProperty),+ - { - fn calc_lerp_value(&self, from: &Self::Value, to: &Self::Value, rate: f32) -> Self::Value{ - ($(self.$idx.calc_lerp_value(&from.$idx, &to.$idx, rate),)+) - } - } - } -} - -impl_tuple_property! {T0, 0, T1, 1, T2, 2, T3, 3, T4, 4, T5, 5, T6, 6, T7, 7,T8, 8, T9, 9, - T10, 10, T11, 11, T12, 12, T13, 13, T14, 14, T15, 15, T16, 16, T17, 17,T18, 18, T19, 19, - T20, 20, T21, 21, T22, 22, T23, 23, T24, 24, T25, 25, T26, 26, T27, 27,T28, 28, T29, 29, - T30, 30, T31, 31 -} - -impl Clone for Prop { - fn clone(&self) -> Self { - Prop { - target: self.target.clone(), - getter: self.getter.clone(), - setter: self.setter.clone(), - } - } -} diff --git a/core/src/animation/transition.rs b/core/src/animation/transition.rs index 80a263fc9..e3c16b556 100644 --- a/core/src/animation/transition.rs +++ b/core/src/animation/transition.rs @@ -2,7 +2,8 @@ use super::easing::Easing; use crate::prelude::{BuildCtx, *}; use std::{ops::Deref, rc::Rc, time::Duration}; -/// Transition describe how the state change form init to final smoothly. +/// Transition use rate to describe how the state change form init to final +/// smoothly. #[derive(Declare, Clone, Debug, PartialEq)] pub struct Transition { #[declare(default, convert=strip_option)] @@ -13,6 +14,45 @@ pub struct Transition { pub repeat: Option, } +/// Trait help to transition the state. +pub trait TransitionState: Sized + 'static { + fn transition(mut self, transition: T, ctx: &BuildCtx) + where + Self: AnimateState, + ::S: Clone, + { + let mut c_state = self.state().clone(); + + let from = c_state.state_ref().clone(); + let mut animate: State> = Animate::declare2_builder() + .transition(transition) + .from(from) + .state(self) + .build(ctx); + + let mut c_animate = animate.clone(); + let h = c_state.modifies().subscribe(move |_| { + animate.state_ref().from = c_state.state_ref().clone(); + animate.run(); + }); + c_animate.own_data(h); + } + + /// Transition the state with a lerp function. + fn transition_with( + self, + transition: T, + lerp_fn: impl FnMut(&Self::S, &Self::S, f32) -> Self::S + 'static, + ctx: &BuildCtx, + ) where + Self: Share, + Self::S: Clone, + { + let animate_state = LerpFnState::new(self, lerp_fn); + animate_state.transition(transition, ctx) + } +} + /// Calc the rate of change over time. pub trait Roc { /// Calc the rate of change of the duration from animation start. @@ -46,6 +86,14 @@ impl Roc for Transition { } } +impl TransitionState for S {} +impl TransitionState for LerpFnState +where + S: Share + 'static, + F: FnMut(&::S, &::S, f32) -> ::S + 'static, +{ +} + impl Roc for Stateful { #[inline] fn rate_of_change(&self, dur: Duration) -> AnimateProgress { diff --git a/core/src/builtin_widgets.rs b/core/src/builtin_widgets.rs index 8aeb158c0..e9906c456 100644 --- a/core/src/builtin_widgets.rs +++ b/core/src/builtin_widgets.rs @@ -83,6 +83,11 @@ macro_rules! impl_fat_obj { } } + pub fn unwrap(self) -> T { + $(assert!(self.[< $builtin_ty: snake:lower >].is_none());)* + self.host + } + $( pub fn [< with_ $builtin_ty: snake:lower >]( mut self, builtin: State<$builtin_ty> @@ -155,6 +160,23 @@ macro_rules! impl_fat_obj { } } } + + impl SingleWithChild for Option> + where + T: SingleWithChild, + C: Into, + as SingleWithChild>::Target: Into + { + type Target = Widget; + fn with_child(self, c: C, ctx: &BuildCtx) -> Self::Target { + + if let Some(this) = self { + this.with_child(c, ctx).into() + } else { + c.into() + } + } + } } }; } diff --git a/core/src/builtin_widgets/anchor.rs b/core/src/builtin_widgets/anchor.rs index 9a4862101..f9dab4973 100644 --- a/core/src/builtin_widgets/anchor.rs +++ b/core/src/builtin_widgets/anchor.rs @@ -124,12 +124,10 @@ impl PositionUnit { } } - pub fn lerp_fn(self_size: f32) -> impl Fn(&Self, &Self, f32) -> Self + Clone { - move |from, to, rate| { - let from = from.abs_value(self_size); - let to = to.abs_value(self_size); - PositionUnit::Pixel(from.lerp(&to, rate)) - } + pub fn lerp_fn(from: &Self, to: &Self, rate: f32, self_size: f32) -> PositionUnit { + let from = from.abs_value(self_size); + let to = to.abs_value(self_size); + PositionUnit::Pixel(from.lerp(&to, rate)) } } diff --git a/core/src/builtin_widgets/clip.rs b/core/src/builtin_widgets/clip.rs index d69884f02..fcf234b7e 100644 --- a/core/src/builtin_widgets/clip.rs +++ b/core/src/builtin_widgets/clip.rs @@ -7,7 +7,7 @@ pub enum ClipType { Path(Path), } -#[derive(SingleChild, Clone, Declare)] +#[derive(SingleChild, Clone, Declare, Declare2)] pub struct Clip { #[declare(default)] pub clip: ClipType, @@ -27,7 +27,7 @@ impl Render for Clip { fn paint(&self, ctx: &mut PaintingCtx) { let path = match &self.clip { ClipType::Auto => { - let rect = Rect::from_size( + let rect: lyon_geom::euclid::Rect = Rect::from_size( ctx .box_rect() .expect("impossible without size in painting stage") diff --git a/core/src/builtin_widgets/fitted_box.rs b/core/src/builtin_widgets/fitted_box.rs index b9ac76232..1a6f871c4 100644 --- a/core/src/builtin_widgets/fitted_box.rs +++ b/core/src/builtin_widgets/fitted_box.rs @@ -111,7 +111,7 @@ mod tests { wnd.draw_frame(); assert_layout_result_by_path!(wnd, {path = [0], size == expect,} ); - assert_eq!(c_fit.shallow_ref().scale_cache.get(), expected_scale); + assert_eq!(c_fit.state_ref().scale_cache.get(), expected_scale); } } diff --git a/core/src/builtin_widgets/ignore_pointer.rs b/core/src/builtin_widgets/ignore_pointer.rs index fcbc367e4..4fe23d940 100644 --- a/core/src/builtin_widgets/ignore_pointer.rs +++ b/core/src/builtin_widgets/ignore_pointer.rs @@ -1,6 +1,6 @@ use crate::{impl_query_self_only, prelude::*}; -#[derive(Declare, SingleChild, Clone)] +#[derive(Declare, Declare2, SingleChild, Clone)] pub struct IgnorePointer { #[declare(default = true)] pub ignore: bool, diff --git a/core/src/builtin_widgets/theme/transition_theme.rs b/core/src/builtin_widgets/theme/transition_theme.rs index 3c0e94452..b018ecfcc 100644 --- a/core/src/builtin_widgets/theme/transition_theme.rs +++ b/core/src/builtin_widgets/theme/transition_theme.rs @@ -47,7 +47,6 @@ pub mod transitions { EASE_IN, EASE_OUT, EASE_IN_OUT, - SMOOTH_SCROLL, THEME_EXTEND ); @@ -124,12 +123,6 @@ impl Default for TransitionTheme { duration: Duration::from_millis(250), easing: easing::EASE_IN_OUT, repeat: None, - }, - transitions::SMOOTH_SCROLL:Transition { - delay: None, - duration: Duration::from_millis(200), - easing: easing::EASE_IN, - repeat: None, } } diff --git a/core/src/pipe.rs b/core/src/pipe.rs index 2aa9279b4..075de411e 100644 --- a/core/src/pipe.rs +++ b/core/src/pipe.rs @@ -68,7 +68,6 @@ impl Pipe { } impl> WidgetBuilder for Pipe { - #[inline] fn build(self, ctx: &crate::context::BuildCtx) -> WidgetId { let (v, modifies) = self.unzip(); let id = v.into().build(ctx); @@ -99,6 +98,14 @@ impl> WidgetBuilder for Pipe { } } +impl + 'static> WidgetBuilder for Pipe> { + fn build(self, ctx: &crate::context::BuildCtx) -> WidgetId { + self + .map(|w| w.map_or_else(|| Widget::from(Void), |w| w.into())) + .build(ctx) + } +} + impl Pipe { pub(crate) fn into_only_parent(self, ctx: &mut BuildCtx) -> WidgetId where diff --git a/core/src/state.rs b/core/src/state.rs index 8b6048057..57f1f0765 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -96,6 +96,7 @@ pub trait RefShare: DerefMut { pub trait StateSplitter { type Origin; type Target; + fn split(origin: &Self::Origin) -> &Self::Target; fn split_mut(origin: &mut Self::Origin) -> &mut Self::Target; } @@ -295,6 +296,7 @@ impl<'a, W> RefShare for StateRef<'a, W> { impl<'a, W> std::ops::Deref for StateRef<'a, W> { type Target = W; + #[track_caller] fn deref(&self) -> &Self::Target { match self { StateRef::Stateful(s) => s.deref(), @@ -304,6 +306,7 @@ impl<'a, W> std::ops::Deref for StateRef<'a, W> { } impl<'a, W> std::ops::DerefMut for StateRef<'a, W> { + #[track_caller] fn deref_mut(&mut self) -> &mut Self::Target { match self { StateRef::Stateful(s) => s.deref_mut(), diff --git a/core/src/state/stateful.rs b/core/src/state/stateful.rs index 97b507d13..40557123d 100644 --- a/core/src/state/stateful.rs +++ b/core/src/state/stateful.rs @@ -136,17 +136,6 @@ impl Stateful { #[inline] pub fn silent_ref(&self) -> StatefulRef { StatefulRef::new(self, ModifyScope::DATA) } - /// Return a reference of `Stateful`, modify across this reference will notify - /// framework only. That means this modify only effect framework but not - /// effect on data. And usually use it to temporary to modify the `Stateful`. - /// - /// If you not very clear how `shallow_ref` work, use [`Stateful::state_ref`]! - /// instead of. - #[inline] - pub(crate) fn shallow_ref(&self) -> StatefulRef { - StatefulRef::new(self, ModifyScope::FRAMEWORK) - } - pub fn raw_modifies(&self) -> Subject<'static, ModifyScope, Infallible> { self.modifier.raw_modifies() } diff --git a/core/src/window.rs b/core/src/window.rs index 51aaefdb5..c1d43149e 100644 --- a/core/src/window.rs +++ b/core/src/window.rs @@ -1,5 +1,4 @@ use crate::{ - animation::AnimateTrack, context::AppCtx, events::{ dispatcher::Dispatcher, @@ -18,7 +17,7 @@ use ribir_geom::Point; use rxrust::{scheduler::FuturesLocalScheduler, subject::Subject}; use std::{ borrow::BorrowMut, - cell::RefCell, + cell::{Cell, RefCell}, collections::VecDeque, convert::Infallible, ops::{Deref, DerefMut}, @@ -41,7 +40,7 @@ pub struct Window { pub(crate) widget_tree: RefCell, pub(crate) frame_ticker: FrameTicker, pub(crate) focus_mgr: RefCell, - pub(crate) running_animates: Rc>, + pub(crate) running_animates: Rc>, /// This vector store the task to emit events. When perform layout, dispatch /// event and so on, some part of window may be already mutable borrowed and /// the user event callback may also query borrow that part, so we can't emit @@ -110,12 +109,9 @@ impl Window { self.frame_ticker.frame_tick_stream() } - pub fn animate_track(&self) -> AnimateTrack { - AnimateTrack { - actived: false, - actived_cnt: self.running_animates.clone(), - } - } + pub fn inc_running_animate(&self) { self.running_animates.set(self.running_animates.get() + 1); } + + pub fn dec_running_animate(&self) { self.running_animates.set(self.running_animates.get() - 1); } pub fn frame_process(&self) { self.frame_ticker.emit(FrameMsg::NewFrame(Instant::now())); @@ -180,7 +176,7 @@ impl Window { } pub fn need_draw(&self) -> bool { - self.widget_tree.borrow().is_dirty() || *self.running_animates.borrow() > 0 + self.widget_tree.borrow().is_dirty() || self.running_animates.get() > 0 } pub fn new(root: Widget, shell_wnd: Box) -> Rc { @@ -197,7 +193,7 @@ impl Window { focus_mgr, delay_emitter: <_>::default(), frame_ticker: FrameTicker::default(), - running_animates: Rc::new(RefCell::new(0)), + running_animates: Rc::new(Cell::new(0)), frame_pool: RefCell::new(<_>::default()), shell_wnd: RefCell::new(shell_wnd), }; diff --git a/macros/src/declare_derive2.rs b/macros/src/declare_derive2.rs index 671a7067e..e4dc4b88b 100644 --- a/macros/src/declare_derive2.rs +++ b/macros/src/declare_derive2.rs @@ -28,6 +28,7 @@ struct DeclareAttr { custom: Option, // field with `skip` attr, will not generate setter method and use default to init value. skip: Option, + strict: Option, } struct DeclareField<'a> { @@ -41,6 +42,7 @@ mod kw { custom_keyword!(default); custom_keyword!(custom); custom_keyword!(skip); + custom_keyword!(strict); // todo: tmp code, only for compatibility. custom_keyword!(convert); } @@ -82,6 +84,8 @@ impl Parse for DeclareAttr { attr.default = Some(input.parse()?); } else if lookahead.peek(kw::skip) { attr.skip = Some(input.parse()?); + } else if lookahead.peek(kw::strict) { + attr.strict = Some(input.parse()?); } else if lookahead.peek(kw::convert) { input.parse::()?; input.parse::()?; @@ -97,6 +101,15 @@ impl Parse for DeclareAttr { d.set_spans(vec![rename.span().unwrap(), builtin.span().unwrap()]); d.emit(); } + if let (Some(convert), Some(skip)) = (attr.strict.as_ref(), attr.skip.as_ref()) { + let mut d = Diagnostic::new( + Level::Error, + "field is marked as `skip` is not allowed to use `convert`.", + ); + d.set_spans(vec![convert.span().unwrap(), skip.span().unwrap()]); + d.emit(); + } + if !input.is_empty() { input.parse::()?; } @@ -153,16 +166,25 @@ fn struct_with_fields_gen( let field_name = f.field.ident.as_ref().unwrap(); let ty = &f.field.ty; let set_method = f.set_method_name(); - - builder_methods.extend(quote! { - #[inline] - #vis fn #set_method<_M, _V>(mut self, v: _V) -> Self - where DeclareInit<#ty>: DeclareFrom<_V, _M> - { - self.#field_name = Some(DeclareInit::declare_from(v)); - self - } - }); + if f.attr.as_ref().map_or(false, |attr| attr.strict.is_some()) { + builder_methods.extend(quote! { + #[inline] + #vis fn #set_method(mut self, v: #ty) -> Self { + self.#field_name = Some(DeclareInit::Value(v)); + self + } + }); + } else { + builder_methods.extend(quote! { + #[inline] + #vis fn #set_method<_M, _V>(mut self, v: _V) -> Self + where DeclareInit<#ty>: DeclareFrom<_V, _M> + { + self.#field_name = Some(DeclareInit::declare_from(v)); + self + } + }); + } }); // builder define diff --git a/themes/material/src/lib.rs b/themes/material/src/lib.rs index 74f815413..f16a9205c 100644 --- a/themes/material/src/lib.rs +++ b/themes/material/src/lib.rs @@ -268,71 +268,77 @@ fn override_compose_decorator(theme: &mut FullTheme) { let styles = &mut theme.compose_decorators; styles.override_compose_decorator::(|this, host| { - widget! { - states { this } - init ctx => { - let smooth_scroll = transitions::SMOOTH_SCROLL.of(ctx); - } - DynWidget { - id: thumb, - left_anchor: this.offset, - dyns: scrollbar_thumb(host, EdgeInsets::vertical(1.)) - } + fn_widget! { + let host = scrollbar_thumb(host, EdgeInsets::vertical(1.)); + let mut thumb = @ $host { left_anchor: pipe!($this.offset) }; - transition prop!(thumb.left_anchor, PositionUnit::lerp_fn(thumb.layout_width())) { - by: smooth_scroll, - } + let left_trans = partial_state!($thumb.left_anchor, LeftAnchor -> PositionUnit); + left_trans.transition_with( + transitions::LINEAR.of(ctx!()), + move |from, to, rate| PositionUnit::lerp_fn(from, to, rate, $thumb.layout_width()), + ctx!() + ); + + thumb } .into() }); styles.override_compose_decorator::(|this, host| { - widget! { - states { this } - init ctx => { - let smooth_scroll = transitions::SMOOTH_SCROLL.of(ctx); - } - DynWidget { - id: thumb, - top_anchor: this.offset, - dyns: scrollbar_thumb(host, EdgeInsets::vertical(1.)) - } + fn_widget! { + let host = scrollbar_thumb(host, EdgeInsets::vertical(1.)); + let mut thumb = @ $host { top_anchor: pipe!($this.offset) }; - transition prop!(thumb.top_anchor, PositionUnit::lerp_fn(thumb.layout_height())) { - by: smooth_scroll - } + let top_trans = partial_state!($thumb.top_anchor, TopAnchor -> PositionUnit); + top_trans.transition_with( + transitions::LINEAR.of(ctx!()), + move |from, to, rate| PositionUnit::lerp_fn(from, to, rate, $thumb.layout_height()), + ctx!() + ); + + thumb } .into() }); styles.override_compose_decorator::(|style, host| { - widget! { - states { style } - init ctx => { - let ease_in = transitions::EASE_IN.of(ctx); - } - DynWidget { - id: indicator, - left_anchor: match style.pos { - Position::Top | Position::Bottom => style.rect.origin.x - + (style.rect.size.width - INDICATOR_SIZE) / 2., - Position::Left => style.rect.size.width - style.extent, - Position::Right => 0., + fn_widget! { + + let mut indicator = @ $host { + left_anchor: pipe!{ + let mut style = $style; + match style.pos { + Position::Top | Position::Bottom => style.rect.origin.x + + (style.rect.size.width - INDICATOR_SIZE) / 2., + Position::Left => style.rect.size.width - style.extent, + Position::Right => 0., + } }, - top_anchor: match style.pos { - Position::Left | Position::Right => style.rect.origin.y - + (style.rect.size.height - INDICATOR_SIZE) / 2., - Position::Top => style.rect.size.height - style.extent, - Position::Bottom => 0., + top_anchor: pipe!{ + let style = $style; + match style.pos { + Position::Left | Position::Right => style.rect.origin.y + + (style.rect.size.height - INDICATOR_SIZE) / 2., + Position::Top => style.rect.size.height - style.extent, + Position::Bottom => 0., + } }, - dyns: host, - } - transition prop!( - indicator.left_anchor, - PositionUnit::lerp_fn(style.rect.size.width) - ) { by: ease_in.clone() } - transition prop!( - indicator.top_anchor, - PositionUnit::lerp_fn(style.rect.size.height) - ) { by: ease_in } + }; + + let ease_in = transitions::EASE_IN.of(ctx!()); + let left = partial_state!($indicator.left_anchor, LeftAnchor -> PositionUnit); + 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); + top.transition_with( + ease_in, + move |from, to, rate| PositionUnit::lerp_fn(from, to, rate, $style.rect.height()), + ctx!() + ); + + indicator } .into() }); diff --git a/themes/material/src/ripple.rs b/themes/material/src/ripple.rs index aecaff681..9a8871484 100644 --- a/themes/material/src/ripple.rs +++ b/themes/material/src/ripple.rs @@ -25,7 +25,7 @@ pub struct Ripple { } /// Config how ripples show outside of the host widget box. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone, Copy)] pub enum RippleBound { /// Ripples visible outside of the host widget. Unbounded, @@ -38,87 +38,87 @@ pub enum RippleBound { impl ComposeChild for Ripple { type Child = Widget; - fn compose_child(this: State, child: Self::Child) -> Widget { - widget! { - states { this: this.into_writable() } - init ctx => { - let linear_transition = transitions::LINEAR.of(ctx); - let ease_out = transitions::EASE_OUT.of(ctx); - } - Stack { - id: container, - fit: StackFit::Passthrough, - on_pointer_down: move |e| this.launch_pos = if this.center { - let center = container.layout_size() / 2.; - Some(Point::new(center.width, center.height)) - } else { - Some(e.position()) - }, - widget::from(child) - Option::map(this.launch_pos, |launch_at| { - let radius = this.radius.unwrap_or_else(|| { - let size = container.layout_size(); + fn compose_child(mut this: State, child: Self::Child) -> Widget { + fn_widget! { + let mut container = @Stack { fit: StackFit::Passthrough }; + + let ripple_widget = pipe!{ + let launch_at = $this.launch_pos; + launch_at.map(|launch_at|{ + let radius = $this.radius.unwrap_or_else(|| { + let size = $container.layout_size(); let distance_x = f32::max(launch_at.x , size.width - launch_at.x); let distance_y = f32::max(launch_at.y, size.height - launch_at.y); (distance_x.powf(2.) + distance_y.powf(2.)).sqrt() }); - let linear_transition = linear_transition.clone(); - let ease_out = ease_out.clone(); - widget!{ - IgnorePointer { - id: ripple, - opacity: 1., - delay_drop_until: !ripper_fade_out.is_running(), - on_disposed: move |_| ripper_fade_out.run(), - DynWidget { - dyns: widget::then(this.bounded != RippleBound::Unbounded, || { - let rect = Rect::from_size(container.layout_size()); - let path = match this.bounded { - RippleBound::Unbounded => unreachable!(), - RippleBound::Bounded => Path::rect(&rect), - RippleBound::Radius(radius) => { - Path::rect_round(&rect, &radius) - } - }; - Clip { clip: ClipType::Path(path) } - }), - Container { - size: container.layout_size(), - PathPaintKit { - id: ripple_path, - brush: StateRole::pressed().calc_color(this.color), - path: Path::circle(launch_at, radius), - on_mounted: move |_| { ripper_enter.run(); } - } - } - } - } - Animate { - id: ripper_enter, - transition: linear_transition, - prop: prop!(ripple_path.path, move |_, _, rate| { + let mut ripple = @IgnorePointer {}; + + let mut ripple_path = @PathPaintKit { + brush: pipe!(StateRole::pressed().calc_color($this.color)), + path: Path::circle(launch_at, radius), + }; + + let mut ripper_enter = @Animate { + transition: transitions::LINEAR.of(ctx!()), + state: LerpFnState::new( + partial_state!($ripple_path.path, PathPaintKit -> Path), + move |_, _, rate| { let radius = Lerp::lerp(&0., &radius, rate); - let center = this.launch_pos.clone().unwrap(); + let center = $this.launch_pos.clone().unwrap(); Path::circle(center, radius) - }), - from: Path::circle(Point::zero(), 0.) - } - Animate { - id: ripper_fade_out, - from: 0., - prop: prop!(ripple.opacity, |from, to, rate| to.lerp(from,rate)), - transition: ease_out, - } - finally { - let_watch!(!container.pointer_pressed() && !ripper_enter.is_running()) - .filter(|b| *b) - .subscribe(move |_| { - this.launch_pos.take(); - }); + } + ), + from: Path::circle(Point::zero(), 0.) + }.unwrap(); + + let h = watch!(!$container.pointer_pressed() && !$ripper_enter.is_running()) + .filter(|b| *b) + .subscribe(move |_| { $this.launch_pos.take();}); + ripper_enter.own_data(h); + + + let mut ripper_fade_out = @Animate { + from: 0., + state: partial_state!($ripple.opacity, Opacity -> f32), + transition: transitions::EASE_OUT.of(ctx!()), + }; + + let bounded = $this.bounded; + let clipper = (bounded != RippleBound::Unbounded).then(|| { + let rect = Rect::from_size($container.layout_size()); + let path = match bounded { + RippleBound::Unbounded => unreachable!(), + RippleBound::Bounded => Path::rect(&rect), + RippleBound::Radius(radius) => { + Path::rect_round(&rect, &radius) + } + }; + @Clip { clip: ClipType::Path(path) } + }); + + @ $ripple { + delay_drop_until: !$ripper_fade_out.is_running(), + on_disposed: move |_| ripper_fade_out.run(), + @Container { + size: $container.layout_size(), + @ $clipper { + @ $ripple_path { on_mounted: move |_| { ripper_enter.run(); } } + } } } }) + }; + + @ $container { + on_pointer_down: move |e| $this.launch_pos = if $this.center { + let center = $container.layout_size() / 2.; + Some(Point::new(center.width, center.height)) + } else { + Some(e.position()) + }, + @{ child } + @{ ripple_widget } } } .into() diff --git a/widgets/src/input.rs b/widgets/src/input.rs index 8c18b2194..8b1628a40 100644 --- a/widgets/src/input.rs +++ b/widgets/src/input.rs @@ -32,7 +32,7 @@ impl CustomStyle for InputStyle { fn default_style(_: &BuildCtx) -> Self { InputStyle { size: Some(20.) } } } -#[derive(Declare)] +#[derive(Declare, Declare2)] pub struct Input { #[declare(default = TypographyTheme::of(ctx).body_large.text.clone())] pub style: CowArc, diff --git a/widgets/src/layout/container.rs b/widgets/src/layout/container.rs index 77fe09892..fd82a9846 100644 --- a/widgets/src/layout/container.rs +++ b/widgets/src/layout/container.rs @@ -1,7 +1,7 @@ use ribir_core::prelude::*; /// Widget with fixed size as a container for its child. -#[derive(Declare, SingleChild)] +#[derive(Declare, Declare2, SingleChild)] pub struct Container { pub size: Size, } diff --git a/widgets/src/layout/expanded.rs b/widgets/src/layout/expanded.rs index dada9ae00..5bc64c79a 100644 --- a/widgets/src/layout/expanded.rs +++ b/widgets/src/layout/expanded.rs @@ -5,7 +5,7 @@ use super::ConstrainedBox; /// A widget that expanded a child of `Flex`, so that the child fills the /// available space. If multiple children are expanded, the available space is /// divided among them according to the flex factor. -#[derive(Clone, PartialEq, Declare)] +#[derive(Clone, PartialEq, Declare, Declare2)] pub struct Expanded { pub flex: f32, } diff --git a/widgets/src/path.rs b/widgets/src/path.rs index 87ca6220b..0344284bb 100644 --- a/widgets/src/path.rs +++ b/widgets/src/path.rs @@ -2,7 +2,7 @@ use ribir_core::{impl_query_self_only, prelude::*}; /// Widget just use as a paint kit for a path and not care about its size. Use /// `[PathWidget]!` instead of. -#[derive(Declare, Clone)] +#[derive(Declare, Declare2, Clone)] pub struct PathPaintKit { pub path: Path, #[declare(convert=into)] diff --git a/widgets/src/text_field.rs b/widgets/src/text_field.rs index 79d36a3a9..ec59331d5 100644 --- a/widgets/src/text_field.rs +++ b/widgets/src/text_field.rs @@ -1,9 +1,4 @@ -use crate::{ - common_widget::{Leading, LeadingText, Trailing, TrailingText}, - input::Placeholder, - layout::{Column, ConstrainedBox, Container}, - prelude::{Expanded, Icon, Input, Label, Row, Stack, Text}, -}; +use crate::prelude::*; use ribir_core::prelude::*; use std::{collections::HashMap, hash::Hash, ops::Deref}; @@ -94,8 +89,8 @@ where fn get(&self, state: S) -> Option<&T> { self.themes.get(&state) } } -#[derive(Declare)] -struct ThemeSuitProxy +#[derive(Declare, Declare2)] +struct ThemeSuitProxy where S: Hash + Eq, { @@ -107,32 +102,27 @@ type TextFieldThemeProxy = ThemeSuitProxy; impl ComposeChild for TextFieldThemeProxy { type Child = Widget; - fn compose_child(this: State, child: Self::Child) -> Widget - where - Self: Sized, - { - widget! { - states {this: this.into_writable()} - DynWidget { - dyns: { - child - }, + fn compose_child(mut this: State, child: Self::Child) -> Widget { + fn_widget! { + @ $child { on_tap: move |_| { + let mut this = $this; match this.state { TextFieldState::Enabled => this.state = TextFieldState::Focused, TextFieldState::Hovered => this.state = TextFieldState::Focused, _ => (), }; }, - on_pointer_move: move |_| { + let mut this = $this; if this.state == TextFieldState::Enabled { this.state = TextFieldState::Hovered } }, - on_pointer_leave: move |_| { + let mut this = $this; if this.state == TextFieldState::Hovered { this.state = TextFieldState::Enabled } }, on_focus_out: move |_| { + let mut this = $this; if this.state == TextFieldState::Focused { this.state = TextFieldState::Enabled } }, } @@ -291,59 +281,46 @@ macro_rules! take_option_field { impl ComposeChild for TextField { type Child = Option; - fn compose_child(this: State, config: Self::Child) -> Widget - where - Self: Sized, - { - let mut config = config.unwrap_or_default(); - widget! { - states { - this: this.into_writable(), - } - init ctx => { - let theme_suit = TextFieldThemeSuit::of(ctx).clone(); - } - init { - take_option_field!({leading_icon, trailing_icon}, config); - } + fn compose_child(mut this: State, config: Self::Child) -> Widget { + fn_widget! { + let mut config = config.unwrap_or_default(); + take_option_field!({leading_icon, trailing_icon}, config); - TextFieldThemeProxy { - id: theme, + let theme_suit = TextFieldThemeSuit::of(ctx!()); + let mut theme = @TextFieldThemeProxy { suit: theme_suit, state: TextFieldState::default(), - - Stack { - Container { - size: Size::new(0., theme.container_height), - background: theme.container_color, - } - Row { - ConstrainedBox { - clamp: BoxClamp::EXPAND_Y, - DynWidget { - v_align: VAlign::Center, - dyns: build_icon(leading_icon.map(|l| l.child)) - } - } - Expanded { - flex: 1., - build_content_area(no_watch!(&mut this), no_watch!(&mut theme), config) - } - ConstrainedBox { - clamp: BoxClamp::EXPAND_Y, - DynWidget { - v_align: VAlign::Center, - dyns: build_icon(trailing_icon.map(|t| t.child)) - } - } - } - - Container { - v_align: VAlign::Bottom, - size: Size::new(f32::MAX, theme.indicator_height), - background: theme.indicator, - } + }; + @Stack { + @Container { + size: pipe!(Size::new(0., $theme.container_height)), + background: pipe!($theme.container_color), + } + @Row { + justify_content: JustifyContent::Center, + align_items: Align::Stretch, + @{ + leading_icon.map(|t| @Icon { + size: IconSize::of(ctx!()).small, + @{ t.child } + }) + } + @Expanded { + flex: 1., + @{ build_content_area(this.clone(), theme.clone(), config) } + } + @{ + trailing_icon.map(|t| @Icon { + size: IconSize::of(ctx!()).small, + @{ t.child } + }) } + } + @Container { + v_align: VAlign::Bottom, + size: pipe!(Size::new(f32::MAX, $theme.indicator_height)), + background: pipe!($theme.indicator), + } } } .into() @@ -351,169 +328,113 @@ impl ComposeChild for TextField { } fn build_input_area( - this: &mut StatefulRef, - theme: &mut StatefulRef, + mut this: State, + mut theme: State, prefix: Option, suffix: Option, placeholder: Option, ) -> Widget { - fn text_label(text: CowArc, theme: StatefulRef) -> Text { - Text { - text, - foreground: theme.foreground.clone(), - text_style: theme.text.clone(), - path_style: PathPaintStyle::Fill, - overflow: Overflow::Clip, - text_align: TextAlign::Start, - } - } + fn_widget! { + let mut input_area = @Row { + visible: pipe!(!$this.text.is_empty() || $theme.state == TextFieldState::Focused), + }; - widget! { - states { this: this.clone_stateful(), theme: theme.clone_stateful() } - init ctx => { - let linear = transitions::LINEAR.of(ctx); - let prefix = prefix.map(move |p| p.child); - let suffix = suffix.map(move|s| s.child); - } - Row { - id: input_area, - visible: !this.text.is_empty() || theme.state == TextFieldState::Focused, - Option::map(prefix.clone(), move |text| text_label(text, theme)) - Expanded { - flex: 1., - Input { - id: input, - style: theme.text.clone(), - widget::from(placeholder) - } - } - Option::map(suffix.clone(), move |text| text_label(text, theme)) + let mut visible = partial_state!($input_area.visible, Visibility -> bool); + visible.transition(transitions::LINEAR.of(ctx!()), ctx!()); - } - transition prop!(input_area.visible, move |_from, to, rate| *to && rate >= 1.) { - by: linear, - } + let mut input = @Input{ style: pipe!($theme.text.clone()) }; + $input.set_text($this.text.clone()); - finally { - input.set_text(this.text.clone()); - let_watch!(input.text()) - .distinct_until_changed() - .subscribe(move |val| { - this.silent().text = val; - }); - let_watch!(this.text.clone()) - .distinct_until_changed() - .subscribe(move |val| input.set_text(val)); - let_watch!(theme.state) - .distinct_until_changed() - .subscribe(move |state| { - if state == TextFieldState::Focused { - input.request_focus(); - } - }); + watch!($input.text()) + .distinct_until_changed() + .subscribe(move |val| $this.silent().text = val); + + watch!($this.text.clone()) + .distinct_until_changed() + .subscribe(move |val| $input.set_text(val)); + + let h = watch!($theme.state) + .distinct_until_changed() + .filter(|state| state == &TextFieldState::Focused) + .subscribe(move |_| $input.request_focus()) + .unsubscribe_when_dropped(); + input.own_data(h); + + + @Row { + @{ + prefix.map(|p| @Text{ + text: p.child, + foreground: pipe!($theme.foreground.clone()), + text_style: pipe!($theme.text.clone()), + }) + } + @Expanded { + flex: 1., + @ $input { @{placeholder} } + } + @{ + suffix.map(|s| @Text{ + text: s.child, + foreground: pipe!($theme.foreground.clone()), + text_style: pipe!($theme.text.clone()), + }) + } } } .into() } -#[derive(Declare)] +#[derive(Declare, Declare2)] struct TextFieldLabel { text: CowArc, style: CowArc, } impl Compose for TextFieldLabel { - fn compose(this: State) -> Widget { - widget! { - states { this: this.into_readonly() } - init ctx => { - let linear = transitions::LINEAR.of(ctx); - } - Text { - id: label, + fn compose(mut this: State) -> Widget { + fn_widget! { + let label = @Text { v_align: VAlign::Top, - text: this.text.clone(), - text_style: this.style.clone(), - } + text: pipe!($this.text.clone()), + text_style: pipe!($this.style.clone()), + }; - // todo: prop with inner field's property - // transition prop!(label.style.font_size) { - // by: transitions::LINEAR.of(ctx) - // } - transition prop!(label.text_style, move |from, to, rate| { - let from_size = from.font_size.into_pixel(); - let to_size = to.font_size.into_pixel(); - - let mut res = to.clone(); - res.to_mut().font_size = FontSize::Pixel(Pixel(from_size.0.lerp(&to_size.0, rate).into())); - res - }) { - by: linear, - } + let mut font_size = partial_state!($this.style.font_size, TextFieldLabel -> FontSize); + font_size.transition(transitions::LINEAR.of(ctx!()), ctx!()); + + label } .into() } } fn build_content_area( - this: &mut StatefulRef, - theme: &mut StatefulRef, + mut this: State, + mut theme: State, mut config: TextFieldTml, ) -> Widget { - widget! { - states { this: this.clone_stateful(), theme: theme.clone_stateful(), } - init ctx => { - let linear = transitions::LINEAR.of(ctx); - } - init { - take_option_field!({label, prefix, suffix, placeholder}, config); - } - Column { - id: content_area, - padding: theme.input_padding(this.text.is_empty()), - - DynWidget { - dyns: label.map(move |label| { - widget! { - Expanded { - flex: 1., - TextFieldLabel { - text: label.0.clone(), - style: theme.label_style(this.text.is_empty()), - } - } + fn_widget! { + take_option_field!({label, prefix, suffix, placeholder}, config); + let mut content_area = @Column { + padding: pipe!($theme.input_padding($this.text.is_empty())), + }; + + let mut padding = partial_state!($content_area.padding, Padding -> EdgeInsets); + padding.transition(transitions::LINEAR.of(ctx!()), ctx!()); + + @ $content_area { + @ { + label.map(|label| @Expanded { + flex: 1., + @TextFieldLabel { + text: label.0, + style: pipe!($theme.label_style($this.text.is_empty())), } }) } - build_input_area( - no_watch!(&mut this), - no_watch!(&mut theme), - prefix, - suffix, - placeholder - ) + @ { build_input_area(this.clone(), theme.clone(), prefix, suffix, placeholder)} } - - transition prop!(content_area.padding) { by: linear } } .into() } - -fn build_icon(icon: Option) -> Widget { - if icon.is_some() { - widget! { - init ctx => { - let icon_size = IconSize::of(ctx).small; - } - Icon { - size: icon_size, - DynWidget { - dyns: icon.unwrap() - } - } - } - .into() - } else { - Void.into() - } -}