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, TileExt, event::Event, geom::Size};
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 let time = Instant::now() + delay;
66 if let Some(row) = self
67 .time_updates
68 .iter_mut()
69 .find(|row| row.1 == id && row.2 == handle)
70 {
71 let earliest = handle.earliest();
72 if earliest && row.0 <= time || !earliest && row.0 >= time {
73 return;
74 }
75
76 row.0 = time;
77 } else {
78 log::trace!(
79 target: "kas_core::event",
80 "request_timer: update {id} at now+{}ms",
81 delay.as_millis()
82 );
83 self.time_updates.push((time, id, handle));
84 }
85
86 self.time_updates.sort_by(|a, b| b.0.cmp(&a.0)); // reverse sort
87 }
88
89 /// Schedule a frame timer update
90 ///
91 /// Widget `id` will receive [`Event::Timer`] with this `handle` either
92 /// before or soon after the next frame is drawn.
93 ///
94 /// This may be useful for animations which mutate widget state. Animations
95 /// which don't mutate widget state may use
96 /// [`Draw::animate`](crate::draw::Draw::animate) instead.
97 ///
98 /// It is expected that `handle.earliest() == true` (style guide).
99 pub fn request_frame_timer(&mut self, id: Id, handle: TimerHandle) {
100 debug_assert!(handle.earliest());
101 self.frame_updates.insert((id, handle));
102 }
103}
104
105impl<'a> EventCx<'a> {
106 /// Pre-draw / pre-sleep
107 ///
108 /// This method should be called once per frame as well as after the last
109 /// frame before a long sleep.
110 pub(crate) fn frame_update(&mut self, mut widget: Node<'_>) {
111 self.need_frame_update = false;
112 log::trace!(target: "kas_core::event", "Processing frame update");
113 if let Some((target, affine)) = self.mouse.frame_update() {
114 self.send_event(widget.re(), target, Event::Pan(affine));
115 }
116 self.touch_frame_update(widget.re());
117
118 let frame_updates = std::mem::take(&mut self.frame_updates);
119 for (id, handle) in frame_updates.into_iter() {
120 self.send_event(widget.re(), id, Event::Timer(handle));
121 }
122
123 // Set IME cursor area, if moved.
124 if self.ime.is_some()
125 && let Some(target) = self.sel_focus.as_ref()
126 && let Some((mut rect, translation)) = widget.as_tile().find_tile_rect(target)
127 {
128 if self.ime_cursor_area.size != Size::ZERO {
129 rect = self.ime_cursor_area;
130 }
131 rect += translation;
132 if rect != self.last_ime_rect {
133 self.window.set_ime_cursor_area(rect);
134 self.last_ime_rect = rect;
135 }
136 }
137 }
138
139 /// Update widgets due to timer
140 pub(crate) fn update_timer(&mut self, mut widget: Node<'_>) {
141 let now = Instant::now();
142
143 // assumption: time_updates are sorted in reverse order
144 while !self.time_updates.is_empty() {
145 if self.time_updates.last().unwrap().0 > now {
146 break;
147 }
148
149 let update = self.time_updates.pop().unwrap();
150 self.send_event(widget.re(), update.1, Event::Timer(update.2));
151 }
152
153 self.time_updates.sort_by(|a, b| b.0.cmp(&a.0)); // reverse sort
154 }
155}