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