1use zenthra_text::prelude::*;
2pub use zenthra_text::prelude::FontWeight;
3use crate::ui::{DrawCommand, TextDraw, Ui};
5use zenthra_core::{Color, EdgeInsets, Role, SemanticNode, Rect, Align, Id};
6
7pub struct TextBuilder<'u, 'a> {
8 ui: &'u mut Ui<'a>,
9 id: Id,
10 content: String,
11 options: TextOptions,
12
13 padding: Padding,
15 bg_color: Option<Color>,
16 fill_x: bool,
17
18 margin: EdgeInsets,
19 radius: [f32; 4],
20 border_color: Option<Color>,
21 border_width: f32,
22 shadow_color: Option<Color>,
23 shadow_offset: [f32; 2],
24 shadow_blur: f32,
25 shadow_opacity: f32,
26 opacity: f32,
27 cursor: CursorIcon,
28 render_mode: Option<zenthra_core::RenderMode>,
29 start_x: f32,
30 start_y: f32,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum CursorIcon {
35 Default,
36 Text,
37 Pointer,
38 Crosshair,
39 ColResize,
40}
41
42impl<'u, 'a> TextBuilder<'u, 'a> {
43 pub fn new(ui: &'u mut Ui<'a>, content: &str) -> Self {
44 let x = ui.cursor_x;
45 let y = ui.cursor_y;
46 let sf = ui.scale_factor;
47
48 let mut hasher = std::collections::hash_map::DefaultHasher::new();
50 use std::hash::{Hash, Hasher};
51 content.hash(&mut hasher);
52 if let Some(parent) = ui.semantic_stack.last() {
54 parent.hash(&mut hasher);
55 }
56 let id_raw = hasher.finish();
57 let id = Id::from_u64(id_raw);
58
59
60 Self {
61 ui,
62 id,
63 content: content.to_string(),
64 options: TextOptions::new()
65 .at(0.0, 0.0) .scale_factor(sf),
67 padding: Padding::ZERO,
68 bg_color: None,
69 fill_x: false,
70 margin: EdgeInsets::ZERO,
71 radius: [0.0; 4],
72 border_color: None,
73 border_width: 0.0,
74 shadow_color: None,
75 shadow_offset: [0.0; 2],
76 shadow_blur: 0.0,
77 shadow_opacity: 1.0,
78 opacity: 1.0,
79 cursor: CursorIcon::Default,
80 render_mode: None,
81 start_x: x,
82 start_y: y,
83 }
84 }
85
86 pub fn min_width(mut self, w: f32) -> Self {
87 self.options = self.options.min_width(w);
88 self
89 }
90
91 pub fn size(mut self, s: f32) -> Self {
92 self.options = self.options.font_size(s);
93 self
94 }
95 pub fn color(mut self, c: Color) -> Self {
96 self.options = self.options.color(c);
97 self
98 }
99 pub fn weight(mut self, w: impl Into<FontWeight>) -> Self {
100 self.options = self.options.font_weight(w.into());
101 self
102 }
103 pub fn bold(mut self) -> Self {
104 self.options = self.options.font_weight(FontWeight::Bold);
105 self
106 }
107 pub fn italic(mut self) -> Self {
108 self.options = self.options.font_style(FontStyle::Italic);
109 self
110 }
111 pub fn family(mut self, f: impl Into<String>) -> Self {
112 self.options = self.options.font_family(f);
113 self
114 }
115 pub fn monospace(mut self) -> Self {
116 self.options = self.options.font_family("monospace");
117 self
118 }
119 pub fn pos(mut self, x: f32, y: f32) -> Self {
120 self.start_x = x;
121 self.start_y = y;
122 self.options = self.options.at(0.0, 0.0);
123 self
124 }
125 pub fn max_width(mut self, w: f32) -> Self {
126 self.options = self.options.max_width(w);
127 self
128 }
129
130 pub fn padding(mut self, t: f32, r: f32, b: f32, l: f32) -> Self {
131 self.padding = Padding { top: t, right: r, bottom: b, left: l };
132 self
133 }
134 pub fn padding_x(mut self, p: f32) -> Self {
135 self.padding.left = p;
136 self.padding.right = p;
137 self
138 }
139 pub fn padding_y(mut self, p: f32) -> Self {
140 self.padding.top = p;
141 self.padding.bottom = p;
142 self
143 }
144 pub fn padding_top(mut self, p: f32) -> Self {
145 self.padding.top = p;
146 self
147 }
148 pub fn padding_bottom(mut self, p: f32) -> Self {
149 self.padding.bottom = p;
150 self
151 }
152 pub fn padding_left(mut self, p: f32) -> Self {
153 self.padding.left = p;
154 self
155 }
156 pub fn padding_right(mut self, p: f32) -> Self {
157 self.padding.right = p;
158 self
159 }
160
161 pub fn margin(mut self, t: f32, r: f32, b: f32, l: f32) -> Self {
162 self.margin = EdgeInsets { top: t, right: r, bottom: b, left: l };
163 self
164 }
165 pub fn margin_x(mut self, m: f32) -> Self {
166 self.margin.left = m;
167 self.margin.right = m;
168 self
169 }
170 pub fn margin_y(mut self, m: f32) -> Self {
171 self.margin.top = m;
172 self.margin.bottom = m;
173 self
174 }
175 pub fn margin_top(mut self, m: f32) -> Self {
176 self.margin.top = m;
177 self
178 }
179 pub fn margin_bottom(mut self, m: f32) -> Self {
180 self.margin.bottom = m;
181 self
182 }
183 pub fn margin_left(mut self, m: f32) -> Self {
184 self.margin.left = m;
185 self
186 }
187 pub fn margin_right(mut self, m: f32) -> Self {
188 self.margin.right = m;
189 self
190 }
191
192 pub fn line_height(mut self, lh: f32) -> Self {
193 self.options = self.options.line_height(lh);
194 self
195 }
196
197 pub fn bg(mut self, c: Color) -> Self {
198 self.bg_color = Some(c);
199 self
200 }
201
202 pub fn border(mut self, color: Color, width: f32) -> Self {
203 self.border_color = Some(color);
204 self.border_width = width;
205 self
206 }
207
208 pub fn radius(mut self, tl: f32, tr: f32, br: f32, bl: f32) -> Self {
209 self.radius = [tl, tr, br, bl];
210 self
211 }
212
213 pub fn radius_all(mut self, r: f32) -> Self {
214 self.radius = [r; 4];
215 self
216 }
217
218 pub fn radius_top(mut self, r: f32) -> Self {
219 self.radius[0] = r;
220 self.radius[1] = r;
221 self
222 }
223
224 pub fn radius_bottom(mut self, r: f32) -> Self {
225 self.radius[2] = r;
226 self.radius[3] = r;
227 self
228 }
229
230 pub fn radius_top_left(mut self, r: f32) -> Self {
231 self.radius[0] = r;
232 self
233 }
234
235 pub fn radius_top_right(mut self, r: f32) -> Self {
236 self.radius[1] = r;
237 self
238 }
239
240 pub fn radius_bottom_right(mut self, r: f32) -> Self {
241 self.radius[2] = r;
242 self
243 }
244
245 pub fn radius_bottom_left(mut self, r: f32) -> Self {
246 self.radius[3] = r;
247 self
248 }
249
250 pub fn radius_left(mut self, r: f32) -> Self {
251 self.radius[0] = r;
252 self.radius[3] = r;
253 self
254 }
255
256 pub fn radius_right(mut self, r: f32) -> Self {
257 self.radius[1] = r;
258 self.radius[2] = r;
259 self
260 }
261
262 pub fn shadow(mut self, color: Color, ox: f32, oy: f32, blur: f32) -> Self {
263 self.shadow_color = Some(color);
264 self.shadow_offset = [ox, oy];
265 self.shadow_blur = blur;
266 self
267 }
268
269 pub fn opacity(mut self, o: f32) -> Self {
270 self.opacity = o;
271 self
272 }
273
274 pub fn highlight(mut self, c: Color) -> Self {
275 self.options = self.options.highlight(c);
276 self
277 }
278
279 pub fn fill_x(mut self, enabled: bool) -> Self {
280 self.fill_x = enabled;
281 self
282 }
283
284 pub fn id(mut self, id: impl std::hash::Hash) -> Self {
285 let mut hasher = std::collections::hash_map::DefaultHasher::new();
286 use std::hash::{Hash, Hasher};
287 id.hash(&mut hasher);
288 if let Some(parent) = self.ui.semantic_stack.last() {
289 parent.hash(&mut hasher);
290 }
291 self.id = zenthra_core::Id::from_u64(hasher.finish());
292 self
293 }
294
295 pub fn wrap(mut self, strategy: impl Into<TextWrap>) -> Self {
296 self.options = self.options.wrap(strategy.into());
297 self
298 }
299
300 pub fn align(mut self, alignment: Align) -> Self {
301 let halign = match alignment {
302 Align::Left => HorizontalAlignment::Left,
303 Align::Center => HorizontalAlignment::Center,
304 Align::Right => HorizontalAlignment::Right,
305 _ => HorizontalAlignment::Left,
306 };
307 self.options = self.options.align(halign);
308 self
309 }
310
311 pub fn align_left(mut self) -> Self {
312 self.options = self.options.align(HorizontalAlignment::Left);
313 self
314 }
315
316 pub fn align_center(mut self) -> Self {
317 self.options = self.options.align(HorizontalAlignment::Center);
318 self
319 }
320
321 pub fn align_right(mut self) -> Self {
322 self.options = self.options.align(HorizontalAlignment::Right);
323 self
324 }
325
326 pub fn halign(self, alignment: Align) -> Self {
327 self.align(alignment)
328 }
329
330 pub fn valign(mut self, alignment: Align) -> Self {
331 let valign = match alignment {
332 Align::Top => VerticalAlignment::Top,
333 Align::Center => VerticalAlignment::Center,
334 Align::Bottom => VerticalAlignment::Bottom,
335 _ => VerticalAlignment::Top,
336 };
337 self.options = self.options.valign(valign);
338 self
339 }
340
341 pub fn cursor(mut self, c: CursorIcon) -> Self {
342 self.cursor = c;
343 self
344 }
345 pub fn cursor_text(mut self) -> Self {
346 self.cursor = CursorIcon::Text;
347 self
348 }
349 pub fn cursor_pointer(mut self) -> Self {
350 self.cursor = CursorIcon::Pointer;
351 self
352 }
353
354 pub fn clip_rect(mut self, x: f32, y: f32, w: f32, h: f32) -> Self {
355 self.options = self.options.clip_rect(x, y, w, h);
356 self
357 }
358
359 pub fn render_mode(mut self, mode: zenthra_core::RenderMode) -> Self {
360 self.render_mode = Some(mode);
361 self
362 }
363
364 pub fn continuous(mut self) -> Self {
365 self.render_mode = Some(zenthra_core::RenderMode::Continuous);
366 self
367 }
368
369 pub fn static_mode(mut self) -> Self {
370 self.render_mode = Some(zenthra_core::RenderMode::Static);
371 self
372 }
373
374 pub fn show(mut self) -> (zenthra_core::Response, Option<ShapedBuffer>) {
375 if let Some(mode) = self.render_mode {
376 self.ui.render_mode_stack.push(mode);
377 }
378
379 let horiz = self.margin.horizontal();
380 let vert = self.margin.vertical();
381 let (w, h, buffer, start) = self.draw_and_measure();
382
383 self.ui.register_semantic(
384 SemanticNode::new(self.id, Role::Label, Rect::new(self.start_x, self.start_y, w, h))
385 .with_label(self.content.clone())
386 );
387
388 self.ui.advance(w + horiz, h + vert, start);
389
390 if self.render_mode.is_some() {
391 self.ui.render_mode_stack.pop();
392 }
393
394 let is_hovered = self.ui.mouse_in_rect(self.start_x + self.ui.offset_x, self.start_y + self.ui.offset_y, w, h);
395 let response = zenthra_core::Response {
396 clicked: self.ui.clicked && is_hovered,
397 hovered: is_hovered,
398 pressed: is_hovered && self.ui.mouse_down,
399 };
400
401 (response, buffer)
402 }
403
404 pub fn draw_and_measure(&mut self) -> (f32, f32, Option<ShapedBuffer>, usize) {
405 let (w, h, buffer) = if let Some(fs) = self.ui.font_system.as_ref() {
406 let mut adapter = CosmicFontProvider::new_with_system(fs.clone());
407
408 let layout_width = self.options.max_width.unwrap_or_else(|| {
410 (self.ui.available_width - self.padding.horizontal()).max(0.0)
411 });
412
413 self.options.max_width = Some(layout_width);
414
415 adapter.set_layout_size(layout_width, self.ui.height);
416
417 let buffer = adapter.shape(&self.content, &self.options);
418 let (cw, ch) = buffer.size();
419
420 let mut w = cw + self.padding.horizontal();
422 if self.fill_x {
423 w = self.ui.max_x - self.start_x;
424 } else if let Some(min_w) = self.options.min_width {
425 if w < min_w { w = min_w; }
426 }
427 let h = ch + self.padding.vertical();
428 self.ui.record_layout(self.id, Rect::new(self.start_x, self.start_y, w, h));
429
430 (w, h, Some(buffer))
431 } else {
432 (100.0, 20.0, None)
433 };
434
435 let start_draw = self.ui.draws.len();
436 let clip = self.options.clip_rect.unwrap_or([-100000.0, -100000.0, 2000000.0, 2000000.0]);
437
438 if let Some(bg) = self.bg_color {
440 use zenthra_render::RectInstance;
441 use crate::ui::RectDraw;
442
443 self.ui.draws.push(DrawCommand::Rect(RectDraw {
444 instance: RectInstance {
445 pos: [self.start_x, self.start_y],
446 size: [w, h],
447 color: bg.to_array(),
448 radius: [
449 self.radius[3],
450 self.radius[2],
451 self.radius[1],
452 self.radius[0],
453 ],
454 border_width: self.border_width,
455 border_color: self.border_color.unwrap_or(Color::TRANSPARENT).to_array(),
456 shadow_color: self.shadow_color.map(|mut c| { c.a *= self.shadow_opacity; c.to_array() }).unwrap_or([0.0; 4]),
457 shadow_offset: self.shadow_offset,
458 shadow_blur: self.shadow_blur,
459 clip_rect: clip,
460 grayscale: 0.0,
461 brightness: 1.0,
462 opacity: self.opacity,
463 ..Default::default()
464 }
465 }));
466 }
467
468 self.ui.draws.push(DrawCommand::Text(TextDraw {
470 text: self.content.clone().into(),
471 pos: [self.start_x + self.padding.left, self.start_y + self.padding.top],
472 options: self.options.clone(),
473 clip,
474 }));
475
476 (w, h, buffer, start_draw)
477 }
478}