1use core::ffi::c_void;
7
8use azul_core::{
9 callbacks::{TimerCallbackReturn, Update},
10 dom::{DomId, OptionDomNodeId},
11 geom::{LogicalPosition, LogicalSize, OptionLogicalPosition},
12 id::NodeId,
13 menu::Menu,
14 refany::{OptionRefAny, RefAny},
15 resources::ImageRef,
16 task::{
17 Duration, GetSystemTimeCallback, Instant, OptionDuration, OptionInstant, TerminateTimer,
18 ThreadId, TimerId,
19 },
20 window::{KeyboardState, MouseState, WindowFlags},
21};
22
23use azul_css::AzString;
24
25use crate::{
26 callbacks::CallbackInfo,
27 thread::Thread,
28 window_state::{FullWindowState, WindowCreateOptions},
29};
30
31const DEFAULT_TIMER_TICK_MS: u64 = 10;
33
34pub type TimerCallbackType = extern "C" fn(
36 RefAny,
37 TimerCallbackInfo,
38) -> TimerCallbackReturn;
39
40#[repr(C)]
42pub struct TimerCallback {
43 pub cb: TimerCallbackType,
44 pub ctx: OptionRefAny,
47}
48
49impl TimerCallback {
50 pub fn create(cb: TimerCallbackType) -> Self {
51 Self {
52 cb,
53 ctx: OptionRefAny::None,
54 }
55 }
56}
57
58impl core::fmt::Debug for TimerCallback {
59 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
60 write!(f, "TimerCallback {{ cb: {:p} }}", self.cb as *const ())
61 }
62}
63
64impl Clone for TimerCallback {
65 fn clone(&self) -> Self {
66 Self {
67 cb: self.cb,
68 ctx: self.ctx.clone(),
69 }
70 }
71}
72
73impl From<TimerCallbackType> for TimerCallback {
74 fn from(cb: TimerCallbackType) -> Self {
75 Self {
76 cb,
77 ctx: OptionRefAny::None,
78 }
79 }
80}
81
82impl PartialEq for TimerCallback {
83 fn eq(&self, other: &Self) -> bool {
84 std::ptr::eq(self.cb as *const (), other.cb as *const ())
85 }
86}
87
88impl Eq for TimerCallback {}
89
90impl PartialOrd for TimerCallback {
91 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
92 (self.cb as *const () as usize).partial_cmp(&(other.cb as *const () as usize))
93 }
94}
95
96impl Ord for TimerCallback {
97 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
98 (self.cb as *const () as usize).cmp(&(other.cb as *const () as usize))
99 }
100}
101
102impl core::hash::Hash for TimerCallback {
103 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
104 (self.cb as *const () as usize).hash(state);
105 }
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, Hash)]
110#[repr(C)]
111pub struct Timer {
112 pub refany: RefAny,
113 pub node_id: OptionDomNodeId,
114 pub created: Instant,
115 pub last_run: OptionInstant,
116 pub run_count: usize,
117 pub delay: OptionDuration,
118 pub interval: OptionDuration,
119 pub timeout: OptionDuration,
120 pub callback: TimerCallback,
121}
122
123impl Timer {
124 pub fn create<C: Into<TimerCallback>>(
125 refany: RefAny,
126 callback: C,
127 get_system_time_fn: GetSystemTimeCallback,
128 ) -> Self {
129 Self {
130 refany,
131 node_id: None.into(),
132 created: (get_system_time_fn.cb)(),
133 run_count: 0,
134 last_run: OptionInstant::None,
135 delay: OptionDuration::None,
136 interval: OptionDuration::None,
137 timeout: OptionDuration::None,
138 callback: callback.into(),
139 }
140 }
141
142 #[must_use] pub const fn tick_millis(&self) -> u64 {
143 match self.interval.as_ref() {
144 Some(Duration::System(s)) => s.millis(),
145 Some(Duration::Tick(s)) => s.tick_diff,
146 None => DEFAULT_TIMER_TICK_MS,
147 }
148 }
149
150 #[must_use] pub fn is_about_to_finish(&self, instant_now: &Instant) -> bool {
151 match self.timeout {
152 OptionDuration::Some(timeout) => {
153 instant_now.duration_since(&self.created).greater_than(&timeout)
154 }
155 OptionDuration::None => false,
156 }
157 }
158
159 #[must_use] pub fn instant_of_next_run(&self) -> Instant {
160 let last_run = self.last_run.as_ref().map_or(&self.created, |s| s);
161
162 last_run
163 .clone()
164 .add_optional_duration(self.delay.as_ref())
165 .add_optional_duration(self.interval.as_ref())
166 }
167
168 #[inline]
169 #[must_use] pub const fn with_delay(mut self, delay: Duration) -> Self {
170 self.delay = OptionDuration::Some(delay);
171 self
172 }
173
174 #[inline]
175 #[must_use] pub const fn with_interval(mut self, interval: Duration) -> Self {
176 self.interval = OptionDuration::Some(interval);
177 self
178 }
179
180 #[inline]
181 #[must_use] pub const fn with_timeout(mut self, timeout: Duration) -> Self {
182 self.timeout = OptionDuration::Some(timeout);
183 self
184 }
185
186 pub fn invoke(
192 &mut self,
193 callback_info: &CallbackInfo,
194 get_system_time_fn: &GetSystemTimeCallback,
195 ) -> TimerCallbackReturn {
196 let now = (get_system_time_fn.cb)();
197
198 match self.last_run.as_ref() {
200 Some(last_run) => {
201 if let OptionDuration::Some(interval) = self.interval {
203 if now.duration_since(last_run).smaller_than(&interval) {
204 return TimerCallbackReturn {
205 should_update: Update::DoNothing,
206 should_terminate: TerminateTimer::Continue,
207 };
208 }
209 }
210 }
211 None => {
212 if let OptionDuration::Some(delay) = self.delay {
214 if now.duration_since(&self.created).smaller_than(&delay) {
215 return TimerCallbackReturn {
216 should_update: Update::DoNothing,
217 should_terminate: TerminateTimer::Continue,
218 };
219 }
220 }
221 }
222 }
223
224 let is_about_to_finish = self.is_about_to_finish(&now);
225
226 let mut timer_callback_info = TimerCallbackInfo {
229 callback_info: *callback_info,
230 node_id: self.node_id,
231 frame_start: now.clone(),
232 call_count: self.run_count,
233 is_about_to_finish,
234 _abi_ref: core::ptr::null(),
235 _abi_mut: core::ptr::null_mut(),
236 };
237
238 let mut result = (self.callback.cb)(self.refany.clone(), timer_callback_info);
239
240 if is_about_to_finish {
241 result.should_terminate = TerminateTimer::Terminate;
242 }
243
244 self.run_count += 1;
245 self.last_run = OptionInstant::Some(now);
246
247 result
248 }
249}
250
251impl Default for Timer {
252 fn default() -> Self {
253 extern "C" fn default_callback(_: RefAny, _: TimerCallbackInfo) -> TimerCallbackReturn {
254 TimerCallbackReturn::terminate_unchanged()
255 }
256
257 const extern "C" fn default_time() -> Instant {
258 Instant::Tick(azul_core::task::SystemTick { tick_counter: 0 })
259 }
260
261 let cb: TimerCallbackType = default_callback;
262 Self::create(
263 RefAny::new(()),
264 cb,
265 GetSystemTimeCallback { cb: default_time },
266 )
267 }
268}
269
270#[derive(Debug, Clone)]
275#[repr(C)]
276#[allow(clippy::pub_underscore_fields)] pub struct TimerCallbackInfo {
278 pub callback_info: CallbackInfo,
279 pub node_id: OptionDomNodeId,
280 pub frame_start: Instant,
281 pub call_count: usize,
282 pub is_about_to_finish: bool,
283 pub _abi_ref: *const c_void,
284 pub _abi_mut: *mut c_void,
285}
286
287impl TimerCallbackInfo {
288 #[must_use] pub const fn create(
289 callback_info: CallbackInfo,
290 node_id: OptionDomNodeId,
291 frame_start: Instant,
292 call_count: usize,
293 is_about_to_finish: bool,
294 ) -> Self {
295 Self {
296 callback_info,
297 node_id,
298 frame_start,
299 call_count,
300 is_about_to_finish,
301 _abi_ref: core::ptr::null(),
302 _abi_mut: core::ptr::null_mut(),
303 }
304 }
305
306 #[must_use] pub fn get_attached_node_size(&self) -> Option<LogicalSize> {
307 let node_id = self.node_id.into_option()?;
308 self.callback_info.get_node_size(node_id)
309 }
310
311 #[must_use] pub fn get_attached_node_position(&self) -> Option<LogicalPosition> {
312 let node_id = self.node_id.into_option()?;
313 self.callback_info.get_node_position(node_id)
314 }
315
316 #[must_use] pub const fn get_callback_info(&self) -> &CallbackInfo {
317 &self.callback_info
318 }
319
320 pub const fn get_callback_info_mut(&mut self) -> &mut CallbackInfo {
321 &mut self.callback_info
322 }
323
324 #[must_use] pub fn get_ctx(&self) -> OptionRefAny {
330 self.callback_info.get_ctx()
331 }
332
333 pub fn add_timer(&mut self, timer_id: TimerId, timer: Timer) {
335 self.callback_info.add_timer(timer_id, timer);
336 }
337
338 pub fn remove_timer(&mut self, timer_id: TimerId) {
340 self.callback_info.remove_timer(timer_id);
341 }
342
343 pub fn add_thread(&mut self, thread_id: ThreadId, thread: Thread) {
345 self.callback_info.add_thread(thread_id, thread);
346 }
347
348 pub fn remove_thread(&mut self, thread_id: ThreadId) {
350 self.callback_info.remove_thread(thread_id);
351 }
352
353 pub fn stop_propagation(&mut self) {
355 self.callback_info.stop_propagation();
356 }
357
358 pub fn create_window(&mut self, options: WindowCreateOptions) {
360 self.callback_info.create_window(options);
361 }
362
363 pub fn close_window(&mut self) {
365 self.callback_info.close_window();
366 }
367
368 pub fn modify_window_state(&mut self, state: FullWindowState) {
370 self.callback_info.modify_window_state(state);
371 }
372
373 pub fn add_image_to_cache(&mut self, id: AzString, image: ImageRef) {
375 self.callback_info.add_image_to_cache(id, image);
376 }
377
378 pub fn remove_image_from_cache(&mut self, id: AzString) {
380 self.callback_info.remove_image_from_cache(id);
381 }
382
383 pub fn update_all_image_callbacks(&mut self) {
388 self.callback_info.update_all_image_callbacks();
389 }
390
391 pub fn trigger_virtual_view_rerender(&mut self, dom_id: DomId, node_id: NodeId) {
393 self.callback_info.trigger_virtual_view_rerender(dom_id, node_id);
394 }
395
396 pub fn reload_system_fonts(&mut self) {
398 self.callback_info.reload_system_fonts();
399 }
400
401 pub fn prevent_default(&mut self) {
403 self.callback_info.prevent_default();
404 }
405
406 pub fn open_menu(&mut self, menu: Menu) {
408 self.callback_info.open_menu(menu);
409 }
410
411 pub fn open_menu_at(&mut self, menu: Menu, position: LogicalPosition) {
413 self.callback_info.open_menu_at(menu, position);
414 }
415
416 pub fn show_tooltip(&mut self, text: AzString) {
418 self.callback_info.show_tooltip(text);
419 }
420
421 pub fn show_tooltip_at(&mut self, text: AzString, position: LogicalPosition) {
423 self.callback_info.show_tooltip_at(text, position);
424 }
425
426 pub fn hide_tooltip(&mut self) {
428 self.callback_info.hide_tooltip();
429 }
430
431 pub fn open_menu_for_hit_node(&mut self, menu: Menu) -> bool {
433 self.callback_info.open_menu_for_hit_node(menu)
434 }
435
436 #[must_use] pub const fn get_current_window_flags(&self) -> WindowFlags {
438 self.callback_info.get_current_window_flags()
439 }
440
441 #[must_use] pub fn get_current_keyboard_state(&self) -> KeyboardState {
443 self.callback_info.get_current_keyboard_state()
444 }
445
446 #[must_use] pub const fn get_current_mouse_state(&self) -> MouseState {
448 self.callback_info.get_current_mouse_state()
449 }
450
451 #[must_use] pub const fn get_cursor_relative_to_node(&self) -> azul_core::geom::OptionCursorNodePosition {
453 self.callback_info.get_cursor_relative_to_node()
454 }
455
456 #[must_use] pub const fn get_cursor_relative_to_viewport(&self) -> OptionLogicalPosition {
458 self.callback_info.get_cursor_relative_to_viewport()
459 }
460
461 #[must_use] pub fn get_cursor_position(&self) -> Option<LogicalPosition> {
463 self.callback_info.get_cursor_position()
464 }
465
466 #[must_use] pub fn get_current_time(&self) -> Instant {
468 self.frame_start.clone()
469 }
470
471 #[must_use] pub fn is_dom_focused(&self, dom_id: DomId) -> bool {
473 self.callback_info.is_dom_focused(dom_id)
474 }
475
476 #[must_use] pub fn is_pen_in_contact(&self) -> bool {
478 self.callback_info.is_pen_in_contact()
479 }
480
481 #[must_use] pub fn is_pen_eraser(&self) -> bool {
483 self.callback_info.is_pen_eraser()
484 }
485
486 #[must_use] pub fn is_pen_barrel_button_pressed(&self) -> bool {
488 self.callback_info.is_pen_barrel_button_pressed()
489 }
490
491 #[must_use] pub const fn is_dragging(&self) -> bool {
493 self.callback_info.get_current_mouse_state().left_down
494 }
495
496 #[must_use] pub const fn is_drag_active(&self) -> bool {
498 self.callback_info.get_current_mouse_state().left_down
499 }
500
501 #[must_use] pub const fn is_node_drag_active(&self) -> bool {
503 self.callback_info.get_current_mouse_state().left_down
504 }
505
506 #[must_use] pub fn is_file_drag_active(&self) -> bool {
508 self.callback_info.is_file_drag_active()
509 }
510
511 #[must_use] pub fn has_sufficient_history_for_gestures(&self) -> bool {
513 self.callback_info.has_sufficient_history_for_gestures()
514 }
515
516 #[must_use] pub fn get_scroll_node_info(
522 &self,
523 dom_id: DomId,
524 node_id: NodeId,
525 ) -> Option<crate::managers::scroll_state::ScrollNodeInfo> {
526 self.callback_info.get_scroll_node_info(dom_id, node_id)
527 }
528
529 #[must_use] pub fn find_scroll_parent(
534 &self,
535 dom_id: DomId,
536 node_id: NodeId,
537 ) -> Option<NodeId> {
538 self.callback_info.find_scroll_parent(dom_id, node_id)
539 }
540
541 #[cfg(feature = "std")]
546 #[must_use] pub fn get_scroll_input_queue(
547 &self,
548 ) -> crate::managers::scroll_state::ScrollInputQueue {
549 self.callback_info.get_scroll_input_queue()
550 }
551
552 pub fn scroll_to(
557 &mut self,
558 dom_id: DomId,
559 node_id: azul_core::styled_dom::NodeHierarchyItemId,
560 position: LogicalPosition,
561 ) {
562 self.callback_info.scroll_to(dom_id, node_id, position);
563 }
564
565 pub fn scroll_to_unclamped(
567 &mut self,
568 dom_id: DomId,
569 node_id: azul_core::styled_dom::NodeHierarchyItemId,
570 position: LogicalPosition,
571 ) {
572 self.callback_info.scroll_to_unclamped(dom_id, node_id, position);
573 }
574
575 pub fn set_cursor_visibility(&mut self, visible: bool) {
579 self.callback_info.set_cursor_visibility(visible);
580 }
581
582 pub fn set_cursor_visibility_toggle(&mut self) {
584 use crate::callbacks::CallbackChange;
585 self.callback_info.push_change(CallbackChange::ToggleCursorVisibility);
586 }
587
588 pub fn reset_cursor_blink(&mut self) {
590 self.callback_info.reset_cursor_blink();
591 }
592}
593
594#[derive(Debug, Clone)]
596#[repr(C, u8)]
597#[allow(variant_size_differences)] #[allow(clippy::large_enum_variant)]
600pub enum OptionTimer {
601 None,
602 Some(Timer),
603}
604
605impl From<Option<Timer>> for OptionTimer {
606 fn from(o: Option<Timer>) -> Self {
607 o.map_or_else(|| Self::None, Self::Some)
608 }
609}
610
611impl OptionTimer {
612 #[must_use] pub fn into_option(self) -> Option<Timer> {
613 match self {
614 Self::None => None,
615 Self::Some(t) => Some(t),
616 }
617 }
618}