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_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {}
2779
2780 fn set_camera_3d(&mut self, _camera: &Camera3D) {}
2783
2784 fn push_transform_3d(&mut self, _transform: &Transform3D) {}
2787
2788 fn pop_transform_3d(&mut self) {}
2790
2791 fn render_scene_node_3d(
2800 &mut self,
2801 _position: [f32; 3],
2802 _rotation: [f32; 4],
2803 _scale: [f32; 3],
2804 _color: [f32; 4],
2805 _meshes: &[Mesh],
2806 ) {
2807 }
2809
2810 fn draw_linear_gradient(
2812 &mut self,
2813 _rect: Rect,
2814 _start_color: [f32; 4],
2815 _end_color: [f32; 4],
2816 _angle: f32,
2817 ) {
2818 }
2819 fn draw_radial_gradient(
2821 &mut self,
2822 _rect: Rect,
2823 _inner_color: [f32; 4],
2824 _outer_color: [f32; 4],
2825 ) {
2826 }
2827 fn draw_drop_shadow(
2829 &mut self,
2830 _rect: Rect,
2831 _radius: f32,
2832 _color: [f32; 4],
2833 _blur: f32,
2834 _spread: f32,
2835 ) {
2836 }
2837 fn stroke_dashed_rounded_rect(
2839 &mut self,
2840 _rect: Rect,
2841 _radius: f32,
2842 _color: [f32; 4],
2843 _width: f32,
2844 _dash: f32,
2845 _gap: f32,
2846 ) {
2847 }
2848 fn draw_9slice(
2850 &mut self,
2851 _image_name: &str,
2852 _rect: Rect,
2853 _left: f32,
2854 _top: f32,
2855 _right: f32,
2856 _bottom: f32,
2857 ) {
2858 }
2859
2860 fn push_clip_rect(&mut self, _rect: Rect) {}
2864 fn pop_clip_rect(&mut self) {}
2866 fn current_clip_rect(&self) -> Rect {
2869 Rect::new(-10000.0, -10000.0, 20000.0, 20000.0)
2870 }
2871
2872 fn push_opacity(&mut self, _opacity: f32) {}
2876 fn pop_opacity(&mut self) {}
2878
2879 fn set_theme(&mut self, _theme: ColorTheme) {}
2881 fn set_rage(&mut self, _rage: f32) {}
2882 fn set_berserker_mode(&mut self, _state: BerserkerMode) {}
2883 fn trigger_shatter_event(&mut self, _origin: [f32; 2], _force: f32) {}
2884 fn set_scene(&mut self, _scene: &str) {}
2886
2887 fn capture_png(&mut self) -> Vec<u8> {
2890 Vec::new()
2891 }
2892 fn print(&mut self) {}
2894
2895 fn set_scene_preset(&mut self, _preset: u32) {}
2896
2897 fn bifrost(&mut self, _rect: Rect, _blur: f32, _saturation: f32, _opacity: f32) {}
2900 fn gungnir(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32, _intensity: f32) {}
2902 fn mani_glow(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32) {}
2904 fn push_mjolnir_slice(&mut self, _angle: f32, _offset: f32) {}
2906 fn pop_mjolnir_slice(&mut self) {}
2907 fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer));
2911 fn mjolnir_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2913 fn mjolnir_fluid_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2914 fn draw_mjolnir_bolt(&mut self, _from: [f32; 2], _to: [f32; 2], _color: [f32; 4]) {}
2916
2917 fn set_aria_role(&mut self, _role: &str) {}
2919 fn set_aria_label(&mut self, _label: &str) {}
2920
2921 fn register_shared_element(&mut self, _id: &str, _rect: Rect) {}
2923
2924 fn set_key(&mut self, _key: &str) {}
2926
2927 fn get_telemetry(&self) -> TelemetryData {
2930 TelemetryData::default()
2931 }
2932
2933 fn push_shadow(&mut self, _radius: f32, _color: [f32; 4], _offset: [f32; 2]) {}
2936 fn pop_shadow(&mut self) {}
2938
2939 fn push_vnode(&mut self, _rect: Rect, _name: &'static str) {}
2942 fn pop_vnode(&mut self) {}
2944 fn register_handler(
2946 &mut self,
2947 _event_type: &str,
2948 _handler: std::sync::Arc<dyn Fn(Event) + Send + Sync>,
2949 ) {
2950 }
2951
2952 fn set_z_index(&mut self, _z: f32) {}
2956 fn get_z_index(&self) -> f32 {
2958 0.0
2959 }
2960
2961 fn load_svg(&mut self, _name: &str, _svg_data: &[u8]) {}
2964 fn draw_svg(&mut self, _name: &str, _rect: Rect) {}
2966 fn serialize_svg(&mut self, _name: &str) -> Result<String, String> {
2970 Err("SVG serialization not supported by this renderer".into())
2971 }
2972 fn apply_svg_filter(
2976 &mut self,
2977 _name: &str,
2978 _filter_id: &str,
2979 _region: Rect,
2980 ) -> Result<String, String> {
2981 Err("SVG filter not supported by this renderer".into())
2982 }
2983
2984 fn push_transform(&mut self, _translation: [f32; 2], _scale: [f32; 2], _rotation: f32) {}
2989 fn push_affine(&mut self, _transform: [f32; 6]) {}
2992 fn pop_transform(&mut self) {}
2994 fn query_layout(&self, _node_id: scene_graph::NodeId) -> Option<Rect> {
2996 None
2997 }
2998 fn set_debug_layout(&mut self, _enabled: bool) {}
3000 fn get_debug_layout(&self) -> bool {
3002 false
3003 }
3004
3005 fn set_material(&mut self, _material: crate::material::DrawMaterial) {}
3009 fn current_material(&self) -> crate::material::DrawMaterial {
3011 crate::material::DrawMaterial::Opaque
3012 }
3013
3014 fn mimir_intent(&self) -> [f32; 2] {
3017 [0.0, 0.0]
3018 }
3019 fn magnetic_warp(&self, pointer: [f32; 2], anchor_rect: Rect, strength: f32) -> [f32; 2] {
3021 if strength <= 0.0 {
3022 return pointer;
3023 }
3024 let cx = anchor_rect.x + anchor_rect.width / 2.0;
3025 let cy = anchor_rect.y + anchor_rect.height / 2.0;
3026 let dx = pointer[0] - cx;
3027 let dy = pointer[1] - cy;
3028 let dist = (dx * dx + dy * dy).sqrt();
3029 let radius = 120.0;
3030 if dist < radius && dist > 0.0 {
3031 let force = (1.0 - dist / radius) * strength;
3032 [pointer[0] - dx * force, pointer[1] - dy * force]
3033 } else {
3034 pointer
3035 }
3036 }
3037 fn mani_glow_intensity(&self, pointer: [f32; 2], bounds: Rect, radius: f32) -> f32 {
3039 let cx = bounds.x + bounds.width / 2.0;
3040 let cy = bounds.y + bounds.height / 2.0;
3041 let dist = ((pointer[0] - cx).powi(2) + (pointer[1] - cy).powi(2)).sqrt();
3042 if dist < radius {
3043 (1.0 - dist / radius).clamp(0.0, 1.0)
3044 } else {
3045 0.0
3046 }
3047 }
3048 fn fafnir_evolve(&self, pointer: [f32; 2], bounds: Rect, max_scale: f32) -> f32 {
3050 let prox = self.mani_glow_intensity(pointer, bounds, 120.0);
3051 1.0 + (max_scale - 1.0) * prox
3052 }
3053 fn set_sdf_shape(&mut self, _shape: crate::layout::SdfShape) {}
3055}
3056
3057pub mod accessibility {
3059 pub fn relative_luminance(color: [f32; 4]) -> f32 {
3061 let f = |c: f32| {
3062 if c <= 0.03928 {
3063 c / 12.92
3064 } else {
3065 ((c + 0.055) / 1.055).powf(2.4)
3066 }
3067 };
3068 0.2126 * f(color[0]) + 0.7152 * f(color[1]) + 0.0722 * f(color[2])
3069 }
3070
3071 pub fn contrast_ratio(c1: [f32; 4], c2: [f32; 4]) -> f32 {
3073 let l1 = relative_luminance(c1);
3074 let l2 = relative_luminance(c2);
3075 let (light, dark) = if l1 > l2 { (l1, l2) } else { (l2, l1) };
3076 (light + 0.05) / (dark + 0.05)
3077 }
3078}
3079#[derive(
3081 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
3082)]
3083pub enum RenderTier {
3084 Tier1GPU = 0,
3086 Tier2GPU = 1,
3088 Tier3Fallback = 2,
3090}
3091use bytemuck::{Pod, Zeroable};
3095#[repr(C)]
3097#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3098pub struct ColorTheme {
3099 pub primary_neon: [f32; 4], pub shatter_neon: [f32; 4],
3101 pub glass_base: [f32; 4],
3102 pub glass_edge: [f32; 4],
3103 pub rune_glow: [f32; 4],
3104 pub ember_core: [f32; 4],
3105 pub background_deep: [f32; 4],
3106 pub mani_glow: [f32; 4], pub glass_blur_strength: f32,
3108 pub shatter_edge_width: f32,
3109 pub neon_bloom_radius: f32,
3110 pub rune_opacity: f32,
3111}
3112impl ColorTheme {
3113 pub fn asgard() -> Self {
3115 Self {
3116 primary_neon: [0.0, 1.0, 0.95, 1.2],
3117 shatter_neon: [1.0, 0.0, 0.75, 1.5],
3118 glass_base: [0.04, 0.04, 0.06, 0.82],
3119 glass_edge: [0.0, 0.45, 0.55, 0.6],
3120 rune_glow: [0.75, 0.98, 1.0, 0.9],
3121 ember_core: [0.95, 0.12, 0.12, 1.0],
3122 background_deep: [0.01, 0.01, 0.03, 1.0],
3123 mani_glow: [0.7, 0.9, 1.0, 0.05],
3124 glass_blur_strength: 0.6,
3125 shatter_edge_width: 1.8,
3126 neon_bloom_radius: 0.022,
3127 rune_opacity: 0.55,
3128 }
3129 }
3130
3131 pub fn midgard() -> Self {
3133 Self {
3134 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],
3140 background_deep: [0.05, 0.05, 0.07, 1.0],
3141 mani_glow: [0.0, 0.0, 0.0, 0.0], glass_blur_strength: 0.0, shatter_edge_width: 1.0,
3144 neon_bloom_radius: 0.0,
3145 rune_opacity: 0.0,
3146 }
3147 }
3148
3149 pub fn cyberpunk_viking() -> Self {
3150 Self::asgard()
3151 }
3152 pub fn vibrant_glass() -> Self {
3153 Self {
3154 primary_neon: [0.0, 1.0, 0.95, 1.2],
3155 shatter_neon: [1.0, 0.0, 0.75, 1.5],
3156 glass_base: [0.55, 0.6, 0.7, 0.08], glass_edge: [0.7, 0.85, 1.0, 0.45], rune_glow: [0.75, 0.98, 1.0, 0.9],
3159 ember_core: [1.0, 0.4, 0.1, 1.0],
3160 background_deep: [0.05, 0.05, 0.1, 1.0],
3161 mani_glow: [0.7, 0.9, 1.0, 0.05],
3162 glass_blur_strength: 0.9,
3163 shatter_edge_width: 1.8,
3164 neon_bloom_radius: 0.022,
3165 rune_opacity: 0.55,
3166 }
3167 }
3168}
3169impl Default for ColorTheme {
3170 fn default() -> Self {
3171 Self::vibrant_glass()
3172 }
3173}
3174#[repr(C)]
3176#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3177pub struct SceneUniforms {
3178 pub view: glam::Mat4,
3179 pub proj: glam::Mat4,
3180 pub time: f32,
3181 pub delta_time: f32,
3182 pub resolution: [f32; 2],
3183 pub mouse: [f32; 2],
3184 pub mouse_velocity: [f32; 2],
3185 pub shatter_origin: [f32; 2],
3186 pub shatter_time: f32,
3187 pub shatter_force: f32,
3188 pub berzerker_rage: f32,
3189 pub berzerker_mode: u32,
3190 pub scroll_offset: f32,
3191 pub scale_factor: f32,
3192 pub scene_type: u32,
3193 pub _pad: [f32; 3], }
3195
3196pub const SCENE_AURORA: u32 = 0;
3197pub const SCENE_VOID: u32 = 1;
3198pub const SCENE_NEBULA: u32 = 2;
3199pub const SCENE_GLITCH: u32 = 3;
3200pub const SCENE_YGGDRASIL: u32 = 4;
3201
3202impl SceneUniforms {
3203 pub fn new(width: f32, height: f32) -> Self {
3204 Self {
3205 view: glam::Mat4::IDENTITY,
3206 proj: glam::Mat4::orthographic_lh(0.0, width, height, 0.0, -100.0, 100.0),
3207 time: 0.0,
3208 delta_time: 0.016,
3209 resolution: [width, height],
3210 mouse: [0.5, 0.5],
3211 mouse_velocity: [0.0, 0.0],
3212 shatter_origin: [0.5, 0.5],
3213 shatter_time: -100.0,
3214 shatter_force: 0.0,
3215 berzerker_rage: 0.0,
3216 berzerker_mode: 0,
3217 scroll_offset: 0.0,
3218 scale_factor: 1.0,
3219 scene_type: SCENE_AURORA,
3220 _pad: [0.0; 3],
3221 }
3222 }
3223}
3224#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
3226pub struct Mesh {
3227 pub vertices: Vec<[f32; 3]>,
3228 pub normals: Vec<[f32; 3]>,
3229 pub indices: Vec<u32>,
3230}
3231impl Mesh {
3232 pub fn from_obj(data: &[u8]) -> anyhow::Result<Vec<Self>> {
3233 let mut cursor = std::io::Cursor::new(data);
3234 let (models, _) = tobj::load_obj_buf(&mut cursor, &tobj::LoadOptions::default(), |_| {
3235 Ok((Vec::new(), Default::default()))
3236 })?;
3237 let mut meshes = Vec::new();
3238 for m in models {
3239 let mesh = m.mesh;
3240 let vertices: Vec<[f32; 3]> = mesh
3241 .positions
3242 .chunks(3)
3243 .map(|c| [c[0], c[1], c[2]])
3244 .collect();
3245 let normals = if mesh.normals.is_empty() {
3246 vec![[0.0, 0.0, 1.0]; vertices.len()]
3247 } else {
3248 mesh.normals.chunks(3).map(|c| [c[0], c[1], c[2]]).collect()
3249 };
3250 meshes.push(Mesh {
3251 vertices,
3252 normals,
3253 indices: mesh.indices,
3254 });
3255 }
3256 Ok(meshes)
3257 }
3258 pub fn from_stl(data: &[u8]) -> anyhow::Result<Self> {
3259 let mut cursor = std::io::Cursor::new(data);
3260 let stl = stl_io::read_stl(&mut cursor)?;
3261 let vertices: Vec<[f32; 3]> = stl.vertices.iter().map(|v| [v[0], v[1], v[2]]).collect();
3262 let mut indices = Vec::new();
3263 for face in stl.faces {
3264 indices.push(face.vertices[0] as u32);
3265 indices.push(face.vertices[1] as u32);
3266 indices.push(face.vertices[2] as u32);
3267 }
3268 let normals = vec![[0.0, 0.0, 1.0]; vertices.len()];
3269 Ok(Mesh {
3270 vertices,
3271 normals,
3272 indices,
3273 })
3274 }
3275}
3276
3277#[derive(Debug, Clone, Copy, PartialEq)]
3283pub struct Transform3D {
3284 pub position: glam::Vec3,
3285 pub rotation: glam::Quat,
3286 pub scale: glam::Vec3,
3287}
3288
3289impl Default for Transform3D {
3290 fn default() -> Self {
3291 Self {
3292 position: glam::Vec3::ZERO,
3293 rotation: glam::Quat::IDENTITY,
3294 scale: glam::Vec3::ONE,
3295 }
3296 }
3297}
3298
3299impl Transform3D {
3300 pub fn to_matrix(&self) -> glam::Mat4 {
3302 glam::Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.position)
3303 }
3304
3305 pub fn from_2d(x: f32, y: f32, rotation: f32) -> Self {
3307 Self {
3308 position: glam::Vec3::new(x, y, 0.0),
3309 rotation: glam::Quat::from_rotation_z(rotation),
3310 scale: glam::Vec3::ONE,
3311 }
3312 }
3313}
3314
3315#[derive(Debug, Clone, Copy)]
3317pub struct Camera3D {
3318 pub position: glam::Vec3,
3320 pub target: glam::Vec3,
3322 pub up: glam::Vec3,
3324 pub fov_y: f32,
3326 pub near: f32,
3328 pub far: f32,
3330 pub perspective: bool,
3332 pub aspect: f32,
3334}
3335
3336#[derive(Debug, Clone, Copy, PartialEq)]
3338pub struct Material3D {
3339 pub base_color: [f32; 4],
3341 pub metallic: f32,
3343 pub roughness: f32,
3345 pub emissive: [f32; 3],
3347 pub opacity: f32,
3349}
3350
3351impl Default for Material3D {
3352 fn default() -> Self {
3353 Self {
3354 base_color: [1.0, 1.0, 1.0, 1.0],
3355 metallic: 0.0,
3356 roughness: 0.5,
3357 emissive: [0.0, 0.0, 0.0],
3358 opacity: 1.0,
3359 }
3360 }
3361}
3362
3363impl Material3D {
3364 pub fn unlit(color: [f32; 4]) -> Self {
3366 Self {
3367 base_color: color,
3368 metallic: 0.0,
3369 roughness: 1.0,
3370 emissive: [0.0, 0.0, 0.0],
3371 opacity: color[3],
3372 }
3373 }
3374
3375 pub fn metallic(color: [f32; 4], roughness: f32) -> Self {
3377 Self {
3378 base_color: color,
3379 metallic: 1.0,
3380 roughness: roughness.clamp(0.0, 1.0),
3381 emissive: [0.0, 0.0, 0.0],
3382 opacity: color[3],
3383 }
3384 }
3385}
3386
3387impl Default for Camera3D {
3388 fn default() -> Self {
3389 Self {
3390 position: glam::Vec3::new(0.0, 0.0, 10.0),
3391 target: glam::Vec3::ZERO,
3392 up: glam::Vec3::Y,
3393 fov_y: 45.0f32.to_radians(),
3394 near: 0.1,
3395 far: 1000.0,
3396 perspective: true,
3397 aspect: 16.0 / 9.0,
3398 }
3399 }
3400}
3401
3402impl Camera3D {
3403 pub fn view_matrix(&self) -> glam::Mat4 {
3405 glam::Mat4::look_at_lh(self.position, self.target, self.up)
3406 }
3407
3408 pub fn projection_matrix(&self) -> glam::Mat4 {
3410 if self.perspective {
3411 glam::Mat4::perspective_lh(self.fov_y, self.aspect, self.near, self.far)
3412 } else {
3413 let top = self.fov_y;
3415 let right = top * self.aspect;
3416 glam::Mat4::orthographic_lh(-right, right, -top, top, self.near, self.far)
3417 }
3418 }
3419
3420 pub fn view_projection(&self) -> glam::Mat4 {
3422 self.projection_matrix() * self.view_matrix()
3423 }
3424}
3425
3426pub trait FrameRenderer<E = ()>: Renderer {
3429 fn begin_frame(&mut self) -> E;
3430 fn render_frame(&mut self) {
3431 }
3433 fn end_frame(&mut self, encoder: E);
3434}
3435use std::sync::Arc;
3436type SubscriberList<T> = Arc<std::sync::Mutex<Vec<Box<dyn Fn(&T) + Send + Sync>>>>;
3437#[derive(Clone)]
3439pub struct State<T: Clone + Send + Sync + 'static> {
3440 swap: Arc<arc_swap::ArcSwap<T>>,
3441 metadata_swap: Arc<arc_swap::ArcSwap<Option<agents::MutationMetadata>>>,
3442 #[cfg(not(target_arch = "wasm32"))]
3443 tvar: Arc<stm::TVar<T>>,
3444 #[cfg(not(target_arch = "wasm32"))]
3445 metadata_tvar: Arc<stm::TVar<Option<agents::MutationMetadata>>>,
3446 subscribers: SubscriberList<T>,
3447 version: Arc<std::sync::atomic::AtomicU64>,
3448 resolution: agents::ConflictResolution,
3449}
3450impl<T: Clone + Send + Sync + 'static> State<T> {
3451 pub fn new(value: T) -> Self {
3453 #[cfg(not(target_arch = "wasm32"))]
3454 let tvar = Arc::new(stm::TVar::new(value.clone()));
3455 #[cfg(not(target_arch = "wasm32"))]
3456 let metadata_tvar = Arc::new(stm::TVar::new(None));
3457 Self {
3458 swap: Arc::new(arc_swap::ArcSwap::from_pointee(value)),
3459 metadata_swap: Arc::new(arc_swap::ArcSwap::new(Arc::new(None))),
3460 #[cfg(not(target_arch = "wasm32"))]
3461 tvar,
3462 #[cfg(not(target_arch = "wasm32"))]
3463 metadata_tvar,
3464 subscribers: Arc::new(std::sync::Mutex::new(Vec::new())),
3465 version: Arc::new(std::sync::atomic::AtomicU64::new(0)),
3466 resolution: agents::ConflictResolution::default(),
3467 }
3468 }
3469 pub fn with_resolution(mut self, resolution: agents::ConflictResolution) -> Self {
3471 self.resolution = resolution;
3472 self
3473 }
3474 pub fn get(&self) -> T {
3476 (**self.swap.load()).clone()
3477 }
3478 pub fn set(&self, value: T) {
3480 #[cfg(not(target_arch = "wasm32"))]
3481 let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3482 let new_meta = agents::get_current_mutation_metadata();
3483 let existing_meta = self.metadata_tvar.read(tx)?;
3484 let mut skip = false;
3485 if self.resolution == agents::ConflictResolution::PriorityWins
3486 && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3487 && new_m.priority < old_m.priority
3488 {
3489 skip = true;
3490 }
3491 if !skip {
3492 self.tvar.write(tx, value.clone())?;
3493 self.metadata_tvar.write(tx, new_meta)?;
3494 Ok((false, value.clone(), new_meta))
3495 } else {
3496 Ok((true, self.tvar.read(tx)?, existing_meta))
3497 }
3498 });
3499 #[cfg(target_arch = "wasm32")]
3500 let (was_skipped, final_val, final_meta) =
3501 (false, value, agents::get_current_mutation_metadata());
3502 if was_skipped {
3503 if let (Some(new_m), Some(old_m)) =
3504 (agents::get_current_mutation_metadata(), final_meta)
3505 {
3506 agents::notify_conflict(agents::ConflictEvent {
3507 agent_id: new_m.agent_id,
3508 priority: new_m.priority,
3509 existing_agent_id: old_m.agent_id,
3510 existing_priority: old_m.priority,
3511 timestamp_ms: new_m.timestamp_ms,
3512 });
3513 }
3514 return;
3515 }
3516 self.swap.store(Arc::new(final_val.clone()));
3517 self.metadata_swap.store(Arc::new(final_meta));
3518 self.version
3519 .fetch_add(1, std::sync::atomic::Ordering::Release);
3520 let subs = Arc::clone(&self.subscribers);
3521 if crate::is_batching() {
3522 crate::enqueue_batch_task(Box::new(move || {
3523 let s = subs.lock().unwrap();
3524 for cb in s.iter() {
3525 cb(&final_val);
3526 }
3527 }));
3528 } else {
3529 let s = subs.lock().unwrap();
3530 for cb in s.iter() {
3531 cb(&final_val);
3532 }
3533 }
3534 }
3535 pub fn mutate<F: Fn(&T) -> T>(&self, f: F) {
3536 #[cfg(not(target_arch = "wasm32"))]
3537 {
3538 let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3539 let new_meta = agents::get_current_mutation_metadata();
3540 let existing_meta = self.metadata_tvar.read(tx)?;
3541 let mut skip = false;
3542 if self.resolution == agents::ConflictResolution::PriorityWins
3543 && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3544 && new_m.priority < old_m.priority
3545 {
3546 skip = true;
3547 }
3548 if !skip {
3549 let current = self.tvar.read(tx)?;
3550 let next = f(¤t);
3551 self.tvar.write(tx, next.clone())?;
3552 self.metadata_tvar.write(tx, new_meta)?;
3553 Ok((false, next, new_meta))
3554 } else {
3555 Ok((true, self.tvar.read(tx)?, existing_meta))
3556 }
3557 });
3558 if was_skipped {
3559 if let (Some(new_m), Some(old_m)) =
3560 (agents::get_current_mutation_metadata(), final_meta)
3561 {
3562 agents::notify_conflict(agents::ConflictEvent {
3563 agent_id: new_m.agent_id,
3564 priority: new_m.priority,
3565 existing_agent_id: old_m.agent_id,
3566 existing_priority: old_m.priority,
3567 timestamp_ms: new_m.timestamp_ms,
3568 });
3569 }
3570 return;
3571 }
3572 self.swap.store(Arc::new(final_val.clone()));
3573 self.metadata_swap.store(Arc::new(final_meta));
3574 self.version
3575 .fetch_add(1, std::sync::atomic::Ordering::Release);
3576 let subs = Arc::clone(&self.subscribers);
3577 if crate::is_batching() {
3578 crate::enqueue_batch_task(Box::new(move || {
3579 let s = subs.lock().unwrap();
3580 for cb in s.iter() {
3581 cb(&final_val);
3582 }
3583 }));
3584 } else {
3585 let s = subs.lock().unwrap();
3586 for cb in s.iter() {
3587 cb(&final_val);
3588 }
3589 }
3590 }
3591 #[cfg(target_arch = "wasm32")]
3592 {
3593 self.set(f(&self.get()));
3594 }
3595 }
3596 pub fn version(&self) -> u64 {
3598 self.version.load(std::sync::atomic::Ordering::Acquire)
3599 }
3600 pub fn subscribe<F: Fn(&T) + Send + Sync + 'static>(&self, callback: F) {
3602 self.subscribers.lock().unwrap().push(Box::new(callback));
3603 }
3604}
3605use crate::runtime::NodeStateSnapshot;
3606use std::sync::OnceLock;
3607use std::sync::atomic::{AtomicBool, Ordering};
3608pub static SYSTEM_STATE: OnceLock<Arc<arc_swap::ArcSwap<KnowledgeState>>> = OnceLock::new();
3610#[cfg(not(target_arch = "wasm32"))]
3611static KNOWLEDGE_TVAR: OnceLock<stm::TVar<KnowledgeState>> = OnceLock::new();
3612static IS_BATCHING: AtomicBool = AtomicBool::new(false);
3613pub static IS_RENDERING: AtomicBool = AtomicBool::new(false);
3614pub static LAYOUT_DIRTY: AtomicBool = AtomicBool::new(false);
3615type BatchQueue = OnceLock<std::sync::Mutex<Vec<Box<dyn FnOnce() + Send + Sync>>>>;
3616static BATCH_QUEUE: BatchQueue = OnceLock::new();
3617static STATE_WRITE_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
3620pub fn is_batching() -> bool {
3622 IS_BATCHING.load(Ordering::Acquire)
3623}
3624pub fn is_rendering() -> bool {
3626 IS_RENDERING.load(Ordering::Acquire)
3627}
3628pub fn begin_render_phase() {
3630 IS_RENDERING.store(true, Ordering::Release);
3631}
3632pub fn end_render_phase() {
3634 IS_RENDERING.store(false, Ordering::Release);
3635}
3636pub fn enqueue_batch_task(task: Box<dyn FnOnce() + Send + Sync>) {
3638 let mut queue = BATCH_QUEUE
3639 .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3640 .lock()
3641 .unwrap();
3642 queue.push(task);
3643}
3644pub fn batch<F: FnOnce()>(f: F) {
3648 if IS_BATCHING.swap(true, Ordering::AcqRel) {
3649 f();
3651 return;
3652 }
3653 f();
3654 IS_BATCHING.store(false, Ordering::Release);
3655 let mut queue = BATCH_QUEUE
3656 .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3657 .lock()
3658 .unwrap();
3659 let tasks: Vec<_> = queue.drain(..).collect();
3660 drop(queue);
3661 for task in tasks {
3662 task();
3663 }
3664}
3665pub fn get_system_state() -> Arc<arc_swap::ArcSwap<KnowledgeState>> {
3667 SYSTEM_STATE
3668 .get_or_init(|| Arc::new(arc_swap::ArcSwap::from_pointee(KnowledgeState::default())))
3669 .clone()
3670}
3671pub fn load_system_state() -> arc_swap::Guard<Arc<KnowledgeState>> {
3672 get_system_state().load()
3673}
3674pub fn update_system_state<F>(f: F)
3675where
3676 F: FnOnce(&KnowledgeState) -> KnowledgeState,
3677{
3678 let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3679 if is_rendering() {
3680 log::warn!(
3681 "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3682 );
3683 }
3684 LAYOUT_DIRTY.store(true, Ordering::SeqCst);
3685 let swap = get_system_state();
3686 let current = swap.load();
3687 let new_state = Arc::new(f(¤t));
3688 swap.store(Arc::clone(&new_state));
3689 #[cfg(not(target_arch = "wasm32"))]
3690 {
3691 let tvar = KNOWLEDGE_TVAR.get_or_init(|| stm::TVar::new((*new_state).clone()));
3692 stm::atomically(|tx| tvar.write(tx, (*new_state).clone()));
3693 }
3694}
3695pub fn transact_system_state<F>(f: F)
3696where
3697 F: Fn(&KnowledgeState) -> KnowledgeState,
3698{
3699 let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3700 #[cfg(not(target_arch = "wasm32"))]
3701 {
3702 if is_rendering() {
3703 log::warn!(
3704 "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3705 );
3706 }
3707 let tvar = KNOWLEDGE_TVAR
3708 .get_or_init(|| stm::TVar::new((**get_system_state().load()).clone()))
3709 .clone();
3710 let new_state = stm::atomically(move |tx| {
3711 let current = tvar.read(tx)?;
3712 let next = f(¤t);
3713 tvar.write(tx, next.clone())?;
3714 Ok(next)
3715 });
3716 get_system_state().store(Arc::new(new_state));
3717 }
3718 #[cfg(target_arch = "wasm32")]
3719 {
3720 if is_rendering() {
3721 log::warn!(
3722 "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3723 );
3724 }
3725 update_system_state(f);
3726 }
3727}
3728impl KnowledgeState {
3729 pub fn new() -> Self {
3731 Self::default()
3732 }
3733 pub fn set_component_state<T: 'static + Send + Sync>(&mut self, id: u64, state: T) {
3735 self.component_states
3736 .insert(id, Arc::new(std::sync::RwLock::new(state)));
3737 }
3738 pub fn get_component_state<T: 'static + Send + Sync>(
3740 &self,
3741 id: u64,
3742 ) -> Option<Arc<std::sync::RwLock<T>>> {
3743 let lock = self.component_states.get(&id)?;
3744 let any_ref = lock.read().ok()?;
3748 if any_ref.is::<T>() {
3749 drop(any_ref);
3751 let cloned: Arc<std::sync::RwLock<dyn std::any::Any + Send + Sync>> = Arc::clone(lock);
3752 Some(unsafe {
3755 let raw = Arc::into_raw(cloned);
3756 Arc::from_raw(raw as *const std::sync::RwLock<T>)
3757 })
3758 } else {
3759 None
3760 }
3761 }
3762 pub fn remember(&mut self, fragment: KnowledgeFragment) {
3764 self.fragments.insert(fragment.id.clone(), fragment);
3765 }
3766 pub fn process_query(&mut self, query: &str) {
3768 let query_lower = query.to_lowercase();
3769 let mut results: Vec<(f32, String)> = self
3770 .fragments
3771 .iter()
3772 .map(|(id, frag)| {
3773 let mut score = 0.0;
3774 if frag.summary.to_lowercase().contains(&query_lower) {
3775 score += 1.0;
3776 }
3777 if frag.source.to_lowercase().contains(&query_lower) {
3778 score += 0.5;
3779 }
3780 (score, id.clone())
3781 })
3782 .filter(|(score, _)| *score > 0.0)
3783 .collect();
3784 results.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
3786 self.last_query_results = results.into_iter().map(|(_, id)| id).take(5).collect();
3787 }
3788 pub fn snapshot(&self) -> Vec<NodeStateSnapshot> {
3790 let mut snapshots = Vec::new();
3791 for frag in self.fragments.values() {
3793 if let Ok(val) = serde_json::to_value(frag) {
3794 snapshots.push(NodeStateSnapshot { id: 0, state: val });
3795 }
3796 }
3797 snapshots
3798 }
3799}
3800#[derive(Clone)]
3802pub struct Binding<T: Clone + Send + Sync + 'static> {
3803 swap: Arc<arc_swap::ArcSwap<T>>,
3804 #[cfg(not(target_arch = "wasm32"))]
3805 tvar: Arc<stm::TVar<T>>,
3806 version: Arc<std::sync::atomic::AtomicU64>,
3807}
3808impl<T: Clone + Send + Sync + 'static> Binding<T> {
3809 pub fn from_state(state: &State<T>) -> Self {
3811 Self {
3812 swap: Arc::clone(&state.swap),
3813 #[cfg(not(target_arch = "wasm32"))]
3814 tvar: Arc::clone(&state.tvar),
3815 version: Arc::clone(&state.version),
3816 }
3817 }
3818 pub fn get(&self) -> T {
3820 (**self.swap.load()).clone()
3821 }
3822 pub fn set(&self, value: T) {
3824 self.swap.store(Arc::new(value.clone()));
3825 #[cfg(not(target_arch = "wasm32"))]
3826 {
3827 let tvar = Arc::clone(&self.tvar);
3828 let v = value.clone();
3829 stm::atomically(move |tx| tvar.write(tx, v.clone()));
3830 }
3831 self.version
3832 .fetch_add(1, std::sync::atomic::Ordering::Release);
3833 }
3834 pub fn version(&self) -> u64 {
3836 self.version.load(std::sync::atomic::Ordering::Acquire)
3837 }
3838}
3839#[cfg(not(target_arch = "wasm32"))]
3840pub fn transact_pair<A, B, F>(state_a: &State<A>, state_b: &State<B>, f: F)
3841where
3842 A: Clone + Send + Sync + 'static,
3843 B: Clone + Send + Sync + 'static,
3844 F: Fn(&A, &B) -> (A, B),
3845{
3846 let tvar_a = Arc::clone(&state_a.tvar);
3847 let tvar_b = Arc::clone(&state_b.tvar);
3848 let (new_a, new_b) = stm::atomically(move |tx| {
3849 let a = tvar_a.read(tx)?;
3850 let b = tvar_b.read(tx)?;
3851 let (na, nb) = f(&a, &b);
3852 tvar_a.write(tx, na.clone())?;
3853 tvar_b.write(tx, nb.clone())?;
3854 Ok((na, nb))
3855 });
3856 state_a.swap.store(Arc::new(new_a.clone()));
3857 state_b.swap.store(Arc::new(new_b.clone()));
3858 state_a
3859 .version
3860 .fetch_add(1, std::sync::atomic::Ordering::Release);
3861 state_b
3862 .version
3863 .fetch_add(1, std::sync::atomic::Ordering::Release);
3864 let subs_a = Arc::clone(&state_a.subscribers);
3865 let subs_b = Arc::clone(&state_b.subscribers);
3866 if crate::is_batching() {
3867 crate::enqueue_batch_task(Box::new(move || {
3868 {
3869 let s = subs_a.lock().unwrap();
3870 for cb in s.iter() {
3871 cb(&new_a);
3872 }
3873 }
3874 {
3875 let s = subs_b.lock().unwrap();
3876 for cb in s.iter() {
3877 cb(&new_b);
3878 }
3879 }
3880 }));
3881 } else {
3882 {
3883 let s = subs_a.lock().unwrap();
3884 for cb in s.iter() {
3885 cb(&new_a);
3886 }
3887 }
3888 {
3889 let s = subs_b.lock().unwrap();
3890 for cb in s.iter() {
3891 cb(&new_b);
3892 }
3893 }
3894 }
3895}
3896use std::any::TypeId;
3897use std::sync::Mutex;
3898pub(crate) static ENVIRONMENT: OnceLock<
3900 Mutex<HashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>>,
3901> = OnceLock::new();
3902pub trait EnvKey: 'static + Send + Sync {
3905 type Value: Clone + Send + Sync + 'static;
3907 fn default_value() -> Self::Value;
3909}
3910pub struct YggdrasilKey;
3912impl EnvKey for YggdrasilKey {
3913 type Value = YggdrasilTokens;
3914 fn default_value() -> Self::Value {
3915 default_tokens()
3916 }
3917}
3918#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3921pub enum Appearance {
3922 Light,
3923 Dark,
3924}
3925#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3927pub enum Orientation {
3928 Horizontal,
3929 Vertical,
3930}
3931#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3933pub struct GridPlacement {
3934 pub column: i32,
3936 pub column_span: u32,
3938 pub row: i32,
3940 pub row_span: u32,
3942}
3943#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3945pub enum Alignment {
3946 #[default]
3947 Center,
3948 Leading,
3949 Trailing,
3950 Top,
3951 Bottom,
3952}
3953#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3955pub enum Distribution {
3956 #[default]
3957 Fill,
3958 Center,
3959 Leading,
3960 Trailing,
3961 SpaceBetween,
3962 SpaceAround,
3963 SpaceEvenly,
3964}
3965#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
3967pub struct Color {
3968 pub r: f32,
3969 pub g: f32,
3970 pub b: f32,
3971 pub a: f32,
3972}
3973impl Color {
3974 pub const BLACK: Color = Color {
3975 r: 0.0,
3976 g: 0.0,
3977 b: 0.0,
3978 a: 1.0,
3979 };
3980 pub const WHITE: Color = Color {
3981 r: 1.0,
3982 g: 1.0,
3983 b: 1.0,
3984 a: 1.0,
3985 };
3986 pub const TRANSPARENT: Color = Color {
3987 r: 0.0,
3988 g: 0.0,
3989 b: 0.0,
3990 a: 0.0,
3991 };
3992 pub const RED: Color = Color {
3993 r: 1.0,
3994 g: 0.0,
3995 b: 0.0,
3996 a: 1.0,
3997 };
3998 pub const GREEN: Color = Color {
3999 r: 0.0,
4000 g: 1.0,
4001 b: 0.0,
4002 a: 1.0,
4003 };
4004 pub const BLUE: Color = Color {
4005 r: 0.0,
4006 g: 0.0,
4007 b: 1.0,
4008 a: 1.0,
4009 };
4010 pub const VIKING_GOLD: Color = Color {
4011 r: 1.0,
4012 g: 0.84,
4013 b: 0.0,
4014 a: 1.0,
4015 };
4016 pub const MAGENTA_LIQUID: Color = Color {
4017 r: 1.0,
4018 g: 0.0,
4019 b: 1.0,
4020 a: 1.0,
4021 };
4022 pub const TACTICAL_OBSIDIAN: Color = Color {
4023 r: 0.05,
4024 g: 0.05,
4025 b: 0.07,
4026 a: 1.0,
4027 };
4028 pub fn relative_luminance(&self) -> f32 {
4030 fn res(c: f32) -> f32 {
4031 if c <= 0.03928 {
4032 c / 12.92
4033 } else {
4034 ((c + 0.055) / 1.055).powf(2.4)
4035 }
4036 }
4037 0.2126 * res(self.r) + 0.7152 * res(self.g) + 0.0722 * res(self.b)
4038 }
4039 pub fn contrast_ratio(&self, other: &Color) -> f32 {
4041 let l1 = self.relative_luminance();
4042 let l2 = other.relative_luminance();
4043 if l1 > l2 {
4044 (l1 + 0.05) / (l2 + 0.05)
4045 } else {
4046 (l2 + 0.05) / (l1 + 0.05)
4047 }
4048 }
4049 pub const CYAN: Color = Color {
4050 r: 0.0,
4051 g: 1.0,
4052 b: 1.0,
4053 a: 1.0,
4054 };
4055 pub const YELLOW: Color = Color {
4056 r: 1.0,
4057 g: 1.0,
4058 b: 0.0,
4059 a: 1.0,
4060 };
4061 pub const MAGENTA: Color = Color {
4062 r: 1.0,
4063 g: 0.0,
4064 b: 1.0,
4065 a: 1.0,
4066 };
4067 pub const GRAY: Color = Color {
4068 r: 0.5,
4069 g: 0.5,
4070 b: 0.5,
4071 a: 1.0,
4072 };
4073 pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
4075 Self { r, g, b, a }
4076 }
4077 pub fn as_array(&self) -> [f32; 4] {
4079 [self.r, self.g, self.b, self.a]
4080 }
4081
4082 pub fn lighten(&self, amount: f32) -> Self {
4088 Self {
4089 r: (self.r + amount).clamp(0.0, 1.0),
4090 g: (self.g + amount).clamp(0.0, 1.0),
4091 b: (self.b + amount).clamp(0.0, 1.0),
4092 a: self.a,
4093 }
4094 }
4095
4096 pub fn darken(&self, amount: f32) -> Self {
4098 Self {
4099 r: (self.r - amount).clamp(0.0, 1.0),
4100 g: (self.g - amount).clamp(0.0, 1.0),
4101 b: (self.b - amount).clamp(0.0, 1.0),
4102 a: self.a,
4103 }
4104 }
4105}
4106impl View for Color {
4107 type Body = Never;
4108 fn body(self) -> Self::Body {
4109 unreachable!()
4110 }
4111 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
4112 renderer.fill_rect(rect, self.as_array());
4113 }
4114}
4115pub struct AppearanceKey;
4117impl EnvKey for AppearanceKey {
4118 type Value = Appearance;
4119 fn default_value() -> Self::Value {
4120 Appearance::Dark }
4122}
4123pub struct StyleResolver;
4125impl StyleResolver {
4126 pub fn color(key: &str) -> String {
4128 let tokens = Environment::<YggdrasilKey>::new().get();
4129 let appearance = Environment::<AppearanceKey>::new().get();
4130 let is_dark = appearance == Appearance::Dark;
4131 tokens
4132 .get_color(key, is_dark)
4133 .unwrap_or_else(|| "#FF00FF".to_string()) }
4135 pub fn get<T: FromStr>(category: &str, key: &str) -> Option<T> {
4137 let tokens = Environment::<YggdrasilKey>::new().get();
4138 let appearance = Environment::<AppearanceKey>::new().get();
4139 let is_dark = appearance == Appearance::Dark;
4140 tokens.get(category, key, is_dark)
4141 }
4142 pub fn color_array(key: &str) -> [f32; 4] {
4146 let hex = Self::color(key);
4147 parse_hex_color(&hex)
4148 }
4149}
4150
4151fn parse_hex_color(hex: &str) -> [f32; 4] {
4153 let hex = hex.trim_start_matches('#');
4154 if hex.len() >= 6 {
4155 let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(255) as f32 / 255.0;
4156 let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0) as f32 / 255.0;
4157 let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(255) as f32 / 255.0;
4158 let a = if hex.len() >= 8 {
4159 u8::from_str_radix(&hex[6..8], 16).unwrap_or(255) as f32 / 255.0
4160 } else {
4161 1.0
4162 };
4163 [r, g, b, a]
4164 } else {
4165 [1.0, 0.0, 1.0, 1.0] }
4167}
4168
4169pub fn default_tokens() -> YggdrasilTokens {
4171 let mut tokens = YggdrasilTokens::new();
4172 tokens.color.insert(
4174 "background".to_string(),
4175 TokenValue::Single {
4176 value: "#000000".to_string(), },
4178 );
4179 tokens.color.insert(
4180 "primary".to_string(),
4181 TokenValue::Single {
4182 value: "#00FFFF".to_string(), },
4184 );
4185 tokens.color.insert(
4186 "secondary".to_string(),
4187 TokenValue::Single {
4188 value: "#FF00FF".to_string(), },
4190 );
4191 tokens.color.insert(
4192 "surface".to_string(),
4193 TokenValue::Adaptive {
4194 light: "#FFFFFF".to_string(),
4195 dark: "#121212".to_string(),
4196 },
4197 );
4198 tokens.color.insert(
4199 "text".to_string(),
4200 TokenValue::Adaptive {
4201 light: "#000000".to_string(),
4202 dark: "#FFFFFF".to_string(),
4203 },
4204 );
4205 tokens.color.insert(
4207 "surface_elevated".to_string(),
4208 TokenValue::Adaptive {
4209 light: "#FFFFFF".to_string(),
4210 dark: "#1A1A24".to_string(),
4211 },
4212 );
4213 tokens.color.insert(
4214 "surface_overlay".to_string(),
4215 TokenValue::Adaptive {
4216 light: "#FFFFFF".to_string(),
4217 dark: "#1E1E2E".to_string(),
4218 },
4219 );
4220 tokens.color.insert(
4221 "border".to_string(),
4222 TokenValue::Adaptive {
4223 light: "#D0D0D8".to_string(),
4224 dark: "#2A2A3A".to_string(),
4225 },
4226 );
4227 tokens.color.insert(
4228 "border_strong".to_string(),
4229 TokenValue::Adaptive {
4230 light: "#A0A0B0".to_string(),
4231 dark: "#3A3A50".to_string(),
4232 },
4233 );
4234 tokens.color.insert(
4235 "text_muted".to_string(),
4236 TokenValue::Adaptive {
4237 light: "#606070".to_string(),
4238 dark: "#8080A0".to_string(),
4239 },
4240 );
4241 tokens.color.insert(
4242 "text_dim".to_string(),
4243 TokenValue::Adaptive {
4244 light: "#9090A0".to_string(),
4245 dark: "#505070".to_string(),
4246 },
4247 );
4248 tokens.color.insert(
4249 "accent".to_string(),
4250 TokenValue::Single {
4251 value: "#00FFFF".to_string(), },
4253 );
4254 tokens.color.insert(
4255 "accent_hover".to_string(),
4256 TokenValue::Single {
4257 value: "#33FFFF".to_string(),
4258 },
4259 );
4260 tokens.color.insert(
4261 "success".to_string(),
4262 TokenValue::Single {
4263 value: "#00E676".to_string(),
4264 },
4265 );
4266 tokens.color.insert(
4267 "warning".to_string(),
4268 TokenValue::Single {
4269 value: "#FFB300".to_string(),
4270 },
4271 );
4272 tokens.color.insert(
4273 "error".to_string(),
4274 TokenValue::Single {
4275 value: "#FF5252".to_string(),
4276 },
4277 );
4278 tokens.color.insert(
4279 "info".to_string(),
4280 TokenValue::Single {
4281 value: "#448AFF".to_string(),
4282 },
4283 );
4284 tokens.color.insert(
4285 "hover".to_string(),
4286 TokenValue::Adaptive {
4287 light: "#F0F0F5".to_string(),
4288 dark: "#252535".to_string(),
4289 },
4290 );
4291 tokens.color.insert(
4292 "active".to_string(),
4293 TokenValue::Adaptive {
4294 light: "#E0E0EB".to_string(),
4295 dark: "#303045".to_string(),
4296 },
4297 );
4298 tokens.color.insert(
4299 "disabled".to_string(),
4300 TokenValue::Adaptive {
4301 light: "#E8E8F0".to_string(),
4302 dark: "#1A1A28".to_string(),
4303 },
4304 );
4305 tokens.color.insert(
4306 "disabled_text".to_string(),
4307 TokenValue::Adaptive {
4308 light: "#B0B0C0".to_string(),
4309 dark: "#404060".to_string(),
4310 },
4311 );
4312 tokens.color.insert(
4313 "focus_ring".to_string(),
4314 TokenValue::Single {
4315 value: "#00FFFF".to_string(),
4316 },
4317 );
4318 tokens.color.insert(
4319 "shadow".to_string(),
4320 TokenValue::Adaptive {
4321 light: "#00000020".to_string(),
4322 dark: "#00000060".to_string(),
4323 },
4324 );
4325 tokens.color.insert(
4326 "code_bg".to_string(),
4327 TokenValue::Adaptive {
4328 light: "#F5F5FA".to_string(),
4329 dark: "#0D0D18".to_string(),
4330 },
4331 );
4332 tokens.bifrost.insert(
4334 "blur".to_string(),
4335 TokenValue::Single {
4336 value: "25.0".to_string(),
4337 },
4338 );
4339 tokens.bifrost.insert(
4340 "saturation".to_string(),
4341 TokenValue::Single {
4342 value: "1.2".to_string(),
4343 },
4344 );
4345 tokens.bifrost.insert(
4346 "opacity".to_string(),
4347 TokenValue::Single {
4348 value: "0.65".to_string(),
4349 },
4350 );
4351 tokens.gungnir.insert(
4353 "intensity".to_string(),
4354 TokenValue::Single {
4355 value: "1.0".to_string(),
4356 },
4357 );
4358 tokens.gungnir.insert(
4359 "radius".to_string(),
4360 TokenValue::Single {
4361 value: "15.0".to_string(),
4362 },
4363 );
4364 tokens.mjolnir.insert(
4366 "clip_angle".to_string(),
4367 TokenValue::Single {
4368 value: "12.0".to_string(),
4369 },
4370 );
4371 tokens.mjolnir.insert(
4372 "border_width".to_string(),
4373 TokenValue::Single {
4374 value: "2.0".to_string(),
4375 },
4376 );
4377 tokens.anim.insert(
4379 "stiffness".to_string(),
4380 TokenValue::Single {
4381 value: "170.0".to_string(),
4382 },
4383 );
4384 tokens.anim.insert(
4385 "damping".to_string(),
4386 TokenValue::Single {
4387 value: "26.0".to_string(),
4388 },
4389 );
4390 tokens.anim.insert(
4391 "mass".to_string(),
4392 TokenValue::Single {
4393 value: "1.0".to_string(),
4394 },
4395 );
4396 tokens.accessibility.insert(
4398 "reduce_motion".to_string(),
4399 TokenValue::Single {
4400 value: "false".to_string(),
4401 },
4402 );
4403 tokens
4404}
4405pub struct Environment<K: EnvKey> {
4407 _marker: std::marker::PhantomData<K>,
4408}
4409impl<K: EnvKey> Default for Environment<K> {
4410 fn default() -> Self {
4411 Self::new()
4412 }
4413}
4414impl<K: EnvKey> Environment<K> {
4415 pub fn new() -> Self {
4417 Self {
4418 _marker: std::marker::PhantomData,
4419 }
4420 }
4421 pub fn get(&self) -> K::Value {
4423 if let Some(env_store) = ENVIRONMENT.get() {
4424 let env_lock = env_store.lock().unwrap();
4425 if let Some(val) = env_lock.get(&std::any::TypeId::of::<K>()) {
4426 if let Some(typed_val) = val.downcast_ref::<K::Value>() {
4427 return typed_val.clone();
4428 } else {
4429 log::warn!(
4430 "Environment: Downcast failed for key type {:?}",
4431 std::any::type_name::<K>()
4432 );
4433 }
4434 } else {
4435 log::debug!(
4436 "Environment: Key not found: {:?}. Returning default.",
4437 std::any::type_name::<K>()
4438 );
4439 }
4440 } else {
4441 log::debug!(
4442 "Environment: Store not initialized. Key: {:?}. Returning default.",
4443 std::any::type_name::<K>()
4444 );
4445 }
4446 K::default_value()
4447 }
4448}
4449pub mod env {
4451 pub fn insert<K: super::EnvKey>(value: K::Value) {
4453 let store = super::ENVIRONMENT
4454 .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
4455 let mut env_map = store.lock().unwrap();
4456 env_map.insert(std::any::TypeId::of::<K>(), Box::new(value));
4457 }
4458 pub fn remove<K: super::EnvKey>() {
4460 if let Some(store) = super::ENVIRONMENT.get() {
4461 let mut env_map = store.lock().unwrap();
4462 env_map.remove(&std::any::TypeId::of::<K>());
4463 }
4464 }
4465}
4466#[derive(Debug, Clone, Copy, PartialEq)]
4469pub struct Size {
4470 pub width: f32,
4471 pub height: f32,
4472}
4473
4474impl Size {
4475 pub const ZERO: Self = Self {
4476 width: 0.0,
4477 height: 0.0,
4478 };
4479
4480 pub fn new(width: f32, height: f32) -> Self {
4481 Self { width, height }
4482 }
4483}
4484
4485#[derive(Debug, Clone, Copy, PartialEq)]
4487pub struct EdgeInsets {
4488 pub top: f32,
4489 pub leading: f32,
4490 pub bottom: f32,
4491 pub trailing: f32,
4492}
4493
4494impl EdgeInsets {
4495 pub fn all(value: f32) -> Self {
4497 Self {
4498 top: value,
4499 leading: value,
4500 bottom: value,
4501 trailing: value,
4502 }
4503 }
4504
4505 pub fn vertical(value: f32) -> Self {
4507 Self {
4508 top: value,
4509 leading: 0.0,
4510 bottom: value,
4511 trailing: 0.0,
4512 }
4513 }
4514
4515 pub fn horizontal(value: f32) -> Self {
4517 Self {
4518 top: 0.0,
4519 leading: value,
4520 bottom: 0.0,
4521 trailing: value,
4522 }
4523 }
4524}
4525
4526#[derive(Debug, Clone, Copy, PartialEq)]
4530pub struct FrameModifier {
4531 pub width: Option<f32>,
4533 pub height: Option<f32>,
4535 pub min_width: Option<f32>,
4537 pub max_width: Option<f32>,
4539 pub min_height: Option<f32>,
4541 pub max_height: Option<f32>,
4543 pub alignment: Alignment,
4545}
4546
4547impl Default for FrameModifier {
4548 fn default() -> Self {
4550 Self::new()
4551 }
4552}
4553
4554impl FrameModifier {
4555 pub fn new() -> Self {
4557 Self {
4558 width: None,
4559 height: None,
4560 min_width: None,
4561 max_width: None,
4562 min_height: None,
4563 max_height: None,
4564 alignment: Alignment::Center,
4565 }
4566 }
4567
4568 pub fn width(mut self, width: f32) -> Self {
4570 self.width = Some(width);
4571 self
4572 }
4573
4574 pub fn height(mut self, height: f32) -> Self {
4576 self.height = Some(height);
4577 self
4578 }
4579
4580 pub fn size(mut self, width: f32, height: f32) -> Self {
4582 self.width = Some(width);
4583 self.height = Some(height);
4584 self
4585 }
4586
4587 pub fn min_width(mut self, min_width: f32) -> Self {
4589 self.min_width = Some(min_width);
4590 self
4591 }
4592
4593 pub fn max_width(mut self, max_width: f32) -> Self {
4595 self.max_width = Some(max_width);
4596 self
4597 }
4598
4599 pub fn min_height(mut self, min_height: f32) -> Self {
4601 self.min_height = Some(min_height);
4602 self
4603 }
4604
4605 pub fn max_height(mut self, max_height: f32) -> Self {
4607 self.max_height = Some(max_height);
4608 self
4609 }
4610
4611 pub fn alignment(mut self, alignment: Alignment) -> Self {
4613 self.alignment = alignment;
4614 self
4615 }
4616}
4617
4618impl ViewModifier for FrameModifier {
4619 fn modify<V: View>(self, content: V) -> impl View {
4621 ModifiedView::new(content, self)
4622 }
4623
4624 fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
4626 let w = if let Some(width) = self.width {
4627 Some(width)
4628 } else {
4629 proposal.width.map(|pw| {
4630 pw.clamp(
4631 self.min_width.unwrap_or(0.0),
4632 self.max_width.unwrap_or(f32::INFINITY),
4633 )
4634 })
4635 };
4636 let h = if let Some(height) = self.height {
4637 Some(height)
4638 } else {
4639 proposal.height.map(|ph| {
4640 ph.clamp(
4641 self.min_height.unwrap_or(0.0),
4642 self.max_height.unwrap_or(f32::INFINITY),
4643 )
4644 })
4645 };
4646 SizeProposal {
4647 width: w,
4648 height: h,
4649 }
4650 }
4651
4652 fn transform_size(&self, child_size: Size) -> Size {
4654 let w = if let Some(width) = self.width {
4655 width
4656 } else {
4657 child_size.width.clamp(
4658 self.min_width.unwrap_or(0.0),
4659 self.max_width.unwrap_or(f32::INFINITY),
4660 )
4661 };
4662 let h = if let Some(height) = self.height {
4663 height
4664 } else {
4665 child_size.height.clamp(
4666 self.min_height.unwrap_or(0.0),
4667 self.max_height.unwrap_or(f32::INFINITY),
4668 )
4669 };
4670 Size {
4671 width: w,
4672 height: h,
4673 }
4674 }
4675
4676 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4678 self.render(renderer, rect);
4679 let child_proposal =
4680 self.transform_proposal(SizeProposal::new(Some(rect.width), Some(rect.height)));
4681 let child_size = view.intrinsic_size(renderer, child_proposal);
4682
4683 let mut child_x = rect.x;
4684 let mut child_y = rect.y;
4685
4686 match self.alignment {
4687 Alignment::Leading => {
4688 child_y = rect.y + (rect.height - child_size.height) / 2.0;
4689 }
4690 Alignment::Trailing => {
4691 child_x = rect.x + rect.width - child_size.width;
4692 child_y = rect.y + (rect.height - child_size.height) / 2.0;
4693 }
4694 Alignment::Top => {
4695 child_x = rect.x + (rect.width - child_size.width) / 2.0;
4696 }
4697 Alignment::Bottom => {
4698 child_x = rect.x + (rect.width - child_size.width) / 2.0;
4699 child_y = rect.y + rect.height - child_size.height;
4700 }
4701 Alignment::Center => {
4702 child_x = rect.x + (rect.width - child_size.width) / 2.0;
4703 child_y = rect.y + (rect.height - child_size.height) / 2.0;
4704 }
4705 }
4706
4707 let child_rect = Rect {
4708 x: child_x,
4709 y: child_y,
4710 width: child_size.width,
4711 height: child_size.height,
4712 };
4713
4714 view.render(renderer, child_rect);
4715 self.post_render(renderer, rect);
4716 }
4717}
4718
4719#[derive(Debug, Clone, Copy, PartialEq)]
4721pub struct FlexModifier {
4722 pub weight: f32,
4723}
4724
4725impl ViewModifier for FlexModifier {
4726 fn modify<V: View>(self, content: V) -> impl View {
4727 ModifiedView::new(content, self)
4728 }
4729
4730 fn child_flex_weight<V: View>(&self, _view: &V) -> f32 {
4731 self.weight
4732 }
4733}
4734
4735#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4737pub struct GridPlacementModifier {
4738 pub placement: GridPlacement,
4740}
4741
4742impl ViewModifier for GridPlacementModifier {
4743 fn modify<V: View>(self, content: V) -> impl View {
4745 ModifiedView::new(content, self)
4746 }
4747
4748 fn get_grid_placement(&self) -> Option<GridPlacement> {
4750 Some(self.placement)
4751 }
4752}
4753
4754#[derive(Clone)]
4757pub struct OverlayModifier {
4758 pub overlay: AnyView,
4760 pub alignment: Alignment,
4762 pub offset: [f32; 2],
4764 pub on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
4766}
4767
4768impl ViewModifier for OverlayModifier {
4769 fn modify<V: View>(self, content: V) -> impl View {
4771 ModifiedView::new(content, self)
4772 }
4773
4774 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4776 view.render(renderer, rect);
4778
4779 let overlay_size = self
4781 .overlay
4782 .intrinsic_size(renderer, SizeProposal::unspecified());
4783
4784 let mut overlay_x;
4786 let mut overlay_y;
4787
4788 match self.alignment {
4789 Alignment::Leading => {
4790 overlay_x = rect.x - overlay_size.width;
4791 overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4792 }
4793 Alignment::Trailing => {
4794 overlay_x = rect.x + rect.width;
4795 overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4796 }
4797 Alignment::Top => {
4798 overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4799 overlay_y = rect.y - overlay_size.height;
4800 }
4801 Alignment::Bottom => {
4802 overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4803 overlay_y = rect.y + rect.height;
4804 }
4805 Alignment::Center => {
4806 overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4807 overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4808 }
4809 }
4810
4811 overlay_x += self.offset[0];
4812 overlay_y += self.offset[1];
4813
4814 let overlay_rect = Rect {
4815 x: overlay_x,
4816 y: overlay_y,
4817 width: overlay_size.width,
4818 height: overlay_size.height,
4819 };
4820
4821 if let Some(on_dismiss) = &self.on_dismiss {
4823 let dismiss = on_dismiss.clone();
4824 renderer.register_handler(
4825 "pointerdown",
4826 Arc::new(move |event| {
4827 if let Event::PointerDown { x, y, .. } = event {
4828 let click_inside = x >= overlay_rect.x
4829 && x <= overlay_rect.x + overlay_rect.width
4830 && y >= overlay_rect.y
4831 && y <= overlay_rect.y + overlay_rect.height;
4832 if !click_inside {
4833 dismiss();
4834 }
4835 }
4836 }),
4837 );
4838 }
4839
4840 self.overlay.render(renderer, overlay_rect);
4842 }
4843}
4844
4845#[derive(Debug, Clone, Copy, PartialEq)]
4847pub struct OffsetModifier {
4848 pub x: f32,
4849 pub y: f32,
4850}
4851
4852impl OffsetModifier {
4853 pub fn new(x: f32, y: f32) -> Self {
4854 Self { x, y }
4855 }
4856}
4857
4858impl ViewModifier for OffsetModifier {
4859 fn modify<V: View>(self, content: V) -> impl View {
4860 ModifiedView::new(content, self)
4861 }
4862}
4863
4864#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4866pub struct ZIndexModifier {
4867 pub z_index: i32,
4868}
4869
4870impl ZIndexModifier {
4871 pub fn new(z_index: i32) -> Self {
4872 Self { z_index }
4873 }
4874}
4875
4876impl ViewModifier for ZIndexModifier {
4877 fn modify<V: View>(self, content: V) -> impl View {
4878 ModifiedView::new(content, self)
4879 }
4880}
4881
4882#[derive(Debug, Clone, Copy, PartialEq, Default)]
4884pub struct LayoutConstraints {
4885 pub min_width: Option<f32>,
4886 pub max_width: Option<f32>,
4887 pub min_height: Option<f32>,
4888 pub max_height: Option<f32>,
4889}
4890
4891#[derive(Debug, Clone, Copy, PartialEq)]
4893pub struct LayoutModifier {
4894 pub constraints: LayoutConstraints,
4895}
4896
4897impl LayoutModifier {
4898 pub fn new(constraints: LayoutConstraints) -> Self {
4899 Self { constraints }
4900 }
4901}
4902
4903impl ViewModifier for LayoutModifier {
4904 fn modify<V: View>(self, content: V) -> impl View {
4905 ModifiedView::new(content, self)
4906 }
4907}
4908
4909#[derive(Debug, Clone, Copy, PartialEq)]
4911pub struct SafeAreaModifier {
4912 pub ignores: bool,
4913}
4914
4915impl ViewModifier for SafeAreaModifier {
4916 fn modify<V: View>(self, content: V) -> impl View {
4917 ModifiedView::new(content, self)
4918 }
4919}
4920
4921#[derive(Debug, Clone, Copy, PartialEq)]
4923pub struct ElevationModifier {
4924 pub level: f32,
4925}
4926
4927impl ViewModifier for ElevationModifier {
4928 fn modify<V: View>(self, content: V) -> impl View {
4929 ModifiedView::new(content, self)
4930 }
4931
4932 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4933 if self.level > 0.0 {
4934 let radius = self.level * 2.0;
4935 let offset_y = self.level * 0.5;
4936 let shadow_color = [0.0, 0.0, 0.0, 0.3];
4937 renderer.push_shadow(radius, shadow_color, [0.0, offset_y]);
4938 view.render(renderer, rect);
4939 renderer.pop_shadow();
4940 } else {
4941 view.render(renderer, rect);
4942 }
4943 }
4944}
4945
4946pub mod layout {
4948 use super::*;
4949
4950 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4953 pub struct LayoutKey {
4954 pub view_hash: u64,
4955 pub generation: u64,
4956 }
4957
4958 pub struct LayoutCache {
4960 pub safe_area: SafeArea,
4961 size_cache: HashMap<(u64, u32, u32), Size>, generation: u64,
4966 }
4967
4968 impl Default for LayoutCache {
4969 fn default() -> Self {
4970 Self::new()
4971 }
4972 }
4973
4974 impl LayoutCache {
4975 pub fn new() -> Self {
4976 Self {
4977 safe_area: SafeArea::default(),
4978 size_cache: HashMap::new(),
4979 generation: 0,
4980 }
4981 }
4982
4983 pub fn generation(&self) -> u64 {
4985 self.generation
4986 }
4987
4988 pub fn invalidate(&mut self) {
4992 self.generation = self.generation.wrapping_add(1);
4993 }
4994
4995 pub fn is_valid(&self, key: LayoutKey, current_gen: u64) -> bool {
4998 key.generation == current_gen && key.generation == self.generation
4999 }
5000
5001 pub fn clear(&mut self) {
5002 self.safe_area = SafeArea::default();
5003 self.size_cache.clear();
5004 }
5005
5006 pub fn get_size(&self, view_hash: u64, proposal: SizeProposal) -> Option<Size> {
5007 let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
5008 let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
5009 self.size_cache.get(&(view_hash, pw, ph)).copied()
5010 }
5011
5012 pub fn set_size(&mut self, view_hash: u64, proposal: SizeProposal, size: Size) {
5013 let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
5014 let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
5015 self.size_cache.insert((view_hash, pw, ph), size);
5016 }
5017
5018 pub fn invalidate_view(&mut self, view_hash: u64) {
5020 self.size_cache.retain(|&(hash, _, _), _| hash != view_hash);
5021 }
5022 }
5023
5024 #[derive(Debug, Clone, Copy, PartialEq)]
5026 pub struct SizeProposal {
5027 pub width: Option<f32>,
5028 pub height: Option<f32>,
5029 }
5030
5031 impl SizeProposal {
5032 pub fn unspecified() -> Self {
5033 Self {
5034 width: None,
5035 height: None,
5036 }
5037 }
5038
5039 pub fn width(width: f32) -> Self {
5040 Self {
5041 width: Some(width),
5042 height: None,
5043 }
5044 }
5045
5046 pub fn height(height: f32) -> Self {
5047 Self {
5048 width: None,
5049 height: Some(height),
5050 }
5051 }
5052
5053 pub fn tight(width: f32, height: f32) -> Self {
5054 Self {
5055 width: Some(width),
5056 height: Some(height),
5057 }
5058 }
5059
5060 pub fn new(width: Option<f32>, height: Option<f32>) -> Self {
5061 Self { width, height }
5062 }
5063 }
5064
5065 pub trait LayoutView: Send {
5067 fn size_that_fits(
5069 &self,
5070 proposal: SizeProposal,
5071 subviews: &[&dyn LayoutView],
5072 cache: &mut LayoutCache,
5073 ) -> Size;
5074
5075 fn place_subviews(
5077 &self,
5078 bounds: Rect,
5079 subviews: &mut [&mut dyn LayoutView],
5080 cache: &mut LayoutCache,
5081 );
5082
5083 fn flex_weight(&self) -> f32 {
5085 0.0
5086 }
5087
5088 fn debug_layout(&self, indent: usize) -> String {
5091 let prefix = " ".repeat(indent);
5092 format!("{}LayoutView", prefix)
5093 }
5094 }
5095 #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
5097 pub struct EdgeInsets {
5098 pub top: f32,
5099 pub leading: f32,
5100 pub bottom: f32,
5101 pub trailing: f32,
5102 }
5103
5104 impl EdgeInsets {
5105 pub fn new(top: f32, leading: f32, bottom: f32, trailing: f32) -> Self {
5106 Self {
5107 top,
5108 leading,
5109 bottom,
5110 trailing,
5111 }
5112 }
5113
5114 pub fn all(value: f32) -> Self {
5115 Self {
5116 top: value,
5117 leading: value,
5118 bottom: value,
5119 trailing: value,
5120 }
5121 }
5122 }
5123
5124 #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
5126 pub struct SafeArea {
5127 pub insets: EdgeInsets,
5128 }
5129
5130 #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
5132 pub enum SdfShape {
5133 Rect(Rect),
5134 RoundedRect { rect: Rect, radius: f32 },
5135 Circle { center: [f32; 2], radius: f32 },
5136 }
5137
5138 #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
5140 pub struct Rect {
5141 pub x: f32,
5142 pub y: f32,
5143 pub width: f32,
5144 pub height: f32,
5145 }
5146
5147 impl Rect {
5148 pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
5149 Self {
5150 x,
5151 y,
5152 width,
5153 height,
5154 }
5155 }
5156
5157 pub fn inset(&self, amount: f32) -> Self {
5158 Self {
5159 x: self.x + amount,
5160 y: self.y + amount,
5161 width: (self.width - amount * 2.0).max(0.0),
5162 height: (self.height - amount * 2.0).max(0.0),
5163 }
5164 }
5165
5166 pub fn offset(&self, dx: f32, dy: f32) -> Self {
5167 Self {
5168 x: self.x + dx,
5169 y: self.y + dy,
5170 ..*self
5171 }
5172 }
5173
5174 pub fn zero() -> Self {
5175 Self {
5176 x: 0.0,
5177 y: 0.0,
5178 width: 0.0,
5179 height: 0.0,
5180 }
5181 }
5182
5183 pub fn contains(&self, x: f32, y: f32) -> bool {
5184 x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
5185 }
5186
5187 pub fn size(&self) -> Size {
5188 Size {
5189 width: self.width,
5190 height: self.height,
5191 }
5192 }
5193
5194 pub fn split_horizontal(&self, n: usize) -> Vec<Rect> {
5196 if n == 0 {
5197 return vec![];
5198 }
5199 let item_width = self.width / n as f32;
5200 (0..n)
5201 .map(|i| Rect {
5202 x: self.x + i as f32 * item_width,
5203 y: self.y,
5204 width: item_width,
5205 height: self.height,
5206 })
5207 .collect()
5208 }
5209
5210 pub fn split_vertical(&self, n: usize) -> Vec<Rect> {
5212 if n == 0 {
5213 return vec![];
5214 }
5215 let item_height = self.height / n as f32;
5216 (0..n)
5217 .map(|i| Rect {
5218 x: self.x,
5219 y: self.y + i as f32 * item_height,
5220 width: self.width,
5221 height: item_height,
5222 })
5223 .collect()
5224 }
5225 }
5226}
5227
5228pub use layout::{LayoutCache, LayoutKey, LayoutView, Rect, SizeProposal};
5230pub mod agents;
5233pub mod animation;
5234pub mod gpu;
5235pub mod material;
5236pub mod runtime;
5237pub mod scene_graph;
5238pub mod sdf_shadow;
5239
5240pub use material::DrawMaterial;
5241pub use scene_graph::{NodeId, bifrost_registry};
5242
5243pub trait AssetManager: Send + Sync {
5247 fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>>;
5249
5250 fn preload_image(&self, url: &str);
5252}
5253
5254#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5256pub enum TouchPhase {
5257 Began,
5259 Moved,
5261 Ended,
5263 Cancelled,
5265}
5266
5267#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
5269pub enum Event {
5270 PointerDown {
5271 x: f32,
5272 y: f32,
5273 button: u32,
5274 proximity_field: f32,
5275 tilt: Option<f32>,
5276 azimuth: Option<f32>,
5277 pressure: Option<f32>,
5278 barrel_rotation: Option<f32>,
5279 },
5280 PointerUp {
5281 x: f32,
5282 y: f32,
5283 button: u32,
5284 tilt: Option<f32>,
5285 azimuth: Option<f32>,
5286 pressure: Option<f32>,
5287 barrel_rotation: Option<f32>,
5288 },
5289 PointerMove {
5290 x: f32,
5291 y: f32,
5292 proximity_field: f32,
5293 tilt: Option<f32>,
5294 azimuth: Option<f32>,
5295 pressure: Option<f32>,
5296 barrel_rotation: Option<f32>,
5297 },
5298 PointerClick {
5299 x: f32,
5300 y: f32,
5301 button: u32,
5302 tilt: Option<f32>,
5303 azimuth: Option<f32>,
5304 pressure: Option<f32>,
5305 barrel_rotation: Option<f32>,
5306 },
5307 PointerEnter,
5308 PointerLeave,
5309 PointerWheel {
5312 x: f32,
5313 y: f32,
5314 delta_x: f32,
5315 delta_y: f32,
5316 },
5317 PointerDoubleClick {
5319 x: f32,
5320 y: f32,
5321 button: u32,
5322 },
5323 DragStart {
5325 x: f32,
5326 y: f32,
5327 button: u32,
5328 },
5329 DragMove {
5331 x: f32,
5332 y: f32,
5333 },
5334 DragEnd {
5336 x: f32,
5337 y: f32,
5338 },
5339 KeyDown {
5340 key: String,
5341 },
5342 KeyUp {
5343 key: String,
5344 },
5345 FocusIn,
5347 FocusOut,
5349 Copy,
5351 Cut,
5353 Paste(String),
5355 Ime(String),
5357 TouchStart {
5359 x: f32,
5360 y: f32,
5361 touch_id: u64,
5362 },
5363 TouchMove {
5365 x: f32,
5366 y: f32,
5367 touch_id: u64,
5368 },
5369 TouchEnd {
5371 x: f32,
5372 y: f32,
5373 touch_id: u64,
5374 },
5375 TouchCancel {
5377 touch_id: u64,
5378 },
5379 GesturePinch {
5385 center: [f32; 2],
5386 scale: f32,
5387 velocity: f32,
5388 phase: TouchPhase,
5389 },
5390 GestureSwipe {
5395 direction: [f32; 2],
5396 velocity: f32,
5397 phase: TouchPhase,
5398 },
5399 FileDrop {
5401 path: String,
5402 },
5403}
5404
5405impl Event {
5406 pub fn name(&self) -> &'static str {
5408 match self {
5409 Self::PointerDown { .. } => "pointerdown",
5410 Self::PointerUp { .. } => "pointerup",
5411 Self::PointerMove { .. } => "pointermove",
5412 Self::PointerClick { .. } => "pointerclick",
5413 Self::PointerEnter => "pointerenter",
5414 Self::PointerLeave => "pointerleave",
5415 Self::PointerWheel { .. } => "pointerwheel",
5416 Self::PointerDoubleClick { .. } => "pointerdoubleclick",
5417 Self::DragStart { .. } => "dragstart",
5418 Self::DragMove { .. } => "dragmove",
5419 Self::DragEnd { .. } => "dragend",
5420 Self::KeyDown { .. } => "keydown",
5421 Self::KeyUp { .. } => "keyup",
5422 Self::FocusIn => "focusin",
5423 Self::FocusOut => "focusout",
5424 Self::Copy => "copy",
5425 Self::Cut => "cut",
5426 Self::Paste(_) => "paste",
5427 Self::Ime(_) => "ime",
5428 Self::TouchStart { .. } => "touchstart",
5429 Self::TouchMove { .. } => "touchmove",
5430 Self::TouchEnd { .. } => "touchend",
5431 Self::TouchCancel { .. } => "touchcancel",
5432 Self::GesturePinch { .. } => "gesturepinch",
5433 Self::GestureSwipe { .. } => "gestureswipe",
5434 Self::FileDrop { .. } => "filedrop",
5435 }
5436 }
5437}
5438
5439#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5441pub enum EventResponse {
5442 Handled,
5443 Ignored,
5444}
5445
5446pub struct DefaultAssetManager {
5448 cache: AssetCache,
5449}
5450type AssetCache = Arc<arc_swap::ArcSwap<HashMap<String, AssetState<Arc<Vec<u8>>>>>>;
5451
5452impl Default for DefaultAssetManager {
5453 fn default() -> Self {
5454 Self::new()
5455 }
5456}
5457
5458impl DefaultAssetManager {
5459 pub fn new() -> Self {
5460 Self {
5461 cache: Arc::new(arc_swap::ArcSwap::from_pointee(HashMap::new())),
5462 }
5463 }
5464}
5465
5466impl AssetManager for DefaultAssetManager {
5467 fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>> {
5468 if let Some(state) = self.cache.load().get(url) {
5469 return state.clone();
5470 }
5471
5472 self.cache.rcu(|map| {
5473 let mut m = (**map).clone();
5474 m.entry(url.to_string()).or_insert(AssetState::Loading);
5475 m
5476 });
5477 AssetState::Loading
5478 }
5479
5480 fn preload_image(&self, _url: &str) {}
5481}
5482
5483use std::future::Future;
5484
5485pub struct Suspense<T: Clone + Send + Sync + 'static> {
5488 inner: State<AssetState<T>>,
5489}
5490
5491impl<T: Clone + Send + Sync + 'static> Default for Suspense<T> {
5492 fn default() -> Self {
5493 Self::new()
5494 }
5495}
5496
5497impl<T: Clone + Send + Sync + 'static> Suspense<T> {
5498 pub fn new() -> Self {
5499 Self {
5500 inner: State::new(AssetState::Loading),
5501 }
5502 }
5503
5504 pub fn new_async<F>(future: F) -> Self
5505 where
5506 F: Future<Output = Result<T, String>> + Send + 'static,
5507 {
5508 let suspense = Self::new();
5509 let suspense_clone = suspense.clone();
5510
5511 #[cfg(not(target_arch = "wasm32"))]
5512 {
5513 if let Ok(handle) = tokio::runtime::Handle::try_current() {
5515 handle.spawn(async move {
5516 let result = future.await;
5517 match result {
5518 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5519 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5520 }
5521 });
5522 } else {
5523 std::thread::spawn(move || {
5524 let rt = tokio::runtime::Builder::new_current_thread()
5525 .enable_all()
5526 .build()
5527 .unwrap();
5528 rt.block_on(async {
5529 let result = future.await;
5530 match result {
5531 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5532 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5533 }
5534 });
5535 });
5536 }
5537 }
5538 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5539 {
5540 wasm_bindgen_futures::spawn_local(async move {
5541 let result = future.await;
5542 match result {
5543 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5544 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5545 }
5546 });
5547 }
5548
5549 suspense
5550 }
5551
5552 pub fn ready(value: T) -> Self {
5553 Self {
5554 inner: State::new(AssetState::Ready(value)),
5555 }
5556 }
5557
5558 pub fn error(message: impl Into<String>) -> Self {
5559 Self {
5560 inner: State::new(AssetState::Error(message.into())),
5561 }
5562 }
5563
5564 pub fn get(&self) -> AssetState<T> {
5565 self.inner.get()
5566 }
5567
5568 pub fn get_ref(&self) -> AssetState<T> {
5569 self.inner.get()
5570 }
5571
5572 pub fn is_loading(&self) -> bool {
5573 matches!(self.get(), AssetState::Loading)
5574 }
5575
5576 pub fn is_ready(&self) -> bool {
5577 matches!(self.get(), AssetState::Ready(_))
5578 }
5579
5580 pub fn is_error(&self) -> bool {
5581 matches!(self.get(), AssetState::Error(_))
5582 }
5583
5584 pub fn ready_value(&self) -> Option<T> {
5585 match self.get() {
5586 AssetState::Ready(value) => Some(value),
5587 _ => None,
5588 }
5589 }
5590
5591 pub fn error_message(&self) -> Option<String> {
5592 match self.get() {
5593 AssetState::Error(message) => Some(message),
5594 _ => None,
5595 }
5596 }
5597
5598 pub fn subscribe<F: Fn(&AssetState<T>) + Send + Sync + 'static>(&self, callback: F) {
5599 self.inner.subscribe(callback)
5600 }
5601
5602 pub fn inner_state(&self) -> &State<AssetState<T>> {
5603 &self.inner
5604 }
5605}
5606
5607impl<T: Clone + Send + Sync + 'static> Clone for Suspense<T> {
5608 fn clone(&self) -> Self {
5609 Self {
5610 inner: self.inner.clone(),
5611 }
5612 }
5613}
5614
5615impl<T: Clone + Send + Sync + 'static> From<T> for Suspense<T> {
5616 fn from(value: T) -> Self {
5617 Self::ready(value)
5618 }
5619}
5620
5621impl<T: Clone + Send + Sync + 'static> From<Result<T, String>> for Suspense<T> {
5622 fn from(result: Result<T, String>) -> Self {
5623 match result {
5624 Ok(value) => Self::ready(value),
5625 Err(error) => Self::error(error),
5626 }
5627 }
5628}
5629
5630#[cfg(test)]
5631mod phase1_test;
5632
5633#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5635pub enum BerserkerMode {
5636 Normal,
5637 Rage, Frenzy, GodMode, }
5641
5642pub trait Seer: Send + Sync {
5645 fn predict(&self, context: &str) -> String;
5647 fn whispers(&self) -> Vec<String>;
5649}
5650
5651#[cfg(test)]
5652mod vili_tests {
5653 use super::*;
5654
5655 struct DummyRenderer;
5656 impl ElapsedTime for DummyRenderer {
5657 fn elapsed_time(&self) -> f32 {
5658 0.0
5659 }
5660 fn delta_time(&self) -> f32 {
5661 0.0
5662 }
5663 }
5664 impl Renderer for DummyRenderer {
5665 fn fill_rect(&mut self, _r: Rect, _c: [f32; 4]) {}
5666 fn fill_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4]) {}
5667 fn fill_ellipse(&mut self, _r: Rect, _c: [f32; 4]) {}
5668 fn stroke_rect(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5669 fn stroke_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4], _w: f32) {}
5670 fn stroke_ellipse(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5671 fn draw_line(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, _c: [f32; 4], _w: f32) {}
5672 fn draw_text(&mut self, _t: &str, _x: f32, _y: f32, _s: f32, _c: [f32; 4]) {}
5673 fn measure_text(&mut self, _t: &str, _s: f32) -> (f32, f32) {
5674 (0.0, 0.0)
5675 }
5676 fn memoize(&mut self, _id: u64, _hash: u64, _r: &dyn Fn(&mut dyn Renderer)) {}
5677 fn draw_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {}
5678 fn set_camera_3d(&mut self, _camera: &Camera3D) {}
5679 fn push_transform_3d(&mut self, _transform: &Transform3D) {}
5680 fn pop_transform_3d(&mut self) {}
5681 }
5682
5683 #[test]
5684 fn test_magnetic_warp() {
5685 let renderer = DummyRenderer;
5686 let anchor = Rect {
5687 x: 100.0,
5688 y: 100.0,
5689 width: 50.0,
5690 height: 50.0,
5691 };
5692 let pointer = [125.0, 50.0];
5694 let warp = renderer.magnetic_warp(pointer, anchor, 1.0);
5697 assert!(warp[1] > 50.0);
5699
5700 let far_pointer = [500.0, 500.0];
5702 let far_warp = renderer.magnetic_warp(far_pointer, anchor, 1.0);
5703 assert_eq!(far_pointer, far_warp);
5704 }
5705
5706 #[test]
5707 fn test_mani_glow() {
5708 let renderer = DummyRenderer;
5709 let bounds = Rect {
5710 x: 0.0,
5711 y: 0.0,
5712 width: 100.0,
5713 height: 100.0,
5714 };
5715 let pointer_inside = [50.0, 50.0];
5716 let glow_max = renderer.mani_glow_intensity(pointer_inside, bounds, 120.0);
5717 assert_eq!(glow_max, 1.0);
5718
5719 let pointer_edge = [50.0, -10.0];
5720 let glow_partial = renderer.mani_glow_intensity(pointer_edge, bounds, 120.0);
5721 assert!(glow_partial > 0.0 && glow_partial < 1.0);
5722 }
5723
5724 #[test]
5725 fn test_fafnir_evolve() {
5726 let renderer = DummyRenderer;
5727 let bounds = Rect {
5728 x: 0.0,
5729 y: 0.0,
5730 width: 100.0,
5731 height: 100.0,
5732 };
5733 let pointer_inside = [50.0, 50.0];
5734 let scale = renderer.fafnir_evolve(pointer_inside, bounds, 1.2);
5735 assert_eq!(scale, 1.2); }
5737
5738 #[test]
5739 fn test_undo_manager_basic() {
5740 let mut manager = UndoManager::new(3, 0.5);
5741 let val = std::sync::Arc::new(std::sync::Mutex::new(0));
5742
5743 let v1 = val.clone();
5744 let v2 = val.clone();
5745 manager.push(
5746 "Add",
5747 move || *v1.lock().unwrap() -= 1,
5748 move || *v2.lock().unwrap() += 1,
5749 );
5750
5751 assert!(manager.can_undo());
5752 assert!(!manager.can_redo());
5753
5754 let undo = manager.undo().unwrap();
5755 undo();
5756 assert_eq!(*val.lock().unwrap(), -1);
5757 assert!(!manager.can_undo());
5758 assert!(manager.can_redo());
5759
5760 let redo = manager.redo().unwrap();
5761 redo();
5762 assert_eq!(*val.lock().unwrap(), 0);
5763 }
5764
5765 #[test]
5766 fn test_undo_manager_depth_limit() {
5767 let mut manager = UndoManager::new(2, 0.5);
5768 manager.push("1", || {}, || {});
5769 manager.push("2", || {}, || {});
5770 manager.push("3", || {}, || {});
5771
5772 assert_eq!(manager.stack.len(), 2);
5773 assert_eq!(manager.position, 2);
5774 }
5775
5776 #[test]
5777 fn test_undo_manager_coalescing() {
5778 let mut manager = UndoManager::new(10, 1.0);
5779 let count = std::sync::Arc::new(std::sync::Mutex::new(0));
5780
5781 let c = count.clone();
5782 manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5783
5784 let c = count.clone();
5785 manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5786
5787 assert_eq!(manager.stack.len(), 1);
5788
5789 let undo = manager.undo().unwrap();
5790 undo();
5791 assert_eq!(*count.lock().unwrap(), -2);
5792 }
5793}
5794
5795use std::cell::RefCell;
5807
5808thread_local! {
5809 static THEME_CONTEXT: RefCell<Option<color::SemanticColors>> = const { RefCell::new(None) };
5811}
5812
5813pub use color::SemanticColors;
5819
5820pub fn set_current_theme(colors: color::SemanticColors) {
5823 THEME_CONTEXT.with(|cell| {
5824 *cell.borrow_mut() = Some(colors);
5825 });
5826}
5827
5828pub fn clear_current_theme() {
5830 THEME_CONTEXT.with(|cell| {
5831 *cell.borrow_mut() = None;
5832 });
5833}
5834
5835pub fn use_theme() -> color::SemanticColors {
5850 THEME_CONTEXT.with(|cell| {
5851 cell.borrow()
5852 .clone()
5853 .unwrap_or_else(color::SemanticColors::dark)
5854 })
5855}
5856
5857pub mod color {
5866 use super::Color;
5867
5868 #[derive(Debug, Clone)]
5885 pub struct SemanticColors {
5886 pub primary: Color,
5888 pub secondary: Color,
5890 pub accent: Color,
5892 pub background: Color,
5894 pub surface: Color,
5896 pub error: Color,
5898 pub warning: Color,
5900 pub success: Color,
5902 pub text: Color,
5904 pub text_dim: Color,
5906 }
5907
5908 impl SemanticColors {
5909 pub fn dark() -> Self {
5911 Self {
5912 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), }
5923 }
5924
5925 pub fn light() -> Self {
5927 Self {
5928 primary: Color::new(0.35, 0.30, 0.70, 1.0),
5929 secondary: Color::new(0.30, 0.50, 0.30, 1.0),
5930 accent: Color::new(0.30, 0.35, 0.75, 1.0),
5931 background: Color::new(0.97, 0.97, 0.98, 1.0),
5932 surface: Color::new(0.93, 0.93, 0.95, 1.0),
5933 error: Color::new(0.75, 0.15, 0.15, 1.0),
5934 warning: Color::new(0.80, 0.60, 0.0, 1.0),
5935 success: Color::new(0.15, 0.65, 0.30, 1.0),
5936 text: Color::new(0.08, 0.08, 0.10, 1.0),
5937 text_dim: Color::new(0.40, 0.40, 0.45, 1.0),
5938 }
5939 }
5940
5941 pub fn accent_states(&self) -> InteractiveColorStates {
5946 InteractiveColorStates::from_color(self.accent)
5947 }
5948
5949 pub fn primary_states(&self) -> InteractiveColorStates {
5951 InteractiveColorStates::from_color(self.primary)
5952 }
5953
5954 pub fn error_states(&self) -> InteractiveColorStates {
5956 InteractiveColorStates::from_color(self.error)
5957 }
5958
5959 pub fn success_states(&self) -> InteractiveColorStates {
5961 InteractiveColorStates::from_color(self.success)
5962 }
5963 }
5964
5965 #[derive(Debug, Clone)]
5970 pub struct InteractiveColorStates {
5971 pub default: Color,
5972 pub hover: Color,
5973 pub active: Color,
5974 pub focus: Color,
5975 pub disabled: Color,
5976 pub focus_ring: Color,
5977 }
5978
5979 impl InteractiveColorStates {
5980 pub fn from_color(base: Color) -> Self {
5989 Self {
5990 default: base,
5991 hover: base.lighten(0.15),
5992 active: base.darken(0.15),
5993 focus: base,
5994 disabled: Color::new(base.r, base.g, base.b, base.a * 0.4),
5995 focus_ring: Color::new(base.r, base.g, base.b, base.a * 0.7),
5996 }
5997 }
5998
5999 pub fn color_for(&self, state: InteractiveState) -> Color {
6001 match state {
6002 InteractiveState::Default => self.default,
6003 InteractiveState::Hover => self.hover,
6004 InteractiveState::Active => self.active,
6005 InteractiveState::Focus => self.focus,
6006 InteractiveState::Disabled => self.disabled,
6007 }
6008 }
6009 }
6010
6011 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6013 pub enum InteractiveState {
6014 Default,
6015 Hover,
6016 Active,
6017 Focus,
6018 Disabled,
6019 }
6020}
6021
6022pub fn use_state<T: Clone + Send + Sync + 'static>(
6041 id: u64,
6042 initial: T,
6043) -> (impl Fn() -> T, impl Fn(T)) {
6044 let already_exists = load_system_state().get_component_state::<T>(id).is_some();
6046 if !already_exists {
6047 update_system_state(|s| {
6048 let mut ns = s.clone();
6049 ns.set_component_state(id, initial.clone());
6050 ns
6051 });
6052 }
6053
6054 let getter = move || -> T {
6055 load_system_state()
6056 .get_component_state::<T>(id)
6057 .map(|arc_lock| {
6058 arc_lock
6059 .read()
6060 .ok()
6061 .map(|guard| (*guard).clone())
6062 .unwrap_or_else(|| initial.clone())
6063 })
6064 .unwrap_or_else(|| initial.clone())
6065 };
6066
6067 let setter = {
6068 move |value| {
6069 update_system_state(|s| {
6070 let mut ns = s.clone();
6071 ns.set_component_state(id, value);
6072 ns
6073 });
6074 }
6075 };
6076
6077 (getter, setter)
6078}
6079
6080pub fn use_state_hash(key: &str) -> u64 {
6092 use std::hash::{Hash, Hasher};
6093 let mut s = std::collections::hash_map::DefaultHasher::new();
6094 key.hash(&mut s);
6095 s.finish()
6096}
6097
6098thread_local! {
6108 static ACCESSIBILITY_PREFS: std::cell::RefCell<AccessibilityPreferences> =
6111 std::cell::RefCell::new(AccessibilityPreferences::default());
6112}
6113
6114#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
6121pub struct AccessibilityPreferences {
6122 pub reduce_motion: bool,
6124 pub reduce_transparency: bool,
6126 pub increase_contrast: bool,
6128}
6129
6130impl AccessibilityPreferences {
6131 pub fn detect_from_system() -> Self {
6136 #[cfg(target_os = "macos")]
6137 {
6138 let reduce_motion = std::process::Command::new("defaults")
6140 .args(["read", "-g", "com.apple.universalaccess", "reduceMotion"])
6141 .output()
6142 .ok()
6143 .and_then(|o| String::from_utf8(o.stdout).ok())
6144 .map(|s| s.trim() == "1")
6145 .unwrap_or(false);
6146
6147 let reduce_transparency = std::process::Command::new("defaults")
6148 .args([
6149 "read",
6150 "-g",
6151 "com.apple.universalaccess",
6152 "reduceTransparency",
6153 ])
6154 .output()
6155 .ok()
6156 .and_then(|o| String::from_utf8(o.stdout).ok())
6157 .map(|s| s.trim() == "1")
6158 .unwrap_or(false);
6159
6160 let increase_contrast = std::process::Command::new("defaults")
6161 .args([
6162 "read",
6163 "-g",
6164 "com.apple.universalaccess",
6165 "increaseContrast",
6166 ])
6167 .output()
6168 .ok()
6169 .and_then(|o| String::from_utf8(o.stdout).ok())
6170 .map(|s| s.trim() == "1")
6171 .unwrap_or(false);
6172
6173 Self {
6174 reduce_motion,
6175 reduce_transparency,
6176 increase_contrast,
6177 }
6178 }
6179 #[cfg(not(target_os = "macos"))]
6180 {
6181 Self::default()
6182 }
6183 }
6184
6185 pub fn min_alpha(&self, requested: f32) -> f32 {
6187 if self.increase_contrast {
6188 requested.max(0.5)
6189 } else {
6190 requested
6191 }
6192 }
6193
6194 pub fn should_disable_glass(&self) -> bool {
6196 self.reduce_transparency
6197 }
6198
6199 pub fn should_reduce_motion(&self) -> bool {
6201 self.reduce_motion
6202 }
6203
6204 pub fn should_increase_contrast(&self) -> bool {
6206 self.increase_contrast
6207 }
6208}
6209
6210pub fn accessibility_preferences() -> AccessibilityPreferences {
6212 ACCESSIBILITY_PREFS.with(|p| *p.borrow())
6213}
6214
6215pub fn set_accessibility_preferences(prefs: AccessibilityPreferences) {
6220 ACCESSIBILITY_PREFS.with(|p| {
6221 *p.borrow_mut() = prefs;
6222 });
6223}
6224
6225pub trait ClipboardProvider: Send + Sync {
6234 fn read_text(&self) -> Option<String>;
6236 fn write_text(&self, text: &str);
6238}
6239
6240#[cfg(not(target_arch = "wasm32"))]
6244pub struct SystemClipboard;
6245
6246#[cfg(not(target_arch = "wasm32"))]
6247impl ClipboardProvider for SystemClipboard {
6248 fn read_text(&self) -> Option<String> {
6249 use std::process::Command;
6250 Command::new("pbpaste")
6252 .output()
6253 .ok()
6254 .and_then(|o| String::from_utf8(o.stdout).ok())
6255 }
6256
6257 fn write_text(&self, text: &str) {
6258 use std::process::Command;
6259 if let Ok(mut child) = Command::new("pbcopy")
6261 .stdin(std::process::Stdio::piped())
6262 .spawn()
6263 {
6264 if let Some(stdin) = child.stdin.as_mut() {
6265 use std::io::Write;
6266 let _ = stdin.write_all(text.as_bytes());
6267 }
6268 let _ = child.wait();
6269 }
6270 }
6271}
6272
6273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6279pub enum TextDirection {
6280 Forward,
6281 Backward,
6282 Up,
6283 Down,
6284 LineStart,
6285 LineEnd,
6286 WordForward,
6287 WordBackward,
6288}
6289
6290#[derive(Debug, Clone, Default)]
6295pub struct TextInputState {
6296 pub text: String,
6298 pub cursor_pos: usize,
6300 pub selection_anchor: Option<usize>,
6303 pub focused: bool,
6305 pub caret_visible: bool,
6307 pub last_edit_time: f32,
6309}
6310
6311impl TextInputState {
6312 pub fn new(text: impl Into<String>) -> Self {
6314 let text = text.into();
6315 let cursor_pos = text.len();
6316 Self {
6317 text,
6318 cursor_pos,
6319 selection_anchor: None,
6320 focused: false,
6321 caret_visible: true,
6322 last_edit_time: 0.0,
6323 }
6324 }
6325
6326 pub fn selection_range(&self) -> Option<(usize, usize)> {
6329 self.selection_anchor.map(|anchor| {
6330 if anchor <= self.cursor_pos {
6331 (anchor, self.cursor_pos)
6332 } else {
6333 (self.cursor_pos, anchor)
6334 }
6335 })
6336 }
6337
6338 pub fn selected_text(&self) -> String {
6340 self.selection_range()
6341 .map(|(start, end)| self.text[start..end].to_string())
6342 .unwrap_or_default()
6343 }
6344
6345 pub fn insert(&mut self, new_text: &str) {
6347 if let Some((start, end)) = self.selection_range() {
6348 self.text.replace_range(start..end, new_text);
6349 self.cursor_pos = start + new_text.len();
6350 } else {
6351 self.text.insert_str(self.cursor_pos, new_text);
6352 self.cursor_pos += new_text.len();
6353 }
6354 self.selection_anchor = None;
6355 }
6356
6357 pub fn delete(&mut self, backward: bool, count: usize) -> String {
6360 if let Some((start, end)) = self.selection_range() {
6361 let deleted = self.text[start..end].to_string();
6362 self.text.replace_range(start..end, "");
6363 self.cursor_pos = start;
6364 self.selection_anchor = None;
6365 return deleted;
6366 }
6367
6368 if backward && self.cursor_pos > 0 {
6369 let start = self.cursor_pos.saturating_sub(count);
6370 let deleted = self.text[start..self.cursor_pos].to_string();
6371 self.text.replace_range(start..self.cursor_pos, "");
6372 self.cursor_pos = start;
6373 deleted
6374 } else if !backward && self.cursor_pos < self.text.len() {
6375 let end = (self.cursor_pos + count).min(self.text.len());
6376 let deleted = self.text[self.cursor_pos..end].to_string();
6377 self.text.replace_range(self.cursor_pos..end, "");
6378 deleted
6379 } else {
6380 String::new()
6381 }
6382 }
6383
6384 pub fn move_cursor(&mut self, direction: TextDirection, extend_selection: bool) {
6386 if !extend_selection {
6387 self.selection_anchor = None;
6388 } else if self.selection_anchor.is_none() {
6389 self.selection_anchor = Some(self.cursor_pos);
6390 }
6391
6392 match direction {
6393 TextDirection::Forward if self.cursor_pos < self.text.len() => {
6394 let next = self.text[self.cursor_pos..]
6396 .char_indices()
6397 .nth(1)
6398 .map(|(i, _)| self.cursor_pos + i)
6399 .unwrap_or(self.text.len());
6400 self.cursor_pos = next;
6401 }
6402 TextDirection::Backward if self.cursor_pos > 0 => {
6403 let prev = self.text[..self.cursor_pos]
6404 .char_indices()
6405 .next_back()
6406 .map(|(i, _)| i)
6407 .unwrap_or(0);
6408 self.cursor_pos = prev;
6409 }
6410 TextDirection::LineStart => {
6411 self.cursor_pos = 0;
6412 }
6413 TextDirection::LineEnd => {
6414 self.cursor_pos = self.text.len();
6415 }
6416 TextDirection::WordForward => {
6417 let rest = &self.text[self.cursor_pos..];
6419 let after_word = rest
6421 .char_indices()
6422 .find(|(_, c)| !c.is_alphanumeric())
6423 .map(|(i, _)| i)
6424 .unwrap_or(rest.len());
6425 let after_space = rest[after_word..]
6427 .char_indices()
6428 .find(|(_, c)| !c.is_whitespace())
6429 .map(|(i, _)| after_word + i)
6430 .unwrap_or(rest.len());
6431 self.cursor_pos = (self.cursor_pos + after_space).min(self.text.len());
6432 }
6433 TextDirection::WordBackward => {
6434 let before = &self.text[..self.cursor_pos];
6435 let before_word = before
6437 .char_indices()
6438 .rev()
6439 .find(|(_, c)| !c.is_whitespace())
6440 .map(|(i, _)| i)
6441 .unwrap_or(0);
6442 let word_start = before[..before_word]
6444 .char_indices()
6445 .rev()
6446 .find(|(_, c)| !c.is_alphanumeric())
6447 .map(|(i, _)| i)
6448 .unwrap_or(0);
6449 self.cursor_pos = word_start;
6450 }
6451 _ => {} }
6453
6454 if !extend_selection {
6455 self.selection_anchor = None;
6456 }
6457 }
6458
6459 pub fn select_all(&mut self) {
6461 self.cursor_pos = self.text.len();
6462 self.selection_anchor = Some(0);
6463 }
6464
6465 pub fn cursor_byte_pos(&self) -> usize {
6467 self.cursor_pos
6468 }
6469}
6470
6471#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6473pub struct NotificationAction {
6474 pub id: String,
6476 pub title: String,
6478 pub is_destructive: bool,
6480}
6481
6482#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6484pub enum NotificationPriority {
6485 Passive,
6487 #[default]
6489 Active,
6490 TimeSensitive,
6492}
6493
6494#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
6496pub struct Notification {
6497 pub id: String,
6499 pub app_name: Option<String>,
6501 pub title: String,
6503 pub body: String,
6505 pub icon: Option<String>,
6507 pub sound: Option<String>,
6509 pub actions: Vec<NotificationAction>,
6511 pub timeout: Option<f32>,
6513 pub priority: NotificationPriority,
6515 pub timestamp: f32,
6517 pub dismissed: bool,
6519}
6520
6521#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, thiserror::Error)]
6523pub enum NotificationError {
6524 #[error("Notification permission denied")]
6526 PermissionDenied,
6527 #[error("Failed to post notification")]
6529 PostFailed,
6530}
6531
6532#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6534pub enum NotificationPermission {
6535 Granted,
6537 Denied,
6539 #[default]
6541 NotDetermined,
6542}
6543
6544pub trait NotificationHandler: Send + Sync {
6546 fn show(&self, notification: Notification) -> Result<(), NotificationError>;
6548 fn dismiss(&self, id: &str) -> Result<(), NotificationError>;
6550 fn request_permission(&self) -> NotificationPermission;
6552}
6553
6554static NEXT_NOTIFICATION_ID: std::sync::atomic::AtomicUsize =
6555 std::sync::atomic::AtomicUsize::new(1);
6556
6557#[derive(Clone, Copy, Debug, Default)]
6559pub struct DefaultNotificationHandler;
6560
6561impl NotificationHandler for DefaultNotificationHandler {
6562 fn show(&self, notification: Notification) -> Result<(), NotificationError> {
6564 let mut notif = notification;
6565 if notif.id.is_empty() {
6566 let id = NEXT_NOTIFICATION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6567 notif.id = format!("notif_{}", id);
6568 }
6569 update_system_state(|state| {
6570 let mut new_state = state.clone();
6571 new_state.notifications.push(notif.clone());
6572 new_state
6573 });
6574 Ok(())
6575 }
6576
6577 fn dismiss(&self, id: &str) -> Result<(), NotificationError> {
6579 update_system_state(|state| {
6580 let mut new_state = state.clone();
6581 for notif in &mut new_state.notifications {
6582 if notif.id == id {
6583 notif.dismissed = true;
6584 }
6585 }
6586 new_state
6587 });
6588 Ok(())
6589 }
6590
6591 fn request_permission(&self) -> NotificationPermission {
6593 NotificationPermission::Granted
6594 }
6595}
6596
6597static NOTIFICATION_HANDLER: once_cell::sync::OnceCell<std::sync::Arc<dyn NotificationHandler>> =
6598 once_cell::sync::OnceCell::new();
6599
6600pub fn set_notification_handler(handler: std::sync::Arc<dyn NotificationHandler>) {
6602 let _ = NOTIFICATION_HANDLER.set(handler);
6603}
6604
6605pub fn get_notification_handler() -> std::sync::Arc<dyn NotificationHandler> {
6607 NOTIFICATION_HANDLER
6608 .get_or_init(|| std::sync::Arc::new(DefaultNotificationHandler))
6609 .clone()
6610}
6611
6612#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6614pub struct FileFilter {
6615 pub name: String,
6617 pub extensions: Vec<String>,
6619}
6620
6621#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
6623pub enum FileDialogMode {
6624 #[default]
6626 OpenFile,
6627 OpenDirectory,
6629 SaveFile,
6631}
6632
6633#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6635pub struct FileDialog {
6636 pub title: String,
6638 pub default_path: Option<String>,
6640 pub filters: Vec<FileFilter>,
6642 pub mode: FileDialogMode,
6644 pub allow_multiple: bool,
6646}
6647
6648#[derive(Debug, thiserror::Error)]
6650pub enum FileDialogError {
6651 #[error("File dialog cancelled")]
6653 Cancelled,
6654 #[error("I/O error: {0}")]
6656 Io(#[from] std::io::Error),
6657 #[error("Platform error: {0}")]
6659 Platform(String),
6660}
6661
6662impl FileDialog {
6663 pub fn new(mode: FileDialogMode) -> Self {
6665 Self {
6666 mode,
6667 ..Default::default()
6668 }
6669 }
6670
6671 pub fn title(mut self, title: impl Into<String>) -> Self {
6673 self.title = title.into();
6674 self
6675 }
6676
6677 pub fn add_filter(mut self, name: &str, extensions: &[&str]) -> Self {
6679 self.filters.push(FileFilter {
6680 name: name.to_string(),
6681 extensions: extensions.iter().map(|s| s.to_string()).collect(),
6682 });
6683 self
6684 }
6685
6686 pub fn default_path(mut self, path: impl Into<String>) -> Self {
6688 self.default_path = Some(path.into());
6689 self
6690 }
6691
6692 pub fn allow_multiple(mut self, allow: bool) -> Self {
6694 self.allow_multiple = allow;
6695 self
6696 }
6697}
6698
6699#[cfg(not(target_arch = "wasm32"))]
6700impl FileDialog {
6701 pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6703 let mut dialog = rfd::FileDialog::new();
6704 dialog = dialog.set_title(&self.title);
6705 if let Some(path) = &self.default_path {
6706 dialog = dialog.set_directory(path);
6707 }
6708 for filter in &self.filters {
6709 let refs: Vec<&str> = filter.extensions.iter().map(|s| s.as_str()).collect();
6710 dialog = dialog.add_filter(&filter.name, &refs);
6711 }
6712
6713 match self.mode {
6714 FileDialogMode::OpenFile => {
6715 if self.allow_multiple {
6716 dialog.pick_files().ok_or(FileDialogError::Cancelled)
6717 } else {
6718 Ok(dialog.pick_file().into_iter().collect())
6719 }
6720 }
6721 FileDialogMode::OpenDirectory => Ok(dialog.pick_folder().into_iter().collect()),
6722 FileDialogMode::SaveFile => Ok(dialog.save_file().into_iter().collect()),
6723 }
6724 }
6725
6726 pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6728 let results = self.pick()?;
6729 Ok(results.into_iter().next())
6730 }
6731}
6732
6733#[cfg(target_arch = "wasm32")]
6734impl FileDialog {
6735 pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6737 Err(FileDialogError::Platform(
6738 "FileDialog is not supported synchronously on WebAssembly".to_string(),
6739 ))
6740 }
6741
6742 pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6744 Err(FileDialogError::Platform(
6745 "FileDialog is not supported synchronously on WebAssembly".to_string(),
6746 ))
6747 }
6748}
6749
6750#[derive(Debug, thiserror::Error)]
6752pub enum DocumentError {
6753 #[error("I/O error: {0}")]
6755 Io(#[from] std::io::Error),
6756 #[error("Parse error: {0}")]
6758 Parse(String),
6759 #[error("Serialization error: {0}")]
6761 Serialize(String),
6762}
6763
6764pub trait Document: Send + Sync {
6766 fn read_from(path: &std::path::Path) -> Result<Self, DocumentError>
6768 where
6769 Self: Sized;
6770
6771 fn write_to(&self, path: &std::path::Path) -> Result<(), DocumentError>;
6773
6774 fn is_dirty(&self) -> bool;
6776
6777 fn mark_clean(&mut self);
6779}
6780
6781pub struct AutoSaveManager {
6783 pub interval: f32,
6785 pub timer: f32,
6787 pub documents: Vec<(std::path::PathBuf, Box<dyn Document>)>,
6789}
6790
6791impl AutoSaveManager {
6792 pub fn new(interval: f32) -> Self {
6794 Self {
6795 interval,
6796 timer: 0.0,
6797 documents: Vec::new(),
6798 }
6799 }
6800
6801 pub fn register(&mut self, path: std::path::PathBuf, doc: Box<dyn Document>) {
6803 self.documents.push((path, doc));
6804 }
6805
6806 pub fn tick(&mut self, dt: f32) {
6808 self.timer += dt;
6809 if self.timer >= self.interval {
6810 self.timer = 0.0;
6811 for (path, doc) in &mut self.documents {
6812 if doc.is_dirty() {
6813 match doc.write_to(path) {
6814 Ok(()) => {
6815 doc.mark_clean();
6816 log::info!("[AutoSaveManager] Auto-saved document to {:?}", path);
6817 }
6818 Err(e) => {
6819 log::error!(
6820 "[AutoSaveManager] Failed to auto-save document to {:?}: {:?}",
6821 path,
6822 e
6823 );
6824 }
6825 }
6826 }
6827 }
6828 }
6829 }
6830}
6831
6832#[derive(Debug, Clone, PartialEq, Eq, Default)]
6840pub struct Modifiers {
6841 pub cmd: bool,
6843 pub shift: bool,
6845 pub alt: bool,
6847 pub ctrl: bool,
6849}
6850
6851#[derive(Debug, Clone)]
6853pub struct KeyboardShortcut {
6854 pub key: String,
6856 pub modifiers: Modifiers,
6858}
6859
6860impl KeyboardShortcut {
6861 pub fn cmd(key: impl Into<String>) -> Self {
6863 Self {
6864 key: key.into(),
6865 modifiers: Modifiers {
6866 cmd: true,
6867 ..Default::default()
6868 },
6869 }
6870 }
6871
6872 pub fn cmd_shift(key: impl Into<String>) -> Self {
6874 Self {
6875 key: key.into(),
6876 modifiers: Modifiers {
6877 cmd: true,
6878 shift: true,
6879 ..Default::default()
6880 },
6881 }
6882 }
6883}
6884
6885pub enum MenuItem {
6891 Action {
6893 label: String,
6894 shortcut: Option<KeyboardShortcut>,
6895 action: std::sync::Arc<dyn Fn() + Send + Sync>,
6896 enabled: bool,
6897 },
6898 Submenu { label: String, items: Vec<MenuItem> },
6900 Separator,
6902}
6903
6904impl std::fmt::Debug for MenuItem {
6905 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6906 match self {
6907 Self::Action { label, enabled, .. } => f
6908 .debug_struct("Action")
6909 .field("label", label)
6910 .field("enabled", enabled)
6911 .finish(),
6912 Self::Submenu { label, items } => f
6913 .debug_struct("Submenu")
6914 .field("label", label)
6915 .field("items", items)
6916 .finish(),
6917 Self::Separator => write!(f, "Separator"),
6918 }
6919 }
6920}
6921
6922pub struct MenuBar {
6927 pub items: Vec<MenuItem>,
6929}
6930
6931impl MenuBar {
6932 pub fn new() -> Self {
6934 Self { items: Vec::new() }
6935 }
6936
6937 pub fn add_item(&mut self, item: MenuItem) {
6939 self.items.push(item);
6940 }
6941
6942 #[allow(clippy::too_many_arguments)]
6954 pub fn standard(
6955 new_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6956 open_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6957 save_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6958 close_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6959 quit_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6960 undo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6961 redo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6962 cut_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6963 copy_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6964 paste_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6965 select_all_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6966 find_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6967 ) -> Self {
6968 let mut bar = Self::new();
6969
6970 bar.add_item(MenuItem::Submenu {
6972 label: "File".to_string(),
6973 items: vec![
6974 MenuItem::Action {
6975 label: "New".to_string(),
6976 shortcut: Some(KeyboardShortcut::cmd("n")),
6977 action: new_fn,
6978 enabled: true,
6979 },
6980 MenuItem::Action {
6981 label: "Open…".to_string(),
6982 shortcut: Some(KeyboardShortcut::cmd("o")),
6983 action: open_fn,
6984 enabled: true,
6985 },
6986 MenuItem::Separator,
6987 MenuItem::Action {
6988 label: "Save".to_string(),
6989 shortcut: Some(KeyboardShortcut::cmd("s")),
6990 action: save_fn,
6991 enabled: true,
6992 },
6993 MenuItem::Separator,
6994 MenuItem::Action {
6995 label: "Close".to_string(),
6996 shortcut: Some(KeyboardShortcut::cmd("w")),
6997 action: close_fn,
6998 enabled: true,
6999 },
7000 MenuItem::Separator,
7001 MenuItem::Action {
7002 label: "Quit".to_string(),
7003 shortcut: Some(KeyboardShortcut::cmd("q")),
7004 action: quit_fn,
7005 enabled: true,
7006 },
7007 ],
7008 });
7009
7010 bar.add_item(MenuItem::Submenu {
7012 label: "Edit".to_string(),
7013 items: vec![
7014 MenuItem::Action {
7015 label: "Undo".to_string(),
7016 shortcut: Some(KeyboardShortcut::cmd("z")),
7017 action: undo_fn,
7018 enabled: true,
7019 },
7020 MenuItem::Action {
7021 label: "Redo".to_string(),
7022 shortcut: Some(KeyboardShortcut::cmd_shift("z")),
7023 action: redo_fn,
7024 enabled: true,
7025 },
7026 MenuItem::Separator,
7027 MenuItem::Action {
7028 label: "Cut".to_string(),
7029 shortcut: Some(KeyboardShortcut::cmd("x")),
7030 action: cut_fn,
7031 enabled: true,
7032 },
7033 MenuItem::Action {
7034 label: "Copy".to_string(),
7035 shortcut: Some(KeyboardShortcut::cmd("c")),
7036 action: copy_fn,
7037 enabled: true,
7038 },
7039 MenuItem::Action {
7040 label: "Paste".to_string(),
7041 shortcut: Some(KeyboardShortcut::cmd("v")),
7042 action: paste_fn,
7043 enabled: true,
7044 },
7045 MenuItem::Separator,
7046 MenuItem::Action {
7047 label: "Select All".to_string(),
7048 shortcut: Some(KeyboardShortcut::cmd("a")),
7049 action: select_all_fn,
7050 enabled: true,
7051 },
7052 MenuItem::Separator,
7053 MenuItem::Action {
7054 label: "Find…".to_string(),
7055 shortcut: Some(KeyboardShortcut::cmd("f")),
7056 action: find_fn,
7057 enabled: true,
7058 },
7059 ],
7060 });
7061
7062 let noop: std::sync::Arc<dyn Fn() + Send + Sync> = std::sync::Arc::new(|| {});
7066 bar.add_item(MenuItem::Submenu {
7067 label: "View".to_string(),
7068 items: vec![
7069 MenuItem::Action {
7070 label: "Zoom In".to_string(),
7071 shortcut: Some(KeyboardShortcut::cmd("=")),
7072 action: noop.clone(),
7073 enabled: true,
7074 },
7075 MenuItem::Action {
7076 label: "Zoom Out".to_string(),
7077 shortcut: Some(KeyboardShortcut::cmd("-")),
7078 action: noop.clone(),
7079 enabled: true,
7080 },
7081 MenuItem::Separator,
7082 MenuItem::Action {
7083 label: "Toggle Fullscreen".to_string(),
7084 shortcut: Some(KeyboardShortcut {
7085 key: "f".to_string(),
7086 modifiers: Modifiers {
7087 ctrl: true,
7088 ..Default::default()
7089 },
7090 }),
7091 action: noop.clone(),
7092 enabled: true,
7093 },
7094 ],
7095 });
7096
7097 bar.add_item(MenuItem::Submenu {
7099 label: "Window".to_string(),
7100 items: vec![
7101 MenuItem::Action {
7102 label: "Minimize".to_string(),
7103 shortcut: Some(KeyboardShortcut::cmd("m")),
7104 action: noop.clone(),
7105 enabled: true,
7106 },
7107 MenuItem::Action {
7108 label: "Zoom".to_string(),
7109 shortcut: None,
7110 action: noop.clone(),
7111 enabled: true,
7112 },
7113 MenuItem::Separator,
7114 MenuItem::Action {
7115 label: "Bring All to Front".to_string(),
7116 shortcut: None,
7117 action: noop.clone(),
7118 enabled: true,
7119 },
7120 ],
7121 });
7122
7123 bar.add_item(MenuItem::Submenu {
7125 label: "Help".to_string(),
7126 items: vec![MenuItem::Action {
7127 label: "Search Help".to_string(),
7128 shortcut: None,
7129 action: noop,
7130 enabled: true,
7131 }],
7132 });
7133
7134 bar
7135 }
7136}
7137
7138impl Default for MenuBar {
7139 fn default() -> Self {
7140 Self::new()
7141 }
7142}
7143
7144use std::sync::RwLock;
7150
7151#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7153pub enum Direction {
7154 #[default]
7155 LTR,
7156 RTL,
7157 Auto,
7158}
7159
7160impl Direction {
7161 pub fn is_rtl(self) -> bool {
7162 matches!(self, Direction::RTL)
7163 }
7164}
7165#[derive(Clone, Debug)]
7166pub struct L10nBundle {
7167 pub locale: String,
7168 pub strings: HashMap<String, String>,
7169 pub is_rtl: bool,
7170}
7171
7172impl L10nBundle {
7173 pub fn new(locale: impl Into<String>) -> Self {
7174 Self {
7175 locale: locale.into(),
7176 strings: HashMap::new(),
7177 is_rtl: false,
7178 }
7179 }
7180
7181 pub fn add(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
7182 self.strings.insert(key.into(), value.into());
7183 self
7184 }
7185
7186 pub fn from_strings_format(locale: impl Into<String>, input: &str) -> Self {
7187 let mut bundle = Self::new(locale);
7188 for line in input.lines() {
7189 let line = line.trim();
7190 if line.is_empty() || line.starts_with("//") {
7191 continue;
7192 }
7193 if let Some(eq_pos) = line.find(" = ") {
7194 let key = line[..eq_pos].trim_matches('"').to_string();
7195 let val = line[eq_pos + 3..]
7196 .trim_end_matches(';')
7197 .trim_matches('"')
7198 .to_string();
7199 bundle.strings.insert(key, val);
7200 }
7201 }
7202 bundle
7203 }
7204 pub fn t(&self, key: &str) -> String {
7206 self.strings
7207 .get(key)
7208 .map(|s| s.to_string())
7209 .unwrap_or_else(|| key.to_string())
7210 }
7211
7212 pub fn tf(&self, key: &str, args: &[&str]) -> String {
7214 let mut result = self.t(key);
7215 for (i, arg) in args.iter().enumerate() {
7216 result = result.replace(&format!("{{{}}}", i), arg);
7217 }
7218 result
7219 }
7220}
7221
7222pub struct L10n {
7224 bundles: HashMap<String, L10nBundle>,
7225 current: String,
7226}
7227
7228impl L10n {
7229 pub fn new(default_locale: &str) -> Self {
7230 Self {
7231 bundles: HashMap::new(),
7232 current: default_locale.to_string(),
7233 }
7234 }
7235
7236 pub fn add_bundle(&mut self, bundle: L10nBundle) {
7237 self.bundles.insert(bundle.locale.clone(), bundle);
7238 }
7239
7240 pub fn set_locale(&mut self, locale: &str) {
7241 self.current = locale.to_string();
7242 }
7243 pub fn current_locale(&self) -> &str {
7244 &self.current
7245 }
7246
7247 pub fn is_rtl(&self) -> bool {
7248 self.bundles
7249 .get(self.current.as_str())
7250 .map(|b| b.is_rtl)
7251 .unwrap_or(false)
7252 }
7253
7254 pub fn t(&self, key: &str) -> String {
7255 self.bundles
7256 .get(self.current.as_str())
7257 .map(|b| b.t(key))
7258 .unwrap_or_else(|| key.to_string())
7259 }
7260
7261 pub fn tf(&self, key: &str, args: &[&str]) -> String {
7262 let mut result = self.t(key);
7263 for (i, arg) in args.iter().enumerate() {
7264 result = result.replace(&format!("{{{}}}", i), arg);
7265 }
7266 result
7267 }
7268
7269 pub fn direction(&self) -> Direction {
7270 if self.is_rtl() {
7271 Direction::RTL
7272 } else {
7273 Direction::LTR
7274 }
7275 }
7276}
7277
7278static L10N: once_cell::sync::Lazy<Arc<RwLock<L10n>>> =
7279 once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(L10n::new("en"))));
7280
7281pub fn init_l10n(l10n: L10n) {
7282 if let Ok(mut guard) = L10N.write() {
7283 *guard = l10n;
7284 }
7285}
7286
7287pub fn l10n() -> Arc<RwLock<L10n>> {
7288 L10N.clone()
7289}
7290
7291pub fn t(key: &str) -> String {
7292 L10N.read()
7293 .map(|g| g.t(key).to_string())
7294 .unwrap_or_else(|_| key.to_string())
7295}
7296
7297pub fn tf(key: &str, args: &[&str]) -> String {
7298 L10N.read()
7299 .map(|g| g.tf(key, args))
7300 .unwrap_or_else(|_| key.to_string())
7301}
7302
7303pub fn set_locale(locale: &str) {
7304 if let Ok(mut guard) = L10N.write() {
7305 guard.set_locale(locale);
7306 }
7307}
7308
7309pub fn current_locale() -> String {
7310 L10N.read()
7311 .map(|g| g.current_locale().to_string())
7312 .unwrap_or_else(|_| "en".to_string())
7313}
7314
7315pub fn is_rtl() -> bool {
7316 L10N.read().map(|g| g.is_rtl()).unwrap_or(false)
7317}
7318
7319#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7331pub enum SystemTheme {
7332 #[default]
7334 Dark,
7335 Light,
7337}
7338
7339pub fn detect_system_theme() -> SystemTheme {
7349 std::env::var("CVKG_THEME")
7350 .ok()
7351 .and_then(|v| match v.as_str() {
7352 "light" => Some(SystemTheme::Light),
7353 "dark" => Some(SystemTheme::Dark),
7354 _ => None,
7355 })
7356 .unwrap_or(SystemTheme::Dark)
7357}
7358
7359pub mod audio_haptic;
7365pub use audio_haptic::{
7366 AudioEngine, HapticEngine, HapticIntensity, NullAudioEngine, NullHapticEngine, haptic_error,
7367 haptic_impact, haptic_selection, haptic_success, play_sound, set_audio_engine,
7368 set_haptic_engine, sounds,
7369};