Skip to content

Commit a8f3781

Browse files
committed
implement timers
1 parent 3574002 commit a8f3781

File tree

12 files changed

+263
-42
lines changed

12 files changed

+263
-42
lines changed

masonry/examples/animation.rs

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2019 the Xilem Authors and the Druid Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! This is a very small example of how to setup a masonry application.
5+
//! It does the almost bare minimum while still being useful.
6+
7+
// On Windows platform, don't show a console when opening the app.
8+
//#![windows_subsystem = "windows"]
9+
10+
use masonry::dpi::LogicalSize;
11+
use masonry::text::StyleProperty;
12+
use masonry::widget::{Button, Flex, Label, ProgressBar, RootWidget};
13+
use masonry::{Action, AppDriver, DriverCtx, FontWeight, WidgetId};
14+
use winit::window::Window;
15+
16+
const VERTICAL_WIDGET_SPACING: f64 = 20.0;
17+
18+
struct Driver;
19+
20+
impl AppDriver for Driver {
21+
fn on_action(&mut self, _ctx: &mut DriverCtx<'_>, _widget_id: WidgetId, action: Action) {
22+
match action {
23+
Action::ButtonPressed(_) => {
24+
println!("Hello");
25+
}
26+
action => {
27+
eprintln!("Unexpected action {action:?}");
28+
}
29+
}
30+
}
31+
}
32+
33+
fn main() {
34+
let progress_bar = ProgressBar::new(None).animate(true);
35+
36+
let window_size = LogicalSize::new(400.0, 400.0);
37+
let window_attributes = Window::default_attributes()
38+
.with_title("Hello World!")
39+
.with_resizable(true)
40+
.with_min_inner_size(window_size);
41+
42+
masonry::event_loop_runner::run(
43+
masonry::event_loop_runner::EventLoop::with_user_event(),
44+
window_attributes,
45+
RootWidget::new(progress_bar),
46+
Driver,
47+
)
48+
.unwrap();
49+
}

masonry/src/contexts.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
//! The context types that are passed into various widget methods.
55
6-
use std::time::Duration;
6+
use std::time::{Duration, Instant};
77

