Skip to main content

pkecs_window/
lib.rs

1//! Provides a graphical application over pkecs.
2
3pub mod component;
4pub mod config;
5pub mod event;
6mod metrics;
7pub mod schedule;
8
9use log::debug;
10use winit::{
11    application::ApplicationHandler,
12    dpi::PhysicalSize,
13    error::EventLoopError,
14    event::{DeviceEvent, DeviceId, ElementState, KeyEvent, MouseButton, WindowEvent},
15    event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
16    window::WindowId,
17};
18use pkecs::{
19    app::{Application, plugin::Plugin},
20    core::{
21        entity::{Entities},
22        event::{Events, EventHandler},
23        schedule::{Schedule, System},
24    },
25};
26use component::{lifecycle::ApplicationLifecycle, time::GameTime, window::WindowContext};
27use config::GraphicalApplicationOptions;
28use event::{resize::Resized, key::KeyPress, mouse::MouseMotion};
29use metrics::Metrics;
30use schedule::{Startup, Update, FixedUpdate, Shutdown};
31
32/// An application with a graphical interface.
33#[derive(Default)]
34pub struct GraphicalApplication {
35    entities: Entities,
36    events: Events,
37    schedule: Schedule,
38    options: GraphicalApplicationOptions,
39    metrics: Metrics,
40}
41
42impl GraphicalApplication {
43    /// Initializes [`self`] from [`GraphicalApplicationOptions`].
44    pub fn from_config(options: impl Into<GraphicalApplicationOptions>) -> Self {
45        let entities = Entities::default();
46        let events = Events::default();
47        let schedule = Schedule::default();
48        let options = options.into();
49        let metrics = Metrics::default();
50
51        Self { schedule, entities, events, options, metrics }
52    }
53
54    /// Runs the application.
55    ///
56    /// # Panics
57    /// Will panics if the event loop fails.
58    pub fn run(&mut self) {
59        debug!("Running application...");
60
61        self.run_inner()
62            .expect("Application failed unexpectedly!");
63    }
64
65    /// Runs a winit event loop.
66    ///
67    /// Configures the event loop to poll as this is ideal for real-time graphics.
68    fn run_inner(&mut self) -> Result<(), EventLoopError> {
69        let event_loop = EventLoop::new()?;
70        event_loop.set_control_flow(ControlFlow::Poll);
71        event_loop.run_app(self)?;
72
73        Ok(())
74    }
75
76    /// Performs actions while a [`Window`] closes.
77    ///
78    /// - [`Shutdown`] systems are executed.
79    fn close(&mut self, event_loop: &ActiveEventLoop) {
80        event_loop.exit();
81
82        // Run after the event loop closes for the window to shut properly.
83        self.schedule.run_schedule(Shutdown, &mut self.entities, &mut self.events);
84    }
85
86    /// Performs actions during a redraw.
87    ///
88    /// - [`Update`] and [`FixedUpdate`] systems are ran.
89    /// - [`GameTime`] is measured.
90    /// - [`Window`] is redrawn.
91    fn redraw(&mut self) {
92        self.schedule.run_schedule(Update, &mut self.entities, &mut self.events);
93
94        self.metrics.measure();
95        if self.metrics.elapsed() > self.options.tick_rate {
96            self.schedule.run_schedule(FixedUpdate, &mut self.entities, &mut self.events);
97            self.metrics.reset_elapsed();
98        }
99
100        self.time_measure();
101        self.window_redraw();
102    }
103
104    /// Measures the applications temporals.
105    fn time_measure(&mut self) {
106        let mut time_query = self.entities.query_mut::<GameTime>();
107        let time = time_query
108            .first_mut()
109            .expect("Failed to retrieve a temporal device while redrawing.");
110
111        time.delta = self.metrics.frame_time() as f32;
112    }
113
114    /// Performs a redraw of the [`Window`].
115    fn window_redraw(&self) {
116        let context_query = self.entities.query::<WindowContext>();
117        let context = context_query
118            .first()
119            .expect("Failed to retrieve a window while redrawing.");
120
121        context.redraw();
122    }
123
124    /// Executed when the window resizes.
125    fn resize(&mut self, width: u32, height: u32) {
126        self.options.with_size(width, height);
127
128        let resized = Resized::from_size(width, height);
129        self.events.trigger(&resized, &mut self.entities);
130    }
131
132    /// Handles keyboard input.
133    fn keyboard_input(&mut self, event: &KeyEvent) {
134        let Ok(key) = KeyPress::try_from(event) else {
135            return;
136        };
137
138        self.events.trigger(&key, &mut self.entities);
139    }
140
141    /// Handles mouse input.
142    fn mouse_input(&mut self, state: &ElementState, button: &MouseButton) {
143        if !state.is_pressed() {
144            return;
145        }
146
147        let mouse = Into::<MouseButton>::into(*button);
148        self.events.trigger(&mouse, &mut self.entities);
149    }
150
151    /// Handles mouse motion.
152    fn mouse_motion(&mut self, delta_x: f64, delta_y: f64) {
153        let motion = MouseMotion::from_delta(delta_x, delta_y);
154        self.events.trigger(&motion, &mut self.entities)
155    }
156}
157
158impl ApplicationHandler for GraphicalApplication {
159    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
160        debug!("Window resumed...");
161
162        let Ok(window) = event_loop.create_window(self.options.to_attr()) else {
163            panic!("Failed to initialize the window!");
164        };
165
166        let context = WindowContext::from_window(window);
167        self.entities.spawn(context);
168
169        let lifecycle = ApplicationLifecycle::default();
170        self.entities.spawn(lifecycle);
171
172        let time = GameTime::default();
173        self.entities.spawn(time);
174
175        self.schedule.run_schedule(Startup, &mut self.entities, &mut self.events);
176
177        // Trigger to ensure the surface is ready for presentation.
178        self.events.trigger(&self.options.to_resized(), &mut self.entities);
179    }
180
181    fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
182        match event {
183            WindowEvent::CloseRequested => self.close(event_loop),
184            WindowEvent::RedrawRequested => self.redraw(),
185            WindowEvent::Resized(PhysicalSize { width, height }) => self.resize(width, height),
186            WindowEvent::KeyboardInput { event, .. } => self.keyboard_input(&event),
187            WindowEvent::MouseInput { state, button, .. } => self.mouse_input(&state, &button),
188            _ => { },
189        };
190
191        let lifecycle = self.entities.query::<ApplicationLifecycle>();
192        if lifecycle.iter().any(|l| l.is_exiting()) {
193            self.close(event_loop);
194        }
195    }
196
197    fn device_event(&mut self, event_loop: &ActiveEventLoop, _id: DeviceId, event: DeviceEvent) {
198        match event {
199            DeviceEvent::MouseMotion { delta: (delta_x, delta_y) } => self.mouse_motion(delta_x, delta_y),
200            _ => { }
201        };
202
203        let lifecycle = self.entities.query::<ApplicationLifecycle>();
204        if lifecycle.iter().any(|l| l.is_exiting()) {
205            self.close(event_loop);
206        }
207    }
208}
209
210impl Application for GraphicalApplication {
211    /// Adds a system to the application.
212    fn add_system<TSchedule, TSystem>(&mut self, schedule: TSchedule, system: TSystem) -> &mut Self
213        where
214            TSchedule: 'static,
215            TSystem: System + 'static,
216    {
217        self.schedule.add_system(schedule, system);
218
219        self
220    }
221
222    fn handle_event<TEvent, TEventHandler>(&mut self, handler: TEventHandler) -> &mut Self
223        where
224            TEvent: 'static,
225            TEventHandler: EventHandler<TEvent> + 'static,
226    {
227        self.events.register(handler);
228
229        self
230    }
231
232    fn add_plugin<TPlugin>(&mut self) -> &mut Self
233        where
234            TPlugin: Plugin,
235    {
236        TPlugin::attach(&mut self.events, &mut self.schedule);
237
238        self
239    }
240}