diff --git a/druid/src/contexts.rs b/druid/src/contexts.rs index df89f149d5..dc3f9df4dd 100644 --- a/druid/src/contexts.rs +++ b/druid/src/contexts.rs @@ -357,7 +357,9 @@ impl<'a> EventCtx<'a> { /// request with the event. pub fn request_timer(&mut self, deadline: Duration) -> TimerToken { self.base_state.request_timer = true; - self.window.request_timer(deadline) + let timer_token = self.window.request_timer(deadline); + self.base_state.add_timer(timer_token); + timer_token } /// The layout size. diff --git a/druid/src/core.rs b/druid/src/core.rs index c43d72e3da..1c3cca22f1 100644 --- a/druid/src/core.rs +++ b/druid/src/core.rs @@ -14,15 +14,15 @@ //! The fundamental druid types. -use std::collections::VecDeque; +use std::collections::{HashMap, VecDeque}; use crate::bloom::Bloom; use crate::kurbo::{Affine, Insets, Point, Rect, Shape, Size, Vec2}; use crate::piet::RenderContext; use crate::{ BoxConstraints, Command, Data, Env, Event, EventCtx, InternalEvent, InternalLifeCycle, - LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Region, Target, UpdateCtx, Widget, WidgetId, - WindowId, + LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Region, Target, TimerToken, UpdateCtx, Widget, + WidgetId, WindowId, }; /// Our queue type @@ -109,6 +109,8 @@ pub(crate) struct BaseState { pub(crate) request_focus: Option, pub(crate) children: Bloom, pub(crate) children_changed: bool, + /// Associate timers with widgets that requested them. + pub(crate) timers: HashMap, } /// Methods by which a widget can attempt to change focus state. @@ -516,6 +518,15 @@ impl> WidgetPod { } } } + InternalEvent::RouteTimer(token, widget_id) => { + let widget_id = *widget_id; + if widget_id != child_ctx.base_state.id { + recurse = child_ctx.base_state.children.may_contain(&widget_id); + Event::Internal(InternalEvent::RouteTimer(*token, widget_id)) + } else { + Event::Timer(*token) + } + } }, Event::WindowConnected => Event::WindowConnected, Event::WindowSize(size) => { @@ -591,9 +602,9 @@ impl> WidgetPod { recurse = had_active || child_ctx.base_state.is_hot; Event::Zoom(*zoom) } - Event::Timer(id) => { - recurse = child_ctx.base_state.request_timer; - Event::Timer(*id) + Event::Timer(token) => { + recurse = false; + Event::Timer(*token) } Event::Command(cmd) => Event::Command(cmd.clone()), }; @@ -604,6 +615,8 @@ impl> WidgetPod { }; ctx.base_state.merge_up(&child_ctx.base_state); + // Clear current widget's timers after merging with parent. + child_ctx.base_state.timers.clear(); ctx.is_handled |= child_ctx.is_handled; } @@ -788,9 +801,14 @@ impl BaseState { focus_chain: Vec::new(), children: Bloom::new(), children_changed: false, + timers: HashMap::new(), } } + pub(crate) fn add_timer(&mut self, timer_token: TimerToken) { + self.timers.insert(timer_token, self.id); + } + /// Update to incorporate state changes from a child. fn merge_up(&mut self, child_state: &BaseState) { let mut child_region = child_state.invalid.clone(); @@ -809,6 +827,7 @@ impl BaseState { self.has_focus |= child_state.has_focus; self.children_changed |= child_state.children_changed; self.request_focus = self.request_focus.or(child_state.request_focus); + self.timers.extend(&child_state.timers); } #[inline] diff --git a/druid/src/event.rs b/druid/src/event.rs index 6bb306f9c8..cb2ebd885d 100644 --- a/druid/src/event.rs +++ b/druid/src/event.rs @@ -147,6 +147,8 @@ pub enum InternalEvent { MouseLeave, /// A command still in the process of being dispatched. TargetedCommand(Target, Command), + /// Used for routing timer events. + RouteTimer(TimerToken, WidgetId), } /// Application life cycle events. diff --git a/druid/src/window.rs b/druid/src/window.rs index 9f6a787c22..2437530a89 100644 --- a/druid/src/window.rs +++ b/druid/src/window.rs @@ -14,6 +14,7 @@ //! Management of multiple windows. +use std::collections::HashMap; use std::mem; // Automatically defaults to std::time::Instant on non Wasm platforms @@ -28,8 +29,8 @@ use crate::widget::LabelText; use crate::win_handler::RUN_COMMANDS_TOKEN; use crate::{ BoxConstraints, Command, Data, Env, Event, EventCtx, InternalEvent, InternalLifeCycle, - LayoutCtx, LifeCycle, LifeCycleCtx, MenuDesc, PaintCtx, UpdateCtx, Widget, WidgetId, WidgetPod, - WindowDesc, + LayoutCtx, LifeCycle, LifeCycleCtx, MenuDesc, PaintCtx, TimerToken, UpdateCtx, Widget, + WidgetId, WidgetPod, WindowDesc, }; /// A unique identifier for a window. @@ -48,6 +49,7 @@ pub struct Window { pub(crate) last_mouse_pos: Option, pub(crate) focus: Option, pub(crate) handle: WindowHandle, + pub(crate) timers: HashMap, // delegate? } @@ -64,6 +66,7 @@ impl Window { last_mouse_pos: None, focus: None, handle, + timers: HashMap::new(), } } } @@ -171,6 +174,14 @@ impl Window { self.size = Size::new(size.width * scale, size.height * scale); Event::WindowSize(self.size) } + Event::Timer(token) => { + if let Some(widget_id) = self.timers.get(&token) { + Event::Internal(InternalEvent::RouteTimer(token, *widget_id)) + } else { + log::error!("No widget found for timer {:?}", token); + return false; + } + } other => other, }; @@ -215,6 +226,17 @@ impl Window { self.post_event_processing(queue, data, env, false); + //In some platforms, timer tokens are reused. So it is necessary to remove the token from + //the window's timer map before adding new tokens to it. + if let Event::Internal(InternalEvent::RouteTimer(token, _)) = event { + self.timers.remove(&token); + } + + //If at least one widget requested a timer, add all the requested timers to window's timers map. + if base_state.request_timer { + self.timers.extend(base_state.timers); + } + is_handled }