Skip to main content

zenthra_widgets/
image.rs

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    // Sizing & Position
11    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    // Spacing
20    padding: EdgeInsets,
21    margin: EdgeInsets,
22
23    // Appearance (Idle State)
24    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    // Interaction Styling (Hover & Active States)
37    hover_opacity: Option<f32>,
38    hover_grayscale: Option<f32>,
39    hover_border: Option<Color>,
40    active_opacity: Option<f32>,
41
42    // Advanced Controls
43    internal_scale: [f32; 2],
44    internal_offset: [f32; 2],
45    rotation: [f32; 3], // (x, y, z)
46    flip_h: bool,
47    flip_v: bool,
48
49    // Framework Mechanics
50    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    // Padding
130    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    // Margin
166    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    // Appearance
198    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    // Advanced Controls
289    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    // Interaction Styling
335    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    // Framework
353    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        // Interaction state overrides
421        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                        // Behave like Contain
486                        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                        // Behave like None
497                        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], // Managed by containers
536                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        // Only advance cursor if not absolutely positioned
556        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}