1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
use accesskit::Action;
use accesskit_winit::ActionRequestEvent;
use freya_common::EventMessage;
use freya_core::prelude::*;
use freya_elements::events::keyboard::{
    map_winit_key, map_winit_modifiers, map_winit_physical_key,
};
use torin::geometry::CursorPoint;
use winit::event::{
    ElementState, Event, KeyEvent, MouseScrollDelta, StartCause, Touch, TouchPhase, WindowEvent,
};
use winit::event_loop::{EventLoop, EventLoopProxy};
use winit::keyboard::{KeyCode, ModifiersState, PhysicalKey};

use crate::app::App;
use crate::HoveredNode;

// https://github.com/emilk/egui/issues/461
// https://github.com/rust-windowing/winit/issues/22
// https://github.com/flutter/flutter/issues/71385
const WHEEL_SPEED_MODIFIER: f32 = 53.0;

pub fn run_event_loop<State: Clone>(
    mut app: App<State>,
    event_loop: EventLoop<EventMessage>,
    proxy: EventLoopProxy<EventMessage>,
    hovered_node: HoveredNode,
) {
    let mut cursor_pos = CursorPoint::default();
    let mut modifiers_state = ModifiersState::empty();

    let window_env = app.window_env();

    window_env.run_on_setup();

    event_loop
        .run(move |event, event_loop| match event {
            Event::NewEvents(StartCause::Init) => {
                _ = proxy.send_event(EventMessage::PollVDOM);
            }
            Event::UserEvent(EventMessage::FocusAccessibilityNode(id)) => {
                app.accessibility().set_accessibility_focus(id);
            }
            Event::UserEvent(EventMessage::RequestRerender) => {
                app.window_env().window_mut().request_redraw();
            }
            Event::UserEvent(EventMessage::RequestRedraw) => app.render(&hovered_node),
            Event::UserEvent(EventMessage::RequestRelayout) => {
                app.process_layout();
            }
            Event::UserEvent(EventMessage::RemeasureTextGroup(text_id)) => {
                app.measure_text_group(&text_id);
            }
            Event::UserEvent(EventMessage::ActionRequestEvent(ActionRequestEvent {
                request,
                ..
            })) => {
                if Action::Focus == request.action {
                    app.accessibility().set_accessibility_focus(request.target);
                }
            }
            Event::UserEvent(EventMessage::SetCursorIcon(icon)) => {
                app.window_env().window.set_cursor_icon(icon)
            }
            Event::UserEvent(ev) => {
                if let EventMessage::UpdateTemplate(template) = ev {
                    app.vdom_replace_template(template);
                }

                if matches!(ev, EventMessage::PollVDOM)
                    || matches!(ev, EventMessage::UpdateTemplate(_))
                {
                    app.poll_vdom();
                }
            }
            Event::WindowEvent { event, .. } => {
                app.process_accessibility_event(&event);
                match event {
                    WindowEvent::CloseRequested => event_loop.exit(),
                    WindowEvent::RedrawRequested => {
                        app.process_layout();
                        app.render(&hovered_node);
                        app.tick();
                    }
                    WindowEvent::MouseInput { state, button, .. } => {
                        app.set_navigation_mode(NavigationMode::NotKeyboard);

                        let event_name = match state {
                            ElementState::Pressed => "mousedown",
                            ElementState::Released => "click",
                        };

                        app.send_event(FreyaEvent::Mouse {
                            name: event_name.to_string(),
                            cursor: cursor_pos,
                            button: Some(button),
                        });
                    }
                    WindowEvent::MouseWheel { delta, phase, .. } => {
                        if TouchPhase::Moved == phase {
                            let scroll_data = {
                                match delta {
                                    MouseScrollDelta::LineDelta(x, y) => (
                                        (x * WHEEL_SPEED_MODIFIER) as f64,
                                        (y * WHEEL_SPEED_MODIFIER) as f64,
                                    ),
                                    MouseScrollDelta::PixelDelta(pos) => (pos.x, pos.y),
                                }
                            };

                            app.send_event(FreyaEvent::Wheel {
                                name: "wheel".to_string(),
                                scroll: CursorPoint::from(scroll_data),
                                cursor: cursor_pos,
                            });
                        }
                    }
                    WindowEvent::ModifiersChanged(modifiers) => {
                        modifiers_state = modifiers.state();
                    }
                    WindowEvent::KeyboardInput {
                        event:
                            KeyEvent {
                                physical_key,
                                logical_key,
                                state,
                                ..
                            },
                        ..
                    } => {
                        if state == ElementState::Pressed
                            && physical_key == PhysicalKey::Code(KeyCode::Tab)
                        {
                            app.set_navigation_mode(NavigationMode::Keyboard);

                            let direction = if modifiers_state.shift_key() {
                                AccessibilityFocusDirection::Backward
                            } else {
                                AccessibilityFocusDirection::Forward
                            };

                            app.focus_next_node(direction);

                            return;
                        }

                        let event_name = match state {
                            ElementState::Pressed => "keydown",
                            ElementState::Released => "keyup",
                        };
                        app.send_event(FreyaEvent::Keyboard {
                            name: event_name.to_string(),
                            key: map_winit_key(&logical_key),
                            code: map_winit_physical_key(&physical_key),
                            modifiers: map_winit_modifiers(modifiers_state),
                        })
                    }
                    WindowEvent::CursorMoved { position, .. } => {
                        cursor_pos = CursorPoint::from((position.x, position.y));

                        app.send_event(FreyaEvent::Mouse {
                            name: "mouseover".to_string(),
                            cursor: cursor_pos,
                            button: None,
                        });
                    }
                    WindowEvent::Touch(Touch {
                        location,
                        phase,
                        id,
                        force,
                        ..
                    }) => {
                        cursor_pos = CursorPoint::from((location.x, location.y));

                        let event_name = match phase {
                            TouchPhase::Cancelled => "touchcancel",
                            TouchPhase::Ended => "touchend",
                            TouchPhase::Moved => "touchmove",
                            TouchPhase::Started => "touchstart",
                        };

                        app.send_event(FreyaEvent::Touch {
                            name: event_name.to_string(),
                            location: cursor_pos,
                            finger_id: id,
                            phase,
                            force,
                        });
                    }
                    WindowEvent::Resized(size) => {
                        app.resize(size);
                    }
                    _ => {}
                }
            }
            Event::LoopExiting => {
                app.window_env().run_on_exit();
            }
            _ => (),
        })
        .expect("Failed to run Eventloop.");
}