From ed3180569305c78496f8d736ba74e7a468ee4603 Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Thu, 2 Nov 2023 07:48:30 -0700 Subject: [PATCH] Tuple animations --- Cargo.lock | 12 +-- examples/animation.rs | 9 ++- src/animation.rs | 168 +++++++++++++++++++++++++++++++++--------- src/context.rs | 48 +++++++++++- src/lib.rs | 13 ++++ src/value.rs | 25 +++++-- src/widgets/button.rs | 12 ++- src/widgets/scroll.rs | 36 +++++---- src/window.rs | 79 +++++++++++++++----- src/with_clone.rs | 9 +-- 10 files changed, 310 insertions(+), 101 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab0d3d8..685798f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -383,9 +383,9 @@ dependencies = [ [[package]] name = "cursor-icon" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740bb192a8e2d1350119916954f4409ee7f62f149b536911eeb78ba5a20526bf" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" [[package]] name = "d3d12" @@ -2428,18 +2428,18 @@ checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" [[package]] name = "zerocopy" -version = "0.7.21" +version = "0.7.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686b7e407015242119c33dab17b8f61ba6843534de936d94368856528eae4dcc" +checksum = "e50cbb27c30666a6108abd6bc7577556265b44f243e2be89a8bc4e07a528c107" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.21" +version = "0.7.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020f3dfe25dfc38dfea49ce62d5d45ecdd7f0d8a724fa63eb36b6eba4ec76806" +checksum = "a25f293fe55f0a48e7010d65552bb63704f6ceb55a1a385da10d41d8f78e4a3d" dependencies = [ "proc-macro2", "quote", diff --git a/examples/animation.rs b/examples/animation.rs index 972bf3b..d987f50 100644 --- a/examples/animation.rs +++ b/examples/animation.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use gooey::animation::{Animation, AnimationHandle, Spawn}; +use gooey::animation::{AnimationHandle, AnimationTarget, Spawn}; use gooey::value::Dynamic; use gooey::widgets::{Button, Label, Stack}; use gooey::{widgets, Run, WithClone}; @@ -24,7 +24,12 @@ fn animate_to( ) -> impl FnMut(()) { (animation, value).with_clone(|(animation, value)| { move |_| { - animation.set(Animation::linear(value.clone(), target, Duration::from_secs(1)).spawn()) + animation.set( + value + .transition_to(target) + .over(Duration::from_secs(1)) + .spawn(), + ) } }) } diff --git a/src/animation.rs b/src/animation.rs index 4d560e7..f8ee704 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -11,6 +11,7 @@ use alot::{LotId, Lots}; use kempt::Set; use kludgine::Color; +use crate::impl_all_tuples; use crate::value::Dynamic; static ANIMATIONS: Mutex = Mutex::new(Animating::new()); @@ -106,61 +107,137 @@ pub trait Animate: Send + Sync { fn animate(&mut self, elapsed: Duration) -> ControlFlow; } +/// A pending transition for a [`Dynamic`] to a new value. +pub struct DynamicTransition { + /// The dynamic value to change. + pub dynamic: Dynamic, + /// The final value to store in the [`Dynamic`]. + pub new_value: T, +} + +impl AnimationTarget for DynamicTransition +where + T: LinearInterpolate + Clone + Send + Sync, +{ + type Running = TransitioningDynamic; + + fn begin(self) -> Self::Running { + self.into() + } +} + +/// A [`DynamicTransition`] that has begun its transition. +pub struct TransitioningDynamic { + change: DynamicTransition, + start: T, +} + +impl From> for TransitioningDynamic +where + T: Clone, +{ + fn from(change: DynamicTransition) -> Self { + Self { + start: change.dynamic.get(), + change, + } + } +} + +impl AnimateTarget for TransitioningDynamic +where + T: LinearInterpolate + Clone + Send + Sync, +{ + fn update(&self, percent: f32) { + self.change + .dynamic + .set(self.start.lerp(&self.change.new_value, percent)); + } + + fn finish(&self) { + self.change.dynamic.set(self.change.new_value.clone()); + } +} + /// Describes a change to a new value for a [`Dynamic`] over a specified /// [`Duration`], using the `Easing` generic parameter to control how the value /// is interpolated. #[must_use = "animations are not performed until they are spawned"] -pub struct Animation { - value: Dynamic, - end: T, +pub struct Animation +where + Target: AnimationTarget, +{ + value: Target, duration: Duration, _easing: PhantomData, } impl Animation where - T: LinearInterpolate + Clone + Send + Sync + 'static, + T: AnimationTarget, { - /// Returns a linearly interpolated animation that transitions `value` to - /// `end_value` over `duration`. - pub fn linear(value: Dynamic, end_value: T, duration: Duration) -> Self { - Self::new(value, end_value, duration) - } -} - -impl Animation -where - T: LinearInterpolate + Clone + Send + Sync + 'static, - Easing: self::Easing, -{ - /// Returns an animation that transitions `value` to `end_value` over - /// `duration` using `Easing` for interpolation. - pub fn new(value: Dynamic, end_value: T, duration: Duration) -> Self { + fn new(value: T, duration: Duration) -> Self { Self { value, - end: end_value, duration, _easing: PhantomData, } } + + /// Returns this animation with a different easing function. + pub fn with_easing(self) -> Animation { + Animation { + value: self.value, + duration: self.duration, + _easing: PhantomData, + } + } } impl IntoAnimate for Animation where - T: LinearInterpolate + Clone + Send + Sync + 'static, + T: AnimationTarget, Easing: self::Easing, { - type Animate = RunningAnimation; + type Animate = RunningAnimation; fn into_animate(self) -> Self::Animate { RunningAnimation { - start: self.value.get(), - animation: self, + target: self.value.begin(), + duration: self.duration, elapsed: Duration::ZERO, + _easing: PhantomData, } } } +/// A target for a timed [`Animation`]. +pub trait AnimationTarget: Sized + Send + Sync { + /// The type that can linearly interpolate this target. + type Running: AnimateTarget; + + /// Record the current value of the target, and return a type that can + /// linearly interpolate between the current value and the desired value. + fn begin(self) -> Self::Running; + + /// Returns a pending animation that linearly transitions `self` over + /// `duration`. + /// + /// A different [`Easing`] can be used by calling + /// [`with_easing`](Animation::with_easing) on the result of this function. + fn over(self, duration: Duration) -> Animation { + Animation::new(self, duration) + } +} + +/// The target of an [`Animate`] implementor. +pub trait AnimateTarget: Send + Sync { + /// Updates the target with linear interpolation. + fn update(&self, percent: f32); + /// Sets the target to the desired completion state. + fn finish(&self); +} + /// A type that can convert into `Box`. pub trait BoxAnimate { /// Returns the boxed animation. @@ -177,11 +254,35 @@ pub trait IntoAnimate: Sized + Send + Sync { /// Returns an combined animation that performs `self` and `other` in /// sequence. - fn chain(self, other: Other) -> Chain { + fn and_then(self, other: Other) -> Chain { Chain::new(self, other) } } +macro_rules! impl_tuple_animate { + ($($type:ident $field:tt),+) => { + impl<$($type),+> AnimationTarget for ($($type,)+) where $($type: AnimationTarget),+ { + type Running = ($(<$type>::Running,)+); + + fn begin(self) -> Self::Running { + ($(self.$field.begin(),)+) + } + } + + impl<$($type),+> AnimateTarget for ($($type,)+) where $($type: AnimateTarget),+ { + fn update(&self, percent: f32) { + $(self.$field.update(percent);)+ + } + + fn finish(&self) { + $(self.$field.finish();)+ + } + } + } +} + +impl_all_tuples!(impl_tuple_animate); + impl BoxAnimate for T where T: IntoAnimate + 'static, @@ -219,22 +320,20 @@ impl Spawn for Box { impl Animate for RunningAnimation where - T: LinearInterpolate + Clone + Send + Sync, + T: AnimateTarget, Easing: self::Easing, { fn animate(&mut self, elapsed: Duration) -> ControlFlow { self.elapsed = self.elapsed.checked_add(elapsed).unwrap_or(Duration::MAX); - if let Some(remaining_elapsed) = self.elapsed.checked_sub(self.animation.duration) { - self.animation.value.set(self.animation.end.clone()); + if let Some(remaining_elapsed) = self.elapsed.checked_sub(self.duration) { + self.target.finish(); ControlFlow::Break(remaining_elapsed) } else { let progress = Easing::ease(ZeroToOne::new( - self.elapsed.as_secs_f32() / self.animation.duration.as_secs_f32(), + self.elapsed.as_secs_f32() / self.duration.as_secs_f32(), )); - self.animation - .value - .set(self.start.lerp(&self.animation.end, progress)); + self.target.update(progress); ControlFlow::Continue(()) } } @@ -248,9 +347,10 @@ where /// created: [`IntoAnimate::into_animate`]. [`Easing`] is used to customize how /// interpolation is performed. pub struct RunningAnimation { - animation: Animation, - start: T, + target: T, + duration: Duration, elapsed: Duration, + _easing: PhantomData, } /// A handle to a spawned animation. When dropped, the associated animation will diff --git a/src/context.rs b/src/context.rs index 6ee7f96..3fda1f4 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,5 +1,7 @@ //! Types that provide access to the Gooey runtime. use std::ops::{Deref, DerefMut}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use kludgine::app::winit::event::{ DeviceId, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase, @@ -14,7 +16,7 @@ use crate::styles::components::HighlightColor; use crate::styles::{ComponentDefaultvalue, ComponentDefinition, Styles}; use crate::value::Dynamic; use crate::widget::{EventHandling, ManagedWidget, WidgetInstance}; -use crate::window::RunningWindow; +use crate::window::{sealed, RunningWindow}; use crate::ConstraintLimit; /// A context to an event function. @@ -378,6 +380,7 @@ impl<'window> AsEventContext<'window> for GraphicsContext<'_, 'window, '_, '_, ' /// specific widget. pub struct WidgetContext<'context, 'window> { current_node: &'context ManagedWidget, + redraw_status: &'context RedrawStatus, window: &'context mut RunningWindow<'window>, pending_state: PendingState<'context>, } @@ -385,10 +388,12 @@ pub struct WidgetContext<'context, 'window> { impl<'context, 'window> WidgetContext<'context, 'window> { pub(crate) fn new( current_node: &'context ManagedWidget, + redraw_status: &'context RedrawStatus, window: &'context mut RunningWindow<'window>, ) -> Self { Self { current_node, + redraw_status, window, pending_state: PendingState::Owned(PendingWidgetState { focus: current_node @@ -407,6 +412,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> { pub fn borrowed(&mut self) -> WidgetContext<'_, 'window> { WidgetContext { current_node: self.current_node, + redraw_status: self.redraw_status, window: &mut *self.window, pending_state: self.pending_state.borrowed(), } @@ -419,6 +425,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> { ) -> WidgetContext<'child, 'window> { WidgetContext { current_node: widget, + redraw_status: self.redraw_status, window: &mut *self.window, pending_state: self.pending_state.borrowed(), } @@ -430,7 +437,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> { /// Ensures that this widget will be redrawn when `value` has been updated. pub fn redraw_when_changed(&self, value: &Dynamic) { - value.redraw_when_changed(self.window.handle()); + value.redraw_when_changed(self.handle()); } /// Returns the region that this widget last rendered at. @@ -576,6 +583,26 @@ impl<'context, 'window> WidgetContext<'context, 'window> { ) -> Component::ComponentType { self.current_node.tree.query_style(self.current_node, query) } + + pub(crate) fn handle(&self) -> WindowHandle { + WindowHandle { + kludgine: self.window.handle(), + redraw_status: self.redraw_status.clone(), + } + } +} + +pub(crate) struct WindowHandle { + kludgine: kludgine::app::WindowHandle, + redraw_status: RedrawStatus, +} + +impl WindowHandle { + pub fn redraw(&self) { + if self.redraw_status.should_send_refresh() { + let _result = self.kludgine.send(sealed::WindowCommand::Redraw); + } + } } impl dyn AsEventContext<'_> {} @@ -637,3 +664,20 @@ impl DerefMut for PendingState<'_> { } } } + +#[derive(Default, Clone)] +pub(crate) struct RedrawStatus { + refresh_sent: Arc, +} + +impl RedrawStatus { + pub fn should_send_refresh(&self) -> bool { + self.refresh_sent + .compare_exchange(false, true, Ordering::Release, Ordering::Acquire) + .is_ok() + } + + pub fn refresh_received(&self) { + self.refresh_sent.store(false, Ordering::Release); + } +} diff --git a/src/lib.rs b/src/lib.rs index bad1944..be8dd20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,3 +100,16 @@ macro_rules! styles { $crate::styles!($($component => $value),*) }}; } + +#[doc(hidden)] +#[macro_export] +macro_rules! impl_all_tuples { + ($macro_name:ident) => { + $macro_name!(T0 0); + $macro_name!(T0 0, T1 1); + $macro_name!(T0 0, T1 1, T2 2); + $macro_name!(T0 0, T1 1, T2 2, T3 3); + $macro_name!(T0 0, T1 1, T2 2, T3 3, T4 4); + $macro_name!(T0 0, T1 1, T2 2, T3 3, T4 4, T5 5); + } +} diff --git a/src/value.rs b/src/value.rs index fdfab23..17db149 100644 --- a/src/value.rs +++ b/src/value.rs @@ -4,10 +4,8 @@ use std::fmt::Debug; use std::panic::AssertUnwindSafe; use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError}; -use kludgine::app::WindowHandle; - -use crate::context::WidgetContext; -use crate::window::sealed::WindowCommand; +use crate::animation::{DynamicTransition, LinearInterpolate}; +use crate::context::{WidgetContext, WindowHandle}; /// An instance of a value that provides APIs to observe and react to its /// contents. @@ -90,7 +88,7 @@ impl Dynamic { with_clone(self.clone()) } - pub(crate) fn redraw_when_changed(&self, window: WindowHandle) { + pub(crate) fn redraw_when_changed(&self, window: WindowHandle) { self.0.redraw_when_changed(window); } @@ -136,6 +134,17 @@ impl Dynamic { pub fn generation(&self) -> Generation { self.state().wrapped.generation } + + /// Returns a pending transition for this value to `new_value`. + pub fn transition_to(&self, new_value: T) -> DynamicTransition + where + T: LinearInterpolate + Clone + Send + Sync, + { + DynamicTransition { + dynamic: self.clone(), + new_value, + } + } } impl Default for Dynamic @@ -185,7 +194,7 @@ impl DynamicData { .map_or_else(PoisonError::into_inner, |g| g) } - pub fn redraw_when_changed(&self, window: WindowHandle) { + pub fn redraw_when_changed(&self, window: WindowHandle) { let mut state = self.state(); state.windows.push(window); } @@ -211,7 +220,7 @@ impl DynamicData { callback.update(&state.wrapped); } for window in state.windows.drain(..) { - let _result = window.send(WindowCommand::Redraw); + window.redraw(); } result }; @@ -251,7 +260,7 @@ impl DynamicData { struct State { wrapped: GenerationalValue, callbacks: Vec>>, - windows: Vec>, + windows: Vec, readers: usize, } diff --git a/src/widgets/button.rs b/src/widgets/button.rs index 481f4fe..1cad4e9 100644 --- a/src/widgets/button.rs +++ b/src/widgets/button.rs @@ -11,7 +11,7 @@ use kludgine::shapes::Shape; use kludgine::text::Text; use kludgine::Color; -use crate::animation::{Animation, AnimationHandle, Spawn}; +use crate::animation::{AnimationHandle, AnimationTarget, Spawn}; use crate::context::{EventContext, GraphicsContext, WidgetContext}; use crate::names::Name; use crate::styles::components::{HighlightColor, IntrinsicPadding, TextColor}; @@ -77,12 +77,10 @@ impl Button { match (immediate, &self.background_color) { (false, Some(dynamic)) => { - self.background_color_animation = Animation::linear( - dynamic.clone(), - background_color, - Duration::from_millis(150), - ) - .spawn(); + self.background_color_animation = dynamic + .transition_to(background_color) + .over(Duration::from_millis(150)) + .spawn(); } (true, Some(dynamic)) => { dynamic.set(background_color); diff --git a/src/widgets/scroll.rs b/src/widgets/scroll.rs index 510a771..d087d30 100644 --- a/src/widgets/scroll.rs +++ b/src/widgets/scroll.rs @@ -11,7 +11,7 @@ use kludgine::figures::{ use kludgine::shapes::Shape; use kludgine::Color; -use crate::animation::{Animation, AnimationHandle, IntoAnimate, Spawn, ZeroToOne}; +use crate::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn, ZeroToOne}; use crate::context::{AsEventContext, EventContext}; use crate::styles::{ ComponentDefinition, ComponentGroup, ComponentName, Dimension, NamedComponent, @@ -78,27 +78,25 @@ impl Widget for Scroll { } fn hover(&mut self, _location: Point, _context: &mut EventContext<'_, '_>) { - self.scrollbar_opacity_animation = Animation::linear( - self.scrollbar_opacity.clone(), - ZeroToOne::ONE, - Duration::from_millis(300), - ) - .chain(Duration::from_secs(1)) - .chain(Animation::linear( - self.scrollbar_opacity.clone(), - ZeroToOne::ZERO, - Duration::from_millis(300), - )) - .spawn(); + self.scrollbar_opacity_animation = self + .scrollbar_opacity + .transition_to(ZeroToOne::ONE) + .over(Duration::from_millis(300)) + .and_then(Duration::from_secs(1)) + .and_then( + self.scrollbar_opacity + .transition_to(ZeroToOne::ZERO) + .over(Duration::from_millis(300)), + ) + .spawn(); } fn unhover(&mut self, _context: &mut EventContext<'_, '_>) { - self.scrollbar_opacity_animation = Animation::linear( - self.scrollbar_opacity.clone(), - ZeroToOne::ZERO, - Duration::from_millis(300), - ) - .spawn(); + self.scrollbar_opacity_animation = self + .scrollbar_opacity + .transition_to(ZeroToOne::ZERO) + .over(Duration::from_millis(300)) + .spawn(); } fn redraw(&mut self, context: &mut crate::context::GraphicsContext<'_, '_, '_, '_, '_>) { diff --git a/src/window.rs b/src/window.rs index b93a5f3..a9ebbf7 100644 --- a/src/window.rs +++ b/src/window.rs @@ -3,7 +3,11 @@ use std::cell::RefCell; use std::collections::HashMap; +use std::ffi::OsStr; use std::panic::{AssertUnwindSafe, UnwindSafe}; +use std::path::Path; +use std::string::ToString; +use std::sync::OnceLock; use kludgine::app::winit::dpi::PhysicalPosition; use kludgine::app::winit::event::{ @@ -16,7 +20,7 @@ use kludgine::figures::Point; use kludgine::render::Drawing; use kludgine::Kludgine; -use crate::context::{EventContext, Exclusive, GraphicsContext, WidgetContext}; +use crate::context::{EventContext, Exclusive, GraphicsContext, RedrawStatus, WidgetContext}; use crate::graphics::Graphics; use crate::tree::Tree; use crate::utils::ModifiersExt; @@ -69,9 +73,24 @@ where /// Returns a new instance using `context` to initialize the window upon /// opening. pub fn new(context: Behavior::Context) -> Self { + static EXECUTABLE_NAME: OnceLock = OnceLock::new(); + + let title = EXECUTABLE_NAME + .get_or_init(|| { + std::env::args_os() + .next() + .and_then(|path| { + Path::new(&path) + .file_name() + .and_then(OsStr::to_str) + .map(ToString::to_string) + }) + .unwrap_or_else(|| String::from("Gooey App")) + }) + .clone(); Self { attributes: WindowAttributes { - title: String::from("Gooey App"), + title, ..WindowAttributes::default() }, context, @@ -132,6 +151,7 @@ struct GooeyWindow { contents: Drawing, should_close: bool, mouse_state: MouseState, + redraw_status: RedrawStatus, } impl GooeyWindow @@ -169,15 +189,17 @@ where widget: None, devices: HashMap::default(), }, + redraw_status: RedrawStatus::default(), } } fn prepare(&mut self, mut window: RunningWindow<'_>, graphics: &mut kludgine::Graphics<'_>) { + self.redraw_status.refresh_received(); graphics.reset_text_attributes(); self.root.tree.reset_render_order(); let graphics = self.contents.new_frame(graphics); GraphicsContext { - widget: WidgetContext::new(&self.root, &mut window), + widget: WidgetContext::new(&self.root, &self.redraw_status, &mut window), graphics: Exclusive::Owned(Graphics::new(graphics)), } .redraw(); @@ -248,7 +270,10 @@ where ) { let target = self.root.tree.focused_widget().unwrap_or(self.root.id); let target = self.root.tree.widget(target); - let mut target = EventContext::new(WidgetContext::new(&target, &mut window), kludgine); + let mut target = EventContext::new( + WidgetContext::new(&target, &self.redraw_status, &mut window), + kludgine, + ); let handled = recursively_handle_event(&mut target, |widget| { widget.keyboard_input(device_id, input.clone(), is_synthetic) @@ -281,7 +306,10 @@ where let widget = self.root.tree.hovered_widget().unwrap_or(self.root.id); let widget = self.root.tree.widget(widget); - let mut widget = EventContext::new(WidgetContext::new(&widget, &mut window), kludgine); + let mut widget = EventContext::new( + WidgetContext::new(&widget, &self.redraw_status, &mut window), + kludgine, + ); recursively_handle_event(&mut widget, |widget| { widget.mouse_wheel(device_id, delta, phase) }); @@ -292,7 +320,10 @@ where fn ime(&mut self, mut window: RunningWindow<'_>, kludgine: &mut Kludgine, ime: Ime) { let target = self.root.tree.focused_widget().unwrap_or(self.root.id); let target = self.root.tree.widget(target); - let mut target = EventContext::new(WidgetContext::new(&target, &mut window), kludgine); + let mut target = EventContext::new( + WidgetContext::new(&target, &self.redraw_status, &mut window), + kludgine, + ); let _handled = recursively_handle_event(&mut target, |widget| widget.ime(ime.clone())).is_some(); @@ -311,15 +342,19 @@ where if let Some(state) = self.mouse_state.devices.get(&device_id) { // Mouse Drag for (button, handler) in state { - let mut context = - EventContext::new(WidgetContext::new(handler, &mut window), kludgine); + let mut context = EventContext::new( + WidgetContext::new(handler, &self.redraw_status, &mut window), + kludgine, + ); let last_rendered_at = context.last_rendered_at().expect("passed hit test"); context.mouse_drag(location - last_rendered_at.origin, device_id, *button); } } else { // Hover - let mut context = - EventContext::new(WidgetContext::new(&self.root, &mut window), kludgine); + let mut context = EventContext::new( + WidgetContext::new(&self.root, &self.redraw_status, &mut window), + kludgine, + ); self.mouse_state.widget = None; for widget in self.root.tree.widgets_at_point(location) { let mut widget_context = context.for_other(&widget); @@ -350,8 +385,10 @@ where _device_id: DeviceId, ) { if self.mouse_state.widget.take().is_some() { - let mut context = - EventContext::new(WidgetContext::new(&self.root, &mut window), kludgine); + let mut context = EventContext::new( + WidgetContext::new(&self.root, &self.redraw_status, &mut window), + kludgine, + ); context.clear_hover(); } } @@ -366,14 +403,20 @@ where ) { match state { ElementState::Pressed => { - EventContext::new(WidgetContext::new(&self.root, &mut window), kludgine) - .clear_focus(); + EventContext::new( + WidgetContext::new(&self.root, &self.redraw_status, &mut window), + kludgine, + ) + .clear_focus(); if let (ElementState::Pressed, Some(location), Some(hovered)) = (state, &self.mouse_state.location, &self.mouse_state.widget) { if let Some(handler) = recursively_handle_event( - &mut EventContext::new(WidgetContext::new(hovered, &mut window), kludgine), + &mut EventContext::new( + WidgetContext::new(hovered, &self.redraw_status, &mut window), + kludgine, + ), |context| { let relative = *location - context.last_rendered_at().expect("passed hit test").origin; @@ -399,8 +442,10 @@ where self.mouse_state.devices.remove(&device_id); } - let mut context = - EventContext::new(WidgetContext::new(&handler, &mut window), kludgine); + let mut context = EventContext::new( + WidgetContext::new(&handler, &self.redraw_status, &mut window), + kludgine, + ); let relative = if let (Some(last_rendered), Some(location)) = (context.last_rendered_at(), self.mouse_state.location) diff --git a/src/with_clone.rs b/src/with_clone.rs index 6c758a4..17b94ec 100644 --- a/src/with_clone.rs +++ b/src/with_clone.rs @@ -1,3 +1,5 @@ +use crate::impl_all_tuples; + /// Invokes a function with a clone of `self`. pub trait WithClone: Sized { /// The type that results from cloning. @@ -31,9 +33,4 @@ where } } -impl_with_clone!(T1 0); -impl_with_clone!(T1 0, T2 1); -impl_with_clone!(T1 0, T2 1, T3 2); -impl_with_clone!(T1 0, T2 1, T3 2, T4 3); -impl_with_clone!(T1 0, T2 1, T3 2, T4 3, T5 4); -impl_with_clone!(T1 0, T2 1, T3 2, T4 3, T5 4, T6 5); +impl_all_tuples!(impl_with_clone);