1use zenthra_text::prelude::*;
2pub use zenthra_text::prelude::{FontWeight, TextWrap, FontStyle};
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 ellipsis(mut self, enabled: bool) -> Self {
301 self.options = self.options.ellipsis(enabled);
302 self
303 }
304
305
306 pub fn align(mut self, alignment: Align) -> Self {
307 let halign = match alignment {
308 Align::Left => HorizontalAlignment::Left,
309 Align::Center => HorizontalAlignment::Center,
310 Align::Right => HorizontalAlignment::Right,
311 _ => HorizontalAlignment::Left,
312 };
313 self.options = self.options.align(halign);
314 self
315 }
316
317 pub fn align_left(mut self) -> Self {
318 self.options = self.options.align(HorizontalAlignment::Left);
319 self
320 }
321
322 pub fn align_center(mut self) -> Self {
323 self.options = self.options.align(HorizontalAlignment::Center);
324 self
325 }
326
327 pub fn align_right(mut self) -> Self {
328 self.options = self.options.align(HorizontalAlignment::Right);
329 self
330 }
331
332 pub fn halign(self, alignment: Align) -> Self {
333 self.align(alignment)
334 }
335
336 pub fn valign(mut self, alignment: Align) -> Self {
337 let valign = match alignment {
338 Align::Top => VerticalAlignment::Top,
339 Align::Center => VerticalAlignment::Center,
340 Align::Bottom => VerticalAlignment::Bottom,
341 _ => VerticalAlignment::Top,
342 };
343 self.options = self.options.valign(valign);
344 self
345 }
346
347 pub fn cursor(mut self, c: CursorIcon) -> Self {
348 self.cursor = c;
349 self
350 }
351 pub fn cursor_text(mut self) -> Self {
352 self.cursor = CursorIcon::Text;
353 self
354 }
355 pub fn cursor_pointer(mut self) -> Self {
356 self.cursor = CursorIcon::Pointer;
357 self
358 }
359
360 pub fn clip_rect(mut self, x: f32, y: f32, w: f32, h: f32) -> Self {
361 self.options = self.options.clip_rect(x, y, w, h);
362 self
363 }
364
365 pub fn render_mode(mut self, mode: zenthra_core::RenderMode) -> Self {
366 self.render_mode = Some(mode);
367 self
368 }
369
370 pub fn continuous(mut self) -> Self {
371 self.render_mode = Some(zenthra_core::RenderMode::Continuous);
372 self
373 }
374
375 pub fn static_mode(mut self) -> Self {
376 self.render_mode = Some(zenthra_core::RenderMode::Static);
377 self
378 }
379
380 pub fn show(mut self) -> (zenthra_core::Response, Option<ShapedBuffer>) {
381 if let Some(mode) = self.render_mode {
382 self.ui.render_mode_stack.push(mode);
383 }
384
385 let horiz = self.margin.horizontal();
386 let vert = self.margin.vertical();
387 let (w, h, buffer, start) = self.draw_and_measure();
388
389 self.ui.register_semantic(
390 SemanticNode::new(self.id, Role::Label, Rect::new(self.start_x, self.start_y, w, h))
391 .with_label(self.content.clone())
392 );
393
394 self.ui.advance(w + horiz, h + vert, start);
395
396 if self.render_mode.is_some() {
397 self.ui.render_mode_stack.pop();
398 }
399
400 let is_hovered = self.ui.mouse_in_rect(self.start_x + self.ui.offset_x, self.start_y + self.ui.offset_y, w, h);
401 let response = zenthra_core::Response {
402 clicked: self.ui.clicked && is_hovered,
403 hovered: is_hovered,
404 pressed: is_hovered && self.ui.mouse_down,
405 };
406
407 (response, buffer)
408 }
409
410 pub fn draw_and_measure(&mut self) -> (f32, f32, Option<ShapedBuffer>, usize) {
411 let (w, h, buffer) = if let Some(fs) = self.ui.font_system.as_ref() {
412 let mut adapter = CosmicFontProvider::new_with_system(fs.clone());
413
414 let layout_width = self.options.max_width.unwrap_or_else(|| {
416 (self.ui.available_width - self.padding.horizontal()).max(0.0)
417 });
418
419 self.options.max_width = Some(layout_width);
420
421 adapter.set_layout_size(layout_width, self.ui.height);
422
423 let buffer = adapter.shape(&self.content, &self.options);
424 let (cw, ch) = buffer.size();
425
426 let mut w = cw + self.padding.horizontal();
428 if self.fill_x {
429 w = self.ui.max_x - self.start_x;
430 } else if let Some(min_w) = self.options.min_width {
431 if w < min_w { w = min_w; }
432 }
433 let h = ch + self.padding.vertical();
434 self.ui.record_layout(self.id, Rect::new(self.start_x, self.start_y, w, h));
435
436 (w, h, Some(buffer))
437 } else {
438 (100.0, 20.0, None)
439 };
440
441 let start_draw = self.ui.draws.len();
442 let clip = self.options.clip_rect.unwrap_or([-100000.0, -100000.0, 2000000.0, 2000000.0]);
443
444 if let Some(bg) = self.bg_color {
446 use zenthra_render::RectInstance;
447 use crate::ui::RectDraw;
448
449 self.ui.draws.push(DrawCommand::Rect(RectDraw {
450 instance: RectInstance {
451 pos: [self.start_x, self.start_y],
452 size: [w, h],
453 color: bg.to_array(),
454 radius: [
455 self.radius[3],
456 self.radius[2],
457 self.radius[1],
458 self.radius[0],
459 ],
460 border_width: self.border_width,
461 border_color: self.border_color.unwrap_or(Color::TRANSPARENT).to_array(),
462 shadow_color: self.shadow_color.map(|mut c| { c.a *= self.shadow_opacity; c.to_array() }).unwrap_or([0.0; 4]),
463 shadow_offset: self.shadow_offset,
464 shadow_blur: self.shadow_blur,
465 clip_rect: clip,
466 grayscale: 0.0,
467 brightness: 1.0,
468 opacity: self.opacity,
469 ..Default::default()
470 }
471 }));
472 }
473
474 self.ui.draws.push(DrawCommand::Text(TextDraw {
476 text: self.content.clone().into(),
477 pos: [self.start_x + self.padding.left, self.start_y + self.padding.top],
478 options: self.options.clone(),
479 clip,
480 }));
481
482 (w, h, buffer, start_draw)
483 }
484}