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
use super::glutin_runtime::*;
use super::glutin_thread_event::*;

use ::desync::*;

use glutin::event_loop::{EventLoop, EventLoopProxy};

use std::mem;
use std::sync::*;
use std::sync::mpsc;
use std::thread;
use std::collections::{HashMap};

lazy_static! {
    static ref GLUTIN_THREAD: Desync<Option<Arc<GlutinThread>>> = Desync::new(None);
}

///
/// Represents the thread running the glutin event loop
///
pub struct GlutinThread {
    event_proxy: Desync<EventLoopProxy<GlutinThreadEvent>>
}

impl GlutinThread {
    ///
    /// Sends an event to the Glutin thread
    ///
    pub fn send_event(&self, event: GlutinThreadEvent) {
        self.event_proxy.desync(move |proxy| { proxy.send_event(event).ok(); });
    }
}

///
/// Creates or retrieves the glutin thread
///
pub fn glutin_thread() -> Arc<GlutinThread> {
    GLUTIN_THREAD.sync(|thread| {
        if let Some(thread) = thread {
            // Thread is already running
            Arc::clone(thread)
        } else {
            // Need to start a new thread
            let new_thread  = create_glutin_thread();
            *thread         = Some(Arc::clone(&new_thread));

            new_thread
        }
    })
}

struct StopGlutinWhenDropped;
impl Drop for StopGlutinWhenDropped {
    fn drop(&mut self) {
        glutin_thread().send_event(GlutinThreadEvent::StopWhenAllWindowsClosed);
    }
}

///
/// Steals the current thread to run the UI event loop and calls the application function
/// back to continue execution
///
/// This is required because some operating systems (OS X) can't handle UI events from any
/// thread other than the one that's created when the app starts. `flo_draw` will work
/// without this call on operating systems with more robust event handling designs.
///
/// This will also ensure that any graphics are displayed until the user closes the window,
/// which may be useful behaviour even on operating systems where the thread takeover is
/// not required.
///
pub fn with_2d_graphics<TAppFn: 'static+Send+FnOnce() -> ()>(app_fn: TAppFn) {
    // The event loop thread will send us a proxy once it's initialized
    let (send_proxy, recv_proxy) = mpsc::channel();

    // Run the application on a background thread
    thread::Builder::new()
        .name("Application thread".into())
        .spawn(move || {
            GLUTIN_THREAD.sync(move |thread| {
                // Wait for the proxy to be created
                let proxy = recv_proxy.recv().expect("Glutin thread will send us a proxy after initialising");

                // Create the main thread object
                *thread = Some(Arc::new(GlutinThread {
                    event_proxy: Desync::new(proxy)
                }));
            });

            // Call back to start the app running
            let stop_glutin = StopGlutinWhenDropped;

            app_fn();

            mem::drop(stop_glutin);
        })
        .expect("Application thread is running");

    // Run the graphics thread on this thread
    run_glutin_thread(send_proxy);
}

///
/// Starts the glutin thread running
///
fn create_glutin_thread() -> Arc<GlutinThread> {
    // The event loop thread will send us a proxy once it's initialized
    let (send_proxy, recv_proxy) = mpsc::channel();

    // Run the event loop on its own thread
    thread::Builder::new()
        .name("Glutin event thread".into())
        .spawn(move || {
            run_glutin_thread(send_proxy)
        })
        .expect("Glutin thread is running");

    // Wait for the proxy to be created
    let proxy = recv_proxy.recv().expect("Glutin thread will send us a proxy after initialising");

    // Create a GlutinThread object to communicate with this thread
    Arc::new(GlutinThread {
        event_proxy: Desync::new(proxy)
    })
}

///
/// Runs a glutin thread, posting the proxy to the specified channel
///
fn run_glutin_thread(send_proxy: mpsc::Sender<EventLoopProxy<GlutinThreadEvent>>) {
    // Create the event loop
    let event_loop  = EventLoop::with_user_event();

    // We communicate with the event loop via the proxy
    let proxy       = event_loop.create_proxy();

    // Send the proxy back to the creating thread
    send_proxy.send(proxy).expect("Main thread is waiting to receive its proxy");

    // The runtime struct is used to maintain state when the event loop is running
    let mut runtime = GlutinRuntime { 
        window_events:              HashMap::new(),
        futures:                    HashMap::new(),
        will_stop_when_no_windows:  false,
        will_exit:                  false,
        pointer_id:                 HashMap::new(),
        pointer_state:              HashMap::new()
    };

    // Run the glutin event loop
    event_loop.run(move |event, window_target, control_flow| { 
        runtime.handle_event(event, window_target, control_flow);
    });
}