88
use accesskit::TreeUpdate;
99
use dpi::LogicalPosition;
@@ -18,6 +18,8 @@ use crate::passes::layout::run_layout_on;
1818
use crate::render_root::{MutateCallback, RenderRootSignal, RenderRootState};
1919
use crate::text::BrushIndex;
2020
use crate::theme::get_debug_color;
21+
use crate::timers::Timer;
22+
pub use crate::timers::TimerId;
2123
use crate::widget::{WidgetMut, WidgetRef, WidgetState};
2224
use crate::{
2325
AllowRawMut, BoxConstraints, Color, Insets, Point, Rect, Size, Widget, WidgetId, WidgetPod,
@@ -714,8 +716,13 @@ impl_context_method!(
714716
///
715717
/// The return value is a token, which can be used to associate the
716718
/// request with the event.
717-
pub fn request_timer(&mut self, _deadline: Duration) -> TimerToken {
718-
todo!("request_timer");
719+
pub fn request_timer(&mut self, deadline: Duration) -> TimerId {
720+
let deadline = Instant::now() + deadline;
721+
let timer = Timer::new(self.widget_id(), deadline);
722+
let id = timer.id;
723+
self.global_state
724+
.emit_signal(RenderRootSignal::TimerRequested(timer));
725+
id
719726
}
720727

721728
/// Mark child widget as stashed.
@@ -738,9 +745,6 @@ impl_context_method!(
738745
}
739746
);
740747

741-
// FIXME - Remove
742-
pub struct TimerToken;
743-
744748
impl EventCtx<'_> {
745749
// TODO - clearly document all semantics of pointer capture when they've been decided on
746750
// TODO - Figure out cases where widget should be notified of pointer capture

masonry/src/event.rs

+8
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
//! Events.
55
66
use std::path::PathBuf;
7+
use std::time::Instant;
78

89
use winit::event::{Force, Ime, KeyEvent, Modifiers};
910
use winit::keyboard::ModifiersState;
1011

1112
use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize};
1213
use crate::kurbo::Rect;
14+
use crate::timers::TimerId;
1315

1416
// TODO - Occluded(bool) event
1517
// TODO - winit ActivationTokenDone thing
@@ -213,6 +215,12 @@ pub struct AccessEvent {
213215
pub data: Option<accesskit::ActionData>,
214216
}
215217

218+
#[derive(Debug, Clone, Copy)]
219+
pub struct TimerEvent {
220+
pub id: TimerId,
221+
pub deadline: Instant,
222+
}
223+
216224
#[derive(Debug, Clone)]
217225
pub struct PointerState {
218226
// TODO

masonry/src/event_loop_runner.rs

+41-30
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Copyright 2024 the Xilem Authors
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use std::cmp::Reverse;
5-
use std::collections::BinaryHeap;
64
use std::num::NonZeroUsize;
75
use std::sync::Arc;
86
use std::time::Instant;
@@ -19,13 +17,14 @@ use winit::event::{
1917
DeviceEvent as WinitDeviceEvent, DeviceId, MouseButton as WinitMouseButton,
2018
WindowEvent as WinitWindowEvent,
2119
};
22-
use winit::event_loop::ActiveEventLoop;
20+
use winit::event_loop::{ActiveEventLoop, ControlFlow};
2321
use winit::window::{Window, WindowAttributes, WindowId};
2422

2523
use crate::app_driver::{AppDriver, DriverCtx};
2624
use crate::dpi::LogicalPosition;
2725
use crate::event::{PointerButton, PointerState, WindowEvent};
2826
use crate::render_root::{self, RenderRoot, WindowSizePolicy};
27+
use crate::timers::TimerQueue;
2928
use crate::{Color, PointerEvent, TextEvent, Widget, WidgetId};
3029

3130
#[derive(Debug)]
@@ -83,8 +82,7 @@ pub struct MasonryState<'a> {
8382
proxy: EventLoopProxy,
8483
#[cfg(feature = "tracy")]
8584
frame: Option<tracing_tracy::client::Frame>,
86-
// timers as a min priority queue
87-
timers: BinaryHeap<Reverse<Instant>>,
85+
timers: TimerQueue,
8886

8987
// Per-Window state
9088
// In future, this will support multiple windows
@@ -163,20 +161,12 @@ impl ApplicationHandler<MasonryUserEvent> for MainState<'_> {
163161
self.masonry_state.handle_suspended(event_loop);
164162
}
165163

166-
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
167-
// check if timers have elapsed and set event loop to wake at next timer deadline
168-
let now = Instant::now();
169-
loop {
170-
let Some(next_timer) = self.masonry_state.timers.peek().cloned() else {
171-
break;
172-
};
173-
let next_timer = next_timer.0;
174-
if next_timer > now {
175-
break;
176-
}
177-
// timer has elapsed - remove from heap and handle
178-
self.masonry_state.timers.pop();
179-
}
164+
fn new_events(
165+
&mut self,
166+
event_loop: &winit::event_loop::ActiveEventLoop,
167+
cause: winit::event::StartCause,
168+
) {
169+
self.masonry_state.handle_new_events(event_loop, cause);
180170
}
181171

182172
fn window_event(
@@ -220,14 +210,6 @@ impl ApplicationHandler<MasonryUserEvent> for MainState<'_> {
220210
self.masonry_state.handle_about_to_wait(event_loop);
221211
}
222212

223-
fn new_events(
224-
&mut self,
225-
event_loop: &winit::event_loop::ActiveEventLoop,
226-
cause: winit::event::StartCause,
227-
) {
228-
self.masonry_state.handle_new_events(event_loop, cause);
229-
}
230-
231213
fn exiting(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
232214
self.masonry_state.handle_exiting(event_loop);
233215
}
@@ -264,6 +246,7 @@ impl MasonryState<'_> {
264246
frame: None,
265247
pointer_state: PointerState::empty(),
266248
proxy: event_loop.create_proxy(),
249+
timers: TimerQueue::new(),
267250

268251
window: WindowState::Uninitialized(window),
269252
background_color,
@@ -678,10 +661,35 @@ impl MasonryState<'_> {
678661
self.handle_signals(event_loop, app_driver);
679662
}
680663

