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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
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, Code, Key,
};
use torin::geometry::CursorPoint;
use winit::event::{
    ElementState, Event, Ime, KeyEvent, MouseButton, 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 mut dropped_file_path = None;

    app.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, &app.window_env.window);
            }
            Event::UserEvent(EventMessage::RequestRerender) => {
                app.window_env.window.request_redraw();
            }
            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, &app.window_env.window);
                }
            }
            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.accessibility
                    .process_accessibility_event(&event, &app.window_env.window);
                match event {
                    WindowEvent::CloseRequested => event_loop.exit(),
                    WindowEvent::Ime(Ime::Commit(text)) => {
                        app.send_event(PlatformEvent::Keyboard {
                            name: EventName::KeyDown,
                            key: Key::Character(text),
                            code: Code::Unidentified,
                            modifiers: map_winit_modifiers(modifiers_state),
                        });
                    }
                    WindowEvent::RedrawRequested => {
                        if app.measure_layout_on_next_render {
                            app.process_layout();

                            app.measure_layout_on_next_render = false;
                        }
                        app.render(&hovered_node);
                        app.event_loop_tick();
                    }
                    WindowEvent::MouseInput { state, button, .. } => {
                        app.set_navigation_mode(NavigationMode::NotKeyboard);

                        let name = match state {
                            ElementState::Pressed => EventName::MouseDown,
                            ElementState::Released => match button {
                                MouseButton::Middle => EventName::MiddleClick,
                                MouseButton::Right => EventName::RightClick,
                                MouseButton::Left => EventName::Click,
                                _ => EventName::PointerUp,
                            },
                        };

                        app.send_event(PlatformEvent::Mouse {
                            name,
                            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(PlatformEvent::Wheel {
                                name: EventName::Wheel,
                                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 name = match state {
                            ElementState::Pressed => EventName::KeyDown,
                            ElementState::Released => EventName::KeyUp,
                        };
                        app.send_event(PlatformEvent::Keyboard {
                            name,
                            key: map_winit_key(&logical_key),
                            code: map_winit_physical_key(&physical_key),
                            modifiers: map_winit_modifiers(modifiers_state),
                        })
                    }
                    WindowEvent::CursorLeft { .. } => {
                        cursor_pos = CursorPoint::new(-1.0, -1.0);

                        app.send_event(PlatformEvent::Mouse {
                            name: EventName::MouseOver,
                            cursor: cursor_pos,
                            button: None,
                        });
                    }
                    WindowEvent::CursorMoved { position, .. } => {
                        cursor_pos = CursorPoint::from((position.x, position.y));

                        app.send_event(PlatformEvent::Mouse {
                            name: EventName::MouseOver,
                            cursor: cursor_pos,
                            button: None,
                        });

                        if let Some(dropped_file_path) = dropped_file_path.take() {
                            app.send_event(PlatformEvent::File {
                                name: EventName::FileDrop,
                                file_path: Some(dropped_file_path),
                                cursor: cursor_pos,
                            });
                        }
                    }
                    WindowEvent::Touch(Touch {
                        location,
                        phase,
                        id,
                        force,
                        ..
                    }) => {
                        cursor_pos = CursorPoint::from((location.x, location.y));

                        let name = match phase {
                            TouchPhase::Cancelled => EventName::TouchCancel,
                            TouchPhase::Ended => EventName::TouchEnd,
                            TouchPhase::Moved => EventName::TouchMove,
                            TouchPhase::Started => EventName::TouchStart,
                        };

                        app.send_event(PlatformEvent::Touch {
                            name,
                            location: cursor_pos,
                            finger_id: id,
                            phase,
                            force,
                        });
                    }
                    WindowEvent::Resized(size) => {
                        app.resize(size);
                    }
                    WindowEvent::DroppedFile(file_path) => {
                        dropped_file_path = Some(file_path);
                    }
                    WindowEvent::HoveredFile(file_path) => {
                        app.send_event(PlatformEvent::File {
                            name: EventName::GlobalFileHover,
                            file_path: Some(file_path),
                            cursor: cursor_pos,
                        });
                    }
                    WindowEvent::HoveredFileCancelled => {
                        app.send_event(PlatformEvent::File {
                            name: EventName::GlobalFileHoverCancelled,
                            file_path: None,
                            cursor: cursor_pos,
                        });
                    }
                    _ => {}
                }
            }
            Event::LoopExiting => {
                app.window_env.run_on_exit();
            }
            _ => (),
        })
        .expect("Failed to run Eventloop.");
}