1use crate::ui::{DrawCommand, RectDraw, TextDraw, Ui};
4use zenthra_core::{Color, EdgeInsets, Id, Rect, Role, SemanticNode};
6use zenthra_render::RectInstance;
7
8pub struct ButtonBuilder<'u, 'a> {
9 ui: &'u mut Ui<'a>,
10 id: Id,
11 label: String,
12
13 pos: Option<(f32, f32)>,
15 width: Option<f32>,
16 height: Option<f32>,
17 padding: EdgeInsets,
18
19 bg: Color,
21 text_color: Color,
22 radius: [f32; 4],
23 font_size: f32,
24 border_width: f32,
25 border_color: Color,
26
27 shadow_offset: [f32; 2],
29 shadow_blur: f32,
30 shadow_color: Color,
31 shadow_opacity: f32,
32
33 hover_bg: Option<Color>,
35 active_bg: Option<Color>,
36
37 opacity: f32,
39 render_mode: Option<zenthra_core::RenderMode>,
40 hover_brightness: f32,
41 hover_scale: f32,
42 hover_border_color: Option<Color>,
43 hover_border_width: Option<f32>,
44 active_border_color: Option<Color>,
45 active_border_width: Option<f32>,
46 wrap: zenthra_text::prelude::TextWrap,
47 fill_x: bool,
48 align: zenthra_core::Align,
49 font_family: Option<String>,
50}
51
52impl<'u, 'a> ButtonBuilder<'u, 'a> {
53 pub fn new(ui: &'u mut Ui<'a>, label: &str) -> Self {
54 let id = ui.id();
55 Self {
56 ui,
57 id,
58 label: label.to_string(),
59 pos: None,
60 width: None,
61 height: None,
62 padding: EdgeInsets::symmetric(6.0, 12.0),
63 bg: Color::rgb(0.2, 0.2, 0.25),
64 text_color: Color::WHITE,
65 radius: [4.0; 4],
66 font_size: 14.0,
67 border_width: 0.0,
68 border_color: Color::TRANSPARENT,
69 shadow_offset: [0.0, 0.0],
70 shadow_blur: 0.0,
71 shadow_color: Color::TRANSPARENT,
72 shadow_opacity: 0.0,
73 hover_bg: None,
74 active_bg: None,
75 opacity: 1.0,
76 render_mode: None,
77 hover_brightness: 1.0,
78 hover_scale: 1.0,
79 hover_border_color: None,
80 hover_border_width: None,
81 active_border_color: None,
82 active_border_width: None,
83 wrap: zenthra_text::prelude::TextWrap::Word,
84 fill_x: false,
85 align: zenthra_core::Align::Center,
86 font_family: None,
87 }
88 }
89
90 pub fn id(mut self, id: impl std::hash::Hash) -> Self {
91 let mut hasher = std::collections::hash_map::DefaultHasher::new();
92 use std::hash::Hasher;
93 id.hash(&mut hasher);
94 self.id = Id::from_u64(hasher.finish());
95 self
96 }
97
98 pub fn pos(mut self, x: f32, y: f32) -> Self {
99 self.pos = Some((x, y));
100 self
101 }
102
103 pub fn width(mut self, w: f32) -> Self {
104 self.width = Some(w);
105 self
106 }
107
108 pub fn height(mut self, h: f32) -> Self {
109 self.height = Some(h);
110 self
111 }
112
113 pub fn bg(mut self, color: Color) -> Self {
114 self.bg = color;
115 self
116 }
117
118 pub fn text_color(mut self, color: Color) -> Self {
119 self.text_color = color;
120 self
121 }
122
123 pub fn hover_bg(mut self, color: Color) -> Self {
124 self.hover_bg = Some(color);
125 self
126 }
127
128 pub fn active_bg(mut self, color: Color) -> Self {
129 self.active_bg = Some(color);
130 self
131 }
132
133 pub fn radius(mut self, tl: f32, tr: f32, br: f32, bl: f32) -> Self {
134 self.radius = [tl, tr, br, bl];
135 self
136 }
137
138 pub fn radius_all(mut self, r: f32) -> Self {
139 self.radius = [r, r, r, r];
140 self
141 }
142
143 pub fn radius_top(mut self, r: f32) -> Self {
144 self.radius[0] = r;
145 self.radius[1] = r;
146 self
147 }
148
149 pub fn radius_bottom(mut self, r: f32) -> Self {
150 self.radius[2] = r;
151 self.radius[3] = r;
152 self
153 }
154
155 pub fn radius_top_left(mut self, r: f32) -> Self {
156 self.radius[0] = r;
157 self
158 }
159
160 pub fn radius_top_right(mut self, r: f32) -> Self {
161 self.radius[1] = r;
162 self
163 }
164
165 pub fn radius_bottom_right(mut self, r: f32) -> Self {
166 self.radius[2] = r;
167 self
168 }
169
170 pub fn radius_bottom_left(mut self, r: f32) -> Self {
171 self.radius[3] = r;
172 self
173 }
174
175 pub fn radius_left(mut self, r: f32) -> Self {
176 self.radius[0] = r;
177 self.radius[3] = r;
178 self
179 }
180
181 pub fn radius_right(mut self, r: f32) -> Self {
182 self.radius[1] = r;
183 self.radius[2] = r;
184 self
185 }
186
187 pub fn border(mut self, color: Color, width: f32) -> Self {
188 self.border_color = color;
189 self.border_width = width;
190 self
191 }
192
193 pub fn shadow(mut self, color: Color, x: f32, y: f32, blur: f32) -> Self {
194 self.shadow_color = color;
195 self.shadow_offset = [x, y];
196 self.shadow_blur = blur;
197 self
198 }
199
200 pub fn shadow_opacity(mut self, opacity: f32) -> Self {
201 self.shadow_opacity = opacity;
202 self
203 }
204
205 pub fn padding(mut self, t: f32, r: f32, b: f32, l: f32) -> Self {
206 self.padding = zenthra_core::EdgeInsets { top: t, right: r, bottom: b, left: l };
207 self
208 }
209
210 pub fn padding_x(mut self, x: f32) -> Self {
211 self.padding.left = x;
212 self.padding.right = x;
213 self
214 }
215
216 pub fn padding_y(mut self, y: f32) -> Self {
217 self.padding.top = y;
218 self.padding.bottom = y;
219 self
220 }
221
222 pub fn padding_top(mut self, t: f32) -> Self {
223 self.padding.top = t;
224 self
225 }
226
227 pub fn padding_bottom(mut self, b: f32) -> Self {
228 self.padding.bottom = b;
229 self
230 }
231
232 pub fn padding_left(mut self, l: f32) -> Self {
233 self.padding.left = l;
234 self
235 }
236
237 pub fn padding_right(mut self, r: f32) -> Self {
238 self.padding.right = r;
239 self
240 }
241
242 pub fn opacity(mut self, o: f32) -> Self {
243 self.opacity = o;
244 self
245 }
246
247 pub fn hover_brightness(mut self, b: f32) -> Self {
248 self.hover_brightness = b;
249 self
250 }
251
252 pub fn hover_scale(mut self, s: f32) -> Self {
253 self.hover_scale = s;
254 self
255 }
256
257 pub fn hover_border(mut self, color: Color, weight: f32) -> Self {
258 self.hover_border_color = Some(color);
259 self.hover_border_width = Some(weight);
260 self
261 }
262
263 pub fn active_border(mut self, color: Color, weight: f32) -> Self {
264 self.active_border_color = Some(color);
265 self.active_border_width = Some(weight);
266 self
267 }
268
269 pub fn size(mut self, size: f32) -> Self {
270 self.font_size = size;
271 self
272 }
273
274 pub fn render_mode(mut self, mode: zenthra_core::RenderMode) -> Self {
275 self.render_mode = Some(mode);
276 self
277 }
278
279 pub fn wrap(mut self, strategy: impl Into<zenthra_text::prelude::TextWrap>) -> Self {
280 self.wrap = strategy.into();
281 self
282 }
283
284 pub fn fill_x(mut self) -> Self {
285 self.fill_x = true;
286 self
287 }
288
289 pub fn align(mut self, strategy: zenthra_core::Align) -> Self {
290 self.align = strategy;
291 self
292 }
293
294 pub fn font_family(mut self, family: impl Into<String>) -> Self {
295 self.font_family = Some(family.into());
296 self
297 }
298
299 pub fn on_click<F>(self, mut f: F) -> Self
300 where
301 F: FnMut() + 'a,
302 {
303 self.ui.add_listener(self.id, crate::ui::EventPhase::Bubble, move |_, event| {
304 if let crate::ui::WidgetEvent::Click = event {
305 f();
306 }
307 });
308 self
309 }
310
311 pub fn on_hover<F>(self, mut f: F) -> Self
312 where
313 F: FnMut(bool) + 'a,
314 {
315 self.ui.add_listener(self.id, crate::ui::EventPhase::Bubble, move |_, event| {
316 if let crate::ui::WidgetEvent::Hover(hovered) = event {
317 f(*hovered);
318 }
319 });
320 self
321 }
322
323 pub fn on_scroll<F>(self, mut f: F) -> Self
324 where
325 F: FnMut(f32, f32) + 'a,
326 {
327 self.ui.add_listener(self.id, crate::ui::EventPhase::Bubble, move |_, event| {
328 if let crate::ui::WidgetEvent::Scroll(dx, dy) = event {
329 f(*dx, *dy);
330 }
331 });
332 self
333 }
334
335 pub fn on_event<F>(self, phase: crate::ui::EventPhase, f: F) -> Self
336 where
337 F: FnMut(&mut crate::ui::EventContext, &crate::ui::WidgetEvent) + 'a,
338 {
339 self.ui.add_listener(self.id, phase, f);
340 self
341 }
342
343 pub fn show(self) -> zenthra_core::Response {
344 if let Some(mode) = self.render_mode {
345 self.ui.render_mode_stack.push(mode);
346 }
347
348 let (x, y) = self.pos.unwrap_or((self.ui.cursor_x, self.ui.cursor_y));
349
350 let mut clicked = false;
351 let ox = x + self.ui.offset_x;
352 let oy = y + self.ui.offset_y;
353
354 let is_hovered = self.ui.is_hovered(
355 self.id,
356 ox,
357 oy,
358 self.width.unwrap_or(100.0),
359 self.height.unwrap_or(40.0),
360 );
361
362 let is_pressed = is_hovered && self.ui.mouse_down;
363
364 if self.ui.clicked && is_hovered {
365 clicked = true;
366 }
367
368 self.ui.dispatch_event(self.id, crate::ui::WidgetEvent::Hover(is_hovered));
370
371 if clicked {
372 self.ui.dispatch_event(self.id, crate::ui::WidgetEvent::Click);
373 }
374
375 let events = std::mem::take(&mut self.ui.input_events);
376 for event in &events {
377 if let zenthra_platform::event::PlatformEvent::MouseWheel { delta_x, delta_y } = event {
378 if is_hovered {
379 self.ui.dispatch_event(self.id, crate::ui::WidgetEvent::Scroll(*delta_x, *delta_y));
380 }
381 }
382 }
383 self.ui.input_events = events;
384
385 let mut current_bg = self.bg;
387 let current_text = self.text_color;
388 let mut current_brightness = 1.0;
389
390 if is_pressed {
391 current_bg = self.active_bg.unwrap_or(self.bg);
392 current_brightness = 0.8;
393 } else if is_hovered {
394 current_bg = self.hover_bg.unwrap_or(self.bg);
395 current_brightness = self.hover_brightness;
396 }
397
398 let mut final_border_w = self.border_width;
399 let mut final_border_c = self.border_color;
400
401 if is_pressed {
402 if let Some(c) = self.active_border_color { final_border_c = c; }
403 if let Some(w) = self.active_border_width { final_border_w = w; }
404 } else if is_hovered {
405 if let Some(c) = self.hover_border_color { final_border_c = c; }
406 if let Some(w) = self.hover_border_width { final_border_w = w; }
407 }
408
409 let mut text_w = 0.0;
411 let mut text_h = 0.0;
412 if let Some(fs) = self.ui.font_system.as_ref() {
413 let mut adapter =
415 zenthra_text::prelude::CosmicFontProvider::new_with_system(fs.clone());
416 let mut options = zenthra_text::prelude::TextOptions::new()
417 .font_size(self.font_size)
418 .wrap(self.wrap);
419 if let Some(ref family) = self.font_family {
420 options = options.font_family(family.clone());
421 }
422 let buffer = adapter.shape(&self.label, &options);
423 let (cw, ch) = buffer.content_size();
424 text_w = cw;
425 text_h = ch;
426 }
427
428 let mut final_w = if self.fill_x {
429 self.ui.available_width
430 } else {
431 self.width.unwrap_or(text_w + self.padding.horizontal())
432 };
433 let mut final_h = self.height.unwrap_or(text_h + self.padding.vertical());
434
435 let final_border_w = final_border_w;
436 let final_border_c = final_border_c;
437
438 if is_hovered {
439 final_w *= self.hover_scale;
440 final_h *= self.hover_scale;
441 }
442
443 let start_draw = self.ui.draws.len();
444
445 self.ui.draws.push(DrawCommand::Rect(RectDraw {
447 instance: RectInstance {
448 pos: [x, y],
449 size: [final_w, final_h],
450 color: current_bg.to_array(),
451 radius: [
452 self.radius[3],
453 self.radius[2],
454 self.radius[1],
455 self.radius[0],
456 ],
457 border_width: final_border_w,
458 border_color: final_border_c.to_array(),
459 shadow_color: {
460 let mut c = self.shadow_color;
461 c.a *= self.shadow_opacity;
462 c.to_array()
463 },
464 shadow_offset: self.shadow_offset,
465 shadow_blur: self.shadow_blur,
466 clip_rect: [0.0, 0.0, 9999.0, 9999.0],
467 grayscale: 0.0,
468 brightness: current_brightness,
469 opacity: self.opacity,
470 ..Default::default()
471 },
472 }));
473
474 let tx = match self.align {
476 zenthra_core::Align::Left => x + self.padding.left,
477 zenthra_core::Align::Right => x + final_w - text_w - self.padding.right,
478 _ => x + (final_w - text_w) / 2.0,
479 };
480 let ty = y + (final_h - text_h) / 2.0;
481
482 let mut text_opts = zenthra_text::prelude::TextOptions::new()
483 .font_size(self.font_size)
484 .color(current_text)
485 .wrap(self.wrap)
486 .at(tx, ty);
487 if let Some(ref family) = self.font_family {
488 text_opts = text_opts.font_family(family.clone());
489 }
490
491 self.ui.draws.push(DrawCommand::Text(TextDraw {
492 text: self.label.clone(),
493 pos: [tx, ty],
494 options: text_opts,
495 clip: [0.0, 0.0, 9999.0, 9999.0],
496 }));
497
498 self.ui.register_semantic(
500 SemanticNode::new(self.id, Role::Button, Rect::new(x, y, final_w, final_h))
501 .with_label(self.label.clone()),
502 );
503
504 self.ui
505 .record_layout(self.id, Rect::new(x, y, final_w, final_h));
506 self.ui.advance(final_w, final_h, start_draw);
507
508 if self.render_mode.is_some() {
509 self.ui.render_mode_stack.pop();
510 }
511
512 zenthra_core::Response {
513 clicked,
514 hovered: is_hovered,
515 pressed: is_pressed,
516 }
517 }
518}