Lines
32.67 %
Functions
32.26 %
Branches
100 %
use std::collections::HashSet;
use std::sync::Arc;
use std::time::{Duration, Instant};
use figures::{DisplayScale, Displayable, One, Pixels, Points, Rectlike, SizedRect};
use winit::event::VirtualKeyCode;
use winit::window::Theme;
use crate::math::{Point, Scale, Scaled, Size, Vector};
use crate::shape::Shape;
use crate::sprite::RenderedSprite;
use crate::text::prepared::PreparedSpan;
/// An individual render instruction.
#[derive(Debug)]
pub enum Element {
/// A rendered sprite.
Sprite {
/// The sprite being rendered.
sprite: RenderedSprite,
/// The current clipping rect.
clip: Option<SizedRect<u32, Pixels>>,
},
/// A rendered span of text.
Text {
/// The span being rendered.
span: PreparedSpan,
/// A rendered shape.
Shape(Shape<Pixels>),
}
/// An event instructing how to render frames.
pub enum SceneEvent {
/// Begin a new frame with the given size.
BeginFrame {
/// The frame size to render.
size: Size<u32, Pixels>,
/// Render an element.
Render(Element),
/// Finish the current frame.
EndFrame,
/// The main rendering destination, usually interacted with through [`Target`].
pub struct Scene {
/// The virtual key codes curently depressed.
pub keys_pressed: HashSet<VirtualKeyCode>,
scale_factor: DisplayScale<f32>,
event_sender: flume::Sender<SceneEvent>,
now: Option<Instant>,
elapsed: Option<Duration>,
system_theme: Theme,
impl From<Arc<Scene>> for Target {
fn from(scene: Arc<Scene>) -> Self {
Self {
scene,
clip: None,
offset: None,
impl From<Scene> for Target {
fn from(scene: Scene) -> Self {
Self::from(Arc::new(scene))
/// A render target
#[derive(Clone, Debug)]
pub struct Target {
/// The scene to draw into.
pub scene: Arc<Scene>,
/// The curent clipping rect. All drawing calls will be clipped to this
/// area.
pub clip: Option<SizedRect<u32, Pixels>>,
/// The current offset (translation) of drawing calls.
pub offset: Option<Vector<f32, Pixels>>,
impl Target {
/// Returns a new [`Target`] with the intersection of `new_clip` an the
/// current `clip`, if any. The scene and offset are cloned.
#[must_use]
pub fn clipped_to(&self, new_clip: SizedRect<u32, Pixels>) -> Self {
scene: self.scene.clone(),
clip: Some(match &self.clip {
Some(existing_clip) => existing_clip
.intersection(&new_clip)
.unwrap_or_default()
.as_sized(),
None => new_clip
.intersection(&SizedRect::from(self.size_in_pixels()))
}),
offset: self.offset,
/// Returns a new [`Target`] offset by `delta` from the current `offset`, if
/// any. The scene and clipping rect are cloned.
pub fn offset_by(&self, delta: Vector<f32, Pixels>) -> Self {
clip: self.clip,
offset: Some(match self.offset {
Some(offset) => offset + delta,
None => delta,
/// Translates `point` by the current `offset`, if any.
pub fn offset_point(&self, point: Point<f32, Scaled>) -> Point<f32, Scaled> {
match self.offset {
Some(offset) => point + offset.to_scaled(self.scale()),
None => point,
pub fn offset_point_raw(&self, point: Point<f32, Pixels>) -> Point<f32, Pixels> {
Some(offset) => point + offset,
/// Returns the scene as a mutable reference. Will only succeed if no other
/// references exist. Not intended for use inside of `kludgine-app`.
pub fn scene_mut(&mut self) -> Option<&mut Scene> {
Arc::get_mut(&mut self.scene)
impl std::ops::Deref for Target {
type Target = Scene;
fn deref(&self) -> &Self::Target {
self.scene.as_ref()
/// The state of keyboard modifier keys.
#[allow(clippy::struct_excessive_bools)]
pub struct Modifiers {
/// If true, a control key is currently depressed.
pub control: bool,
/// If true, an alt key is currently depressed.
pub alt: bool,
/// If true, an "Operating System key" is currently depressed. For most
/// keyboards, this is the Windows key or the Command/Apple key.
pub operating_system: bool,
/// If true, a shift key is currently depressed.
pub shift: bool,
impl Modifiers {
/// Returns true if the primary modifier of the current OS is depressed. For
/// Mac and iOS, this returns `operating_system`. For all other OSes, this
/// returns `control`.
pub const fn primary_modifier(&self) -> bool {
#[cfg(any(target_os = "macos", target_os = "ios"))]
{
self.operating_system
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
self.control
/// Returns true if the command key/Apple key is pressed. This only returns
/// true if `operating_system` key is true and the current operating system
/// is Mac or iOS.
#[allow(clippy::unused_self)]
pub const fn command_key(&self) -> bool {
false
impl Scene {
/// Returns a new Scene that emits [`SceneEvent`]s to `event_sender`.
pub fn new(event_sender: flume::Sender<SceneEvent>, default_system_theme: Theme) -> Self {
event_sender,
scale_factor: DisplayScale::one(),
size: Size::default(),
keys_pressed: HashSet::new(),
now: None,
elapsed: None,
system_theme: default_system_theme,
/// Returns the currently set [`Theme`].
pub const fn system_theme(&self) -> Theme {
self.system_theme
/// Sets the [`Theme`].
pub fn set_system_theme(&mut self, system_theme: Theme) {
self.system_theme = system_theme;
pub(crate) fn push_element(&self, element: Element) {
drop(self.event_sender.send(SceneEvent::Render(element)));
/// Sets the size of the scene.
pub fn set_size(&mut self, size: Size<u32, Pixels>) {
self.size = size;
/// Sets the DPI scale.
pub fn set_dpi_scale(&mut self, scale_factor: Scale<f32, Points, Pixels>) {
self.scale_factor = DisplayScale::new(scale_factor, Scale::one());
/// Sets the additional scaling factor.
pub fn set_additional_scale(&mut self, scale: Scale<f32, Scaled, Points>) {
self.scale_factor.set_additional_scale(scale);
/// Returns the current [`DisplayScale`].
pub const fn scale(&self) -> &DisplayScale<f32> {
&self.scale_factor
/// Returns true if any of `keys` are currently pressed.
pub fn any_key_pressed(&self, keys: &[VirtualKeyCode]) -> bool {
for key in keys {
if self.keys_pressed.contains(key) {
return true;
/// Returns the currently depressed modifier keys.
pub fn modifiers_pressed(&self) -> Modifiers {
Modifiers {
control: self.any_key_pressed(&[VirtualKeyCode::RControl, VirtualKeyCode::LControl]),
alt: self.any_key_pressed(&[VirtualKeyCode::RAlt, VirtualKeyCode::LAlt]),
shift: self.any_key_pressed(&[VirtualKeyCode::LShift, VirtualKeyCode::RShift]),
operating_system: self.any_key_pressed(&[VirtualKeyCode::RWin, VirtualKeyCode::LWin]),
/// Begins a new frame with the current size.
pub fn start_frame(&mut self) {
let last_start = self.now;
self.now = Some(Instant::now());
self.elapsed = match last_start {
Some(last_start) => self.now.unwrap().checked_duration_since(last_start),
None => None,
};
drop(
self.event_sender
.send(SceneEvent::BeginFrame { size: self.size }),
);
/// Ends the current frame, allowing it to be rendered.
pub fn end_frame(&self) {
drop(self.event_sender.send(SceneEvent::EndFrame));
/// Returns the current size of the scene in [`Scaled`] units.
pub fn size(&self) -> Size<f32, Scaled> {
self.size.cast::<f32>().to_scaled(self.scale())
/// Returns the current size of the scene in [`Pixels`] units.
pub const fn size_in_pixels(&self) -> Size<u32, Pixels> {
self.size
/// Returns the [`Instant`] when the frame began.
pub fn now(&self) -> Instant {
self.now.expect("now() called without starting a frame")
/// Returns the elapsed [`Duration`] since the scene was created.
pub const fn elapsed(&self) -> Option<Duration> {
self.elapsed
/// Returns true if this is the first frame being rendered.
pub const fn is_initial_frame(&self) -> bool {
self.elapsed.is_none()
// pub fn register_font(&mut self, font: &Font) {
// let family = font.family().expect("Unable to register VecFonts");
// self.fonts
// .entry(family)
// .and_modify(|fonts| fonts.push(font.clone()))
// .or_insert_with(|| vec![font.clone()]);
// }
// pub fn lookup_font(
// &self,
// family: &str,
// weight: Weight,
// style: FontStyle,
// ) -> kludgine::Result<Font> {
// let fonts = self.fonts.get(family);
// match fonts {
// Some(fonts) => {
// let mut closest_font = None;
// let mut closest_weight = None;
// for font in fonts.iter() {
// let font_weight = font.weight();
// let font_style = font.style();
// if font_weight == weight && font_style == style {
// return Ok(font.clone());
// } else {
// // If it's not the right style, we want to heavily
// penalize the score // But if no font matches the
// style, we should pick the weight that matches // best
// in another style. let style_multiplier = if
// font_style == style { 1 } else { 10 }; let delta =
// (font.weight().to_number() as i32 - weight.to_number() as i32)
// .abs()
// * style_multiplier;
// if closest_weight.is_none() || closest_weight.unwrap() >
// delta { closest_weight = Some(delta);
// closest_font = Some(font.clone());
// closest_font.ok_or_else(||
// KludgineError::FontFamilyNotFound(family.to_owned())) }
// None => Err(KludgineError::FontFamilyNotFound(family.to_owned())),