Skip to main content

simple_render/ui/render/
mod.rs

1use super::*;
2
3mod geometry;
4mod image;
5mod text;
6
7pub(super) use geometry::*;
8use image::*;
9use text::*;
10
11const RETAINED_TEXT_RUN_CAPACITY: usize = 256;
12const RETAINED_GLYPH_CAPACITY: usize = 4096;
13
14type GlyphPaint = (PhysicalGlyph, usize, CosmicColor);
15
16#[derive(Clone, Copy)]
17struct ByteRun {
18    start: usize,
19    end: usize,
20    is_emoji: bool,
21}
22
23#[derive(Clone)]
24struct TextBufferRun {
25    source: Arc<str>,
26    byte_run: ByteRun,
27    style: TextStyle,
28    emoji_family: Option<String>,
29}
30
31impl TextBufferRun {
32    fn text(&self) -> &str {
33        &self.source[self.byte_run.start..self.byte_run.end]
34    }
35
36    fn is_emoji(&self) -> bool {
37        self.byte_run.is_emoji
38    }
39}
40
41pub struct FontCtx {
42    font_system: FontSystem,
43    swash_cache: SwashCache,
44    text_buffer: Buffer,
45    text_runs: Vec<TextBufferRun>,
46    glyphs: Vec<GlyphPaint>,
47}
48
49#[derive(Clone, Debug, Default)]
50pub struct FontCtxOptions {
51    sources: Option<Vec<FontSource>>,
52}
53
54impl FontCtxOptions {
55    pub fn system() -> Self {
56        Self { sources: None }
57    }
58
59    pub fn from_font_sources(fonts: impl IntoIterator<Item = FontSource>) -> Self {
60        Self {
61            sources: Some(fonts.into_iter().collect()),
62        }
63    }
64
65    fn create(&self) -> FontCtx {
66        match &self.sources {
67            Some(sources) => FontCtx::new_with_font_sources(sources.iter().cloned()),
68            None => FontCtx::new(),
69        }
70    }
71}
72
73pub struct LazyFontCtx {
74    options: FontCtxOptions,
75    fonts: Option<FontCtx>,
76}
77
78impl LazyFontCtx {
79    pub fn new() -> Self {
80        Self::with_options(FontCtxOptions::system())
81    }
82
83    pub fn with_options(options: FontCtxOptions) -> Self {
84        Self {
85            options,
86            fonts: None,
87        }
88    }
89
90    pub fn is_loaded(&self) -> bool {
91        self.fonts.is_some()
92    }
93
94    pub fn get(&mut self) -> &mut FontCtx {
95        if self.fonts.is_none() {
96            self.fonts = Some(self.options.create());
97        }
98        self.fonts.as_mut().expect("font context was just created")
99    }
100
101    pub fn clear_raster_cache(&mut self) {
102        if let Some(fonts) = &mut self.fonts {
103            fonts.clear_raster_cache();
104        }
105    }
106
107    pub fn trim_scratch(&mut self) {
108        if let Some(fonts) = &mut self.fonts {
109            fonts.trim_scratch();
110        }
111    }
112
113    pub fn trim_frame_memory(&mut self) {
114        self.clear_raster_cache();
115        self.trim_scratch();
116    }
117
118    pub fn release(&mut self) {
119        self.fonts = None;
120        crate::memory::trim_free_heap_pages();
121    }
122}
123
124impl Default for LazyFontCtx {
125    fn default() -> Self {
126        Self::new()
127    }
128}
129
130impl FontCtx {
131    pub fn new() -> Self {
132        Self::from_font_system(FontSystem::new())
133    }
134
135    pub fn new_with_font_sources(fonts: impl IntoIterator<Item = FontSource>) -> Self {
136        let mut db = fontdb::Database::new();
137        for source in fonts {
138            db.load_font_source(source);
139        }
140        let default_family = db
141            .faces()
142            .find_map(|face| face.families.first().map(|(family, _)| family.clone()));
143        if let Some(family) = default_family {
144            db.set_sans_serif_family(family.clone());
145            db.set_serif_family(family.clone());
146            db.set_monospace_family(family);
147        }
148
149        Self::from_font_system(FontSystem::new_with_locale_and_db("en-US".into(), db))
150    }
151
152    fn from_font_system(font_system: FontSystem) -> Self {
153        Self {
154            font_system,
155            swash_cache: SwashCache::new(),
156            text_buffer: Buffer::new_empty(Metrics::new(1.0, 1.3)),
157            text_runs: Vec::new(),
158            glyphs: Vec::new(),
159        }
160    }
161
162    pub fn clear_raster_cache(&mut self) {
163        self.swash_cache = SwashCache::new();
164    }
165
166    pub fn trim_scratch(&mut self) {
167        self.text_buffer = Buffer::new_empty(Metrics::new(1.0, 1.3));
168        trim_vec_capacity(&mut self.text_runs, RETAINED_TEXT_RUN_CAPACITY);
169        trim_vec_capacity(&mut self.glyphs, RETAINED_GLYPH_CAPACITY);
170    }
171}
172
173impl Default for FontCtx {
174    fn default() -> Self {
175        Self::new()
176    }
177}
178
179fn trim_vec_capacity<T>(values: &mut Vec<T>, retained_capacity: usize) {
180    if values.capacity() > retained_capacity {
181        values.clear();
182        values.shrink_to(retained_capacity);
183    }
184}
185
186mod paint;
187mod rect;
188
189use paint::*;
190pub use rect::{Rect, Ui};
191
192struct UiRenderer {
193    root: Rect,
194    fonts: LazyFontCtx,
195}
196
197impl Renderer for UiRenderer {
198    fn draw(&mut self, canvas: &mut Canvas<'_>, context: RenderContext) -> FrameAction {
199        canvas.clear(Color::TRANSPARENT.into());
200        let fonts = self.fonts.get();
201        self.root.paint_scaled_with_fonts(
202            canvas,
203            fonts,
204            context.width,
205            context.height,
206            context.scale,
207        );
208        self.fonts.trim_frame_memory();
209        FrameAction::Wait
210    }
211
212    fn closed_surface(&mut self, _: SurfaceId) {
213        self.fonts.release();
214    }
215}
216
217fn measure_element(
218    element: &Rect,
219    fonts: &mut FontCtx,
220    available_width: u32,
221    available_height: u32,
222) -> Size {
223    let content_available =
224        Bounds::new(0, 0, available_width, available_height).inset(element.padding);
225    let content_size = match &element.content {
226        Some(Content::Text(text)) => {
227            measure_text(fonts, TextContent::Plain(text), content_available.width)
228        }
229        Some(Content::RichText(text)) => {
230            measure_text(fonts, TextContent::Rich(text), content_available.width)
231        }
232        Some(Content::Image(image)) => {
233            measure_image(image, content_available.width, content_available.height)
234        }
235        None => Size::default(),
236    };
237    let child_size = measure_children(
238        element,
239        fonts,
240        content_available.width,
241        content_available.height,
242    );
243    let measured = Size::new(
244        content_size.width.max(child_size.width),
245        content_size.height.max(child_size.height),
246    );
247    Size::new(
248        constrain_dimension(
249            measured
250                .width
251                .saturating_add(element.padding.left)
252                .saturating_add(element.padding.right)
253                .min(available_width),
254            element.min_width,
255            element.max_width,
256        ),
257        constrain_dimension(
258            measured
259                .height
260                .saturating_add(element.padding.top)
261                .saturating_add(element.padding.bottom)
262                .min(available_height),
263            element.min_height,
264            element.max_height,
265        ),
266    )
267}
268
269fn measure_children(
270    parent: &Rect,
271    fonts: &mut FontCtx,
272    available_width: u32,
273    available_height: u32,
274) -> Size {
275    if parent
276        .children
277        .iter()
278        .all(|child| child.position != Position::Flow)
279    {
280        return Size::default();
281    }
282
283    let direction = parent.direction;
284    let axis_available = match direction {
285        Direction::Row => available_width,
286        Direction::Column => available_height,
287    };
288    let cross_available = match direction {
289        Direction::Row => available_height,
290        Direction::Column => available_width,
291    };
292    let flow_count = parent
293        .children
294        .iter()
295        .filter(|child| child.position == Position::Flow)
296        .count();
297    let gap_total = parent
298        .gap
299        .saturating_mul(flow_count.saturating_sub(1) as u32);
300    let axis_available_without_gaps = axis_available.saturating_sub(gap_total);
301    let mut axis_used = 0_u32;
302    let mut cross_used = 0_u32;
303
304    for child in parent
305        .children
306        .iter()
307        .filter(|child| child.position == Position::Flow)
308    {
309        let measured = measure_element(child, fonts, available_width, available_height);
310        let axis = match axis_length(child, direction) {
311            Length::Fill => constrain_axis(measured.axis(direction), child, direction),
312            _ => resolve_axis_length(
313                child,
314                direction,
315                axis_available_without_gaps,
316                measured.axis(direction),
317            ),
318        };
319        let cross = match cross_length(child, direction) {
320            Length::Fill => constrain_cross(measured.cross(direction), child, direction),
321            _ => resolve_cross_length(child, direction, cross_available, measured.cross(direction)),
322        };
323        axis_used = axis_used.saturating_add(axis);
324        cross_used = cross_used.max(cross);
325    }
326
327    axis_used = axis_used.saturating_add(gap_total).min(axis_available);
328    match direction {
329        Direction::Row => Size::new(axis_used, cross_used.min(cross_available)),
330        Direction::Column => Size::new(cross_used.min(cross_available), axis_used),
331    }
332}
333
334fn layout_children(
335    parent: &Rect,
336    content: Bounds,
337    fonts: &mut FontCtx,
338    mut layout_child: impl FnMut(usize, &Rect, Bounds, Size, &mut FontCtx),
339) {
340    let count = parent
341        .children
342        .iter()
343        .filter(|child| child.position == Position::Flow)
344        .count();
345    if count == 0 {
346        layout_absolute_children(parent, content, fonts, layout_child);
347        return;
348    }
349
350    let gap_total = parent.gap.saturating_mul(count.saturating_sub(1) as u32);
351    let axis_total = match parent.direction {
352        Direction::Row => content.width,
353        Direction::Column => content.height,
354    };
355    let axis_available = axis_total.saturating_sub(gap_total);
356    let cross_available = match parent.direction {
357        Direction::Row => content.height,
358        Direction::Column => content.width,
359    };
360
361    let mut fixed_axis = 0_u32;
362    let mut fill_weight = 0_u32;
363    for child in parent
364        .children
365        .iter()
366        .filter(|child| child.position == Position::Flow)
367    {
368        match axis_length(child, parent.direction) {
369            Length::Fill => {
370                fill_weight = fill_weight.saturating_add(child.fill.max(1));
371            }
372            _ => {
373                let measured = measure_element(child, fonts, content.width, content.height);
374                let axis = resolve_axis_length(
375                    child,
376                    parent.direction,
377                    axis_available,
378                    measured.axis(parent.direction),
379                );
380                fixed_axis = fixed_axis.saturating_add(axis);
381            }
382        }
383    }
384
385    let remaining = axis_available.saturating_sub(fixed_axis);
386    let mut distributed = 0_u32;
387    let mut remaining_weight = fill_weight;
388    let mut used_axis = gap_total;
389    for child in parent
390        .children
391        .iter()
392        .filter(|child| child.position == Position::Flow)
393    {
394        let axis = match axis_length(child, parent.direction) {
395            Length::Fill => {
396                let weight = child.fill.max(1);
397                let share = if remaining_weight <= weight {
398                    remaining.saturating_sub(distributed)
399                } else {
400                    remaining.saturating_sub(distributed) * weight / remaining_weight
401                };
402                remaining_weight = remaining_weight.saturating_sub(weight);
403                distributed = distributed.saturating_add(share);
404                constrain_axis(share, child, parent.direction)
405            }
406            _ => {
407                let measured = measure_element(child, fonts, content.width, content.height);
408                resolve_axis_length(
409                    child,
410                    parent.direction,
411                    axis_available,
412                    measured.axis(parent.direction),
413                )
414            }
415        };
416        used_axis = used_axis.saturating_add(axis);
417    }
418
419    let start_offset = align_offset(parent.justify, axis_total, used_axis);
420    let mut cursor = match parent.direction {
421        Direction::Row => content.x.saturating_add(start_offset),
422        Direction::Column => content.y.saturating_add(start_offset),
423    };
424
425    let mut distributed = 0_u32;
426    let mut remaining_weight = fill_weight;
427    for (index, child) in parent
428        .children
429        .iter()
430        .enumerate()
431        .filter(|(_, child)| child.position == Position::Flow)
432    {
433        let measured = measure_element(child, fonts, content.width, content.height);
434        let axis = match axis_length(child, parent.direction) {
435            Length::Fill => {
436                let weight = child.fill.max(1);
437                let share = if remaining_weight <= weight {
438                    remaining.saturating_sub(distributed)
439                } else {
440                    remaining.saturating_sub(distributed) * weight / remaining_weight
441                };
442                remaining_weight = remaining_weight.saturating_sub(weight);
443                distributed = distributed.saturating_add(share);
444                constrain_axis(share, child, parent.direction)
445            }
446            _ => resolve_axis_length(
447                child,
448                parent.direction,
449                axis_available,
450                measured.axis(parent.direction),
451            ),
452        };
453        if axis == 0 {
454            continue;
455        };
456        let cross = match cross_length(child, parent.direction) {
457            Length::Fill => cross_available,
458            Length::Fit if parent.align == Align::Stretch => cross_available,
459            _ => resolve_cross_length(
460                child,
461                parent.direction,
462                cross_available,
463                measured.cross(parent.direction),
464            ),
465        };
466        let cross_offset = align_offset(parent.align, cross_available, cross);
467        let rect = match parent.direction {
468            Direction::Row => Bounds {
469                x: cursor,
470                y: content.y.saturating_add(cross_offset),
471                width: axis,
472                height: cross,
473            },
474            Direction::Column => Bounds {
475                x: content.x.saturating_add(cross_offset),
476                y: cursor,
477                width: cross,
478                height: axis,
479            },
480        };
481        cursor = cursor.saturating_add(axis).saturating_add(parent.gap);
482        layout_child(index, child, rect, measured, fonts);
483    }
484
485    layout_absolute_children(parent, content, fonts, layout_child);
486}
487
488fn layout_absolute_children(
489    parent: &Rect,
490    content: Bounds,
491    fonts: &mut FontCtx,
492    mut layout_child: impl FnMut(usize, &Rect, Bounds, Size, &mut FontCtx),
493) {
494    for (index, child) in parent
495        .children
496        .iter()
497        .enumerate()
498        .filter(|(_, child)| child.position == Position::Absolute)
499    {
500        let (rect, measured) = absolute_child_rect(child, content, fonts);
501        if rect.width == 0 || rect.height == 0 {
502            continue;
503        }
504        layout_child(index, child, rect, measured, fonts);
505    }
506}
507
508fn absolute_child_rect(child: &Rect, content: Bounds, fonts: &mut FontCtx) -> (Bounds, Size) {
509    let left = child.inset.left.unwrap_or(0);
510    let right = child.inset.right.unwrap_or(0);
511    let top = child.inset.top.unwrap_or(0);
512    let bottom = child.inset.bottom.unwrap_or(0);
513    let available_width = content.width.saturating_sub(left).saturating_sub(right);
514    let available_height = content.height.saturating_sub(top).saturating_sub(bottom);
515    let measured = measure_element(child, fonts, available_width, available_height);
516    let width = if child.inset.left.is_some()
517        && child.inset.right.is_some()
518        && matches!(child.width, Length::Fill)
519    {
520        constrain_dimension(available_width, child.min_width, child.max_width)
521    } else {
522        resolve_length(
523            child.width,
524            available_width,
525            measured.width,
526            child.min_width,
527            child.max_width,
528        )
529    };
530    let height = if child.inset.top.is_some()
531        && child.inset.bottom.is_some()
532        && matches!(child.height, Length::Fill)
533    {
534        constrain_dimension(available_height, child.min_height, child.max_height)
535    } else {
536        resolve_length(
537            child.height,
538            available_height,
539            measured.height,
540            child.min_height,
541            child.max_height,
542        )
543    };
544    let x = match (child.inset.left, child.inset.right) {
545        (Some(left), _) => content.x.saturating_add(left),
546        (None, Some(right)) => content.right().saturating_sub(right).saturating_sub(width),
547        (None, None) => content.x,
548    };
549    let y = match (child.inset.top, child.inset.bottom) {
550        (Some(top), _) => content.y.saturating_add(top),
551        (None, Some(bottom)) => content
552            .bottom()
553            .saturating_sub(bottom)
554            .saturating_sub(height),
555        (None, None) => content.y,
556    };
557
558    (
559        Bounds {
560            x,
561            y,
562            width,
563            height,
564        },
565        measured,
566    )
567}
568
569fn axis_length(element: &Rect, direction: Direction) -> Length {
570    match direction {
571        Direction::Row => element.width,
572        Direction::Column => element.height,
573    }
574}
575
576fn cross_length(element: &Rect, direction: Direction) -> Length {
577    match direction {
578        Direction::Row => element.height,
579        Direction::Column => element.width,
580    }
581}
582
583fn resolve_axis_length(element: &Rect, direction: Direction, available: u32, fit: u32) -> u32 {
584    let (min, max) = match direction {
585        Direction::Row => (element.min_width, element.max_width),
586        Direction::Column => (element.min_height, element.max_height),
587    };
588    resolve_length(axis_length(element, direction), available, fit, min, max)
589}
590
591fn resolve_cross_length(element: &Rect, direction: Direction, available: u32, fit: u32) -> u32 {
592    let (min, max) = match direction {
593        Direction::Row => (element.min_height, element.max_height),
594        Direction::Column => (element.min_width, element.max_width),
595    };
596    resolve_length(cross_length(element, direction), available, fit, min, max)
597}
598
599fn constrain_axis(size: u32, element: &Rect, direction: Direction) -> u32 {
600    let (min, max) = match direction {
601        Direction::Row => (element.min_width, element.max_width),
602        Direction::Column => (element.min_height, element.max_height),
603    };
604    constrain_dimension(size, min, max)
605}
606
607fn constrain_cross(size: u32, element: &Rect, direction: Direction) -> u32 {
608    let (min, max) = match direction {
609        Direction::Row => (element.min_height, element.max_height),
610        Direction::Column => (element.min_width, element.max_width),
611    };
612    constrain_dimension(size, min, max)
613}
614
615fn resolve_length(length: Length, available: u32, fit: u32, min: u32, max: Option<u32>) -> u32 {
616    let resolved = match length {
617        Length::Fit => fit,
618        Length::Fill => available,
619        Length::Px(value) => value.min(available),
620        Length::Percent(percent) => {
621            ((available as f32 * percent.clamp(0.0, 100.0) / 100.0).round() as u32).min(available)
622        }
623    };
624    constrain_dimension(resolved, min, max)
625}
626
627fn constrain_dimension(size: u32, min: u32, max: Option<u32>) -> u32 {
628    let max = max.unwrap_or(u32::MAX).max(min);
629    size.max(min).min(max)
630}