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, Copy, PartialEq, Eq)]
209pub enum AnnouncementPriority {
210 Polite,
212 Assertive,
214}
215#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct TemporalNode {
217 pub id: String,
219 pub fragment_id: KnowledgeId,
221 pub timestamp: u64,
223 pub layer: MemoryLayer,
225 pub weight: f32,
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct TemporalEdge {
232 pub source: String,
234 pub target: String,
236 pub relation: String,
238 pub weight: f32,
240}
241
242pub struct UndoGroup {
244 pub label: String,
246 pub timestamp: f32,
248 pub undo: Arc<dyn Fn() + Send + Sync>,
250 pub redo: Arc<dyn Fn() + Send + Sync>,
252}
253
254impl Clone for UndoGroup {
255 fn clone(&self) -> Self {
257 Self {
258 label: self.label.clone(),
259 timestamp: self.timestamp,
260 undo: Arc::clone(&self.undo),
261 redo: Arc::clone(&self.redo),
262 }
263 }
264}
265
266impl std::fmt::Debug for UndoGroup {
267 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269 f.debug_struct("UndoGroup")
270 .field("label", &self.label)
271 .field("timestamp", &self.timestamp)
272 .finish()
273 }
274}
275
276pub struct UndoManager {
279 stack: Vec<UndoGroup>,
281 position: usize,
283 max_depth: usize,
285 coalesce_window: f32,
287}
288
289impl Default for UndoManager {
290 fn default() -> Self {
292 Self {
293 stack: Vec::new(),
294 position: 0,
295 max_depth: 100,
296 coalesce_window: 0.5,
297 }
298 }
299}
300
301impl Clone for UndoManager {
302 fn clone(&self) -> Self {
304 Self {
305 stack: self.stack.clone(),
306 position: self.position,
307 max_depth: self.max_depth,
308 coalesce_window: self.coalesce_window,
309 }
310 }
311}
312
313impl std::fmt::Debug for UndoManager {
314 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316 f.debug_struct("UndoManager")
317 .field("stack_len", &self.stack.len())
318 .field("position", &self.position)
319 .field("max_depth", &self.max_depth)
320 .field("coalesce_window", &self.coalesce_window)
321 .finish()
322 }
323}
324
325impl UndoManager {
326 pub fn new(max_depth: usize, coalesce_window: f32) -> Self {
328 Self {
329 stack: Vec::new(),
330 position: 0,
331 max_depth,
332 coalesce_window,
333 }
334 }
335
336 pub fn push(
338 &mut self,
339 label: &str,
340 undo: impl Fn() + Send + Sync + 'static,
341 redo: impl Fn() + Send + Sync + 'static,
342 ) {
343 if self.position < self.stack.len() {
344 self.stack.truncate(self.position);
345 }
346
347 let timestamp = std::time::SystemTime::now()
348 .duration_since(std::time::UNIX_EPOCH)
349 .unwrap_or_default()
350 .as_secs_f32();
351
352 self.stack.push(UndoGroup {
353 label: label.to_string(),
354 timestamp,
355 undo: Arc::new(undo),
356 redo: Arc::new(redo),
357 });
358
359 if self.stack.len() > self.max_depth {
360 self.stack.remove(0);
361 }
362 self.position = self.stack.len();
363 }
364
365 pub fn undo(&mut self) -> Option<Arc<dyn Fn() + Send + Sync>> {
368 if self.can_undo() {
369 self.position -= 1;
370 Some(Arc::clone(&self.stack[self.position].undo))
371 } else {
372 None
373 }
374 }
375
376 pub fn redo(&mut self) -> Option<Arc<dyn Fn() + Send + Sync>> {
379 if self.can_redo() {
380 let group = &self.stack[self.position];
381 self.position += 1;
382 Some(Arc::clone(&group.redo))
383 } else {
384 None
385 }
386 }
387
388 pub fn can_undo(&self) -> bool {
390 self.position > 0
391 }
392
393 pub fn can_redo(&self) -> bool {
395 self.position < self.stack.len()
396 }
397
398 pub fn clear(&mut self) {
400 self.stack.clear();
401 self.position = 0;
402 }
403
404 pub fn push_coalesceable(
408 &mut self,
409 label: &str,
410 undo: impl Fn() + Send + Sync + 'static,
411 redo: impl Fn() + Send + Sync + 'static,
412 ) {
413 let now = std::time::SystemTime::now()
414 .duration_since(std::time::UNIX_EPOCH)
415 .unwrap_or_default()
416 .as_secs_f32();
417
418 if self.position == self.stack.len() && !self.stack.is_empty() {
419 let last_idx = self.stack.len() - 1;
420 let last = &self.stack[last_idx];
421 if last.label == label && (now - last.timestamp).abs() <= self.coalesce_window {
422 let old_undo = Arc::clone(&last.undo);
423 let old_redo = Arc::clone(&last.redo);
424 let new_undo = Arc::new(undo);
425 let new_redo = Arc::new(redo);
426
427 self.stack[last_idx].undo = Arc::new(move || {
428 new_undo();
429 old_undo();
430 });
431 self.stack[last_idx].redo = Arc::new(move || {
432 old_redo();
433 new_redo();
434 });
435 self.stack[last_idx].timestamp = now;
436 return;
437 }
438 }
439
440 self.push(label, undo, redo);
441 }
442}
443
444#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
446pub struct WindowId(pub u64);
447
448#[derive(
450 Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default,
451)]
452pub enum WindowLevel {
453 #[default]
455 Normal,
456 AlwaysOnTop,
458 PopUpMenu,
460}
461
462#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
464pub struct WindowConfig {
465 pub title: String,
467 pub size: (f32, f32),
469 pub min_size: Option<(f32, f32)>,
471 pub max_size: Option<(f32, f32)>,
473 pub resizable: bool,
475 pub transparent: bool,
477 pub decorations: bool,
479 pub level: WindowLevel,
481}
482
483impl Default for WindowConfig {
484 fn default() -> Self {
486 Self {
487 title: "CVKG Window".to_string(),
488 size: (800.0, 600.0),
489 min_size: None,
490 max_size: None,
491 resizable: true,
492 transparent: false,
493 decorations: true,
494 level: WindowLevel::Normal,
495 }
496 }
497}
498
499pub trait Window: Send + Sync {
502 fn close(&self);
504 fn set_title(&self, title: &str);
506 fn set_size(&self, width: f32, height: f32);
508 fn is_key(&self) -> bool;
510 fn is_main(&self) -> bool;
512 fn is_visible(&self) -> bool;
514 fn set_visible(&self, visible: bool);
516 fn bring_to_front(&self);
518}
519
520#[derive(Clone)]
522pub struct WindowHandle {
523 pub id: WindowId,
525 pub inner: Arc<dyn Window>,
527}
528
529impl std::fmt::Debug for WindowHandle {
530 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
531 f.debug_struct("WindowHandle")
532 .field("id", &self.id)
533 .finish()
534 }
535}
536
537impl WindowHandle {
538 pub fn new(id: WindowId, inner: Arc<dyn Window>) -> Self {
540 Self { id, inner }
541 }
542 pub fn close(self) {
544 self.inner.close();
545 }
546 pub fn set_title(&self, title: &str) {
548 self.inner.set_title(title);
549 }
550 pub fn set_size(&self, width: f32, height: f32) {
552 self.inner.set_size(width, height);
553 }
554 pub fn is_key(&self) -> bool {
556 self.inner.is_key()
557 }
558 pub fn is_main(&self) -> bool {
560 self.inner.is_main()
561 }
562 pub fn is_visible(&self) -> bool {
564 self.inner.is_visible()
565 }
566 pub fn set_visible(&self, visible: bool) {
568 self.inner.set_visible(visible);
569 }
570 pub fn bring_to_front(&self) {
572 self.inner.bring_to_front();
573 }
574}
575
576#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
578pub enum WindowCloseAction {
579 Allow,
581 Confirm,
583 Deny,
585}
586
587#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
588pub struct AssetKey(pub String);
589
590impl EnvKey for AssetKey {
591 type Value = Arc<dyn AssetManager>;
592 fn default_value() -> Self::Value {
593 Arc::new(DefaultAssetManager::new())
594 }
595}
596
597#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
599pub enum AssetState<T> {
600 Loading,
601 Ready(T),
602 Error(String),
603}
604
605#[derive(Debug, Clone, Serialize, Deserialize)]
607#[serde(untagged)]
608pub enum TokenValue {
609 Single { value: String },
611 Adaptive { light: String, dark: String },
613}
614
615#[derive(Debug, Clone, Serialize, Deserialize)]
617pub struct YggdrasilTokens {
618 pub color: HashMap<String, TokenValue>,
619 pub font: HashMap<String, TokenValue>,
620 pub spacing: HashMap<String, TokenValue>,
621 pub radius: HashMap<String, TokenValue>,
622 pub shadow: HashMap<String, TokenValue>,
623 pub border: HashMap<String, TokenValue>,
624 pub anim: HashMap<String, TokenValue>,
625 pub bifrost: HashMap<String, TokenValue>,
626 pub gungnir: HashMap<String, TokenValue>,
627 pub mjolnir: HashMap<String, TokenValue>,
628 pub accessibility: HashMap<String, TokenValue>,
629}
630
631impl Default for YggdrasilTokens {
632 fn default() -> Self {
633 Self::new()
634 }
635}
636
637impl YggdrasilTokens {
638 pub fn new() -> Self {
639 Self {
640 color: HashMap::new(),
641 font: HashMap::new(),
642 spacing: HashMap::new(),
643 radius: HashMap::new(),
644 shadow: HashMap::new(),
645 border: HashMap::new(),
646 anim: HashMap::new(),
647 bifrost: HashMap::new(),
648 gungnir: HashMap::new(),
649 mjolnir: HashMap::new(),
650 accessibility: HashMap::new(),
651 }
652 }
653
654 pub fn get_color(&self, key: &str, is_dark: bool) -> Option<String> {
656 self.color.get(key).map(|token| match token {
657 TokenValue::Single { value } => value.clone(),
658 TokenValue::Adaptive { light, dark } => {
659 if is_dark {
660 dark.clone()
661 } else {
662 light.clone()
663 }
664 }
665 })
666 }
667
668 pub fn get<T: FromStr>(&self, category: &str, key: &str, is_dark: bool) -> Option<T> {
670 let map = match category {
671 "color" => &self.color,
672 "font" => &self.font,
673 "spacing" => &self.spacing,
674 "radius" => &self.radius,
675 "shadow" => &self.shadow,
676 "border" => &self.border,
677 "anim" => &self.anim,
678 "bifrost" => &self.bifrost,
679 "gungnir" => &self.gungnir,
680 "mjolnir" => &self.mjolnir,
681 "accessibility" => &self.accessibility,
682 _ => return None,
683 };
684
685 map.get(key).and_then(|token| match token {
686 TokenValue::Single { value } => value.parse().ok(),
687 TokenValue::Adaptive { light, dark } => {
688 let value = if is_dark { dark } else { light };
689 value.parse().ok()
690 }
691 })
692 }
693}
694
695pub trait View: Sized + Send {
696 type Body: View;
699
700 fn body(self) -> Self::Body;
701
702 fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
705
706 fn intrinsic_size(&self, _renderer: &mut dyn Renderer, _proposal: SizeProposal) -> Size {
709 Size::ZERO
710 }
711
712 fn layout(&self) -> Option<&dyn layout::LayoutView> {
714 None
715 }
716
717 fn flex_weight(&self) -> f32 {
719 0.0
720 }
721
722 fn get_grid_placement(&self) -> Option<GridPlacement> {
724 None
725 }
726
727 fn modifier<M: ViewModifier>(self, m: M) -> ModifiedView<Self, M> {
729 ModifiedView::new(self, m)
730 }
731
732 fn bifrost(
734 self,
735 blur: f32,
736 saturation: f32,
737 opacity: f32,
738 ) -> ModifiedView<Self, BifrostModifier> {
739 self.modifier(BifrostModifier {
740 blur,
741 saturation,
742 opacity,
743 })
744 }
745
746 fn gungnir(
748 self,
749 color: impl Into<String>,
750 radius: f32,
751 intensity: f32,
752 ) -> ModifiedView<Self, GungnirModifier> {
753 self.modifier(GungnirModifier {
754 color: color.into(),
755 radius,
756 intensity,
757 })
758 }
759
760 fn mjolnir_slice(self, angle: f32, offset: f32) -> ModifiedView<Self, MjolnirSliceModifier> {
762 self.modifier(MjolnirSliceModifier { angle, offset })
763 }
764
765 fn mjolnir_shatter(
767 self,
768 pieces: u32,
769 force: f32,
770 ) -> ModifiedView<Self, MjolnirShatterModifier> {
771 self.modifier(MjolnirShatterModifier { pieces, force })
772 }
773
774 fn bifrost_bridge(self, id: impl Into<String>) -> ModifiedView<Self, BifrostBridgeModifier> {
776 self.modifier(BifrostBridgeModifier { id: id.into() })
777 }
778
779 fn background(self, color: [f32; 4]) -> ModifiedView<Self, BackgroundModifier> {
781 self.modifier(BackgroundModifier { color })
782 }
783
784 fn padding(self, amount: f32) -> ModifiedView<Self, PaddingModifier> {
786 self.modifier(PaddingModifier { amount })
787 }
788
789 fn opacity(self, opacity: f32) -> ModifiedView<Self, OpacityModifier> {
791 self.modifier(OpacityModifier {
792 opacity: opacity.clamp(0.0, 1.0),
793 })
794 }
795
796 fn foreground_color(self, color: [f32; 4]) -> ModifiedView<Self, ForegroundColorModifier> {
798 self.modifier(ForegroundColorModifier { color })
799 }
800
801 fn frame(self, width: Option<f32>, height: Option<f32>) -> ModifiedView<Self, FrameModifier> {
804 self.modifier(FrameModifier {
805 width,
806 height,
807 min_width: None,
808 max_width: None,
809 min_height: None,
810 max_height: None,
811 alignment: Alignment::Center,
812 })
813 }
814
815 fn flex(self, weight: f32) -> ModifiedView<Self, FlexModifier> {
817 self.modifier(FlexModifier { weight })
818 }
819
820 fn grid_placement(self, placement: GridPlacement) -> ModifiedView<Self, GridPlacementModifier> {
822 self.modifier(GridPlacementModifier { placement })
823 }
824
825 fn overlay<O: View + Clone + 'static>(
827 self,
828 overlay: O,
829 alignment: Alignment,
830 offset: [f32; 2],
831 on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
832 ) -> ModifiedView<Self, OverlayModifier> {
833 self.modifier(OverlayModifier {
834 overlay: overlay.erase(),
835 alignment,
836 offset,
837 on_dismiss,
838 })
839 }
840
841 fn safe_area_padding(self) -> ModifiedView<Self, SafeAreaModifier> {
843 self.modifier(SafeAreaModifier { ignores: false })
844 }
845
846 fn ignores_safe_area(self) -> ModifiedView<Self, SafeAreaModifier> {
848 self.modifier(SafeAreaModifier { ignores: true })
849 }
850
851 fn clip_to_bounds(self) -> ModifiedView<Self, ClipModifier> {
853 self.modifier(ClipModifier)
854 }
855
856 fn border(self, color: [f32; 4], width: f32) -> ModifiedView<Self, BorderModifier> {
858 self.modifier(BorderModifier { color, width })
859 }
860
861 fn elevation(self, level: f32) -> ModifiedView<Self, ElevationModifier> {
863 self.modifier(ElevationModifier { level })
864 }
865
866 fn magnetic(self, radius: f32, intensity: f32) -> ModifiedView<Self, MagneticModifier> {
868 self.modifier(MagneticModifier { radius, intensity })
869 }
870
871 fn mani_glow(self, color: [f32; 4], radius: f32) -> ModifiedView<Self, ManiGlowModifier> {
873 self.modifier(ManiGlowModifier { color, radius })
874 }
875
876 fn memory_layer(self, layer: MemoryLayer) -> ModifiedView<Self, BifrostLayerModifier> {
878 self.modifier(BifrostLayerModifier { layer })
879 }
880
881 fn fafnir_evolve(self, id: u64) -> ModifiedView<Self, FafnirModifier> {
883 self.modifier(FafnirModifier { id })
884 }
885
886 fn mimir_intent(self) -> ModifiedView<Self, MimirIntentModifier> {
888 self.modifier(MimirIntentModifier)
889 }
890
891 fn kvasir_vibes(self, complexity: f32) -> ModifiedView<Self, KvasirVibeModifier> {
893 self.modifier(KvasirVibeModifier { complexity })
894 }
895
896 fn odins_eye(self) -> ModifiedView<Self, OdinsEyeModifier> {
898 self.modifier(OdinsEyeModifier)
899 }
900
901 fn on_appear<F: Fn() + Send + Sync + 'static>(
903 self,
904 action: F,
905 ) -> ModifiedView<Self, LifecycleModifier> {
906 self.modifier(LifecycleModifier {
907 on_appear: Some(Arc::new(action)),
908 on_disappear: None,
909 })
910 }
911
912 fn on_disappear<F: Fn() + Send + Sync + 'static>(
914 self,
915 action: F,
916 ) -> ModifiedView<Self, LifecycleModifier> {
917 self.modifier(LifecycleModifier {
918 on_appear: None,
919 on_disappear: Some(Arc::new(action)),
920 })
921 }
922
923 fn on_click<F: Fn() + Send + Sync + 'static>(
925 self,
926 action: F,
927 ) -> ModifiedView<Self, OnClickModifier> {
928 self.modifier(OnClickModifier {
929 action: Arc::new(action),
930 })
931 }
932
933 fn on_pointer_enter<F: Fn() + Send + Sync + 'static>(
935 self,
936 action: F,
937 ) -> ModifiedView<Self, OnPointerEnterModifier> {
938 self.modifier(OnPointerEnterModifier {
939 action: Arc::new(action),
940 })
941 }
942
943 fn on_pointer_leave<F: Fn() + Send + Sync + 'static>(
945 self,
946 action: F,
947 ) -> ModifiedView<Self, OnPointerLeaveModifier> {
948 self.modifier(OnPointerLeaveModifier {
949 action: Arc::new(action),
950 })
951 }
952
953 fn on_pointer_move<F: Fn(f32, f32) + Send + Sync + 'static>(
955 self,
956 action: F,
957 ) -> ModifiedView<Self, OnPointerMoveModifier> {
958 self.modifier(OnPointerMoveModifier {
959 action: Arc::new(action),
960 })
961 }
962
963 fn on_pointer_down<F: Fn() + Send + Sync + 'static>(
965 self,
966 action: F,
967 ) -> ModifiedView<Self, OnPointerDownModifier> {
968 self.modifier(OnPointerDownModifier {
969 action: Arc::new(action),
970 })
971 }
972
973 fn on_pointer_up<F: Fn() + Send + Sync + 'static>(
975 self,
976 action: F,
977 ) -> ModifiedView<Self, OnPointerUpModifier> {
978 self.modifier(OnPointerUpModifier {
979 action: Arc::new(action),
980 })
981 }
982
983 fn erase(self) -> AnyView
985 where
986 Self: Clone + 'static,
987 {
988 AnyView::new(self)
989 }
990
991 fn aria_properties(&self) -> Option<AriaProperties> {
999 None
1000 }
1001
1002 fn on_key_event(&self, _key: &str, _modifiers: KeyModifiers) -> bool {
1005 false
1006 }
1007
1008 fn key_shortcuts(&self) -> Vec<KeyShortcut> {
1010 vec![]
1011 }
1012}
1013
1014#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1020pub enum AriaRole {
1021 Alert,
1022 Alertdialog,
1023 Article,
1024 Banner,
1025 Button,
1026 Checkbox,
1027 Columnheader,
1028 Combobox,
1029 Complementary,
1030 Contentinfo,
1031 Dialog,
1032 Form,
1033 Grid,
1034 Gridcell,
1035 Heading,
1036 Img,
1037 Link,
1038 List,
1039 Listbox,
1040 Listitem,
1041 Main,
1042 Menu,
1043 Menubar,
1044 Menuitem,
1045 Menuitemcheckbox,
1046 Menuitemradio,
1047 Navigation,
1048 None,
1049 Note,
1050 Option,
1051 Presentation,
1052 Progressbar,
1053 Radio,
1054 Radiogroup,
1055 Region,
1056 Row,
1057 Rowgroup,
1058 Rowheader,
1059 Search,
1060 Separator,
1061 Slider,
1062 Spinbutton,
1063 Status,
1064 Switch,
1065 Tab,
1066 Table,
1067 Tablist,
1068 Tabpanel,
1069 Textbox,
1070 Toolbar,
1071 Tooltip,
1072 Tree,
1073 Treeitem,
1074}
1075
1076#[derive(Debug, Clone, Serialize, Deserialize)]
1078pub struct AriaProperties {
1079 pub role: AriaRole,
1080 pub label: String,
1081 pub description: Option<String>,
1082 pub value: Option<String>,
1083 pub pressed: Option<bool>,
1084 pub checked: Option<bool>,
1085 pub expanded: Option<bool>,
1086 pub disabled: bool,
1087 pub hidden: bool,
1088 pub level: Option<u8>,
1089 pub shortcut: Option<String>,
1090 pub focused: bool,
1091 pub live: Option<String>,
1092 pub atomic: bool,
1093}
1094
1095impl AriaProperties {
1096 pub fn new(role: AriaRole, label: impl Into<String>) -> Self {
1097 Self {
1098 role,
1099 label: label.into(),
1100 description: None,
1101 value: None,
1102 pressed: None,
1103 checked: None,
1104 expanded: None,
1105 disabled: false,
1106 hidden: false,
1107 level: None,
1108 shortcut: None,
1109 focused: false,
1110 live: None,
1111 atomic: false,
1112 }
1113 }
1114
1115 pub fn description(mut self, d: impl Into<String>) -> Self {
1116 self.description = Some(d.into());
1117 self
1118 }
1119 pub fn value(mut self, v: impl Into<String>) -> Self {
1120 self.value = Some(v.into());
1121 self
1122 }
1123 pub fn checked(mut self, c: bool) -> Self {
1124 self.checked = Some(c);
1125 self
1126 }
1127 pub fn disabled(mut self, d: bool) -> Self {
1128 self.disabled = d;
1129 self
1130 }
1131 pub fn expanded(mut self, e: bool) -> Self {
1132 self.expanded = Some(e);
1133 self
1134 }
1135 pub fn level(mut self, l: u8) -> Self {
1136 self.level = Some(l.clamp(1, 6));
1137 self
1138 }
1139 pub fn shortcut(mut self, s: impl Into<String>) -> Self {
1140 self.shortcut = Some(s.into());
1141 self
1142 }
1143 pub fn focused(mut self, f: bool) -> Self {
1144 self.focused = f;
1145 self
1146 }
1147}
1148
1149#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
1155pub struct KeyModifiers {
1156 pub shift: bool,
1157 pub ctrl: bool,
1158 pub alt: bool,
1159 pub meta: bool,
1160}
1161
1162#[derive(Debug, Clone, Serialize, Deserialize)]
1164pub struct KeyShortcut {
1165 pub key: String,
1166 pub modifiers: KeyModifiers,
1167 pub description: String,
1168}
1169
1170impl KeyShortcut {
1171 pub fn new(key: impl Into<String>, desc: impl Into<String>) -> Self {
1172 Self {
1173 key: key.into(),
1174 modifiers: KeyModifiers::default(),
1175 description: desc.into(),
1176 }
1177 }
1178 pub fn with_ctrl(mut self) -> Self {
1179 self.modifiers.ctrl = true;
1180 self
1181 }
1182 pub fn with_shift(mut self) -> Self {
1183 self.modifiers.shift = true;
1184 self
1185 }
1186 pub fn with_alt(mut self) -> Self {
1187 self.modifiers.alt = true;
1188 self
1189 }
1190 pub fn with_meta(mut self) -> Self {
1191 self.modifiers.meta = true;
1192 self
1193 }
1194}
1195
1196#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1202pub struct FocusableId(String);
1203
1204impl From<&str> for FocusableId {
1205 fn from(s: &str) -> Self {
1206 Self(s.to_string())
1207 }
1208}
1209impl From<String> for FocusableId {
1210 fn from(s: String) -> Self {
1211 Self(s)
1212 }
1213}
1214
1215#[derive(Debug, Clone)]
1217pub struct FocusTrap {
1218 pub id: FocusableId,
1219 pub order: Vec<FocusableId>,
1220 pub wrap: bool,
1221}
1222
1223impl FocusTrap {
1224 pub fn new(id: impl Into<FocusableId>, order: Vec<FocusableId>) -> Self {
1225 Self {
1226 id: id.into(),
1227 order,
1228 wrap: true,
1229 }
1230 }
1231}
1232
1233#[derive(Debug, Default)]
1235pub struct FocusManager {
1236 order: Vec<FocusableId>,
1237 focused: Option<FocusableId>,
1238 traps: Vec<FocusTrap>,
1239}
1240
1241impl FocusManager {
1242 pub fn new() -> Self {
1243 Self::default()
1244 }
1245
1246 pub fn register(&mut self, id: impl Into<FocusableId>) {
1247 let id = id.into();
1248 if !self.order.contains(&id) {
1249 self.order.push(id);
1250 }
1251 }
1252
1253 pub fn unregister(&mut self, id: &FocusableId) {
1254 self.order.retain(|x| x != id);
1255 if self.focused.as_ref() == Some(id) {
1256 self.focused = None;
1257 }
1258 }
1259
1260 pub fn focused(&self) -> Option<&FocusableId> {
1261 self.focused.as_ref()
1262 }
1263
1264 pub fn focus(&mut self, id: impl Into<FocusableId>) -> bool {
1265 let id = id.into();
1266 if self.order.contains(&id) || self.traps.iter().any(|t| t.order.contains(&id)) {
1267 self.focused = Some(id);
1268 true
1269 } else {
1270 false
1271 }
1272 }
1273
1274 pub fn focus_next(&mut self) -> Option<&FocusableId> {
1275 let order = self.effective_order();
1276 if order.is_empty() {
1277 return None;
1278 }
1279 let idx = self
1280 .focused
1281 .as_ref()
1282 .and_then(|f| order.iter().position(|x| x == f));
1283 let next = match idx {
1284 Some(i) if i + 1 < order.len() => &order[i + 1],
1285 _ => &order[0],
1286 };
1287 self.focused = Some(next.clone());
1288 self.focused.as_ref()
1289 }
1290
1291 pub fn focus_prev(&mut self) -> Option<&FocusableId> {
1292 let order = self.effective_order();
1293 if order.is_empty() {
1294 return None;
1295 }
1296 let idx = self
1297 .focused
1298 .as_ref()
1299 .and_then(|f| order.iter().position(|x| x == f));
1300 let prev = match idx {
1301 Some(i) if i > 0 => &order[i - 1],
1302 _ => &order[order.len() - 1],
1303 };
1304 self.focused = Some(prev.clone());
1305 self.focused.as_ref()
1306 }
1307
1308 pub fn push_trap(&mut self, trap: FocusTrap) -> FocusableId {
1309 let id = trap.id.clone();
1310 self.traps.push(trap);
1311 id
1312 }
1313
1314 pub fn pop_trap(&mut self) {
1315 self.traps.pop();
1316 }
1317 pub fn trap_count(&self) -> usize {
1318 self.traps.len()
1319 }
1320
1321 fn effective_order(&self) -> &[FocusableId] {
1322 self.traps
1323 .last()
1324 .map(|t| t.order.as_slice())
1325 .unwrap_or(&self.order)
1326 }
1327}
1328
1329pub fn is_reduced_motion() -> bool {
1335 std::env::var("GTK_THEME")
1336 .map(|v| v.to_lowercase().contains("reduced"))
1337 .unwrap_or(false)
1338 || std::env::var("NO_ANIMATIONS")
1339 .map(|v| v == "1" || v.to_lowercase() == "true")
1340 .unwrap_or(false)
1341 || std::env::var("ACCESSIBILITY_REDUCED_MOTION")
1342 .map(|v| v == "1" || v.to_lowercase() == "true")
1343 .unwrap_or(false)
1344}
1345
1346pub fn effective_duration(secs: f32) -> f32 {
1348 if is_reduced_motion() { 0.0 } else { secs }
1349}
1350
1351pub trait ErasedView: Send {
1353 fn render_erased(&self, renderer: &mut dyn Renderer, rect: Rect);
1354 fn name(&self) -> &'static str;
1355 fn flex_weight_erased(&self) -> f32;
1356 fn layout_erased(&self) -> Option<&dyn layout::LayoutView>;
1357 fn grid_placement_erased(&self) -> Option<GridPlacement>;
1358 fn clone_box(&self) -> Box<dyn ErasedView>;
1359}
1360
1361impl<V: View + Clone + 'static> ErasedView for V {
1362 fn render_erased(&self, renderer: &mut dyn Renderer, rect: Rect) {
1363 self.render(renderer, rect);
1364 }
1365
1366 fn name(&self) -> &'static str {
1367 std::any::type_name::<V>()
1368 }
1369
1370 fn flex_weight_erased(&self) -> f32 {
1371 self.flex_weight()
1372 }
1373
1374 fn layout_erased(&self) -> Option<&dyn layout::LayoutView> {
1375 self.layout()
1376 }
1377
1378 fn grid_placement_erased(&self) -> Option<GridPlacement> {
1379 self.get_grid_placement()
1380 }
1381
1382 fn clone_box(&self) -> Box<dyn ErasedView> {
1383 Box::new(self.clone())
1384 }
1385}
1386
1387pub struct MemoView<V, F> {
1390 id: u64,
1391 data_hash: u64,
1392 builder: F,
1393 _v: std::marker::PhantomData<V>,
1394}
1395
1396impl<V: View, F: Fn() -> V + Send + Sync> MemoView<V, F> {
1397 pub fn new(id: u64, data_hash: u64, builder: F) -> Self {
1399 Self {
1400 id,
1401 data_hash,
1402 builder,
1403 _v: std::marker::PhantomData,
1404 }
1405 }
1406}
1407
1408impl<V: View + 'static, F: Fn() -> V + Send + Sync + 'static> View for MemoView<V, F> {
1409 type Body = Never;
1410 fn body(self) -> Self::Body {
1411 unreachable!("MemoView does not have a body")
1412 }
1413
1414 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1415 renderer.memoize(self.id, self.data_hash, &|r| {
1416 let view = (self.builder)();
1417 view.render(r, rect);
1418 });
1419 }
1420}
1421
1422pub struct AnyView {
1424 inner: Box<dyn ErasedView>,
1425}
1426
1427impl Clone for AnyView {
1428 fn clone(&self) -> Self {
1429 Self {
1430 inner: self.inner.clone_box(),
1431 }
1432 }
1433}
1434
1435impl AnyView {
1436 pub fn new<V: View + Clone + 'static>(view: V) -> Self {
1437 Self {
1438 inner: Box::new(view),
1439 }
1440 }
1441}
1442
1443impl View for AnyView {
1444 type Body = Never;
1445 fn body(self) -> Self::Body {
1446 unreachable!()
1447 }
1448
1449 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1450 renderer.push_vnode(rect, self.inner.name());
1451 self.inner.render_erased(renderer, rect);
1452 renderer.pop_vnode();
1453 }
1454
1455 fn flex_weight(&self) -> f32 {
1456 self.inner.flex_weight_erased()
1457 }
1458
1459 fn layout(&self) -> Option<&dyn layout::LayoutView> {
1460 self.inner.layout_erased()
1461 }
1462
1463 fn get_grid_placement(&self) -> Option<GridPlacement> {
1464 self.inner.grid_placement_erased()
1465 }
1466}
1467
1468#[derive(Debug, Clone, PartialEq)]
1472pub struct BifrostBridgeModifier {
1473 pub id: String,
1474}
1475
1476impl ViewModifier for BifrostBridgeModifier {
1477 fn modify<V: View>(self, content: V) -> impl View {
1478 ModifiedView::new(content, self)
1479 }
1480
1481 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1482 renderer.register_shared_element(&self.id, rect);
1484 }
1485}
1486
1487#[derive(Debug, Clone, Copy, PartialEq)]
1490pub struct MjolnirSliceModifier {
1491 pub angle: f32,
1492 pub offset: f32,
1493}
1494
1495impl ViewModifier for MjolnirSliceModifier {
1496 fn modify<V: View>(self, content: V) -> impl View {
1497 ModifiedView::new(content, self)
1498 }
1499
1500 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1501 renderer.push_mjolnir_slice(self.angle, self.offset);
1502 }
1503
1504 fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1505 renderer.pop_mjolnir_slice();
1506 }
1507}
1508
1509#[derive(Debug, Clone, Copy, PartialEq)]
1512pub struct MjolnirShatterModifier {
1513 pub pieces: u32,
1514 pub force: f32,
1515}
1516
1517impl ViewModifier for MjolnirShatterModifier {
1518 fn modify<V: View>(self, content: V) -> impl View {
1519 ModifiedView::new(content, self)
1520 }
1521
1522 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1523 let pieces = self.pieces.max(1);
1525 for i in 0..pieces {
1526 let progress = i as f32 / pieces as f32;
1527 let next_progress = (i + 1) as f32 / pieces as f32;
1528
1529 let angle_start = progress * 360.0;
1530 let angle_end = next_progress * 360.0;
1531
1532 renderer.push_mjolnir_slice(angle_start, 0.0);
1534 renderer.push_mjolnir_slice(angle_end + 180.0, 0.0);
1535
1536 let mid_angle = (angle_start + angle_end) / 2.0;
1538 let rad = mid_angle.to_radians();
1539 let dx = rad.cos() * self.force;
1540 let dy = rad.sin() * self.force;
1541
1542 let shard_rect = Rect {
1543 x: rect.x + dx,
1544 y: rect.y + dy,
1545 ..rect
1546 };
1547
1548 view.render(renderer, shard_rect);
1549
1550 renderer.pop_mjolnir_slice();
1551 renderer.pop_mjolnir_slice();
1552 }
1553 }
1554}
1555
1556#[derive(Debug, Clone, Copy, PartialEq)]
1559pub struct BifrostModifier {
1560 pub blur: f32,
1561 pub saturation: f32,
1562 pub opacity: f32,
1563}
1564
1565impl ViewModifier for BifrostModifier {
1566 fn modify<V: View>(self, content: V) -> impl View {
1567 ModifiedView::new(content, self)
1568 }
1569
1570 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1571 if renderer.is_over_budget() {
1572 renderer.bifrost(rect, self.blur * 0.5, self.saturation, self.opacity);
1574 } else {
1575 renderer.bifrost(rect, self.blur, self.saturation, self.opacity);
1576 }
1577 }
1578}
1579
1580#[derive(Debug, Clone, Copy, PartialEq)]
1582pub struct BackgroundModifier {
1583 pub color: [f32; 4],
1584}
1585
1586impl ViewModifier for BackgroundModifier {
1587 fn modify<V: View>(self, content: V) -> impl View {
1588 ModifiedView::new(content, self)
1589 }
1590
1591 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1592 renderer.fill_rect(rect, self.color);
1593 }
1594}
1595
1596#[derive(Debug, Clone, Copy, PartialEq)]
1598pub struct PaddingModifier {
1599 pub amount: f32,
1600}
1601
1602impl ViewModifier for PaddingModifier {
1603 fn modify<V: View>(self, content: V) -> impl View {
1604 ModifiedView::new(content, self)
1605 }
1606
1607 fn transform_rect(&self, rect: Rect) -> Rect {
1608 Rect {
1609 x: rect.x + self.amount,
1610 y: rect.y + self.amount,
1611 width: (rect.width - 2.0 * self.amount).max(0.0),
1612 height: (rect.height - 2.0 * self.amount).max(0.0),
1613 }
1614 }
1615
1616 fn transform_proposal(&self, mut proposal: SizeProposal) -> SizeProposal {
1617 if let Some(w) = proposal.width {
1618 proposal.width = Some((w - 2.0 * self.amount).max(0.0));
1619 }
1620 if let Some(h) = proposal.height {
1621 proposal.height = Some((h - 2.0 * self.amount).max(0.0));
1622 }
1623 proposal
1624 }
1625
1626 fn transform_size(&self, mut size: Size) -> Size {
1627 size.width += 2.0 * self.amount;
1628 size.height += 2.0 * self.amount;
1629 size
1630 }
1631}
1632
1633#[derive(Debug, Clone, PartialEq)]
1636pub struct GungnirModifier {
1637 pub color: String,
1638 pub radius: f32,
1639 pub intensity: f32,
1640}
1641
1642impl ViewModifier for GungnirModifier {
1643 fn modify<V: View>(self, content: V) -> impl View {
1644 ModifiedView::new(content, self)
1645 }
1646
1647 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1648 renderer.stroke_rect(rect, [0.0, 1.0, 1.0, self.intensity], self.radius / 10.0);
1650 }
1651}
1652
1653#[derive(Debug, Clone, Copy, PartialEq)]
1655pub struct GungnirPulseModifier {
1656 pub color: [f32; 4],
1657 pub radius: f32,
1658 pub speed: f32,
1659}
1660
1661impl ViewModifier for GungnirPulseModifier {
1662 fn modify<V: View>(self, content: V) -> impl View {
1663 ModifiedView::new(content, self)
1664 }
1665
1666 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1667 let time = std::time::SystemTime::now()
1668 .duration_since(std::time::UNIX_EPOCH)
1669 .unwrap_or_default()
1670 .as_secs_f32();
1671
1672 let intensity = (time * self.speed).sin() * 0.5 + 0.5;
1675 let mut color = self.color;
1676 color[3] *= intensity;
1677
1678 renderer.stroke_rect(rect, color, self.radius);
1680 }
1681}
1682
1683#[derive(Debug, Clone, Copy, PartialEq)]
1685pub struct MagneticModifier {
1686 pub radius: f32,
1687 pub intensity: f32,
1688}
1689
1690impl ViewModifier for MagneticModifier {
1691 fn modify<V: View>(self, content: V) -> impl View {
1692 ModifiedView::new(content, self)
1693 }
1694
1695 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1696 let [px, py] = renderer.get_pointer_position();
1697 let center_x = rect.x + rect.width / 2.0;
1698 let center_y = rect.y + rect.height / 2.0;
1699
1700 let dx = px - center_x;
1701 let dy = py - center_y;
1702 let dist = (dx * dx + dy * dy).sqrt();
1703
1704 let mut offset_x = 0.0;
1705 let mut offset_y = 0.0;
1706
1707 if dist < self.radius && dist > 0.0 {
1708 let force = (1.0 - dist / self.radius) * self.intensity;
1709 offset_x = dx * force;
1710 offset_y = dy * force;
1711 }
1712
1713 let magnetic_rect = Rect {
1714 x: rect.x + offset_x,
1715 y: rect.y + offset_y,
1716 ..rect
1717 };
1718
1719 view.render(renderer, magnetic_rect);
1720 }
1721}
1722
1723#[derive(Debug, Clone, Copy, PartialEq)]
1726pub struct ManiGlowModifier {
1727 pub color: [f32; 4],
1728 pub radius: f32,
1729}
1730
1731impl ViewModifier for ManiGlowModifier {
1732 fn modify<V: View>(self, content: V) -> impl View {
1733 ModifiedView::new(content, self)
1734 }
1735
1736 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1737 if crate::load_system_state().realm == Realm::Asgard {
1738 renderer.mani_glow(rect, self.color, self.radius);
1739 }
1740 view.render(renderer, rect);
1741 }
1742}
1743
1744#[derive(Debug, Clone, Copy, PartialEq)]
1749pub struct BifrostLayerModifier {
1750 pub layer: MemoryLayer,
1751}
1752
1753impl ViewModifier for BifrostLayerModifier {
1754 fn modify<V: View>(self, content: V) -> impl View {
1755 ModifiedView::new(content, self)
1756 }
1757
1758 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1759 let realm = crate::load_system_state().realm;
1760 match self.layer {
1761 MemoryLayer::Episodic => {
1762 if realm == Realm::Asgard {
1763 renderer.bifrost(rect, 40.0, 1.2, 0.7);
1764 } else {
1765 renderer.fill_rect(rect, [0.1, 0.12, 0.15, 0.8]);
1766 }
1767 }
1768 MemoryLayer::Semantic => {
1769 if realm == Realm::Asgard {
1770 renderer.gungnir(rect, [1.0, 0.84, 0.0, 1.0], 15.0, 0.6);
1771 } else {
1772 renderer.stroke_rect(rect, [0.4, 0.4, 0.4, 1.0], 1.5);
1773 }
1774 }
1775 MemoryLayer::Procedural => {
1776 renderer.fill_rect(rect, [0.05, 0.05, 0.07, 0.95]);
1777 let stroke_color = if realm == Realm::Asgard {
1778 [0.3, 0.3, 0.3, 1.0]
1779 } else {
1780 [0.2, 0.2, 0.2, 1.0]
1781 };
1782 renderer.stroke_rect(rect, stroke_color, 2.0);
1783 }
1784 }
1785 view.render(renderer, rect);
1786 }
1787}
1788
1789#[derive(Debug, Clone, Copy, PartialEq)]
1793pub struct FafnirModifier {
1794 pub id: u64,
1796}
1797
1798impl ViewModifier for FafnirModifier {
1799 fn modify<V: View>(self, content: V) -> impl View {
1800 ModifiedView::new(content, self)
1801 }
1802
1803 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1804 let state = crate::load_system_state();
1805 let vitality = state
1806 .get_component_state::<f32>(self.id)
1807 .map(|v| *v.read().unwrap())
1808 .unwrap_or(1.0);
1809
1810 let growth = (vitality - 1.0).clamp(0.0, 4.0);
1813 let scale = 1.0 + growth * 0.12;
1814 let glow_intensity = growth * 0.25;
1815
1816 let id = self.id;
1818 renderer.register_handler(
1819 "pointermove",
1820 std::sync::Arc::new(move |_| {
1821 crate::update_system_state(|s| {
1822 let mut s = s.clone();
1823 let v = s
1824 .get_component_state::<f32>(id)
1825 .map(|v| *v.read().unwrap())
1826 .unwrap_or(1.0);
1827 s.set_component_state(id, (v + 0.05).min(5.0)); s
1829 });
1830 }),
1831 );
1832
1833 if scale > 1.01 {
1834 renderer.push_transform([0.0, 0.0], [scale, scale], 0.0);
1835 }
1836
1837 if glow_intensity > 0.1 && state.realm == Realm::Asgard {
1838 renderer.gungnir(rect, [1.0, 0.84, 0.0, 1.0], 15.0 * vitality, glow_intensity);
1839 }
1840
1841 view.render(renderer, rect);
1842
1843 if scale > 1.01 {
1844 renderer.pop_transform();
1845 }
1846 }
1847}
1848
1849#[derive(Debug, Clone, Copy, PartialEq)]
1851pub struct MimirIntentModifier;
1852
1853impl ViewModifier for MimirIntentModifier {
1854 fn modify<V: View>(self, content: V) -> impl View {
1855 ModifiedView::new(content, self)
1856 }
1857
1858 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1859 let state = crate::load_system_state();
1860 let pos = state.last_pointer_pos;
1861 let vel = state.pointer_velocity;
1862
1863 let center = [rect.x + rect.width / 2.0, rect.y + rect.height / 2.0];
1865 let dx = center[0] - pos[0];
1866 let dy = center[1] - pos[1];
1867
1868 let dot = vel[0] * dx + vel[1] * dy;
1870 let speed_sq = vel[0] * vel[0] + vel[1] * vel[1];
1871 let dist_sq = dx * dx + dy * dy;
1872
1873 if dot > 0.0 && dist_sq < 250.0 * 250.0 && speed_sq > 0.5 && state.realm == Realm::Asgard {
1874 let intent_strength = (dot / (speed_sq.sqrt() * dist_sq.sqrt())).clamp(0.0, 1.0);
1876 renderer.stroke_rect(rect, [0.0, 0.9, 1.0, 0.3 * intent_strength], 1.5);
1877 }
1878
1879 view.render(renderer, rect);
1880 }
1881}
1882
1883#[derive(Debug, Clone, Copy, PartialEq)]
1885pub struct KvasirVibeModifier {
1886 pub complexity: f32,
1887}
1888
1889impl ViewModifier for KvasirVibeModifier {
1890 fn modify<V: View>(self, content: V) -> impl View {
1891 ModifiedView::new(content, self)
1892 }
1893
1894 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1895 if crate::load_system_state().realm == Realm::Asgard {
1896 let t = renderer.elapsed_time();
1897 let c = self.complexity.clamp(0.0, 1.0);
1898
1899 let blur = 20.0 + c * 40.0;
1902 let turbulence_x = (t * (1.0 + c * 2.0)).sin() * 8.0 * c;
1903 let turbulence_y = (t * (0.8 + c * 1.5)).cos() * 5.0 * c;
1904 renderer.bifrost(
1905 rect.offset(turbulence_x, turbulence_y),
1906 blur,
1907 0.8 + c * 0.4,
1908 0.25,
1909 );
1910
1911 if c > 0.2 {
1913 let pulse = (t * (3.0 + c * 5.0)).sin().abs() * c;
1914 let color = [0.0, 0.9, 1.0, 0.4 * pulse]; renderer.gungnir(rect, color, 12.0 + c * 24.0, 0.6 * pulse);
1916 }
1917
1918 if c > 0.7 {
1920 let instability = (t * 15.0).cos().abs() * (c - 0.7) * 3.3;
1921 let warning_color = [1.0, 0.0, 0.4, 0.12 * instability];
1922 renderer.fill_rect(rect, warning_color);
1923 renderer.stroke_rect(rect, [1.0, 0.0, 0.2, 0.45 * instability], 1.8);
1924 }
1925 }
1926 view.render(renderer, rect);
1927 }
1928}
1929
1930#[derive(Debug, Clone, Copy, PartialEq)]
1932pub struct OdinsEyeModifier;
1933
1934impl ViewModifier for OdinsEyeModifier {
1935 fn modify<V: View>(self, content: V) -> impl View {
1936 ModifiedView::new(content, self)
1937 }
1938
1939 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1940 let state = crate::load_system_state();
1941 let t = renderer.elapsed_time();
1942
1943 view.render(renderer, rect);
1945
1946 if state.realm == Realm::Asgard {
1947 let eye_pulse = (t * 0.5).sin().abs() * 0.05;
1950 renderer.draw_radial_gradient(
1951 rect,
1952 [0.0, 0.6, 0.8, 0.08 + eye_pulse], [0.0, 0.0, 0.0, 0.0], );
1955
1956 let hugin_rect = Rect {
1958 x: rect.x + 20.0,
1959 y: rect.y + 40.0,
1960 width: 200.0,
1961 height: rect.height - 80.0,
1962 };
1963 renderer.draw_text(
1964 "HUGIN: THOUGHT",
1965 hugin_rect.x,
1966 hugin_rect.y,
1967 10.0,
1968 [0.0, 1.0, 1.0, 0.6],
1969 );
1970 for (i, thought) in state.thoughts.iter().rev().take(10).enumerate() {
1971 renderer.draw_text(
1972 thought,
1973 hugin_rect.x,
1974 hugin_rect.y + 20.0 + i as f32 * 14.0,
1975 9.0,
1976 [1.0, 1.0, 1.0, 0.4],
1977 );
1978 }
1979
1980 let munin_rect = Rect {
1982 x: rect.x + rect.width - 220.0,
1983 y: rect.y + 40.0,
1984 width: 200.0,
1985 height: rect.height - 80.0,
1986 };
1987 renderer.draw_text(
1988 "MUNIN: MEMORY",
1989 munin_rect.x,
1990 munin_rect.y,
1991 10.0,
1992 [1.0, 0.84, 0.0, 0.6],
1993 );
1994 for (i, node) in state.nodes.iter().take(10).enumerate() {
1995 let opacity = (node.weight.min(1.0)) * 0.5;
1996 renderer.draw_text(
1997 &node.id,
1998 munin_rect.x,
1999 munin_rect.y + 20.0 + i as f32 * 14.0,
2000 9.0,
2001 [1.0, 1.0, 1.0, opacity],
2002 );
2003 }
2004
2005 if let Some(focus_id) = &state.odin_focus {
2007 renderer.draw_text(
2009 &format!("EYE FOCUS: {}", focus_id),
2010 rect.x + rect.width / 2.0 - 50.0,
2011 rect.y + 20.0,
2012 12.0,
2013 [0.0, 1.0, 1.0, 0.8],
2014 );
2015
2016 renderer.gungnir(
2019 Rect {
2020 x: rect.x + rect.width / 2.0 - 1.0,
2021 y: rect.y,
2022 width: 2.0,
2023 height: rect.height,
2024 },
2025 [0.0, 1.0, 1.0, 1.0],
2026 20.0,
2027 0.4,
2028 );
2029 }
2030 }
2031 }
2032}
2033
2034#[derive(Debug, Clone, Copy, PartialEq)]
2036pub struct SleipnirParams {
2037 pub stiffness: f32,
2038 pub damping: f32,
2039 pub mass: f32,
2040}
2041
2042impl SleipnirParams {
2043 pub fn snappy() -> Self {
2044 Self {
2045 stiffness: 230.0,
2046 damping: 22.0,
2047 mass: 1.0,
2048 }
2049 }
2050 pub fn fluid() -> Self {
2051 Self {
2052 stiffness: 170.0,
2053 damping: 26.0,
2054 mass: 1.0,
2055 }
2056 }
2057 pub fn heavy() -> Self {
2058 Self {
2059 stiffness: 90.0,
2060 damping: 20.0,
2061 mass: 1.0,
2062 }
2063 }
2064 pub fn bouncy() -> Self {
2065 Self {
2066 stiffness: 190.0,
2067 damping: 14.0,
2068 mass: 1.0,
2069 }
2070 }
2071}
2072
2073impl Default for SleipnirParams {
2074 fn default() -> Self {
2075 Self::fluid()
2076 }
2077}
2078
2079#[derive(Debug, Clone, Copy, PartialEq)]
2080struct SolverState {
2081 x: f32,
2082 v: f32,
2083}
2084
2085#[derive(Debug, Clone, Copy, PartialEq)]
2088pub struct SleipnirSolver {
2089 params: SleipnirParams,
2090 target: f32,
2091 state: SolverState,
2092}
2093
2094impl SleipnirSolver {
2095 pub fn new(params: SleipnirParams, target: f32, current: f32) -> Self {
2097 Self {
2098 params,
2099 target,
2100 state: SolverState { x: current, v: 0.0 },
2101 }
2102 }
2103
2104 pub fn tick(&mut self, dt: f32) -> f32 {
2106 if dt <= 0.0 {
2107 return self.state.x;
2108 }
2109
2110 let mut remaining = dt;
2112 let step = 1.0 / 120.0;
2113
2114 while remaining > 0.0 {
2115 let d = remaining.min(step);
2116 self.step(d);
2117 remaining -= d;
2118 }
2119
2120 self.state.x
2121 }
2122
2123 fn step(&mut self, dt: f32) {
2124 let a = self.evaluate(self.state, 0.0, SolverState { x: 0.0, v: 0.0 });
2125 let b = self.evaluate(self.state, dt * 0.5, a);
2126 let c = self.evaluate(self.state, dt * 0.5, b);
2127 let d = self.evaluate(self.state, dt, c);
2128
2129 let dxdt = 1.0 / 6.0 * (a.x + 2.0 * (b.x + c.x) + d.x);
2130 let dvdt = 1.0 / 6.0 * (a.v + 2.0 * (b.v + c.v) + d.v);
2131
2132 self.state.x += dxdt * dt;
2133 self.state.v += dvdt * dt;
2134 }
2135
2136 fn evaluate(&self, initial: SolverState, dt: f32, d: SolverState) -> SolverState {
2137 let state = SolverState {
2138 x: initial.x + d.x * dt,
2139 v: initial.v + d.v * dt,
2140 };
2141 let force =
2142 -self.params.stiffness * (state.x - self.target) - self.params.damping * state.v;
2143 let mass = self.params.mass.max(0.001);
2144 SolverState {
2145 x: state.v,
2146 v: force / mass,
2147 }
2148 }
2149
2150 pub fn is_settled(&self) -> bool {
2151 (self.state.x - self.target).abs() < 0.001 && self.state.v.abs() < 0.001
2152 }
2153
2154 pub fn set_target(&mut self, target: f32) {
2155 self.target = target;
2156 }
2157
2158 pub fn current_value(&self) -> f32 {
2159 self.state.x
2160 }
2161}
2162
2163#[derive(Debug, Clone, PartialEq)]
2165pub struct SleipnirModifier {
2166 pub id: u64,
2167 pub target: f32,
2168 pub params: SleipnirParams,
2169}
2170
2171impl ViewModifier for SleipnirModifier {
2172 fn modify<V: View>(self, content: V) -> impl View {
2173 ModifiedView::new(content, self)
2174 }
2175
2176 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2177 let state = load_system_state();
2178
2179 let solver_lock_opt = state.get_component_state::<SleipnirSolver>(self.id);
2181
2182 let current_val;
2183
2184 if let Some(lock) = solver_lock_opt {
2185 let mut solver = lock.write().unwrap();
2187 solver.set_target(self.target);
2188 current_val = solver.tick(renderer.delta_time());
2189
2190 if !solver.is_settled() {
2192 renderer.request_redraw();
2193 }
2194 } else {
2195 let solver = SleipnirSolver::new(
2197 self.params,
2198 self.target,
2199 self.target, );
2201
2202 get_system_state().rcu(|old| {
2204 let mut new_state = (**old).clone();
2205 new_state.set_component_state(self.id, solver);
2206 new_state
2207 });
2208
2209 current_val = self.target;
2210 }
2211
2212 renderer.push_transform([0.0, current_val], [1.0, 1.0], 0.0);
2214 view.render(renderer, rect);
2215 renderer.pop_transform();
2216 }
2217}
2218
2219#[derive(Debug, Clone, Copy, PartialEq)]
2222pub struct TransformModifier {
2223 pub translation: [f32; 2],
2224 pub scale: [f32; 2],
2225 pub rotation: f32,
2226}
2227
2228impl Default for TransformModifier {
2229 fn default() -> Self {
2230 Self::new()
2231 }
2232}
2233
2234impl TransformModifier {
2235 pub fn new() -> Self {
2236 Self {
2237 translation: [0.0, 0.0],
2238 scale: [1.0, 1.0],
2239 rotation: 0.0,
2240 }
2241 }
2242
2243 pub fn translate(mut self, x: f32, y: f32) -> Self {
2244 self.translation = [x, y];
2245 self
2246 }
2247
2248 pub fn scale(mut self, x: f32, y: f32) -> Self {
2249 self.scale = [x, y];
2250 self
2251 }
2252
2253 pub fn rotate(mut self, radians: f32) -> Self {
2254 self.rotation = radians;
2255 self
2256 }
2257}
2258
2259impl ViewModifier for TransformModifier {
2260 fn modify<V: View>(self, content: V) -> impl View {
2261 ModifiedView::new(content, self)
2262 }
2263
2264 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2265 renderer.push_transform(self.translation, self.scale, self.rotation);
2266 view.render(renderer, rect);
2267 renderer.pop_transform();
2268 }
2269}
2270
2271#[derive(Clone)]
2274pub struct LifecycleModifier {
2275 pub on_appear: Option<Arc<dyn Fn() + Send + Sync>>,
2276 pub on_disappear: Option<Arc<dyn Fn() + Send + Sync>>,
2277}
2278
2279impl ViewModifier for LifecycleModifier {
2280 fn modify<V: View>(self, content: V) -> impl View {
2281 ModifiedView::new(content, self)
2282 }
2283}
2284
2285#[derive(Debug, Clone, Copy, PartialEq)]
2288pub struct OpacityModifier {
2289 pub opacity: f32,
2290}
2291
2292impl ViewModifier for OpacityModifier {
2293 fn modify<V: View>(self, content: V) -> impl View {
2294 ModifiedView::new(content, self)
2295 }
2296
2297 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2298 renderer.push_opacity(self.opacity);
2299 }
2300
2301 fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2302 renderer.pop_opacity();
2303 }
2304}
2305
2306#[derive(Clone)]
2308pub struct OnClickModifier {
2309 pub action: Arc<dyn Fn() + Send + Sync>,
2310}
2311
2312impl ViewModifier for OnClickModifier {
2313 fn modify<V: View>(self, content: V) -> impl View {
2314 ModifiedView::new(content, self)
2315 }
2316
2317 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2318 let action = self.action.clone();
2319 renderer.register_handler(
2320 "pointerclick",
2321 std::sync::Arc::new(move |event| {
2322 if let Event::PointerClick { .. } = event {
2323 (action)();
2324 }
2325 }),
2326 );
2327 }
2328}
2329
2330#[derive(Clone)]
2332pub struct OnPointerEnterModifier {
2333 pub action: Arc<dyn Fn() + Send + Sync>,
2334}
2335
2336impl ViewModifier for OnPointerEnterModifier {
2337 fn modify<V: View>(self, content: V) -> impl View {
2338 ModifiedView::new(content, self)
2339 }
2340
2341 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2342 let action = self.action.clone();
2343 renderer.register_handler(
2344 "pointerenter",
2345 std::sync::Arc::new(move |event| {
2346 if let Event::PointerEnter = event {
2347 (action)();
2348 }
2349 }),
2350 );
2351 }
2352}
2353
2354#[derive(Clone)]
2356pub struct OnPointerLeaveModifier {
2357 pub action: Arc<dyn Fn() + Send + Sync>,
2358}
2359
2360impl ViewModifier for OnPointerLeaveModifier {
2361 fn modify<V: View>(self, content: V) -> impl View {
2362 ModifiedView::new(content, self)
2363 }
2364
2365 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2366 let action = self.action.clone();
2367 renderer.register_handler(
2368 "pointerleave",
2369 std::sync::Arc::new(move |event| {
2370 if let Event::PointerLeave = event {
2371 (action)();
2372 }
2373 }),
2374 );
2375 }
2376}
2377
2378#[derive(Clone)]
2380pub struct OnPointerMoveModifier {
2381 pub action: Arc<dyn Fn(f32, f32) + Send + Sync>,
2382}
2383
2384impl ViewModifier for OnPointerMoveModifier {
2385 fn modify<V: View>(self, content: V) -> impl View {
2386 ModifiedView::new(content, self)
2387 }
2388
2389 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2390 let action = self.action.clone();
2391 renderer.register_handler(
2392 "pointermove",
2393 std::sync::Arc::new(move |event| {
2394 if let Event::PointerMove { x, y, .. } = event {
2395 (action)(x, y);
2396 }
2397 }),
2398 );
2399 }
2400}
2401
2402#[derive(Clone)]
2404pub struct OnPointerDownModifier {
2405 pub action: Arc<dyn Fn() + Send + Sync>,
2406}
2407
2408impl ViewModifier for OnPointerDownModifier {
2409 fn modify<V: View>(self, content: V) -> impl View {
2410 ModifiedView::new(content, self)
2411 }
2412
2413 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2414 let action = self.action.clone();
2415 renderer.register_handler(
2416 "pointerdown",
2417 std::sync::Arc::new(move |event| {
2418 if let Event::PointerDown { .. } = event {
2419 (action)();
2420 }
2421 }),
2422 );
2423 }
2424}
2425
2426#[derive(Clone)]
2428pub struct OnPointerUpModifier {
2429 pub action: Arc<dyn Fn() + Send + Sync>,
2430}
2431
2432impl ViewModifier for OnPointerUpModifier {
2433 fn modify<V: View>(self, content: V) -> impl View {
2434 ModifiedView::new(content, self)
2435 }
2436
2437 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2438 let action = self.action.clone();
2439 renderer.register_handler(
2440 "pointerup",
2441 std::sync::Arc::new(move |event| {
2442 if let Event::PointerUp { .. } = event {
2443 (action)();
2444 }
2445 }),
2446 );
2447 }
2448}
2449
2450#[derive(Debug, Clone, Copy, PartialEq)]
2453pub struct ForegroundColorModifier {
2454 pub color: [f32; 4],
2455}
2456
2457impl ViewModifier for ForegroundColorModifier {
2458 fn modify<V: View>(self, content: V) -> impl View {
2459 ModifiedView::new(content, self)
2460 }
2461}
2462
2463#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2466pub struct ClipModifier;
2467
2468impl ViewModifier for ClipModifier {
2469 fn modify<V: View>(self, content: V) -> impl View {
2470 ModifiedView::new(content, self)
2471 }
2472
2473 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2474 renderer.push_clip_rect(rect);
2475 }
2476
2477 fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2478 renderer.pop_clip_rect();
2479 }
2480}
2481
2482#[derive(Debug, Clone, Copy, PartialEq)]
2484pub struct BorderModifier {
2485 pub color: [f32; 4],
2486 pub width: f32,
2487}
2488
2489impl ViewModifier for BorderModifier {
2490 fn modify<V: View>(self, content: V) -> impl View {
2491 ModifiedView::new(content, self)
2492 }
2493
2494 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2495 renderer.stroke_rect(rect, self.color, self.width);
2496 }
2497}
2498
2499#[doc(hidden)]
2501pub enum Never {}
2502
2503impl View for Never {
2504 type Body = Never;
2505 fn body(self) -> Never {
2506 unreachable!()
2507 }
2508}
2509
2510#[derive(Debug, Clone, Copy, Default)]
2512pub struct EmptyView;
2513
2514impl View for EmptyView {
2515 type Body = Never;
2516 fn body(self) -> Self::Body {
2517 unreachable!()
2518 }
2519 fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2520 fn intrinsic_size(&self, _renderer: &mut dyn Renderer, _proposal: SizeProposal) -> Size {
2521 Size {
2522 width: 0.0,
2523 height: 0.0,
2524 }
2525 }
2526}
2527
2528#[derive(Clone)]
2531pub struct ModifiedView<V, M> {
2532 view: V,
2533 modifier: M,
2534}
2535
2536impl<V: View, M: ViewModifier> ModifiedView<V, M> {
2537 #[doc(hidden)]
2538 pub fn new(view: V, modifier: M) -> Self {
2539 Self { view, modifier }
2540 }
2541}
2542
2543impl<V: View, M: ViewModifier> View for ModifiedView<V, M> {
2544 type Body = ModifiedView<V::Body, M>;
2545
2546 fn body(self) -> Self::Body {
2547 ModifiedView {
2548 view: self.view.body(),
2549 modifier: self.modifier.clone(),
2550 }
2551 }
2552
2553 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2554 self.modifier.render_view(&self.view, renderer, rect);
2555 }
2556
2557 fn intrinsic_size(&self, renderer: &mut dyn Renderer, proposal: SizeProposal) -> Size {
2558 self.modifier.measure_view(&self.view, renderer, proposal)
2559 }
2560
2561 fn flex_weight(&self) -> f32 {
2562 self.modifier.child_flex_weight(&self.view)
2563 }
2564
2565 fn layout(&self) -> Option<&dyn layout::LayoutView> {
2566 self.modifier.layout().or_else(|| self.view.layout())
2567 }
2568
2569 fn get_grid_placement(&self) -> Option<GridPlacement> {
2570 self.modifier
2571 .get_grid_placement()
2572 .or_else(|| self.view.get_grid_placement())
2573 }
2574}
2575
2576pub trait ViewModifier: Send + Clone {
2577 fn modify<V: View>(self, content: V) -> impl View;
2578
2579 fn get_grid_placement(&self) -> Option<GridPlacement> {
2581 None
2582 }
2583
2584 fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2586
2587 fn post_render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2589
2590 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2593 self.render(renderer, rect);
2594 let child_rect = self.transform_rect(rect);
2595 view.render(renderer, child_rect);
2596 self.post_render(renderer, rect);
2597 }
2598
2599 fn transform_rect(&self, rect: Rect) -> Rect {
2600 rect
2601 }
2602
2603 fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
2605 proposal
2606 }
2607
2608 fn transform_size(&self, size: Size) -> Size {
2610 size
2611 }
2612
2613 fn measure_view<V: View>(
2615 &self,
2616 view: &V,
2617 renderer: &mut dyn Renderer,
2618 proposal: SizeProposal,
2619 ) -> Size {
2620 let child_proposal = self.transform_proposal(proposal);
2621 let child_size = view.intrinsic_size(renderer, child_proposal);
2622 self.transform_size(child_size)
2623 }
2624
2625 fn child_flex_weight<V: View>(&self, view: &V) -> f32 {
2627 view.flex_weight()
2628 }
2629
2630 fn layout(&self) -> Option<&dyn layout::LayoutView> {
2631 None
2632 }
2633}
2634
2635#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
2637pub struct TelemetryData {
2638 pub frame_time_ms: f32,
2639 pub p99_frame_time_ms: f32,
2641 pub frame_jitter_ms: f32,
2643 pub hardware_stall_detected: bool,
2645
2646 pub input_time_ms: f32,
2648 pub state_flush_time_ms: f32,
2649 pub layout_time_ms: f32,
2650 pub draw_time_ms: f32,
2651 pub gpu_submit_time_ms: f32,
2652
2653 pub draw_calls: u32,
2654 pub vertices: u32,
2655
2656 pub berserker_rage: f32,
2658
2659 pub vram_usage_mb: f32,
2661 pub vram_textures_mb: f32,
2662 pub vram_buffers_mb: f32,
2663 pub vram_pipelines_mb: f32,
2664 pub vram_exhausted: bool,
2666}
2667
2668#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2670pub struct FrameBudget {
2671 pub target_ms: f32,
2673 pub allow_degradation: bool,
2676}
2677
2678impl Default for FrameBudget {
2679 fn default() -> Self {
2680 Self {
2681 target_ms: 16.0,
2682 allow_degradation: true,
2683 }
2684 }
2685}
2686
2687pub trait ElapsedTime {
2695 fn elapsed_time(&self) -> f32;
2697
2698 fn delta_time(&self) -> f32;
2700}
2701
2702pub trait Renderer: ElapsedTime + Send {
2709 fn request_redraw(&mut self) {}
2712
2713 fn is_over_budget(&self) -> bool {
2716 false
2717 }
2718
2719 fn fill_rect(&mut self, rect: Rect, color: [f32; 4]);
2721 fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]);
2722 fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]);
2724
2725 fn draw_3d_cube(&mut self, _rect: Rect, _color: [f32; 4], _rotation: [f32; 3]) {}
2728
2729 fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
2731 fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32);
2732 fn stroke_ellipse(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
2734 fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: [f32; 4], stroke_width: f32);
2736 fn fill_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4]) {}
2738 fn stroke_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4], _stroke_width: f32) {}
2740
2741 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]);
2743 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32);
2745
2746 fn shape_rich_text(
2747 &mut self,
2748 _spans: &[cvkg_runic_text::TextSpan],
2749 _max_width: Option<f32>,
2750 _align: cvkg_runic_text::TextAlign,
2751 _overflow: cvkg_runic_text::TextOverflow,
2752 ) -> Option<cvkg_runic_text::ShapedText> {
2753 None
2754 }
2755
2756 fn draw_shaped_text(&mut self, _text: &cvkg_runic_text::ShapedText, _x: f32, _y: f32) {}
2757
2758 fn draw_texture(&mut self, _texture_id: u32, _rect: Rect) {}
2761 fn draw_image(&mut self, _image_name: &str, _rect: Rect) {}
2763 fn load_image(&mut self, _name: &str, _data: &[u8]) {}
2765 fn prewarm_vram(&mut self, _assets: Vec<(String, Vec<u8>)>) {}
2768
2769 fn get_pointer_position(&self) -> [f32; 2] {
2771 [0.0, 0.0]
2772 }
2773
2774 fn upload_data_texture(&mut self, _id: &str, _data: &[f32], _width: u32, _height: u32) {}
2777 fn draw_heatmap(&mut self, _texture_id: &str, _rect: Rect, _palette: &str) {}
2779
2780 fn draw_mesh(&mut self, _mesh: &Mesh, _color: [f32; 4], _transform: glam::Mat4) {}
2783
2784 fn draw_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {}
2786
2787 fn set_camera_3d(&mut self, _camera: &Camera3D) {}
2790
2791 fn push_transform_3d(&mut self, _transform: &Transform3D) {}
2794
2795 fn pop_transform_3d(&mut self) {}
2797
2798 fn render_scene_node_3d(
2807 &mut self,
2808 _position: [f32; 3],
2809 _rotation: [f32; 4],
2810 _scale: [f32; 3],
2811 _color: [f32; 4],
2812 _meshes: &[Mesh],
2813 ) {
2814 }
2816
2817 fn draw_linear_gradient(
2819 &mut self,
2820 _rect: Rect,
2821 _start_color: [f32; 4],
2822 _end_color: [f32; 4],
2823 _angle: f32,
2824 ) {
2825 }
2826 fn draw_radial_gradient(
2828 &mut self,
2829 _rect: Rect,
2830 _inner_color: [f32; 4],
2831 _outer_color: [f32; 4],
2832 ) {
2833 }
2834 fn draw_drop_shadow(
2836 &mut self,
2837 _rect: Rect,
2838 _radius: f32,
2839 _color: [f32; 4],
2840 _blur: f32,
2841 _spread: f32,
2842 ) {
2843 }
2844 fn stroke_dashed_rounded_rect(
2846 &mut self,
2847 _rect: Rect,
2848 _radius: f32,
2849 _color: [f32; 4],
2850 _width: f32,
2851 _dash: f32,
2852 _gap: f32,
2853 ) {
2854 }
2855 fn draw_9slice(
2857 &mut self,
2858 _image_name: &str,
2859 _rect: Rect,
2860 _left: f32,
2861 _top: f32,
2862 _right: f32,
2863 _bottom: f32,
2864 ) {
2865 }
2866
2867 fn push_clip_rect(&mut self, _rect: Rect) {}
2871 fn pop_clip_rect(&mut self) {}
2873 fn current_clip_rect(&self) -> Rect {
2876 Rect::new(-10000.0, -10000.0, 20000.0, 20000.0)
2877 }
2878
2879 fn push_opacity(&mut self, _opacity: f32) {}
2883 fn pop_opacity(&mut self) {}
2885
2886 fn set_theme(&mut self, _theme: ColorTheme) {}
2888 fn set_rage(&mut self, _rage: f32) {}
2889 fn set_berserker_mode(&mut self, _state: BerserkerMode) {}
2890 fn trigger_shatter_event(&mut self, _origin: [f32; 2], _force: f32) {}
2891 fn set_scene(&mut self, _scene: &str) {}
2893
2894 fn capture_png(&mut self) -> Vec<u8> {
2897 Vec::new()
2898 }
2899 fn print(&mut self) {}
2901
2902 fn set_scene_preset(&mut self, _preset: u32) {}
2903
2904 fn bifrost(&mut self, _rect: Rect, _blur: f32, _saturation: f32, _opacity: f32) {}
2907 fn gungnir(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32, _intensity: f32) {}
2909 fn mani_glow(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32) {}
2911 fn push_mjolnir_slice(&mut self, _angle: f32, _offset: f32) {}
2913 fn pop_mjolnir_slice(&mut self) {}
2914 fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer));
2918 fn mjolnir_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2920 fn mjolnir_fluid_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2921 fn draw_mjolnir_bolt(&mut self, _from: [f32; 2], _to: [f32; 2], _color: [f32; 4]) {}
2923
2924 fn set_aria_role(&mut self, _role: &str) {}
2926 fn set_aria_label(&mut self, _label: &str) {}
2927
2928 fn register_shared_element(&mut self, _id: &str, _rect: Rect) {}
2930
2931 fn set_key(&mut self, _key: &str) {}
2933
2934 fn get_telemetry(&self) -> TelemetryData {
2937 TelemetryData::default()
2938 }
2939
2940 fn push_shadow(&mut self, _radius: f32, _color: [f32; 4], _offset: [f32; 2]) {}
2943 fn pop_shadow(&mut self) {}
2945
2946 fn push_vnode(&mut self, _rect: Rect, _name: &'static str) {}
2949 fn pop_vnode(&mut self) {}
2951 fn register_handler(
2953 &mut self,
2954 _event_type: &str,
2955 _handler: std::sync::Arc<dyn Fn(Event) + Send + Sync>,
2956 ) {
2957 }
2958
2959 fn set_z_index(&mut self, _z: f32) {}
2963 fn get_z_index(&self) -> f32 {
2965 0.0
2966 }
2967
2968 fn load_svg(&mut self, _name: &str, _svg_data: &[u8]) {}
2971 fn draw_svg(&mut self, _name: &str, _rect: Rect) {}
2973 fn serialize_svg(&mut self, _name: &str) -> Result<String, String> {
2977 Err("SVG serialization not supported by this renderer".into())
2978 }
2979 fn apply_svg_filter(
2983 &mut self,
2984 _name: &str,
2985 _filter_id: &str,
2986 _region: Rect,
2987 ) -> Result<String, String> {
2988 Err("SVG filter not supported by this renderer".into())
2989 }
2990
2991 fn push_transform(&mut self, _translation: [f32; 2], _scale: [f32; 2], _rotation: f32) {}
2996 fn push_affine(&mut self, _transform: [f32; 6]) {}
2999 fn pop_transform(&mut self) {}
3001 fn query_layout(&self, _node_id: scene_graph::NodeId) -> Option<Rect> {
3003 None
3004 }
3005 fn set_debug_layout(&mut self, _enabled: bool) {}
3007 fn get_debug_layout(&self) -> bool {
3009 false
3010 }
3011
3012 fn set_material(&mut self, _material: crate::material::DrawMaterial) {}
3016 fn current_material(&self) -> crate::material::DrawMaterial {
3018 crate::material::DrawMaterial::Opaque
3019 }
3020
3021 fn mimir_intent(&self) -> [f32; 2] {
3024 [0.0, 0.0]
3025 }
3026 fn magnetic_warp(&self, pointer: [f32; 2], anchor_rect: Rect, strength: f32) -> [f32; 2] {
3028 if strength <= 0.0 {
3029 return pointer;
3030 }
3031 let cx = anchor_rect.x + anchor_rect.width / 2.0;
3032 let cy = anchor_rect.y + anchor_rect.height / 2.0;
3033 let dx = pointer[0] - cx;
3034 let dy = pointer[1] - cy;
3035 let dist = (dx * dx + dy * dy).sqrt();
3036 let radius = 120.0;
3037 if dist < radius && dist > 0.0 {
3038 let force = (1.0 - dist / radius) * strength;
3039 [pointer[0] - dx * force, pointer[1] - dy * force]
3040 } else {
3041 pointer
3042 }
3043 }
3044 fn mani_glow_intensity(&self, pointer: [f32; 2], bounds: Rect, radius: f32) -> f32 {
3046 let cx = bounds.x + bounds.width / 2.0;
3047 let cy = bounds.y + bounds.height / 2.0;
3048 let dist = ((pointer[0] - cx).powi(2) + (pointer[1] - cy).powi(2)).sqrt();
3049 if dist < radius {
3050 (1.0 - dist / radius).clamp(0.0, 1.0)
3051 } else {
3052 0.0
3053 }
3054 }
3055 fn fafnir_evolve(&self, pointer: [f32; 2], bounds: Rect, max_scale: f32) -> f32 {
3057 let prox = self.mani_glow_intensity(pointer, bounds, 120.0);
3058 1.0 + (max_scale - 1.0) * prox
3059 }
3060 fn set_sdf_shape(&mut self, _shape: crate::layout::SdfShape) {}
3062
3063 fn enter_portal(&mut self, _z_index: i32) {}
3073
3074 fn exit_portal(&mut self) {}
3078
3079 fn viewport_size(&self) -> Rect {
3082 Rect::new(0.0, 0.0, 1920.0, 1080.0)
3083 }
3084
3085 fn announce(&mut self, _message: &str, _priority: AnnouncementPriority) {}
3091}
3092
3093pub mod accessibility {
3095 pub fn relative_luminance(color: [f32; 4]) -> f32 {
3097 let f = |c: f32| {
3098 if c <= 0.03928 {
3099 c / 12.92
3100 } else {
3101 ((c + 0.055) / 1.055).powf(2.4)
3102 }
3103 };
3104 0.2126 * f(color[0]) + 0.7152 * f(color[1]) + 0.0722 * f(color[2])
3105 }
3106
3107 pub fn contrast_ratio(c1: [f32; 4], c2: [f32; 4]) -> f32 {
3109 let l1 = relative_luminance(c1);
3110 let l2 = relative_luminance(c2);
3111 let (light, dark) = if l1 > l2 { (l1, l2) } else { (l2, l1) };
3112 (light + 0.05) / (dark + 0.05)
3113 }
3114}
3115#[derive(
3117 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
3118)]
3119pub enum RenderTier {
3120 Tier1GPU = 0,
3122 Tier2GPU = 1,
3124 Tier3Fallback = 2,
3126}
3127use bytemuck::{Pod, Zeroable};
3131#[repr(C)]
3133#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3134pub struct ColorTheme {
3135 pub primary_neon: [f32; 4], pub shatter_neon: [f32; 4],
3137 pub glass_base: [f32; 4],
3138 pub glass_edge: [f32; 4],
3139 pub rune_glow: [f32; 4],
3140 pub ember_core: [f32; 4],
3141 pub background_deep: [f32; 4],
3142 pub mani_glow: [f32; 4], pub glass_blur_strength: f32,
3144 pub shatter_edge_width: f32,
3145 pub neon_bloom_radius: f32,
3146 pub rune_opacity: f32,
3147}
3148impl ColorTheme {
3149 pub fn asgard() -> Self {
3151 Self {
3152 primary_neon: [0.0, 1.0, 0.95, 1.2],
3153 shatter_neon: [1.0, 0.0, 0.75, 1.5],
3154 glass_base: [0.04, 0.04, 0.06, 0.82],
3155 glass_edge: [0.0, 0.45, 0.55, 0.6],
3156 rune_glow: [0.75, 0.98, 1.0, 0.9],
3157 ember_core: [0.95, 0.12, 0.12, 1.0],
3158 background_deep: [0.01, 0.01, 0.03, 1.0],
3159 mani_glow: [0.7, 0.9, 1.0, 0.05],
3160 glass_blur_strength: 0.6,
3161 shatter_edge_width: 1.8,
3162 neon_bloom_radius: 0.022,
3163 rune_opacity: 0.55,
3164 }
3165 }
3166
3167 pub fn midgard() -> Self {
3169 Self {
3170 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],
3176 background_deep: [0.05, 0.05, 0.07, 1.0],
3177 mani_glow: [0.0, 0.0, 0.0, 0.0], glass_blur_strength: 0.0, shatter_edge_width: 1.0,
3180 neon_bloom_radius: 0.0,
3181 rune_opacity: 0.0,
3182 }
3183 }
3184
3185 pub fn cyberpunk_viking() -> Self {
3186 Self::asgard()
3187 }
3188 pub fn vibrant_glass() -> Self {
3189 Self {
3190 primary_neon: [0.0, 1.0, 0.95, 1.2],
3191 shatter_neon: [1.0, 0.0, 0.75, 1.5],
3192 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],
3195 ember_core: [1.0, 0.4, 0.1, 1.0],
3196 background_deep: [0.05, 0.05, 0.1, 1.0],
3197 mani_glow: [0.7, 0.9, 1.0, 0.05],
3198 glass_blur_strength: 0.9,
3199 shatter_edge_width: 1.8,
3200 neon_bloom_radius: 0.022,
3201 rune_opacity: 0.55,
3202 }
3203 }
3204}
3205impl Default for ColorTheme {
3206 fn default() -> Self {
3207 Self::vibrant_glass()
3208 }
3209}
3210#[repr(C)]
3212#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3213pub struct SceneUniforms {
3214 pub view: glam::Mat4,
3215 pub proj: glam::Mat4,
3216 pub time: f32,
3217 pub delta_time: f32,
3218 pub resolution: [f32; 2],
3219 pub mouse: [f32; 2],
3220 pub mouse_velocity: [f32; 2],
3221 pub shatter_origin: [f32; 2],
3222 pub shatter_time: f32,
3223 pub shatter_force: f32,
3224 pub berzerker_rage: f32,
3225 pub berzerker_mode: u32,
3226 pub scroll_offset: f32,
3227 pub scale_factor: f32,
3228 pub scene_type: u32,
3229 pub _pad: [f32; 3], }
3231
3232pub const SCENE_AURORA: u32 = 0;
3233pub const SCENE_VOID: u32 = 1;
3234pub const SCENE_NEBULA: u32 = 2;
3235pub const SCENE_GLITCH: u32 = 3;
3236pub const SCENE_YGGDRASIL: u32 = 4;
3237
3238impl SceneUniforms {
3239 pub fn new(width: f32, height: f32) -> Self {
3240 Self {
3241 view: glam::Mat4::IDENTITY,
3242 proj: glam::Mat4::orthographic_lh(0.0, width, height, 0.0, -100.0, 100.0),
3243 time: 0.0,
3244 delta_time: 0.016,
3245 resolution: [width, height],
3246 mouse: [0.5, 0.5],
3247 mouse_velocity: [0.0, 0.0],
3248 shatter_origin: [0.5, 0.5],
3249 shatter_time: -100.0,
3250 shatter_force: 0.0,
3251 berzerker_rage: 0.0,
3252 berzerker_mode: 0,
3253 scroll_offset: 0.0,
3254 scale_factor: 1.0,
3255 scene_type: SCENE_AURORA,
3256 _pad: [0.0; 3],
3257 }
3258 }
3259}
3260#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
3262pub struct Mesh {
3263 pub vertices: Vec<[f32; 3]>,
3264 pub normals: Vec<[f32; 3]>,
3265 pub indices: Vec<u32>,
3266}
3267impl Mesh {
3268 pub fn from_obj(data: &[u8]) -> anyhow::Result<Vec<Self>> {
3269 let mut cursor = std::io::Cursor::new(data);
3270 let (models, _) = tobj::load_obj_buf(&mut cursor, &tobj::LoadOptions::default(), |_| {
3271 Ok((Vec::new(), Default::default()))
3272 })?;
3273 let mut meshes = Vec::new();
3274 for m in models {
3275 let mesh = m.mesh;
3276 let vertices: Vec<[f32; 3]> = mesh
3277 .positions
3278 .chunks(3)
3279 .map(|c| [c[0], c[1], c[2]])
3280 .collect();
3281 let normals = if mesh.normals.is_empty() {
3282 vec![[0.0, 0.0, 1.0]; vertices.len()]
3283 } else {
3284 mesh.normals.chunks(3).map(|c| [c[0], c[1], c[2]]).collect()
3285 };
3286 meshes.push(Mesh {
3287 vertices,
3288 normals,
3289 indices: mesh.indices,
3290 });
3291 }
3292 Ok(meshes)
3293 }
3294 pub fn from_stl(data: &[u8]) -> anyhow::Result<Self> {
3295 let mut cursor = std::io::Cursor::new(data);
3296 let stl = stl_io::read_stl(&mut cursor)?;
3297 let vertices: Vec<[f32; 3]> = stl.vertices.iter().map(|v| [v[0], v[1], v[2]]).collect();
3298 let mut indices = Vec::new();
3299 for face in stl.faces {
3300 indices.push(face.vertices[0] as u32);
3301 indices.push(face.vertices[1] as u32);
3302 indices.push(face.vertices[2] as u32);
3303 }
3304 let normals = vec![[0.0, 0.0, 1.0]; vertices.len()];
3305 Ok(Mesh {
3306 vertices,
3307 normals,
3308 indices,
3309 })
3310 }
3311}
3312
3313#[derive(Debug, Clone, Copy, PartialEq)]
3319pub struct Transform3D {
3320 pub position: glam::Vec3,
3321 pub rotation: glam::Quat,
3322 pub scale: glam::Vec3,
3323}
3324
3325impl Default for Transform3D {
3326 fn default() -> Self {
3327 Self {
3328 position: glam::Vec3::ZERO,
3329 rotation: glam::Quat::IDENTITY,
3330 scale: glam::Vec3::ONE,
3331 }
3332 }
3333}
3334
3335impl Transform3D {
3336 pub fn to_matrix(&self) -> glam::Mat4 {
3338 glam::Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.position)
3339 }
3340
3341 pub fn from_2d(x: f32, y: f32, rotation: f32) -> Self {
3343 Self {
3344 position: glam::Vec3::new(x, y, 0.0),
3345 rotation: glam::Quat::from_rotation_z(rotation),
3346 scale: glam::Vec3::ONE,
3347 }
3348 }
3349}
3350
3351#[derive(Debug, Clone, Copy)]
3353pub struct Camera3D {
3354 pub position: glam::Vec3,
3356 pub target: glam::Vec3,
3358 pub up: glam::Vec3,
3360 pub fov_y: f32,
3362 pub near: f32,
3364 pub far: f32,
3366 pub perspective: bool,
3368 pub aspect: f32,
3370}
3371
3372#[derive(Debug, Clone, Copy, PartialEq)]
3374pub struct Material3D {
3375 pub base_color: [f32; 4],
3377 pub metallic: f32,
3379 pub roughness: f32,
3381 pub emissive: [f32; 3],
3383 pub opacity: f32,
3385}
3386
3387impl Default for Material3D {
3388 fn default() -> Self {
3389 Self {
3390 base_color: [1.0, 1.0, 1.0, 1.0],
3391 metallic: 0.0,
3392 roughness: 0.5,
3393 emissive: [0.0, 0.0, 0.0],
3394 opacity: 1.0,
3395 }
3396 }
3397}
3398
3399impl Material3D {
3400 pub fn unlit(color: [f32; 4]) -> Self {
3402 Self {
3403 base_color: color,
3404 metallic: 0.0,
3405 roughness: 1.0,
3406 emissive: [0.0, 0.0, 0.0],
3407 opacity: color[3],
3408 }
3409 }
3410
3411 pub fn metallic(color: [f32; 4], roughness: f32) -> Self {
3413 Self {
3414 base_color: color,
3415 metallic: 1.0,
3416 roughness: roughness.clamp(0.0, 1.0),
3417 emissive: [0.0, 0.0, 0.0],
3418 opacity: color[3],
3419 }
3420 }
3421}
3422
3423impl Default for Camera3D {
3424 fn default() -> Self {
3425 Self {
3426 position: glam::Vec3::new(0.0, 0.0, 10.0),
3427 target: glam::Vec3::ZERO,
3428 up: glam::Vec3::Y,
3429 fov_y: 45.0f32.to_radians(),
3430 near: 0.1,
3431 far: 1000.0,
3432 perspective: true,
3433 aspect: 16.0 / 9.0,
3434 }
3435 }
3436}
3437
3438impl Camera3D {
3439 pub fn view_matrix(&self) -> glam::Mat4 {
3441 glam::Mat4::look_at_lh(self.position, self.target, self.up)
3442 }
3443
3444 pub fn projection_matrix(&self) -> glam::Mat4 {
3446 if self.perspective {
3447 glam::Mat4::perspective_lh(self.fov_y, self.aspect, self.near, self.far)
3448 } else {
3449 let top = self.fov_y;
3451 let right = top * self.aspect;
3452 glam::Mat4::orthographic_lh(-right, right, -top, top, self.near, self.far)
3453 }
3454 }
3455
3456 pub fn view_projection(&self) -> glam::Mat4 {
3458 self.projection_matrix() * self.view_matrix()
3459 }
3460}
3461
3462pub trait FrameRenderer<E = ()>: Renderer {
3465 fn begin_frame(&mut self) -> E;
3466 fn render_frame(&mut self) {
3467 }
3469 fn end_frame(&mut self, encoder: E);
3470}
3471use std::sync::Arc;
3472type SubscriberList<T> = Arc<std::sync::Mutex<Vec<Box<dyn Fn(&T) + Send + Sync>>>>;
3473#[derive(Clone)]
3475pub struct State<T: Clone + Send + Sync + 'static> {
3476 swap: Arc<arc_swap::ArcSwap<T>>,
3477 metadata_swap: Arc<arc_swap::ArcSwap<Option<agents::MutationMetadata>>>,
3478 #[cfg(not(target_arch = "wasm32"))]
3479 tvar: Arc<stm::TVar<T>>,
3480 #[cfg(not(target_arch = "wasm32"))]
3481 metadata_tvar: Arc<stm::TVar<Option<agents::MutationMetadata>>>,
3482 subscribers: SubscriberList<T>,
3483 version: Arc<std::sync::atomic::AtomicU64>,
3484 resolution: agents::ConflictResolution,
3485}
3486impl<T: Clone + Send + Sync + 'static> State<T> {
3487 pub fn new(value: T) -> Self {
3489 #[cfg(not(target_arch = "wasm32"))]
3490 let tvar = Arc::new(stm::TVar::new(value.clone()));
3491 #[cfg(not(target_arch = "wasm32"))]
3492 let metadata_tvar = Arc::new(stm::TVar::new(None));
3493 Self {
3494 swap: Arc::new(arc_swap::ArcSwap::from_pointee(value)),
3495 metadata_swap: Arc::new(arc_swap::ArcSwap::new(Arc::new(None))),
3496 #[cfg(not(target_arch = "wasm32"))]
3497 tvar,
3498 #[cfg(not(target_arch = "wasm32"))]
3499 metadata_tvar,
3500 subscribers: Arc::new(std::sync::Mutex::new(Vec::new())),
3501 version: Arc::new(std::sync::atomic::AtomicU64::new(0)),
3502 resolution: agents::ConflictResolution::default(),
3503 }
3504 }
3505 pub fn with_resolution(mut self, resolution: agents::ConflictResolution) -> Self {
3507 self.resolution = resolution;
3508 self
3509 }
3510 pub fn get(&self) -> T {
3512 (**self.swap.load()).clone()
3513 }
3514 pub fn set(&self, value: T) {
3516 #[cfg(not(target_arch = "wasm32"))]
3517 let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3518 let new_meta = agents::get_current_mutation_metadata();
3519 let existing_meta = self.metadata_tvar.read(tx)?;
3520 let mut skip = false;
3521 if self.resolution == agents::ConflictResolution::PriorityWins
3522 && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3523 && new_m.priority < old_m.priority
3524 {
3525 skip = true;
3526 }
3527 if !skip {
3528 self.tvar.write(tx, value.clone())?;
3529 self.metadata_tvar.write(tx, new_meta)?;
3530 Ok((false, value.clone(), new_meta))
3531 } else {
3532 Ok((true, self.tvar.read(tx)?, existing_meta))
3533 }
3534 });
3535 #[cfg(target_arch = "wasm32")]
3536 let (was_skipped, final_val, final_meta) =
3537 (false, value, agents::get_current_mutation_metadata());
3538 if was_skipped {
3539 if let (Some(new_m), Some(old_m)) =
3540 (agents::get_current_mutation_metadata(), final_meta)
3541 {
3542 agents::notify_conflict(agents::ConflictEvent {
3543 agent_id: new_m.agent_id,
3544 priority: new_m.priority,
3545 existing_agent_id: old_m.agent_id,
3546 existing_priority: old_m.priority,
3547 timestamp_ms: new_m.timestamp_ms,
3548 });
3549 }
3550 return;
3551 }
3552 self.swap.store(Arc::new(final_val.clone()));
3553 self.metadata_swap.store(Arc::new(final_meta));
3554 self.version
3555 .fetch_add(1, std::sync::atomic::Ordering::Release);
3556 let subs = Arc::clone(&self.subscribers);
3557 if crate::is_batching() {
3558 crate::enqueue_batch_task(Box::new(move || {
3559 let s = subs.lock().unwrap();
3560 for cb in s.iter() {
3561 cb(&final_val);
3562 }
3563 }));
3564 } else {
3565 let s = subs.lock().unwrap();
3566 for cb in s.iter() {
3567 cb(&final_val);
3568 }
3569 }
3570 }
3571 pub fn mutate<F: Fn(&T) -> T>(&self, f: F) {
3572 #[cfg(not(target_arch = "wasm32"))]
3573 {
3574 let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3575 let new_meta = agents::get_current_mutation_metadata();
3576 let existing_meta = self.metadata_tvar.read(tx)?;
3577 let mut skip = false;
3578 if self.resolution == agents::ConflictResolution::PriorityWins
3579 && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3580 && new_m.priority < old_m.priority
3581 {
3582 skip = true;
3583 }
3584 if !skip {
3585 let current = self.tvar.read(tx)?;
3586 let next = f(¤t);
3587 self.tvar.write(tx, next.clone())?;
3588 self.metadata_tvar.write(tx, new_meta)?;
3589 Ok((false, next, new_meta))
3590 } else {
3591 Ok((true, self.tvar.read(tx)?, existing_meta))
3592 }
3593 });
3594 if was_skipped {
3595 if let (Some(new_m), Some(old_m)) =
3596 (agents::get_current_mutation_metadata(), final_meta)
3597 {
3598 agents::notify_conflict(agents::ConflictEvent {
3599 agent_id: new_m.agent_id,
3600 priority: new_m.priority,
3601 existing_agent_id: old_m.agent_id,
3602 existing_priority: old_m.priority,
3603 timestamp_ms: new_m.timestamp_ms,
3604 });
3605 }
3606 return;
3607 }
3608 self.swap.store(Arc::new(final_val.clone()));
3609 self.metadata_swap.store(Arc::new(final_meta));
3610 self.version
3611 .fetch_add(1, std::sync::atomic::Ordering::Release);
3612 let subs = Arc::clone(&self.subscribers);
3613 if crate::is_batching() {
3614 crate::enqueue_batch_task(Box::new(move || {
3615 let s = subs.lock().unwrap();
3616 for cb in s.iter() {
3617 cb(&final_val);
3618 }
3619 }));
3620 } else {
3621 let s = subs.lock().unwrap();
3622 for cb in s.iter() {
3623 cb(&final_val);
3624 }
3625 }
3626 }
3627 #[cfg(target_arch = "wasm32")]
3628 {
3629 self.set(f(&self.get()));
3630 }
3631 }
3632 pub fn version(&self) -> u64 {
3634 self.version.load(std::sync::atomic::Ordering::Acquire)
3635 }
3636 pub fn subscribe<F: Fn(&T) + Send + Sync + 'static>(&self, callback: F) {
3638 self.subscribers.lock().unwrap().push(Box::new(callback));
3639 }
3640}
3641use crate::runtime::NodeStateSnapshot;
3642use std::sync::OnceLock;
3643use std::sync::atomic::{AtomicBool, Ordering};
3644pub static SYSTEM_STATE: OnceLock<Arc<arc_swap::ArcSwap<KnowledgeState>>> = OnceLock::new();
3646#[cfg(not(target_arch = "wasm32"))]
3647static KNOWLEDGE_TVAR: OnceLock<stm::TVar<KnowledgeState>> = OnceLock::new();
3648static IS_BATCHING: AtomicBool = AtomicBool::new(false);
3649pub static IS_RENDERING: AtomicBool = AtomicBool::new(false);
3650pub static LAYOUT_DIRTY: AtomicBool = AtomicBool::new(false);
3651type BatchQueue = OnceLock<std::sync::Mutex<Vec<Box<dyn FnOnce() + Send + Sync>>>>;
3652static BATCH_QUEUE: BatchQueue = OnceLock::new();
3653static STATE_WRITE_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
3656pub fn is_batching() -> bool {
3658 IS_BATCHING.load(Ordering::Acquire)
3659}
3660pub fn is_rendering() -> bool {
3662 IS_RENDERING.load(Ordering::Acquire)
3663}
3664pub fn begin_render_phase() {
3666 IS_RENDERING.store(true, Ordering::Release);
3667}
3668pub fn end_render_phase() {
3670 IS_RENDERING.store(false, Ordering::Release);
3671}
3672pub fn enqueue_batch_task(task: Box<dyn FnOnce() + Send + Sync>) {
3674 let mut queue = BATCH_QUEUE
3675 .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3676 .lock()
3677 .unwrap();
3678 queue.push(task);
3679}
3680pub fn batch<F: FnOnce()>(f: F) {
3684 if IS_BATCHING.swap(true, Ordering::AcqRel) {
3685 f();
3687 return;
3688 }
3689 f();
3690 IS_BATCHING.store(false, Ordering::Release);
3691 let mut queue = BATCH_QUEUE
3692 .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3693 .lock()
3694 .unwrap();
3695 let tasks: Vec<_> = queue.drain(..).collect();
3696 drop(queue);
3697 for task in tasks {
3698 task();
3699 }
3700}
3701pub fn get_system_state() -> Arc<arc_swap::ArcSwap<KnowledgeState>> {
3703 SYSTEM_STATE
3704 .get_or_init(|| Arc::new(arc_swap::ArcSwap::from_pointee(KnowledgeState::default())))
3705 .clone()
3706}
3707pub fn load_system_state() -> arc_swap::Guard<Arc<KnowledgeState>> {
3708 get_system_state().load()
3709}
3710pub fn update_system_state<F>(f: F)
3711where
3712 F: FnOnce(&KnowledgeState) -> KnowledgeState,
3713{
3714 let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3715 if is_rendering() {
3716 log::warn!(
3717 "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3718 );
3719 }
3720 LAYOUT_DIRTY.store(true, Ordering::SeqCst);
3721 let swap = get_system_state();
3722 let current = swap.load();
3723 let new_state = Arc::new(f(¤t));
3724 swap.store(Arc::clone(&new_state));
3725 #[cfg(not(target_arch = "wasm32"))]
3726 {
3727 let tvar = KNOWLEDGE_TVAR.get_or_init(|| stm::TVar::new((*new_state).clone()));
3728 stm::atomically(|tx| tvar.write(tx, (*new_state).clone()));
3729 }
3730}
3731pub fn transact_system_state<F>(f: F)
3732where
3733 F: Fn(&KnowledgeState) -> KnowledgeState,
3734{
3735 let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3736 #[cfg(not(target_arch = "wasm32"))]
3737 {
3738 if is_rendering() {
3739 log::warn!(
3740 "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3741 );
3742 }
3743 let tvar = KNOWLEDGE_TVAR
3744 .get_or_init(|| stm::TVar::new((**get_system_state().load()).clone()))
3745 .clone();
3746 let new_state = stm::atomically(move |tx| {
3747 let current = tvar.read(tx)?;
3748 let next = f(¤t);
3749 tvar.write(tx, next.clone())?;
3750 Ok(next)
3751 });
3752 get_system_state().store(Arc::new(new_state));
3753 }
3754 #[cfg(target_arch = "wasm32")]
3755 {
3756 if is_rendering() {
3757 log::warn!(
3758 "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3759 );
3760 }
3761 update_system_state(f);
3762 }
3763}
3764impl KnowledgeState {
3765 pub fn new() -> Self {
3767 Self::default()
3768 }
3769 pub fn set_component_state<T: 'static + Send + Sync>(&mut self, id: u64, state: T) {
3771 self.component_states
3772 .insert(id, Arc::new(std::sync::RwLock::new(state)));
3773 }
3774 pub fn get_component_state<T: 'static + Send + Sync>(
3776 &self,
3777 id: u64,
3778 ) -> Option<Arc<std::sync::RwLock<T>>> {
3779 let lock = self.component_states.get(&id)?;
3780 let any_ref = lock.read().ok()?;
3784 if any_ref.is::<T>() {
3785 drop(any_ref);
3787 let cloned: Arc<std::sync::RwLock<dyn std::any::Any + Send + Sync>> = Arc::clone(lock);
3788 Some(unsafe {
3791 let raw = Arc::into_raw(cloned);
3792 Arc::from_raw(raw as *const std::sync::RwLock<T>)
3793 })
3794 } else {
3795 None
3796 }
3797 }
3798 pub fn remember(&mut self, fragment: KnowledgeFragment) {
3800 self.fragments.insert(fragment.id.clone(), fragment);
3801 }
3802 pub fn process_query(&mut self, query: &str) {
3804 let query_lower = query.to_lowercase();
3805 let mut results: Vec<(f32, String)> = self
3806 .fragments
3807 .iter()
3808 .map(|(id, frag)| {
3809 let mut score = 0.0;
3810 if frag.summary.to_lowercase().contains(&query_lower) {
3811 score += 1.0;
3812 }
3813 if frag.source.to_lowercase().contains(&query_lower) {
3814 score += 0.5;
3815 }
3816 (score, id.clone())
3817 })
3818 .filter(|(score, _)| *score > 0.0)
3819 .collect();
3820 results.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
3822 self.last_query_results = results.into_iter().map(|(_, id)| id).take(5).collect();
3823 }
3824 pub fn snapshot(&self) -> Vec<NodeStateSnapshot> {
3826 let mut snapshots = Vec::new();
3827 for frag in self.fragments.values() {
3829 if let Ok(val) = serde_json::to_value(frag) {
3830 snapshots.push(NodeStateSnapshot { id: 0, state: val });
3831 }
3832 }
3833 snapshots
3834 }
3835}
3836#[derive(Clone)]
3838pub struct Binding<T: Clone + Send + Sync + 'static> {
3839 swap: Arc<arc_swap::ArcSwap<T>>,
3840 #[cfg(not(target_arch = "wasm32"))]
3841 tvar: Arc<stm::TVar<T>>,
3842 version: Arc<std::sync::atomic::AtomicU64>,
3843}
3844impl<T: Clone + Send + Sync + 'static> Binding<T> {
3845 pub fn from_state(state: &State<T>) -> Self {
3847 Self {
3848 swap: Arc::clone(&state.swap),
3849 #[cfg(not(target_arch = "wasm32"))]
3850 tvar: Arc::clone(&state.tvar),
3851 version: Arc::clone(&state.version),
3852 }
3853 }
3854 pub fn get(&self) -> T {
3856 (**self.swap.load()).clone()
3857 }
3858 pub fn set(&self, value: T) {
3860 self.swap.store(Arc::new(value.clone()));
3861 #[cfg(not(target_arch = "wasm32"))]
3862 {
3863 let tvar = Arc::clone(&self.tvar);
3864 let v = value.clone();
3865 stm::atomically(move |tx| tvar.write(tx, v.clone()));
3866 }
3867 self.version
3868 .fetch_add(1, std::sync::atomic::Ordering::Release);
3869 }
3870 pub fn version(&self) -> u64 {
3872 self.version.load(std::sync::atomic::Ordering::Acquire)
3873 }
3874}
3875#[cfg(not(target_arch = "wasm32"))]
3876pub fn transact_pair<A, B, F>(state_a: &State<A>, state_b: &State<B>, f: F)
3877where
3878 A: Clone + Send + Sync + 'static,
3879 B: Clone + Send + Sync + 'static,
3880 F: Fn(&A, &B) -> (A, B),
3881{
3882 let tvar_a = Arc::clone(&state_a.tvar);
3883 let tvar_b = Arc::clone(&state_b.tvar);
3884 let (new_a, new_b) = stm::atomically(move |tx| {
3885 let a = tvar_a.read(tx)?;
3886 let b = tvar_b.read(tx)?;
3887 let (na, nb) = f(&a, &b);
3888 tvar_a.write(tx, na.clone())?;
3889 tvar_b.write(tx, nb.clone())?;
3890 Ok((na, nb))
3891 });
3892 state_a.swap.store(Arc::new(new_a.clone()));
3893 state_b.swap.store(Arc::new(new_b.clone()));
3894 state_a
3895 .version
3896 .fetch_add(1, std::sync::atomic::Ordering::Release);
3897 state_b
3898 .version
3899 .fetch_add(1, std::sync::atomic::Ordering::Release);
3900 let subs_a = Arc::clone(&state_a.subscribers);
3901 let subs_b = Arc::clone(&state_b.subscribers);
3902 if crate::is_batching() {
3903 crate::enqueue_batch_task(Box::new(move || {
3904 {
3905 let s = subs_a.lock().unwrap();
3906 for cb in s.iter() {
3907 cb(&new_a);
3908 }
3909 }
3910 {
3911 let s = subs_b.lock().unwrap();
3912 for cb in s.iter() {
3913 cb(&new_b);
3914 }
3915 }
3916 }));
3917 } else {
3918 {
3919 let s = subs_a.lock().unwrap();
3920 for cb in s.iter() {
3921 cb(&new_a);
3922 }
3923 }
3924 {
3925 let s = subs_b.lock().unwrap();
3926 for cb in s.iter() {
3927 cb(&new_b);
3928 }
3929 }
3930 }
3931}
3932use std::any::TypeId;
3933use std::sync::Mutex;
3934pub(crate) static ENVIRONMENT: OnceLock<
3936 Mutex<HashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>>,
3937> = OnceLock::new();
3938pub trait EnvKey: 'static + Send + Sync {
3941 type Value: Clone + Send + Sync + 'static;
3943 fn default_value() -> Self::Value;
3945}
3946pub struct YggdrasilKey;
3948impl EnvKey for YggdrasilKey {
3949 type Value = YggdrasilTokens;
3950 fn default_value() -> Self::Value {
3951 default_tokens()
3952 }
3953}
3954#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3957pub enum Appearance {
3958 Light,
3959 Dark,
3960}
3961#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3963pub enum Orientation {
3964 Horizontal,
3965 Vertical,
3966}
3967#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3969pub struct GridPlacement {
3970 pub column: i32,
3972 pub column_span: u32,
3974 pub row: i32,
3976 pub row_span: u32,
3978}
3979#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3981pub enum Alignment {
3982 #[default]
3983 Center,
3984 Leading,
3985 Trailing,
3986 Top,
3987 Bottom,
3988}
3989#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3991pub enum Distribution {
3992 #[default]
3993 Fill,
3994 Center,
3995 Leading,
3996 Trailing,
3997 SpaceBetween,
3998 SpaceAround,
3999 SpaceEvenly,
4000}
4001#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
4003pub struct Color {
4004 pub r: f32,
4005 pub g: f32,
4006 pub b: f32,
4007 pub a: f32,
4008}
4009impl Color {
4010 pub const BLACK: Color = Color {
4011 r: 0.0,
4012 g: 0.0,
4013 b: 0.0,
4014 a: 1.0,
4015 };
4016 pub const WHITE: Color = Color {
4017 r: 1.0,
4018 g: 1.0,
4019 b: 1.0,
4020 a: 1.0,
4021 };
4022 pub const TRANSPARENT: Color = Color {
4023 r: 0.0,
4024 g: 0.0,
4025 b: 0.0,
4026 a: 0.0,
4027 };
4028 pub const RED: Color = Color {
4029 r: 1.0,
4030 g: 0.0,
4031 b: 0.0,
4032 a: 1.0,
4033 };
4034 pub const GREEN: Color = Color {
4035 r: 0.0,
4036 g: 1.0,
4037 b: 0.0,
4038 a: 1.0,
4039 };
4040 pub const BLUE: Color = Color {
4041 r: 0.0,
4042 g: 0.0,
4043 b: 1.0,
4044 a: 1.0,
4045 };
4046 pub const VIKING_GOLD: Color = Color {
4047 r: 1.0,
4048 g: 0.84,
4049 b: 0.0,
4050 a: 1.0,
4051 };
4052 pub const MAGENTA_LIQUID: Color = Color {
4053 r: 1.0,
4054 g: 0.0,
4055 b: 1.0,
4056 a: 1.0,
4057 };
4058 pub const TACTICAL_OBSIDIAN: Color = Color {
4059 r: 0.05,
4060 g: 0.05,
4061 b: 0.07,
4062 a: 1.0,
4063 };
4064 pub fn relative_luminance(&self) -> f32 {
4066 fn res(c: f32) -> f32 {
4067 if c <= 0.03928 {
4068 c / 12.92
4069 } else {
4070 ((c + 0.055) / 1.055).powf(2.4)
4071 }
4072 }
4073 0.2126 * res(self.r) + 0.7152 * res(self.g) + 0.0722 * res(self.b)
4074 }
4075 pub fn contrast_ratio(&self, other: &Color) -> f32 {
4077 let l1 = self.relative_luminance();
4078 let l2 = other.relative_luminance();
4079 if l1 > l2 {
4080 (l1 + 0.05) / (l2 + 0.05)
4081 } else {
4082 (l2 + 0.05) / (l1 + 0.05)
4083 }
4084 }
4085 pub const CYAN: Color = Color {
4086 r: 0.0,
4087 g: 1.0,
4088 b: 1.0,
4089 a: 1.0,
4090 };
4091 pub const YELLOW: Color = Color {
4092 r: 1.0,
4093 g: 1.0,
4094 b: 0.0,
4095 a: 1.0,
4096 };
4097 pub const MAGENTA: Color = Color {
4098 r: 1.0,
4099 g: 0.0,
4100 b: 1.0,
4101 a: 1.0,
4102 };
4103 pub const GRAY: Color = Color {
4104 r: 0.5,
4105 g: 0.5,
4106 b: 0.5,
4107 a: 1.0,
4108 };
4109 pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
4111 Self { r, g, b, a }
4112 }
4113 pub fn as_array(&self) -> [f32; 4] {
4115 [self.r, self.g, self.b, self.a]
4116 }
4117
4118 pub fn lighten(&self, amount: f32) -> Self {
4124 Self {
4125 r: (self.r + amount).clamp(0.0, 1.0),
4126 g: (self.g + amount).clamp(0.0, 1.0),
4127 b: (self.b + amount).clamp(0.0, 1.0),
4128 a: self.a,
4129 }
4130 }
4131
4132 pub fn darken(&self, amount: f32) -> Self {
4134 Self {
4135 r: (self.r - amount).clamp(0.0, 1.0),
4136 g: (self.g - amount).clamp(0.0, 1.0),
4137 b: (self.b - amount).clamp(0.0, 1.0),
4138 a: self.a,
4139 }
4140 }
4141}
4142impl View for Color {
4143 type Body = Never;
4144 fn body(self) -> Self::Body {
4145 unreachable!()
4146 }
4147 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
4148 renderer.fill_rect(rect, self.as_array());
4149 }
4150}
4151pub struct AppearanceKey;
4153impl EnvKey for AppearanceKey {
4154 type Value = Appearance;
4155 fn default_value() -> Self::Value {
4156 Appearance::Dark }
4158}
4159pub struct StyleResolver;
4161impl StyleResolver {
4162 pub fn color(key: &str) -> String {
4164 let tokens = Environment::<YggdrasilKey>::new().get();
4165 let appearance = Environment::<AppearanceKey>::new().get();
4166 let is_dark = appearance == Appearance::Dark;
4167 tokens
4168 .get_color(key, is_dark)
4169 .unwrap_or_else(|| "#FF00FF".to_string()) }
4171 pub fn get<T: FromStr>(category: &str, key: &str) -> Option<T> {
4173 let tokens = Environment::<YggdrasilKey>::new().get();
4174 let appearance = Environment::<AppearanceKey>::new().get();
4175 let is_dark = appearance == Appearance::Dark;
4176 tokens.get(category, key, is_dark)
4177 }
4178 pub fn color_array(key: &str) -> [f32; 4] {
4182 let hex = Self::color(key);
4183 parse_hex_color(&hex)
4184 }
4185}
4186
4187fn parse_hex_color(hex: &str) -> [f32; 4] {
4189 let hex = hex.trim_start_matches('#');
4190 if hex.len() >= 6 {
4191 let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(255) as f32 / 255.0;
4192 let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0) as f32 / 255.0;
4193 let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(255) as f32 / 255.0;
4194 let a = if hex.len() >= 8 {
4195 u8::from_str_radix(&hex[6..8], 16).unwrap_or(255) as f32 / 255.0
4196 } else {
4197 1.0
4198 };
4199 [r, g, b, a]
4200 } else {
4201 [1.0, 0.0, 1.0, 1.0] }
4203}
4204
4205pub fn default_tokens() -> YggdrasilTokens {
4207 let mut tokens = YggdrasilTokens::new();
4208 tokens.color.insert(
4210 "background".to_string(),
4211 TokenValue::Single {
4212 value: "#000000".to_string(), },
4214 );
4215 tokens.color.insert(
4216 "primary".to_string(),
4217 TokenValue::Single {
4218 value: "#00FFFF".to_string(), },
4220 );
4221 tokens.color.insert(
4222 "secondary".to_string(),
4223 TokenValue::Single {
4224 value: "#FF00FF".to_string(), },
4226 );
4227 tokens.color.insert(
4228 "surface".to_string(),
4229 TokenValue::Adaptive {
4230 light: "#FFFFFF".to_string(),
4231 dark: "#121212".to_string(),
4232 },
4233 );
4234 tokens.color.insert(
4235 "text".to_string(),
4236 TokenValue::Adaptive {
4237 light: "#000000".to_string(),
4238 dark: "#FFFFFF".to_string(),
4239 },
4240 );
4241 tokens.color.insert(
4243 "surface_elevated".to_string(),
4244 TokenValue::Adaptive {
4245 light: "#FFFFFF".to_string(),
4246 dark: "#1A1A24".to_string(),
4247 },
4248 );
4249 tokens.color.insert(
4250 "surface_overlay".to_string(),
4251 TokenValue::Adaptive {
4252 light: "#FFFFFF".to_string(),
4253 dark: "#1E1E2E".to_string(),
4254 },
4255 );
4256 tokens.color.insert(
4257 "border".to_string(),
4258 TokenValue::Adaptive {
4259 light: "#D0D0D8".to_string(),
4260 dark: "#2A2A3A".to_string(),
4261 },
4262 );
4263 tokens.color.insert(
4264 "border_strong".to_string(),
4265 TokenValue::Adaptive {
4266 light: "#A0A0B0".to_string(),
4267 dark: "#3A3A50".to_string(),
4268 },
4269 );
4270 tokens.color.insert(
4271 "text_muted".to_string(),
4272 TokenValue::Adaptive {
4273 light: "#606070".to_string(),
4274 dark: "#8080A0".to_string(),
4275 },
4276 );
4277 tokens.color.insert(
4278 "text_dim".to_string(),
4279 TokenValue::Adaptive {
4280 light: "#9090A0".to_string(),
4281 dark: "#505070".to_string(),
4282 },
4283 );
4284 tokens.color.insert(
4285 "accent".to_string(),
4286 TokenValue::Single {
4287 value: "#00FFFF".to_string(), },
4289 );
4290 tokens.color.insert(
4291 "accent_hover".to_string(),
4292 TokenValue::Single {
4293 value: "#33FFFF".to_string(),
4294 },
4295 );
4296 tokens.color.insert(
4297 "success".to_string(),
4298 TokenValue::Single {
4299 value: "#00E676".to_string(),
4300 },
4301 );
4302 tokens.color.insert(
4303 "warning".to_string(),
4304 TokenValue::Single {
4305 value: "#FFB300".to_string(),
4306 },
4307 );
4308 tokens.color.insert(
4309 "error".to_string(),
4310 TokenValue::Single {
4311 value: "#FF5252".to_string(),
4312 },
4313 );
4314 tokens.color.insert(
4315 "info".to_string(),
4316 TokenValue::Single {
4317 value: "#448AFF".to_string(),
4318 },
4319 );
4320 tokens.color.insert(
4321 "hover".to_string(),
4322 TokenValue::Adaptive {
4323 light: "#F0F0F5".to_string(),
4324 dark: "#252535".to_string(),
4325 },
4326 );
4327 tokens.color.insert(
4328 "active".to_string(),
4329 TokenValue::Adaptive {
4330 light: "#E0E0EB".to_string(),
4331 dark: "#303045".to_string(),
4332 },
4333 );
4334 tokens.color.insert(
4335 "disabled".to_string(),
4336 TokenValue::Adaptive {
4337 light: "#E8E8F0".to_string(),
4338 dark: "#1A1A28".to_string(),
4339 },
4340 );
4341 tokens.color.insert(
4342 "disabled_text".to_string(),
4343 TokenValue::Adaptive {
4344 light: "#B0B0C0".to_string(),
4345 dark: "#404060".to_string(),
4346 },
4347 );
4348 tokens.color.insert(
4349 "focus_ring".to_string(),
4350 TokenValue::Single {
4351 value: "#00FFFF".to_string(),
4352 },
4353 );
4354 tokens.color.insert(
4355 "shadow".to_string(),
4356 TokenValue::Adaptive {
4357 light: "#00000020".to_string(),
4358 dark: "#00000060".to_string(),
4359 },
4360 );
4361 tokens.color.insert(
4362 "code_bg".to_string(),
4363 TokenValue::Adaptive {
4364 light: "#F5F5FA".to_string(),
4365 dark: "#0D0D18".to_string(),
4366 },
4367 );
4368 tokens.bifrost.insert(
4370 "blur".to_string(),
4371 TokenValue::Single {
4372 value: "25.0".to_string(),
4373 },
4374 );
4375 tokens.bifrost.insert(
4376 "saturation".to_string(),
4377 TokenValue::Single {
4378 value: "1.2".to_string(),
4379 },
4380 );
4381 tokens.bifrost.insert(
4382 "opacity".to_string(),
4383 TokenValue::Single {
4384 value: "0.65".to_string(),
4385 },
4386 );
4387 tokens.gungnir.insert(
4389 "intensity".to_string(),
4390 TokenValue::Single {
4391 value: "1.0".to_string(),
4392 },
4393 );
4394 tokens.gungnir.insert(
4395 "radius".to_string(),
4396 TokenValue::Single {
4397 value: "15.0".to_string(),
4398 },
4399 );
4400 tokens.mjolnir.insert(
4402 "clip_angle".to_string(),
4403 TokenValue::Single {
4404 value: "12.0".to_string(),
4405 },
4406 );
4407 tokens.mjolnir.insert(
4408 "border_width".to_string(),
4409 TokenValue::Single {
4410 value: "2.0".to_string(),
4411 },
4412 );
4413 tokens.anim.insert(
4415 "stiffness".to_string(),
4416 TokenValue::Single {
4417 value: "170.0".to_string(),
4418 },
4419 );
4420 tokens.anim.insert(
4421 "damping".to_string(),
4422 TokenValue::Single {
4423 value: "26.0".to_string(),
4424 },
4425 );
4426 tokens.anim.insert(
4427 "mass".to_string(),
4428 TokenValue::Single {
4429 value: "1.0".to_string(),
4430 },
4431 );
4432 tokens.accessibility.insert(
4434 "reduce_motion".to_string(),
4435 TokenValue::Single {
4436 value: "false".to_string(),
4437 },
4438 );
4439 tokens
4440}
4441pub struct Environment<K: EnvKey> {
4443 _marker: std::marker::PhantomData<K>,
4444}
4445impl<K: EnvKey> Default for Environment<K> {
4446 fn default() -> Self {
4447 Self::new()
4448 }
4449}
4450impl<K: EnvKey> Environment<K> {
4451 pub fn new() -> Self {
4453 Self {
4454 _marker: std::marker::PhantomData,
4455 }
4456 }
4457 pub fn get(&self) -> K::Value {
4459 if let Some(env_store) = ENVIRONMENT.get() {
4460 let env_lock = env_store.lock().unwrap();
4461 if let Some(val) = env_lock.get(&std::any::TypeId::of::<K>()) {
4462 if let Some(typed_val) = val.downcast_ref::<K::Value>() {
4463 return typed_val.clone();
4464 } else {
4465 log::warn!(
4466 "Environment: Downcast failed for key type {:?}",
4467 std::any::type_name::<K>()
4468 );
4469 }
4470 } else {
4471 log::debug!(
4472 "Environment: Key not found: {:?}. Returning default.",
4473 std::any::type_name::<K>()
4474 );
4475 }
4476 } else {
4477 log::debug!(
4478 "Environment: Store not initialized. Key: {:?}. Returning default.",
4479 std::any::type_name::<K>()
4480 );
4481 }
4482 K::default_value()
4483 }
4484}
4485pub mod env {
4487 pub fn insert<K: super::EnvKey>(value: K::Value) {
4489 let store = super::ENVIRONMENT
4490 .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
4491 let mut env_map = store.lock().unwrap();
4492 env_map.insert(std::any::TypeId::of::<K>(), Box::new(value));
4493 }
4494 pub fn remove<K: super::EnvKey>() {
4496 if let Some(store) = super::ENVIRONMENT.get() {
4497 let mut env_map = store.lock().unwrap();
4498 env_map.remove(&std::any::TypeId::of::<K>());
4499 }
4500 }
4501}
4502#[derive(Debug, Clone, Copy, PartialEq)]
4505pub struct Size {
4506 pub width: f32,
4507 pub height: f32,
4508}
4509
4510impl Size {
4511 pub const ZERO: Self = Self {
4512 width: 0.0,
4513 height: 0.0,
4514 };
4515
4516 pub fn new(width: f32, height: f32) -> Self {
4517 Self { width, height }
4518 }
4519}
4520
4521#[derive(Debug, Clone, Copy, PartialEq)]
4523pub struct EdgeInsets {
4524 pub top: f32,
4525 pub leading: f32,
4526 pub bottom: f32,
4527 pub trailing: f32,
4528}
4529
4530impl EdgeInsets {
4531 pub fn all(value: f32) -> Self {
4533 Self {
4534 top: value,
4535 leading: value,
4536 bottom: value,
4537 trailing: value,
4538 }
4539 }
4540
4541 pub fn vertical(value: f32) -> Self {
4543 Self {
4544 top: value,
4545 leading: 0.0,
4546 bottom: value,
4547 trailing: 0.0,
4548 }
4549 }
4550
4551 pub fn horizontal(value: f32) -> Self {
4553 Self {
4554 top: 0.0,
4555 leading: value,
4556 bottom: 0.0,
4557 trailing: value,
4558 }
4559 }
4560}
4561
4562#[derive(Debug, Clone, Copy, PartialEq)]
4566pub struct FrameModifier {
4567 pub width: Option<f32>,
4569 pub height: Option<f32>,
4571 pub min_width: Option<f32>,
4573 pub max_width: Option<f32>,
4575 pub min_height: Option<f32>,
4577 pub max_height: Option<f32>,
4579 pub alignment: Alignment,
4581}
4582
4583impl Default for FrameModifier {
4584 fn default() -> Self {
4586 Self::new()
4587 }
4588}
4589
4590impl FrameModifier {
4591 pub fn new() -> Self {
4593 Self {
4594 width: None,
4595 height: None,
4596 min_width: None,
4597 max_width: None,
4598 min_height: None,
4599 max_height: None,
4600 alignment: Alignment::Center,
4601 }
4602 }
4603
4604 pub fn width(mut self, width: f32) -> Self {
4606 self.width = Some(width);
4607 self
4608 }
4609
4610 pub fn height(mut self, height: f32) -> Self {
4612 self.height = Some(height);
4613 self
4614 }
4615
4616 pub fn size(mut self, width: f32, height: f32) -> Self {
4618 self.width = Some(width);
4619 self.height = Some(height);
4620 self
4621 }
4622
4623 pub fn min_width(mut self, min_width: f32) -> Self {
4625 self.min_width = Some(min_width);
4626 self
4627 }
4628
4629 pub fn max_width(mut self, max_width: f32) -> Self {
4631 self.max_width = Some(max_width);
4632 self
4633 }
4634
4635 pub fn min_height(mut self, min_height: f32) -> Self {
4637 self.min_height = Some(min_height);
4638 self
4639 }
4640
4641 pub fn max_height(mut self, max_height: f32) -> Self {
4643 self.max_height = Some(max_height);
4644 self
4645 }
4646
4647 pub fn alignment(mut self, alignment: Alignment) -> Self {
4649 self.alignment = alignment;
4650 self
4651 }
4652}
4653
4654impl ViewModifier for FrameModifier {
4655 fn modify<V: View>(self, content: V) -> impl View {
4657 ModifiedView::new(content, self)
4658 }
4659
4660 fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
4662 let w = if let Some(width) = self.width {
4663 Some(width)
4664 } else {
4665 proposal.width.map(|pw| {
4666 pw.clamp(
4667 self.min_width.unwrap_or(0.0),
4668 self.max_width.unwrap_or(f32::INFINITY),
4669 )
4670 })
4671 };
4672 let h = if let Some(height) = self.height {
4673 Some(height)
4674 } else {
4675 proposal.height.map(|ph| {
4676 ph.clamp(
4677 self.min_height.unwrap_or(0.0),
4678 self.max_height.unwrap_or(f32::INFINITY),
4679 )
4680 })
4681 };
4682 SizeProposal {
4683 width: w,
4684 height: h,
4685 }
4686 }
4687
4688 fn transform_size(&self, child_size: Size) -> Size {
4690 let w = if let Some(width) = self.width {
4691 width
4692 } else {
4693 child_size.width.clamp(
4694 self.min_width.unwrap_or(0.0),
4695 self.max_width.unwrap_or(f32::INFINITY),
4696 )
4697 };
4698 let h = if let Some(height) = self.height {
4699 height
4700 } else {
4701 child_size.height.clamp(
4702 self.min_height.unwrap_or(0.0),
4703 self.max_height.unwrap_or(f32::INFINITY),
4704 )
4705 };
4706 Size {
4707 width: w,
4708 height: h,
4709 }
4710 }
4711
4712 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4714 self.render(renderer, rect);
4715 let child_proposal =
4716 self.transform_proposal(SizeProposal::new(Some(rect.width), Some(rect.height)));
4717 let child_size = view.intrinsic_size(renderer, child_proposal);
4718
4719 let mut child_x = rect.x;
4720 let mut child_y = rect.y;
4721
4722 match self.alignment {
4723 Alignment::Leading => {
4724 child_y = rect.y + (rect.height - child_size.height) / 2.0;
4725 }
4726 Alignment::Trailing => {
4727 child_x = rect.x + rect.width - child_size.width;
4728 child_y = rect.y + (rect.height - child_size.height) / 2.0;
4729 }
4730 Alignment::Top => {
4731 child_x = rect.x + (rect.width - child_size.width) / 2.0;
4732 }
4733 Alignment::Bottom => {
4734 child_x = rect.x + (rect.width - child_size.width) / 2.0;
4735 child_y = rect.y + rect.height - child_size.height;
4736 }
4737 Alignment::Center => {
4738 child_x = rect.x + (rect.width - child_size.width) / 2.0;
4739 child_y = rect.y + (rect.height - child_size.height) / 2.0;
4740 }
4741 }
4742
4743 let child_rect = Rect {
4744 x: child_x,
4745 y: child_y,
4746 width: child_size.width,
4747 height: child_size.height,
4748 };
4749
4750 view.render(renderer, child_rect);
4751 self.post_render(renderer, rect);
4752 }
4753}
4754
4755#[derive(Debug, Clone, Copy, PartialEq)]
4757pub struct FlexModifier {
4758 pub weight: f32,
4759}
4760
4761impl ViewModifier for FlexModifier {
4762 fn modify<V: View>(self, content: V) -> impl View {
4763 ModifiedView::new(content, self)
4764 }
4765
4766 fn child_flex_weight<V: View>(&self, _view: &V) -> f32 {
4767 self.weight
4768 }
4769}
4770
4771#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4773pub struct GridPlacementModifier {
4774 pub placement: GridPlacement,
4776}
4777
4778impl ViewModifier for GridPlacementModifier {
4779 fn modify<V: View>(self, content: V) -> impl View {
4781 ModifiedView::new(content, self)
4782 }
4783
4784 fn get_grid_placement(&self) -> Option<GridPlacement> {
4786 Some(self.placement)
4787 }
4788}
4789
4790#[derive(Clone)]
4793pub struct OverlayModifier {
4794 pub overlay: AnyView,
4796 pub alignment: Alignment,
4798 pub offset: [f32; 2],
4800 pub on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
4802}
4803
4804impl ViewModifier for OverlayModifier {
4805 fn modify<V: View>(self, content: V) -> impl View {
4807 ModifiedView::new(content, self)
4808 }
4809
4810 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4812 view.render(renderer, rect);
4814
4815 let overlay_size = self
4817 .overlay
4818 .intrinsic_size(renderer, SizeProposal::unspecified());
4819
4820 let mut overlay_x;
4822 let mut overlay_y;
4823
4824 match self.alignment {
4825 Alignment::Leading => {
4826 overlay_x = rect.x - overlay_size.width;
4827 overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4828 }
4829 Alignment::Trailing => {
4830 overlay_x = rect.x + rect.width;
4831 overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4832 }
4833 Alignment::Top => {
4834 overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4835 overlay_y = rect.y - overlay_size.height;
4836 }
4837 Alignment::Bottom => {
4838 overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4839 overlay_y = rect.y + rect.height;
4840 }
4841 Alignment::Center => {
4842 overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4843 overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4844 }
4845 }
4846
4847 overlay_x += self.offset[0];
4848 overlay_y += self.offset[1];
4849
4850 let overlay_rect = Rect {
4851 x: overlay_x,
4852 y: overlay_y,
4853 width: overlay_size.width,
4854 height: overlay_size.height,
4855 };
4856
4857 if let Some(on_dismiss) = &self.on_dismiss {
4859 let dismiss = on_dismiss.clone();
4860 renderer.register_handler(
4861 "pointerdown",
4862 Arc::new(move |event| {
4863 if let Event::PointerDown { x, y, .. } = event {
4864 let click_inside = x >= overlay_rect.x
4865 && x <= overlay_rect.x + overlay_rect.width
4866 && y >= overlay_rect.y
4867 && y <= overlay_rect.y + overlay_rect.height;
4868 if !click_inside {
4869 dismiss();
4870 }
4871 }
4872 }),
4873 );
4874 }
4875
4876 self.overlay.render(renderer, overlay_rect);
4878 }
4879}
4880
4881#[derive(Debug, Clone, Copy, PartialEq)]
4883pub struct OffsetModifier {
4884 pub x: f32,
4885 pub y: f32,
4886}
4887
4888impl OffsetModifier {
4889 pub fn new(x: f32, y: f32) -> Self {
4890 Self { x, y }
4891 }
4892}
4893
4894impl ViewModifier for OffsetModifier {
4895 fn modify<V: View>(self, content: V) -> impl View {
4896 ModifiedView::new(content, self)
4897 }
4898}
4899
4900#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4902pub struct ZIndexModifier {
4903 pub z_index: i32,
4904}
4905
4906impl ZIndexModifier {
4907 pub fn new(z_index: i32) -> Self {
4908 Self { z_index }
4909 }
4910}
4911
4912impl ViewModifier for ZIndexModifier {
4913 fn modify<V: View>(self, content: V) -> impl View {
4914 ModifiedView::new(content, self)
4915 }
4916}
4917
4918#[derive(Debug, Clone, Copy, PartialEq, Default)]
4920pub struct LayoutConstraints {
4921 pub min_width: Option<f32>,
4922 pub max_width: Option<f32>,
4923 pub min_height: Option<f32>,
4924 pub max_height: Option<f32>,
4925}
4926
4927#[derive(Debug, Clone, Copy, PartialEq)]
4929pub struct LayoutModifier {
4930 pub constraints: LayoutConstraints,
4931}
4932
4933impl LayoutModifier {
4934 pub fn new(constraints: LayoutConstraints) -> Self {
4935 Self { constraints }
4936 }
4937}
4938
4939impl ViewModifier for LayoutModifier {
4940 fn modify<V: View>(self, content: V) -> impl View {
4941 ModifiedView::new(content, self)
4942 }
4943}
4944
4945#[derive(Debug, Clone, Copy, PartialEq)]
4947pub struct SafeAreaModifier {
4948 pub ignores: bool,
4949}
4950
4951impl ViewModifier for SafeAreaModifier {
4952 fn modify<V: View>(self, content: V) -> impl View {
4953 ModifiedView::new(content, self)
4954 }
4955}
4956
4957#[derive(Debug, Clone, Copy, PartialEq)]
4959pub struct ElevationModifier {
4960 pub level: f32,
4961}
4962
4963impl ViewModifier for ElevationModifier {
4964 fn modify<V: View>(self, content: V) -> impl View {
4965 ModifiedView::new(content, self)
4966 }
4967
4968 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4969 if self.level > 0.0 {
4970 let radius = self.level * 2.0;
4971 let offset_y = self.level * 0.5;
4972 let shadow_color = [0.0, 0.0, 0.0, 0.3];
4973 renderer.push_shadow(radius, shadow_color, [0.0, offset_y]);
4974 view.render(renderer, rect);
4975 renderer.pop_shadow();
4976 } else {
4977 view.render(renderer, rect);
4978 }
4979 }
4980}
4981
4982pub mod layout {
4984 use super::*;
4985
4986 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4989 pub struct LayoutKey {
4990 pub view_hash: u64,
4991 pub generation: u64,
4992 }
4993
4994 pub struct LayoutCache {
4996 pub safe_area: SafeArea,
4997 size_cache: HashMap<(u64, u32, u32), Size>, generation: u64,
5002 }
5003
5004 impl Default for LayoutCache {
5005 fn default() -> Self {
5006 Self::new()
5007 }
5008 }
5009
5010 impl LayoutCache {
5011 pub fn new() -> Self {
5012 Self {
5013 safe_area: SafeArea::default(),
5014 size_cache: HashMap::new(),
5015 generation: 0,
5016 }
5017 }
5018
5019 pub fn generation(&self) -> u64 {
5021 self.generation
5022 }
5023
5024 pub fn invalidate(&mut self) {
5028 self.generation = self.generation.wrapping_add(1);
5029 }
5030
5031 pub fn is_valid(&self, key: LayoutKey, current_gen: u64) -> bool {
5034 key.generation == current_gen && key.generation == self.generation
5035 }
5036
5037 pub fn clear(&mut self) {
5038 self.safe_area = SafeArea::default();
5039 self.size_cache.clear();
5040 }
5041
5042 pub fn get_size(&self, view_hash: u64, proposal: SizeProposal) -> Option<Size> {
5043 let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
5044 let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
5045 self.size_cache.get(&(view_hash, pw, ph)).copied()
5046 }
5047
5048 pub fn set_size(&mut self, view_hash: u64, proposal: SizeProposal, size: Size) {
5049 let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
5050 let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
5051 self.size_cache.insert((view_hash, pw, ph), size);
5052 }
5053
5054 pub fn invalidate_view(&mut self, view_hash: u64) {
5056 self.size_cache.retain(|&(hash, _, _), _| hash != view_hash);
5057 }
5058 }
5059
5060 #[derive(Debug, Clone, Copy, PartialEq)]
5062 pub struct SizeProposal {
5063 pub width: Option<f32>,
5064 pub height: Option<f32>,
5065 }
5066
5067 impl SizeProposal {
5068 pub fn unspecified() -> Self {
5069 Self {
5070 width: None,
5071 height: None,
5072 }
5073 }
5074
5075 pub fn width(width: f32) -> Self {
5076 Self {
5077 width: Some(width),
5078 height: None,
5079 }
5080 }
5081
5082 pub fn height(height: f32) -> Self {
5083 Self {
5084 width: None,
5085 height: Some(height),
5086 }
5087 }
5088
5089 pub fn tight(width: f32, height: f32) -> Self {
5090 Self {
5091 width: Some(width),
5092 height: Some(height),
5093 }
5094 }
5095
5096 pub fn new(width: Option<f32>, height: Option<f32>) -> Self {
5097 Self { width, height }
5098 }
5099 }
5100
5101 pub trait LayoutView: Send {
5103 fn size_that_fits(
5105 &self,
5106 proposal: SizeProposal,
5107 subviews: &[&dyn LayoutView],
5108 cache: &mut LayoutCache,
5109 ) -> Size;
5110
5111 fn place_subviews(
5113 &self,
5114 bounds: Rect,
5115 subviews: &mut [&mut dyn LayoutView],
5116 cache: &mut LayoutCache,
5117 );
5118
5119 fn flex_weight(&self) -> f32 {
5121 0.0
5122 }
5123
5124 fn debug_layout(&self, indent: usize) -> String {
5127 let prefix = " ".repeat(indent);
5128 format!("{}LayoutView", prefix)
5129 }
5130 }
5131 #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
5133 pub struct EdgeInsets {
5134 pub top: f32,
5135 pub leading: f32,
5136 pub bottom: f32,
5137 pub trailing: f32,
5138 }
5139
5140 impl EdgeInsets {
5141 pub fn new(top: f32, leading: f32, bottom: f32, trailing: f32) -> Self {
5142 Self {
5143 top,
5144 leading,
5145 bottom,
5146 trailing,
5147 }
5148 }
5149
5150 pub fn all(value: f32) -> Self {
5151 Self {
5152 top: value,
5153 leading: value,
5154 bottom: value,
5155 trailing: value,
5156 }
5157 }
5158 }
5159
5160 #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
5162 pub struct SafeArea {
5163 pub insets: EdgeInsets,
5164 }
5165
5166 #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
5168 pub enum SdfShape {
5169 Rect(Rect),
5170 RoundedRect { rect: Rect, radius: f32 },
5171 Circle { center: [f32; 2], radius: f32 },
5172 }
5173
5174 #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
5176 pub struct Rect {
5177 pub x: f32,
5178 pub y: f32,
5179 pub width: f32,
5180 pub height: f32,
5181 }
5182
5183 impl Rect {
5184 pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
5185 Self {
5186 x,
5187 y,
5188 width,
5189 height,
5190 }
5191 }
5192
5193 pub fn inset(&self, amount: f32) -> Self {
5194 Self {
5195 x: self.x + amount,
5196 y: self.y + amount,
5197 width: (self.width - amount * 2.0).max(0.0),
5198 height: (self.height - amount * 2.0).max(0.0),
5199 }
5200 }
5201
5202 pub fn offset(&self, dx: f32, dy: f32) -> Self {
5203 Self {
5204 x: self.x + dx,
5205 y: self.y + dy,
5206 ..*self
5207 }
5208 }
5209
5210 pub fn zero() -> Self {
5211 Self {
5212 x: 0.0,
5213 y: 0.0,
5214 width: 0.0,
5215 height: 0.0,
5216 }
5217 }
5218
5219 pub fn contains(&self, x: f32, y: f32) -> bool {
5220 x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
5221 }
5222
5223 pub fn size(&self) -> Size {
5224 Size {
5225 width: self.width,
5226 height: self.height,
5227 }
5228 }
5229
5230 pub fn split_horizontal(&self, n: usize) -> Vec<Rect> {
5232 if n == 0 {
5233 return vec![];
5234 }
5235 let item_width = self.width / n as f32;
5236 (0..n)
5237 .map(|i| Rect {
5238 x: self.x + i as f32 * item_width,
5239 y: self.y,
5240 width: item_width,
5241 height: self.height,
5242 })
5243 .collect()
5244 }
5245
5246 pub fn split_vertical(&self, n: usize) -> Vec<Rect> {
5248 if n == 0 {
5249 return vec![];
5250 }
5251 let item_height = self.height / n as f32;
5252 (0..n)
5253 .map(|i| Rect {
5254 x: self.x,
5255 y: self.y + i as f32 * item_height,
5256 width: self.width,
5257 height: item_height,
5258 })
5259 .collect()
5260 }
5261 }
5262}
5263
5264pub use layout::{LayoutCache, LayoutKey, LayoutView, Rect, SizeProposal};
5266pub mod agents;
5269pub mod animation;
5270pub mod gpu;
5271pub mod material;
5272pub mod runtime;
5273pub mod scene_graph;
5274pub mod sdf_shadow;
5275
5276pub use material::DrawMaterial;
5277pub use scene_graph::{NodeId, bifrost_registry};
5278
5279pub trait AssetManager: Send + Sync {
5283 fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>>;
5285
5286 fn preload_image(&self, url: &str);
5288}
5289
5290#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5292pub enum TouchPhase {
5293 Began,
5295 Moved,
5297 Ended,
5299 Cancelled,
5301}
5302
5303#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
5305pub enum Event {
5306 PointerDown {
5307 x: f32,
5308 y: f32,
5309 button: u32,
5310 proximity_field: f32,
5311 tilt: Option<f32>,
5312 azimuth: Option<f32>,
5313 pressure: Option<f32>,
5314 barrel_rotation: Option<f32>,
5315 },
5316 PointerUp {
5317 x: f32,
5318 y: f32,
5319 button: u32,
5320 tilt: Option<f32>,
5321 azimuth: Option<f32>,
5322 pressure: Option<f32>,
5323 barrel_rotation: Option<f32>,
5324 },
5325 PointerMove {
5326 x: f32,
5327 y: f32,
5328 proximity_field: f32,
5329 tilt: Option<f32>,
5330 azimuth: Option<f32>,
5331 pressure: Option<f32>,
5332 barrel_rotation: Option<f32>,
5333 },
5334 PointerClick {
5335 x: f32,
5336 y: f32,
5337 button: u32,
5338 tilt: Option<f32>,
5339 azimuth: Option<f32>,
5340 pressure: Option<f32>,
5341 barrel_rotation: Option<f32>,
5342 },
5343 PointerEnter,
5344 PointerLeave,
5345 PointerWheel {
5348 x: f32,
5349 y: f32,
5350 delta_x: f32,
5351 delta_y: f32,
5352 },
5353 PointerDoubleClick {
5355 x: f32,
5356 y: f32,
5357 button: u32,
5358 },
5359 DragStart {
5361 x: f32,
5362 y: f32,
5363 button: u32,
5364 },
5365 DragMove {
5367 x: f32,
5368 y: f32,
5369 },
5370 DragEnd {
5372 x: f32,
5373 y: f32,
5374 },
5375 KeyDown {
5376 key: String,
5377 },
5378 KeyUp {
5379 key: String,
5380 },
5381 FocusIn,
5383 FocusOut,
5385 Copy,
5387 Cut,
5389 Paste(String),
5391 Ime(String),
5393 TouchStart {
5395 x: f32,
5396 y: f32,
5397 touch_id: u64,
5398 },
5399 TouchMove {
5401 x: f32,
5402 y: f32,
5403 touch_id: u64,
5404 },
5405 TouchEnd {
5407 x: f32,
5408 y: f32,
5409 touch_id: u64,
5410 },
5411 TouchCancel {
5413 touch_id: u64,
5414 },
5415 GesturePinch {
5421 center: [f32; 2],
5422 scale: f32,
5423 velocity: f32,
5424 phase: TouchPhase,
5425 },
5426 GestureSwipe {
5431 direction: [f32; 2],
5432 velocity: f32,
5433 phase: TouchPhase,
5434 },
5435 FileDrop {
5437 path: String,
5438 },
5439}
5440
5441impl Event {
5442 pub fn name(&self) -> &'static str {
5444 match self {
5445 Self::PointerDown { .. } => "pointerdown",
5446 Self::PointerUp { .. } => "pointerup",
5447 Self::PointerMove { .. } => "pointermove",
5448 Self::PointerClick { .. } => "pointerclick",
5449 Self::PointerEnter => "pointerenter",
5450 Self::PointerLeave => "pointerleave",
5451 Self::PointerWheel { .. } => "pointerwheel",
5452 Self::PointerDoubleClick { .. } => "pointerdoubleclick",
5453 Self::DragStart { .. } => "dragstart",
5454 Self::DragMove { .. } => "dragmove",
5455 Self::DragEnd { .. } => "dragend",
5456 Self::KeyDown { .. } => "keydown",
5457 Self::KeyUp { .. } => "keyup",
5458 Self::FocusIn => "focusin",
5459 Self::FocusOut => "focusout",
5460 Self::Copy => "copy",
5461 Self::Cut => "cut",
5462 Self::Paste(_) => "paste",
5463 Self::Ime(_) => "ime",
5464 Self::TouchStart { .. } => "touchstart",
5465 Self::TouchMove { .. } => "touchmove",
5466 Self::TouchEnd { .. } => "touchend",
5467 Self::TouchCancel { .. } => "touchcancel",
5468 Self::GesturePinch { .. } => "gesturepinch",
5469 Self::GestureSwipe { .. } => "gestureswipe",
5470 Self::FileDrop { .. } => "filedrop",
5471 }
5472 }
5473}
5474
5475#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5477pub enum EventResponse {
5478 Handled,
5479 Ignored,
5480}
5481
5482pub struct DefaultAssetManager {
5484 cache: AssetCache,
5485}
5486type AssetCache = Arc<arc_swap::ArcSwap<HashMap<String, AssetState<Arc<Vec<u8>>>>>>;
5487
5488impl Default for DefaultAssetManager {
5489 fn default() -> Self {
5490 Self::new()
5491 }
5492}
5493
5494impl DefaultAssetManager {
5495 pub fn new() -> Self {
5496 Self {
5497 cache: Arc::new(arc_swap::ArcSwap::from_pointee(HashMap::new())),
5498 }
5499 }
5500}
5501
5502impl AssetManager for DefaultAssetManager {
5503 fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>> {
5504 if let Some(state) = self.cache.load().get(url) {
5505 return state.clone();
5506 }
5507
5508 self.cache.rcu(|map| {
5509 let mut m = (**map).clone();
5510 m.entry(url.to_string()).or_insert(AssetState::Loading);
5511 m
5512 });
5513 AssetState::Loading
5514 }
5515
5516 fn preload_image(&self, _url: &str) {}
5517}
5518
5519use std::future::Future;
5520
5521pub struct Suspense<T: Clone + Send + Sync + 'static> {
5524 inner: State<AssetState<T>>,
5525}
5526
5527impl<T: Clone + Send + Sync + 'static> Default for Suspense<T> {
5528 fn default() -> Self {
5529 Self::new()
5530 }
5531}
5532
5533impl<T: Clone + Send + Sync + 'static> Suspense<T> {
5534 pub fn new() -> Self {
5535 Self {
5536 inner: State::new(AssetState::Loading),
5537 }
5538 }
5539
5540 pub fn new_async<F>(future: F) -> Self
5541 where
5542 F: Future<Output = Result<T, String>> + Send + 'static,
5543 {
5544 let suspense = Self::new();
5545 let suspense_clone = suspense.clone();
5546
5547 #[cfg(not(target_arch = "wasm32"))]
5548 {
5549 if let Ok(handle) = tokio::runtime::Handle::try_current() {
5551 handle.spawn(async move {
5552 let result = future.await;
5553 match result {
5554 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5555 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5556 }
5557 });
5558 } else {
5559 std::thread::spawn(move || {
5560 let rt = tokio::runtime::Builder::new_current_thread()
5561 .enable_all()
5562 .build()
5563 .unwrap();
5564 rt.block_on(async {
5565 let result = future.await;
5566 match result {
5567 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5568 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5569 }
5570 });
5571 });
5572 }
5573 }
5574 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5575 {
5576 wasm_bindgen_futures::spawn_local(async move {
5577 let result = future.await;
5578 match result {
5579 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5580 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5581 }
5582 });
5583 }
5584
5585 suspense
5586 }
5587
5588 pub fn ready(value: T) -> Self {
5589 Self {
5590 inner: State::new(AssetState::Ready(value)),
5591 }
5592 }
5593
5594 pub fn error(message: impl Into<String>) -> Self {
5595 Self {
5596 inner: State::new(AssetState::Error(message.into())),
5597 }
5598 }
5599
5600 pub fn get(&self) -> AssetState<T> {
5601 self.inner.get()
5602 }
5603
5604 pub fn get_ref(&self) -> AssetState<T> {
5605 self.inner.get()
5606 }
5607
5608 pub fn is_loading(&self) -> bool {
5609 matches!(self.get(), AssetState::Loading)
5610 }
5611
5612 pub fn is_ready(&self) -> bool {
5613 matches!(self.get(), AssetState::Ready(_))
5614 }
5615
5616 pub fn is_error(&self) -> bool {
5617 matches!(self.get(), AssetState::Error(_))
5618 }
5619
5620 pub fn ready_value(&self) -> Option<T> {
5621 match self.get() {
5622 AssetState::Ready(value) => Some(value),
5623 _ => None,
5624 }
5625 }
5626
5627 pub fn error_message(&self) -> Option<String> {
5628 match self.get() {
5629 AssetState::Error(message) => Some(message),
5630 _ => None,
5631 }
5632 }
5633
5634 pub fn subscribe<F: Fn(&AssetState<T>) + Send + Sync + 'static>(&self, callback: F) {
5635 self.inner.subscribe(callback)
5636 }
5637
5638 pub fn inner_state(&self) -> &State<AssetState<T>> {
5639 &self.inner
5640 }
5641}
5642
5643impl<T: Clone + Send + Sync + 'static> Clone for Suspense<T> {
5644 fn clone(&self) -> Self {
5645 Self {
5646 inner: self.inner.clone(),
5647 }
5648 }
5649}
5650
5651impl<T: Clone + Send + Sync + 'static> From<T> for Suspense<T> {
5652 fn from(value: T) -> Self {
5653 Self::ready(value)
5654 }
5655}
5656
5657impl<T: Clone + Send + Sync + 'static> From<Result<T, String>> for Suspense<T> {
5658 fn from(result: Result<T, String>) -> Self {
5659 match result {
5660 Ok(value) => Self::ready(value),
5661 Err(error) => Self::error(error),
5662 }
5663 }
5664}
5665
5666#[cfg(test)]
5667mod phase1_test;
5668
5669#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5671pub enum BerserkerMode {
5672 Normal,
5673 Rage, Frenzy, GodMode, }
5677
5678pub trait Seer: Send + Sync {
5681 fn predict(&self, context: &str) -> String;
5683 fn whispers(&self) -> Vec<String>;
5685}
5686
5687#[cfg(test)]
5688mod vili_tests {
5689 use super::*;
5690
5691 struct DummyRenderer;
5692 impl ElapsedTime for DummyRenderer {
5693 fn elapsed_time(&self) -> f32 {
5694 0.0
5695 }
5696 fn delta_time(&self) -> f32 {
5697 0.0
5698 }
5699 }
5700 impl Renderer for DummyRenderer {
5701 fn fill_rect(&mut self, _r: Rect, _c: [f32; 4]) {}
5702 fn fill_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4]) {}
5703 fn fill_ellipse(&mut self, _r: Rect, _c: [f32; 4]) {}
5704 fn stroke_rect(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5705 fn stroke_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4], _w: f32) {}
5706 fn stroke_ellipse(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5707 fn draw_line(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, _c: [f32; 4], _w: f32) {}
5708 fn draw_text(&mut self, _t: &str, _x: f32, _y: f32, _s: f32, _c: [f32; 4]) {}
5709 fn measure_text(&mut self, _t: &str, _s: f32) -> (f32, f32) {
5710 (0.0, 0.0)
5711 }
5712 fn memoize(&mut self, _id: u64, _hash: u64, _r: &dyn Fn(&mut dyn Renderer)) {}
5713 fn draw_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {}
5714 fn set_camera_3d(&mut self, _camera: &Camera3D) {}
5715 fn push_transform_3d(&mut self, _transform: &Transform3D) {}
5716 fn pop_transform_3d(&mut self) {}
5717 }
5718
5719 #[test]
5720 fn test_magnetic_warp() {
5721 let renderer = DummyRenderer;
5722 let anchor = Rect {
5723 x: 100.0,
5724 y: 100.0,
5725 width: 50.0,
5726 height: 50.0,
5727 };
5728 let pointer = [125.0, 50.0];
5730 let warp = renderer.magnetic_warp(pointer, anchor, 1.0);
5733 assert!(warp[1] > 50.0);
5735
5736 let far_pointer = [500.0, 500.0];
5738 let far_warp = renderer.magnetic_warp(far_pointer, anchor, 1.0);
5739 assert_eq!(far_pointer, far_warp);
5740 }
5741
5742 #[test]
5743 fn test_mani_glow() {
5744 let renderer = DummyRenderer;
5745 let bounds = Rect {
5746 x: 0.0,
5747 y: 0.0,
5748 width: 100.0,
5749 height: 100.0,
5750 };
5751 let pointer_inside = [50.0, 50.0];
5752 let glow_max = renderer.mani_glow_intensity(pointer_inside, bounds, 120.0);
5753 assert_eq!(glow_max, 1.0);
5754
5755 let pointer_edge = [50.0, -10.0];
5756 let glow_partial = renderer.mani_glow_intensity(pointer_edge, bounds, 120.0);
5757 assert!(glow_partial > 0.0 && glow_partial < 1.0);
5758 }
5759
5760 #[test]
5761 fn test_fafnir_evolve() {
5762 let renderer = DummyRenderer;
5763 let bounds = Rect {
5764 x: 0.0,
5765 y: 0.0,
5766 width: 100.0,
5767 height: 100.0,
5768 };
5769 let pointer_inside = [50.0, 50.0];
5770 let scale = renderer.fafnir_evolve(pointer_inside, bounds, 1.2);
5771 assert_eq!(scale, 1.2); }
5773
5774 #[test]
5775 fn test_undo_manager_basic() {
5776 let mut manager = UndoManager::new(3, 0.5);
5777 let val = std::sync::Arc::new(std::sync::Mutex::new(0));
5778
5779 let v1 = val.clone();
5780 let v2 = val.clone();
5781 manager.push(
5782 "Add",
5783 move || *v1.lock().unwrap() -= 1,
5784 move || *v2.lock().unwrap() += 1,
5785 );
5786
5787 assert!(manager.can_undo());
5788 assert!(!manager.can_redo());
5789
5790 let undo = manager.undo().unwrap();
5791 undo();
5792 assert_eq!(*val.lock().unwrap(), -1);
5793 assert!(!manager.can_undo());
5794 assert!(manager.can_redo());
5795
5796 let redo = manager.redo().unwrap();
5797 redo();
5798 assert_eq!(*val.lock().unwrap(), 0);
5799 }
5800
5801 #[test]
5802 fn test_undo_manager_depth_limit() {
5803 let mut manager = UndoManager::new(2, 0.5);
5804 manager.push("1", || {}, || {});
5805 manager.push("2", || {}, || {});
5806 manager.push("3", || {}, || {});
5807
5808 assert_eq!(manager.stack.len(), 2);
5809 assert_eq!(manager.position, 2);
5810 }
5811
5812 #[test]
5813 fn test_undo_manager_coalescing() {
5814 let mut manager = UndoManager::new(10, 1.0);
5815 let count = std::sync::Arc::new(std::sync::Mutex::new(0));
5816
5817 let c = count.clone();
5818 manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5819
5820 let c = count.clone();
5821 manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5822
5823 assert_eq!(manager.stack.len(), 1);
5824
5825 let undo = manager.undo().unwrap();
5826 undo();
5827 assert_eq!(*count.lock().unwrap(), -2);
5828 }
5829}
5830
5831use std::cell::RefCell;
5843
5844thread_local! {
5845 static THEME_CONTEXT: RefCell<Option<color::SemanticColors>> = const { RefCell::new(None) };
5847}
5848
5849pub use color::SemanticColors;
5855
5856pub fn set_current_theme(colors: color::SemanticColors) {
5859 THEME_CONTEXT.with(|cell| {
5860 *cell.borrow_mut() = Some(colors);
5861 });
5862}
5863
5864pub fn clear_current_theme() {
5866 THEME_CONTEXT.with(|cell| {
5867 *cell.borrow_mut() = None;
5868 });
5869}
5870
5871pub fn use_theme() -> color::SemanticColors {
5886 THEME_CONTEXT.with(|cell| {
5887 cell.borrow()
5888 .clone()
5889 .unwrap_or_else(color::SemanticColors::dark)
5890 })
5891}
5892
5893pub mod color {
5902 use super::Color;
5903
5904 #[derive(Debug, Clone)]
5921 pub struct SemanticColors {
5922 pub primary: Color,
5924 pub secondary: Color,
5926 pub accent: Color,
5928 pub background: Color,
5930 pub surface: Color,
5932 pub error: Color,
5934 pub warning: Color,
5936 pub success: Color,
5938 pub text: Color,
5940 pub text_dim: Color,
5942 }
5943
5944 impl SemanticColors {
5945 pub fn dark() -> Self {
5947 Self {
5948 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), }
5959 }
5960
5961 pub fn light() -> Self {
5963 Self {
5964 primary: Color::new(0.35, 0.30, 0.70, 1.0),
5965 secondary: Color::new(0.30, 0.50, 0.30, 1.0),
5966 accent: Color::new(0.30, 0.35, 0.75, 1.0),
5967 background: Color::new(0.97, 0.97, 0.98, 1.0),
5968 surface: Color::new(0.93, 0.93, 0.95, 1.0),
5969 error: Color::new(0.75, 0.15, 0.15, 1.0),
5970 warning: Color::new(0.80, 0.60, 0.0, 1.0),
5971 success: Color::new(0.15, 0.65, 0.30, 1.0),
5972 text: Color::new(0.08, 0.08, 0.10, 1.0),
5973 text_dim: Color::new(0.40, 0.40, 0.45, 1.0),
5974 }
5975 }
5976
5977 pub fn accent_states(&self) -> InteractiveColorStates {
5982 InteractiveColorStates::from_color(self.accent)
5983 }
5984
5985 pub fn primary_states(&self) -> InteractiveColorStates {
5987 InteractiveColorStates::from_color(self.primary)
5988 }
5989
5990 pub fn error_states(&self) -> InteractiveColorStates {
5992 InteractiveColorStates::from_color(self.error)
5993 }
5994
5995 pub fn success_states(&self) -> InteractiveColorStates {
5997 InteractiveColorStates::from_color(self.success)
5998 }
5999 }
6000
6001 #[derive(Debug, Clone)]
6006 pub struct InteractiveColorStates {
6007 pub default: Color,
6008 pub hover: Color,
6009 pub active: Color,
6010 pub focus: Color,
6011 pub disabled: Color,
6012 pub focus_ring: Color,
6013 }
6014
6015 impl InteractiveColorStates {
6016 pub fn from_color(base: Color) -> Self {
6025 Self {
6026 default: base,
6027 hover: base.lighten(0.15),
6028 active: base.darken(0.15),
6029 focus: base,
6030 disabled: Color::new(base.r, base.g, base.b, base.a * 0.4),
6031 focus_ring: Color::new(base.r, base.g, base.b, base.a * 0.7),
6032 }
6033 }
6034
6035 pub fn color_for(&self, state: InteractiveState) -> Color {
6037 match state {
6038 InteractiveState::Default => self.default,
6039 InteractiveState::Hover => self.hover,
6040 InteractiveState::Active => self.active,
6041 InteractiveState::Focus => self.focus,
6042 InteractiveState::Disabled => self.disabled,
6043 }
6044 }
6045 }
6046
6047 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6049 pub enum InteractiveState {
6050 Default,
6051 Hover,
6052 Active,
6053 Focus,
6054 Disabled,
6055 }
6056}
6057
6058pub fn use_state<T: Clone + Send + Sync + 'static>(
6077 id: u64,
6078 initial: T,
6079) -> (impl Fn() -> T, impl Fn(T)) {
6080 let already_exists = load_system_state().get_component_state::<T>(id).is_some();
6082 if !already_exists {
6083 update_system_state(|s| {
6084 let mut ns = s.clone();
6085 ns.set_component_state(id, initial.clone());
6086 ns
6087 });
6088 }
6089
6090 let getter = move || -> T {
6091 load_system_state()
6092 .get_component_state::<T>(id)
6093 .map(|arc_lock| {
6094 arc_lock
6095 .read()
6096 .ok()
6097 .map(|guard| (*guard).clone())
6098 .unwrap_or_else(|| initial.clone())
6099 })
6100 .unwrap_or_else(|| initial.clone())
6101 };
6102
6103 let setter = {
6104 move |value| {
6105 update_system_state(|s| {
6106 let mut ns = s.clone();
6107 ns.set_component_state(id, value);
6108 ns
6109 });
6110 }
6111 };
6112
6113 (getter, setter)
6114}
6115
6116pub fn use_state_hash(key: &str) -> u64 {
6128 use std::hash::{Hash, Hasher};
6129 let mut s = std::collections::hash_map::DefaultHasher::new();
6130 key.hash(&mut s);
6131 s.finish()
6132}
6133
6134thread_local! {
6144 static ACCESSIBILITY_PREFS: std::cell::RefCell<AccessibilityPreferences> =
6147 std::cell::RefCell::new(AccessibilityPreferences::default());
6148}
6149
6150#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
6157pub struct AccessibilityPreferences {
6158 pub reduce_motion: bool,
6160 pub reduce_transparency: bool,
6162 pub increase_contrast: bool,
6164}
6165
6166impl AccessibilityPreferences {
6167 pub fn detect_from_system() -> Self {
6172 #[cfg(target_os = "macos")]
6173 {
6174 let reduce_motion = std::process::Command::new("defaults")
6176 .args(["read", "-g", "com.apple.universalaccess", "reduceMotion"])
6177 .output()
6178 .ok()
6179 .and_then(|o| String::from_utf8(o.stdout).ok())
6180 .map(|s| s.trim() == "1")
6181 .unwrap_or(false);
6182
6183 let reduce_transparency = std::process::Command::new("defaults")
6184 .args([
6185 "read",
6186 "-g",
6187 "com.apple.universalaccess",
6188 "reduceTransparency",
6189 ])
6190 .output()
6191 .ok()
6192 .and_then(|o| String::from_utf8(o.stdout).ok())
6193 .map(|s| s.trim() == "1")
6194 .unwrap_or(false);
6195
6196 let increase_contrast = std::process::Command::new("defaults")
6197 .args([
6198 "read",
6199 "-g",
6200 "com.apple.universalaccess",
6201 "increaseContrast",
6202 ])
6203 .output()
6204 .ok()
6205 .and_then(|o| String::from_utf8(o.stdout).ok())
6206 .map(|s| s.trim() == "1")
6207 .unwrap_or(false);
6208
6209 Self {
6210 reduce_motion,
6211 reduce_transparency,
6212 increase_contrast,
6213 }
6214 }
6215 #[cfg(not(target_os = "macos"))]
6216 {
6217 Self::default()
6218 }
6219 }
6220
6221 pub fn min_alpha(&self, requested: f32) -> f32 {
6223 if self.increase_contrast {
6224 requested.max(0.5)
6225 } else {
6226 requested
6227 }
6228 }
6229
6230 pub fn should_disable_glass(&self) -> bool {
6232 self.reduce_transparency
6233 }
6234
6235 pub fn should_reduce_motion(&self) -> bool {
6237 self.reduce_motion
6238 }
6239
6240 pub fn should_increase_contrast(&self) -> bool {
6242 self.increase_contrast
6243 }
6244}
6245
6246pub fn accessibility_preferences() -> AccessibilityPreferences {
6248 ACCESSIBILITY_PREFS.with(|p| *p.borrow())
6249}
6250
6251pub fn set_accessibility_preferences(prefs: AccessibilityPreferences) {
6256 ACCESSIBILITY_PREFS.with(|p| {
6257 *p.borrow_mut() = prefs;
6258 });
6259}
6260
6261pub trait ClipboardProvider: Send + Sync {
6270 fn read_text(&self) -> Option<String>;
6272 fn write_text(&self, text: &str);
6274}
6275
6276#[cfg(not(target_arch = "wasm32"))]
6280pub struct SystemClipboard;
6281
6282#[cfg(not(target_arch = "wasm32"))]
6283impl ClipboardProvider for SystemClipboard {
6284 fn read_text(&self) -> Option<String> {
6285 use std::process::Command;
6286 Command::new("pbpaste")
6288 .output()
6289 .ok()
6290 .and_then(|o| String::from_utf8(o.stdout).ok())
6291 }
6292
6293 fn write_text(&self, text: &str) {
6294 use std::process::Command;
6295 if let Ok(mut child) = Command::new("pbcopy")
6297 .stdin(std::process::Stdio::piped())
6298 .spawn()
6299 {
6300 if let Some(stdin) = child.stdin.as_mut() {
6301 use std::io::Write;
6302 let _ = stdin.write_all(text.as_bytes());
6303 }
6304 let _ = child.wait();
6305 }
6306 }
6307}
6308
6309#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6315pub enum TextDirection {
6316 Forward,
6317 Backward,
6318 Up,
6319 Down,
6320 LineStart,
6321 LineEnd,
6322 WordForward,
6323 WordBackward,
6324}
6325
6326#[derive(Debug, Clone, Default)]
6331pub struct TextInputState {
6332 pub text: String,
6334 pub cursor_pos: usize,
6336 pub selection_anchor: Option<usize>,
6339 pub focused: bool,
6341 pub caret_visible: bool,
6343 pub last_edit_time: f32,
6345}
6346
6347impl TextInputState {
6348 pub fn new(text: impl Into<String>) -> Self {
6350 let text = text.into();
6351 let cursor_pos = text.len();
6352 Self {
6353 text,
6354 cursor_pos,
6355 selection_anchor: None,
6356 focused: false,
6357 caret_visible: true,
6358 last_edit_time: 0.0,
6359 }
6360 }
6361
6362 pub fn selection_range(&self) -> Option<(usize, usize)> {
6365 self.selection_anchor.map(|anchor| {
6366 if anchor <= self.cursor_pos {
6367 (anchor, self.cursor_pos)
6368 } else {
6369 (self.cursor_pos, anchor)
6370 }
6371 })
6372 }
6373
6374 pub fn selected_text(&self) -> String {
6376 self.selection_range()
6377 .map(|(start, end)| self.text[start..end].to_string())
6378 .unwrap_or_default()
6379 }
6380
6381 pub fn insert(&mut self, new_text: &str) {
6383 if let Some((start, end)) = self.selection_range() {
6384 self.text.replace_range(start..end, new_text);
6385 self.cursor_pos = start + new_text.len();
6386 } else {
6387 self.text.insert_str(self.cursor_pos, new_text);
6388 self.cursor_pos += new_text.len();
6389 }
6390 self.selection_anchor = None;
6391 }
6392
6393 pub fn delete(&mut self, backward: bool, count: usize) -> String {
6396 if let Some((start, end)) = self.selection_range() {
6397 let deleted = self.text[start..end].to_string();
6398 self.text.replace_range(start..end, "");
6399 self.cursor_pos = start;
6400 self.selection_anchor = None;
6401 return deleted;
6402 }
6403
6404 if backward && self.cursor_pos > 0 {
6405 let start = self.cursor_pos.saturating_sub(count);
6406 let deleted = self.text[start..self.cursor_pos].to_string();
6407 self.text.replace_range(start..self.cursor_pos, "");
6408 self.cursor_pos = start;
6409 deleted
6410 } else if !backward && self.cursor_pos < self.text.len() {
6411 let end = (self.cursor_pos + count).min(self.text.len());
6412 let deleted = self.text[self.cursor_pos..end].to_string();
6413 self.text.replace_range(self.cursor_pos..end, "");
6414 deleted
6415 } else {
6416 String::new()
6417 }
6418 }
6419
6420 pub fn move_cursor(&mut self, direction: TextDirection, extend_selection: bool) {
6422 if !extend_selection {
6423 self.selection_anchor = None;
6424 } else if self.selection_anchor.is_none() {
6425 self.selection_anchor = Some(self.cursor_pos);
6426 }
6427
6428 match direction {
6429 TextDirection::Forward if self.cursor_pos < self.text.len() => {
6430 let next = self.text[self.cursor_pos..]
6432 .char_indices()
6433 .nth(1)
6434 .map(|(i, _)| self.cursor_pos + i)
6435 .unwrap_or(self.text.len());
6436 self.cursor_pos = next;
6437 }
6438 TextDirection::Backward if self.cursor_pos > 0 => {
6439 let prev = self.text[..self.cursor_pos]
6440 .char_indices()
6441 .next_back()
6442 .map(|(i, _)| i)
6443 .unwrap_or(0);
6444 self.cursor_pos = prev;
6445 }
6446 TextDirection::LineStart => {
6447 self.cursor_pos = 0;
6448 }
6449 TextDirection::LineEnd => {
6450 self.cursor_pos = self.text.len();
6451 }
6452 TextDirection::WordForward => {
6453 let rest = &self.text[self.cursor_pos..];
6455 let after_word = rest
6457 .char_indices()
6458 .find(|(_, c)| !c.is_alphanumeric())
6459 .map(|(i, _)| i)
6460 .unwrap_or(rest.len());
6461 let after_space = rest[after_word..]
6463 .char_indices()
6464 .find(|(_, c)| !c.is_whitespace())
6465 .map(|(i, _)| after_word + i)
6466 .unwrap_or(rest.len());
6467 self.cursor_pos = (self.cursor_pos + after_space).min(self.text.len());
6468 }
6469 TextDirection::WordBackward => {
6470 let before = &self.text[..self.cursor_pos];
6471 let before_word = before
6473 .char_indices()
6474 .rev()
6475 .find(|(_, c)| !c.is_whitespace())
6476 .map(|(i, _)| i)
6477 .unwrap_or(0);
6478 let word_start = before[..before_word]
6480 .char_indices()
6481 .rev()
6482 .find(|(_, c)| !c.is_alphanumeric())
6483 .map(|(i, _)| i)
6484 .unwrap_or(0);
6485 self.cursor_pos = word_start;
6486 }
6487 _ => {} }
6489
6490 if !extend_selection {
6491 self.selection_anchor = None;
6492 }
6493 }
6494
6495 pub fn select_all(&mut self) {
6497 self.cursor_pos = self.text.len();
6498 self.selection_anchor = Some(0);
6499 }
6500
6501 pub fn cursor_byte_pos(&self) -> usize {
6503 self.cursor_pos
6504 }
6505}
6506
6507#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6509pub struct NotificationAction {
6510 pub id: String,
6512 pub title: String,
6514 pub is_destructive: bool,
6516}
6517
6518#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6520pub enum NotificationPriority {
6521 Passive,
6523 #[default]
6525 Active,
6526 TimeSensitive,
6528}
6529
6530#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
6532pub struct Notification {
6533 pub id: String,
6535 pub app_name: Option<String>,
6537 pub title: String,
6539 pub body: String,
6541 pub icon: Option<String>,
6543 pub sound: Option<String>,
6545 pub actions: Vec<NotificationAction>,
6547 pub timeout: Option<f32>,
6549 pub priority: NotificationPriority,
6551 pub timestamp: f32,
6553 pub dismissed: bool,
6555}
6556
6557#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, thiserror::Error)]
6559pub enum NotificationError {
6560 #[error("Notification permission denied")]
6562 PermissionDenied,
6563 #[error("Failed to post notification")]
6565 PostFailed,
6566}
6567
6568#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6570pub enum NotificationPermission {
6571 Granted,
6573 Denied,
6575 #[default]
6577 NotDetermined,
6578}
6579
6580pub trait NotificationHandler: Send + Sync {
6582 fn show(&self, notification: Notification) -> Result<(), NotificationError>;
6584 fn dismiss(&self, id: &str) -> Result<(), NotificationError>;
6586 fn request_permission(&self) -> NotificationPermission;
6588}
6589
6590static NEXT_NOTIFICATION_ID: std::sync::atomic::AtomicUsize =
6591 std::sync::atomic::AtomicUsize::new(1);
6592
6593#[derive(Clone, Copy, Debug, Default)]
6595pub struct DefaultNotificationHandler;
6596
6597impl NotificationHandler for DefaultNotificationHandler {
6598 fn show(&self, notification: Notification) -> Result<(), NotificationError> {
6600 let mut notif = notification;
6601 if notif.id.is_empty() {
6602 let id = NEXT_NOTIFICATION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6603 notif.id = format!("notif_{}", id);
6604 }
6605 update_system_state(|state| {
6606 let mut new_state = state.clone();
6607 new_state.notifications.push(notif.clone());
6608 new_state
6609 });
6610 Ok(())
6611 }
6612
6613 fn dismiss(&self, id: &str) -> Result<(), NotificationError> {
6615 update_system_state(|state| {
6616 let mut new_state = state.clone();
6617 for notif in &mut new_state.notifications {
6618 if notif.id == id {
6619 notif.dismissed = true;
6620 }
6621 }
6622 new_state
6623 });
6624 Ok(())
6625 }
6626
6627 fn request_permission(&self) -> NotificationPermission {
6629 NotificationPermission::Granted
6630 }
6631}
6632
6633static NOTIFICATION_HANDLER: once_cell::sync::OnceCell<std::sync::Arc<dyn NotificationHandler>> =
6634 once_cell::sync::OnceCell::new();
6635
6636pub fn set_notification_handler(handler: std::sync::Arc<dyn NotificationHandler>) {
6638 let _ = NOTIFICATION_HANDLER.set(handler);
6639}
6640
6641pub fn get_notification_handler() -> std::sync::Arc<dyn NotificationHandler> {
6643 NOTIFICATION_HANDLER
6644 .get_or_init(|| std::sync::Arc::new(DefaultNotificationHandler))
6645 .clone()
6646}
6647
6648#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6650pub struct FileFilter {
6651 pub name: String,
6653 pub extensions: Vec<String>,
6655}
6656
6657#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
6659pub enum FileDialogMode {
6660 #[default]
6662 OpenFile,
6663 OpenDirectory,
6665 SaveFile,
6667}
6668
6669#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6671pub struct FileDialog {
6672 pub title: String,
6674 pub default_path: Option<String>,
6676 pub filters: Vec<FileFilter>,
6678 pub mode: FileDialogMode,
6680 pub allow_multiple: bool,
6682}
6683
6684#[derive(Debug, thiserror::Error)]
6686pub enum FileDialogError {
6687 #[error("File dialog cancelled")]
6689 Cancelled,
6690 #[error("I/O error: {0}")]
6692 Io(#[from] std::io::Error),
6693 #[error("Platform error: {0}")]
6695 Platform(String),
6696}
6697
6698impl FileDialog {
6699 pub fn new(mode: FileDialogMode) -> Self {
6701 Self {
6702 mode,
6703 ..Default::default()
6704 }
6705 }
6706
6707 pub fn title(mut self, title: impl Into<String>) -> Self {
6709 self.title = title.into();
6710 self
6711 }
6712
6713 pub fn add_filter(mut self, name: &str, extensions: &[&str]) -> Self {
6715 self.filters.push(FileFilter {
6716 name: name.to_string(),
6717 extensions: extensions.iter().map(|s| s.to_string()).collect(),
6718 });
6719 self
6720 }
6721
6722 pub fn default_path(mut self, path: impl Into<String>) -> Self {
6724 self.default_path = Some(path.into());
6725 self
6726 }
6727
6728 pub fn allow_multiple(mut self, allow: bool) -> Self {
6730 self.allow_multiple = allow;
6731 self
6732 }
6733}
6734
6735#[cfg(not(target_arch = "wasm32"))]
6736impl FileDialog {
6737 pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6739 let mut dialog = rfd::FileDialog::new();
6740 dialog = dialog.set_title(&self.title);
6741 if let Some(path) = &self.default_path {
6742 dialog = dialog.set_directory(path);
6743 }
6744 for filter in &self.filters {
6745 let refs: Vec<&str> = filter.extensions.iter().map(|s| s.as_str()).collect();
6746 dialog = dialog.add_filter(&filter.name, &refs);
6747 }
6748
6749 match self.mode {
6750 FileDialogMode::OpenFile => {
6751 if self.allow_multiple {
6752 dialog.pick_files().ok_or(FileDialogError::Cancelled)
6753 } else {
6754 Ok(dialog.pick_file().into_iter().collect())
6755 }
6756 }
6757 FileDialogMode::OpenDirectory => Ok(dialog.pick_folder().into_iter().collect()),
6758 FileDialogMode::SaveFile => Ok(dialog.save_file().into_iter().collect()),
6759 }
6760 }
6761
6762 pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6764 let results = self.pick()?;
6765 Ok(results.into_iter().next())
6766 }
6767}
6768
6769#[cfg(target_arch = "wasm32")]
6770impl FileDialog {
6771 pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6773 Err(FileDialogError::Platform(
6774 "FileDialog is not supported synchronously on WebAssembly".to_string(),
6775 ))
6776 }
6777
6778 pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6780 Err(FileDialogError::Platform(
6781 "FileDialog is not supported synchronously on WebAssembly".to_string(),
6782 ))
6783 }
6784}
6785
6786#[derive(Debug, thiserror::Error)]
6788pub enum DocumentError {
6789 #[error("I/O error: {0}")]
6791 Io(#[from] std::io::Error),
6792 #[error("Parse error: {0}")]
6794 Parse(String),
6795 #[error("Serialization error: {0}")]
6797 Serialize(String),
6798}
6799
6800pub trait Document: Send + Sync {
6802 fn read_from(path: &std::path::Path) -> Result<Self, DocumentError>
6804 where
6805 Self: Sized;
6806
6807 fn write_to(&self, path: &std::path::Path) -> Result<(), DocumentError>;
6809
6810 fn is_dirty(&self) -> bool;
6812
6813 fn mark_clean(&mut self);
6815}
6816
6817pub struct AutoSaveManager {
6819 pub interval: f32,
6821 pub timer: f32,
6823 pub documents: Vec<(std::path::PathBuf, Box<dyn Document>)>,
6825}
6826
6827impl AutoSaveManager {
6828 pub fn new(interval: f32) -> Self {
6830 Self {
6831 interval,
6832 timer: 0.0,
6833 documents: Vec::new(),
6834 }
6835 }
6836
6837 pub fn register(&mut self, path: std::path::PathBuf, doc: Box<dyn Document>) {
6839 self.documents.push((path, doc));
6840 }
6841
6842 pub fn tick(&mut self, dt: f32) {
6844 self.timer += dt;
6845 if self.timer >= self.interval {
6846 self.timer = 0.0;
6847 for (path, doc) in &mut self.documents {
6848 if doc.is_dirty() {
6849 match doc.write_to(path) {
6850 Ok(()) => {
6851 doc.mark_clean();
6852 log::info!("[AutoSaveManager] Auto-saved document to {:?}", path);
6853 }
6854 Err(e) => {
6855 log::error!(
6856 "[AutoSaveManager] Failed to auto-save document to {:?}: {:?}",
6857 path,
6858 e
6859 );
6860 }
6861 }
6862 }
6863 }
6864 }
6865 }
6866}
6867
6868#[derive(Debug, Clone, PartialEq, Eq, Default)]
6876pub struct Modifiers {
6877 pub cmd: bool,
6879 pub shift: bool,
6881 pub alt: bool,
6883 pub ctrl: bool,
6885}
6886
6887#[derive(Debug, Clone)]
6889pub struct KeyboardShortcut {
6890 pub key: String,
6892 pub modifiers: Modifiers,
6894}
6895
6896impl KeyboardShortcut {
6897 pub fn cmd(key: impl Into<String>) -> Self {
6899 Self {
6900 key: key.into(),
6901 modifiers: Modifiers {
6902 cmd: true,
6903 ..Default::default()
6904 },
6905 }
6906 }
6907
6908 pub fn cmd_shift(key: impl Into<String>) -> Self {
6910 Self {
6911 key: key.into(),
6912 modifiers: Modifiers {
6913 cmd: true,
6914 shift: true,
6915 ..Default::default()
6916 },
6917 }
6918 }
6919}
6920
6921pub enum MenuItem {
6927 Action {
6929 label: String,
6930 shortcut: Option<KeyboardShortcut>,
6931 action: std::sync::Arc<dyn Fn() + Send + Sync>,
6932 enabled: bool,
6933 },
6934 Submenu { label: String, items: Vec<MenuItem> },
6936 Separator,
6938}
6939
6940impl std::fmt::Debug for MenuItem {
6941 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6942 match self {
6943 Self::Action { label, enabled, .. } => f
6944 .debug_struct("Action")
6945 .field("label", label)
6946 .field("enabled", enabled)
6947 .finish(),
6948 Self::Submenu { label, items } => f
6949 .debug_struct("Submenu")
6950 .field("label", label)
6951 .field("items", items)
6952 .finish(),
6953 Self::Separator => write!(f, "Separator"),
6954 }
6955 }
6956}
6957
6958pub struct MenuBar {
6963 pub items: Vec<MenuItem>,
6965}
6966
6967impl MenuBar {
6968 pub fn new() -> Self {
6970 Self { items: Vec::new() }
6971 }
6972
6973 pub fn add_item(&mut self, item: MenuItem) {
6975 self.items.push(item);
6976 }
6977
6978 #[allow(clippy::too_many_arguments)]
6990 pub fn standard(
6991 new_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6992 open_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6993 save_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6994 close_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6995 quit_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6996 undo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6997 redo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6998 cut_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6999 copy_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7000 paste_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7001 select_all_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7002 find_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7003 ) -> Self {
7004 let mut bar = Self::new();
7005
7006 bar.add_item(MenuItem::Submenu {
7008 label: "File".to_string(),
7009 items: vec![
7010 MenuItem::Action {
7011 label: "New".to_string(),
7012 shortcut: Some(KeyboardShortcut::cmd("n")),
7013 action: new_fn,
7014 enabled: true,
7015 },
7016 MenuItem::Action {
7017 label: "Open…".to_string(),
7018 shortcut: Some(KeyboardShortcut::cmd("o")),
7019 action: open_fn,
7020 enabled: true,
7021 },
7022 MenuItem::Separator,
7023 MenuItem::Action {
7024 label: "Save".to_string(),
7025 shortcut: Some(KeyboardShortcut::cmd("s")),
7026 action: save_fn,
7027 enabled: true,
7028 },
7029 MenuItem::Separator,
7030 MenuItem::Action {
7031 label: "Close".to_string(),
7032 shortcut: Some(KeyboardShortcut::cmd("w")),
7033 action: close_fn,
7034 enabled: true,
7035 },
7036 MenuItem::Separator,
7037 MenuItem::Action {
7038 label: "Quit".to_string(),
7039 shortcut: Some(KeyboardShortcut::cmd("q")),
7040 action: quit_fn,
7041 enabled: true,
7042 },
7043 ],
7044 });
7045
7046 bar.add_item(MenuItem::Submenu {
7048 label: "Edit".to_string(),
7049 items: vec![
7050 MenuItem::Action {
7051 label: "Undo".to_string(),
7052 shortcut: Some(KeyboardShortcut::cmd("z")),
7053 action: undo_fn,
7054 enabled: true,
7055 },
7056 MenuItem::Action {
7057 label: "Redo".to_string(),
7058 shortcut: Some(KeyboardShortcut::cmd_shift("z")),
7059 action: redo_fn,
7060 enabled: true,
7061 },
7062 MenuItem::Separator,
7063 MenuItem::Action {
7064 label: "Cut".to_string(),
7065 shortcut: Some(KeyboardShortcut::cmd("x")),
7066 action: cut_fn,
7067 enabled: true,
7068 },
7069 MenuItem::Action {
7070 label: "Copy".to_string(),
7071 shortcut: Some(KeyboardShortcut::cmd("c")),
7072 action: copy_fn,
7073 enabled: true,
7074 },
7075 MenuItem::Action {
7076 label: "Paste".to_string(),
7077 shortcut: Some(KeyboardShortcut::cmd("v")),
7078 action: paste_fn,
7079 enabled: true,
7080 },
7081 MenuItem::Separator,
7082 MenuItem::Action {
7083 label: "Select All".to_string(),
7084 shortcut: Some(KeyboardShortcut::cmd("a")),
7085 action: select_all_fn,
7086 enabled: true,
7087 },
7088 MenuItem::Separator,
7089 MenuItem::Action {
7090 label: "Find…".to_string(),
7091 shortcut: Some(KeyboardShortcut::cmd("f")),
7092 action: find_fn,
7093 enabled: true,
7094 },
7095 ],
7096 });
7097
7098 let noop: std::sync::Arc<dyn Fn() + Send + Sync> = std::sync::Arc::new(|| {});
7102 bar.add_item(MenuItem::Submenu {
7103 label: "View".to_string(),
7104 items: vec![
7105 MenuItem::Action {
7106 label: "Zoom In".to_string(),
7107 shortcut: Some(KeyboardShortcut::cmd("=")),
7108 action: noop.clone(),
7109 enabled: true,
7110 },
7111 MenuItem::Action {
7112 label: "Zoom Out".to_string(),
7113 shortcut: Some(KeyboardShortcut::cmd("-")),
7114 action: noop.clone(),
7115 enabled: true,
7116 },
7117 MenuItem::Separator,
7118 MenuItem::Action {
7119 label: "Toggle Fullscreen".to_string(),
7120 shortcut: Some(KeyboardShortcut {
7121 key: "f".to_string(),
7122 modifiers: Modifiers {
7123 ctrl: true,
7124 ..Default::default()
7125 },
7126 }),
7127 action: noop.clone(),
7128 enabled: true,
7129 },
7130 ],
7131 });
7132
7133 bar.add_item(MenuItem::Submenu {
7135 label: "Window".to_string(),
7136 items: vec![
7137 MenuItem::Action {
7138 label: "Minimize".to_string(),
7139 shortcut: Some(KeyboardShortcut::cmd("m")),
7140 action: noop.clone(),
7141 enabled: true,
7142 },
7143 MenuItem::Action {
7144 label: "Zoom".to_string(),
7145 shortcut: None,
7146 action: noop.clone(),
7147 enabled: true,
7148 },
7149 MenuItem::Separator,
7150 MenuItem::Action {
7151 label: "Bring All to Front".to_string(),
7152 shortcut: None,
7153 action: noop.clone(),
7154 enabled: true,
7155 },
7156 ],
7157 });
7158
7159 bar.add_item(MenuItem::Submenu {
7161 label: "Help".to_string(),
7162 items: vec![MenuItem::Action {
7163 label: "Search Help".to_string(),
7164 shortcut: None,
7165 action: noop,
7166 enabled: true,
7167 }],
7168 });
7169
7170 bar
7171 }
7172}
7173
7174impl Default for MenuBar {
7175 fn default() -> Self {
7176 Self::new()
7177 }
7178}
7179
7180use std::sync::RwLock;
7186
7187#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7189pub enum Direction {
7190 #[default]
7191 LTR,
7192 RTL,
7193 Auto,
7194}
7195
7196impl Direction {
7197 pub fn is_rtl(self) -> bool {
7198 matches!(self, Direction::RTL)
7199 }
7200}
7201#[derive(Clone, Debug)]
7202pub struct L10nBundle {
7203 pub locale: String,
7204 pub strings: HashMap<String, String>,
7205 pub is_rtl: bool,
7206}
7207
7208impl L10nBundle {
7209 pub fn new(locale: impl Into<String>) -> Self {
7210 Self {
7211 locale: locale.into(),
7212 strings: HashMap::new(),
7213 is_rtl: false,
7214 }
7215 }
7216
7217 pub fn add(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
7218 self.strings.insert(key.into(), value.into());
7219 self
7220 }
7221
7222 pub fn from_strings_format(locale: impl Into<String>, input: &str) -> Self {
7223 let mut bundle = Self::new(locale);
7224 for line in input.lines() {
7225 let line = line.trim();
7226 if line.is_empty() || line.starts_with("//") {
7227 continue;
7228 }
7229 if let Some(eq_pos) = line.find(" = ") {
7230 let key = line[..eq_pos].trim_matches('"').to_string();
7231 let val = line[eq_pos + 3..]
7232 .trim_end_matches(';')
7233 .trim_matches('"')
7234 .to_string();
7235 bundle.strings.insert(key, val);
7236 }
7237 }
7238 bundle
7239 }
7240 pub fn t(&self, key: &str) -> String {
7242 self.strings
7243 .get(key)
7244 .map(|s| s.to_string())
7245 .unwrap_or_else(|| key.to_string())
7246 }
7247
7248 pub fn tf(&self, key: &str, args: &[&str]) -> String {
7250 let mut result = self.t(key);
7251 for (i, arg) in args.iter().enumerate() {
7252 result = result.replace(&format!("{{{}}}", i), arg);
7253 }
7254 result
7255 }
7256}
7257
7258pub struct L10n {
7260 bundles: HashMap<String, L10nBundle>,
7261 current: String,
7262}
7263
7264impl L10n {
7265 pub fn new(default_locale: &str) -> Self {
7266 Self {
7267 bundles: HashMap::new(),
7268 current: default_locale.to_string(),
7269 }
7270 }
7271
7272 pub fn add_bundle(&mut self, bundle: L10nBundle) {
7273 self.bundles.insert(bundle.locale.clone(), bundle);
7274 }
7275
7276 pub fn set_locale(&mut self, locale: &str) {
7277 self.current = locale.to_string();
7278 }
7279 pub fn current_locale(&self) -> &str {
7280 &self.current
7281 }
7282
7283 pub fn is_rtl(&self) -> bool {
7284 self.bundles
7285 .get(self.current.as_str())
7286 .map(|b| b.is_rtl)
7287 .unwrap_or(false)
7288 }
7289
7290 pub fn t(&self, key: &str) -> String {
7291 self.bundles
7292 .get(self.current.as_str())
7293 .map(|b| b.t(key))
7294 .unwrap_or_else(|| key.to_string())
7295 }
7296
7297 pub fn tf(&self, key: &str, args: &[&str]) -> String {
7298 let mut result = self.t(key);
7299 for (i, arg) in args.iter().enumerate() {
7300 result = result.replace(&format!("{{{}}}", i), arg);
7301 }
7302 result
7303 }
7304
7305 pub fn direction(&self) -> Direction {
7306 if self.is_rtl() {
7307 Direction::RTL
7308 } else {
7309 Direction::LTR
7310 }
7311 }
7312}
7313
7314static L10N: once_cell::sync::Lazy<Arc<RwLock<L10n>>> =
7315 once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(L10n::new("en"))));
7316
7317pub fn init_l10n(l10n: L10n) {
7318 if let Ok(mut guard) = L10N.write() {
7319 *guard = l10n;
7320 }
7321}
7322
7323pub fn l10n() -> Arc<RwLock<L10n>> {
7324 L10N.clone()
7325}
7326
7327pub fn t(key: &str) -> String {
7328 L10N.read()
7329 .map(|g| g.t(key).to_string())
7330 .unwrap_or_else(|_| key.to_string())
7331}
7332
7333pub fn tf(key: &str, args: &[&str]) -> String {
7334 L10N.read()
7335 .map(|g| g.tf(key, args))
7336 .unwrap_or_else(|_| key.to_string())
7337}
7338
7339pub fn set_locale(locale: &str) {
7340 if let Ok(mut guard) = L10N.write() {
7341 guard.set_locale(locale);
7342 }
7343}
7344
7345pub fn current_locale() -> String {
7346 L10N.read()
7347 .map(|g| g.current_locale().to_string())
7348 .unwrap_or_else(|_| "en".to_string())
7349}
7350
7351pub fn is_rtl() -> bool {
7352 L10N.read().map(|g| g.is_rtl()).unwrap_or(false)
7353}
7354
7355#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7367pub enum SystemTheme {
7368 #[default]
7370 Dark,
7371 Light,
7373}
7374
7375pub fn detect_system_theme() -> SystemTheme {
7385 std::env::var("CVKG_THEME")
7386 .ok()
7387 .and_then(|v| match v.as_str() {
7388 "light" => Some(SystemTheme::Light),
7389 "dark" => Some(SystemTheme::Dark),
7390 _ => None,
7391 })
7392 .unwrap_or(SystemTheme::Dark)
7393}
7394
7395pub mod audio_haptic;
7401pub use audio_haptic::{
7402 AudioEngine, HapticEngine, HapticIntensity, NullAudioEngine, NullHapticEngine, haptic_error,
7403 haptic_impact, haptic_selection, haptic_success, play_sound, set_audio_engine,
7404 set_haptic_engine, sounds,
7405};