kludgine_core/scene.rs
1use std::collections::HashSet;
2use std::sync::Arc;
3use std::time::{Duration, Instant};
4
5use figures::{DisplayScale, Displayable, One, Pixels, Points, Rectlike, SizedRect};
6use winit::event::VirtualKeyCode;
7use winit::window::Theme;
8
9use crate::math::{Point, Scale, Scaled, Size, Vector};
10use crate::shape::Shape;
11use crate::sprite::RenderedSprite;
12use crate::text::prepared::PreparedSpan;
13
14/// An individual render instruction.
15#[derive(Debug)]
16pub enum Element {
17 /// A rendered sprite.
18 Sprite {
19 /// The sprite being rendered.
20 sprite: RenderedSprite,
21 /// The current clipping rect.
22 clip: Option<SizedRect<u32, Pixels>>,
23 },
24 /// A rendered span of text.
25 Text {
26 /// The span being rendered.
27 span: PreparedSpan,
28 /// The current clipping rect.
29 clip: Option<SizedRect<u32, Pixels>>,
30 },
31 /// A rendered shape.
32 Shape(Shape<Pixels>),
33}
34
35/// An event instructing how to render frames.
36pub enum SceneEvent {
37 /// Begin a new frame with the given size.
38 BeginFrame {
39 /// The frame size to render.
40 size: Size<u32, Pixels>,
41 },
42 /// Render an element.
43 Render(Element),
44 /// Finish the current frame.
45 EndFrame,
46}
47
48/// The main rendering destination, usually interacted with through [`Target`].
49#[derive(Debug)]
50pub struct Scene {
51 /// The virtual key codes curently depressed.
52 pub keys_pressed: HashSet<VirtualKeyCode>,
53 scale_factor: DisplayScale<f32>,
54 size: Size<u32, Pixels>,
55 event_sender: flume::Sender<SceneEvent>,
56 now: Option<Instant>,
57 elapsed: Option<Duration>,
58 system_theme: Theme,
59}
60
61impl From<Arc<Scene>> for Target {
62 fn from(scene: Arc<Scene>) -> Self {
63 Self {
64 scene,
65 clip: None,
66 offset: None,
67 }
68 }
69}
70
71impl From<Scene> for Target {
72 fn from(scene: Scene) -> Self {
73 Self::from(Arc::new(scene))
74 }
75}
76
77/// A render target
78#[derive(Clone, Debug)]
79pub struct Target {
80 /// The scene to draw into.
81 pub scene: Arc<Scene>,
82 /// The curent clipping rect. All drawing calls will be clipped to this
83 /// area.
84 pub clip: Option<SizedRect<u32, Pixels>>,
85 /// The current offset (translation) of drawing calls.
86 pub offset: Option<Vector<f32, Pixels>>,
87}
88
89impl Target {
90 /// Returns a new [`Target`] with the intersection of `new_clip` an the
91 /// current `clip`, if any. The scene and offset are cloned.
92 #[must_use]
93 pub fn clipped_to(&self, new_clip: SizedRect<u32, Pixels>) -> Self {
94 Self {
95 scene: self.scene.clone(),
96 clip: Some(match &self.clip {
97 Some(existing_clip) => existing_clip
98 .intersection(&new_clip)
99 .unwrap_or_default()
100 .as_sized(),
101 None => new_clip
102 .intersection(&SizedRect::from(self.size_in_pixels()))
103 .unwrap_or_default()
104 .as_sized(),
105 }),
106 offset: self.offset,
107 }
108 }
109
110 /// Returns a new [`Target`] offset by `delta` from the current `offset`, if
111 /// any. The scene and clipping rect are cloned.
112 #[must_use]
113 pub fn offset_by(&self, delta: Vector<f32, Pixels>) -> Self {
114 Self {
115 scene: self.scene.clone(),
116 clip: self.clip,
117 offset: Some(match self.offset {
118 Some(offset) => offset + delta,
119 None => delta,
120 }),
121 }
122 }
123
124 /// Translates `point` by the current `offset`, if any.
125 #[must_use]
126 pub fn offset_point(&self, point: Point<f32, Scaled>) -> Point<f32, Scaled> {
127 match self.offset {
128 Some(offset) => point + offset.to_scaled(self.scale()),
129 None => point,
130 }
131 }
132
133 /// Translates `point` by the current `offset`, if any.
134 #[must_use]
135 pub fn offset_point_raw(&self, point: Point<f32, Pixels>) -> Point<f32, Pixels> {
136 match self.offset {
137 Some(offset) => point + offset,
138 None => point,
139 }
140 }
141
142 /// Returns the scene as a mutable reference. Will only succeed if no other
143 /// references exist. Not intended for use inside of `kludgine-app`.
144 #[must_use]
145 pub fn scene_mut(&mut self) -> Option<&mut Scene> {
146 Arc::get_mut(&mut self.scene)
147 }
148}
149
150impl std::ops::Deref for Target {
151 type Target = Scene;
152
153 fn deref(&self) -> &Self::Target {
154 self.scene.as_ref()
155 }
156}
157
158/// The state of keyboard modifier keys.
159#[allow(clippy::struct_excessive_bools)]
160pub struct Modifiers {
161 /// If true, a control key is currently depressed.
162 pub control: bool,
163 /// If true, an alt key is currently depressed.
164 pub alt: bool,
165 /// If true, an "Operating System key" is currently depressed. For most
166 /// keyboards, this is the Windows key or the Command/Apple key.
167 pub operating_system: bool,
168 /// If true, a shift key is currently depressed.
169 pub shift: bool,
170}
171
172impl Modifiers {
173 /// Returns true if the primary modifier of the current OS is depressed. For
174 /// Mac and iOS, this returns `operating_system`. For all other OSes, this
175 /// returns `control`.
176 #[must_use]
177 pub const fn primary_modifier(&self) -> bool {
178 #[cfg(any(target_os = "macos", target_os = "ios"))]
179 {
180 self.operating_system
181 }
182
183 #[cfg(not(any(target_os = "macos", target_os = "ios")))]
184 {
185 self.control
186 }
187 }
188
189 /// Returns true if the command key/Apple key is pressed. This only returns
190 /// true if `operating_system` key is true and the current operating system
191 /// is Mac or iOS.
192 #[must_use]
193 #[allow(clippy::unused_self)]
194 pub const fn command_key(&self) -> bool {
195 #[cfg(any(target_os = "macos", target_os = "ios"))]
196 {
197 self.operating_system
198 }
199
200 #[cfg(not(any(target_os = "macos", target_os = "ios")))]
201 {
202 false
203 }
204 }
205}
206
207impl Scene {
208 /// Returns a new Scene that emits [`SceneEvent`]s to `event_sender`.
209 #[must_use]
210 pub fn new(event_sender: flume::Sender<SceneEvent>, default_system_theme: Theme) -> Self {
211 Self {
212 event_sender,
213 scale_factor: DisplayScale::one(),
214 size: Size::default(),
215 keys_pressed: HashSet::new(),
216 now: None,
217 elapsed: None,
218 system_theme: default_system_theme,
219 }
220 }
221
222 /// Returns the currently set [`Theme`].
223 #[must_use]
224 pub const fn system_theme(&self) -> Theme {
225 self.system_theme
226 }
227
228 /// Sets the [`Theme`].
229 pub fn set_system_theme(&mut self, system_theme: Theme) {
230 self.system_theme = system_theme;
231 }
232
233 pub(crate) fn push_element(&self, element: Element) {
234 drop(self.event_sender.send(SceneEvent::Render(element)));
235 }
236
237 /// Sets the size of the scene.
238 pub fn set_size(&mut self, size: Size<u32, Pixels>) {
239 self.size = size;
240 }
241
242 /// Sets the DPI scale.
243 pub fn set_dpi_scale(&mut self, scale_factor: Scale<f32, Points, Pixels>) {
244 self.scale_factor = DisplayScale::new(scale_factor, Scale::one());
245 }
246
247 /// Sets the additional scaling factor.
248 pub fn set_additional_scale(&mut self, scale: Scale<f32, Scaled, Points>) {
249 self.scale_factor.set_additional_scale(scale);
250 }
251
252 /// Returns the current [`DisplayScale`].
253 #[must_use]
254 pub const fn scale(&self) -> &DisplayScale<f32> {
255 &self.scale_factor
256 }
257
258 /// Returns true if any of `keys` are currently pressed.
259 #[must_use]
260 pub fn any_key_pressed(&self, keys: &[VirtualKeyCode]) -> bool {
261 for key in keys {
262 if self.keys_pressed.contains(key) {
263 return true;
264 }
265 }
266 false
267 }
268
269 /// Returns the currently depressed modifier keys.
270 #[must_use]
271 pub fn modifiers_pressed(&self) -> Modifiers {
272 Modifiers {
273 control: self.any_key_pressed(&[VirtualKeyCode::RControl, VirtualKeyCode::LControl]),
274 alt: self.any_key_pressed(&[VirtualKeyCode::RAlt, VirtualKeyCode::LAlt]),
275 shift: self.any_key_pressed(&[VirtualKeyCode::LShift, VirtualKeyCode::RShift]),
276 operating_system: self.any_key_pressed(&[VirtualKeyCode::RWin, VirtualKeyCode::LWin]),
277 }
278 }
279
280 /// Begins a new frame with the current size.
281 pub fn start_frame(&mut self) {
282 let last_start = self.now;
283 self.now = Some(Instant::now());
284 self.elapsed = match last_start {
285 Some(last_start) => self.now.unwrap().checked_duration_since(last_start),
286 None => None,
287 };
288 drop(
289 self.event_sender
290 .send(SceneEvent::BeginFrame { size: self.size }),
291 );
292 }
293
294 /// Ends the current frame, allowing it to be rendered.
295 pub fn end_frame(&self) {
296 drop(self.event_sender.send(SceneEvent::EndFrame));
297 }
298
299 /// Returns the current size of the scene in [`Scaled`] units.
300 #[must_use]
301 pub fn size(&self) -> Size<f32, Scaled> {
302 self.size.cast::<f32>().to_scaled(self.scale())
303 }
304
305 /// Returns the current size of the scene in [`Pixels`] units.
306 #[must_use]
307 pub const fn size_in_pixels(&self) -> Size<u32, Pixels> {
308 self.size
309 }
310
311 /// Returns the [`Instant`] when the frame began.
312 #[must_use]
313 pub fn now(&self) -> Instant {
314 self.now.expect("now() called without starting a frame")
315 }
316
317 /// Returns the elapsed [`Duration`] since the scene was created.
318 #[must_use]
319 pub const fn elapsed(&self) -> Option<Duration> {
320 self.elapsed
321 }
322
323 /// Returns true if this is the first frame being rendered.
324 #[must_use]
325 pub const fn is_initial_frame(&self) -> bool {
326 self.elapsed.is_none()
327 }
328
329 // pub fn register_font(&mut self, font: &Font) {
330 // let family = font.family().expect("Unable to register VecFonts");
331 // self.fonts
332 // .entry(family)
333 // .and_modify(|fonts| fonts.push(font.clone()))
334 // .or_insert_with(|| vec![font.clone()]);
335 // }
336
337 // pub fn lookup_font(
338 // &self,
339 // family: &str,
340 // weight: Weight,
341 // style: FontStyle,
342 // ) -> kludgine::Result<Font> {
343 // let fonts = self.fonts.get(family);
344
345 // match fonts {
346 // Some(fonts) => {
347 // let mut closest_font = None;
348 // let mut closest_weight = None;
349
350 // for font in fonts.iter() {
351 // let font_weight = font.weight();
352 // let font_style = font.style();
353
354 // if font_weight == weight && font_style == style {
355 // return Ok(font.clone());
356 // } else {
357 // // If it's not the right style, we want to heavily
358 // penalize the score // But if no font matches the
359 // style, we should pick the weight that matches // best
360 // in another style. let style_multiplier = if
361 // font_style == style { 1 } else { 10 }; let delta =
362 // (font.weight().to_number() as i32 - weight.to_number() as i32)
363 // .abs()
364 // * style_multiplier;
365
366 // if closest_weight.is_none() || closest_weight.unwrap() >
367 // delta { closest_weight = Some(delta);
368 // closest_font = Some(font.clone());
369 // }
370 // }
371 // }
372
373 // closest_font.ok_or_else(||
374 // KludgineError::FontFamilyNotFound(family.to_owned())) }
375 // None => Err(KludgineError::FontFamilyNotFound(family.to_owned())),
376 // }
377 // }
378}