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