cvkg_core/renderer_trait.rs
1use crate::*;
2use crate::error_types::CvkgError;
3
4/// Vertical alignment of text within its bounding box.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum TextVAlign {
7 Top,
8 Middle,
9 Bottom,
10}
11
12/// Horizontal alignment of text within its bounding box.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum TextHAlign {
15 Left,
16 Center,
17 Right,
18}
19
20pub trait ElapsedTime {
21 /// Returns the cumulative time since the renderer started in seconds.
22 fn elapsed_time(&self) -> f32;
23
24 /// Returns the time elapsed since the last frame in seconds.
25 fn delta_time(&self) -> f32;
26}
27
28/// The Renderer trait defines the atomic drawing operations for all CVKG backends.
29/// This trait is object-safe and used by the View::render system.
30/// # Implementation Requirements
31/// 1. Coordinate system is origin-top-left (0,0) with Y increasing downwards.
32/// 2. Colors are [R, G, B, A] in the [0.0, 1.0] range.
33/// 3. All operations must be batchable by the underlying backend.
34///
35/// Sub-traits in `renderer/mod.rs` (RendererCore, RendererShapes, etc.) are
36/// capability markers. Backends implement the monolithic `Renderer` trait.
37/// The sub-traits exist so consumer code can depend on only the capability
38/// slice it needs (e.g., `fn render<R: RendererShapes>(shapes: R)`).
39/// Callback interface for renderer error reporting.
40///
41/// Backends override `on_render_error` to intercept non-fatal errors that occur
42/// during drawing operations. The default implementation logs the error.
43///
44/// Design note: `render()` stays infallible to avoid proliferating `Result`
45/// through the entire View trait hierarchy. Errors that cannot be recovered from
46/// within a draw call are routed through this trait method instead.
47pub trait RendererErrorHandler {
48 /// Called when a non-fatal render error occurs during a draw operation.
49 /// The renderer continues operating. Backends should log and optionally
50 /// track error counts for health monitoring.
51 fn on_render_error(&mut self, error: &CvkgError) {
52 tracing::error!("[RenderError] {error}");
53 }
54
55 /// Called when a fatal error occurs that prevents further rendering.
56 /// The backend should attempt graceful shutdown.
57 fn on_fatal_error(&mut self, error: &CvkgError) {
58 tracing::error!("[Fatal] {error}");
59 }
60
61 /// Returns true if the backend is in an error state.
62 fn has_error(&self) -> bool {
63 false
64 }
65}
66
67pub trait Renderer: ElapsedTime + Send + RendererErrorHandler {
68 /// Requests that the renderer redraws as soon as possible.
69 /// Used for continuous animations.
70 fn request_redraw(&mut self) {}
71
72 /// Returns true if the current frame is over the time budget.
73 /// This can be used to skip expensive visual effects.
74 fn is_over_budget(&self) -> bool {
75 false
76 }
77
78 // ── Filled shapes ────────────────────────────────────────────────────
79 fn fill_rect(&mut self, rect: Rect, color: [f32; 4]);
80 fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]);
81 /// Fill an ellipse/circle that fits inside `rect`.
82 fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]);
83
84 /// Draw a background image that fills the entire rect.
85 /// This is a convenience wrapper around `draw_image` for the common case
86 /// of a full-rect background. The image must have been pre-warmed via
87 /// `prewarm_vram` before the first frame.
88 fn draw_background_image(&mut self, image_name: &str, rect: Rect) {
89 Renderer::draw_image(self, image_name, rect);
90 }
91
92 /// Fill a rounded rect with glass material for frosted backdrop effect.
93 /// This is the proper way to render glass cards for macOS Tahoe-style blur.
94 /// The blur_radius controls the intensity of the backdrop blur.
95 fn fill_glass_rect(&mut self, rect: Rect, radius: f32, blur_radius: f32) {
96 // Default no-op implementation; GPU backend overrides
97 let _ = (rect, radius, blur_radius);
98 }
99 /// Fill a rounded rect with glass material with explicit intensity control.
100 /// `glass_intensity` ranges from 0.0 (solid) to 1.0 (full glass). Default: 1.0.
101 fn fill_glass_rect_with_intensity(
102 &mut self,
103 rect: Rect,
104 radius: f32,
105 blur_radius: f32,
106 glass_intensity: f32,
107 ) {
108 let _ = (rect, radius, blur_radius, glass_intensity);
109 }
110 /// Fill a rounded rect with glass material with explicit tint color and intensity.
111 /// `tint_color` is the glass fill color (RGBA). `glass_intensity` ranges from 0.0 (solid) to 1.0 (full glass).
112 fn fill_glass_rect_with_tint(
113 &mut self,
114 rect: Rect,
115 radius: f32,
116 blur_radius: f32,
117 tint_color: [f32; 4],
118 glass_intensity: f32,
119 ) {
120 // Default: delegate to intensity-only version using tint color as a simple fill
121 let _ = (rect, radius, blur_radius, tint_color, glass_intensity);
122 }
123 /// Fill a rounded rect with glass material, modulated by touch pressure.
124 /// `pressure` ranges from 0.0 (no touch) to 1.0 (full pressure).
125 /// When pressure > 0, refraction distortion is scaled by pressure amount.
126 /// Desktop stub: pressure is always 1.0 for mouse clicks, 0.0 otherwise.
127 fn fill_glass_rect_with_pressure(
128 &mut self,
129 rect: Rect,
130 radius: f32,
131 blur_radius: f32,
132 pressure: f32,
133 ) {
134 // Default: delegate to standard glass with intensity = pressure
135 Renderer::fill_glass_rect_with_intensity(self, rect, radius, blur_radius, pressure);
136 }
137
138 /// Fill a squircle (superellipse) for Apple-style icon silhouettes.
139 /// `n` controls the squareness: 2.0 = rounded rect, 4.0 = classic squircle, higher = more square.
140 fn fill_squircle(&mut self, rect: Rect, _n: f32, color: [f32; 4]) {
141 // Default fallback to rounded rect
142 Renderer::fill_rounded_rect(self, rect, rect.width.min(rect.height) * 0.22, color);
143 }
144
145 /// Stroke a squircle (superellipse) outline.
146 fn stroke_squircle(&mut self, rect: Rect, _n: f32, color: [f32; 4], stroke_width: f32) {
147 Renderer::stroke_rounded_rect(
148 self,
149 rect,
150 rect.width.min(rect.height) * 0.22,
151 color,
152 stroke_width,
153 );
154 }
155
156 /// Draw a focus ring around a rect (for keyboard navigation accessibility).
157 /// `offset` is the gap between the rect and the ring, `width` is the ring thickness.
158 fn draw_focus_ring(
159 &mut self,
160 rect: Rect,
161 radius: f32,
162 offset: f32,
163 width: f32,
164 color: [f32; 4],
165 ) {
166 // Default fallback to a stroked rounded rect
167 let ring_rect = Rect {
168 x: rect.x - offset,
169 y: rect.y - offset,
170 width: rect.width + 2.0 * offset,
171 height: rect.height + 2.0 * offset,
172 };
173 Renderer::stroke_rounded_rect(self, ring_rect, radius + offset, color, width);
174 }
175
176 /// Draw a high-fidelity 3D cube inside the given rectangle using specialized shader logic.
177 /// `rotation` is [pitch, yaw, roll] in radians.
178 fn draw_3d_cube(&mut self, _rect: Rect, _color: [f32; 4], _rotation: [f32; 3]) {}
179
180 // ── Stroked shapes ───────────────────────────────────────────────────
181 fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
182 fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32);
183 /// Stroke an ellipse/circle that fits inside `rect`.
184 fn stroke_ellipse(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
185 /// Draw a straight line from (x1,y1) to (x2,y2).
186 fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: [f32; 4], stroke_width: f32);
187 /// Fill a polygon defined by a set of vertices.
188 fn fill_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4]) {}
189 /// Stroke a polygon defined by a set of vertices.
190 fn stroke_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4], _stroke_width: f32) {}
191
192 // ── Text ─────────────────────────────────────────────────────────────
193 /// Draw text aligned within a bounding box.
194 /// This is the primary method — callers should use this for proper
195 /// vertical and horizontal alignment within their layout rects.
196 fn draw_text(&mut self, text: &str, rect: &Rect, size: f32, color: [f32; 4], h_align: TextHAlign, v_align: TextVAlign) {
197 // Default implementation delegates to draw_text_raw for backward compat
198 let text_w = self.measure_text(text, size).0;
199 let x = match h_align {
200 TextHAlign::Left => rect.x,
201 TextHAlign::Center => rect.x + (rect.width - text_w) / 2.0,
202 TextHAlign::Right => rect.x + rect.width - text_w,
203 };
204 let ascent = size * 0.8;
205 let y = match v_align {
206 TextVAlign::Top => rect.y + ascent,
207 TextVAlign::Middle => rect.y + (rect.height + ascent * 0.75) / 2.0,
208 TextVAlign::Bottom => rect.y + rect.height - (size - ascent),
209 };
210 self.draw_text_raw(text, x, y, size, color);
211 }
212
213 /// Draw text at a raw baseline position (x = left, y = baseline).
214 /// Use this when you need precise control over baseline positioning.
215 fn draw_text_raw(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
216 let r = (color[0] * 255.0).clamp(0.0, 255.0) as u8;
217 let g = (color[1] * 255.0).clamp(0.0, 255.0) as u8;
218 let b = (color[2] * 255.0).clamp(0.0, 255.0) as u8;
219 let a = (color[3] * 255.0).clamp(0.0, 255.0) as u8;
220
221 let mut style = cvkg_runic_text::TextStyle::new("Inter", size);
222 style.color = [r, g, b, a];
223 let spans = [cvkg_runic_text::TextSpan::new(text, style)];
224
225 if let Some(shaped) = self.shape_rich_text(
226 &spans,
227 None,
228 cvkg_runic_text::TextAlign::Start,
229 cvkg_runic_text::TextOverflow::Visible,
230 ) {
231 self.draw_shaped_text(&shaped, x, y);
232 }
233 }
234
235 /// Draw text centered within a bounding box (convenience).
236 fn draw_text_centered(&mut self, text: &str, rect: &Rect, size: f32, color: [f32; 4]) {
237 self.draw_text(text, rect, size, color, TextHAlign::Center, TextVAlign::Middle)
238 }
239
240 /// Measure the width and height of the specified text.
241 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
242 let span = cvkg_runic_text::TextSpan::new(
243 text,
244 cvkg_runic_text::TextStyle {
245 family: "Inter".to_string(),
246 font_size: size,
247 fallback_families: vec![
248 "SF Pro".to_string(),
249 "SF Pro Text".to_string(),
250 "Helvetica Neue".to_string(),
251 "Helvetica".to_string(),
252 "Arial".to_string(),
253 "sans-serif".to_string(),
254 ],
255 ..Default::default()
256 },
257 );
258 if let Some(shaped) = Renderer::shape_rich_text(
259 self,
260 &[span],
261 None,
262 cvkg_runic_text::TextAlign::Start,
263 cvkg_runic_text::TextOverflow::Visible,
264 ) {
265 let scale = self.text_scale_factor().max(1.0);
266 (shaped.width / scale, shaped.height / scale)
267 } else {
268 (0.0, 0.0)
269 }
270 }
271
272 /// Return the baseline offset (ascent) for the given text and size.
273 /// This is the distance from the text origin (y in draw_text) to the baseline.
274 /// Default returns 0.0; override in renderers that support text shaping.
275 fn measure_text_baseline(&mut self, text: &str, size: f32) -> f32 {
276 let span = cvkg_runic_text::TextSpan::new(
277 text,
278 cvkg_runic_text::TextStyle {
279 family: "Inter".to_string(),
280 font_size: size,
281 fallback_families: vec![
282 "SF Pro".to_string(),
283 "SF Pro Text".to_string(),
284 "Helvetica Neue".to_string(),
285 "Helvetica".to_string(),
286 "Arial".to_string(),
287 "sans-serif".to_string(),
288 ],
289 ..Default::default()
290 },
291 );
292 if let Some(shaped) = Renderer::shape_rich_text(
293 self,
294 &[span],
295 None,
296 cvkg_runic_text::TextAlign::Start,
297 cvkg_runic_text::TextOverflow::Visible,
298 ) {
299 shaped.ascent / self.text_scale_factor().max(1.0)
300 } else {
301 0.0
302 }
303 }
304
305 /// Scale factor used by text measurement helpers.
306 ///
307 /// Renderers that shape text in device pixels should return their current
308 /// device scale so `measure_text` and `measure_text_baseline` stay in logical pixels.
309 fn text_scale_factor(&self) -> f32 {
310 1.0
311 }
312
313 fn shape_rich_text(
314 &mut self,
315 _spans: &[cvkg_runic_text::TextSpan],
316 _max_width: Option<f32>,
317 _align: cvkg_runic_text::TextAlign,
318 _overflow: cvkg_runic_text::TextOverflow,
319 ) -> Option<cvkg_runic_text::ShapedText> {
320 None
321 }
322
323 fn draw_shaped_text(&mut self, _text: &cvkg_runic_text::ShapedText, _x: f32, _y: f32) {}
324
325 // ── Images & textures ────────────────────────────────────────────────
326 /// Draw a texture (GPU-side) at the specified rect.
327 fn draw_texture(&mut self, _texture_id: u32, _rect: Rect) {}
328 /// Draw an image asset by name or path.
329 fn draw_image(&mut self, _image_name: &str, _rect: Rect) {}
330 /// Load an image asset from memory.
331 fn load_image(&mut self, _name: &str, _data: &[u8]) {}
332 /// Pre-warm the renderer with assets. Implementations can use this
333 /// to populate texture atlases or warm up shader caches.
334 fn prewarm_vram(&mut self, _assets: Vec<(String, Vec<u8>)>) {}
335
336 /// Get the current pointer (mouse/touch) position.
337 fn get_pointer_position(&self) -> [f32; 2] {
338 [0.0, 0.0]
339 }
340
341 // ── Data Visualization ───────────────────────────────────────────────
342 /// Upload raw float data as a GPU texture for heatmap rendering.
343 fn upload_data_texture(&mut self, _id: &str, _data: &[f32], _width: u32, _height: u32) {}
344 /// Draw a heatmap using a previously uploaded data texture.
345 fn draw_heatmap(&mut self, _texture_id: &str, _rect: Rect, _palette: &str) {}
346
347 // ── 3D Objects ───────────────────────────────────────────────────────
348 /// Draw a 3D mesh.
349 fn draw_mesh(&mut self, _mesh: &Mesh, _color: [f32; 4], _transform: glam::Mat4) {}
350
351 /// Draw a 3D mesh with full material and transform support.
352 fn draw_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {}
353
354 /// Set the 3D camera for perspective/orthographic projection.
355 /// If not called, rendering defaults to the 2D orthographic projection.
356 fn set_camera_3d(&mut self, _camera: &Camera3D) {}
357
358 /// Push a 3D transform onto the transform stack.
359 /// All subsequent drawing is affected until `pop_transform_3d`.
360 fn push_transform_3d(&mut self, _transform: &Transform3D) {}
361
362 /// Pop the most recently pushed 3D transform.
363 fn pop_transform_3d(&mut self) {}
364
365 /// Render a 3D scene graph node. Reads position_3d, rotation_3d, scale_3d
366 /// from the node and emits the appropriate draw call.
367 /// Default implementation is a no-op; 3D renderers override this.
368 ///
369 /// `position`: [x, y, z] world-space position
370 /// `rotation`: [x, y, z, w] quaternion rotation
371 /// `scale`: [x, y, z] scale factors
372 /// `color`: [r, g, b, a] base color for unlit rendering
373 fn render_scene_node_3d(
374 &mut self,
375 _position: [f32; 3],
376 _rotation: [f32; 4],
377 _scale: [f32; 3],
378 _color: [f32; 4],
379 _meshes: &[Mesh],
380 ) {
381 // Default no-op: 2D renderers ignore 3D scene nodes
382 }
383
384 /// Draw a linear gradient between two colors at the specified angle.
385 fn draw_linear_gradient(
386 &mut self,
387 _rect: Rect,
388 _start_color: [f32; 4],
389 _end_color: [f32; 4],
390 _angle: f32,
391 ) {
392 }
393 /// Draw a radial gradient between two colors.
394 fn draw_radial_gradient(
395 &mut self,
396 _rect: Rect,
397 _inner_color: [f32; 4],
398 _outer_color: [f32; 4],
399 ) {
400 }
401 /// Draw a multi-stop linear gradient (GPU-accelerated).
402 /// stops: array of [R, G, B, position] where position is 0.0-1.0.
403 /// angle: gradient angle in radians.
404 fn draw_linear_gradient_multi(&mut self, _rect: Rect, _stops: &[[f32; 4]], _angle: f32) {}
405 /// Draw a multi-stop radial gradient (GPU-accelerated).
406 /// stops: array of [R, G, B, position] where position is 0.0-1.0.
407 fn draw_radial_gradient_multi(&mut self, _rect: Rect, _stops: &[[f32; 4]]) {}
408 /// Draw a high-fidelity drop shadow for a rounded rectangle.
409 fn draw_drop_shadow(
410 &mut self,
411 _rect: Rect,
412 _radius: f32,
413 _color: [f32; 4],
414 _blur: f32,
415 _spread: f32,
416 ) {
417 }
418 /// Draw a dashed border for a rounded rectangle.
419 fn stroke_dashed_rounded_rect(
420 &mut self,
421 _rect: Rect,
422 _radius: f32,
423 _color: [f32; 4],
424 _width: f32,
425 _dash: f32,
426 _gap: f32,
427 ) {
428 }
429 /// Draw a 9-slice / patch scaled image.
430 fn draw_9slice(
431 &mut self,
432 _image_name: &str,
433 _rect: Rect,
434 _left: f32,
435 _top: f32,
436 _right: f32,
437 _bottom: f32,
438 ) {
439 }
440
441 // ── Clipping ─────────────────────────────────────────────────────────
442 /// Push a clip rectangle. All subsequent drawing is clipped to `rect`.
443 /// Implementations that do not support clipping may ignore this call.
444 fn push_clip_rect(&mut self, _rect: Rect) {}
445 /// Pop the most recently pushed clip rectangle.
446 fn pop_clip_rect(&mut self) {}
447 /// Get the current clip rectangle in screen coordinates.
448 /// Returns a rect covering the entire screen if no clip is active.
449 fn current_clip_rect(&self) -> Rect {
450 Rect::new(-10000.0, -10000.0, 20000.0, 20000.0)
451 }
452
453 // ── Global opacity ───────────────────────────────────────────────────
454 /// Set a global opacity multiplier applied to all subsequent draw calls
455 /// until `pop_opacity` is called. `opacity` is in [0.0, 1.0].
456 fn push_opacity(&mut self, _opacity: f32) {}
457 /// Restore the previous opacity level.
458 fn pop_opacity(&mut self) {}
459
460 // ── Berserker Pipeline State ─────────────────────────────────────────
461 fn set_theme(&mut self, _theme: ColorTheme) {}
462 fn set_rage(&mut self, _rage: f32) {}
463 fn set_berserker_mode(&mut self, _state: RenderIntensityMode) {}
464 fn trigger_shatter_event(&mut self, _origin: [f32; 2], _force: f32) {}
465 /// Set the fireball position for dynamic glass specular highlights.
466 fn set_fireball_pos(&mut self, _pos: [f32; 2]) {}
467 /// Set the desktop scene preset (Aurora, Void, Nebula, Glitch, Yggdrasil).
468 fn set_scene(&mut self, _scene: &str) {}
469 /// Set the desktop scene by name. Case-insensitive.
470 /// Supports: "aurora", "void", "nebula", "glitch", "yggdrasil".
471 /// Aliases: "empty", "none", "blank" → Void.
472 fn set_scene_by_name(&mut self, name: &str) {
473 if let Some(preset) = resolve_scene_by_name(name) {
474 Renderer::set_scene_preset(self, preset);
475 }
476 }
477
478 // ── Export & Print ───────────────────────────────────────────────────
479 /// Capture the current frame as a PNG byte buffer.
480 fn capture_png(&mut self) -> Vec<u8> {
481 Vec::new()
482 }
483 /// Trigger a native print dialog or spooling operation.
484 fn print(&mut self) {}
485
486 fn set_scene_preset(&mut self, _preset: u32) {}
487
488 // ── Cyberpunk Effects ────────────────────────────────────────────────
489 /// Apply a Bifrost (Frosted Glass) effect to the specified rect.
490 fn bifrost(&mut self, _rect: Rect, _blur: f32, _saturation: f32, _opacity: f32) {}
491 /// Apply a Gungnir (Neon Glow) effect to the specified rect.
492 fn gungnir(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32, _intensity: f32) {}
493 /// Soft glow variant -- half the intensity of gungnir(). Use for hover highlights.
494 fn gungnir_soft(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32, _intensity: f32) {}
495 /// Set the default background color for the canvas (RGBA).
496 /// Used when the app does not draw its own background.
497 fn set_default_background_color(&mut self, _color: [f32; 4]) {}
498 /// Apply a ManiGlow (Lunar Illuminator) effect.
499 fn mani_glow(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32) {}
500 /// Push a Mjolnir Slice (geometric clipping).
501 fn push_mjolnir_slice(&mut self, _angle: f32, _offset: f32) {}
502 fn pop_mjolnir_slice(&mut self) {}
503 /// Execute a render function with memoization.
504 /// If the renderer supports caching and the `id` + `data_hash` match a previous run,
505 /// it may replay cached commands instead of executing the function.
506 fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer));
507 /// Capture current renderer stack depths for later panic recovery.
508 /// The default implementation returns `RenderStateSnapshot::default()`,
509 /// which is safe but does nothing useful -- backends with stack state
510 /// must override this to record their actual depths.
511 fn snapshot_render_state(&self) -> RenderStateSnapshot {
512 RenderStateSnapshot::default()
513 }
514 /// Restore renderer stack state by popping items pushed beyond the
515 /// snapshot point. Used by `ErrorBoundary` to recover from mid-render
516 /// panics so sibling views don't inherit leaked clip/opacity/transform
517 /// state. Idempotent: a no-op if stacks are already at or below the
518 /// snapshot depths. Default implementation is a no-op for backends
519 /// that have no stack state.
520 fn restore_render_state(&mut self, _snap: RenderStateSnapshot) {}
521 /// Apply a Mjolnir Shatter effect (fragmentation) to the specified rect.
522 fn mjolnir_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
523 fn mjolnir_fluid_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
524 fn draw_mjolnir_bolt(&mut self, _from: [f32; 2], _to: [f32; 2], _color: [f32; 4]) {}
525
526 // ── Futuristic UI Compute & Volumetric ───────────────────────────────
527 /// Dispatches a burst of GPU particles (e.g. fireworks, data streams).
528 fn dispatch_particles(
529 &mut self,
530 _origin: [f32; 2],
531 _count: u32,
532 _effect_type: &str,
533 _color: [f32; 4],
534 ) {
535 }
536
537 /// Draws a volumetric hologram into the specified bounding rectangle.
538 fn draw_hologram(&mut self, _rect: Rect, _hologram_id: &str, _time: f32) {}
539
540 // ── Accessibility (ShieldWall) ───────────────────────────────────────
541 fn set_aria_role(&mut self, _role: &str) {}
542 fn set_aria_label(&mut self, _label: &str) {}
543 fn set_aria_valuemin(&mut self, _min: f32) {}
544 fn set_aria_valuemax(&mut self, _max: f32) {}
545 fn set_aria_valuenow(&mut self, _now: f32) {}
546
547 /// Convenience method: register aria role and label in one call.
548 /// Components with both `role` and `label` props should call this.
549 fn register_a11y(&mut self, role: &str, label: &str) {
550 self.set_aria_role(role);
551 self.set_aria_label(label);
552 }
553
554 /// Push a focus trap onto the stack. While active, keyboard focus is
555 /// trapped within the specified element and its children.
556 /// Returns a trap ID that must be passed to `pop_focus_trap`.
557 fn push_focus_trap(&mut self, _element_id: &str) -> u64 {
558 0
559 }
560
561 /// Pop the most recently pushed focus trap.
562 fn pop_focus_trap(&mut self, _trap_id: u64) {}
563
564 /// Register a shared element for Bifrost Bridge transitions.
565 fn register_shared_element(&mut self, _id: &str, _rect: Rect) {}
566
567 /// Set a unique key for the current VDOM node to ensure stable identity during diffing.
568 fn set_key(&mut self, _key: &str) {}
569
570 // ── Telemetry ────────────────────────────────────────────────────────
571 /// Get real-time performance telemetry.
572 fn get_telemetry(&self) -> TelemetryData {
573 TelemetryData::default()
574 }
575
576 // ── GPU State Management ─────────────────────────────────────────────
577 /// Push a shadow state to the stack. All following draw calls will have this shadow.
578 fn push_shadow(&mut self, _radius: f32, _color: [f32; 4], _offset: [f32; 2]) {}
579 /// Pop the last shadow state from the stack.
580 fn pop_shadow(&mut self) {}
581
582 // ── VDOM & Scene Graph ───────────────────────────────────────────────
583 /// Push a Virtual DOM node onto the stack for hierarchy tracking.
584 fn push_vnode(&mut self, _rect: Rect, _name: &'static str) {}
585
586 /// Push a Virtual DOM node with companion state auto-initialization.
587 /// Default implementation ignores companions and delegates to `push_vnode`.
588 fn push_vnode_with_companions(
589 &mut self,
590 rect: Rect,
591 name: &'static str,
592 _companions: Vec<Box<dyn Companion>>,
593 ) {
594 self.push_vnode(rect, name);
595 }
596
597 /// Pop the current Virtual DOM node from the stack.
598 fn pop_vnode(&mut self) {}
599
600 /// Register an event handler for the current VDOM node.
601 fn register_handler(
602 &mut self,
603 _event_type: &str,
604 _handler: std::sync::Arc<dyn Fn(Event) + Send + Sync>,
605 ) {
606 }
607
608 /// Retrieve a companion state for the VNode currently at the top of the stack.
609 /// Returns None if the companion type is not registered on this node.
610 fn current_companion(&self) -> Option<&dyn Companion> {
611 None
612 }
613
614 /// Set the current Z-index for depth sorting.
615 /// Higher values appear closer to the viewer.
616 fn set_z_index(&mut self, _z: f32) {}
617
618 /// Get the current Z-index.
619 fn get_z_index(&self) -> f32 {
620 0.0
621 }
622
623 // ── Vector Graphics ──────────────────────────────────────────────────
624 fn load_svg(&mut self, _name: &str, _svg_data: &[u8]) {}
625 /// Draw a pre-loaded SVG model.
626 fn draw_svg(&mut self, _name: &str, _rect: Rect) {}
627 /// Draw a pre-loaded SVG model with a per-instance animation time offset.
628 /// The offset shifts the animation phase, allowing multiple draws of the same
629 /// SVG to animate independently. Default delegates to draw_svg (no offset).
630 fn draw_svg_with_offset(&mut self, name: &str, rect: Rect, _animation_time_offset: f32) {
631 Renderer::draw_svg(self, name, rect);
632 }
633 /// Draw a pre-loaded SVG model with explicit draw_order for z-sorting.
634 /// draw_order=200 renders above UI chrome (draw_order=0).
635 fn draw_svg_with_order(&mut self, name: &str, rect: Rect, _draw_order: i32) {
636 Renderer::draw_svg(self, name, rect);
637 }
638 /// Serialize a pre-loaded SVG model back to SVG XML markup.
639 /// Returns the serialized SVG string, or an error if the model is not loaded
640 /// or serialization is not supported by this renderer.
641 fn serialize_svg(&mut self, _name: &str) -> Result<String, String> {
642 Err("SVG serialization not supported by this renderer".into())
643 }
644 /// Apply an SVG filter to a pre-loaded SVG model by filter element ID.
645 /// The filter is evaluated and the result composited back into the SVG.
646 /// Returns the filtered SVG as XML, or an error if not supported.
647 fn apply_svg_filter(
648 &mut self,
649 _name: &str,
650 _filter_id: &str,
651 _region: Rect,
652 ) -> Result<String, String> {
653 Err("SVG filter not supported by this renderer".into())
654 }
655
656 // ── 3D World Space Panels ────────────────────────────────────────────────
657
658 /// Called when the traversal enters a VNode with a WorldSpacePanel configured.
659 /// Redirects subsequent draw calls into an offscreen texture for the panel,
660 /// similar to push_target.
661 fn begin_world_space_panel(
662 &mut self,
663 node_id: u64,
664 transform: &Transform3D,
665 glass: Option<cvkg_materials::GlassMaterial>,
666 pixels_per_unit: f32,
667 world_size: (f32, f32),
668 ) {
669 let _ = (node_id, transform, glass, pixels_per_unit, world_size);
670 }
671
672 /// Called when the traversal exits a VNode with a WorldSpacePanel configured.
673 /// Ends offscreen redirection and records the panel into the renderer state
674 /// for 3D compositing later in the frame graph.
675 fn end_world_space_panel(&mut self, node_id: u64) {
676 let _ = node_id;
677 }
678
679 // ── Coordinate Transforms ──────────────────────────────────────────────
680 /// Push a 2D transform (translation, scale, rotation) onto the stack.
681 /// This transform should be applied to all subsequent draw calls until popped.
682 /// Transform-only animations use this to avoid re-triggering the layout engine.
683 fn push_transform(&mut self, _translation: [f32; 2], _scale: [f32; 2], _rotation: f32) {}
684 /// Push a raw 2D affine transform matrix [a, b, c, d, e, f] corresponding to
685 /// [m11, m12, m21, m22, tx, ty].
686 fn push_affine(&mut self, _transform: [f32; 6]) {}
687 /// Pop the last 2D transform from the stack.
688 fn pop_transform(&mut self) {}
689 /// Return the resolved layout bounds for a specific node ID if it exists.
690 fn query_layout(&self, _node_id: scene_graph::NodeId) -> Option<Rect> {
691 None
692 }
693 /// Enable or disable the layout debug overlay (bounds, padding, margin).
694 fn set_debug_layout(&mut self, _enabled: bool) {}
695 /// Check if the layout debug overlay is currently enabled.
696 fn get_debug_layout(&self) -> bool {
697 false
698 }
699
700 // ── Material Routing ─────────────────────────────────────────────────
701 /// Set the active material for subsequent draw calls.
702 /// Controls which pass a draw call is routed to in the multi-pass pipeline.
703 fn set_material(&mut self, _material: crate::material::DrawMaterial) {}
704 /// Return the currently active material (defaults to Opaque).
705 fn current_material(&self) -> crate::material::DrawMaterial {
706 crate::material::DrawMaterial::Opaque
707 }
708
709 // ── Vili Interaction Paradigm ──────────────────────────────────────────
710 /// Compute the user's velocity/intent vector.
711 fn mimir_intent(&self) -> [f32; 2] {
712 [0.0, 0.0]
713 }
714 /// Calculate magnetic coordinate warp towards an anchor.
715 fn magnetic_warp(&self, pointer: [f32; 2], anchor_rect: Rect, strength: f32) -> [f32; 2] {
716 if strength <= 0.0 {
717 return pointer;
718 }
719 let cx = anchor_rect.x + anchor_rect.width / 2.0;
720 let cy = anchor_rect.y + anchor_rect.height / 2.0;
721 let dx = pointer[0] - cx;
722 let dy = pointer[1] - cy;
723 let dist = (dx * dx + dy * dy).sqrt();
724 let radius = 120.0;
725 if dist < radius && dist > 0.0 {
726 let force = (1.0 - dist / radius) * strength;
727 [pointer[0] - dx * force, pointer[1] - dy * force]
728 } else {
729 pointer
730 }
731 }
732 /// Calculate kinematic glow intensity based on proximity.
733 fn mani_glow_intensity(&self, pointer: [f32; 2], bounds: Rect, radius: f32) -> f32 {
734 let cx = bounds.x + bounds.width / 2.0;
735 let cy = bounds.y + bounds.height / 2.0;
736 let dist = ((pointer[0] - cx).powi(2) + (pointer[1] - cy).powi(2)).sqrt();
737 if dist < radius {
738 (1.0 - dist / radius).clamp(0.0, 1.0)
739 } else {
740 0.0
741 }
742 }
743 /// Calculate dynamic element attention (scaling/morphing) statelessly per frame.
744 fn fafnir_evolve(&self, pointer: [f32; 2], bounds: Rect, max_scale: f32) -> f32 {
745 let prox = self.mani_glow_intensity(pointer, bounds, 120.0);
746 1.0 + (max_scale - 1.0) * prox
747 }
748 /// Sets the precise Vili SDF Shape boundary for hit-testing.
749 fn set_sdf_shape(&mut self, _shape: crate::layout::SdfShape) {}
750
751 // -- Portal / PhaseGate rendering -----------------------------------------
752
753 // ── Theme stack ──────────────────────────────────────────────────────
754
755 /// Push a theme override for the current subtree.
756 /// All child nodes rendered until `pop_theme()` are drawn with this theme.
757 fn push_theme(&mut self, _theme: ColorTheme) {}
758
759 /// Pop the theme override and restore the previous theme.
760 fn pop_theme(&mut self) {}
761
762 /// Return the current theme at the current stack depth.
763 /// Falls back to the default theme if no theme has been pushed.
764 fn current_theme(&self) -> ColorTheme {
765 ColorTheme::default()
766 }
767
768 /// Save theme state for portal inheritance before entering a new buffer.
769 /// Legacy — prefer using the theme stack (`push_theme`/`pop_theme`) instead.
770 fn save_theme(&mut self) {}
771 /// Restore the saved theme when exiting a portal buffer.
772 /// Legacy — prefer using the theme stack (`push_theme`/`pop_theme`) instead.
773 fn restore_theme(&mut self) {}
774
775 /// Begin rendering into the portal root layer instead of the inline tree.
776 /// All draw calls between `enter_portal` and `exit_portal` are collected
777 /// into a separate buffer that is composited AFTER the main tree.
778 ///
779 /// WHY separate buffer: The main tree may have clipping, transforms, or
780 /// opacity that should NOT affect overlays. The portal layer renders on top
781 /// of everything, ignoring the local coordinate system.
782 fn enter_portal(&mut self, _z_index: i32) {}
783
784 /// Exit the portal layer and return to inline rendering.
785 /// The portal content collected since `enter_portal` is now sealed --
786 /// no more draw calls will be appended to it.
787 fn exit_portal(&mut self) {}
788
789 /// Get the current viewport size in logical pixels.
790 /// Used by portal content to size itself to the full screen.
791 fn viewport_size(&self) -> Rect {
792 Rect::new(0.0, 0.0, 1920.0, 1080.0)
793 }
794
795 // -- Accessibility announcements -----------------------------------------
796
797 /// Announce a message to screen readers via the platform accessibility API.
798 /// This call is non-blocking. The message is queued and the screen reader
799 /// will speak it at its own pace.
800 fn announce(&mut self, _message: &str, _priority: AnnouncementPriority) {}
801}