kas_core/event/cx/timer.rs
1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4// https://www.apache.org/licenses/LICENSE-2.0
5
6//! Event context: timers
7
8use super::{EventCx, EventState};
9use crate::{Id, Node, event::Event};
10use std::time::{Duration, Instant};
11
12/// A timer handle
13#[derive(Debug, Copy, Clone, PartialEq, Eq)]
14pub struct TimerHandle(i64);
15impl TimerHandle {
16 /// Construct a new handle
17 ///
18 /// The code must be positive. If a widget uses multiple timers, each must
19 /// have a unique code.
20 ///
21 /// When a timer update is requested multiple times before delivery using
22 /// the same `TimerHandle`, these requests are merged, choosing the
23 /// earliest time if `earliest`, otherwise the latest time.
24 pub const fn new(code: i64, earliest: bool) -> Self {
25 assert!(code >= 0);
26 if earliest {
27 TimerHandle(-code - 1)
28 } else {
29 TimerHandle(code)
30 }
31 }
32
33 /// Check whether this timer chooses the earliest time when merging
34 pub fn earliest(self) -> bool {
35 self.0 < 0
36 }
37}
38
39impl EventState {
40 /// Get the next resume time
41 pub(crate) fn next_resume(&self) -> Option<Instant> {
42 self.time_updates.last().map(|time| time.0)
43 }
44
45 pub(crate) fn need_frame_update(&self) -> bool {
46 self.need_frame_update || !self.frame_updates.is_empty() || !self.fut_messages.is_empty()
47 }
48
49 /// Schedule a timed update
50 ///
51 /// Widget updates may be used for delayed action. For animation, prefer to
52 /// use [`Draw::animate`](crate::draw::Draw::animate) or
53 /// [`Self::request_frame_timer`].
54 ///
55 /// Widget `id` will receive [`Event::Timer`] with this `handle` at
56 /// approximately `time = now + delay` (or possibly a little later due to
57 /// frame-rate limiters and processing time).
58 ///
59 /// Requesting an update with `delay == 0` is valid, except from an
60 /// [`Event::Timer`] handler (where it may cause an infinite loop).
61 ///
62 /// Multiple timer requests with the same `id` and `handle` are merged
63 /// (see [`TimerHandle`] documentation).
64 pub fn request_timer(&mut self, id: Id, handle: TimerHandle, delay: Duration) {
65 debug_assert!(id.is_valid());
66 let time = Instant::now() + delay;
67 if let Some(row) = self
68 .time_updates
69 .iter_mut()
70 .find(|row| row.1 == id && row.2 == handle)
71 {
72 let earliest = handle.earliest();
73 if earliest && row.0 <= time || !earliest && row.0 >= time {
74 return;
75 }
76
77 row.0 = time;
78 } else {
79 log::trace!(
80 target: "kas_core::event",
81 "request_timer: update {id} at now+{}ms",
82 delay.as_millis()
83 );
84 self.time_updates.push((time, id, handle));
85 }
86
87 self.time_updates.sort_by(|a, b| b.0.cmp(&a.0)); // reverse sort
88 }
89
90 /// Schedule a frame timer update
91 ///
92 /// Widget `id` will receive [`Event::Timer`] with this `handle` either
93 /// before or soon after the next frame is drawn.
94 ///
95 /// This may be useful for animations which mutate widget state. Animations
96 /// which don't mutate widget state may use
97 /// [`Draw::animate`](crate::draw::Draw::animate) instead.
98 ///
99 /// It is expected that `handle.earliest() == true` (style guide).
100 pub fn request_frame_timer(&mut self, id: Id, handle: TimerHandle) {
101 debug_assert!(handle.earliest());
102 self.frame_updates.insert((id, handle));
103 }
104}
105
106impl<'a> EventCx<'a> {
107 /// Pre-draw / pre-sleep
108 ///
109 /// This method should be called once per frame as well as after the last
110 /// frame before a long sleep.
111 pub(crate) fn frame_update(&mut self, mut widget: Node<'_>) {
112 self.need_frame_update = false;
113 log::trace!(target: "kas_core::event", "Processing frame update");
114 if let Some((target, affine)) = self.mouse.frame_update() {
115 self.send_event(widget.re(), target, Event::Pan(affine));
116 }
117 self.touch_frame_update(widget.re());
118
119 let frame_updates = std::mem::take(&mut self.frame_updates);
120 for (id, handle) in frame_updates.into_iter() {
121 self.send_event(widget.re(), id, Event::Timer(handle));
122 }
123
124 self.cx.input.frame_update(self.window, widget.as_tile());
125 }
126
127 /// Update widgets due to timer
128 pub(crate) fn update_timer(&mut self, mut widget: Node<'_>) {
129 let now = Instant::now();
130
131 // assumption: time_updates are sorted in reverse order
132 while !self.time_updates.is_empty() {
133 if self.time_updates.last().unwrap().0 > now {
134 break;
135 }
136
137 let update = self.time_updates.pop().unwrap();
138 self.send_event(widget.re(), update.1, Event::Timer(update.2));
139 }
140
141 self.time_updates.sort_by(|a, b| b.0.cmp(&a.0)); // reverse sort
142 }
143}