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}