681-
// --- MARK: EMPTY WINIT HANDLERS ---
682-
pub fn handle_about_to_wait(&mut self, _: &ActiveEventLoop) {}
664+
// --- MARK: TIMERS ---
665+
pub fn handle_new_events(&mut self, _: &ActiveEventLoop, _: winit::event::StartCause) {
666+
// check if timers have elapsed and set event loop to wake at next timer deadline
667+
let now = Instant::now();
668+
loop {
669+
let Some(next_timer) = self.timers.peek() else {
670+
break;
671+
};
672+
if next_timer.deadline > now {
673+
break;
674+
}
675+
// timer has elapsed - remove from heap and handle
676+
let Some(elapsed_timer) = self.timers.pop() else {
677+
debug_panic!("should be unreachable: peek was Some");
678+
break;
679+
};
680+
self.render_root.handle_elapsed_timer(elapsed_timer);
681+
}
682+
}
683+
684+
pub fn handle_about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
685+
if let Some(next_timer) = self.timers.peek() {
686+
event_loop.set_control_flow(ControlFlow::WaitUntil(next_timer.deadline));
687+
} else {
688+
event_loop.set_control_flow(ControlFlow::Wait);
689+
}
690+
}
683691

684-
pub fn handle_new_events(&mut self, _: &ActiveEventLoop, _: winit::event::StartCause) {}
692+
// --- MARK: EMPTY WINIT HANDLERS ---
685693

686694
pub fn handle_exiting(&mut self, _: &ActiveEventLoop) {}
687695

@@ -706,6 +714,9 @@ impl MasonryState<'_> {
706714
app_driver.on_action(&mut driver_ctx, widget_id, action);
707715
});
708716
}
717+
render_root::RenderRootSignal::TimerRequested(timer) => {
718+
self.timers.push(timer);
719+
}
709720
render_root::RenderRootSignal::StartIme => {
710721
window.set_ime_allowed(true);
711722
}

masonry/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ mod event;
160160
mod paint_scene_helpers;
161161
mod passes;
162162
mod render_root;
163+
mod timers;
163164
mod tracing_backend;
164165

165166
pub mod event_loop_runner;

masonry/src/passes/event.rs

+22
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ use tracing::{debug, info_span, trace};
66
use winit::event::ElementState;
77
use winit::keyboard::{KeyCode, PhysicalKey};
88

9+
use crate::event::TimerEvent;
910
use crate::passes::{enter_span, merge_state_up};
1011
use crate::render_root::RenderRoot;
12+
use crate::timers::Timer;
1113
use crate::{AccessEvent, EventCtx, Handled, PointerEvent, TextEvent, Widget, WidgetId};
1214

1315
// --- MARK: HELPERS ---
@@ -135,6 +137,26 @@ pub(crate) fn run_on_pointer_event_pass(root: &mut RenderRoot, event: &PointerEv
135137
handled
136138
}
137139

140+
pub(crate) fn run_on_elapsed_timer_pass(root: &mut RenderRoot, timer: &Timer) -> Handled {
141+
let event = TimerEvent {
142+
deadline: timer.deadline,
143+
id: timer.id,
144+
};
145+
let handled = run_event_pass(
146+
root,
147+
Some(timer.widget_id),
148+
&event,
149+
false,
150+
|widget, ctx, event| {
151+
widget.on_timer_expired(ctx, event);
152+
// don't traverse for this event
153+
ctx.set_handled();
154+
},
155+
true,
156+
);
157+
handled
158+
}
159+
138160
// TODO https://github.com/linebender/xilem/issues/376 - Some implicit invariants:
139161
// - If a Widget gets a keyboard event or an ImeStateChange, then
140162
// focus is on it, its child or its parent.

