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 },
5094 PointerUp {
5095 x: f32,
5096 y: f32,
5097 button: u32,
5098 },
5099 PointerMove {
5100 x: f32,
5101 y: f32,
5102 proximity_field: f32,
5103 },
5104 PointerClick {
5105 x: f32,
5106 y: f32,
5107 button: u32,
5108 },
5109 PointerEnter,
5110 PointerLeave,
5111 PointerWheel {
5114 x: f32,
5115 y: f32,
5116 delta_x: f32,
5117 delta_y: f32,
5118 },
5119 PointerDoubleClick {
5121 x: f32,
5122 y: f32,
5123 button: u32,
5124 },
5125 DragStart {
5127 x: f32,
5128 y: f32,
5129 button: u32,
5130 },
5131 DragMove {
5133 x: f32,
5134 y: f32,
5135 },
5136 DragEnd {
5138 x: f32,
5139 y: f32,
5140 },
5141 KeyDown {
5142 key: String,
5143 },
5144 KeyUp {
5145 key: String,
5146 },
5147 FocusIn,
5149 FocusOut,
5151 Copy,
5153 Cut,
5155 Paste(String),
5157 Ime(String),
5159 TouchStart {
5161 x: f32,
5162 y: f32,
5163 touch_id: u64,
5164 },
5165 TouchMove {
5167 x: f32,
5168 y: f32,
5169 touch_id: u64,
5170 },
5171 TouchEnd {
5173 x: f32,
5174 y: f32,
5175 touch_id: u64,
5176 },
5177 TouchCancel {
5179 touch_id: u64,
5180 },
5181 GesturePinch {
5187 center: [f32; 2],
5188 scale: f32,
5189 velocity: f32,
5190 phase: TouchPhase,
5191 },
5192 GestureSwipe {
5197 direction: [f32; 2],
5198 velocity: f32,
5199 phase: TouchPhase,
5200 },
5201 FileDrop {
5203 path: String,
5204 },
5205}
5206
5207impl Event {
5208 pub fn name(&self) -> &'static str {
5210 match self {
5211 Self::PointerDown { .. } => "pointerdown",
5212 Self::PointerUp { .. } => "pointerup",
5213 Self::PointerMove { .. } => "pointermove",
5214 Self::PointerClick { .. } => "pointerclick",
5215 Self::PointerEnter => "pointerenter",
5216 Self::PointerLeave => "pointerleave",
5217 Self::PointerWheel { .. } => "pointerwheel",
5218 Self::PointerDoubleClick { .. } => "pointerdoubleclick",
5219 Self::DragStart { .. } => "dragstart",
5220 Self::DragMove { .. } => "dragmove",
5221 Self::DragEnd { .. } => "dragend",
5222 Self::KeyDown { .. } => "keydown",
5223 Self::KeyUp { .. } => "keyup",
5224 Self::FocusIn => "focusin",
5225 Self::FocusOut => "focusout",
5226 Self::Copy => "copy",
5227 Self::Cut => "cut",
5228 Self::Paste(_) => "paste",
5229 Self::Ime(_) => "ime",
5230 Self::TouchStart { .. } => "touchstart",
5231 Self::TouchMove { .. } => "touchmove",
5232 Self::TouchEnd { .. } => "touchend",
5233 Self::TouchCancel { .. } => "touchcancel",
5234 Self::GesturePinch { .. } => "gesturepinch",
5235 Self::GestureSwipe { .. } => "gestureswipe",
5236 Self::FileDrop { .. } => "filedrop",
5237 }
5238 }
5239}
5240
5241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5243pub enum EventResponse {
5244 Handled,
5245 Ignored,
5246}
5247
5248pub struct DefaultAssetManager {
5250 cache: AssetCache,
5251}
5252type AssetCache = Arc<arc_swap::ArcSwap<HashMap<String, AssetState<Arc<Vec<u8>>>>>>;
5253
5254impl Default for DefaultAssetManager {
5255 fn default() -> Self {
5256 Self::new()
5257 }
5258}
5259
5260impl DefaultAssetManager {
5261 pub fn new() -> Self {
5262 Self {
5263 cache: Arc::new(arc_swap::ArcSwap::from_pointee(HashMap::new())),
5264 }
5265 }
5266}
5267
5268impl AssetManager for DefaultAssetManager {
5269 fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>> {
5270 if let Some(state) = self.cache.load().get(url) {
5271 return state.clone();
5272 }
5273
5274 self.cache.rcu(|map| {
5275 let mut m = (**map).clone();
5276 m.entry(url.to_string()).or_insert(AssetState::Loading);
5277 m
5278 });
5279 AssetState::Loading
5280 }
5281
5282 fn preload_image(&self, _url: &str) {}
5283}
5284
5285use std::future::Future;
5286
5287pub struct Suspense<T: Clone + Send + Sync + 'static> {
5290 inner: State<AssetState<T>>,
5291}
5292
5293impl<T: Clone + Send + Sync + 'static> Default for Suspense<T> {
5294 fn default() -> Self {
5295 Self::new()
5296 }
5297}
5298
5299impl<T: Clone + Send + Sync + 'static> Suspense<T> {
5300 pub fn new() -> Self {
5301 Self {
5302 inner: State::new(AssetState::Loading),
5303 }
5304 }
5305
5306 pub fn new_async<F>(future: F) -> Self
5307 where
5308 F: Future<Output = Result<T, String>> + Send + 'static,
5309 {
5310 let suspense = Self::new();
5311 let suspense_clone = suspense.clone();
5312
5313 #[cfg(not(target_arch = "wasm32"))]
5314 {
5315 if let Ok(handle) = tokio::runtime::Handle::try_current() {
5317 handle.spawn(async move {
5318 let result = future.await;
5319 match result {
5320 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5321 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5322 }
5323 });
5324 } else {
5325 std::thread::spawn(move || {
5326 let rt = tokio::runtime::Builder::new_current_thread()
5327 .enable_all()
5328 .build()
5329 .unwrap();
5330 rt.block_on(async {
5331 let result = future.await;
5332 match result {
5333 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5334 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5335 }
5336 });
5337 });
5338 }
5339 }
5340 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5341 {
5342 wasm_bindgen_futures::spawn_local(async move {
5343 let result = future.await;
5344 match result {
5345 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5346 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5347 }
5348 });
5349 }
5350
5351 suspense
5352 }
5353
5354 pub fn ready(value: T) -> Self {
5355 Self {
5356 inner: State::new(AssetState::Ready(value)),
5357 }
5358 }
5359
5360 pub fn error(message: impl Into<String>) -> Self {
5361 Self {
5362 inner: State::new(AssetState::Error(message.into())),
5363 }
5364 }
5365
5366 pub fn get(&self) -> AssetState<T> {
5367 self.inner.get()
5368 }
5369
5370 pub fn get_ref(&self) -> AssetState<T> {
5371 self.inner.get()
5372 }
5373
5374 pub fn is_loading(&self) -> bool {
5375 matches!(self.get(), AssetState::Loading)
5376 }
5377
5378 pub fn is_ready(&self) -> bool {
5379 matches!(self.get(), AssetState::Ready(_))
5380 }
5381
5382 pub fn is_error(&self) -> bool {
5383 matches!(self.get(), AssetState::Error(_))
5384 }
5385
5386 pub fn ready_value(&self) -> Option<T> {
5387 match self.get() {
5388 AssetState::Ready(value) => Some(value),
5389 _ => None,
5390 }
5391 }
5392
5393 pub fn error_message(&self) -> Option<String> {
5394 match self.get() {
5395 AssetState::Error(message) => Some(message),
5396 _ => None,
5397 }
5398 }
5399
5400 pub fn subscribe<F: Fn(&AssetState<T>) + Send + Sync + 'static>(&self, callback: F) {
5401 self.inner.subscribe(callback)
5402 }
5403
5404 pub fn inner_state(&self) -> &State<AssetState<T>> {
5405 &self.inner
5406 }
5407}
5408
5409impl<T: Clone + Send + Sync + 'static> Clone for Suspense<T> {
5410 fn clone(&self) -> Self {
5411 Self {
5412 inner: self.inner.clone(),
5413 }
5414 }
5415}
5416
5417impl<T: Clone + Send + Sync + 'static> From<T> for Suspense<T> {
5418 fn from(value: T) -> Self {
5419 Self::ready(value)
5420 }
5421}
5422
5423impl<T: Clone + Send + Sync + 'static> From<Result<T, String>> for Suspense<T> {
5424 fn from(result: Result<T, String>) -> Self {
5425 match result {
5426 Ok(value) => Self::ready(value),
5427 Err(error) => Self::error(error),
5428 }
5429 }
5430}
5431
5432#[cfg(test)]
5433mod phase1_test;
5434
5435#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5437pub enum BerserkerMode {
5438 Normal,
5439 Rage, Frenzy, GodMode, }
5443
5444pub trait Seer: Send + Sync {
5447 fn predict(&self, context: &str) -> String;
5449 fn whispers(&self) -> Vec<String>;
5451}
5452
5453#[cfg(test)]
5454mod vili_tests {
5455 use super::*;
5456
5457 struct DummyRenderer;
5458 impl ElapsedTime for DummyRenderer {
5459 fn elapsed_time(&self) -> f32 {
5460 0.0
5461 }
5462 fn delta_time(&self) -> f32 {
5463 0.0
5464 }
5465 }
5466 impl Renderer for DummyRenderer {
5467 fn fill_rect(&mut self, _r: Rect, _c: [f32; 4]) {}
5468 fn fill_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4]) {}
5469 fn fill_ellipse(&mut self, _r: Rect, _c: [f32; 4]) {}
5470 fn stroke_rect(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5471 fn stroke_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4], _w: f32) {}
5472 fn stroke_ellipse(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5473 fn draw_line(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, _c: [f32; 4], _w: f32) {}
5474 fn draw_text(&mut self, _t: &str, _x: f32, _y: f32, _s: f32, _c: [f32; 4]) {}
5475 fn measure_text(&mut self, _t: &str, _s: f32) -> (f32, f32) {
5476 (0.0, 0.0)
5477 }
5478 fn memoize(&mut self, _id: u64, _hash: u64, _r: &dyn Fn(&mut dyn Renderer)) {}
5479 }
5480
5481 #[test]
5482 fn test_magnetic_warp() {
5483 let renderer = DummyRenderer;
5484 let anchor = Rect {
5485 x: 100.0,
5486 y: 100.0,
5487 width: 50.0,
5488 height: 50.0,
5489 };
5490 let pointer = [125.0, 50.0];
5492 let warp = renderer.magnetic_warp(pointer, anchor, 1.0);
5495 assert!(warp[1] > 50.0);
5497
5498 let far_pointer = [500.0, 500.0];
5500 let far_warp = renderer.magnetic_warp(far_pointer, anchor, 1.0);
5501 assert_eq!(far_pointer, far_warp);
5502 }
5503
5504 #[test]
5505 fn test_mani_glow() {
5506 let renderer = DummyRenderer;
5507 let bounds = Rect {
5508 x: 0.0,
5509 y: 0.0,
5510 width: 100.0,
5511 height: 100.0,
5512 };
5513 let pointer_inside = [50.0, 50.0];
5514 let glow_max = renderer.mani_glow_intensity(pointer_inside, bounds, 120.0);
5515 assert_eq!(glow_max, 1.0);
5516
5517 let pointer_edge = [50.0, -10.0];
5518 let glow_partial = renderer.mani_glow_intensity(pointer_edge, bounds, 120.0);
5519 assert!(glow_partial > 0.0 && glow_partial < 1.0);
5520 }
5521
5522 #[test]
5523 fn test_fafnir_evolve() {
5524 let renderer = DummyRenderer;
5525 let bounds = Rect {
5526 x: 0.0,
5527 y: 0.0,
5528 width: 100.0,
5529 height: 100.0,
5530 };
5531 let pointer_inside = [50.0, 50.0];
5532 let scale = renderer.fafnir_evolve(pointer_inside, bounds, 1.2);
5533 assert_eq!(scale, 1.2); }
5535
5536 #[test]
5537 fn test_undo_manager_basic() {
5538 let mut manager = UndoManager::new(3, 0.5);
5539 let val = std::sync::Arc::new(std::sync::Mutex::new(0));
5540
5541 let v1 = val.clone();
5542 let v2 = val.clone();
5543 manager.push(
5544 "Add",
5545 move || *v1.lock().unwrap() -= 1,
5546 move || *v2.lock().unwrap() += 1,
5547 );
5548
5549 assert!(manager.can_undo());
5550 assert!(!manager.can_redo());
5551
5552 let undo = manager.undo().unwrap();
5553 undo();
5554 assert_eq!(*val.lock().unwrap(), -1);
5555 assert!(!manager.can_undo());
5556 assert!(manager.can_redo());
5557
5558 let redo = manager.redo().unwrap();
5559 redo();
5560 assert_eq!(*val.lock().unwrap(), 0);
5561 }
5562
5563 #[test]
5564 fn test_undo_manager_depth_limit() {
5565 let mut manager = UndoManager::new(2, 0.5);
5566 manager.push("1", || {}, || {});
5567 manager.push("2", || {}, || {});
5568 manager.push("3", || {}, || {});
5569
5570 assert_eq!(manager.stack.len(), 2);
5571 assert_eq!(manager.position, 2);
5572 }
5573
5574 #[test]
5575 fn test_undo_manager_coalescing() {
5576 let mut manager = UndoManager::new(10, 1.0);
5577 let count = std::sync::Arc::new(std::sync::Mutex::new(0));
5578
5579 let c = count.clone();
5580 manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5581
5582 let c = count.clone();
5583 manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5584
5585 assert_eq!(manager.stack.len(), 1);
5586
5587 let undo = manager.undo().unwrap();
5588 undo();
5589 assert_eq!(*count.lock().unwrap(), -2);
5590 }
5591}
5592
5593use std::cell::RefCell;
5605
5606thread_local! {
5607 static THEME_CONTEXT: RefCell<Option<color::SemanticColors>> = const { RefCell::new(None) };
5609}
5610
5611pub use color::SemanticColors;
5617
5618pub fn set_current_theme(colors: color::SemanticColors) {
5621 THEME_CONTEXT.with(|cell| {
5622 *cell.borrow_mut() = Some(colors);
5623 });
5624}
5625
5626pub fn clear_current_theme() {
5628 THEME_CONTEXT.with(|cell| {
5629 *cell.borrow_mut() = None;
5630 });
5631}
5632
5633pub fn use_theme() -> color::SemanticColors {
5648 THEME_CONTEXT.with(|cell| {
5649 cell.borrow()
5650 .clone()
5651 .unwrap_or_else(color::SemanticColors::dark)
5652 })
5653}
5654
5655pub mod color {
5664 use super::Color;
5665
5666 #[derive(Debug, Clone)]
5683 pub struct SemanticColors {
5684 pub primary: Color,
5686 pub secondary: Color,
5688 pub accent: Color,
5690 pub background: Color,
5692 pub surface: Color,
5694 pub error: Color,
5696 pub warning: Color,
5698 pub success: Color,
5700 pub text: Color,
5702 pub text_dim: Color,
5704 }
5705
5706 impl SemanticColors {
5707 pub fn dark() -> Self {
5709 Self {
5710 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), }
5721 }
5722
5723 pub fn light() -> Self {
5725 Self {
5726 primary: Color::new(0.35, 0.30, 0.70, 1.0),
5727 secondary: Color::new(0.30, 0.50, 0.30, 1.0),
5728 accent: Color::new(0.30, 0.35, 0.75, 1.0),
5729 background: Color::new(0.97, 0.97, 0.98, 1.0),
5730 surface: Color::new(0.93, 0.93, 0.95, 1.0),
5731 error: Color::new(0.75, 0.15, 0.15, 1.0),
5732 warning: Color::new(0.80, 0.60, 0.0, 1.0),
5733 success: Color::new(0.15, 0.65, 0.30, 1.0),
5734 text: Color::new(0.08, 0.08, 0.10, 1.0),
5735 text_dim: Color::new(0.40, 0.40, 0.45, 1.0),
5736 }
5737 }
5738
5739 pub fn accent_states(&self) -> InteractiveColorStates {
5744 InteractiveColorStates::from_color(self.accent)
5745 }
5746
5747 pub fn primary_states(&self) -> InteractiveColorStates {
5749 InteractiveColorStates::from_color(self.primary)
5750 }
5751
5752 pub fn error_states(&self) -> InteractiveColorStates {
5754 InteractiveColorStates::from_color(self.error)
5755 }
5756
5757 pub fn success_states(&self) -> InteractiveColorStates {
5759 InteractiveColorStates::from_color(self.success)
5760 }
5761 }
5762
5763 #[derive(Debug, Clone)]
5768 pub struct InteractiveColorStates {
5769 pub default: Color,
5770 pub hover: Color,
5771 pub active: Color,
5772 pub focus: Color,
5773 pub disabled: Color,
5774 pub focus_ring: Color,
5775 }
5776
5777 impl InteractiveColorStates {
5778 pub fn from_color(base: Color) -> Self {
5787 Self {
5788 default: base,
5789 hover: base.lighten(0.15),
5790 active: base.darken(0.15),
5791 focus: base,
5792 disabled: Color::new(base.r, base.g, base.b, base.a * 0.4),
5793 focus_ring: Color::new(base.r, base.g, base.b, base.a * 0.7),
5794 }
5795 }
5796
5797 pub fn color_for(&self, state: InteractiveState) -> Color {
5799 match state {
5800 InteractiveState::Default => self.default,
5801 InteractiveState::Hover => self.hover,
5802 InteractiveState::Active => self.active,
5803 InteractiveState::Focus => self.focus,
5804 InteractiveState::Disabled => self.disabled,
5805 }
5806 }
5807 }
5808
5809 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5811 pub enum InteractiveState {
5812 Default,
5813 Hover,
5814 Active,
5815 Focus,
5816 Disabled,
5817 }
5818}
5819
5820pub fn use_state<T: Clone + Send + Sync + 'static>(
5839 id: u64,
5840 initial: T,
5841) -> (impl Fn() -> T, impl Fn(T)) {
5842 let already_exists = load_system_state().get_component_state::<T>(id).is_some();
5844 if !already_exists {
5845 update_system_state(|s| {
5846 let mut ns = s.clone();
5847 ns.set_component_state(id, initial.clone());
5848 ns
5849 });
5850 }
5851
5852 let getter = move || -> T {
5853 load_system_state()
5854 .get_component_state::<T>(id)
5855 .map(|arc_lock| {
5856 arc_lock
5857 .read()
5858 .ok()
5859 .map(|guard| (*guard).clone())
5860 .unwrap_or_else(|| initial.clone())
5861 })
5862 .unwrap_or_else(|| initial.clone())
5863 };
5864
5865 let setter = {
5866 move |value| {
5867 update_system_state(|s| {
5868 let mut ns = s.clone();
5869 ns.set_component_state(id, value);
5870 ns
5871 });
5872 }
5873 };
5874
5875 (getter, setter)
5876}
5877
5878pub fn use_state_hash(key: &str) -> u64 {
5890 use std::hash::{Hash, Hasher};
5891 let mut s = std::collections::hash_map::DefaultHasher::new();
5892 key.hash(&mut s);
5893 s.finish()
5894}
5895
5896thread_local! {
5906 static ACCESSIBILITY_PREFS: std::cell::RefCell<AccessibilityPreferences> =
5909 std::cell::RefCell::new(AccessibilityPreferences::default());
5910}
5911
5912#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5919pub struct AccessibilityPreferences {
5920 pub reduce_motion: bool,
5922 pub reduce_transparency: bool,
5924 pub increase_contrast: bool,
5926}
5927
5928impl AccessibilityPreferences {
5929 pub fn detect_from_system() -> Self {
5934 #[cfg(target_os = "macos")]
5935 {
5936 let reduce_motion = std::process::Command::new("defaults")
5938 .args(["read", "-g", "com.apple.universalaccess", "reduceMotion"])
5939 .output()
5940 .ok()
5941 .and_then(|o| String::from_utf8(o.stdout).ok())
5942 .map(|s| s.trim() == "1")
5943 .unwrap_or(false);
5944
5945 let reduce_transparency = std::process::Command::new("defaults")
5946 .args([
5947 "read",
5948 "-g",
5949 "com.apple.universalaccess",
5950 "reduceTransparency",
5951 ])
5952 .output()
5953 .ok()
5954 .and_then(|o| String::from_utf8(o.stdout).ok())
5955 .map(|s| s.trim() == "1")
5956 .unwrap_or(false);
5957
5958 let increase_contrast = std::process::Command::new("defaults")
5959 .args([
5960 "read",
5961 "-g",
5962 "com.apple.universalaccess",
5963 "increaseContrast",
5964 ])
5965 .output()
5966 .ok()
5967 .and_then(|o| String::from_utf8(o.stdout).ok())
5968 .map(|s| s.trim() == "1")
5969 .unwrap_or(false);
5970
5971 Self {
5972 reduce_motion,
5973 reduce_transparency,
5974 increase_contrast,
5975 }
5976 }
5977 #[cfg(not(target_os = "macos"))]
5978 {
5979 Self::default()
5980 }
5981 }
5982
5983 pub fn min_alpha(&self, requested: f32) -> f32 {
5985 if self.increase_contrast {
5986 requested.max(0.5)
5987 } else {
5988 requested
5989 }
5990 }
5991
5992 pub fn should_disable_glass(&self) -> bool {
5994 self.reduce_transparency
5995 }
5996
5997 pub fn should_reduce_motion(&self) -> bool {
5999 self.reduce_motion
6000 }
6001
6002 pub fn should_increase_contrast(&self) -> bool {
6004 self.increase_contrast
6005 }
6006}
6007
6008pub fn accessibility_preferences() -> AccessibilityPreferences {
6010 ACCESSIBILITY_PREFS.with(|p| *p.borrow())
6011}
6012
6013pub fn set_accessibility_preferences(prefs: AccessibilityPreferences) {
6018 ACCESSIBILITY_PREFS.with(|p| {
6019 *p.borrow_mut() = prefs;
6020 });
6021}
6022
6023pub trait ClipboardProvider: Send + Sync {
6032 fn read_text(&self) -> Option<String>;
6034 fn write_text(&self, text: &str);
6036}
6037
6038#[cfg(not(target_arch = "wasm32"))]
6042pub struct SystemClipboard;
6043
6044#[cfg(not(target_arch = "wasm32"))]
6045impl ClipboardProvider for SystemClipboard {
6046 fn read_text(&self) -> Option<String> {
6047 use std::process::Command;
6048 Command::new("pbpaste")
6050 .output()
6051 .ok()
6052 .and_then(|o| String::from_utf8(o.stdout).ok())
6053 }
6054
6055 fn write_text(&self, text: &str) {
6056 use std::process::Command;
6057 if let Ok(mut child) = Command::new("pbcopy")
6059 .stdin(std::process::Stdio::piped())
6060 .spawn()
6061 {
6062 if let Some(stdin) = child.stdin.as_mut() {
6063 use std::io::Write;
6064 let _ = stdin.write_all(text.as_bytes());
6065 }
6066 let _ = child.wait();
6067 }
6068 }
6069}
6070
6071#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6077pub enum TextDirection {
6078 Forward,
6079 Backward,
6080 Up,
6081 Down,
6082 LineStart,
6083 LineEnd,
6084 WordForward,
6085 WordBackward,
6086}
6087
6088#[derive(Debug, Clone, Default)]
6093pub struct TextInputState {
6094 pub text: String,
6096 pub cursor_pos: usize,
6098 pub selection_anchor: Option<usize>,
6101 pub focused: bool,
6103 pub caret_visible: bool,
6105 pub last_edit_time: f32,
6107}
6108
6109impl TextInputState {
6110 pub fn new(text: impl Into<String>) -> Self {
6112 let text = text.into();
6113 let cursor_pos = text.len();
6114 Self {
6115 text,
6116 cursor_pos,
6117 selection_anchor: None,
6118 focused: false,
6119 caret_visible: true,
6120 last_edit_time: 0.0,
6121 }
6122 }
6123
6124 pub fn selection_range(&self) -> Option<(usize, usize)> {
6127 self.selection_anchor.map(|anchor| {
6128 if anchor <= self.cursor_pos {
6129 (anchor, self.cursor_pos)
6130 } else {
6131 (self.cursor_pos, anchor)
6132 }
6133 })
6134 }
6135
6136 pub fn selected_text(&self) -> String {
6138 self.selection_range()
6139 .map(|(start, end)| self.text[start..end].to_string())
6140 .unwrap_or_default()
6141 }
6142
6143 pub fn insert(&mut self, new_text: &str) {
6145 if let Some((start, end)) = self.selection_range() {
6146 self.text.replace_range(start..end, new_text);
6147 self.cursor_pos = start + new_text.len();
6148 } else {
6149 self.text.insert_str(self.cursor_pos, new_text);
6150 self.cursor_pos += new_text.len();
6151 }
6152 self.selection_anchor = None;
6153 }
6154
6155 pub fn delete(&mut self, backward: bool, count: usize) -> String {
6158 if let Some((start, end)) = self.selection_range() {
6159 let deleted = self.text[start..end].to_string();
6160 self.text.replace_range(start..end, "");
6161 self.cursor_pos = start;
6162 self.selection_anchor = None;
6163 return deleted;
6164 }
6165
6166 if backward && self.cursor_pos > 0 {
6167 let start = self.cursor_pos.saturating_sub(count);
6168 let deleted = self.text[start..self.cursor_pos].to_string();
6169 self.text.replace_range(start..self.cursor_pos, "");
6170 self.cursor_pos = start;
6171 deleted
6172 } else if !backward && self.cursor_pos < self.text.len() {
6173 let end = (self.cursor_pos + count).min(self.text.len());
6174 let deleted = self.text[self.cursor_pos..end].to_string();
6175 self.text.replace_range(self.cursor_pos..end, "");
6176 deleted
6177 } else {
6178 String::new()
6179 }
6180 }
6181
6182 pub fn move_cursor(&mut self, direction: TextDirection, extend_selection: bool) {
6184 if !extend_selection {
6185 self.selection_anchor = None;
6186 } else if self.selection_anchor.is_none() {
6187 self.selection_anchor = Some(self.cursor_pos);
6188 }
6189
6190 match direction {
6191 TextDirection::Forward if self.cursor_pos < self.text.len() => {
6192 let next = self.text[self.cursor_pos..]
6194 .char_indices()
6195 .nth(1)
6196 .map(|(i, _)| self.cursor_pos + i)
6197 .unwrap_or(self.text.len());
6198 self.cursor_pos = next;
6199 }
6200 TextDirection::Backward if self.cursor_pos > 0 => {
6201 let prev = self.text[..self.cursor_pos]
6202 .char_indices()
6203 .next_back()
6204 .map(|(i, _)| i)
6205 .unwrap_or(0);
6206 self.cursor_pos = prev;
6207 }
6208 TextDirection::LineStart => {
6209 self.cursor_pos = 0;
6210 }
6211 TextDirection::LineEnd => {
6212 self.cursor_pos = self.text.len();
6213 }
6214 TextDirection::WordForward => {
6215 let rest = &self.text[self.cursor_pos..];
6217 let after_word = rest
6219 .char_indices()
6220 .find(|(_, c)| !c.is_alphanumeric())
6221 .map(|(i, _)| i)
6222 .unwrap_or(rest.len());
6223 let after_space = rest[after_word..]
6225 .char_indices()
6226 .find(|(_, c)| !c.is_whitespace())
6227 .map(|(i, _)| after_word + i)
6228 .unwrap_or(rest.len());
6229 self.cursor_pos = (self.cursor_pos + after_space).min(self.text.len());
6230 }
6231 TextDirection::WordBackward => {
6232 let before = &self.text[..self.cursor_pos];
6233 let before_word = before
6235 .char_indices()
6236 .rev()
6237 .find(|(_, c)| !c.is_whitespace())
6238 .map(|(i, _)| i)
6239 .unwrap_or(0);
6240 let word_start = before[..before_word]
6242 .char_indices()
6243 .rev()
6244 .find(|(_, c)| !c.is_alphanumeric())
6245 .map(|(i, _)| i)
6246 .unwrap_or(0);
6247 self.cursor_pos = word_start;
6248 }
6249 _ => {} }
6251
6252 if !extend_selection {
6253 self.selection_anchor = None;
6254 }
6255 }
6256
6257 pub fn select_all(&mut self) {
6259 self.cursor_pos = self.text.len();
6260 self.selection_anchor = Some(0);
6261 }
6262
6263 pub fn cursor_byte_pos(&self) -> usize {
6265 self.cursor_pos
6266 }
6267}
6268
6269#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6271pub struct NotificationAction {
6272 pub id: String,
6274 pub title: String,
6276 pub is_destructive: bool,
6278}
6279
6280#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6282pub enum NotificationPriority {
6283 Passive,
6285 #[default]
6287 Active,
6288 TimeSensitive,
6290}
6291
6292#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
6294pub struct Notification {
6295 pub id: String,
6297 pub app_name: Option<String>,
6299 pub title: String,
6301 pub body: String,
6303 pub icon: Option<String>,
6305 pub sound: Option<String>,
6307 pub actions: Vec<NotificationAction>,
6309 pub timeout: Option<f32>,
6311 pub priority: NotificationPriority,
6313 pub timestamp: f32,
6315 pub dismissed: bool,
6317}
6318
6319#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, thiserror::Error)]
6321pub enum NotificationError {
6322 #[error("Notification permission denied")]
6324 PermissionDenied,
6325 #[error("Failed to post notification")]
6327 PostFailed,
6328}
6329
6330#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6332pub enum NotificationPermission {
6333 Granted,
6335 Denied,
6337 #[default]
6339 NotDetermined,
6340}
6341
6342pub trait NotificationHandler: Send + Sync {
6344 fn show(&self, notification: Notification) -> Result<(), NotificationError>;
6346 fn dismiss(&self, id: &str) -> Result<(), NotificationError>;
6348 fn request_permission(&self) -> NotificationPermission;
6350}
6351
6352static NEXT_NOTIFICATION_ID: std::sync::atomic::AtomicUsize =
6353 std::sync::atomic::AtomicUsize::new(1);
6354
6355#[derive(Clone, Copy, Debug, Default)]
6357pub struct DefaultNotificationHandler;
6358
6359impl NotificationHandler for DefaultNotificationHandler {
6360 fn show(&self, notification: Notification) -> Result<(), NotificationError> {
6362 let mut notif = notification;
6363 if notif.id.is_empty() {
6364 let id = NEXT_NOTIFICATION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6365 notif.id = format!("notif_{}", id);
6366 }
6367 update_system_state(|state| {
6368 let mut new_state = state.clone();
6369 new_state.notifications.push(notif.clone());
6370 new_state
6371 });
6372 Ok(())
6373 }
6374
6375 fn dismiss(&self, id: &str) -> Result<(), NotificationError> {
6377 update_system_state(|state| {
6378 let mut new_state = state.clone();
6379 for notif in &mut new_state.notifications {
6380 if notif.id == id {
6381 notif.dismissed = true;
6382 }
6383 }
6384 new_state
6385 });
6386 Ok(())
6387 }
6388
6389 fn request_permission(&self) -> NotificationPermission {
6391 NotificationPermission::Granted
6392 }
6393}
6394
6395static NOTIFICATION_HANDLER: once_cell::sync::OnceCell<std::sync::Arc<dyn NotificationHandler>> =
6396 once_cell::sync::OnceCell::new();
6397
6398pub fn set_notification_handler(handler: std::sync::Arc<dyn NotificationHandler>) {
6400 let _ = NOTIFICATION_HANDLER.set(handler);
6401}
6402
6403pub fn get_notification_handler() -> std::sync::Arc<dyn NotificationHandler> {
6405 NOTIFICATION_HANDLER
6406 .get_or_init(|| std::sync::Arc::new(DefaultNotificationHandler))
6407 .clone()
6408}
6409
6410#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6412pub struct FileFilter {
6413 pub name: String,
6415 pub extensions: Vec<String>,
6417}
6418
6419#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
6421pub enum FileDialogMode {
6422 #[default]
6424 OpenFile,
6425 OpenDirectory,
6427 SaveFile,
6429}
6430
6431#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6433pub struct FileDialog {
6434 pub title: String,
6436 pub default_path: Option<String>,
6438 pub filters: Vec<FileFilter>,
6440 pub mode: FileDialogMode,
6442 pub allow_multiple: bool,
6444}
6445
6446#[derive(Debug, thiserror::Error)]
6448pub enum FileDialogError {
6449 #[error("File dialog cancelled")]
6451 Cancelled,
6452 #[error("I/O error: {0}")]
6454 Io(#[from] std::io::Error),
6455 #[error("Platform error: {0}")]
6457 Platform(String),
6458}
6459
6460impl FileDialog {
6461 pub fn new(mode: FileDialogMode) -> Self {
6463 Self {
6464 mode,
6465 ..Default::default()
6466 }
6467 }
6468
6469 pub fn title(mut self, title: impl Into<String>) -> Self {
6471 self.title = title.into();
6472 self
6473 }
6474
6475 pub fn add_filter(mut self, name: &str, extensions: &[&str]) -> Self {
6477 self.filters.push(FileFilter {
6478 name: name.to_string(),
6479 extensions: extensions.iter().map(|s| s.to_string()).collect(),
6480 });
6481 self
6482 }
6483
6484 pub fn default_path(mut self, path: impl Into<String>) -> Self {
6486 self.default_path = Some(path.into());
6487 self
6488 }
6489
6490 pub fn allow_multiple(mut self, allow: bool) -> Self {
6492 self.allow_multiple = allow;
6493 self
6494 }
6495}
6496
6497#[cfg(not(target_arch = "wasm32"))]
6498impl FileDialog {
6499 pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6501 let mut dialog = rfd::FileDialog::new();
6502 dialog = dialog.set_title(&self.title);
6503 if let Some(path) = &self.default_path {
6504 dialog = dialog.set_directory(path);
6505 }
6506 for filter in &self.filters {
6507 let refs: Vec<&str> = filter.extensions.iter().map(|s| s.as_str()).collect();
6508 dialog = dialog.add_filter(&filter.name, &refs);
6509 }
6510
6511 match self.mode {
6512 FileDialogMode::OpenFile => {
6513 if self.allow_multiple {
6514 dialog.pick_files().ok_or(FileDialogError::Cancelled)
6515 } else {
6516 Ok(dialog.pick_file().into_iter().collect())
6517 }
6518 }
6519 FileDialogMode::OpenDirectory => Ok(dialog.pick_folder().into_iter().collect()),
6520 FileDialogMode::SaveFile => Ok(dialog.save_file().into_iter().collect()),
6521 }
6522 }
6523
6524 pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6526 let results = self.pick()?;
6527 Ok(results.into_iter().next())
6528 }
6529}
6530
6531#[cfg(target_arch = "wasm32")]
6532impl FileDialog {
6533 pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6535 Err(FileDialogError::Platform(
6536 "FileDialog is not supported synchronously on WebAssembly".to_string(),
6537 ))
6538 }
6539
6540 pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6542 Err(FileDialogError::Platform(
6543 "FileDialog is not supported synchronously on WebAssembly".to_string(),
6544 ))
6545 }
6546}
6547
6548#[derive(Debug, thiserror::Error)]
6550pub enum DocumentError {
6551 #[error("I/O error: {0}")]
6553 Io(#[from] std::io::Error),
6554 #[error("Parse error: {0}")]
6556 Parse(String),
6557 #[error("Serialization error: {0}")]
6559 Serialize(String),
6560}
6561
6562pub trait Document: Send + Sync {
6564 fn read_from(path: &std::path::Path) -> Result<Self, DocumentError>
6566 where
6567 Self: Sized;
6568
6569 fn write_to(&self, path: &std::path::Path) -> Result<(), DocumentError>;
6571
6572 fn is_dirty(&self) -> bool;
6574
6575 fn mark_clean(&mut self);
6577}
6578
6579pub struct AutoSaveManager {
6581 pub interval: f32,
6583 pub timer: f32,
6585 pub documents: Vec<(std::path::PathBuf, Box<dyn Document>)>,
6587}
6588
6589impl AutoSaveManager {
6590 pub fn new(interval: f32) -> Self {
6592 Self {
6593 interval,
6594 timer: 0.0,
6595 documents: Vec::new(),
6596 }
6597 }
6598
6599 pub fn register(&mut self, path: std::path::PathBuf, doc: Box<dyn Document>) {
6601 self.documents.push((path, doc));
6602 }
6603
6604 pub fn tick(&mut self, dt: f32) {
6606 self.timer += dt;
6607 if self.timer >= self.interval {
6608 self.timer = 0.0;
6609 for (path, doc) in &mut self.documents {
6610 if doc.is_dirty() {
6611 match doc.write_to(path) {
6612 Ok(()) => {
6613 doc.mark_clean();
6614 log::info!("[AutoSaveManager] Auto-saved document to {:?}", path);
6615 }
6616 Err(e) => {
6617 log::error!(
6618 "[AutoSaveManager] Failed to auto-save document to {:?}: {:?}",
6619 path,
6620 e
6621 );
6622 }
6623 }
6624 }
6625 }
6626 }
6627 }
6628}
6629
6630#[derive(Debug, Clone, PartialEq, Eq, Default)]
6638pub struct Modifiers {
6639 pub cmd: bool,
6641 pub shift: bool,
6643 pub alt: bool,
6645 pub ctrl: bool,
6647}
6648
6649#[derive(Debug, Clone)]
6651pub struct KeyboardShortcut {
6652 pub key: String,
6654 pub modifiers: Modifiers,
6656}
6657
6658impl KeyboardShortcut {
6659 pub fn cmd(key: impl Into<String>) -> Self {
6661 Self {
6662 key: key.into(),
6663 modifiers: Modifiers {
6664 cmd: true,
6665 ..Default::default()
6666 },
6667 }
6668 }
6669
6670 pub fn cmd_shift(key: impl Into<String>) -> Self {
6672 Self {
6673 key: key.into(),
6674 modifiers: Modifiers {
6675 cmd: true,
6676 shift: true,
6677 ..Default::default()
6678 },
6679 }
6680 }
6681}
6682
6683pub enum MenuItem {
6689 Action {
6691 label: String,
6692 shortcut: Option<KeyboardShortcut>,
6693 action: std::sync::Arc<dyn Fn() + Send + Sync>,
6694 enabled: bool,
6695 },
6696 Submenu { label: String, items: Vec<MenuItem> },
6698 Separator,
6700}
6701
6702impl std::fmt::Debug for MenuItem {
6703 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6704 match self {
6705 Self::Action { label, enabled, .. } => f
6706 .debug_struct("Action")
6707 .field("label", label)
6708 .field("enabled", enabled)
6709 .finish(),
6710 Self::Submenu { label, items } => f
6711 .debug_struct("Submenu")
6712 .field("label", label)
6713 .field("items", items)
6714 .finish(),
6715 Self::Separator => write!(f, "Separator"),
6716 }
6717 }
6718}
6719
6720pub struct MenuBar {
6725 pub items: Vec<MenuItem>,
6727}
6728
6729impl MenuBar {
6730 pub fn new() -> Self {
6732 Self { items: Vec::new() }
6733 }
6734
6735 pub fn add_item(&mut self, item: MenuItem) {
6737 self.items.push(item);
6738 }
6739
6740 #[allow(clippy::too_many_arguments)]
6752 pub fn standard(
6753 new_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6754 open_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6755 save_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6756 close_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6757 quit_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6758 undo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6759 redo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6760 cut_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6761 copy_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6762 paste_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6763 select_all_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6764 find_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6765 ) -> Self {
6766 let mut bar = Self::new();
6767
6768 bar.add_item(MenuItem::Submenu {
6770 label: "File".to_string(),
6771 items: vec![
6772 MenuItem::Action {
6773 label: "New".to_string(),
6774 shortcut: Some(KeyboardShortcut::cmd("n")),
6775 action: new_fn,
6776 enabled: true,
6777 },
6778 MenuItem::Action {
6779 label: "Open…".to_string(),
6780 shortcut: Some(KeyboardShortcut::cmd("o")),
6781 action: open_fn,
6782 enabled: true,
6783 },
6784 MenuItem::Separator,
6785 MenuItem::Action {
6786 label: "Save".to_string(),
6787 shortcut: Some(KeyboardShortcut::cmd("s")),
6788 action: save_fn,
6789 enabled: true,
6790 },
6791 MenuItem::Separator,
6792 MenuItem::Action {
6793 label: "Close".to_string(),
6794 shortcut: Some(KeyboardShortcut::cmd("w")),
6795 action: close_fn,
6796 enabled: true,
6797 },
6798 MenuItem::Separator,
6799 MenuItem::Action {
6800 label: "Quit".to_string(),
6801 shortcut: Some(KeyboardShortcut::cmd("q")),
6802 action: quit_fn,
6803 enabled: true,
6804 },
6805 ],
6806 });
6807
6808 bar.add_item(MenuItem::Submenu {
6810 label: "Edit".to_string(),
6811 items: vec![
6812 MenuItem::Action {
6813 label: "Undo".to_string(),
6814 shortcut: Some(KeyboardShortcut::cmd("z")),
6815 action: undo_fn,
6816 enabled: true,
6817 },
6818 MenuItem::Action {
6819 label: "Redo".to_string(),
6820 shortcut: Some(KeyboardShortcut::cmd_shift("z")),
6821 action: redo_fn,
6822 enabled: true,
6823 },
6824 MenuItem::Separator,
6825 MenuItem::Action {
6826 label: "Cut".to_string(),
6827 shortcut: Some(KeyboardShortcut::cmd("x")),
6828 action: cut_fn,
6829 enabled: true,
6830 },
6831 MenuItem::Action {
6832 label: "Copy".to_string(),
6833 shortcut: Some(KeyboardShortcut::cmd("c")),
6834 action: copy_fn,
6835 enabled: true,
6836 },
6837 MenuItem::Action {
6838 label: "Paste".to_string(),
6839 shortcut: Some(KeyboardShortcut::cmd("v")),
6840 action: paste_fn,
6841 enabled: true,
6842 },
6843 MenuItem::Separator,
6844 MenuItem::Action {
6845 label: "Select All".to_string(),
6846 shortcut: Some(KeyboardShortcut::cmd("a")),
6847 action: select_all_fn,
6848 enabled: true,
6849 },
6850 MenuItem::Separator,
6851 MenuItem::Action {
6852 label: "Find…".to_string(),
6853 shortcut: Some(KeyboardShortcut::cmd("f")),
6854 action: find_fn,
6855 enabled: true,
6856 },
6857 ],
6858 });
6859
6860 let noop: std::sync::Arc<dyn Fn() + Send + Sync> = std::sync::Arc::new(|| {});
6864 bar.add_item(MenuItem::Submenu {
6865 label: "View".to_string(),
6866 items: vec![
6867 MenuItem::Action {
6868 label: "Zoom In".to_string(),
6869 shortcut: Some(KeyboardShortcut::cmd("=")),
6870 action: noop.clone(),
6871 enabled: true,
6872 },
6873 MenuItem::Action {
6874 label: "Zoom Out".to_string(),
6875 shortcut: Some(KeyboardShortcut::cmd("-")),
6876 action: noop.clone(),
6877 enabled: true,
6878 },
6879 MenuItem::Separator,
6880 MenuItem::Action {
6881 label: "Toggle Fullscreen".to_string(),
6882 shortcut: Some(KeyboardShortcut {
6883 key: "f".to_string(),
6884 modifiers: Modifiers {
6885 ctrl: true,
6886 ..Default::default()
6887 },
6888 }),
6889 action: noop.clone(),
6890 enabled: true,
6891 },
6892 ],
6893 });
6894
6895 bar.add_item(MenuItem::Submenu {
6897 label: "Window".to_string(),
6898 items: vec![
6899 MenuItem::Action {
6900 label: "Minimize".to_string(),
6901 shortcut: Some(KeyboardShortcut::cmd("m")),
6902 action: noop.clone(),
6903 enabled: true,
6904 },
6905 MenuItem::Action {
6906 label: "Zoom".to_string(),
6907 shortcut: None,
6908 action: noop.clone(),
6909 enabled: true,
6910 },
6911 MenuItem::Separator,
6912 MenuItem::Action {
6913 label: "Bring All to Front".to_string(),
6914 shortcut: None,
6915 action: noop.clone(),
6916 enabled: true,
6917 },
6918 ],
6919 });
6920
6921 bar.add_item(MenuItem::Submenu {
6923 label: "Help".to_string(),
6924 items: vec![MenuItem::Action {
6925 label: "Search Help".to_string(),
6926 shortcut: None,
6927 action: noop,
6928 enabled: true,
6929 }],
6930 });
6931
6932 bar
6933 }
6934}
6935
6936impl Default for MenuBar {
6937 fn default() -> Self {
6938 Self::new()
6939 }
6940}
6941
6942use std::sync::RwLock;
6948
6949#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
6951pub enum Direction {
6952 #[default]
6953 LTR,
6954 RTL,
6955 Auto,
6956}
6957
6958impl Direction {
6959 pub fn is_rtl(self) -> bool {
6960 matches!(self, Direction::RTL)
6961 }
6962}
6963#[derive(Clone, Debug)]
6964pub struct L10nBundle {
6965 pub locale: String,
6966 pub strings: HashMap<String, String>,
6967 pub is_rtl: bool,
6968}
6969
6970impl L10nBundle {
6971 pub fn new(locale: impl Into<String>) -> Self {
6972 Self {
6973 locale: locale.into(),
6974 strings: HashMap::new(),
6975 is_rtl: false,
6976 }
6977 }
6978
6979 pub fn add(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
6980 self.strings.insert(key.into(), value.into());
6981 self
6982 }
6983
6984 pub fn from_strings_format(locale: impl Into<String>, input: &str) -> Self {
6985 let mut bundle = Self::new(locale);
6986 for line in input.lines() {
6987 let line = line.trim();
6988 if line.is_empty() || line.starts_with("//") {
6989 continue;
6990 }
6991 if let Some(eq_pos) = line.find(" = ") {
6992 let key = line[..eq_pos].trim_matches('"').to_string();
6993 let val = line[eq_pos + 3..]
6994 .trim_end_matches(';')
6995 .trim_matches('"')
6996 .to_string();
6997 bundle.strings.insert(key, val);
6998 }
6999 }
7000 bundle
7001 }
7002 pub fn t(&self, key: &str) -> String {
7004 self.strings
7005 .get(key)
7006 .map(|s| s.to_string())
7007 .unwrap_or_else(|| key.to_string())
7008 }
7009
7010 pub fn tf(&self, key: &str, args: &[&str]) -> String {
7012 let mut result = self.t(key);
7013 for (i, arg) in args.iter().enumerate() {
7014 result = result.replace(&format!("{{{}}}", i), arg);
7015 }
7016 result
7017 }
7018}
7019
7020pub struct L10n {
7022 bundles: HashMap<String, L10nBundle>,
7023 current: String,
7024}
7025
7026impl L10n {
7027 pub fn new(default_locale: &str) -> Self {
7028 Self {
7029 bundles: HashMap::new(),
7030 current: default_locale.to_string(),
7031 }
7032 }
7033
7034 pub fn add_bundle(&mut self, bundle: L10nBundle) {
7035 self.bundles.insert(bundle.locale.clone(), bundle);
7036 }
7037
7038 pub fn set_locale(&mut self, locale: &str) {
7039 self.current = locale.to_string();
7040 }
7041 pub fn current_locale(&self) -> &str {
7042 &self.current
7043 }
7044
7045 pub fn is_rtl(&self) -> bool {
7046 self.bundles
7047 .get(self.current.as_str())
7048 .map(|b| b.is_rtl)
7049 .unwrap_or(false)
7050 }
7051
7052 pub fn t(&self, key: &str) -> String {
7053 self.bundles
7054 .get(self.current.as_str())
7055 .map(|b| b.t(key))
7056 .unwrap_or_else(|| key.to_string())
7057 }
7058
7059 pub fn tf(&self, key: &str, args: &[&str]) -> String {
7060 let mut result = self.t(key);
7061 for (i, arg) in args.iter().enumerate() {
7062 result = result.replace(&format!("{{{}}}", i), arg);
7063 }
7064 result
7065 }
7066
7067 pub fn direction(&self) -> Direction {
7068 if self.is_rtl() {
7069 Direction::RTL
7070 } else {
7071 Direction::LTR
7072 }
7073 }
7074}
7075
7076static L10N: once_cell::sync::Lazy<Arc<RwLock<L10n>>> =
7077 once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(L10n::new("en"))));
7078
7079pub fn init_l10n(l10n: L10n) {
7080 if let Ok(mut guard) = L10N.write() {
7081 *guard = l10n;
7082 }
7083}
7084
7085pub fn l10n() -> Arc<RwLock<L10n>> {
7086 L10N.clone()
7087}
7088
7089pub fn t(key: &str) -> String {
7090 L10N.read()
7091 .map(|g| g.t(key).to_string())
7092 .unwrap_or_else(|_| key.to_string())
7093}
7094
7095pub fn tf(key: &str, args: &[&str]) -> String {
7096 L10N.read()
7097 .map(|g| g.tf(key, args))
7098 .unwrap_or_else(|_| key.to_string())
7099}
7100
7101pub fn set_locale(locale: &str) {
7102 if let Ok(mut guard) = L10N.write() {
7103 guard.set_locale(locale);
7104 }
7105}
7106
7107pub fn current_locale() -> String {
7108 L10N.read()
7109 .map(|g| g.current_locale().to_string())
7110 .unwrap_or_else(|_| "en".to_string())
7111}
7112
7113pub fn is_rtl() -> bool {
7114 L10N.read().map(|g| g.is_rtl()).unwrap_or(false)
7115}
7116
7117#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7129pub enum SystemTheme {
7130 #[default]
7132 Dark,
7133 Light,
7135}
7136
7137pub fn detect_system_theme() -> SystemTheme {
7147 std::env::var("CVKG_THEME")
7148 .ok()
7149 .and_then(|v| match v.as_str() {
7150 "light" => Some(SystemTheme::Light),
7151 "dark" => Some(SystemTheme::Dark),
7152 _ => None,
7153 })
7154 .unwrap_or(SystemTheme::Dark)
7155}
7156
7157pub mod audio_haptic;
7163pub use audio_haptic::{
7164 AudioEngine, HapticEngine, HapticIntensity, NullAudioEngine, NullHapticEngine, haptic_error,
7165 haptic_impact, haptic_selection, haptic_success, play_sound, set_audio_engine,
7166 set_haptic_engine, sounds,
7167};