Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Associate timers with widget ids #831

Merged
merged 12 commits into from
Apr 28, 2020
4 changes: 3 additions & 1 deletion druid/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
31 changes: 25 additions & 6 deletions druid/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -109,6 +109,8 @@ pub(crate) struct BaseState {
pub(crate) request_focus: Option<FocusChange>,
pub(crate) children: Bloom<WidgetId>,
pub(crate) children_changed: bool,
/// Associate timers with widgets that requested them.
pub(crate) timers: HashMap<TimerToken, WidgetId>,
}

/// Methods by which a widget can attempt to change focus state.
Expand Down Expand Up @@ -516,6 +518,15 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
}
}
}
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) => {
Expand Down Expand Up @@ -591,9 +602,9 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
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()),
};
Expand All @@ -604,6 +615,8 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
};

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;
}

Expand Down Expand Up @@ -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();
Expand All @@ -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]
Expand Down
2 changes: 2 additions & 0 deletions druid/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment here describing this variant. Something simple like Used to route timer events. would work.

}

/// Application life cycle events.
Expand Down
26 changes: 24 additions & 2 deletions druid/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -48,6 +49,7 @@ pub struct Window<T> {
pub(crate) last_mouse_pos: Option<Point>,
pub(crate) focus: Option<WidgetId>,
pub(crate) handle: WindowHandle,
pub(crate) timers: HashMap<TimerToken, WidgetId>,
// delegate?
}

Expand All @@ -64,6 +66,7 @@ impl<T> Window<T> {
last_mouse_pos: None,
focus: None,
handle,
timers: HashMap::new(),
}
}
}
Expand Down Expand Up @@ -171,6 +174,14 @@ impl<T: Data> Window<T> {
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,
};

Expand Down Expand Up @@ -215,6 +226,17 @@ impl<T: Data> Window<T> {

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
}

Expand Down