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
30 changes: 23 additions & 7 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,10 +602,7 @@ 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) => Event::Timer(*token),
Copy link
Member

Choose a reason for hiding this comment

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

Add recurse = false; here because if we get this then we're already a descendant of the targeted widget.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. Missed this important change. :(
Thanks.

Event::Command(cmd) => Event::Command(cmd.clone()),
};
if recurse {
Expand All @@ -604,6 +612,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 +798,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 +824,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
1 change: 1 addition & 0 deletions druid/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ pub enum InternalEvent {
MouseLeave,
/// A command still in the process of being dispatched.
TargetedCommand(Target, Command),
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 token from
//window's timer before adding base state's timers to it.
Copy link
Member

Choose a reason for hiding this comment

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

In On some platforms, timer tokens are reused. So it is necessary to remove the token from the window's timer map before adding base state's timers new tokens to it.

if let Event::Internal(InternalEvent::RouteTimer(token, _)) = event {
self.timers.remove(&token);
}

//If at least one widget requested timer, collect those timers from widgets and add to window's timers map.
Copy link
Member

Choose a reason for hiding this comment

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

If at least one widget requested a timer, collect those timers from widgets and 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