livesplit_core/rendering/resource/
allocation.rs

1use crate::settings::Font;
2
3use super::SharedOwnership;
4
5/// The kind of text that the font is going to be used for.
6#[derive(Clone, Copy, PartialEq, Eq)]
7pub enum FontKind {
8    /// The font is going to be used for the timer.
9    Timer,
10    /// The font is going to be used for times.
11    Times,
12    /// The font is going to be used for regular text.
13    Text,
14}
15
16impl FontKind {
17    /// Returns whether this kind of font is intended to be monospaced. This
18    /// usually means the `tabular-nums` variant of the font is supposed to be
19    /// activated. If that variant is not available, then it may be necessary to
20    /// emulate monospaced digits instead.
21    pub fn is_monospaced(self) -> bool {
22        self != FontKind::Text
23    }
24}
25
26/// A resource allocator provides all the paths and images necessary to place
27/// [`Entities`](super::super::Entity) in a [`Scene`](super::super::Scene). This
28/// is usually implemented by a specific renderer where the paths and images are
29/// types that the renderer can directly render out.
30pub trait ResourceAllocator {
31    /// The type the renderer uses for building paths.
32    type PathBuilder: PathBuilder<Path = Self::Path>;
33    /// The type the renderer uses for paths.
34    type Path: SharedOwnership;
35    /// The type the renderer uses for images.
36    type Image: SharedOwnership;
37    /// The type the renderer uses for fonts.
38    type Font;
39    /// The type the renderer uses for text labels.
40    type Label: Label;
41
42    /// Creates a new [`PathBuilder`] to build a new path.
43    fn path_builder(&mut self) -> Self::PathBuilder;
44
45    /// Builds a new circle. A default implementation that approximates the
46    /// circle with 4 cubic bézier curves is provided. For more accuracy or
47    /// performance you can change the implementation.
48    fn build_circle(&mut self, x: f32, y: f32, r: f32) -> Self::Path {
49        // Based on https://spencermortensen.com/articles/bezier-circle/
50        const C: f64 = 0.551915024494;
51        let c = (C * r as f64) as f32;
52        let mut builder = self.path_builder();
53        builder.move_to(x, y - r);
54        builder.curve_to(x + c, y - r, x + r, y - c, x + r, y);
55        builder.curve_to(x + r, y + c, x + c, y + r, x, y + r);
56        builder.curve_to(x - c, y + r, x - r, y + c, x - r, y);
57        builder.curve_to(x - r, y - c, x - c, y - r, x, y - r);
58        builder.close();
59        builder.finish()
60    }
61
62    /// Creates an image out of the image data provided. The data represents the
63    /// image in its original file format. It needs to be parsed in order to be
64    /// visualized. The parsed image as well as the aspect ratio (width /
65    /// height) are returned in case the image was parsed successfully.
66    fn create_image(&mut self, data: &[u8]) -> Option<(Self::Image, f32)>;
67
68    /// Creates a font from the font description provided. It is expected that
69    /// the the font description is used in a font matching algorithm to find
70    /// the most suitable font as described in [CSS Fonts Module Level
71    /// 3](https://drafts.csswg.org/css-fonts-3/#font-matching-algorithm). The
72    /// [`FontKind`] is used to provide additional information about the kind of
73    /// font we are looking for. If the font is [`None`] or the font can't be
74    /// found, then the default font to use is derived from the [`FontKind`].
75    /// Also the timer and times fonts are meant to be monospaced fonts, so
76    /// `tabular-nums` are supposed to be used if available and ideally some
77    /// sort of emulation should happen if that's not the case. The default text
78    /// and times font is provided as [`TEXT_FONT`](super::super::TEXT_FONT) and
79    /// the timer's default font is provided as
80    /// [`TIMER_FONT`](super::super::TIMER_FONT).
81    fn create_font(&mut self, font: Option<&Font>, kind: FontKind) -> Self::Font;
82
83    /// Creates a new text label with the text and font provided. An optional
84    /// maximum width is provided as well. If the width of the text measured at
85    /// a size of 1 (spanning from the descender to the ascender) is greater
86    /// than the maximum width, then it is expected to be truncated with an
87    /// ellipsis such that it fits within the maximum width.
88    fn create_label(
89        &mut self,
90        text: &str,
91        font: &mut Self::Font,
92        max_width: Option<f32>,
93    ) -> Self::Label;
94
95    /// Updates an existing text label with the new text and font provided. An
96    /// optional maximum width is provided as well. If the width of the text
97    /// measured at a size of 1 (spanning from the descender to the ascender) is
98    /// greater than the maximum width, then it is expected to be truncated with
99    /// an ellipsis such that it fits within the maximum width.
100    fn update_label(
101        &mut self,
102        label: &mut Self::Label,
103        text: &str,
104        font: &mut Self::Font,
105        max_width: Option<f32>,
106    );
107}
108
109/// A text label created by a [`ResourceAllocator`].
110pub trait Label: SharedOwnership {
111    /// The width of the current text scaled by the scale factor provided. The
112    /// scale is meant to be the distance from the descender to the ascender.
113    fn width(&self, scale: f32) -> f32;
114
115    /// The width of the current text scaled by the scale factor provided as if
116    /// it wasn't truncated by the maximum width that was provided. The scale is
117    /// meant to be the distance from the descender to the ascender.
118    fn width_without_max_width(&self, scale: f32) -> f32;
119}
120
121/// The [`ResourceAllocator`] provides a path builder that defines how to build
122/// paths that can be used with the renderer.
123pub trait PathBuilder {
124    /// The type of the path to build. This needs to be identical to the type of
125    /// the path used by the [`ResourceAllocator`].
126    type Path: SharedOwnership;
127
128    /// Moves the cursor to a specific position and starts a new contour.
129    fn move_to(&mut self, x: f32, y: f32);
130
131    /// Adds a line from the previous position to the position specified, while
132    /// also moving the cursor along.
133    fn line_to(&mut self, x: f32, y: f32);
134
135    /// Adds a quadratic bézier curve from the previous position to the position
136    /// specified, while also moving the cursor along. (x1, y1) specifies the
137    /// control point.
138    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32);
139
140    /// Adds a cubic bézier curve from the previous position to the position
141    /// specified, while also moving the cursor along. (x1, y1) and (x2, y2)
142    /// specify the two control points.
143    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32);
144
145    /// Closes the current contour. The current position and the initial
146    /// position get connected by a line, forming a continuous loop. Nothing
147    /// if the path is empty or already closed.
148    fn close(&mut self);
149
150    /// Finishes building the path.
151    fn finish(self) -> Self::Path;
152}
153
154pub struct MutPathBuilder<PB>(PB);
155
156impl<PB: PathBuilder> PathBuilder for MutPathBuilder<PB> {
157    type Path = PB::Path;
158
159    fn move_to(&mut self, x: f32, y: f32) {
160        self.0.move_to(x, y)
161    }
162
163    fn line_to(&mut self, x: f32, y: f32) {
164        self.0.line_to(x, y)
165    }
166
167    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
168        self.0.quad_to(x1, y1, x, y)
169    }
170
171    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
172        self.0.curve_to(x1, y1, x2, y2, x, y)
173    }
174
175    fn close(&mut self) {
176        self.0.close()
177    }
178
179    fn finish(self) -> Self::Path {
180        self.0.finish()
181    }
182}
183
184impl<A: ResourceAllocator> ResourceAllocator for &mut A {
185    type PathBuilder = MutPathBuilder<A::PathBuilder>;
186    type Path = A::Path;
187    type Image = A::Image;
188    type Font = A::Font;
189    type Label = A::Label;
190
191    fn path_builder(&mut self) -> Self::PathBuilder {
192        MutPathBuilder((*self).path_builder())
193    }
194
195    fn create_image(&mut self, data: &[u8]) -> Option<(Self::Image, f32)> {
196        (*self).create_image(data)
197    }
198
199    fn create_font(&mut self, font: Option<&Font>, kind: FontKind) -> Self::Font {
200        (*self).create_font(font, kind)
201    }
202
203    fn create_label(
204        &mut self,
205        text: &str,
206        font: &mut Self::Font,
207        max_width: Option<f32>,
208    ) -> Self::Label {
209        (*self).create_label(text, font, max_width)
210    }
211
212    fn update_label(
213        &mut self,
214        label: &mut Self::Label,
215        text: &str,
216        font: &mut Self::Font,
217        max_width: Option<f32>,
218    ) {
219        (*self).update_label(label, text, font, max_width)
220    }
221}