use crate::{prelude::*, ticker::FrameMsg, window::WindowFlags, wrap_render::*};
smooth_pos_widget_impl!(SmoothPos, Point);
smooth_pos_widget_impl!(SmoothY, f32, y);
smooth_pos_widget_impl!(SmoothX, f32, x);
smooth_size_widget_impl!(SmoothSize, Size);
smooth_size_widget_impl!(SmoothHeight, f32, height);
smooth_size_widget_impl!(SmoothWidth, f32, width);
#[derive(Default, Debug)]
struct SmoothImpl<T> {
running: bool,
value: T,
}
impl<T: Copy + PartialEq + 'static> Stateful<SmoothImpl<T>> {
fn transition(
&self, transition: impl Transition + 'static,
) -> Stateful<Animate<impl AnimateState + 'static>>
where
T: Lerp,
{
let animate = part_writer!(&mut self.value).transition(transition);
let this = self.clone_writer();
watch!($animate.is_running())
.distinct_until_changed()
.subscribe(move |running| {
let mut w = this.write();
w.running = running;
w.forget_modifies();
});
animate
}
}
fn on_frame_end_once(ctx: &LayoutCtx, f: impl FnMut(FrameMsg) + 'static) {
ctx
.window()
.frame_tick_stream()
.filter(|msg| matches!(msg, FrameMsg::Finish(_)))
.take(1)
.subscribe(f);
}
macro_rules! smooth_size_widget_impl {
($name:ident, $size_ty:ty $(, $field:ident)?) => {
#[doc = "This widget enables smooth size transitions for its child after layout.\
See the [module-level documentation](self) for more."]
#[derive(Default)]
pub struct $name(Stateful<SmoothImpl<$size_ty>>);
impl WrapRender for $name {
fn perform_layout(&self, mut clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx)
-> Size
{
if !ctx.window().flags().contains(WindowFlags::ANIMATIONS) {
return host.perform_layout(clamp, ctx);
}
let SmoothImpl { running, value } = *self.0.read();
if !running {
let size = host.perform_layout(clamp, ctx);
let new_v = size $(.$field)?;
if value != new_v {
let this = self.0.clone_writer();
on_frame_end_once(ctx, move |_| this.write().value = new_v);
}
}
clamp.min $(.$field)? = value;
clamp.max $(.$field)? = value;
host.perform_layout(clamp, ctx)
}
}
impl<'c> ComposeChild<'c> for $name {
type Child = Widget<'c>;
fn compose_child(this: impl StateWriter<Value = Self>, child: Self::Child) -> Widget<'c> {
fn_widget!{
let modifies = this.read().0.raw_modifies();
WrapRender::combine_child(this, child)
.on_build(move |id, | id.dirty_on(modifies) )
}.into_widget()
}
}
impl $name {
#[doc = "Enable the transition with the provided argument and return the animation of the transition."]
pub fn transition(&self, transition: impl Transition + 'static)
-> Stateful< Animate<impl AnimateState + 'static>>
{
self.0.transition(transition, )
}
}
};
}
macro_rules! smooth_pos_widget_impl {
($name:ident, $size_ty:ty $(, $field:ident)?) => {
#[doc = "This widget enables smooth position transitions for its child after layout.\
See the [module-level documentation](self) for more."]
#[derive(Default)]
pub struct $name(Stateful<SmoothImpl<$size_ty>>);
impl WrapRender for $name {
fn perform_layout(&self, clamp: BoxClamp, host: &dyn Render, ctx: &mut LayoutCtx) -> Size {
if !ctx.window().flags().contains(WindowFlags::ANIMATIONS) {
return host.perform_layout(clamp, ctx);
}
let smooth = self.0.clone_writer();
if !smooth.read().running {
let wid = ctx.widget_id();
let wnd = ctx.window();
on_frame_end_once(ctx, move |_| {
let pos = wnd.map_to_global(Point::zero(), wid);
smooth.write().value = pos$(.$field)?;
});
}
host.perform_layout(clamp, ctx)
}
fn paint(&self, host: &dyn Render, ctx: &mut PaintingCtx) {
if !ctx.window().flags().contains(WindowFlags::ANIMATIONS) {
return host.paint(ctx);
}
let SmoothImpl { running, value } = *self.0.read();
if running {
let pos = ctx.map_to_global(Point::zero());
#[allow(unused_assignments)]
let mut expect = pos;
expect $(.$field)? = value;
let offset = expect - pos;
ctx.painter().translate(offset.x, offset.y);
}
host.paint(ctx);
}
}
impl_compose_child_for_wrap_render!($name);
impl $name {
#[doc = "Enable the transition with the provided argument and return the animation of the transition."]
pub fn transition(&self, transition: impl Transition + 'static)
-> Stateful< Animate<impl AnimateState + 'static>>
{
self.0.transition(transition)
}
}
};
}
use smooth_pos_widget_impl;
use smooth_size_widget_impl;