Skip to main content

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}