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
189pub use paint::PaintTransform;
190use paint::*;
191pub use rect::{Rect, Ui};
192
193struct UiRenderer {
194    root: Rect,
195    fonts: LazyFontCtx,
196}
197
198impl Renderer for UiRenderer {
199    fn draw(&mut self, canvas: &mut Canvas<'_>, context: RenderContext) -> FrameAction {
200        canvas.clear(Color::TRANSPARENT.into());
201        let fonts = self.fonts.get();
202        self.root.paint_scaled_with_fonts(
203            canvas,
204            fonts,
205            context.width,
206            context.height,
207            context.scale,
208        );
209        self.fonts.trim_frame_memory();
210        FrameAction::Wait
211    }
212
213    fn closed_surface(&mut self, _: SurfaceId) {
214        self.fonts.release();
215    }
216}
217
218fn measure_element(
219    element: &Rect,
220    fonts: &mut FontCtx,
221    available_width: u32,
222    available_height: u32,
223) -> Size {
224    let content_available =
225        Bounds::new(0, 0, available_width, available_height).inset(element.padding);
226    let content_size = match &element.content {
227        Some(Content::Text(text)) => {
228            measure_text(fonts, TextContent::Plain(text), content_available.width)
229        }
230        Some(Content::RichText(text)) => {
231            measure_text(fonts, TextContent::Rich(text), content_available.width)
232        }
233        Some(Content::Image(image)) => {
234            measure_image(image, content_available.width, content_available.height)
235        }
236        None => Size::default(),
237    };
238    let child_size = measure_children(
239        element,
240        fonts,
241        content_available.width,
242        content_available.height,
243    );
244    let measured = Size::new(
245        content_size.width.max(child_size.width),
246        content_size.height.max(child_size.height),
247    );
248    Size::new(
249        constrain_dimension(
250            measured
251                .width
252                .saturating_add(element.padding.left)
253                .saturating_add(element.padding.right)
254                .min(available_width),
255            element.min_width,
256            element.max_width,
257        ),
258        constrain_dimension(
259            measured
260                .height
261                .saturating_add(element.padding.top)
262                .saturating_add(element.padding.bottom)
263                .min(available_height),
264            element.min_height,
265            element.max_height,
266        ),
267    )
268}
269
270fn measure_children(
271    parent: &Rect,
272    fonts: &mut FontCtx,
273    available_width: u32,
274    available_height: u32,
275) -> Size {
276    if parent
277        .children
278        .iter()
279        .all(|child| child.position != Position::Flow)
280    {
281        return Size::default();
282    }
283
284    let direction = parent.direction;
285    let axis_available = match direction {
286        Direction::Row => available_width,
287        Direction::Column => available_height,
288    };
289    let cross_available = match direction {
290        Direction::Row => available_height,
291        Direction::Column => available_width,
292    };
293    let flow_count = parent
294        .children
295        .iter()
296        .filter(|child| child.position == Position::Flow)
297        .count();
298    let gap_total = parent
299        .gap
300        .saturating_mul(flow_count.saturating_sub(1) as u32);
301    let axis_available_without_gaps = axis_available.saturating_sub(gap_total);
302    let mut axis_used = 0_u32;
303    let mut cross_used = 0_u32;
304
305    for child in parent
306        .children
307        .iter()
308        .filter(|child| child.position == Position::Flow)
309    {
310        let measured = measure_element(child, fonts, available_width, available_height);
311        let axis = match axis_length(child, direction) {
312            Length::Fill => constrain_axis(measured.axis(direction), child, direction),
313            _ => resolve_axis_length(
314                child,
315                direction,
316                axis_available_without_gaps,
317                measured.axis(direction),
318            ),
319        };
320        let cross = match cross_length(child, direction) {
321            Length::Fill => constrain_cross(measured.cross(direction), child, direction),
322            _ => resolve_cross_length(child, direction, cross_available, measured.cross(direction)),
323        };
324        axis_used = axis_used.saturating_add(axis);
325        cross_used = cross_used.max(cross);
326    }
327
328    axis_used = axis_used.saturating_add(gap_total).min(axis_available);
329    match direction {
330        Direction::Row => Size::new(axis_used, cross_used.min(cross_available)),
331        Direction::Column => Size::new(cross_used.min(cross_available), axis_used),
332    }
333}
334
335fn layout_children(
336    parent: &Rect,
337    content: Bounds,
338    fonts: &mut FontCtx,
339    mut layout_child: impl FnMut(usize, &Rect, Bounds, Size, &mut FontCtx),
340) {
341    let count = parent
342        .children
343        .iter()
344        .filter(|child| child.position == Position::Flow)
345        .count();
346    if count == 0 {
347        layout_absolute_children(parent, content, fonts, layout_child);
348        return;
349    }
350
351    let gap_total = parent.gap.saturating_mul(count.saturating_sub(1) as u32);
352    let axis_total = match parent.direction {
353        Direction::Row => content.width,
354        Direction::Column => content.height,
355    };
356    let axis_available = axis_total.saturating_sub(gap_total);
357    let cross_available = match parent.direction {
358        Direction::Row => content.height,
359        Direction::Column => content.width,
360    };
361
362    let mut fixed_axis = 0_u32;
363    let mut fill_weight = 0_u32;
364    for child in parent
365        .children
366        .iter()
367        .filter(|child| child.position == Position::Flow)
368    {
369        match axis_length(child, parent.direction) {
370            Length::Fill => {
371                fill_weight = fill_weight.saturating_add(child.fill.max(1));
372            }
373            _ => {
374                let measured = measure_element(child, fonts, content.width, content.height);
375                let axis = resolve_axis_length(
376                    child,
377                    parent.direction,
378                    axis_available,
379                    measured.axis(parent.direction),
380                );
381                fixed_axis = fixed_axis.saturating_add(axis);
382            }
383        }
384    }
385
386    let remaining = axis_available.saturating_sub(fixed_axis);
387    let mut distributed = 0_u32;
388    let mut remaining_weight = fill_weight;
389    let mut used_axis = gap_total;
390    for child in parent
391        .children
392        .iter()
393        .filter(|child| child.position == Position::Flow)
394    {
395        let axis = match axis_length(child, parent.direction) {
396            Length::Fill => {
397                let weight = child.fill.max(1);
398                let share = if remaining_weight <= weight {
399                    remaining.saturating_sub(distributed)
400                } else {
401                    remaining.saturating_sub(distributed) * weight / remaining_weight
402                };
403                remaining_weight = remaining_weight.saturating_sub(weight);
404                distributed = distributed.saturating_add(share);
405                constrain_axis(share, child, parent.direction)
406            }
407            _ => {
408                let measured = measure_element(child, fonts, content.width, content.height);
409                resolve_axis_length(
410                    child,
411                    parent.direction,
412                    axis_available,
413                    measured.axis(parent.direction),
414                )
415            }
416        };
417        used_axis = used_axis.saturating_add(axis);
418    }
419
420    let start_offset = align_offset(parent.justify, axis_total, used_axis);
421    let mut cursor = match parent.direction {
422        Direction::Row => content.x.saturating_add(start_offset),
423        Direction::Column => content.y.saturating_add(start_offset),
424    };
425
426    let mut distributed = 0_u32;
427    let mut remaining_weight = fill_weight;
428    for (index, child) in parent
429        .children
430        .iter()
431        .enumerate()
432        .filter(|(_, child)| child.position == Position::Flow)
433    {
434        let measured = measure_element(child, fonts, content.width, content.height);
435        let axis = match axis_length(child, parent.direction) {
436            Length::Fill => {
437                let weight = child.fill.max(1);
438                let share = if remaining_weight <= weight {
439                    remaining.saturating_sub(distributed)
440                } else {
441                    remaining.saturating_sub(distributed) * weight / remaining_weight
442                };
443                remaining_weight = remaining_weight.saturating_sub(weight);
444                distributed = distributed.saturating_add(share);
445                constrain_axis(share, child, parent.direction)
446            }
447            _ => resolve_axis_length(
448                child,
449                parent.direction,
450                axis_available,
451                measured.axis(parent.direction),
452            ),
453        };
454        if axis == 0 {
455            continue;
456        };
457        let cross = match cross_length(child, parent.direction) {
458            Length::Fill => cross_available,
459            Length::Fit if parent.align == Align::Stretch => cross_available,
460            _ => resolve_cross_length(
461                child,
462                parent.direction,
463                cross_available,
464                measured.cross(parent.direction),
465            ),
466        };
467        let cross_offset = align_offset(parent.align, cross_available, cross);
468        let rect = match parent.direction {
469            Direction::Row => Bounds {
470                x: cursor,
471                y: content.y.saturating_add(cross_offset),
472                width: axis,
473                height: cross,
474            },
475            Direction::Column => Bounds {
476                x: content.x.saturating_add(cross_offset),
477                y: cursor,
478                width: cross,
479                height: axis,
480            },
481        };
482        cursor = cursor.saturating_add(axis).saturating_add(parent.gap);
483        layout_child(index, child, rect, measured, fonts);
484    }
485
486    layout_absolute_children(parent, content, fonts, layout_child);
487}
488
489fn layout_absolute_children(
490    parent: &Rect,
491    content: Bounds,
492    fonts: &mut FontCtx,
493    mut layout_child: impl FnMut(usize, &Rect, Bounds, Size, &mut FontCtx),
494) {
495    for (index, child) in parent
496        .children
497        .iter()
498        .enumerate()
499        .filter(|(_, child)| child.position == Position::Absolute)
500    {
501        let (rect, measured) = absolute_child_rect(child, content, fonts);
502        if rect.width == 0 || rect.height == 0 {
503            continue;
504        }
505        layout_child(index, child, rect, measured, fonts);
506    }
507}
508
509fn absolute_child_rect(child: &Rect, content: Bounds, fonts: &mut FontCtx) -> (Bounds, Size) {
510    let left = child.inset.left.unwrap_or(0);
511    let right = child.inset.right.unwrap_or(0);
512    let top = child.inset.top.unwrap_or(0);
513    let bottom = child.inset.bottom.unwrap_or(0);
514    let available_width = content.width.saturating_sub(left).saturating_sub(right);
515    let available_height = content.height.saturating_sub(top).saturating_sub(bottom);
516    let measured = measure_element(child, fonts, available_width, available_height);
517    let width = if child.inset.left.is_some()
518        && child.inset.right.is_some()
519        && matches!(child.width, Length::Fill)
520    {
521        constrain_dimension(available_width, child.min_width, child.max_width)
522    } else {
523        resolve_length(
524            child.width,
525            available_width,
526            measured.width,
527            child.min_width,
528            child.max_width,
529        )
530    };
531    let height = if child.inset.top.is_some()
532        && child.inset.bottom.is_some()
533        && matches!(child.height, Length::Fill)
534    {
535        constrain_dimension(available_height, child.min_height, child.max_height)
536    } else {
537        resolve_length(
538            child.height,
539            available_height,
540            measured.height,
541            child.min_height,
542            child.max_height,
543        )
544    };
545    let x = match (child.inset.left, child.inset.right) {
546        (Some(left), _) => content.x.saturating_add(left),
547        (None, Some(right)) => content.right().saturating_sub(right).saturating_sub(width),
548        (None, None) => content.x,
549    };
550    let y = match (child.inset.top, child.inset.bottom) {
551        (Some(top), _) => content.y.saturating_add(top),
552        (None, Some(bottom)) => content
553            .bottom()
554            .saturating_sub(bottom)
555            .saturating_sub(height),
556        (None, None) => content.y,
557    };
558
559    (
560        Bounds {
561            x,
562            y,
563            width,
564            height,
565        },
566        measured,
567    )
568}
569
570fn axis_length(element: &Rect, direction: Direction) -> Length {
571    match direction {
572        Direction::Row => element.width,
573        Direction::Column => element.height,
574    }
575}
576
577fn cross_length(element: &Rect, direction: Direction) -> Length {
578    match direction {
579        Direction::Row => element.height,
580        Direction::Column => element.width,
581    }
582}
583
584fn resolve_axis_length(element: &Rect, direction: Direction, available: u32, fit: u32) -> u32 {
585    let (min, max) = match direction {
586        Direction::Row => (element.min_width, element.max_width),
587        Direction::Column => (element.min_height, element.max_height),
588    };
589    resolve_length(axis_length(element, direction), available, fit, min, max)
590}
591
592fn resolve_cross_length(element: &Rect, direction: Direction, available: u32, fit: u32) -> u32 {
593    let (min, max) = match direction {
594        Direction::Row => (element.min_height, element.max_height),
595        Direction::Column => (element.min_width, element.max_width),
596    };
597    resolve_length(cross_length(element, direction), available, fit, min, max)
598}
599
600fn constrain_axis(size: u32, element: &Rect, direction: Direction) -> u32 {
601    let (min, max) = match direction {
602        Direction::Row => (element.min_width, element.max_width),
603        Direction::Column => (element.min_height, element.max_height),
604    };
605    constrain_dimension(size, min, max)
606}
607
608fn constrain_cross(size: u32, element: &Rect, direction: Direction) -> u32 {
609    let (min, max) = match direction {
610        Direction::Row => (element.min_height, element.max_height),
611        Direction::Column => (element.min_width, element.max_width),
612    };
613    constrain_dimension(size, min, max)
614}
615
616fn resolve_length(length: Length, available: u32, fit: u32, min: u32, max: Option<u32>) -> u32 {
617    let resolved = match length {
618        Length::Fit => fit,
619        Length::Fill => available,
620        Length::Px(value) => value.min(available),
621        Length::Percent(percent) => {
622            ((available as f32 * percent.clamp(0.0, 100.0) / 100.0).round() as u32).min(available)
623        }
624    };
625    constrain_dimension(resolved, min, max)
626}
627
628fn constrain_dimension(size: u32, min: u32, max: Option<u32>) -> u32 {
629    let max = max.unwrap_or(u32::MAX).max(min);
630    size.max(min).min(max)
631}