masonry/src/render_root.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ use crate::passes::accessibility::run_accessibility_pass;
2424
use crate::passes::anim::run_update_anim_pass;
2525
use crate::passes::compose::run_compose_pass;
2626
use crate::passes::event::{
27-
run_on_access_event_pass, run_on_pointer_event_pass, run_on_text_event_pass,
27+
run_on_access_event_pass, run_on_elapsed_timer_pass, run_on_pointer_event_pass,
28+
run_on_text_event_pass,
2829
};
2930
use crate::passes::layout::run_layout_pass;
3031
use crate::passes::mutate::{mutate_widget, run_mutate_pass};
@@ -36,6 +37,7 @@ use crate::passes::update::{
3637
};
3738
use crate::passes::{recurse_on_children, PassTracing};
3839
use crate::text::BrushIndex;
40+
use crate::timers::Timer;
3941
use crate::widget::{WidgetArena, WidgetMut, WidgetRef, WidgetState};
4042
use crate::{AccessEvent, Action, CursorIcon, Handled, QueryCtx, Widget, WidgetId, WidgetPod};
4143

@@ -123,6 +125,7 @@ pub struct RenderRootOptions {
123125

124126
pub enum RenderRootSignal {
125127
Action(Action, WidgetId),
128+
TimerRequested(Timer),
126129
StartIme,
127130
EndIme,
128131
ImeMoved(LogicalPosition<f64>, LogicalSize<f64>),
@@ -259,6 +262,14 @@ impl RenderRoot {
259262
}
260263

261264
// --- MARK: PUB FUNCTIONS ---
265+
pub fn handle_elapsed_timer(&mut self, timer: Timer) -> Handled {
266+
let _span = info_span!("elapsed_timer");
267+
let handled = run_on_elapsed_timer_pass(self, &timer);
268+
self.run_rewrite_passes();
269+
270+
handled
271+
}
272+
262273
pub fn handle_pointer_event(&mut self, event: PointerEvent) -> Handled {
263274
let _span = info_span!("pointer_event");
264275
let handled = run_on_pointer_event_pass(self, &event);

masonry/src/testing/harness.rs

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use crate::passes::anim::run_update_anim_pass;
2525
use crate::render_root::{RenderRoot, RenderRootOptions, RenderRootSignal, WindowSizePolicy};
2626
use crate::testing::screenshots::get_image_diff;
2727
use crate::testing::snapshot_utils::get_cargo_workspace;
28+
use crate::timers::TimerQueue;
2829
use crate::tracing_backend::try_init_test_tracing;
2930
use crate::widget::{WidgetMut, WidgetRef};
3031
use crate::{Color, Handled, Point, Size, Vec2, Widget, WidgetId};
@@ -110,6 +111,7 @@ pub struct TestHarness {
110111
has_ime_session: bool,
111112
ime_rect: (LogicalPosition<f64>, LogicalSize<f64>),
112113
title: String,
114+
timers: TimerQueue,
113115
}
114116

115117
pub struct TestHarnessParams {
@@ -215,6 +217,7 @@ impl TestHarness {
215217
has_ime_session: false,
216218
ime_rect: Default::default(),
217219
title: String::new(),
220+
timers: TimerQueue::new(),
218221
};
219222
harness.process_window_event(WindowEvent::Resize(window_size));
220223

@@ -260,6 +263,9 @@ impl TestHarness {
260263
RenderRootSignal::Action(action, widget_id) => {
261264
self.action_queue.push_back((action, widget_id));
262265
}
266+
RenderRootSignal::TimerRequested(timer) => {
267+
self.timers.push(timer);
268+
}
263269
RenderRootSignal::StartIme => {
264270
self.has_ime_session = true;
265271
}

0 commit comments

Comments
 (0)