1use serde::{Deserialize, Serialize};
35use std::collections::HashMap;
36use std::str::FromStr;
37
38pub mod error_types;
39
40pub mod security;
41
42#[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#[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 pub nodes: Vec<TemporalNode>,
74 pub edges: Vec<TemporalEdge>,
76 pub realm: Realm,
78 pub last_pointer_pos: [f32; 2],
80 pub pointer_velocity: [f32; 2],
82 pub odin_focus: Option<String>,
84 pub agent_attention: HashMap<String, f32>,
86 #[serde(skip)]
88 pub component_states: HashMap<u64, Arc<std::sync::RwLock<dyn std::any::Any + Send + Sync>>>,
89 #[serde(skip)]
91 pub undo_manager: UndoManager,
92 #[serde(default)]
94 pub notifications: Vec<Notification>,
95 #[serde(default)]
97 pub notification_center_visible: bool,
98 #[serde(default)]
100 pub modifiers_shift: bool,
101 #[serde(default)]
103 pub modifiers_ctrl: bool,
104 #[serde(default)]
106 pub modifiers_alt: bool,
107 #[serde(default)]
109 pub modifiers_logo: bool,
110 #[serde(default)]
112 pub performance_overlay_visible: bool,
113}
114
115impl KnowledgeState {
116 pub fn apply_decay(&mut self, decay_factor: f32) {
120 for node in &mut self.nodes {
121 node.weight *= decay_factor;
122 }
123
124 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 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 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}
152pub type KnowledgeId = String;
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct KnowledgeFragment {
159 pub id: String,
161 pub summary: String,
163 pub source: String,
165 pub created_at: u64,
167 pub accessed_count: u32,
169 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#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
188pub enum MemoryLayer {
189 Episodic,
191 Semantic,
193 Procedural,
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
201pub enum Realm {
202 Midgard,
203 #[default]
204 Asgard,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct TemporalNode {
210 pub id: String,
212 pub fragment_id: KnowledgeId,
214 pub timestamp: u64,
216 pub layer: MemoryLayer,
218 pub weight: f32,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct TemporalEdge {
225 pub source: String,
227 pub target: String,
229 pub relation: String,
231 pub weight: f32,
233}
234
235pub struct UndoGroup {
237 pub label: String,
239 pub timestamp: f32,
241 pub undo: Arc<dyn Fn() + Send + Sync>,
243 pub redo: Arc<dyn Fn() + Send + Sync>,
245}
246
247impl Clone for UndoGroup {
248 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 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
269pub struct UndoManager {
272 stack: Vec<UndoGroup>,
274 position: usize,
276 max_depth: usize,
278 coalesce_window: f32,
280}
281
282impl Default for UndoManager {
283 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 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 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 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 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 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 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 pub fn can_undo(&self) -> bool {
383 self.position > 0
384 }
385
386 pub fn can_redo(&self) -> bool {
388 self.position < self.stack.len()
389 }
390
391 pub fn clear(&mut self) {
393 self.stack.clear();
394 self.position = 0;
395 }
396
397 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
439pub struct WindowId(pub u64);
440
441#[derive(
443 Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default,
444)]
445pub enum WindowLevel {
446 #[default]
448 Normal,
449 AlwaysOnTop,
451 PopUpMenu,
453}
454
455#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
457pub struct WindowConfig {
458 pub title: String,
460 pub size: (f32, f32),
462 pub min_size: Option<(f32, f32)>,
464 pub max_size: Option<(f32, f32)>,
466 pub resizable: bool,
468 pub transparent: bool,
470 pub decorations: bool,
472 pub level: WindowLevel,
474}
475
476impl Default for WindowConfig {
477 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
492pub trait Window: Send + Sync {
495 fn close(&self);
497 fn set_title(&self, title: &str);
499 fn set_size(&self, width: f32, height: f32);
501 fn is_key(&self) -> bool;
503 fn is_main(&self) -> bool;
505 fn is_visible(&self) -> bool;
507 fn set_visible(&self, visible: bool);
509 fn bring_to_front(&self);
511}
512
513#[derive(Clone)]
515pub struct WindowHandle {
516 pub id: WindowId,
518 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 pub fn new(id: WindowId, inner: Arc<dyn Window>) -> Self {
533 Self { id, inner }
534 }
535 pub fn close(self) {
537 self.inner.close();
538 }
539 pub fn set_title(&self, title: &str) {
541 self.inner.set_title(title);
542 }
543 pub fn set_size(&self, width: f32, height: f32) {
545 self.inner.set_size(width, height);
546 }
547 pub fn is_key(&self) -> bool {
549 self.inner.is_key()
550 }
551 pub fn is_main(&self) -> bool {
553 self.inner.is_main()
554 }
555 pub fn is_visible(&self) -> bool {
557 self.inner.is_visible()
558 }
559 pub fn set_visible(&self, visible: bool) {
561 self.inner.set_visible(visible);
562 }
563 pub fn bring_to_front(&self) {
565 self.inner.bring_to_front();
566 }
567}
568
569#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
571pub enum WindowCloseAction {
572 Allow,
574 Confirm,
576 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#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
592pub enum AssetState<T> {
593 Loading,
594 Ready(T),
595 Error(String),
596}
597
598#[derive(Debug, Clone, Serialize, Deserialize)]
600#[serde(untagged)]
601pub enum TokenValue {
602 Single { value: String },
604 Adaptive { light: String, dark: String },
606}
607
608#[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 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 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 type Body: View;
692
693 fn body(self) -> Self::Body;
694
695 fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
698
699 fn intrinsic_size(&self, _renderer: &mut dyn Renderer, _proposal: SizeProposal) -> Size {
702 Size::ZERO
703 }
704
705 fn layout(&self) -> Option<&dyn layout::LayoutView> {
707 None
708 }
709
710 fn flex_weight(&self) -> f32 {
712 0.0
713 }
714
715 fn get_grid_placement(&self) -> Option<GridPlacement> {
717 None
718 }
719
720 fn modifier<M: ViewModifier>(self, m: M) -> ModifiedView<Self, M> {
722 ModifiedView::new(self, m)
723 }
724
725 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 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 fn mjolnir_slice(self, angle: f32, offset: f32) -> ModifiedView<Self, MjolnirSliceModifier> {
755 self.modifier(MjolnirSliceModifier { angle, offset })
756 }
757
758 fn mjolnir_shatter(
760 self,
761 pieces: u32,
762 force: f32,
763 ) -> ModifiedView<Self, MjolnirShatterModifier> {
764 self.modifier(MjolnirShatterModifier { pieces, force })
765 }
766
767 fn bifrost_bridge(self, id: impl Into<String>) -> ModifiedView<Self, BifrostBridgeModifier> {
769 self.modifier(BifrostBridgeModifier { id: id.into() })
770 }
771
772 fn background(self, color: [f32; 4]) -> ModifiedView<Self, BackgroundModifier> {
774 self.modifier(BackgroundModifier { color })
775 }
776
777 fn padding(self, amount: f32) -> ModifiedView<Self, PaddingModifier> {
779 self.modifier(PaddingModifier { amount })
780 }
781
782 fn opacity(self, opacity: f32) -> ModifiedView<Self, OpacityModifier> {
784 self.modifier(OpacityModifier {
785 opacity: opacity.clamp(0.0, 1.0),
786 })
787 }
788
789 fn foreground_color(self, color: [f32; 4]) -> ModifiedView<Self, ForegroundColorModifier> {
791 self.modifier(ForegroundColorModifier { color })
792 }
793
794 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 fn flex(self, weight: f32) -> ModifiedView<Self, FlexModifier> {
810 self.modifier(FlexModifier { weight })
811 }
812
813 fn grid_placement(self, placement: GridPlacement) -> ModifiedView<Self, GridPlacementModifier> {
815 self.modifier(GridPlacementModifier { placement })
816 }
817
818 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 fn safe_area_padding(self) -> ModifiedView<Self, SafeAreaModifier> {
836 self.modifier(SafeAreaModifier { ignores: false })
837 }
838
839 fn ignores_safe_area(self) -> ModifiedView<Self, SafeAreaModifier> {
841 self.modifier(SafeAreaModifier { ignores: true })
842 }
843
844 fn clip_to_bounds(self) -> ModifiedView<Self, ClipModifier> {
846 self.modifier(ClipModifier)
847 }
848
849 fn border(self, color: [f32; 4], width: f32) -> ModifiedView<Self, BorderModifier> {
851 self.modifier(BorderModifier { color, width })
852 }
853
854 fn elevation(self, level: f32) -> ModifiedView<Self, ElevationModifier> {
856 self.modifier(ElevationModifier { level })
857 }
858
859 fn magnetic(self, radius: f32, intensity: f32) -> ModifiedView<Self, MagneticModifier> {
861 self.modifier(MagneticModifier { radius, intensity })
862 }
863
864 fn mani_glow(self, color: [f32; 4], radius: f32) -> ModifiedView<Self, ManiGlowModifier> {
866 self.modifier(ManiGlowModifier { color, radius })
867 }
868
869 fn memory_layer(self, layer: MemoryLayer) -> ModifiedView<Self, BifrostLayerModifier> {
871 self.modifier(BifrostLayerModifier { layer })
872 }
873
874 fn fafnir_evolve(self, id: u64) -> ModifiedView<Self, FafnirModifier> {
876 self.modifier(FafnirModifier { id })
877 }
878
879 fn mimir_intent(self) -> ModifiedView<Self, MimirIntentModifier> {
881 self.modifier(MimirIntentModifier)
882 }
883
884 fn kvasir_vibes(self, complexity: f32) -> ModifiedView<Self, KvasirVibeModifier> {
886 self.modifier(KvasirVibeModifier { complexity })
887 }
888
889 fn odins_eye(self) -> ModifiedView<Self, OdinsEyeModifier> {
891 self.modifier(OdinsEyeModifier)
892 }
893
894 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 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 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 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 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 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 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 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 fn erase(self) -> AnyView
978 where
979 Self: Clone + 'static,
980 {
981 AnyView::new(self)
982 }
983
984 fn aria_properties(&self) -> Option<AriaProperties> {
992 None
993 }
994
995 fn on_key_event(&self, _key: &str, _modifiers: KeyModifiers) -> bool {
998 false
999 }
1000
1001 fn key_shortcuts(&self) -> Vec<KeyShortcut> {
1003 vec![]
1004 }
1005}
1006
1007#[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#[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#[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#[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#[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#[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#[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
1322pub 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
1339pub fn effective_duration(secs: f32) -> f32 {
1341 if is_reduced_motion() { 0.0 } else { secs }
1342}
1343
1344pub 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
1380pub 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 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
1415pub 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#[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 renderer.register_shared_element(&self.id, rect);
1477 }
1478}
1479
1480#[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#[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 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 renderer.push_mjolnir_slice(angle_start, 0.0);
1527 renderer.push_mjolnir_slice(angle_end + 180.0, 0.0);
1528
1529 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#[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 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#[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#[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#[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 renderer.stroke_rect(rect, [0.0, 1.0, 1.0, self.intensity], self.radius / 10.0);
1643 }
1644}
1645
1646#[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 let intensity = (time * self.speed).sin() * 0.5 + 0.5;
1668 let mut color = self.color;
1669 color[3] *= intensity;
1670
1671 renderer.stroke_rect(rect, color, self.radius);
1673 }
1674}
1675
1676#[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#[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#[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#[derive(Debug, Clone, Copy, PartialEq)]
1786pub struct FafnirModifier {
1787 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 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 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)); 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#[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 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 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 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#[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 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 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]; renderer.gungnir(rect, color, 12.0 + c * 24.0, 0.6 * pulse);
1909 }
1910
1911 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#[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 view.render(renderer, rect);
1938
1939 if state.realm == Realm::Asgard {
1940 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], [0.0, 0.0, 0.0, 0.0], );
1948
1949 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 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 if let Some(focus_id) = &state.odin_focus {
2000 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 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#[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#[derive(Debug, Clone, Copy, PartialEq)]
2081pub struct SleipnirSolver {
2082 params: SleipnirParams,
2083 target: f32,
2084 state: SolverState,
2085}
2086
2087impl SleipnirSolver {
2088 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 pub fn tick(&mut self, dt: f32) -> f32 {
2099 if dt <= 0.0 {
2100 return self.state.x;
2101 }
2102
2103 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#[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 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 let mut solver = lock.write().unwrap();
2180 solver.set_target(self.target);
2181 current_val = solver.tick(renderer.delta_time());
2182
2183 if !solver.is_settled() {
2185 renderer.request_redraw();
2186 }
2187 } else {
2188 let solver = SleipnirSolver::new(
2190 self.params,
2191 self.target,
2192 self.target, );
2194
2195 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 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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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 fn get_grid_placement(&self) -> Option<GridPlacement> {
2574 None
2575 }
2576
2577 fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2579
2580 fn post_render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2582
2583 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 fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
2598 proposal
2599 }
2600
2601 fn transform_size(&self, size: Size) -> Size {
2603 size
2604 }
2605
2606 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 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#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
2630pub struct TelemetryData {
2631 pub frame_time_ms: f32,
2632 pub p99_frame_time_ms: f32,
2634 pub frame_jitter_ms: f32,
2636 pub hardware_stall_detected: bool,
2638
2639 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 pub berserker_rage: f32,
2651
2652 pub vram_usage_mb: f32,
2654 pub vram_textures_mb: f32,
2655 pub vram_buffers_mb: f32,
2656 pub vram_pipelines_mb: f32,
2657 pub vram_exhausted: bool,
2659}
2660
2661#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2663pub struct FrameBudget {
2664 pub target_ms: f32,
2666 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
2680pub trait ElapsedTime {
2688 fn elapsed_time(&self) -> f32;
2690
2691 fn delta_time(&self) -> f32;
2693}
2694
2695pub trait Renderer: ElapsedTime + Send {
2702 fn request_redraw(&mut self) {}
2705
2706 fn is_over_budget(&self) -> bool {
2709 false
2710 }
2711
2712 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 fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]);
2717
2718 fn draw_3d_cube(&mut self, _rect: Rect, _color: [f32; 4], _rotation: [f32; 3]) {}
2721
2722 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 fn stroke_ellipse(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
2727 fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: [f32; 4], stroke_width: f32);
2729 fn fill_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4]) {}
2731 fn stroke_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4], _stroke_width: f32) {}
2733
2734 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]);
2736 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 fn draw_texture(&mut self, _texture_id: u32, _rect: Rect) {}
2754 fn draw_image(&mut self, _image_name: &str, _rect: Rect) {}
2756 fn load_image(&mut self, _name: &str, _data: &[u8]) {}
2758 fn prewarm_vram(&mut self, _assets: Vec<(String, Vec<u8>)>) {}
2761
2762 fn get_pointer_position(&self) -> [f32; 2] {
2764 [0.0, 0.0]
2765 }
2766
2767 fn upload_data_texture(&mut self, _id: &str, _data: &[f32], _width: u32, _height: u32) {}
2770 fn draw_heatmap(&mut self, _texture_id: &str, _rect: Rect, _palette: &str) {}
2772
2773 fn draw_mesh(&mut self, _mesh: &Mesh, _color: [f32; 4], _transform: glam::Mat4) {}
2776
2777 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 fn draw_radial_gradient(
2789 &mut self,
2790 _rect: Rect,
2791 _inner_color: [f32; 4],
2792 _outer_color: [f32; 4],
2793 ) {
2794 }
2795 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 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 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 fn push_clip_rect(&mut self, _rect: Rect) {}
2832 fn pop_clip_rect(&mut self) {}
2834 fn current_clip_rect(&self) -> Rect {
2837 Rect::new(-10000.0, -10000.0, 20000.0, 20000.0)
2838 }
2839
2840 fn push_opacity(&mut self, _opacity: f32) {}
2844 fn pop_opacity(&mut self) {}
2846
2847 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 fn set_scene(&mut self, _scene: &str) {}
2854
2855 fn capture_png(&mut self) -> Vec<u8> {
2858 Vec::new()
2859 }
2860 fn print(&mut self) {}
2862
2863 fn set_scene_preset(&mut self, _preset: u32) {}
2864
2865 fn bifrost(&mut self, _rect: Rect, _blur: f32, _saturation: f32, _opacity: f32) {}
2868 fn gungnir(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32, _intensity: f32) {}
2870 fn mani_glow(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32) {}
2872 fn push_mjolnir_slice(&mut self, _angle: f32, _offset: f32) {}
2874 fn pop_mjolnir_slice(&mut self) {}
2875 fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer));
2879 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 fn draw_mjolnir_bolt(&mut self, _from: [f32; 2], _to: [f32; 2], _color: [f32; 4]) {}
2884
2885 fn set_aria_role(&mut self, _role: &str) {}
2887 fn set_aria_label(&mut self, _label: &str) {}
2888
2889 fn register_shared_element(&mut self, _id: &str, _rect: Rect) {}
2891
2892 fn set_key(&mut self, _key: &str) {}
2894
2895 fn get_telemetry(&self) -> TelemetryData {
2898 TelemetryData::default()
2899 }
2900
2901 fn push_shadow(&mut self, _radius: f32, _color: [f32; 4], _offset: [f32; 2]) {}
2904 fn pop_shadow(&mut self) {}
2906
2907 fn push_vnode(&mut self, _rect: Rect, _name: &'static str) {}
2910 fn pop_vnode(&mut self) {}
2912 fn register_handler(
2914 &mut self,
2915 _event_type: &str,
2916 _handler: std::sync::Arc<dyn Fn(Event) + Send + Sync>,
2917 ) {
2918 }
2919
2920 fn set_z_index(&mut self, _z: f32) {}
2924 fn get_z_index(&self) -> f32 {
2926 0.0
2927 }
2928
2929 fn load_svg(&mut self, _name: &str, _svg_data: &[u8]) {}
2932 fn draw_svg(&mut self, _name: &str, _rect: Rect) {}
2934 fn serialize_svg(&mut self, _name: &str) -> Result<String, String> {
2938 Err("SVG serialization not supported by this renderer".into())
2939 }
2940 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 fn push_transform(&mut self, _translation: [f32; 2], _scale: [f32; 2], _rotation: f32) {}
2957 fn push_affine(&mut self, _transform: [f32; 6]) {}
2960 fn pop_transform(&mut self) {}
2962 fn query_layout(&self, _node_id: scene_graph::NodeId) -> Option<Rect> {
2964 None
2965 }
2966 fn set_debug_layout(&mut self, _enabled: bool) {}
2968 fn get_debug_layout(&self) -> bool {
2970 false
2971 }
2972
2973 fn set_material(&mut self, _material: crate::material::DrawMaterial) {}
2977 fn current_material(&self) -> crate::material::DrawMaterial {
2979 crate::material::DrawMaterial::Opaque
2980 }
2981
2982 fn mimir_intent(&self) -> [f32; 2] {
2985 [0.0, 0.0]
2986 }
2987 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 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 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 fn set_sdf_shape(&mut self, _shape: crate::layout::SdfShape) {}
3023}
3024
3025pub mod accessibility {
3027 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 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#[derive(
3049 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
3050)]
3051pub enum RenderTier {
3052 Tier1GPU = 0,
3054 Tier2GPU = 1,
3056 Tier3Fallback = 2,
3058}
3059use bytemuck::{Pod, Zeroable};
3063#[repr(C)]
3065#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3066pub struct ColorTheme {
3067 pub primary_neon: [f32; 4], 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], 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 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 pub fn midgard() -> Self {
3101 Self {
3102 primary_neon: [0.2, 0.4, 0.6, 1.0], shatter_neon: [0.5, 0.5, 0.5, 1.0], glass_base: [0.1, 0.12, 0.15, 1.0], glass_edge: [0.3, 0.35, 0.4, 1.0], rune_glow: [0.8, 0.8, 0.8, 0.0], 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], glass_blur_strength: 0.0, 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], glass_edge: [0.7, 0.85, 1.0, 0.45], 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#[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], }
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#[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}
3244pub trait FrameRenderer<E = ()>: Renderer {
3247 fn begin_frame(&mut self) -> E;
3248 fn render_frame(&mut self) {
3249 }
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#[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 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 pub fn with_resolution(mut self, resolution: agents::ConflictResolution) -> Self {
3289 self.resolution = resolution;
3290 self
3291 }
3292 pub fn get(&self) -> T {
3294 (**self.swap.load()).clone()
3295 }
3296 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(¤t);
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 pub fn version(&self) -> u64 {
3416 self.version.load(std::sync::atomic::Ordering::Acquire)
3417 }
3418 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};
3426pub 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();
3435static STATE_WRITE_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
3438pub fn is_batching() -> bool {
3440 IS_BATCHING.load(Ordering::Acquire)
3441}
3442pub fn is_rendering() -> bool {
3444 IS_RENDERING.load(Ordering::Acquire)
3445}
3446pub fn begin_render_phase() {
3448 IS_RENDERING.store(true, Ordering::Release);
3449}
3450pub fn end_render_phase() {
3452 IS_RENDERING.store(false, Ordering::Release);
3453}
3454pub 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}
3462pub fn batch<F: FnOnce()>(f: F) {
3466 if IS_BATCHING.swap(true, Ordering::AcqRel) {
3467 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}
3483pub 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(¤t));
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(¤t);
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 pub fn new() -> Self {
3549 Self::default()
3550 }
3551 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 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 let any_ref = lock.read().ok()?;
3566 if any_ref.is::<T>() {
3567 drop(any_ref);
3569 let cloned: Arc<std::sync::RwLock<dyn std::any::Any + Send + Sync>> = Arc::clone(lock);
3570 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 pub fn remember(&mut self, fragment: KnowledgeFragment) {
3582 self.fragments.insert(fragment.id.clone(), fragment);
3583 }
3584 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 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 pub fn snapshot(&self) -> Vec<NodeStateSnapshot> {
3608 let mut snapshots = Vec::new();
3609 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#[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 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 pub fn get(&self) -> T {
3638 (**self.swap.load()).clone()
3639 }
3640 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 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;
3716pub(crate) static ENVIRONMENT: OnceLock<
3718 Mutex<HashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>>,
3719> = OnceLock::new();
3720pub trait EnvKey: 'static + Send + Sync {
3723 type Value: Clone + Send + Sync + 'static;
3725 fn default_value() -> Self::Value;
3727}
3728pub struct YggdrasilKey;
3730impl EnvKey for YggdrasilKey {
3731 type Value = YggdrasilTokens;
3732 fn default_value() -> Self::Value {
3733 default_tokens()
3734 }
3735}
3736#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3739pub enum Appearance {
3740 Light,
3741 Dark,
3742}
3743#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3745pub enum Orientation {
3746 Horizontal,
3747 Vertical,
3748}
3749#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3751pub struct GridPlacement {
3752 pub column: i32,
3754 pub column_span: u32,
3756 pub row: i32,
3758 pub row_span: u32,
3760}
3761#[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#[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#[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 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 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 pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
3893 Self { r, g, b, a }
3894 }
3895 pub fn as_array(&self) -> [f32; 4] {
3897 [self.r, self.g, self.b, self.a]
3898 }
3899
3900 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 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}
3933pub struct AppearanceKey;
3935impl EnvKey for AppearanceKey {
3936 type Value = Appearance;
3937 fn default_value() -> Self::Value {
3938 Appearance::Dark }
3940}
3941pub struct StyleResolver;
3943impl StyleResolver {
3944 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()) }
3953 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 pub fn color_array(key: &str) -> [f32; 4] {
3964 let hex = Self::color(key);
3965 parse_hex_color(&hex)
3966 }
3967}
3968
3969fn 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] }
3985}
3986
3987pub fn default_tokens() -> YggdrasilTokens {
3989 let mut tokens = YggdrasilTokens::new();
3990 tokens.color.insert(
3992 "background".to_string(),
3993 TokenValue::Single {
3994 value: "#000000".to_string(), },
3996 );
3997 tokens.color.insert(
3998 "primary".to_string(),
3999 TokenValue::Single {
4000 value: "#00FFFF".to_string(), },
4002 );
4003 tokens.color.insert(
4004 "secondary".to_string(),
4005 TokenValue::Single {
4006 value: "#FF00FF".to_string(), },
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 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(), },
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 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 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 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 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 tokens.accessibility.insert(
4216 "reduce_motion".to_string(),
4217 TokenValue::Single {
4218 value: "false".to_string(),
4219 },
4220 );
4221 tokens
4222}
4223pub 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 pub fn new() -> Self {
4235 Self {
4236 _marker: std::marker::PhantomData,
4237 }
4238 }
4239 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}
4267pub mod env {
4269 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 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#[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#[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 pub fn all(value: f32) -> Self {
4315 Self {
4316 top: value,
4317 leading: value,
4318 bottom: value,
4319 trailing: value,
4320 }
4321 }
4322
4323 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 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#[derive(Debug, Clone, Copy, PartialEq)]
4348pub struct FrameModifier {
4349 pub width: Option<f32>,
4351 pub height: Option<f32>,
4353 pub min_width: Option<f32>,
4355 pub max_width: Option<f32>,
4357 pub min_height: Option<f32>,
4359 pub max_height: Option<f32>,
4361 pub alignment: Alignment,
4363}
4364
4365impl Default for FrameModifier {
4366 fn default() -> Self {
4368 Self::new()
4369 }
4370}
4371
4372impl FrameModifier {
4373 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 pub fn width(mut self, width: f32) -> Self {
4388 self.width = Some(width);
4389 self
4390 }
4391
4392 pub fn height(mut self, height: f32) -> Self {
4394 self.height = Some(height);
4395 self
4396 }
4397
4398 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 pub fn min_width(mut self, min_width: f32) -> Self {
4407 self.min_width = Some(min_width);
4408 self
4409 }
4410
4411 pub fn max_width(mut self, max_width: f32) -> Self {
4413 self.max_width = Some(max_width);
4414 self
4415 }
4416
4417 pub fn min_height(mut self, min_height: f32) -> Self {
4419 self.min_height = Some(min_height);
4420 self
4421 }
4422
4423 pub fn max_height(mut self, max_height: f32) -> Self {
4425 self.max_height = Some(max_height);
4426 self
4427 }
4428
4429 pub fn alignment(mut self, alignment: Alignment) -> Self {
4431 self.alignment = alignment;
4432 self
4433 }
4434}
4435
4436impl ViewModifier for FrameModifier {
4437 fn modify<V: View>(self, content: V) -> impl View {
4439 ModifiedView::new(content, self)
4440 }
4441
4442 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 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 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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4555pub struct GridPlacementModifier {
4556 pub placement: GridPlacement,
4558}
4559
4560impl ViewModifier for GridPlacementModifier {
4561 fn modify<V: View>(self, content: V) -> impl View {
4563 ModifiedView::new(content, self)
4564 }
4565
4566 fn get_grid_placement(&self) -> Option<GridPlacement> {
4568 Some(self.placement)
4569 }
4570}
4571
4572#[derive(Clone)]
4575pub struct OverlayModifier {
4576 pub overlay: AnyView,
4578 pub alignment: Alignment,
4580 pub offset: [f32; 2],
4582 pub on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
4584}
4585
4586impl ViewModifier for OverlayModifier {
4587 fn modify<V: View>(self, content: V) -> impl View {
4589 ModifiedView::new(content, self)
4590 }
4591
4592 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4594 view.render(renderer, rect);
4596
4597 let overlay_size = self
4599 .overlay
4600 .intrinsic_size(renderer, SizeProposal::unspecified());
4601
4602 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 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 self.overlay.render(renderer, overlay_rect);
4660 }
4661}
4662
4663#[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#[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#[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#[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#[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#[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
4764pub mod layout {
4766 use super::*;
4767
4768 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4771 pub struct LayoutKey {
4772 pub view_hash: u64,
4773 pub generation: u64,
4774 }
4775
4776 pub struct LayoutCache {
4778 pub safe_area: SafeArea,
4779 size_cache: HashMap<(u64, u32, u32), Size>, 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 pub fn generation(&self) -> u64 {
4803 self.generation
4804 }
4805
4806 pub fn invalidate(&mut self) {
4810 self.generation = self.generation.wrapping_add(1);
4811 }
4812
4813 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 pub fn invalidate_view(&mut self, view_hash: u64) {
4838 self.size_cache.retain(|&(hash, _, _), _| hash != view_hash);
4839 }
4840 }
4841
4842 #[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 pub trait LayoutView: Send {
4885 fn size_that_fits(
4887 &self,
4888 proposal: SizeProposal,
4889 subviews: &[&dyn LayoutView],
4890 cache: &mut LayoutCache,
4891 ) -> Size;
4892
4893 fn place_subviews(
4895 &self,
4896 bounds: Rect,
4897 subviews: &mut [&mut dyn LayoutView],
4898 cache: &mut LayoutCache,
4899 );
4900
4901 fn flex_weight(&self) -> f32 {
4903 0.0
4904 }
4905
4906 fn debug_layout(&self, indent: usize) -> String {
4909 let prefix = " ".repeat(indent);
4910 format!("{}LayoutView", prefix)
4911 }
4912 }
4913 #[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 #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
4944 pub struct SafeArea {
4945 pub insets: EdgeInsets,
4946 }
4947
4948 #[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 #[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 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 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
5046pub use layout::{LayoutCache, LayoutKey, LayoutView, Rect, SizeProposal};
5048pub 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
5061pub trait AssetManager: Send + Sync {
5065 fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>>;
5067
5068 fn preload_image(&self, url: &str);
5070}
5071
5072#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5074pub enum TouchPhase {
5075 Began,
5077 Moved,
5079 Ended,
5081 Cancelled,
5083}
5084
5085#[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 tilt: Option<f32>,
5094 azimuth: Option<f32>,
5095 pressure: Option<f32>,
5096 barrel_rotation: Option<f32>,
5097 },
5098 PointerUp {
5099 x: f32,
5100 y: f32,
5101 button: u32,
5102 tilt: Option<f32>,
5103 azimuth: Option<f32>,
5104 pressure: Option<f32>,
5105 barrel_rotation: Option<f32>,
5106 },
5107 PointerMove {
5108 x: f32,
5109 y: f32,
5110 proximity_field: f32,
5111 tilt: Option<f32>,
5112 azimuth: Option<f32>,
5113 pressure: Option<f32>,
5114 barrel_rotation: Option<f32>,
5115 },
5116 PointerClick {
5117 x: f32,
5118 y: f32,
5119 button: u32,
5120 tilt: Option<f32>,
5121 azimuth: Option<f32>,
5122 pressure: Option<f32>,
5123 barrel_rotation: Option<f32>,
5124 },
5125 PointerEnter,
5126 PointerLeave,
5127 PointerWheel {
5130 x: f32,
5131 y: f32,
5132 delta_x: f32,
5133 delta_y: f32,
5134 },
5135 PointerDoubleClick {
5137 x: f32,
5138 y: f32,
5139 button: u32,
5140 },
5141 DragStart {
5143 x: f32,
5144 y: f32,
5145 button: u32,
5146 },
5147 DragMove {
5149 x: f32,
5150 y: f32,
5151 },
5152 DragEnd {
5154 x: f32,
5155 y: f32,
5156 },
5157 KeyDown {
5158 key: String,
5159 },
5160 KeyUp {
5161 key: String,
5162 },
5163 FocusIn,
5165 FocusOut,
5167 Copy,
5169 Cut,
5171 Paste(String),
5173 Ime(String),
5175 TouchStart {
5177 x: f32,
5178 y: f32,
5179 touch_id: u64,
5180 },
5181 TouchMove {
5183 x: f32,
5184 y: f32,
5185 touch_id: u64,
5186 },
5187 TouchEnd {
5189 x: f32,
5190 y: f32,
5191 touch_id: u64,
5192 },
5193 TouchCancel {
5195 touch_id: u64,
5196 },
5197 GesturePinch {
5203 center: [f32; 2],
5204 scale: f32,
5205 velocity: f32,
5206 phase: TouchPhase,
5207 },
5208 GestureSwipe {
5213 direction: [f32; 2],
5214 velocity: f32,
5215 phase: TouchPhase,
5216 },
5217 FileDrop {
5219 path: String,
5220 },
5221}
5222
5223impl Event {
5224 pub fn name(&self) -> &'static str {
5226 match self {
5227 Self::PointerDown { .. } => "pointerdown",
5228 Self::PointerUp { .. } => "pointerup",
5229 Self::PointerMove { .. } => "pointermove",
5230 Self::PointerClick { .. } => "pointerclick",
5231 Self::PointerEnter => "pointerenter",
5232 Self::PointerLeave => "pointerleave",
5233 Self::PointerWheel { .. } => "pointerwheel",
5234 Self::PointerDoubleClick { .. } => "pointerdoubleclick",
5235 Self::DragStart { .. } => "dragstart",
5236 Self::DragMove { .. } => "dragmove",
5237 Self::DragEnd { .. } => "dragend",
5238 Self::KeyDown { .. } => "keydown",
5239 Self::KeyUp { .. } => "keyup",
5240 Self::FocusIn => "focusin",
5241 Self::FocusOut => "focusout",
5242 Self::Copy => "copy",
5243 Self::Cut => "cut",
5244 Self::Paste(_) => "paste",
5245 Self::Ime(_) => "ime",
5246 Self::TouchStart { .. } => "touchstart",
5247 Self::TouchMove { .. } => "touchmove",
5248 Self::TouchEnd { .. } => "touchend",
5249 Self::TouchCancel { .. } => "touchcancel",
5250 Self::GesturePinch { .. } => "gesturepinch",
5251 Self::GestureSwipe { .. } => "gestureswipe",
5252 Self::FileDrop { .. } => "filedrop",
5253 }
5254 }
5255}
5256
5257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5259pub enum EventResponse {
5260 Handled,
5261 Ignored,
5262}
5263
5264pub struct DefaultAssetManager {
5266 cache: AssetCache,
5267}
5268type AssetCache = Arc<arc_swap::ArcSwap<HashMap<String, AssetState<Arc<Vec<u8>>>>>>;
5269
5270impl Default for DefaultAssetManager {
5271 fn default() -> Self {
5272 Self::new()
5273 }
5274}
5275
5276impl DefaultAssetManager {
5277 pub fn new() -> Self {
5278 Self {
5279 cache: Arc::new(arc_swap::ArcSwap::from_pointee(HashMap::new())),
5280 }
5281 }
5282}
5283
5284impl AssetManager for DefaultAssetManager {
5285 fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>> {
5286 if let Some(state) = self.cache.load().get(url) {
5287 return state.clone();
5288 }
5289
5290 self.cache.rcu(|map| {
5291 let mut m = (**map).clone();
5292 m.entry(url.to_string()).or_insert(AssetState::Loading);
5293 m
5294 });
5295 AssetState::Loading
5296 }
5297
5298 fn preload_image(&self, _url: &str) {}
5299}
5300
5301use std::future::Future;
5302
5303pub struct Suspense<T: Clone + Send + Sync + 'static> {
5306 inner: State<AssetState<T>>,
5307}
5308
5309impl<T: Clone + Send + Sync + 'static> Default for Suspense<T> {
5310 fn default() -> Self {
5311 Self::new()
5312 }
5313}
5314
5315impl<T: Clone + Send + Sync + 'static> Suspense<T> {
5316 pub fn new() -> Self {
5317 Self {
5318 inner: State::new(AssetState::Loading),
5319 }
5320 }
5321
5322 pub fn new_async<F>(future: F) -> Self
5323 where
5324 F: Future<Output = Result<T, String>> + Send + 'static,
5325 {
5326 let suspense = Self::new();
5327 let suspense_clone = suspense.clone();
5328
5329 #[cfg(not(target_arch = "wasm32"))]
5330 {
5331 if let Ok(handle) = tokio::runtime::Handle::try_current() {
5333 handle.spawn(async move {
5334 let result = future.await;
5335 match result {
5336 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5337 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5338 }
5339 });
5340 } else {
5341 std::thread::spawn(move || {
5342 let rt = tokio::runtime::Builder::new_current_thread()
5343 .enable_all()
5344 .build()
5345 .unwrap();
5346 rt.block_on(async {
5347 let result = future.await;
5348 match result {
5349 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5350 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5351 }
5352 });
5353 });
5354 }
5355 }
5356 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5357 {
5358 wasm_bindgen_futures::spawn_local(async move {
5359 let result = future.await;
5360 match result {
5361 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5362 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5363 }
5364 });
5365 }
5366
5367 suspense
5368 }
5369
5370 pub fn ready(value: T) -> Self {
5371 Self {
5372 inner: State::new(AssetState::Ready(value)),
5373 }
5374 }
5375
5376 pub fn error(message: impl Into<String>) -> Self {
5377 Self {
5378 inner: State::new(AssetState::Error(message.into())),
5379 }
5380 }
5381
5382 pub fn get(&self) -> AssetState<T> {
5383 self.inner.get()
5384 }
5385
5386 pub fn get_ref(&self) -> AssetState<T> {
5387 self.inner.get()
5388 }
5389
5390 pub fn is_loading(&self) -> bool {
5391 matches!(self.get(), AssetState::Loading)
5392 }
5393
5394 pub fn is_ready(&self) -> bool {
5395 matches!(self.get(), AssetState::Ready(_))
5396 }
5397
5398 pub fn is_error(&self) -> bool {
5399 matches!(self.get(), AssetState::Error(_))
5400 }
5401
5402 pub fn ready_value(&self) -> Option<T> {
5403 match self.get() {
5404 AssetState::Ready(value) => Some(value),
5405 _ => None,
5406 }
5407 }
5408
5409 pub fn error_message(&self) -> Option<String> {
5410 match self.get() {
5411 AssetState::Error(message) => Some(message),
5412 _ => None,
5413 }
5414 }
5415
5416 pub fn subscribe<F: Fn(&AssetState<T>) + Send + Sync + 'static>(&self, callback: F) {
5417 self.inner.subscribe(callback)
5418 }
5419
5420 pub fn inner_state(&self) -> &State<AssetState<T>> {
5421 &self.inner
5422 }
5423}
5424
5425impl<T: Clone + Send + Sync + 'static> Clone for Suspense<T> {
5426 fn clone(&self) -> Self {
5427 Self {
5428 inner: self.inner.clone(),
5429 }
5430 }
5431}
5432
5433impl<T: Clone + Send + Sync + 'static> From<T> for Suspense<T> {
5434 fn from(value: T) -> Self {
5435 Self::ready(value)
5436 }
5437}
5438
5439impl<T: Clone + Send + Sync + 'static> From<Result<T, String>> for Suspense<T> {
5440 fn from(result: Result<T, String>) -> Self {
5441 match result {
5442 Ok(value) => Self::ready(value),
5443 Err(error) => Self::error(error),
5444 }
5445 }
5446}
5447
5448#[cfg(test)]
5449mod phase1_test;
5450
5451#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5453pub enum BerserkerMode {
5454 Normal,
5455 Rage, Frenzy, GodMode, }
5459
5460pub trait Seer: Send + Sync {
5463 fn predict(&self, context: &str) -> String;
5465 fn whispers(&self) -> Vec<String>;
5467}
5468
5469#[cfg(test)]
5470mod vili_tests {
5471 use super::*;
5472
5473 struct DummyRenderer;
5474 impl ElapsedTime for DummyRenderer {
5475 fn elapsed_time(&self) -> f32 {
5476 0.0
5477 }
5478 fn delta_time(&self) -> f32 {
5479 0.0
5480 }
5481 }
5482 impl Renderer for DummyRenderer {
5483 fn fill_rect(&mut self, _r: Rect, _c: [f32; 4]) {}
5484 fn fill_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4]) {}
5485 fn fill_ellipse(&mut self, _r: Rect, _c: [f32; 4]) {}
5486 fn stroke_rect(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5487 fn stroke_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4], _w: f32) {}
5488 fn stroke_ellipse(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5489 fn draw_line(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, _c: [f32; 4], _w: f32) {}
5490 fn draw_text(&mut self, _t: &str, _x: f32, _y: f32, _s: f32, _c: [f32; 4]) {}
5491 fn measure_text(&mut self, _t: &str, _s: f32) -> (f32, f32) {
5492 (0.0, 0.0)
5493 }
5494 fn memoize(&mut self, _id: u64, _hash: u64, _r: &dyn Fn(&mut dyn Renderer)) {}
5495 }
5496
5497 #[test]
5498 fn test_magnetic_warp() {
5499 let renderer = DummyRenderer;
5500 let anchor = Rect {
5501 x: 100.0,
5502 y: 100.0,
5503 width: 50.0,
5504 height: 50.0,
5505 };
5506 let pointer = [125.0, 50.0];
5508 let warp = renderer.magnetic_warp(pointer, anchor, 1.0);
5511 assert!(warp[1] > 50.0);
5513
5514 let far_pointer = [500.0, 500.0];
5516 let far_warp = renderer.magnetic_warp(far_pointer, anchor, 1.0);
5517 assert_eq!(far_pointer, far_warp);
5518 }
5519
5520 #[test]
5521 fn test_mani_glow() {
5522 let renderer = DummyRenderer;
5523 let bounds = Rect {
5524 x: 0.0,
5525 y: 0.0,
5526 width: 100.0,
5527 height: 100.0,
5528 };
5529 let pointer_inside = [50.0, 50.0];
5530 let glow_max = renderer.mani_glow_intensity(pointer_inside, bounds, 120.0);
5531 assert_eq!(glow_max, 1.0);
5532
5533 let pointer_edge = [50.0, -10.0];
5534 let glow_partial = renderer.mani_glow_intensity(pointer_edge, bounds, 120.0);
5535 assert!(glow_partial > 0.0 && glow_partial < 1.0);
5536 }
5537
5538 #[test]
5539 fn test_fafnir_evolve() {
5540 let renderer = DummyRenderer;
5541 let bounds = Rect {
5542 x: 0.0,
5543 y: 0.0,
5544 width: 100.0,
5545 height: 100.0,
5546 };
5547 let pointer_inside = [50.0, 50.0];
5548 let scale = renderer.fafnir_evolve(pointer_inside, bounds, 1.2);
5549 assert_eq!(scale, 1.2); }
5551
5552 #[test]
5553 fn test_undo_manager_basic() {
5554 let mut manager = UndoManager::new(3, 0.5);
5555 let val = std::sync::Arc::new(std::sync::Mutex::new(0));
5556
5557 let v1 = val.clone();
5558 let v2 = val.clone();
5559 manager.push(
5560 "Add",
5561 move || *v1.lock().unwrap() -= 1,
5562 move || *v2.lock().unwrap() += 1,
5563 );
5564
5565 assert!(manager.can_undo());
5566 assert!(!manager.can_redo());
5567
5568 let undo = manager.undo().unwrap();
5569 undo();
5570 assert_eq!(*val.lock().unwrap(), -1);
5571 assert!(!manager.can_undo());
5572 assert!(manager.can_redo());
5573
5574 let redo = manager.redo().unwrap();
5575 redo();
5576 assert_eq!(*val.lock().unwrap(), 0);
5577 }
5578
5579 #[test]
5580 fn test_undo_manager_depth_limit() {
5581 let mut manager = UndoManager::new(2, 0.5);
5582 manager.push("1", || {}, || {});
5583 manager.push("2", || {}, || {});
5584 manager.push("3", || {}, || {});
5585
5586 assert_eq!(manager.stack.len(), 2);
5587 assert_eq!(manager.position, 2);
5588 }
5589
5590 #[test]
5591 fn test_undo_manager_coalescing() {
5592 let mut manager = UndoManager::new(10, 1.0);
5593 let count = std::sync::Arc::new(std::sync::Mutex::new(0));
5594
5595 let c = count.clone();
5596 manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5597
5598 let c = count.clone();
5599 manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5600
5601 assert_eq!(manager.stack.len(), 1);
5602
5603 let undo = manager.undo().unwrap();
5604 undo();
5605 assert_eq!(*count.lock().unwrap(), -2);
5606 }
5607}
5608
5609use std::cell::RefCell;
5621
5622thread_local! {
5623 static THEME_CONTEXT: RefCell<Option<color::SemanticColors>> = const { RefCell::new(None) };
5625}
5626
5627pub use color::SemanticColors;
5633
5634pub fn set_current_theme(colors: color::SemanticColors) {
5637 THEME_CONTEXT.with(|cell| {
5638 *cell.borrow_mut() = Some(colors);
5639 });
5640}
5641
5642pub fn clear_current_theme() {
5644 THEME_CONTEXT.with(|cell| {
5645 *cell.borrow_mut() = None;
5646 });
5647}
5648
5649pub fn use_theme() -> color::SemanticColors {
5664 THEME_CONTEXT.with(|cell| {
5665 cell.borrow()
5666 .clone()
5667 .unwrap_or_else(color::SemanticColors::dark)
5668 })
5669}
5670
5671pub mod color {
5680 use super::Color;
5681
5682 #[derive(Debug, Clone)]
5699 pub struct SemanticColors {
5700 pub primary: Color,
5702 pub secondary: Color,
5704 pub accent: Color,
5706 pub background: Color,
5708 pub surface: Color,
5710 pub error: Color,
5712 pub warning: Color,
5714 pub success: Color,
5716 pub text: Color,
5718 pub text_dim: Color,
5720 }
5721
5722 impl SemanticColors {
5723 pub fn dark() -> Self {
5725 Self {
5726 primary: Color::new(1.0, 0.84, 0.0, 1.0), secondary: Color::new(1.0, 0.0, 1.0, 1.0), accent: Color::new(1.0, 0.0, 0.4, 1.0), background: Color::new(0.02, 0.02, 0.05, 1.0), surface: Color::new(0.05, 0.05, 0.07, 1.0), error: Color::new(1.0, 0.2, 0.2, 1.0), warning: Color::new(1.0, 0.8, 0.0, 1.0), success: Color::new(0.0, 1.0, 0.5, 1.0), text: Color::new(0.95, 0.95, 1.0, 1.0), text_dim: Color::new(0.6, 0.6, 0.7, 1.0), }
5737 }
5738
5739 pub fn light() -> Self {
5741 Self {
5742 primary: Color::new(0.35, 0.30, 0.70, 1.0),
5743 secondary: Color::new(0.30, 0.50, 0.30, 1.0),
5744 accent: Color::new(0.30, 0.35, 0.75, 1.0),
5745 background: Color::new(0.97, 0.97, 0.98, 1.0),
5746 surface: Color::new(0.93, 0.93, 0.95, 1.0),
5747 error: Color::new(0.75, 0.15, 0.15, 1.0),
5748 warning: Color::new(0.80, 0.60, 0.0, 1.0),
5749 success: Color::new(0.15, 0.65, 0.30, 1.0),
5750 text: Color::new(0.08, 0.08, 0.10, 1.0),
5751 text_dim: Color::new(0.40, 0.40, 0.45, 1.0),
5752 }
5753 }
5754
5755 pub fn accent_states(&self) -> InteractiveColorStates {
5760 InteractiveColorStates::from_color(self.accent)
5761 }
5762
5763 pub fn primary_states(&self) -> InteractiveColorStates {
5765 InteractiveColorStates::from_color(self.primary)
5766 }
5767
5768 pub fn error_states(&self) -> InteractiveColorStates {
5770 InteractiveColorStates::from_color(self.error)
5771 }
5772
5773 pub fn success_states(&self) -> InteractiveColorStates {
5775 InteractiveColorStates::from_color(self.success)
5776 }
5777 }
5778
5779 #[derive(Debug, Clone)]
5784 pub struct InteractiveColorStates {
5785 pub default: Color,
5786 pub hover: Color,
5787 pub active: Color,
5788 pub focus: Color,
5789 pub disabled: Color,
5790 pub focus_ring: Color,
5791 }
5792
5793 impl InteractiveColorStates {
5794 pub fn from_color(base: Color) -> Self {
5803 Self {
5804 default: base,
5805 hover: base.lighten(0.15),
5806 active: base.darken(0.15),
5807 focus: base,
5808 disabled: Color::new(base.r, base.g, base.b, base.a * 0.4),
5809 focus_ring: Color::new(base.r, base.g, base.b, base.a * 0.7),
5810 }
5811 }
5812
5813 pub fn color_for(&self, state: InteractiveState) -> Color {
5815 match state {
5816 InteractiveState::Default => self.default,
5817 InteractiveState::Hover => self.hover,
5818 InteractiveState::Active => self.active,
5819 InteractiveState::Focus => self.focus,
5820 InteractiveState::Disabled => self.disabled,
5821 }
5822 }
5823 }
5824
5825 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5827 pub enum InteractiveState {
5828 Default,
5829 Hover,
5830 Active,
5831 Focus,
5832 Disabled,
5833 }
5834}
5835
5836pub fn use_state<T: Clone + Send + Sync + 'static>(
5855 id: u64,
5856 initial: T,
5857) -> (impl Fn() -> T, impl Fn(T)) {
5858 let already_exists = load_system_state().get_component_state::<T>(id).is_some();
5860 if !already_exists {
5861 update_system_state(|s| {
5862 let mut ns = s.clone();
5863 ns.set_component_state(id, initial.clone());
5864 ns
5865 });
5866 }
5867
5868 let getter = move || -> T {
5869 load_system_state()
5870 .get_component_state::<T>(id)
5871 .map(|arc_lock| {
5872 arc_lock
5873 .read()
5874 .ok()
5875 .map(|guard| (*guard).clone())
5876 .unwrap_or_else(|| initial.clone())
5877 })
5878 .unwrap_or_else(|| initial.clone())
5879 };
5880
5881 let setter = {
5882 move |value| {
5883 update_system_state(|s| {
5884 let mut ns = s.clone();
5885 ns.set_component_state(id, value);
5886 ns
5887 });
5888 }
5889 };
5890
5891 (getter, setter)
5892}
5893
5894pub fn use_state_hash(key: &str) -> u64 {
5906 use std::hash::{Hash, Hasher};
5907 let mut s = std::collections::hash_map::DefaultHasher::new();
5908 key.hash(&mut s);
5909 s.finish()
5910}
5911
5912thread_local! {
5922 static ACCESSIBILITY_PREFS: std::cell::RefCell<AccessibilityPreferences> =
5925 std::cell::RefCell::new(AccessibilityPreferences::default());
5926}
5927
5928#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5935pub struct AccessibilityPreferences {
5936 pub reduce_motion: bool,
5938 pub reduce_transparency: bool,
5940 pub increase_contrast: bool,
5942}
5943
5944impl AccessibilityPreferences {
5945 pub fn detect_from_system() -> Self {
5950 #[cfg(target_os = "macos")]
5951 {
5952 let reduce_motion = std::process::Command::new("defaults")
5954 .args(["read", "-g", "com.apple.universalaccess", "reduceMotion"])
5955 .output()
5956 .ok()
5957 .and_then(|o| String::from_utf8(o.stdout).ok())
5958 .map(|s| s.trim() == "1")
5959 .unwrap_or(false);
5960
5961 let reduce_transparency = std::process::Command::new("defaults")
5962 .args([
5963 "read",
5964 "-g",
5965 "com.apple.universalaccess",
5966 "reduceTransparency",
5967 ])
5968 .output()
5969 .ok()
5970 .and_then(|o| String::from_utf8(o.stdout).ok())
5971 .map(|s| s.trim() == "1")
5972 .unwrap_or(false);
5973
5974 let increase_contrast = std::process::Command::new("defaults")
5975 .args([
5976 "read",
5977 "-g",
5978 "com.apple.universalaccess",
5979 "increaseContrast",
5980 ])
5981 .output()
5982 .ok()
5983 .and_then(|o| String::from_utf8(o.stdout).ok())
5984 .map(|s| s.trim() == "1")
5985 .unwrap_or(false);
5986
5987 Self {
5988 reduce_motion,
5989 reduce_transparency,
5990 increase_contrast,
5991 }
5992 }
5993 #[cfg(not(target_os = "macos"))]
5994 {
5995 Self::default()
5996 }
5997 }
5998
5999 pub fn min_alpha(&self, requested: f32) -> f32 {
6001 if self.increase_contrast {
6002 requested.max(0.5)
6003 } else {
6004 requested
6005 }
6006 }
6007
6008 pub fn should_disable_glass(&self) -> bool {
6010 self.reduce_transparency
6011 }
6012
6013 pub fn should_reduce_motion(&self) -> bool {
6015 self.reduce_motion
6016 }
6017
6018 pub fn should_increase_contrast(&self) -> bool {
6020 self.increase_contrast
6021 }
6022}
6023
6024pub fn accessibility_preferences() -> AccessibilityPreferences {
6026 ACCESSIBILITY_PREFS.with(|p| *p.borrow())
6027}
6028
6029pub fn set_accessibility_preferences(prefs: AccessibilityPreferences) {
6034 ACCESSIBILITY_PREFS.with(|p| {
6035 *p.borrow_mut() = prefs;
6036 });
6037}
6038
6039pub trait ClipboardProvider: Send + Sync {
6048 fn read_text(&self) -> Option<String>;
6050 fn write_text(&self, text: &str);
6052}
6053
6054#[cfg(not(target_arch = "wasm32"))]
6058pub struct SystemClipboard;
6059
6060#[cfg(not(target_arch = "wasm32"))]
6061impl ClipboardProvider for SystemClipboard {
6062 fn read_text(&self) -> Option<String> {
6063 use std::process::Command;
6064 Command::new("pbpaste")
6066 .output()
6067 .ok()
6068 .and_then(|o| String::from_utf8(o.stdout).ok())
6069 }
6070
6071 fn write_text(&self, text: &str) {
6072 use std::process::Command;
6073 if let Ok(mut child) = Command::new("pbcopy")
6075 .stdin(std::process::Stdio::piped())
6076 .spawn()
6077 {
6078 if let Some(stdin) = child.stdin.as_mut() {
6079 use std::io::Write;
6080 let _ = stdin.write_all(text.as_bytes());
6081 }
6082 let _ = child.wait();
6083 }
6084 }
6085}
6086
6087#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6093pub enum TextDirection {
6094 Forward,
6095 Backward,
6096 Up,
6097 Down,
6098 LineStart,
6099 LineEnd,
6100 WordForward,
6101 WordBackward,
6102}
6103
6104#[derive(Debug, Clone, Default)]
6109pub struct TextInputState {
6110 pub text: String,
6112 pub cursor_pos: usize,
6114 pub selection_anchor: Option<usize>,
6117 pub focused: bool,
6119 pub caret_visible: bool,
6121 pub last_edit_time: f32,
6123}
6124
6125impl TextInputState {
6126 pub fn new(text: impl Into<String>) -> Self {
6128 let text = text.into();
6129 let cursor_pos = text.len();
6130 Self {
6131 text,
6132 cursor_pos,
6133 selection_anchor: None,
6134 focused: false,
6135 caret_visible: true,
6136 last_edit_time: 0.0,
6137 }
6138 }
6139
6140 pub fn selection_range(&self) -> Option<(usize, usize)> {
6143 self.selection_anchor.map(|anchor| {
6144 if anchor <= self.cursor_pos {
6145 (anchor, self.cursor_pos)
6146 } else {
6147 (self.cursor_pos, anchor)
6148 }
6149 })
6150 }
6151
6152 pub fn selected_text(&self) -> String {
6154 self.selection_range()
6155 .map(|(start, end)| self.text[start..end].to_string())
6156 .unwrap_or_default()
6157 }
6158
6159 pub fn insert(&mut self, new_text: &str) {
6161 if let Some((start, end)) = self.selection_range() {
6162 self.text.replace_range(start..end, new_text);
6163 self.cursor_pos = start + new_text.len();
6164 } else {
6165 self.text.insert_str(self.cursor_pos, new_text);
6166 self.cursor_pos += new_text.len();
6167 }
6168 self.selection_anchor = None;
6169 }
6170
6171 pub fn delete(&mut self, backward: bool, count: usize) -> String {
6174 if let Some((start, end)) = self.selection_range() {
6175 let deleted = self.text[start..end].to_string();
6176 self.text.replace_range(start..end, "");
6177 self.cursor_pos = start;
6178 self.selection_anchor = None;
6179 return deleted;
6180 }
6181
6182 if backward && self.cursor_pos > 0 {
6183 let start = self.cursor_pos.saturating_sub(count);
6184 let deleted = self.text[start..self.cursor_pos].to_string();
6185 self.text.replace_range(start..self.cursor_pos, "");
6186 self.cursor_pos = start;
6187 deleted
6188 } else if !backward && self.cursor_pos < self.text.len() {
6189 let end = (self.cursor_pos + count).min(self.text.len());
6190 let deleted = self.text[self.cursor_pos..end].to_string();
6191 self.text.replace_range(self.cursor_pos..end, "");
6192 deleted
6193 } else {
6194 String::new()
6195 }
6196 }
6197
6198 pub fn move_cursor(&mut self, direction: TextDirection, extend_selection: bool) {
6200 if !extend_selection {
6201 self.selection_anchor = None;
6202 } else if self.selection_anchor.is_none() {
6203 self.selection_anchor = Some(self.cursor_pos);
6204 }
6205
6206 match direction {
6207 TextDirection::Forward if self.cursor_pos < self.text.len() => {
6208 let next = self.text[self.cursor_pos..]
6210 .char_indices()
6211 .nth(1)
6212 .map(|(i, _)| self.cursor_pos + i)
6213 .unwrap_or(self.text.len());
6214 self.cursor_pos = next;
6215 }
6216 TextDirection::Backward if self.cursor_pos > 0 => {
6217 let prev = self.text[..self.cursor_pos]
6218 .char_indices()
6219 .next_back()
6220 .map(|(i, _)| i)
6221 .unwrap_or(0);
6222 self.cursor_pos = prev;
6223 }
6224 TextDirection::LineStart => {
6225 self.cursor_pos = 0;
6226 }
6227 TextDirection::LineEnd => {
6228 self.cursor_pos = self.text.len();
6229 }
6230 TextDirection::WordForward => {
6231 let rest = &self.text[self.cursor_pos..];
6233 let after_word = rest
6235 .char_indices()
6236 .find(|(_, c)| !c.is_alphanumeric())
6237 .map(|(i, _)| i)
6238 .unwrap_or(rest.len());
6239 let after_space = rest[after_word..]
6241 .char_indices()
6242 .find(|(_, c)| !c.is_whitespace())
6243 .map(|(i, _)| after_word + i)
6244 .unwrap_or(rest.len());
6245 self.cursor_pos = (self.cursor_pos + after_space).min(self.text.len());
6246 }
6247 TextDirection::WordBackward => {
6248 let before = &self.text[..self.cursor_pos];
6249 let before_word = before
6251 .char_indices()
6252 .rev()
6253 .find(|(_, c)| !c.is_whitespace())
6254 .map(|(i, _)| i)
6255 .unwrap_or(0);
6256 let word_start = before[..before_word]
6258 .char_indices()
6259 .rev()
6260 .find(|(_, c)| !c.is_alphanumeric())
6261 .map(|(i, _)| i)
6262 .unwrap_or(0);
6263 self.cursor_pos = word_start;
6264 }
6265 _ => {} }
6267
6268 if !extend_selection {
6269 self.selection_anchor = None;
6270 }
6271 }
6272
6273 pub fn select_all(&mut self) {
6275 self.cursor_pos = self.text.len();
6276 self.selection_anchor = Some(0);
6277 }
6278
6279 pub fn cursor_byte_pos(&self) -> usize {
6281 self.cursor_pos
6282 }
6283}
6284
6285#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6287pub struct NotificationAction {
6288 pub id: String,
6290 pub title: String,
6292 pub is_destructive: bool,
6294}
6295
6296#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6298pub enum NotificationPriority {
6299 Passive,
6301 #[default]
6303 Active,
6304 TimeSensitive,
6306}
6307
6308#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
6310pub struct Notification {
6311 pub id: String,
6313 pub app_name: Option<String>,
6315 pub title: String,
6317 pub body: String,
6319 pub icon: Option<String>,
6321 pub sound: Option<String>,
6323 pub actions: Vec<NotificationAction>,
6325 pub timeout: Option<f32>,
6327 pub priority: NotificationPriority,
6329 pub timestamp: f32,
6331 pub dismissed: bool,
6333}
6334
6335#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, thiserror::Error)]
6337pub enum NotificationError {
6338 #[error("Notification permission denied")]
6340 PermissionDenied,
6341 #[error("Failed to post notification")]
6343 PostFailed,
6344}
6345
6346#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6348pub enum NotificationPermission {
6349 Granted,
6351 Denied,
6353 #[default]
6355 NotDetermined,
6356}
6357
6358pub trait NotificationHandler: Send + Sync {
6360 fn show(&self, notification: Notification) -> Result<(), NotificationError>;
6362 fn dismiss(&self, id: &str) -> Result<(), NotificationError>;
6364 fn request_permission(&self) -> NotificationPermission;
6366}
6367
6368static NEXT_NOTIFICATION_ID: std::sync::atomic::AtomicUsize =
6369 std::sync::atomic::AtomicUsize::new(1);
6370
6371#[derive(Clone, Copy, Debug, Default)]
6373pub struct DefaultNotificationHandler;
6374
6375impl NotificationHandler for DefaultNotificationHandler {
6376 fn show(&self, notification: Notification) -> Result<(), NotificationError> {
6378 let mut notif = notification;
6379 if notif.id.is_empty() {
6380 let id = NEXT_NOTIFICATION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6381 notif.id = format!("notif_{}", id);
6382 }
6383 update_system_state(|state| {
6384 let mut new_state = state.clone();
6385 new_state.notifications.push(notif.clone());
6386 new_state
6387 });
6388 Ok(())
6389 }
6390
6391 fn dismiss(&self, id: &str) -> Result<(), NotificationError> {
6393 update_system_state(|state| {
6394 let mut new_state = state.clone();
6395 for notif in &mut new_state.notifications {
6396 if notif.id == id {
6397 notif.dismissed = true;
6398 }
6399 }
6400 new_state
6401 });
6402 Ok(())
6403 }
6404
6405 fn request_permission(&self) -> NotificationPermission {
6407 NotificationPermission::Granted
6408 }
6409}
6410
6411static NOTIFICATION_HANDLER: once_cell::sync::OnceCell<std::sync::Arc<dyn NotificationHandler>> =
6412 once_cell::sync::OnceCell::new();
6413
6414pub fn set_notification_handler(handler: std::sync::Arc<dyn NotificationHandler>) {
6416 let _ = NOTIFICATION_HANDLER.set(handler);
6417}
6418
6419pub fn get_notification_handler() -> std::sync::Arc<dyn NotificationHandler> {
6421 NOTIFICATION_HANDLER
6422 .get_or_init(|| std::sync::Arc::new(DefaultNotificationHandler))
6423 .clone()
6424}
6425
6426#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6428pub struct FileFilter {
6429 pub name: String,
6431 pub extensions: Vec<String>,
6433}
6434
6435#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
6437pub enum FileDialogMode {
6438 #[default]
6440 OpenFile,
6441 OpenDirectory,
6443 SaveFile,
6445}
6446
6447#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6449pub struct FileDialog {
6450 pub title: String,
6452 pub default_path: Option<String>,
6454 pub filters: Vec<FileFilter>,
6456 pub mode: FileDialogMode,
6458 pub allow_multiple: bool,
6460}
6461
6462#[derive(Debug, thiserror::Error)]
6464pub enum FileDialogError {
6465 #[error("File dialog cancelled")]
6467 Cancelled,
6468 #[error("I/O error: {0}")]
6470 Io(#[from] std::io::Error),
6471 #[error("Platform error: {0}")]
6473 Platform(String),
6474}
6475
6476impl FileDialog {
6477 pub fn new(mode: FileDialogMode) -> Self {
6479 Self {
6480 mode,
6481 ..Default::default()
6482 }
6483 }
6484
6485 pub fn title(mut self, title: impl Into<String>) -> Self {
6487 self.title = title.into();
6488 self
6489 }
6490
6491 pub fn add_filter(mut self, name: &str, extensions: &[&str]) -> Self {
6493 self.filters.push(FileFilter {
6494 name: name.to_string(),
6495 extensions: extensions.iter().map(|s| s.to_string()).collect(),
6496 });
6497 self
6498 }
6499
6500 pub fn default_path(mut self, path: impl Into<String>) -> Self {
6502 self.default_path = Some(path.into());
6503 self
6504 }
6505
6506 pub fn allow_multiple(mut self, allow: bool) -> Self {
6508 self.allow_multiple = allow;
6509 self
6510 }
6511}
6512
6513#[cfg(not(target_arch = "wasm32"))]
6514impl FileDialog {
6515 pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6517 let mut dialog = rfd::FileDialog::new();
6518 dialog = dialog.set_title(&self.title);
6519 if let Some(path) = &self.default_path {
6520 dialog = dialog.set_directory(path);
6521 }
6522 for filter in &self.filters {
6523 let refs: Vec<&str> = filter.extensions.iter().map(|s| s.as_str()).collect();
6524 dialog = dialog.add_filter(&filter.name, &refs);
6525 }
6526
6527 match self.mode {
6528 FileDialogMode::OpenFile => {
6529 if self.allow_multiple {
6530 dialog.pick_files().ok_or(FileDialogError::Cancelled)
6531 } else {
6532 Ok(dialog.pick_file().into_iter().collect())
6533 }
6534 }
6535 FileDialogMode::OpenDirectory => Ok(dialog.pick_folder().into_iter().collect()),
6536 FileDialogMode::SaveFile => Ok(dialog.save_file().into_iter().collect()),
6537 }
6538 }
6539
6540 pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6542 let results = self.pick()?;
6543 Ok(results.into_iter().next())
6544 }
6545}
6546
6547#[cfg(target_arch = "wasm32")]
6548impl FileDialog {
6549 pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6551 Err(FileDialogError::Platform(
6552 "FileDialog is not supported synchronously on WebAssembly".to_string(),
6553 ))
6554 }
6555
6556 pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6558 Err(FileDialogError::Platform(
6559 "FileDialog is not supported synchronously on WebAssembly".to_string(),
6560 ))
6561 }
6562}
6563
6564#[derive(Debug, thiserror::Error)]
6566pub enum DocumentError {
6567 #[error("I/O error: {0}")]
6569 Io(#[from] std::io::Error),
6570 #[error("Parse error: {0}")]
6572 Parse(String),
6573 #[error("Serialization error: {0}")]
6575 Serialize(String),
6576}
6577
6578pub trait Document: Send + Sync {
6580 fn read_from(path: &std::path::Path) -> Result<Self, DocumentError>
6582 where
6583 Self: Sized;
6584
6585 fn write_to(&self, path: &std::path::Path) -> Result<(), DocumentError>;
6587
6588 fn is_dirty(&self) -> bool;
6590
6591 fn mark_clean(&mut self);
6593}
6594
6595pub struct AutoSaveManager {
6597 pub interval: f32,
6599 pub timer: f32,
6601 pub documents: Vec<(std::path::PathBuf, Box<dyn Document>)>,
6603}
6604
6605impl AutoSaveManager {
6606 pub fn new(interval: f32) -> Self {
6608 Self {
6609 interval,
6610 timer: 0.0,
6611 documents: Vec::new(),
6612 }
6613 }
6614
6615 pub fn register(&mut self, path: std::path::PathBuf, doc: Box<dyn Document>) {
6617 self.documents.push((path, doc));
6618 }
6619
6620 pub fn tick(&mut self, dt: f32) {
6622 self.timer += dt;
6623 if self.timer >= self.interval {
6624 self.timer = 0.0;
6625 for (path, doc) in &mut self.documents {
6626 if doc.is_dirty() {
6627 match doc.write_to(path) {
6628 Ok(()) => {
6629 doc.mark_clean();
6630 log::info!("[AutoSaveManager] Auto-saved document to {:?}", path);
6631 }
6632 Err(e) => {
6633 log::error!(
6634 "[AutoSaveManager] Failed to auto-save document to {:?}: {:?}",
6635 path,
6636 e
6637 );
6638 }
6639 }
6640 }
6641 }
6642 }
6643 }
6644}
6645
6646#[derive(Debug, Clone, PartialEq, Eq, Default)]
6654pub struct Modifiers {
6655 pub cmd: bool,
6657 pub shift: bool,
6659 pub alt: bool,
6661 pub ctrl: bool,
6663}
6664
6665#[derive(Debug, Clone)]
6667pub struct KeyboardShortcut {
6668 pub key: String,
6670 pub modifiers: Modifiers,
6672}
6673
6674impl KeyboardShortcut {
6675 pub fn cmd(key: impl Into<String>) -> Self {
6677 Self {
6678 key: key.into(),
6679 modifiers: Modifiers {
6680 cmd: true,
6681 ..Default::default()
6682 },
6683 }
6684 }
6685
6686 pub fn cmd_shift(key: impl Into<String>) -> Self {
6688 Self {
6689 key: key.into(),
6690 modifiers: Modifiers {
6691 cmd: true,
6692 shift: true,
6693 ..Default::default()
6694 },
6695 }
6696 }
6697}
6698
6699pub enum MenuItem {
6705 Action {
6707 label: String,
6708 shortcut: Option<KeyboardShortcut>,
6709 action: std::sync::Arc<dyn Fn() + Send + Sync>,
6710 enabled: bool,
6711 },
6712 Submenu { label: String, items: Vec<MenuItem> },
6714 Separator,
6716}
6717
6718impl std::fmt::Debug for MenuItem {
6719 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6720 match self {
6721 Self::Action { label, enabled, .. } => f
6722 .debug_struct("Action")
6723 .field("label", label)
6724 .field("enabled", enabled)
6725 .finish(),
6726 Self::Submenu { label, items } => f
6727 .debug_struct("Submenu")
6728 .field("label", label)
6729 .field("items", items)
6730 .finish(),
6731 Self::Separator => write!(f, "Separator"),
6732 }
6733 }
6734}
6735
6736pub struct MenuBar {
6741 pub items: Vec<MenuItem>,
6743}
6744
6745impl MenuBar {
6746 pub fn new() -> Self {
6748 Self { items: Vec::new() }
6749 }
6750
6751 pub fn add_item(&mut self, item: MenuItem) {
6753 self.items.push(item);
6754 }
6755
6756 #[allow(clippy::too_many_arguments)]
6768 pub fn standard(
6769 new_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6770 open_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6771 save_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6772 close_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6773 quit_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6774 undo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6775 redo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6776 cut_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6777 copy_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6778 paste_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6779 select_all_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6780 find_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6781 ) -> Self {
6782 let mut bar = Self::new();
6783
6784 bar.add_item(MenuItem::Submenu {
6786 label: "File".to_string(),
6787 items: vec![
6788 MenuItem::Action {
6789 label: "New".to_string(),
6790 shortcut: Some(KeyboardShortcut::cmd("n")),
6791 action: new_fn,
6792 enabled: true,
6793 },
6794 MenuItem::Action {
6795 label: "Open…".to_string(),
6796 shortcut: Some(KeyboardShortcut::cmd("o")),
6797 action: open_fn,
6798 enabled: true,
6799 },
6800 MenuItem::Separator,
6801 MenuItem::Action {
6802 label: "Save".to_string(),
6803 shortcut: Some(KeyboardShortcut::cmd("s")),
6804 action: save_fn,
6805 enabled: true,
6806 },
6807 MenuItem::Separator,
6808 MenuItem::Action {
6809 label: "Close".to_string(),
6810 shortcut: Some(KeyboardShortcut::cmd("w")),
6811 action: close_fn,
6812 enabled: true,
6813 },
6814 MenuItem::Separator,
6815 MenuItem::Action {
6816 label: "Quit".to_string(),
6817 shortcut: Some(KeyboardShortcut::cmd("q")),
6818 action: quit_fn,
6819 enabled: true,
6820 },
6821 ],
6822 });
6823
6824 bar.add_item(MenuItem::Submenu {
6826 label: "Edit".to_string(),
6827 items: vec![
6828 MenuItem::Action {
6829 label: "Undo".to_string(),
6830 shortcut: Some(KeyboardShortcut::cmd("z")),
6831 action: undo_fn,
6832 enabled: true,
6833 },
6834 MenuItem::Action {
6835 label: "Redo".to_string(),
6836 shortcut: Some(KeyboardShortcut::cmd_shift("z")),
6837 action: redo_fn,
6838 enabled: true,
6839 },
6840 MenuItem::Separator,
6841 MenuItem::Action {
6842 label: "Cut".to_string(),
6843 shortcut: Some(KeyboardShortcut::cmd("x")),
6844 action: cut_fn,
6845 enabled: true,
6846 },
6847 MenuItem::Action {
6848 label: "Copy".to_string(),
6849 shortcut: Some(KeyboardShortcut::cmd("c")),
6850 action: copy_fn,
6851 enabled: true,
6852 },
6853 MenuItem::Action {
6854 label: "Paste".to_string(),
6855 shortcut: Some(KeyboardShortcut::cmd("v")),
6856 action: paste_fn,
6857 enabled: true,
6858 },
6859 MenuItem::Separator,
6860 MenuItem::Action {
6861 label: "Select All".to_string(),
6862 shortcut: Some(KeyboardShortcut::cmd("a")),
6863 action: select_all_fn,
6864 enabled: true,
6865 },
6866 MenuItem::Separator,
6867 MenuItem::Action {
6868 label: "Find…".to_string(),
6869 shortcut: Some(KeyboardShortcut::cmd("f")),
6870 action: find_fn,
6871 enabled: true,
6872 },
6873 ],
6874 });
6875
6876 let noop: std::sync::Arc<dyn Fn() + Send + Sync> = std::sync::Arc::new(|| {});
6880 bar.add_item(MenuItem::Submenu {
6881 label: "View".to_string(),
6882 items: vec![
6883 MenuItem::Action {
6884 label: "Zoom In".to_string(),
6885 shortcut: Some(KeyboardShortcut::cmd("=")),
6886 action: noop.clone(),
6887 enabled: true,
6888 },
6889 MenuItem::Action {
6890 label: "Zoom Out".to_string(),
6891 shortcut: Some(KeyboardShortcut::cmd("-")),
6892 action: noop.clone(),
6893 enabled: true,
6894 },
6895 MenuItem::Separator,
6896 MenuItem::Action {
6897 label: "Toggle Fullscreen".to_string(),
6898 shortcut: Some(KeyboardShortcut {
6899 key: "f".to_string(),
6900 modifiers: Modifiers {
6901 ctrl: true,
6902 ..Default::default()
6903 },
6904 }),
6905 action: noop.clone(),
6906 enabled: true,
6907 },
6908 ],
6909 });
6910
6911 bar.add_item(MenuItem::Submenu {
6913 label: "Window".to_string(),
6914 items: vec![
6915 MenuItem::Action {
6916 label: "Minimize".to_string(),
6917 shortcut: Some(KeyboardShortcut::cmd("m")),
6918 action: noop.clone(),
6919 enabled: true,
6920 },
6921 MenuItem::Action {
6922 label: "Zoom".to_string(),
6923 shortcut: None,
6924 action: noop.clone(),
6925 enabled: true,
6926 },
6927 MenuItem::Separator,
6928 MenuItem::Action {
6929 label: "Bring All to Front".to_string(),
6930 shortcut: None,
6931 action: noop.clone(),
6932 enabled: true,
6933 },
6934 ],
6935 });
6936
6937 bar.add_item(MenuItem::Submenu {
6939 label: "Help".to_string(),
6940 items: vec![MenuItem::Action {
6941 label: "Search Help".to_string(),
6942 shortcut: None,
6943 action: noop,
6944 enabled: true,
6945 }],
6946 });
6947
6948 bar
6949 }
6950}
6951
6952impl Default for MenuBar {
6953 fn default() -> Self {
6954 Self::new()
6955 }
6956}
6957
6958use std::sync::RwLock;
6964
6965#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
6967pub enum Direction {
6968 #[default]
6969 LTR,
6970 RTL,
6971 Auto,
6972}
6973
6974impl Direction {
6975 pub fn is_rtl(self) -> bool {
6976 matches!(self, Direction::RTL)
6977 }
6978}
6979#[derive(Clone, Debug)]
6980pub struct L10nBundle {
6981 pub locale: String,
6982 pub strings: HashMap<String, String>,
6983 pub is_rtl: bool,
6984}
6985
6986impl L10nBundle {
6987 pub fn new(locale: impl Into<String>) -> Self {
6988 Self {
6989 locale: locale.into(),
6990 strings: HashMap::new(),
6991 is_rtl: false,
6992 }
6993 }
6994
6995 pub fn add(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
6996 self.strings.insert(key.into(), value.into());
6997 self
6998 }
6999
7000 pub fn from_strings_format(locale: impl Into<String>, input: &str) -> Self {
7001 let mut bundle = Self::new(locale);
7002 for line in input.lines() {
7003 let line = line.trim();
7004 if line.is_empty() || line.starts_with("//") {
7005 continue;
7006 }
7007 if let Some(eq_pos) = line.find(" = ") {
7008 let key = line[..eq_pos].trim_matches('"').to_string();
7009 let val = line[eq_pos + 3..]
7010 .trim_end_matches(';')
7011 .trim_matches('"')
7012 .to_string();
7013 bundle.strings.insert(key, val);
7014 }
7015 }
7016 bundle
7017 }
7018 pub fn t(&self, key: &str) -> String {
7020 self.strings
7021 .get(key)
7022 .map(|s| s.to_string())
7023 .unwrap_or_else(|| key.to_string())
7024 }
7025
7026 pub fn tf(&self, key: &str, args: &[&str]) -> String {
7028 let mut result = self.t(key);
7029 for (i, arg) in args.iter().enumerate() {
7030 result = result.replace(&format!("{{{}}}", i), arg);
7031 }
7032 result
7033 }
7034}
7035
7036pub struct L10n {
7038 bundles: HashMap<String, L10nBundle>,
7039 current: String,
7040}
7041
7042impl L10n {
7043 pub fn new(default_locale: &str) -> Self {
7044 Self {
7045 bundles: HashMap::new(),
7046 current: default_locale.to_string(),
7047 }
7048 }
7049
7050 pub fn add_bundle(&mut self, bundle: L10nBundle) {
7051 self.bundles.insert(bundle.locale.clone(), bundle);
7052 }
7053
7054 pub fn set_locale(&mut self, locale: &str) {
7055 self.current = locale.to_string();
7056 }
7057 pub fn current_locale(&self) -> &str {
7058 &self.current
7059 }
7060
7061 pub fn is_rtl(&self) -> bool {
7062 self.bundles
7063 .get(self.current.as_str())
7064 .map(|b| b.is_rtl)
7065 .unwrap_or(false)
7066 }
7067
7068 pub fn t(&self, key: &str) -> String {
7069 self.bundles
7070 .get(self.current.as_str())
7071 .map(|b| b.t(key))
7072 .unwrap_or_else(|| key.to_string())
7073 }
7074
7075 pub fn tf(&self, key: &str, args: &[&str]) -> String {
7076 let mut result = self.t(key);
7077 for (i, arg) in args.iter().enumerate() {
7078 result = result.replace(&format!("{{{}}}", i), arg);
7079 }
7080 result
7081 }
7082
7083 pub fn direction(&self) -> Direction {
7084 if self.is_rtl() {
7085 Direction::RTL
7086 } else {
7087 Direction::LTR
7088 }
7089 }
7090}
7091
7092static L10N: once_cell::sync::Lazy<Arc<RwLock<L10n>>> =
7093 once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(L10n::new("en"))));
7094
7095pub fn init_l10n(l10n: L10n) {
7096 if let Ok(mut guard) = L10N.write() {
7097 *guard = l10n;
7098 }
7099}
7100
7101pub fn l10n() -> Arc<RwLock<L10n>> {
7102 L10N.clone()
7103}
7104
7105pub fn t(key: &str) -> String {
7106 L10N.read()
7107 .map(|g| g.t(key).to_string())
7108 .unwrap_or_else(|_| key.to_string())
7109}
7110
7111pub fn tf(key: &str, args: &[&str]) -> String {
7112 L10N.read()
7113 .map(|g| g.tf(key, args))
7114 .unwrap_or_else(|_| key.to_string())
7115}
7116
7117pub fn set_locale(locale: &str) {
7118 if let Ok(mut guard) = L10N.write() {
7119 guard.set_locale(locale);
7120 }
7121}
7122
7123pub fn current_locale() -> String {
7124 L10N.read()
7125 .map(|g| g.current_locale().to_string())
7126 .unwrap_or_else(|_| "en".to_string())
7127}
7128
7129pub fn is_rtl() -> bool {
7130 L10N.read().map(|g| g.is_rtl()).unwrap_or(false)
7131}
7132
7133#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7145pub enum SystemTheme {
7146 #[default]
7148 Dark,
7149 Light,
7151}
7152
7153pub fn detect_system_theme() -> SystemTheme {
7163 std::env::var("CVKG_THEME")
7164 .ok()
7165 .and_then(|v| match v.as_str() {
7166 "light" => Some(SystemTheme::Light),
7167 "dark" => Some(SystemTheme::Dark),
7168 _ => None,
7169 })
7170 .unwrap_or(SystemTheme::Dark)
7171}
7172
7173pub mod audio_haptic;
7179pub use audio_haptic::{
7180 AudioEngine, HapticEngine, HapticIntensity, NullAudioEngine, NullHapticEngine, haptic_error,
7181 haptic_impact, haptic_selection, haptic_success, play_sound, set_audio_engine,
7182 set_haptic_engine, sounds,
7183};