1mod component;
74mod consts;
75mod entity;
76mod font;
77mod icon;
78mod resource;
79mod scene;
80
81#[cfg(feature = "path-based-text-engine")]
82pub mod path_based_text_engine;
83
84#[cfg(feature = "software-rendering")]
85pub mod software;
86
87use self::{
88 consts::{
89 DEFAULT_TEXT_SIZE, DEFAULT_VERTICAL_WIDTH, PADDING, TEXT_ALIGN_BOTTOM, TEXT_ALIGN_TOP,
90 TWO_ROW_HEIGHT,
91 },
92 font::{AbbreviatedLabel, CachedLabel, FontCache},
93 icon::Icon,
94 resource::Handles,
95};
96use crate::{
97 layout::{LayoutDirection, LayoutState},
98 platform::prelude::*,
99 settings::{Color, Gradient},
100};
101use alloc::borrow::Cow;
102use bytemuck::{Pod, Zeroable};
103use core::iter;
104
105pub use self::{
106 entity::Entity,
107 font::{TEXT_FONT, TIMER_FONT},
108 resource::{
109 FontKind, Handle, Label, LabelHandle, PathBuilder, ResourceAllocator, SharedOwnership,
110 },
111 scene::{Layer, Scene},
112};
113
114pub type Pos = [f32; 2];
116pub type Rgba = [f32; 4];
119
120#[derive(Copy, Clone, Pod, Zeroable)]
123#[repr(C)]
124pub struct Transform {
125 pub scale_x: f32,
127 pub scale_y: f32,
129 pub x: f32,
131 pub y: f32,
133}
134
135#[derive(Copy, Clone, PartialEq)]
137pub enum FillShader {
138 SolidColor(Rgba),
140 VerticalGradient(Rgba, Rgba),
142 HorizontalGradient(Rgba, Rgba),
144}
145
146enum CachedSize {
147 Vertical(f32),
148 Horizontal(f32),
149}
150
151pub struct SceneManager<P, I, F, L> {
162 scene: Scene<P, I, L>,
163 components: Vec<component::Cache<I, L>>,
164 next_id: usize,
165 cached_size: Option<CachedSize>,
166 fonts: FontCache<F>,
167}
168
169impl<P: SharedOwnership, I: SharedOwnership, F, L: SharedOwnership> SceneManager<P, I, F, L> {
170 pub fn new(
172 mut allocator: impl ResourceAllocator<Path = P, Image = I, Font = F, Label = L>,
173 ) -> Self {
174 let mut builder = allocator.path_builder();
175 builder.move_to(0.0, 0.0);
176 builder.line_to(0.0, 1.0);
177 builder.line_to(1.0, 1.0);
178 builder.line_to(1.0, 0.0);
179 builder.close();
180 let rectangle = Handle::new(0, builder.finish());
181
182 let mut handles = Handles::new(1, allocator);
183 let fonts = FontCache::new(&mut handles);
184
185 Self {
186 components: Vec::new(),
187 next_id: handles.into_next_id(),
188 scene: Scene::new(rectangle),
189 cached_size: None,
190 fonts,
191 }
192 }
193
194 pub const fn scene(&self) -> &Scene<P, I, L> {
196 &self.scene
197 }
198
199 pub fn update_scene<A: ResourceAllocator<Path = P, Image = I, Font = F, Label = L>>(
208 &mut self,
209 allocator: A,
210 resolution: (f32, f32),
211 state: &LayoutState,
212 ) -> Option<(f32, f32)> {
213 self.scene.clear();
214
215 self.scene
216 .set_background(decode_gradient(&state.background));
217
218 if let Some(new_components) = state.components.get(self.components.len()..) {
220 self.components
221 .extend(new_components.iter().map(component::Cache::new));
222 } else {
223 self.components.truncate(state.components.len());
224 }
225
226 let new_dimensions = match state.direction {
227 LayoutDirection::Vertical => self.render_vertical(allocator, resolution, state),
228 LayoutDirection::Horizontal => self.render_horizontal(allocator, resolution, state),
229 };
230
231 self.scene.recalculate_if_bottom_layer_changed();
232
233 new_dimensions
234 }
235
236 fn render_vertical(
237 &mut self,
238 allocator: impl ResourceAllocator<Path = P, Image = I, Font = F, Label = L>,
239 resolution: (f32, f32),
240 state: &LayoutState,
241 ) -> Option<(f32, f32)> {
242 let total_height = component::layout_height(state);
243
244 let cached_total_size = self
245 .cached_size
246 .get_or_insert(CachedSize::Vertical(total_height));
247 let mut new_resolution = None;
248
249 match cached_total_size {
250 CachedSize::Vertical(cached_total_height) => {
251 if cached_total_height.to_bits() != total_height.to_bits() {
252 new_resolution = Some((
253 resolution.0,
254 resolution.1 / *cached_total_height * total_height,
255 ));
256 *cached_total_height = total_height;
257 }
258 }
259 CachedSize::Horizontal(_) => {
260 let to_pixels = resolution.1 / TWO_ROW_HEIGHT;
261 let new_height = total_height * to_pixels;
262 let new_width = DEFAULT_VERTICAL_WIDTH * to_pixels;
263 new_resolution = Some((new_width, new_height));
264 *cached_total_size = CachedSize::Vertical(total_height);
265 }
266 }
267
268 let aspect_ratio = resolution.0 / resolution.1;
269
270 let mut context = RenderContext {
271 handles: Handles::new(self.next_id, allocator),
272 transform: Transform::scale(resolution.0, resolution.1),
273 scene: &mut self.scene,
274 fonts: &mut self.fonts,
275 };
276
277 context.fonts.maybe_reload(&mut context.handles, state);
278
279 context.scale_non_uniform_x(aspect_ratio.recip());
282
283 context.scale(total_height.recip());
287
288 let width = aspect_ratio * total_height;
291
292 for (component, cache) in state.components.iter().zip(&mut self.components) {
293 let height = component::height(component);
294 let dim = [width, height];
295 component::render(cache, &mut context, component, state, dim);
296 context.translate(0.0, height);
300 }
301
302 self.next_id = context.handles.into_next_id();
303
304 new_resolution
305 }
306
307 fn render_horizontal(
308 &mut self,
309 allocator: impl ResourceAllocator<Path = P, Image = I, Font = F, Label = L>,
310 resolution: (f32, f32),
311 state: &LayoutState,
312 ) -> Option<(f32, f32)> {
313 let total_width = component::layout_width(state);
314
315 let cached_total_size = self
316 .cached_size
317 .get_or_insert(CachedSize::Horizontal(total_width));
318 let mut new_resolution = None;
319
320 match cached_total_size {
321 CachedSize::Vertical(cached_total_height) => {
322 let new_height = resolution.1 * TWO_ROW_HEIGHT / *cached_total_height;
323 let new_width = total_width * new_height / TWO_ROW_HEIGHT;
324 new_resolution = Some((new_width, new_height));
325 *cached_total_size = CachedSize::Horizontal(total_width);
326 }
327 CachedSize::Horizontal(cached_total_width) => {
328 if cached_total_width.to_bits() != total_width.to_bits() {
329 new_resolution = Some((
330 resolution.0 / *cached_total_width * total_width,
331 resolution.1,
332 ));
333 *cached_total_width = total_width;
334 }
335 }
336 }
337
338 let aspect_ratio = resolution.0 / resolution.1;
339
340 let mut context = RenderContext {
341 handles: Handles::new(self.next_id, allocator),
342 transform: Transform::scale(resolution.0, resolution.1),
343 scene: &mut self.scene,
344 fonts: &mut self.fonts,
345 };
346
347 context.fonts.maybe_reload(&mut context.handles, state);
348
349 context.scale_non_uniform_x(aspect_ratio.recip());
352
353 context.scale(TWO_ROW_HEIGHT.recip());
359
360 let width_scaling = TWO_ROW_HEIGHT * aspect_ratio / total_width;
364
365 for (component, cache) in state.components.iter().zip(&mut self.components) {
366 let width = component::width(component) * width_scaling;
367 let height = TWO_ROW_HEIGHT;
368 let dim = [width, height];
369 component::render(cache, &mut context, component, state, dim);
370 context.translate(width, 0.0);
374 }
375
376 self.next_id = context.handles.into_next_id();
377
378 new_resolution
379 }
380}
381
382struct RenderContext<'b, A: ResourceAllocator> {
383 transform: Transform,
384 handles: Handles<A>,
385 scene: &'b mut Scene<A::Path, A::Image, A::Label>,
386 fonts: &'b mut FontCache<A::Font>,
387}
388
389impl<A: ResourceAllocator> RenderContext<'_, A> {
390 fn rectangle(&self) -> Handle<A::Path> {
391 self.scene.rectangle()
392 }
393
394 fn render_background(&mut self, [w, h]: Pos, gradient: &Gradient) {
395 if let Some(shader) = decode_gradient(gradient) {
396 let rectangle = self.rectangle();
397 self.scene.bottom_layer_mut().push(Entity::FillPath(
398 rectangle,
399 shader,
400 self.transform.pre_scale(w, h),
401 ));
402 }
403 }
404
405 fn backend_render_rectangle(&mut self, [x1, y1]: Pos, [x2, y2]: Pos, shader: FillShader) {
406 let transform = self
407 .transform
408 .pre_translate(x1, y1)
409 .pre_scale(x2 - x1, y2 - y1);
410
411 let rectangle = self.rectangle();
412
413 self.scene
414 .bottom_layer_mut()
415 .push(Entity::FillPath(rectangle, shader, transform));
416 }
417
418 fn backend_render_top_rectangle(&mut self, [x1, y1]: Pos, [x2, y2]: Pos, shader: FillShader) {
419 let transform = self
420 .transform
421 .pre_translate(x1, y1)
422 .pre_scale(x2 - x1, y2 - y1);
423
424 let rectangle = self.rectangle();
425
426 self.scene
427 .top_layer_mut()
428 .push(Entity::FillPath(rectangle, shader, transform));
429 }
430
431 fn top_layer_path(&mut self, path: Handle<A::Path>, color: Color) {
432 self.scene
433 .top_layer_mut()
434 .push(Entity::FillPath(path, solid(&color), self.transform));
435 }
436
437 fn top_layer_stroke_path(&mut self, path: Handle<A::Path>, color: Color, stroke_width: f32) {
438 self.scene.top_layer_mut().push(Entity::StrokePath(
439 path,
440 stroke_width,
441 color.to_array(),
442 self.transform,
443 ));
444 }
445
446 fn create_icon(&mut self, image_data: &[u8]) -> Option<Icon<A::Image>> {
447 let (image, aspect_ratio) = self.handles.create_image(image_data)?;
448 Some(Icon {
449 aspect_ratio,
450 image,
451 })
452 }
453
454 fn scale(&mut self, factor: f32) {
455 self.transform = self.transform.pre_scale(factor, factor);
456 }
457
458 fn scale_non_uniform_x(&mut self, x: f32) {
459 self.transform = self.transform.pre_scale(x, 1.0);
460 }
461
462 fn translate(&mut self, x: f32, y: f32) {
463 self.transform = self.transform.pre_translate(x, y);
464 }
465
466 fn render_rectangle(&mut self, top_left: Pos, bottom_right: Pos, gradient: &Gradient) {
467 if let Some(colors) = decode_gradient(gradient) {
468 self.backend_render_rectangle(top_left, bottom_right, colors);
469 }
470 }
471
472 fn render_top_rectangle(&mut self, top_left: Pos, bottom_right: Pos, gradient: &Gradient) {
473 if let Some(colors) = decode_gradient(gradient) {
474 self.backend_render_top_rectangle(top_left, bottom_right, colors);
475 }
476 }
477
478 fn render_icon(
479 &mut self,
480 [mut x, mut y]: Pos,
481 [mut width, mut height]: Pos,
482 icon: &Icon<A::Image>,
483 ) {
484 let box_aspect_ratio = width / height;
485 let aspect_ratio_diff = box_aspect_ratio / icon.aspect_ratio;
486
487 if aspect_ratio_diff > 1.0 {
488 let new_width = width / aspect_ratio_diff;
489 let diff_width = width - new_width;
490 x += 0.5 * diff_width;
491 width = new_width;
492 } else if aspect_ratio_diff < 1.0 {
493 let new_height = height * aspect_ratio_diff;
494 let diff_height = height - new_height;
495 y += 0.5 * diff_height;
496 height = new_height;
497 }
498
499 let transform = self.transform.pre_translate(x, y).pre_scale(width, height);
500
501 self.scene
502 .bottom_layer_mut()
503 .push(Entity::Image(icon.image.share(), transform));
504 }
505
506 fn render_key_value_component(
507 &mut self,
508 key: &str,
509 abbreviations: &[Cow<'_, str>],
510 key_label: &mut AbbreviatedLabel<A::Label>,
511 value: &str,
512 value_label: &mut CachedLabel<A::Label>,
513 updates_frequently: bool,
514 [width, height]: [f32; 2],
515 key_color: Color,
516 value_color: Color,
517 display_two_rows: bool,
518 ) {
519 let left_of_value_x = self.render_numbers(
520 value,
521 value_label,
522 Layer::from_updates_frequently(updates_frequently),
523 [width - PADDING, height + TEXT_ALIGN_BOTTOM],
524 DEFAULT_TEXT_SIZE,
525 solid(&value_color),
526 );
527 let end_x = if display_two_rows {
528 width
529 } else {
530 left_of_value_x
531 };
532
533 self.render_abbreviated_text_ellipsis(
534 iter::once(key).chain(abbreviations.iter().map(|x| &**x)),
535 key_label,
536 [PADDING, TEXT_ALIGN_TOP],
537 DEFAULT_TEXT_SIZE,
538 solid(&key_color),
539 end_x - PADDING,
540 );
541 }
542
543 fn render_abbreviated_text_ellipsis<'a>(
544 &mut self,
545 abbreviations: impl IntoIterator<Item = &'a str> + Clone,
546 label: &mut AbbreviatedLabel<A::Label>,
547 pos @ [x, _]: Pos,
548 scale: f32,
549 shader: FillShader,
550 max_x: f32,
551 ) -> f32 {
552 let label = label.update(
553 abbreviations,
554 &mut self.handles,
555 &mut self.fonts.text.font,
556 (max_x - x) / scale,
557 );
558
559 self.scene.bottom_layer_mut().push(Entity::Label(
560 label.share(),
561 shader,
562 font::left_aligned(&self.transform, pos, scale),
563 ));
564
565 x + label.width(scale)
566 }
567
568 fn render_text_ellipsis(
569 &mut self,
570 text: &str,
571 label: &mut CachedLabel<A::Label>,
572 pos @ [x, _]: Pos,
573 scale: f32,
574 shader: FillShader,
575 max_x: f32,
576 ) -> f32 {
577 let label = label.update(
578 text,
579 &mut self.handles,
580 &mut self.fonts.text.font,
581 Some((max_x - x) / scale),
582 );
583
584 self.scene.bottom_layer_mut().push(Entity::Label(
585 label.share(),
586 shader,
587 font::left_aligned(&self.transform, pos, scale),
588 ));
589
590 x + label.width(scale)
591 }
592
593 fn render_text_centered(
594 &mut self,
595 text: &str,
596 label: &mut CachedLabel<A::Label>,
597 min_x: f32,
598 max_x: f32,
599 pos: Pos,
600 scale: f32,
601 shader: FillShader,
602 ) {
603 let label = label.update(
604 text,
605 &mut self.handles,
606 &mut self.fonts.text.font,
607 Some((max_x - min_x) / scale),
608 );
609
610 self.scene.bottom_layer_mut().push(Entity::Label(
611 label.share(),
612 shader,
613 font::centered(
614 &self.transform,
615 pos,
616 scale,
617 label.width(scale),
618 min_x,
619 max_x,
620 ),
621 ));
622 }
623
624 fn render_abbreviated_text_centered<'a>(
625 &mut self,
626 abbreviations: impl IntoIterator<Item = &'a str> + Clone,
627 label: &mut AbbreviatedLabel<A::Label>,
628 min_x: f32,
629 max_x: f32,
630 pos: Pos,
631 scale: f32,
632 shader: FillShader,
633 ) {
634 let label = label.update(
635 abbreviations,
636 &mut self.handles,
637 &mut self.fonts.text.font,
638 (max_x - min_x) / scale,
639 );
640
641 self.scene.bottom_layer_mut().push(Entity::Label(
642 label.share(),
643 shader,
644 font::centered(
645 &self.transform,
646 pos,
647 scale,
648 label.width(scale),
649 min_x,
650 max_x,
651 ),
652 ));
653 }
654
655 fn render_text_right_align(
656 &mut self,
657 text: &str,
658 label: &mut CachedLabel<A::Label>,
659 layer: Layer,
660 pos @ [x, _]: Pos,
661 scale: f32,
662 shader: FillShader,
663 ) -> f32 {
664 let label = label.update(text, &mut self.handles, &mut self.fonts.text.font, None);
665 let width = label.width(scale);
666
667 self.scene.layer_mut(layer).push(Entity::Label(
668 label.share(),
669 shader,
670 font::right_aligned(&self.transform, pos, scale, width),
671 ));
672
673 x - width
674 }
675
676 fn render_abbreviated_text_align<'a>(
677 &mut self,
678 abbreviations: impl IntoIterator<Item = &'a str> + Clone,
679 label: &mut AbbreviatedLabel<A::Label>,
680 min_x: f32,
681 max_x: f32,
682 pos: Pos,
683 scale: f32,
684 centered: bool,
685 shader: FillShader,
686 ) {
687 if centered {
688 self.render_abbreviated_text_centered(
689 abbreviations,
690 label,
691 min_x,
692 max_x,
693 pos,
694 scale,
695 shader,
696 );
697 } else {
698 self.render_abbreviated_text_ellipsis(abbreviations, label, pos, scale, shader, max_x);
699 }
700 }
701
702 fn render_numbers(
703 &mut self,
704 text: &str,
705 label: &mut CachedLabel<A::Label>,
706 layer: Layer,
707 pos @ [x, _]: Pos,
708 scale: f32,
709 shader: FillShader,
710 ) -> f32 {
711 let label = label.update(text, &mut self.handles, &mut self.fonts.times.font, None);
712 let width = label.width(scale);
713
714 self.scene.layer_mut(layer).push(Entity::Label(
715 label.share(),
716 shader,
717 font::right_aligned(&self.transform, pos, scale, width),
718 ));
719
720 x - width
721 }
722
723 fn render_timer(
724 &mut self,
725 text: &str,
726 label: &mut CachedLabel<A::Label>,
727 layer: Layer,
728 pos @ [x, _]: Pos,
729 scale: f32,
730 shader: FillShader,
731 ) -> f32 {
732 let label = label.update(text, &mut self.handles, &mut self.fonts.timer.font, None);
733 let width = label.width(scale);
734
735 self.scene.layer_mut(layer).push(Entity::Label(
736 label.share(),
737 shader,
738 font::right_aligned(&self.transform, pos, scale, width),
739 ));
740
741 x - width
742 }
743
744 fn measure_numbers(
745 &mut self,
746 text: &str,
747 label: &mut CachedLabel<A::Label>,
748 scale: f32,
749 ) -> f32 {
750 let label = label.update(text, &mut self.handles, &mut self.fonts.times.font, None);
751 label.width(scale)
752 }
753}
754
755const fn decode_gradient(gradient: &Gradient) -> Option<FillShader> {
756 Some(match gradient {
757 Gradient::Transparent => return None,
758 Gradient::Horizontal(left, right) => {
759 FillShader::HorizontalGradient(left.to_array(), right.to_array())
760 }
761 Gradient::Vertical(top, bottom) => {
762 FillShader::VerticalGradient(top.to_array(), bottom.to_array())
763 }
764 Gradient::Plain(plain) => FillShader::SolidColor(plain.to_array()),
765 })
766}
767
768const fn solid(color: &Color) -> FillShader {
769 FillShader::SolidColor(color.to_array())
770}
771
772impl Transform {
773 const fn scale(scale_x: f32, scale_y: f32) -> Transform {
774 Self {
775 x: 0.0,
776 y: 0.0,
777 scale_x,
778 scale_y,
779 }
780 }
781
782 pub fn pre_scale(&self, scale_x: f32, scale_y: f32) -> Transform {
784 Self {
785 scale_x: self.scale_x * scale_x,
786 scale_y: self.scale_y * scale_y,
787 x: self.x,
788 y: self.y,
789 }
790 }
791
792 pub fn pre_translate(&self, x: f32, y: f32) -> Transform {
794 Self {
795 scale_x: self.scale_x,
796 scale_y: self.scale_y,
797 x: self.x + self.scale_x * x,
798 y: self.y + self.scale_y * y,
799 }
800 }
801
802 #[cfg(feature = "software-rendering")]
803 fn transform_y(&self, y: f32) -> f32 {
804 self.y + self.scale_y * y
805 }
806}