agui_primitives/text/
mod.rs1use 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 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}