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}