agui_primitives/text/
mod.rs

1use glyph_brush_layout::{
2    ab_glyph::{Font, ScaleFont},
3    BuiltInLineBreaker, GlyphPositioner, SectionGeometry, SectionText, ToSectionText,
4};
5
6use agui_core::{
7    context::WidgetContext,
8    layout::Layout,
9    unit::{Color, Margin, Position, Sizing, Units},
10    widget::{BuildResult, WidgetBuilder},
11};
12use agui_macros::Widget;
13
14mod font;
15
16pub use self::font::{FontArc, Fonts, GlyphLayout};
17pub use glyph_brush_layout::{FontId, HorizontalAlign, SectionGlyph, VerticalAlign};
18
19pub struct TextSection {
20    pub font: FontId,
21    pub text: String,
22    pub scale: f32,
23}
24
25impl TextSection {
26    pub fn new(font: FontId, scale: f32, text: String) -> Self {
27        Self { font, text, scale }
28    }
29}
30
31impl ToSectionText for TextSection {
32    fn to_section_text(&self) -> SectionText<'_> {
33        SectionText {
34            text: &self.text,
35            scale: self.scale.into(),
36            font_id: self.font,
37        }
38    }
39}
40
41#[derive(Widget)]
42pub struct Text {
43    pub position: Position,
44    pub sizing: Sizing,
45    pub max_sizing: Sizing,
46
47    pub wrap: bool,
48
49    pub color: Color,
50    pub sections: Vec<TextSection>,
51}
52
53impl Default for Text {
54    fn default() -> Self {
55        Self {
56            position: Position::default(),
57            sizing: Sizing::default(),
58            max_sizing: Sizing::default(),
59
60            wrap: true,
61
62            color: Color::Black,
63            sections: Vec::default(),
64        }
65    }
66}
67
68impl WidgetBuilder for Text {
69    fn build(&self, ctx: &WidgetContext) -> BuildResult {
70        let sizing = match self.sizing {
71            Sizing::Auto => {
72                let fonts = ctx.use_global(Fonts::default);
73                let fonts = fonts.read();
74                let fonts = fonts.get_fonts();
75
76                let glyphs = self.get_glyphs(fonts, (f32::MAX, f32::MAX));
77
78                let mut max_x: f32 = 0.0;
79                let mut max_y: f32 = 0.0;
80
81                for g in glyphs {
82                    if let Some(font) = fonts.get(g.font_id.0) {
83                        max_x += font.as_scaled(g.glyph.scale).h_advance(g.glyph.id);
84                        max_y = max_y.max(g.glyph.scale.y);
85                    }
86                }
87
88                Sizing::Axis {
89                    // What even is this magic number
90                    width: Units::Pixels(max_x),
91                    height: Units::Pixels(max_y),
92                }
93            }
94            sizing => sizing,
95        };
96
97        ctx.set_layout(
98            Layout {
99                position: self.position,
100                min_sizing: Sizing::default(),
101                max_sizing: self.max_sizing,
102                sizing,
103                margin: Margin::default(),
104            }
105            .into(),
106        );
107
108        BuildResult::Empty
109    }
110}
111
112impl Text {
113    pub fn is(font: FontId, scale: f32, text: String) -> Self {
114        Self {
115            sections: vec![TextSection::new(font, scale, text)],
116            ..Text::default()
117        }
118    }
119
120    pub fn new(sections: Vec<TextSection>) -> Self {
121        Self {
122            sections,
123            ..Text::default()
124        }
125    }
126
127    pub fn color(mut self, color: Color) -> Self {
128        self.color = color;
129        self
130    }
131
132    pub fn nowrap(mut self) -> Self {
133        self.wrap = false;
134        self
135    }
136
137    pub fn get_glyphs(&self, fonts: &[FontArc], bounds: (f32, f32)) -> Vec<SectionGlyph> {
138        let glyphs_layout = GlyphLayout::Wrap {
139            line_breaker: BuiltInLineBreaker::UnicodeLineBreaker,
140            h_align: HorizontalAlign::Left,
141            v_align: VerticalAlign::Top,
142        };
143
144        glyphs_layout.calculate_glyphs(
145            fonts,
146            &SectionGeometry {
147                screen_position: (0.0, 0.0),
148                bounds,
149            },
150            &self.sections,
151        )
152    }
153}