Skip to main content

uzor_render/
context.rs

1//! Platform-agnostic rendering context trait
2//!
3//! This module provides the `RenderContext` trait that abstracts away
4//! platform-specific rendering.
5//!
6//! Applications implement this trait to provide rendering capabilities.
7
8use crate::types::{TextAlign, TextBaseline};
9
10/// Platform-agnostic rendering context
11///
12/// Applications implement this trait to provide rendering capabilities
13/// to primitives, charts, and widgets.
14pub trait RenderContext {
15    // =========================================================================
16    // Dimensions
17    // =========================================================================
18
19    /// Device pixel ratio for crisp rendering
20    fn dpr(&self) -> f64;
21
22    // =========================================================================
23    // Stroke Style
24    // =========================================================================
25
26    /// Set stroke color (hex string like "#RRGGBB" or "#RRGGBBAA")
27    fn set_stroke_color(&mut self, color: &str);
28
29    /// Set stroke width in pixels
30    fn set_stroke_width(&mut self, width: f64);
31
32    /// Set line dash pattern (empty for solid)
33    fn set_line_dash(&mut self, pattern: &[f64]);
34
35    /// Set line cap style ("butt", "round", "square")
36    fn set_line_cap(&mut self, cap: &str);
37
38    /// Set line join style ("miter", "round", "bevel")
39    fn set_line_join(&mut self, join: &str);
40
41    // =========================================================================
42    // Fill Style
43    // =========================================================================
44
45    /// Set fill color (hex string)
46    fn set_fill_color(&mut self, color: &str);
47
48    /// Set fill color with alpha transparency
49    /// Default implementation uses set_fill_color + set_global_alpha
50    fn set_fill_color_alpha(&mut self, color: &str, alpha: f64) {
51        self.set_fill_color(color);
52        self.set_global_alpha(alpha.clamp(0.0, 1.0));
53    }
54
55    /// Set global alpha (transparency)
56    fn set_global_alpha(&mut self, alpha: f64);
57
58    /// Reset global alpha to 1.0
59    fn reset_alpha(&mut self) {
60        self.set_global_alpha(1.0);
61    }
62
63    // =========================================================================
64    // Path Operations
65    // =========================================================================
66
67    /// Begin a new path
68    fn begin_path(&mut self);
69
70    /// Move to point (without drawing)
71    fn move_to(&mut self, x: f64, y: f64);
72
73    /// Draw line to point
74    fn line_to(&mut self, x: f64, y: f64);
75
76    /// Close the current path
77    fn close_path(&mut self);
78
79    /// Add rectangle to current path (without stroking or filling)
80    fn rect(&mut self, x: f64, y: f64, w: f64, h: f64);
81
82    /// Draw arc (for partial circles)
83    fn arc(&mut self, cx: f64, cy: f64, radius: f64, start_angle: f64, end_angle: f64);
84
85    /// Draw ellipse (center, radii, rotation, start_angle, end_angle)
86    fn ellipse(
87        &mut self,
88        cx: f64,
89        cy: f64,
90        rx: f64,
91        ry: f64,
92        rotation: f64,
93        start: f64,
94        end: f64,
95    );
96
97    /// Quadratic bezier curve
98    fn quadratic_curve_to(&mut self, cpx: f64, cpy: f64, x: f64, y: f64);
99
100    /// Cubic bezier curve
101    fn bezier_curve_to(
102        &mut self,
103        cp1x: f64,
104        cp1y: f64,
105        cp2x: f64,
106        cp2y: f64,
107        x: f64,
108        y: f64,
109    );
110
111    // =========================================================================
112    // Stroke/Fill Operations
113    // =========================================================================
114
115    /// Stroke the current path
116    fn stroke(&mut self);
117
118    /// Fill the current path
119    fn fill(&mut self);
120
121    /// Clip to the current path
122    fn clip(&mut self);
123
124    /// Set a clipping rectangle. All subsequent drawing operations will be clipped to this rect.
125    /// This is a convenience method that creates a rect path and applies it as a clip.
126    /// Must be called within save/restore to limit the clip scope.
127    fn clip_rect(&mut self, x: f64, y: f64, width: f64, height: f64) {
128        self.begin_path();
129        self.rect(x, y, width, height);
130        self.clip();
131    }
132
133    // =========================================================================
134    // Shape Helpers (convenience methods)
135    // =========================================================================
136
137    /// Stroke a rectangle
138    fn stroke_rect(&mut self, x: f64, y: f64, w: f64, h: f64);
139
140    /// Fill a rectangle
141    fn fill_rect(&mut self, x: f64, y: f64, w: f64, h: f64);
142
143    /// Fill a rounded rectangle (convenience method with default impl)
144    fn fill_rounded_rect(&mut self, x: f64, y: f64, w: f64, h: f64, radius: f64) {
145        self.begin_path();
146        self.rounded_rect(x, y, w, h, radius);
147        self.fill();
148    }
149
150    /// Stroke a rounded rectangle
151    fn stroke_rounded_rect(&mut self, x: f64, y: f64, w: f64, h: f64, radius: f64) {
152        self.begin_path();
153        self.rounded_rect(x, y, w, h, radius);
154        self.stroke();
155    }
156
157    /// Add rounded rectangle to path (default impl using arcs)
158    fn rounded_rect(&mut self, x: f64, y: f64, w: f64, h: f64, r: f64) {
159        let r = r.min(w / 2.0).min(h / 2.0);
160        self.move_to(x + r, y);
161        self.line_to(x + w - r, y);
162        self.arc(x + w - r, y + r, r, -std::f64::consts::FRAC_PI_2, 0.0);
163        self.line_to(x + w, y + h - r);
164        self.arc(
165            x + w - r,
166            y + h - r,
167            r,
168            0.0,
169            std::f64::consts::FRAC_PI_2,
170        );
171        self.line_to(x + r, y + h);
172        self.arc(
173            x + r,
174            y + h - r,
175            r,
176            std::f64::consts::FRAC_PI_2,
177            std::f64::consts::PI,
178        );
179        self.line_to(x, y + r);
180        self.arc(
181            x + r,
182            y + r,
183            r,
184            std::f64::consts::PI,
185            std::f64::consts::PI * 1.5,
186        );
187    }
188
189    // =========================================================================
190    // Text Rendering
191    // =========================================================================
192
193    /// Set font (CSS-style: "14px sans-serif" or "bold 16px monospace")
194    fn set_font(&mut self, font: &str);
195
196    /// Set text horizontal alignment
197    fn set_text_align(&mut self, align: TextAlign);
198
199    /// Set text vertical baseline
200    fn set_text_baseline(&mut self, baseline: TextBaseline);
201
202    /// Fill text at position
203    fn fill_text(&mut self, text: &str, x: f64, y: f64);
204
205    /// Stroke text at position
206    fn stroke_text(&mut self, text: &str, x: f64, y: f64);
207
208    /// Measure text width
209    fn measure_text(&self, text: &str) -> f64;
210
211    /// Fill text with rotation around the anchor point.
212    /// Default implementation uses save/translate/rotate/fill_text/restore.
213    fn fill_text_rotated(&mut self, text: &str, x: f64, y: f64, angle: f64) {
214        if angle.abs() < 0.001 {
215            self.fill_text(text, x, y);
216        } else {
217            self.save();
218            self.translate(x, y);
219            self.rotate(angle);
220            self.fill_text(text, 0.0, 0.0);
221            self.restore();
222        }
223    }
224
225    /// Fill text centered at position.
226    /// Default implementation sets alignment and baseline then calls fill_text.
227    fn fill_text_centered(&mut self, text: &str, x: f64, y: f64) {
228        self.set_text_align(TextAlign::Center);
229        self.set_text_baseline(TextBaseline::Middle);
230        self.fill_text(text, x, y);
231    }
232
233    // =========================================================================
234    // Transform Operations
235    // =========================================================================
236
237    /// Save current state (transforms, styles)
238    fn save(&mut self);
239
240    /// Restore previously saved state
241    fn restore(&mut self);
242
243    /// Translate origin
244    fn translate(&mut self, x: f64, y: f64);
245
246    /// Rotate around origin
247    fn rotate(&mut self, angle: f64);
248
249    /// Scale from origin
250    fn scale(&mut self, x: f64, y: f64);
251
252    // =========================================================================
253    // Images
254    // =========================================================================
255
256    /// Draw an image at the specified position
257    ///
258    /// # Arguments
259    /// * `image_id` - Unique identifier for the cached image (URL or data URI)
260    /// * `x`, `y` - Top-left corner position
261    /// * `width`, `height` - Dimensions to draw the image
262    ///
263    /// Returns true if the image was drawn, false if not yet loaded/cached.
264    fn draw_image(&mut self, image_id: &str, x: f64, y: f64, width: f64, height: f64) -> bool {
265        let _ = (image_id, x, y, width, height);
266        false
267    }
268
269    /// Draw raw RGBA pixel data as an image
270    ///
271    /// # Arguments
272    /// * `data` - RGBA pixel data (4 bytes per pixel, row-major, top-to-bottom)
273    /// * `img_width`, `img_height` - Source image dimensions in pixels
274    /// * `x`, `y` - Top-left corner position on canvas
275    /// * `width`, `height` - Target dimensions to draw (stretches/shrinks to fit)
276    ///
277    /// Default implementation does nothing. Override in platform-specific contexts.
278    fn draw_image_rgba(
279        &mut self,
280        data: &[u8],
281        img_width: u32,
282        img_height: u32,
283        x: f64,
284        y: f64,
285        width: f64,
286        height: f64,
287    ) {
288        let _ = (data, img_width, img_height, x, y, width, height);
289    }
290
291    // =========================================================================
292    // Blur Background (FrostedGlass/LiquidGlass effects)
293    // =========================================================================
294
295    /// Draw blurred background for UI elements (FrostedGlass/LiquidGlass effects)
296    ///
297    /// When a blur-enabled style is active, this draws a clipped portion of the
298    /// blurred chart texture as background for UI elements like toolbars, sidebars, modals.
299    ///
300    /// # Arguments
301    /// * `x`, `y` - Top-left corner position
302    /// * `width`, `height` - Dimensions of the blur region
303    ///
304    /// Default implementation does nothing. Override in platform-specific contexts
305    /// that support blur effects (e.g., VelloGpuRenderContext).
306    fn draw_blur_background(&mut self, x: f64, y: f64, width: f64, height: f64) {
307        let _ = (x, y, width, height);
308    }
309
310    /// Check if blur background is available
311    ///
312    /// Returns true if blur image is set and ready for drawing.
313    /// Use this to conditionally draw blur backgrounds only when supported.
314    fn has_blur_background(&self) -> bool {
315        false
316    }
317
318    /// Check if 3D convex glass buttons should be used
319    ///
320    /// Returns true if blur is active AND convex glass button style is enabled.
321    fn use_convex_glass_buttons(&self) -> bool {
322        false
323    }
324
325    // =========================================================================
326    // UI State Rectangles (Hover/Active)
327    // =========================================================================
328
329    /// Draw a hover state rectangle
330    ///
331    /// Centralized rendering for hover states on UI elements.
332    /// For FrostedGlass/LiquidGlass with Convex3D style, uses 3D glass button effect.
333    ///
334    /// # Arguments
335    /// * `x`, `y` - Top-left corner position
336    /// * `width`, `height` - Dimensions of the rectangle
337    /// * `color` - Fill color (should be styled via theme.hover_bg_styled())
338    fn draw_hover_rect(&mut self, x: f64, y: f64, width: f64, height: f64, color: &str) {
339        if self.use_convex_glass_buttons() {
340            // 3D convex glass button effect with theme color
341            self.draw_glass_button_3d(x, y, width, height, 2.0, false, color);
342        } else if self.has_blur_background() {
343            // Flat glass: blur + color overlay
344            self.draw_blur_background(x, y, width, height);
345            self.set_fill_color(color);
346            self.fill_rect(x, y, width, height);
347        } else {
348            // Solid: just color
349            self.set_fill_color(color);
350            self.fill_rect(x, y, width, height);
351        }
352    }
353
354    /// Draw an active state rectangle
355    ///
356    /// Centralized rendering for active/pressed states on UI elements.
357    /// For FrostedGlass/LiquidGlass with Convex3D style, uses 3D glass button effect.
358    ///
359    /// # Arguments
360    /// * `x`, `y` - Top-left corner position
361    /// * `width`, `height` - Dimensions of the rectangle
362    /// * `color` - Fill color (should be styled via theme.active_bg_styled())
363    fn draw_active_rect(&mut self, x: f64, y: f64, width: f64, height: f64, color: &str) {
364        if self.use_convex_glass_buttons() {
365            // 3D convex glass button effect (pressed) with theme color
366            self.draw_glass_button_3d(x, y, width, height, 2.0, true, color);
367        } else if self.has_blur_background() {
368            // Flat glass: blur + color overlay
369            self.draw_blur_background(x, y, width, height);
370            self.set_fill_color(color);
371            self.fill_rect(x, y, width, height);
372        } else {
373            // Solid: just color
374            self.set_fill_color(color);
375            self.fill_rect(x, y, width, height);
376        }
377    }
378
379    /// Draw a hover state rounded rectangle
380    ///
381    /// Centralized rendering for hover states on toolbar buttons.
382    /// For FrostedGlass/LiquidGlass with Convex3D style, uses 3D glass button effect.
383    ///
384    /// # Arguments
385    /// * `x`, `y` - Top-left corner position
386    /// * `width`, `height` - Dimensions of the rectangle
387    /// * `radius` - Corner radius
388    /// * `color` - Fill color (should be styled via theme.hover_bg_styled())
389    fn draw_hover_rounded_rect(
390        &mut self,
391        x: f64,
392        y: f64,
393        width: f64,
394        height: f64,
395        radius: f64,
396        color: &str,
397    ) {
398        if self.use_convex_glass_buttons() {
399            // 3D convex glass button effect with theme color
400            self.draw_glass_button_3d(x, y, width, height, radius, false, color);
401        } else if self.has_blur_background() {
402            // Flat glass: blur + color overlay
403            self.draw_blur_background(x, y, width, height);
404            self.set_fill_color(color);
405            self.fill_rounded_rect(x, y, width, height, radius);
406        } else {
407            // Solid: just color
408            self.set_fill_color(color);
409            self.fill_rounded_rect(x, y, width, height, radius);
410        }
411    }
412
413    /// Draw an active state rounded rectangle
414    ///
415    /// Centralized rendering for active/pressed states on toolbar buttons.
416    /// For FrostedGlass/LiquidGlass with Convex3D style, uses 3D glass button effect.
417    ///
418    /// # Arguments
419    /// * `x`, `y` - Top-left corner position
420    /// * `width`, `height` - Dimensions of the rectangle
421    /// * `radius` - Corner radius
422    /// * `color` - Fill color (should be styled via theme.active_bg_styled())
423    fn draw_active_rounded_rect(
424        &mut self,
425        x: f64,
426        y: f64,
427        width: f64,
428        height: f64,
429        radius: f64,
430        color: &str,
431    ) {
432        if self.use_convex_glass_buttons() {
433            // 3D convex glass button effect (pressed) with theme color
434            self.draw_glass_button_3d(x, y, width, height, radius, true, color);
435        } else if self.has_blur_background() {
436            // Flat glass: blur + color overlay
437            self.draw_blur_background(x, y, width, height);
438            self.set_fill_color(color);
439            self.fill_rounded_rect(x, y, width, height, radius);
440        } else {
441            // Solid: just color
442            self.set_fill_color(color);
443            self.fill_rounded_rect(x, y, width, height, radius);
444        }
445    }
446
447    /// Draw a sidebar hover item with vertical accent indicator
448    ///
449    /// Centralized rendering for hovered sidebar tabs/items.
450    /// Draws a vertical accent bar on the left + hover background.
451    /// Used for vertical toolbars in FrostedGlass/LiquidGlass styles.
452    ///
453    /// # Arguments
454    /// * `x`, `y` - Top-left corner position
455    /// * `width`, `height` - Dimensions of the item
456    /// * `accent_color` - Color for the left accent bar (theme.colors.accent)
457    /// * `bg_color` - Background color (theme styled hover color)
458    /// * `indicator_width` - Width of the accent bar (typically 3-4px)
459    fn draw_sidebar_hover_item(
460        &mut self,
461        x: f64,
462        y: f64,
463        width: f64,
464        height: f64,
465        accent_color: &str,
466        bg_color: &str,
467        indicator_width: f64,
468    ) {
469        // Draw accent indicator bar on the left
470        self.set_fill_color(accent_color);
471        self.fill_rect(x, y, indicator_width, height);
472
473        // Draw hover background (with blur for Glass styles)
474        self.draw_hover_rect(x + indicator_width, y, width - indicator_width, height, bg_color);
475    }
476
477    /// Draw a sidebar active item with vertical accent indicator
478    ///
479    /// Centralized rendering for active sidebar tabs/items.
480    /// Draws a vertical accent bar on the left + active background.
481    /// Used in modal sidebars (Chart Settings, Indicator Settings, Add Indicator, etc.)
482    ///
483    /// # Arguments
484    /// * `x`, `y` - Top-left corner position
485    /// * `width`, `height` - Dimensions of the item
486    /// * `accent_color` - Color for the left accent bar (theme.colors.accent)
487    /// * `bg_color` - Background color (theme styled active color)
488    /// * `indicator_width` - Width of the accent bar (typically 3-4px)
489    fn draw_sidebar_active_item(
490        &mut self,
491        x: f64,
492        y: f64,
493        width: f64,
494        height: f64,
495        accent_color: &str,
496        bg_color: &str,
497        indicator_width: f64,
498    ) {
499        // Draw accent indicator bar on the left
500        self.set_fill_color(accent_color);
501        self.fill_rect(x, y, indicator_width, height);
502
503        // Draw active background (with blur for Glass styles)
504        self.draw_active_rect(x + indicator_width, y, width - indicator_width, height, bg_color);
505    }
506
507    // =========================================================================
508    // 3D Glass Button Effects (FrostedGlass/LiquidGlass only)
509    // =========================================================================
510
511    /// Draw a 3D convex glass button effect
512    ///
513    /// Creates iOS-style raised glass button with:
514    /// - Blur background (backdrop)
515    /// - Theme color overlay (hover/active color from theme)
516    /// - Convex bulge effect (lighter top, darker bottom)
517    /// - Specular highlight (white stripe at top)
518    /// - Inner shadow (depth at edges)
519    /// - Fresnel rim lighting (subtle edge glow)
520    ///
521    /// Only has visual effect when blur background is available (Glass styles).
522    /// Falls back to simple hover/active rendering otherwise.
523    ///
524    /// # Arguments
525    /// * `x`, `y` - Top-left corner position
526    /// * `width`, `height` - Button dimensions
527    /// * `radius` - Corner radius
528    /// * `is_active` - true for pressed state (flattened bulge), false for hover
529    /// * `color` - Theme color for the button (hover_bg or active_bg)
530    fn draw_glass_button_3d(
531        &mut self,
532        x: f64,
533        y: f64,
534        width: f64,
535        height: f64,
536        radius: f64,
537        _is_active: bool,
538        color: &str,
539    ) {
540        // Default implementation: just draw blur background + color
541        // VelloGpuRenderContext overrides this with full 3D effect
542        self.draw_blur_background(x, y, width, height);
543        self.set_fill_color(color);
544        self.fill_rounded_rect(x, y, width, height, radius);
545    }
546
547}
548
549/// Extension trait for platform-specific blur features
550///
551/// Provides type-safe blur image management for RenderContext implementations.
552pub trait RenderContextExt: RenderContext {
553    type BlurImage: Clone;
554    fn set_blur_image(&mut self, _image: Option<Self::BlurImage>, _width: u32, _height: u32) {}
555    fn set_use_convex_glass_buttons(&mut self, _use_convex: bool) {}
556}