egui_kittest/
node.rs

1use egui::accesskit::ActionRequest;
2use egui::mutex::Mutex;
3use egui::{Modifiers, PointerButton, Pos2, accesskit};
4use kittest::{AccessKitNode, NodeT, debug_fmt_node};
5use std::fmt::{Debug, Formatter};
6
7pub(crate) enum EventType {
8    Event(egui::Event),
9    Modifiers(Modifiers),
10}
11
12pub(crate) type EventQueue = Mutex<Vec<EventType>>;
13
14#[derive(Clone, Copy)]
15pub struct Node<'tree> {
16    pub(crate) accesskit_node: AccessKitNode<'tree>,
17    pub(crate) queue: &'tree EventQueue,
18}
19
20impl Debug for Node<'_> {
21    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
22        debug_fmt_node(self, f)
23    }
24}
25
26impl<'tree> NodeT<'tree> for Node<'tree> {
27    fn accesskit_node(&self) -> AccessKitNode<'tree> {
28        self.accesskit_node
29    }
30
31    fn new_related(&self, child_node: AccessKitNode<'tree>) -> Self {
32        Self {
33            queue: self.queue,
34            accesskit_node: child_node,
35        }
36    }
37}
38
39impl Node<'_> {
40    fn event(&self, event: egui::Event) {
41        self.queue.lock().push(EventType::Event(event));
42    }
43
44    fn modifiers(&self, modifiers: Modifiers) {
45        self.queue.lock().push(EventType::Modifiers(modifiers));
46    }
47
48    pub fn hover(&self) {
49        self.event(egui::Event::PointerMoved(self.rect().center()));
50    }
51
52    /// Click at the node center with the primary button.
53    pub fn click(&self) {
54        self.click_button(PointerButton::Primary);
55    }
56
57    #[deprecated = "Use `click()` instead."]
58    pub fn simulate_click(&self) {
59        self.click();
60    }
61
62    pub fn click_secondary(&self) {
63        self.click_button(PointerButton::Secondary);
64    }
65
66    pub fn click_button(&self, button: PointerButton) {
67        self.hover();
68        for pressed in [true, false] {
69            self.event(egui::Event::PointerButton {
70                pos: self.rect().center(),
71                button,
72                pressed,
73                modifiers: Modifiers::default(),
74            });
75        }
76    }
77
78    pub fn click_modifiers(&self, modifiers: Modifiers) {
79        self.click_button_modifiers(PointerButton::Primary, modifiers);
80    }
81
82    pub fn click_button_modifiers(&self, button: PointerButton, modifiers: Modifiers) {
83        self.hover();
84        self.modifiers(modifiers);
85        for pressed in [true, false] {
86            self.event(egui::Event::PointerButton {
87                pos: self.rect().center(),
88                button,
89                pressed,
90                modifiers,
91            });
92        }
93        self.modifiers(Modifiers::default());
94    }
95
96    /// Click the node via accesskit.
97    ///
98    /// This will trigger a [`accesskit::Action::Click`] action.
99    /// In contrast to `click()`, this can also click widgets that are not currently visible.
100    pub fn click_accesskit(&self) {
101        self.event(egui::Event::AccessKitActionRequest(
102            accesskit::ActionRequest {
103                target: self.accesskit_node.id(),
104                action: accesskit::Action::Click,
105                data: None,
106            },
107        ));
108    }
109
110    pub fn rect(&self) -> egui::Rect {
111        let rect = self
112            .accesskit_node
113            .bounding_box()
114            .expect("Every egui node should have a rect");
115        egui::Rect {
116            min: Pos2::new(rect.x0 as f32, rect.y0 as f32),
117            max: Pos2::new(rect.x1 as f32, rect.y1 as f32),
118        }
119    }
120
121    pub fn focus(&self) {
122        self.event(egui::Event::AccessKitActionRequest(ActionRequest {
123            action: accesskit::Action::Focus,
124            target: self.accesskit_node.id(),
125            data: None,
126        }));
127    }
128
129    #[deprecated = "Use `Harness::key_down` instead."]
130    pub fn key_down(&self, key: egui::Key) {
131        self.event(egui::Event::Key {
132            key,
133            pressed: true,
134            modifiers: Modifiers::default(),
135            repeat: false,
136            physical_key: None,
137        });
138    }
139
140    #[deprecated = "Use `Harness::key_up` instead."]
141    pub fn key_up(&self, key: egui::Key) {
142        self.event(egui::Event::Key {
143            key,
144            pressed: false,
145            modifiers: Modifiers::default(),
146            repeat: false,
147            physical_key: None,
148        });
149    }
150
151    pub fn type_text(&self, text: &str) {
152        self.event(egui::Event::Text(text.to_owned()));
153    }
154
155    pub fn value(&self) -> Option<String> {
156        self.accesskit_node.value()
157    }
158
159    pub fn is_focused(&self) -> bool {
160        self.accesskit_node.is_focused()
161    }
162
163    /// Scroll the node into view.
164    pub fn scroll_to_me(&self) {
165        self.event(egui::Event::AccessKitActionRequest(ActionRequest {
166            action: accesskit::Action::ScrollIntoView,
167            target: self.accesskit_node.id(),
168            data: None,
169        }));
170    }
171
172    /// Scroll the [`egui::ScrollArea`] containing this node down (100px).
173    pub fn scroll_down(&self) {
174        self.event(egui::Event::AccessKitActionRequest(ActionRequest {
175            action: accesskit::Action::ScrollDown,
176            target: self.accesskit_node.id(),
177            data: None,
178        }));
179    }
180
181    /// Scroll the [`egui::ScrollArea`] containing this node up (100px).
182    pub fn scroll_up(&self) {
183        self.event(egui::Event::AccessKitActionRequest(ActionRequest {
184            action: accesskit::Action::ScrollUp,
185            target: self.accesskit_node.id(),
186            data: None,
187        }));
188    }
189
190    /// Scroll the [`egui::ScrollArea`] containing this node left (100px).
191    pub fn scroll_left(&self) {
192        self.event(egui::Event::AccessKitActionRequest(ActionRequest {
193            action: accesskit::Action::ScrollLeft,
194            target: self.accesskit_node.id(),
195            data: None,
196        }));
197    }
198
199    /// Scroll the [`egui::ScrollArea`] containing this node right (100px).
200    pub fn scroll_right(&self) {
201        self.event(egui::Event::AccessKitActionRequest(ActionRequest {
202            action: accesskit::Action::ScrollRight,
203            target: self.accesskit_node.id(),
204            data: None,
205        }));
206    }
207}