Skip to main content

care_game/
window.rs

1use std::{
2    sync::{
3        atomic::{AtomicBool, Ordering},
4        Arc,
5    },
6    time::Instant,
7};
8
9use parking_lot::{Mutex, RwLock};
10use winit::{
11    application::ApplicationHandler,
12    dpi::{LogicalPosition, LogicalSize},
13    event::{KeyEvent, WindowEvent},
14    event_loop::{ControlFlow, EventLoop},
15    keyboard::{Key as WKey, NamedKey, SmolStr},
16    window::{Window, WindowAttributes},
17};
18
19use crate::{math::Vec2, prelude::Key};
20
21static HAS_INITIALIZED: AtomicBool = AtomicBool::new(false);
22static INIT_COMPLETE: AtomicBool = AtomicBool::new(false);
23thread_local! {
24    static EVENT_LOOP: RwLock<Option<EventLoop<()>>> = const { RwLock::new(None) };
25}
26
27pub(crate) static CREATE_WINDOWS: Mutex<Vec<WindowAttributes>> = Mutex::new(Vec::new());
28pub(crate) static WINDOWS: RwLock<Vec<Arc<Window>>> = RwLock::new(Vec::new());
29
30fn init() {
31    #[cfg(any(target_os = "linux", target_os = "windows"))]
32    assert!(
33        std::thread::current().name() == Some("main"),
34        "Window init must be called on the main thread!"
35    );
36    if HAS_INITIALIZED
37        .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
38        .is_ok()
39    {
40        INIT_COMPLETE.store(false, Ordering::Release);
41        EVENT_LOOP.with(|el_cell| {
42            let mut el = el_cell.write();
43            if el.is_none() {
44                let tmp = EventLoop::new().unwrap();
45                tmp.set_control_flow(ControlFlow::Poll);
46                *el = Some(tmp);
47            }
48        });
49    }
50}
51
52/// Settings specifying how to open a window, see [open_with_settings].
53#[derive(Debug)]
54pub struct WindowSettings<'a> {
55    // Title/name of the window
56    name: &'a str,
57    /// Size, in pixels
58    size: Option<Vec2>,
59    // Whether the window is resizable or not
60    resizable: bool,
61    // Position, in pixels
62    pos: Option<Vec2>,
63}
64
65impl Default for WindowSettings<'_> {
66    fn default() -> Self {
67        Self {
68            name: "CÄRE game",
69            size: Some((800, 600).into()),
70            resizable: false,
71            pos: None,
72        }
73    }
74}
75
76/// Open a window with the specified name
77///
78/// # NOTE
79/// Can only be called from the main thread, calling on any other thread will panic.
80pub fn open(name: &str) {
81    open_with_settings(WindowSettings {
82        name,
83        ..WindowSettings::default()
84    })
85}
86
87/// Open a window with the specified window settings
88pub fn open_with_settings(settings: WindowSettings) {
89    let mut attribs = Window::default_attributes()
90        .with_title(settings.name)
91        .with_resizable(settings.resizable);
92    if let Some(size) = settings.size {
93        attribs = attribs.with_inner_size(LogicalSize::new(size.0.x, size.0.y));
94    }
95    if let Some(pos) = settings.pos {
96        attribs = attribs.with_position(LogicalPosition::new(pos.0.x, pos.0.y));
97    }
98    CREATE_WINDOWS.lock().push(attribs);
99}
100
101/// WIP Function to set the main window size
102pub fn set_window_size(size: impl Into<Vec2>) {
103    let size = size.into();
104    let mut windows = WINDOWS.write();
105    let window = windows.first_mut().unwrap();
106    let _ = window.request_inner_size(LogicalSize::new(size.x(), size.y()));
107}
108
109fn convert_key(key: winit::keyboard::Key<SmolStr>) -> Key {
110    match key {
111        WKey::Named(NamedKey::ArrowUp) => Key::Up,
112        WKey::Named(NamedKey::ArrowDown) => Key::Down,
113        WKey::Named(NamedKey::ArrowLeft) => Key::Left,
114        WKey::Named(NamedKey::ArrowRight) => Key::Right,
115        WKey::Named(NamedKey::Space) => Key::Space,
116        WKey::Named(NamedKey::Enter) => Key::Enter,
117        WKey::Named(NamedKey::Escape) => Key::Escape,
118        WKey::Named(NamedKey::Backspace) => Key::Backspace,
119        WKey::Named(NamedKey::Delete) => Key::Delete,
120        WKey::Named(NamedKey::Shift) => Key::Shift,
121        WKey::Named(NamedKey::Control) => Key::Control,
122        WKey::Named(NamedKey::Alt) => Key::Alt,
123        WKey::Named(NamedKey::Meta) => Key::Meta,
124        WKey::Character(ch) => Key::Char(ch.chars().next().unwrap()),
125        _ => Key::Unknown,
126    }
127}
128
129enum AppData<T, F: FnOnce() -> T> {
130    Init(Option<F>),
131    Data(T),
132}
133
134struct AppHandler<T, F: FnMut(&mut T), I: FnOnce() -> T> {
135    data: AppData<T, I>,
136    loop_fn: F,
137}
138
139impl<T, F: FnMut(&mut T), I: FnOnce() -> T> ApplicationHandler for AppHandler<T, F, I> {
140    fn resumed(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {}
141
142    fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
143        for attribs in CREATE_WINDOWS.lock().drain(..) {
144            WINDOWS.write().push(Arc::new(
145                    event_loop
146                    .create_window(attribs)
147                    .expect("Failed to open window"),
148            ));
149        }
150        if let AppData::Init(init) = &mut self.data {
151            self.data = AppData::Data((init.take().unwrap())());
152        };
153        let AppData::Data(data) = &mut self.data else {
154            panic!("Impossible");
155        };
156        (self.loop_fn)(data);
157    }
158
159    fn window_event(
160        &mut self,
161        event_loop: &winit::event_loop::ActiveEventLoop,
162        window_id: winit::window::WindowId,
163        ev: WindowEvent,
164    ) {
165        match ev {
166            WindowEvent::CloseRequested => {
167                event_loop.exit();
168            }
169            WindowEvent::KeyboardInput {
170                event:
171                    KeyEvent {
172                        logical_key,
173                        state,
174                        repeat,
175                        ..
176                    },
177                ..
178            } => {
179                if !repeat {
180                    crate::event::handle_event(crate::event::Event {
181                        timestamp: Instant::now(),
182                        data: crate::event::EventData::KeyEvent {
183                            key: convert_key(logical_key),
184                            pressed: state.is_pressed(),
185                        },
186                    });
187                }
188            }
189            WindowEvent::CursorMoved { position, .. } => {
190                let position: LogicalPosition<f64> = position.to_logical(
191                    WINDOWS
192                        .read()
193                        .iter()
194                        .find(|w| w.id() == window_id)
195                        .map(|w| w.scale_factor())
196                        .unwrap_or(1.0),
197                );
198                crate::event::handle_event(crate::event::Event {
199                    timestamp: Instant::now(),
200                    data: crate::event::EventData::MouseMoved {
201                        position: Vec2::new(position.x, position.y),
202                    },
203                });
204            }
205            WindowEvent::MouseInput { state, button, .. } => {
206                crate::event::handle_event(crate::event::Event {
207                    timestamp: Instant::now(),
208                    data: crate::event::EventData::MouseClick {
209                        button: match button {
210                            winit::event::MouseButton::Left => 1,
211                            winit::event::MouseButton::Right => 2,
212                            winit::event::MouseButton::Middle => 3,
213                            winit::event::MouseButton::Back => 4,
214                            winit::event::MouseButton::Forward => 5,
215                            winit::event::MouseButton::Other(n) => n as i32 + 6,
216                        },
217                        pressed: state.is_pressed(),
218                    },
219                });
220            }
221            _ => {}
222        }
223    }
224}
225
226/// Window implementation of the event loop running function
227///
228/// Also initializes the window system
229pub(crate) fn run<T>(init_fn: impl FnOnce() -> T, loop_fn: impl FnMut(&mut T)) {
230    init();
231    EVENT_LOOP.with(move |el_call| {
232        let el = el_call
233            .write()
234            .take()
235            .expect("Event loop must be run from the main thread");
236        el.run_app(&mut AppHandler {
237            data: AppData::Init(Some(init_fn)),
238            loop_fn,
239        })
240        .unwrap();
241    });
242}