teksilo_core/widget_tree/
query_impl.rs1use super::*;
5
6impl WidgetTree {
7 #[allow(dead_code)]
9 pub(crate) fn arena_get(&self, id: WidgetId) -> Option<&crate::arena::WidgetNode> {
10 self.arena.get(id)
11 }
12
13 pub fn bounds(&self, id: WidgetId) -> Rect {
14 self.arena.bounds(id)
15 }
16
17 pub fn last_pointer_position(&self) -> Option<teksilo_canvas::Point> {
25 self.pointers.primary().map(|e| e.position)
26 }
27
28 pub fn pointer_position(
34 &self,
35 pointer: crate::pointer::PointerId,
36 ) -> Option<teksilo_canvas::Point> {
37 self.pointers.get(pointer).map(|e| e.position)
38 }
39
40 pub fn captured_by(&self, pointer: crate::pointer::PointerId) -> Option<WidgetId> {
45 self.pointers.get(pointer).and_then(|e| e.captured_by)
46 }
47
48 pub fn pointer_captured_by(&self) -> Option<WidgetId> {
51 self.pointers.primary().and_then(|e| e.captured_by)
52 }
53
54 pub fn live_pointers(&self) -> impl Iterator<Item = crate::pointer::PointerInfo> + '_ {
60 self.pointers.iter().map(|e| e.info)
61 }
62
63 pub fn primary_pointer(&self) -> Option<crate::pointer::PointerInfo> {
72 self.pointers.primary().map(|e| e.info)
73 }
74
75 pub fn hover_owner(&self) -> Option<crate::pointer::PointerInfo> {
79 self.pointers.hover_owner().map(|e| e.info)
80 }
81
82 pub fn widget_as_any(&self, id: WidgetId) -> Option<&dyn std::any::Any> {
87 self.arena.get(id).and_then(|node| node.widget.as_any())
88 }
89
90 pub fn widget_as_any_mut(&mut self, id: WidgetId) -> Option<&mut dyn std::any::Any> {
96 self.arena
97 .get_mut(id)
98 .and_then(|node| node.widget.as_any_mut())
99 }
100
101 pub fn children(&self, id: WidgetId) -> Vec<WidgetId> {
102 self.arena.children(id).to_vec()
103 }
104
105 pub fn roots(&self) -> Vec<WidgetId> {
108 self.arena.roots()
109 }
110
111 pub fn widget_type_name(&self, id: WidgetId) -> Option<&'static str> {
116 self.arena.get(id).map(|n| n.widget.type_name())
117 }
118
119 pub fn widget_debug_string(&self, id: WidgetId) -> Option<String> {
123 self.arena.get(id).map(|n| format!("{:?}", n.widget))
124 }
125
126 pub fn widget_clips_children(&self, id: WidgetId) -> bool {
129 self.arena
130 .get(id)
131 .map(|n| n.clips_children)
132 .unwrap_or(false)
133 }
134
135 pub fn last_proposal(&self) -> SizeProposal {
142 self.last_proposal
143 }
144
145 pub fn at_version(&self) -> &crate::signal::Signal<u64> {
156 &self.at_version
157 }
158
159 pub fn announcements_since(&self, seq: u64) -> Vec<crate::accessibility::Announcement> {
167 self.automation_announcements
168 .iter()
169 .filter(|a| a.seq > seq)
170 .cloned()
171 .collect()
172 }
173
174 pub fn parent(&self, id: WidgetId) -> Option<WidgetId> {
176 self.arena.parent(id)
177 }
178
179 pub fn needs_layout(&self) -> bool {
180 self.arena.any_needs_layout()
181 }
182
183 pub fn needs_paint(&self) -> bool {
184 self.arena.any_needs_paint()
185 }
186
187 pub fn active_animation_count(&self) -> usize {
188 self.animation_scheduler.active_count()
189 }
190
191 pub fn pending_tooltip_count(&self) -> usize {
192 self.tooltips
193 .iter()
194 .filter(|entry| entry.overlay_id.is_none() && entry.real_hover_start.is_some())
195 .count()
196 }
197
198 pub fn has_idle_work(&self) -> bool {
200 !self.idle_queue.is_empty()
201 }
202
203 pub fn has_pending_modal_requests(&self) -> bool {
204 !self.pending_modal_requests.is_empty()
205 }
206
207 pub fn has_pending_modal_dismissal(&self) -> bool {
208 self.pending_modal_dismissal
209 }
210
211 pub fn current_cursor(&self) -> crate::widget::CursorIcon {
212 self.current_cursor
213 }
214
215 pub fn hovered(&self) -> Option<WidgetId> {
222 self.hovered_id()
223 }
224
225 pub fn hovered_for(&self, pointer: crate::pointer::PointerId) -> Option<WidgetId> {
230 self.pointers.get(pointer).and_then(|e| e.hovered)
231 }
232
233 pub fn has_pending_dispatch(&self) -> bool {
239 !self.pending_dispatch.is_empty()
240 }
241
242 pub fn last_pointer_kind_signal(&self) -> crate::signal::Signal<teksilo_tokens::PointerKind> {
249 self.last_pointer_kind_signal.clone()
250 }
251
252 pub fn hovered_signal(&self) -> crate::signal::Signal<Option<WidgetId>> {
259 self.hovered_signal.clone()
260 }
261
262 pub fn focused_signal(&self) -> crate::signal::Signal<Option<WidgetId>> {
266 self.focused_signal.clone()
267 }
268
269 pub fn run_idle_callbacks(&mut self, budget: std::time::Duration) {
272 let callbacks = self.idle_queue.drain();
273 for callback in callbacks {
274 callback(crate::idle::IdleDeadline::new(budget));
275 }
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282 use crate::overlay::{DismissBehavior, OverlayLayer, OverlayPlacement, OverlayRequest};
283 use crate::test_widgets::FillWidget;
284 use crate::widget_builder::WidgetBuilder;
285 use crate::{ModalCloseBehavior, ModalContent, ModalPresentation, ModalRequest};
286 use std::cell::Cell;
287 use std::rc::Rc;
288 use teksilo_canvas::Size;
289
290 #[derive(Debug)]
291 struct FixedWidget(f32, f32);
292
293 impl Widget for FixedWidget {
294 fn layout_response(
295 &self,
296 _proposal: SizeProposal,
297 _ctx: &LayoutContext,
298 ) -> crate::widget::LayoutResponse {
299 Size::new(self.0, self.1).into()
300 }
301 }
302
303 #[test]
304 fn destroy_removes_from_arena() {
305 let mut tree = WidgetTree::new();
306 let widget = tree.add(FillWidget::new().label("Gone"));
307 tree.layout(SizeProposal::exact(100.0, 50.0));
308 assert!(tree.find_by_label("Gone").is_some());
309
310 tree.arena.destroy(widget);
311 assert!(tree.find_by_label("Gone").is_none());
312 }
313
314 #[test]
315 fn idle_callback_requested_from_event_handler() {
316 let called = Rc::new(Cell::new(false));
317 let called_flag = called.clone();
318 let mut tree = WidgetTree::new();
319 let widget = tree.add(FillWidget::new().on_tap(move |_pos, ctx| {
320 let called = called_flag.clone();
321 ctx.request_idle_callback(move |_deadline| {
322 called.set(true);
323 });
324 }));
325 tree.layout(SizeProposal::exact(100.0, 50.0));
326
327 assert!(!tree.has_idle_work());
328
329 tree.click(widget);
330
331 assert!(tree.has_idle_work());
332 assert!(!called.get());
333
334 tree.run_idle_callbacks(std::time::Duration::from_millis(16));
335
336 assert!(called.get());
337 assert!(!tree.has_idle_work());
338 }
339
340 #[test]
341 fn set_locale_from_event_handler_is_parked_not_applied() {
342 let mut tree = WidgetTree::new();
343 let widget = tree.add(FillWidget::new().on_tap(|_pos, ctx| {
344 ctx.set_locale("fr-FR");
345 }));
346 tree.layout(SizeProposal::exact(100.0, 50.0));
347 assert!(tree.locale().is_none());
348
349 tree.click(widget);
350
351 assert_eq!(tree.locale(), None);
356 assert_eq!(
358 tree.take_pending_locale_request(),
359 Some("fr-FR".to_string())
360 );
361 assert_eq!(tree.take_pending_locale_request(), None);
363 }
364
365 #[test]
366 fn set_theme_from_event_handler_is_parked_not_applied() {
367 use crate::ThemeAppearance;
368
369 let mut tree = WidgetTree::new();
370 assert_eq!(tree.theme().appearance, ThemeAppearance::Light);
372
373 let widget = tree.add(FillWidget::new().on_tap(|_pos, ctx| {
374 ctx.set_theme(crate::presets::intui::dark());
375 }));
376 tree.layout(SizeProposal::exact(100.0, 50.0));
377
378 tree.click(widget);
379
380 assert_eq!(tree.theme().appearance, ThemeAppearance::Light);
384 let parked = tree.take_pending_theme_request();
386 assert_eq!(parked.map(|t| t.appearance), Some(ThemeAppearance::Dark));
387 assert!(tree.take_pending_theme_request().is_none());
389 }
390
391 #[test]
392 fn idle_deadline_provides_time_budget() {
393 let deadline = crate::idle::IdleDeadline::new(std::time::Duration::from_millis(100));
394 assert!(!deadline.did_timeout());
395 assert!(deadline.time_remaining() > std::time::Duration::ZERO);
396 }
397
398 #[test]
399 fn modal_request_requested_from_event_handler() {
400 let mut tree = WidgetTree::new();
401 let content = tree.add(FillWidget::new().label("Modal content"));
402 let trigger = tree.add(FillWidget::new().on_tap(move |_pos, ctx| {
403 ctx.present_modal(
404 ModalRequest::in_tree(content)
405 .presentation(ModalPresentation::InTree)
406 .close_behavior(ModalCloseBehavior::Manual),
407 );
408 }));
409 tree.layout(SizeProposal::exact(100.0, 50.0));
410
411 assert!(!tree.has_pending_modal_requests());
412
413 tree.click(trigger);
414
415 assert!(tree.has_pending_modal_requests());
416 let requests = tree.drain_pending_modal_requests();
417 assert_eq!(requests.len(), 1);
418 assert_eq!(requests[0].source_widget, trigger);
419 assert_eq!(requests[0].request.presentation, ModalPresentation::InTree);
420 assert_eq!(
421 requests[0].request.close_behavior,
422 ModalCloseBehavior::Manual
423 );
424 match requests[0].request.content {
425 ModalContent::ExistingWidget(id) => assert_eq!(id, content),
426 ModalContent::Deferred(_) => panic!("expected ExistingWidget content"),
427 }
428 assert!(!tree.has_pending_modal_requests());
429 }
430
431 #[test]
432 fn draining_modal_requests_clears_queue() {
433 let mut tree = WidgetTree::new();
434 let content = tree.add(FillWidget::new());
435 let trigger = tree.add(FillWidget::new().on_tap(move |_pos, ctx| {
436 ctx.present_modal(ModalRequest::in_tree(content));
437 }));
438 tree.layout(SizeProposal::exact(100.0, 50.0));
439
440 tree.click(trigger);
441 assert_eq!(tree.drain_pending_modal_requests().len(), 1);
442 assert!(tree.drain_pending_modal_requests().is_empty());
443 }
444
445 #[test]
446 fn dismiss_modal_closes_centered_overlay_for_source_widget() {
447 let mut tree = WidgetTree::new();
448 let trigger = tree.add(FillWidget::new().label("Trigger"));
449 let modal_content = tree.add(FixedWidget(120.0, 48.0).on_tap(|_pos, ctx| {
450 ctx.dismiss_modal();
451 }));
452 tree.layout(SizeProposal::exact(320.0, 200.0));
453
454 tree.show_overlay(OverlayRequest {
455 content_id: modal_content,
456 anchor: trigger,
457 placement: OverlayPlacement::Centered,
458 dismiss: DismissBehavior::Manual,
459 layer: OverlayLayer::InTree,
460 parent_overlay: None,
461 on_dismiss: None,
462 fade_duration: None,
463 });
464 tree.layout(SizeProposal::exact(320.0, 200.0));
465
466 assert_eq!(tree.active_overlays().len(), 1);
467
468 let center = tree
469 .overlay_manager()
470 .topmost_centered()
471 .expect("expected centered modal overlay")
472 .bounds
473 .center();
474 tree.pointer_down_button(center, PointerButton::Primary);
475 tree.pointer_up_button(center, PointerButton::Primary);
476
477 assert!(tree.active_overlays().is_empty());
478 assert!(!tree.has_pending_modal_dismissal());
479 }
480
481 #[test]
482 fn dismiss_modal_without_in_tree_modal_queues_window_dismissal() {
483 let mut tree = WidgetTree::new();
484 let trigger = tree.add(FillWidget::new().on_tap(|_pos, ctx| {
485 ctx.dismiss_modal();
486 }));
487 tree.layout(SizeProposal::exact(100.0, 50.0));
488
489 assert!(!tree.has_pending_modal_dismissal());
490
491 tree.click(trigger);
492
493 assert!(tree.has_pending_modal_dismissal());
494 assert!(tree.drain_pending_modal_dismissal());
495 assert!(!tree.has_pending_modal_dismissal());
496 }
497}