1use crate::ui::{DrawCommand, ImageDraw, Ui};
2use zenthra_core::{Color, EdgeInsets, Id, Rect, Role, SemanticNode, ImageSource, ObjectFit};
3use zenthra_render::ImageInstance;
4
5pub struct ImageBuilder<'u, 'a> {
6 ui: &'u mut Ui<'a>,
7 id: Id,
8 source: ImageSource,
9
10 pos: Option<(f32, f32)>,
12 width: Option<f32>,
13 height: Option<f32>,
14 max_width: Option<f32>,
15 max_height: Option<f32>,
16 aspect_ratio: Option<f32>,
17 original_size: bool,
18
19 padding: EdgeInsets,
21 margin: EdgeInsets,
22
23 fit: ObjectFit,
25 radius: [f32; 4],
26 border_color: Color,
27 border_width: f32,
28 bg: Color,
29 opacity: f32,
30 grayscale: f32,
31 shadow_color: Color,
32 shadow_offset: [f32; 2],
33 shadow_blur: f32,
34 shadow_opacity: f32,
35
36 hover_opacity: Option<f32>,
38 hover_grayscale: Option<f32>,
39 hover_border: Option<Color>,
40 active_opacity: Option<f32>,
41
42 internal_scale: [f32; 2],
44 internal_offset: [f32; 2],
45 rotation: [f32; 3], flip_h: bool,
47 flip_v: bool,
48
49 cursor: crate::text::CursorIcon,
51 render_mode: Option<zenthra_core::RenderMode>,
52}
53
54impl<'u, 'a> ImageBuilder<'u, 'a> {
55 pub fn new(ui: &'u mut Ui<'a>, source: ImageSource) -> Self {
56 let id = ui.id();
57 Self {
58 ui,
59 id,
60 source,
61 pos: None,
62 width: None,
63 height: None,
64 max_width: None,
65 max_height: None,
66 padding: EdgeInsets::ZERO,
67 margin: EdgeInsets::ZERO,
68 fit: ObjectFit::Contain,
69 radius: [0.0; 4],
70 border_color: Color::TRANSPARENT,
71 border_width: 0.0,
72 bg: Color::TRANSPARENT,
73 opacity: 1.0,
74 grayscale: 0.0,
75 shadow_color: Color::TRANSPARENT,
76 shadow_offset: [0.0, 0.0],
77 shadow_blur: 0.0,
78 shadow_opacity: 1.0,
79 hover_opacity: None,
80 hover_grayscale: None,
81 hover_border: None,
82 active_opacity: None,
83 aspect_ratio: None,
84 original_size: false,
85 internal_scale: [1.0, 1.0],
86 internal_offset: [0.0; 2],
87 rotation: [0.0; 3],
88 flip_h: false,
89 flip_v: false,
90 cursor: crate::text::CursorIcon::Default,
91 render_mode: None,
92 }
93 }
94
95 pub fn pos(mut self, x: f32, y: f32) -> Self {
96 self.pos = Some((x, y));
97 self
98 }
99 pub fn width(mut self, w: f32) -> Self {
100 self.width = Some(w);
101 self
102 }
103 pub fn height(mut self, h: f32) -> Self {
104 self.height = Some(h);
105 self
106 }
107 pub fn size(mut self, w: f32, h: f32) -> Self {
108 self.width = Some(w);
109 self.height = Some(h);
110 self
111 }
112 pub fn max_width(mut self, w: f32) -> Self {
113 self.max_width = Some(w);
114 self
115 }
116 pub fn max_height(mut self, h: f32) -> Self {
117 self.max_height = Some(h);
118 self
119 }
120 pub fn aspect_ratio(mut self, ratio: f32) -> Self {
121 self.aspect_ratio = Some(ratio);
122 self
123 }
124 pub fn original_size(mut self) -> Self {
125 self.original_size = true;
126 self
127 }
128
129 pub fn padding(mut self, t: f32, r: f32, b: f32, l: f32) -> Self {
131 self.padding = EdgeInsets { top: t, right: r, bottom: b, left: l };
132 self
133 }
134 pub fn padding_all(mut self, p: f32) -> Self {
135 self.padding = EdgeInsets { top: p, right: p, bottom: p, left: p };
136 self
137 }
138 pub fn padding_x(mut self, p: f32) -> Self {
139 self.padding.left = p;
140 self.padding.right = p;
141 self
142 }
143 pub fn padding_y(mut self, p: f32) -> Self {
144 self.padding.top = p;
145 self.padding.bottom = p;
146 self
147 }
148 pub fn padding_top(mut self, p: f32) -> Self {
149 self.padding.top = p;
150 self
151 }
152 pub fn padding_bottom(mut self, p: f32) -> Self {
153 self.padding.bottom = p;
154 self
155 }
156 pub fn padding_left(mut self, p: f32) -> Self {
157 self.padding.left = p;
158 self
159 }
160 pub fn padding_right(mut self, p: f32) -> Self {
161 self.padding.right = p;
162 self
163 }
164
165 pub fn margin(mut self, t: f32, r: f32, b: f32, l: f32) -> Self {
167 self.margin = EdgeInsets { top: t, right: r, bottom: b, left: l };
168 self
169 }
170 pub fn margin_x(mut self, m: f32) -> Self {
171 self.margin.left = m;
172 self.margin.right = m;
173 self
174 }
175 pub fn margin_y(mut self, m: f32) -> Self {
176 self.margin.top = m;
177 self.margin.bottom = m;
178 self
179 }
180 pub fn margin_top(mut self, m: f32) -> Self {
181 self.margin.top = m;
182 self
183 }
184 pub fn margin_bottom(mut self, m: f32) -> Self {
185 self.margin.bottom = m;
186 self
187 }
188 pub fn margin_left(mut self, m: f32) -> Self {
189 self.margin.left = m;
190 self
191 }
192 pub fn margin_right(mut self, m: f32) -> Self {
193 self.margin.right = m;
194 self
195 }
196
197 pub fn fit(mut self, fit: ObjectFit) -> Self {
199 self.fit = fit;
200 self
201 }
202 pub fn border_radius(mut self, radius: f32) -> Self {
203 self.radius = [radius; 4] ;
204 self
205 }
206
207 pub fn radius(mut self, tl: f32, tr: f32, br: f32, bl: f32) -> Self {
208 self.radius = [tl, tr, br, bl];
209 self
210 }
211
212 pub fn radius_all(mut self, r: f32) -> Self {
213 self.radius = [r; 4];
214 self
215 }
216
217 pub fn radius_top(mut self, r: f32) -> Self {
218 self.radius[0] = r;
219 self.radius[1] = r;
220 self
221 }
222
223 pub fn radius_bottom(mut self, r: f32) -> Self {
224 self.radius[2] = r;
225 self.radius[3] = r;
226 self
227 }
228
229 pub fn radius_top_left(mut self, r: f32) -> Self {
230 self.radius[0] = r;
231 self
232 }
233
234 pub fn radius_top_right(mut self, r: f32) -> Self {
235 self.radius[1] = r;
236 self
237 }
238
239 pub fn radius_bottom_right(mut self, r: f32) -> Self {
240 self.radius[2] = r;
241 self
242 }
243
244 pub fn radius_bottom_left(mut self, r: f32) -> Self {
245 self.radius[3] = r;
246 self
247 }
248
249 pub fn radius_left(mut self, r: f32) -> Self {
250 self.radius[0] = r;
251 self.radius[3] = r;
252 self
253 }
254
255 pub fn radius_right(mut self, r: f32) -> Self {
256 self.radius[1] = r;
257 self.radius[2] = r;
258 self
259 }
260 pub fn border(mut self, color: Color, width: f32) -> Self {
261 self.border_color = color;
262 self.border_width = width;
263 self
264 }
265 pub fn bg(mut self, color: Color) -> Self {
266 self.bg = color;
267 self
268 }
269 pub fn opacity(mut self, alpha: f32) -> Self {
270 self.opacity = alpha;
271 self
272 }
273 pub fn grayscale(mut self, amount: f32) -> Self {
274 self.grayscale = amount;
275 self
276 }
277 pub fn shadow(mut self, color: Color, x: f32, y: f32, blur: f32) -> Self {
278 self.shadow_color = color;
279 self.shadow_offset = [x, y];
280 self.shadow_blur = blur;
281 self
282 }
283 pub fn shadow_opacity(mut self, opacity: f32) -> Self {
284 self.shadow_opacity = opacity;
285 self
286 }
287
288 pub fn scale(mut self, s: f32) -> Self {
290 self.internal_scale = [s, s];
291 self
292 }
293 pub fn scale_x(mut self, x: f32) -> Self {
294 self.internal_scale[0] = x;
295 self
296 }
297 pub fn scale_y(mut self, y: f32) -> Self {
298 self.internal_scale[1] = y;
299 self
300 }
301 pub fn zoom(mut self, z: f32) -> Self {
302 self.internal_scale = [z, z];
303 self
304 }
305 pub fn offset(mut self, x: f32, y: f32) -> Self {
306 self.internal_offset = [x, y];
307 self
308 }
309 pub fn rotate(mut self, x: f32, y: f32, z: f32) -> Self {
310 self.rotation = [x.to_radians(), y.to_radians(), z.to_radians()];
311 self
312 }
313 pub fn rotate_x(mut self, x: f32) -> Self {
314 self.rotation[0] = x.to_radians();
315 self
316 }
317 pub fn rotate_y(mut self, y: f32) -> Self {
318 self.rotation[1] = y.to_radians();
319 self
320 }
321 pub fn rotate_z(mut self, z: f32) -> Self {
322 self.rotation[2] = z.to_radians();
323 self
324 }
325 pub fn flip_h(mut self, flip: bool) -> Self {
326 self.flip_h = flip;
327 self
328 }
329 pub fn flip_v(mut self, flip: bool) -> Self {
330 self.flip_v = flip;
331 self
332 }
333
334 pub fn hover_opacity(mut self, alpha: f32) -> Self {
336 self.hover_opacity = Some(alpha);
337 self
338 }
339 pub fn hover_grayscale(mut self, amount: f32) -> Self {
340 self.hover_grayscale = Some(amount);
341 self
342 }
343 pub fn hover_border(mut self, color: Color) -> Self {
344 self.hover_border = Some(color);
345 self
346 }
347 pub fn active_opacity(mut self, alpha: f32) -> Self {
348 self.active_opacity = Some(alpha);
349 self
350 }
351
352 pub fn id(mut self, id: impl std::hash::Hash) -> Self {
354 let mut hasher = std::collections::hash_map::DefaultHasher::new();
355 use std::hash::{Hash, Hasher};
356 id.hash(&mut hasher);
357 if let Some(parent) = self.ui.semantic_stack.last() {
358 parent.hash(&mut hasher);
359 }
360 self.id = zenthra_core::Id::from_u64(hasher.finish());
361 self
362 }
363 pub fn cursor(mut self, c: crate::text::CursorIcon) -> Self {
364 self.cursor = c;
365 self
366 }
367 pub fn continuous(mut self) -> Self {
368 self.render_mode = Some(zenthra_core::RenderMode::Continuous);
369 self
370 }
371 pub fn static_mode(mut self) -> Self {
372 self.render_mode = Some(zenthra_core::RenderMode::Static);
373 self
374 }
375
376 pub fn show(self) -> zenthra_core::Response {
377 if let Some(mode) = self.render_mode {
378 self.ui.render_mode_stack.push(mode);
379 }
380
381 let start_x = self.pos.map(|p| p.0).unwrap_or(self.ui.cursor_x);
382 let start_y = self.pos.map(|p| p.1).unwrap_or(self.ui.cursor_y);
383
384 let (orig_w, orig_h) = self.ui.image_sizes.get(&self.source)
385 .map(|(w, h)| (*w as f32, *h as f32))
386 .unwrap_or((100.0, 100.0));
387
388 let ratio = self.aspect_ratio.unwrap_or(orig_w / orig_h);
389
390 let (final_w, final_h) = if self.original_size {
391 (orig_w, orig_h)
392 } else {
393 match (self.width, self.height) {
394 (Some(w), Some(h)) => (w, h),
395 (Some(w), None) => (w, w / ratio),
396 (None, Some(h)) => (h * ratio, h),
397 (None, None) => {
398 if orig_w > self.ui.available_width && self.ui.available_width > 0.0 {
399 (self.ui.available_width, self.ui.available_width / ratio)
400 } else {
401 (orig_w, orig_h)
402 }
403 }
404 }
405 };
406
407 let w = if let Some(mw) = self.max_width { final_w.min(mw) } else { final_w };
408 let h = if let Some(mh) = self.max_height { final_h.min(mh) } else { final_h };
409
410 let draw_start = self.ui.draws.len();
411
412 let local_x = start_x + self.margin.left;
413 let local_y = start_y + self.margin.top;
414
415 let is_hovered = self.ui.is_hovered(self.id, local_x + self.ui.offset_x, local_y + self.ui.offset_y, w, h);
416
417 let is_pressed = is_hovered && self.ui.mouse_down;
418 let clicked = self.ui.clicked && is_hovered;
419
420 let current_opacity = if is_pressed {
422 self.active_opacity.unwrap_or(self.opacity)
423 } else if is_hovered {
424 self.hover_opacity.unwrap_or(self.opacity)
425 } else {
426 self.opacity
427 };
428
429 let current_grayscale = if is_hovered && !is_pressed {
430 self.hover_grayscale.unwrap_or(self.grayscale)
431 } else {
432 self.grayscale
433 };
434
435 let current_border_color = if is_hovered && !is_pressed {
436 self.hover_border.unwrap_or(self.border_color)
437 } else {
438 self.border_color
439 };
440
441 let shadow_color_arr = {
442 let mut c = self.shadow_color;
443 c.a *= self.shadow_opacity;
444 c.to_array()
445 };
446
447 let uv_rect = if w <= 0.0 || h <= 0.0 || orig_w <= 0.0 || orig_h <= 0.0 {
448 [0.0, 0.0, 1.0, 1.0]
449 } else {
450 let r_t = orig_w / orig_h;
451 let r_w = w / h;
452 let base_uv = match self.fit {
453 ObjectFit::Fill => [0.0, 0.0, 1.0, 1.0],
454 ObjectFit::Cover => {
455 if r_t > r_w {
456 let u_size = r_w / r_t;
457 let u_start = (1.0 - u_size) / 2.0;
458 [u_start, 0.0, u_size, 1.0]
459 } else {
460 let v_size = r_t / r_w;
461 let v_start = (1.0 - v_size) / 2.0;
462 [0.0, v_start, 1.0, v_size]
463 }
464 }
465 ObjectFit::Contain => {
466 if r_t > r_w {
467 let v_size = r_t / r_w;
468 let v_start = -0.5 * (v_size - 1.0);
469 [0.0, v_start, 1.0, v_size]
470 } else {
471 let u_size = r_w / r_t;
472 let u_start = -0.5 * (u_size - 1.0);
473 [u_start, 0.0, u_size, 1.0]
474 }
475 }
476 ObjectFit::None => {
477 let u_size = w / orig_w;
478 let u_start = -0.5 * (u_size - 1.0);
479 let v_size = h / orig_h;
480 let v_start = -0.5 * (v_size - 1.0);
481 [u_start, v_start, u_size, v_size]
482 }
483 ObjectFit::ScaleDown => {
484 if orig_w > w || orig_h > h {
485 if r_t > r_w {
487 let v_size = r_t / r_w;
488 let v_start = -0.5 * (v_size - 1.0);
489 [0.0, v_start, 1.0, v_size]
490 } else {
491 let u_size = r_w / r_t;
492 let u_start = -0.5 * (u_size - 1.0);
493 [u_start, 0.0, u_size, 1.0]
494 }
495 } else {
496 let u_size = w / orig_w;
498 let u_start = -0.5 * (u_size - 1.0);
499 let v_size = h / orig_h;
500 let v_start = -0.5 * (v_size - 1.0);
501 [u_start, v_start, u_size, v_size]
502 }
503 }
504 };
505
506 let scale_x = self.internal_scale[0].max(0.001);
507 let scale_y = self.internal_scale[1].max(0.001);
508
509 let new_u_size = base_uv[2] / scale_x;
510 let new_v_size = base_uv[3] / scale_y;
511
512 let new_u_start = base_uv[0] + (base_uv[2] - new_u_size) / 2.0;
513 let new_v_start = base_uv[1] + (base_uv[3] - new_v_size) / 2.0;
514
515 let final_u_start = new_u_start - (self.internal_offset[0] / w) * new_u_size;
516 let final_v_start = new_v_start - (self.internal_offset[1] / h) * new_v_size;
517
518 [final_u_start, final_v_start, new_u_size, new_v_size]
519 };
520
521 self.ui.draws.push(DrawCommand::Image(ImageDraw {
522 source: self.source.clone(),
523 fit: self.fit,
524 internal_scale: self.internal_scale,
525 internal_offset: self.internal_offset,
526 instance: ImageInstance {
527 pos: [local_x, local_y],
528 size: [w, h],
529 radius: self.radius,
530 border_width: self.border_width,
531 border_color: current_border_color.to_array(),
532 shadow_color: shadow_color_arr,
533 shadow_offset: self.shadow_offset,
534 shadow_blur: self.shadow_blur,
535 clip_rect: [0.0, 0.0, 9999.0, 9999.0], grayscale: current_grayscale,
537 brightness: 1.0,
538 opacity: current_opacity,
539 uv_rect,
540 bg_color: self.bg.to_array(),
541 rotation: self.rotation,
542 flip: [if self.flip_h { -1.0 } else { 1.0 }, if self.flip_v { -1.0 } else { 1.0 }],
543 },
544 }));
545
546 let total_w = w + self.margin.horizontal();
547 let total_h = h + self.margin.vertical();
548
549 self.ui.register_semantic(
550 SemanticNode::new(self.id, Role::Image, Rect::new(start_x, start_y, total_w, total_h))
551 );
552
553 self.ui.record_layout(self.id, Rect::new(start_x, start_y, total_w, total_h));
554
555 if self.pos.is_none() {
557 self.ui.advance(total_w, total_h, draw_start);
558 }
559
560 if self.render_mode.is_some() {
561 self.ui.render_mode_stack.pop();
562 }
563
564 zenthra_core::Response {
565 clicked,
566 hovered: is_hovered,
567 pressed: is_pressed,
568 }
569 }
570}