label.rs

  1use crate::{
  2    color::ColorU,
  3    font_cache::FamilyId,
  4    fonts::Properties,
  5    geometry::vector::{vec2f, Vector2F},
  6    text_layout::Line,
  7    AfterLayoutContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
  8};
  9use std::{ops::Range, sync::Arc};
 10
 11pub struct Label {
 12    text: String,
 13    family_id: FamilyId,
 14    font_properties: Properties,
 15    font_size: f32,
 16    highlights: Option<Highlights>,
 17}
 18
 19pub struct LayoutState {
 20    line: Arc<Line>,
 21    colors: Vec<(Range<usize>, ColorU)>,
 22}
 23
 24pub struct Highlights {
 25    color: ColorU,
 26    indices: Vec<usize>,
 27    font_properties: Properties,
 28}
 29
 30impl Label {
 31    pub fn new(text: String, family_id: FamilyId, font_size: f32) -> Self {
 32        Self {
 33            text,
 34            family_id,
 35            font_properties: Properties::new(),
 36            font_size,
 37            highlights: None,
 38        }
 39    }
 40
 41    pub fn with_highlights(
 42        mut self,
 43        color: ColorU,
 44        font_properties: Properties,
 45        indices: Vec<usize>,
 46    ) -> Self {
 47        self.highlights = Some(Highlights {
 48            color,
 49            font_properties,
 50            indices,
 51        });
 52        self
 53    }
 54}
 55
 56impl Element for Label {
 57    type LayoutState = LayoutState;
 58    type PaintState = ();
 59
 60    fn layout(
 61        &mut self,
 62        constraint: SizeConstraint,
 63        ctx: &mut LayoutContext,
 64    ) -> (Vector2F, Self::LayoutState) {
 65        let font_id = ctx
 66            .font_cache
 67            .select_font(self.family_id, &self.font_properties)
 68            .unwrap();
 69        let text_len = self.text.chars().count();
 70        let mut styles;
 71        let mut colors;
 72        if let Some(highlights) = self.highlights.as_ref() {
 73            styles = Vec::new();
 74            colors = Vec::new();
 75            let highlight_font_id = ctx
 76                .font_cache
 77                .select_font(self.family_id, &highlights.font_properties)
 78                .unwrap_or(font_id);
 79            let mut pending_highlight: Option<Range<usize>> = None;
 80            for ix in &highlights.indices {
 81                if let Some(pending_highlight) = pending_highlight.as_mut() {
 82                    if *ix == pending_highlight.end {
 83                        pending_highlight.end += 1;
 84                    } else {
 85                        styles.push((pending_highlight.clone(), highlight_font_id));
 86                        colors.push((pending_highlight.clone(), highlights.color));
 87                        styles.push((pending_highlight.end..*ix, font_id));
 88                        colors.push((pending_highlight.end..*ix, ColorU::black()));
 89                        *pending_highlight = *ix..*ix + 1;
 90                    }
 91                } else {
 92                    styles.push((0..*ix, font_id));
 93                    colors.push((0..*ix, ColorU::black()));
 94                    pending_highlight = Some(*ix..*ix + 1);
 95                }
 96            }
 97            if let Some(pending_highlight) = pending_highlight.as_mut() {
 98                styles.push((pending_highlight.clone(), highlight_font_id));
 99                colors.push((pending_highlight.clone(), highlights.color));
100                if text_len > pending_highlight.end {
101                    styles.push((pending_highlight.end..text_len, font_id));
102                    colors.push((pending_highlight.end..text_len, ColorU::black()));
103                }
104            } else {
105                styles.push((0..text_len, font_id));
106                colors.push((0..text_len, ColorU::black()));
107            }
108        } else {
109            styles = vec![(0..text_len, font_id)];
110            colors = vec![(0..text_len, ColorU::black())];
111        }
112
113        let line =
114            ctx.text_layout_cache
115                .layout_str(self.text.as_str(), self.font_size, styles.as_slice());
116
117        let size = vec2f(
118            line.width.max(constraint.min.x()).min(constraint.max.x()),
119            ctx.font_cache.line_height(font_id, self.font_size).ceil(),
120        );
121
122        (size, LayoutState { line, colors })
123    }
124
125    fn after_layout(&mut self, _: Vector2F, _: &mut Self::LayoutState, _: &mut AfterLayoutContext) {
126    }
127
128    fn paint(
129        &mut self,
130        bounds: pathfinder_geometry::rect::RectF,
131        layout: &mut Self::LayoutState,
132        ctx: &mut PaintContext,
133    ) -> Self::PaintState {
134        layout.line.paint(bounds, layout.colors.as_slice(), ctx)
135    }
136
137    fn dispatch_event(
138        &mut self,
139        _: &Event,
140        _: pathfinder_geometry::rect::RectF,
141        _: &mut Self::LayoutState,
142        _: &mut Self::PaintState,
143        _: &mut EventContext,
144    ) -> bool {
145        false
146    }
147}