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}