Skip to main content

cvkg_core/
lib.rs

1//! # CVKG Agentic Development Guidelines (v1.2)
2//!
3//! All AI agents contributing to this crate MUST follow ALL seven rules:
4//!
5//! ── Karpathy Guidelines (1–4) ────────────────────────────────────────────
6//! 1. THINK FIRST     — State assumptions. Surface ambiguity. Push back on complexity.
7//! 2. STAY SIMPLE     — Minimum code. No speculative features. No unasked-for abstractions.
8//! 3. BE SURGICAL     — Touch only what's required. Own your orphans. Don't improve neighbors.
9//! 4. VERIFY GOALS    — Turn tasks into checkable criteria. Loop until they pass. Never commit broken.
10//!
11//! ── CVKG Extended Protocols (5–7) ────────────────────────────────────────
12//! 5. TRIPLE-PASS     — Read the target, its surrounding context, and its full call graph
13//                      at least THREE TIMES before making any edit or revision.
14//! 6. COMMENT ALL     — Every major pub fn, unsafe block, and non-trivial algorithm in
15//                      every .rs/.ts/.h/.wgsl file MUST have a descriptive doc comment.
16//                      Comments describe WHY and WHAT CONTRACT, not HOW mechanically.
17//! 7. MONITOR LOOPS   — Check every tool call / command for progress every 30 seconds.
18//                      After 3 consecutive identical failures, stop, write BLOCKED.md,
19//                      and move to unblocked work. Never silently accept a broken state.
20//!
21//! Sources:
22//   Karpathy: https://github.com/multica-ai/andrej-karpathy-skills
23//   CVKG Extended: Section 2 of the CVKG Design Specification
24
25//! The View trait is the fundamental building block of CVKG. Every UI element — from a plain text label
26//! to a complex navigation controller — is a View. The trait is intentionally minimal; complexity emerges
27//! through modifier composition.
28//!
29//! # Conformance rules:
30//! 1. `body()` must be pure and side-effect free
31//! 2. Primitive views use `Never` as `Body` and register a `PaintCommand` directly with the scene graph
32//! 3. `View` types must implement `Send` but not necessarily `Sync`, enabling safe multi-threaded layout passes
33
34use serde::{Deserialize, Serialize};
35use std::collections::HashMap;
36use std::str::FromStr;
37
38pub mod error_types;
39
40pub mod security;
41
42/// Error state for fault isolation at the component level.
43#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
44pub struct ComponentErrorState {
45    pub has_error: bool,
46    pub error_message: Option<String>,
47    pub error_location: Option<String>,
48}
49impl ComponentErrorState {
50    pub fn clear() -> Self {
51        Self::default()
52    }
53
54    pub fn error(message: impl Into<String>, location: impl Into<String>) -> Self {
55        Self {
56            has_error: true,
57            error_message: Some(message.into()),
58            error_location: Some(location.into()),
59        }
60    }
61}
62
63/// Knowledge state for the agentic memory system.
64#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
65pub struct KnowledgeState {
66    pub thoughts: Vec<String>,
67    pub actions: Vec<String>,
68    pub context: HashMap<String, String>,
69    pub last_query_results: Vec<KnowledgeId>,
70    #[serde(alias = "items")]
71    pub fragments: std::collections::HashMap<KnowledgeId, KnowledgeFragment>,
72    /// The Temporal Graph nodes
73    pub nodes: Vec<TemporalNode>,
74    /// The Temporal Graph edges
75    pub edges: Vec<TemporalEdge>,
76    /// The current operational Realm (Midgard/Asgard)
77    pub realm: Realm,
78    /// Last known pointer position (X, Y)
79    pub last_pointer_pos: [f32; 2],
80    /// Resolved pointer velocity (pixels per frame)
81    pub pointer_velocity: [f32; 2],
82    /// The current 'Focus' node ID (Odin's Eye focus)
83    pub odin_focus: Option<String>,
84    /// Agent attention heatmap (node_id -> intensity)
85    pub agent_attention: HashMap<String, f32>,
86    // Component state storage for dynamic state
87    #[serde(skip)]
88    pub component_states: HashMap<u64, Arc<std::sync::RwLock<dyn std::any::Any + Send + Sync>>>,
89    /// Global undo/redo manager tracking document and input states.
90    #[serde(skip)]
91    pub undo_manager: UndoManager,
92    /// Active notification list.
93    #[serde(default)]
94    pub notifications: Vec<Notification>,
95    /// Flag indicating whether the notification center panel is visible.
96    #[serde(default)]
97    pub notification_center_visible: bool,
98    /// Modifier key state: shift key pressed.
99    #[serde(default)]
100    pub modifiers_shift: bool,
101    /// Modifier key state: control key pressed.
102    #[serde(default)]
103    pub modifiers_ctrl: bool,
104    /// Modifier key state: alt/option key pressed.
105    #[serde(default)]
106    pub modifiers_alt: bool,
107    /// Modifier key state: logo/command/windows key pressed.
108    #[serde(default)]
109    pub modifiers_logo: bool,
110    /// Whether the performance profiling overlay (Cmd+Shift+P) is currently visible.
111    #[serde(default)]
112    pub performance_overlay_visible: bool,
113}
114
115impl KnowledgeState {
116    /// Apply activation decay to all temporal nodes and evolving components.
117    /// Nodes with weight below a threshold drift out of the primary context.
118    /// Components lose vitality (Fafnir's Decay) if not actively 'fed'.
119    pub fn apply_decay(&mut self, decay_factor: f32) {
120        for node in &mut self.nodes {
121            node.weight *= decay_factor;
122        }
123
124        // Fafnir's Decay: Components naturally revert to base state over time
125        for state in self.component_states.values() {
126            if let Ok(mut lock) = state.write()
127                && let Some(v) = lock.downcast_mut::<f32>()
128            {
129                *v = (*v * decay_factor).max(1.0);
130            }
131        }
132    }
133
134    /// Increase the importance weight of nodes associated with a successful task.
135    pub fn reinforce(&mut self, node_ids: &[String], boost: f32) {
136        for node in &mut self.nodes {
137            if node_ids.contains(&node.id) {
138                node.weight += boost;
139            }
140        }
141    }
142
143    /// Update pointer kinematics based on a new position.
144    pub fn update_pointer(&mut self, new_pos: [f32; 2]) {
145        self.pointer_velocity = [
146            new_pos[0] - self.last_pointer_pos[0],
147            new_pos[1] - self.last_pointer_pos[1],
148        ];
149        self.last_pointer_pos = new_pos;
150    }
151}
152// Knowledge System Types
153/// Unique identifier for knowledge fragments
154pub type KnowledgeId = String;
155
156/// A knowledge fragment stored in the memory system
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct KnowledgeFragment {
159    /// Unique identifier for this fragment
160    pub id: String,
161    /// Short summary for prompt injection and quick search
162    pub summary: String,
163    /// Reference source (e.g. filename, URL, or conversation ID)
164    pub source: String,
165    /// Frame number or timestamp of creation
166    pub created_at: u64,
167    /// Number of times this fragment has been retrieved
168    pub accessed_count: u32,
169    /// Full content (optional, can be loaded on-demand)
170    pub content: Option<String>,
171}
172
173impl KnowledgeFragment {
174    pub fn new(id: String, summary: String, source: String) -> Self {
175        Self {
176            id,
177            summary,
178            source,
179            created_at: 0,
180            accessed_count: 0,
181            content: None,
182        }
183    }
184}
185
186/// Memory layers for the layered cognitive engine
187#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
188pub enum MemoryLayer {
189    /// Raw mission events (short-term)
190    Episodic,
191    /// Extracted facts and tactical intelligence (long-term)
192    Semantic,
193    /// Successful command sequences and tool chains
194    Procedural,
195}
196
197/// The operational Realm of the UI.
198/// Midgard: Classic, functional, 2D tactical UI for mortals.
199/// Asgard: High-fidelity, cognitive, shader-heavy UI for the Singularity.
200#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
201pub enum Realm {
202    Midgard,
203    #[default]
204    Asgard,
205}
206
207/// Priority for screen reader announcements via `Renderer::announce`.
208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209pub enum AnnouncementPriority {
210    /// Wait for current speech to finish before announcing.
211    Polite,
212    /// Interrupt current speech to announce immediately.
213    Assertive,
214}
215#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct TemporalNode {
217    /// Unique identifier for this node
218    pub id: String,
219    /// ID of the underlying knowledge fragment
220    pub fragment_id: KnowledgeId,
221    /// Timestamp of the event
222    pub timestamp: u64,
223    /// The memory layer this node belongs to
224    pub layer: MemoryLayer,
225    /// Importance weight for activation decay and retrieval
226    pub weight: f32,
227}
228
229/// An edge in the Temporal Graph representing a relationship between nodes
230#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct TemporalEdge {
232    /// Source node ID
233    pub source: String,
234    /// Target node ID
235    pub target: String,
236    /// Type of relationship (e.g. "causal", "semantic", "temporal")
237    pub relation: String,
238    /// Weight/strength of the connection
239    pub weight: f32,
240}
241
242/// A single action group representing an undo/redo step.
243pub struct UndoGroup {
244    /// Descriptive label of the action (e.g. "Type", "Delete").
245    pub label: String,
246    /// Time when the action was recorded, in seconds.
247    pub timestamp: f32,
248    /// Closure to revert the action.
249    pub undo: Arc<dyn Fn() + Send + Sync>,
250    /// Closure to re-apply the action.
251    pub redo: Arc<dyn Fn() + Send + Sync>,
252}
253
254impl Clone for UndoGroup {
255    /// Clone the undo/redo group. The closures are shared via Arc.
256    fn clone(&self) -> Self {
257        Self {
258            label: self.label.clone(),
259            timestamp: self.timestamp,
260            undo: Arc::clone(&self.undo),
261            redo: Arc::clone(&self.redo),
262        }
263    }
264}
265
266impl std::fmt::Debug for UndoGroup {
267    /// Debug format helper to avoid printing closures.
268    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269        f.debug_struct("UndoGroup")
270            .field("label", &self.label)
271            .field("timestamp", &self.timestamp)
272            .finish()
273    }
274}
275
276/// Unified manager for undo and redo stacks.
277/// Supports grouping of actions, max undo depth clamping, and coalescing.
278pub struct UndoManager {
279    /// History stack of undo/redo groups.
280    stack: Vec<UndoGroup>,
281    /// Current position/index in the stack.
282    position: usize,
283    /// Maximum allowed undo steps before discarding oldest.
284    max_depth: usize,
285    /// Time window in seconds to coalesce consecutive actions of the same type.
286    coalesce_window: f32,
287}
288
289impl Default for UndoManager {
290    /// Create a default UndoManager with a depth of 100 and a 0.5s coalesce window.
291    fn default() -> Self {
292        Self {
293            stack: Vec::new(),
294            position: 0,
295            max_depth: 100,
296            coalesce_window: 0.5,
297        }
298    }
299}
300
301impl Clone for UndoManager {
302    /// Clone the undo manager, preserving stacks and position.
303    fn clone(&self) -> Self {
304        Self {
305            stack: self.stack.clone(),
306            position: self.position,
307            max_depth: self.max_depth,
308            coalesce_window: self.coalesce_window,
309        }
310    }
311}
312
313impl std::fmt::Debug for UndoManager {
314    /// Debug format helper for UndoManager.
315    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316        f.debug_struct("UndoManager")
317            .field("stack_len", &self.stack.len())
318            .field("position", &self.position)
319            .field("max_depth", &self.max_depth)
320            .field("coalesce_window", &self.coalesce_window)
321            .finish()
322    }
323}
324
325impl UndoManager {
326    /// Create a new UndoManager with custom settings.
327    pub fn new(max_depth: usize, coalesce_window: f32) -> Self {
328        Self {
329            stack: Vec::new(),
330            position: 0,
331            max_depth,
332            coalesce_window,
333        }
334    }
335
336    /// Push a new undo/redo group to the stack, clearing any forward redo history.
337    pub fn push(
338        &mut self,
339        label: &str,
340        undo: impl Fn() + Send + Sync + 'static,
341        redo: impl Fn() + Send + Sync + 'static,
342    ) {
343        if self.position < self.stack.len() {
344            self.stack.truncate(self.position);
345        }
346
347        let timestamp = std::time::SystemTime::now()
348            .duration_since(std::time::UNIX_EPOCH)
349            .unwrap_or_default()
350            .as_secs_f32();
351
352        self.stack.push(UndoGroup {
353            label: label.to_string(),
354            timestamp,
355            undo: Arc::new(undo),
356            redo: Arc::new(redo),
357        });
358
359        if self.stack.len() > self.max_depth {
360            self.stack.remove(0);
361        }
362        self.position = self.stack.len();
363    }
364
365    /// Perform the undo action if possible, moving the position back.
366    /// Returns the undo closure to be executed outside of any state lock.
367    pub fn undo(&mut self) -> Option<Arc<dyn Fn() + Send + Sync>> {
368        if self.can_undo() {
369            self.position -= 1;
370            Some(Arc::clone(&self.stack[self.position].undo))
371        } else {
372            None
373        }
374    }
375
376    /// Perform the redo action if possible, moving the position forward.
377    /// Returns the redo closure to be executed outside of any state lock.
378    pub fn redo(&mut self) -> Option<Arc<dyn Fn() + Send + Sync>> {
379        if self.can_redo() {
380            let group = &self.stack[self.position];
381            self.position += 1;
382            Some(Arc::clone(&group.redo))
383        } else {
384            None
385        }
386    }
387
388    /// Returns true if there is an action that can be undone.
389    pub fn can_undo(&self) -> bool {
390        self.position > 0
391    }
392
393    /// Returns true if there is an action that can be redone.
394    pub fn can_redo(&self) -> bool {
395        self.position < self.stack.len()
396    }
397
398    /// Clear all undo/redo history.
399    pub fn clear(&mut self) {
400        self.stack.clear();
401        self.position = 0;
402    }
403
404    /// Push a new coalesceable action. If the last action in the stack matches the label,
405    /// is within the coalesce window, and the position is at the end of the stack, their undo/redo
406    /// functions will be combined instead of creating a new group.
407    pub fn push_coalesceable(
408        &mut self,
409        label: &str,
410        undo: impl Fn() + Send + Sync + 'static,
411        redo: impl Fn() + Send + Sync + 'static,
412    ) {
413        let now = std::time::SystemTime::now()
414            .duration_since(std::time::UNIX_EPOCH)
415            .unwrap_or_default()
416            .as_secs_f32();
417
418        if self.position == self.stack.len() && !self.stack.is_empty() {
419            let last_idx = self.stack.len() - 1;
420            let last = &self.stack[last_idx];
421            if last.label == label && (now - last.timestamp).abs() <= self.coalesce_window {
422                let old_undo = Arc::clone(&last.undo);
423                let old_redo = Arc::clone(&last.redo);
424                let new_undo = Arc::new(undo);
425                let new_redo = Arc::new(redo);
426
427                self.stack[last_idx].undo = Arc::new(move || {
428                    new_undo();
429                    old_undo();
430                });
431                self.stack[last_idx].redo = Arc::new(move || {
432                    old_redo();
433                    new_redo();
434                });
435                self.stack[last_idx].timestamp = now;
436                return;
437            }
438        }
439
440        self.push(label, undo, redo);
441    }
442}
443
444/// Unique identifier for a window instance.
445#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
446pub struct WindowId(pub u64);
447
448/// Specifies the layering behavior of the window relative to other windows.
449#[derive(
450    Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default,
451)]
452pub enum WindowLevel {
453    /// Standard window.
454    #[default]
455    Normal,
456    /// Window stays above all standard windows.
457    AlwaysOnTop,
458    /// Menu or pop-up level window.
459    PopUpMenu,
460}
461
462/// Configuration settings for creating a new window.
463#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
464pub struct WindowConfig {
465    /// The window title bar text.
466    pub title: String,
467    /// Default width and height of the window.
468    pub size: (f32, f32),
469    /// Minimum allowed dimensions.
470    pub min_size: Option<(f32, f32)>,
471    /// Maximum allowed dimensions.
472    pub max_size: Option<(f32, f32)>,
473    /// Whether the window can be resized by the user.
474    pub resizable: bool,
475    /// Whether the window background is transparent.
476    pub transparent: bool,
477    /// Whether the window title bar and border decorations are drawn.
478    pub decorations: bool,
479    /// The window level layer.
480    pub level: WindowLevel,
481}
482
483impl Default for WindowConfig {
484    /// Create a standard default window configuration.
485    fn default() -> Self {
486        Self {
487            title: "CVKG Window".to_string(),
488            size: (800.0, 600.0),
489            min_size: None,
490            max_size: None,
491            resizable: true,
492            transparent: false,
493            decorations: true,
494            level: WindowLevel::Normal,
495        }
496    }
497}
498
499/// Abstract trait representing a platform-native window.
500/// Implementations delegate calls back to the platform renderers and events.
501pub trait Window: Send + Sync {
502    /// Request closing of the window.
503    fn close(&self);
504    /// Change the title bar text of the window.
505    fn set_title(&self, title: &str);
506    /// Update the window's physical dimensions.
507    fn set_size(&self, width: f32, height: f32);
508    /// Check if the window currently has keyboard focus.
509    fn is_key(&self) -> bool;
510    /// Check if this is the primary main application window.
511    fn is_main(&self) -> bool;
512    /// Check if the window is currently visible/mapped.
513    fn is_visible(&self) -> bool;
514    /// Hide or show the window.
515    fn set_visible(&self, visible: bool);
516    /// Bring the window to the front and focus it.
517    fn bring_to_front(&self);
518}
519
520/// A handle to a native window that can be used by application code.
521#[derive(Clone)]
522pub struct WindowHandle {
523    /// The unique identifier of this window.
524    pub id: WindowId,
525    /// Reference to the underlying platform window.
526    pub inner: Arc<dyn Window>,
527}
528
529impl std::fmt::Debug for WindowHandle {
530    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
531        f.debug_struct("WindowHandle")
532            .field("id", &self.id)
533            .finish()
534    }
535}
536
537impl WindowHandle {
538    /// Create a new WindowHandle.
539    pub fn new(id: WindowId, inner: Arc<dyn Window>) -> Self {
540        Self { id, inner }
541    }
542    /// Request the window to close.
543    pub fn close(self) {
544        self.inner.close();
545    }
546    /// Set the title text of the window.
547    pub fn set_title(&self, title: &str) {
548        self.inner.set_title(title);
549    }
550    /// Resize the window.
551    pub fn set_size(&self, width: f32, height: f32) {
552        self.inner.set_size(width, height);
553    }
554    /// Returns true if this window has key focus.
555    pub fn is_key(&self) -> bool {
556        self.inner.is_key()
557    }
558    /// Returns true if this is the main application window.
559    pub fn is_main(&self) -> bool {
560        self.inner.is_main()
561    }
562    /// Returns true if the window is visible.
563    pub fn is_visible(&self) -> bool {
564        self.inner.is_visible()
565    }
566    /// Set visibility of the window.
567    pub fn set_visible(&self, visible: bool) {
568        self.inner.set_visible(visible);
569    }
570    /// Bring this window to the foreground.
571    pub fn bring_to_front(&self) {
572        self.inner.bring_to_front();
573    }
574}
575
576/// Action to take when a window close request event is received.
577#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
578pub enum WindowCloseAction {
579    /// Close the window immediately.
580    Allow,
581    /// Request confirmation from the user (e.g. show dialog).
582    Confirm,
583    /// Ignore the close request.
584    Deny,
585}
586
587#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
588pub struct AssetKey(pub String);
589
590impl EnvKey for AssetKey {
591    type Value = Arc<dyn AssetManager>;
592    fn default_value() -> Self::Value {
593        Arc::new(DefaultAssetManager::new())
594    }
595}
596
597/// Asset state for async resource loading.
598#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
599pub enum AssetState<T> {
600    Loading,
601    Ready(T),
602    Error(String),
603}
604
605/// Design token value that can adapt to light/dark mode
606#[derive(Debug, Clone, Serialize, Deserialize)]
607#[serde(untagged)]
608pub enum TokenValue {
609    /// Single value (same for light and dark)
610    Single { value: String },
611    /// Different values for light and dark mode
612    Adaptive { light: String, dark: String },
613}
614
615/// YggdrasilTokens is the authoritative container for all design tokens in the CVKG ecosystem.
616#[derive(Debug, Clone, Serialize, Deserialize)]
617pub struct YggdrasilTokens {
618    pub color: HashMap<String, TokenValue>,
619    pub font: HashMap<String, TokenValue>,
620    pub spacing: HashMap<String, TokenValue>,
621    pub radius: HashMap<String, TokenValue>,
622    pub shadow: HashMap<String, TokenValue>,
623    pub border: HashMap<String, TokenValue>,
624    pub anim: HashMap<String, TokenValue>,
625    pub bifrost: HashMap<String, TokenValue>,
626    pub gungnir: HashMap<String, TokenValue>,
627    pub mjolnir: HashMap<String, TokenValue>,
628    pub accessibility: HashMap<String, TokenValue>,
629}
630
631impl Default for YggdrasilTokens {
632    fn default() -> Self {
633        Self::new()
634    }
635}
636
637impl YggdrasilTokens {
638    pub fn new() -> Self {
639        Self {
640            color: HashMap::new(),
641            font: HashMap::new(),
642            spacing: HashMap::new(),
643            radius: HashMap::new(),
644            shadow: HashMap::new(),
645            border: HashMap::new(),
646            anim: HashMap::new(),
647            bifrost: HashMap::new(),
648            gungnir: HashMap::new(),
649            mjolnir: HashMap::new(),
650            accessibility: HashMap::new(),
651        }
652    }
653
654    /// Get a color token value for the current mode
655    pub fn get_color(&self, key: &str, is_dark: bool) -> Option<String> {
656        self.color.get(key).map(|token| match token {
657            TokenValue::Single { value } => value.clone(),
658            TokenValue::Adaptive { light, dark } => {
659                if is_dark {
660                    dark.clone()
661                } else {
662                    light.clone()
663                }
664            }
665        })
666    }
667
668    /// Get a token value of any type and parse it into the target type
669    pub fn get<T: FromStr>(&self, category: &str, key: &str, is_dark: bool) -> Option<T> {
670        let map = match category {
671            "color" => &self.color,
672            "font" => &self.font,
673            "spacing" => &self.spacing,
674            "radius" => &self.radius,
675            "shadow" => &self.shadow,
676            "border" => &self.border,
677            "anim" => &self.anim,
678            "bifrost" => &self.bifrost,
679            "gungnir" => &self.gungnir,
680            "mjolnir" => &self.mjolnir,
681            "accessibility" => &self.accessibility,
682            _ => return None,
683        };
684
685        map.get(key).and_then(|token| match token {
686            TokenValue::Single { value } => value.parse().ok(),
687            TokenValue::Adaptive { light, dark } => {
688                let value = if is_dark { dark } else { light };
689                value.parse().ok()
690            }
691        })
692    }
693}
694
695pub trait View: Sized + Send {
696    /// The concrete type produced after applying modifiers.
697    /// For primitive views this is Self.
698    type Body: View;
699
700    fn body(self) -> Self::Body;
701
702    /// Render this view into the provided renderer at the specified bounds.
703    /// Primitive views override this to perform drawing operations.
704    fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
705
706    /// Calculate the natural (intrinsic) size of this view given proposed constraints.
707    /// This allows views like Buttons or Labels to inform the layout engine of their needs.
708    fn intrinsic_size(&self, _renderer: &mut dyn Renderer, _proposal: SizeProposal) -> Size {
709        Size::ZERO
710    }
711
712    /// Optionally provide a layout implementation for this view.
713    fn layout(&self) -> Option<&dyn layout::LayoutView> {
714        None
715    }
716
717    /// Returns the flex weight of this view for proportional distribution in stacks.
718    fn flex_weight(&self) -> f32 {
719        0.0
720    }
721
722    /// Returns the grid placement configuration for this view if it is laid out in a Grid.
723    fn get_grid_placement(&self) -> Option<GridPlacement> {
724        None
725    }
726
727    /// Provided modifier entry point
728    fn modifier<M: ViewModifier>(self, m: M) -> ModifiedView<Self, M> {
729        ModifiedView::new(self, m)
730    }
731
732    /// Apply a Bifrost (Frosted Glass) effect to the view
733    fn bifrost(
734        self,
735        blur: f32,
736        saturation: f32,
737        opacity: f32,
738    ) -> ModifiedView<Self, BifrostModifier> {
739        self.modifier(BifrostModifier {
740            blur,
741            saturation,
742            opacity,
743        })
744    }
745
746    /// Apply a Gungnir (Neon Glow) effect to the view
747    fn gungnir(
748        self,
749        color: impl Into<String>,
750        radius: f32,
751        intensity: f32,
752    ) -> ModifiedView<Self, GungnirModifier> {
753        self.modifier(GungnirModifier {
754            color: color.into(),
755            radius,
756            intensity,
757        })
758    }
759
760    /// Apply a Mjolnir Slice (Geometric cut) to the view
761    fn mjolnir_slice(self, angle: f32, offset: f32) -> ModifiedView<Self, MjolnirSliceModifier> {
762        self.modifier(MjolnirSliceModifier { angle, offset })
763    }
764
765    /// Apply a Mjolnir Shatter (Fragmented transition) to the view
766    fn mjolnir_shatter(
767        self,
768        pieces: u32,
769        force: f32,
770    ) -> ModifiedView<Self, MjolnirShatterModifier> {
771        self.modifier(MjolnirShatterModifier { pieces, force })
772    }
773
774    /// Mark this view as a Bifrost Bridge (Shared Element) for cross-view persistence
775    fn bifrost_bridge(self, id: impl Into<String>) -> ModifiedView<Self, BifrostBridgeModifier> {
776        self.modifier(BifrostBridgeModifier { id: id.into() })
777    }
778
779    /// Add a background color to this view
780    fn background(self, color: [f32; 4]) -> ModifiedView<Self, BackgroundModifier> {
781        self.modifier(BackgroundModifier { color })
782    }
783
784    /// Add padding to this view
785    fn padding(self, amount: f32) -> ModifiedView<Self, PaddingModifier> {
786        self.modifier(PaddingModifier { amount })
787    }
788
789    /// Set the opacity (alpha) of this view in the range [0.0, 1.0].
790    fn opacity(self, opacity: f32) -> ModifiedView<Self, OpacityModifier> {
791        self.modifier(OpacityModifier {
792            opacity: opacity.clamp(0.0, 1.0),
793        })
794    }
795
796    /// Override the foreground (text / icon) color of this view.
797    fn foreground_color(self, color: [f32; 4]) -> ModifiedView<Self, ForegroundColorModifier> {
798        self.modifier(ForegroundColorModifier { color })
799    }
800
801    /// Constrain this view to an explicit width and/or height.
802    /// Constrains the size of this view using fixed width/height values.
803    fn frame(self, width: Option<f32>, height: Option<f32>) -> ModifiedView<Self, FrameModifier> {
804        self.modifier(FrameModifier {
805            width,
806            height,
807            min_width: None,
808            max_width: None,
809            min_height: None,
810            max_height: None,
811            alignment: Alignment::Center,
812        })
813    }
814
815    /// Give this view a flex weight for proportional space distribution in stacks.
816    fn flex(self, weight: f32) -> ModifiedView<Self, FlexModifier> {
817        self.modifier(FlexModifier { weight })
818    }
819
820    /// Specify the grid placement configuration (column, row, column_span, row_span) for this view.
821    fn grid_placement(self, placement: GridPlacement) -> ModifiedView<Self, GridPlacementModifier> {
822        self.modifier(GridPlacementModifier { placement })
823    }
824
825    /// Overlay a view on top of this view, aligned and offset relative to it.
826    fn overlay<O: View + Clone + 'static>(
827        self,
828        overlay: O,
829        alignment: Alignment,
830        offset: [f32; 2],
831        on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
832    ) -> ModifiedView<Self, OverlayModifier> {
833        self.modifier(OverlayModifier {
834            overlay: overlay.erase(),
835            alignment,
836            offset,
837            on_dismiss,
838        })
839    }
840
841    /// Automatically add padding to avoid overlapping with platform safe areas (notches, bars).
842    fn safe_area_padding(self) -> ModifiedView<Self, SafeAreaModifier> {
843        self.modifier(SafeAreaModifier { ignores: false })
844    }
845
846    /// Explicitly ignore platform safe areas and draw into the margins.
847    fn ignores_safe_area(self) -> ModifiedView<Self, SafeAreaModifier> {
848        self.modifier(SafeAreaModifier { ignores: true })
849    }
850
851    /// Clip all child drawing to this view's bounds.
852    fn clip_to_bounds(self) -> ModifiedView<Self, ClipModifier> {
853        self.modifier(ClipModifier)
854    }
855
856    /// Draw a colored border around this view.
857    fn border(self, color: [f32; 4], width: f32) -> ModifiedView<Self, BorderModifier> {
858        self.modifier(BorderModifier { color, width })
859    }
860
861    /// Add elevation (shadow) to the view. Level determines the shadow depth.
862    fn elevation(self, level: f32) -> ModifiedView<Self, ElevationModifier> {
863        self.modifier(ElevationModifier { level })
864    }
865
866    /// Add a magnetic effect that pulls the view towards the cursor.
867    fn magnetic(self, radius: f32, intensity: f32) -> ModifiedView<Self, MagneticModifier> {
868        self.modifier(MagneticModifier { radius, intensity })
869    }
870
871    /// Add a ManiGlow (Lunar Illuminator) effect that glows near the cursor.
872    fn mani_glow(self, color: [f32; 4], radius: f32) -> ModifiedView<Self, ManiGlowModifier> {
873        self.modifier(ManiGlowModifier { color, radius })
874    }
875
876    /// Theme this view based on a specific memory layer.
877    fn memory_layer(self, layer: MemoryLayer) -> ModifiedView<Self, BifrostLayerModifier> {
878        self.modifier(BifrostLayerModifier { layer })
879    }
880
881    /// Enable Fafnir's Evolution: The component grows and glows as it is used.
882    fn fafnir_evolve(self, id: u64) -> ModifiedView<Self, FafnirModifier> {
883        self.modifier(FafnirModifier { id })
884    }
885
886    /// Enable Mimir's Intent: The component anticipates user interaction via pointer kinematics.
887    fn mimir_intent(self) -> ModifiedView<Self, MimirIntentModifier> {
888        self.modifier(MimirIntentModifier)
889    }
890
891    /// Enable Kvasir's Vibes: Subconscious telemetry representing cognitive complexity.
892    fn kvasir_vibes(self, complexity: f32) -> ModifiedView<Self, KvasirVibeModifier> {
893        self.modifier(KvasirVibeModifier { complexity })
894    }
895
896    /// Bestow Odin's Eye: Global omniscient observability layer.
897    fn odins_eye(self) -> ModifiedView<Self, OdinsEyeModifier> {
898        self.modifier(OdinsEyeModifier)
899    }
900
901    /// Trigger an action when the view appears
902    fn on_appear<F: Fn() + Send + Sync + 'static>(
903        self,
904        action: F,
905    ) -> ModifiedView<Self, LifecycleModifier> {
906        self.modifier(LifecycleModifier {
907            on_appear: Some(Arc::new(action)),
908            on_disappear: None,
909        })
910    }
911
912    /// Trigger an action when the view disappears
913    fn on_disappear<F: Fn() + Send + Sync + 'static>(
914        self,
915        action: F,
916    ) -> ModifiedView<Self, LifecycleModifier> {
917        self.modifier(LifecycleModifier {
918            on_appear: None,
919            on_disappear: Some(Arc::new(action)),
920        })
921    }
922
923    /// Trigger an action when the view is clicked
924    fn on_click<F: Fn() + Send + Sync + 'static>(
925        self,
926        action: F,
927    ) -> ModifiedView<Self, OnClickModifier> {
928        self.modifier(OnClickModifier {
929            action: Arc::new(action),
930        })
931    }
932
933    /// Trigger an action when the pointer enters the view bounds
934    fn on_pointer_enter<F: Fn() + Send + Sync + 'static>(
935        self,
936        action: F,
937    ) -> ModifiedView<Self, OnPointerEnterModifier> {
938        self.modifier(OnPointerEnterModifier {
939            action: Arc::new(action),
940        })
941    }
942
943    /// Trigger an action when the pointer leaves the view bounds
944    fn on_pointer_leave<F: Fn() + Send + Sync + 'static>(
945        self,
946        action: F,
947    ) -> ModifiedView<Self, OnPointerLeaveModifier> {
948        self.modifier(OnPointerLeaveModifier {
949            action: Arc::new(action),
950        })
951    }
952
953    /// Trigger an action when the pointer moves inside the view bounds
954    fn on_pointer_move<F: Fn(f32, f32) + Send + Sync + 'static>(
955        self,
956        action: F,
957    ) -> ModifiedView<Self, OnPointerMoveModifier> {
958        self.modifier(OnPointerMoveModifier {
959            action: Arc::new(action),
960        })
961    }
962
963    /// Trigger an action when the pointer is pressed down
964    fn on_pointer_down<F: Fn() + Send + Sync + 'static>(
965        self,
966        action: F,
967    ) -> ModifiedView<Self, OnPointerDownModifier> {
968        self.modifier(OnPointerDownModifier {
969            action: Arc::new(action),
970        })
971    }
972
973    /// Trigger an action when the pointer is released
974    fn on_pointer_up<F: Fn() + Send + Sync + 'static>(
975        self,
976        action: F,
977    ) -> ModifiedView<Self, OnPointerUpModifier> {
978        self.modifier(OnPointerUpModifier {
979            action: Arc::new(action),
980        })
981    }
982
983    /// Type-erase this view into AnyView
984    fn erase(self) -> AnyView
985    where
986        Self: Clone + 'static,
987    {
988        AnyView::new(self)
989    }
990
991    // =============================================================================
992    // ACCESSIBILITY
993    // =============================================================================
994
995    /// Return accessibility properties for this view.
996    /// Override to expose semantic role, label, state to assistive technology.
997    /// Default returns `None` (view is not explicitly accessible).
998    fn aria_properties(&self) -> Option<AriaProperties> {
999        None
1000    }
1001
1002    /// Handle a keyboard navigation event.
1003    /// Return true if consumed, false to bubble.
1004    fn on_key_event(&self, _key: &str, _modifiers: KeyModifiers) -> bool {
1005        false
1006    }
1007
1008    /// Return keyboard shortcuts this view responds to.
1009    fn key_shortcuts(&self) -> Vec<KeyShortcut> {
1010        vec![]
1011    }
1012}
1013
1014// =============================================================================
1015// ARIA PROPERTIES
1016// =============================================================================
1017
1018/// Semantic role for assistive technology (WCAG 2.1 §4.1.2).
1019#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1020pub enum AriaRole {
1021    Alert,
1022    Alertdialog,
1023    Article,
1024    Banner,
1025    Button,
1026    Checkbox,
1027    Columnheader,
1028    Combobox,
1029    Complementary,
1030    Contentinfo,
1031    Dialog,
1032    Form,
1033    Grid,
1034    Gridcell,
1035    Heading,
1036    Img,
1037    Link,
1038    List,
1039    Listbox,
1040    Listitem,
1041    Main,
1042    Menu,
1043    Menubar,
1044    Menuitem,
1045    Menuitemcheckbox,
1046    Menuitemradio,
1047    Navigation,
1048    None,
1049    Note,
1050    Option,
1051    Presentation,
1052    Progressbar,
1053    Radio,
1054    Radiogroup,
1055    Region,
1056    Row,
1057    Rowgroup,
1058    Rowheader,
1059    Search,
1060    Separator,
1061    Slider,
1062    Spinbutton,
1063    Status,
1064    Switch,
1065    Tab,
1066    Table,
1067    Tablist,
1068    Tabpanel,
1069    Textbox,
1070    Toolbar,
1071    Tooltip,
1072    Tree,
1073    Treeitem,
1074}
1075
1076/// Accessible properties for a view, describing its semantic role and state.
1077#[derive(Debug, Clone, Serialize, Deserialize)]
1078pub struct AriaProperties {
1079    pub role: AriaRole,
1080    pub label: String,
1081    pub description: Option<String>,
1082    pub value: Option<String>,
1083    pub pressed: Option<bool>,
1084    pub checked: Option<bool>,
1085    pub expanded: Option<bool>,
1086    pub disabled: bool,
1087    pub hidden: bool,
1088    pub level: Option<u8>,
1089    pub shortcut: Option<String>,
1090    pub focused: bool,
1091    pub live: Option<String>,
1092    pub atomic: bool,
1093}
1094
1095impl AriaProperties {
1096    pub fn new(role: AriaRole, label: impl Into<String>) -> Self {
1097        Self {
1098            role,
1099            label: label.into(),
1100            description: None,
1101            value: None,
1102            pressed: None,
1103            checked: None,
1104            expanded: None,
1105            disabled: false,
1106            hidden: false,
1107            level: None,
1108            shortcut: None,
1109            focused: false,
1110            live: None,
1111            atomic: false,
1112        }
1113    }
1114
1115    pub fn description(mut self, d: impl Into<String>) -> Self {
1116        self.description = Some(d.into());
1117        self
1118    }
1119    pub fn value(mut self, v: impl Into<String>) -> Self {
1120        self.value = Some(v.into());
1121        self
1122    }
1123    pub fn checked(mut self, c: bool) -> Self {
1124        self.checked = Some(c);
1125        self
1126    }
1127    pub fn disabled(mut self, d: bool) -> Self {
1128        self.disabled = d;
1129        self
1130    }
1131    pub fn expanded(mut self, e: bool) -> Self {
1132        self.expanded = Some(e);
1133        self
1134    }
1135    pub fn level(mut self, l: u8) -> Self {
1136        self.level = Some(l.clamp(1, 6));
1137        self
1138    }
1139    pub fn shortcut(mut self, s: impl Into<String>) -> Self {
1140        self.shortcut = Some(s.into());
1141        self
1142    }
1143    pub fn focused(mut self, f: bool) -> Self {
1144        self.focused = f;
1145        self
1146    }
1147}
1148
1149// =============================================================================
1150// KEYBOARD NAVIGATION
1151// =============================================================================
1152
1153/// Modifier keys for keyboard events.
1154#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
1155pub struct KeyModifiers {
1156    pub shift: bool,
1157    pub ctrl: bool,
1158    pub alt: bool,
1159    pub meta: bool,
1160}
1161
1162/// A keyboard shortcut binding.
1163#[derive(Debug, Clone, Serialize, Deserialize)]
1164pub struct KeyShortcut {
1165    pub key: String,
1166    pub modifiers: KeyModifiers,
1167    pub description: String,
1168}
1169
1170impl KeyShortcut {
1171    pub fn new(key: impl Into<String>, desc: impl Into<String>) -> Self {
1172        Self {
1173            key: key.into(),
1174            modifiers: KeyModifiers::default(),
1175            description: desc.into(),
1176        }
1177    }
1178    pub fn with_ctrl(mut self) -> Self {
1179        self.modifiers.ctrl = true;
1180        self
1181    }
1182    pub fn with_shift(mut self) -> Self {
1183        self.modifiers.shift = true;
1184        self
1185    }
1186    pub fn with_alt(mut self) -> Self {
1187        self.modifiers.alt = true;
1188        self
1189    }
1190    pub fn with_meta(mut self) -> Self {
1191        self.modifiers.meta = true;
1192        self
1193    }
1194}
1195
1196// =============================================================================
1197// FOCUS MANAGEMENT
1198// =============================================================================
1199
1200/// Unique ID for a focusable element.
1201#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1202pub struct FocusableId(String);
1203
1204impl From<&str> for FocusableId {
1205    fn from(s: &str) -> Self {
1206        Self(s.to_string())
1207    }
1208}
1209impl From<String> for FocusableId {
1210    fn from(s: String) -> Self {
1211        Self(s)
1212    }
1213}
1214
1215/// Focus trap for confining Tab navigation (e.g., modals).
1216#[derive(Debug, Clone)]
1217pub struct FocusTrap {
1218    pub id: FocusableId,
1219    pub order: Vec<FocusableId>,
1220    pub wrap: bool,
1221}
1222
1223impl FocusTrap {
1224    pub fn new(id: impl Into<FocusableId>, order: Vec<FocusableId>) -> Self {
1225        Self {
1226            id: id.into(),
1227            order,
1228            wrap: true,
1229        }
1230    }
1231}
1232
1233/// Manages focus order, Tab/Shift+Tab navigation, and focus traps.
1234#[derive(Debug, Default)]
1235pub struct FocusManager {
1236    order: Vec<FocusableId>,
1237    focused: Option<FocusableId>,
1238    traps: Vec<FocusTrap>,
1239}
1240
1241impl FocusManager {
1242    pub fn new() -> Self {
1243        Self::default()
1244    }
1245
1246    pub fn register(&mut self, id: impl Into<FocusableId>) {
1247        let id = id.into();
1248        if !self.order.contains(&id) {
1249            self.order.push(id);
1250        }
1251    }
1252
1253    pub fn unregister(&mut self, id: &FocusableId) {
1254        self.order.retain(|x| x != id);
1255        if self.focused.as_ref() == Some(id) {
1256            self.focused = None;
1257        }
1258    }
1259
1260    pub fn focused(&self) -> Option<&FocusableId> {
1261        self.focused.as_ref()
1262    }
1263
1264    pub fn focus(&mut self, id: impl Into<FocusableId>) -> bool {
1265        let id = id.into();
1266        if self.order.contains(&id) || self.traps.iter().any(|t| t.order.contains(&id)) {
1267            self.focused = Some(id);
1268            true
1269        } else {
1270            false
1271        }
1272    }
1273
1274    pub fn focus_next(&mut self) -> Option<&FocusableId> {
1275        let order = self.effective_order();
1276        if order.is_empty() {
1277            return None;
1278        }
1279        let idx = self
1280            .focused
1281            .as_ref()
1282            .and_then(|f| order.iter().position(|x| x == f));
1283        let next = match idx {
1284            Some(i) if i + 1 < order.len() => &order[i + 1],
1285            _ => &order[0],
1286        };
1287        self.focused = Some(next.clone());
1288        self.focused.as_ref()
1289    }
1290
1291    pub fn focus_prev(&mut self) -> Option<&FocusableId> {
1292        let order = self.effective_order();
1293        if order.is_empty() {
1294            return None;
1295        }
1296        let idx = self
1297            .focused
1298            .as_ref()
1299            .and_then(|f| order.iter().position(|x| x == f));
1300        let prev = match idx {
1301            Some(i) if i > 0 => &order[i - 1],
1302            _ => &order[order.len() - 1],
1303        };
1304        self.focused = Some(prev.clone());
1305        self.focused.as_ref()
1306    }
1307
1308    pub fn push_trap(&mut self, trap: FocusTrap) -> FocusableId {
1309        let id = trap.id.clone();
1310        self.traps.push(trap);
1311        id
1312    }
1313
1314    pub fn pop_trap(&mut self) {
1315        self.traps.pop();
1316    }
1317    pub fn trap_count(&self) -> usize {
1318        self.traps.len()
1319    }
1320
1321    fn effective_order(&self) -> &[FocusableId] {
1322        self.traps
1323            .last()
1324            .map(|t| t.order.as_slice())
1325            .unwrap_or(&self.order)
1326    }
1327}
1328
1329// =============================================================================
1330// REDUCED MOTION
1331// =============================================================================
1332
1333/// Detects OS-level reduced motion preference.
1334pub fn is_reduced_motion() -> bool {
1335    std::env::var("GTK_THEME")
1336        .map(|v| v.to_lowercase().contains("reduced"))
1337        .unwrap_or(false)
1338        || std::env::var("NO_ANIMATIONS")
1339            .map(|v| v == "1" || v.to_lowercase() == "true")
1340            .unwrap_or(false)
1341        || std::env::var("ACCESSIBILITY_REDUCED_MOTION")
1342            .map(|v| v == "1" || v.to_lowercase() == "true")
1343            .unwrap_or(false)
1344}
1345
1346/// Returns effective animation duration (0.0 if reduced motion is active).
1347pub fn effective_duration(secs: f32) -> f32 {
1348    if is_reduced_motion() { 0.0 } else { secs }
1349}
1350
1351/// An object-safe version of the View trait for type erasure.
1352pub trait ErasedView: Send {
1353    fn render_erased(&self, renderer: &mut dyn Renderer, rect: Rect);
1354    fn name(&self) -> &'static str;
1355    fn flex_weight_erased(&self) -> f32;
1356    fn layout_erased(&self) -> Option<&dyn layout::LayoutView>;
1357    fn grid_placement_erased(&self) -> Option<GridPlacement>;
1358    fn clone_box(&self) -> Box<dyn ErasedView>;
1359}
1360
1361impl<V: View + Clone + 'static> ErasedView for V {
1362    fn render_erased(&self, renderer: &mut dyn Renderer, rect: Rect) {
1363        self.render(renderer, rect);
1364    }
1365
1366    fn name(&self) -> &'static str {
1367        std::any::type_name::<V>()
1368    }
1369
1370    fn flex_weight_erased(&self) -> f32 {
1371        self.flex_weight()
1372    }
1373
1374    fn layout_erased(&self) -> Option<&dyn layout::LayoutView> {
1375        self.layout()
1376    }
1377
1378    fn grid_placement_erased(&self) -> Option<GridPlacement> {
1379        self.get_grid_placement()
1380    }
1381
1382    fn clone_box(&self) -> Box<dyn ErasedView> {
1383        Box::new(self.clone())
1384    }
1385}
1386
1387/// A view that memoizes its rendering based on a stable ID and data hash.
1388/// The renderer can use this to skip re-rendering the sub-tree if the data hasn't changed.
1389pub struct MemoView<V, F> {
1390    id: u64,
1391    data_hash: u64,
1392    builder: F,
1393    _v: std::marker::PhantomData<V>,
1394}
1395
1396impl<V: View, F: Fn() -> V + Send + Sync> MemoView<V, F> {
1397    /// Create a new MemoView with a stable ID and a data hash.
1398    pub fn new(id: u64, data_hash: u64, builder: F) -> Self {
1399        Self {
1400            id,
1401            data_hash,
1402            builder,
1403            _v: std::marker::PhantomData,
1404        }
1405    }
1406}
1407
1408impl<V: View + 'static, F: Fn() -> V + Send + Sync + 'static> View for MemoView<V, F> {
1409    type Body = Never;
1410    fn body(self) -> Self::Body {
1411        unreachable!("MemoView does not have a body")
1412    }
1413
1414    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1415        renderer.memoize(self.id, self.data_hash, &|r| {
1416            let view = (self.builder)();
1417            view.render(r, rect);
1418        });
1419    }
1420}
1421
1422/// A type-erased View wrapper.
1423pub struct AnyView {
1424    inner: Box<dyn ErasedView>,
1425}
1426
1427impl Clone for AnyView {
1428    fn clone(&self) -> Self {
1429        Self {
1430            inner: self.inner.clone_box(),
1431        }
1432    }
1433}
1434
1435impl AnyView {
1436    pub fn new<V: View + Clone + 'static>(view: V) -> Self {
1437        Self {
1438            inner: Box::new(view),
1439        }
1440    }
1441}
1442
1443impl View for AnyView {
1444    type Body = Never;
1445    fn body(self) -> Self::Body {
1446        unreachable!()
1447    }
1448
1449    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1450        renderer.push_vnode(rect, self.inner.name());
1451        self.inner.render_erased(renderer, rect);
1452        renderer.pop_vnode();
1453    }
1454
1455    fn flex_weight(&self) -> f32 {
1456        self.inner.flex_weight_erased()
1457    }
1458
1459    fn layout(&self) -> Option<&dyn layout::LayoutView> {
1460        self.inner.layout_erased()
1461    }
1462
1463    fn get_grid_placement(&self) -> Option<GridPlacement> {
1464        self.inner.grid_placement_erased()
1465    }
1466}
1467
1468/// BifrostBridgeModifier enables shared-element transitions.
1469/// When two views share the same Bifrost Bridge ID, the Sleipnir solver will
1470/// interpolate their geometry and effects (blur, glow) during the transition.
1471#[derive(Debug, Clone, PartialEq)]
1472pub struct BifrostBridgeModifier {
1473    pub id: String,
1474}
1475
1476impl ViewModifier for BifrostBridgeModifier {
1477    fn modify<V: View>(self, content: V) -> impl View {
1478        ModifiedView::new(content, self)
1479    }
1480
1481    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1482        // Register this element with the renderer for shared-element transition logic
1483        renderer.register_shared_element(&self.id, rect);
1484    }
1485}
1486
1487/// MjolnirSliceModifier implements the "Geometric Slice" aesthetic.
1488/// It uses a signed distance field (SDF) to clip the view along a sharp angled line.
1489#[derive(Debug, Clone, Copy, PartialEq)]
1490pub struct MjolnirSliceModifier {
1491    pub angle: f32,
1492    pub offset: f32,
1493}
1494
1495impl ViewModifier for MjolnirSliceModifier {
1496    fn modify<V: View>(self, content: V) -> impl View {
1497        ModifiedView::new(content, self)
1498    }
1499
1500    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1501        renderer.push_mjolnir_slice(self.angle, self.offset);
1502    }
1503
1504    fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1505        renderer.pop_mjolnir_slice();
1506    }
1507}
1508
1509/// MjolnirShatterModifier implements the "Shattering" effect.
1510/// It breaks the view into discrete geometric fragments that can be animated.
1511#[derive(Debug, Clone, Copy, PartialEq)]
1512pub struct MjolnirShatterModifier {
1513    pub pieces: u32,
1514    pub force: f32,
1515}
1516
1517impl ViewModifier for MjolnirShatterModifier {
1518    fn modify<V: View>(self, content: V) -> impl View {
1519        ModifiedView::new(content, self)
1520    }
1521
1522    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1523        // RADIAL SHATTER: Fragment the view into wedges
1524        let pieces = self.pieces.max(1);
1525        for i in 0..pieces {
1526            let progress = i as f32 / pieces as f32;
1527            let next_progress = (i + 1) as f32 / pieces as f32;
1528
1529            let angle_start = progress * 360.0;
1530            let angle_end = next_progress * 360.0;
1531
1532            // Wedge slice: intersection of two half-planes
1533            renderer.push_mjolnir_slice(angle_start, 0.0);
1534            renderer.push_mjolnir_slice(angle_end + 180.0, 0.0);
1535
1536            // Apply radial force offset
1537            let mid_angle = (angle_start + angle_end) / 2.0;
1538            let rad = mid_angle.to_radians();
1539            let dx = rad.cos() * self.force;
1540            let dy = rad.sin() * self.force;
1541
1542            let shard_rect = Rect {
1543                x: rect.x + dx,
1544                y: rect.y + dy,
1545                ..rect
1546            };
1547
1548            view.render(renderer, shard_rect);
1549
1550            renderer.pop_mjolnir_slice();
1551            renderer.pop_mjolnir_slice();
1552        }
1553    }
1554}
1555
1556/// BifrostModifier implements the Cyberpunk "Frosted Glass" aesthetic.
1557/// It triggers backdrop blurring and light scattering in the render pipeline.
1558#[derive(Debug, Clone, Copy, PartialEq)]
1559pub struct BifrostModifier {
1560    pub blur: f32,
1561    pub saturation: f32,
1562    pub opacity: f32,
1563}
1564
1565impl ViewModifier for BifrostModifier {
1566    fn modify<V: View>(self, content: V) -> impl View {
1567        ModifiedView::new(content, self)
1568    }
1569
1570    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1571        if renderer.is_over_budget() {
1572            // Degrade: Use lower quality (half blur) if over budget
1573            renderer.bifrost(rect, self.blur * 0.5, self.saturation, self.opacity);
1574        } else {
1575            renderer.bifrost(rect, self.blur, self.saturation, self.opacity);
1576        }
1577    }
1578}
1579
1580/// A modifier that adds a background color to a view.
1581#[derive(Debug, Clone, Copy, PartialEq)]
1582pub struct BackgroundModifier {
1583    pub color: [f32; 4],
1584}
1585
1586impl ViewModifier for BackgroundModifier {
1587    fn modify<V: View>(self, content: V) -> impl View {
1588        ModifiedView::new(content, self)
1589    }
1590
1591    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1592        renderer.fill_rect(rect, self.color);
1593    }
1594}
1595
1596/// A modifier that adds padding to a view.
1597#[derive(Debug, Clone, Copy, PartialEq)]
1598pub struct PaddingModifier {
1599    pub amount: f32,
1600}
1601
1602impl ViewModifier for PaddingModifier {
1603    fn modify<V: View>(self, content: V) -> impl View {
1604        ModifiedView::new(content, self)
1605    }
1606
1607    fn transform_rect(&self, rect: Rect) -> Rect {
1608        Rect {
1609            x: rect.x + self.amount,
1610            y: rect.y + self.amount,
1611            width: (rect.width - 2.0 * self.amount).max(0.0),
1612            height: (rect.height - 2.0 * self.amount).max(0.0),
1613        }
1614    }
1615
1616    fn transform_proposal(&self, mut proposal: SizeProposal) -> SizeProposal {
1617        if let Some(w) = proposal.width {
1618            proposal.width = Some((w - 2.0 * self.amount).max(0.0));
1619        }
1620        if let Some(h) = proposal.height {
1621            proposal.height = Some((h - 2.0 * self.amount).max(0.0));
1622        }
1623        proposal
1624    }
1625
1626    fn transform_size(&self, mut size: Size) -> Size {
1627        size.width += 2.0 * self.amount;
1628        size.height += 2.0 * self.amount;
1629        size
1630    }
1631}
1632
1633/// GungnirModifier implements the "Neon Glow" aesthetic.
1634/// It uses additive blending and multi-pass blurring to simulate glowing light.
1635#[derive(Debug, Clone, PartialEq)]
1636pub struct GungnirModifier {
1637    pub color: String,
1638    pub radius: f32,
1639    pub intensity: f32,
1640}
1641
1642impl ViewModifier for GungnirModifier {
1643    fn modify<V: View>(self, content: V) -> impl View {
1644        ModifiedView::new(content, self)
1645    }
1646
1647    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1648        // Neon Glow using Mode 1 in the Surtr pipeline
1649        renderer.stroke_rect(rect, [0.0, 1.0, 1.0, self.intensity], self.radius / 10.0);
1650    }
1651}
1652
1653/// GungnirPulseModifier implements a "breathing" neon effect.
1654#[derive(Debug, Clone, Copy, PartialEq)]
1655pub struct GungnirPulseModifier {
1656    pub color: [f32; 4],
1657    pub radius: f32,
1658    pub speed: f32,
1659}
1660
1661impl ViewModifier for GungnirPulseModifier {
1662    fn modify<V: View>(self, content: V) -> impl View {
1663        ModifiedView::new(content, self)
1664    }
1665
1666    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1667        let time = std::time::SystemTime::now()
1668            .duration_since(std::time::UNIX_EPOCH)
1669            .unwrap_or_default()
1670            .as_secs_f32();
1671
1672        // Mode 19: Dashed Border
1673        // Mode 20: 9-Slice / Patch Scaling
1674        let intensity = (time * self.speed).sin() * 0.5 + 0.5;
1675        let mut color = self.color;
1676        color[3] *= intensity;
1677
1678        // Mode 1 neon glow with dynamic intensity
1679        renderer.stroke_rect(rect, color, self.radius);
1680    }
1681}
1682
1683/// MagneticModifier makes a view "magnetic", subtly leaning towards or pulling the cursor.
1684#[derive(Debug, Clone, Copy, PartialEq)]
1685pub struct MagneticModifier {
1686    pub radius: f32,
1687    pub intensity: f32,
1688}
1689
1690impl ViewModifier for MagneticModifier {
1691    fn modify<V: View>(self, content: V) -> impl View {
1692        ModifiedView::new(content, self)
1693    }
1694
1695    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1696        let [px, py] = renderer.get_pointer_position();
1697        let center_x = rect.x + rect.width / 2.0;
1698        let center_y = rect.y + rect.height / 2.0;
1699
1700        let dx = px - center_x;
1701        let dy = py - center_y;
1702        let dist = (dx * dx + dy * dy).sqrt();
1703
1704        let mut offset_x = 0.0;
1705        let mut offset_y = 0.0;
1706
1707        if dist < self.radius && dist > 0.0 {
1708            let force = (1.0 - dist / self.radius) * self.intensity;
1709            offset_x = dx * force;
1710            offset_y = dy * force;
1711        }
1712
1713        let magnetic_rect = Rect {
1714            x: rect.x + offset_x,
1715            y: rect.y + offset_y,
1716            ..rect
1717        };
1718
1719        view.render(renderer, magnetic_rect);
1720    }
1721}
1722
1723/// ManiGlowModifier adds a soft, lunar-like cursor glow to a view.
1724/// Named after Máni, the personification of the Moon.
1725#[derive(Debug, Clone, Copy, PartialEq)]
1726pub struct ManiGlowModifier {
1727    pub color: [f32; 4],
1728    pub radius: f32,
1729}
1730
1731impl ViewModifier for ManiGlowModifier {
1732    fn modify<V: View>(self, content: V) -> impl View {
1733        ModifiedView::new(content, self)
1734    }
1735
1736    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1737        if crate::load_system_state().realm == Realm::Asgard {
1738            renderer.mani_glow(rect, self.color, self.radius);
1739        }
1740        view.render(renderer, rect);
1741    }
1742}
1743
1744/// BifrostLayerModifier themes a view based on its cognitive memory layer.
1745/// Episodic: Shifting aurora clouds.
1746/// Semantic: Crystalline gold.
1747/// Procedural: Heavy obsidian stone.
1748#[derive(Debug, Clone, Copy, PartialEq)]
1749pub struct BifrostLayerModifier {
1750    pub layer: MemoryLayer,
1751}
1752
1753impl ViewModifier for BifrostLayerModifier {
1754    fn modify<V: View>(self, content: V) -> impl View {
1755        ModifiedView::new(content, self)
1756    }
1757
1758    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1759        let realm = crate::load_system_state().realm;
1760        match self.layer {
1761            MemoryLayer::Episodic => {
1762                if realm == Realm::Asgard {
1763                    renderer.bifrost(rect, 40.0, 1.2, 0.7);
1764                } else {
1765                    renderer.fill_rect(rect, [0.1, 0.12, 0.15, 0.8]);
1766                }
1767            }
1768            MemoryLayer::Semantic => {
1769                if realm == Realm::Asgard {
1770                    renderer.gungnir(rect, [1.0, 0.84, 0.0, 1.0], 15.0, 0.6);
1771                } else {
1772                    renderer.stroke_rect(rect, [0.4, 0.4, 0.4, 1.0], 1.5);
1773                }
1774            }
1775            MemoryLayer::Procedural => {
1776                renderer.fill_rect(rect, [0.05, 0.05, 0.07, 0.95]);
1777                let stroke_color = if realm == Realm::Asgard {
1778                    [0.3, 0.3, 0.3, 1.0]
1779                } else {
1780                    [0.2, 0.2, 0.2, 1.0]
1781                };
1782                renderer.stroke_rect(rect, stroke_color, 2.0);
1783            }
1784        }
1785        view.render(renderer, rect);
1786    }
1787}
1788
1789/// FafnirModifier enables self-evolving UI capabilities.
1790/// Named after Fafnir, the dragon who grows in power based on the gold he hoards.
1791/// In CVKG, 'Gold' is user attention/interaction.
1792#[derive(Debug, Clone, Copy, PartialEq)]
1793pub struct FafnirModifier {
1794    /// Unique ID for tracking this component's vitality across frames.
1795    pub id: u64,
1796}
1797
1798impl ViewModifier for FafnirModifier {
1799    fn modify<V: View>(self, content: V) -> impl View {
1800        ModifiedView::new(content, self)
1801    }
1802
1803    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1804        let state = crate::load_system_state();
1805        let vitality = state
1806            .get_component_state::<f32>(self.id)
1807            .map(|v| *v.read().unwrap())
1808            .unwrap_or(1.0);
1809
1810        // Calculate evolutionary growth factors
1811        // Max growth at vitality 5.0 (50% scale increase, strong glow)
1812        let growth = (vitality - 1.0).clamp(0.0, 4.0);
1813        let scale = 1.0 + growth * 0.12;
1814        let glow_intensity = growth * 0.25;
1815
1816        // Feed Fafnir: Register interaction to boost vitality
1817        let id = self.id;
1818        renderer.register_handler(
1819            "pointermove",
1820            std::sync::Arc::new(move |_| {
1821                crate::update_system_state(|s| {
1822                    let mut s = s.clone();
1823                    let v = s
1824                        .get_component_state::<f32>(id)
1825                        .map(|v| *v.read().unwrap())
1826                        .unwrap_or(1.0);
1827                    s.set_component_state(id, (v + 0.05).min(5.0)); // Cap at 5.0
1828                    s
1829                });
1830            }),
1831        );
1832
1833        if scale > 1.01 {
1834            renderer.push_transform([0.0, 0.0], [scale, scale], 0.0);
1835        }
1836
1837        if glow_intensity > 0.1 && state.realm == Realm::Asgard {
1838            renderer.gungnir(rect, [1.0, 0.84, 0.0, 1.0], 15.0 * vitality, glow_intensity);
1839        }
1840
1841        view.render(renderer, rect);
1842
1843        if scale > 1.01 {
1844            renderer.pop_transform();
1845        }
1846    }
1847}
1848
1849/// MimirIntentModifier anticipates user movement and manifests holographic ghosts.
1850#[derive(Debug, Clone, Copy, PartialEq)]
1851pub struct MimirIntentModifier;
1852
1853impl ViewModifier for MimirIntentModifier {
1854    fn modify<V: View>(self, content: V) -> impl View {
1855        ModifiedView::new(content, self)
1856    }
1857
1858    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1859        let state = crate::load_system_state();
1860        let pos = state.last_pointer_pos;
1861        let vel = state.pointer_velocity;
1862
1863        // Calculate if the cursor is moving towards this rect
1864        let center = [rect.x + rect.width / 2.0, rect.y + rect.height / 2.0];
1865        let dx = center[0] - pos[0];
1866        let dy = center[1] - pos[1];
1867
1868        // Dot product of velocity and direction to center
1869        let dot = vel[0] * dx + vel[1] * dy;
1870        let speed_sq = vel[0] * vel[0] + vel[1] * vel[1];
1871        let dist_sq = dx * dx + dy * dy;
1872
1873        if dot > 0.0 && dist_sq < 250.0 * 250.0 && speed_sq > 0.5 && state.realm == Realm::Asgard {
1874            // Intent detected: render a subtle "ghost" reveal
1875            let intent_strength = (dot / (speed_sq.sqrt() * dist_sq.sqrt())).clamp(0.0, 1.0);
1876            renderer.stroke_rect(rect, [0.0, 0.9, 1.0, 0.3 * intent_strength], 1.5);
1877        }
1878
1879        view.render(renderer, rect);
1880    }
1881}
1882
1883/// KvasirVibeModifier renders a cognitive telemetry cloud representing agent complexity.
1884#[derive(Debug, Clone, Copy, PartialEq)]
1885pub struct KvasirVibeModifier {
1886    pub complexity: f32,
1887}
1888
1889impl ViewModifier for KvasirVibeModifier {
1890    fn modify<V: View>(self, content: V) -> impl View {
1891        ModifiedView::new(content, self)
1892    }
1893
1894    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1895        if crate::load_system_state().realm == Realm::Asgard {
1896            let t = renderer.elapsed_time();
1897            let c = self.complexity.clamp(0.0, 1.0);
1898
1899            // 1. Core Cognitive Cloud (Bifrost)
1900            // Turbulence increases with complexity
1901            let blur = 20.0 + c * 40.0;
1902            let turbulence_x = (t * (1.0 + c * 2.0)).sin() * 8.0 * c;
1903            let turbulence_y = (t * (0.8 + c * 1.5)).cos() * 5.0 * c;
1904            renderer.bifrost(
1905                rect.offset(turbulence_x, turbulence_y),
1906                blur,
1907                0.8 + c * 0.4,
1908                0.25,
1909            );
1910
1911            // 2. Synaptic Discharge (Gungnir pulses)
1912            if c > 0.2 {
1913                let pulse = (t * (3.0 + c * 5.0)).sin().abs() * c;
1914                let color = [0.0, 0.9, 1.0, 0.4 * pulse]; // Cyan synaptic pulse
1915                renderer.gungnir(rect, color, 12.0 + c * 24.0, 0.6 * pulse);
1916            }
1917
1918            // 3. Unstable Resonance (Magenta/Red shift for high complexity)
1919            if c > 0.7 {
1920                let instability = (t * 15.0).cos().abs() * (c - 0.7) * 3.3;
1921                let warning_color = [1.0, 0.0, 0.4, 0.12 * instability];
1922                renderer.fill_rect(rect, warning_color);
1923                renderer.stroke_rect(rect, [1.0, 0.0, 0.2, 0.45 * instability], 1.8);
1924            }
1925        }
1926        view.render(renderer, rect);
1927    }
1928}
1929
1930/// OdinsEyeModifier bestows omniscient observability over the entire scene graph.
1931#[derive(Debug, Clone, Copy, PartialEq)]
1932pub struct OdinsEyeModifier;
1933
1934impl ViewModifier for OdinsEyeModifier {
1935    fn modify<V: View>(self, content: V) -> impl View {
1936        ModifiedView::new(content, self)
1937    }
1938
1939    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1940        let state = crate::load_system_state();
1941        let t = renderer.elapsed_time();
1942
1943        // 1. Render Background content
1944        view.render(renderer, rect);
1945
1946        if state.realm == Realm::Asgard {
1947            // 2. Bestow Odin's Eye (Atmospheric Overlay)
1948            // Soft, large circular pulse representing the 'Eye'
1949            let eye_pulse = (t * 0.5).sin().abs() * 0.05;
1950            renderer.draw_radial_gradient(
1951                rect,
1952                [0.0, 0.6, 0.8, 0.08 + eye_pulse], // Inner Cyan
1953                [0.0, 0.0, 0.0, 0.0],              // Outer Black
1954            );
1955
1956            // 3. Hugin (Thought) Telemetry - Left Side
1957            let hugin_rect = Rect {
1958                x: rect.x + 20.0,
1959                y: rect.y + 40.0,
1960                width: 200.0,
1961                height: rect.height - 80.0,
1962            };
1963            renderer.draw_text(
1964                "HUGIN: THOUGHT",
1965                hugin_rect.x,
1966                hugin_rect.y,
1967                10.0,
1968                [0.0, 1.0, 1.0, 0.6],
1969            );
1970            for (i, thought) in state.thoughts.iter().rev().take(10).enumerate() {
1971                renderer.draw_text(
1972                    thought,
1973                    hugin_rect.x,
1974                    hugin_rect.y + 20.0 + i as f32 * 14.0,
1975                    9.0,
1976                    [1.0, 1.0, 1.0, 0.4],
1977                );
1978            }
1979
1980            // 4. Munin (Memory) Telemetry - Right Side
1981            let munin_rect = Rect {
1982                x: rect.x + rect.width - 220.0,
1983                y: rect.y + 40.0,
1984                width: 200.0,
1985                height: rect.height - 80.0,
1986            };
1987            renderer.draw_text(
1988                "MUNIN: MEMORY",
1989                munin_rect.x,
1990                munin_rect.y,
1991                10.0,
1992                [1.0, 0.84, 0.0, 0.6],
1993            );
1994            for (i, node) in state.nodes.iter().take(10).enumerate() {
1995                let opacity = (node.weight.min(1.0)) * 0.5;
1996                renderer.draw_text(
1997                    &node.id,
1998                    munin_rect.x,
1999                    munin_rect.y + 20.0 + i as f32 * 14.0,
2000                    9.0,
2001                    [1.0, 1.0, 1.0, opacity],
2002                );
2003            }
2004
2005            // 5. Omniscient Focus Beams (Gungnir Beams)
2006            if let Some(focus_id) = &state.odin_focus {
2007                // Visualize causal links to the focus node
2008                renderer.draw_text(
2009                    &format!("EYE FOCUS: {}", focus_id),
2010                    rect.x + rect.width / 2.0 - 50.0,
2011                    rect.y + 20.0,
2012                    12.0,
2013                    [0.0, 1.0, 1.0, 0.8],
2014                );
2015
2016                // In a real implementation, we would find the rect of the focus_id component.
2017                // For the 'Eye', we manifest a central beam of wisdom.
2018                renderer.gungnir(
2019                    Rect {
2020                        x: rect.x + rect.width / 2.0 - 1.0,
2021                        y: rect.y,
2022                        width: 2.0,
2023                        height: rect.height,
2024                    },
2025                    [0.0, 1.0, 1.0, 1.0],
2026                    20.0,
2027                    0.4,
2028                );
2029            }
2030        }
2031    }
2032}
2033
2034/// Sleipnir spring parameters for the physics solver
2035#[derive(Debug, Clone, Copy, PartialEq)]
2036pub struct SleipnirParams {
2037    pub stiffness: f32,
2038    pub damping: f32,
2039    pub mass: f32,
2040}
2041
2042impl SleipnirParams {
2043    pub fn snappy() -> Self {
2044        Self {
2045            stiffness: 230.0,
2046            damping: 22.0,
2047            mass: 1.0,
2048        }
2049    }
2050    pub fn fluid() -> Self {
2051        Self {
2052            stiffness: 170.0,
2053            damping: 26.0,
2054            mass: 1.0,
2055        }
2056    }
2057    pub fn heavy() -> Self {
2058        Self {
2059            stiffness: 90.0,
2060            damping: 20.0,
2061            mass: 1.0,
2062        }
2063    }
2064    pub fn bouncy() -> Self {
2065        Self {
2066            stiffness: 190.0,
2067            damping: 14.0,
2068            mass: 1.0,
2069        }
2070    }
2071}
2072
2073impl Default for SleipnirParams {
2074    fn default() -> Self {
2075        Self::fluid()
2076    }
2077}
2078
2079#[derive(Debug, Clone, Copy, PartialEq)]
2080struct SolverState {
2081    x: f32,
2082    v: f32,
2083}
2084
2085/// SleipnirSolver implements a 4th-order Runge-Kutta (RK4) integration for springs.
2086/// This provides superior stability for high-fidelity interactive motion.
2087#[derive(Debug, Clone, Copy, PartialEq)]
2088pub struct SleipnirSolver {
2089    params: SleipnirParams,
2090    target: f32,
2091    state: SolverState,
2092}
2093
2094impl SleipnirSolver {
2095    /// Create a new solver with a target value and starting state.
2096    pub fn new(params: SleipnirParams, target: f32, current: f32) -> Self {
2097        Self {
2098            params,
2099            target,
2100            state: SolverState { x: current, v: 0.0 },
2101        }
2102    }
2103
2104    /// Advance the simulation by dt seconds using RK4 integration.
2105    pub fn tick(&mut self, dt: f32) -> f32 {
2106        if dt <= 0.0 {
2107            return self.state.x;
2108        }
2109
2110        // Use a fixed time step for stability if dt is too large
2111        let mut remaining = dt;
2112        let step = 1.0 / 120.0;
2113
2114        while remaining > 0.0 {
2115            let d = remaining.min(step);
2116            self.step(d);
2117            remaining -= d;
2118        }
2119
2120        self.state.x
2121    }
2122
2123    fn step(&mut self, dt: f32) {
2124        let a = self.evaluate(self.state, 0.0, SolverState { x: 0.0, v: 0.0 });
2125        let b = self.evaluate(self.state, dt * 0.5, a);
2126        let c = self.evaluate(self.state, dt * 0.5, b);
2127        let d = self.evaluate(self.state, dt, c);
2128
2129        let dxdt = 1.0 / 6.0 * (a.x + 2.0 * (b.x + c.x) + d.x);
2130        let dvdt = 1.0 / 6.0 * (a.v + 2.0 * (b.v + c.v) + d.v);
2131
2132        self.state.x += dxdt * dt;
2133        self.state.v += dvdt * dt;
2134    }
2135
2136    fn evaluate(&self, initial: SolverState, dt: f32, d: SolverState) -> SolverState {
2137        let state = SolverState {
2138            x: initial.x + d.x * dt,
2139            v: initial.v + d.v * dt,
2140        };
2141        let force =
2142            -self.params.stiffness * (state.x - self.target) - self.params.damping * state.v;
2143        let mass = self.params.mass.max(0.001);
2144        SolverState {
2145            x: state.v,
2146            v: force / mass,
2147        }
2148    }
2149
2150    pub fn is_settled(&self) -> bool {
2151        (self.state.x - self.target).abs() < 0.001 && self.state.v.abs() < 0.001
2152    }
2153
2154    pub fn set_target(&mut self, target: f32) {
2155        self.target = target;
2156    }
2157
2158    pub fn current_value(&self) -> f32 {
2159        self.state.x
2160    }
2161}
2162
2163/// SleipnirModifier handles physics-based animations via the Sleipnir RK4 solver.
2164#[derive(Debug, Clone, PartialEq)]
2165pub struct SleipnirModifier {
2166    pub id: u64,
2167    pub target: f32,
2168    pub params: SleipnirParams,
2169}
2170
2171impl ViewModifier for SleipnirModifier {
2172    fn modify<V: View>(self, content: V) -> impl View {
2173        ModifiedView::new(content, self)
2174    }
2175
2176    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2177        let state = load_system_state();
2178
2179        // Try to fetch the solver from persistent state.
2180        let solver_lock_opt = state.get_component_state::<SleipnirSolver>(self.id);
2181
2182        let current_val;
2183
2184        if let Some(lock) = solver_lock_opt {
2185            // Found a solver. Tick it.
2186            let mut solver = lock.write().unwrap();
2187            solver.set_target(self.target);
2188            current_val = solver.tick(renderer.delta_time());
2189
2190            // If the solver hasn't settled yet, request another frame.
2191            if !solver.is_settled() {
2192                renderer.request_redraw();
2193            }
2194        } else {
2195            // First time seeing this ID. Initialize solver state.
2196            let solver = SleipnirSolver::new(
2197                self.params,
2198                self.target,
2199                self.target, // Initialize at target to avoid jump on first frame
2200            );
2201
2202            // Insert into registry for next frame.
2203            get_system_state().rcu(|old| {
2204                let mut new_state = (**old).clone();
2205                new_state.set_component_state(self.id, solver);
2206                new_state
2207            });
2208
2209            current_val = self.target;
2210        }
2211
2212        // Apply the solved value as a vertical translation.
2213        renderer.push_transform([0.0, current_val], [1.0, 1.0], 0.0);
2214        view.render(renderer, rect);
2215        renderer.pop_transform();
2216    }
2217}
2218
2219/// TransformModifier applies a 2D transform (translation, scale, rotation) to its child.
2220/// This modifier is "layout-neutral" and can be animated without re-running the layout engine.
2221#[derive(Debug, Clone, Copy, PartialEq)]
2222pub struct TransformModifier {
2223    pub translation: [f32; 2],
2224    pub scale: [f32; 2],
2225    pub rotation: f32,
2226}
2227
2228impl Default for TransformModifier {
2229    fn default() -> Self {
2230        Self::new()
2231    }
2232}
2233
2234impl TransformModifier {
2235    pub fn new() -> Self {
2236        Self {
2237            translation: [0.0, 0.0],
2238            scale: [1.0, 1.0],
2239            rotation: 0.0,
2240        }
2241    }
2242
2243    pub fn translate(mut self, x: f32, y: f32) -> Self {
2244        self.translation = [x, y];
2245        self
2246    }
2247
2248    pub fn scale(mut self, x: f32, y: f32) -> Self {
2249        self.scale = [x, y];
2250        self
2251    }
2252
2253    pub fn rotate(mut self, radians: f32) -> Self {
2254        self.rotation = radians;
2255        self
2256    }
2257}
2258
2259impl ViewModifier for TransformModifier {
2260    fn modify<V: View>(self, content: V) -> impl View {
2261        ModifiedView::new(content, self)
2262    }
2263
2264    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2265        renderer.push_transform(self.translation, self.scale, self.rotation);
2266        view.render(renderer, rect);
2267        renderer.pop_transform();
2268    }
2269}
2270
2271/// LifecycleModifier handles on_appear and on_disappear hooks.
2272
2273#[derive(Clone)]
2274pub struct LifecycleModifier {
2275    pub on_appear: Option<Arc<dyn Fn() + Send + Sync>>,
2276    pub on_disappear: Option<Arc<dyn Fn() + Send + Sync>>,
2277}
2278
2279impl ViewModifier for LifecycleModifier {
2280    fn modify<V: View>(self, content: V) -> impl View {
2281        ModifiedView::new(content, self)
2282    }
2283}
2284
2285/// OpacityModifier fades this view and all its descendants to the given alpha.
2286/// The renderer is expected to honour `push_opacity`/`pop_opacity` on the Renderer trait.
2287#[derive(Debug, Clone, Copy, PartialEq)]
2288pub struct OpacityModifier {
2289    pub opacity: f32,
2290}
2291
2292impl ViewModifier for OpacityModifier {
2293    fn modify<V: View>(self, content: V) -> impl View {
2294        ModifiedView::new(content, self)
2295    }
2296
2297    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2298        renderer.push_opacity(self.opacity);
2299    }
2300
2301    fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2302        renderer.pop_opacity();
2303    }
2304}
2305
2306/// OnClickModifier registers a click handler for this view.
2307#[derive(Clone)]
2308pub struct OnClickModifier {
2309    pub action: Arc<dyn Fn() + Send + Sync>,
2310}
2311
2312impl ViewModifier for OnClickModifier {
2313    fn modify<V: View>(self, content: V) -> impl View {
2314        ModifiedView::new(content, self)
2315    }
2316
2317    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2318        let action = self.action.clone();
2319        renderer.register_handler(
2320            "pointerclick",
2321            std::sync::Arc::new(move |event| {
2322                if let Event::PointerClick { .. } = event {
2323                    (action)();
2324                }
2325            }),
2326        );
2327    }
2328}
2329
2330/// OnPointerEnterModifier registers a pointer enter handler.
2331#[derive(Clone)]
2332pub struct OnPointerEnterModifier {
2333    pub action: Arc<dyn Fn() + Send + Sync>,
2334}
2335
2336impl ViewModifier for OnPointerEnterModifier {
2337    fn modify<V: View>(self, content: V) -> impl View {
2338        ModifiedView::new(content, self)
2339    }
2340
2341    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2342        let action = self.action.clone();
2343        renderer.register_handler(
2344            "pointerenter",
2345            std::sync::Arc::new(move |event| {
2346                if let Event::PointerEnter = event {
2347                    (action)();
2348                }
2349            }),
2350        );
2351    }
2352}
2353
2354/// OnPointerLeaveModifier registers a pointer leave handler.
2355#[derive(Clone)]
2356pub struct OnPointerLeaveModifier {
2357    pub action: Arc<dyn Fn() + Send + Sync>,
2358}
2359
2360impl ViewModifier for OnPointerLeaveModifier {
2361    fn modify<V: View>(self, content: V) -> impl View {
2362        ModifiedView::new(content, self)
2363    }
2364
2365    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2366        let action = self.action.clone();
2367        renderer.register_handler(
2368            "pointerleave",
2369            std::sync::Arc::new(move |event| {
2370                if let Event::PointerLeave = event {
2371                    (action)();
2372                }
2373            }),
2374        );
2375    }
2376}
2377
2378/// OnPointerMoveModifier registers a pointer move handler.
2379#[derive(Clone)]
2380pub struct OnPointerMoveModifier {
2381    pub action: Arc<dyn Fn(f32, f32) + Send + Sync>,
2382}
2383
2384impl ViewModifier for OnPointerMoveModifier {
2385    fn modify<V: View>(self, content: V) -> impl View {
2386        ModifiedView::new(content, self)
2387    }
2388
2389    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2390        let action = self.action.clone();
2391        renderer.register_handler(
2392            "pointermove",
2393            std::sync::Arc::new(move |event| {
2394                if let Event::PointerMove { x, y, .. } = event {
2395                    (action)(x, y);
2396                }
2397            }),
2398        );
2399    }
2400}
2401
2402/// OnPointerDownModifier registers a pointer down handler.
2403#[derive(Clone)]
2404pub struct OnPointerDownModifier {
2405    pub action: Arc<dyn Fn() + Send + Sync>,
2406}
2407
2408impl ViewModifier for OnPointerDownModifier {
2409    fn modify<V: View>(self, content: V) -> impl View {
2410        ModifiedView::new(content, self)
2411    }
2412
2413    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2414        let action = self.action.clone();
2415        renderer.register_handler(
2416            "pointerdown",
2417            std::sync::Arc::new(move |event| {
2418                if let Event::PointerDown { .. } = event {
2419                    (action)();
2420                }
2421            }),
2422        );
2423    }
2424}
2425
2426/// OnPointerUpModifier registers a pointer up handler.
2427#[derive(Clone)]
2428pub struct OnPointerUpModifier {
2429    pub action: Arc<dyn Fn() + Send + Sync>,
2430}
2431
2432impl ViewModifier for OnPointerUpModifier {
2433    fn modify<V: View>(self, content: V) -> impl View {
2434        ModifiedView::new(content, self)
2435    }
2436
2437    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2438        let action = self.action.clone();
2439        renderer.register_handler(
2440            "pointerup",
2441            std::sync::Arc::new(move |event| {
2442                if let Event::PointerUp { .. } = event {
2443                    (action)();
2444                }
2445            }),
2446        );
2447    }
2448}
2449
2450/// ForegroundColorModifier overrides the foreground (text / icon) color inherited
2451/// by all descendants until another ForegroundColorModifier is encountered.
2452#[derive(Debug, Clone, Copy, PartialEq)]
2453pub struct ForegroundColorModifier {
2454    pub color: [f32; 4],
2455}
2456
2457impl ViewModifier for ForegroundColorModifier {
2458    fn modify<V: View>(self, content: V) -> impl View {
2459        ModifiedView::new(content, self)
2460    }
2461}
2462
2463/// ClipModifier restricts all child drawing to the view's layout rectangle.
2464/// The renderer must support `push_clip_rect`/`pop_clip_rect`.
2465#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2466pub struct ClipModifier;
2467
2468impl ViewModifier for ClipModifier {
2469    fn modify<V: View>(self, content: V) -> impl View {
2470        ModifiedView::new(content, self)
2471    }
2472
2473    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2474        renderer.push_clip_rect(rect);
2475    }
2476
2477    fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2478        renderer.pop_clip_rect();
2479    }
2480}
2481
2482/// BorderModifier draws a solid-color border around the view bounds.
2483#[derive(Debug, Clone, Copy, PartialEq)]
2484pub struct BorderModifier {
2485    pub color: [f32; 4],
2486    pub width: f32,
2487}
2488
2489impl ViewModifier for BorderModifier {
2490    fn modify<V: View>(self, content: V) -> impl View {
2491        ModifiedView::new(content, self)
2492    }
2493
2494    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2495        renderer.stroke_rect(rect, self.color, self.width);
2496    }
2497}
2498
2499// Primitive (leaf) views implement Never as body
2500#[doc(hidden)]
2501pub enum Never {}
2502
2503impl View for Never {
2504    type Body = Never;
2505    fn body(self) -> Never {
2506        unreachable!()
2507    }
2508}
2509
2510/// EmptyView - A view that renders nothing and takes up no space.
2511#[derive(Debug, Clone, Copy, Default)]
2512pub struct EmptyView;
2513
2514impl View for EmptyView {
2515    type Body = Never;
2516    fn body(self) -> Self::Body {
2517        unreachable!()
2518    }
2519    fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2520    fn intrinsic_size(&self, _renderer: &mut dyn Renderer, _proposal: SizeProposal) -> Size {
2521        Size {
2522            width: 0.0,
2523            height: 0.0,
2524        }
2525    }
2526}
2527
2528/// A view that has been transformed by a modifier.
2529/// Section 4.3: "Each modifier implements ViewModifier and produces a ModifiedView<Inner, Self>."
2530#[derive(Clone)]
2531pub struct ModifiedView<V, M> {
2532    view: V,
2533    modifier: M,
2534}
2535
2536impl<V: View, M: ViewModifier> ModifiedView<V, M> {
2537    #[doc(hidden)]
2538    pub fn new(view: V, modifier: M) -> Self {
2539        Self { view, modifier }
2540    }
2541}
2542
2543impl<V: View, M: ViewModifier> View for ModifiedView<V, M> {
2544    type Body = ModifiedView<V::Body, M>;
2545
2546    fn body(self) -> Self::Body {
2547        ModifiedView {
2548            view: self.view.body(),
2549            modifier: self.modifier.clone(),
2550        }
2551    }
2552
2553    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2554        self.modifier.render_view(&self.view, renderer, rect);
2555    }
2556
2557    fn intrinsic_size(&self, renderer: &mut dyn Renderer, proposal: SizeProposal) -> Size {
2558        self.modifier.measure_view(&self.view, renderer, proposal)
2559    }
2560
2561    fn flex_weight(&self) -> f32 {
2562        self.modifier.child_flex_weight(&self.view)
2563    }
2564
2565    fn layout(&self) -> Option<&dyn layout::LayoutView> {
2566        self.modifier.layout().or_else(|| self.view.layout())
2567    }
2568
2569    fn get_grid_placement(&self) -> Option<GridPlacement> {
2570        self.modifier
2571            .get_grid_placement()
2572            .or_else(|| self.view.get_grid_placement())
2573    }
2574}
2575
2576pub trait ViewModifier: Send + Clone {
2577    fn modify<V: View>(self, content: V) -> impl View;
2578
2579    /// Returns the grid placement configuration if this modifier defines one.
2580    fn get_grid_placement(&self) -> Option<GridPlacement> {
2581        None
2582    }
2583
2584    /// Core rendering hook called before child views.
2585    fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2586
2587    /// Cleanup hook called after child views.
2588    fn post_render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2589
2590    /// Allows a modifier to completely override or wrap the rendering of its child.
2591    /// Default implementation performs a standard push -> transform -> render child -> pop sequence.
2592    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2593        self.render(renderer, rect);
2594        let child_rect = self.transform_rect(rect);
2595        view.render(renderer, child_rect);
2596        self.post_render(renderer, rect);
2597    }
2598
2599    fn transform_rect(&self, rect: Rect) -> Rect {
2600        rect
2601    }
2602
2603    /// Allows a modifier to transform the layout proposal before it reaches the child.
2604    fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
2605        proposal
2606    }
2607
2608    /// Allows a modifier to transform the resulting size from the child.
2609    fn transform_size(&self, size: Size) -> Size {
2610        size
2611    }
2612
2613    /// Measure hook that coordinates size propagation.
2614    fn measure_view<V: View>(
2615        &self,
2616        view: &V,
2617        renderer: &mut dyn Renderer,
2618        proposal: SizeProposal,
2619    ) -> Size {
2620        let child_proposal = self.transform_proposal(proposal);
2621        let child_size = view.intrinsic_size(renderer, child_proposal);
2622        self.transform_size(child_size)
2623    }
2624
2625    /// Allows a modifier to override or pass through the child's flex weight.
2626    fn child_flex_weight<V: View>(&self, view: &V) -> f32 {
2627        view.flex_weight()
2628    }
2629
2630    fn layout(&self) -> Option<&dyn layout::LayoutView> {
2631        None
2632    }
2633}
2634
2635/// TelemetryData tracks real-time performance metrics for the GPU renderer.
2636#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
2637pub struct TelemetryData {
2638    pub frame_time_ms: f32,
2639    /// 99th percentile frame time over the last window, used to detect tail latency.
2640    pub p99_frame_time_ms: f32,
2641    /// Statistical jitter (variance in frame timing).
2642    pub frame_jitter_ms: f32,
2643    /// Indicates if a hardware stall (DRAM refresh, thermal spike) was detected.
2644    pub hardware_stall_detected: bool,
2645
2646    // Pass timing
2647    pub input_time_ms: f32,
2648    pub state_flush_time_ms: f32,
2649    pub layout_time_ms: f32,
2650    pub draw_time_ms: f32,
2651    pub gpu_submit_time_ms: f32,
2652
2653    pub draw_calls: u32,
2654    pub vertices: u32,
2655
2656    /// Global Berserker Pipeline Intensity (0.0 - 1.0+)
2657    pub berserker_rage: f32,
2658
2659    // Memory breakdown
2660    pub vram_usage_mb: f32,
2661    pub vram_textures_mb: f32,
2662    pub vram_buffers_mb: f32,
2663    pub vram_pipelines_mb: f32,
2664    /// Indicates if the Mega-Atlas or VRAM pools are at capacity.
2665    pub vram_exhausted: bool,
2666}
2667
2668/// Configuration for render-loop frame timing and degradation strategies.
2669#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2670pub struct FrameBudget {
2671    /// Target frame time in milliseconds (default: 16.0 for 60FPS)
2672    pub target_ms: f32,
2673    /// If true, the renderer is allowed to dynamically skip non-critical effects
2674    /// (like heavy blurs or complex shadows) when the budget is exceeded.
2675    pub allow_degradation: bool,
2676}
2677
2678impl Default for FrameBudget {
2679    fn default() -> Self {
2680        Self {
2681            target_ms: 16.0,
2682            allow_degradation: true,
2683        }
2684    }
2685}
2686
2687/// The Renderer trait defines the atomic drawing operations for all CVKG backends.
2688/// This trait is object-safe and used by the View::render system.
2689/// # Implementation Requirements
2690/// 1. Coordinate system is origin-top-left (0,0) with Y increasing downwards.
2691/// 2. Colors are [R, G, B, A] in the [0.0, 1.0] range.
2692/// 3. All operations must be batchable by the underlying backend.
2693///    Trait providing timing information for the render loop.
2694pub trait ElapsedTime {
2695    /// Returns the cumulative time since the renderer started in seconds.
2696    fn elapsed_time(&self) -> f32;
2697
2698    /// Returns the time elapsed since the last frame in seconds.
2699    fn delta_time(&self) -> f32;
2700}
2701
2702/// The Renderer trait defines the atomic drawing operations for all CVKG backends.
2703/// This trait is object-safe and used by the View::render system.
2704/// # Implementation Requirements
2705/// 1. Coordinate system is origin-top-left (0,0) with Y increasing downwards.
2706/// 2. Colors are [R, G, B, A] in the [0.0, 1.0] range.
2707/// 3. All operations must be batchable by the underlying backend.
2708pub trait Renderer: ElapsedTime + Send {
2709    /// Requests that the renderer redraws as soon as possible.
2710    /// Used for continuous animations.
2711    fn request_redraw(&mut self) {}
2712
2713    /// Returns true if the current frame is over the time budget.
2714    /// This can be used to skip expensive visual effects.
2715    fn is_over_budget(&self) -> bool {
2716        false
2717    }
2718
2719    // ── Filled shapes ────────────────────────────────────────────────────
2720    fn fill_rect(&mut self, rect: Rect, color: [f32; 4]);
2721    fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]);
2722    /// Fill an ellipse/circle that fits inside `rect`.
2723    fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]);
2724
2725    /// Draw a high-fidelity 3D cube inside the given rectangle using specialized shader logic.
2726    /// `rotation` is [pitch, yaw, roll] in radians.
2727    fn draw_3d_cube(&mut self, _rect: Rect, _color: [f32; 4], _rotation: [f32; 3]) {}
2728
2729    // ── Stroked shapes ───────────────────────────────────────────────────
2730    fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
2731    fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32);
2732    /// Stroke an ellipse/circle that fits inside `rect`.
2733    fn stroke_ellipse(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
2734    /// Draw a straight line from (x1,y1) to (x2,y2).
2735    fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: [f32; 4], stroke_width: f32);
2736    /// Fill a polygon defined by a set of vertices.
2737    fn fill_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4]) {}
2738    /// Stroke a polygon defined by a set of vertices.
2739    fn stroke_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4], _stroke_width: f32) {}
2740
2741    // ── Text ─────────────────────────────────────────────────────────────
2742    fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]);
2743    /// Measure the width and height of the specified text.
2744    fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32);
2745
2746    fn shape_rich_text(
2747        &mut self,
2748        _spans: &[cvkg_runic_text::TextSpan],
2749        _max_width: Option<f32>,
2750        _align: cvkg_runic_text::TextAlign,
2751        _overflow: cvkg_runic_text::TextOverflow,
2752    ) -> Option<cvkg_runic_text::ShapedText> {
2753        None
2754    }
2755
2756    fn draw_shaped_text(&mut self, _text: &cvkg_runic_text::ShapedText, _x: f32, _y: f32) {}
2757
2758    // ── Images & textures ────────────────────────────────────────────────
2759    /// Draw a texture (GPU-side) at the specified rect.
2760    fn draw_texture(&mut self, _texture_id: u32, _rect: Rect) {}
2761    /// Draw an image asset by name or path.
2762    fn draw_image(&mut self, _image_name: &str, _rect: Rect) {}
2763    /// Load an image asset from memory.
2764    fn load_image(&mut self, _name: &str, _data: &[u8]) {}
2765    /// Pre-warm the renderer with assets. Implementations can use this
2766    /// to populate texture atlases or warm up shader caches.
2767    fn prewarm_vram(&mut self, _assets: Vec<(String, Vec<u8>)>) {}
2768
2769    /// Get the current pointer (mouse/touch) position.
2770    fn get_pointer_position(&self) -> [f32; 2] {
2771        [0.0, 0.0]
2772    }
2773
2774    // ── Data Visualization ───────────────────────────────────────────────
2775    /// Upload raw float data as a GPU texture for heatmap rendering.
2776    fn upload_data_texture(&mut self, _id: &str, _data: &[f32], _width: u32, _height: u32) {}
2777    /// Draw a heatmap using a previously uploaded data texture.
2778    fn draw_heatmap(&mut self, _texture_id: &str, _rect: Rect, _palette: &str) {}
2779
2780    // ── 3D Objects ───────────────────────────────────────────────────────
2781    /// Draw a 3D mesh.
2782    fn draw_mesh(&mut self, _mesh: &Mesh, _color: [f32; 4], _transform: glam::Mat4) {}
2783
2784    /// Draw a 3D mesh with full material and transform support.
2785    fn draw_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {}
2786
2787    /// Set the 3D camera for perspective/orthographic projection.
2788    /// If not called, rendering defaults to the 2D orthographic projection.
2789    fn set_camera_3d(&mut self, _camera: &Camera3D) {}
2790
2791    /// Push a 3D transform onto the transform stack.
2792    /// All subsequent drawing is affected until `pop_transform_3d`.
2793    fn push_transform_3d(&mut self, _transform: &Transform3D) {}
2794
2795    /// Pop the most recently pushed 3D transform.
2796    fn pop_transform_3d(&mut self) {}
2797
2798    /// Render a 3D scene graph node. Reads position_3d, rotation_3d, scale_3d
2799    /// from the node and emits the appropriate draw call.
2800    /// Default implementation is a no-op; 3D renderers override this.
2801    ///
2802    /// `position`: [x, y, z] world-space position
2803    /// `rotation`: [x, y, z, w] quaternion rotation
2804    /// `scale`: [x, y, z] scale factors
2805    /// `color`: [r, g, b, a] base color for unlit rendering
2806    fn render_scene_node_3d(
2807        &mut self,
2808        _position: [f32; 3],
2809        _rotation: [f32; 4],
2810        _scale: [f32; 3],
2811        _color: [f32; 4],
2812        _meshes: &[Mesh],
2813    ) {
2814        // Default no-op: 2D renderers ignore 3D scene nodes
2815    }
2816
2817    /// Draw a linear gradient between two colors at the specified angle.
2818    fn draw_linear_gradient(
2819        &mut self,
2820        _rect: Rect,
2821        _start_color: [f32; 4],
2822        _end_color: [f32; 4],
2823        _angle: f32,
2824    ) {
2825    }
2826    /// Draw a radial gradient between two colors.
2827    fn draw_radial_gradient(
2828        &mut self,
2829        _rect: Rect,
2830        _inner_color: [f32; 4],
2831        _outer_color: [f32; 4],
2832    ) {
2833    }
2834    /// Draw a high-fidelity drop shadow for a rounded rectangle.
2835    fn draw_drop_shadow(
2836        &mut self,
2837        _rect: Rect,
2838        _radius: f32,
2839        _color: [f32; 4],
2840        _blur: f32,
2841        _spread: f32,
2842    ) {
2843    }
2844    /// Draw a dashed border for a rounded rectangle.
2845    fn stroke_dashed_rounded_rect(
2846        &mut self,
2847        _rect: Rect,
2848        _radius: f32,
2849        _color: [f32; 4],
2850        _width: f32,
2851        _dash: f32,
2852        _gap: f32,
2853    ) {
2854    }
2855    /// Draw a 9-slice / patch scaled image.
2856    fn draw_9slice(
2857        &mut self,
2858        _image_name: &str,
2859        _rect: Rect,
2860        _left: f32,
2861        _top: f32,
2862        _right: f32,
2863        _bottom: f32,
2864    ) {
2865    }
2866
2867    // ── Clipping ─────────────────────────────────────────────────────────
2868    /// Push a clip rectangle.  All subsequent drawing is clipped to `rect`.
2869    /// Implementations that do not support clipping may ignore this call.
2870    fn push_clip_rect(&mut self, _rect: Rect) {}
2871    /// Pop the most recently pushed clip rectangle.
2872    fn pop_clip_rect(&mut self) {}
2873    /// Get the current clip rectangle in screen coordinates.
2874    /// Returns a rect covering the entire screen if no clip is active.
2875    fn current_clip_rect(&self) -> Rect {
2876        Rect::new(-10000.0, -10000.0, 20000.0, 20000.0)
2877    }
2878
2879    // ── Global opacity ───────────────────────────────────────────────────
2880    /// Set a global opacity multiplier applied to all subsequent draw calls
2881    /// until `pop_opacity` is called.  `opacity` is in [0.0, 1.0].
2882    fn push_opacity(&mut self, _opacity: f32) {}
2883    /// Restore the previous opacity level.
2884    fn pop_opacity(&mut self) {}
2885
2886    // ── Berserker Pipeline State ─────────────────────────────────────────
2887    fn set_theme(&mut self, _theme: ColorTheme) {}
2888    fn set_rage(&mut self, _rage: f32) {}
2889    fn set_berserker_mode(&mut self, _state: BerserkerMode) {}
2890    fn trigger_shatter_event(&mut self, _origin: [f32; 2], _force: f32) {}
2891    /// Set the desktop scene preset (Aurora, Void, Nebula, Glitch, Yggdrasil).
2892    fn set_scene(&mut self, _scene: &str) {}
2893
2894    // ── Export & Print ───────────────────────────────────────────────────
2895    /// Capture the current frame as a PNG byte buffer.
2896    fn capture_png(&mut self) -> Vec<u8> {
2897        Vec::new()
2898    }
2899    /// Trigger a native print dialog or spooling operation.
2900    fn print(&mut self) {}
2901
2902    fn set_scene_preset(&mut self, _preset: u32) {}
2903
2904    // ── Cyberpunk Effects ────────────────────────────────────────────────
2905    /// Apply a Bifrost (Frosted Glass) effect to the specified rect.
2906    fn bifrost(&mut self, _rect: Rect, _blur: f32, _saturation: f32, _opacity: f32) {}
2907    /// Apply a Gungnir (Neon Glow) effect to the specified rect.
2908    fn gungnir(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32, _intensity: f32) {}
2909    /// Apply a ManiGlow (Lunar Illuminator) effect.
2910    fn mani_glow(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32) {}
2911    /// Push a Mjolnir Slice (geometric clipping).
2912    fn push_mjolnir_slice(&mut self, _angle: f32, _offset: f32) {}
2913    fn pop_mjolnir_slice(&mut self) {}
2914    /// Execute a render function with memoization.
2915    /// If the renderer supports caching and the `id` + `data_hash` match a previous run,
2916    /// it may replay cached commands instead of executing the function.
2917    fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer));
2918    /// Apply a Mjolnir Shatter effect (fragmentation) to the specified rect.
2919    fn mjolnir_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2920    fn mjolnir_fluid_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2921    /// Draw a Mjolnir Bolt (lightning strike) between two points.
2922    fn draw_mjolnir_bolt(&mut self, _from: [f32; 2], _to: [f32; 2], _color: [f32; 4]) {}
2923
2924    // ── Accessibility (ShieldWall) ───────────────────────────────────────
2925    fn set_aria_role(&mut self, _role: &str) {}
2926    fn set_aria_label(&mut self, _label: &str) {}
2927
2928    /// Register a shared element for Bifrost Bridge transitions.
2929    fn register_shared_element(&mut self, _id: &str, _rect: Rect) {}
2930
2931    /// Set a unique key for the current VDOM node to ensure stable identity during diffing.
2932    fn set_key(&mut self, _key: &str) {}
2933
2934    // ── Telemetry ────────────────────────────────────────────────────────
2935    /// Get real-time performance telemetry.
2936    fn get_telemetry(&self) -> TelemetryData {
2937        TelemetryData::default()
2938    }
2939
2940    // ── GPU State Management ─────────────────────────────────────────────
2941    /// Push a shadow state to the stack. All following draw calls will have this shadow.
2942    fn push_shadow(&mut self, _radius: f32, _color: [f32; 4], _offset: [f32; 2]) {}
2943    /// Pop the last shadow state from the stack.
2944    fn pop_shadow(&mut self) {}
2945
2946    // ── VDOM & Scene Graph ───────────────────────────────────────────────
2947    /// Push a Virtual DOM node onto the stack for hierarchy tracking.
2948    fn push_vnode(&mut self, _rect: Rect, _name: &'static str) {}
2949    /// Pop the current Virtual DOM node from the stack.
2950    fn pop_vnode(&mut self) {}
2951    /// Register an event handler for the current VDOM node.
2952    fn register_handler(
2953        &mut self,
2954        _event_type: &str,
2955        _handler: std::sync::Arc<dyn Fn(Event) + Send + Sync>,
2956    ) {
2957    }
2958
2959    // ── Z-Index & Depth ──────────────────────────────────────────────────
2960    /// Set the current Z-index for depth sorting.
2961    /// Higher values appear closer to the viewer.
2962    fn set_z_index(&mut self, _z: f32) {}
2963    /// Get the current Z-index.
2964    fn get_z_index(&self) -> f32 {
2965        0.0
2966    }
2967
2968    // ── Vector Graphics ──────────────────────────────────────────────────
2969    /// Load an SVG model from raw bytes.
2970    fn load_svg(&mut self, _name: &str, _svg_data: &[u8]) {}
2971    /// Draw a pre-loaded SVG model.
2972    fn draw_svg(&mut self, _name: &str, _rect: Rect) {}
2973    /// Serialize a pre-loaded SVG model back to SVG XML markup.
2974    /// Returns the serialized SVG string, or an error if the model is not loaded
2975    /// or serialization is not supported by this renderer.
2976    fn serialize_svg(&mut self, _name: &str) -> Result<String, String> {
2977        Err("SVG serialization not supported by this renderer".into())
2978    }
2979    /// Apply an SVG filter to a pre-loaded SVG model by filter element ID.
2980    /// The filter is evaluated and the result composited back into the SVG.
2981    /// Returns the filtered SVG as XML, or an error if not supported.
2982    fn apply_svg_filter(
2983        &mut self,
2984        _name: &str,
2985        _filter_id: &str,
2986        _region: Rect,
2987    ) -> Result<String, String> {
2988        Err("SVG filter not supported by this renderer".into())
2989    }
2990
2991    // ── GPU Transformations ──────────────────────────────────────────────
2992    /// Push a 2D transform (translation, scale, rotation) onto the stack.
2993    /// This transform should be applied to all subsequent draw calls until popped.
2994    /// Transform-only animations use this to avoid re-triggering the layout engine.
2995    fn push_transform(&mut self, _translation: [f32; 2], _scale: [f32; 2], _rotation: f32) {}
2996    /// Push a raw 2D affine transform matrix [a, b, c, d, e, f] corresponding to
2997    /// [m11, m12, m21, m22, tx, ty].
2998    fn push_affine(&mut self, _transform: [f32; 6]) {}
2999    /// Pop the last 2D transform from the stack.
3000    fn pop_transform(&mut self) {}
3001    /// Return the resolved layout bounds for a specific node ID if it exists.
3002    fn query_layout(&self, _node_id: scene_graph::NodeId) -> Option<Rect> {
3003        None
3004    }
3005    /// Enable or disable the layout debug overlay (bounds, padding, margin).
3006    fn set_debug_layout(&mut self, _enabled: bool) {}
3007    /// Check if the layout debug overlay is currently enabled.
3008    fn get_debug_layout(&self) -> bool {
3009        false
3010    }
3011
3012    // ── Material Routing ─────────────────────────────────────────────────
3013    /// Set the active material for subsequent draw calls.
3014    /// Controls which pass a draw call is routed to in the multi-pass pipeline.
3015    fn set_material(&mut self, _material: crate::material::DrawMaterial) {}
3016    /// Return the currently active material (defaults to Opaque).
3017    fn current_material(&self) -> crate::material::DrawMaterial {
3018        crate::material::DrawMaterial::Opaque
3019    }
3020
3021    // ── Vili Interaction Paradigm ──────────────────────────────────────────
3022    /// Compute the user's velocity/intent vector.
3023    fn mimir_intent(&self) -> [f32; 2] {
3024        [0.0, 0.0]
3025    }
3026    /// Calculate magnetic coordinate warp towards an anchor.
3027    fn magnetic_warp(&self, pointer: [f32; 2], anchor_rect: Rect, strength: f32) -> [f32; 2] {
3028        if strength <= 0.0 {
3029            return pointer;
3030        }
3031        let cx = anchor_rect.x + anchor_rect.width / 2.0;
3032        let cy = anchor_rect.y + anchor_rect.height / 2.0;
3033        let dx = pointer[0] - cx;
3034        let dy = pointer[1] - cy;
3035        let dist = (dx * dx + dy * dy).sqrt();
3036        let radius = 120.0;
3037        if dist < radius && dist > 0.0 {
3038            let force = (1.0 - dist / radius) * strength;
3039            [pointer[0] - dx * force, pointer[1] - dy * force]
3040        } else {
3041            pointer
3042        }
3043    }
3044    /// Calculate kinematic glow intensity based on proximity.
3045    fn mani_glow_intensity(&self, pointer: [f32; 2], bounds: Rect, radius: f32) -> f32 {
3046        let cx = bounds.x + bounds.width / 2.0;
3047        let cy = bounds.y + bounds.height / 2.0;
3048        let dist = ((pointer[0] - cx).powi(2) + (pointer[1] - cy).powi(2)).sqrt();
3049        if dist < radius {
3050            (1.0 - dist / radius).clamp(0.0, 1.0)
3051        } else {
3052            0.0
3053        }
3054    }
3055    /// Calculate dynamic element attention (scaling/morphing) statelessly per frame.
3056    fn fafnir_evolve(&self, pointer: [f32; 2], bounds: Rect, max_scale: f32) -> f32 {
3057        let prox = self.mani_glow_intensity(pointer, bounds, 120.0);
3058        1.0 + (max_scale - 1.0) * prox
3059    }
3060    /// Sets the precise Vili SDF Shape boundary for hit-testing.
3061    fn set_sdf_shape(&mut self, _shape: crate::layout::SdfShape) {}
3062
3063    // -- Portal / PhaseGate rendering -----------------------------------------
3064
3065    /// Begin rendering into the portal root layer instead of the inline tree.
3066    /// All draw calls between `enter_portal` and `exit_portal` are collected
3067    /// into a separate buffer that is composited AFTER the main tree.
3068    ///
3069    /// WHY separate buffer: The main tree may have clipping, transforms, or
3070    /// opacity that should NOT affect overlays. The portal layer renders on top
3071    /// of everything, ignoring the local coordinate system.
3072    fn enter_portal(&mut self, _z_index: i32) {}
3073
3074    /// Exit the portal layer and return to inline rendering.
3075    /// The portal content collected since `enter_portal` is now sealed --
3076    /// no more draw calls will be appended to it.
3077    fn exit_portal(&mut self) {}
3078
3079    /// Get the current viewport size in logical pixels.
3080    /// Used by portal content to size itself to the full screen.
3081    fn viewport_size(&self) -> Rect {
3082        Rect::new(0.0, 0.0, 1920.0, 1080.0)
3083    }
3084
3085    // -- Accessibility announcements -----------------------------------------
3086
3087    /// Announce a message to screen readers via the platform accessibility API.
3088    /// This call is non-blocking. The message is queued and the screen reader
3089    /// will speak it at its own pace.
3090    fn announce(&mut self, _message: &str, _priority: AnnouncementPriority) {}
3091}
3092
3093/// Utility for accessibility compliance (WCAG 2.1).
3094pub mod accessibility {
3095    /// Calculate the relative luminance of an sRGB color.
3096    pub fn relative_luminance(color: [f32; 4]) -> f32 {
3097        let f = |c: f32| {
3098            if c <= 0.03928 {
3099                c / 12.92
3100            } else {
3101                ((c + 0.055) / 1.055).powf(2.4)
3102            }
3103        };
3104        0.2126 * f(color[0]) + 0.7152 * f(color[1]) + 0.0722 * f(color[2])
3105    }
3106
3107    /// Calculate the contrast ratio between two colors.
3108    pub fn contrast_ratio(c1: [f32; 4], c2: [f32; 4]) -> f32 {
3109        let l1 = relative_luminance(c1);
3110        let l2 = relative_luminance(c2);
3111        let (light, dark) = if l1 > l2 { (l1, l2) } else { (l2, l1) };
3112        (light + 0.05) / (dark + 0.05)
3113    }
3114}
3115/// Defines the hardware acceleration tier and feature set available to the renderer.
3116#[derive(
3117    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
3118)]
3119pub enum RenderTier {
3120    /// High-performance GPU path (WebGPU / Vulkan / Metal / DX12) with full shader support.
3121    Tier1GPU = 0,
3122    /// Mid-tier GPU path (WebGL2 / OpenGL 3.3) with standard shader support.
3123    Tier2GPU = 1,
3124    /// Fallback software or basic hardware path (Canvas 2D / GDI+) with limited effects.
3125    Tier3Fallback = 2,
3126}
3127// =============================================================================
3128// BERSERKER UNIFORMS
3129// =============================================================================
3130use bytemuck::{Pod, Zeroable};
3131/// Fully themeable color palette for the Berserker pipeline.
3132#[repr(C)]
3133#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3134pub struct ColorTheme {
3135    pub primary_neon: [f32; 4], // (R, G, B, intensity)
3136    pub shatter_neon: [f32; 4],
3137    pub glass_base: [f32; 4],
3138    pub glass_edge: [f32; 4],
3139    pub rune_glow: [f32; 4],
3140    pub ember_core: [f32; 4],
3141    pub background_deep: [f32; 4],
3142    pub mani_glow: [f32; 4], // (R, G, B, radius)
3143    pub glass_blur_strength: f32,
3144    pub shatter_edge_width: f32,
3145    pub neon_bloom_radius: f32,
3146    pub rune_opacity: f32,
3147}
3148impl ColorTheme {
3149    /// Asgard Mode: The high-fidelity "Cyberpunk Viking" aesthetic.
3150    pub fn asgard() -> Self {
3151        Self {
3152            primary_neon: [0.0, 1.0, 0.95, 1.2],
3153            shatter_neon: [1.0, 0.0, 0.75, 1.5],
3154            glass_base: [0.04, 0.04, 0.06, 0.82],
3155            glass_edge: [0.0, 0.45, 0.55, 0.6],
3156            rune_glow: [0.75, 0.98, 1.0, 0.9],
3157            ember_core: [0.95, 0.12, 0.12, 1.0],
3158            background_deep: [0.01, 0.01, 0.03, 1.0],
3159            mani_glow: [0.7, 0.9, 1.0, 0.05],
3160            glass_blur_strength: 0.6,
3161            shatter_edge_width: 1.8,
3162            neon_bloom_radius: 0.022,
3163            rune_opacity: 0.55,
3164        }
3165    }
3166
3167    /// Midgard Mode: A clean, functional tactical HUD for standard operations.
3168    pub fn midgard() -> Self {
3169        Self {
3170            primary_neon: [0.2, 0.4, 0.6, 1.0], // Muted blue
3171            shatter_neon: [0.5, 0.5, 0.5, 1.0], // Neutral gray
3172            glass_base: [0.1, 0.12, 0.15, 1.0], // Solid slate
3173            glass_edge: [0.3, 0.35, 0.4, 1.0],  // Subtle border
3174            rune_glow: [0.8, 0.8, 0.8, 0.0],    // Runes disabled
3175            ember_core: [0.5, 0.5, 0.5, 1.0],
3176            background_deep: [0.05, 0.05, 0.07, 1.0],
3177            mani_glow: [0.0, 0.0, 0.0, 0.0], // No cursor glow
3178            glass_blur_strength: 0.0,        // No blur
3179            shatter_edge_width: 1.0,
3180            neon_bloom_radius: 0.0,
3181            rune_opacity: 0.0,
3182        }
3183    }
3184
3185    pub fn cyberpunk_viking() -> Self {
3186        Self::asgard()
3187    }
3188    pub fn vibrant_glass() -> Self {
3189        Self {
3190            primary_neon: [0.0, 1.0, 0.95, 1.2],
3191            shatter_neon: [1.0, 0.0, 0.75, 1.5],
3192            glass_base: [0.55, 0.6, 0.7, 0.08], // Luminous cool tint
3193            glass_edge: [0.7, 0.85, 1.0, 0.45], // Subtle blue-white rim
3194            rune_glow: [0.75, 0.98, 1.0, 0.9],
3195            ember_core: [1.0, 0.4, 0.1, 1.0],
3196            background_deep: [0.05, 0.05, 0.1, 1.0],
3197            mani_glow: [0.7, 0.9, 1.0, 0.05],
3198            glass_blur_strength: 0.9,
3199            shatter_edge_width: 1.8,
3200            neon_bloom_radius: 0.022,
3201            rune_opacity: 0.55,
3202        }
3203    }
3204}
3205impl Default for ColorTheme {
3206    fn default() -> Self {
3207        Self::vibrant_glass()
3208    }
3209}
3210/// Per-frame scene state for the Berserker pipeline.
3211#[repr(C)]
3212#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3213pub struct SceneUniforms {
3214    pub view: glam::Mat4,
3215    pub proj: glam::Mat4,
3216    pub time: f32,
3217    pub delta_time: f32,
3218    pub resolution: [f32; 2],
3219    pub mouse: [f32; 2],
3220    pub mouse_velocity: [f32; 2],
3221    pub shatter_origin: [f32; 2],
3222    pub shatter_time: f32,
3223    pub shatter_force: f32,
3224    pub berzerker_rage: f32,
3225    pub berzerker_mode: u32,
3226    pub scroll_offset: f32,
3227    pub scale_factor: f32,
3228    pub scene_type: u32,
3229    pub _pad: [f32; 3], // Align to 16 bytes if needed, but current struct is 4x16 + 4x16 + 4x16 + ...
3230}
3231
3232pub const SCENE_AURORA: u32 = 0;
3233pub const SCENE_VOID: u32 = 1;
3234pub const SCENE_NEBULA: u32 = 2;
3235pub const SCENE_GLITCH: u32 = 3;
3236pub const SCENE_YGGDRASIL: u32 = 4;
3237
3238impl SceneUniforms {
3239    pub fn new(width: f32, height: f32) -> Self {
3240        Self {
3241            view: glam::Mat4::IDENTITY,
3242            proj: glam::Mat4::orthographic_lh(0.0, width, height, 0.0, -100.0, 100.0),
3243            time: 0.0,
3244            delta_time: 0.016,
3245            resolution: [width, height],
3246            mouse: [0.5, 0.5],
3247            mouse_velocity: [0.0, 0.0],
3248            shatter_origin: [0.5, 0.5],
3249            shatter_time: -100.0,
3250            shatter_force: 0.0,
3251            berzerker_rage: 0.0,
3252            berzerker_mode: 0,
3253            scroll_offset: 0.0,
3254            scale_factor: 1.0,
3255            scene_type: SCENE_AURORA,
3256            _pad: [0.0; 3],
3257        }
3258    }
3259}
3260/// A 3D mesh containing vertex and index data.
3261#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
3262pub struct Mesh {
3263    pub vertices: Vec<[f32; 3]>,
3264    pub normals: Vec<[f32; 3]>,
3265    pub indices: Vec<u32>,
3266}
3267impl Mesh {
3268    pub fn from_obj(data: &[u8]) -> anyhow::Result<Vec<Self>> {
3269        let mut cursor = std::io::Cursor::new(data);
3270        let (models, _) = tobj::load_obj_buf(&mut cursor, &tobj::LoadOptions::default(), |_| {
3271            Ok((Vec::new(), Default::default()))
3272        })?;
3273        let mut meshes = Vec::new();
3274        for m in models {
3275            let mesh = m.mesh;
3276            let vertices: Vec<[f32; 3]> = mesh
3277                .positions
3278                .chunks(3)
3279                .map(|c| [c[0], c[1], c[2]])
3280                .collect();
3281            let normals = if mesh.normals.is_empty() {
3282                vec![[0.0, 0.0, 1.0]; vertices.len()]
3283            } else {
3284                mesh.normals.chunks(3).map(|c| [c[0], c[1], c[2]]).collect()
3285            };
3286            meshes.push(Mesh {
3287                vertices,
3288                normals,
3289                indices: mesh.indices,
3290            });
3291        }
3292        Ok(meshes)
3293    }
3294    pub fn from_stl(data: &[u8]) -> anyhow::Result<Self> {
3295        let mut cursor = std::io::Cursor::new(data);
3296        let stl = stl_io::read_stl(&mut cursor)?;
3297        let vertices: Vec<[f32; 3]> = stl.vertices.iter().map(|v| [v[0], v[1], v[2]]).collect();
3298        let mut indices = Vec::new();
3299        for face in stl.faces {
3300            indices.push(face.vertices[0] as u32);
3301            indices.push(face.vertices[1] as u32);
3302            indices.push(face.vertices[2] as u32);
3303        }
3304        let normals = vec![[0.0, 0.0, 1.0]; vertices.len()];
3305        Ok(Mesh {
3306            vertices,
3307            normals,
3308            indices,
3309        })
3310    }
3311}
3312
3313// ══════════════════════════════════════════════════════════════════════════
3314// 3D TYPES — Phase 1: Camera, Transform, and 2.5D layer support
3315// ══════════════════════════════════════════════════════════════════════════
3316
3317/// A 3D transform: position, rotation (quaternion), and scale.
3318#[derive(Debug, Clone, Copy, PartialEq)]
3319pub struct Transform3D {
3320    pub position: glam::Vec3,
3321    pub rotation: glam::Quat,
3322    pub scale: glam::Vec3,
3323}
3324
3325impl Default for Transform3D {
3326    fn default() -> Self {
3327        Self {
3328            position: glam::Vec3::ZERO,
3329            rotation: glam::Quat::IDENTITY,
3330            scale: glam::Vec3::ONE,
3331        }
3332    }
3333}
3334
3335impl Transform3D {
3336    /// Convert this transform to a 4x4 model matrix.
3337    pub fn to_matrix(&self) -> glam::Mat4 {
3338        glam::Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.position)
3339    }
3340
3341    /// Create a 2D-compatible transform (z=0, no rotation on z axis).
3342    pub fn from_2d(x: f32, y: f32, rotation: f32) -> Self {
3343        Self {
3344            position: glam::Vec3::new(x, y, 0.0),
3345            rotation: glam::Quat::from_rotation_z(rotation),
3346            scale: glam::Vec3::ONE,
3347        }
3348    }
3349}
3350
3351/// Camera definition for 3D rendering.
3352#[derive(Debug, Clone, Copy)]
3353pub struct Camera3D {
3354    /// World-space camera position.
3355    pub position: glam::Vec3,
3356    /// World-space point the camera looks at.
3357    pub target: glam::Vec3,
3358    /// World-space up vector.
3359    pub up: glam::Vec3,
3360    /// Field of view in radians (perspective) or half-height (orthographic).
3361    pub fov_y: f32,
3362    /// Near clipping plane distance.
3363    pub near: f32,
3364    /// Far clipping plane distance.
3365    pub far: f32,
3366    /// If true, use perspective projection. If false, use orthographic.
3367    pub perspective: bool,
3368    /// Aspect ratio (width / height). Used for perspective projection.
3369    pub aspect: f32,
3370}
3371
3372/// Material properties for 3D rendering.
3373#[derive(Debug, Clone, Copy, PartialEq)]
3374pub struct Material3D {
3375    /// Base color (RGBA).
3376    pub base_color: [f32; 4],
3377    /// Metallic factor (0 = dielectric, 1 = metallic).
3378    pub metallic: f32,
3379    /// Roughness factor (0 = mirror, 1 = fully diffuse).
3380    pub roughness: f32,
3381    /// Emissive color (RGB) for self-illumination.
3382    pub emissive: [f32; 3],
3383    /// Opacity (0 = transparent, 1 = opaque).
3384    pub opacity: f32,
3385}
3386
3387impl Default for Material3D {
3388    fn default() -> Self {
3389        Self {
3390            base_color: [1.0, 1.0, 1.0, 1.0],
3391            metallic: 0.0,
3392            roughness: 0.5,
3393            emissive: [0.0, 0.0, 0.0],
3394            opacity: 1.0,
3395        }
3396    }
3397}
3398
3399impl Material3D {
3400    /// Create a simple unlit material with just a color.
3401    pub fn unlit(color: [f32; 4]) -> Self {
3402        Self {
3403            base_color: color,
3404            metallic: 0.0,
3405            roughness: 1.0,
3406            emissive: [0.0, 0.0, 0.0],
3407            opacity: color[3],
3408        }
3409    }
3410
3411    /// Create a metallic material.
3412    pub fn metallic(color: [f32; 4], roughness: f32) -> Self {
3413        Self {
3414            base_color: color,
3415            metallic: 1.0,
3416            roughness: roughness.clamp(0.0, 1.0),
3417            emissive: [0.0, 0.0, 0.0],
3418            opacity: color[3],
3419        }
3420    }
3421}
3422
3423impl Default for Camera3D {
3424    fn default() -> Self {
3425        Self {
3426            position: glam::Vec3::new(0.0, 0.0, 10.0),
3427            target: glam::Vec3::ZERO,
3428            up: glam::Vec3::Y,
3429            fov_y: 45.0f32.to_radians(),
3430            near: 0.1,
3431            far: 1000.0,
3432            perspective: true,
3433            aspect: 16.0 / 9.0,
3434        }
3435    }
3436}
3437
3438impl Camera3D {
3439    /// Compute the view matrix (world → camera space).
3440    pub fn view_matrix(&self) -> glam::Mat4 {
3441        glam::Mat4::look_at_lh(self.position, self.target, self.up)
3442    }
3443
3444    /// Compute the projection matrix.
3445    pub fn projection_matrix(&self) -> glam::Mat4 {
3446        if self.perspective {
3447            glam::Mat4::perspective_lh(self.fov_y, self.aspect, self.near, self.far)
3448        } else {
3449            // Orthographic with fov_y as half-height
3450            let top = self.fov_y;
3451            let right = top * self.aspect;
3452            glam::Mat4::orthographic_lh(-right, right, -top, top, self.near, self.far)
3453        }
3454    }
3455
3456    /// Compute the combined view-projection matrix.
3457    pub fn view_projection(&self) -> glam::Mat4 {
3458        self.projection_matrix() * self.view_matrix()
3459    }
3460}
3461
3462/// FrameRenderer extends Renderer with frame lifecycle management.
3463/// It is typically implemented by the host windowing/rendering environment.
3464pub trait FrameRenderer<E = ()>: Renderer {
3465    fn begin_frame(&mut self) -> E;
3466    fn render_frame(&mut self) {
3467        // Default implementation does nothing - override for custom frame rendering
3468    }
3469    fn end_frame(&mut self, encoder: E);
3470}
3471use std::sync::Arc;
3472type SubscriberList<T> = Arc<std::sync::Mutex<Vec<Box<dyn Fn(&T) + Send + Sync>>>>;
3473/// State wrapper that owns a value and notifies subscribers when changed
3474#[derive(Clone)]
3475pub struct State<T: Clone + Send + Sync + 'static> {
3476    swap: Arc<arc_swap::ArcSwap<T>>,
3477    metadata_swap: Arc<arc_swap::ArcSwap<Option<agents::MutationMetadata>>>,
3478    #[cfg(not(target_arch = "wasm32"))]
3479    tvar: Arc<stm::TVar<T>>,
3480    #[cfg(not(target_arch = "wasm32"))]
3481    metadata_tvar: Arc<stm::TVar<Option<agents::MutationMetadata>>>,
3482    subscribers: SubscriberList<T>,
3483    version: Arc<std::sync::atomic::AtomicU64>,
3484    resolution: agents::ConflictResolution,
3485}
3486impl<T: Clone + Send + Sync + 'static> State<T> {
3487    /// Create a new State with initial value
3488    pub fn new(value: T) -> Self {
3489        #[cfg(not(target_arch = "wasm32"))]
3490        let tvar = Arc::new(stm::TVar::new(value.clone()));
3491        #[cfg(not(target_arch = "wasm32"))]
3492        let metadata_tvar = Arc::new(stm::TVar::new(None));
3493        Self {
3494            swap: Arc::new(arc_swap::ArcSwap::from_pointee(value)),
3495            metadata_swap: Arc::new(arc_swap::ArcSwap::new(Arc::new(None))),
3496            #[cfg(not(target_arch = "wasm32"))]
3497            tvar,
3498            #[cfg(not(target_arch = "wasm32"))]
3499            metadata_tvar,
3500            subscribers: Arc::new(std::sync::Mutex::new(Vec::new())),
3501            version: Arc::new(std::sync::atomic::AtomicU64::new(0)),
3502            resolution: agents::ConflictResolution::default(),
3503        }
3504    }
3505    /// Set the conflict resolution strategy for this state.
3506    pub fn with_resolution(mut self, resolution: agents::ConflictResolution) -> Self {
3507        self.resolution = resolution;
3508        self
3509    }
3510    /// Get the current value
3511    pub fn get(&self) -> T {
3512        (**self.swap.load()).clone()
3513    }
3514    /// Set a new value, notifying all subscribers. Applies conflict resolution if agents are present.
3515    pub fn set(&self, value: T) {
3516        #[cfg(not(target_arch = "wasm32"))]
3517        let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3518            let new_meta = agents::get_current_mutation_metadata();
3519            let existing_meta = self.metadata_tvar.read(tx)?;
3520            let mut skip = false;
3521            if self.resolution == agents::ConflictResolution::PriorityWins
3522                && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3523                && new_m.priority < old_m.priority
3524            {
3525                skip = true;
3526            }
3527            if !skip {
3528                self.tvar.write(tx, value.clone())?;
3529                self.metadata_tvar.write(tx, new_meta)?;
3530                Ok((false, value.clone(), new_meta))
3531            } else {
3532                Ok((true, self.tvar.read(tx)?, existing_meta))
3533            }
3534        });
3535        #[cfg(target_arch = "wasm32")]
3536        let (was_skipped, final_val, final_meta) =
3537            (false, value, agents::get_current_mutation_metadata());
3538        if was_skipped {
3539            if let (Some(new_m), Some(old_m)) =
3540                (agents::get_current_mutation_metadata(), final_meta)
3541            {
3542                agents::notify_conflict(agents::ConflictEvent {
3543                    agent_id: new_m.agent_id,
3544                    priority: new_m.priority,
3545                    existing_agent_id: old_m.agent_id,
3546                    existing_priority: old_m.priority,
3547                    timestamp_ms: new_m.timestamp_ms,
3548                });
3549            }
3550            return;
3551        }
3552        self.swap.store(Arc::new(final_val.clone()));
3553        self.metadata_swap.store(Arc::new(final_meta));
3554        self.version
3555            .fetch_add(1, std::sync::atomic::Ordering::Release);
3556        let subs = Arc::clone(&self.subscribers);
3557        if crate::is_batching() {
3558            crate::enqueue_batch_task(Box::new(move || {
3559                let s = subs.lock().unwrap();
3560                for cb in s.iter() {
3561                    cb(&final_val);
3562                }
3563            }));
3564        } else {
3565            let s = subs.lock().unwrap();
3566            for cb in s.iter() {
3567                cb(&final_val);
3568            }
3569        }
3570    }
3571    pub fn mutate<F: Fn(&T) -> T>(&self, f: F) {
3572        #[cfg(not(target_arch = "wasm32"))]
3573        {
3574            let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3575                let new_meta = agents::get_current_mutation_metadata();
3576                let existing_meta = self.metadata_tvar.read(tx)?;
3577                let mut skip = false;
3578                if self.resolution == agents::ConflictResolution::PriorityWins
3579                    && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3580                    && new_m.priority < old_m.priority
3581                {
3582                    skip = true;
3583                }
3584                if !skip {
3585                    let current = self.tvar.read(tx)?;
3586                    let next = f(&current);
3587                    self.tvar.write(tx, next.clone())?;
3588                    self.metadata_tvar.write(tx, new_meta)?;
3589                    Ok((false, next, new_meta))
3590                } else {
3591                    Ok((true, self.tvar.read(tx)?, existing_meta))
3592                }
3593            });
3594            if was_skipped {
3595                if let (Some(new_m), Some(old_m)) =
3596                    (agents::get_current_mutation_metadata(), final_meta)
3597                {
3598                    agents::notify_conflict(agents::ConflictEvent {
3599                        agent_id: new_m.agent_id,
3600                        priority: new_m.priority,
3601                        existing_agent_id: old_m.agent_id,
3602                        existing_priority: old_m.priority,
3603                        timestamp_ms: new_m.timestamp_ms,
3604                    });
3605                }
3606                return;
3607            }
3608            self.swap.store(Arc::new(final_val.clone()));
3609            self.metadata_swap.store(Arc::new(final_meta));
3610            self.version
3611                .fetch_add(1, std::sync::atomic::Ordering::Release);
3612            let subs = Arc::clone(&self.subscribers);
3613            if crate::is_batching() {
3614                crate::enqueue_batch_task(Box::new(move || {
3615                    let s = subs.lock().unwrap();
3616                    for cb in s.iter() {
3617                        cb(&final_val);
3618                    }
3619                }));
3620            } else {
3621                let s = subs.lock().unwrap();
3622                for cb in s.iter() {
3623                    cb(&final_val);
3624                }
3625            }
3626        }
3627        #[cfg(target_arch = "wasm32")]
3628        {
3629            self.set(f(&self.get()));
3630        }
3631    }
3632    /// Get current version
3633    pub fn version(&self) -> u64 {
3634        self.version.load(std::sync::atomic::Ordering::Acquire)
3635    }
3636    /// Subscribe to state changes
3637    pub fn subscribe<F: Fn(&T) + Send + Sync + 'static>(&self, callback: F) {
3638        self.subscribers.lock().unwrap().push(Box::new(callback));
3639    }
3640}
3641use crate::runtime::NodeStateSnapshot;
3642use std::sync::OnceLock;
3643use std::sync::atomic::{AtomicBool, Ordering};
3644/// Global application state registry.
3645pub static SYSTEM_STATE: OnceLock<Arc<arc_swap::ArcSwap<KnowledgeState>>> = OnceLock::new();
3646#[cfg(not(target_arch = "wasm32"))]
3647static KNOWLEDGE_TVAR: OnceLock<stm::TVar<KnowledgeState>> = OnceLock::new();
3648static IS_BATCHING: AtomicBool = AtomicBool::new(false);
3649pub static IS_RENDERING: AtomicBool = AtomicBool::new(false);
3650pub static LAYOUT_DIRTY: AtomicBool = AtomicBool::new(false);
3651type BatchQueue = OnceLock<std::sync::Mutex<Vec<Box<dyn FnOnce() + Send + Sync>>>>;
3652static BATCH_QUEUE: BatchQueue = OnceLock::new();
3653/// Global write lock to serialize updates to SYSTEM_STATE and KNOWLEDGE_TVAR,
3654/// preventing parallel race conditions between STM transactions and the lock-free reader state.
3655static STATE_WRITE_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
3656/// Returns true if state updates are currently being batched.
3657pub fn is_batching() -> bool {
3658    IS_BATCHING.load(Ordering::Acquire)
3659}
3660/// Returns true if the system is currently in the render phase.
3661pub fn is_rendering() -> bool {
3662    IS_RENDERING.load(Ordering::Acquire)
3663}
3664/// Signals the start of the render phase. Mutations during this phase trigger warnings.
3665pub fn begin_render_phase() {
3666    IS_RENDERING.store(true, Ordering::Release);
3667}
3668/// Signals the end of the render phase.
3669pub fn end_render_phase() {
3670    IS_RENDERING.store(false, Ordering::Release);
3671}
3672/// Enqueues a notification task to be run when the current batch flushes.
3673pub fn enqueue_batch_task(task: Box<dyn FnOnce() + Send + Sync>) {
3674    let mut queue = BATCH_QUEUE
3675        .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3676        .lock()
3677        .unwrap();
3678    queue.push(task);
3679}
3680/// Executes multiple state updates in a single batch, deferring all subscriber
3681/// notifications until the closure completes. This prevents layout thrashing
3682/// and redundant render cycles when modifying multiple independent states.
3683pub fn batch<F: FnOnce()>(f: F) {
3684    if IS_BATCHING.swap(true, Ordering::AcqRel) {
3685        // Already inside a batch, just execute
3686        f();
3687        return;
3688    }
3689    f();
3690    IS_BATCHING.store(false, Ordering::Release);
3691    let mut queue = BATCH_QUEUE
3692        .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3693        .lock()
3694        .unwrap();
3695    let tasks: Vec<_> = queue.drain(..).collect();
3696    drop(queue);
3697    for task in tasks {
3698        task();
3699    }
3700}
3701/// Get a reference to the global system state.
3702pub fn get_system_state() -> Arc<arc_swap::ArcSwap<KnowledgeState>> {
3703    SYSTEM_STATE
3704        .get_or_init(|| Arc::new(arc_swap::ArcSwap::from_pointee(KnowledgeState::default())))
3705        .clone()
3706}
3707pub fn load_system_state() -> arc_swap::Guard<Arc<KnowledgeState>> {
3708    get_system_state().load()
3709}
3710pub fn update_system_state<F>(f: F)
3711where
3712    F: FnOnce(&KnowledgeState) -> KnowledgeState,
3713{
3714    let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3715    if is_rendering() {
3716        log::warn!(
3717            "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3718        );
3719    }
3720    LAYOUT_DIRTY.store(true, Ordering::SeqCst);
3721    let swap = get_system_state();
3722    let current = swap.load();
3723    let new_state = Arc::new(f(&current));
3724    swap.store(Arc::clone(&new_state));
3725    #[cfg(not(target_arch = "wasm32"))]
3726    {
3727        let tvar = KNOWLEDGE_TVAR.get_or_init(|| stm::TVar::new((*new_state).clone()));
3728        stm::atomically(|tx| tvar.write(tx, (*new_state).clone()));
3729    }
3730}
3731pub fn transact_system_state<F>(f: F)
3732where
3733    F: Fn(&KnowledgeState) -> KnowledgeState,
3734{
3735    let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3736    #[cfg(not(target_arch = "wasm32"))]
3737    {
3738        if is_rendering() {
3739            log::warn!(
3740                "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3741            );
3742        }
3743        let tvar = KNOWLEDGE_TVAR
3744            .get_or_init(|| stm::TVar::new((**get_system_state().load()).clone()))
3745            .clone();
3746        let new_state = stm::atomically(move |tx| {
3747            let current = tvar.read(tx)?;
3748            let next = f(&current);
3749            tvar.write(tx, next.clone())?;
3750            Ok(next)
3751        });
3752        get_system_state().store(Arc::new(new_state));
3753    }
3754    #[cfg(target_arch = "wasm32")]
3755    {
3756        if is_rendering() {
3757            log::warn!(
3758                "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3759            );
3760        }
3761        update_system_state(f);
3762    }
3763}
3764impl KnowledgeState {
3765    /// Create a new empty KnowledgeState.
3766    pub fn new() -> Self {
3767        Self::default()
3768    }
3769    /// Set a component's internal state.
3770    pub fn set_component_state<T: 'static + Send + Sync>(&mut self, id: u64, state: T) {
3771        self.component_states
3772            .insert(id, Arc::new(std::sync::RwLock::new(state)));
3773    }
3774    /// Get a reference to a component's internal state.
3775    pub fn get_component_state<T: 'static + Send + Sync>(
3776        &self,
3777        id: u64,
3778    ) -> Option<Arc<std::sync::RwLock<T>>> {
3779        let lock = self.component_states.get(&id)?;
3780        // Attempt to clone the Arc and downcast the inner RwLock<dyn Any> to RwLock<T>
3781        // We use a two-step approach: check if the inner type matches via Any, then transmute the Arc
3782        // SAFETY: We verify the type via Any::is::<T> before transmuting
3783        let any_ref = lock.read().ok()?;
3784        if any_ref.is::<T>() {
3785            // Type matches — safe to transmute the Arc
3786            drop(any_ref);
3787            let cloned: Arc<std::sync::RwLock<dyn std::any::Any + Send + Sync>> = Arc::clone(lock);
3788            // Transmute Arc<RwLock<dyn Any>> to Arc<RwLock<T>>
3789            // This is safe because we just verified the inner type is T
3790            Some(unsafe {
3791                let raw = Arc::into_raw(cloned);
3792                Arc::from_raw(raw as *const std::sync::RwLock<T>)
3793            })
3794        } else {
3795            None
3796        }
3797    }
3798    /// Add a new fragment to memory.
3799    pub fn remember(&mut self, fragment: KnowledgeFragment) {
3800        self.fragments.insert(fragment.id.clone(), fragment);
3801    }
3802    /// Process a search query against the local knowledge base.
3803    pub fn process_query(&mut self, query: &str) {
3804        let query_lower = query.to_lowercase();
3805        let mut results: Vec<(f32, String)> = self
3806            .fragments
3807            .iter()
3808            .map(|(id, frag)| {
3809                let mut score = 0.0;
3810                if frag.summary.to_lowercase().contains(&query_lower) {
3811                    score += 1.0;
3812                }
3813                if frag.source.to_lowercase().contains(&query_lower) {
3814                    score += 0.5;
3815                }
3816                (score, id.clone())
3817            })
3818            .filter(|(score, _)| *score > 0.0)
3819            .collect();
3820        // Sort by relevance score
3821        results.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
3822        self.last_query_results = results.into_iter().map(|(_, id)| id).take(5).collect();
3823    }
3824    /// Captures a snapshot of the current state for debugging and hot-reloading.
3825    pub fn snapshot(&self) -> Vec<NodeStateSnapshot> {
3826        let mut snapshots = Vec::new();
3827        // Snapshots of agentic fragments
3828        for frag in self.fragments.values() {
3829            if let Ok(val) = serde_json::to_value(frag) {
3830                snapshots.push(NodeStateSnapshot { id: 0, state: val });
3831            }
3832        }
3833        snapshots
3834    }
3835}
3836/// A read/write projection into a `State<T>` owned elsewhere.
3837#[derive(Clone)]
3838pub struct Binding<T: Clone + Send + Sync + 'static> {
3839    swap: Arc<arc_swap::ArcSwap<T>>,
3840    #[cfg(not(target_arch = "wasm32"))]
3841    tvar: Arc<stm::TVar<T>>,
3842    version: Arc<std::sync::atomic::AtomicU64>,
3843}
3844impl<T: Clone + Send + Sync + 'static> Binding<T> {
3845    /// Create a binding from a State
3846    pub fn from_state(state: &State<T>) -> Self {
3847        Self {
3848            swap: Arc::clone(&state.swap),
3849            #[cfg(not(target_arch = "wasm32"))]
3850            tvar: Arc::clone(&state.tvar),
3851            version: Arc::clone(&state.version),
3852        }
3853    }
3854    /// Get the current value
3855    pub fn get(&self) -> T {
3856        (**self.swap.load()).clone()
3857    }
3858    /// Set a new value
3859    pub fn set(&self, value: T) {
3860        self.swap.store(Arc::new(value.clone()));
3861        #[cfg(not(target_arch = "wasm32"))]
3862        {
3863            let tvar = Arc::clone(&self.tvar);
3864            let v = value.clone();
3865            stm::atomically(move |tx| tvar.write(tx, v.clone()));
3866        }
3867        self.version
3868            .fetch_add(1, std::sync::atomic::Ordering::Release);
3869    }
3870    /// Get current version
3871    pub fn version(&self) -> u64 {
3872        self.version.load(std::sync::atomic::Ordering::Acquire)
3873    }
3874}
3875#[cfg(not(target_arch = "wasm32"))]
3876pub fn transact_pair<A, B, F>(state_a: &State<A>, state_b: &State<B>, f: F)
3877where
3878    A: Clone + Send + Sync + 'static,
3879    B: Clone + Send + Sync + 'static,
3880    F: Fn(&A, &B) -> (A, B),
3881{
3882    let tvar_a = Arc::clone(&state_a.tvar);
3883    let tvar_b = Arc::clone(&state_b.tvar);
3884    let (new_a, new_b) = stm::atomically(move |tx| {
3885        let a = tvar_a.read(tx)?;
3886        let b = tvar_b.read(tx)?;
3887        let (na, nb) = f(&a, &b);
3888        tvar_a.write(tx, na.clone())?;
3889        tvar_b.write(tx, nb.clone())?;
3890        Ok((na, nb))
3891    });
3892    state_a.swap.store(Arc::new(new_a.clone()));
3893    state_b.swap.store(Arc::new(new_b.clone()));
3894    state_a
3895        .version
3896        .fetch_add(1, std::sync::atomic::Ordering::Release);
3897    state_b
3898        .version
3899        .fetch_add(1, std::sync::atomic::Ordering::Release);
3900    let subs_a = Arc::clone(&state_a.subscribers);
3901    let subs_b = Arc::clone(&state_b.subscribers);
3902    if crate::is_batching() {
3903        crate::enqueue_batch_task(Box::new(move || {
3904            {
3905                let s = subs_a.lock().unwrap();
3906                for cb in s.iter() {
3907                    cb(&new_a);
3908                }
3909            }
3910            {
3911                let s = subs_b.lock().unwrap();
3912                for cb in s.iter() {
3913                    cb(&new_b);
3914                }
3915            }
3916        }));
3917    } else {
3918        {
3919            let s = subs_a.lock().unwrap();
3920            for cb in s.iter() {
3921                cb(&new_a);
3922            }
3923        }
3924        {
3925            let s = subs_b.lock().unwrap();
3926            for cb in s.iter() {
3927                cb(&new_b);
3928            }
3929        }
3930    }
3931}
3932use std::any::TypeId;
3933use std::sync::Mutex;
3934/// Global environment storage using TypeId as keys.
3935pub(crate) static ENVIRONMENT: OnceLock<
3936    Mutex<HashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>>,
3937> = OnceLock::new();
3938/// Environment key type for accessing ambient values
3939/// Implement this trait to define a new environment key.
3940pub trait EnvKey: 'static + Send + Sync {
3941    /// The type of value stored in the environment
3942    type Value: Clone + Send + Sync + 'static;
3943    /// Get a default value for this key
3944    fn default_value() -> Self::Value;
3945}
3946/// Key for accessing the Yggdrasil design token tree
3947pub struct YggdrasilKey;
3948impl EnvKey for YggdrasilKey {
3949    type Value = YggdrasilTokens;
3950    fn default_value() -> Self::Value {
3951        default_tokens()
3952    }
3953}
3954// Duplicate AssetKey removed - original definition at line 63
3955/// System appearance (Light/Dark mode)
3956#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3957pub enum Appearance {
3958    Light,
3959    Dark,
3960}
3961/// Orientation for layouts
3962#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3963pub enum Orientation {
3964    Horizontal,
3965    Vertical,
3966}
3967/// Placement configuration for placing a view within a Grid layout.
3968#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3969pub struct GridPlacement {
3970    /// 0-based column index. Negative values count from the end of columns.
3971    pub column: i32,
3972    /// Number of columns the view spans (default is 1).
3973    pub column_span: u32,
3974    /// 0-based row index. Negative values count from the end of rows.
3975    pub row: i32,
3976    /// Number of rows the view spans (default is 1).
3977    pub row_span: u32,
3978}
3979/// Cross-axis alignment for layout containers.
3980#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3981pub enum Alignment {
3982    #[default]
3983    Center,
3984    Leading,
3985    Trailing,
3986    Top,
3987    Bottom,
3988}
3989/// Main-axis distribution for linear layout containers.
3990#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3991pub enum Distribution {
3992    #[default]
3993    Fill,
3994    Center,
3995    Leading,
3996    Trailing,
3997    SpaceBetween,
3998    SpaceAround,
3999    SpaceEvenly,
4000}
4001/// A color represented by RGBA components in the [0.0, 1.0] range.
4002#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
4003pub struct Color {
4004    pub r: f32,
4005    pub g: f32,
4006    pub b: f32,
4007    pub a: f32,
4008}
4009impl Color {
4010    pub const BLACK: Color = Color {
4011        r: 0.0,
4012        g: 0.0,
4013        b: 0.0,
4014        a: 1.0,
4015    };
4016    pub const WHITE: Color = Color {
4017        r: 1.0,
4018        g: 1.0,
4019        b: 1.0,
4020        a: 1.0,
4021    };
4022    pub const TRANSPARENT: Color = Color {
4023        r: 0.0,
4024        g: 0.0,
4025        b: 0.0,
4026        a: 0.0,
4027    };
4028    pub const RED: Color = Color {
4029        r: 1.0,
4030        g: 0.0,
4031        b: 0.0,
4032        a: 1.0,
4033    };
4034    pub const GREEN: Color = Color {
4035        r: 0.0,
4036        g: 1.0,
4037        b: 0.0,
4038        a: 1.0,
4039    };
4040    pub const BLUE: Color = Color {
4041        r: 0.0,
4042        g: 0.0,
4043        b: 1.0,
4044        a: 1.0,
4045    };
4046    pub const VIKING_GOLD: Color = Color {
4047        r: 1.0,
4048        g: 0.84,
4049        b: 0.0,
4050        a: 1.0,
4051    };
4052    pub const MAGENTA_LIQUID: Color = Color {
4053        r: 1.0,
4054        g: 0.0,
4055        b: 1.0,
4056        a: 1.0,
4057    };
4058    pub const TACTICAL_OBSIDIAN: Color = Color {
4059        r: 0.05,
4060        g: 0.05,
4061        b: 0.07,
4062        a: 1.0,
4063    };
4064    /// Calculate the relative luminance of the color as defined by WCAG 2.x
4065    pub fn relative_luminance(&self) -> f32 {
4066        fn res(c: f32) -> f32 {
4067            if c <= 0.03928 {
4068                c / 12.92
4069            } else {
4070                ((c + 0.055) / 1.055).powf(2.4)
4071            }
4072        }
4073        0.2126 * res(self.r) + 0.7152 * res(self.g) + 0.0722 * res(self.b)
4074    }
4075    /// Calculate the contrast ratio between this color and another color
4076    pub fn contrast_ratio(&self, other: &Color) -> f32 {
4077        let l1 = self.relative_luminance();
4078        let l2 = other.relative_luminance();
4079        if l1 > l2 {
4080            (l1 + 0.05) / (l2 + 0.05)
4081        } else {
4082            (l2 + 0.05) / (l1 + 0.05)
4083        }
4084    }
4085    pub const CYAN: Color = Color {
4086        r: 0.0,
4087        g: 1.0,
4088        b: 1.0,
4089        a: 1.0,
4090    };
4091    pub const YELLOW: Color = Color {
4092        r: 1.0,
4093        g: 1.0,
4094        b: 0.0,
4095        a: 1.0,
4096    };
4097    pub const MAGENTA: Color = Color {
4098        r: 1.0,
4099        g: 0.0,
4100        b: 1.0,
4101        a: 1.0,
4102    };
4103    pub const GRAY: Color = Color {
4104        r: 0.5,
4105        g: 0.5,
4106        b: 0.5,
4107        a: 1.0,
4108    };
4109    /// Create a new color from RGBA components.
4110    pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
4111        Self { r, g, b, a }
4112    }
4113    /// Convert the color to a [r, g, b, a] array.
4114    pub fn as_array(&self) -> [f32; 4] {
4115        [self.r, self.g, self.b, self.a]
4116    }
4117
4118    /// Return a new color with lightness increased by `amount`.
4119    ///
4120    /// Adds `amount` to each RGB channel and clamps to [0.0, 1.0].
4121    /// This is a simple sRGB lightness adjustment, not perceptually uniform.
4122    /// For perceptually uniform adjustments, use OKLCH via cvkg-themes.
4123    pub fn lighten(&self, amount: f32) -> Self {
4124        Self {
4125            r: (self.r + amount).clamp(0.0, 1.0),
4126            g: (self.g + amount).clamp(0.0, 1.0),
4127            b: (self.b + amount).clamp(0.0, 1.0),
4128            a: self.a,
4129        }
4130    }
4131
4132    /// Return a new color with lightness decreased by `amount`.
4133    pub fn darken(&self, amount: f32) -> Self {
4134        Self {
4135            r: (self.r - amount).clamp(0.0, 1.0),
4136            g: (self.g - amount).clamp(0.0, 1.0),
4137            b: (self.b - amount).clamp(0.0, 1.0),
4138            a: self.a,
4139        }
4140    }
4141}
4142impl View for Color {
4143    type Body = Never;
4144    fn body(self) -> Self::Body {
4145        unreachable!()
4146    }
4147    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
4148        renderer.fill_rect(rect, self.as_array());
4149    }
4150}
4151/// Key for accessing the current system appearance
4152pub struct AppearanceKey;
4153impl EnvKey for AppearanceKey {
4154    type Value = Appearance;
4155    fn default_value() -> Self::Value {
4156        Appearance::Dark // Default to Dark (Ginnungagap) for Berserker aesthetic
4157    }
4158}
4159/// StyleResolver provides high-level access to themed values from the environment.
4160pub struct StyleResolver;
4161impl StyleResolver {
4162    /// Resolve a color from the current environment
4163    pub fn color(key: &str) -> String {
4164        let tokens = Environment::<YggdrasilKey>::new().get();
4165        let appearance = Environment::<AppearanceKey>::new().get();
4166        let is_dark = appearance == Appearance::Dark;
4167        tokens
4168            .get_color(key, is_dark)
4169            .unwrap_or_else(|| "#FF00FF".to_string()) // Default to MuspelMagenta on failure
4170    }
4171    /// Resolve a generic token value
4172    pub fn get<T: FromStr>(category: &str, key: &str) -> Option<T> {
4173        let tokens = Environment::<YggdrasilKey>::new().get();
4174        let appearance = Environment::<AppearanceKey>::new().get();
4175        let is_dark = appearance == Appearance::Dark;
4176        tokens.get(category, key, is_dark)
4177    }
4178    /// Resolve a color from the current environment as a [f32; 4] RGBA array.
4179    /// Returns the color value for the current appearance (light/dark).
4180    /// Falls back to magenta (#FF00FF) if the key is not found.
4181    pub fn color_array(key: &str) -> [f32; 4] {
4182        let hex = Self::color(key);
4183        parse_hex_color(&hex)
4184    }
4185}
4186
4187/// Parse a hex color string (#RRGGBB or #RRGGBBAA) into [f32; 4] RGBA.
4188fn parse_hex_color(hex: &str) -> [f32; 4] {
4189    let hex = hex.trim_start_matches('#');
4190    if hex.len() >= 6 {
4191        let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(255) as f32 / 255.0;
4192        let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0) as f32 / 255.0;
4193        let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(255) as f32 / 255.0;
4194        let a = if hex.len() >= 8 {
4195            u8::from_str_radix(&hex[6..8], 16).unwrap_or(255) as f32 / 255.0
4196        } else {
4197            1.0
4198        };
4199        [r, g, b, a]
4200    } else {
4201        [1.0, 0.0, 1.0, 1.0] // Magenta fallback
4202    }
4203}
4204
4205/// The authoritative Cyberpunk Viking default tokens
4206pub fn default_tokens() -> YggdrasilTokens {
4207    let mut tokens = YggdrasilTokens::new();
4208    // Core Norse Colorways
4209    tokens.color.insert(
4210        "background".to_string(),
4211        TokenValue::Single {
4212            value: "#000000".to_string(), // Ginnungagap (The Void)
4213        },
4214    );
4215    tokens.color.insert(
4216        "primary".to_string(),
4217        TokenValue::Single {
4218            value: "#00FFFF".to_string(), // NiflCyan (Aesir Primary)
4219        },
4220    );
4221    tokens.color.insert(
4222        "secondary".to_string(),
4223        TokenValue::Single {
4224            value: "#FF00FF".to_string(), // MuspelMagenta (Berserker Secondary)
4225        },
4226    );
4227    tokens.color.insert(
4228        "surface".to_string(),
4229        TokenValue::Adaptive {
4230            light: "#FFFFFF".to_string(),
4231            dark: "#121212".to_string(),
4232        },
4233    );
4234    tokens.color.insert(
4235        "text".to_string(),
4236        TokenValue::Adaptive {
4237            light: "#000000".to_string(),
4238            dark: "#FFFFFF".to_string(),
4239        },
4240    );
4241    // Semantic component tokens
4242    tokens.color.insert(
4243        "surface_elevated".to_string(),
4244        TokenValue::Adaptive {
4245            light: "#FFFFFF".to_string(),
4246            dark: "#1A1A24".to_string(),
4247        },
4248    );
4249    tokens.color.insert(
4250        "surface_overlay".to_string(),
4251        TokenValue::Adaptive {
4252            light: "#FFFFFF".to_string(),
4253            dark: "#1E1E2E".to_string(),
4254        },
4255    );
4256    tokens.color.insert(
4257        "border".to_string(),
4258        TokenValue::Adaptive {
4259            light: "#D0D0D8".to_string(),
4260            dark: "#2A2A3A".to_string(),
4261        },
4262    );
4263    tokens.color.insert(
4264        "border_strong".to_string(),
4265        TokenValue::Adaptive {
4266            light: "#A0A0B0".to_string(),
4267            dark: "#3A3A50".to_string(),
4268        },
4269    );
4270    tokens.color.insert(
4271        "text_muted".to_string(),
4272        TokenValue::Adaptive {
4273            light: "#606070".to_string(),
4274            dark: "#8080A0".to_string(),
4275        },
4276    );
4277    tokens.color.insert(
4278        "text_dim".to_string(),
4279        TokenValue::Adaptive {
4280            light: "#9090A0".to_string(),
4281            dark: "#505070".to_string(),
4282        },
4283    );
4284    tokens.color.insert(
4285        "accent".to_string(),
4286        TokenValue::Single {
4287            value: "#00FFFF".to_string(), // NiflCyan
4288        },
4289    );
4290    tokens.color.insert(
4291        "accent_hover".to_string(),
4292        TokenValue::Single {
4293            value: "#33FFFF".to_string(),
4294        },
4295    );
4296    tokens.color.insert(
4297        "success".to_string(),
4298        TokenValue::Single {
4299            value: "#00E676".to_string(),
4300        },
4301    );
4302    tokens.color.insert(
4303        "warning".to_string(),
4304        TokenValue::Single {
4305            value: "#FFB300".to_string(),
4306        },
4307    );
4308    tokens.color.insert(
4309        "error".to_string(),
4310        TokenValue::Single {
4311            value: "#FF5252".to_string(),
4312        },
4313    );
4314    tokens.color.insert(
4315        "info".to_string(),
4316        TokenValue::Single {
4317            value: "#448AFF".to_string(),
4318        },
4319    );
4320    tokens.color.insert(
4321        "hover".to_string(),
4322        TokenValue::Adaptive {
4323            light: "#F0F0F5".to_string(),
4324            dark: "#252535".to_string(),
4325        },
4326    );
4327    tokens.color.insert(
4328        "active".to_string(),
4329        TokenValue::Adaptive {
4330            light: "#E0E0EB".to_string(),
4331            dark: "#303045".to_string(),
4332        },
4333    );
4334    tokens.color.insert(
4335        "disabled".to_string(),
4336        TokenValue::Adaptive {
4337            light: "#E8E8F0".to_string(),
4338            dark: "#1A1A28".to_string(),
4339        },
4340    );
4341    tokens.color.insert(
4342        "disabled_text".to_string(),
4343        TokenValue::Adaptive {
4344            light: "#B0B0C0".to_string(),
4345            dark: "#404060".to_string(),
4346        },
4347    );
4348    tokens.color.insert(
4349        "focus_ring".to_string(),
4350        TokenValue::Single {
4351            value: "#00FFFF".to_string(),
4352        },
4353    );
4354    tokens.color.insert(
4355        "shadow".to_string(),
4356        TokenValue::Adaptive {
4357            light: "#00000020".to_string(),
4358            dark: "#00000060".to_string(),
4359        },
4360    );
4361    tokens.color.insert(
4362        "code_bg".to_string(),
4363        TokenValue::Adaptive {
4364            light: "#F5F5FA".to_string(),
4365            dark: "#0D0D18".to_string(),
4366        },
4367    );
4368    // Bifrost (Glassmorphism) - Frosted Style
4369    tokens.bifrost.insert(
4370        "blur".to_string(),
4371        TokenValue::Single {
4372            value: "25.0".to_string(),
4373        },
4374    );
4375    tokens.bifrost.insert(
4376        "saturation".to_string(),
4377        TokenValue::Single {
4378            value: "1.2".to_string(),
4379        },
4380    );
4381    tokens.bifrost.insert(
4382        "opacity".to_string(),
4383        TokenValue::Single {
4384            value: "0.65".to_string(),
4385        },
4386    );
4387    // Gungnir (Neon Glow)
4388    tokens.gungnir.insert(
4389        "intensity".to_string(),
4390        TokenValue::Single {
4391            value: "1.0".to_string(),
4392        },
4393    );
4394    tokens.gungnir.insert(
4395        "radius".to_string(),
4396        TokenValue::Single {
4397            value: "15.0".to_string(),
4398        },
4399    );
4400    // Mjolnir (Sharp Geometry)
4401    tokens.mjolnir.insert(
4402        "clip_angle".to_string(),
4403        TokenValue::Single {
4404            value: "12.0".to_string(),
4405        },
4406    );
4407    tokens.mjolnir.insert(
4408        "border_width".to_string(),
4409        TokenValue::Single {
4410            value: "2.0".to_string(),
4411        },
4412    );
4413    // Sleipnir (Spring Animation)
4414    tokens.anim.insert(
4415        "stiffness".to_string(),
4416        TokenValue::Single {
4417            value: "170.0".to_string(),
4418        },
4419    );
4420    tokens.anim.insert(
4421        "damping".to_string(),
4422        TokenValue::Single {
4423            value: "26.0".to_string(),
4424        },
4425    );
4426    tokens.anim.insert(
4427        "mass".to_string(),
4428        TokenValue::Single {
4429            value: "1.0".to_string(),
4430        },
4431    );
4432    // Accessibility
4433    tokens.accessibility.insert(
4434        "reduce_motion".to_string(),
4435        TokenValue::Single {
4436            value: "false".to_string(),
4437        },
4438    );
4439    tokens
4440}
4441/// Environment wrapper for accessing ambient values
4442pub struct Environment<K: EnvKey> {
4443    _marker: std::marker::PhantomData<K>,
4444}
4445impl<K: EnvKey> Default for Environment<K> {
4446    fn default() -> Self {
4447        Self::new()
4448    }
4449}
4450impl<K: EnvKey> Environment<K> {
4451    /// Create a new Environment
4452    pub fn new() -> Self {
4453        Self {
4454            _marker: std::marker::PhantomData,
4455        }
4456    }
4457    /// Get the current value from the environment
4458    pub fn get(&self) -> K::Value {
4459        if let Some(env_store) = ENVIRONMENT.get() {
4460            let env_lock = env_store.lock().unwrap();
4461            if let Some(val) = env_lock.get(&std::any::TypeId::of::<K>()) {
4462                if let Some(typed_val) = val.downcast_ref::<K::Value>() {
4463                    return typed_val.clone();
4464                } else {
4465                    log::warn!(
4466                        "Environment: Downcast failed for key type {:?}",
4467                        std::any::type_name::<K>()
4468                    );
4469                }
4470            } else {
4471                log::debug!(
4472                    "Environment: Key not found: {:?}. Returning default.",
4473                    std::any::type_name::<K>()
4474                );
4475            }
4476        } else {
4477            log::debug!(
4478                "Environment: Store not initialized. Key: {:?}. Returning default.",
4479                std::any::type_name::<K>()
4480            );
4481        }
4482        K::default_value()
4483    }
4484}
4485/// Ambient environment management
4486pub mod env {
4487    /// Insert a value into the environment
4488    pub fn insert<K: super::EnvKey>(value: K::Value) {
4489        let store = super::ENVIRONMENT
4490            .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
4491        let mut env_map = store.lock().unwrap();
4492        env_map.insert(std::any::TypeId::of::<K>(), Box::new(value));
4493    }
4494    /// Remove a value from the environment.
4495    pub fn remove<K: super::EnvKey>() {
4496        if let Some(store) = super::ENVIRONMENT.get() {
4497            let mut env_map = store.lock().unwrap();
4498            env_map.remove(&std::any::TypeId::of::<K>());
4499        }
4500    }
4501}
4502/// Geometry modifiers
4503/// Size of the view in logical pixels
4504#[derive(Debug, Clone, Copy, PartialEq)]
4505pub struct Size {
4506    pub width: f32,
4507    pub height: f32,
4508}
4509
4510impl Size {
4511    pub const ZERO: Self = Self {
4512        width: 0.0,
4513        height: 0.0,
4514    };
4515
4516    pub fn new(width: f32, height: f32) -> Self {
4517        Self { width, height }
4518    }
4519}
4520
4521/// Insets for padding
4522#[derive(Debug, Clone, Copy, PartialEq)]
4523pub struct EdgeInsets {
4524    pub top: f32,
4525    pub leading: f32,
4526    pub bottom: f32,
4527    pub trailing: f32,
4528}
4529
4530impl EdgeInsets {
4531    /// Equal insets on all edges
4532    pub fn all(value: f32) -> Self {
4533        Self {
4534            top: value,
4535            leading: value,
4536            bottom: value,
4537            trailing: value,
4538        }
4539    }
4540
4541    /// Vertical insets (top and bottom)
4542    pub fn vertical(value: f32) -> Self {
4543        Self {
4544            top: value,
4545            leading: 0.0,
4546            bottom: value,
4547            trailing: 0.0,
4548        }
4549    }
4550
4551    /// Horizontal insets (leading and trailing)
4552    pub fn horizontal(value: f32) -> Self {
4553        Self {
4554            top: 0.0,
4555            leading: value,
4556            bottom: 0.0,
4557            trailing: value,
4558        }
4559    }
4560}
4561
4562/// Modifier to set the size and alignment constraints of a view.
4563/// This determines the proposal size passed to the child and how the child is aligned
4564/// within the layout rect allocated to the frame.
4565#[derive(Debug, Clone, Copy, PartialEq)]
4566pub struct FrameModifier {
4567    /// Exact width to assign to the child view.
4568    pub width: Option<f32>,
4569    /// Exact height to assign to the child view.
4570    pub height: Option<f32>,
4571    /// Minimum width constraint for the view.
4572    pub min_width: Option<f32>,
4573    /// Maximum width constraint for the view.
4574    pub max_width: Option<f32>,
4575    /// Minimum height constraint for the view.
4576    pub min_height: Option<f32>,
4577    /// Maximum height constraint for the view.
4578    pub max_height: Option<f32>,
4579    /// The alignment strategy for positioning the child view within the frame.
4580    pub alignment: Alignment,
4581}
4582
4583impl Default for FrameModifier {
4584    /// Returns the default frame configuration which has no constraints and center alignment.
4585    fn default() -> Self {
4586        Self::new()
4587    }
4588}
4589
4590impl FrameModifier {
4591    /// Creates a new FrameModifier with all dimensions unspecified and center alignment.
4592    pub fn new() -> Self {
4593        Self {
4594            width: None,
4595            height: None,
4596            min_width: None,
4597            max_width: None,
4598            min_height: None,
4599            max_height: None,
4600            alignment: Alignment::Center,
4601        }
4602    }
4603
4604    /// Sets the fixed width of the frame.
4605    pub fn width(mut self, width: f32) -> Self {
4606        self.width = Some(width);
4607        self
4608    }
4609
4610    /// Sets the fixed height of the frame.
4611    pub fn height(mut self, height: f32) -> Self {
4612        self.height = Some(height);
4613        self
4614    }
4615
4616    /// Sets both the fixed width and height of the frame.
4617    pub fn size(mut self, width: f32, height: f32) -> Self {
4618        self.width = Some(width);
4619        self.height = Some(height);
4620        self
4621    }
4622
4623    /// Sets the minimum width constraint.
4624    pub fn min_width(mut self, min_width: f32) -> Self {
4625        self.min_width = Some(min_width);
4626        self
4627    }
4628
4629    /// Sets the maximum width constraint.
4630    pub fn max_width(mut self, max_width: f32) -> Self {
4631        self.max_width = Some(max_width);
4632        self
4633    }
4634
4635    /// Sets the minimum height constraint.
4636    pub fn min_height(mut self, min_height: f32) -> Self {
4637        self.min_height = Some(min_height);
4638        self
4639    }
4640
4641    /// Sets the maximum height constraint.
4642    pub fn max_height(mut self, max_height: f32) -> Self {
4643        self.max_height = Some(max_height);
4644        self
4645    }
4646
4647    /// Sets the alignment strategy for the child within the frame's layout bounds.
4648    pub fn alignment(mut self, alignment: Alignment) -> Self {
4649        self.alignment = alignment;
4650        self
4651    }
4652}
4653
4654impl ViewModifier for FrameModifier {
4655    /// Wraps the child view in a ModifiedView using this frame modifier.
4656    fn modify<V: View>(self, content: V) -> impl View {
4657        ModifiedView::new(content, self)
4658    }
4659
4660    /// Transforms the layout size proposal offered to the child to comply with frame constraints.
4661    fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
4662        let w = if let Some(width) = self.width {
4663            Some(width)
4664        } else {
4665            proposal.width.map(|pw| {
4666                pw.clamp(
4667                    self.min_width.unwrap_or(0.0),
4668                    self.max_width.unwrap_or(f32::INFINITY),
4669                )
4670            })
4671        };
4672        let h = if let Some(height) = self.height {
4673            Some(height)
4674        } else {
4675            proposal.height.map(|ph| {
4676                ph.clamp(
4677                    self.min_height.unwrap_or(0.0),
4678                    self.max_height.unwrap_or(f32::INFINITY),
4679                )
4680            })
4681        };
4682        SizeProposal {
4683            width: w,
4684            height: h,
4685        }
4686    }
4687
4688    /// Constraints and transforms the child's resulting size to fit the frame's bounds.
4689    fn transform_size(&self, child_size: Size) -> Size {
4690        let w = if let Some(width) = self.width {
4691            width
4692        } else {
4693            child_size.width.clamp(
4694                self.min_width.unwrap_or(0.0),
4695                self.max_width.unwrap_or(f32::INFINITY),
4696            )
4697        };
4698        let h = if let Some(height) = self.height {
4699            height
4700        } else {
4701            child_size.height.clamp(
4702                self.min_height.unwrap_or(0.0),
4703                self.max_height.unwrap_or(f32::INFINITY),
4704            )
4705        };
4706        Size {
4707            width: w,
4708            height: h,
4709        }
4710    }
4711
4712    /// Renders the frame's child view aligned within the layout rect.
4713    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4714        self.render(renderer, rect);
4715        let child_proposal =
4716            self.transform_proposal(SizeProposal::new(Some(rect.width), Some(rect.height)));
4717        let child_size = view.intrinsic_size(renderer, child_proposal);
4718
4719        let mut child_x = rect.x;
4720        let mut child_y = rect.y;
4721
4722        match self.alignment {
4723            Alignment::Leading => {
4724                child_y = rect.y + (rect.height - child_size.height) / 2.0;
4725            }
4726            Alignment::Trailing => {
4727                child_x = rect.x + rect.width - child_size.width;
4728                child_y = rect.y + (rect.height - child_size.height) / 2.0;
4729            }
4730            Alignment::Top => {
4731                child_x = rect.x + (rect.width - child_size.width) / 2.0;
4732            }
4733            Alignment::Bottom => {
4734                child_x = rect.x + (rect.width - child_size.width) / 2.0;
4735                child_y = rect.y + rect.height - child_size.height;
4736            }
4737            Alignment::Center => {
4738                child_x = rect.x + (rect.width - child_size.width) / 2.0;
4739                child_y = rect.y + (rect.height - child_size.height) / 2.0;
4740            }
4741        }
4742
4743        let child_rect = Rect {
4744            x: child_x,
4745            y: child_y,
4746            width: child_size.width,
4747            height: child_size.height,
4748        };
4749
4750        view.render(renderer, child_rect);
4751        self.post_render(renderer, rect);
4752    }
4753}
4754
4755/// Modifier to set the flex weight of a view
4756#[derive(Debug, Clone, Copy, PartialEq)]
4757pub struct FlexModifier {
4758    pub weight: f32,
4759}
4760
4761impl ViewModifier for FlexModifier {
4762    fn modify<V: View>(self, content: V) -> impl View {
4763        ModifiedView::new(content, self)
4764    }
4765
4766    fn child_flex_weight<V: View>(&self, _view: &V) -> f32 {
4767        self.weight
4768    }
4769}
4770
4771/// Modifier that specifies the column and row placement of a view inside a Grid layout.
4772#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4773pub struct GridPlacementModifier {
4774    /// The grid placement settings containing column/row indexes and spans.
4775    pub placement: GridPlacement,
4776}
4777
4778impl ViewModifier for GridPlacementModifier {
4779    /// Wraps the child view in a ModifiedView using this modifier.
4780    fn modify<V: View>(self, content: V) -> impl View {
4781        ModifiedView::new(content, self)
4782    }
4783
4784    /// Exposes the grid placement metadata to parent layout engines.
4785    fn get_grid_placement(&self) -> Option<GridPlacement> {
4786        Some(self.placement)
4787    }
4788}
4789
4790/// Modifier to render a popover, tooltip, or menu view overlaying an anchored view.
4791/// It supports alignment positioning and outside-click dismissal.
4792#[derive(Clone)]
4793pub struct OverlayModifier {
4794    /// The overlay content view.
4795    pub overlay: AnyView,
4796    /// Where the overlay is aligned relative to the anchored view.
4797    pub alignment: Alignment,
4798    /// Additional offset in logical pixels.
4799    pub offset: [f32; 2],
4800    /// Optional dismissal callback triggered by click-outside events.
4801    pub on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
4802}
4803
4804impl ViewModifier for OverlayModifier {
4805    /// Wraps the child view in a ModifiedView using this overlay modifier.
4806    fn modify<V: View>(self, content: V) -> impl View {
4807        ModifiedView::new(content, self)
4808    }
4809
4810    /// Renders the overlay content positioned above the child view.
4811    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4812        // 1. Render primary anchored view
4813        view.render(renderer, rect);
4814
4815        // 2. Measure overlay content
4816        let overlay_size = self
4817            .overlay
4818            .intrinsic_size(renderer, SizeProposal::unspecified());
4819
4820        // 3. Align overlay rect relative to anchored rect
4821        let mut overlay_x;
4822        let mut overlay_y;
4823
4824        match self.alignment {
4825            Alignment::Leading => {
4826                overlay_x = rect.x - overlay_size.width;
4827                overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4828            }
4829            Alignment::Trailing => {
4830                overlay_x = rect.x + rect.width;
4831                overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4832            }
4833            Alignment::Top => {
4834                overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4835                overlay_y = rect.y - overlay_size.height;
4836            }
4837            Alignment::Bottom => {
4838                overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4839                overlay_y = rect.y + rect.height;
4840            }
4841            Alignment::Center => {
4842                overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4843                overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4844            }
4845        }
4846
4847        overlay_x += self.offset[0];
4848        overlay_y += self.offset[1];
4849
4850        let overlay_rect = Rect {
4851            x: overlay_x,
4852            y: overlay_y,
4853            width: overlay_size.width,
4854            height: overlay_size.height,
4855        };
4856
4857        // 4. Handle click-outside dismissal
4858        if let Some(on_dismiss) = &self.on_dismiss {
4859            let dismiss = on_dismiss.clone();
4860            renderer.register_handler(
4861                "pointerdown",
4862                Arc::new(move |event| {
4863                    if let Event::PointerDown { x, y, .. } = event {
4864                        let click_inside = x >= overlay_rect.x
4865                            && x <= overlay_rect.x + overlay_rect.width
4866                            && y >= overlay_rect.y
4867                            && y <= overlay_rect.y + overlay_rect.height;
4868                        if !click_inside {
4869                            dismiss();
4870                        }
4871                    }
4872                }),
4873            );
4874        }
4875
4876        // 5. Render overlay view
4877        self.overlay.render(renderer, overlay_rect);
4878    }
4879}
4880
4881/// Modifier to offset a view
4882#[derive(Debug, Clone, Copy, PartialEq)]
4883pub struct OffsetModifier {
4884    pub x: f32,
4885    pub y: f32,
4886}
4887
4888impl OffsetModifier {
4889    pub fn new(x: f32, y: f32) -> Self {
4890        Self { x, y }
4891    }
4892}
4893
4894impl ViewModifier for OffsetModifier {
4895    fn modify<V: View>(self, content: V) -> impl View {
4896        ModifiedView::new(content, self)
4897    }
4898}
4899
4900/// Modifier to set the z-index of a view
4901#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4902pub struct ZIndexModifier {
4903    pub z_index: i32,
4904}
4905
4906impl ZIndexModifier {
4907    pub fn new(z_index: i32) -> Self {
4908        Self { z_index }
4909    }
4910}
4911
4912impl ViewModifier for ZIndexModifier {
4913    fn modify<V: View>(self, content: V) -> impl View {
4914        ModifiedView::new(content, self)
4915    }
4916}
4917
4918/// Layout constraints for views
4919#[derive(Debug, Clone, Copy, PartialEq, Default)]
4920pub struct LayoutConstraints {
4921    pub min_width: Option<f32>,
4922    pub max_width: Option<f32>,
4923    pub min_height: Option<f32>,
4924    pub max_height: Option<f32>,
4925}
4926
4927/// Modifier to set layout constraints
4928#[derive(Debug, Clone, Copy, PartialEq)]
4929pub struct LayoutModifier {
4930    pub constraints: LayoutConstraints,
4931}
4932
4933impl LayoutModifier {
4934    pub fn new(constraints: LayoutConstraints) -> Self {
4935        Self { constraints }
4936    }
4937}
4938
4939impl ViewModifier for LayoutModifier {
4940    fn modify<V: View>(self, content: V) -> impl View {
4941        ModifiedView::new(content, self)
4942    }
4943}
4944
4945/// Modifier to handle platform safe areas
4946#[derive(Debug, Clone, Copy, PartialEq)]
4947pub struct SafeAreaModifier {
4948    pub ignores: bool,
4949}
4950
4951impl ViewModifier for SafeAreaModifier {
4952    fn modify<V: View>(self, content: V) -> impl View {
4953        ModifiedView::new(content, self)
4954    }
4955}
4956
4957/// Modifier to add elevation (shadow) to a view
4958#[derive(Debug, Clone, Copy, PartialEq)]
4959pub struct ElevationModifier {
4960    pub level: f32,
4961}
4962
4963impl ViewModifier for ElevationModifier {
4964    fn modify<V: View>(self, content: V) -> impl View {
4965        ModifiedView::new(content, self)
4966    }
4967
4968    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4969        if self.level > 0.0 {
4970            let radius = self.level * 2.0;
4971            let offset_y = self.level * 0.5;
4972            let shadow_color = [0.0, 0.0, 0.0, 0.3];
4973            renderer.push_shadow(radius, shadow_color, [0.0, offset_y]);
4974            view.render(renderer, rect);
4975            renderer.pop_shadow();
4976        } else {
4977            view.render(renderer, rect);
4978        }
4979    }
4980}
4981
4982// Layout subsystem
4983pub mod layout {
4984    use super::*;
4985
4986    /// Key used to identify a cached layout entry.
4987    /// Combines a view hash with a generation counter for cache invalidation.
4988    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4989    pub struct LayoutKey {
4990        pub view_hash: u64,
4991        pub generation: u64,
4992    }
4993
4994    // Layout pass scratch space
4995    pub struct LayoutCache {
4996        pub safe_area: SafeArea,
4997        size_cache: HashMap<(u64, u32, u32), Size>, // (ViewHash, ProposalW, ProposalH)
4998        /// Monotonically increasing generation counter for cache invalidation.
4999        /// When a view tree changes, bumping the generation causes stale entries
5000        /// to be treated as invalid without eagerly clearing the entire cache.
5001        generation: u64,
5002    }
5003
5004    impl Default for LayoutCache {
5005        fn default() -> Self {
5006            Self::new()
5007        }
5008    }
5009
5010    impl LayoutCache {
5011        pub fn new() -> Self {
5012            Self {
5013                safe_area: SafeArea::default(),
5014                size_cache: HashMap::new(),
5015                generation: 0,
5016            }
5017        }
5018
5019        /// Returns the current generation counter.
5020        pub fn generation(&self) -> u64 {
5021            self.generation
5022        }
5023
5024        /// Bump the generation counter, logically invalidating all cached entries
5025        /// without eagerly clearing them. Subsequent lookups with the old generation
5026        /// will miss until re-populated.
5027        pub fn invalidate(&mut self) {
5028            self.generation = self.generation.wrapping_add(1);
5029        }
5030
5031        /// Check whether a cached entry for the given key is still valid
5032        /// against the current generation.
5033        pub fn is_valid(&self, key: LayoutKey, current_gen: u64) -> bool {
5034            key.generation == current_gen && key.generation == self.generation
5035        }
5036
5037        pub fn clear(&mut self) {
5038            self.safe_area = SafeArea::default();
5039            self.size_cache.clear();
5040        }
5041
5042        pub fn get_size(&self, view_hash: u64, proposal: SizeProposal) -> Option<Size> {
5043            let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
5044            let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
5045            self.size_cache.get(&(view_hash, pw, ph)).copied()
5046        }
5047
5048        pub fn set_size(&mut self, view_hash: u64, proposal: SizeProposal, size: Size) {
5049            let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
5050            let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
5051            self.size_cache.insert((view_hash, pw, ph), size);
5052        }
5053
5054        /// Remove all cached size entries for a specific view hash.
5055        pub fn invalidate_view(&mut self, view_hash: u64) {
5056            self.size_cache.retain(|&(hash, _, _), _| hash != view_hash);
5057        }
5058    }
5059
5060    /// Proposed size from parent view
5061    #[derive(Debug, Clone, Copy, PartialEq)]
5062    pub struct SizeProposal {
5063        pub width: Option<f32>,
5064        pub height: Option<f32>,
5065    }
5066
5067    impl SizeProposal {
5068        pub fn unspecified() -> Self {
5069            Self {
5070                width: None,
5071                height: None,
5072            }
5073        }
5074
5075        pub fn width(width: f32) -> Self {
5076            Self {
5077                width: Some(width),
5078                height: None,
5079            }
5080        }
5081
5082        pub fn height(height: f32) -> Self {
5083            Self {
5084                width: None,
5085                height: Some(height),
5086            }
5087        }
5088
5089        pub fn tight(width: f32, height: f32) -> Self {
5090            Self {
5091                width: Some(width),
5092                height: Some(height),
5093            }
5094        }
5095
5096        pub fn new(width: Option<f32>, height: Option<f32>) -> Self {
5097            Self { width, height }
5098        }
5099    }
5100
5101    /// A view that can participate in layout
5102    pub trait LayoutView: Send {
5103        /// Propose a size for this view given the available space
5104        fn size_that_fits(
5105            &self,
5106            proposal: SizeProposal,
5107            subviews: &[&dyn LayoutView],
5108            cache: &mut LayoutCache,
5109        ) -> Size;
5110
5111        /// Place subviews within the given bounds
5112        fn place_subviews(
5113            &self,
5114            bounds: Rect,
5115            subviews: &mut [&mut dyn LayoutView],
5116            cache: &mut LayoutCache,
5117        );
5118
5119        /// Returns the flex weight of this view (default is 0.0, which means fixed/intrinsic)
5120        fn flex_weight(&self) -> f32 {
5121            0.0
5122        }
5123
5124        /// Return a debug representation of this layout subtree.
5125        /// The `indent` parameter controls the indentation level for nested display.
5126        fn debug_layout(&self, indent: usize) -> String {
5127            let prefix = " ".repeat(indent);
5128            format!("{}LayoutView", prefix)
5129        }
5130    }
5131    /// Edge insets for padding, margins, and safe areas
5132    #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
5133    pub struct EdgeInsets {
5134        pub top: f32,
5135        pub leading: f32,
5136        pub bottom: f32,
5137        pub trailing: f32,
5138    }
5139
5140    impl EdgeInsets {
5141        pub fn new(top: f32, leading: f32, bottom: f32, trailing: f32) -> Self {
5142            Self {
5143                top,
5144                leading,
5145                bottom,
5146                trailing,
5147            }
5148        }
5149
5150        pub fn all(value: f32) -> Self {
5151            Self {
5152                top: value,
5153                leading: value,
5154                bottom: value,
5155                trailing: value,
5156            }
5157        }
5158    }
5159
5160    /// SafeArea constraints provided by the platform
5161    #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
5162    pub struct SafeArea {
5163        pub insets: EdgeInsets,
5164    }
5165
5166    /// SDF Shape definitions for Vili Interaction Paradigm hit-testing.
5167    #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
5168    pub enum SdfShape {
5169        Rect(Rect),
5170        RoundedRect { rect: Rect, radius: f32 },
5171        Circle { center: [f32; 2], radius: f32 },
5172    }
5173
5174    /// Rectangle in logical pixels
5175    #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
5176    pub struct Rect {
5177        pub x: f32,
5178        pub y: f32,
5179        pub width: f32,
5180        pub height: f32,
5181    }
5182
5183    impl Rect {
5184        pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
5185            Self {
5186                x,
5187                y,
5188                width,
5189                height,
5190            }
5191        }
5192
5193        pub fn inset(&self, amount: f32) -> Self {
5194            Self {
5195                x: self.x + amount,
5196                y: self.y + amount,
5197                width: (self.width - amount * 2.0).max(0.0),
5198                height: (self.height - amount * 2.0).max(0.0),
5199            }
5200        }
5201
5202        pub fn offset(&self, dx: f32, dy: f32) -> Self {
5203            Self {
5204                x: self.x + dx,
5205                y: self.y + dy,
5206                ..*self
5207            }
5208        }
5209
5210        pub fn zero() -> Self {
5211            Self {
5212                x: 0.0,
5213                y: 0.0,
5214                width: 0.0,
5215                height: 0.0,
5216            }
5217        }
5218
5219        pub fn contains(&self, x: f32, y: f32) -> bool {
5220            x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
5221        }
5222
5223        pub fn size(&self) -> Size {
5224            Size {
5225                width: self.width,
5226                height: self.height,
5227            }
5228        }
5229
5230        /// Split the rect horizontally into N equal pieces
5231        pub fn split_horizontal(&self, n: usize) -> Vec<Rect> {
5232            if n == 0 {
5233                return vec![];
5234            }
5235            let item_width = self.width / n as f32;
5236            (0..n)
5237                .map(|i| Rect {
5238                    x: self.x + i as f32 * item_width,
5239                    y: self.y,
5240                    width: item_width,
5241                    height: self.height,
5242                })
5243                .collect()
5244        }
5245
5246        /// Split the rect vertically into N equal pieces
5247        pub fn split_vertical(&self, n: usize) -> Vec<Rect> {
5248            if n == 0 {
5249                return vec![];
5250            }
5251            let item_height = self.height / n as f32;
5252            (0..n)
5253                .map(|i| Rect {
5254                    x: self.x,
5255                    y: self.y + i as f32 * item_height,
5256                    width: self.width,
5257                    height: item_height,
5258                })
5259                .collect()
5260        }
5261    }
5262}
5263
5264// Re-export layout items for convenience
5265pub use layout::{LayoutCache, LayoutKey, LayoutView, Rect, SizeProposal};
5266// Size and FrameRenderer are pub items in this module; no re-export alias needed.
5267
5268pub mod agents;
5269pub mod animation;
5270pub mod gpu;
5271pub mod material;
5272pub mod runtime;
5273pub mod scene_graph;
5274pub mod sdf_shadow;
5275
5276pub use material::DrawMaterial;
5277pub use scene_graph::{NodeId, bifrost_registry};
5278
5279// Duplicate AssetState removed - original definition at line 67
5280
5281/// AssetManager defines the interface for loading and caching external resources.
5282pub trait AssetManager: Send + Sync {
5283    /// Request an image asset. Returns the current state (Loading, Ready, or Error).
5284    fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>>;
5285
5286    /// Pre-load an image into the cache.
5287    fn preload_image(&self, url: &str);
5288}
5289
5290/// The phase of a touch or gesture event in its lifecycle.
5291#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5292pub enum TouchPhase {
5293    /// The touch/gesture has just begun.
5294    Began,
5295    /// The touch/gesture is moving.
5296    Moved,
5297    /// The touch/gesture has ended normally.
5298    Ended,
5299    /// The touch/gesture was cancelled (e.g., by the system).
5300    Cancelled,
5301}
5302
5303/// User input event types
5304#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
5305pub enum Event {
5306    PointerDown {
5307        x: f32,
5308        y: f32,
5309        button: u32,
5310        proximity_field: f32,
5311        tilt: Option<f32>,
5312        azimuth: Option<f32>,
5313        pressure: Option<f32>,
5314        barrel_rotation: Option<f32>,
5315    },
5316    PointerUp {
5317        x: f32,
5318        y: f32,
5319        button: u32,
5320        tilt: Option<f32>,
5321        azimuth: Option<f32>,
5322        pressure: Option<f32>,
5323        barrel_rotation: Option<f32>,
5324    },
5325    PointerMove {
5326        x: f32,
5327        y: f32,
5328        proximity_field: f32,
5329        tilt: Option<f32>,
5330        azimuth: Option<f32>,
5331        pressure: Option<f32>,
5332        barrel_rotation: Option<f32>,
5333    },
5334    PointerClick {
5335        x: f32,
5336        y: f32,
5337        button: u32,
5338        tilt: Option<f32>,
5339        azimuth: Option<f32>,
5340        pressure: Option<f32>,
5341        barrel_rotation: Option<f32>,
5342    },
5343    PointerEnter,
5344    PointerLeave,
5345    /// Mouse wheel / trackpad scroll event.
5346    /// `delta_x` is the horizontal scroll amount, `delta_y` is the vertical scroll amount (positive = scroll down).
5347    PointerWheel {
5348        x: f32,
5349        y: f32,
5350        delta_x: f32,
5351        delta_y: f32,
5352    },
5353    /// Double-click event (rapid successive clicks).
5354    PointerDoubleClick {
5355        x: f32,
5356        y: f32,
5357        button: u32,
5358    },
5359    /// Drag-and-drop: drag started (pointer moved while button held past threshold).
5360    DragStart {
5361        x: f32,
5362        y: f32,
5363        button: u32,
5364    },
5365    /// Drag-and-drop: drag in progress.
5366    DragMove {
5367        x: f32,
5368        y: f32,
5369    },
5370    /// Drag-and-drop: drag ended (pointer released).
5371    DragEnd {
5372        x: f32,
5373        y: f32,
5374    },
5375    KeyDown {
5376        key: String,
5377    },
5378    KeyUp {
5379        key: String,
5380    },
5381    /// Focus gained by a node.
5382    FocusIn,
5383    /// Focus lost by a node.
5384    FocusOut,
5385    /// Clipboard copy event.
5386    Copy,
5387    /// Clipboard cut event.
5388    Cut,
5389    /// Clipboard paste event with the pasted text content.
5390    Paste(String),
5391    /// Input Method Editor event (e.g. CJK character composition)
5392    Ime(String),
5393    /// Touch began at the given position.
5394    TouchStart {
5395        x: f32,
5396        y: f32,
5397        touch_id: u64,
5398    },
5399    /// Touch moved to a new position.
5400    TouchMove {
5401        x: f32,
5402        y: f32,
5403        touch_id: u64,
5404    },
5405    /// Touch ended at the given position.
5406    TouchEnd {
5407        x: f32,
5408        y: f32,
5409        touch_id: u64,
5410    },
5411    /// Touch cancelled.
5412    TouchCancel {
5413        touch_id: u64,
5414    },
5415    /// Multi-touch pinch gesture.
5416    /// `center` is the gesture anchor point in device-independent pixels.
5417    /// `scale` is the relative pinch scale (>1 = expand, <1 = contract).
5418    /// `velocity` is the instantaneous velocity of the pinch.
5419    /// `phase` indicates the current phase of the gesture lifecycle.
5420    GesturePinch {
5421        center: [f32; 2],
5422        scale: f32,
5423        velocity: f32,
5424        phase: TouchPhase,
5425    },
5426    /// Multi-touch swipe/pan gesture.
5427    /// `direction` is the normalized direction vector [dx, dy].
5428    /// `velocity` is the instantaneous velocity of the swipe.
5429    /// `phase` indicates the current phase of the gesture lifecycle.
5430    GestureSwipe {
5431        direction: [f32; 2],
5432        velocity: f32,
5433        phase: TouchPhase,
5434    },
5435    /// Drag-and-drop: external file dropped onto window.
5436    FileDrop {
5437        path: String,
5438    },
5439}
5440
5441impl Event {
5442    /// Returns the canonical string name of the event for lookup in handler maps.
5443    pub fn name(&self) -> &'static str {
5444        match self {
5445            Self::PointerDown { .. } => "pointerdown",
5446            Self::PointerUp { .. } => "pointerup",
5447            Self::PointerMove { .. } => "pointermove",
5448            Self::PointerClick { .. } => "pointerclick",
5449            Self::PointerEnter => "pointerenter",
5450            Self::PointerLeave => "pointerleave",
5451            Self::PointerWheel { .. } => "pointerwheel",
5452            Self::PointerDoubleClick { .. } => "pointerdoubleclick",
5453            Self::DragStart { .. } => "dragstart",
5454            Self::DragMove { .. } => "dragmove",
5455            Self::DragEnd { .. } => "dragend",
5456            Self::KeyDown { .. } => "keydown",
5457            Self::KeyUp { .. } => "keyup",
5458            Self::FocusIn => "focusin",
5459            Self::FocusOut => "focusout",
5460            Self::Copy => "copy",
5461            Self::Cut => "cut",
5462            Self::Paste(_) => "paste",
5463            Self::Ime(_) => "ime",
5464            Self::TouchStart { .. } => "touchstart",
5465            Self::TouchMove { .. } => "touchmove",
5466            Self::TouchEnd { .. } => "touchend",
5467            Self::TouchCancel { .. } => "touchcancel",
5468            Self::GesturePinch { .. } => "gesturepinch",
5469            Self::GestureSwipe { .. } => "gestureswipe",
5470            Self::FileDrop { .. } => "filedrop",
5471        }
5472    }
5473}
5474
5475/// Response from an event handler
5476#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5477pub enum EventResponse {
5478    Handled,
5479    Ignored,
5480}
5481
5482/// A basic implementation of AssetManager that can be overridden by platform backends.
5483pub struct DefaultAssetManager {
5484    cache: AssetCache,
5485}
5486type AssetCache = Arc<arc_swap::ArcSwap<HashMap<String, AssetState<Arc<Vec<u8>>>>>>;
5487
5488impl Default for DefaultAssetManager {
5489    fn default() -> Self {
5490        Self::new()
5491    }
5492}
5493
5494impl DefaultAssetManager {
5495    pub fn new() -> Self {
5496        Self {
5497            cache: Arc::new(arc_swap::ArcSwap::from_pointee(HashMap::new())),
5498        }
5499    }
5500}
5501
5502impl AssetManager for DefaultAssetManager {
5503    fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>> {
5504        if let Some(state) = self.cache.load().get(url) {
5505            return state.clone();
5506        }
5507
5508        self.cache.rcu(|map| {
5509            let mut m = (**map).clone();
5510            m.entry(url.to_string()).or_insert(AssetState::Loading);
5511            m
5512        });
5513        AssetState::Loading
5514    }
5515
5516    fn preload_image(&self, _url: &str) {}
5517}
5518
5519use std::future::Future;
5520
5521/// Suspense wrapper for asynchronous state management.
5522/// Integrates with State<T> to provide loading/error/ready states for async operations.
5523pub struct Suspense<T: Clone + Send + Sync + 'static> {
5524    inner: State<AssetState<T>>,
5525}
5526
5527impl<T: Clone + Send + Sync + 'static> Default for Suspense<T> {
5528    fn default() -> Self {
5529        Self::new()
5530    }
5531}
5532
5533impl<T: Clone + Send + Sync + 'static> Suspense<T> {
5534    pub fn new() -> Self {
5535        Self {
5536            inner: State::new(AssetState::Loading),
5537        }
5538    }
5539
5540    pub fn new_async<F>(future: F) -> Self
5541    where
5542        F: Future<Output = Result<T, String>> + Send + 'static,
5543    {
5544        let suspense = Self::new();
5545        let suspense_clone = suspense.clone();
5546
5547        #[cfg(not(target_arch = "wasm32"))]
5548        {
5549            // Try to use an existing tokio runtime, or fallback to a dedicated thread
5550            if let Ok(handle) = tokio::runtime::Handle::try_current() {
5551                handle.spawn(async move {
5552                    let result = future.await;
5553                    match result {
5554                        Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5555                        Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5556                    }
5557                });
5558            } else {
5559                std::thread::spawn(move || {
5560                    let rt = tokio::runtime::Builder::new_current_thread()
5561                        .enable_all()
5562                        .build()
5563                        .unwrap();
5564                    rt.block_on(async {
5565                        let result = future.await;
5566                        match result {
5567                            Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5568                            Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5569                        }
5570                    });
5571                });
5572            }
5573        }
5574        #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5575        {
5576            wasm_bindgen_futures::spawn_local(async move {
5577                let result = future.await;
5578                match result {
5579                    Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5580                    Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5581                }
5582            });
5583        }
5584
5585        suspense
5586    }
5587
5588    pub fn ready(value: T) -> Self {
5589        Self {
5590            inner: State::new(AssetState::Ready(value)),
5591        }
5592    }
5593
5594    pub fn error(message: impl Into<String>) -> Self {
5595        Self {
5596            inner: State::new(AssetState::Error(message.into())),
5597        }
5598    }
5599
5600    pub fn get(&self) -> AssetState<T> {
5601        self.inner.get()
5602    }
5603
5604    pub fn get_ref(&self) -> AssetState<T> {
5605        self.inner.get()
5606    }
5607
5608    pub fn is_loading(&self) -> bool {
5609        matches!(self.get(), AssetState::Loading)
5610    }
5611
5612    pub fn is_ready(&self) -> bool {
5613        matches!(self.get(), AssetState::Ready(_))
5614    }
5615
5616    pub fn is_error(&self) -> bool {
5617        matches!(self.get(), AssetState::Error(_))
5618    }
5619
5620    pub fn ready_value(&self) -> Option<T> {
5621        match self.get() {
5622            AssetState::Ready(value) => Some(value),
5623            _ => None,
5624        }
5625    }
5626
5627    pub fn error_message(&self) -> Option<String> {
5628        match self.get() {
5629            AssetState::Error(message) => Some(message),
5630            _ => None,
5631        }
5632    }
5633
5634    pub fn subscribe<F: Fn(&AssetState<T>) + Send + Sync + 'static>(&self, callback: F) {
5635        self.inner.subscribe(callback)
5636    }
5637
5638    pub fn inner_state(&self) -> &State<AssetState<T>> {
5639        &self.inner
5640    }
5641}
5642
5643impl<T: Clone + Send + Sync + 'static> Clone for Suspense<T> {
5644    fn clone(&self) -> Self {
5645        Self {
5646            inner: self.inner.clone(),
5647        }
5648    }
5649}
5650
5651impl<T: Clone + Send + Sync + 'static> From<T> for Suspense<T> {
5652    fn from(value: T) -> Self {
5653        Self::ready(value)
5654    }
5655}
5656
5657impl<T: Clone + Send + Sync + 'static> From<Result<T, String>> for Suspense<T> {
5658    fn from(result: Result<T, String>) -> Self {
5659        match result {
5660            Ok(value) => Self::ready(value),
5661            Err(error) => Self::error(error),
5662        }
5663    }
5664}
5665
5666#[cfg(test)]
5667mod phase1_test;
5668
5669/// Berserker mode states for the rendering pipeline.
5670#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5671pub enum BerserkerMode {
5672    Normal,
5673    Rage,    // Red tint, slight shake
5674    Frenzy,  // Heavy red tint, motion blur, aggressive shake
5675    GodMode, // Golden aura, lightning arcs
5676}
5677
5678/// Seer trait for AI-assisted UI components.
5679/// Allows components to receive "prophecies" (predictions) from an AI backend.
5680pub trait Seer: Send + Sync {
5681    /// Provide a prediction for the next user action or content.
5682    fn predict(&self, context: &str) -> String;
5683    /// Stream real-time "whispers" (transcriptions/intent).
5684    fn whispers(&self) -> Vec<String>;
5685}
5686
5687#[cfg(test)]
5688mod vili_tests {
5689    use super::*;
5690
5691    struct DummyRenderer;
5692    impl ElapsedTime for DummyRenderer {
5693        fn elapsed_time(&self) -> f32 {
5694            0.0
5695        }
5696        fn delta_time(&self) -> f32 {
5697            0.0
5698        }
5699    }
5700    impl Renderer for DummyRenderer {
5701        fn fill_rect(&mut self, _r: Rect, _c: [f32; 4]) {}
5702        fn fill_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4]) {}
5703        fn fill_ellipse(&mut self, _r: Rect, _c: [f32; 4]) {}
5704        fn stroke_rect(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5705        fn stroke_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4], _w: f32) {}
5706        fn stroke_ellipse(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5707        fn draw_line(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, _c: [f32; 4], _w: f32) {}
5708        fn draw_text(&mut self, _t: &str, _x: f32, _y: f32, _s: f32, _c: [f32; 4]) {}
5709        fn measure_text(&mut self, _t: &str, _s: f32) -> (f32, f32) {
5710            (0.0, 0.0)
5711        }
5712        fn memoize(&mut self, _id: u64, _hash: u64, _r: &dyn Fn(&mut dyn Renderer)) {}
5713        fn draw_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {}
5714        fn set_camera_3d(&mut self, _camera: &Camera3D) {}
5715        fn push_transform_3d(&mut self, _transform: &Transform3D) {}
5716        fn pop_transform_3d(&mut self) {}
5717    }
5718
5719    #[test]
5720    fn test_magnetic_warp() {
5721        let renderer = DummyRenderer;
5722        let anchor = Rect {
5723            x: 100.0,
5724            y: 100.0,
5725            width: 50.0,
5726            height: 50.0,
5727        };
5728        // Pointer is near the anchor (distance < 120)
5729        let pointer = [125.0, 50.0];
5730        // distance from center (125, 125) is 75.
5731        // force = (1.0 - 75/120) * strength
5732        let warp = renderer.magnetic_warp(pointer, anchor, 1.0);
5733        // It should pull closer to (125, 125), so Y should be > 50
5734        assert!(warp[1] > 50.0);
5735
5736        // Out of range pointer should remain unchanged
5737        let far_pointer = [500.0, 500.0];
5738        let far_warp = renderer.magnetic_warp(far_pointer, anchor, 1.0);
5739        assert_eq!(far_pointer, far_warp);
5740    }
5741
5742    #[test]
5743    fn test_mani_glow() {
5744        let renderer = DummyRenderer;
5745        let bounds = Rect {
5746            x: 0.0,
5747            y: 0.0,
5748            width: 100.0,
5749            height: 100.0,
5750        };
5751        let pointer_inside = [50.0, 50.0];
5752        let glow_max = renderer.mani_glow_intensity(pointer_inside, bounds, 120.0);
5753        assert_eq!(glow_max, 1.0);
5754
5755        let pointer_edge = [50.0, -10.0];
5756        let glow_partial = renderer.mani_glow_intensity(pointer_edge, bounds, 120.0);
5757        assert!(glow_partial > 0.0 && glow_partial < 1.0);
5758    }
5759
5760    #[test]
5761    fn test_fafnir_evolve() {
5762        let renderer = DummyRenderer;
5763        let bounds = Rect {
5764            x: 0.0,
5765            y: 0.0,
5766            width: 100.0,
5767            height: 100.0,
5768        };
5769        let pointer_inside = [50.0, 50.0];
5770        let scale = renderer.fafnir_evolve(pointer_inside, bounds, 1.2);
5771        assert_eq!(scale, 1.2); // Full scale when hovering center
5772    }
5773
5774    #[test]
5775    fn test_undo_manager_basic() {
5776        let mut manager = UndoManager::new(3, 0.5);
5777        let val = std::sync::Arc::new(std::sync::Mutex::new(0));
5778
5779        let v1 = val.clone();
5780        let v2 = val.clone();
5781        manager.push(
5782            "Add",
5783            move || *v1.lock().unwrap() -= 1,
5784            move || *v2.lock().unwrap() += 1,
5785        );
5786
5787        assert!(manager.can_undo());
5788        assert!(!manager.can_redo());
5789
5790        let undo = manager.undo().unwrap();
5791        undo();
5792        assert_eq!(*val.lock().unwrap(), -1);
5793        assert!(!manager.can_undo());
5794        assert!(manager.can_redo());
5795
5796        let redo = manager.redo().unwrap();
5797        redo();
5798        assert_eq!(*val.lock().unwrap(), 0);
5799    }
5800
5801    #[test]
5802    fn test_undo_manager_depth_limit() {
5803        let mut manager = UndoManager::new(2, 0.5);
5804        manager.push("1", || {}, || {});
5805        manager.push("2", || {}, || {});
5806        manager.push("3", || {}, || {});
5807
5808        assert_eq!(manager.stack.len(), 2);
5809        assert_eq!(manager.position, 2);
5810    }
5811
5812    #[test]
5813    fn test_undo_manager_coalescing() {
5814        let mut manager = UndoManager::new(10, 1.0);
5815        let count = std::sync::Arc::new(std::sync::Mutex::new(0));
5816
5817        let c = count.clone();
5818        manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5819
5820        let c = count.clone();
5821        manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5822
5823        assert_eq!(manager.stack.len(), 1);
5824
5825        let undo = manager.undo().unwrap();
5826        undo();
5827        assert_eq!(*count.lock().unwrap(), -2);
5828    }
5829}
5830
5831// =============================================================================
5832// THEME CONTEXT — Thread-local theme access for components
5833// =============================================================================
5834//
5835// Components call `use_theme()` to get the current SemanticColors.
5836// The native renderer sets this via `set_current_theme()` before each frame.
5837// Falls back to dark theme defaults if no theme has been set.
5838//
5839// We store SemanticColors directly (not the full Theme) to avoid depending
5840// on cvkg-themes from cvkg-core. The colors are cloned into thread-local storage.
5841
5842use std::cell::RefCell;
5843
5844thread_local! {
5845    /// Thread-local semantic colors for the current frame.
5846    static THEME_CONTEXT: RefCell<Option<color::SemanticColors>> = const { RefCell::new(None) };
5847}
5848
5849/// Semantic colors extracted from the theme for use by components.
5850/// This is a standalone type defined in cvkg-core so cvkg-components
5851/// can use it without depending on cvkg-themes.
5852///
5853/// Components should access these via `use_theme()` rather than hardcoding RGBA.
5854pub use color::SemanticColors;
5855
5856/// Set the current semantic colors for this thread.
5857/// Called by the native renderer before each frame.
5858pub fn set_current_theme(colors: color::SemanticColors) {
5859    THEME_CONTEXT.with(|cell| {
5860        *cell.borrow_mut() = Some(colors);
5861    });
5862}
5863
5864/// Clear the current theme. Called after each frame.
5865pub fn clear_current_theme() {
5866    THEME_CONTEXT.with(|cell| {
5867        *cell.borrow_mut() = None;
5868    });
5869}
5870
5871/// Access the current semantic colors from within a component's `render()` method.
5872///
5873/// Returns the colors set by the most recent `set_current_theme()` call.
5874/// Falls back to dark theme defaults if no theme has been set.
5875///
5876/// # Example
5877/// ```no_run
5878/// use cvkg_core::{use_theme, Renderer, Rect};
5879///
5880/// fn render_button(renderer: &mut dyn Renderer, rect: Rect) {
5881///     let colors = use_theme();
5882///     renderer.fill_rounded_rect(rect, 8.0, [colors.accent.r, colors.accent.g, colors.accent.b, colors.accent.a]);
5883/// }
5884/// ```
5885pub fn use_theme() -> color::SemanticColors {
5886    THEME_CONTEXT.with(|cell| {
5887        cell.borrow()
5888            .clone()
5889            .unwrap_or_else(color::SemanticColors::dark)
5890    })
5891}
5892
5893// =============================================================================
5894// COLOR MODULE — Standalone semantic colors type
5895// =============================================================================
5896//
5897// This module provides `SemanticColors`, a self-contained color palette that
5898// components can use without depending on `cvkg-themes`. The `use_theme()`
5899// function returns the current `SemanticColors` from thread-local storage.
5900
5901pub mod color {
5902    use super::Color;
5903
5904    /// A complete set of semantic colors for UI components.
5905    ///
5906    /// Each color serves a specific role in the UI. Components should reference
5907    /// these semantic roles rather than hardcoding RGBA values.
5908    ///
5909    /// # Example
5910    /// ```no_run
5911    /// use cvkg_core::{use_theme, Renderer, Rect};
5912    ///
5913    /// fn render_button(renderer: &mut dyn Renderer, rect: Rect) {
5914    ///     let colors = use_theme();
5915    ///     // Use accent color for the button background
5916    ///     renderer.fill_rounded_rect(rect, 8.0,
5917    ///         [colors.accent.r, colors.accent.g, colors.accent.b, colors.accent.a]);
5918    /// }
5919    /// ```
5920    #[derive(Debug, Clone)]
5921    pub struct SemanticColors {
5922        /// Primary brand color — used for key interactive elements.
5923        pub primary: Color,
5924        /// Secondary color — used for less prominent interactive elements.
5925        pub secondary: Color,
5926        /// Accent color — used for highlights, focus rings, CTAs.
5927        pub accent: Color,
5928        /// Page/window background color.
5929        pub background: Color,
5930        /// Surface color — used for cards, panels, sheets.
5931        pub surface: Color,
5932        /// Error color — used for destructive actions, error messages.
5933        pub error: Color,
5934        /// Warning color — used for caution indicators.
5935        pub warning: Color,
5936        /// Success color — used for positive feedback.
5937        pub success: Color,
5938        /// Primary text color.
5939        pub text: Color,
5940        /// Dimmed/disabled text color.
5941        pub text_dim: Color,
5942    }
5943
5944    impl SemanticColors {
5945        /// Dark theme semantic colors (default fallback).
5946        pub fn dark() -> Self {
5947            Self {
5948                primary: Color::new(1.0, 0.84, 0.0, 1.0),      // Viking Gold
5949                secondary: Color::new(1.0, 0.0, 1.0, 1.0),     // Magenta Liquid
5950                accent: Color::new(1.0, 0.0, 0.4, 1.0),        // Crimson Flash
5951                background: Color::new(0.02, 0.02, 0.05, 1.0), // Deep Void
5952                surface: Color::new(0.05, 0.05, 0.07, 1.0),    // Tactical Obsidian
5953                error: Color::new(1.0, 0.2, 0.2, 1.0),         // Red
5954                warning: Color::new(1.0, 0.8, 0.0, 1.0),       // Yellow
5955                success: Color::new(0.0, 1.0, 0.5, 1.0),       // Green
5956                text: Color::new(0.95, 0.95, 1.0, 1.0),        // Near-white
5957                text_dim: Color::new(0.6, 0.6, 0.7, 1.0),      // Gray
5958            }
5959        }
5960
5961        /// Light theme semantic colors.
5962        pub fn light() -> Self {
5963            Self {
5964                primary: Color::new(0.35, 0.30, 0.70, 1.0),
5965                secondary: Color::new(0.30, 0.50, 0.30, 1.0),
5966                accent: Color::new(0.30, 0.35, 0.75, 1.0),
5967                background: Color::new(0.97, 0.97, 0.98, 1.0),
5968                surface: Color::new(0.93, 0.93, 0.95, 1.0),
5969                error: Color::new(0.75, 0.15, 0.15, 1.0),
5970                warning: Color::new(0.80, 0.60, 0.0, 1.0),
5971                success: Color::new(0.15, 0.65, 0.30, 1.0),
5972                text: Color::new(0.08, 0.08, 0.10, 1.0),
5973                text_dim: Color::new(0.40, 0.40, 0.45, 1.0),
5974            }
5975        }
5976
5977        /// Convert the accent color semantic color into interactive state colors.
5978        ///
5979        /// This provides hover/active/focus/disabled variants derived from the
5980        /// accent color, matching the pattern that `cvkg-themes::StateColors` uses.
5981        pub fn accent_states(&self) -> InteractiveColorStates {
5982            InteractiveColorStates::from_color(self.accent)
5983        }
5984
5985        /// Convert the primary color into interactive state colors.
5986        pub fn primary_states(&self) -> InteractiveColorStates {
5987            InteractiveColorStates::from_color(self.primary)
5988        }
5989
5990        /// Convert the error color into interactive state colors.
5991        pub fn error_states(&self) -> InteractiveColorStates {
5992            InteractiveColorStates::from_color(self.error)
5993        }
5994
5995        /// Convert the success color into interactive state colors.
5996        pub fn success_states(&self) -> InteractiveColorStates {
5997            InteractiveColorStates::from_color(self.success)
5998        }
5999    }
6000
6001    /// Interactive state colors derived from a single base color.
6002    ///
6003    /// Provides hover/active/focus/disabled variants for any color,
6004    /// derived via simple lightness adjustments in sRGB space.
6005    #[derive(Debug, Clone)]
6006    pub struct InteractiveColorStates {
6007        pub default: Color,
6008        pub hover: Color,
6009        pub active: Color,
6010        pub focus: Color,
6011        pub disabled: Color,
6012        pub focus_ring: Color,
6013    }
6014
6015    impl InteractiveColorStates {
6016        /// Derive interactive state colors from a base sRGB color.
6017        ///
6018        /// Uses simple lightness adjustments:
6019        /// - Hover: +15% lightness
6020        /// - Active: -15% lightness
6021        /// - Focus: same as default
6022        /// - Disabled: 40% opacity
6023        /// - Focus ring: base color at 70% opacity
6024        pub fn from_color(base: Color) -> Self {
6025            Self {
6026                default: base,
6027                hover: base.lighten(0.15),
6028                active: base.darken(0.15),
6029                focus: base,
6030                disabled: Color::new(base.r, base.g, base.b, base.a * 0.4),
6031                focus_ring: Color::new(base.r, base.g, base.b, base.a * 0.7),
6032            }
6033        }
6034
6035        /// Get the color for a specific interactive state.
6036        pub fn color_for(&self, state: InteractiveState) -> Color {
6037            match state {
6038                InteractiveState::Default => self.default,
6039                InteractiveState::Hover => self.hover,
6040                InteractiveState::Active => self.active,
6041                InteractiveState::Focus => self.focus,
6042                InteractiveState::Disabled => self.disabled,
6043            }
6044        }
6045    }
6046
6047    /// Interactive state for a component.
6048    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6049    pub enum InteractiveState {
6050        Default,
6051        Hover,
6052        Active,
6053        Focus,
6054        Disabled,
6055    }
6056}
6057
6058// =============================================================================
6059// USE_STATE HOOK — Local component state with automatic re-render
6060// =============================================================================
6061//
6062// Components call `use_state(id, initial)` to get a `(getter, setter)` pair.
6063// The setter updates the global system state and triggers a re-render.
6064//
6065// This is the minimal state primitive needed for interactive components.
6066// For complex state, use the global `KnowledgeState` directly.
6067
6068/// Local state hook for components.
6069///
6070/// Returns a `(getter, setter)` pair:
6071/// - `getter()` returns the current value of type `T`
6072/// - `setter(value)` updates the value and triggers a re-render
6073///
6074/// The `id` must be unique per component instance (use a hash of the
6075/// component's label or a generated UUID).
6076pub fn use_state<T: Clone + Send + Sync + 'static>(
6077    id: u64,
6078    initial: T,
6079) -> (impl Fn() -> T, impl Fn(T)) {
6080    // Initialize the state if not already present
6081    let already_exists = load_system_state().get_component_state::<T>(id).is_some();
6082    if !already_exists {
6083        update_system_state(|s| {
6084            let mut ns = s.clone();
6085            ns.set_component_state(id, initial.clone());
6086            ns
6087        });
6088    }
6089
6090    let getter = move || -> T {
6091        load_system_state()
6092            .get_component_state::<T>(id)
6093            .map(|arc_lock| {
6094                arc_lock
6095                    .read()
6096                    .ok()
6097                    .map(|guard| (*guard).clone())
6098                    .unwrap_or_else(|| initial.clone())
6099            })
6100            .unwrap_or_else(|| initial.clone())
6101    };
6102
6103    let setter = {
6104        move |value| {
6105            update_system_state(|s| {
6106                let mut ns = s.clone();
6107                ns.set_component_state(id, value);
6108                ns
6109            });
6110        }
6111    };
6112
6113    (getter, setter)
6114}
6115
6116/// Generate a stable hash ID from a string key.
6117///
6118/// Use this to create unique IDs for `use_state` based on component labels
6119/// or other stable identifiers.
6120///
6121/// # Example
6122/// ```no_run
6123/// use cvkg_core::{use_state, use_state_hash};
6124/// let id = use_state_hash("my-checkbox");
6125/// let (value, set_value) = use_state(id, false);
6126/// ```
6127pub fn use_state_hash(key: &str) -> u64 {
6128    use std::hash::{Hash, Hasher};
6129    let mut s = std::collections::hash_map::DefaultHasher::new();
6130    key.hash(&mut s);
6131    s.finish()
6132}
6133
6134// =============================================================================
6135// ACCESSIBILITY PREFERENCES — System accessibility settings
6136// =============================================================================
6137//
6138// Components and the renderer query these to adapt behavior:
6139// - Reduce Motion: disable non-essential animations
6140// - Reduce Transparency: replace glass materials with opaque surfaces
6141// - Increase Contrast: make borders visible, minimum alpha 0.5
6142
6143thread_local! {
6144    /// Thread-local accessibility preferences.
6145    /// Defaults to no restrictions (all false).
6146    static ACCESSIBILITY_PREFS: std::cell::RefCell<AccessibilityPreferences> =
6147        std::cell::RefCell::new(AccessibilityPreferences::default());
6148}
6149
6150/// System accessibility preferences that components and the renderer must honor.
6151///
6152/// These map to macOS System Settings > Accessibility:
6153/// - `reduce_motion`: Disables non-essential animations (spring, bounce, etc.)
6154/// - `reduce_transparency`: Replaces glass/transparent materials with opaque surfaces
6155/// - `increase_contrast`: Makes all borders visible, minimum alpha 0.5 for all elements
6156#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
6157pub struct AccessibilityPreferences {
6158    /// User prefers reduced motion. Animations should be instant or very short.
6159    pub reduce_motion: bool,
6160    /// User prefers reduced transparency. Glass materials should be opaque.
6161    pub reduce_transparency: bool,
6162    /// User prefers increased contrast. Borders must be visible, min alpha 0.5.
6163    pub increase_contrast: bool,
6164}
6165
6166impl AccessibilityPreferences {
6167    /// Detect system accessibility preferences (macOS).
6168    ///
6169    /// On non-macOS platforms, returns defaults (all false).
6170    /// In a production implementation, this would query the OS APIs.
6171    pub fn detect_from_system() -> Self {
6172        #[cfg(target_os = "macos")]
6173        {
6174            // Try to read macOS accessibility preferences via defaults command
6175            let reduce_motion = std::process::Command::new("defaults")
6176                .args(["read", "-g", "com.apple.universalaccess", "reduceMotion"])
6177                .output()
6178                .ok()
6179                .and_then(|o| String::from_utf8(o.stdout).ok())
6180                .map(|s| s.trim() == "1")
6181                .unwrap_or(false);
6182
6183            let reduce_transparency = std::process::Command::new("defaults")
6184                .args([
6185                    "read",
6186                    "-g",
6187                    "com.apple.universalaccess",
6188                    "reduceTransparency",
6189                ])
6190                .output()
6191                .ok()
6192                .and_then(|o| String::from_utf8(o.stdout).ok())
6193                .map(|s| s.trim() == "1")
6194                .unwrap_or(false);
6195
6196            let increase_contrast = std::process::Command::new("defaults")
6197                .args([
6198                    "read",
6199                    "-g",
6200                    "com.apple.universalaccess",
6201                    "increaseContrast",
6202                ])
6203                .output()
6204                .ok()
6205                .and_then(|o| String::from_utf8(o.stdout).ok())
6206                .map(|s| s.trim() == "1")
6207                .unwrap_or(false);
6208
6209            Self {
6210                reduce_motion,
6211                reduce_transparency,
6212                increase_contrast,
6213            }
6214        }
6215        #[cfg(not(target_os = "macos"))]
6216        {
6217            Self::default()
6218        }
6219    }
6220
6221    /// Apply a minimum alpha constraint for increase-contrast mode.
6222    pub fn min_alpha(&self, requested: f32) -> f32 {
6223        if self.increase_contrast {
6224            requested.max(0.5)
6225        } else {
6226            requested
6227        }
6228    }
6229
6230    /// Returns true if glass effects should be replaced with opaque surfaces.
6231    pub fn should_disable_glass(&self) -> bool {
6232        self.reduce_transparency
6233    }
6234
6235    /// Returns true if animations should be instant.
6236    pub fn should_reduce_motion(&self) -> bool {
6237        self.reduce_motion
6238    }
6239
6240    /// Returns true if borders should be made visible.
6241    pub fn should_increase_contrast(&self) -> bool {
6242        self.increase_contrast
6243    }
6244}
6245
6246/// Get the current accessibility preferences for this thread.
6247pub fn accessibility_preferences() -> AccessibilityPreferences {
6248    ACCESSIBILITY_PREFS.with(|p| *p.borrow())
6249}
6250
6251/// Set the accessibility preferences for this thread.
6252///
6253/// The native renderer should call this on startup and when system
6254/// preferences change (via `detect_from_system()`).
6255pub fn set_accessibility_preferences(prefs: AccessibilityPreferences) {
6256    ACCESSIBILITY_PREFS.with(|p| {
6257        *p.borrow_mut() = prefs;
6258    });
6259}
6260
6261// =============================================================================
6262// CLIPBOARD — System clipboard access
6263// =============================================================================
6264
6265/// Trait for clipboard operations.
6266///
6267/// The native renderer implements this via `arboard` on desktop platforms.
6268/// On WASM, it uses the browser Clipboard API.
6269pub trait ClipboardProvider: Send + Sync {
6270    /// Read text from the system clipboard.
6271    fn read_text(&self) -> Option<String>;
6272    /// Write text to the system clipboard.
6273    fn write_text(&self, text: &str);
6274}
6275
6276/// Default clipboard implementation using `arboard`.
6277/// Note: This is only available when the `arboard` feature is enabled.
6278/// The renderer provides the concrete implementation.
6279#[cfg(not(target_arch = "wasm32"))]
6280pub struct SystemClipboard;
6281
6282#[cfg(not(target_arch = "wasm32"))]
6283impl ClipboardProvider for SystemClipboard {
6284    fn read_text(&self) -> Option<String> {
6285        use std::process::Command;
6286        // Fallback: try pbpaste on macOS
6287        Command::new("pbpaste")
6288            .output()
6289            .ok()
6290            .and_then(|o| String::from_utf8(o.stdout).ok())
6291    }
6292
6293    fn write_text(&self, text: &str) {
6294        use std::process::Command;
6295        // Fallback: try pbcopy on macOS
6296        if let Ok(mut child) = Command::new("pbcopy")
6297            .stdin(std::process::Stdio::piped())
6298            .spawn()
6299        {
6300            if let Some(stdin) = child.stdin.as_mut() {
6301                use std::io::Write;
6302                let _ = stdin.write_all(text.as_bytes());
6303            }
6304            let _ = child.wait();
6305        }
6306    }
6307}
6308
6309// =============================================================================
6310// TEXT INPUT — Direction enum for cursor movement
6311// =============================================================================
6312
6313/// Direction for cursor movement in text input.
6314#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6315pub enum TextDirection {
6316    Forward,
6317    Backward,
6318    Up,
6319    Down,
6320    LineStart,
6321    LineEnd,
6322    WordForward,
6323    WordBackward,
6324}
6325
6326/// Text input state managed by the renderer.
6327///
6328/// Components don't store this directly — the renderer maintains it
6329/// and components query/modify it through the Renderer trait methods.
6330#[derive(Debug, Clone, Default)]
6331pub struct TextInputState {
6332    /// The full text content.
6333    pub text: String,
6334    /// Cursor position as byte offset into the text.
6335    pub cursor_pos: usize,
6336    /// Selection anchor. If Some, the selection is from anchor to cursor.
6337    /// If None, there is no selection.
6338    pub selection_anchor: Option<usize>,
6339    /// Whether the input is focused (shows cursor, accepts keyboard).
6340    pub focused: bool,
6341    /// Whether the caret is currently visible (for blinking).
6342    pub caret_visible: bool,
6343    /// Last edit timestamp for undo coalescing.
6344    pub last_edit_time: f32,
6345}
6346
6347impl TextInputState {
6348    /// Create a new TextInputState with the given initial text.
6349    pub fn new(text: impl Into<String>) -> Self {
6350        let text = text.into();
6351        let cursor_pos = text.len();
6352        Self {
6353            text,
6354            cursor_pos,
6355            selection_anchor: None,
6356            focused: false,
6357            caret_visible: true,
6358            last_edit_time: 0.0,
6359        }
6360    }
6361
6362    /// Get the selection range as (start, end) byte offsets.
6363    /// Returns None if there is no selection.
6364    pub fn selection_range(&self) -> Option<(usize, usize)> {
6365        self.selection_anchor.map(|anchor| {
6366            if anchor <= self.cursor_pos {
6367                (anchor, self.cursor_pos)
6368            } else {
6369                (self.cursor_pos, anchor)
6370            }
6371        })
6372    }
6373
6374    /// Get the selected text, or empty string if no selection.
6375    pub fn selected_text(&self) -> String {
6376        self.selection_range()
6377            .map(|(start, end)| self.text[start..end].to_string())
6378            .unwrap_or_default()
6379    }
6380
6381    /// Insert text at the current cursor position, replacing any selection.
6382    pub fn insert(&mut self, new_text: &str) {
6383        if let Some((start, end)) = self.selection_range() {
6384            self.text.replace_range(start..end, new_text);
6385            self.cursor_pos = start + new_text.len();
6386        } else {
6387            self.text.insert_str(self.cursor_pos, new_text);
6388            self.cursor_pos += new_text.len();
6389        }
6390        self.selection_anchor = None;
6391    }
6392
6393    /// Delete characters. If there's a selection, delete it.
6394    /// Otherwise delete `count` characters backward (backspace) or forward (delete).
6395    pub fn delete(&mut self, backward: bool, count: usize) -> String {
6396        if let Some((start, end)) = self.selection_range() {
6397            let deleted = self.text[start..end].to_string();
6398            self.text.replace_range(start..end, "");
6399            self.cursor_pos = start;
6400            self.selection_anchor = None;
6401            return deleted;
6402        }
6403
6404        if backward && self.cursor_pos > 0 {
6405            let start = self.cursor_pos.saturating_sub(count);
6406            let deleted = self.text[start..self.cursor_pos].to_string();
6407            self.text.replace_range(start..self.cursor_pos, "");
6408            self.cursor_pos = start;
6409            deleted
6410        } else if !backward && self.cursor_pos < self.text.len() {
6411            let end = (self.cursor_pos + count).min(self.text.len());
6412            let deleted = self.text[self.cursor_pos..end].to_string();
6413            self.text.replace_range(self.cursor_pos..end, "");
6414            deleted
6415        } else {
6416            String::new()
6417        }
6418    }
6419
6420    /// Move the cursor in the given direction.
6421    pub fn move_cursor(&mut self, direction: TextDirection, extend_selection: bool) {
6422        if !extend_selection {
6423            self.selection_anchor = None;
6424        } else if self.selection_anchor.is_none() {
6425            self.selection_anchor = Some(self.cursor_pos);
6426        }
6427
6428        match direction {
6429            TextDirection::Forward if self.cursor_pos < self.text.len() => {
6430                // Move to next character boundary (UTF-8 safe)
6431                let next = self.text[self.cursor_pos..]
6432                    .char_indices()
6433                    .nth(1)
6434                    .map(|(i, _)| self.cursor_pos + i)
6435                    .unwrap_or(self.text.len());
6436                self.cursor_pos = next;
6437            }
6438            TextDirection::Backward if self.cursor_pos > 0 => {
6439                let prev = self.text[..self.cursor_pos]
6440                    .char_indices()
6441                    .next_back()
6442                    .map(|(i, _)| i)
6443                    .unwrap_or(0);
6444                self.cursor_pos = prev;
6445            }
6446            TextDirection::LineStart => {
6447                self.cursor_pos = 0;
6448            }
6449            TextDirection::LineEnd => {
6450                self.cursor_pos = self.text.len();
6451            }
6452            TextDirection::WordForward => {
6453                // Find next word boundary
6454                let rest = &self.text[self.cursor_pos..];
6455                // Skip current word chars
6456                let after_word = rest
6457                    .char_indices()
6458                    .find(|(_, c)| !c.is_alphanumeric())
6459                    .map(|(i, _)| i)
6460                    .unwrap_or(rest.len());
6461                // Skip whitespace
6462                let after_space = rest[after_word..]
6463                    .char_indices()
6464                    .find(|(_, c)| !c.is_whitespace())
6465                    .map(|(i, _)| after_word + i)
6466                    .unwrap_or(rest.len());
6467                self.cursor_pos = (self.cursor_pos + after_space).min(self.text.len());
6468            }
6469            TextDirection::WordBackward => {
6470                let before = &self.text[..self.cursor_pos];
6471                // Skip whitespace going backward
6472                let before_word = before
6473                    .char_indices()
6474                    .rev()
6475                    .find(|(_, c)| !c.is_whitespace())
6476                    .map(|(i, _)| i)
6477                    .unwrap_or(0);
6478                // Skip word chars going backward
6479                let word_start = before[..before_word]
6480                    .char_indices()
6481                    .rev()
6482                    .find(|(_, c)| !c.is_alphanumeric())
6483                    .map(|(i, _)| i)
6484                    .unwrap_or(0);
6485                self.cursor_pos = word_start;
6486            }
6487            _ => {} // Up/Down handled by multi-line components
6488        }
6489
6490        if !extend_selection {
6491            self.selection_anchor = None;
6492        }
6493    }
6494
6495    /// Select all text.
6496    pub fn select_all(&mut self) {
6497        self.cursor_pos = self.text.len();
6498        self.selection_anchor = Some(0);
6499    }
6500
6501    /// Get the byte offset of the cursor.
6502    pub fn cursor_byte_pos(&self) -> usize {
6503        self.cursor_pos
6504    }
6505}
6506
6507/// Action details for interactive buttons inside a notification.
6508#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6509pub struct NotificationAction {
6510    /// Unique identifier of the action.
6511    pub id: String,
6512    /// The text label to display on the action button.
6513    pub title: String,
6514    /// Indicates whether the action performs a destructive task (e.g. Delete).
6515    pub is_destructive: bool,
6516}
6517
6518/// Priority tier of a notification.
6519#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6520pub enum NotificationPriority {
6521    /// Placed silently into the notification center without visual alerts.
6522    Passive,
6523    /// Triggers a visual alert (toast) but does not interrupt focus.
6524    #[default]
6525    Active,
6526    /// Important alert that bypasses standard DND/Focus bounds.
6527    TimeSensitive,
6528}
6529
6530/// A structured notification representation.
6531#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
6532pub struct Notification {
6533    /// Unique identifier for this notification.
6534    pub id: String,
6535    /// App or source identifier spawning this notification.
6536    pub app_name: Option<String>,
6537    /// The bold heading/title text.
6538    pub title: String,
6539    /// The detailed descriptive body text.
6540    pub body: String,
6541    /// Optional URI or path to an icon asset.
6542    pub icon: Option<String>,
6543    /// Optional sound identifier to play when posting.
6544    pub sound: Option<String>,
6545    /// Interactive actions available on this notification.
6546    pub actions: Vec<NotificationAction>,
6547    /// Timer duration in seconds after which the toast auto-dismisses.
6548    pub timeout: Option<f32>,
6549    /// Priority level for delivery logic.
6550    pub priority: NotificationPriority,
6551    /// Time (in seconds since renderer startup) when this notification was posted.
6552    pub timestamp: f32,
6553    /// Whether the notification has been dismissed/read.
6554    pub dismissed: bool,
6555}
6556
6557/// Error type indicating a failure in generating or posting a notification.
6558#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, thiserror::Error)]
6559pub enum NotificationError {
6560    /// Permissions denied.
6561    #[error("Notification permission denied")]
6562    PermissionDenied,
6563    /// Failed to post the notification.
6564    #[error("Failed to post notification")]
6565    PostFailed,
6566}
6567
6568/// State of notification permissions.
6569#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6570pub enum NotificationPermission {
6571    /// Explicitly allowed.
6572    Granted,
6573    /// Explicitly blocked.
6574    Denied,
6575    /// Prompt has not been shown or decided yet.
6576    #[default]
6577    NotDetermined,
6578}
6579
6580/// Core interface for routing and dispatching notification events.
6581pub trait NotificationHandler: Send + Sync {
6582    /// Posts a new notification.
6583    fn show(&self, notification: Notification) -> Result<(), NotificationError>;
6584    /// Dismisses a notification by ID.
6585    fn dismiss(&self, id: &str) -> Result<(), NotificationError>;
6586    /// Requests delivery permission.
6587    fn request_permission(&self) -> NotificationPermission;
6588}
6589
6590static NEXT_NOTIFICATION_ID: std::sync::atomic::AtomicUsize =
6591    std::sync::atomic::AtomicUsize::new(1);
6592
6593/// Default in-app notification handler that writes state to KnowledgeState.
6594#[derive(Clone, Copy, Debug, Default)]
6595pub struct DefaultNotificationHandler;
6596
6597impl NotificationHandler for DefaultNotificationHandler {
6598    /// Save the notification to the global system state (history) and auto-assign an ID if empty.
6599    fn show(&self, notification: Notification) -> Result<(), NotificationError> {
6600        let mut notif = notification;
6601        if notif.id.is_empty() {
6602            let id = NEXT_NOTIFICATION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6603            notif.id = format!("notif_{}", id);
6604        }
6605        update_system_state(|state| {
6606            let mut new_state = state.clone();
6607            new_state.notifications.push(notif.clone());
6608            new_state
6609        });
6610        Ok(())
6611    }
6612
6613    /// Mark a notification as dismissed/read in the global system state.
6614    fn dismiss(&self, id: &str) -> Result<(), NotificationError> {
6615        update_system_state(|state| {
6616            let mut new_state = state.clone();
6617            for notif in &mut new_state.notifications {
6618                if notif.id == id {
6619                    notif.dismissed = true;
6620                }
6621            }
6622            new_state
6623        });
6624        Ok(())
6625    }
6626
6627    /// Returns the permission state (always Granted for internal in-app notifications).
6628    fn request_permission(&self) -> NotificationPermission {
6629        NotificationPermission::Granted
6630    }
6631}
6632
6633static NOTIFICATION_HANDLER: once_cell::sync::OnceCell<std::sync::Arc<dyn NotificationHandler>> =
6634    once_cell::sync::OnceCell::new();
6635
6636/// Sets the global notification handler.
6637pub fn set_notification_handler(handler: std::sync::Arc<dyn NotificationHandler>) {
6638    let _ = NOTIFICATION_HANDLER.set(handler);
6639}
6640
6641/// Gets the global notification handler, fallback to DefaultNotificationHandler.
6642pub fn get_notification_handler() -> std::sync::Arc<dyn NotificationHandler> {
6643    NOTIFICATION_HANDLER
6644        .get_or_init(|| std::sync::Arc::new(DefaultNotificationHandler))
6645        .clone()
6646}
6647
6648/// Filter mapping name to extension list for a file dialog.
6649#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6650pub struct FileFilter {
6651    /// Friendly name of the filter (e.g. "Images").
6652    pub name: String,
6653    /// List of file extensions (e.g. ["png", "jpg"]).
6654    pub extensions: Vec<String>,
6655}
6656
6657/// The mode/purpose of the file dialog.
6658#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
6659pub enum FileDialogMode {
6660    /// Pick a single or multiple files to open.
6661    #[default]
6662    OpenFile,
6663    /// Pick a directory path.
6664    OpenDirectory,
6665    /// Prompt for a location/name to save a file.
6666    SaveFile,
6667}
6668
6669/// Dialog options for picking files or directories.
6670#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6671pub struct FileDialog {
6672    /// Title displayed in the dialog window.
6673    pub title: String,
6674    /// Optional starting directory path.
6675    pub default_path: Option<String>,
6676    /// Extensions used to filter selection.
6677    pub filters: Vec<FileFilter>,
6678    /// Open/save mode.
6679    pub mode: FileDialogMode,
6680    /// Allows selecting multiple files if in OpenFile mode.
6681    pub allow_multiple: bool,
6682}
6683
6684/// Errors returned by the file dialog.
6685#[derive(Debug, thiserror::Error)]
6686pub enum FileDialogError {
6687    /// The user closed the dialog without selecting anything.
6688    #[error("File dialog cancelled")]
6689    Cancelled,
6690    /// An input/output error occurred.
6691    #[error("I/O error: {0}")]
6692    Io(#[from] std::io::Error),
6693    /// Platform-specific error.
6694    #[error("Platform error: {0}")]
6695    Platform(String),
6696}
6697
6698impl FileDialog {
6699    /// Creates a new FileDialog with the given mode.
6700    pub fn new(mode: FileDialogMode) -> Self {
6701        Self {
6702            mode,
6703            ..Default::default()
6704        }
6705    }
6706
6707    /// Sets the dialog title.
6708    pub fn title(mut self, title: impl Into<String>) -> Self {
6709        self.title = title.into();
6710        self
6711    }
6712
6713    /// Adds a file filter.
6714    pub fn add_filter(mut self, name: &str, extensions: &[&str]) -> Self {
6715        self.filters.push(FileFilter {
6716            name: name.to_string(),
6717            extensions: extensions.iter().map(|s| s.to_string()).collect(),
6718        });
6719        self
6720    }
6721
6722    /// Sets the default starting directory path.
6723    pub fn default_path(mut self, path: impl Into<String>) -> Self {
6724        self.default_path = Some(path.into());
6725        self
6726    }
6727
6728    /// Sets whether selecting multiple files is allowed.
6729    pub fn allow_multiple(mut self, allow: bool) -> Self {
6730        self.allow_multiple = allow;
6731        self
6732    }
6733}
6734
6735#[cfg(not(target_arch = "wasm32"))]
6736impl FileDialog {
6737    /// Pick file(s) or folder based on current mode configuration.
6738    pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6739        let mut dialog = rfd::FileDialog::new();
6740        dialog = dialog.set_title(&self.title);
6741        if let Some(path) = &self.default_path {
6742            dialog = dialog.set_directory(path);
6743        }
6744        for filter in &self.filters {
6745            let refs: Vec<&str> = filter.extensions.iter().map(|s| s.as_str()).collect();
6746            dialog = dialog.add_filter(&filter.name, &refs);
6747        }
6748
6749        match self.mode {
6750            FileDialogMode::OpenFile => {
6751                if self.allow_multiple {
6752                    dialog.pick_files().ok_or(FileDialogError::Cancelled)
6753                } else {
6754                    Ok(dialog.pick_file().into_iter().collect())
6755                }
6756            }
6757            FileDialogMode::OpenDirectory => Ok(dialog.pick_folder().into_iter().collect()),
6758            FileDialogMode::SaveFile => Ok(dialog.save_file().into_iter().collect()),
6759        }
6760    }
6761
6762    /// Helper to pick a single file/directory, returning None if cancelled.
6763    pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6764        let results = self.pick()?;
6765        Ok(results.into_iter().next())
6766    }
6767}
6768
6769#[cfg(target_arch = "wasm32")]
6770impl FileDialog {
6771    /// Pick is unsupported/mocked on WASM.
6772    pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6773        Err(FileDialogError::Platform(
6774            "FileDialog is not supported synchronously on WebAssembly".to_string(),
6775        ))
6776    }
6777
6778    /// Helper to pick a single file/directory, returning None if cancelled.
6779    pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6780        Err(FileDialogError::Platform(
6781            "FileDialog is not supported synchronously on WebAssembly".to_string(),
6782        ))
6783    }
6784}
6785
6786/// Error type representing a failure in Document load/save/parse operations.
6787#[derive(Debug, thiserror::Error)]
6788pub enum DocumentError {
6789    /// An input/output error occurred.
6790    #[error("I/O error: {0}")]
6791    Io(#[from] std::io::Error),
6792    /// Failure during deserialization or parsing.
6793    #[error("Parse error: {0}")]
6794    Parse(String),
6795    /// Failure during serialization.
6796    #[error("Serialization error: {0}")]
6797    Serialize(String),
6798}
6799
6800/// A document interface mapping to local filesystem persistence.
6801pub trait Document: Send + Sync {
6802    /// Loads the document from the specified path.
6803    fn read_from(path: &std::path::Path) -> Result<Self, DocumentError>
6804    where
6805        Self: Sized;
6806
6807    /// Saves the document to the specified path.
6808    fn write_to(&self, path: &std::path::Path) -> Result<(), DocumentError>;
6809
6810    /// Returns true if the document has unsaved modifications.
6811    fn is_dirty(&self) -> bool;
6812
6813    /// Marks the document as clean/saved.
6814    fn mark_clean(&mut self);
6815}
6816
6817/// Periodic auto-save coordinator for open Documents.
6818pub struct AutoSaveManager {
6819    /// Time interval in seconds between auto-saves.
6820    pub interval: f32,
6821    /// Elapsed timer tracker.
6822    pub timer: f32,
6823    /// Registered open documents under management.
6824    pub documents: Vec<(std::path::PathBuf, Box<dyn Document>)>,
6825}
6826
6827impl AutoSaveManager {
6828    /// Creates a new AutoSaveManager with the specified check interval.
6829    pub fn new(interval: f32) -> Self {
6830        Self {
6831            interval,
6832            timer: 0.0,
6833            documents: Vec::new(),
6834        }
6835    }
6836
6837    /// Register a document with its current file path.
6838    pub fn register(&mut self, path: std::path::PathBuf, doc: Box<dyn Document>) {
6839        self.documents.push((path, doc));
6840    }
6841
6842    /// Advance the timer and auto-save any dirty documents when the interval is reached.
6843    pub fn tick(&mut self, dt: f32) {
6844        self.timer += dt;
6845        if self.timer >= self.interval {
6846            self.timer = 0.0;
6847            for (path, doc) in &mut self.documents {
6848                if doc.is_dirty() {
6849                    match doc.write_to(path) {
6850                        Ok(()) => {
6851                            doc.mark_clean();
6852                            log::info!("[AutoSaveManager] Auto-saved document to {:?}", path);
6853                        }
6854                        Err(e) => {
6855                            log::error!(
6856                                "[AutoSaveManager] Failed to auto-save document to {:?}: {:?}",
6857                                path,
6858                                e
6859                            );
6860                        }
6861                    }
6862                }
6863            }
6864        }
6865    }
6866}
6867
6868// ── Menu Bar API ──────────────────────────────────────────────────────────────
6869
6870/// Keyboard modifier flags used by [`KeyboardShortcut`].
6871///
6872/// On macOS, `cmd` maps to the Command (⌘) key.
6873/// On all other platforms, `cmd` maps to the Control key.
6874/// This is enforced at the renderer level, not here; the data model is OS-agnostic.
6875#[derive(Debug, Clone, PartialEq, Eq, Default)]
6876pub struct Modifiers {
6877    /// Command on macOS, Control on Windows/Linux.
6878    pub cmd: bool,
6879    /// Shift key.
6880    pub shift: bool,
6881    /// Alt/Option key.
6882    pub alt: bool,
6883    /// Control key (distinct from cmd on all platforms).
6884    pub ctrl: bool,
6885}
6886
6887/// A keyboard shortcut binding to a menu action.
6888#[derive(Debug, Clone)]
6889pub struct KeyboardShortcut {
6890    /// The key character or name, e.g. `"s"`, `"z"`, `"Return"`.
6891    pub key: String,
6892    /// The required modifier combination.
6893    pub modifiers: Modifiers,
6894}
6895
6896impl KeyboardShortcut {
6897    /// Convenience constructor: cmd (or ctrl on non-macOS) + `key`.
6898    pub fn cmd(key: impl Into<String>) -> Self {
6899        Self {
6900            key: key.into(),
6901            modifiers: Modifiers {
6902                cmd: true,
6903                ..Default::default()
6904            },
6905        }
6906    }
6907
6908    /// Convenience constructor: cmd+Shift + `key`.
6909    pub fn cmd_shift(key: impl Into<String>) -> Self {
6910        Self {
6911            key: key.into(),
6912            modifiers: Modifiers {
6913                cmd: true,
6914                shift: true,
6915                ..Default::default()
6916            },
6917        }
6918    }
6919}
6920
6921/// A single entry in a [`MenuBar`].
6922///
6923/// Actions hold a callback that is invoked when the user activates the item
6924/// (either via the menu UI or via the associated keyboard shortcut).
6925/// Separators provide visual grouping. Submenus allow hierarchical menus.
6926pub enum MenuItem {
6927    /// An activatable menu entry with an optional shortcut and enabled/disabled state.
6928    Action {
6929        label: String,
6930        shortcut: Option<KeyboardShortcut>,
6931        action: std::sync::Arc<dyn Fn() + Send + Sync>,
6932        enabled: bool,
6933    },
6934    /// A nested submenu.
6935    Submenu { label: String, items: Vec<MenuItem> },
6936    /// A visual separator line between groups of items.
6937    Separator,
6938}
6939
6940impl std::fmt::Debug for MenuItem {
6941    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6942        match self {
6943            Self::Action { label, enabled, .. } => f
6944                .debug_struct("Action")
6945                .field("label", label)
6946                .field("enabled", enabled)
6947                .finish(),
6948            Self::Submenu { label, items } => f
6949                .debug_struct("Submenu")
6950                .field("label", label)
6951                .field("items", items)
6952                .finish(),
6953            Self::Separator => write!(f, "Separator"),
6954        }
6955    }
6956}
6957
6958/// A top-level menu bar containing [`MenuItem`]s.
6959///
6960/// The menu bar is a data model only; rendering it into an OS-native menu is
6961/// handled by the platform renderer (`cvkg-render-native`).
6962pub struct MenuBar {
6963    /// Ordered list of top-level menu items.
6964    pub items: Vec<MenuItem>,
6965}
6966
6967impl MenuBar {
6968    /// Create an empty menu bar.
6969    pub fn new() -> Self {
6970        Self { items: Vec::new() }
6971    }
6972
6973    /// Append a menu item to the bar.
6974    pub fn add_item(&mut self, item: MenuItem) {
6975        self.items.push(item);
6976    }
6977
6978    /// Build the standard CVKG menu structure with all conventional shortcuts.
6979    ///
6980    /// The `cmd` modifier maps to ⌘ on macOS and Ctrl on Windows/Linux — this
6981    /// translation is enforced by the renderer, not here.
6982    ///
6983    /// Menus included:
6984    /// - **File**: New, Open, Save, Close
6985    /// - **Edit**: Undo, Redo, Cut, Copy, Paste, Select All, Find
6986    /// - **View**: Zoom In, Zoom Out, Fullscreen
6987    /// - **Window**: Minimize, Zoom, Bring All to Front
6988    /// - **Help**: Search Help
6989    #[allow(clippy::too_many_arguments)]
6990    pub fn standard(
6991        new_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6992        open_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6993        save_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6994        close_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6995        quit_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6996        undo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6997        redo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6998        cut_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6999        copy_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7000        paste_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7001        select_all_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7002        find_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7003    ) -> Self {
7004        let mut bar = Self::new();
7005
7006        // ── File ──────────────────────────────────────────────────────────────
7007        bar.add_item(MenuItem::Submenu {
7008            label: "File".to_string(),
7009            items: vec![
7010                MenuItem::Action {
7011                    label: "New".to_string(),
7012                    shortcut: Some(KeyboardShortcut::cmd("n")),
7013                    action: new_fn,
7014                    enabled: true,
7015                },
7016                MenuItem::Action {
7017                    label: "Open…".to_string(),
7018                    shortcut: Some(KeyboardShortcut::cmd("o")),
7019                    action: open_fn,
7020                    enabled: true,
7021                },
7022                MenuItem::Separator,
7023                MenuItem::Action {
7024                    label: "Save".to_string(),
7025                    shortcut: Some(KeyboardShortcut::cmd("s")),
7026                    action: save_fn,
7027                    enabled: true,
7028                },
7029                MenuItem::Separator,
7030                MenuItem::Action {
7031                    label: "Close".to_string(),
7032                    shortcut: Some(KeyboardShortcut::cmd("w")),
7033                    action: close_fn,
7034                    enabled: true,
7035                },
7036                MenuItem::Separator,
7037                MenuItem::Action {
7038                    label: "Quit".to_string(),
7039                    shortcut: Some(KeyboardShortcut::cmd("q")),
7040                    action: quit_fn,
7041                    enabled: true,
7042                },
7043            ],
7044        });
7045
7046        // ── Edit ──────────────────────────────────────────────────────────────
7047        bar.add_item(MenuItem::Submenu {
7048            label: "Edit".to_string(),
7049            items: vec![
7050                MenuItem::Action {
7051                    label: "Undo".to_string(),
7052                    shortcut: Some(KeyboardShortcut::cmd("z")),
7053                    action: undo_fn,
7054                    enabled: true,
7055                },
7056                MenuItem::Action {
7057                    label: "Redo".to_string(),
7058                    shortcut: Some(KeyboardShortcut::cmd_shift("z")),
7059                    action: redo_fn,
7060                    enabled: true,
7061                },
7062                MenuItem::Separator,
7063                MenuItem::Action {
7064                    label: "Cut".to_string(),
7065                    shortcut: Some(KeyboardShortcut::cmd("x")),
7066                    action: cut_fn,
7067                    enabled: true,
7068                },
7069                MenuItem::Action {
7070                    label: "Copy".to_string(),
7071                    shortcut: Some(KeyboardShortcut::cmd("c")),
7072                    action: copy_fn,
7073                    enabled: true,
7074                },
7075                MenuItem::Action {
7076                    label: "Paste".to_string(),
7077                    shortcut: Some(KeyboardShortcut::cmd("v")),
7078                    action: paste_fn,
7079                    enabled: true,
7080                },
7081                MenuItem::Separator,
7082                MenuItem::Action {
7083                    label: "Select All".to_string(),
7084                    shortcut: Some(KeyboardShortcut::cmd("a")),
7085                    action: select_all_fn,
7086                    enabled: true,
7087                },
7088                MenuItem::Separator,
7089                MenuItem::Action {
7090                    label: "Find…".to_string(),
7091                    shortcut: Some(KeyboardShortcut::cmd("f")),
7092                    action: find_fn,
7093                    enabled: true,
7094                },
7095            ],
7096        });
7097
7098        // ── View ──────────────────────────────────────────────────────────────
7099        // View items carry no application-level callbacks at the model layer;
7100        // zoom and fullscreen are handled by the renderer directly.
7101        let noop: std::sync::Arc<dyn Fn() + Send + Sync> = std::sync::Arc::new(|| {});
7102        bar.add_item(MenuItem::Submenu {
7103            label: "View".to_string(),
7104            items: vec![
7105                MenuItem::Action {
7106                    label: "Zoom In".to_string(),
7107                    shortcut: Some(KeyboardShortcut::cmd("=")),
7108                    action: noop.clone(),
7109                    enabled: true,
7110                },
7111                MenuItem::Action {
7112                    label: "Zoom Out".to_string(),
7113                    shortcut: Some(KeyboardShortcut::cmd("-")),
7114                    action: noop.clone(),
7115                    enabled: true,
7116                },
7117                MenuItem::Separator,
7118                MenuItem::Action {
7119                    label: "Toggle Fullscreen".to_string(),
7120                    shortcut: Some(KeyboardShortcut {
7121                        key: "f".to_string(),
7122                        modifiers: Modifiers {
7123                            ctrl: true,
7124                            ..Default::default()
7125                        },
7126                    }),
7127                    action: noop.clone(),
7128                    enabled: true,
7129                },
7130            ],
7131        });
7132
7133        // ── Window ────────────────────────────────────────────────────────────
7134        bar.add_item(MenuItem::Submenu {
7135            label: "Window".to_string(),
7136            items: vec![
7137                MenuItem::Action {
7138                    label: "Minimize".to_string(),
7139                    shortcut: Some(KeyboardShortcut::cmd("m")),
7140                    action: noop.clone(),
7141                    enabled: true,
7142                },
7143                MenuItem::Action {
7144                    label: "Zoom".to_string(),
7145                    shortcut: None,
7146                    action: noop.clone(),
7147                    enabled: true,
7148                },
7149                MenuItem::Separator,
7150                MenuItem::Action {
7151                    label: "Bring All to Front".to_string(),
7152                    shortcut: None,
7153                    action: noop.clone(),
7154                    enabled: true,
7155                },
7156            ],
7157        });
7158
7159        // ── Help ──────────────────────────────────────────────────────────────
7160        bar.add_item(MenuItem::Submenu {
7161            label: "Help".to_string(),
7162            items: vec![MenuItem::Action {
7163                label: "Search Help".to_string(),
7164                shortcut: None,
7165                action: noop,
7166                enabled: true,
7167            }],
7168        });
7169
7170        bar
7171    }
7172}
7173
7174impl Default for MenuBar {
7175    fn default() -> Self {
7176        Self::new()
7177    }
7178}
7179
7180// =============================================================================
7181// LOCALIZATION — Item 12: Localization / Internationalization
7182// =============================================================================
7183// OS-agnostic: works on all platforms. No platform-specific string loading.
7184
7185use std::sync::RwLock;
7186
7187/// Layout direction for UI elements (LTR or RTL).
7188#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7189pub enum Direction {
7190    #[default]
7191    LTR,
7192    RTL,
7193    Auto,
7194}
7195
7196impl Direction {
7197    pub fn is_rtl(self) -> bool {
7198        matches!(self, Direction::RTL)
7199    }
7200}
7201#[derive(Clone, Debug)]
7202pub struct L10nBundle {
7203    pub locale: String,
7204    pub strings: HashMap<String, String>,
7205    pub is_rtl: bool,
7206}
7207
7208impl L10nBundle {
7209    pub fn new(locale: impl Into<String>) -> Self {
7210        Self {
7211            locale: locale.into(),
7212            strings: HashMap::new(),
7213            is_rtl: false,
7214        }
7215    }
7216
7217    pub fn add(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
7218        self.strings.insert(key.into(), value.into());
7219        self
7220    }
7221
7222    pub fn from_strings_format(locale: impl Into<String>, input: &str) -> Self {
7223        let mut bundle = Self::new(locale);
7224        for line in input.lines() {
7225            let line = line.trim();
7226            if line.is_empty() || line.starts_with("//") {
7227                continue;
7228            }
7229            if let Some(eq_pos) = line.find(" = ") {
7230                let key = line[..eq_pos].trim_matches('"').to_string();
7231                let val = line[eq_pos + 3..]
7232                    .trim_end_matches(';')
7233                    .trim_matches('"')
7234                    .to_string();
7235                bundle.strings.insert(key, val);
7236            }
7237        }
7238        bundle
7239    }
7240    /// Get a translated string by key. Returns the key itself if not found.
7241    pub fn t(&self, key: &str) -> String {
7242        self.strings
7243            .get(key)
7244            .map(|s| s.to_string())
7245            .unwrap_or_else(|| key.to_string())
7246    }
7247
7248    /// Translate with interpolation. Replaces {0}, {1}, etc. with args.
7249    pub fn tf(&self, key: &str, args: &[&str]) -> String {
7250        let mut result = self.t(key);
7251        for (i, arg) in args.iter().enumerate() {
7252            result = result.replace(&format!("{{{}}}", i), arg);
7253        }
7254        result
7255    }
7256}
7257
7258/// Global localization manager.
7259pub struct L10n {
7260    bundles: HashMap<String, L10nBundle>,
7261    current: String,
7262}
7263
7264impl L10n {
7265    pub fn new(default_locale: &str) -> Self {
7266        Self {
7267            bundles: HashMap::new(),
7268            current: default_locale.to_string(),
7269        }
7270    }
7271
7272    pub fn add_bundle(&mut self, bundle: L10nBundle) {
7273        self.bundles.insert(bundle.locale.clone(), bundle);
7274    }
7275
7276    pub fn set_locale(&mut self, locale: &str) {
7277        self.current = locale.to_string();
7278    }
7279    pub fn current_locale(&self) -> &str {
7280        &self.current
7281    }
7282
7283    pub fn is_rtl(&self) -> bool {
7284        self.bundles
7285            .get(self.current.as_str())
7286            .map(|b| b.is_rtl)
7287            .unwrap_or(false)
7288    }
7289
7290    pub fn t(&self, key: &str) -> String {
7291        self.bundles
7292            .get(self.current.as_str())
7293            .map(|b| b.t(key))
7294            .unwrap_or_else(|| key.to_string())
7295    }
7296
7297    pub fn tf(&self, key: &str, args: &[&str]) -> String {
7298        let mut result = self.t(key);
7299        for (i, arg) in args.iter().enumerate() {
7300            result = result.replace(&format!("{{{}}}", i), arg);
7301        }
7302        result
7303    }
7304
7305    pub fn direction(&self) -> Direction {
7306        if self.is_rtl() {
7307            Direction::RTL
7308        } else {
7309            Direction::LTR
7310        }
7311    }
7312}
7313
7314static L10N: once_cell::sync::Lazy<Arc<RwLock<L10n>>> =
7315    once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(L10n::new("en"))));
7316
7317pub fn init_l10n(l10n: L10n) {
7318    if let Ok(mut guard) = L10N.write() {
7319        *guard = l10n;
7320    }
7321}
7322
7323pub fn l10n() -> Arc<RwLock<L10n>> {
7324    L10N.clone()
7325}
7326
7327pub fn t(key: &str) -> String {
7328    L10N.read()
7329        .map(|g| g.t(key).to_string())
7330        .unwrap_or_else(|_| key.to_string())
7331}
7332
7333pub fn tf(key: &str, args: &[&str]) -> String {
7334    L10N.read()
7335        .map(|g| g.tf(key, args))
7336        .unwrap_or_else(|_| key.to_string())
7337}
7338
7339pub fn set_locale(locale: &str) {
7340    if let Ok(mut guard) = L10N.write() {
7341        guard.set_locale(locale);
7342    }
7343}
7344
7345pub fn current_locale() -> String {
7346    L10N.read()
7347        .map(|g| g.current_locale().to_string())
7348        .unwrap_or_else(|_| "en".to_string())
7349}
7350
7351pub fn is_rtl() -> bool {
7352    L10N.read().map(|g| g.is_rtl()).unwrap_or(false)
7353}
7354
7355// =============================================================================
7356// SYSTEM THEME DETECTION — Dark/Light mode detection
7357// =============================================================================
7358//
7359// OS-agnostic theme detection. Checks the CVKG_THEME environment variable first,
7360// then falls back to dark mode (safe default).
7361//
7362// Platform backends may override this with native OS queries (e.g.,
7363// dark-light crate on desktop, prefers-color-scheme on web).
7364
7365/// The detected system theme.
7366#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7367pub enum SystemTheme {
7368    /// Dark mode (default).
7369    #[default]
7370    Dark,
7371    /// Light mode.
7372    Light,
7373}
7374
7375/// Detect the current system theme.
7376///
7377/// Checks `CVKG_THEME` environment variable first:
7378/// - `"dark"` → `SystemTheme::Dark`
7379/// - `"light"` → `SystemTheme::Light`
7380/// - unset or any other value → `SystemTheme::Dark` (default)
7381///
7382/// Platform backends can call this and override with native detection
7383/// (e.g., `dark-light` crate on desktop, `prefers-color-scheme` on web).
7384pub fn detect_system_theme() -> SystemTheme {
7385    std::env::var("CVKG_THEME")
7386        .ok()
7387        .and_then(|v| match v.as_str() {
7388            "light" => Some(SystemTheme::Light),
7389            "dark" => Some(SystemTheme::Dark),
7390            _ => None,
7391        })
7392        .unwrap_or(SystemTheme::Dark)
7393}
7394
7395// =============================================================================
7396// AUDIO / HAPTIC — Item 14: Spatial Audio / Haptic Feedback
7397// =============================================================================
7398// OS-agnostic: pure trait abstractions. Platform backends via cfg in renderer.
7399
7400pub mod audio_haptic;
7401pub use audio_haptic::{
7402    AudioEngine, HapticEngine, HapticIntensity, NullAudioEngine, NullHapticEngine, haptic_error,
7403    haptic_impact, haptic_selection, haptic_success, play_sound, set_audio_engine,
7404    set_haptic_engine, sounds,
7405};