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    // ── Advanced Visual Effects ──────────────────────────────────────────
2778    /// Draw a linear gradient between two colors at the specified angle.
2779    fn draw_linear_gradient(
2780        &mut self,
2781        _rect: Rect,
2782        _start_color: [f32; 4],
2783        _end_color: [f32; 4],
2784        _angle: f32,
2785    ) {
2786    }
2787    /// Draw a radial gradient between two colors.
2788    fn draw_radial_gradient(
2789        &mut self,
2790        _rect: Rect,
2791        _inner_color: [f32; 4],
2792        _outer_color: [f32; 4],
2793    ) {
2794    }
2795    /// Draw a high-fidelity drop shadow for a rounded rectangle.
2796    fn draw_drop_shadow(
2797        &mut self,
2798        _rect: Rect,
2799        _radius: f32,
2800        _color: [f32; 4],
2801        _blur: f32,
2802        _spread: f32,
2803    ) {
2804    }
2805    /// Draw a dashed border for a rounded rectangle.
2806    fn stroke_dashed_rounded_rect(
2807        &mut self,
2808        _rect: Rect,
2809        _radius: f32,
2810        _color: [f32; 4],
2811        _width: f32,
2812        _dash: f32,
2813        _gap: f32,
2814    ) {
2815    }
2816    /// Draw a 9-slice / patch scaled image.
2817    fn draw_9slice(
2818        &mut self,
2819        _image_name: &str,
2820        _rect: Rect,
2821        _left: f32,
2822        _top: f32,
2823        _right: f32,
2824        _bottom: f32,
2825    ) {
2826    }
2827
2828    // ── Clipping ─────────────────────────────────────────────────────────
2829    /// Push a clip rectangle.  All subsequent drawing is clipped to `rect`.
2830    /// Implementations that do not support clipping may ignore this call.
2831    fn push_clip_rect(&mut self, _rect: Rect) {}
2832    /// Pop the most recently pushed clip rectangle.
2833    fn pop_clip_rect(&mut self) {}
2834    /// Get the current clip rectangle in screen coordinates.
2835    /// Returns a rect covering the entire screen if no clip is active.
2836    fn current_clip_rect(&self) -> Rect {
2837        Rect::new(-10000.0, -10000.0, 20000.0, 20000.0)
2838    }
2839
2840    // ── Global opacity ───────────────────────────────────────────────────
2841    /// Set a global opacity multiplier applied to all subsequent draw calls
2842    /// until `pop_opacity` is called.  `opacity` is in [0.0, 1.0].
2843    fn push_opacity(&mut self, _opacity: f32) {}
2844    /// Restore the previous opacity level.
2845    fn pop_opacity(&mut self) {}
2846
2847    // ── Berserker Pipeline State ─────────────────────────────────────────
2848    fn set_theme(&mut self, _theme: ColorTheme) {}
2849    fn set_rage(&mut self, _rage: f32) {}
2850    fn set_berserker_mode(&mut self, _state: BerserkerMode) {}
2851    fn trigger_shatter_event(&mut self, _origin: [f32; 2], _force: f32) {}
2852    /// Set the desktop scene preset (Aurora, Void, Nebula, Glitch, Yggdrasil).
2853    fn set_scene(&mut self, _scene: &str) {}
2854
2855    // ── Export & Print ───────────────────────────────────────────────────
2856    /// Capture the current frame as a PNG byte buffer.
2857    fn capture_png(&mut self) -> Vec<u8> {
2858        Vec::new()
2859    }
2860    /// Trigger a native print dialog or spooling operation.
2861    fn print(&mut self) {}
2862
2863    fn set_scene_preset(&mut self, _preset: u32) {}
2864
2865    // ── Cyberpunk Effects ────────────────────────────────────────────────
2866    /// Apply a Bifrost (Frosted Glass) effect to the specified rect.
2867    fn bifrost(&mut self, _rect: Rect, _blur: f32, _saturation: f32, _opacity: f32) {}
2868    /// Apply a Gungnir (Neon Glow) effect to the specified rect.
2869    fn gungnir(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32, _intensity: f32) {}
2870    /// Apply a ManiGlow (Lunar Illuminator) effect.
2871    fn mani_glow(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32) {}
2872    /// Push a Mjolnir Slice (geometric clipping).
2873    fn push_mjolnir_slice(&mut self, _angle: f32, _offset: f32) {}
2874    fn pop_mjolnir_slice(&mut self) {}
2875    /// Execute a render function with memoization.
2876    /// If the renderer supports caching and the `id` + `data_hash` match a previous run,
2877    /// it may replay cached commands instead of executing the function.
2878    fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer));
2879    /// Apply a Mjolnir Shatter effect (fragmentation) to the specified rect.
2880    fn mjolnir_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2881    fn mjolnir_fluid_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2882    /// Draw a Mjolnir Bolt (lightning strike) between two points.
2883    fn draw_mjolnir_bolt(&mut self, _from: [f32; 2], _to: [f32; 2], _color: [f32; 4]) {}
2884
2885    // ── Accessibility (ShieldWall) ───────────────────────────────────────
2886    fn set_aria_role(&mut self, _role: &str) {}
2887    fn set_aria_label(&mut self, _label: &str) {}
2888
2889    /// Register a shared element for Bifrost Bridge transitions.
2890    fn register_shared_element(&mut self, _id: &str, _rect: Rect) {}
2891
2892    /// Set a unique key for the current VDOM node to ensure stable identity during diffing.
2893    fn set_key(&mut self, _key: &str) {}
2894
2895    // ── Telemetry ────────────────────────────────────────────────────────
2896    /// Get real-time performance telemetry.
2897    fn get_telemetry(&self) -> TelemetryData {
2898        TelemetryData::default()
2899    }
2900
2901    // ── GPU State Management ─────────────────────────────────────────────
2902    /// Push a shadow state to the stack. All following draw calls will have this shadow.
2903    fn push_shadow(&mut self, _radius: f32, _color: [f32; 4], _offset: [f32; 2]) {}
2904    /// Pop the last shadow state from the stack.
2905    fn pop_shadow(&mut self) {}
2906
2907    // ── VDOM & Scene Graph ───────────────────────────────────────────────
2908    /// Push a Virtual DOM node onto the stack for hierarchy tracking.
2909    fn push_vnode(&mut self, _rect: Rect, _name: &'static str) {}
2910    /// Pop the current Virtual DOM node from the stack.
2911    fn pop_vnode(&mut self) {}
2912    /// Register an event handler for the current VDOM node.
2913    fn register_handler(
2914        &mut self,
2915        _event_type: &str,
2916        _handler: std::sync::Arc<dyn Fn(Event) + Send + Sync>,
2917    ) {
2918    }
2919
2920    // ── Z-Index & Depth ──────────────────────────────────────────────────
2921    /// Set the current Z-index for depth sorting.
2922    /// Higher values appear closer to the viewer.
2923    fn set_z_index(&mut self, _z: f32) {}
2924    /// Get the current Z-index.
2925    fn get_z_index(&self) -> f32 {
2926        0.0
2927    }
2928
2929    // ── Vector Graphics ──────────────────────────────────────────────────
2930    /// Load an SVG model from raw bytes.
2931    fn load_svg(&mut self, _name: &str, _svg_data: &[u8]) {}
2932    /// Draw a pre-loaded SVG model.
2933    fn draw_svg(&mut self, _name: &str, _rect: Rect) {}
2934    /// Serialize a pre-loaded SVG model back to SVG XML markup.
2935    /// Returns the serialized SVG string, or an error if the model is not loaded
2936    /// or serialization is not supported by this renderer.
2937    fn serialize_svg(&mut self, _name: &str) -> Result<String, String> {
2938        Err("SVG serialization not supported by this renderer".into())
2939    }
2940    /// Apply an SVG filter to a pre-loaded SVG model by filter element ID.
2941    /// The filter is evaluated and the result composited back into the SVG.
2942    /// Returns the filtered SVG as XML, or an error if not supported.
2943    fn apply_svg_filter(
2944        &mut self,
2945        _name: &str,
2946        _filter_id: &str,
2947        _region: Rect,
2948    ) -> Result<String, String> {
2949        Err("SVG filter not supported by this renderer".into())
2950    }
2951
2952    // ── GPU Transformations ──────────────────────────────────────────────
2953    /// Push a 2D transform (translation, scale, rotation) onto the stack.
2954    /// This transform should be applied to all subsequent draw calls until popped.
2955    /// Transform-only animations use this to avoid re-triggering the layout engine.
2956    fn push_transform(&mut self, _translation: [f32; 2], _scale: [f32; 2], _rotation: f32) {}
2957    /// Push a raw 2D affine transform matrix [a, b, c, d, e, f] corresponding to
2958    /// [m11, m12, m21, m22, tx, ty].
2959    fn push_affine(&mut self, _transform: [f32; 6]) {}
2960    /// Pop the last 2D transform from the stack.
2961    fn pop_transform(&mut self) {}
2962    /// Return the resolved layout bounds for a specific node ID if it exists.
2963    fn query_layout(&self, _node_id: scene_graph::NodeId) -> Option<Rect> {
2964        None
2965    }
2966    /// Enable or disable the layout debug overlay (bounds, padding, margin).
2967    fn set_debug_layout(&mut self, _enabled: bool) {}
2968    /// Check if the layout debug overlay is currently enabled.
2969    fn get_debug_layout(&self) -> bool {
2970        false
2971    }
2972
2973    // ── Material Routing ─────────────────────────────────────────────────
2974    /// Set the active material for subsequent draw calls.
2975    /// Controls which pass a draw call is routed to in the multi-pass pipeline.
2976    fn set_material(&mut self, _material: crate::material::DrawMaterial) {}
2977    /// Return the currently active material (defaults to Opaque).
2978    fn current_material(&self) -> crate::material::DrawMaterial {
2979        crate::material::DrawMaterial::Opaque
2980    }
2981
2982    // ── Vili Interaction Paradigm ──────────────────────────────────────────
2983    /// Compute the user's velocity/intent vector.
2984    fn mimir_intent(&self) -> [f32; 2] {
2985        [0.0, 0.0]
2986    }
2987    /// Calculate magnetic coordinate warp towards an anchor.
2988    fn magnetic_warp(&self, pointer: [f32; 2], anchor_rect: Rect, strength: f32) -> [f32; 2] {
2989        if strength <= 0.0 {
2990            return pointer;
2991        }
2992        let cx = anchor_rect.x + anchor_rect.width / 2.0;
2993        let cy = anchor_rect.y + anchor_rect.height / 2.0;
2994        let dx = pointer[0] - cx;
2995        let dy = pointer[1] - cy;
2996        let dist = (dx * dx + dy * dy).sqrt();
2997        let radius = 120.0;
2998        if dist < radius && dist > 0.0 {
2999            let force = (1.0 - dist / radius) * strength;
3000            [pointer[0] - dx * force, pointer[1] - dy * force]
3001        } else {
3002            pointer
3003        }
3004    }
3005    /// Calculate kinematic glow intensity based on proximity.
3006    fn mani_glow_intensity(&self, pointer: [f32; 2], bounds: Rect, radius: f32) -> f32 {
3007        let cx = bounds.x + bounds.width / 2.0;
3008        let cy = bounds.y + bounds.height / 2.0;
3009        let dist = ((pointer[0] - cx).powi(2) + (pointer[1] - cy).powi(2)).sqrt();
3010        if dist < radius {
3011            (1.0 - dist / radius).clamp(0.0, 1.0)
3012        } else {
3013            0.0
3014        }
3015    }
3016    /// Calculate dynamic element attention (scaling/morphing) statelessly per frame.
3017    fn fafnir_evolve(&self, pointer: [f32; 2], bounds: Rect, max_scale: f32) -> f32 {
3018        let prox = self.mani_glow_intensity(pointer, bounds, 120.0);
3019        1.0 + (max_scale - 1.0) * prox
3020    }
3021    /// Sets the precise Vili SDF Shape boundary for hit-testing.
3022    fn set_sdf_shape(&mut self, _shape: crate::layout::SdfShape) {}
3023}
3024
3025/// Utility for accessibility compliance (WCAG 2.1).
3026pub mod accessibility {
3027    /// Calculate the relative luminance of an sRGB color.
3028    pub fn relative_luminance(color: [f32; 4]) -> f32 {
3029        let f = |c: f32| {
3030            if c <= 0.03928 {
3031                c / 12.92
3032            } else {
3033                ((c + 0.055) / 1.055).powf(2.4)
3034            }
3035        };
3036        0.2126 * f(color[0]) + 0.7152 * f(color[1]) + 0.0722 * f(color[2])
3037    }
3038
3039    /// Calculate the contrast ratio between two colors.
3040    pub fn contrast_ratio(c1: [f32; 4], c2: [f32; 4]) -> f32 {
3041        let l1 = relative_luminance(c1);
3042        let l2 = relative_luminance(c2);
3043        let (light, dark) = if l1 > l2 { (l1, l2) } else { (l2, l1) };
3044        (light + 0.05) / (dark + 0.05)
3045    }
3046}
3047/// Defines the hardware acceleration tier and feature set available to the renderer.
3048#[derive(
3049    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
3050)]
3051pub enum RenderTier {
3052    /// High-performance GPU path (WebGPU / Vulkan / Metal / DX12) with full shader support.
3053    Tier1GPU = 0,
3054    /// Mid-tier GPU path (WebGL2 / OpenGL 3.3) with standard shader support.
3055    Tier2GPU = 1,
3056    /// Fallback software or basic hardware path (Canvas 2D / GDI+) with limited effects.
3057    Tier3Fallback = 2,
3058}
3059// =============================================================================
3060// BERSERKER UNIFORMS
3061// =============================================================================
3062use bytemuck::{Pod, Zeroable};
3063/// Fully themeable color palette for the Berserker pipeline.
3064#[repr(C)]
3065#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3066pub struct ColorTheme {
3067    pub primary_neon: [f32; 4], // (R, G, B, intensity)
3068    pub shatter_neon: [f32; 4],
3069    pub glass_base: [f32; 4],
3070    pub glass_edge: [f32; 4],
3071    pub rune_glow: [f32; 4],
3072    pub ember_core: [f32; 4],
3073    pub background_deep: [f32; 4],
3074    pub mani_glow: [f32; 4], // (R, G, B, radius)
3075    pub glass_blur_strength: f32,
3076    pub shatter_edge_width: f32,
3077    pub neon_bloom_radius: f32,
3078    pub rune_opacity: f32,
3079}
3080impl ColorTheme {
3081    /// Asgard Mode: The high-fidelity "Cyberpunk Viking" aesthetic.
3082    pub fn asgard() -> Self {
3083        Self {
3084            primary_neon: [0.0, 1.0, 0.95, 1.2],
3085            shatter_neon: [1.0, 0.0, 0.75, 1.5],
3086            glass_base: [0.04, 0.04, 0.06, 0.82],
3087            glass_edge: [0.0, 0.45, 0.55, 0.6],
3088            rune_glow: [0.75, 0.98, 1.0, 0.9],
3089            ember_core: [0.95, 0.12, 0.12, 1.0],
3090            background_deep: [0.01, 0.01, 0.03, 1.0],
3091            mani_glow: [0.7, 0.9, 1.0, 0.05],
3092            glass_blur_strength: 0.6,
3093            shatter_edge_width: 1.8,
3094            neon_bloom_radius: 0.022,
3095            rune_opacity: 0.55,
3096        }
3097    }
3098
3099    /// Midgard Mode: A clean, functional tactical HUD for standard operations.
3100    pub fn midgard() -> Self {
3101        Self {
3102            primary_neon: [0.2, 0.4, 0.6, 1.0], // Muted blue
3103            shatter_neon: [0.5, 0.5, 0.5, 1.0], // Neutral gray
3104            glass_base: [0.1, 0.12, 0.15, 1.0], // Solid slate
3105            glass_edge: [0.3, 0.35, 0.4, 1.0],  // Subtle border
3106            rune_glow: [0.8, 0.8, 0.8, 0.0],    // Runes disabled
3107            ember_core: [0.5, 0.5, 0.5, 1.0],
3108            background_deep: [0.05, 0.05, 0.07, 1.0],
3109            mani_glow: [0.0, 0.0, 0.0, 0.0], // No cursor glow
3110            glass_blur_strength: 0.0,        // No blur
3111            shatter_edge_width: 1.0,
3112            neon_bloom_radius: 0.0,
3113            rune_opacity: 0.0,
3114        }
3115    }
3116
3117    pub fn cyberpunk_viking() -> Self {
3118        Self::asgard()
3119    }
3120    pub fn vibrant_glass() -> Self {
3121        Self {
3122            primary_neon: [0.0, 1.0, 0.95, 1.2],
3123            shatter_neon: [1.0, 0.0, 0.75, 1.5],
3124            glass_base: [0.55, 0.6, 0.7, 0.08], // Luminous cool tint
3125            glass_edge: [0.7, 0.85, 1.0, 0.45], // Subtle blue-white rim
3126            rune_glow: [0.75, 0.98, 1.0, 0.9],
3127            ember_core: [1.0, 0.4, 0.1, 1.0],
3128            background_deep: [0.05, 0.05, 0.1, 1.0],
3129            mani_glow: [0.7, 0.9, 1.0, 0.05],
3130            glass_blur_strength: 0.9,
3131            shatter_edge_width: 1.8,
3132            neon_bloom_radius: 0.022,
3133            rune_opacity: 0.55,
3134        }
3135    }
3136}
3137impl Default for ColorTheme {
3138    fn default() -> Self {
3139        Self::vibrant_glass()
3140    }
3141}
3142/// Per-frame scene state for the Berserker pipeline.
3143#[repr(C)]
3144#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3145pub struct SceneUniforms {
3146    pub view: glam::Mat4,
3147    pub proj: glam::Mat4,
3148    pub time: f32,
3149    pub delta_time: f32,
3150    pub resolution: [f32; 2],
3151    pub mouse: [f32; 2],
3152    pub mouse_velocity: [f32; 2],
3153    pub shatter_origin: [f32; 2],
3154    pub shatter_time: f32,
3155    pub shatter_force: f32,
3156    pub berzerker_rage: f32,
3157    pub berzerker_mode: u32,
3158    pub scroll_offset: f32,
3159    pub scale_factor: f32,
3160    pub scene_type: u32,
3161    pub _pad: [f32; 3], // Align to 16 bytes if needed, but current struct is 4x16 + 4x16 + 4x16 + ...
3162}
3163
3164pub const SCENE_AURORA: u32 = 0;
3165pub const SCENE_VOID: u32 = 1;
3166pub const SCENE_NEBULA: u32 = 2;
3167pub const SCENE_GLITCH: u32 = 3;
3168pub const SCENE_YGGDRASIL: u32 = 4;
3169
3170impl SceneUniforms {
3171    pub fn new(width: f32, height: f32) -> Self {
3172        Self {
3173            view: glam::Mat4::IDENTITY,
3174            proj: glam::Mat4::orthographic_lh(0.0, width, height, 0.0, -100.0, 100.0),
3175            time: 0.0,
3176            delta_time: 0.016,
3177            resolution: [width, height],
3178            mouse: [0.5, 0.5],
3179            mouse_velocity: [0.0, 0.0],
3180            shatter_origin: [0.5, 0.5],
3181            shatter_time: -100.0,
3182            shatter_force: 0.0,
3183            berzerker_rage: 0.0,
3184            berzerker_mode: 0,
3185            scroll_offset: 0.0,
3186            scale_factor: 1.0,
3187            scene_type: SCENE_AURORA,
3188            _pad: [0.0; 3],
3189        }
3190    }
3191}
3192/// A 3D mesh containing vertex and index data.
3193#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
3194pub struct Mesh {
3195    pub vertices: Vec<[f32; 3]>,
3196    pub normals: Vec<[f32; 3]>,
3197    pub indices: Vec<u32>,
3198}
3199impl Mesh {
3200    pub fn from_obj(data: &[u8]) -> anyhow::Result<Vec<Self>> {
3201        let mut cursor = std::io::Cursor::new(data);
3202        let (models, _) = tobj::load_obj_buf(&mut cursor, &tobj::LoadOptions::default(), |_| {
3203            Ok((Vec::new(), Default::default()))
3204        })?;
3205        let mut meshes = Vec::new();
3206        for m in models {
3207            let mesh = m.mesh;
3208            let vertices: Vec<[f32; 3]> = mesh
3209                .positions
3210                .chunks(3)
3211                .map(|c| [c[0], c[1], c[2]])
3212                .collect();
3213            let normals = if mesh.normals.is_empty() {
3214                vec![[0.0, 0.0, 1.0]; vertices.len()]
3215            } else {
3216                mesh.normals.chunks(3).map(|c| [c[0], c[1], c[2]]).collect()
3217            };
3218            meshes.push(Mesh {
3219                vertices,
3220                normals,
3221                indices: mesh.indices,
3222            });
3223        }
3224        Ok(meshes)
3225    }
3226    pub fn from_stl(data: &[u8]) -> anyhow::Result<Self> {
3227        let mut cursor = std::io::Cursor::new(data);
3228        let stl = stl_io::read_stl(&mut cursor)?;
3229        let vertices: Vec<[f32; 3]> = stl.vertices.iter().map(|v| [v[0], v[1], v[2]]).collect();
3230        let mut indices = Vec::new();
3231        for face in stl.faces {
3232            indices.push(face.vertices[0] as u32);
3233            indices.push(face.vertices[1] as u32);
3234            indices.push(face.vertices[2] as u32);
3235        }
3236        let normals = vec![[0.0, 0.0, 1.0]; vertices.len()];
3237        Ok(Mesh {
3238            vertices,
3239            normals,
3240            indices,
3241        })
3242    }
3243}
3244/// FrameRenderer extends Renderer with frame lifecycle management.
3245/// It is typically implemented by the host windowing/rendering environment.
3246pub trait FrameRenderer<E = ()>: Renderer {
3247    fn begin_frame(&mut self) -> E;
3248    fn render_frame(&mut self) {
3249        // Default implementation does nothing - override for custom frame rendering
3250    }
3251    fn end_frame(&mut self, encoder: E);
3252}
3253use std::sync::Arc;
3254type SubscriberList<T> = Arc<std::sync::Mutex<Vec<Box<dyn Fn(&T) + Send + Sync>>>>;
3255/// State wrapper that owns a value and notifies subscribers when changed
3256#[derive(Clone)]
3257pub struct State<T: Clone + Send + Sync + 'static> {
3258    swap: Arc<arc_swap::ArcSwap<T>>,
3259    metadata_swap: Arc<arc_swap::ArcSwap<Option<agents::MutationMetadata>>>,
3260    #[cfg(not(target_arch = "wasm32"))]
3261    tvar: Arc<stm::TVar<T>>,
3262    #[cfg(not(target_arch = "wasm32"))]
3263    metadata_tvar: Arc<stm::TVar<Option<agents::MutationMetadata>>>,
3264    subscribers: SubscriberList<T>,
3265    version: Arc<std::sync::atomic::AtomicU64>,
3266    resolution: agents::ConflictResolution,
3267}
3268impl<T: Clone + Send + Sync + 'static> State<T> {
3269    /// Create a new State with initial value
3270    pub fn new(value: T) -> Self {
3271        #[cfg(not(target_arch = "wasm32"))]
3272        let tvar = Arc::new(stm::TVar::new(value.clone()));
3273        #[cfg(not(target_arch = "wasm32"))]
3274        let metadata_tvar = Arc::new(stm::TVar::new(None));
3275        Self {
3276            swap: Arc::new(arc_swap::ArcSwap::from_pointee(value)),
3277            metadata_swap: Arc::new(arc_swap::ArcSwap::new(Arc::new(None))),
3278            #[cfg(not(target_arch = "wasm32"))]
3279            tvar,
3280            #[cfg(not(target_arch = "wasm32"))]
3281            metadata_tvar,
3282            subscribers: Arc::new(std::sync::Mutex::new(Vec::new())),
3283            version: Arc::new(std::sync::atomic::AtomicU64::new(0)),
3284            resolution: agents::ConflictResolution::default(),
3285        }
3286    }
3287    /// Set the conflict resolution strategy for this state.
3288    pub fn with_resolution(mut self, resolution: agents::ConflictResolution) -> Self {
3289        self.resolution = resolution;
3290        self
3291    }
3292    /// Get the current value
3293    pub fn get(&self) -> T {
3294        (**self.swap.load()).clone()
3295    }
3296    /// Set a new value, notifying all subscribers. Applies conflict resolution if agents are present.
3297    pub fn set(&self, value: T) {
3298        #[cfg(not(target_arch = "wasm32"))]
3299        let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3300            let new_meta = agents::get_current_mutation_metadata();
3301            let existing_meta = self.metadata_tvar.read(tx)?;
3302            let mut skip = false;
3303            if self.resolution == agents::ConflictResolution::PriorityWins
3304                && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3305                && new_m.priority < old_m.priority
3306            {
3307                skip = true;
3308            }
3309            if !skip {
3310                self.tvar.write(tx, value.clone())?;
3311                self.metadata_tvar.write(tx, new_meta)?;
3312                Ok((false, value.clone(), new_meta))
3313            } else {
3314                Ok((true, self.tvar.read(tx)?, existing_meta))
3315            }
3316        });
3317        #[cfg(target_arch = "wasm32")]
3318        let (was_skipped, final_val, final_meta) =
3319            (false, value, agents::get_current_mutation_metadata());
3320        if was_skipped {
3321            if let (Some(new_m), Some(old_m)) =
3322                (agents::get_current_mutation_metadata(), final_meta)
3323            {
3324                agents::notify_conflict(agents::ConflictEvent {
3325                    agent_id: new_m.agent_id,
3326                    priority: new_m.priority,
3327                    existing_agent_id: old_m.agent_id,
3328                    existing_priority: old_m.priority,
3329                    timestamp_ms: new_m.timestamp_ms,
3330                });
3331            }
3332            return;
3333        }
3334        self.swap.store(Arc::new(final_val.clone()));
3335        self.metadata_swap.store(Arc::new(final_meta));
3336        self.version
3337            .fetch_add(1, std::sync::atomic::Ordering::Release);
3338        let subs = Arc::clone(&self.subscribers);
3339        if crate::is_batching() {
3340            crate::enqueue_batch_task(Box::new(move || {
3341                let s = subs.lock().unwrap();
3342                for cb in s.iter() {
3343                    cb(&final_val);
3344                }
3345            }));
3346        } else {
3347            let s = subs.lock().unwrap();
3348            for cb in s.iter() {
3349                cb(&final_val);
3350            }
3351        }
3352    }
3353    pub fn mutate<F: Fn(&T) -> T>(&self, f: F) {
3354        #[cfg(not(target_arch = "wasm32"))]
3355        {
3356            let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3357                let new_meta = agents::get_current_mutation_metadata();
3358                let existing_meta = self.metadata_tvar.read(tx)?;
3359                let mut skip = false;
3360                if self.resolution == agents::ConflictResolution::PriorityWins
3361                    && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3362                    && new_m.priority < old_m.priority
3363                {
3364                    skip = true;
3365                }
3366                if !skip {
3367                    let current = self.tvar.read(tx)?;
3368                    let next = f(&current);
3369                    self.tvar.write(tx, next.clone())?;
3370                    self.metadata_tvar.write(tx, new_meta)?;
3371                    Ok((false, next, new_meta))
3372                } else {
3373                    Ok((true, self.tvar.read(tx)?, existing_meta))
3374                }
3375            });
3376            if was_skipped {
3377                if let (Some(new_m), Some(old_m)) =
3378                    (agents::get_current_mutation_metadata(), final_meta)
3379                {
3380                    agents::notify_conflict(agents::ConflictEvent {
3381                        agent_id: new_m.agent_id,
3382                        priority: new_m.priority,
3383                        existing_agent_id: old_m.agent_id,
3384                        existing_priority: old_m.priority,
3385                        timestamp_ms: new_m.timestamp_ms,
3386                    });
3387                }
3388                return;
3389            }
3390            self.swap.store(Arc::new(final_val.clone()));
3391            self.metadata_swap.store(Arc::new(final_meta));
3392            self.version
3393                .fetch_add(1, std::sync::atomic::Ordering::Release);
3394            let subs = Arc::clone(&self.subscribers);
3395            if crate::is_batching() {
3396                crate::enqueue_batch_task(Box::new(move || {
3397                    let s = subs.lock().unwrap();
3398                    for cb in s.iter() {
3399                        cb(&final_val);
3400                    }
3401                }));
3402            } else {
3403                let s = subs.lock().unwrap();
3404                for cb in s.iter() {
3405                    cb(&final_val);
3406                }
3407            }
3408        }
3409        #[cfg(target_arch = "wasm32")]
3410        {
3411            self.set(f(&self.get()));
3412        }
3413    }
3414    /// Get current version
3415    pub fn version(&self) -> u64 {
3416        self.version.load(std::sync::atomic::Ordering::Acquire)
3417    }
3418    /// Subscribe to state changes
3419    pub fn subscribe<F: Fn(&T) + Send + Sync + 'static>(&self, callback: F) {
3420        self.subscribers.lock().unwrap().push(Box::new(callback));
3421    }
3422}
3423use crate::runtime::NodeStateSnapshot;
3424use std::sync::OnceLock;
3425use std::sync::atomic::{AtomicBool, Ordering};
3426/// Global application state registry.
3427pub static SYSTEM_STATE: OnceLock<Arc<arc_swap::ArcSwap<KnowledgeState>>> = OnceLock::new();
3428#[cfg(not(target_arch = "wasm32"))]
3429static KNOWLEDGE_TVAR: OnceLock<stm::TVar<KnowledgeState>> = OnceLock::new();
3430static IS_BATCHING: AtomicBool = AtomicBool::new(false);
3431pub static IS_RENDERING: AtomicBool = AtomicBool::new(false);
3432pub static LAYOUT_DIRTY: AtomicBool = AtomicBool::new(false);
3433type BatchQueue = OnceLock<std::sync::Mutex<Vec<Box<dyn FnOnce() + Send + Sync>>>>;
3434static BATCH_QUEUE: BatchQueue = OnceLock::new();
3435/// Global write lock to serialize updates to SYSTEM_STATE and KNOWLEDGE_TVAR,
3436/// preventing parallel race conditions between STM transactions and the lock-free reader state.
3437static STATE_WRITE_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
3438/// Returns true if state updates are currently being batched.
3439pub fn is_batching() -> bool {
3440    IS_BATCHING.load(Ordering::Acquire)
3441}
3442/// Returns true if the system is currently in the render phase.
3443pub fn is_rendering() -> bool {
3444    IS_RENDERING.load(Ordering::Acquire)
3445}
3446/// Signals the start of the render phase. Mutations during this phase trigger warnings.
3447pub fn begin_render_phase() {
3448    IS_RENDERING.store(true, Ordering::Release);
3449}
3450/// Signals the end of the render phase.
3451pub fn end_render_phase() {
3452    IS_RENDERING.store(false, Ordering::Release);
3453}
3454/// Enqueues a notification task to be run when the current batch flushes.
3455pub fn enqueue_batch_task(task: Box<dyn FnOnce() + Send + Sync>) {
3456    let mut queue = BATCH_QUEUE
3457        .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3458        .lock()
3459        .unwrap();
3460    queue.push(task);
3461}
3462/// Executes multiple state updates in a single batch, deferring all subscriber
3463/// notifications until the closure completes. This prevents layout thrashing
3464/// and redundant render cycles when modifying multiple independent states.
3465pub fn batch<F: FnOnce()>(f: F) {
3466    if IS_BATCHING.swap(true, Ordering::AcqRel) {
3467        // Already inside a batch, just execute
3468        f();
3469        return;
3470    }
3471    f();
3472    IS_BATCHING.store(false, Ordering::Release);
3473    let mut queue = BATCH_QUEUE
3474        .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3475        .lock()
3476        .unwrap();
3477    let tasks: Vec<_> = queue.drain(..).collect();
3478    drop(queue);
3479    for task in tasks {
3480        task();
3481    }
3482}
3483/// Get a reference to the global system state.
3484pub fn get_system_state() -> Arc<arc_swap::ArcSwap<KnowledgeState>> {
3485    SYSTEM_STATE
3486        .get_or_init(|| Arc::new(arc_swap::ArcSwap::from_pointee(KnowledgeState::default())))
3487        .clone()
3488}
3489pub fn load_system_state() -> arc_swap::Guard<Arc<KnowledgeState>> {
3490    get_system_state().load()
3491}
3492pub fn update_system_state<F>(f: F)
3493where
3494    F: FnOnce(&KnowledgeState) -> KnowledgeState,
3495{
3496    let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3497    if is_rendering() {
3498        log::warn!(
3499            "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3500        );
3501    }
3502    LAYOUT_DIRTY.store(true, Ordering::SeqCst);
3503    let swap = get_system_state();
3504    let current = swap.load();
3505    let new_state = Arc::new(f(&current));
3506    swap.store(Arc::clone(&new_state));
3507    #[cfg(not(target_arch = "wasm32"))]
3508    {
3509        let tvar = KNOWLEDGE_TVAR.get_or_init(|| stm::TVar::new((*new_state).clone()));
3510        stm::atomically(|tx| tvar.write(tx, (*new_state).clone()));
3511    }
3512}
3513pub fn transact_system_state<F>(f: F)
3514where
3515    F: Fn(&KnowledgeState) -> KnowledgeState,
3516{
3517    let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3518    #[cfg(not(target_arch = "wasm32"))]
3519    {
3520        if is_rendering() {
3521            log::warn!(
3522                "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3523            );
3524        }
3525        let tvar = KNOWLEDGE_TVAR
3526            .get_or_init(|| stm::TVar::new((**get_system_state().load()).clone()))
3527            .clone();
3528        let new_state = stm::atomically(move |tx| {
3529            let current = tvar.read(tx)?;
3530            let next = f(&current);
3531            tvar.write(tx, next.clone())?;
3532            Ok(next)
3533        });
3534        get_system_state().store(Arc::new(new_state));
3535    }
3536    #[cfg(target_arch = "wasm32")]
3537    {
3538        if is_rendering() {
3539            log::warn!(
3540                "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3541            );
3542        }
3543        update_system_state(f);
3544    }
3545}
3546impl KnowledgeState {
3547    /// Create a new empty KnowledgeState.
3548    pub fn new() -> Self {
3549        Self::default()
3550    }
3551    /// Set a component's internal state.
3552    pub fn set_component_state<T: 'static + Send + Sync>(&mut self, id: u64, state: T) {
3553        self.component_states
3554            .insert(id, Arc::new(std::sync::RwLock::new(state)));
3555    }
3556    /// Get a reference to a component's internal state.
3557    pub fn get_component_state<T: 'static + Send + Sync>(
3558        &self,
3559        id: u64,
3560    ) -> Option<Arc<std::sync::RwLock<T>>> {
3561        let lock = self.component_states.get(&id)?;
3562        // Attempt to clone the Arc and downcast the inner RwLock<dyn Any> to RwLock<T>
3563        // We use a two-step approach: check if the inner type matches via Any, then transmute the Arc
3564        // SAFETY: We verify the type via Any::is::<T> before transmuting
3565        let any_ref = lock.read().ok()?;
3566        if any_ref.is::<T>() {
3567            // Type matches — safe to transmute the Arc
3568            drop(any_ref);
3569            let cloned: Arc<std::sync::RwLock<dyn std::any::Any + Send + Sync>> = Arc::clone(lock);
3570            // Transmute Arc<RwLock<dyn Any>> to Arc<RwLock<T>>
3571            // This is safe because we just verified the inner type is T
3572            Some(unsafe {
3573                let raw = Arc::into_raw(cloned);
3574                Arc::from_raw(raw as *const std::sync::RwLock<T>)
3575            })
3576        } else {
3577            None
3578        }
3579    }
3580    /// Add a new fragment to memory.
3581    pub fn remember(&mut self, fragment: KnowledgeFragment) {
3582        self.fragments.insert(fragment.id.clone(), fragment);
3583    }
3584    /// Process a search query against the local knowledge base.
3585    pub fn process_query(&mut self, query: &str) {
3586        let query_lower = query.to_lowercase();
3587        let mut results: Vec<(f32, String)> = self
3588            .fragments
3589            .iter()
3590            .map(|(id, frag)| {
3591                let mut score = 0.0;
3592                if frag.summary.to_lowercase().contains(&query_lower) {
3593                    score += 1.0;
3594                }
3595                if frag.source.to_lowercase().contains(&query_lower) {
3596                    score += 0.5;
3597                }
3598                (score, id.clone())
3599            })
3600            .filter(|(score, _)| *score > 0.0)
3601            .collect();
3602        // Sort by relevance score
3603        results.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
3604        self.last_query_results = results.into_iter().map(|(_, id)| id).take(5).collect();
3605    }
3606    /// Captures a snapshot of the current state for debugging and hot-reloading.
3607    pub fn snapshot(&self) -> Vec<NodeStateSnapshot> {
3608        let mut snapshots = Vec::new();
3609        // Snapshots of agentic fragments
3610        for frag in self.fragments.values() {
3611            if let Ok(val) = serde_json::to_value(frag) {
3612                snapshots.push(NodeStateSnapshot { id: 0, state: val });
3613            }
3614        }
3615        snapshots
3616    }
3617}
3618/// A read/write projection into a `State<T>` owned elsewhere.
3619#[derive(Clone)]
3620pub struct Binding<T: Clone + Send + Sync + 'static> {
3621    swap: Arc<arc_swap::ArcSwap<T>>,
3622    #[cfg(not(target_arch = "wasm32"))]
3623    tvar: Arc<stm::TVar<T>>,
3624    version: Arc<std::sync::atomic::AtomicU64>,
3625}
3626impl<T: Clone + Send + Sync + 'static> Binding<T> {
3627    /// Create a binding from a State
3628    pub fn from_state(state: &State<T>) -> Self {
3629        Self {
3630            swap: Arc::clone(&state.swap),
3631            #[cfg(not(target_arch = "wasm32"))]
3632            tvar: Arc::clone(&state.tvar),
3633            version: Arc::clone(&state.version),
3634        }
3635    }
3636    /// Get the current value
3637    pub fn get(&self) -> T {
3638        (**self.swap.load()).clone()
3639    }
3640    /// Set a new value
3641    pub fn set(&self, value: T) {
3642        self.swap.store(Arc::new(value.clone()));
3643        #[cfg(not(target_arch = "wasm32"))]
3644        {
3645            let tvar = Arc::clone(&self.tvar);
3646            let v = value.clone();
3647            stm::atomically(move |tx| tvar.write(tx, v.clone()));
3648        }
3649        self.version
3650            .fetch_add(1, std::sync::atomic::Ordering::Release);
3651    }
3652    /// Get current version
3653    pub fn version(&self) -> u64 {
3654        self.version.load(std::sync::atomic::Ordering::Acquire)
3655    }
3656}
3657#[cfg(not(target_arch = "wasm32"))]
3658pub fn transact_pair<A, B, F>(state_a: &State<A>, state_b: &State<B>, f: F)
3659where
3660    A: Clone + Send + Sync + 'static,
3661    B: Clone + Send + Sync + 'static,
3662    F: Fn(&A, &B) -> (A, B),
3663{
3664    let tvar_a = Arc::clone(&state_a.tvar);
3665    let tvar_b = Arc::clone(&state_b.tvar);
3666    let (new_a, new_b) = stm::atomically(move |tx| {
3667        let a = tvar_a.read(tx)?;
3668        let b = tvar_b.read(tx)?;
3669        let (na, nb) = f(&a, &b);
3670        tvar_a.write(tx, na.clone())?;
3671        tvar_b.write(tx, nb.clone())?;
3672        Ok((na, nb))
3673    });
3674    state_a.swap.store(Arc::new(new_a.clone()));
3675    state_b.swap.store(Arc::new(new_b.clone()));
3676    state_a
3677        .version
3678        .fetch_add(1, std::sync::atomic::Ordering::Release);
3679    state_b
3680        .version
3681        .fetch_add(1, std::sync::atomic::Ordering::Release);
3682    let subs_a = Arc::clone(&state_a.subscribers);
3683    let subs_b = Arc::clone(&state_b.subscribers);
3684    if crate::is_batching() {
3685        crate::enqueue_batch_task(Box::new(move || {
3686            {
3687                let s = subs_a.lock().unwrap();
3688                for cb in s.iter() {
3689                    cb(&new_a);
3690                }
3691            }
3692            {
3693                let s = subs_b.lock().unwrap();
3694                for cb in s.iter() {
3695                    cb(&new_b);
3696                }
3697            }
3698        }));
3699    } else {
3700        {
3701            let s = subs_a.lock().unwrap();
3702            for cb in s.iter() {
3703                cb(&new_a);
3704            }
3705        }
3706        {
3707            let s = subs_b.lock().unwrap();
3708            for cb in s.iter() {
3709                cb(&new_b);
3710            }
3711        }
3712    }
3713}
3714use std::any::TypeId;
3715use std::sync::Mutex;
3716/// Global environment storage using TypeId as keys.
3717pub(crate) static ENVIRONMENT: OnceLock<
3718    Mutex<HashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>>,
3719> = OnceLock::new();
3720/// Environment key type for accessing ambient values
3721/// Implement this trait to define a new environment key.
3722pub trait EnvKey: 'static + Send + Sync {
3723    /// The type of value stored in the environment
3724    type Value: Clone + Send + Sync + 'static;
3725    /// Get a default value for this key
3726    fn default_value() -> Self::Value;
3727}
3728/// Key for accessing the Yggdrasil design token tree
3729pub struct YggdrasilKey;
3730impl EnvKey for YggdrasilKey {
3731    type Value = YggdrasilTokens;
3732    fn default_value() -> Self::Value {
3733        default_tokens()
3734    }
3735}
3736// Duplicate AssetKey removed - original definition at line 63
3737/// System appearance (Light/Dark mode)
3738#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3739pub enum Appearance {
3740    Light,
3741    Dark,
3742}
3743/// Orientation for layouts
3744#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3745pub enum Orientation {
3746    Horizontal,
3747    Vertical,
3748}
3749/// Placement configuration for placing a view within a Grid layout.
3750#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3751pub struct GridPlacement {
3752    /// 0-based column index. Negative values count from the end of columns.
3753    pub column: i32,
3754    /// Number of columns the view spans (default is 1).
3755    pub column_span: u32,
3756    /// 0-based row index. Negative values count from the end of rows.
3757    pub row: i32,
3758    /// Number of rows the view spans (default is 1).
3759    pub row_span: u32,
3760}
3761/// Cross-axis alignment for layout containers.
3762#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3763pub enum Alignment {
3764    #[default]
3765    Center,
3766    Leading,
3767    Trailing,
3768    Top,
3769    Bottom,
3770}
3771/// Main-axis distribution for linear layout containers.
3772#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3773pub enum Distribution {
3774    #[default]
3775    Fill,
3776    Center,
3777    Leading,
3778    Trailing,
3779    SpaceBetween,
3780    SpaceAround,
3781    SpaceEvenly,
3782}
3783/// A color represented by RGBA components in the [0.0, 1.0] range.
3784#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
3785pub struct Color {
3786    pub r: f32,
3787    pub g: f32,
3788    pub b: f32,
3789    pub a: f32,
3790}
3791impl Color {
3792    pub const BLACK: Color = Color {
3793        r: 0.0,
3794        g: 0.0,
3795        b: 0.0,
3796        a: 1.0,
3797    };
3798    pub const WHITE: Color = Color {
3799        r: 1.0,
3800        g: 1.0,
3801        b: 1.0,
3802        a: 1.0,
3803    };
3804    pub const TRANSPARENT: Color = Color {
3805        r: 0.0,
3806        g: 0.0,
3807        b: 0.0,
3808        a: 0.0,
3809    };
3810    pub const RED: Color = Color {
3811        r: 1.0,
3812        g: 0.0,
3813        b: 0.0,
3814        a: 1.0,
3815    };
3816    pub const GREEN: Color = Color {
3817        r: 0.0,
3818        g: 1.0,
3819        b: 0.0,
3820        a: 1.0,
3821    };
3822    pub const BLUE: Color = Color {
3823        r: 0.0,
3824        g: 0.0,
3825        b: 1.0,
3826        a: 1.0,
3827    };
3828    pub const VIKING_GOLD: Color = Color {
3829        r: 1.0,
3830        g: 0.84,
3831        b: 0.0,
3832        a: 1.0,
3833    };
3834    pub const MAGENTA_LIQUID: Color = Color {
3835        r: 1.0,
3836        g: 0.0,
3837        b: 1.0,
3838        a: 1.0,
3839    };
3840    pub const TACTICAL_OBSIDIAN: Color = Color {
3841        r: 0.05,
3842        g: 0.05,
3843        b: 0.07,
3844        a: 1.0,
3845    };
3846    /// Calculate the relative luminance of the color as defined by WCAG 2.x
3847    pub fn relative_luminance(&self) -> f32 {
3848        fn res(c: f32) -> f32 {
3849            if c <= 0.03928 {
3850                c / 12.92
3851            } else {
3852                ((c + 0.055) / 1.055).powf(2.4)
3853            }
3854        }
3855        0.2126 * res(self.r) + 0.7152 * res(self.g) + 0.0722 * res(self.b)
3856    }
3857    /// Calculate the contrast ratio between this color and another color
3858    pub fn contrast_ratio(&self, other: &Color) -> f32 {
3859        let l1 = self.relative_luminance();
3860        let l2 = other.relative_luminance();
3861        if l1 > l2 {
3862            (l1 + 0.05) / (l2 + 0.05)
3863        } else {
3864            (l2 + 0.05) / (l1 + 0.05)
3865        }
3866    }
3867    pub const CYAN: Color = Color {
3868        r: 0.0,
3869        g: 1.0,
3870        b: 1.0,
3871        a: 1.0,
3872    };
3873    pub const YELLOW: Color = Color {
3874        r: 1.0,
3875        g: 1.0,
3876        b: 0.0,
3877        a: 1.0,
3878    };
3879    pub const MAGENTA: Color = Color {
3880        r: 1.0,
3881        g: 0.0,
3882        b: 1.0,
3883        a: 1.0,
3884    };
3885    pub const GRAY: Color = Color {
3886        r: 0.5,
3887        g: 0.5,
3888        b: 0.5,
3889        a: 1.0,
3890    };
3891    /// Create a new color from RGBA components.
3892    pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
3893        Self { r, g, b, a }
3894    }
3895    /// Convert the color to a [r, g, b, a] array.
3896    pub fn as_array(&self) -> [f32; 4] {
3897        [self.r, self.g, self.b, self.a]
3898    }
3899
3900    /// Return a new color with lightness increased by `amount`.
3901    ///
3902    /// Adds `amount` to each RGB channel and clamps to [0.0, 1.0].
3903    /// This is a simple sRGB lightness adjustment, not perceptually uniform.
3904    /// For perceptually uniform adjustments, use OKLCH via cvkg-themes.
3905    pub fn lighten(&self, amount: f32) -> Self {
3906        Self {
3907            r: (self.r + amount).clamp(0.0, 1.0),
3908            g: (self.g + amount).clamp(0.0, 1.0),
3909            b: (self.b + amount).clamp(0.0, 1.0),
3910            a: self.a,
3911        }
3912    }
3913
3914    /// Return a new color with lightness decreased by `amount`.
3915    pub fn darken(&self, amount: f32) -> Self {
3916        Self {
3917            r: (self.r - amount).clamp(0.0, 1.0),
3918            g: (self.g - amount).clamp(0.0, 1.0),
3919            b: (self.b - amount).clamp(0.0, 1.0),
3920            a: self.a,
3921        }
3922    }
3923}
3924impl View for Color {
3925    type Body = Never;
3926    fn body(self) -> Self::Body {
3927        unreachable!()
3928    }
3929    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
3930        renderer.fill_rect(rect, self.as_array());
3931    }
3932}
3933/// Key for accessing the current system appearance
3934pub struct AppearanceKey;
3935impl EnvKey for AppearanceKey {
3936    type Value = Appearance;
3937    fn default_value() -> Self::Value {
3938        Appearance::Dark // Default to Dark (Ginnungagap) for Berserker aesthetic
3939    }
3940}
3941/// StyleResolver provides high-level access to themed values from the environment.
3942pub struct StyleResolver;
3943impl StyleResolver {
3944    /// Resolve a color from the current environment
3945    pub fn color(key: &str) -> String {
3946        let tokens = Environment::<YggdrasilKey>::new().get();
3947        let appearance = Environment::<AppearanceKey>::new().get();
3948        let is_dark = appearance == Appearance::Dark;
3949        tokens
3950            .get_color(key, is_dark)
3951            .unwrap_or_else(|| "#FF00FF".to_string()) // Default to MuspelMagenta on failure
3952    }
3953    /// Resolve a generic token value
3954    pub fn get<T: FromStr>(category: &str, key: &str) -> Option<T> {
3955        let tokens = Environment::<YggdrasilKey>::new().get();
3956        let appearance = Environment::<AppearanceKey>::new().get();
3957        let is_dark = appearance == Appearance::Dark;
3958        tokens.get(category, key, is_dark)
3959    }
3960    /// Resolve a color from the current environment as a [f32; 4] RGBA array.
3961    /// Returns the color value for the current appearance (light/dark).
3962    /// Falls back to magenta (#FF00FF) if the key is not found.
3963    pub fn color_array(key: &str) -> [f32; 4] {
3964        let hex = Self::color(key);
3965        parse_hex_color(&hex)
3966    }
3967}
3968
3969/// Parse a hex color string (#RRGGBB or #RRGGBBAA) into [f32; 4] RGBA.
3970fn parse_hex_color(hex: &str) -> [f32; 4] {
3971    let hex = hex.trim_start_matches('#');
3972    if hex.len() >= 6 {
3973        let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(255) as f32 / 255.0;
3974        let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0) as f32 / 255.0;
3975        let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(255) as f32 / 255.0;
3976        let a = if hex.len() >= 8 {
3977            u8::from_str_radix(&hex[6..8], 16).unwrap_or(255) as f32 / 255.0
3978        } else {
3979            1.0
3980        };
3981        [r, g, b, a]
3982    } else {
3983        [1.0, 0.0, 1.0, 1.0] // Magenta fallback
3984    }
3985}
3986
3987/// The authoritative Cyberpunk Viking default tokens
3988pub fn default_tokens() -> YggdrasilTokens {
3989    let mut tokens = YggdrasilTokens::new();
3990    // Core Norse Colorways
3991    tokens.color.insert(
3992        "background".to_string(),
3993        TokenValue::Single {
3994            value: "#000000".to_string(), // Ginnungagap (The Void)
3995        },
3996    );
3997    tokens.color.insert(
3998        "primary".to_string(),
3999        TokenValue::Single {
4000            value: "#00FFFF".to_string(), // NiflCyan (Aesir Primary)
4001        },
4002    );
4003    tokens.color.insert(
4004        "secondary".to_string(),
4005        TokenValue::Single {
4006            value: "#FF00FF".to_string(), // MuspelMagenta (Berserker Secondary)
4007        },
4008    );
4009    tokens.color.insert(
4010        "surface".to_string(),
4011        TokenValue::Adaptive {
4012            light: "#FFFFFF".to_string(),
4013            dark: "#121212".to_string(),
4014        },
4015    );
4016    tokens.color.insert(
4017        "text".to_string(),
4018        TokenValue::Adaptive {
4019            light: "#000000".to_string(),
4020            dark: "#FFFFFF".to_string(),
4021        },
4022    );
4023    // Semantic component tokens
4024    tokens.color.insert(
4025        "surface_elevated".to_string(),
4026        TokenValue::Adaptive {
4027            light: "#FFFFFF".to_string(),
4028            dark: "#1A1A24".to_string(),
4029        },
4030    );
4031    tokens.color.insert(
4032        "surface_overlay".to_string(),
4033        TokenValue::Adaptive {
4034            light: "#FFFFFF".to_string(),
4035            dark: "#1E1E2E".to_string(),
4036        },
4037    );
4038    tokens.color.insert(
4039        "border".to_string(),
4040        TokenValue::Adaptive {
4041            light: "#D0D0D8".to_string(),
4042            dark: "#2A2A3A".to_string(),
4043        },
4044    );
4045    tokens.color.insert(
4046        "border_strong".to_string(),
4047        TokenValue::Adaptive {
4048            light: "#A0A0B0".to_string(),
4049            dark: "#3A3A50".to_string(),
4050        },
4051    );
4052    tokens.color.insert(
4053        "text_muted".to_string(),
4054        TokenValue::Adaptive {
4055            light: "#606070".to_string(),
4056            dark: "#8080A0".to_string(),
4057        },
4058    );
4059    tokens.color.insert(
4060        "text_dim".to_string(),
4061        TokenValue::Adaptive {
4062            light: "#9090A0".to_string(),
4063            dark: "#505070".to_string(),
4064        },
4065    );
4066    tokens.color.insert(
4067        "accent".to_string(),
4068        TokenValue::Single {
4069            value: "#00FFFF".to_string(), // NiflCyan
4070        },
4071    );
4072    tokens.color.insert(
4073        "accent_hover".to_string(),
4074        TokenValue::Single {
4075            value: "#33FFFF".to_string(),
4076        },
4077    );
4078    tokens.color.insert(
4079        "success".to_string(),
4080        TokenValue::Single {
4081            value: "#00E676".to_string(),
4082        },
4083    );
4084    tokens.color.insert(
4085        "warning".to_string(),
4086        TokenValue::Single {
4087            value: "#FFB300".to_string(),
4088        },
4089    );
4090    tokens.color.insert(
4091        "error".to_string(),
4092        TokenValue::Single {
4093            value: "#FF5252".to_string(),
4094        },
4095    );
4096    tokens.color.insert(
4097        "info".to_string(),
4098        TokenValue::Single {
4099            value: "#448AFF".to_string(),
4100        },
4101    );
4102    tokens.color.insert(
4103        "hover".to_string(),
4104        TokenValue::Adaptive {
4105            light: "#F0F0F5".to_string(),
4106            dark: "#252535".to_string(),
4107        },
4108    );
4109    tokens.color.insert(
4110        "active".to_string(),
4111        TokenValue::Adaptive {
4112            light: "#E0E0EB".to_string(),
4113            dark: "#303045".to_string(),
4114        },
4115    );
4116    tokens.color.insert(
4117        "disabled".to_string(),
4118        TokenValue::Adaptive {
4119            light: "#E8E8F0".to_string(),
4120            dark: "#1A1A28".to_string(),
4121        },
4122    );
4123    tokens.color.insert(
4124        "disabled_text".to_string(),
4125        TokenValue::Adaptive {
4126            light: "#B0B0C0".to_string(),
4127            dark: "#404060".to_string(),
4128        },
4129    );
4130    tokens.color.insert(
4131        "focus_ring".to_string(),
4132        TokenValue::Single {
4133            value: "#00FFFF".to_string(),
4134        },
4135    );
4136    tokens.color.insert(
4137        "shadow".to_string(),
4138        TokenValue::Adaptive {
4139            light: "#00000020".to_string(),
4140            dark: "#00000060".to_string(),
4141        },
4142    );
4143    tokens.color.insert(
4144        "code_bg".to_string(),
4145        TokenValue::Adaptive {
4146            light: "#F5F5FA".to_string(),
4147            dark: "#0D0D18".to_string(),
4148        },
4149    );
4150    // Bifrost (Glassmorphism) - Frosted Style
4151    tokens.bifrost.insert(
4152        "blur".to_string(),
4153        TokenValue::Single {
4154            value: "25.0".to_string(),
4155        },
4156    );
4157    tokens.bifrost.insert(
4158        "saturation".to_string(),
4159        TokenValue::Single {
4160            value: "1.2".to_string(),
4161        },
4162    );
4163    tokens.bifrost.insert(
4164        "opacity".to_string(),
4165        TokenValue::Single {
4166            value: "0.65".to_string(),
4167        },
4168    );
4169    // Gungnir (Neon Glow)
4170    tokens.gungnir.insert(
4171        "intensity".to_string(),
4172        TokenValue::Single {
4173            value: "1.0".to_string(),
4174        },
4175    );
4176    tokens.gungnir.insert(
4177        "radius".to_string(),
4178        TokenValue::Single {
4179            value: "15.0".to_string(),
4180        },
4181    );
4182    // Mjolnir (Sharp Geometry)
4183    tokens.mjolnir.insert(
4184        "clip_angle".to_string(),
4185        TokenValue::Single {
4186            value: "12.0".to_string(),
4187        },
4188    );
4189    tokens.mjolnir.insert(
4190        "border_width".to_string(),
4191        TokenValue::Single {
4192            value: "2.0".to_string(),
4193        },
4194    );
4195    // Sleipnir (Spring Animation)
4196    tokens.anim.insert(
4197        "stiffness".to_string(),
4198        TokenValue::Single {
4199            value: "170.0".to_string(),
4200        },
4201    );
4202    tokens.anim.insert(
4203        "damping".to_string(),
4204        TokenValue::Single {
4205            value: "26.0".to_string(),
4206        },
4207    );
4208    tokens.anim.insert(
4209        "mass".to_string(),
4210        TokenValue::Single {
4211            value: "1.0".to_string(),
4212        },
4213    );
4214    // Accessibility
4215    tokens.accessibility.insert(
4216        "reduce_motion".to_string(),
4217        TokenValue::Single {
4218            value: "false".to_string(),
4219        },
4220    );
4221    tokens
4222}
4223/// Environment wrapper for accessing ambient values
4224pub struct Environment<K: EnvKey> {
4225    _marker: std::marker::PhantomData<K>,
4226}
4227impl<K: EnvKey> Default for Environment<K> {
4228    fn default() -> Self {
4229        Self::new()
4230    }
4231}
4232impl<K: EnvKey> Environment<K> {
4233    /// Create a new Environment
4234    pub fn new() -> Self {
4235        Self {
4236            _marker: std::marker::PhantomData,
4237        }
4238    }
4239    /// Get the current value from the environment
4240    pub fn get(&self) -> K::Value {
4241        if let Some(env_store) = ENVIRONMENT.get() {
4242            let env_lock = env_store.lock().unwrap();
4243            if let Some(val) = env_lock.get(&std::any::TypeId::of::<K>()) {
4244                if let Some(typed_val) = val.downcast_ref::<K::Value>() {
4245                    return typed_val.clone();
4246                } else {
4247                    log::warn!(
4248                        "Environment: Downcast failed for key type {:?}",
4249                        std::any::type_name::<K>()
4250                    );
4251                }
4252            } else {
4253                log::debug!(
4254                    "Environment: Key not found: {:?}. Returning default.",
4255                    std::any::type_name::<K>()
4256                );
4257            }
4258        } else {
4259            log::debug!(
4260                "Environment: Store not initialized. Key: {:?}. Returning default.",
4261                std::any::type_name::<K>()
4262            );
4263        }
4264        K::default_value()
4265    }
4266}
4267/// Ambient environment management
4268pub mod env {
4269    /// Insert a value into the environment
4270    pub fn insert<K: super::EnvKey>(value: K::Value) {
4271        let store = super::ENVIRONMENT
4272            .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
4273        let mut env_map = store.lock().unwrap();
4274        env_map.insert(std::any::TypeId::of::<K>(), Box::new(value));
4275    }
4276    /// Remove a value from the environment.
4277    pub fn remove<K: super::EnvKey>() {
4278        if let Some(store) = super::ENVIRONMENT.get() {
4279            let mut env_map = store.lock().unwrap();
4280            env_map.remove(&std::any::TypeId::of::<K>());
4281        }
4282    }
4283}
4284/// Geometry modifiers
4285/// Size of the view in logical pixels
4286#[derive(Debug, Clone, Copy, PartialEq)]
4287pub struct Size {
4288    pub width: f32,
4289    pub height: f32,
4290}
4291
4292impl Size {
4293    pub const ZERO: Self = Self {
4294        width: 0.0,
4295        height: 0.0,
4296    };
4297
4298    pub fn new(width: f32, height: f32) -> Self {
4299        Self { width, height }
4300    }
4301}
4302
4303/// Insets for padding
4304#[derive(Debug, Clone, Copy, PartialEq)]
4305pub struct EdgeInsets {
4306    pub top: f32,
4307    pub leading: f32,
4308    pub bottom: f32,
4309    pub trailing: f32,
4310}
4311
4312impl EdgeInsets {
4313    /// Equal insets on all edges
4314    pub fn all(value: f32) -> Self {
4315        Self {
4316            top: value,
4317            leading: value,
4318            bottom: value,
4319            trailing: value,
4320        }
4321    }
4322
4323    /// Vertical insets (top and bottom)
4324    pub fn vertical(value: f32) -> Self {
4325        Self {
4326            top: value,
4327            leading: 0.0,
4328            bottom: value,
4329            trailing: 0.0,
4330        }
4331    }
4332
4333    /// Horizontal insets (leading and trailing)
4334    pub fn horizontal(value: f32) -> Self {
4335        Self {
4336            top: 0.0,
4337            leading: value,
4338            bottom: 0.0,
4339            trailing: value,
4340        }
4341    }
4342}
4343
4344/// Modifier to set the size and alignment constraints of a view.
4345/// This determines the proposal size passed to the child and how the child is aligned
4346/// within the layout rect allocated to the frame.
4347#[derive(Debug, Clone, Copy, PartialEq)]
4348pub struct FrameModifier {
4349    /// Exact width to assign to the child view.
4350    pub width: Option<f32>,
4351    /// Exact height to assign to the child view.
4352    pub height: Option<f32>,
4353    /// Minimum width constraint for the view.
4354    pub min_width: Option<f32>,
4355    /// Maximum width constraint for the view.
4356    pub max_width: Option<f32>,
4357    /// Minimum height constraint for the view.
4358    pub min_height: Option<f32>,
4359    /// Maximum height constraint for the view.
4360    pub max_height: Option<f32>,
4361    /// The alignment strategy for positioning the child view within the frame.
4362    pub alignment: Alignment,
4363}
4364
4365impl Default for FrameModifier {
4366    /// Returns the default frame configuration which has no constraints and center alignment.
4367    fn default() -> Self {
4368        Self::new()
4369    }
4370}
4371
4372impl FrameModifier {
4373    /// Creates a new FrameModifier with all dimensions unspecified and center alignment.
4374    pub fn new() -> Self {
4375        Self {
4376            width: None,
4377            height: None,
4378            min_width: None,
4379            max_width: None,
4380            min_height: None,
4381            max_height: None,
4382            alignment: Alignment::Center,
4383        }
4384    }
4385
4386    /// Sets the fixed width of the frame.
4387    pub fn width(mut self, width: f32) -> Self {
4388        self.width = Some(width);
4389        self
4390    }
4391
4392    /// Sets the fixed height of the frame.
4393    pub fn height(mut self, height: f32) -> Self {
4394        self.height = Some(height);
4395        self
4396    }
4397
4398    /// Sets both the fixed width and height of the frame.
4399    pub fn size(mut self, width: f32, height: f32) -> Self {
4400        self.width = Some(width);
4401        self.height = Some(height);
4402        self
4403    }
4404
4405    /// Sets the minimum width constraint.
4406    pub fn min_width(mut self, min_width: f32) -> Self {
4407        self.min_width = Some(min_width);
4408        self
4409    }
4410
4411    /// Sets the maximum width constraint.
4412    pub fn max_width(mut self, max_width: f32) -> Self {
4413        self.max_width = Some(max_width);
4414        self
4415    }
4416
4417    /// Sets the minimum height constraint.
4418    pub fn min_height(mut self, min_height: f32) -> Self {
4419        self.min_height = Some(min_height);
4420        self
4421    }
4422
4423    /// Sets the maximum height constraint.
4424    pub fn max_height(mut self, max_height: f32) -> Self {
4425        self.max_height = Some(max_height);
4426        self
4427    }
4428
4429    /// Sets the alignment strategy for the child within the frame's layout bounds.
4430    pub fn alignment(mut self, alignment: Alignment) -> Self {
4431        self.alignment = alignment;
4432        self
4433    }
4434}
4435
4436impl ViewModifier for FrameModifier {
4437    /// Wraps the child view in a ModifiedView using this frame modifier.
4438    fn modify<V: View>(self, content: V) -> impl View {
4439        ModifiedView::new(content, self)
4440    }
4441
4442    /// Transforms the layout size proposal offered to the child to comply with frame constraints.
4443    fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
4444        let w = if let Some(width) = self.width {
4445            Some(width)
4446        } else {
4447            proposal.width.map(|pw| {
4448                pw.clamp(
4449                    self.min_width.unwrap_or(0.0),
4450                    self.max_width.unwrap_or(f32::INFINITY),
4451                )
4452            })
4453        };
4454        let h = if let Some(height) = self.height {
4455            Some(height)
4456        } else {
4457            proposal.height.map(|ph| {
4458                ph.clamp(
4459                    self.min_height.unwrap_or(0.0),
4460                    self.max_height.unwrap_or(f32::INFINITY),
4461                )
4462            })
4463        };
4464        SizeProposal {
4465            width: w,
4466            height: h,
4467        }
4468    }
4469
4470    /// Constraints and transforms the child's resulting size to fit the frame's bounds.
4471    fn transform_size(&self, child_size: Size) -> Size {
4472        let w = if let Some(width) = self.width {
4473            width
4474        } else {
4475            child_size.width.clamp(
4476                self.min_width.unwrap_or(0.0),
4477                self.max_width.unwrap_or(f32::INFINITY),
4478            )
4479        };
4480        let h = if let Some(height) = self.height {
4481            height
4482        } else {
4483            child_size.height.clamp(
4484                self.min_height.unwrap_or(0.0),
4485                self.max_height.unwrap_or(f32::INFINITY),
4486            )
4487        };
4488        Size {
4489            width: w,
4490            height: h,
4491        }
4492    }
4493
4494    /// Renders the frame's child view aligned within the layout rect.
4495    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4496        self.render(renderer, rect);
4497        let child_proposal =
4498            self.transform_proposal(SizeProposal::new(Some(rect.width), Some(rect.height)));
4499        let child_size = view.intrinsic_size(renderer, child_proposal);
4500
4501        let mut child_x = rect.x;
4502        let mut child_y = rect.y;
4503
4504        match self.alignment {
4505            Alignment::Leading => {
4506                child_y = rect.y + (rect.height - child_size.height) / 2.0;
4507            }
4508            Alignment::Trailing => {
4509                child_x = rect.x + rect.width - child_size.width;
4510                child_y = rect.y + (rect.height - child_size.height) / 2.0;
4511            }
4512            Alignment::Top => {
4513                child_x = rect.x + (rect.width - child_size.width) / 2.0;
4514            }
4515            Alignment::Bottom => {
4516                child_x = rect.x + (rect.width - child_size.width) / 2.0;
4517                child_y = rect.y + rect.height - child_size.height;
4518            }
4519            Alignment::Center => {
4520                child_x = rect.x + (rect.width - child_size.width) / 2.0;
4521                child_y = rect.y + (rect.height - child_size.height) / 2.0;
4522            }
4523        }
4524
4525        let child_rect = Rect {
4526            x: child_x,
4527            y: child_y,
4528            width: child_size.width,
4529            height: child_size.height,
4530        };
4531
4532        view.render(renderer, child_rect);
4533        self.post_render(renderer, rect);
4534    }
4535}
4536
4537/// Modifier to set the flex weight of a view
4538#[derive(Debug, Clone, Copy, PartialEq)]
4539pub struct FlexModifier {
4540    pub weight: f32,
4541}
4542
4543impl ViewModifier for FlexModifier {
4544    fn modify<V: View>(self, content: V) -> impl View {
4545        ModifiedView::new(content, self)
4546    }
4547
4548    fn child_flex_weight<V: View>(&self, _view: &V) -> f32 {
4549        self.weight
4550    }
4551}
4552
4553/// Modifier that specifies the column and row placement of a view inside a Grid layout.
4554#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4555pub struct GridPlacementModifier {
4556    /// The grid placement settings containing column/row indexes and spans.
4557    pub placement: GridPlacement,
4558}
4559
4560impl ViewModifier for GridPlacementModifier {
4561    /// Wraps the child view in a ModifiedView using this modifier.
4562    fn modify<V: View>(self, content: V) -> impl View {
4563        ModifiedView::new(content, self)
4564    }
4565
4566    /// Exposes the grid placement metadata to parent layout engines.
4567    fn get_grid_placement(&self) -> Option<GridPlacement> {
4568        Some(self.placement)
4569    }
4570}
4571
4572/// Modifier to render a popover, tooltip, or menu view overlaying an anchored view.
4573/// It supports alignment positioning and outside-click dismissal.
4574#[derive(Clone)]
4575pub struct OverlayModifier {
4576    /// The overlay content view.
4577    pub overlay: AnyView,
4578    /// Where the overlay is aligned relative to the anchored view.
4579    pub alignment: Alignment,
4580    /// Additional offset in logical pixels.
4581    pub offset: [f32; 2],
4582    /// Optional dismissal callback triggered by click-outside events.
4583    pub on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
4584}
4585
4586impl ViewModifier for OverlayModifier {
4587    /// Wraps the child view in a ModifiedView using this overlay modifier.
4588    fn modify<V: View>(self, content: V) -> impl View {
4589        ModifiedView::new(content, self)
4590    }
4591
4592    /// Renders the overlay content positioned above the child view.
4593    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4594        // 1. Render primary anchored view
4595        view.render(renderer, rect);
4596
4597        // 2. Measure overlay content
4598        let overlay_size = self
4599            .overlay
4600            .intrinsic_size(renderer, SizeProposal::unspecified());
4601
4602        // 3. Align overlay rect relative to anchored rect
4603        let mut overlay_x;
4604        let mut overlay_y;
4605
4606        match self.alignment {
4607            Alignment::Leading => {
4608                overlay_x = rect.x - overlay_size.width;
4609                overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4610            }
4611            Alignment::Trailing => {
4612                overlay_x = rect.x + rect.width;
4613                overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4614            }
4615            Alignment::Top => {
4616                overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4617                overlay_y = rect.y - overlay_size.height;
4618            }
4619            Alignment::Bottom => {
4620                overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4621                overlay_y = rect.y + rect.height;
4622            }
4623            Alignment::Center => {
4624                overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4625                overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4626            }
4627        }
4628
4629        overlay_x += self.offset[0];
4630        overlay_y += self.offset[1];
4631
4632        let overlay_rect = Rect {
4633            x: overlay_x,
4634            y: overlay_y,
4635            width: overlay_size.width,
4636            height: overlay_size.height,
4637        };
4638
4639        // 4. Handle click-outside dismissal
4640        if let Some(on_dismiss) = &self.on_dismiss {
4641            let dismiss = on_dismiss.clone();
4642            renderer.register_handler(
4643                "pointerdown",
4644                Arc::new(move |event| {
4645                    if let Event::PointerDown { x, y, .. } = event {
4646                        let click_inside = x >= overlay_rect.x
4647                            && x <= overlay_rect.x + overlay_rect.width
4648                            && y >= overlay_rect.y
4649                            && y <= overlay_rect.y + overlay_rect.height;
4650                        if !click_inside {
4651                            dismiss();
4652                        }
4653                    }
4654                }),
4655            );
4656        }
4657
4658        // 5. Render overlay view
4659        self.overlay.render(renderer, overlay_rect);
4660    }
4661}
4662
4663/// Modifier to offset a view
4664#[derive(Debug, Clone, Copy, PartialEq)]
4665pub struct OffsetModifier {
4666    pub x: f32,
4667    pub y: f32,
4668}
4669
4670impl OffsetModifier {
4671    pub fn new(x: f32, y: f32) -> Self {
4672        Self { x, y }
4673    }
4674}
4675
4676impl ViewModifier for OffsetModifier {
4677    fn modify<V: View>(self, content: V) -> impl View {
4678        ModifiedView::new(content, self)
4679    }
4680}
4681
4682/// Modifier to set the z-index of a view
4683#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4684pub struct ZIndexModifier {
4685    pub z_index: i32,
4686}
4687
4688impl ZIndexModifier {
4689    pub fn new(z_index: i32) -> Self {
4690        Self { z_index }
4691    }
4692}
4693
4694impl ViewModifier for ZIndexModifier {
4695    fn modify<V: View>(self, content: V) -> impl View {
4696        ModifiedView::new(content, self)
4697    }
4698}
4699
4700/// Layout constraints for views
4701#[derive(Debug, Clone, Copy, PartialEq, Default)]
4702pub struct LayoutConstraints {
4703    pub min_width: Option<f32>,
4704    pub max_width: Option<f32>,
4705    pub min_height: Option<f32>,
4706    pub max_height: Option<f32>,
4707}
4708
4709/// Modifier to set layout constraints
4710#[derive(Debug, Clone, Copy, PartialEq)]
4711pub struct LayoutModifier {
4712    pub constraints: LayoutConstraints,
4713}
4714
4715impl LayoutModifier {
4716    pub fn new(constraints: LayoutConstraints) -> Self {
4717        Self { constraints }
4718    }
4719}
4720
4721impl ViewModifier for LayoutModifier {
4722    fn modify<V: View>(self, content: V) -> impl View {
4723        ModifiedView::new(content, self)
4724    }
4725}
4726
4727/// Modifier to handle platform safe areas
4728#[derive(Debug, Clone, Copy, PartialEq)]
4729pub struct SafeAreaModifier {
4730    pub ignores: bool,
4731}
4732
4733impl ViewModifier for SafeAreaModifier {
4734    fn modify<V: View>(self, content: V) -> impl View {
4735        ModifiedView::new(content, self)
4736    }
4737}
4738
4739/// Modifier to add elevation (shadow) to a view
4740#[derive(Debug, Clone, Copy, PartialEq)]
4741pub struct ElevationModifier {
4742    pub level: f32,
4743}
4744
4745impl ViewModifier for ElevationModifier {
4746    fn modify<V: View>(self, content: V) -> impl View {
4747        ModifiedView::new(content, self)
4748    }
4749
4750    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4751        if self.level > 0.0 {
4752            let radius = self.level * 2.0;
4753            let offset_y = self.level * 0.5;
4754            let shadow_color = [0.0, 0.0, 0.0, 0.3];
4755            renderer.push_shadow(radius, shadow_color, [0.0, offset_y]);
4756            view.render(renderer, rect);
4757            renderer.pop_shadow();
4758        } else {
4759            view.render(renderer, rect);
4760        }
4761    }
4762}
4763
4764// Layout subsystem
4765pub mod layout {
4766    use super::*;
4767
4768    /// Key used to identify a cached layout entry.
4769    /// Combines a view hash with a generation counter for cache invalidation.
4770    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4771    pub struct LayoutKey {
4772        pub view_hash: u64,
4773        pub generation: u64,
4774    }
4775
4776    // Layout pass scratch space
4777    pub struct LayoutCache {
4778        pub safe_area: SafeArea,
4779        size_cache: HashMap<(u64, u32, u32), Size>, // (ViewHash, ProposalW, ProposalH)
4780        /// Monotonically increasing generation counter for cache invalidation.
4781        /// When a view tree changes, bumping the generation causes stale entries
4782        /// to be treated as invalid without eagerly clearing the entire cache.
4783        generation: u64,
4784    }
4785
4786    impl Default for LayoutCache {
4787        fn default() -> Self {
4788            Self::new()
4789        }
4790    }
4791
4792    impl LayoutCache {
4793        pub fn new() -> Self {
4794            Self {
4795                safe_area: SafeArea::default(),
4796                size_cache: HashMap::new(),
4797                generation: 0,
4798            }
4799        }
4800
4801        /// Returns the current generation counter.
4802        pub fn generation(&self) -> u64 {
4803            self.generation
4804        }
4805
4806        /// Bump the generation counter, logically invalidating all cached entries
4807        /// without eagerly clearing them. Subsequent lookups with the old generation
4808        /// will miss until re-populated.
4809        pub fn invalidate(&mut self) {
4810            self.generation = self.generation.wrapping_add(1);
4811        }
4812
4813        /// Check whether a cached entry for the given key is still valid
4814        /// against the current generation.
4815        pub fn is_valid(&self, key: LayoutKey, current_gen: u64) -> bool {
4816            key.generation == current_gen && key.generation == self.generation
4817        }
4818
4819        pub fn clear(&mut self) {
4820            self.safe_area = SafeArea::default();
4821            self.size_cache.clear();
4822        }
4823
4824        pub fn get_size(&self, view_hash: u64, proposal: SizeProposal) -> Option<Size> {
4825            let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
4826            let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
4827            self.size_cache.get(&(view_hash, pw, ph)).copied()
4828        }
4829
4830        pub fn set_size(&mut self, view_hash: u64, proposal: SizeProposal, size: Size) {
4831            let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
4832            let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
4833            self.size_cache.insert((view_hash, pw, ph), size);
4834        }
4835
4836        /// Remove all cached size entries for a specific view hash.
4837        pub fn invalidate_view(&mut self, view_hash: u64) {
4838            self.size_cache.retain(|&(hash, _, _), _| hash != view_hash);
4839        }
4840    }
4841
4842    /// Proposed size from parent view
4843    #[derive(Debug, Clone, Copy, PartialEq)]
4844    pub struct SizeProposal {
4845        pub width: Option<f32>,
4846        pub height: Option<f32>,
4847    }
4848
4849    impl SizeProposal {
4850        pub fn unspecified() -> Self {
4851            Self {
4852                width: None,
4853                height: None,
4854            }
4855        }
4856
4857        pub fn width(width: f32) -> Self {
4858            Self {
4859                width: Some(width),
4860                height: None,
4861            }
4862        }
4863
4864        pub fn height(height: f32) -> Self {
4865            Self {
4866                width: None,
4867                height: Some(height),
4868            }
4869        }
4870
4871        pub fn tight(width: f32, height: f32) -> Self {
4872            Self {
4873                width: Some(width),
4874                height: Some(height),
4875            }
4876        }
4877
4878        pub fn new(width: Option<f32>, height: Option<f32>) -> Self {
4879            Self { width, height }
4880        }
4881    }
4882
4883    /// A view that can participate in layout
4884    pub trait LayoutView: Send {
4885        /// Propose a size for this view given the available space
4886        fn size_that_fits(
4887            &self,
4888            proposal: SizeProposal,
4889            subviews: &[&dyn LayoutView],
4890            cache: &mut LayoutCache,
4891        ) -> Size;
4892
4893        /// Place subviews within the given bounds
4894        fn place_subviews(
4895            &self,
4896            bounds: Rect,
4897            subviews: &mut [&mut dyn LayoutView],
4898            cache: &mut LayoutCache,
4899        );
4900
4901        /// Returns the flex weight of this view (default is 0.0, which means fixed/intrinsic)
4902        fn flex_weight(&self) -> f32 {
4903            0.0
4904        }
4905
4906        /// Return a debug representation of this layout subtree.
4907        /// The `indent` parameter controls the indentation level for nested display.
4908        fn debug_layout(&self, indent: usize) -> String {
4909            let prefix = " ".repeat(indent);
4910            format!("{}LayoutView", prefix)
4911        }
4912    }
4913    /// Edge insets for padding, margins, and safe areas
4914    #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
4915    pub struct EdgeInsets {
4916        pub top: f32,
4917        pub leading: f32,
4918        pub bottom: f32,
4919        pub trailing: f32,
4920    }
4921
4922    impl EdgeInsets {
4923        pub fn new(top: f32, leading: f32, bottom: f32, trailing: f32) -> Self {
4924            Self {
4925                top,
4926                leading,
4927                bottom,
4928                trailing,
4929            }
4930        }
4931
4932        pub fn all(value: f32) -> Self {
4933            Self {
4934                top: value,
4935                leading: value,
4936                bottom: value,
4937                trailing: value,
4938            }
4939        }
4940    }
4941
4942    /// SafeArea constraints provided by the platform
4943    #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
4944    pub struct SafeArea {
4945        pub insets: EdgeInsets,
4946    }
4947
4948    /// SDF Shape definitions for Vili Interaction Paradigm hit-testing.
4949    #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
4950    pub enum SdfShape {
4951        Rect(Rect),
4952        RoundedRect { rect: Rect, radius: f32 },
4953        Circle { center: [f32; 2], radius: f32 },
4954    }
4955
4956    /// Rectangle in logical pixels
4957    #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
4958    pub struct Rect {
4959        pub x: f32,
4960        pub y: f32,
4961        pub width: f32,
4962        pub height: f32,
4963    }
4964
4965    impl Rect {
4966        pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
4967            Self {
4968                x,
4969                y,
4970                width,
4971                height,
4972            }
4973        }
4974
4975        pub fn inset(&self, amount: f32) -> Self {
4976            Self {
4977                x: self.x + amount,
4978                y: self.y + amount,
4979                width: (self.width - amount * 2.0).max(0.0),
4980                height: (self.height - amount * 2.0).max(0.0),
4981            }
4982        }
4983
4984        pub fn offset(&self, dx: f32, dy: f32) -> Self {
4985            Self {
4986                x: self.x + dx,
4987                y: self.y + dy,
4988                ..*self
4989            }
4990        }
4991
4992        pub fn zero() -> Self {
4993            Self {
4994                x: 0.0,
4995                y: 0.0,
4996                width: 0.0,
4997                height: 0.0,
4998            }
4999        }
5000
5001        pub fn contains(&self, x: f32, y: f32) -> bool {
5002            x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
5003        }
5004
5005        pub fn size(&self) -> Size {
5006            Size {
5007                width: self.width,
5008                height: self.height,
5009            }
5010        }
5011
5012        /// Split the rect horizontally into N equal pieces
5013        pub fn split_horizontal(&self, n: usize) -> Vec<Rect> {
5014            if n == 0 {
5015                return vec![];
5016            }
5017            let item_width = self.width / n as f32;
5018            (0..n)
5019                .map(|i| Rect {
5020                    x: self.x + i as f32 * item_width,
5021                    y: self.y,
5022                    width: item_width,
5023                    height: self.height,
5024                })
5025                .collect()
5026        }
5027
5028        /// Split the rect vertically into N equal pieces
5029        pub fn split_vertical(&self, n: usize) -> Vec<Rect> {
5030            if n == 0 {
5031                return vec![];
5032            }
5033            let item_height = self.height / n as f32;
5034            (0..n)
5035                .map(|i| Rect {
5036                    x: self.x,
5037                    y: self.y + i as f32 * item_height,
5038                    width: self.width,
5039                    height: item_height,
5040                })
5041                .collect()
5042        }
5043    }
5044}
5045
5046// Re-export layout items for convenience
5047pub use layout::{LayoutCache, LayoutKey, LayoutView, Rect, SizeProposal};
5048// Size and FrameRenderer are pub items in this module; no re-export alias needed.
5049
5050pub mod agents;
5051pub mod animation;
5052pub mod gpu;
5053pub mod material;
5054pub mod runtime;
5055pub mod scene_graph;
5056pub mod sdf_shadow;
5057
5058pub use material::DrawMaterial;
5059pub use scene_graph::{NodeId, bifrost_registry};
5060
5061// Duplicate AssetState removed - original definition at line 67
5062
5063/// AssetManager defines the interface for loading and caching external resources.
5064pub trait AssetManager: Send + Sync {
5065    /// Request an image asset. Returns the current state (Loading, Ready, or Error).
5066    fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>>;
5067
5068    /// Pre-load an image into the cache.
5069    fn preload_image(&self, url: &str);
5070}
5071
5072/// The phase of a touch or gesture event in its lifecycle.
5073#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5074pub enum TouchPhase {
5075    /// The touch/gesture has just begun.
5076    Began,
5077    /// The touch/gesture is moving.
5078    Moved,
5079    /// The touch/gesture has ended normally.
5080    Ended,
5081    /// The touch/gesture was cancelled (e.g., by the system).
5082    Cancelled,
5083}
5084
5085/// User input event types
5086#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
5087pub enum Event {
5088    PointerDown {
5089        x: f32,
5090        y: f32,
5091        button: u32,
5092        proximity_field: f32,
5093    },
5094    PointerUp {
5095        x: f32,
5096        y: f32,
5097        button: u32,
5098    },
5099    PointerMove {
5100        x: f32,
5101        y: f32,
5102        proximity_field: f32,
5103    },
5104    PointerClick {
5105        x: f32,
5106        y: f32,
5107        button: u32,
5108    },
5109    PointerEnter,
5110    PointerLeave,
5111    /// Mouse wheel / trackpad scroll event.
5112    /// `delta_x` is the horizontal scroll amount, `delta_y` is the vertical scroll amount (positive = scroll down).
5113    PointerWheel {
5114        x: f32,
5115        y: f32,
5116        delta_x: f32,
5117        delta_y: f32,
5118    },
5119    /// Double-click event (rapid successive clicks).
5120    PointerDoubleClick {
5121        x: f32,
5122        y: f32,
5123        button: u32,
5124    },
5125    /// Drag-and-drop: drag started (pointer moved while button held past threshold).
5126    DragStart {
5127        x: f32,
5128        y: f32,
5129        button: u32,
5130    },
5131    /// Drag-and-drop: drag in progress.
5132    DragMove {
5133        x: f32,
5134        y: f32,
5135    },
5136    /// Drag-and-drop: drag ended (pointer released).
5137    DragEnd {
5138        x: f32,
5139        y: f32,
5140    },
5141    KeyDown {
5142        key: String,
5143    },
5144    KeyUp {
5145        key: String,
5146    },
5147    /// Focus gained by a node.
5148    FocusIn,
5149    /// Focus lost by a node.
5150    FocusOut,
5151    /// Clipboard copy event.
5152    Copy,
5153    /// Clipboard cut event.
5154    Cut,
5155    /// Clipboard paste event with the pasted text content.
5156    Paste(String),
5157    /// Input Method Editor event (e.g. CJK character composition)
5158    Ime(String),
5159    /// Touch began at the given position.
5160    TouchStart {
5161        x: f32,
5162        y: f32,
5163        touch_id: u64,
5164    },
5165    /// Touch moved to a new position.
5166    TouchMove {
5167        x: f32,
5168        y: f32,
5169        touch_id: u64,
5170    },
5171    /// Touch ended at the given position.
5172    TouchEnd {
5173        x: f32,
5174        y: f32,
5175        touch_id: u64,
5176    },
5177    /// Touch cancelled.
5178    TouchCancel {
5179        touch_id: u64,
5180    },
5181    /// Multi-touch pinch gesture.
5182    /// `center` is the gesture anchor point in device-independent pixels.
5183    /// `scale` is the relative pinch scale (>1 = expand, <1 = contract).
5184    /// `velocity` is the instantaneous velocity of the pinch.
5185    /// `phase` indicates the current phase of the gesture lifecycle.
5186    GesturePinch {
5187        center: [f32; 2],
5188        scale: f32,
5189        velocity: f32,
5190        phase: TouchPhase,
5191    },
5192    /// Multi-touch swipe/pan gesture.
5193    /// `direction` is the normalized direction vector [dx, dy].
5194    /// `velocity` is the instantaneous velocity of the swipe.
5195    /// `phase` indicates the current phase of the gesture lifecycle.
5196    GestureSwipe {
5197        direction: [f32; 2],
5198        velocity: f32,
5199        phase: TouchPhase,
5200    },
5201    /// Drag-and-drop: external file dropped onto window.
5202    FileDrop {
5203        path: String,
5204    },
5205}
5206
5207impl Event {
5208    /// Returns the canonical string name of the event for lookup in handler maps.
5209    pub fn name(&self) -> &'static str {
5210        match self {
5211            Self::PointerDown { .. } => "pointerdown",
5212            Self::PointerUp { .. } => "pointerup",
5213            Self::PointerMove { .. } => "pointermove",
5214            Self::PointerClick { .. } => "pointerclick",
5215            Self::PointerEnter => "pointerenter",
5216            Self::PointerLeave => "pointerleave",
5217            Self::PointerWheel { .. } => "pointerwheel",
5218            Self::PointerDoubleClick { .. } => "pointerdoubleclick",
5219            Self::DragStart { .. } => "dragstart",
5220            Self::DragMove { .. } => "dragmove",
5221            Self::DragEnd { .. } => "dragend",
5222            Self::KeyDown { .. } => "keydown",
5223            Self::KeyUp { .. } => "keyup",
5224            Self::FocusIn => "focusin",
5225            Self::FocusOut => "focusout",
5226            Self::Copy => "copy",
5227            Self::Cut => "cut",
5228            Self::Paste(_) => "paste",
5229            Self::Ime(_) => "ime",
5230            Self::TouchStart { .. } => "touchstart",
5231            Self::TouchMove { .. } => "touchmove",
5232            Self::TouchEnd { .. } => "touchend",
5233            Self::TouchCancel { .. } => "touchcancel",
5234            Self::GesturePinch { .. } => "gesturepinch",
5235            Self::GestureSwipe { .. } => "gestureswipe",
5236            Self::FileDrop { .. } => "filedrop",
5237        }
5238    }
5239}
5240
5241/// Response from an event handler
5242#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5243pub enum EventResponse {
5244    Handled,
5245    Ignored,
5246}
5247
5248/// A basic implementation of AssetManager that can be overridden by platform backends.
5249pub struct DefaultAssetManager {
5250    cache: AssetCache,
5251}
5252type AssetCache = Arc<arc_swap::ArcSwap<HashMap<String, AssetState<Arc<Vec<u8>>>>>>;
5253
5254impl Default for DefaultAssetManager {
5255    fn default() -> Self {
5256        Self::new()
5257    }
5258}
5259
5260impl DefaultAssetManager {
5261    pub fn new() -> Self {
5262        Self {
5263            cache: Arc::new(arc_swap::ArcSwap::from_pointee(HashMap::new())),
5264        }
5265    }
5266}
5267
5268impl AssetManager for DefaultAssetManager {
5269    fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>> {
5270        if let Some(state) = self.cache.load().get(url) {
5271            return state.clone();
5272        }
5273
5274        self.cache.rcu(|map| {
5275            let mut m = (**map).clone();
5276            m.entry(url.to_string()).or_insert(AssetState::Loading);
5277            m
5278        });
5279        AssetState::Loading
5280    }
5281
5282    fn preload_image(&self, _url: &str) {}
5283}
5284
5285use std::future::Future;
5286
5287/// Suspense wrapper for asynchronous state management.
5288/// Integrates with State<T> to provide loading/error/ready states for async operations.
5289pub struct Suspense<T: Clone + Send + Sync + 'static> {
5290    inner: State<AssetState<T>>,
5291}
5292
5293impl<T: Clone + Send + Sync + 'static> Default for Suspense<T> {
5294    fn default() -> Self {
5295        Self::new()
5296    }
5297}
5298
5299impl<T: Clone + Send + Sync + 'static> Suspense<T> {
5300    pub fn new() -> Self {
5301        Self {
5302            inner: State::new(AssetState::Loading),
5303        }
5304    }
5305
5306    pub fn new_async<F>(future: F) -> Self
5307    where
5308        F: Future<Output = Result<T, String>> + Send + 'static,
5309    {
5310        let suspense = Self::new();
5311        let suspense_clone = suspense.clone();
5312
5313        #[cfg(not(target_arch = "wasm32"))]
5314        {
5315            // Try to use an existing tokio runtime, or fallback to a dedicated thread
5316            if let Ok(handle) = tokio::runtime::Handle::try_current() {
5317                handle.spawn(async move {
5318                    let result = future.await;
5319                    match result {
5320                        Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5321                        Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5322                    }
5323                });
5324            } else {
5325                std::thread::spawn(move || {
5326                    let rt = tokio::runtime::Builder::new_current_thread()
5327                        .enable_all()
5328                        .build()
5329                        .unwrap();
5330                    rt.block_on(async {
5331                        let result = future.await;
5332                        match result {
5333                            Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5334                            Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5335                        }
5336                    });
5337                });
5338            }
5339        }
5340        #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5341        {
5342            wasm_bindgen_futures::spawn_local(async move {
5343                let result = future.await;
5344                match result {
5345                    Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5346                    Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5347                }
5348            });
5349        }
5350
5351        suspense
5352    }
5353
5354    pub fn ready(value: T) -> Self {
5355        Self {
5356            inner: State::new(AssetState::Ready(value)),
5357        }
5358    }
5359
5360    pub fn error(message: impl Into<String>) -> Self {
5361        Self {
5362            inner: State::new(AssetState::Error(message.into())),
5363        }
5364    }
5365
5366    pub fn get(&self) -> AssetState<T> {
5367        self.inner.get()
5368    }
5369
5370    pub fn get_ref(&self) -> AssetState<T> {
5371        self.inner.get()
5372    }
5373
5374    pub fn is_loading(&self) -> bool {
5375        matches!(self.get(), AssetState::Loading)
5376    }
5377
5378    pub fn is_ready(&self) -> bool {
5379        matches!(self.get(), AssetState::Ready(_))
5380    }
5381
5382    pub fn is_error(&self) -> bool {
5383        matches!(self.get(), AssetState::Error(_))
5384    }
5385
5386    pub fn ready_value(&self) -> Option<T> {
5387        match self.get() {
5388            AssetState::Ready(value) => Some(value),
5389            _ => None,
5390        }
5391    }
5392
5393    pub fn error_message(&self) -> Option<String> {
5394        match self.get() {
5395            AssetState::Error(message) => Some(message),
5396            _ => None,
5397        }
5398    }
5399
5400    pub fn subscribe<F: Fn(&AssetState<T>) + Send + Sync + 'static>(&self, callback: F) {
5401        self.inner.subscribe(callback)
5402    }
5403
5404    pub fn inner_state(&self) -> &State<AssetState<T>> {
5405        &self.inner
5406    }
5407}
5408
5409impl<T: Clone + Send + Sync + 'static> Clone for Suspense<T> {
5410    fn clone(&self) -> Self {
5411        Self {
5412            inner: self.inner.clone(),
5413        }
5414    }
5415}
5416
5417impl<T: Clone + Send + Sync + 'static> From<T> for Suspense<T> {
5418    fn from(value: T) -> Self {
5419        Self::ready(value)
5420    }
5421}
5422
5423impl<T: Clone + Send + Sync + 'static> From<Result<T, String>> for Suspense<T> {
5424    fn from(result: Result<T, String>) -> Self {
5425        match result {
5426            Ok(value) => Self::ready(value),
5427            Err(error) => Self::error(error),
5428        }
5429    }
5430}
5431
5432#[cfg(test)]
5433mod phase1_test;
5434
5435/// Berserker mode states for the rendering pipeline.
5436#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5437pub enum BerserkerMode {
5438    Normal,
5439    Rage,    // Red tint, slight shake
5440    Frenzy,  // Heavy red tint, motion blur, aggressive shake
5441    GodMode, // Golden aura, lightning arcs
5442}
5443
5444/// Seer trait for AI-assisted UI components.
5445/// Allows components to receive "prophecies" (predictions) from an AI backend.
5446pub trait Seer: Send + Sync {
5447    /// Provide a prediction for the next user action or content.
5448    fn predict(&self, context: &str) -> String;
5449    /// Stream real-time "whispers" (transcriptions/intent).
5450    fn whispers(&self) -> Vec<String>;
5451}
5452
5453#[cfg(test)]
5454mod vili_tests {
5455    use super::*;
5456
5457    struct DummyRenderer;
5458    impl ElapsedTime for DummyRenderer {
5459        fn elapsed_time(&self) -> f32 {
5460            0.0
5461        }
5462        fn delta_time(&self) -> f32 {
5463            0.0
5464        }
5465    }
5466    impl Renderer for DummyRenderer {
5467        fn fill_rect(&mut self, _r: Rect, _c: [f32; 4]) {}
5468        fn fill_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4]) {}
5469        fn fill_ellipse(&mut self, _r: Rect, _c: [f32; 4]) {}
5470        fn stroke_rect(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5471        fn stroke_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4], _w: f32) {}
5472        fn stroke_ellipse(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5473        fn draw_line(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, _c: [f32; 4], _w: f32) {}
5474        fn draw_text(&mut self, _t: &str, _x: f32, _y: f32, _s: f32, _c: [f32; 4]) {}
5475        fn measure_text(&mut self, _t: &str, _s: f32) -> (f32, f32) {
5476            (0.0, 0.0)
5477        }
5478        fn memoize(&mut self, _id: u64, _hash: u64, _r: &dyn Fn(&mut dyn Renderer)) {}
5479    }
5480
5481    #[test]
5482    fn test_magnetic_warp() {
5483        let renderer = DummyRenderer;
5484        let anchor = Rect {
5485            x: 100.0,
5486            y: 100.0,
5487            width: 50.0,
5488            height: 50.0,
5489        };
5490        // Pointer is near the anchor (distance < 120)
5491        let pointer = [125.0, 50.0];
5492        // distance from center (125, 125) is 75.
5493        // force = (1.0 - 75/120) * strength
5494        let warp = renderer.magnetic_warp(pointer, anchor, 1.0);
5495        // It should pull closer to (125, 125), so Y should be > 50
5496        assert!(warp[1] > 50.0);
5497
5498        // Out of range pointer should remain unchanged
5499        let far_pointer = [500.0, 500.0];
5500        let far_warp = renderer.magnetic_warp(far_pointer, anchor, 1.0);
5501        assert_eq!(far_pointer, far_warp);
5502    }
5503
5504    #[test]
5505    fn test_mani_glow() {
5506        let renderer = DummyRenderer;
5507        let bounds = Rect {
5508            x: 0.0,
5509            y: 0.0,
5510            width: 100.0,
5511            height: 100.0,
5512        };
5513        let pointer_inside = [50.0, 50.0];
5514        let glow_max = renderer.mani_glow_intensity(pointer_inside, bounds, 120.0);
5515        assert_eq!(glow_max, 1.0);
5516
5517        let pointer_edge = [50.0, -10.0];
5518        let glow_partial = renderer.mani_glow_intensity(pointer_edge, bounds, 120.0);
5519        assert!(glow_partial > 0.0 && glow_partial < 1.0);
5520    }
5521
5522    #[test]
5523    fn test_fafnir_evolve() {
5524        let renderer = DummyRenderer;
5525        let bounds = Rect {
5526            x: 0.0,
5527            y: 0.0,
5528            width: 100.0,
5529            height: 100.0,
5530        };
5531        let pointer_inside = [50.0, 50.0];
5532        let scale = renderer.fafnir_evolve(pointer_inside, bounds, 1.2);
5533        assert_eq!(scale, 1.2); // Full scale when hovering center
5534    }
5535
5536    #[test]
5537    fn test_undo_manager_basic() {
5538        let mut manager = UndoManager::new(3, 0.5);
5539        let val = std::sync::Arc::new(std::sync::Mutex::new(0));
5540
5541        let v1 = val.clone();
5542        let v2 = val.clone();
5543        manager.push(
5544            "Add",
5545            move || *v1.lock().unwrap() -= 1,
5546            move || *v2.lock().unwrap() += 1,
5547        );
5548
5549        assert!(manager.can_undo());
5550        assert!(!manager.can_redo());
5551
5552        let undo = manager.undo().unwrap();
5553        undo();
5554        assert_eq!(*val.lock().unwrap(), -1);
5555        assert!(!manager.can_undo());
5556        assert!(manager.can_redo());
5557
5558        let redo = manager.redo().unwrap();
5559        redo();
5560        assert_eq!(*val.lock().unwrap(), 0);
5561    }
5562
5563    #[test]
5564    fn test_undo_manager_depth_limit() {
5565        let mut manager = UndoManager::new(2, 0.5);
5566        manager.push("1", || {}, || {});
5567        manager.push("2", || {}, || {});
5568        manager.push("3", || {}, || {});
5569
5570        assert_eq!(manager.stack.len(), 2);
5571        assert_eq!(manager.position, 2);
5572    }
5573
5574    #[test]
5575    fn test_undo_manager_coalescing() {
5576        let mut manager = UndoManager::new(10, 1.0);
5577        let count = std::sync::Arc::new(std::sync::Mutex::new(0));
5578
5579        let c = count.clone();
5580        manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5581
5582        let c = count.clone();
5583        manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5584
5585        assert_eq!(manager.stack.len(), 1);
5586
5587        let undo = manager.undo().unwrap();
5588        undo();
5589        assert_eq!(*count.lock().unwrap(), -2);
5590    }
5591}
5592
5593// =============================================================================
5594// THEME CONTEXT — Thread-local theme access for components
5595// =============================================================================
5596//
5597// Components call `use_theme()` to get the current SemanticColors.
5598// The native renderer sets this via `set_current_theme()` before each frame.
5599// Falls back to dark theme defaults if no theme has been set.
5600//
5601// We store SemanticColors directly (not the full Theme) to avoid depending
5602// on cvkg-themes from cvkg-core. The colors are cloned into thread-local storage.
5603
5604use std::cell::RefCell;
5605
5606thread_local! {
5607    /// Thread-local semantic colors for the current frame.
5608    static THEME_CONTEXT: RefCell<Option<color::SemanticColors>> = const { RefCell::new(None) };
5609}
5610
5611/// Semantic colors extracted from the theme for use by components.
5612/// This is a standalone type defined in cvkg-core so cvkg-components
5613/// can use it without depending on cvkg-themes.
5614///
5615/// Components should access these via `use_theme()` rather than hardcoding RGBA.
5616pub use color::SemanticColors;
5617
5618/// Set the current semantic colors for this thread.
5619/// Called by the native renderer before each frame.
5620pub fn set_current_theme(colors: color::SemanticColors) {
5621    THEME_CONTEXT.with(|cell| {
5622        *cell.borrow_mut() = Some(colors);
5623    });
5624}
5625
5626/// Clear the current theme. Called after each frame.
5627pub fn clear_current_theme() {
5628    THEME_CONTEXT.with(|cell| {
5629        *cell.borrow_mut() = None;
5630    });
5631}
5632
5633/// Access the current semantic colors from within a component's `render()` method.
5634///
5635/// Returns the colors set by the most recent `set_current_theme()` call.
5636/// Falls back to dark theme defaults if no theme has been set.
5637///
5638/// # Example
5639/// ```no_run
5640/// use cvkg_core::{use_theme, Renderer, Rect};
5641///
5642/// fn render_button(renderer: &mut dyn Renderer, rect: Rect) {
5643///     let colors = use_theme();
5644///     renderer.fill_rounded_rect(rect, 8.0, [colors.accent.r, colors.accent.g, colors.accent.b, colors.accent.a]);
5645/// }
5646/// ```
5647pub fn use_theme() -> color::SemanticColors {
5648    THEME_CONTEXT.with(|cell| {
5649        cell.borrow()
5650            .clone()
5651            .unwrap_or_else(color::SemanticColors::dark)
5652    })
5653}
5654
5655// =============================================================================
5656// COLOR MODULE — Standalone semantic colors type
5657// =============================================================================
5658//
5659// This module provides `SemanticColors`, a self-contained color palette that
5660// components can use without depending on `cvkg-themes`. The `use_theme()`
5661// function returns the current `SemanticColors` from thread-local storage.
5662
5663pub mod color {
5664    use super::Color;
5665
5666    /// A complete set of semantic colors for UI components.
5667    ///
5668    /// Each color serves a specific role in the UI. Components should reference
5669    /// these semantic roles rather than hardcoding RGBA values.
5670    ///
5671    /// # Example
5672    /// ```no_run
5673    /// use cvkg_core::{use_theme, Renderer, Rect};
5674    ///
5675    /// fn render_button(renderer: &mut dyn Renderer, rect: Rect) {
5676    ///     let colors = use_theme();
5677    ///     // Use accent color for the button background
5678    ///     renderer.fill_rounded_rect(rect, 8.0,
5679    ///         [colors.accent.r, colors.accent.g, colors.accent.b, colors.accent.a]);
5680    /// }
5681    /// ```
5682    #[derive(Debug, Clone)]
5683    pub struct SemanticColors {
5684        /// Primary brand color — used for key interactive elements.
5685        pub primary: Color,
5686        /// Secondary color — used for less prominent interactive elements.
5687        pub secondary: Color,
5688        /// Accent color — used for highlights, focus rings, CTAs.
5689        pub accent: Color,
5690        /// Page/window background color.
5691        pub background: Color,
5692        /// Surface color — used for cards, panels, sheets.
5693        pub surface: Color,
5694        /// Error color — used for destructive actions, error messages.
5695        pub error: Color,
5696        /// Warning color — used for caution indicators.
5697        pub warning: Color,
5698        /// Success color — used for positive feedback.
5699        pub success: Color,
5700        /// Primary text color.
5701        pub text: Color,
5702        /// Dimmed/disabled text color.
5703        pub text_dim: Color,
5704    }
5705
5706    impl SemanticColors {
5707        /// Dark theme semantic colors (default fallback).
5708        pub fn dark() -> Self {
5709            Self {
5710                primary: Color::new(1.0, 0.84, 0.0, 1.0),      // Viking Gold
5711                secondary: Color::new(1.0, 0.0, 1.0, 1.0),     // Magenta Liquid
5712                accent: Color::new(1.0, 0.0, 0.4, 1.0),        // Crimson Flash
5713                background: Color::new(0.02, 0.02, 0.05, 1.0), // Deep Void
5714                surface: Color::new(0.05, 0.05, 0.07, 1.0),    // Tactical Obsidian
5715                error: Color::new(1.0, 0.2, 0.2, 1.0),         // Red
5716                warning: Color::new(1.0, 0.8, 0.0, 1.0),       // Yellow
5717                success: Color::new(0.0, 1.0, 0.5, 1.0),       // Green
5718                text: Color::new(0.95, 0.95, 1.0, 1.0),        // Near-white
5719                text_dim: Color::new(0.6, 0.6, 0.7, 1.0),      // Gray
5720            }
5721        }
5722
5723        /// Light theme semantic colors.
5724        pub fn light() -> Self {
5725            Self {
5726                primary: Color::new(0.35, 0.30, 0.70, 1.0),
5727                secondary: Color::new(0.30, 0.50, 0.30, 1.0),
5728                accent: Color::new(0.30, 0.35, 0.75, 1.0),
5729                background: Color::new(0.97, 0.97, 0.98, 1.0),
5730                surface: Color::new(0.93, 0.93, 0.95, 1.0),
5731                error: Color::new(0.75, 0.15, 0.15, 1.0),
5732                warning: Color::new(0.80, 0.60, 0.0, 1.0),
5733                success: Color::new(0.15, 0.65, 0.30, 1.0),
5734                text: Color::new(0.08, 0.08, 0.10, 1.0),
5735                text_dim: Color::new(0.40, 0.40, 0.45, 1.0),
5736            }
5737        }
5738
5739        /// Convert the accent color semantic color into interactive state colors.
5740        ///
5741        /// This provides hover/active/focus/disabled variants derived from the
5742        /// accent color, matching the pattern that `cvkg-themes::StateColors` uses.
5743        pub fn accent_states(&self) -> InteractiveColorStates {
5744            InteractiveColorStates::from_color(self.accent)
5745        }
5746
5747        /// Convert the primary color into interactive state colors.
5748        pub fn primary_states(&self) -> InteractiveColorStates {
5749            InteractiveColorStates::from_color(self.primary)
5750        }
5751
5752        /// Convert the error color into interactive state colors.
5753        pub fn error_states(&self) -> InteractiveColorStates {
5754            InteractiveColorStates::from_color(self.error)
5755        }
5756
5757        /// Convert the success color into interactive state colors.
5758        pub fn success_states(&self) -> InteractiveColorStates {
5759            InteractiveColorStates::from_color(self.success)
5760        }
5761    }
5762
5763    /// Interactive state colors derived from a single base color.
5764    ///
5765    /// Provides hover/active/focus/disabled variants for any color,
5766    /// derived via simple lightness adjustments in sRGB space.
5767    #[derive(Debug, Clone)]
5768    pub struct InteractiveColorStates {
5769        pub default: Color,
5770        pub hover: Color,
5771        pub active: Color,
5772        pub focus: Color,
5773        pub disabled: Color,
5774        pub focus_ring: Color,
5775    }
5776
5777    impl InteractiveColorStates {
5778        /// Derive interactive state colors from a base sRGB color.
5779        ///
5780        /// Uses simple lightness adjustments:
5781        /// - Hover: +15% lightness
5782        /// - Active: -15% lightness
5783        /// - Focus: same as default
5784        /// - Disabled: 40% opacity
5785        /// - Focus ring: base color at 70% opacity
5786        pub fn from_color(base: Color) -> Self {
5787            Self {
5788                default: base,
5789                hover: base.lighten(0.15),
5790                active: base.darken(0.15),
5791                focus: base,
5792                disabled: Color::new(base.r, base.g, base.b, base.a * 0.4),
5793                focus_ring: Color::new(base.r, base.g, base.b, base.a * 0.7),
5794            }
5795        }
5796
5797        /// Get the color for a specific interactive state.
5798        pub fn color_for(&self, state: InteractiveState) -> Color {
5799            match state {
5800                InteractiveState::Default => self.default,
5801                InteractiveState::Hover => self.hover,
5802                InteractiveState::Active => self.active,
5803                InteractiveState::Focus => self.focus,
5804                InteractiveState::Disabled => self.disabled,
5805            }
5806        }
5807    }
5808
5809    /// Interactive state for a component.
5810    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5811    pub enum InteractiveState {
5812        Default,
5813        Hover,
5814        Active,
5815        Focus,
5816        Disabled,
5817    }
5818}
5819
5820// =============================================================================
5821// USE_STATE HOOK — Local component state with automatic re-render
5822// =============================================================================
5823//
5824// Components call `use_state(id, initial)` to get a `(getter, setter)` pair.
5825// The setter updates the global system state and triggers a re-render.
5826//
5827// This is the minimal state primitive needed for interactive components.
5828// For complex state, use the global `KnowledgeState` directly.
5829
5830/// Local state hook for components.
5831///
5832/// Returns a `(getter, setter)` pair:
5833/// - `getter()` returns the current value of type `T`
5834/// - `setter(value)` updates the value and triggers a re-render
5835///
5836/// The `id` must be unique per component instance (use a hash of the
5837/// component's label or a generated UUID).
5838pub fn use_state<T: Clone + Send + Sync + 'static>(
5839    id: u64,
5840    initial: T,
5841) -> (impl Fn() -> T, impl Fn(T)) {
5842    // Initialize the state if not already present
5843    let already_exists = load_system_state().get_component_state::<T>(id).is_some();
5844    if !already_exists {
5845        update_system_state(|s| {
5846            let mut ns = s.clone();
5847            ns.set_component_state(id, initial.clone());
5848            ns
5849        });
5850    }
5851
5852    let getter = move || -> T {
5853        load_system_state()
5854            .get_component_state::<T>(id)
5855            .map(|arc_lock| {
5856                arc_lock
5857                    .read()
5858                    .ok()
5859                    .map(|guard| (*guard).clone())
5860                    .unwrap_or_else(|| initial.clone())
5861            })
5862            .unwrap_or_else(|| initial.clone())
5863    };
5864
5865    let setter = {
5866        move |value| {
5867            update_system_state(|s| {
5868                let mut ns = s.clone();
5869                ns.set_component_state(id, value);
5870                ns
5871            });
5872        }
5873    };
5874
5875    (getter, setter)
5876}
5877
5878/// Generate a stable hash ID from a string key.
5879///
5880/// Use this to create unique IDs for `use_state` based on component labels
5881/// or other stable identifiers.
5882///
5883/// # Example
5884/// ```no_run
5885/// use cvkg_core::{use_state, use_state_hash};
5886/// let id = use_state_hash("my-checkbox");
5887/// let (value, set_value) = use_state(id, false);
5888/// ```
5889pub fn use_state_hash(key: &str) -> u64 {
5890    use std::hash::{Hash, Hasher};
5891    let mut s = std::collections::hash_map::DefaultHasher::new();
5892    key.hash(&mut s);
5893    s.finish()
5894}
5895
5896// =============================================================================
5897// ACCESSIBILITY PREFERENCES — System accessibility settings
5898// =============================================================================
5899//
5900// Components and the renderer query these to adapt behavior:
5901// - Reduce Motion: disable non-essential animations
5902// - Reduce Transparency: replace glass materials with opaque surfaces
5903// - Increase Contrast: make borders visible, minimum alpha 0.5
5904
5905thread_local! {
5906    /// Thread-local accessibility preferences.
5907    /// Defaults to no restrictions (all false).
5908    static ACCESSIBILITY_PREFS: std::cell::RefCell<AccessibilityPreferences> =
5909        std::cell::RefCell::new(AccessibilityPreferences::default());
5910}
5911
5912/// System accessibility preferences that components and the renderer must honor.
5913///
5914/// These map to macOS System Settings > Accessibility:
5915/// - `reduce_motion`: Disables non-essential animations (spring, bounce, etc.)
5916/// - `reduce_transparency`: Replaces glass/transparent materials with opaque surfaces
5917/// - `increase_contrast`: Makes all borders visible, minimum alpha 0.5 for all elements
5918#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5919pub struct AccessibilityPreferences {
5920    /// User prefers reduced motion. Animations should be instant or very short.
5921    pub reduce_motion: bool,
5922    /// User prefers reduced transparency. Glass materials should be opaque.
5923    pub reduce_transparency: bool,
5924    /// User prefers increased contrast. Borders must be visible, min alpha 0.5.
5925    pub increase_contrast: bool,
5926}
5927
5928impl AccessibilityPreferences {
5929    /// Detect system accessibility preferences (macOS).
5930    ///
5931    /// On non-macOS platforms, returns defaults (all false).
5932    /// In a production implementation, this would query the OS APIs.
5933    pub fn detect_from_system() -> Self {
5934        #[cfg(target_os = "macos")]
5935        {
5936            // Try to read macOS accessibility preferences via defaults command
5937            let reduce_motion = std::process::Command::new("defaults")
5938                .args(["read", "-g", "com.apple.universalaccess", "reduceMotion"])
5939                .output()
5940                .ok()
5941                .and_then(|o| String::from_utf8(o.stdout).ok())
5942                .map(|s| s.trim() == "1")
5943                .unwrap_or(false);
5944
5945            let reduce_transparency = std::process::Command::new("defaults")
5946                .args([
5947                    "read",
5948                    "-g",
5949                    "com.apple.universalaccess",
5950                    "reduceTransparency",
5951                ])
5952                .output()
5953                .ok()
5954                .and_then(|o| String::from_utf8(o.stdout).ok())
5955                .map(|s| s.trim() == "1")
5956                .unwrap_or(false);
5957
5958            let increase_contrast = std::process::Command::new("defaults")
5959                .args([
5960                    "read",
5961                    "-g",
5962                    "com.apple.universalaccess",
5963                    "increaseContrast",
5964                ])
5965                .output()
5966                .ok()
5967                .and_then(|o| String::from_utf8(o.stdout).ok())
5968                .map(|s| s.trim() == "1")
5969                .unwrap_or(false);
5970
5971            Self {
5972                reduce_motion,
5973                reduce_transparency,
5974                increase_contrast,
5975            }
5976        }
5977        #[cfg(not(target_os = "macos"))]
5978        {
5979            Self::default()
5980        }
5981    }
5982
5983    /// Apply a minimum alpha constraint for increase-contrast mode.
5984    pub fn min_alpha(&self, requested: f32) -> f32 {
5985        if self.increase_contrast {
5986            requested.max(0.5)
5987        } else {
5988            requested
5989        }
5990    }
5991
5992    /// Returns true if glass effects should be replaced with opaque surfaces.
5993    pub fn should_disable_glass(&self) -> bool {
5994        self.reduce_transparency
5995    }
5996
5997    /// Returns true if animations should be instant.
5998    pub fn should_reduce_motion(&self) -> bool {
5999        self.reduce_motion
6000    }
6001
6002    /// Returns true if borders should be made visible.
6003    pub fn should_increase_contrast(&self) -> bool {
6004        self.increase_contrast
6005    }
6006}
6007
6008/// Get the current accessibility preferences for this thread.
6009pub fn accessibility_preferences() -> AccessibilityPreferences {
6010    ACCESSIBILITY_PREFS.with(|p| *p.borrow())
6011}
6012
6013/// Set the accessibility preferences for this thread.
6014///
6015/// The native renderer should call this on startup and when system
6016/// preferences change (via `detect_from_system()`).
6017pub fn set_accessibility_preferences(prefs: AccessibilityPreferences) {
6018    ACCESSIBILITY_PREFS.with(|p| {
6019        *p.borrow_mut() = prefs;
6020    });
6021}
6022
6023// =============================================================================
6024// CLIPBOARD — System clipboard access
6025// =============================================================================
6026
6027/// Trait for clipboard operations.
6028///
6029/// The native renderer implements this via `arboard` on desktop platforms.
6030/// On WASM, it uses the browser Clipboard API.
6031pub trait ClipboardProvider: Send + Sync {
6032    /// Read text from the system clipboard.
6033    fn read_text(&self) -> Option<String>;
6034    /// Write text to the system clipboard.
6035    fn write_text(&self, text: &str);
6036}
6037
6038/// Default clipboard implementation using `arboard`.
6039/// Note: This is only available when the `arboard` feature is enabled.
6040/// The renderer provides the concrete implementation.
6041#[cfg(not(target_arch = "wasm32"))]
6042pub struct SystemClipboard;
6043
6044#[cfg(not(target_arch = "wasm32"))]
6045impl ClipboardProvider for SystemClipboard {
6046    fn read_text(&self) -> Option<String> {
6047        use std::process::Command;
6048        // Fallback: try pbpaste on macOS
6049        Command::new("pbpaste")
6050            .output()
6051            .ok()
6052            .and_then(|o| String::from_utf8(o.stdout).ok())
6053    }
6054
6055    fn write_text(&self, text: &str) {
6056        use std::process::Command;
6057        // Fallback: try pbcopy on macOS
6058        if let Ok(mut child) = Command::new("pbcopy")
6059            .stdin(std::process::Stdio::piped())
6060            .spawn()
6061        {
6062            if let Some(stdin) = child.stdin.as_mut() {
6063                use std::io::Write;
6064                let _ = stdin.write_all(text.as_bytes());
6065            }
6066            let _ = child.wait();
6067        }
6068    }
6069}
6070
6071// =============================================================================
6072// TEXT INPUT — Direction enum for cursor movement
6073// =============================================================================
6074
6075/// Direction for cursor movement in text input.
6076#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6077pub enum TextDirection {
6078    Forward,
6079    Backward,
6080    Up,
6081    Down,
6082    LineStart,
6083    LineEnd,
6084    WordForward,
6085    WordBackward,
6086}
6087
6088/// Text input state managed by the renderer.
6089///
6090/// Components don't store this directly — the renderer maintains it
6091/// and components query/modify it through the Renderer trait methods.
6092#[derive(Debug, Clone, Default)]
6093pub struct TextInputState {
6094    /// The full text content.
6095    pub text: String,
6096    /// Cursor position as byte offset into the text.
6097    pub cursor_pos: usize,
6098    /// Selection anchor. If Some, the selection is from anchor to cursor.
6099    /// If None, there is no selection.
6100    pub selection_anchor: Option<usize>,
6101    /// Whether the input is focused (shows cursor, accepts keyboard).
6102    pub focused: bool,
6103    /// Whether the caret is currently visible (for blinking).
6104    pub caret_visible: bool,
6105    /// Last edit timestamp for undo coalescing.
6106    pub last_edit_time: f32,
6107}
6108
6109impl TextInputState {
6110    /// Create a new TextInputState with the given initial text.
6111    pub fn new(text: impl Into<String>) -> Self {
6112        let text = text.into();
6113        let cursor_pos = text.len();
6114        Self {
6115            text,
6116            cursor_pos,
6117            selection_anchor: None,
6118            focused: false,
6119            caret_visible: true,
6120            last_edit_time: 0.0,
6121        }
6122    }
6123
6124    /// Get the selection range as (start, end) byte offsets.
6125    /// Returns None if there is no selection.
6126    pub fn selection_range(&self) -> Option<(usize, usize)> {
6127        self.selection_anchor.map(|anchor| {
6128            if anchor <= self.cursor_pos {
6129                (anchor, self.cursor_pos)
6130            } else {
6131                (self.cursor_pos, anchor)
6132            }
6133        })
6134    }
6135
6136    /// Get the selected text, or empty string if no selection.
6137    pub fn selected_text(&self) -> String {
6138        self.selection_range()
6139            .map(|(start, end)| self.text[start..end].to_string())
6140            .unwrap_or_default()
6141    }
6142
6143    /// Insert text at the current cursor position, replacing any selection.
6144    pub fn insert(&mut self, new_text: &str) {
6145        if let Some((start, end)) = self.selection_range() {
6146            self.text.replace_range(start..end, new_text);
6147            self.cursor_pos = start + new_text.len();
6148        } else {
6149            self.text.insert_str(self.cursor_pos, new_text);
6150            self.cursor_pos += new_text.len();
6151        }
6152        self.selection_anchor = None;
6153    }
6154
6155    /// Delete characters. If there's a selection, delete it.
6156    /// Otherwise delete `count` characters backward (backspace) or forward (delete).
6157    pub fn delete(&mut self, backward: bool, count: usize) -> String {
6158        if let Some((start, end)) = self.selection_range() {
6159            let deleted = self.text[start..end].to_string();
6160            self.text.replace_range(start..end, "");
6161            self.cursor_pos = start;
6162            self.selection_anchor = None;
6163            return deleted;
6164        }
6165
6166        if backward && self.cursor_pos > 0 {
6167            let start = self.cursor_pos.saturating_sub(count);
6168            let deleted = self.text[start..self.cursor_pos].to_string();
6169            self.text.replace_range(start..self.cursor_pos, "");
6170            self.cursor_pos = start;
6171            deleted
6172        } else if !backward && self.cursor_pos < self.text.len() {
6173            let end = (self.cursor_pos + count).min(self.text.len());
6174            let deleted = self.text[self.cursor_pos..end].to_string();
6175            self.text.replace_range(self.cursor_pos..end, "");
6176            deleted
6177        } else {
6178            String::new()
6179        }
6180    }
6181
6182    /// Move the cursor in the given direction.
6183    pub fn move_cursor(&mut self, direction: TextDirection, extend_selection: bool) {
6184        if !extend_selection {
6185            self.selection_anchor = None;
6186        } else if self.selection_anchor.is_none() {
6187            self.selection_anchor = Some(self.cursor_pos);
6188        }
6189
6190        match direction {
6191            TextDirection::Forward if self.cursor_pos < self.text.len() => {
6192                // Move to next character boundary (UTF-8 safe)
6193                let next = self.text[self.cursor_pos..]
6194                    .char_indices()
6195                    .nth(1)
6196                    .map(|(i, _)| self.cursor_pos + i)
6197                    .unwrap_or(self.text.len());
6198                self.cursor_pos = next;
6199            }
6200            TextDirection::Backward if self.cursor_pos > 0 => {
6201                let prev = self.text[..self.cursor_pos]
6202                    .char_indices()
6203                    .next_back()
6204                    .map(|(i, _)| i)
6205                    .unwrap_or(0);
6206                self.cursor_pos = prev;
6207            }
6208            TextDirection::LineStart => {
6209                self.cursor_pos = 0;
6210            }
6211            TextDirection::LineEnd => {
6212                self.cursor_pos = self.text.len();
6213            }
6214            TextDirection::WordForward => {
6215                // Find next word boundary
6216                let rest = &self.text[self.cursor_pos..];
6217                // Skip current word chars
6218                let after_word = rest
6219                    .char_indices()
6220                    .find(|(_, c)| !c.is_alphanumeric())
6221                    .map(|(i, _)| i)
6222                    .unwrap_or(rest.len());
6223                // Skip whitespace
6224                let after_space = rest[after_word..]
6225                    .char_indices()
6226                    .find(|(_, c)| !c.is_whitespace())
6227                    .map(|(i, _)| after_word + i)
6228                    .unwrap_or(rest.len());
6229                self.cursor_pos = (self.cursor_pos + after_space).min(self.text.len());
6230            }
6231            TextDirection::WordBackward => {
6232                let before = &self.text[..self.cursor_pos];
6233                // Skip whitespace going backward
6234                let before_word = before
6235                    .char_indices()
6236                    .rev()
6237                    .find(|(_, c)| !c.is_whitespace())
6238                    .map(|(i, _)| i)
6239                    .unwrap_or(0);
6240                // Skip word chars going backward
6241                let word_start = before[..before_word]
6242                    .char_indices()
6243                    .rev()
6244                    .find(|(_, c)| !c.is_alphanumeric())
6245                    .map(|(i, _)| i)
6246                    .unwrap_or(0);
6247                self.cursor_pos = word_start;
6248            }
6249            _ => {} // Up/Down handled by multi-line components
6250        }
6251
6252        if !extend_selection {
6253            self.selection_anchor = None;
6254        }
6255    }
6256
6257    /// Select all text.
6258    pub fn select_all(&mut self) {
6259        self.cursor_pos = self.text.len();
6260        self.selection_anchor = Some(0);
6261    }
6262
6263    /// Get the byte offset of the cursor.
6264    pub fn cursor_byte_pos(&self) -> usize {
6265        self.cursor_pos
6266    }
6267}
6268
6269/// Action details for interactive buttons inside a notification.
6270#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6271pub struct NotificationAction {
6272    /// Unique identifier of the action.
6273    pub id: String,
6274    /// The text label to display on the action button.
6275    pub title: String,
6276    /// Indicates whether the action performs a destructive task (e.g. Delete).
6277    pub is_destructive: bool,
6278}
6279
6280/// Priority tier of a notification.
6281#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6282pub enum NotificationPriority {
6283    /// Placed silently into the notification center without visual alerts.
6284    Passive,
6285    /// Triggers a visual alert (toast) but does not interrupt focus.
6286    #[default]
6287    Active,
6288    /// Important alert that bypasses standard DND/Focus bounds.
6289    TimeSensitive,
6290}
6291
6292/// A structured notification representation.
6293#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
6294pub struct Notification {
6295    /// Unique identifier for this notification.
6296    pub id: String,
6297    /// App or source identifier spawning this notification.
6298    pub app_name: Option<String>,
6299    /// The bold heading/title text.
6300    pub title: String,
6301    /// The detailed descriptive body text.
6302    pub body: String,
6303    /// Optional URI or path to an icon asset.
6304    pub icon: Option<String>,
6305    /// Optional sound identifier to play when posting.
6306    pub sound: Option<String>,
6307    /// Interactive actions available on this notification.
6308    pub actions: Vec<NotificationAction>,
6309    /// Timer duration in seconds after which the toast auto-dismisses.
6310    pub timeout: Option<f32>,
6311    /// Priority level for delivery logic.
6312    pub priority: NotificationPriority,
6313    /// Time (in seconds since renderer startup) when this notification was posted.
6314    pub timestamp: f32,
6315    /// Whether the notification has been dismissed/read.
6316    pub dismissed: bool,
6317}
6318
6319/// Error type indicating a failure in generating or posting a notification.
6320#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, thiserror::Error)]
6321pub enum NotificationError {
6322    /// Permissions denied.
6323    #[error("Notification permission denied")]
6324    PermissionDenied,
6325    /// Failed to post the notification.
6326    #[error("Failed to post notification")]
6327    PostFailed,
6328}
6329
6330/// State of notification permissions.
6331#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6332pub enum NotificationPermission {
6333    /// Explicitly allowed.
6334    Granted,
6335    /// Explicitly blocked.
6336    Denied,
6337    /// Prompt has not been shown or decided yet.
6338    #[default]
6339    NotDetermined,
6340}
6341
6342/// Core interface for routing and dispatching notification events.
6343pub trait NotificationHandler: Send + Sync {
6344    /// Posts a new notification.
6345    fn show(&self, notification: Notification) -> Result<(), NotificationError>;
6346    /// Dismisses a notification by ID.
6347    fn dismiss(&self, id: &str) -> Result<(), NotificationError>;
6348    /// Requests delivery permission.
6349    fn request_permission(&self) -> NotificationPermission;
6350}
6351
6352static NEXT_NOTIFICATION_ID: std::sync::atomic::AtomicUsize =
6353    std::sync::atomic::AtomicUsize::new(1);
6354
6355/// Default in-app notification handler that writes state to KnowledgeState.
6356#[derive(Clone, Copy, Debug, Default)]
6357pub struct DefaultNotificationHandler;
6358
6359impl NotificationHandler for DefaultNotificationHandler {
6360    /// Save the notification to the global system state (history) and auto-assign an ID if empty.
6361    fn show(&self, notification: Notification) -> Result<(), NotificationError> {
6362        let mut notif = notification;
6363        if notif.id.is_empty() {
6364            let id = NEXT_NOTIFICATION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6365            notif.id = format!("notif_{}", id);
6366        }
6367        update_system_state(|state| {
6368            let mut new_state = state.clone();
6369            new_state.notifications.push(notif.clone());
6370            new_state
6371        });
6372        Ok(())
6373    }
6374
6375    /// Mark a notification as dismissed/read in the global system state.
6376    fn dismiss(&self, id: &str) -> Result<(), NotificationError> {
6377        update_system_state(|state| {
6378            let mut new_state = state.clone();
6379            for notif in &mut new_state.notifications {
6380                if notif.id == id {
6381                    notif.dismissed = true;
6382                }
6383            }
6384            new_state
6385        });
6386        Ok(())
6387    }
6388
6389    /// Returns the permission state (always Granted for internal in-app notifications).
6390    fn request_permission(&self) -> NotificationPermission {
6391        NotificationPermission::Granted
6392    }
6393}
6394
6395static NOTIFICATION_HANDLER: once_cell::sync::OnceCell<std::sync::Arc<dyn NotificationHandler>> =
6396    once_cell::sync::OnceCell::new();
6397
6398/// Sets the global notification handler.
6399pub fn set_notification_handler(handler: std::sync::Arc<dyn NotificationHandler>) {
6400    let _ = NOTIFICATION_HANDLER.set(handler);
6401}
6402
6403/// Gets the global notification handler, fallback to DefaultNotificationHandler.
6404pub fn get_notification_handler() -> std::sync::Arc<dyn NotificationHandler> {
6405    NOTIFICATION_HANDLER
6406        .get_or_init(|| std::sync::Arc::new(DefaultNotificationHandler))
6407        .clone()
6408}
6409
6410/// Filter mapping name to extension list for a file dialog.
6411#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6412pub struct FileFilter {
6413    /// Friendly name of the filter (e.g. "Images").
6414    pub name: String,
6415    /// List of file extensions (e.g. ["png", "jpg"]).
6416    pub extensions: Vec<String>,
6417}
6418
6419/// The mode/purpose of the file dialog.
6420#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
6421pub enum FileDialogMode {
6422    /// Pick a single or multiple files to open.
6423    #[default]
6424    OpenFile,
6425    /// Pick a directory path.
6426    OpenDirectory,
6427    /// Prompt for a location/name to save a file.
6428    SaveFile,
6429}
6430
6431/// Dialog options for picking files or directories.
6432#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6433pub struct FileDialog {
6434    /// Title displayed in the dialog window.
6435    pub title: String,
6436    /// Optional starting directory path.
6437    pub default_path: Option<String>,
6438    /// Extensions used to filter selection.
6439    pub filters: Vec<FileFilter>,
6440    /// Open/save mode.
6441    pub mode: FileDialogMode,
6442    /// Allows selecting multiple files if in OpenFile mode.
6443    pub allow_multiple: bool,
6444}
6445
6446/// Errors returned by the file dialog.
6447#[derive(Debug, thiserror::Error)]
6448pub enum FileDialogError {
6449    /// The user closed the dialog without selecting anything.
6450    #[error("File dialog cancelled")]
6451    Cancelled,
6452    /// An input/output error occurred.
6453    #[error("I/O error: {0}")]
6454    Io(#[from] std::io::Error),
6455    /// Platform-specific error.
6456    #[error("Platform error: {0}")]
6457    Platform(String),
6458}
6459
6460impl FileDialog {
6461    /// Creates a new FileDialog with the given mode.
6462    pub fn new(mode: FileDialogMode) -> Self {
6463        Self {
6464            mode,
6465            ..Default::default()
6466        }
6467    }
6468
6469    /// Sets the dialog title.
6470    pub fn title(mut self, title: impl Into<String>) -> Self {
6471        self.title = title.into();
6472        self
6473    }
6474
6475    /// Adds a file filter.
6476    pub fn add_filter(mut self, name: &str, extensions: &[&str]) -> Self {
6477        self.filters.push(FileFilter {
6478            name: name.to_string(),
6479            extensions: extensions.iter().map(|s| s.to_string()).collect(),
6480        });
6481        self
6482    }
6483
6484    /// Sets the default starting directory path.
6485    pub fn default_path(mut self, path: impl Into<String>) -> Self {
6486        self.default_path = Some(path.into());
6487        self
6488    }
6489
6490    /// Sets whether selecting multiple files is allowed.
6491    pub fn allow_multiple(mut self, allow: bool) -> Self {
6492        self.allow_multiple = allow;
6493        self
6494    }
6495}
6496
6497#[cfg(not(target_arch = "wasm32"))]
6498impl FileDialog {
6499    /// Pick file(s) or folder based on current mode configuration.
6500    pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6501        let mut dialog = rfd::FileDialog::new();
6502        dialog = dialog.set_title(&self.title);
6503        if let Some(path) = &self.default_path {
6504            dialog = dialog.set_directory(path);
6505        }
6506        for filter in &self.filters {
6507            let refs: Vec<&str> = filter.extensions.iter().map(|s| s.as_str()).collect();
6508            dialog = dialog.add_filter(&filter.name, &refs);
6509        }
6510
6511        match self.mode {
6512            FileDialogMode::OpenFile => {
6513                if self.allow_multiple {
6514                    dialog.pick_files().ok_or(FileDialogError::Cancelled)
6515                } else {
6516                    Ok(dialog.pick_file().into_iter().collect())
6517                }
6518            }
6519            FileDialogMode::OpenDirectory => Ok(dialog.pick_folder().into_iter().collect()),
6520            FileDialogMode::SaveFile => Ok(dialog.save_file().into_iter().collect()),
6521        }
6522    }
6523
6524    /// Helper to pick a single file/directory, returning None if cancelled.
6525    pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6526        let results = self.pick()?;
6527        Ok(results.into_iter().next())
6528    }
6529}
6530
6531#[cfg(target_arch = "wasm32")]
6532impl FileDialog {
6533    /// Pick is unsupported/mocked on WASM.
6534    pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6535        Err(FileDialogError::Platform(
6536            "FileDialog is not supported synchronously on WebAssembly".to_string(),
6537        ))
6538    }
6539
6540    /// Helper to pick a single file/directory, returning None if cancelled.
6541    pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6542        Err(FileDialogError::Platform(
6543            "FileDialog is not supported synchronously on WebAssembly".to_string(),
6544        ))
6545    }
6546}
6547
6548/// Error type representing a failure in Document load/save/parse operations.
6549#[derive(Debug, thiserror::Error)]
6550pub enum DocumentError {
6551    /// An input/output error occurred.
6552    #[error("I/O error: {0}")]
6553    Io(#[from] std::io::Error),
6554    /// Failure during deserialization or parsing.
6555    #[error("Parse error: {0}")]
6556    Parse(String),
6557    /// Failure during serialization.
6558    #[error("Serialization error: {0}")]
6559    Serialize(String),
6560}
6561
6562/// A document interface mapping to local filesystem persistence.
6563pub trait Document: Send + Sync {
6564    /// Loads the document from the specified path.
6565    fn read_from(path: &std::path::Path) -> Result<Self, DocumentError>
6566    where
6567        Self: Sized;
6568
6569    /// Saves the document to the specified path.
6570    fn write_to(&self, path: &std::path::Path) -> Result<(), DocumentError>;
6571
6572    /// Returns true if the document has unsaved modifications.
6573    fn is_dirty(&self) -> bool;
6574
6575    /// Marks the document as clean/saved.
6576    fn mark_clean(&mut self);
6577}
6578
6579/// Periodic auto-save coordinator for open Documents.
6580pub struct AutoSaveManager {
6581    /// Time interval in seconds between auto-saves.
6582    pub interval: f32,
6583    /// Elapsed timer tracker.
6584    pub timer: f32,
6585    /// Registered open documents under management.
6586    pub documents: Vec<(std::path::PathBuf, Box<dyn Document>)>,
6587}
6588
6589impl AutoSaveManager {
6590    /// Creates a new AutoSaveManager with the specified check interval.
6591    pub fn new(interval: f32) -> Self {
6592        Self {
6593            interval,
6594            timer: 0.0,
6595            documents: Vec::new(),
6596        }
6597    }
6598
6599    /// Register a document with its current file path.
6600    pub fn register(&mut self, path: std::path::PathBuf, doc: Box<dyn Document>) {
6601        self.documents.push((path, doc));
6602    }
6603
6604    /// Advance the timer and auto-save any dirty documents when the interval is reached.
6605    pub fn tick(&mut self, dt: f32) {
6606        self.timer += dt;
6607        if self.timer >= self.interval {
6608            self.timer = 0.0;
6609            for (path, doc) in &mut self.documents {
6610                if doc.is_dirty() {
6611                    match doc.write_to(path) {
6612                        Ok(()) => {
6613                            doc.mark_clean();
6614                            log::info!("[AutoSaveManager] Auto-saved document to {:?}", path);
6615                        }
6616                        Err(e) => {
6617                            log::error!(
6618                                "[AutoSaveManager] Failed to auto-save document to {:?}: {:?}",
6619                                path,
6620                                e
6621                            );
6622                        }
6623                    }
6624                }
6625            }
6626        }
6627    }
6628}
6629
6630// ── Menu Bar API ──────────────────────────────────────────────────────────────
6631
6632/// Keyboard modifier flags used by [`KeyboardShortcut`].
6633///
6634/// On macOS, `cmd` maps to the Command (⌘) key.
6635/// On all other platforms, `cmd` maps to the Control key.
6636/// This is enforced at the renderer level, not here; the data model is OS-agnostic.
6637#[derive(Debug, Clone, PartialEq, Eq, Default)]
6638pub struct Modifiers {
6639    /// Command on macOS, Control on Windows/Linux.
6640    pub cmd: bool,
6641    /// Shift key.
6642    pub shift: bool,
6643    /// Alt/Option key.
6644    pub alt: bool,
6645    /// Control key (distinct from cmd on all platforms).
6646    pub ctrl: bool,
6647}
6648
6649/// A keyboard shortcut binding to a menu action.
6650#[derive(Debug, Clone)]
6651pub struct KeyboardShortcut {
6652    /// The key character or name, e.g. `"s"`, `"z"`, `"Return"`.
6653    pub key: String,
6654    /// The required modifier combination.
6655    pub modifiers: Modifiers,
6656}
6657
6658impl KeyboardShortcut {
6659    /// Convenience constructor: cmd (or ctrl on non-macOS) + `key`.
6660    pub fn cmd(key: impl Into<String>) -> Self {
6661        Self {
6662            key: key.into(),
6663            modifiers: Modifiers {
6664                cmd: true,
6665                ..Default::default()
6666            },
6667        }
6668    }
6669
6670    /// Convenience constructor: cmd+Shift + `key`.
6671    pub fn cmd_shift(key: impl Into<String>) -> Self {
6672        Self {
6673            key: key.into(),
6674            modifiers: Modifiers {
6675                cmd: true,
6676                shift: true,
6677                ..Default::default()
6678            },
6679        }
6680    }
6681}
6682
6683/// A single entry in a [`MenuBar`].
6684///
6685/// Actions hold a callback that is invoked when the user activates the item
6686/// (either via the menu UI or via the associated keyboard shortcut).
6687/// Separators provide visual grouping. Submenus allow hierarchical menus.
6688pub enum MenuItem {
6689    /// An activatable menu entry with an optional shortcut and enabled/disabled state.
6690    Action {
6691        label: String,
6692        shortcut: Option<KeyboardShortcut>,
6693        action: std::sync::Arc<dyn Fn() + Send + Sync>,
6694        enabled: bool,
6695    },
6696    /// A nested submenu.
6697    Submenu { label: String, items: Vec<MenuItem> },
6698    /// A visual separator line between groups of items.
6699    Separator,
6700}
6701
6702impl std::fmt::Debug for MenuItem {
6703    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6704        match self {
6705            Self::Action { label, enabled, .. } => f
6706                .debug_struct("Action")
6707                .field("label", label)
6708                .field("enabled", enabled)
6709                .finish(),
6710            Self::Submenu { label, items } => f
6711                .debug_struct("Submenu")
6712                .field("label", label)
6713                .field("items", items)
6714                .finish(),
6715            Self::Separator => write!(f, "Separator"),
6716        }
6717    }
6718}
6719
6720/// A top-level menu bar containing [`MenuItem`]s.
6721///
6722/// The menu bar is a data model only; rendering it into an OS-native menu is
6723/// handled by the platform renderer (`cvkg-render-native`).
6724pub struct MenuBar {
6725    /// Ordered list of top-level menu items.
6726    pub items: Vec<MenuItem>,
6727}
6728
6729impl MenuBar {
6730    /// Create an empty menu bar.
6731    pub fn new() -> Self {
6732        Self { items: Vec::new() }
6733    }
6734
6735    /// Append a menu item to the bar.
6736    pub fn add_item(&mut self, item: MenuItem) {
6737        self.items.push(item);
6738    }
6739
6740    /// Build the standard CVKG menu structure with all conventional shortcuts.
6741    ///
6742    /// The `cmd` modifier maps to ⌘ on macOS and Ctrl on Windows/Linux — this
6743    /// translation is enforced by the renderer, not here.
6744    ///
6745    /// Menus included:
6746    /// - **File**: New, Open, Save, Close
6747    /// - **Edit**: Undo, Redo, Cut, Copy, Paste, Select All, Find
6748    /// - **View**: Zoom In, Zoom Out, Fullscreen
6749    /// - **Window**: Minimize, Zoom, Bring All to Front
6750    /// - **Help**: Search Help
6751    #[allow(clippy::too_many_arguments)]
6752    pub fn standard(
6753        new_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6754        open_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6755        save_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6756        close_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6757        quit_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6758        undo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6759        redo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6760        cut_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6761        copy_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6762        paste_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6763        select_all_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6764        find_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6765    ) -> Self {
6766        let mut bar = Self::new();
6767
6768        // ── File ──────────────────────────────────────────────────────────────
6769        bar.add_item(MenuItem::Submenu {
6770            label: "File".to_string(),
6771            items: vec![
6772                MenuItem::Action {
6773                    label: "New".to_string(),
6774                    shortcut: Some(KeyboardShortcut::cmd("n")),
6775                    action: new_fn,
6776                    enabled: true,
6777                },
6778                MenuItem::Action {
6779                    label: "Open…".to_string(),
6780                    shortcut: Some(KeyboardShortcut::cmd("o")),
6781                    action: open_fn,
6782                    enabled: true,
6783                },
6784                MenuItem::Separator,
6785                MenuItem::Action {
6786                    label: "Save".to_string(),
6787                    shortcut: Some(KeyboardShortcut::cmd("s")),
6788                    action: save_fn,
6789                    enabled: true,
6790                },
6791                MenuItem::Separator,
6792                MenuItem::Action {
6793                    label: "Close".to_string(),
6794                    shortcut: Some(KeyboardShortcut::cmd("w")),
6795                    action: close_fn,
6796                    enabled: true,
6797                },
6798                MenuItem::Separator,
6799                MenuItem::Action {
6800                    label: "Quit".to_string(),
6801                    shortcut: Some(KeyboardShortcut::cmd("q")),
6802                    action: quit_fn,
6803                    enabled: true,
6804                },
6805            ],
6806        });
6807
6808        // ── Edit ──────────────────────────────────────────────────────────────
6809        bar.add_item(MenuItem::Submenu {
6810            label: "Edit".to_string(),
6811            items: vec![
6812                MenuItem::Action {
6813                    label: "Undo".to_string(),
6814                    shortcut: Some(KeyboardShortcut::cmd("z")),
6815                    action: undo_fn,
6816                    enabled: true,
6817                },
6818                MenuItem::Action {
6819                    label: "Redo".to_string(),
6820                    shortcut: Some(KeyboardShortcut::cmd_shift("z")),
6821                    action: redo_fn,
6822                    enabled: true,
6823                },
6824                MenuItem::Separator,
6825                MenuItem::Action {
6826                    label: "Cut".to_string(),
6827                    shortcut: Some(KeyboardShortcut::cmd("x")),
6828                    action: cut_fn,
6829                    enabled: true,
6830                },
6831                MenuItem::Action {
6832                    label: "Copy".to_string(),
6833                    shortcut: Some(KeyboardShortcut::cmd("c")),
6834                    action: copy_fn,
6835                    enabled: true,
6836                },
6837                MenuItem::Action {
6838                    label: "Paste".to_string(),
6839                    shortcut: Some(KeyboardShortcut::cmd("v")),
6840                    action: paste_fn,
6841                    enabled: true,
6842                },
6843                MenuItem::Separator,
6844                MenuItem::Action {
6845                    label: "Select All".to_string(),
6846                    shortcut: Some(KeyboardShortcut::cmd("a")),
6847                    action: select_all_fn,
6848                    enabled: true,
6849                },
6850                MenuItem::Separator,
6851                MenuItem::Action {
6852                    label: "Find…".to_string(),
6853                    shortcut: Some(KeyboardShortcut::cmd("f")),
6854                    action: find_fn,
6855                    enabled: true,
6856                },
6857            ],
6858        });
6859
6860        // ── View ──────────────────────────────────────────────────────────────
6861        // View items carry no application-level callbacks at the model layer;
6862        // zoom and fullscreen are handled by the renderer directly.
6863        let noop: std::sync::Arc<dyn Fn() + Send + Sync> = std::sync::Arc::new(|| {});
6864        bar.add_item(MenuItem::Submenu {
6865            label: "View".to_string(),
6866            items: vec![
6867                MenuItem::Action {
6868                    label: "Zoom In".to_string(),
6869                    shortcut: Some(KeyboardShortcut::cmd("=")),
6870                    action: noop.clone(),
6871                    enabled: true,
6872                },
6873                MenuItem::Action {
6874                    label: "Zoom Out".to_string(),
6875                    shortcut: Some(KeyboardShortcut::cmd("-")),
6876                    action: noop.clone(),
6877                    enabled: true,
6878                },
6879                MenuItem::Separator,
6880                MenuItem::Action {
6881                    label: "Toggle Fullscreen".to_string(),
6882                    shortcut: Some(KeyboardShortcut {
6883                        key: "f".to_string(),
6884                        modifiers: Modifiers {
6885                            ctrl: true,
6886                            ..Default::default()
6887                        },
6888                    }),
6889                    action: noop.clone(),
6890                    enabled: true,
6891                },
6892            ],
6893        });
6894
6895        // ── Window ────────────────────────────────────────────────────────────
6896        bar.add_item(MenuItem::Submenu {
6897            label: "Window".to_string(),
6898            items: vec![
6899                MenuItem::Action {
6900                    label: "Minimize".to_string(),
6901                    shortcut: Some(KeyboardShortcut::cmd("m")),
6902                    action: noop.clone(),
6903                    enabled: true,
6904                },
6905                MenuItem::Action {
6906                    label: "Zoom".to_string(),
6907                    shortcut: None,
6908                    action: noop.clone(),
6909                    enabled: true,
6910                },
6911                MenuItem::Separator,
6912                MenuItem::Action {
6913                    label: "Bring All to Front".to_string(),
6914                    shortcut: None,
6915                    action: noop.clone(),
6916                    enabled: true,
6917                },
6918            ],
6919        });
6920
6921        // ── Help ──────────────────────────────────────────────────────────────
6922        bar.add_item(MenuItem::Submenu {
6923            label: "Help".to_string(),
6924            items: vec![MenuItem::Action {
6925                label: "Search Help".to_string(),
6926                shortcut: None,
6927                action: noop,
6928                enabled: true,
6929            }],
6930        });
6931
6932        bar
6933    }
6934}
6935
6936impl Default for MenuBar {
6937    fn default() -> Self {
6938        Self::new()
6939    }
6940}
6941
6942// =============================================================================
6943// LOCALIZATION — Item 12: Localization / Internationalization
6944// =============================================================================
6945// OS-agnostic: works on all platforms. No platform-specific string loading.
6946
6947use std::sync::RwLock;
6948
6949/// Layout direction for UI elements (LTR or RTL).
6950#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
6951pub enum Direction {
6952    #[default]
6953    LTR,
6954    RTL,
6955    Auto,
6956}
6957
6958impl Direction {
6959    pub fn is_rtl(self) -> bool {
6960        matches!(self, Direction::RTL)
6961    }
6962}
6963#[derive(Clone, Debug)]
6964pub struct L10nBundle {
6965    pub locale: String,
6966    pub strings: HashMap<String, String>,
6967    pub is_rtl: bool,
6968}
6969
6970impl L10nBundle {
6971    pub fn new(locale: impl Into<String>) -> Self {
6972        Self {
6973            locale: locale.into(),
6974            strings: HashMap::new(),
6975            is_rtl: false,
6976        }
6977    }
6978
6979    pub fn add(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
6980        self.strings.insert(key.into(), value.into());
6981        self
6982    }
6983
6984    pub fn from_strings_format(locale: impl Into<String>, input: &str) -> Self {
6985        let mut bundle = Self::new(locale);
6986        for line in input.lines() {
6987            let line = line.trim();
6988            if line.is_empty() || line.starts_with("//") {
6989                continue;
6990            }
6991            if let Some(eq_pos) = line.find(" = ") {
6992                let key = line[..eq_pos].trim_matches('"').to_string();
6993                let val = line[eq_pos + 3..]
6994                    .trim_end_matches(';')
6995                    .trim_matches('"')
6996                    .to_string();
6997                bundle.strings.insert(key, val);
6998            }
6999        }
7000        bundle
7001    }
7002    /// Get a translated string by key. Returns the key itself if not found.
7003    pub fn t(&self, key: &str) -> String {
7004        self.strings
7005            .get(key)
7006            .map(|s| s.to_string())
7007            .unwrap_or_else(|| key.to_string())
7008    }
7009
7010    /// Translate with interpolation. Replaces {0}, {1}, etc. with args.
7011    pub fn tf(&self, key: &str, args: &[&str]) -> String {
7012        let mut result = self.t(key);
7013        for (i, arg) in args.iter().enumerate() {
7014            result = result.replace(&format!("{{{}}}", i), arg);
7015        }
7016        result
7017    }
7018}
7019
7020/// Global localization manager.
7021pub struct L10n {
7022    bundles: HashMap<String, L10nBundle>,
7023    current: String,
7024}
7025
7026impl L10n {
7027    pub fn new(default_locale: &str) -> Self {
7028        Self {
7029            bundles: HashMap::new(),
7030            current: default_locale.to_string(),
7031        }
7032    }
7033
7034    pub fn add_bundle(&mut self, bundle: L10nBundle) {
7035        self.bundles.insert(bundle.locale.clone(), bundle);
7036    }
7037
7038    pub fn set_locale(&mut self, locale: &str) {
7039        self.current = locale.to_string();
7040    }
7041    pub fn current_locale(&self) -> &str {
7042        &self.current
7043    }
7044
7045    pub fn is_rtl(&self) -> bool {
7046        self.bundles
7047            .get(self.current.as_str())
7048            .map(|b| b.is_rtl)
7049            .unwrap_or(false)
7050    }
7051
7052    pub fn t(&self, key: &str) -> String {
7053        self.bundles
7054            .get(self.current.as_str())
7055            .map(|b| b.t(key))
7056            .unwrap_or_else(|| key.to_string())
7057    }
7058
7059    pub fn tf(&self, key: &str, args: &[&str]) -> String {
7060        let mut result = self.t(key);
7061        for (i, arg) in args.iter().enumerate() {
7062            result = result.replace(&format!("{{{}}}", i), arg);
7063        }
7064        result
7065    }
7066
7067    pub fn direction(&self) -> Direction {
7068        if self.is_rtl() {
7069            Direction::RTL
7070        } else {
7071            Direction::LTR
7072        }
7073    }
7074}
7075
7076static L10N: once_cell::sync::Lazy<Arc<RwLock<L10n>>> =
7077    once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(L10n::new("en"))));
7078
7079pub fn init_l10n(l10n: L10n) {
7080    if let Ok(mut guard) = L10N.write() {
7081        *guard = l10n;
7082    }
7083}
7084
7085pub fn l10n() -> Arc<RwLock<L10n>> {
7086    L10N.clone()
7087}
7088
7089pub fn t(key: &str) -> String {
7090    L10N.read()
7091        .map(|g| g.t(key).to_string())
7092        .unwrap_or_else(|_| key.to_string())
7093}
7094
7095pub fn tf(key: &str, args: &[&str]) -> String {
7096    L10N.read()
7097        .map(|g| g.tf(key, args))
7098        .unwrap_or_else(|_| key.to_string())
7099}
7100
7101pub fn set_locale(locale: &str) {
7102    if let Ok(mut guard) = L10N.write() {
7103        guard.set_locale(locale);
7104    }
7105}
7106
7107pub fn current_locale() -> String {
7108    L10N.read()
7109        .map(|g| g.current_locale().to_string())
7110        .unwrap_or_else(|_| "en".to_string())
7111}
7112
7113pub fn is_rtl() -> bool {
7114    L10N.read().map(|g| g.is_rtl()).unwrap_or(false)
7115}
7116
7117// =============================================================================
7118// SYSTEM THEME DETECTION — Dark/Light mode detection
7119// =============================================================================
7120//
7121// OS-agnostic theme detection. Checks the CVKG_THEME environment variable first,
7122// then falls back to dark mode (safe default).
7123//
7124// Platform backends may override this with native OS queries (e.g.,
7125// dark-light crate on desktop, prefers-color-scheme on web).
7126
7127/// The detected system theme.
7128#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7129pub enum SystemTheme {
7130    /// Dark mode (default).
7131    #[default]
7132    Dark,
7133    /// Light mode.
7134    Light,
7135}
7136
7137/// Detect the current system theme.
7138///
7139/// Checks `CVKG_THEME` environment variable first:
7140/// - `"dark"` → `SystemTheme::Dark`
7141/// - `"light"` → `SystemTheme::Light`
7142/// - unset or any other value → `SystemTheme::Dark` (default)
7143///
7144/// Platform backends can call this and override with native detection
7145/// (e.g., `dark-light` crate on desktop, `prefers-color-scheme` on web).
7146pub fn detect_system_theme() -> SystemTheme {
7147    std::env::var("CVKG_THEME")
7148        .ok()
7149        .and_then(|v| match v.as_str() {
7150            "light" => Some(SystemTheme::Light),
7151            "dark" => Some(SystemTheme::Dark),
7152            _ => None,
7153        })
7154        .unwrap_or(SystemTheme::Dark)
7155}
7156
7157// =============================================================================
7158// AUDIO / HAPTIC — Item 14: Spatial Audio / Haptic Feedback
7159// =============================================================================
7160// OS-agnostic: pure trait abstractions. Platform backends via cfg in renderer.
7161
7162pub mod audio_haptic;
7163pub use audio_haptic::{
7164    AudioEngine, HapticEngine, HapticIntensity, NullAudioEngine, NullHapticEngine, haptic_error,
7165    haptic_impact, haptic_selection, haptic_success, play_sound, set_audio_engine,
7166    set_haptic_engine, sounds,
7167};