mirror of
https://github.com/danbulant/cushy
synced 2026-05-26 05:12:07 +00:00
Tuple animations
This commit is contained in:
parent
64f46a46e2
commit
ed31805693
10 changed files with 310 additions and 101 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
168
src/animation.rs
168
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<Animating> = Mutex::new(Animating::new());
|
||||
|
|
@ -106,61 +107,137 @@ pub trait Animate: Send + Sync {
|
|||
fn animate(&mut self, elapsed: Duration) -> ControlFlow<Duration>;
|
||||
}
|
||||
|
||||
/// A pending transition for a [`Dynamic`] to a new value.
|
||||
pub struct DynamicTransition<T> {
|
||||
/// The dynamic value to change.
|
||||
pub dynamic: Dynamic<T>,
|
||||
/// The final value to store in the [`Dynamic`].
|
||||
pub new_value: T,
|
||||
}
|
||||
|
||||
impl<T> AnimationTarget for DynamicTransition<T>
|
||||
where
|
||||
T: LinearInterpolate + Clone + Send + Sync,
|
||||
{
|
||||
type Running = TransitioningDynamic<T>;
|
||||
|
||||
fn begin(self) -> Self::Running {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`DynamicTransition`] that has begun its transition.
|
||||
pub struct TransitioningDynamic<T> {
|
||||
change: DynamicTransition<T>,
|
||||
start: T,
|
||||
}
|
||||
|
||||
impl<T> From<DynamicTransition<T>> for TransitioningDynamic<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
fn from(change: DynamicTransition<T>) -> Self {
|
||||
Self {
|
||||
start: change.dynamic.get(),
|
||||
change,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AnimateTarget for TransitioningDynamic<T>
|
||||
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<T, Easing = Linear> {
|
||||
value: Dynamic<T>,
|
||||
end: T,
|
||||
pub struct Animation<Target, Easing = Linear>
|
||||
where
|
||||
Target: AnimationTarget,
|
||||
{
|
||||
value: Target,
|
||||
duration: Duration,
|
||||
_easing: PhantomData<Easing>,
|
||||
}
|
||||
|
||||
impl<T> Animation<T, Linear>
|
||||
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<T>, end_value: T, duration: Duration) -> Self {
|
||||
Self::new(value, end_value, duration)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Easing> Animation<T, Easing>
|
||||
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<T>, 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<Easing: self::Easing>(self) -> Animation<T, Easing> {
|
||||
Animation {
|
||||
value: self.value,
|
||||
duration: self.duration,
|
||||
_easing: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Easing> IntoAnimate for Animation<T, Easing>
|
||||
where
|
||||
T: LinearInterpolate + Clone + Send + Sync + 'static,
|
||||
T: AnimationTarget,
|
||||
Easing: self::Easing,
|
||||
{
|
||||
type Animate = RunningAnimation<T, Easing>;
|
||||
type Animate = RunningAnimation<T::Running, Easing>;
|
||||
|
||||
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<Self, Linear> {
|
||||
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<dyn Animate>`.
|
||||
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<Other: IntoAnimate>(self, other: Other) -> Chain<Self, Other> {
|
||||
fn and_then<Other: IntoAnimate>(self, other: Other) -> Chain<Self, Other> {
|
||||
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<T> BoxAnimate for T
|
||||
where
|
||||
T: IntoAnimate + 'static,
|
||||
|
|
@ -219,22 +320,20 @@ impl Spawn for Box<dyn Animate> {
|
|||
|
||||
impl<T, Easing> Animate for RunningAnimation<T, Easing>
|
||||
where
|
||||
T: LinearInterpolate + Clone + Send + Sync,
|
||||
T: AnimateTarget,
|
||||
Easing: self::Easing,
|
||||
{
|
||||
fn animate(&mut self, elapsed: Duration) -> ControlFlow<Duration> {
|
||||
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<T, Easing> {
|
||||
animation: Animation<T, Easing>,
|
||||
start: T,
|
||||
target: T,
|
||||
duration: Duration,
|
||||
elapsed: Duration,
|
||||
_easing: PhantomData<Easing>,
|
||||
}
|
||||
|
||||
/// A handle to a spawned animation. When dropped, the associated animation will
|
||||
|
|
|
|||
|
|
@ -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<T>(&self, value: &Dynamic<T>) {
|
||||
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<sealed::WindowCommand>,
|
||||
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<AtomicBool>,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
src/lib.rs
13
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
25
src/value.rs
25
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<T> Dynamic<T> {
|
|||
with_clone(self.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn redraw_when_changed(&self, window: WindowHandle<WindowCommand>) {
|
||||
pub(crate) fn redraw_when_changed(&self, window: WindowHandle) {
|
||||
self.0.redraw_when_changed(window);
|
||||
}
|
||||
|
||||
|
|
@ -136,6 +134,17 @@ impl<T> Dynamic<T> {
|
|||
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<T>
|
||||
where
|
||||
T: LinearInterpolate + Clone + Send + Sync,
|
||||
{
|
||||
DynamicTransition {
|
||||
dynamic: self.clone(),
|
||||
new_value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Dynamic<T>
|
||||
|
|
@ -185,7 +194,7 @@ impl<T> DynamicData<T> {
|
|||
.map_or_else(PoisonError::into_inner, |g| g)
|
||||
}
|
||||
|
||||
pub fn redraw_when_changed(&self, window: WindowHandle<WindowCommand>) {
|
||||
pub fn redraw_when_changed(&self, window: WindowHandle) {
|
||||
let mut state = self.state();
|
||||
state.windows.push(window);
|
||||
}
|
||||
|
|
@ -211,7 +220,7 @@ impl<T> DynamicData<T> {
|
|||
callback.update(&state.wrapped);
|
||||
}
|
||||
for window in state.windows.drain(..) {
|
||||
let _result = window.send(WindowCommand::Redraw);
|
||||
window.redraw();
|
||||
}
|
||||
result
|
||||
};
|
||||
|
|
@ -251,7 +260,7 @@ impl<T> DynamicData<T> {
|
|||
struct State<T> {
|
||||
wrapped: GenerationalValue<T>,
|
||||
callbacks: Vec<Box<dyn ValueCallback<T>>>,
|
||||
windows: Vec<WindowHandle<WindowCommand>>,
|
||||
windows: Vec<WindowHandle>,
|
||||
readers: usize,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<Px>, _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<'_, '_, '_, '_, '_>) {
|
||||
|
|
|
|||
|
|
@ -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<String> = 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<T> {
|
|||
contents: Drawing,
|
||||
should_close: bool,
|
||||
mouse_state: MouseState,
|
||||
redraw_status: RedrawStatus,
|
||||
}
|
||||
|
||||
impl<T> GooeyWindow<T>
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue