Skip to main content

loam_egui/
capture.rs

1/// `pointer` and `keyboard` are read at different points in egui's pass.
2#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
3pub struct UiCapture {
4    pub pointer: bool,
5    pub keyboard: bool,
6}
7
8impl UiCapture {
9    /// Read after `begin_pass` and before building widgets.
10    pub fn read(ctx: &egui::Context) -> Self {
11        Self {
12            pointer: ctx.is_using_pointer()
13                || ctx.interaction_snapshot(|i| !i.contains_pointer.is_empty()),
14            keyboard: ctx.wants_keyboard_input(),
15        }
16    }
17}
18
19#[cfg(test)]
20mod tests {
21    use super::UiCapture;
22
23    const SCREEN: egui::Vec2 = egui::vec2(800.0, 600.0);
24    const PANEL_BLANK: egui::Pos2 = egui::pos2(400.0, 8.0);
25    const FIELD: egui::Pos2 = egui::pos2(120.0, 10.0);
26    const OPEN_SCENE: egui::Pos2 = egui::pos2(200.0, 400.0);
27    const WINDOW_POS: egui::Pos2 = egui::pos2(400.0, 300.0);
28    const IN_WINDOW: egui::Pos2 = egui::pos2(430.0, 320.0);
29
30    fn build(ctx: &egui::Context, text: &mut String) {
31        egui::TopBottomPanel::top("bar").show(ctx, |ui| {
32            ui.horizontal(|ui| {
33                let _ = ui.button("File");
34                ui.add(egui::TextEdit::singleline(text).id(egui::Id::new("edit")));
35            });
36        });
37        egui::Window::new("win")
38            .fixed_pos(WINDOW_POS)
39            .fixed_size(egui::vec2(100.0, 80.0))
40            .show(ctx, |ui| {
41                let _ = ui.button("press");
42            });
43    }
44
45    struct Host {
46        ctx: egui::Context,
47        text: String,
48    }
49
50    impl Host {
51        fn new() -> Self {
52            let mut host = Self {
53                ctx: egui::Context::default(),
54                text: String::new(),
55            };
56            host.frame(vec![]);
57            host.frame(vec![]);
58            host
59        }
60
61        fn frame(&mut self, events: Vec<egui::Event>) -> UiCapture {
62            let input = egui::RawInput {
63                screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, SCREEN)),
64                events,
65                ..Default::default()
66            };
67            let mut capture = UiCapture::default();
68            let text = &mut self.text;
69            let _ = self.ctx.run(input, |ctx| {
70                capture = UiCapture::read(ctx);
71                build(ctx, text);
72            });
73            capture
74        }
75
76        fn hover(&mut self, pos: egui::Pos2) -> UiCapture {
77            self.frame(vec![egui::Event::PointerMoved(pos)])
78        }
79
80        fn press(&mut self, pos: egui::Pos2) -> UiCapture {
81            self.frame(vec![
82                egui::Event::PointerMoved(pos),
83                egui::Event::PointerButton {
84                    pos,
85                    button: egui::PointerButton::Primary,
86                    pressed: true,
87                    modifiers: egui::Modifiers::default(),
88                },
89            ])
90        }
91
92        fn click(&mut self, pos: egui::Pos2) -> UiCapture {
93            self.frame(vec![
94                egui::Event::PointerMoved(pos),
95                egui::Event::PointerButton {
96                    pos,
97                    button: egui::PointerButton::Primary,
98                    pressed: true,
99                    modifiers: egui::Modifiers::default(),
100                },
101                egui::Event::PointerButton {
102                    pos,
103                    button: egui::PointerButton::Primary,
104                    pressed: false,
105                    modifiers: egui::Modifiers::default(),
106                },
107            ])
108        }
109
110        fn key(&mut self, key: egui::Key) -> UiCapture {
111            self.frame(vec![egui::Event::Key {
112                key,
113                physical_key: None,
114                pressed: true,
115                repeat: false,
116                modifiers: egui::Modifiers::default(),
117            }])
118        }
119    }
120
121    #[test]
122    fn pointer_capture_reports_a_press_on_the_frame_the_pointer_arrives() {
123        let mut host = Host::new();
124        host.hover(OPEN_SCENE);
125        let capture = host.press(IN_WINDOW);
126        assert!(capture.pointer);
127    }
128
129    #[test]
130    fn pointer_capture_covers_a_hovered_panel_without_claiming_the_keyboard() {
131        let mut host = Host::new();
132        host.hover(OPEN_SCENE);
133        let capture = host.hover(PANEL_BLANK);
134        assert!(capture.pointer);
135        assert!(!capture.keyboard);
136    }
137
138    #[test]
139    fn pointer_capture_follows_a_drag_off_the_widget_it_started_on() {
140        let mut host = Host::new();
141        host.hover(FIELD);
142        host.press(FIELD);
143        let capture = host.hover(OPEN_SCENE);
144        assert!(capture.pointer);
145    }
146
147    #[test]
148    fn a_focused_field_claims_the_keyboard_without_claiming_the_pointer() {
149        let mut host = Host::new();
150        host.click(egui::pos2(120.0, 10.0));
151        let capture = host.hover(OPEN_SCENE);
152        assert!(capture.keyboard);
153        assert!(!capture.pointer);
154    }
155
156    #[test]
157    fn keyboard_capture_trails_the_click_that_focuses_a_field_by_one_build() {
158        let mut host = Host::new();
159        let on_click = host.click(egui::pos2(120.0, 10.0));
160        assert!(!on_click.keyboard);
161        let next = host.frame(vec![]);
162        assert!(next.keyboard);
163    }
164
165    #[test]
166    fn escape_clears_keyboard_capture_on_the_frame_it_is_pressed() {
167        let mut host = Host::new();
168        host.click(egui::pos2(120.0, 10.0));
169        host.frame(vec![]);
170        let capture = host.key(egui::Key::Escape);
171        assert!(!capture.keyboard);
172    }
173}