1use serde::{Deserialize, Serialize};
35use std::collections::HashMap;
36use std::str::FromStr;
37
38pub mod error_types;
39pub mod future_views;
40pub mod security;
41
42pub use future_views::{HologramView, ParticleEmitter, StreamingText};
43
44#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
46pub struct ComponentErrorState {
47 pub has_error: bool,
48 pub error_message: Option<String>,
49 pub error_location: Option<String>,
50}
51impl ComponentErrorState {
52 pub fn clear() -> Self {
53 Self::default()
54 }
55
56 pub fn error(message: impl Into<String>, location: impl Into<String>) -> Self {
57 Self {
58 has_error: true,
59 error_message: Some(message.into()),
60 error_location: Some(location.into()),
61 }
62 }
63}
64
65#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
67pub struct KnowledgeState {
68 pub thoughts: Vec<String>,
69 pub actions: Vec<String>,
70 pub context: HashMap<String, String>,
71 pub last_query_results: Vec<KnowledgeId>,
72 #[serde(alias = "items")]
73 pub fragments: std::collections::HashMap<KnowledgeId, KnowledgeFragment>,
74 pub nodes: Vec<TemporalNode>,
76 pub edges: Vec<TemporalEdge>,
78 pub realm: Realm,
80 pub last_pointer_pos: [f32; 2],
82 pub pointer_velocity: [f32; 2],
84 pub odin_focus: Option<String>,
86 pub agent_attention: HashMap<String, f32>,
88 #[serde(skip)]
90 pub component_states: HashMap<u64, Arc<std::sync::RwLock<dyn std::any::Any + Send + Sync>>>,
91 #[serde(skip)]
93 pub undo_manager: UndoManager,
94 #[serde(default)]
96 pub notifications: Vec<Notification>,
97 #[serde(default)]
99 pub notification_center_visible: bool,
100 #[serde(default)]
102 pub modifiers_shift: bool,
103 #[serde(default)]
105 pub modifiers_ctrl: bool,
106 #[serde(default)]
108 pub modifiers_alt: bool,
109 #[serde(default)]
111 pub modifiers_logo: bool,
112 #[serde(default)]
114 pub performance_overlay_visible: bool,
115}
116
117impl KnowledgeState {
118 pub fn apply_decay(&mut self, decay_factor: f32) {
122 for node in &mut self.nodes {
123 node.weight *= decay_factor;
124 }
125
126 for state in self.component_states.values() {
128 if let Ok(mut lock) = state.write()
129 && let Some(v) = lock.downcast_mut::<f32>()
130 {
131 *v = (*v * decay_factor).max(1.0);
132 }
133 }
134 }
135
136 pub fn reinforce(&mut self, node_ids: &[String], boost: f32) {
138 for node in &mut self.nodes {
139 if node_ids.contains(&node.id) {
140 node.weight += boost;
141 }
142 }
143 }
144
145 pub fn update_pointer(&mut self, new_pos: [f32; 2]) {
147 self.pointer_velocity = [
148 new_pos[0] - self.last_pointer_pos[0],
149 new_pos[1] - self.last_pointer_pos[1],
150 ];
151 self.last_pointer_pos = new_pos;
152 }
153}
154pub type KnowledgeId = String;
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct KnowledgeFragment {
161 pub id: String,
163 pub summary: String,
165 pub source: String,
167 pub created_at: u64,
169 pub accessed_count: u32,
171 pub content: Option<String>,
173}
174
175impl KnowledgeFragment {
176 pub fn new(id: String, summary: String, source: String) -> Self {
177 Self {
178 id,
179 summary,
180 source,
181 created_at: 0,
182 accessed_count: 0,
183 content: None,
184 }
185 }
186}
187
188#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
190pub enum MemoryLayer {
191 Episodic,
193 Semantic,
195 Procedural,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
203pub enum Realm {
204 Midgard,
205 #[default]
206 Asgard,
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub enum AnnouncementPriority {
212 Polite,
214 Assertive,
216}
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct TemporalNode {
219 pub id: String,
221 pub fragment_id: KnowledgeId,
223 pub timestamp: u64,
225 pub layer: MemoryLayer,
227 pub weight: f32,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct TemporalEdge {
234 pub source: String,
236 pub target: String,
238 pub relation: String,
240 pub weight: f32,
242}
243
244pub struct UndoGroup {
246 pub label: String,
248 pub timestamp: f32,
250 pub undo: Arc<dyn Fn() + Send + Sync>,
252 pub redo: Arc<dyn Fn() + Send + Sync>,
254}
255
256impl Clone for UndoGroup {
257 fn clone(&self) -> Self {
259 Self {
260 label: self.label.clone(),
261 timestamp: self.timestamp,
262 undo: Arc::clone(&self.undo),
263 redo: Arc::clone(&self.redo),
264 }
265 }
266}
267
268impl std::fmt::Debug for UndoGroup {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271 f.debug_struct("UndoGroup")
272 .field("label", &self.label)
273 .field("timestamp", &self.timestamp)
274 .finish()
275 }
276}
277
278pub struct UndoManager {
281 stack: Vec<UndoGroup>,
283 position: usize,
285 max_depth: usize,
287 coalesce_window: f32,
289}
290
291impl Default for UndoManager {
292 fn default() -> Self {
294 Self {
295 stack: Vec::new(),
296 position: 0,
297 max_depth: 100,
298 coalesce_window: 0.5,
299 }
300 }
301}
302
303impl Clone for UndoManager {
304 fn clone(&self) -> Self {
306 Self {
307 stack: self.stack.clone(),
308 position: self.position,
309 max_depth: self.max_depth,
310 coalesce_window: self.coalesce_window,
311 }
312 }
313}
314
315impl std::fmt::Debug for UndoManager {
316 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318 f.debug_struct("UndoManager")
319 .field("stack_len", &self.stack.len())
320 .field("position", &self.position)
321 .field("max_depth", &self.max_depth)
322 .field("coalesce_window", &self.coalesce_window)
323 .finish()
324 }
325}
326
327impl UndoManager {
328 pub fn new(max_depth: usize, coalesce_window: f32) -> Self {
330 Self {
331 stack: Vec::new(),
332 position: 0,
333 max_depth,
334 coalesce_window,
335 }
336 }
337
338 pub fn push(
340 &mut self,
341 label: &str,
342 undo: impl Fn() + Send + Sync + 'static,
343 redo: impl Fn() + Send + Sync + 'static,
344 ) {
345 if self.position < self.stack.len() {
346 self.stack.truncate(self.position);
347 }
348
349 let timestamp = std::time::SystemTime::now()
350 .duration_since(std::time::UNIX_EPOCH)
351 .unwrap_or_default()
352 .as_secs_f32();
353
354 self.stack.push(UndoGroup {
355 label: label.to_string(),
356 timestamp,
357 undo: Arc::new(undo),
358 redo: Arc::new(redo),
359 });
360
361 if self.stack.len() > self.max_depth {
362 self.stack.remove(0);
363 }
364 self.position = self.stack.len();
365 }
366
367 pub fn undo(&mut self) -> Option<Arc<dyn Fn() + Send + Sync>> {
370 if self.can_undo() {
371 self.position -= 1;
372 Some(Arc::clone(&self.stack[self.position].undo))
373 } else {
374 None
375 }
376 }
377
378 pub fn redo(&mut self) -> Option<Arc<dyn Fn() + Send + Sync>> {
381 if self.can_redo() {
382 let group = &self.stack[self.position];
383 self.position += 1;
384 Some(Arc::clone(&group.redo))
385 } else {
386 None
387 }
388 }
389
390 pub fn can_undo(&self) -> bool {
392 self.position > 0
393 }
394
395 pub fn can_redo(&self) -> bool {
397 self.position < self.stack.len()
398 }
399
400 pub fn clear(&mut self) {
402 self.stack.clear();
403 self.position = 0;
404 }
405
406 pub fn push_coalesceable(
410 &mut self,
411 label: &str,
412 undo: impl Fn() + Send + Sync + 'static,
413 redo: impl Fn() + Send + Sync + 'static,
414 ) {
415 let now = std::time::SystemTime::now()
416 .duration_since(std::time::UNIX_EPOCH)
417 .unwrap_or_default()
418 .as_secs_f32();
419
420 if self.position == self.stack.len() && !self.stack.is_empty() {
421 let last_idx = self.stack.len() - 1;
422 let last = &self.stack[last_idx];
423 if last.label == label && (now - last.timestamp).abs() <= self.coalesce_window {
424 let old_undo = Arc::clone(&last.undo);
425 let old_redo = Arc::clone(&last.redo);
426 let new_undo = Arc::new(undo);
427 let new_redo = Arc::new(redo);
428
429 self.stack[last_idx].undo = Arc::new(move || {
430 new_undo();
431 old_undo();
432 });
433 self.stack[last_idx].redo = Arc::new(move || {
434 old_redo();
435 new_redo();
436 });
437 self.stack[last_idx].timestamp = now;
438 return;
439 }
440 }
441
442 self.push(label, undo, redo);
443 }
444}
445
446#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
448pub struct WindowId(pub u64);
449
450#[derive(
452 Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default,
453)]
454pub enum WindowLevel {
455 #[default]
457 Normal,
458 AlwaysOnTop,
460 PopUpMenu,
462}
463
464#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
466pub struct WindowConfig {
467 pub title: String,
469 pub size: (f32, f32),
471 pub min_size: Option<(f32, f32)>,
473 pub max_size: Option<(f32, f32)>,
475 pub resizable: bool,
477 pub transparent: bool,
479 pub decorations: bool,
481 pub level: WindowLevel,
483}
484
485impl Default for WindowConfig {
486 fn default() -> Self {
488 Self {
489 title: "CVKG Window".to_string(),
490 size: (800.0, 600.0),
491 min_size: None,
492 max_size: None,
493 resizable: true,
494 transparent: false,
495 decorations: true,
496 level: WindowLevel::Normal,
497 }
498 }
499}
500
501pub trait Window: Send + Sync {
504 fn close(&self);
506 fn set_title(&self, title: &str);
508 fn set_size(&self, width: f32, height: f32);
510 fn is_key(&self) -> bool;
512 fn is_main(&self) -> bool;
514 fn is_visible(&self) -> bool;
516 fn set_visible(&self, visible: bool);
518 fn bring_to_front(&self);
520}
521
522#[derive(Clone)]
524pub struct WindowHandle {
525 pub id: WindowId,
527 pub inner: Arc<dyn Window>,
529}
530
531impl std::fmt::Debug for WindowHandle {
532 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
533 f.debug_struct("WindowHandle")
534 .field("id", &self.id)
535 .finish()
536 }
537}
538
539impl WindowHandle {
540 pub fn new(id: WindowId, inner: Arc<dyn Window>) -> Self {
542 Self { id, inner }
543 }
544 pub fn close(self) {
546 self.inner.close();
547 }
548 pub fn set_title(&self, title: &str) {
550 self.inner.set_title(title);
551 }
552 pub fn set_size(&self, width: f32, height: f32) {
554 self.inner.set_size(width, height);
555 }
556 pub fn is_key(&self) -> bool {
558 self.inner.is_key()
559 }
560 pub fn is_main(&self) -> bool {
562 self.inner.is_main()
563 }
564 pub fn is_visible(&self) -> bool {
566 self.inner.is_visible()
567 }
568 pub fn set_visible(&self, visible: bool) {
570 self.inner.set_visible(visible);
571 }
572 pub fn bring_to_front(&self) {
574 self.inner.bring_to_front();
575 }
576}
577
578#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
580pub enum WindowCloseAction {
581 Allow,
583 Confirm,
585 Deny,
587}
588
589#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
590pub struct AssetKey(pub String);
591
592impl EnvKey for AssetKey {
593 type Value = Arc<dyn AssetManager>;
594 fn default_value() -> Self::Value {
595 Arc::new(DefaultAssetManager::new())
596 }
597}
598
599#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
601pub enum AssetState<T> {
602 Loading,
603 Ready(T),
604 Error(String),
605}
606
607#[derive(Debug, Clone, Serialize, Deserialize)]
609#[serde(untagged)]
610pub enum TokenValue {
611 Single { value: String },
613 Adaptive { light: String, dark: String },
615}
616
617#[derive(Debug, Clone, Serialize, Deserialize)]
619pub struct YggdrasilTokens {
620 pub color: HashMap<String, TokenValue>,
621 pub font: HashMap<String, TokenValue>,
622 pub spacing: HashMap<String, TokenValue>,
623 pub radius: HashMap<String, TokenValue>,
624 pub shadow: HashMap<String, TokenValue>,
625 pub border: HashMap<String, TokenValue>,
626 pub anim: HashMap<String, TokenValue>,
627 pub bifrost: HashMap<String, TokenValue>,
628 pub gungnir: HashMap<String, TokenValue>,
629 pub mjolnir: HashMap<String, TokenValue>,
630 pub accessibility: HashMap<String, TokenValue>,
631}
632
633impl Default for YggdrasilTokens {
634 fn default() -> Self {
635 Self::new()
636 }
637}
638
639impl YggdrasilTokens {
640 pub fn new() -> Self {
641 Self {
642 color: HashMap::new(),
643 font: HashMap::new(),
644 spacing: HashMap::new(),
645 radius: HashMap::new(),
646 shadow: HashMap::new(),
647 border: HashMap::new(),
648 anim: HashMap::new(),
649 bifrost: HashMap::new(),
650 gungnir: HashMap::new(),
651 mjolnir: HashMap::new(),
652 accessibility: HashMap::new(),
653 }
654 }
655
656 pub fn get_color(&self, key: &str, is_dark: bool) -> Option<String> {
658 self.color.get(key).map(|token| match token {
659 TokenValue::Single { value } => value.clone(),
660 TokenValue::Adaptive { light, dark } => {
661 if is_dark {
662 dark.clone()
663 } else {
664 light.clone()
665 }
666 }
667 })
668 }
669
670 pub fn get<T: FromStr>(&self, category: &str, key: &str, is_dark: bool) -> Option<T> {
672 let map = match category {
673 "color" => &self.color,
674 "font" => &self.font,
675 "spacing" => &self.spacing,
676 "radius" => &self.radius,
677 "shadow" => &self.shadow,
678 "border" => &self.border,
679 "anim" => &self.anim,
680 "bifrost" => &self.bifrost,
681 "gungnir" => &self.gungnir,
682 "mjolnir" => &self.mjolnir,
683 "accessibility" => &self.accessibility,
684 _ => return None,
685 };
686
687 map.get(key).and_then(|token| match token {
688 TokenValue::Single { value } => value.parse().ok(),
689 TokenValue::Adaptive { light, dark } => {
690 let value = if is_dark { dark } else { light };
691 value.parse().ok()
692 }
693 })
694 }
695}
696
697pub trait View: Sized + Send {
698 type Body: View;
701
702 fn body(self) -> Self::Body;
703
704 fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
707
708 fn intrinsic_size(&self, _renderer: &mut dyn Renderer, _proposal: SizeProposal) -> Size {
711 Size::ZERO
712 }
713
714 fn layout(&self) -> Option<&dyn layout::LayoutView> {
716 None
717 }
718
719 fn flex_weight(&self) -> f32 {
721 0.0
722 }
723
724 fn get_grid_placement(&self) -> Option<GridPlacement> {
726 None
727 }
728
729 fn modifier<M: ViewModifier>(self, m: M) -> ModifiedView<Self, M> {
731 ModifiedView::new(self, m)
732 }
733
734 fn bifrost(
736 self,
737 blur: f32,
738 saturation: f32,
739 opacity: f32,
740 ) -> ModifiedView<Self, BifrostModifier> {
741 self.modifier(BifrostModifier {
742 blur,
743 saturation,
744 opacity,
745 fresnel_strength: 1.0,
746 })
747 }
748
749 fn bifrost_full(
751 self,
752 blur: f32,
753 saturation: f32,
754 opacity: f32,
755 fresnel_strength: f32,
756 ) -> ModifiedView<Self, BifrostModifier> {
757 self.modifier(BifrostModifier {
758 blur,
759 saturation,
760 opacity,
761 fresnel_strength,
762 })
763 }
764
765 fn gungnir(
767 self,
768 color: impl Into<String>,
769 radius: f32,
770 intensity: f32,
771 ) -> ModifiedView<Self, GungnirModifier> {
772 self.modifier(GungnirModifier {
773 color: color.into(),
774 radius,
775 intensity,
776 })
777 }
778
779 fn mjolnir_slice(self, angle: f32, offset: f32) -> ModifiedView<Self, MjolnirSliceModifier> {
781 self.modifier(MjolnirSliceModifier { angle, offset })
782 }
783
784 fn mjolnir_shatter(
786 self,
787 pieces: u32,
788 force: f32,
789 ) -> ModifiedView<Self, MjolnirShatterModifier> {
790 self.modifier(MjolnirShatterModifier { pieces, force })
791 }
792
793 fn bifrost_bridge(self, id: impl Into<String>) -> ModifiedView<Self, BifrostBridgeModifier> {
795 self.modifier(BifrostBridgeModifier { id: id.into() })
796 }
797
798 fn background(self, color: [f32; 4]) -> ModifiedView<Self, BackgroundModifier> {
800 self.modifier(BackgroundModifier { color })
801 }
802
803 fn padding(self, amount: f32) -> ModifiedView<Self, PaddingModifier> {
805 self.modifier(PaddingModifier { amount })
806 }
807
808 fn opacity(self, opacity: f32) -> ModifiedView<Self, OpacityModifier> {
810 self.modifier(OpacityModifier {
811 opacity: opacity.clamp(0.0, 1.0),
812 })
813 }
814
815 fn foreground_color(self, color: [f32; 4]) -> ModifiedView<Self, ForegroundColorModifier> {
817 self.modifier(ForegroundColorModifier { color })
818 }
819
820 fn frame(self, width: Option<f32>, height: Option<f32>) -> ModifiedView<Self, FrameModifier> {
823 self.modifier(FrameModifier {
824 width,
825 height,
826 min_width: None,
827 max_width: None,
828 min_height: None,
829 max_height: None,
830 alignment: Alignment::Center,
831 })
832 }
833
834 fn flex(self, weight: f32) -> ModifiedView<Self, FlexModifier> {
836 self.modifier(FlexModifier { weight })
837 }
838
839 fn grid_placement(self, placement: GridPlacement) -> ModifiedView<Self, GridPlacementModifier> {
841 self.modifier(GridPlacementModifier { placement })
842 }
843
844 fn overlay<O: View + Clone + 'static>(
846 self,
847 overlay: O,
848 alignment: Alignment,
849 offset: [f32; 2],
850 on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
851 ) -> ModifiedView<Self, OverlayModifier> {
852 self.modifier(OverlayModifier {
853 overlay: overlay.erase(),
854 alignment,
855 offset,
856 on_dismiss,
857 })
858 }
859
860 fn safe_area_padding(self) -> ModifiedView<Self, SafeAreaModifier> {
862 self.modifier(SafeAreaModifier { ignores: false })
863 }
864
865 fn ignores_safe_area(self) -> ModifiedView<Self, SafeAreaModifier> {
867 self.modifier(SafeAreaModifier { ignores: true })
868 }
869
870 fn clip_to_bounds(self) -> ModifiedView<Self, ClipModifier> {
872 self.modifier(ClipModifier)
873 }
874
875 fn border(self, color: [f32; 4], width: f32) -> ModifiedView<Self, BorderModifier> {
877 self.modifier(BorderModifier { color, width })
878 }
879
880 fn elevation(self, level: f32) -> ModifiedView<Self, ElevationModifier> {
882 self.modifier(ElevationModifier { level })
883 }
884
885 fn magnetic(self, radius: f32, intensity: f32) -> ModifiedView<Self, MagneticModifier> {
887 self.modifier(MagneticModifier { radius, intensity })
888 }
889
890 fn mani_glow(self, color: [f32; 4], radius: f32) -> ModifiedView<Self, ManiGlowModifier> {
892 self.modifier(ManiGlowModifier { color, radius })
893 }
894
895 fn memory_layer(self, layer: MemoryLayer) -> ModifiedView<Self, BifrostLayerModifier> {
897 self.modifier(BifrostLayerModifier { layer })
898 }
899
900 fn fafnir_evolve(self, id: u64) -> ModifiedView<Self, FafnirModifier> {
902 self.modifier(FafnirModifier { id })
903 }
904
905 fn mimir_intent(self) -> ModifiedView<Self, MimirIntentModifier> {
907 self.modifier(MimirIntentModifier)
908 }
909
910 fn kvasir_vibes(self, complexity: f32) -> ModifiedView<Self, KvasirVibeModifier> {
912 self.modifier(KvasirVibeModifier { complexity })
913 }
914
915 fn odins_eye(self) -> ModifiedView<Self, OdinsEyeModifier> {
917 self.modifier(OdinsEyeModifier)
918 }
919
920 fn on_appear<F: Fn() + Send + Sync + 'static>(
922 self,
923 action: F,
924 ) -> ModifiedView<Self, LifecycleModifier> {
925 self.modifier(LifecycleModifier {
926 on_appear: Some(Arc::new(action)),
927 on_disappear: None,
928 })
929 }
930
931 fn on_disappear<F: Fn() + Send + Sync + 'static>(
933 self,
934 action: F,
935 ) -> ModifiedView<Self, LifecycleModifier> {
936 self.modifier(LifecycleModifier {
937 on_appear: None,
938 on_disappear: Some(Arc::new(action)),
939 })
940 }
941
942 fn on_click<F: Fn() + Send + Sync + 'static>(
944 self,
945 action: F,
946 ) -> ModifiedView<Self, OnClickModifier> {
947 self.modifier(OnClickModifier {
948 action: Arc::new(action),
949 })
950 }
951
952 fn on_pointer_enter<F: Fn() + Send + Sync + 'static>(
954 self,
955 action: F,
956 ) -> ModifiedView<Self, OnPointerEnterModifier> {
957 self.modifier(OnPointerEnterModifier {
958 action: Arc::new(action),
959 })
960 }
961
962 fn on_pointer_leave<F: Fn() + Send + Sync + 'static>(
964 self,
965 action: F,
966 ) -> ModifiedView<Self, OnPointerLeaveModifier> {
967 self.modifier(OnPointerLeaveModifier {
968 action: Arc::new(action),
969 })
970 }
971
972 fn on_pointer_move<F: Fn(f32, f32) + Send + Sync + 'static>(
974 self,
975 action: F,
976 ) -> ModifiedView<Self, OnPointerMoveModifier> {
977 self.modifier(OnPointerMoveModifier {
978 action: Arc::new(action),
979 })
980 }
981
982 fn on_pointer_down<F: Fn() + Send + Sync + 'static>(
984 self,
985 action: F,
986 ) -> ModifiedView<Self, OnPointerDownModifier> {
987 self.modifier(OnPointerDownModifier {
988 action: Arc::new(action),
989 })
990 }
991
992 fn on_pointer_up<F: Fn() + Send + Sync + 'static>(
994 self,
995 action: F,
996 ) -> ModifiedView<Self, OnPointerUpModifier> {
997 self.modifier(OnPointerUpModifier {
998 action: Arc::new(action),
999 })
1000 }
1001
1002 fn erase(self) -> AnyView
1004 where
1005 Self: Clone + 'static,
1006 {
1007 AnyView::new(self)
1008 }
1009
1010 fn aria_properties(&self) -> Option<AriaProperties> {
1018 None
1019 }
1020
1021 fn on_key_event(&self, _key: &str, _modifiers: KeyModifiers) -> bool {
1024 false
1025 }
1026
1027 fn key_shortcuts(&self) -> Vec<KeyShortcut> {
1029 vec![]
1030 }
1031}
1032
1033#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1039pub enum AriaRole {
1040 Alert,
1041 Alertdialog,
1042 Article,
1043 Banner,
1044 Button,
1045 Checkbox,
1046 Columnheader,
1047 Combobox,
1048 Complementary,
1049 Contentinfo,
1050 Dialog,
1051 Form,
1052 Grid,
1053 Gridcell,
1054 Heading,
1055 Img,
1056 Link,
1057 List,
1058 Listbox,
1059 Listitem,
1060 Main,
1061 Menu,
1062 Menubar,
1063 Menuitem,
1064 Menuitemcheckbox,
1065 Menuitemradio,
1066 Navigation,
1067 None,
1068 Note,
1069 Option,
1070 Presentation,
1071 Progressbar,
1072 Radio,
1073 Radiogroup,
1074 Region,
1075 Row,
1076 Rowgroup,
1077 Rowheader,
1078 Search,
1079 Separator,
1080 Slider,
1081 Spinbutton,
1082 Status,
1083 Switch,
1084 Tab,
1085 Table,
1086 Tablist,
1087 Tabpanel,
1088 Textbox,
1089 Toolbar,
1090 Tooltip,
1091 Tree,
1092 Treeitem,
1093}
1094
1095#[derive(Debug, Clone, Serialize, Deserialize)]
1097pub struct AriaProperties {
1098 pub role: AriaRole,
1099 pub label: String,
1100 pub description: Option<String>,
1101 pub value: Option<String>,
1102 pub pressed: Option<bool>,
1103 pub checked: Option<bool>,
1104 pub expanded: Option<bool>,
1105 pub disabled: bool,
1106 pub hidden: bool,
1107 pub level: Option<u8>,
1108 pub shortcut: Option<String>,
1109 pub focused: bool,
1110 pub live: Option<String>,
1111 pub atomic: bool,
1112}
1113
1114impl AriaProperties {
1115 pub fn new(role: AriaRole, label: impl Into<String>) -> Self {
1116 Self {
1117 role,
1118 label: label.into(),
1119 description: None,
1120 value: None,
1121 pressed: None,
1122 checked: None,
1123 expanded: None,
1124 disabled: false,
1125 hidden: false,
1126 level: None,
1127 shortcut: None,
1128 focused: false,
1129 live: None,
1130 atomic: false,
1131 }
1132 }
1133
1134 pub fn description(mut self, d: impl Into<String>) -> Self {
1135 self.description = Some(d.into());
1136 self
1137 }
1138 pub fn value(mut self, v: impl Into<String>) -> Self {
1139 self.value = Some(v.into());
1140 self
1141 }
1142 pub fn checked(mut self, c: bool) -> Self {
1143 self.checked = Some(c);
1144 self
1145 }
1146 pub fn disabled(mut self, d: bool) -> Self {
1147 self.disabled = d;
1148 self
1149 }
1150 pub fn expanded(mut self, e: bool) -> Self {
1151 self.expanded = Some(e);
1152 self
1153 }
1154 pub fn level(mut self, l: u8) -> Self {
1155 self.level = Some(l.clamp(1, 6));
1156 self
1157 }
1158 pub fn shortcut(mut self, s: impl Into<String>) -> Self {
1159 self.shortcut = Some(s.into());
1160 self
1161 }
1162 pub fn focused(mut self, f: bool) -> Self {
1163 self.focused = f;
1164 self
1165 }
1166}
1167
1168#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
1174pub struct KeyModifiers {
1175 pub shift: bool,
1176 pub ctrl: bool,
1177 pub alt: bool,
1178 pub meta: bool,
1179}
1180
1181#[derive(Debug, Clone, Serialize, Deserialize)]
1183pub struct KeyShortcut {
1184 pub key: String,
1185 pub modifiers: KeyModifiers,
1186 pub description: String,
1187}
1188
1189impl KeyShortcut {
1190 pub fn new(key: impl Into<String>, desc: impl Into<String>) -> Self {
1191 Self {
1192 key: key.into(),
1193 modifiers: KeyModifiers::default(),
1194 description: desc.into(),
1195 }
1196 }
1197 pub fn with_ctrl(mut self) -> Self {
1198 self.modifiers.ctrl = true;
1199 self
1200 }
1201 pub fn with_shift(mut self) -> Self {
1202 self.modifiers.shift = true;
1203 self
1204 }
1205 pub fn with_alt(mut self) -> Self {
1206 self.modifiers.alt = true;
1207 self
1208 }
1209 pub fn with_meta(mut self) -> Self {
1210 self.modifiers.meta = true;
1211 self
1212 }
1213}
1214
1215#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1221pub struct FocusableId(String);
1222
1223impl From<&str> for FocusableId {
1224 fn from(s: &str) -> Self {
1225 Self(s.to_string())
1226 }
1227}
1228impl From<String> for FocusableId {
1229 fn from(s: String) -> Self {
1230 Self(s)
1231 }
1232}
1233
1234#[derive(Debug, Clone)]
1236pub struct FocusTrap {
1237 pub id: FocusableId,
1238 pub order: Vec<FocusableId>,
1239 pub wrap: bool,
1240}
1241
1242impl FocusTrap {
1243 pub fn new(id: impl Into<FocusableId>, order: Vec<FocusableId>) -> Self {
1244 Self {
1245 id: id.into(),
1246 order,
1247 wrap: true,
1248 }
1249 }
1250}
1251
1252#[derive(Debug, Default)]
1254pub struct FocusManager {
1255 order: Vec<FocusableId>,
1256 focused: Option<FocusableId>,
1257 traps: Vec<FocusTrap>,
1258}
1259
1260impl FocusManager {
1261 pub fn new() -> Self {
1262 Self::default()
1263 }
1264
1265 pub fn register(&mut self, id: impl Into<FocusableId>) {
1266 let id = id.into();
1267 if !self.order.contains(&id) {
1268 self.order.push(id);
1269 }
1270 }
1271
1272 pub fn unregister(&mut self, id: &FocusableId) {
1273 self.order.retain(|x| x != id);
1274 if self.focused.as_ref() == Some(id) {
1275 self.focused = None;
1276 }
1277 }
1278
1279 pub fn focused(&self) -> Option<&FocusableId> {
1280 self.focused.as_ref()
1281 }
1282
1283 pub fn focus(&mut self, id: impl Into<FocusableId>) -> bool {
1284 let id = id.into();
1285 if self.order.contains(&id) || self.traps.iter().any(|t| t.order.contains(&id)) {
1286 self.focused = Some(id);
1287 true
1288 } else {
1289 false
1290 }
1291 }
1292
1293 pub fn focus_next(&mut self) -> Option<&FocusableId> {
1294 let order = self.effective_order();
1295 if order.is_empty() {
1296 return None;
1297 }
1298 let idx = self
1299 .focused
1300 .as_ref()
1301 .and_then(|f| order.iter().position(|x| x == f));
1302 let next = match idx {
1303 Some(i) if i + 1 < order.len() => &order[i + 1],
1304 _ => &order[0],
1305 };
1306 self.focused = Some(next.clone());
1307 self.focused.as_ref()
1308 }
1309
1310 pub fn focus_prev(&mut self) -> Option<&FocusableId> {
1311 let order = self.effective_order();
1312 if order.is_empty() {
1313 return None;
1314 }
1315 let idx = self
1316 .focused
1317 .as_ref()
1318 .and_then(|f| order.iter().position(|x| x == f));
1319 let prev = match idx {
1320 Some(i) if i > 0 => &order[i - 1],
1321 _ => &order[order.len() - 1],
1322 };
1323 self.focused = Some(prev.clone());
1324 self.focused.as_ref()
1325 }
1326
1327 pub fn push_trap(&mut self, trap: FocusTrap) -> FocusableId {
1328 let id = trap.id.clone();
1329 self.traps.push(trap);
1330 id
1331 }
1332
1333 pub fn pop_trap(&mut self) {
1334 self.traps.pop();
1335 }
1336 pub fn trap_count(&self) -> usize {
1337 self.traps.len()
1338 }
1339
1340 fn effective_order(&self) -> &[FocusableId] {
1341 self.traps
1342 .last()
1343 .map(|t| t.order.as_slice())
1344 .unwrap_or(&self.order)
1345 }
1346}
1347
1348pub fn is_reduced_motion() -> bool {
1354 std::env::var("GTK_THEME")
1355 .map(|v| v.to_lowercase().contains("reduced"))
1356 .unwrap_or(false)
1357 || std::env::var("NO_ANIMATIONS")
1358 .map(|v| v == "1" || v.to_lowercase() == "true")
1359 .unwrap_or(false)
1360 || std::env::var("ACCESSIBILITY_REDUCED_MOTION")
1361 .map(|v| v == "1" || v.to_lowercase() == "true")
1362 .unwrap_or(false)
1363}
1364
1365pub fn effective_duration(secs: f32) -> f32 {
1367 if is_reduced_motion() { 0.0 } else { secs }
1368}
1369
1370pub trait ErasedView: Send {
1372 fn render_erased(&self, renderer: &mut dyn Renderer, rect: Rect);
1373 fn name(&self) -> &'static str;
1374 fn flex_weight_erased(&self) -> f32;
1375 fn layout_erased(&self) -> Option<&dyn layout::LayoutView>;
1376 fn grid_placement_erased(&self) -> Option<GridPlacement>;
1377 fn clone_box(&self) -> Box<dyn ErasedView>;
1378}
1379
1380impl<V: View + Clone + 'static> ErasedView for V {
1381 fn render_erased(&self, renderer: &mut dyn Renderer, rect: Rect) {
1382 self.render(renderer, rect);
1383 }
1384
1385 fn name(&self) -> &'static str {
1386 std::any::type_name::<V>()
1387 }
1388
1389 fn flex_weight_erased(&self) -> f32 {
1390 self.flex_weight()
1391 }
1392
1393 fn layout_erased(&self) -> Option<&dyn layout::LayoutView> {
1394 self.layout()
1395 }
1396
1397 fn grid_placement_erased(&self) -> Option<GridPlacement> {
1398 self.get_grid_placement()
1399 }
1400
1401 fn clone_box(&self) -> Box<dyn ErasedView> {
1402 Box::new(self.clone())
1403 }
1404}
1405
1406pub struct MemoView<V, F> {
1409 id: u64,
1410 data_hash: u64,
1411 builder: F,
1412 _v: std::marker::PhantomData<V>,
1413}
1414
1415impl<V: View, F: Fn() -> V + Send + Sync> MemoView<V, F> {
1416 pub fn new(id: u64, data_hash: u64, builder: F) -> Self {
1418 Self {
1419 id,
1420 data_hash,
1421 builder,
1422 _v: std::marker::PhantomData,
1423 }
1424 }
1425}
1426
1427impl<V: View + 'static, F: Fn() -> V + Send + Sync + 'static> View for MemoView<V, F> {
1428 type Body = Never;
1429 fn body(self) -> Self::Body {
1430 unreachable!("MemoView does not have a body")
1431 }
1432
1433 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1434 renderer.memoize(self.id, self.data_hash, &|r| {
1435 let view = (self.builder)();
1436 view.render(r, rect);
1437 });
1438 }
1439}
1440
1441pub struct AnyView {
1443 inner: Box<dyn ErasedView>,
1444}
1445
1446impl Clone for AnyView {
1447 fn clone(&self) -> Self {
1448 Self {
1449 inner: self.inner.clone_box(),
1450 }
1451 }
1452}
1453
1454impl AnyView {
1455 pub fn new<V: View + Clone + 'static>(view: V) -> Self {
1456 Self {
1457 inner: Box::new(view),
1458 }
1459 }
1460}
1461
1462impl View for AnyView {
1463 type Body = Never;
1464 fn body(self) -> Self::Body {
1465 unreachable!()
1466 }
1467
1468 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1469 renderer.push_vnode(rect, self.inner.name());
1470 self.inner.render_erased(renderer, rect);
1471 renderer.pop_vnode();
1472 }
1473
1474 fn flex_weight(&self) -> f32 {
1475 self.inner.flex_weight_erased()
1476 }
1477
1478 fn layout(&self) -> Option<&dyn layout::LayoutView> {
1479 self.inner.layout_erased()
1480 }
1481
1482 fn get_grid_placement(&self) -> Option<GridPlacement> {
1483 self.inner.grid_placement_erased()
1484 }
1485}
1486
1487#[derive(Debug, Clone, PartialEq)]
1491pub struct BifrostBridgeModifier {
1492 pub id: String,
1493}
1494
1495impl ViewModifier for BifrostBridgeModifier {
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.register_shared_element(&self.id, rect);
1503 }
1504}
1505
1506#[derive(Debug, Clone, Copy, PartialEq)]
1509pub struct MjolnirSliceModifier {
1510 pub angle: f32,
1511 pub offset: f32,
1512}
1513
1514impl ViewModifier for MjolnirSliceModifier {
1515 fn modify<V: View>(self, content: V) -> impl View {
1516 ModifiedView::new(content, self)
1517 }
1518
1519 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1520 renderer.push_mjolnir_slice(self.angle, self.offset);
1521 }
1522
1523 fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1524 renderer.pop_mjolnir_slice();
1525 }
1526}
1527
1528#[derive(Debug, Clone, Copy, PartialEq)]
1531pub struct MjolnirShatterModifier {
1532 pub pieces: u32,
1533 pub force: f32,
1534}
1535
1536impl ViewModifier for MjolnirShatterModifier {
1537 fn modify<V: View>(self, content: V) -> impl View {
1538 ModifiedView::new(content, self)
1539 }
1540
1541 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1542 let pieces = self.pieces.max(1);
1544 for i in 0..pieces {
1545 let progress = i as f32 / pieces as f32;
1546 let next_progress = (i + 1) as f32 / pieces as f32;
1547
1548 let angle_start = progress * 360.0;
1549 let angle_end = next_progress * 360.0;
1550
1551 renderer.push_mjolnir_slice(angle_start, 0.0);
1553 renderer.push_mjolnir_slice(angle_end + 180.0, 0.0);
1554
1555 let mid_angle = (angle_start + angle_end) / 2.0;
1557 let rad = mid_angle.to_radians();
1558 let dx = rad.cos() * self.force;
1559 let dy = rad.sin() * self.force;
1560
1561 let shard_rect = Rect {
1562 x: rect.x + dx,
1563 y: rect.y + dy,
1564 ..rect
1565 };
1566
1567 view.render(renderer, shard_rect);
1568
1569 renderer.pop_mjolnir_slice();
1570 renderer.pop_mjolnir_slice();
1571 }
1572 }
1573}
1574
1575#[derive(Debug, Clone, Copy, PartialEq)]
1578pub struct BifrostModifier {
1579 pub blur: f32,
1580 pub saturation: f32,
1581 pub opacity: f32,
1582 pub fresnel_strength: f32,
1584}
1585
1586impl ViewModifier for BifrostModifier {
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 if renderer.is_over_budget() {
1593 renderer.bifrost(rect, self.blur * 0.5, self.saturation, self.opacity);
1595 } else {
1596 renderer.bifrost(rect, self.blur, self.saturation, self.opacity);
1597 }
1598 }
1599}
1600
1601#[derive(Debug, Clone, Copy, PartialEq)]
1603pub struct BackgroundModifier {
1604 pub color: [f32; 4],
1605}
1606
1607impl ViewModifier for BackgroundModifier {
1608 fn modify<V: View>(self, content: V) -> impl View {
1609 ModifiedView::new(content, self)
1610 }
1611
1612 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1613 renderer.fill_rect(rect, self.color);
1614 }
1615}
1616
1617#[derive(Debug, Clone, Copy, PartialEq)]
1619pub struct PaddingModifier {
1620 pub amount: f32,
1621}
1622
1623impl ViewModifier for PaddingModifier {
1624 fn modify<V: View>(self, content: V) -> impl View {
1625 ModifiedView::new(content, self)
1626 }
1627
1628 fn transform_rect(&self, rect: Rect) -> Rect {
1629 Rect {
1630 x: rect.x + self.amount,
1631 y: rect.y + self.amount,
1632 width: (rect.width - 2.0 * self.amount).max(0.0),
1633 height: (rect.height - 2.0 * self.amount).max(0.0),
1634 }
1635 }
1636
1637 fn transform_proposal(&self, mut proposal: SizeProposal) -> SizeProposal {
1638 if let Some(w) = proposal.width {
1639 proposal.width = Some((w - 2.0 * self.amount).max(0.0));
1640 }
1641 if let Some(h) = proposal.height {
1642 proposal.height = Some((h - 2.0 * self.amount).max(0.0));
1643 }
1644 proposal
1645 }
1646
1647 fn transform_size(&self, mut size: Size) -> Size {
1648 size.width += 2.0 * self.amount;
1649 size.height += 2.0 * self.amount;
1650 size
1651 }
1652}
1653
1654#[derive(Debug, Clone, PartialEq)]
1657pub struct GungnirModifier {
1658 pub color: String,
1659 pub radius: f32,
1660 pub intensity: f32,
1661}
1662
1663impl ViewModifier for GungnirModifier {
1664 fn modify<V: View>(self, content: V) -> impl View {
1665 ModifiedView::new(content, self)
1666 }
1667
1668 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1669 renderer.stroke_rect(rect, [0.0, 1.0, 1.0, self.intensity], self.radius / 10.0);
1671 }
1672}
1673
1674#[derive(Debug, Clone, Copy, PartialEq)]
1676pub struct GungnirPulseModifier {
1677 pub color: [f32; 4],
1678 pub radius: f32,
1679 pub speed: f32,
1680}
1681
1682impl ViewModifier for GungnirPulseModifier {
1683 fn modify<V: View>(self, content: V) -> impl View {
1684 ModifiedView::new(content, self)
1685 }
1686
1687 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1688 let time = std::time::SystemTime::now()
1689 .duration_since(std::time::UNIX_EPOCH)
1690 .unwrap_or_default()
1691 .as_secs_f32();
1692
1693 let intensity = (time * self.speed).sin() * 0.5 + 0.5;
1696 let mut color = self.color;
1697 color[3] *= intensity;
1698
1699 renderer.stroke_rect(rect, color, self.radius);
1701 }
1702}
1703
1704#[derive(Debug, Clone, Copy, PartialEq)]
1706pub struct MagneticModifier {
1707 pub radius: f32,
1708 pub intensity: f32,
1709}
1710
1711impl ViewModifier for MagneticModifier {
1712 fn modify<V: View>(self, content: V) -> impl View {
1713 ModifiedView::new(content, self)
1714 }
1715
1716 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1717 let [px, py] = renderer.get_pointer_position();
1718 let center_x = rect.x + rect.width / 2.0;
1719 let center_y = rect.y + rect.height / 2.0;
1720
1721 let dx = px - center_x;
1722 let dy = py - center_y;
1723 let dist = (dx * dx + dy * dy).sqrt();
1724
1725 let mut offset_x = 0.0;
1726 let mut offset_y = 0.0;
1727
1728 if dist < self.radius && dist > 0.0 {
1729 let force = (1.0 - dist / self.radius) * self.intensity;
1730 offset_x = dx * force;
1731 offset_y = dy * force;
1732 }
1733
1734 let magnetic_rect = Rect {
1735 x: rect.x + offset_x,
1736 y: rect.y + offset_y,
1737 ..rect
1738 };
1739
1740 view.render(renderer, magnetic_rect);
1741 }
1742}
1743
1744#[derive(Debug, Clone, Copy, PartialEq)]
1747pub struct ManiGlowModifier {
1748 pub color: [f32; 4],
1749 pub radius: f32,
1750}
1751
1752impl ViewModifier for ManiGlowModifier {
1753 fn modify<V: View>(self, content: V) -> impl View {
1754 ModifiedView::new(content, self)
1755 }
1756
1757 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1758 if crate::load_system_state().realm == Realm::Asgard {
1759 renderer.mani_glow(rect, self.color, self.radius);
1760 }
1761 view.render(renderer, rect);
1762 }
1763}
1764
1765#[derive(Debug, Clone, Copy, PartialEq)]
1770pub struct BifrostLayerModifier {
1771 pub layer: MemoryLayer,
1772}
1773
1774impl ViewModifier for BifrostLayerModifier {
1775 fn modify<V: View>(self, content: V) -> impl View {
1776 ModifiedView::new(content, self)
1777 }
1778
1779 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1780 let realm = crate::load_system_state().realm;
1781 match self.layer {
1782 MemoryLayer::Episodic => {
1783 if realm == Realm::Asgard {
1784 renderer.bifrost(rect, 40.0, 1.2, 0.7);
1785 } else {
1786 renderer.fill_rect(rect, [0.1, 0.12, 0.15, 0.8]);
1787 }
1788 }
1789 MemoryLayer::Semantic => {
1790 if realm == Realm::Asgard {
1791 renderer.gungnir(rect, [1.0, 0.84, 0.0, 1.0], 15.0, 0.6);
1792 } else {
1793 renderer.stroke_rect(rect, [0.4, 0.4, 0.4, 1.0], 1.5);
1794 }
1795 }
1796 MemoryLayer::Procedural => {
1797 renderer.fill_rect(rect, [0.05, 0.05, 0.07, 0.95]);
1798 let stroke_color = if realm == Realm::Asgard {
1799 [0.3, 0.3, 0.3, 1.0]
1800 } else {
1801 [0.2, 0.2, 0.2, 1.0]
1802 };
1803 renderer.stroke_rect(rect, stroke_color, 2.0);
1804 }
1805 }
1806 view.render(renderer, rect);
1807 }
1808}
1809
1810#[derive(Debug, Clone, Copy, PartialEq)]
1814pub struct FafnirModifier {
1815 pub id: u64,
1817}
1818
1819impl ViewModifier for FafnirModifier {
1820 fn modify<V: View>(self, content: V) -> impl View {
1821 ModifiedView::new(content, self)
1822 }
1823
1824 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1825 let state = crate::load_system_state();
1826 let vitality = state
1827 .get_component_state::<f32>(self.id)
1828 .map(|v| *v.read().unwrap())
1829 .unwrap_or(1.0);
1830
1831 let growth = (vitality - 1.0).clamp(0.0, 4.0);
1834 let scale = 1.0 + growth * 0.12;
1835 let glow_intensity = growth * 0.25;
1836
1837 let id = self.id;
1839 renderer.register_handler(
1840 "pointermove",
1841 std::sync::Arc::new(move |_| {
1842 crate::update_system_state(|s| {
1843 let mut s = s.clone();
1844 let v = s
1845 .get_component_state::<f32>(id)
1846 .map(|v| *v.read().unwrap())
1847 .unwrap_or(1.0);
1848 s.set_component_state(id, (v + 0.05).min(5.0)); s
1850 });
1851 }),
1852 );
1853
1854 if scale > 1.01 {
1855 renderer.push_transform([0.0, 0.0], [scale, scale], 0.0);
1856 }
1857
1858 if glow_intensity > 0.1 && state.realm == Realm::Asgard {
1859 renderer.gungnir(rect, [1.0, 0.84, 0.0, 1.0], 15.0 * vitality, glow_intensity);
1860 }
1861
1862 view.render(renderer, rect);
1863
1864 if scale > 1.01 {
1865 renderer.pop_transform();
1866 }
1867 }
1868}
1869
1870#[derive(Debug, Clone, Copy, PartialEq)]
1872pub struct MimirIntentModifier;
1873
1874impl ViewModifier for MimirIntentModifier {
1875 fn modify<V: View>(self, content: V) -> impl View {
1876 ModifiedView::new(content, self)
1877 }
1878
1879 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1880 let state = crate::load_system_state();
1881 let pos = state.last_pointer_pos;
1882 let vel = state.pointer_velocity;
1883
1884 let center = [rect.x + rect.width / 2.0, rect.y + rect.height / 2.0];
1886 let dx = center[0] - pos[0];
1887 let dy = center[1] - pos[1];
1888
1889 let dot = vel[0] * dx + vel[1] * dy;
1891 let speed_sq = vel[0] * vel[0] + vel[1] * vel[1];
1892 let dist_sq = dx * dx + dy * dy;
1893
1894 if dot > 0.0 && dist_sq < 250.0 * 250.0 && speed_sq > 0.5 && state.realm == Realm::Asgard {
1895 let intent_strength = (dot / (speed_sq.sqrt() * dist_sq.sqrt())).clamp(0.0, 1.0);
1897 renderer.stroke_rect(rect, [0.0, 0.9, 1.0, 0.3 * intent_strength], 1.5);
1898 }
1899
1900 view.render(renderer, rect);
1901 }
1902}
1903
1904#[derive(Debug, Clone, Copy, PartialEq)]
1906pub struct KvasirVibeModifier {
1907 pub complexity: f32,
1908}
1909
1910impl ViewModifier for KvasirVibeModifier {
1911 fn modify<V: View>(self, content: V) -> impl View {
1912 ModifiedView::new(content, self)
1913 }
1914
1915 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1916 if crate::load_system_state().realm == Realm::Asgard {
1917 let t = renderer.elapsed_time();
1918 let c = self.complexity.clamp(0.0, 1.0);
1919
1920 let blur = 20.0 + c * 40.0;
1923 let turbulence_x = (t * (1.0 + c * 2.0)).sin() * 8.0 * c;
1924 let turbulence_y = (t * (0.8 + c * 1.5)).cos() * 5.0 * c;
1925 renderer.bifrost(
1926 rect.offset(turbulence_x, turbulence_y),
1927 blur,
1928 0.8 + c * 0.4,
1929 0.25,
1930 );
1931
1932 if c > 0.2 {
1934 let pulse = (t * (3.0 + c * 5.0)).sin().abs() * c;
1935 let color = [0.0, 0.9, 1.0, 0.4 * pulse]; renderer.gungnir(rect, color, 12.0 + c * 24.0, 0.6 * pulse);
1937 }
1938
1939 if c > 0.7 {
1941 let instability = (t * 15.0).cos().abs() * (c - 0.7) * 3.3;
1942 let warning_color = [1.0, 0.0, 0.4, 0.12 * instability];
1943 renderer.fill_rect(rect, warning_color);
1944 renderer.stroke_rect(rect, [1.0, 0.0, 0.2, 0.45 * instability], 1.8);
1945 }
1946 }
1947 view.render(renderer, rect);
1948 }
1949}
1950
1951#[derive(Debug, Clone, Copy, PartialEq)]
1953pub struct OdinsEyeModifier;
1954
1955impl ViewModifier for OdinsEyeModifier {
1956 fn modify<V: View>(self, content: V) -> impl View {
1957 ModifiedView::new(content, self)
1958 }
1959
1960 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1961 let state = crate::load_system_state();
1962 let t = renderer.elapsed_time();
1963
1964 view.render(renderer, rect);
1966
1967 if state.realm == Realm::Asgard {
1968 let eye_pulse = (t * 0.5).sin().abs() * 0.05;
1971 renderer.draw_radial_gradient(
1972 rect,
1973 [0.0, 0.6, 0.8, 0.08 + eye_pulse], [0.0, 0.0, 0.0, 0.0], );
1976
1977 let hugin_rect = Rect {
1979 x: rect.x + 20.0,
1980 y: rect.y + 40.0,
1981 width: 200.0,
1982 height: rect.height - 80.0,
1983 };
1984 renderer.draw_text(
1985 "HUGIN: THOUGHT",
1986 hugin_rect.x,
1987 hugin_rect.y,
1988 10.0,
1989 [0.0, 1.0, 1.0, 0.6],
1990 );
1991 for (i, thought) in state.thoughts.iter().rev().take(10).enumerate() {
1992 renderer.draw_text(
1993 thought,
1994 hugin_rect.x,
1995 hugin_rect.y + 20.0 + i as f32 * 14.0,
1996 9.0,
1997 [1.0, 1.0, 1.0, 0.4],
1998 );
1999 }
2000
2001 let munin_rect = Rect {
2003 x: rect.x + rect.width - 220.0,
2004 y: rect.y + 40.0,
2005 width: 200.0,
2006 height: rect.height - 80.0,
2007 };
2008 renderer.draw_text(
2009 "MUNIN: MEMORY",
2010 munin_rect.x,
2011 munin_rect.y,
2012 10.0,
2013 [1.0, 0.84, 0.0, 0.6],
2014 );
2015 for (i, node) in state.nodes.iter().take(10).enumerate() {
2016 let opacity = (node.weight.min(1.0)) * 0.5;
2017 renderer.draw_text(
2018 &node.id,
2019 munin_rect.x,
2020 munin_rect.y + 20.0 + i as f32 * 14.0,
2021 9.0,
2022 [1.0, 1.0, 1.0, opacity],
2023 );
2024 }
2025
2026 if let Some(focus_id) = &state.odin_focus {
2028 renderer.draw_text(
2030 &format!("EYE FOCUS: {}", focus_id),
2031 rect.x + rect.width / 2.0 - 50.0,
2032 rect.y + 20.0,
2033 12.0,
2034 [0.0, 1.0, 1.0, 0.8],
2035 );
2036
2037 renderer.gungnir(
2040 Rect {
2041 x: rect.x + rect.width / 2.0 - 1.0,
2042 y: rect.y,
2043 width: 2.0,
2044 height: rect.height,
2045 },
2046 [0.0, 1.0, 1.0, 1.0],
2047 20.0,
2048 0.4,
2049 );
2050 }
2051 }
2052 }
2053}
2054
2055#[derive(Debug, Clone, Copy, PartialEq)]
2057pub struct SleipnirParams {
2058 pub stiffness: f32,
2059 pub damping: f32,
2060 pub mass: f32,
2061}
2062
2063impl SleipnirParams {
2064 pub fn snappy() -> Self {
2065 Self {
2066 stiffness: 230.0,
2067 damping: 22.0,
2068 mass: 1.0,
2069 }
2070 }
2071 pub fn fluid() -> Self {
2072 Self {
2073 stiffness: 170.0,
2074 damping: 26.0,
2075 mass: 1.0,
2076 }
2077 }
2078 pub fn heavy() -> Self {
2079 Self {
2080 stiffness: 90.0,
2081 damping: 20.0,
2082 mass: 1.0,
2083 }
2084 }
2085 pub fn bouncy() -> Self {
2086 Self {
2087 stiffness: 190.0,
2088 damping: 14.0,
2089 mass: 1.0,
2090 }
2091 }
2092}
2093
2094impl Default for SleipnirParams {
2095 fn default() -> Self {
2096 Self::fluid()
2097 }
2098}
2099
2100#[derive(Debug, Clone, Copy, PartialEq)]
2101struct SolverState {
2102 x: f32,
2103 v: f32,
2104}
2105
2106#[derive(Debug, Clone, Copy, PartialEq)]
2109pub struct SleipnirSolver {
2110 params: SleipnirParams,
2111 target: f32,
2112 state: SolverState,
2113}
2114
2115impl SleipnirSolver {
2116 pub fn new(params: SleipnirParams, target: f32, current: f32) -> Self {
2118 Self {
2119 params,
2120 target,
2121 state: SolverState { x: current, v: 0.0 },
2122 }
2123 }
2124
2125 pub fn tick(&mut self, dt: f32) -> f32 {
2127 if dt <= 0.0 {
2128 return self.state.x;
2129 }
2130
2131 let mut remaining = dt;
2133 let step = 1.0 / 120.0;
2134
2135 while remaining > 0.0 {
2136 let d = remaining.min(step);
2137 self.step(d);
2138 remaining -= d;
2139 }
2140
2141 self.state.x
2142 }
2143
2144 fn step(&mut self, dt: f32) {
2145 let a = self.evaluate(self.state, 0.0, SolverState { x: 0.0, v: 0.0 });
2146 let b = self.evaluate(self.state, dt * 0.5, a);
2147 let c = self.evaluate(self.state, dt * 0.5, b);
2148 let d = self.evaluate(self.state, dt, c);
2149
2150 let dxdt = 1.0 / 6.0 * (a.x + 2.0 * (b.x + c.x) + d.x);
2151 let dvdt = 1.0 / 6.0 * (a.v + 2.0 * (b.v + c.v) + d.v);
2152
2153 self.state.x += dxdt * dt;
2154 self.state.v += dvdt * dt;
2155 }
2156
2157 fn evaluate(&self, initial: SolverState, dt: f32, d: SolverState) -> SolverState {
2158 let state = SolverState {
2159 x: initial.x + d.x * dt,
2160 v: initial.v + d.v * dt,
2161 };
2162 let force =
2163 -self.params.stiffness * (state.x - self.target) - self.params.damping * state.v;
2164 let mass = self.params.mass.max(0.001);
2165 SolverState {
2166 x: state.v,
2167 v: force / mass,
2168 }
2169 }
2170
2171 pub fn is_settled(&self) -> bool {
2172 (self.state.x - self.target).abs() < 0.001 && self.state.v.abs() < 0.001
2173 }
2174
2175 pub fn set_target(&mut self, target: f32) {
2176 self.target = target;
2177 }
2178
2179 pub fn current_value(&self) -> f32 {
2180 self.state.x
2181 }
2182}
2183
2184#[derive(Debug, Clone, PartialEq)]
2186pub struct SleipnirModifier {
2187 pub id: u64,
2188 pub target: f32,
2189 pub params: SleipnirParams,
2190}
2191
2192impl ViewModifier for SleipnirModifier {
2193 fn modify<V: View>(self, content: V) -> impl View {
2194 ModifiedView::new(content, self)
2195 }
2196
2197 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2198 let state = load_system_state();
2199
2200 let solver_lock_opt = state.get_component_state::<SleipnirSolver>(self.id);
2202
2203 let current_val;
2204
2205 if let Some(lock) = solver_lock_opt {
2206 let mut solver = lock.write().unwrap();
2208 solver.set_target(self.target);
2209 current_val = solver.tick(renderer.delta_time());
2210
2211 if !solver.is_settled() {
2213 renderer.request_redraw();
2214 }
2215 } else {
2216 let solver = SleipnirSolver::new(
2218 self.params,
2219 self.target,
2220 self.target, );
2222
2223 get_system_state().rcu(|old| {
2225 let mut new_state = (**old).clone();
2226 new_state.set_component_state(self.id, solver);
2227 new_state
2228 });
2229
2230 current_val = self.target;
2231 }
2232
2233 renderer.push_transform([0.0, current_val], [1.0, 1.0], 0.0);
2235 view.render(renderer, rect);
2236 renderer.pop_transform();
2237 }
2238}
2239
2240#[derive(Debug, Clone, Copy, PartialEq)]
2243pub struct TransformModifier {
2244 pub translation: [f32; 2],
2245 pub scale: [f32; 2],
2246 pub rotation: f32,
2247}
2248
2249impl Default for TransformModifier {
2250 fn default() -> Self {
2251 Self::new()
2252 }
2253}
2254
2255impl TransformModifier {
2256 pub fn new() -> Self {
2257 Self {
2258 translation: [0.0, 0.0],
2259 scale: [1.0, 1.0],
2260 rotation: 0.0,
2261 }
2262 }
2263
2264 pub fn translate(mut self, x: f32, y: f32) -> Self {
2265 self.translation = [x, y];
2266 self
2267 }
2268
2269 pub fn scale(mut self, x: f32, y: f32) -> Self {
2270 self.scale = [x, y];
2271 self
2272 }
2273
2274 pub fn rotate(mut self, radians: f32) -> Self {
2275 self.rotation = radians;
2276 self
2277 }
2278}
2279
2280impl ViewModifier for TransformModifier {
2281 fn modify<V: View>(self, content: V) -> impl View {
2282 ModifiedView::new(content, self)
2283 }
2284
2285 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2286 renderer.push_transform(self.translation, self.scale, self.rotation);
2287 view.render(renderer, rect);
2288 renderer.pop_transform();
2289 }
2290}
2291
2292#[derive(Clone)]
2295pub struct LifecycleModifier {
2296 pub on_appear: Option<Arc<dyn Fn() + Send + Sync>>,
2297 pub on_disappear: Option<Arc<dyn Fn() + Send + Sync>>,
2298}
2299
2300impl ViewModifier for LifecycleModifier {
2301 fn modify<V: View>(self, content: V) -> impl View {
2302 ModifiedView::new(content, self)
2303 }
2304}
2305
2306#[derive(Debug, Clone, Copy, PartialEq)]
2309pub struct OpacityModifier {
2310 pub opacity: f32,
2311}
2312
2313impl ViewModifier for OpacityModifier {
2314 fn modify<V: View>(self, content: V) -> impl View {
2315 ModifiedView::new(content, self)
2316 }
2317
2318 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2319 renderer.push_opacity(self.opacity);
2320 }
2321
2322 fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2323 renderer.pop_opacity();
2324 }
2325}
2326
2327#[derive(Clone)]
2329pub struct OnClickModifier {
2330 pub action: Arc<dyn Fn() + Send + Sync>,
2331}
2332
2333impl ViewModifier for OnClickModifier {
2334 fn modify<V: View>(self, content: V) -> impl View {
2335 ModifiedView::new(content, self)
2336 }
2337
2338 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2339 let action = self.action.clone();
2340 renderer.register_handler(
2341 "pointerclick",
2342 std::sync::Arc::new(move |event| {
2343 if let Event::PointerClick { .. } = event {
2344 (action)();
2345 }
2346 }),
2347 );
2348 }
2349}
2350
2351#[derive(Clone)]
2353pub struct OnPointerEnterModifier {
2354 pub action: Arc<dyn Fn() + Send + Sync>,
2355}
2356
2357impl ViewModifier for OnPointerEnterModifier {
2358 fn modify<V: View>(self, content: V) -> impl View {
2359 ModifiedView::new(content, self)
2360 }
2361
2362 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2363 let action = self.action.clone();
2364 renderer.register_handler(
2365 "pointerenter",
2366 std::sync::Arc::new(move |event| {
2367 if let Event::PointerEnter = event {
2368 (action)();
2369 }
2370 }),
2371 );
2372 }
2373}
2374
2375#[derive(Clone)]
2377pub struct OnPointerLeaveModifier {
2378 pub action: Arc<dyn Fn() + Send + Sync>,
2379}
2380
2381impl ViewModifier for OnPointerLeaveModifier {
2382 fn modify<V: View>(self, content: V) -> impl View {
2383 ModifiedView::new(content, self)
2384 }
2385
2386 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2387 let action = self.action.clone();
2388 renderer.register_handler(
2389 "pointerleave",
2390 std::sync::Arc::new(move |event| {
2391 if let Event::PointerLeave = event {
2392 (action)();
2393 }
2394 }),
2395 );
2396 }
2397}
2398
2399#[derive(Clone)]
2401pub struct OnPointerMoveModifier {
2402 pub action: Arc<dyn Fn(f32, f32) + Send + Sync>,
2403}
2404
2405impl ViewModifier for OnPointerMoveModifier {
2406 fn modify<V: View>(self, content: V) -> impl View {
2407 ModifiedView::new(content, self)
2408 }
2409
2410 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2411 let action = self.action.clone();
2412 renderer.register_handler(
2413 "pointermove",
2414 std::sync::Arc::new(move |event| {
2415 if let Event::PointerMove { x, y, .. } = event {
2416 (action)(x, y);
2417 }
2418 }),
2419 );
2420 }
2421}
2422
2423#[derive(Clone)]
2425pub struct OnPointerDownModifier {
2426 pub action: Arc<dyn Fn() + Send + Sync>,
2427}
2428
2429impl ViewModifier for OnPointerDownModifier {
2430 fn modify<V: View>(self, content: V) -> impl View {
2431 ModifiedView::new(content, self)
2432 }
2433
2434 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2435 let action = self.action.clone();
2436 renderer.register_handler(
2437 "pointerdown",
2438 std::sync::Arc::new(move |event| {
2439 if let Event::PointerDown { .. } = event {
2440 (action)();
2441 }
2442 }),
2443 );
2444 }
2445}
2446
2447#[derive(Clone)]
2449pub struct OnPointerUpModifier {
2450 pub action: Arc<dyn Fn() + Send + Sync>,
2451}
2452
2453impl ViewModifier for OnPointerUpModifier {
2454 fn modify<V: View>(self, content: V) -> impl View {
2455 ModifiedView::new(content, self)
2456 }
2457
2458 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2459 let action = self.action.clone();
2460 renderer.register_handler(
2461 "pointerup",
2462 std::sync::Arc::new(move |event| {
2463 if let Event::PointerUp { .. } = event {
2464 (action)();
2465 }
2466 }),
2467 );
2468 }
2469}
2470
2471#[derive(Debug, Clone, Copy, PartialEq)]
2474pub struct ForegroundColorModifier {
2475 pub color: [f32; 4],
2476}
2477
2478impl ViewModifier for ForegroundColorModifier {
2479 fn modify<V: View>(self, content: V) -> impl View {
2480 ModifiedView::new(content, self)
2481 }
2482}
2483
2484#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2487pub struct ClipModifier;
2488
2489impl ViewModifier for ClipModifier {
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.push_clip_rect(rect);
2496 }
2497
2498 fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2499 renderer.pop_clip_rect();
2500 }
2501}
2502
2503#[derive(Debug, Clone, Copy, PartialEq)]
2505pub struct BorderModifier {
2506 pub color: [f32; 4],
2507 pub width: f32,
2508}
2509
2510impl ViewModifier for BorderModifier {
2511 fn modify<V: View>(self, content: V) -> impl View {
2512 ModifiedView::new(content, self)
2513 }
2514
2515 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2516 renderer.stroke_rect(rect, self.color, self.width);
2517 }
2518}
2519
2520#[doc(hidden)]
2522pub enum Never {}
2523
2524impl View for Never {
2525 type Body = Never;
2526 fn body(self) -> Never {
2527 unreachable!()
2528 }
2529}
2530
2531#[derive(Debug, Clone, Copy, Default)]
2533pub struct EmptyView;
2534
2535impl View for EmptyView {
2536 type Body = Never;
2537 fn body(self) -> Self::Body {
2538 unreachable!()
2539 }
2540 fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2541 fn intrinsic_size(&self, _renderer: &mut dyn Renderer, _proposal: SizeProposal) -> Size {
2542 Size {
2543 width: 0.0,
2544 height: 0.0,
2545 }
2546 }
2547}
2548
2549#[derive(Clone)]
2552pub struct ModifiedView<V, M> {
2553 view: V,
2554 modifier: M,
2555}
2556
2557impl<V: View, M: ViewModifier> ModifiedView<V, M> {
2558 #[doc(hidden)]
2559 pub fn new(view: V, modifier: M) -> Self {
2560 Self { view, modifier }
2561 }
2562}
2563
2564impl<V: View, M: ViewModifier> View for ModifiedView<V, M> {
2565 type Body = ModifiedView<V::Body, M>;
2566
2567 fn body(self) -> Self::Body {
2568 ModifiedView {
2569 view: self.view.body(),
2570 modifier: self.modifier.clone(),
2571 }
2572 }
2573
2574 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2575 self.modifier.render_view(&self.view, renderer, rect);
2576 }
2577
2578 fn intrinsic_size(&self, renderer: &mut dyn Renderer, proposal: SizeProposal) -> Size {
2579 self.modifier.measure_view(&self.view, renderer, proposal)
2580 }
2581
2582 fn flex_weight(&self) -> f32 {
2583 self.modifier.child_flex_weight(&self.view)
2584 }
2585
2586 fn layout(&self) -> Option<&dyn layout::LayoutView> {
2587 self.modifier.layout().or_else(|| self.view.layout())
2588 }
2589
2590 fn get_grid_placement(&self) -> Option<GridPlacement> {
2591 self.modifier
2592 .get_grid_placement()
2593 .or_else(|| self.view.get_grid_placement())
2594 }
2595}
2596
2597pub trait ViewModifier: Send + Clone {
2598 fn modify<V: View>(self, content: V) -> impl View;
2599
2600 fn get_grid_placement(&self) -> Option<GridPlacement> {
2602 None
2603 }
2604
2605 fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2607
2608 fn post_render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2610
2611 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2614 self.render(renderer, rect);
2615 let child_rect = self.transform_rect(rect);
2616 view.render(renderer, child_rect);
2617 self.post_render(renderer, rect);
2618 }
2619
2620 fn transform_rect(&self, rect: Rect) -> Rect {
2621 rect
2622 }
2623
2624 fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
2626 proposal
2627 }
2628
2629 fn transform_size(&self, size: Size) -> Size {
2631 size
2632 }
2633
2634 fn measure_view<V: View>(
2636 &self,
2637 view: &V,
2638 renderer: &mut dyn Renderer,
2639 proposal: SizeProposal,
2640 ) -> Size {
2641 let child_proposal = self.transform_proposal(proposal);
2642 let child_size = view.intrinsic_size(renderer, child_proposal);
2643 self.transform_size(child_size)
2644 }
2645
2646 fn child_flex_weight<V: View>(&self, view: &V) -> f32 {
2648 view.flex_weight()
2649 }
2650
2651 fn layout(&self) -> Option<&dyn layout::LayoutView> {
2652 None
2653 }
2654}
2655
2656#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
2658pub struct TelemetryData {
2659 pub frame_time_ms: f32,
2660 pub p99_frame_time_ms: f32,
2662 pub frame_jitter_ms: f32,
2664 pub hardware_stall_detected: bool,
2666
2667 pub input_time_ms: f32,
2669 pub state_flush_time_ms: f32,
2670 pub layout_time_ms: f32,
2671 pub draw_time_ms: f32,
2672 pub gpu_submit_time_ms: f32,
2673
2674 pub draw_calls: u32,
2675 pub vertices: u32,
2676
2677 pub berserker_rage: f32,
2679
2680 pub vram_usage_mb: f32,
2682 pub vram_textures_mb: f32,
2683 pub vram_buffers_mb: f32,
2684 pub vram_pipelines_mb: f32,
2685 pub vram_exhausted: bool,
2687}
2688
2689#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2691pub struct FrameBudget {
2692 pub target_ms: f32,
2694 pub allow_degradation: bool,
2697}
2698
2699impl Default for FrameBudget {
2700 fn default() -> Self {
2701 Self {
2702 target_ms: 16.0,
2703 allow_degradation: true,
2704 }
2705 }
2706}
2707
2708pub trait ElapsedTime {
2716 fn elapsed_time(&self) -> f32;
2718
2719 fn delta_time(&self) -> f32;
2721}
2722
2723pub trait Renderer: ElapsedTime + Send {
2730 fn request_redraw(&mut self) {}
2733
2734 fn is_over_budget(&self) -> bool {
2737 false
2738 }
2739
2740 fn fill_rect(&mut self, rect: Rect, color: [f32; 4]);
2742 fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]);
2743 fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]);
2745
2746 fn fill_glass_rect(&mut self, rect: Rect, radius: f32, blur_radius: f32) {
2750 let _ = (rect, radius, blur_radius);
2752 }
2753
2754 fn draw_3d_cube(&mut self, _rect: Rect, _color: [f32; 4], _rotation: [f32; 3]) {}
2757
2758 fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
2760 fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32);
2761 fn stroke_ellipse(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
2763 fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: [f32; 4], stroke_width: f32);
2765 fn fill_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4]) {}
2767 fn stroke_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4], _stroke_width: f32) {}
2769
2770 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]);
2772 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32);
2774
2775 fn shape_rich_text(
2776 &mut self,
2777 _spans: &[cvkg_runic_text::TextSpan],
2778 _max_width: Option<f32>,
2779 _align: cvkg_runic_text::TextAlign,
2780 _overflow: cvkg_runic_text::TextOverflow,
2781 ) -> Option<cvkg_runic_text::ShapedText> {
2782 None
2783 }
2784
2785 fn draw_shaped_text(&mut self, _text: &cvkg_runic_text::ShapedText, _x: f32, _y: f32) {}
2786
2787 fn draw_texture(&mut self, _texture_id: u32, _rect: Rect) {}
2790 fn draw_image(&mut self, _image_name: &str, _rect: Rect) {}
2792 fn load_image(&mut self, _name: &str, _data: &[u8]) {}
2794 fn prewarm_vram(&mut self, _assets: Vec<(String, Vec<u8>)>) {}
2797
2798 fn get_pointer_position(&self) -> [f32; 2] {
2800 [0.0, 0.0]
2801 }
2802
2803 fn upload_data_texture(&mut self, _id: &str, _data: &[f32], _width: u32, _height: u32) {}
2806 fn draw_heatmap(&mut self, _texture_id: &str, _rect: Rect, _palette: &str) {}
2808
2809 fn draw_mesh(&mut self, _mesh: &Mesh, _color: [f32; 4], _transform: glam::Mat4) {}
2812
2813 fn draw_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {}
2815
2816 fn set_camera_3d(&mut self, _camera: &Camera3D) {}
2819
2820 fn push_transform_3d(&mut self, _transform: &Transform3D) {}
2823
2824 fn pop_transform_3d(&mut self) {}
2826
2827 fn render_scene_node_3d(
2836 &mut self,
2837 _position: [f32; 3],
2838 _rotation: [f32; 4],
2839 _scale: [f32; 3],
2840 _color: [f32; 4],
2841 _meshes: &[Mesh],
2842 ) {
2843 }
2845
2846 fn draw_linear_gradient(
2848 &mut self,
2849 _rect: Rect,
2850 _start_color: [f32; 4],
2851 _end_color: [f32; 4],
2852 _angle: f32,
2853 ) {
2854 }
2855 fn draw_radial_gradient(
2857 &mut self,
2858 _rect: Rect,
2859 _inner_color: [f32; 4],
2860 _outer_color: [f32; 4],
2861 ) {
2862 }
2863 fn draw_drop_shadow(
2865 &mut self,
2866 _rect: Rect,
2867 _radius: f32,
2868 _color: [f32; 4],
2869 _blur: f32,
2870 _spread: f32,
2871 ) {
2872 }
2873 fn stroke_dashed_rounded_rect(
2875 &mut self,
2876 _rect: Rect,
2877 _radius: f32,
2878 _color: [f32; 4],
2879 _width: f32,
2880 _dash: f32,
2881 _gap: f32,
2882 ) {
2883 }
2884 fn draw_9slice(
2886 &mut self,
2887 _image_name: &str,
2888 _rect: Rect,
2889 _left: f32,
2890 _top: f32,
2891 _right: f32,
2892 _bottom: f32,
2893 ) {
2894 }
2895
2896 fn push_clip_rect(&mut self, _rect: Rect) {}
2900 fn pop_clip_rect(&mut self) {}
2902 fn current_clip_rect(&self) -> Rect {
2905 Rect::new(-10000.0, -10000.0, 20000.0, 20000.0)
2906 }
2907
2908 fn push_opacity(&mut self, _opacity: f32) {}
2912 fn pop_opacity(&mut self) {}
2914
2915 fn set_theme(&mut self, _theme: ColorTheme) {}
2917 fn set_rage(&mut self, _rage: f32) {}
2918 fn set_berserker_mode(&mut self, _state: BerserkerMode) {}
2919 fn trigger_shatter_event(&mut self, _origin: [f32; 2], _force: f32) {}
2920 fn set_scene(&mut self, _scene: &str) {}
2922
2923 fn capture_png(&mut self) -> Vec<u8> {
2926 Vec::new()
2927 }
2928 fn print(&mut self) {}
2930
2931 fn set_scene_preset(&mut self, _preset: u32) {}
2932
2933 fn bifrost(&mut self, _rect: Rect, _blur: f32, _saturation: f32, _opacity: f32) {}
2936 fn gungnir(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32, _intensity: f32) {}
2938 fn mani_glow(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32) {}
2940 fn push_mjolnir_slice(&mut self, _angle: f32, _offset: f32) {}
2942 fn pop_mjolnir_slice(&mut self) {}
2943 fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer));
2947 fn mjolnir_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2949 fn mjolnir_fluid_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2950 fn draw_mjolnir_bolt(&mut self, _from: [f32; 2], _to: [f32; 2], _color: [f32; 4]) {}
2951
2952 fn dispatch_particles(
2955 &mut self,
2956 _origin: [f32; 2],
2957 _count: u32,
2958 _effect_type: &str,
2959 _color: [f32; 4],
2960 ) {
2961 }
2962
2963 fn draw_hologram(&mut self, _rect: Rect, _hologram_id: &str, _time: f32) {}
2965
2966 fn set_aria_role(&mut self, _role: &str) {}
2968 fn set_aria_label(&mut self, _label: &str) {}
2969
2970 fn register_shared_element(&mut self, _id: &str, _rect: Rect) {}
2972
2973 fn set_key(&mut self, _key: &str) {}
2975
2976 fn get_telemetry(&self) -> TelemetryData {
2979 TelemetryData::default()
2980 }
2981
2982 fn push_shadow(&mut self, _radius: f32, _color: [f32; 4], _offset: [f32; 2]) {}
2985 fn pop_shadow(&mut self) {}
2987
2988 fn push_vnode(&mut self, _rect: Rect, _name: &'static str) {}
2991 fn pop_vnode(&mut self) {}
2993 fn register_handler(
2995 &mut self,
2996 _event_type: &str,
2997 _handler: std::sync::Arc<dyn Fn(Event) + Send + Sync>,
2998 ) {
2999 }
3000
3001 fn set_z_index(&mut self, _z: f32) {}
3005 fn get_z_index(&self) -> f32 {
3007 0.0
3008 }
3009
3010 fn load_svg(&mut self, _name: &str, _svg_data: &[u8]) {}
3013 fn draw_svg(&mut self, _name: &str, _rect: Rect) {}
3015 fn serialize_svg(&mut self, _name: &str) -> Result<String, String> {
3019 Err("SVG serialization not supported by this renderer".into())
3020 }
3021 fn apply_svg_filter(
3025 &mut self,
3026 _name: &str,
3027 _filter_id: &str,
3028 _region: Rect,
3029 ) -> Result<String, String> {
3030 Err("SVG filter not supported by this renderer".into())
3031 }
3032
3033 fn push_transform(&mut self, _translation: [f32; 2], _scale: [f32; 2], _rotation: f32) {}
3038 fn push_affine(&mut self, _transform: [f32; 6]) {}
3041 fn pop_transform(&mut self) {}
3043 fn query_layout(&self, _node_id: scene_graph::NodeId) -> Option<Rect> {
3045 None
3046 }
3047 fn set_debug_layout(&mut self, _enabled: bool) {}
3049 fn get_debug_layout(&self) -> bool {
3051 false
3052 }
3053
3054 fn set_material(&mut self, _material: crate::material::DrawMaterial) {}
3058 fn current_material(&self) -> crate::material::DrawMaterial {
3060 crate::material::DrawMaterial::Opaque
3061 }
3062
3063 fn mimir_intent(&self) -> [f32; 2] {
3066 [0.0, 0.0]
3067 }
3068 fn magnetic_warp(&self, pointer: [f32; 2], anchor_rect: Rect, strength: f32) -> [f32; 2] {
3070 if strength <= 0.0 {
3071 return pointer;
3072 }
3073 let cx = anchor_rect.x + anchor_rect.width / 2.0;
3074 let cy = anchor_rect.y + anchor_rect.height / 2.0;
3075 let dx = pointer[0] - cx;
3076 let dy = pointer[1] - cy;
3077 let dist = (dx * dx + dy * dy).sqrt();
3078 let radius = 120.0;
3079 if dist < radius && dist > 0.0 {
3080 let force = (1.0 - dist / radius) * strength;
3081 [pointer[0] - dx * force, pointer[1] - dy * force]
3082 } else {
3083 pointer
3084 }
3085 }
3086 fn mani_glow_intensity(&self, pointer: [f32; 2], bounds: Rect, radius: f32) -> f32 {
3088 let cx = bounds.x + bounds.width / 2.0;
3089 let cy = bounds.y + bounds.height / 2.0;
3090 let dist = ((pointer[0] - cx).powi(2) + (pointer[1] - cy).powi(2)).sqrt();
3091 if dist < radius {
3092 (1.0 - dist / radius).clamp(0.0, 1.0)
3093 } else {
3094 0.0
3095 }
3096 }
3097 fn fafnir_evolve(&self, pointer: [f32; 2], bounds: Rect, max_scale: f32) -> f32 {
3099 let prox = self.mani_glow_intensity(pointer, bounds, 120.0);
3100 1.0 + (max_scale - 1.0) * prox
3101 }
3102 fn set_sdf_shape(&mut self, _shape: crate::layout::SdfShape) {}
3104
3105 fn enter_portal(&mut self, _z_index: i32) {}
3115
3116 fn exit_portal(&mut self) {}
3120
3121 fn viewport_size(&self) -> Rect {
3124 Rect::new(0.0, 0.0, 1920.0, 1080.0)
3125 }
3126
3127 fn announce(&mut self, _message: &str, _priority: AnnouncementPriority) {}
3133}
3134
3135pub mod accessibility {
3137 pub fn relative_luminance(color: [f32; 4]) -> f32 {
3139 let f = |c: f32| {
3140 if c <= 0.03928 {
3141 c / 12.92
3142 } else {
3143 ((c + 0.055) / 1.055).powf(2.4)
3144 }
3145 };
3146 0.2126 * f(color[0]) + 0.7152 * f(color[1]) + 0.0722 * f(color[2])
3147 }
3148
3149 pub fn contrast_ratio(c1: [f32; 4], c2: [f32; 4]) -> f32 {
3151 let l1 = relative_luminance(c1);
3152 let l2 = relative_luminance(c2);
3153 let (light, dark) = if l1 > l2 { (l1, l2) } else { (l2, l1) };
3154 (light + 0.05) / (dark + 0.05)
3155 }
3156}
3157#[derive(
3159 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
3160)]
3161pub enum RenderTier {
3162 Tier1GPU = 0,
3164 Tier2GPU = 1,
3166 Tier3Fallback = 2,
3168}
3169use bytemuck::{Pod, Zeroable};
3173#[repr(C)]
3175#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3176pub struct ColorTheme {
3177 pub primary_neon: [f32; 4], pub shatter_neon: [f32; 4],
3179 pub glass_base: [f32; 4],
3180 pub glass_edge: [f32; 4],
3181 pub rune_glow: [f32; 4],
3182 pub ember_core: [f32; 4],
3183 pub background_deep: [f32; 4],
3184 pub mani_glow: [f32; 4], pub glass_blur_strength: f32,
3186 pub shatter_edge_width: f32,
3187 pub neon_bloom_radius: f32,
3188 pub rune_opacity: f32,
3189 pub glass_tint_adapt: f32,
3192 pub glass_ior: f32,
3194 pub _pad0: f32,
3196 pub _pad1: f32,
3197}
3198impl ColorTheme {
3199 pub fn asgard() -> Self {
3201 Self {
3202 primary_neon: [0.0, 1.0, 0.95, 1.2],
3203 shatter_neon: [1.0, 0.0, 0.75, 1.5],
3204 glass_base: [0.04, 0.04, 0.06, 0.82],
3205 glass_edge: [0.0, 0.45, 0.55, 0.6],
3206 rune_glow: [0.75, 0.98, 1.0, 0.9],
3207 ember_core: [0.95, 0.12, 0.12, 1.0],
3208 background_deep: [0.01, 0.01, 0.03, 1.0],
3209 mani_glow: [0.7, 0.9, 1.0, 0.05],
3210 glass_blur_strength: 0.6,
3211 shatter_edge_width: 1.8,
3212 neon_bloom_radius: 0.022,
3213 rune_opacity: 0.55,
3214 glass_tint_adapt: 0.35,
3215 glass_ior: 1.45,
3216 _pad0: 0.0,
3217 _pad1: 0.0,
3218 }
3219 }
3220
3221 pub fn midgard() -> Self {
3223 Self {
3224 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],
3230 background_deep: [0.05, 0.05, 0.07, 1.0],
3231 mani_glow: [0.0, 0.0, 0.0, 0.0], glass_blur_strength: 0.0, shatter_edge_width: 1.0,
3234 neon_bloom_radius: 0.0,
3235 rune_opacity: 0.0,
3236 glass_tint_adapt: 0.0,
3237 glass_ior: 1.0,
3238 _pad0: 0.0,
3239 _pad1: 0.0,
3240 }
3241 }
3242
3243 pub fn cyberpunk_viking() -> Self {
3244 Self::asgard()
3245 }
3246 pub fn vibrant_glass() -> Self {
3247 Self {
3248 primary_neon: [0.0, 1.0, 0.95, 1.2],
3249 shatter_neon: [1.0, 0.0, 0.75, 1.5],
3250 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],
3253 ember_core: [1.0, 0.4, 0.1, 1.0],
3254 background_deep: [0.05, 0.05, 0.1, 1.0],
3255 mani_glow: [0.7, 0.9, 1.0, 0.05],
3256 glass_blur_strength: 0.9,
3257 shatter_edge_width: 1.8,
3258 neon_bloom_radius: 0.022,
3259 rune_opacity: 0.55,
3260 glass_tint_adapt: 0.65,
3261 glass_ior: 1.45,
3262 _pad0: 0.0,
3263 _pad1: 0.0,
3264 }
3265 }
3266
3267 pub fn berserker() -> Self {
3269 Self {
3270 primary_neon: [1.0, 0.08, 0.12, 1.8],
3271 shatter_neon: [0.95, 0.92, 0.88, 1.6],
3272 glass_base: [0.03, 0.02, 0.02, 0.88],
3273 glass_edge: [0.8, 0.35, 0.08, 0.7],
3274 rune_glow: [0.9, 0.72, 0.3, 1.0],
3275 ember_core: [0.98, 0.25, 0.05, 1.0],
3276 background_deep: [0.01, 0.005, 0.005, 1.0],
3277 mani_glow: [0.8, 0.2, 0.05, 0.08],
3278 glass_blur_strength: 0.85,
3279 shatter_edge_width: 2.8,
3280 neon_bloom_radius: 0.035,
3281 rune_opacity: 0.85,
3282 glass_tint_adapt: 0.15,
3283 glass_ior: 1.85,
3284 _pad0: 0.0,
3285 _pad1: 0.0,
3286 }
3287 }
3288}
3289impl Default for ColorTheme {
3290 fn default() -> Self {
3291 Self::vibrant_glass()
3292 }
3293}
3294#[repr(C)]
3296#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3297pub struct SceneUniforms {
3298 pub view: glam::Mat4,
3299 pub proj: glam::Mat4,
3300 pub time: f32,
3301 pub delta_time: f32,
3302 pub resolution: [f32; 2],
3303 pub mouse: [f32; 2],
3304 pub mouse_velocity: [f32; 2],
3305 pub shatter_origin: [f32; 2],
3306 pub shatter_time: f32,
3307 pub shatter_force: f32,
3308 pub berzerker_rage: f32,
3309 pub berzerker_mode: u32,
3310 pub scroll_offset: f32,
3311 pub scale_factor: f32,
3312 pub scene_type: u32,
3313 pub _pad: [f32; 3], }
3315
3316pub const SCENE_AURORA: u32 = 0;
3317pub const SCENE_VOID: u32 = 1;
3318pub const SCENE_NEBULA: u32 = 2;
3319pub const SCENE_GLITCH: u32 = 3;
3320pub const SCENE_YGGDRASIL: u32 = 4;
3321
3322impl SceneUniforms {
3323 pub fn new(width: f32, height: f32) -> Self {
3324 Self {
3325 view: glam::Mat4::IDENTITY,
3326 proj: glam::Mat4::orthographic_lh(0.0, width, height, 0.0, -100.0, 100.0),
3327 time: 0.0,
3328 delta_time: 0.016,
3329 resolution: [width, height],
3330 mouse: [0.5, 0.5],
3331 mouse_velocity: [0.0, 0.0],
3332 shatter_origin: [0.5, 0.5],
3333 shatter_time: -100.0,
3334 shatter_force: 0.0,
3335 berzerker_rage: 0.0,
3336 berzerker_mode: 0,
3337 scroll_offset: 0.0,
3338 scale_factor: 1.0,
3339 scene_type: SCENE_AURORA,
3340 _pad: [0.0; 3],
3341 }
3342 }
3343}
3344#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
3346pub struct Mesh {
3347 pub vertices: Vec<[f32; 3]>,
3348 pub normals: Vec<[f32; 3]>,
3349 pub indices: Vec<u32>,
3350}
3351impl Mesh {
3352 pub fn from_obj(data: &[u8]) -> anyhow::Result<Vec<Self>> {
3353 let mut cursor = std::io::Cursor::new(data);
3354 let (models, _) = tobj::load_obj_buf(&mut cursor, &tobj::LoadOptions::default(), |_| {
3355 Ok((Vec::new(), Default::default()))
3356 })?;
3357 let mut meshes = Vec::new();
3358 for m in models {
3359 let mesh = m.mesh;
3360 let vertices: Vec<[f32; 3]> = mesh
3361 .positions
3362 .chunks(3)
3363 .map(|c| [c[0], c[1], c[2]])
3364 .collect();
3365 let normals = if mesh.normals.is_empty() {
3366 vec![[0.0, 0.0, 1.0]; vertices.len()]
3367 } else {
3368 mesh.normals.chunks(3).map(|c| [c[0], c[1], c[2]]).collect()
3369 };
3370 meshes.push(Mesh {
3371 vertices,
3372 normals,
3373 indices: mesh.indices,
3374 });
3375 }
3376 Ok(meshes)
3377 }
3378 pub fn from_stl(data: &[u8]) -> anyhow::Result<Self> {
3379 let mut cursor = std::io::Cursor::new(data);
3380 let stl = stl_io::read_stl(&mut cursor)?;
3381 let vertices: Vec<[f32; 3]> = stl.vertices.iter().map(|v| [v[0], v[1], v[2]]).collect();
3382 let mut indices = Vec::new();
3383 for face in stl.faces {
3384 indices.push(face.vertices[0] as u32);
3385 indices.push(face.vertices[1] as u32);
3386 indices.push(face.vertices[2] as u32);
3387 }
3388 let normals = vec![[0.0, 0.0, 1.0]; vertices.len()];
3389 Ok(Mesh {
3390 vertices,
3391 normals,
3392 indices,
3393 })
3394 }
3395}
3396
3397#[derive(Debug, Clone, Copy, PartialEq)]
3403pub struct Transform3D {
3404 pub position: glam::Vec3,
3405 pub rotation: glam::Quat,
3406 pub scale: glam::Vec3,
3407}
3408
3409impl Default for Transform3D {
3410 fn default() -> Self {
3411 Self {
3412 position: glam::Vec3::ZERO,
3413 rotation: glam::Quat::IDENTITY,
3414 scale: glam::Vec3::ONE,
3415 }
3416 }
3417}
3418
3419impl Transform3D {
3420 pub fn to_matrix(&self) -> glam::Mat4 {
3422 glam::Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.position)
3423 }
3424
3425 pub fn from_2d(x: f32, y: f32, rotation: f32) -> Self {
3427 Self {
3428 position: glam::Vec3::new(x, y, 0.0),
3429 rotation: glam::Quat::from_rotation_z(rotation),
3430 scale: glam::Vec3::ONE,
3431 }
3432 }
3433}
3434
3435#[derive(Debug, Clone, Copy)]
3437pub struct Camera3D {
3438 pub position: glam::Vec3,
3440 pub target: glam::Vec3,
3442 pub up: glam::Vec3,
3444 pub fov_y: f32,
3446 pub near: f32,
3448 pub far: f32,
3450 pub perspective: bool,
3452 pub aspect: f32,
3454}
3455
3456#[derive(Debug, Clone, Copy, PartialEq)]
3458pub struct Material3D {
3459 pub base_color: [f32; 4],
3461 pub metallic: f32,
3463 pub roughness: f32,
3465 pub emissive: [f32; 3],
3467 pub opacity: f32,
3469}
3470
3471impl Default for Material3D {
3472 fn default() -> Self {
3473 Self {
3474 base_color: [1.0, 1.0, 1.0, 1.0],
3475 metallic: 0.0,
3476 roughness: 0.5,
3477 emissive: [0.0, 0.0, 0.0],
3478 opacity: 1.0,
3479 }
3480 }
3481}
3482
3483impl Material3D {
3484 pub fn unlit(color: [f32; 4]) -> Self {
3486 Self {
3487 base_color: color,
3488 metallic: 0.0,
3489 roughness: 1.0,
3490 emissive: [0.0, 0.0, 0.0],
3491 opacity: color[3],
3492 }
3493 }
3494
3495 pub fn metallic(color: [f32; 4], roughness: f32) -> Self {
3497 Self {
3498 base_color: color,
3499 metallic: 1.0,
3500 roughness: roughness.clamp(0.0, 1.0),
3501 emissive: [0.0, 0.0, 0.0],
3502 opacity: color[3],
3503 }
3504 }
3505}
3506
3507impl Default for Camera3D {
3508 fn default() -> Self {
3509 Self {
3510 position: glam::Vec3::new(0.0, 0.0, 10.0),
3511 target: glam::Vec3::ZERO,
3512 up: glam::Vec3::Y,
3513 fov_y: 45.0f32.to_radians(),
3514 near: 0.1,
3515 far: 1000.0,
3516 perspective: true,
3517 aspect: 16.0 / 9.0,
3518 }
3519 }
3520}
3521
3522impl Camera3D {
3523 pub fn view_matrix(&self) -> glam::Mat4 {
3525 glam::Mat4::look_at_lh(self.position, self.target, self.up)
3526 }
3527
3528 pub fn projection_matrix(&self) -> glam::Mat4 {
3530 if self.perspective {
3531 glam::Mat4::perspective_lh(self.fov_y, self.aspect, self.near, self.far)
3532 } else {
3533 let top = self.fov_y;
3535 let right = top * self.aspect;
3536 glam::Mat4::orthographic_lh(-right, right, -top, top, self.near, self.far)
3537 }
3538 }
3539
3540 pub fn view_projection(&self) -> glam::Mat4 {
3542 self.projection_matrix() * self.view_matrix()
3543 }
3544}
3545
3546pub trait FrameRenderer<E = ()>: Renderer {
3549 fn begin_frame(&mut self) -> E;
3550 fn render_frame(&mut self) {
3551 }
3553 fn end_frame(&mut self, encoder: E);
3554}
3555use std::sync::Arc;
3556type SubscriberList<T> = Arc<std::sync::Mutex<Vec<Box<dyn Fn(&T) + Send + Sync>>>>;
3557#[derive(Clone)]
3559pub struct State<T: Clone + Send + Sync + 'static> {
3560 swap: Arc<arc_swap::ArcSwap<T>>,
3561 metadata_swap: Arc<arc_swap::ArcSwap<Option<agents::MutationMetadata>>>,
3562 #[cfg(not(target_arch = "wasm32"))]
3563 tvar: Arc<stm::TVar<T>>,
3564 #[cfg(not(target_arch = "wasm32"))]
3565 metadata_tvar: Arc<stm::TVar<Option<agents::MutationMetadata>>>,
3566 subscribers: SubscriberList<T>,
3567 version: Arc<std::sync::atomic::AtomicU64>,
3568 resolution: agents::ConflictResolution,
3569}
3570impl<T: Clone + Send + Sync + 'static> State<T> {
3571 pub fn new(value: T) -> Self {
3573 #[cfg(not(target_arch = "wasm32"))]
3574 let tvar = Arc::new(stm::TVar::new(value.clone()));
3575 #[cfg(not(target_arch = "wasm32"))]
3576 let metadata_tvar = Arc::new(stm::TVar::new(None));
3577 Self {
3578 swap: Arc::new(arc_swap::ArcSwap::from_pointee(value)),
3579 metadata_swap: Arc::new(arc_swap::ArcSwap::new(Arc::new(None))),
3580 #[cfg(not(target_arch = "wasm32"))]
3581 tvar,
3582 #[cfg(not(target_arch = "wasm32"))]
3583 metadata_tvar,
3584 subscribers: Arc::new(std::sync::Mutex::new(Vec::new())),
3585 version: Arc::new(std::sync::atomic::AtomicU64::new(0)),
3586 resolution: agents::ConflictResolution::default(),
3587 }
3588 }
3589 pub fn with_resolution(mut self, resolution: agents::ConflictResolution) -> Self {
3591 self.resolution = resolution;
3592 self
3593 }
3594 pub fn get(&self) -> T {
3596 (**self.swap.load()).clone()
3597 }
3598 pub fn set(&self, value: T) {
3600 #[cfg(not(target_arch = "wasm32"))]
3601 let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3602 let new_meta = agents::get_current_mutation_metadata();
3603 let existing_meta = self.metadata_tvar.read(tx)?;
3604 let mut skip = false;
3605 if self.resolution == agents::ConflictResolution::PriorityWins
3606 && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3607 && new_m.priority < old_m.priority
3608 {
3609 skip = true;
3610 }
3611 if !skip {
3612 self.tvar.write(tx, value.clone())?;
3613 self.metadata_tvar.write(tx, new_meta)?;
3614 Ok((false, value.clone(), new_meta))
3615 } else {
3616 Ok((true, self.tvar.read(tx)?, existing_meta))
3617 }
3618 });
3619 #[cfg(target_arch = "wasm32")]
3620 let (was_skipped, final_val, final_meta) =
3621 (false, value, agents::get_current_mutation_metadata());
3622 if was_skipped {
3623 if let (Some(new_m), Some(old_m)) =
3624 (agents::get_current_mutation_metadata(), final_meta)
3625 {
3626 agents::notify_conflict(agents::ConflictEvent {
3627 agent_id: new_m.agent_id,
3628 priority: new_m.priority,
3629 existing_agent_id: old_m.agent_id,
3630 existing_priority: old_m.priority,
3631 timestamp_ms: new_m.timestamp_ms,
3632 });
3633 }
3634 return;
3635 }
3636 self.swap.store(Arc::new(final_val.clone()));
3637 self.metadata_swap.store(Arc::new(final_meta));
3638 self.version
3639 .fetch_add(1, std::sync::atomic::Ordering::Release);
3640 let subs = Arc::clone(&self.subscribers);
3641 if crate::is_batching() {
3642 crate::enqueue_batch_task(Box::new(move || {
3643 let s = subs.lock().unwrap();
3644 for cb in s.iter() {
3645 cb(&final_val);
3646 }
3647 }));
3648 } else {
3649 let s = subs.lock().unwrap();
3650 for cb in s.iter() {
3651 cb(&final_val);
3652 }
3653 }
3654 }
3655 pub fn mutate<F: Fn(&T) -> T>(&self, f: F) {
3656 #[cfg(not(target_arch = "wasm32"))]
3657 {
3658 let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3659 let new_meta = agents::get_current_mutation_metadata();
3660 let existing_meta = self.metadata_tvar.read(tx)?;
3661 let mut skip = false;
3662 if self.resolution == agents::ConflictResolution::PriorityWins
3663 && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3664 && new_m.priority < old_m.priority
3665 {
3666 skip = true;
3667 }
3668 if !skip {
3669 let current = self.tvar.read(tx)?;
3670 let next = f(¤t);
3671 self.tvar.write(tx, next.clone())?;
3672 self.metadata_tvar.write(tx, new_meta)?;
3673 Ok((false, next, new_meta))
3674 } else {
3675 Ok((true, self.tvar.read(tx)?, existing_meta))
3676 }
3677 });
3678 if was_skipped {
3679 if let (Some(new_m), Some(old_m)) =
3680 (agents::get_current_mutation_metadata(), final_meta)
3681 {
3682 agents::notify_conflict(agents::ConflictEvent {
3683 agent_id: new_m.agent_id,
3684 priority: new_m.priority,
3685 existing_agent_id: old_m.agent_id,
3686 existing_priority: old_m.priority,
3687 timestamp_ms: new_m.timestamp_ms,
3688 });
3689 }
3690 return;
3691 }
3692 self.swap.store(Arc::new(final_val.clone()));
3693 self.metadata_swap.store(Arc::new(final_meta));
3694 self.version
3695 .fetch_add(1, std::sync::atomic::Ordering::Release);
3696 let subs = Arc::clone(&self.subscribers);
3697 if crate::is_batching() {
3698 crate::enqueue_batch_task(Box::new(move || {
3699 let s = subs.lock().unwrap();
3700 for cb in s.iter() {
3701 cb(&final_val);
3702 }
3703 }));
3704 } else {
3705 let s = subs.lock().unwrap();
3706 for cb in s.iter() {
3707 cb(&final_val);
3708 }
3709 }
3710 }
3711 #[cfg(target_arch = "wasm32")]
3712 {
3713 self.set(f(&self.get()));
3714 }
3715 }
3716 pub fn version(&self) -> u64 {
3718 self.version.load(std::sync::atomic::Ordering::Acquire)
3719 }
3720 pub fn subscribe<F: Fn(&T) + Send + Sync + 'static>(&self, callback: F) {
3722 self.subscribers.lock().unwrap().push(Box::new(callback));
3723 }
3724}
3725use crate::runtime::NodeStateSnapshot;
3726use std::sync::OnceLock;
3727use std::sync::atomic::{AtomicBool, Ordering};
3728pub static SYSTEM_STATE: OnceLock<Arc<arc_swap::ArcSwap<KnowledgeState>>> = OnceLock::new();
3730#[cfg(not(target_arch = "wasm32"))]
3731static KNOWLEDGE_TVAR: OnceLock<stm::TVar<KnowledgeState>> = OnceLock::new();
3732static IS_BATCHING: AtomicBool = AtomicBool::new(false);
3733pub static IS_RENDERING: AtomicBool = AtomicBool::new(false);
3734pub static LAYOUT_DIRTY: AtomicBool = AtomicBool::new(false);
3735type BatchQueue = OnceLock<std::sync::Mutex<Vec<Box<dyn FnOnce() + Send + Sync>>>>;
3736static BATCH_QUEUE: BatchQueue = OnceLock::new();
3737static STATE_WRITE_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
3740pub fn is_batching() -> bool {
3742 IS_BATCHING.load(Ordering::Acquire)
3743}
3744pub fn is_rendering() -> bool {
3746 IS_RENDERING.load(Ordering::Acquire)
3747}
3748pub fn begin_render_phase() {
3750 IS_RENDERING.store(true, Ordering::Release);
3751}
3752pub fn end_render_phase() {
3754 IS_RENDERING.store(false, Ordering::Release);
3755}
3756pub fn enqueue_batch_task(task: Box<dyn FnOnce() + Send + Sync>) {
3758 let mut queue = BATCH_QUEUE
3759 .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3760 .lock()
3761 .unwrap();
3762 queue.push(task);
3763}
3764pub fn batch<F: FnOnce()>(f: F) {
3768 if IS_BATCHING.swap(true, Ordering::AcqRel) {
3769 f();
3771 return;
3772 }
3773 f();
3774 IS_BATCHING.store(false, Ordering::Release);
3775 let mut queue = BATCH_QUEUE
3776 .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3777 .lock()
3778 .unwrap();
3779 let tasks: Vec<_> = queue.drain(..).collect();
3780 drop(queue);
3781 for task in tasks {
3782 task();
3783 }
3784}
3785pub fn get_system_state() -> Arc<arc_swap::ArcSwap<KnowledgeState>> {
3787 SYSTEM_STATE
3788 .get_or_init(|| Arc::new(arc_swap::ArcSwap::from_pointee(KnowledgeState::default())))
3789 .clone()
3790}
3791pub fn load_system_state() -> arc_swap::Guard<Arc<KnowledgeState>> {
3792 get_system_state().load()
3793}
3794pub fn update_system_state<F>(f: F)
3795where
3796 F: FnOnce(&KnowledgeState) -> KnowledgeState,
3797{
3798 let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3799 if is_rendering() {
3800 log::warn!(
3801 "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3802 );
3803 }
3804 LAYOUT_DIRTY.store(true, Ordering::SeqCst);
3805 let swap = get_system_state();
3806 let current = swap.load();
3807 let new_state = Arc::new(f(¤t));
3808 swap.store(Arc::clone(&new_state));
3809 #[cfg(not(target_arch = "wasm32"))]
3810 {
3811 let tvar = KNOWLEDGE_TVAR.get_or_init(|| stm::TVar::new((*new_state).clone()));
3812 stm::atomically(|tx| tvar.write(tx, (*new_state).clone()));
3813 }
3814}
3815pub fn transact_system_state<F>(f: F)
3816where
3817 F: Fn(&KnowledgeState) -> KnowledgeState,
3818{
3819 let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3820 #[cfg(not(target_arch = "wasm32"))]
3821 {
3822 if is_rendering() {
3823 log::warn!(
3824 "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3825 );
3826 }
3827 let tvar = KNOWLEDGE_TVAR
3828 .get_or_init(|| stm::TVar::new((**get_system_state().load()).clone()))
3829 .clone();
3830 let new_state = stm::atomically(move |tx| {
3831 let current = tvar.read(tx)?;
3832 let next = f(¤t);
3833 tvar.write(tx, next.clone())?;
3834 Ok(next)
3835 });
3836 get_system_state().store(Arc::new(new_state));
3837 }
3838 #[cfg(target_arch = "wasm32")]
3839 {
3840 if is_rendering() {
3841 log::warn!(
3842 "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3843 );
3844 }
3845 update_system_state(f);
3846 }
3847}
3848impl KnowledgeState {
3849 pub fn new() -> Self {
3851 Self::default()
3852 }
3853 pub fn set_component_state<T: 'static + Send + Sync>(&mut self, id: u64, state: T) {
3855 self.component_states
3856 .insert(id, Arc::new(std::sync::RwLock::new(state)));
3857 }
3858 pub fn get_component_state<T: 'static + Send + Sync>(
3860 &self,
3861 id: u64,
3862 ) -> Option<Arc<std::sync::RwLock<T>>> {
3863 let lock = self.component_states.get(&id)?;
3864 let any_ref = lock.read().ok()?;
3868 if any_ref.is::<T>() {
3869 drop(any_ref);
3871 let cloned: Arc<std::sync::RwLock<dyn std::any::Any + Send + Sync>> = Arc::clone(lock);
3872 Some(unsafe {
3875 let raw = Arc::into_raw(cloned);
3876 Arc::from_raw(raw as *const std::sync::RwLock<T>)
3877 })
3878 } else {
3879 None
3880 }
3881 }
3882 pub fn remember(&mut self, fragment: KnowledgeFragment) {
3884 self.fragments.insert(fragment.id.clone(), fragment);
3885 }
3886 pub fn process_query(&mut self, query: &str) {
3888 let query_lower = query.to_lowercase();
3889 let mut results: Vec<(f32, String)> = self
3890 .fragments
3891 .iter()
3892 .map(|(id, frag)| {
3893 let mut score = 0.0;
3894 if frag.summary.to_lowercase().contains(&query_lower) {
3895 score += 1.0;
3896 }
3897 if frag.source.to_lowercase().contains(&query_lower) {
3898 score += 0.5;
3899 }
3900 (score, id.clone())
3901 })
3902 .filter(|(score, _)| *score > 0.0)
3903 .collect();
3904 results.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
3906 self.last_query_results = results.into_iter().map(|(_, id)| id).take(5).collect();
3907 }
3908 pub fn snapshot(&self) -> Vec<NodeStateSnapshot> {
3910 let mut snapshots = Vec::new();
3911 for frag in self.fragments.values() {
3913 if let Ok(val) = serde_json::to_value(frag) {
3914 snapshots.push(NodeStateSnapshot { id: 0, state: val });
3915 }
3916 }
3917 snapshots
3918 }
3919}
3920#[derive(Clone)]
3922pub struct Binding<T: Clone + Send + Sync + 'static> {
3923 swap: Arc<arc_swap::ArcSwap<T>>,
3924 #[cfg(not(target_arch = "wasm32"))]
3925 tvar: Arc<stm::TVar<T>>,
3926 version: Arc<std::sync::atomic::AtomicU64>,
3927}
3928impl<T: Clone + Send + Sync + 'static> Binding<T> {
3929 pub fn from_state(state: &State<T>) -> Self {
3931 Self {
3932 swap: Arc::clone(&state.swap),
3933 #[cfg(not(target_arch = "wasm32"))]
3934 tvar: Arc::clone(&state.tvar),
3935 version: Arc::clone(&state.version),
3936 }
3937 }
3938 pub fn get(&self) -> T {
3940 (**self.swap.load()).clone()
3941 }
3942 pub fn set(&self, value: T) {
3944 self.swap.store(Arc::new(value.clone()));
3945 #[cfg(not(target_arch = "wasm32"))]
3946 {
3947 let tvar = Arc::clone(&self.tvar);
3948 let v = value.clone();
3949 stm::atomically(move |tx| tvar.write(tx, v.clone()));
3950 }
3951 self.version
3952 .fetch_add(1, std::sync::atomic::Ordering::Release);
3953 }
3954 pub fn version(&self) -> u64 {
3956 self.version.load(std::sync::atomic::Ordering::Acquire)
3957 }
3958}
3959#[cfg(not(target_arch = "wasm32"))]
3960pub fn transact_pair<A, B, F>(state_a: &State<A>, state_b: &State<B>, f: F)
3961where
3962 A: Clone + Send + Sync + 'static,
3963 B: Clone + Send + Sync + 'static,
3964 F: Fn(&A, &B) -> (A, B),
3965{
3966 let tvar_a = Arc::clone(&state_a.tvar);
3967 let tvar_b = Arc::clone(&state_b.tvar);
3968 let (new_a, new_b) = stm::atomically(move |tx| {
3969 let a = tvar_a.read(tx)?;
3970 let b = tvar_b.read(tx)?;
3971 let (na, nb) = f(&a, &b);
3972 tvar_a.write(tx, na.clone())?;
3973 tvar_b.write(tx, nb.clone())?;
3974 Ok((na, nb))
3975 });
3976 state_a.swap.store(Arc::new(new_a.clone()));
3977 state_b.swap.store(Arc::new(new_b.clone()));
3978 state_a
3979 .version
3980 .fetch_add(1, std::sync::atomic::Ordering::Release);
3981 state_b
3982 .version
3983 .fetch_add(1, std::sync::atomic::Ordering::Release);
3984 let subs_a = Arc::clone(&state_a.subscribers);
3985 let subs_b = Arc::clone(&state_b.subscribers);
3986 if crate::is_batching() {
3987 crate::enqueue_batch_task(Box::new(move || {
3988 {
3989 let s = subs_a.lock().unwrap();
3990 for cb in s.iter() {
3991 cb(&new_a);
3992 }
3993 }
3994 {
3995 let s = subs_b.lock().unwrap();
3996 for cb in s.iter() {
3997 cb(&new_b);
3998 }
3999 }
4000 }));
4001 } else {
4002 {
4003 let s = subs_a.lock().unwrap();
4004 for cb in s.iter() {
4005 cb(&new_a);
4006 }
4007 }
4008 {
4009 let s = subs_b.lock().unwrap();
4010 for cb in s.iter() {
4011 cb(&new_b);
4012 }
4013 }
4014 }
4015}
4016use std::any::TypeId;
4017use std::sync::Mutex;
4018pub(crate) static ENVIRONMENT: OnceLock<
4020 Mutex<HashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>>,
4021> = OnceLock::new();
4022pub trait EnvKey: 'static + Send + Sync {
4025 type Value: Clone + Send + Sync + 'static;
4027 fn default_value() -> Self::Value;
4029}
4030pub struct YggdrasilKey;
4032impl EnvKey for YggdrasilKey {
4033 type Value = YggdrasilTokens;
4034 fn default_value() -> Self::Value {
4035 default_tokens()
4036 }
4037}
4038#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4041pub enum Appearance {
4042 Light,
4043 Dark,
4044}
4045#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4047pub enum Orientation {
4048 Horizontal,
4049 Vertical,
4050}
4051#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4053pub struct GridPlacement {
4054 pub column: i32,
4056 pub column_span: u32,
4058 pub row: i32,
4060 pub row_span: u32,
4062}
4063#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
4065pub enum Alignment {
4066 #[default]
4067 Center,
4068 Leading,
4069 Trailing,
4070 Top,
4071 Bottom,
4072}
4073#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
4075pub enum Distribution {
4076 #[default]
4077 Fill,
4078 Center,
4079 Leading,
4080 Trailing,
4081 SpaceBetween,
4082 SpaceAround,
4083 SpaceEvenly,
4084}
4085#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
4087pub struct Color {
4088 pub r: f32,
4089 pub g: f32,
4090 pub b: f32,
4091 pub a: f32,
4092}
4093impl Color {
4094 pub const BLACK: Color = Color {
4095 r: 0.0,
4096 g: 0.0,
4097 b: 0.0,
4098 a: 1.0,
4099 };
4100 pub const WHITE: Color = Color {
4101 r: 1.0,
4102 g: 1.0,
4103 b: 1.0,
4104 a: 1.0,
4105 };
4106 pub const TRANSPARENT: Color = Color {
4107 r: 0.0,
4108 g: 0.0,
4109 b: 0.0,
4110 a: 0.0,
4111 };
4112 pub const RED: Color = Color {
4113 r: 1.0,
4114 g: 0.0,
4115 b: 0.0,
4116 a: 1.0,
4117 };
4118 pub const GREEN: Color = Color {
4119 r: 0.0,
4120 g: 1.0,
4121 b: 0.0,
4122 a: 1.0,
4123 };
4124 pub const BLUE: Color = Color {
4125 r: 0.0,
4126 g: 0.0,
4127 b: 1.0,
4128 a: 1.0,
4129 };
4130 pub const VIKING_GOLD: Color = Color {
4131 r: 1.0,
4132 g: 0.84,
4133 b: 0.0,
4134 a: 1.0,
4135 };
4136 pub const MAGENTA_LIQUID: Color = Color {
4137 r: 1.0,
4138 g: 0.0,
4139 b: 1.0,
4140 a: 1.0,
4141 };
4142 pub const TACTICAL_OBSIDIAN: Color = Color {
4143 r: 0.05,
4144 g: 0.05,
4145 b: 0.07,
4146 a: 1.0,
4147 };
4148 pub fn relative_luminance(&self) -> f32 {
4150 fn res(c: f32) -> f32 {
4151 if c <= 0.03928 {
4152 c / 12.92
4153 } else {
4154 ((c + 0.055) / 1.055).powf(2.4)
4155 }
4156 }
4157 0.2126 * res(self.r) + 0.7152 * res(self.g) + 0.0722 * res(self.b)
4158 }
4159 pub fn contrast_ratio(&self, other: &Color) -> f32 {
4161 let l1 = self.relative_luminance();
4162 let l2 = other.relative_luminance();
4163 if l1 > l2 {
4164 (l1 + 0.05) / (l2 + 0.05)
4165 } else {
4166 (l2 + 0.05) / (l1 + 0.05)
4167 }
4168 }
4169 pub const CYAN: Color = Color {
4170 r: 0.0,
4171 g: 1.0,
4172 b: 1.0,
4173 a: 1.0,
4174 };
4175 pub const YELLOW: Color = Color {
4176 r: 1.0,
4177 g: 1.0,
4178 b: 0.0,
4179 a: 1.0,
4180 };
4181 pub const MAGENTA: Color = Color {
4182 r: 1.0,
4183 g: 0.0,
4184 b: 1.0,
4185 a: 1.0,
4186 };
4187 pub const GRAY: Color = Color {
4188 r: 0.5,
4189 g: 0.5,
4190 b: 0.5,
4191 a: 1.0,
4192 };
4193 pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
4195 Self { r, g, b, a }
4196 }
4197 pub fn as_array(&self) -> [f32; 4] {
4199 [self.r, self.g, self.b, self.a]
4200 }
4201
4202 pub fn lighten(&self, amount: f32) -> Self {
4208 Self {
4209 r: (self.r + amount).clamp(0.0, 1.0),
4210 g: (self.g + amount).clamp(0.0, 1.0),
4211 b: (self.b + amount).clamp(0.0, 1.0),
4212 a: self.a,
4213 }
4214 }
4215
4216 pub fn darken(&self, amount: f32) -> Self {
4218 Self {
4219 r: (self.r - amount).clamp(0.0, 1.0),
4220 g: (self.g - amount).clamp(0.0, 1.0),
4221 b: (self.b - amount).clamp(0.0, 1.0),
4222 a: self.a,
4223 }
4224 }
4225}
4226impl View for Color {
4227 type Body = Never;
4228 fn body(self) -> Self::Body {
4229 unreachable!()
4230 }
4231 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
4232 renderer.fill_rect(rect, self.as_array());
4233 }
4234}
4235pub struct AppearanceKey;
4237impl EnvKey for AppearanceKey {
4238 type Value = Appearance;
4239 fn default_value() -> Self::Value {
4240 Appearance::Dark }
4242}
4243pub struct StyleResolver;
4245impl StyleResolver {
4246 pub fn color(key: &str) -> String {
4248 let tokens = Environment::<YggdrasilKey>::new().get();
4249 let appearance = Environment::<AppearanceKey>::new().get();
4250 let is_dark = appearance == Appearance::Dark;
4251 tokens
4252 .get_color(key, is_dark)
4253 .unwrap_or_else(|| "#FF00FF".to_string()) }
4255 pub fn get<T: FromStr>(category: &str, key: &str) -> Option<T> {
4257 let tokens = Environment::<YggdrasilKey>::new().get();
4258 let appearance = Environment::<AppearanceKey>::new().get();
4259 let is_dark = appearance == Appearance::Dark;
4260 tokens.get(category, key, is_dark)
4261 }
4262 pub fn color_array(key: &str) -> [f32; 4] {
4266 let hex = Self::color(key);
4267 parse_hex_color(&hex)
4268 }
4269}
4270
4271fn parse_hex_color(hex: &str) -> [f32; 4] {
4273 let hex = hex.trim_start_matches('#');
4274 if hex.len() >= 6 {
4275 let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(255) as f32 / 255.0;
4276 let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0) as f32 / 255.0;
4277 let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(255) as f32 / 255.0;
4278 let a = if hex.len() >= 8 {
4279 u8::from_str_radix(&hex[6..8], 16).unwrap_or(255) as f32 / 255.0
4280 } else {
4281 1.0
4282 };
4283 [r, g, b, a]
4284 } else {
4285 [1.0, 0.0, 1.0, 1.0] }
4287}
4288
4289pub fn default_tokens() -> YggdrasilTokens {
4291 let mut tokens = YggdrasilTokens::new();
4292 tokens.color.insert(
4294 "background".to_string(),
4295 TokenValue::Single {
4296 value: "#000000".to_string(), },
4298 );
4299 tokens.color.insert(
4300 "primary".to_string(),
4301 TokenValue::Single {
4302 value: "#00FFFF".to_string(), },
4304 );
4305 tokens.color.insert(
4306 "secondary".to_string(),
4307 TokenValue::Single {
4308 value: "#FF00FF".to_string(), },
4310 );
4311 tokens.color.insert(
4312 "surface".to_string(),
4313 TokenValue::Adaptive {
4314 light: "#FFFFFF".to_string(),
4315 dark: "#121212".to_string(),
4316 },
4317 );
4318 tokens.color.insert(
4319 "text".to_string(),
4320 TokenValue::Adaptive {
4321 light: "#000000".to_string(),
4322 dark: "#FFFFFF".to_string(),
4323 },
4324 );
4325 tokens.color.insert(
4327 "surface_elevated".to_string(),
4328 TokenValue::Adaptive {
4329 light: "#FFFFFF".to_string(),
4330 dark: "#1A1A24".to_string(),
4331 },
4332 );
4333 tokens.color.insert(
4334 "surface_overlay".to_string(),
4335 TokenValue::Adaptive {
4336 light: "#FFFFFF".to_string(),
4337 dark: "#1E1E2E".to_string(),
4338 },
4339 );
4340 tokens.color.insert(
4341 "border".to_string(),
4342 TokenValue::Adaptive {
4343 light: "#D0D0D8".to_string(),
4344 dark: "#2A2A3A".to_string(),
4345 },
4346 );
4347 tokens.color.insert(
4348 "border_strong".to_string(),
4349 TokenValue::Adaptive {
4350 light: "#A0A0B0".to_string(),
4351 dark: "#3A3A50".to_string(),
4352 },
4353 );
4354 tokens.color.insert(
4355 "text_muted".to_string(),
4356 TokenValue::Adaptive {
4357 light: "#606070".to_string(),
4358 dark: "#8080A0".to_string(),
4359 },
4360 );
4361 tokens.color.insert(
4362 "text_dim".to_string(),
4363 TokenValue::Adaptive {
4364 light: "#9090A0".to_string(),
4365 dark: "#505070".to_string(),
4366 },
4367 );
4368 tokens.color.insert(
4369 "accent".to_string(),
4370 TokenValue::Single {
4371 value: "#00FFFF".to_string(), },
4373 );
4374 tokens.color.insert(
4375 "accent_hover".to_string(),
4376 TokenValue::Single {
4377 value: "#33FFFF".to_string(),
4378 },
4379 );
4380 tokens.color.insert(
4381 "success".to_string(),
4382 TokenValue::Single {
4383 value: "#00E676".to_string(),
4384 },
4385 );
4386 tokens.color.insert(
4387 "warning".to_string(),
4388 TokenValue::Single {
4389 value: "#FFB300".to_string(),
4390 },
4391 );
4392 tokens.color.insert(
4393 "error".to_string(),
4394 TokenValue::Single {
4395 value: "#FF5252".to_string(),
4396 },
4397 );
4398 tokens.color.insert(
4399 "info".to_string(),
4400 TokenValue::Single {
4401 value: "#448AFF".to_string(),
4402 },
4403 );
4404 tokens.color.insert(
4405 "hover".to_string(),
4406 TokenValue::Adaptive {
4407 light: "#F0F0F5".to_string(),
4408 dark: "#252535".to_string(),
4409 },
4410 );
4411 tokens.color.insert(
4412 "active".to_string(),
4413 TokenValue::Adaptive {
4414 light: "#E0E0EB".to_string(),
4415 dark: "#303045".to_string(),
4416 },
4417 );
4418 tokens.color.insert(
4419 "disabled".to_string(),
4420 TokenValue::Adaptive {
4421 light: "#E8E8F0".to_string(),
4422 dark: "#1A1A28".to_string(),
4423 },
4424 );
4425 tokens.color.insert(
4426 "disabled_text".to_string(),
4427 TokenValue::Adaptive {
4428 light: "#B0B0C0".to_string(),
4429 dark: "#404060".to_string(),
4430 },
4431 );
4432 tokens.color.insert(
4433 "focus_ring".to_string(),
4434 TokenValue::Single {
4435 value: "#00FFFF".to_string(),
4436 },
4437 );
4438 tokens.color.insert(
4439 "shadow".to_string(),
4440 TokenValue::Adaptive {
4441 light: "#00000020".to_string(),
4442 dark: "#00000060".to_string(),
4443 },
4444 );
4445 tokens.color.insert(
4446 "code_bg".to_string(),
4447 TokenValue::Adaptive {
4448 light: "#F5F5FA".to_string(),
4449 dark: "#0D0D18".to_string(),
4450 },
4451 );
4452 tokens.bifrost.insert(
4454 "blur".to_string(),
4455 TokenValue::Single {
4456 value: "25.0".to_string(),
4457 },
4458 );
4459 tokens.bifrost.insert(
4460 "saturation".to_string(),
4461 TokenValue::Single {
4462 value: "1.2".to_string(),
4463 },
4464 );
4465 tokens.bifrost.insert(
4466 "opacity".to_string(),
4467 TokenValue::Single {
4468 value: "0.65".to_string(),
4469 },
4470 );
4471 tokens.gungnir.insert(
4473 "intensity".to_string(),
4474 TokenValue::Single {
4475 value: "1.0".to_string(),
4476 },
4477 );
4478 tokens.gungnir.insert(
4479 "radius".to_string(),
4480 TokenValue::Single {
4481 value: "15.0".to_string(),
4482 },
4483 );
4484 tokens.mjolnir.insert(
4486 "clip_angle".to_string(),
4487 TokenValue::Single {
4488 value: "12.0".to_string(),
4489 },
4490 );
4491 tokens.mjolnir.insert(
4492 "border_width".to_string(),
4493 TokenValue::Single {
4494 value: "2.0".to_string(),
4495 },
4496 );
4497 tokens.anim.insert(
4499 "stiffness".to_string(),
4500 TokenValue::Single {
4501 value: "170.0".to_string(),
4502 },
4503 );
4504 tokens.anim.insert(
4505 "damping".to_string(),
4506 TokenValue::Single {
4507 value: "26.0".to_string(),
4508 },
4509 );
4510 tokens.anim.insert(
4511 "mass".to_string(),
4512 TokenValue::Single {
4513 value: "1.0".to_string(),
4514 },
4515 );
4516 tokens.accessibility.insert(
4518 "reduce_motion".to_string(),
4519 TokenValue::Single {
4520 value: "false".to_string(),
4521 },
4522 );
4523 tokens
4524}
4525pub struct Environment<K: EnvKey> {
4527 _marker: std::marker::PhantomData<K>,
4528}
4529impl<K: EnvKey> Default for Environment<K> {
4530 fn default() -> Self {
4531 Self::new()
4532 }
4533}
4534impl<K: EnvKey> Environment<K> {
4535 pub fn new() -> Self {
4537 Self {
4538 _marker: std::marker::PhantomData,
4539 }
4540 }
4541 pub fn get(&self) -> K::Value {
4543 if let Some(env_store) = ENVIRONMENT.get() {
4544 let env_lock = env_store.lock().unwrap();
4545 if let Some(val) = env_lock.get(&std::any::TypeId::of::<K>()) {
4546 if let Some(typed_val) = val.downcast_ref::<K::Value>() {
4547 return typed_val.clone();
4548 } else {
4549 log::warn!(
4550 "Environment: Downcast failed for key type {:?}",
4551 std::any::type_name::<K>()
4552 );
4553 }
4554 } else {
4555 log::trace!(
4557 "Environment: Key not found: {:?}. Returning default.",
4558 std::any::type_name::<K>()
4559 );
4560 }
4561 } else {
4562 log::trace!(
4564 "Environment: Store not initialized. Key: {:?}. Returning default.",
4565 std::any::type_name::<K>()
4566 );
4567 }
4568 K::default_value()
4569 }
4570}
4571pub mod env {
4573 pub fn insert<K: super::EnvKey>(value: K::Value) {
4575 let store = super::ENVIRONMENT
4576 .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
4577 let mut env_map = store.lock().unwrap();
4578 env_map.insert(std::any::TypeId::of::<K>(), Box::new(value));
4579 }
4580 pub fn remove<K: super::EnvKey>() {
4582 if let Some(store) = super::ENVIRONMENT.get() {
4583 let mut env_map = store.lock().unwrap();
4584 env_map.remove(&std::any::TypeId::of::<K>());
4585 }
4586 }
4587}
4588#[derive(Debug, Clone, Copy, PartialEq)]
4591pub struct Size {
4592 pub width: f32,
4593 pub height: f32,
4594}
4595
4596impl Size {
4597 pub const ZERO: Self = Self {
4598 width: 0.0,
4599 height: 0.0,
4600 };
4601
4602 pub fn new(width: f32, height: f32) -> Self {
4603 Self { width, height }
4604 }
4605}
4606
4607#[derive(Debug, Clone, Copy, PartialEq)]
4609pub struct EdgeInsets {
4610 pub top: f32,
4611 pub leading: f32,
4612 pub bottom: f32,
4613 pub trailing: f32,
4614}
4615
4616impl EdgeInsets {
4617 pub fn all(value: f32) -> Self {
4619 Self {
4620 top: value,
4621 leading: value,
4622 bottom: value,
4623 trailing: value,
4624 }
4625 }
4626
4627 pub fn vertical(value: f32) -> Self {
4629 Self {
4630 top: value,
4631 leading: 0.0,
4632 bottom: value,
4633 trailing: 0.0,
4634 }
4635 }
4636
4637 pub fn horizontal(value: f32) -> Self {
4639 Self {
4640 top: 0.0,
4641 leading: value,
4642 bottom: 0.0,
4643 trailing: value,
4644 }
4645 }
4646}
4647
4648#[derive(Debug, Clone, Copy, PartialEq)]
4652pub struct FrameModifier {
4653 pub width: Option<f32>,
4655 pub height: Option<f32>,
4657 pub min_width: Option<f32>,
4659 pub max_width: Option<f32>,
4661 pub min_height: Option<f32>,
4663 pub max_height: Option<f32>,
4665 pub alignment: Alignment,
4667}
4668
4669impl Default for FrameModifier {
4670 fn default() -> Self {
4672 Self::new()
4673 }
4674}
4675
4676impl FrameModifier {
4677 pub fn new() -> Self {
4679 Self {
4680 width: None,
4681 height: None,
4682 min_width: None,
4683 max_width: None,
4684 min_height: None,
4685 max_height: None,
4686 alignment: Alignment::Center,
4687 }
4688 }
4689
4690 pub fn width(mut self, width: f32) -> Self {
4692 self.width = Some(width);
4693 self
4694 }
4695
4696 pub fn height(mut self, height: f32) -> Self {
4698 self.height = Some(height);
4699 self
4700 }
4701
4702 pub fn size(mut self, width: f32, height: f32) -> Self {
4704 self.width = Some(width);
4705 self.height = Some(height);
4706 self
4707 }
4708
4709 pub fn min_width(mut self, min_width: f32) -> Self {
4711 self.min_width = Some(min_width);
4712 self
4713 }
4714
4715 pub fn max_width(mut self, max_width: f32) -> Self {
4717 self.max_width = Some(max_width);
4718 self
4719 }
4720
4721 pub fn min_height(mut self, min_height: f32) -> Self {
4723 self.min_height = Some(min_height);
4724 self
4725 }
4726
4727 pub fn max_height(mut self, max_height: f32) -> Self {
4729 self.max_height = Some(max_height);
4730 self
4731 }
4732
4733 pub fn alignment(mut self, alignment: Alignment) -> Self {
4735 self.alignment = alignment;
4736 self
4737 }
4738}
4739
4740impl ViewModifier for FrameModifier {
4741 fn modify<V: View>(self, content: V) -> impl View {
4743 ModifiedView::new(content, self)
4744 }
4745
4746 fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
4748 let w = if let Some(width) = self.width {
4749 Some(width)
4750 } else {
4751 proposal.width.map(|pw| {
4752 pw.clamp(
4753 self.min_width.unwrap_or(0.0),
4754 self.max_width.unwrap_or(f32::INFINITY),
4755 )
4756 })
4757 };
4758 let h = if let Some(height) = self.height {
4759 Some(height)
4760 } else {
4761 proposal.height.map(|ph| {
4762 ph.clamp(
4763 self.min_height.unwrap_or(0.0),
4764 self.max_height.unwrap_or(f32::INFINITY),
4765 )
4766 })
4767 };
4768 SizeProposal {
4769 width: w,
4770 height: h,
4771 }
4772 }
4773
4774 fn transform_size(&self, child_size: Size) -> Size {
4776 let w = if let Some(width) = self.width {
4777 width
4778 } else {
4779 child_size.width.clamp(
4780 self.min_width.unwrap_or(0.0),
4781 self.max_width.unwrap_or(f32::INFINITY),
4782 )
4783 };
4784 let h = if let Some(height) = self.height {
4785 height
4786 } else {
4787 child_size.height.clamp(
4788 self.min_height.unwrap_or(0.0),
4789 self.max_height.unwrap_or(f32::INFINITY),
4790 )
4791 };
4792 Size {
4793 width: w,
4794 height: h,
4795 }
4796 }
4797
4798 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4800 self.render(renderer, rect);
4801 let child_proposal =
4802 self.transform_proposal(SizeProposal::new(Some(rect.width), Some(rect.height)));
4803 let child_size = view.intrinsic_size(renderer, child_proposal);
4804
4805 let mut child_x = rect.x;
4806 let mut child_y = rect.y;
4807
4808 match self.alignment {
4809 Alignment::Leading => {
4810 child_y = rect.y + (rect.height - child_size.height) / 2.0;
4811 }
4812 Alignment::Trailing => {
4813 child_x = rect.x + rect.width - child_size.width;
4814 child_y = rect.y + (rect.height - child_size.height) / 2.0;
4815 }
4816 Alignment::Top => {
4817 child_x = rect.x + (rect.width - child_size.width) / 2.0;
4818 }
4819 Alignment::Bottom => {
4820 child_x = rect.x + (rect.width - child_size.width) / 2.0;
4821 child_y = rect.y + rect.height - child_size.height;
4822 }
4823 Alignment::Center => {
4824 child_x = rect.x + (rect.width - child_size.width) / 2.0;
4825 child_y = rect.y + (rect.height - child_size.height) / 2.0;
4826 }
4827 }
4828
4829 let child_rect = Rect {
4830 x: child_x,
4831 y: child_y,
4832 width: child_size.width,
4833 height: child_size.height,
4834 };
4835
4836 view.render(renderer, child_rect);
4837 self.post_render(renderer, rect);
4838 }
4839}
4840
4841#[derive(Debug, Clone, Copy, PartialEq)]
4843pub struct FlexModifier {
4844 pub weight: f32,
4845}
4846
4847impl ViewModifier for FlexModifier {
4848 fn modify<V: View>(self, content: V) -> impl View {
4849 ModifiedView::new(content, self)
4850 }
4851
4852 fn child_flex_weight<V: View>(&self, _view: &V) -> f32 {
4853 self.weight
4854 }
4855}
4856
4857#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4859pub struct GridPlacementModifier {
4860 pub placement: GridPlacement,
4862}
4863
4864impl ViewModifier for GridPlacementModifier {
4865 fn modify<V: View>(self, content: V) -> impl View {
4867 ModifiedView::new(content, self)
4868 }
4869
4870 fn get_grid_placement(&self) -> Option<GridPlacement> {
4872 Some(self.placement)
4873 }
4874}
4875
4876#[derive(Clone)]
4879pub struct OverlayModifier {
4880 pub overlay: AnyView,
4882 pub alignment: Alignment,
4884 pub offset: [f32; 2],
4886 pub on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
4888}
4889
4890impl ViewModifier for OverlayModifier {
4891 fn modify<V: View>(self, content: V) -> impl View {
4893 ModifiedView::new(content, self)
4894 }
4895
4896 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4898 view.render(renderer, rect);
4900
4901 let overlay_size = self
4903 .overlay
4904 .intrinsic_size(renderer, SizeProposal::unspecified());
4905
4906 let mut overlay_x;
4908 let mut overlay_y;
4909
4910 match self.alignment {
4911 Alignment::Leading => {
4912 overlay_x = rect.x - overlay_size.width;
4913 overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4914 }
4915 Alignment::Trailing => {
4916 overlay_x = rect.x + rect.width;
4917 overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4918 }
4919 Alignment::Top => {
4920 overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4921 overlay_y = rect.y - overlay_size.height;
4922 }
4923 Alignment::Bottom => {
4924 overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4925 overlay_y = rect.y + rect.height;
4926 }
4927 Alignment::Center => {
4928 overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4929 overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4930 }
4931 }
4932
4933 overlay_x += self.offset[0];
4934 overlay_y += self.offset[1];
4935
4936 let overlay_rect = Rect {
4937 x: overlay_x,
4938 y: overlay_y,
4939 width: overlay_size.width,
4940 height: overlay_size.height,
4941 };
4942
4943 if let Some(on_dismiss) = &self.on_dismiss {
4945 let dismiss = on_dismiss.clone();
4946 renderer.register_handler(
4947 "pointerdown",
4948 Arc::new(move |event| {
4949 if let Event::PointerDown { x, y, .. } = event {
4950 let click_inside = x >= overlay_rect.x
4951 && x <= overlay_rect.x + overlay_rect.width
4952 && y >= overlay_rect.y
4953 && y <= overlay_rect.y + overlay_rect.height;
4954 if !click_inside {
4955 dismiss();
4956 }
4957 }
4958 }),
4959 );
4960 }
4961
4962 self.overlay.render(renderer, overlay_rect);
4964 }
4965}
4966
4967#[derive(Debug, Clone, Copy, PartialEq)]
4969pub struct OffsetModifier {
4970 pub x: f32,
4971 pub y: f32,
4972}
4973
4974impl OffsetModifier {
4975 pub fn new(x: f32, y: f32) -> Self {
4976 Self { x, y }
4977 }
4978}
4979
4980impl ViewModifier for OffsetModifier {
4981 fn modify<V: View>(self, content: V) -> impl View {
4982 ModifiedView::new(content, self)
4983 }
4984}
4985
4986#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4988pub struct ZIndexModifier {
4989 pub z_index: i32,
4990}
4991
4992impl ZIndexModifier {
4993 pub fn new(z_index: i32) -> Self {
4994 Self { z_index }
4995 }
4996}
4997
4998impl ViewModifier for ZIndexModifier {
4999 fn modify<V: View>(self, content: V) -> impl View {
5000 ModifiedView::new(content, self)
5001 }
5002}
5003
5004#[derive(Debug, Clone, Copy, PartialEq, Default)]
5006pub struct LayoutConstraints {
5007 pub min_width: Option<f32>,
5008 pub max_width: Option<f32>,
5009 pub min_height: Option<f32>,
5010 pub max_height: Option<f32>,
5011}
5012
5013#[derive(Debug, Clone, Copy, PartialEq)]
5015pub struct LayoutModifier {
5016 pub constraints: LayoutConstraints,
5017}
5018
5019impl LayoutModifier {
5020 pub fn new(constraints: LayoutConstraints) -> Self {
5021 Self { constraints }
5022 }
5023}
5024
5025impl ViewModifier for LayoutModifier {
5026 fn modify<V: View>(self, content: V) -> impl View {
5027 ModifiedView::new(content, self)
5028 }
5029}
5030
5031#[derive(Debug, Clone, Copy, PartialEq)]
5033pub struct SafeAreaModifier {
5034 pub ignores: bool,
5035}
5036
5037impl ViewModifier for SafeAreaModifier {
5038 fn modify<V: View>(self, content: V) -> impl View {
5039 ModifiedView::new(content, self)
5040 }
5041}
5042
5043#[derive(Debug, Clone, Copy, PartialEq)]
5045pub struct ElevationModifier {
5046 pub level: f32,
5047}
5048
5049impl ViewModifier for ElevationModifier {
5050 fn modify<V: View>(self, content: V) -> impl View {
5051 ModifiedView::new(content, self)
5052 }
5053
5054 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
5055 if self.level > 0.0 {
5056 let radius = self.level * 2.0;
5057 let offset_y = self.level * 0.5;
5058 let shadow_color = [0.0, 0.0, 0.0, 0.3];
5059 renderer.push_shadow(radius, shadow_color, [0.0, offset_y]);
5060 view.render(renderer, rect);
5061 renderer.pop_shadow();
5062 } else {
5063 view.render(renderer, rect);
5064 }
5065 }
5066}
5067
5068pub mod layout {
5070 use super::*;
5071
5072 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5075 pub struct LayoutKey {
5076 pub view_hash: u64,
5077 pub generation: u64,
5078 }
5079
5080 pub struct LayoutCache {
5082 pub safe_area: SafeArea,
5083 pub delta_time: f32,
5084 pub scale_factor: f32,
5086 size_cache: HashMap<(u64, u32, u32), Size>, generation: u64,
5091 pub engine: Option<Box<dyn std::any::Any + Send + Sync>>,
5093 pub animators: Option<Box<dyn std::any::Any + Send + Sync>>,
5095 pub previous_rects: HashMap<u64, Rect>,
5097 }
5098
5099 impl Default for LayoutCache {
5100 fn default() -> Self {
5101 Self::new()
5102 }
5103 }
5104
5105 impl LayoutCache {
5106 pub fn new() -> Self {
5107 Self {
5108 safe_area: SafeArea::default(),
5109 delta_time: 0.016,
5110 scale_factor: 1.0,
5111 size_cache: HashMap::new(),
5112 generation: 0,
5113 engine: None,
5114 animators: None,
5115 previous_rects: HashMap::new(),
5116 }
5117 }
5118
5119 pub fn generation(&self) -> u64 {
5121 self.generation
5122 }
5123
5124 pub fn invalidate(&mut self) {
5128 self.generation = self.generation.wrapping_add(1);
5129 }
5130
5131 pub fn is_valid(&self, key: LayoutKey, current_gen: u64) -> bool {
5134 key.generation == current_gen && key.generation == self.generation
5135 }
5136
5137 pub fn clear(&mut self) {
5138 self.safe_area = SafeArea::default();
5139 self.size_cache.clear();
5140 }
5141
5142 pub fn get_size(&self, view_hash: u64, proposal: SizeProposal) -> Option<Size> {
5143 let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
5144 let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
5145 self.size_cache.get(&(view_hash, pw, ph)).copied()
5146 }
5147
5148 pub fn set_size(&mut self, view_hash: u64, proposal: SizeProposal, size: Size) {
5149 let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
5150 let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
5151 self.size_cache.insert((view_hash, pw, ph), size);
5152 }
5153
5154 pub fn invalidate_view(&mut self, view_hash: u64) {
5156 self.size_cache.retain(|&(hash, _, _), _| hash != view_hash);
5157 }
5158 }
5159
5160 #[derive(Debug, Clone, Copy, PartialEq)]
5162 pub struct SizeProposal {
5163 pub width: Option<f32>,
5164 pub height: Option<f32>,
5165 }
5166
5167 impl SizeProposal {
5168 pub fn unspecified() -> Self {
5169 Self {
5170 width: None,
5171 height: None,
5172 }
5173 }
5174
5175 pub fn width(width: f32) -> Self {
5176 Self {
5177 width: Some(width),
5178 height: None,
5179 }
5180 }
5181
5182 pub fn height(height: f32) -> Self {
5183 Self {
5184 width: None,
5185 height: Some(height),
5186 }
5187 }
5188
5189 pub fn tight(width: f32, height: f32) -> Self {
5190 Self {
5191 width: Some(width),
5192 height: Some(height),
5193 }
5194 }
5195
5196 pub fn new(width: Option<f32>, height: Option<f32>) -> Self {
5197 Self { width, height }
5198 }
5199 }
5200
5201 pub trait LayoutView: Send {
5203 fn size_that_fits(
5205 &self,
5206 proposal: SizeProposal,
5207 subviews: &[&dyn LayoutView],
5208 cache: &mut LayoutCache,
5209 ) -> Size;
5210
5211 fn place_subviews(
5213 &self,
5214 bounds: Rect,
5215 subviews: &mut [&mut dyn LayoutView],
5216 cache: &mut LayoutCache,
5217 );
5218
5219 fn flex_weight(&self) -> f32 {
5221 0.0
5222 }
5223
5224 fn view_hash(&self) -> u64 {
5227 0
5228 }
5229
5230 fn debug_layout(&self, indent: usize) -> String {
5233 let prefix = " ".repeat(indent);
5234 format!("{}LayoutView", prefix)
5235 }
5236 }
5237 #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
5239 pub struct EdgeInsets {
5240 pub top: f32,
5241 pub leading: f32,
5242 pub bottom: f32,
5243 pub trailing: f32,
5244 }
5245
5246 impl EdgeInsets {
5247 pub fn new(top: f32, leading: f32, bottom: f32, trailing: f32) -> Self {
5248 Self {
5249 top,
5250 leading,
5251 bottom,
5252 trailing,
5253 }
5254 }
5255
5256 pub fn all(value: f32) -> Self {
5257 Self {
5258 top: value,
5259 leading: value,
5260 bottom: value,
5261 trailing: value,
5262 }
5263 }
5264 }
5265
5266 #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
5268 pub struct SafeArea {
5269 pub insets: EdgeInsets,
5270 }
5271
5272 #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
5274 pub enum SdfShape {
5275 Rect(Rect),
5276 RoundedRect { rect: Rect, radius: f32 },
5277 Circle { center: [f32; 2], radius: f32 },
5278 }
5279
5280 #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
5282 pub struct Rect {
5283 pub x: f32,
5284 pub y: f32,
5285 pub width: f32,
5286 pub height: f32,
5287 }
5288
5289 impl Rect {
5290 pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
5291 Self {
5292 x,
5293 y,
5294 width,
5295 height,
5296 }
5297 }
5298
5299 pub fn inset(&self, amount: f32) -> Self {
5300 Self {
5301 x: self.x + amount,
5302 y: self.y + amount,
5303 width: (self.width - amount * 2.0).max(0.0),
5304 height: (self.height - amount * 2.0).max(0.0),
5305 }
5306 }
5307
5308 pub fn offset(&self, dx: f32, dy: f32) -> Self {
5309 Self {
5310 x: self.x + dx,
5311 y: self.y + dy,
5312 ..*self
5313 }
5314 }
5315
5316 pub fn zero() -> Self {
5317 Self {
5318 x: 0.0,
5319 y: 0.0,
5320 width: 0.0,
5321 height: 0.0,
5322 }
5323 }
5324
5325 pub fn contains(&self, x: f32, y: f32) -> bool {
5326 x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
5327 }
5328
5329 pub fn size(&self) -> Size {
5330 Size {
5331 width: self.width,
5332 height: self.height,
5333 }
5334 }
5335
5336 pub fn split_horizontal(&self, n: usize) -> Vec<Rect> {
5338 if n == 0 {
5339 return vec![];
5340 }
5341 let item_width = self.width / n as f32;
5342 (0..n)
5343 .map(|i| Rect {
5344 x: self.x + i as f32 * item_width,
5345 y: self.y,
5346 width: item_width,
5347 height: self.height,
5348 })
5349 .collect()
5350 }
5351
5352 pub fn split_vertical(&self, n: usize) -> Vec<Rect> {
5354 if n == 0 {
5355 return vec![];
5356 }
5357 let item_height = self.height / n as f32;
5358 (0..n)
5359 .map(|i| Rect {
5360 x: self.x,
5361 y: self.y + i as f32 * item_height,
5362 width: self.width,
5363 height: item_height,
5364 })
5365 .collect()
5366 }
5367 }
5368}
5369
5370pub use layout::{LayoutCache, LayoutKey, LayoutView, Rect, SizeProposal};
5372pub mod agents;
5375pub mod animation;
5376pub mod gpu;
5377pub mod material;
5378pub mod runtime;
5379pub mod scene_graph;
5380pub mod sdf_shadow;
5381
5382pub use material::DrawMaterial;
5383pub use scene_graph::{NodeId, bifrost_registry};
5384
5385pub trait AssetManager: Send + Sync {
5389 fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>>;
5391
5392 fn preload_image(&self, url: &str);
5394}
5395
5396#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5398pub enum TouchPhase {
5399 Began,
5401 Moved,
5403 Ended,
5405 Cancelled,
5407}
5408
5409#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
5411pub enum Event {
5412 PointerDown {
5413 x: f32,
5414 y: f32,
5415 button: u32,
5416 proximity_field: f32,
5417 tilt: Option<f32>,
5418 azimuth: Option<f32>,
5419 pressure: Option<f32>,
5420 barrel_rotation: Option<f32>,
5421 pointer_precision: f32,
5422 },
5423 PointerUp {
5424 x: f32,
5425 y: f32,
5426 button: u32,
5427 tilt: Option<f32>,
5428 azimuth: Option<f32>,
5429 pressure: Option<f32>,
5430 barrel_rotation: Option<f32>,
5431 pointer_precision: f32,
5432 },
5433 PointerMove {
5434 x: f32,
5435 y: f32,
5436 proximity_field: f32,
5437 tilt: Option<f32>,
5438 azimuth: Option<f32>,
5439 pressure: Option<f32>,
5440 barrel_rotation: Option<f32>,
5441 pointer_precision: f32,
5442 },
5443 PointerClick {
5444 x: f32,
5445 y: f32,
5446 button: u32,
5447 tilt: Option<f32>,
5448 azimuth: Option<f32>,
5449 pressure: Option<f32>,
5450 barrel_rotation: Option<f32>,
5451 pointer_precision: f32,
5452 },
5453 PointerEnter,
5454 PointerLeave,
5455 PointerWheel {
5458 x: f32,
5459 y: f32,
5460 delta_x: f32,
5461 delta_y: f32,
5462 pointer_precision: f32,
5463 },
5464 PointerDoubleClick {
5466 x: f32,
5467 y: f32,
5468 button: u32,
5469 pointer_precision: f32,
5470 },
5471 DragStart {
5473 x: f32,
5474 y: f32,
5475 button: u32,
5476 pointer_precision: f32,
5477 },
5478 DragMove {
5480 x: f32,
5481 y: f32,
5482 pointer_precision: f32,
5483 },
5484 DragEnd {
5486 x: f32,
5487 y: f32,
5488 pointer_precision: f32,
5489 },
5490 KeyDown {
5491 key: String,
5492 },
5493 KeyUp {
5494 key: String,
5495 },
5496 FocusIn,
5498 FocusOut,
5500 Copy,
5502 Cut,
5504 Paste(String),
5506 Ime(String),
5508 TouchStart {
5510 x: f32,
5511 y: f32,
5512 touch_id: u64,
5513 },
5514 TouchMove {
5516 x: f32,
5517 y: f32,
5518 touch_id: u64,
5519 },
5520 TouchEnd {
5522 x: f32,
5523 y: f32,
5524 touch_id: u64,
5525 },
5526 TouchCancel {
5528 touch_id: u64,
5529 },
5530 GesturePinch {
5536 center: [f32; 2],
5537 scale: f32,
5538 velocity: f32,
5539 phase: TouchPhase,
5540 },
5541 GestureSwipe {
5546 direction: [f32; 2],
5547 velocity: f32,
5548 phase: TouchPhase,
5549 },
5550 FileDrop {
5552 x: f32,
5553 y: f32,
5554 path: String,
5555 },
5556}
5557
5558impl Event {
5559 pub fn pointer_precision(&self) -> f32 {
5565 match self {
5566 Self::PointerDown {
5567 pointer_precision, ..
5568 }
5569 | Self::PointerUp {
5570 pointer_precision, ..
5571 }
5572 | Self::PointerMove {
5573 pointer_precision, ..
5574 }
5575 | Self::PointerClick {
5576 pointer_precision, ..
5577 }
5578 | Self::PointerWheel {
5579 pointer_precision, ..
5580 }
5581 | Self::PointerDoubleClick {
5582 pointer_precision, ..
5583 }
5584 | Self::DragStart {
5585 pointer_precision, ..
5586 }
5587 | Self::DragMove {
5588 pointer_precision, ..
5589 }
5590 | Self::DragEnd {
5591 pointer_precision, ..
5592 } => *pointer_precision,
5593 _ => 0.0,
5594 }
5595 }
5596
5597 pub fn name(&self) -> &'static str {
5599 match self {
5600 Self::PointerDown { .. } => "pointerdown",
5601 Self::PointerUp { .. } => "pointerup",
5602 Self::PointerMove { .. } => "pointermove",
5603 Self::PointerClick { .. } => "pointerclick",
5604 Self::PointerEnter => "pointerenter",
5605 Self::PointerLeave => "pointerleave",
5606 Self::PointerWheel { .. } => "pointerwheel",
5607 Self::PointerDoubleClick { .. } => "pointerdoubleclick",
5608 Self::DragStart { .. } => "dragstart",
5609 Self::DragMove { .. } => "dragmove",
5610 Self::DragEnd { .. } => "dragend",
5611 Self::KeyDown { .. } => "keydown",
5612 Self::KeyUp { .. } => "keyup",
5613 Self::FocusIn => "focusin",
5614 Self::FocusOut => "focusout",
5615 Self::Copy => "copy",
5616 Self::Cut => "cut",
5617 Self::Paste(_) => "paste",
5618 Self::Ime(_) => "ime",
5619 Self::TouchStart { .. } => "touchstart",
5620 Self::TouchMove { .. } => "touchmove",
5621 Self::TouchEnd { .. } => "touchend",
5622 Self::TouchCancel { .. } => "touchcancel",
5623 Self::GesturePinch { .. } => "gesturepinch",
5624 Self::GestureSwipe { .. } => "gestureswipe",
5625 Self::FileDrop { .. } => "filedrop",
5626 }
5627 }
5628}
5629
5630#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5632pub enum EventResponse {
5633 Handled,
5634 Ignored,
5635}
5636
5637pub struct DefaultAssetManager {
5639 cache: AssetCache,
5640}
5641type AssetCache = Arc<arc_swap::ArcSwap<HashMap<String, AssetState<Arc<Vec<u8>>>>>>;
5642
5643impl Default for DefaultAssetManager {
5644 fn default() -> Self {
5645 Self::new()
5646 }
5647}
5648
5649impl DefaultAssetManager {
5650 pub fn new() -> Self {
5651 Self {
5652 cache: Arc::new(arc_swap::ArcSwap::from_pointee(HashMap::new())),
5653 }
5654 }
5655}
5656
5657impl AssetManager for DefaultAssetManager {
5658 fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>> {
5659 if let Some(state) = self.cache.load().get(url) {
5660 return state.clone();
5661 }
5662
5663 self.cache.rcu(|map| {
5664 let mut m = (**map).clone();
5665 m.entry(url.to_string()).or_insert(AssetState::Loading);
5666 m
5667 });
5668 AssetState::Loading
5669 }
5670
5671 fn preload_image(&self, _url: &str) {}
5672}
5673
5674use std::future::Future;
5675
5676pub struct Suspense<T: Clone + Send + Sync + 'static> {
5679 inner: State<AssetState<T>>,
5680}
5681
5682impl<T: Clone + Send + Sync + 'static> Default for Suspense<T> {
5683 fn default() -> Self {
5684 Self::new()
5685 }
5686}
5687
5688impl<T: Clone + Send + Sync + 'static> Suspense<T> {
5689 pub fn new() -> Self {
5690 Self {
5691 inner: State::new(AssetState::Loading),
5692 }
5693 }
5694
5695 pub fn new_async<F>(future: F) -> Self
5696 where
5697 F: Future<Output = Result<T, String>> + Send + 'static,
5698 {
5699 let suspense = Self::new();
5700 let suspense_clone = suspense.clone();
5701
5702 #[cfg(not(target_arch = "wasm32"))]
5703 {
5704 if let Ok(handle) = tokio::runtime::Handle::try_current() {
5706 handle.spawn(async move {
5707 let result = future.await;
5708 match result {
5709 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5710 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5711 }
5712 });
5713 } else {
5714 std::thread::spawn(move || {
5715 let rt = tokio::runtime::Builder::new_current_thread()
5716 .enable_all()
5717 .build()
5718 .unwrap();
5719 rt.block_on(async {
5720 let result = future.await;
5721 match result {
5722 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5723 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5724 }
5725 });
5726 });
5727 }
5728 }
5729 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5730 {
5731 wasm_bindgen_futures::spawn_local(async move {
5732 let result = future.await;
5733 match result {
5734 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5735 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5736 }
5737 });
5738 }
5739
5740 suspense
5741 }
5742
5743 pub fn ready(value: T) -> Self {
5744 Self {
5745 inner: State::new(AssetState::Ready(value)),
5746 }
5747 }
5748
5749 pub fn error(message: impl Into<String>) -> Self {
5750 Self {
5751 inner: State::new(AssetState::Error(message.into())),
5752 }
5753 }
5754
5755 pub fn get(&self) -> AssetState<T> {
5756 self.inner.get()
5757 }
5758
5759 pub fn get_ref(&self) -> AssetState<T> {
5760 self.inner.get()
5761 }
5762
5763 pub fn is_loading(&self) -> bool {
5764 matches!(self.get(), AssetState::Loading)
5765 }
5766
5767 pub fn is_ready(&self) -> bool {
5768 matches!(self.get(), AssetState::Ready(_))
5769 }
5770
5771 pub fn is_error(&self) -> bool {
5772 matches!(self.get(), AssetState::Error(_))
5773 }
5774
5775 pub fn ready_value(&self) -> Option<T> {
5776 match self.get() {
5777 AssetState::Ready(value) => Some(value),
5778 _ => None,
5779 }
5780 }
5781
5782 pub fn error_message(&self) -> Option<String> {
5783 match self.get() {
5784 AssetState::Error(message) => Some(message),
5785 _ => None,
5786 }
5787 }
5788
5789 pub fn subscribe<F: Fn(&AssetState<T>) + Send + Sync + 'static>(&self, callback: F) {
5790 self.inner.subscribe(callback)
5791 }
5792
5793 pub fn inner_state(&self) -> &State<AssetState<T>> {
5794 &self.inner
5795 }
5796}
5797
5798impl<T: Clone + Send + Sync + 'static> Clone for Suspense<T> {
5799 fn clone(&self) -> Self {
5800 Self {
5801 inner: self.inner.clone(),
5802 }
5803 }
5804}
5805
5806impl<T: Clone + Send + Sync + 'static> From<T> for Suspense<T> {
5807 fn from(value: T) -> Self {
5808 Self::ready(value)
5809 }
5810}
5811
5812impl<T: Clone + Send + Sync + 'static> From<Result<T, String>> for Suspense<T> {
5813 fn from(result: Result<T, String>) -> Self {
5814 match result {
5815 Ok(value) => Self::ready(value),
5816 Err(error) => Self::error(error),
5817 }
5818 }
5819}
5820
5821#[cfg(test)]
5822mod phase1_test;
5823
5824#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5826pub enum BerserkerMode {
5827 Normal,
5828 Rage, Frenzy, GodMode, }
5832
5833pub trait Seer: Send + Sync {
5836 fn predict(&self, context: &str) -> String;
5838 fn whispers(&self) -> Vec<String>;
5840}
5841
5842#[cfg(test)]
5843mod vili_tests {
5844 use super::*;
5845
5846 struct DummyRenderer;
5847 impl ElapsedTime for DummyRenderer {
5848 fn elapsed_time(&self) -> f32 {
5849 0.0
5850 }
5851 fn delta_time(&self) -> f32 {
5852 0.0
5853 }
5854 }
5855 impl Renderer for DummyRenderer {
5856 fn fill_rect(&mut self, _r: Rect, _c: [f32; 4]) {}
5857 fn fill_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4]) {}
5858 fn fill_ellipse(&mut self, _r: Rect, _c: [f32; 4]) {}
5859 fn stroke_rect(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5860 fn stroke_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4], _w: f32) {}
5861 fn stroke_ellipse(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5862 fn draw_line(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, _c: [f32; 4], _w: f32) {}
5863 fn draw_text(&mut self, _t: &str, _x: f32, _y: f32, _s: f32, _c: [f32; 4]) {}
5864 fn measure_text(&mut self, _t: &str, _s: f32) -> (f32, f32) {
5865 (0.0, 0.0)
5866 }
5867 fn memoize(&mut self, _id: u64, _hash: u64, _r: &dyn Fn(&mut dyn Renderer)) {}
5868 fn draw_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {
5869 }
5870 fn set_camera_3d(&mut self, _camera: &Camera3D) {}
5871 fn push_transform_3d(&mut self, _transform: &Transform3D) {}
5872 fn pop_transform_3d(&mut self) {}
5873 }
5874
5875 #[test]
5876 fn test_magnetic_warp() {
5877 let renderer = DummyRenderer;
5878 let anchor = Rect {
5879 x: 100.0,
5880 y: 100.0,
5881 width: 50.0,
5882 height: 50.0,
5883 };
5884 let pointer = [125.0, 50.0];
5886 let warp = renderer.magnetic_warp(pointer, anchor, 1.0);
5889 assert!(warp[1] > 50.0);
5891
5892 let far_pointer = [500.0, 500.0];
5894 let far_warp = renderer.magnetic_warp(far_pointer, anchor, 1.0);
5895 assert_eq!(far_pointer, far_warp);
5896 }
5897
5898 #[test]
5899 fn test_mani_glow() {
5900 let renderer = DummyRenderer;
5901 let bounds = Rect {
5902 x: 0.0,
5903 y: 0.0,
5904 width: 100.0,
5905 height: 100.0,
5906 };
5907 let pointer_inside = [50.0, 50.0];
5908 let glow_max = renderer.mani_glow_intensity(pointer_inside, bounds, 120.0);
5909 assert_eq!(glow_max, 1.0);
5910
5911 let pointer_edge = [50.0, -10.0];
5912 let glow_partial = renderer.mani_glow_intensity(pointer_edge, bounds, 120.0);
5913 assert!(glow_partial > 0.0 && glow_partial < 1.0);
5914 }
5915
5916 #[test]
5917 fn test_fafnir_evolve() {
5918 let renderer = DummyRenderer;
5919 let bounds = Rect {
5920 x: 0.0,
5921 y: 0.0,
5922 width: 100.0,
5923 height: 100.0,
5924 };
5925 let pointer_inside = [50.0, 50.0];
5926 let scale = renderer.fafnir_evolve(pointer_inside, bounds, 1.2);
5927 assert_eq!(scale, 1.2); }
5929
5930 #[test]
5931 fn test_undo_manager_basic() {
5932 let mut manager = UndoManager::new(3, 0.5);
5933 let val = std::sync::Arc::new(std::sync::Mutex::new(0));
5934
5935 let v1 = val.clone();
5936 let v2 = val.clone();
5937 manager.push(
5938 "Add",
5939 move || *v1.lock().unwrap() -= 1,
5940 move || *v2.lock().unwrap() += 1,
5941 );
5942
5943 assert!(manager.can_undo());
5944 assert!(!manager.can_redo());
5945
5946 let undo = manager.undo().unwrap();
5947 undo();
5948 assert_eq!(*val.lock().unwrap(), -1);
5949 assert!(!manager.can_undo());
5950 assert!(manager.can_redo());
5951
5952 let redo = manager.redo().unwrap();
5953 redo();
5954 assert_eq!(*val.lock().unwrap(), 0);
5955 }
5956
5957 #[test]
5958 fn test_undo_manager_depth_limit() {
5959 let mut manager = UndoManager::new(2, 0.5);
5960 manager.push("1", || {}, || {});
5961 manager.push("2", || {}, || {});
5962 manager.push("3", || {}, || {});
5963
5964 assert_eq!(manager.stack.len(), 2);
5965 assert_eq!(manager.position, 2);
5966 }
5967
5968 #[test]
5969 fn test_undo_manager_coalescing() {
5970 let mut manager = UndoManager::new(10, 1.0);
5971 let count = std::sync::Arc::new(std::sync::Mutex::new(0));
5972
5973 let c = count.clone();
5974 manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5975
5976 let c = count.clone();
5977 manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5978
5979 assert_eq!(manager.stack.len(), 1);
5980
5981 let undo = manager.undo().unwrap();
5982 undo();
5983 assert_eq!(*count.lock().unwrap(), -2);
5984 }
5985}
5986
5987use std::cell::RefCell;
5999
6000thread_local! {
6001 static THEME_CONTEXT: RefCell<Option<color::SemanticColors>> = const { RefCell::new(None) };
6003}
6004
6005pub use color::SemanticColors;
6011
6012pub fn set_current_theme(colors: color::SemanticColors) {
6015 THEME_CONTEXT.with(|cell| {
6016 *cell.borrow_mut() = Some(colors);
6017 });
6018}
6019
6020pub fn clear_current_theme() {
6022 THEME_CONTEXT.with(|cell| {
6023 *cell.borrow_mut() = None;
6024 });
6025}
6026
6027pub fn use_theme() -> color::SemanticColors {
6042 THEME_CONTEXT.with(|cell| {
6043 cell.borrow()
6044 .clone()
6045 .unwrap_or_else(color::SemanticColors::dark)
6046 })
6047}
6048
6049pub mod color {
6058 use super::Color;
6059
6060 #[derive(Debug, Clone)]
6077 pub struct SemanticColors {
6078 pub primary: Color,
6080 pub secondary: Color,
6082 pub accent: Color,
6084 pub background: Color,
6086 pub surface: Color,
6088 pub error: Color,
6090 pub warning: Color,
6092 pub success: Color,
6094 pub text: Color,
6096 pub text_dim: Color,
6098 }
6099
6100 impl SemanticColors {
6101 pub fn dark() -> Self {
6103 Self {
6104 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), }
6115 }
6116
6117 pub fn light() -> Self {
6119 Self {
6120 primary: Color::new(0.35, 0.30, 0.70, 1.0),
6121 secondary: Color::new(0.30, 0.50, 0.30, 1.0),
6122 accent: Color::new(0.30, 0.35, 0.75, 1.0),
6123 background: Color::new(0.97, 0.97, 0.98, 1.0),
6124 surface: Color::new(0.93, 0.93, 0.95, 1.0),
6125 error: Color::new(0.75, 0.15, 0.15, 1.0),
6126 warning: Color::new(0.80, 0.60, 0.0, 1.0),
6127 success: Color::new(0.15, 0.65, 0.30, 1.0),
6128 text: Color::new(0.08, 0.08, 0.10, 1.0),
6129 text_dim: Color::new(0.40, 0.40, 0.45, 1.0),
6130 }
6131 }
6132
6133 pub fn accent_states(&self) -> InteractiveColorStates {
6138 InteractiveColorStates::from_color(self.accent)
6139 }
6140
6141 pub fn primary_states(&self) -> InteractiveColorStates {
6143 InteractiveColorStates::from_color(self.primary)
6144 }
6145
6146 pub fn error_states(&self) -> InteractiveColorStates {
6148 InteractiveColorStates::from_color(self.error)
6149 }
6150
6151 pub fn success_states(&self) -> InteractiveColorStates {
6153 InteractiveColorStates::from_color(self.success)
6154 }
6155 }
6156
6157 #[derive(Debug, Clone)]
6162 pub struct InteractiveColorStates {
6163 pub default: Color,
6164 pub hover: Color,
6165 pub active: Color,
6166 pub focus: Color,
6167 pub disabled: Color,
6168 pub focus_ring: Color,
6169 }
6170
6171 impl InteractiveColorStates {
6172 pub fn from_color(base: Color) -> Self {
6181 Self {
6182 default: base,
6183 hover: base.lighten(0.15),
6184 active: base.darken(0.15),
6185 focus: base,
6186 disabled: Color::new(base.r, base.g, base.b, base.a * 0.4),
6187 focus_ring: Color::new(base.r, base.g, base.b, base.a * 0.7),
6188 }
6189 }
6190
6191 pub fn color_for(&self, state: InteractiveState) -> Color {
6193 match state {
6194 InteractiveState::Default => self.default,
6195 InteractiveState::Hover => self.hover,
6196 InteractiveState::Active => self.active,
6197 InteractiveState::Focus => self.focus,
6198 InteractiveState::Disabled => self.disabled,
6199 }
6200 }
6201 }
6202
6203 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6205 pub enum InteractiveState {
6206 Default,
6207 Hover,
6208 Active,
6209 Focus,
6210 Disabled,
6211 }
6212}
6213
6214pub fn use_state<T: Clone + Send + Sync + 'static>(
6233 id: u64,
6234 initial: T,
6235) -> (impl Fn() -> T, impl Fn(T)) {
6236 let already_exists = load_system_state().get_component_state::<T>(id).is_some();
6238 if !already_exists {
6239 update_system_state(|s| {
6240 let mut ns = s.clone();
6241 ns.set_component_state(id, initial.clone());
6242 ns
6243 });
6244 }
6245
6246 let getter = move || -> T {
6247 load_system_state()
6248 .get_component_state::<T>(id)
6249 .map(|arc_lock| {
6250 arc_lock
6251 .read()
6252 .ok()
6253 .map(|guard| (*guard).clone())
6254 .unwrap_or_else(|| initial.clone())
6255 })
6256 .unwrap_or_else(|| initial.clone())
6257 };
6258
6259 let setter = {
6260 move |value| {
6261 update_system_state(|s| {
6262 let mut ns = s.clone();
6263 ns.set_component_state(id, value);
6264 ns
6265 });
6266 }
6267 };
6268
6269 (getter, setter)
6270}
6271
6272pub fn use_state_hash(key: &str) -> u64 {
6284 use std::hash::{Hash, Hasher};
6285 let mut s = std::collections::hash_map::DefaultHasher::new();
6286 key.hash(&mut s);
6287 s.finish()
6288}
6289
6290thread_local! {
6300 static ACCESSIBILITY_PREFS: std::cell::RefCell<AccessibilityPreferences> =
6303 std::cell::RefCell::new(AccessibilityPreferences::default());
6304}
6305
6306#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
6313pub struct AccessibilityPreferences {
6314 pub reduce_motion: bool,
6316 pub reduce_transparency: bool,
6318 pub increase_contrast: bool,
6320}
6321
6322impl AccessibilityPreferences {
6323 pub fn detect_from_system() -> Self {
6328 #[cfg(target_os = "macos")]
6329 {
6330 let reduce_motion = std::process::Command::new("defaults")
6332 .args(["read", "-g", "com.apple.universalaccess", "reduceMotion"])
6333 .output()
6334 .ok()
6335 .and_then(|o| String::from_utf8(o.stdout).ok())
6336 .map(|s| s.trim() == "1")
6337 .unwrap_or(false);
6338
6339 let reduce_transparency = std::process::Command::new("defaults")
6340 .args([
6341 "read",
6342 "-g",
6343 "com.apple.universalaccess",
6344 "reduceTransparency",
6345 ])
6346 .output()
6347 .ok()
6348 .and_then(|o| String::from_utf8(o.stdout).ok())
6349 .map(|s| s.trim() == "1")
6350 .unwrap_or(false);
6351
6352 let increase_contrast = std::process::Command::new("defaults")
6353 .args([
6354 "read",
6355 "-g",
6356 "com.apple.universalaccess",
6357 "increaseContrast",
6358 ])
6359 .output()
6360 .ok()
6361 .and_then(|o| String::from_utf8(o.stdout).ok())
6362 .map(|s| s.trim() == "1")
6363 .unwrap_or(false);
6364
6365 Self {
6366 reduce_motion,
6367 reduce_transparency,
6368 increase_contrast,
6369 }
6370 }
6371 #[cfg(not(target_os = "macos"))]
6372 {
6373 Self::default()
6374 }
6375 }
6376
6377 pub fn min_alpha(&self, requested: f32) -> f32 {
6379 if self.increase_contrast {
6380 requested.max(0.5)
6381 } else {
6382 requested
6383 }
6384 }
6385
6386 pub fn should_disable_glass(&self) -> bool {
6388 self.reduce_transparency
6389 }
6390
6391 pub fn should_reduce_motion(&self) -> bool {
6393 self.reduce_motion
6394 }
6395
6396 pub fn should_increase_contrast(&self) -> bool {
6398 self.increase_contrast
6399 }
6400}
6401
6402pub fn accessibility_preferences() -> AccessibilityPreferences {
6404 ACCESSIBILITY_PREFS.with(|p| *p.borrow())
6405}
6406
6407pub fn set_accessibility_preferences(prefs: AccessibilityPreferences) {
6412 ACCESSIBILITY_PREFS.with(|p| {
6413 *p.borrow_mut() = prefs;
6414 });
6415}
6416
6417pub trait ClipboardProvider: Send + Sync {
6426 fn read_text(&self) -> Option<String>;
6428 fn write_text(&self, text: &str);
6430}
6431
6432#[cfg(not(target_arch = "wasm32"))]
6436pub struct SystemClipboard;
6437
6438#[cfg(not(target_arch = "wasm32"))]
6439impl ClipboardProvider for SystemClipboard {
6440 fn read_text(&self) -> Option<String> {
6441 use std::process::Command;
6442 Command::new("pbpaste")
6444 .output()
6445 .ok()
6446 .and_then(|o| String::from_utf8(o.stdout).ok())
6447 }
6448
6449 fn write_text(&self, text: &str) {
6450 use std::process::Command;
6451 if let Ok(mut child) = Command::new("pbcopy")
6453 .stdin(std::process::Stdio::piped())
6454 .spawn()
6455 {
6456 if let Some(stdin) = child.stdin.as_mut() {
6457 use std::io::Write;
6458 let _ = stdin.write_all(text.as_bytes());
6459 }
6460 let _ = child.wait();
6461 }
6462 }
6463}
6464
6465#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6471pub enum TextDirection {
6472 Forward,
6473 Backward,
6474 Up,
6475 Down,
6476 LineStart,
6477 LineEnd,
6478 WordForward,
6479 WordBackward,
6480}
6481
6482#[derive(Debug, Clone, Default)]
6487pub struct TextInputState {
6488 pub text: String,
6490 pub cursor_pos: usize,
6492 pub selection_anchor: Option<usize>,
6495 pub focused: bool,
6497 pub caret_visible: bool,
6499 pub last_edit_time: f32,
6501}
6502
6503impl TextInputState {
6504 pub fn new(text: impl Into<String>) -> Self {
6506 let text = text.into();
6507 let cursor_pos = text.len();
6508 Self {
6509 text,
6510 cursor_pos,
6511 selection_anchor: None,
6512 focused: false,
6513 caret_visible: true,
6514 last_edit_time: 0.0,
6515 }
6516 }
6517
6518 pub fn selection_range(&self) -> Option<(usize, usize)> {
6521 self.selection_anchor.map(|anchor| {
6522 if anchor <= self.cursor_pos {
6523 (anchor, self.cursor_pos)
6524 } else {
6525 (self.cursor_pos, anchor)
6526 }
6527 })
6528 }
6529
6530 pub fn selected_text(&self) -> String {
6532 self.selection_range()
6533 .map(|(start, end)| self.text[start..end].to_string())
6534 .unwrap_or_default()
6535 }
6536
6537 pub fn insert(&mut self, new_text: &str) {
6539 if let Some((start, end)) = self.selection_range() {
6540 self.text.replace_range(start..end, new_text);
6541 self.cursor_pos = start + new_text.len();
6542 } else {
6543 self.text.insert_str(self.cursor_pos, new_text);
6544 self.cursor_pos += new_text.len();
6545 }
6546 self.selection_anchor = None;
6547 }
6548
6549 pub fn delete(&mut self, backward: bool, count: usize) -> String {
6552 if let Some((start, end)) = self.selection_range() {
6553 let deleted = self.text[start..end].to_string();
6554 self.text.replace_range(start..end, "");
6555 self.cursor_pos = start;
6556 self.selection_anchor = None;
6557 return deleted;
6558 }
6559
6560 if backward && self.cursor_pos > 0 {
6561 let start = self.cursor_pos.saturating_sub(count);
6562 let deleted = self.text[start..self.cursor_pos].to_string();
6563 self.text.replace_range(start..self.cursor_pos, "");
6564 self.cursor_pos = start;
6565 deleted
6566 } else if !backward && self.cursor_pos < self.text.len() {
6567 let end = (self.cursor_pos + count).min(self.text.len());
6568 let deleted = self.text[self.cursor_pos..end].to_string();
6569 self.text.replace_range(self.cursor_pos..end, "");
6570 deleted
6571 } else {
6572 String::new()
6573 }
6574 }
6575
6576 pub fn move_cursor(&mut self, direction: TextDirection, extend_selection: bool) {
6578 if !extend_selection {
6579 self.selection_anchor = None;
6580 } else if self.selection_anchor.is_none() {
6581 self.selection_anchor = Some(self.cursor_pos);
6582 }
6583
6584 match direction {
6585 TextDirection::Forward if self.cursor_pos < self.text.len() => {
6586 let next = self.text[self.cursor_pos..]
6588 .char_indices()
6589 .nth(1)
6590 .map(|(i, _)| self.cursor_pos + i)
6591 .unwrap_or(self.text.len());
6592 self.cursor_pos = next;
6593 }
6594 TextDirection::Backward if self.cursor_pos > 0 => {
6595 let prev = self.text[..self.cursor_pos]
6596 .char_indices()
6597 .next_back()
6598 .map(|(i, _)| i)
6599 .unwrap_or(0);
6600 self.cursor_pos = prev;
6601 }
6602 TextDirection::LineStart => {
6603 self.cursor_pos = 0;
6604 }
6605 TextDirection::LineEnd => {
6606 self.cursor_pos = self.text.len();
6607 }
6608 TextDirection::WordForward => {
6609 let rest = &self.text[self.cursor_pos..];
6611 let after_word = rest
6613 .char_indices()
6614 .find(|(_, c)| !c.is_alphanumeric())
6615 .map(|(i, _)| i)
6616 .unwrap_or(rest.len());
6617 let after_space = rest[after_word..]
6619 .char_indices()
6620 .find(|(_, c)| !c.is_whitespace())
6621 .map(|(i, _)| after_word + i)
6622 .unwrap_or(rest.len());
6623 self.cursor_pos = (self.cursor_pos + after_space).min(self.text.len());
6624 }
6625 TextDirection::WordBackward => {
6626 let before = &self.text[..self.cursor_pos];
6627 let before_word = before
6629 .char_indices()
6630 .rev()
6631 .find(|(_, c)| !c.is_whitespace())
6632 .map(|(i, _)| i)
6633 .unwrap_or(0);
6634 let word_start = before[..before_word]
6636 .char_indices()
6637 .rev()
6638 .find(|(_, c)| !c.is_alphanumeric())
6639 .map(|(i, _)| i)
6640 .unwrap_or(0);
6641 self.cursor_pos = word_start;
6642 }
6643 _ => {} }
6645
6646 if !extend_selection {
6647 self.selection_anchor = None;
6648 }
6649 }
6650
6651 pub fn select_all(&mut self) {
6653 self.cursor_pos = self.text.len();
6654 self.selection_anchor = Some(0);
6655 }
6656
6657 pub fn cursor_byte_pos(&self) -> usize {
6659 self.cursor_pos
6660 }
6661}
6662
6663#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6665pub struct NotificationAction {
6666 pub id: String,
6668 pub title: String,
6670 pub is_destructive: bool,
6672}
6673
6674#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6676pub enum NotificationPriority {
6677 Passive,
6679 #[default]
6681 Active,
6682 TimeSensitive,
6684}
6685
6686#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
6688pub struct Notification {
6689 pub id: String,
6691 pub app_name: Option<String>,
6693 pub title: String,
6695 pub body: String,
6697 pub icon: Option<String>,
6699 pub sound: Option<String>,
6701 pub actions: Vec<NotificationAction>,
6703 pub timeout: Option<f32>,
6705 pub priority: NotificationPriority,
6707 pub timestamp: f32,
6709 pub dismissed: bool,
6711}
6712
6713#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, thiserror::Error)]
6715pub enum NotificationError {
6716 #[error("Notification permission denied")]
6718 PermissionDenied,
6719 #[error("Failed to post notification")]
6721 PostFailed,
6722}
6723
6724#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6726pub enum NotificationPermission {
6727 Granted,
6729 Denied,
6731 #[default]
6733 NotDetermined,
6734}
6735
6736pub trait NotificationHandler: Send + Sync {
6738 fn show(&self, notification: Notification) -> Result<(), NotificationError>;
6740 fn dismiss(&self, id: &str) -> Result<(), NotificationError>;
6742 fn request_permission(&self) -> NotificationPermission;
6744}
6745
6746static NEXT_NOTIFICATION_ID: std::sync::atomic::AtomicUsize =
6747 std::sync::atomic::AtomicUsize::new(1);
6748
6749#[derive(Clone, Copy, Debug, Default)]
6751pub struct DefaultNotificationHandler;
6752
6753impl NotificationHandler for DefaultNotificationHandler {
6754 fn show(&self, notification: Notification) -> Result<(), NotificationError> {
6756 let mut notif = notification;
6757 if notif.id.is_empty() {
6758 let id = NEXT_NOTIFICATION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6759 notif.id = format!("notif_{}", id);
6760 }
6761 update_system_state(|state| {
6762 let mut new_state = state.clone();
6763 new_state.notifications.push(notif.clone());
6764 new_state
6765 });
6766 Ok(())
6767 }
6768
6769 fn dismiss(&self, id: &str) -> Result<(), NotificationError> {
6771 update_system_state(|state| {
6772 let mut new_state = state.clone();
6773 for notif in &mut new_state.notifications {
6774 if notif.id == id {
6775 notif.dismissed = true;
6776 }
6777 }
6778 new_state
6779 });
6780 Ok(())
6781 }
6782
6783 fn request_permission(&self) -> NotificationPermission {
6785 NotificationPermission::Granted
6786 }
6787}
6788
6789static NOTIFICATION_HANDLER: once_cell::sync::OnceCell<std::sync::Arc<dyn NotificationHandler>> =
6790 once_cell::sync::OnceCell::new();
6791
6792pub fn set_notification_handler(handler: std::sync::Arc<dyn NotificationHandler>) {
6794 let _ = NOTIFICATION_HANDLER.set(handler);
6795}
6796
6797pub fn get_notification_handler() -> std::sync::Arc<dyn NotificationHandler> {
6799 NOTIFICATION_HANDLER
6800 .get_or_init(|| std::sync::Arc::new(DefaultNotificationHandler))
6801 .clone()
6802}
6803
6804#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6806pub struct FileFilter {
6807 pub name: String,
6809 pub extensions: Vec<String>,
6811}
6812
6813#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
6815pub enum FileDialogMode {
6816 #[default]
6818 OpenFile,
6819 OpenDirectory,
6821 SaveFile,
6823}
6824
6825#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6827pub struct FileDialog {
6828 pub title: String,
6830 pub default_path: Option<String>,
6832 pub filters: Vec<FileFilter>,
6834 pub mode: FileDialogMode,
6836 pub allow_multiple: bool,
6838}
6839
6840#[derive(Debug, thiserror::Error)]
6842pub enum FileDialogError {
6843 #[error("File dialog cancelled")]
6845 Cancelled,
6846 #[error("I/O error: {0}")]
6848 Io(#[from] std::io::Error),
6849 #[error("Platform error: {0}")]
6851 Platform(String),
6852}
6853
6854impl FileDialog {
6855 pub fn new(mode: FileDialogMode) -> Self {
6857 Self {
6858 mode,
6859 ..Default::default()
6860 }
6861 }
6862
6863 pub fn title(mut self, title: impl Into<String>) -> Self {
6865 self.title = title.into();
6866 self
6867 }
6868
6869 pub fn add_filter(mut self, name: &str, extensions: &[&str]) -> Self {
6871 self.filters.push(FileFilter {
6872 name: name.to_string(),
6873 extensions: extensions.iter().map(|s| s.to_string()).collect(),
6874 });
6875 self
6876 }
6877
6878 pub fn default_path(mut self, path: impl Into<String>) -> Self {
6880 self.default_path = Some(path.into());
6881 self
6882 }
6883
6884 pub fn allow_multiple(mut self, allow: bool) -> Self {
6886 self.allow_multiple = allow;
6887 self
6888 }
6889}
6890
6891#[cfg(not(target_arch = "wasm32"))]
6892impl FileDialog {
6893 pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6895 let mut dialog = rfd::FileDialog::new();
6896 dialog = dialog.set_title(&self.title);
6897 if let Some(path) = &self.default_path {
6898 dialog = dialog.set_directory(path);
6899 }
6900 for filter in &self.filters {
6901 let refs: Vec<&str> = filter.extensions.iter().map(|s| s.as_str()).collect();
6902 dialog = dialog.add_filter(&filter.name, &refs);
6903 }
6904
6905 match self.mode {
6906 FileDialogMode::OpenFile => {
6907 if self.allow_multiple {
6908 dialog.pick_files().ok_or(FileDialogError::Cancelled)
6909 } else {
6910 Ok(dialog.pick_file().into_iter().collect())
6911 }
6912 }
6913 FileDialogMode::OpenDirectory => Ok(dialog.pick_folder().into_iter().collect()),
6914 FileDialogMode::SaveFile => Ok(dialog.save_file().into_iter().collect()),
6915 }
6916 }
6917
6918 pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6920 let results = self.pick()?;
6921 Ok(results.into_iter().next())
6922 }
6923}
6924
6925#[cfg(target_arch = "wasm32")]
6926impl FileDialog {
6927 pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6929 Err(FileDialogError::Platform(
6930 "FileDialog is not supported synchronously on WebAssembly".to_string(),
6931 ))
6932 }
6933
6934 pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6936 Err(FileDialogError::Platform(
6937 "FileDialog is not supported synchronously on WebAssembly".to_string(),
6938 ))
6939 }
6940}
6941
6942#[derive(Debug, thiserror::Error)]
6944pub enum DocumentError {
6945 #[error("I/O error: {0}")]
6947 Io(#[from] std::io::Error),
6948 #[error("Parse error: {0}")]
6950 Parse(String),
6951 #[error("Serialization error: {0}")]
6953 Serialize(String),
6954}
6955
6956pub trait Document: Send + Sync {
6958 fn read_from(path: &std::path::Path) -> Result<Self, DocumentError>
6960 where
6961 Self: Sized;
6962
6963 fn write_to(&self, path: &std::path::Path) -> Result<(), DocumentError>;
6965
6966 fn is_dirty(&self) -> bool;
6968
6969 fn mark_clean(&mut self);
6971}
6972
6973pub struct AutoSaveManager {
6975 pub interval: f32,
6977 pub timer: f32,
6979 pub documents: Vec<(std::path::PathBuf, Box<dyn Document>)>,
6981}
6982
6983impl AutoSaveManager {
6984 pub fn new(interval: f32) -> Self {
6986 Self {
6987 interval,
6988 timer: 0.0,
6989 documents: Vec::new(),
6990 }
6991 }
6992
6993 pub fn register(&mut self, path: std::path::PathBuf, doc: Box<dyn Document>) {
6995 self.documents.push((path, doc));
6996 }
6997
6998 pub fn tick(&mut self, dt: f32) {
7000 self.timer += dt;
7001 if self.timer >= self.interval {
7002 self.timer = 0.0;
7003 for (path, doc) in &mut self.documents {
7004 if doc.is_dirty() {
7005 match doc.write_to(path) {
7006 Ok(()) => {
7007 doc.mark_clean();
7008 log::info!("[AutoSaveManager] Auto-saved document to {:?}", path);
7009 }
7010 Err(e) => {
7011 log::error!(
7012 "[AutoSaveManager] Failed to auto-save document to {:?}: {:?}",
7013 path,
7014 e
7015 );
7016 }
7017 }
7018 }
7019 }
7020 }
7021 }
7022}
7023
7024#[derive(Debug, Clone, PartialEq, Eq, Default)]
7032pub struct Modifiers {
7033 pub cmd: bool,
7035 pub shift: bool,
7037 pub alt: bool,
7039 pub ctrl: bool,
7041}
7042
7043#[derive(Debug, Clone)]
7045pub struct KeyboardShortcut {
7046 pub key: String,
7048 pub modifiers: Modifiers,
7050}
7051
7052impl KeyboardShortcut {
7053 pub fn cmd(key: impl Into<String>) -> Self {
7055 Self {
7056 key: key.into(),
7057 modifiers: Modifiers {
7058 cmd: true,
7059 ..Default::default()
7060 },
7061 }
7062 }
7063
7064 pub fn cmd_shift(key: impl Into<String>) -> Self {
7066 Self {
7067 key: key.into(),
7068 modifiers: Modifiers {
7069 cmd: true,
7070 shift: true,
7071 ..Default::default()
7072 },
7073 }
7074 }
7075}
7076
7077pub enum MenuItem {
7083 Action {
7085 label: String,
7086 shortcut: Option<KeyboardShortcut>,
7087 action: std::sync::Arc<dyn Fn() + Send + Sync>,
7088 enabled: bool,
7089 },
7090 Submenu { label: String, items: Vec<MenuItem> },
7092 Separator,
7094}
7095
7096impl std::fmt::Debug for MenuItem {
7097 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7098 match self {
7099 Self::Action { label, enabled, .. } => f
7100 .debug_struct("Action")
7101 .field("label", label)
7102 .field("enabled", enabled)
7103 .finish(),
7104 Self::Submenu { label, items } => f
7105 .debug_struct("Submenu")
7106 .field("label", label)
7107 .field("items", items)
7108 .finish(),
7109 Self::Separator => write!(f, "Separator"),
7110 }
7111 }
7112}
7113
7114pub struct MenuBar {
7119 pub items: Vec<MenuItem>,
7121}
7122
7123impl MenuBar {
7124 pub fn new() -> Self {
7126 Self { items: Vec::new() }
7127 }
7128
7129 pub fn add_item(&mut self, item: MenuItem) {
7131 self.items.push(item);
7132 }
7133
7134 #[allow(clippy::too_many_arguments)]
7146 pub fn standard(
7147 new_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7148 open_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7149 save_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7150 close_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7151 quit_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7152 undo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7153 redo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7154 cut_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7155 copy_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7156 paste_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7157 select_all_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7158 find_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7159 ) -> Self {
7160 let mut bar = Self::new();
7161
7162 bar.add_item(MenuItem::Submenu {
7164 label: "File".to_string(),
7165 items: vec![
7166 MenuItem::Action {
7167 label: "New".to_string(),
7168 shortcut: Some(KeyboardShortcut::cmd("n")),
7169 action: new_fn,
7170 enabled: true,
7171 },
7172 MenuItem::Action {
7173 label: "Open…".to_string(),
7174 shortcut: Some(KeyboardShortcut::cmd("o")),
7175 action: open_fn,
7176 enabled: true,
7177 },
7178 MenuItem::Separator,
7179 MenuItem::Action {
7180 label: "Save".to_string(),
7181 shortcut: Some(KeyboardShortcut::cmd("s")),
7182 action: save_fn,
7183 enabled: true,
7184 },
7185 MenuItem::Separator,
7186 MenuItem::Action {
7187 label: "Close".to_string(),
7188 shortcut: Some(KeyboardShortcut::cmd("w")),
7189 action: close_fn,
7190 enabled: true,
7191 },
7192 MenuItem::Separator,
7193 MenuItem::Action {
7194 label: "Quit".to_string(),
7195 shortcut: Some(KeyboardShortcut::cmd("q")),
7196 action: quit_fn,
7197 enabled: true,
7198 },
7199 ],
7200 });
7201
7202 bar.add_item(MenuItem::Submenu {
7204 label: "Edit".to_string(),
7205 items: vec![
7206 MenuItem::Action {
7207 label: "Undo".to_string(),
7208 shortcut: Some(KeyboardShortcut::cmd("z")),
7209 action: undo_fn,
7210 enabled: true,
7211 },
7212 MenuItem::Action {
7213 label: "Redo".to_string(),
7214 shortcut: Some(KeyboardShortcut::cmd_shift("z")),
7215 action: redo_fn,
7216 enabled: true,
7217 },
7218 MenuItem::Separator,
7219 MenuItem::Action {
7220 label: "Cut".to_string(),
7221 shortcut: Some(KeyboardShortcut::cmd("x")),
7222 action: cut_fn,
7223 enabled: true,
7224 },
7225 MenuItem::Action {
7226 label: "Copy".to_string(),
7227 shortcut: Some(KeyboardShortcut::cmd("c")),
7228 action: copy_fn,
7229 enabled: true,
7230 },
7231 MenuItem::Action {
7232 label: "Paste".to_string(),
7233 shortcut: Some(KeyboardShortcut::cmd("v")),
7234 action: paste_fn,
7235 enabled: true,
7236 },
7237 MenuItem::Separator,
7238 MenuItem::Action {
7239 label: "Select All".to_string(),
7240 shortcut: Some(KeyboardShortcut::cmd("a")),
7241 action: select_all_fn,
7242 enabled: true,
7243 },
7244 MenuItem::Separator,
7245 MenuItem::Action {
7246 label: "Find…".to_string(),
7247 shortcut: Some(KeyboardShortcut::cmd("f")),
7248 action: find_fn,
7249 enabled: true,
7250 },
7251 ],
7252 });
7253
7254 let noop: std::sync::Arc<dyn Fn() + Send + Sync> = std::sync::Arc::new(|| {});
7258 bar.add_item(MenuItem::Submenu {
7259 label: "View".to_string(),
7260 items: vec![
7261 MenuItem::Action {
7262 label: "Zoom In".to_string(),
7263 shortcut: Some(KeyboardShortcut::cmd("=")),
7264 action: noop.clone(),
7265 enabled: true,
7266 },
7267 MenuItem::Action {
7268 label: "Zoom Out".to_string(),
7269 shortcut: Some(KeyboardShortcut::cmd("-")),
7270 action: noop.clone(),
7271 enabled: true,
7272 },
7273 MenuItem::Separator,
7274 MenuItem::Action {
7275 label: "Toggle Fullscreen".to_string(),
7276 shortcut: Some(KeyboardShortcut {
7277 key: "f".to_string(),
7278 modifiers: Modifiers {
7279 ctrl: true,
7280 ..Default::default()
7281 },
7282 }),
7283 action: noop.clone(),
7284 enabled: true,
7285 },
7286 ],
7287 });
7288
7289 bar.add_item(MenuItem::Submenu {
7291 label: "Window".to_string(),
7292 items: vec![
7293 MenuItem::Action {
7294 label: "Minimize".to_string(),
7295 shortcut: Some(KeyboardShortcut::cmd("m")),
7296 action: noop.clone(),
7297 enabled: true,
7298 },
7299 MenuItem::Action {
7300 label: "Zoom".to_string(),
7301 shortcut: None,
7302 action: noop.clone(),
7303 enabled: true,
7304 },
7305 MenuItem::Separator,
7306 MenuItem::Action {
7307 label: "Bring All to Front".to_string(),
7308 shortcut: None,
7309 action: noop.clone(),
7310 enabled: true,
7311 },
7312 ],
7313 });
7314
7315 bar.add_item(MenuItem::Submenu {
7317 label: "Help".to_string(),
7318 items: vec![MenuItem::Action {
7319 label: "Search Help".to_string(),
7320 shortcut: None,
7321 action: noop,
7322 enabled: true,
7323 }],
7324 });
7325
7326 bar
7327 }
7328}
7329
7330impl Default for MenuBar {
7331 fn default() -> Self {
7332 Self::new()
7333 }
7334}
7335
7336use std::sync::RwLock;
7342
7343#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7345pub enum Direction {
7346 #[default]
7347 LTR,
7348 RTL,
7349 Auto,
7350}
7351
7352impl Direction {
7353 pub fn is_rtl(self) -> bool {
7354 matches!(self, Direction::RTL)
7355 }
7356}
7357#[derive(Clone, Debug)]
7358pub struct L10nBundle {
7359 pub locale: String,
7360 pub strings: HashMap<String, String>,
7361 pub is_rtl: bool,
7362}
7363
7364impl L10nBundle {
7365 pub fn new(locale: impl Into<String>) -> Self {
7366 Self {
7367 locale: locale.into(),
7368 strings: HashMap::new(),
7369 is_rtl: false,
7370 }
7371 }
7372
7373 pub fn add(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
7374 self.strings.insert(key.into(), value.into());
7375 self
7376 }
7377
7378 pub fn from_strings_format(locale: impl Into<String>, input: &str) -> Self {
7379 let mut bundle = Self::new(locale);
7380 for line in input.lines() {
7381 let line = line.trim();
7382 if line.is_empty() || line.starts_with("//") {
7383 continue;
7384 }
7385 if let Some(eq_pos) = line.find(" = ") {
7386 let key = line[..eq_pos].trim_matches('"').to_string();
7387 let val = line[eq_pos + 3..]
7388 .trim_end_matches(';')
7389 .trim_matches('"')
7390 .to_string();
7391 bundle.strings.insert(key, val);
7392 }
7393 }
7394 bundle
7395 }
7396 pub fn t(&self, key: &str) -> String {
7398 self.strings
7399 .get(key)
7400 .map(|s| s.to_string())
7401 .unwrap_or_else(|| key.to_string())
7402 }
7403
7404 pub fn tf(&self, key: &str, args: &[&str]) -> String {
7406 let mut result = self.t(key);
7407 for (i, arg) in args.iter().enumerate() {
7408 result = result.replace(&format!("{{{}}}", i), arg);
7409 }
7410 result
7411 }
7412}
7413
7414pub struct L10n {
7416 bundles: HashMap<String, L10nBundle>,
7417 current: String,
7418}
7419
7420impl L10n {
7421 pub fn new(default_locale: &str) -> Self {
7422 Self {
7423 bundles: HashMap::new(),
7424 current: default_locale.to_string(),
7425 }
7426 }
7427
7428 pub fn add_bundle(&mut self, bundle: L10nBundle) {
7429 self.bundles.insert(bundle.locale.clone(), bundle);
7430 }
7431
7432 pub fn set_locale(&mut self, locale: &str) {
7433 self.current = locale.to_string();
7434 }
7435 pub fn current_locale(&self) -> &str {
7436 &self.current
7437 }
7438
7439 pub fn is_rtl(&self) -> bool {
7440 self.bundles
7441 .get(self.current.as_str())
7442 .map(|b| b.is_rtl)
7443 .unwrap_or(false)
7444 }
7445
7446 pub fn t(&self, key: &str) -> String {
7447 self.bundles
7448 .get(self.current.as_str())
7449 .map(|b| b.t(key))
7450 .unwrap_or_else(|| key.to_string())
7451 }
7452
7453 pub fn tf(&self, key: &str, args: &[&str]) -> String {
7454 let mut result = self.t(key);
7455 for (i, arg) in args.iter().enumerate() {
7456 result = result.replace(&format!("{{{}}}", i), arg);
7457 }
7458 result
7459 }
7460
7461 pub fn direction(&self) -> Direction {
7462 if self.is_rtl() {
7463 Direction::RTL
7464 } else {
7465 Direction::LTR
7466 }
7467 }
7468}
7469
7470static L10N: once_cell::sync::Lazy<Arc<RwLock<L10n>>> =
7471 once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(L10n::new("en"))));
7472
7473pub fn init_l10n(l10n: L10n) {
7474 if let Ok(mut guard) = L10N.write() {
7475 *guard = l10n;
7476 }
7477}
7478
7479pub fn l10n() -> Arc<RwLock<L10n>> {
7480 L10N.clone()
7481}
7482
7483pub fn t(key: &str) -> String {
7484 L10N.read()
7485 .map(|g| g.t(key).to_string())
7486 .unwrap_or_else(|_| key.to_string())
7487}
7488
7489pub fn tf(key: &str, args: &[&str]) -> String {
7490 L10N.read()
7491 .map(|g| g.tf(key, args))
7492 .unwrap_or_else(|_| key.to_string())
7493}
7494
7495pub fn set_locale(locale: &str) {
7496 if let Ok(mut guard) = L10N.write() {
7497 guard.set_locale(locale);
7498 }
7499}
7500
7501pub fn current_locale() -> String {
7502 L10N.read()
7503 .map(|g| g.current_locale().to_string())
7504 .unwrap_or_else(|_| "en".to_string())
7505}
7506
7507pub fn is_rtl() -> bool {
7508 L10N.read().map(|g| g.is_rtl()).unwrap_or(false)
7509}
7510
7511#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7523pub enum SystemTheme {
7524 #[default]
7526 Dark,
7527 Light,
7529}
7530
7531pub fn detect_system_theme() -> SystemTheme {
7541 std::env::var("CVKG_THEME")
7542 .ok()
7543 .and_then(|v| match v.as_str() {
7544 "light" => Some(SystemTheme::Light),
7545 "dark" => Some(SystemTheme::Dark),
7546 _ => None,
7547 })
7548 .unwrap_or(SystemTheme::Dark)
7549}
7550
7551pub mod audio_haptic;
7557pub use audio_haptic::{
7558 AudioEngine, HapticEngine, HapticIntensity, NullAudioEngine, NullHapticEngine, haptic_error,
7559 haptic_impact, haptic_selection, haptic_success, play_sound, set_audio_engine,
7560 set_haptic_engine, sounds,
7561};
7562
7563pub mod parallax;
7568pub use parallax::{DisplayEnvironment, ParallaxModifier, PerformanceContract, Tier3Fallback};