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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
use super::events::*;
use super::glutin_window::*;
use super::glutin_thread::*;
use super::event_conversion::*;
use super::window_properties::*;
use super::glutin_thread_event::*;
use flo_stream::*;
use flo_binding::*;
use glutin::{GlRequest, Api};
use glutin::event::{DeviceId, Event, WindowEvent, ElementState};
use glutin::event_loop::{ControlFlow, EventLoopWindowTarget};
use glutin::window::{WindowId, Fullscreen};
use futures::task;
use futures::prelude::*;
use futures::future::{LocalBoxFuture};
use std::sync::*;
use std::sync::atomic::{AtomicU64, Ordering};
use std::collections::{HashMap};
static NEXT_FUTURE_ID: AtomicU64 = AtomicU64::new(0);
///
/// Represents the state of the Glutin runtime
///
pub (super) struct GlutinRuntime {
/// The event publishers for the windows being managed by the runtime
pub (super) window_events: HashMap<WindowId, Publisher<DrawEvent>>,
/// Maps future IDs to running futures
pub (super) futures: HashMap<u64, LocalBoxFuture<'static, ()>>,
/// Set to true if this runtime will stop when all the windows are closed
pub (super) will_stop_when_no_windows: bool,
/// The pointer ID that we've assigned to glutin devices
pub (super) pointer_id: HashMap<DeviceId, PointerId>,
/// The current state of each pointer (as a glutin device)
pub (super) pointer_state: HashMap<DeviceId, PointerState>,
/// Set to true when we'll set the control flow to 'Exit' once the current set of events have finished processing
pub (super) will_exit: bool
}
///
/// Used to wake a future running on the glutin thread
///
struct GlutinFutureWaker {
/// The ID of the future to wake, or 'None' if the future has already been woken up
future_id: Mutex<Option<u64>>
}
impl GlutinRuntime {
///
/// Retrieves or assigns an ID to a device
///
fn id_for_pointer(&mut self, device_id: &DeviceId) -> PointerId {
if let Some(pointer_id) = self.pointer_id.get(device_id) {
// Already assigned
*pointer_id
} else {
// Assign a new pointer ID
let pointer_id = self.pointer_id.len() as _;
let pointer_id = PointerId(pointer_id);
// Store in the hash map
self.pointer_id.insert(*device_id, pointer_id);
pointer_id
}
}
///
/// Retrieves the current state for a particular pointer in a mutable form
///
fn state_for_pointer<'a>(&'a mut self, device_id: &'a DeviceId) -> &'a mut PointerState {
&mut *self.pointer_state.entry(*device_id)
.or_insert_with(|| PointerState::new())
}
///
/// Handles an event from the rest of the process and updates the state
///
pub fn handle_event(&mut self, event: Event<'_, GlutinThreadEvent>, window_target: &EventLoopWindowTarget<GlutinThreadEvent>, control_flow: &mut ControlFlow) {
use Event::*;
if *control_flow != ControlFlow::Exit {
*control_flow = ControlFlow::Wait;
}
match event {
NewEvents(_cause) => { }
WindowEvent { window_id, event } => { self.handle_window_event(window_id, event); }
DeviceEvent { device_id: _, event: _ } => { }
UserEvent(thread_event) => { self.handle_thread_event(thread_event, window_target); }
Suspended => { }
Resumed => { }
RedrawRequested(window_id) => { self.request_redraw(window_id); }
MainEventsCleared => {
// Glutin doesn't always respond to ControlFlow::Exit requests, setting it after the other events have cleared is an attempt
// to make it exit more reliably (only partially successful).
if self.will_exit {
*control_flow = ControlFlow::Exit;
}
}
RedrawEventsCleared => { }
LoopDestroyed => { }
}
}
///
/// Handles a glutin window event
///
fn handle_window_event(&mut self, window_id: WindowId, event: WindowEvent) {
if let WindowEvent::CloseRequested = event {
// Glutin has a bug (at least in OS X) where setting control_flow to Exit does not actually shut it down
// This issue does not occur if the 'Exit' request is done in response to closing a window
// (This only partially works around the bug: the process will still not quit properly if the last window
// is closed before the main routine finishes, ie when 'will_stop_when_no_windows' is still false)
if self.will_stop_when_no_windows && self.window_events.len() <= 1 {
self.will_exit = true;
}
}
use WindowEvent::*;
// Generate draw_events for the window event
let draw_events = match event {
Resized(new_size) => vec![DrawEvent::Resize(new_size.width as f64, new_size.height as f64)],
Moved(_position) => vec![],
CloseRequested => vec![DrawEvent::Closed],
Destroyed => vec![],
DroppedFile(_path) => vec![],
HoveredFile(_path) => vec![],
HoveredFileCancelled => vec![],
ReceivedCharacter(_c) => vec![],
Focused(_focused) => vec![],
ModifiersChanged(_state) => vec![],
TouchpadPressure { device_id: _, pressure: _, stage: _ } => vec![],
AxisMotion { device_id: _, axis: _, value: _ } => vec![],
Touch(_touch) => vec![],
ScaleFactorChanged { scale_factor, new_inner_size } => vec![DrawEvent::Scale(scale_factor), DrawEvent::Resize(new_inner_size.width as f64, new_inner_size.height as f64)],
ThemeChanged(_theme) => vec![],
// Keyboard events
KeyboardInput { device_id: _, input, is_synthetic: _, } => {
// Convert the keycode
let key = input.virtual_keycode.map(|keycode| key_from_glutin(&keycode));
let key = if key == Some(Key::Unknown) { None } else { key };
// TODO: for modifier keys, generate keydown/up using the modifier state
// Generate the event for this keypress
match input.state {
ElementState::Pressed => vec![DrawEvent::KeyDown(input.scancode as _, key)],
ElementState::Released => vec![DrawEvent::KeyUp(input.scancode as _, key)]
}
},
// Pointer events
CursorMoved { device_id, position, .. } => {
// Update the pointer state
let pointer_id = self.id_for_pointer(&device_id);
let pointer_state = self.state_for_pointer(&device_id);
pointer_state.location_in_window = (position.x, position.y);
// Generate the mouse event
let pointer_state = pointer_state.clone();
let is_drag = pointer_state.buttons.len() > 0;
let action = if is_drag { PointerAction::Drag } else { PointerAction::Move };
vec![DrawEvent::Pointer(action, pointer_id, pointer_state)]
},
CursorEntered { device_id } => {
// Generate the 'entered' event with the current pointer state
let pointer_id = self.id_for_pointer(&device_id);
let pointer_state = self.state_for_pointer(&device_id);
// Generate the mouse event
let pointer_state = pointer_state.clone();
vec![DrawEvent::Pointer(PointerAction::Enter, pointer_id, pointer_state)]
},
CursorLeft { device_id } => {
// Generate the 'entered' event with the current pointer state
let pointer_id = self.id_for_pointer(&device_id);
let pointer_state = self.state_for_pointer(&device_id);
// Generate the mouse event
let pointer_state = pointer_state.clone();
vec![DrawEvent::Pointer(PointerAction::Leave, pointer_id, pointer_state)]
},
MouseInput { device_id, state, button, .. } => {
// Generate the 'entered' event with the current pointer state
let pointer_id = self.id_for_pointer(&device_id);
let pointer_state = self.state_for_pointer(&device_id);
// TODO: for modifier keys, generate keydown/up using the modifier state
// Update the pointe state
let button = button_from_glutin(&button);
let action = match state {
ElementState::Pressed => {
if !pointer_state.buttons.contains(&button) {
pointer_state.buttons.push(button);
}
PointerAction::ButtonDown
}
ElementState::Released => {
pointer_state.buttons.retain(|item| item != &button);
PointerAction::ButtonUp
}
};
// Generate the mouse event
let pointer_state = pointer_state.clone();
vec![DrawEvent::Pointer(action, pointer_id, pointer_state)]
},
MouseWheel { device_id: _, delta: _, phase: _, .. } => vec![],
};
if let Some(window_events) = self.window_events.get_mut(&window_id) {
// Dispatch the draw events using a process
if draw_events.len() > 0 {
// Need to republish the window events so we can share with the process
let mut window_events = window_events.republish();
self.run_process(async move {
for evt in draw_events {
window_events.publish(evt).await;
}
});
}
}
}
///
/// Sends a redraw request to a window
///
fn request_redraw(&mut self, window_id: WindowId) {
if let Some(window_events) = self.window_events.get_mut(&window_id) {
// Need to republish the window events so we can share with the process
let mut window_events = window_events.republish();
self.run_process(async move {
window_events.publish(DrawEvent::Redraw).await;
});
}
}
///
/// Handles one of our user events from the GlutinThreadEvent enum
///
fn handle_thread_event(&mut self, event: GlutinThreadEvent, window_target: &EventLoopWindowTarget<GlutinThreadEvent>) {
use GlutinThreadEvent::*;
match event {
CreateRenderWindow(actions, events, window_properties) => {
// Get the initial set of properties for the window
let title = window_properties.title().get();
let (size_x, size_y) = window_properties.size().get();
let fullscreen = window_properties.fullscreen().get();
let decorations = window_properties.has_decorations().get();
let fullscreen = if fullscreen { Some(Fullscreen::Borderless(None)) } else { None };
// Create a window
let window_builder = glutin::window::WindowBuilder::new()
.with_title(title)
.with_inner_size(glutin::dpi::LogicalSize::new(size_x as f64, size_y as _))
.with_fullscreen(fullscreen)
.with_decorations(decorations);
let windowed_context = glutin::ContextBuilder::new()
.with_gl(GlRequest::Specific(Api::OpenGl, (3, 3)))
.with_vsync(false)
.build_windowed(window_builder, &window_target)
.unwrap();
// Store the window context in a new glutin window
let window_id = windowed_context.window().id();
let size = windowed_context.window().inner_size();
let scale = windowed_context.window().scale_factor();
let window = GlutinWindow::new(windowed_context);
// Store the publisher for the events for this window
let mut initial_events = events.republish_weak();
self.window_events.insert(window_id, events);
// Run the window as a process on this thread
self.run_process(async move {
// Send the initial events for this window (set the size and the DPI)
initial_events.publish(DrawEvent::Resize(size.width as f64, size.height as f64)).await;
initial_events.publish(DrawEvent::Scale(scale)).await;
initial_events.publish(DrawEvent::Redraw).await;
let window_events = initial_events;
// Process the actions for the window
send_actions_to_window(window, actions, window_events, window_properties).await;
// Stop processing events for the window once there are no more actions
glutin_thread().send_event(GlutinThreadEvent::StopSendingToWindow(window_id));
});
}
StopSendingToWindow(window_id) => {
self.window_events.remove(&window_id);
if self.window_events.len() == 0 && self.will_stop_when_no_windows {
self.will_exit = true;
}
}
RunProcess(start_process) => {
self.run_process(start_process());
},
WakeFuture(future_id) => {
self.poll_future(future_id);
},
StopWhenAllWindowsClosed => {
self.will_stop_when_no_windows = true;
if self.window_events.len() == 0 {
self.will_exit = true;
}
}
}
}
///
/// Runs a process in the context of this runtime
///
fn run_process<Fut: 'static+Future<Output=()>>(&mut self, future: Fut) {
// Box the future for polling
let future = future.boxed_local();
// Assign an ID to this future (we use this for waking it up)
let future_id = NEXT_FUTURE_ID.fetch_add(1, Ordering::Relaxed);
// Store in the runtime
self.futures.insert(future_id, future);
// Perform the initial polling operation on the future
self.poll_future(future_id);
}
///
/// Causes the future with the specified ID to be polled
///
fn poll_future(&mut self, future_id: u64) {
if let Some(future) = self.futures.get_mut(&future_id) {
// Create a context to poll this future in
let glutin_waker = GlutinFutureWaker { future_id: Mutex::new(Some(future_id)) };
let glutin_waker = task::waker(Arc::new(glutin_waker));
let mut glutin_context = task::Context::from_waker(&glutin_waker);
// Poll the future
let poll_result = future.poll_unpin(&mut glutin_context);
// Remove the future from the list if it has completed
if let task::Poll::Ready(_) = poll_result {
self.futures.remove(&future_id);
}
}
}
}
impl task::ArcWake for GlutinFutureWaker {
fn wake_by_ref(arc_self: &Arc<Self>) {
// If this is the first wake request for this waker...
if let Some(future_id) = arc_self.future_id.lock().unwrap().take() {
// Send a wake request to glutin
glutin_thread().send_event(GlutinThreadEvent::WakeFuture(future_id));
}
}
}