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 fn measure_text_baseline(&mut self, _text: &str, _size: f32) -> f32 {
2778 0.0
2779 }
2780
2781 fn shape_rich_text(
2782 &mut self,
2783 _spans: &[cvkg_runic_text::TextSpan],
2784 _max_width: Option<f32>,
2785 _align: cvkg_runic_text::TextAlign,
2786 _overflow: cvkg_runic_text::TextOverflow,
2787 ) -> Option<cvkg_runic_text::ShapedText> {
2788 None
2789 }
2790
2791 fn draw_shaped_text(&mut self, _text: &cvkg_runic_text::ShapedText, _x: f32, _y: f32) {}
2792
2793 fn draw_texture(&mut self, _texture_id: u32, _rect: Rect) {}
2796 fn draw_image(&mut self, _image_name: &str, _rect: Rect) {}
2798 fn load_image(&mut self, _name: &str, _data: &[u8]) {}
2800 fn prewarm_vram(&mut self, _assets: Vec<(String, Vec<u8>)>) {}
2803
2804 fn get_pointer_position(&self) -> [f32; 2] {
2806 [0.0, 0.0]
2807 }
2808
2809 fn upload_data_texture(&mut self, _id: &str, _data: &[f32], _width: u32, _height: u32) {}
2812 fn draw_heatmap(&mut self, _texture_id: &str, _rect: Rect, _palette: &str) {}
2814
2815 fn draw_mesh(&mut self, _mesh: &Mesh, _color: [f32; 4], _transform: glam::Mat4) {}
2818
2819 fn draw_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {}
2821
2822 fn set_camera_3d(&mut self, _camera: &Camera3D) {}
2825
2826 fn push_transform_3d(&mut self, _transform: &Transform3D) {}
2829
2830 fn pop_transform_3d(&mut self) {}
2832
2833 fn render_scene_node_3d(
2842 &mut self,
2843 _position: [f32; 3],
2844 _rotation: [f32; 4],
2845 _scale: [f32; 3],
2846 _color: [f32; 4],
2847 _meshes: &[Mesh],
2848 ) {
2849 }
2851
2852 fn draw_linear_gradient(
2854 &mut self,
2855 _rect: Rect,
2856 _start_color: [f32; 4],
2857 _end_color: [f32; 4],
2858 _angle: f32,
2859 ) {
2860 }
2861 fn draw_radial_gradient(
2863 &mut self,
2864 _rect: Rect,
2865 _inner_color: [f32; 4],
2866 _outer_color: [f32; 4],
2867 ) {
2868 }
2869 fn draw_drop_shadow(
2871 &mut self,
2872 _rect: Rect,
2873 _radius: f32,
2874 _color: [f32; 4],
2875 _blur: f32,
2876 _spread: f32,
2877 ) {
2878 }
2879 fn stroke_dashed_rounded_rect(
2881 &mut self,
2882 _rect: Rect,
2883 _radius: f32,
2884 _color: [f32; 4],
2885 _width: f32,
2886 _dash: f32,
2887 _gap: f32,
2888 ) {
2889 }
2890 fn draw_9slice(
2892 &mut self,
2893 _image_name: &str,
2894 _rect: Rect,
2895 _left: f32,
2896 _top: f32,
2897 _right: f32,
2898 _bottom: f32,
2899 ) {
2900 }
2901
2902 fn push_clip_rect(&mut self, _rect: Rect) {}
2906 fn pop_clip_rect(&mut self) {}
2908 fn current_clip_rect(&self) -> Rect {
2911 Rect::new(-10000.0, -10000.0, 20000.0, 20000.0)
2912 }
2913
2914 fn push_opacity(&mut self, _opacity: f32) {}
2918 fn pop_opacity(&mut self) {}
2920
2921 fn set_theme(&mut self, _theme: ColorTheme) {}
2923 fn set_rage(&mut self, _rage: f32) {}
2924 fn set_berserker_mode(&mut self, _state: BerserkerMode) {}
2925 fn trigger_shatter_event(&mut self, _origin: [f32; 2], _force: f32) {}
2926 fn set_scene(&mut self, _scene: &str) {}
2928
2929 fn capture_png(&mut self) -> Vec<u8> {
2932 Vec::new()
2933 }
2934 fn print(&mut self) {}
2936
2937 fn set_scene_preset(&mut self, _preset: u32) {}
2938
2939 fn bifrost(&mut self, _rect: Rect, _blur: f32, _saturation: f32, _opacity: f32) {}
2942 fn gungnir(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32, _intensity: f32) {}
2944 fn mani_glow(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32) {}
2946 fn push_mjolnir_slice(&mut self, _angle: f32, _offset: f32) {}
2948 fn pop_mjolnir_slice(&mut self) {}
2949 fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer));
2953 fn mjolnir_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2955 fn mjolnir_fluid_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2956 fn draw_mjolnir_bolt(&mut self, _from: [f32; 2], _to: [f32; 2], _color: [f32; 4]) {}
2957
2958 fn dispatch_particles(
2961 &mut self,
2962 _origin: [f32; 2],
2963 _count: u32,
2964 _effect_type: &str,
2965 _color: [f32; 4],
2966 ) {
2967 }
2968
2969 fn draw_hologram(&mut self, _rect: Rect, _hologram_id: &str, _time: f32) {}
2971
2972 fn set_aria_role(&mut self, _role: &str) {}
2974 fn set_aria_label(&mut self, _label: &str) {}
2975
2976 fn register_shared_element(&mut self, _id: &str, _rect: Rect) {}
2978
2979 fn set_key(&mut self, _key: &str) {}
2981
2982 fn get_telemetry(&self) -> TelemetryData {
2985 TelemetryData::default()
2986 }
2987
2988 fn push_shadow(&mut self, _radius: f32, _color: [f32; 4], _offset: [f32; 2]) {}
2991 fn pop_shadow(&mut self) {}
2993
2994 fn push_vnode(&mut self, _rect: Rect, _name: &'static str) {}
2997 fn pop_vnode(&mut self) {}
2999 fn register_handler(
3001 &mut self,
3002 _event_type: &str,
3003 _handler: std::sync::Arc<dyn Fn(Event) + Send + Sync>,
3004 ) {
3005 }
3006
3007 fn set_z_index(&mut self, _z: f32) {}
3011 fn get_z_index(&self) -> f32 {
3013 0.0
3014 }
3015
3016 fn load_svg(&mut self, _name: &str, _svg_data: &[u8]) {}
3019 fn draw_svg(&mut self, _name: &str, _rect: Rect) {}
3021 fn draw_svg_with_offset(&mut self, name: &str, rect: Rect, _animation_time_offset: f32) {
3025 self.draw_svg(name, rect);
3026 }
3027 fn serialize_svg(&mut self, _name: &str) -> Result<String, String> {
3031 Err("SVG serialization not supported by this renderer".into())
3032 }
3033 fn apply_svg_filter(
3037 &mut self,
3038 _name: &str,
3039 _filter_id: &str,
3040 _region: Rect,
3041 ) -> Result<String, String> {
3042 Err("SVG filter not supported by this renderer".into())
3043 }
3044
3045 fn push_transform(&mut self, _translation: [f32; 2], _scale: [f32; 2], _rotation: f32) {}
3050 fn push_affine(&mut self, _transform: [f32; 6]) {}
3053 fn pop_transform(&mut self) {}
3055 fn query_layout(&self, _node_id: scene_graph::NodeId) -> Option<Rect> {
3057 None
3058 }
3059 fn set_debug_layout(&mut self, _enabled: bool) {}
3061 fn get_debug_layout(&self) -> bool {
3063 false
3064 }
3065
3066 fn set_material(&mut self, _material: crate::material::DrawMaterial) {}
3070 fn current_material(&self) -> crate::material::DrawMaterial {
3072 crate::material::DrawMaterial::Opaque
3073 }
3074
3075 fn mimir_intent(&self) -> [f32; 2] {
3078 [0.0, 0.0]
3079 }
3080 fn magnetic_warp(&self, pointer: [f32; 2], anchor_rect: Rect, strength: f32) -> [f32; 2] {
3082 if strength <= 0.0 {
3083 return pointer;
3084 }
3085 let cx = anchor_rect.x + anchor_rect.width / 2.0;
3086 let cy = anchor_rect.y + anchor_rect.height / 2.0;
3087 let dx = pointer[0] - cx;
3088 let dy = pointer[1] - cy;
3089 let dist = (dx * dx + dy * dy).sqrt();
3090 let radius = 120.0;
3091 if dist < radius && dist > 0.0 {
3092 let force = (1.0 - dist / radius) * strength;
3093 [pointer[0] - dx * force, pointer[1] - dy * force]
3094 } else {
3095 pointer
3096 }
3097 }
3098 fn mani_glow_intensity(&self, pointer: [f32; 2], bounds: Rect, radius: f32) -> f32 {
3100 let cx = bounds.x + bounds.width / 2.0;
3101 let cy = bounds.y + bounds.height / 2.0;
3102 let dist = ((pointer[0] - cx).powi(2) + (pointer[1] - cy).powi(2)).sqrt();
3103 if dist < radius {
3104 (1.0 - dist / radius).clamp(0.0, 1.0)
3105 } else {
3106 0.0
3107 }
3108 }
3109 fn fafnir_evolve(&self, pointer: [f32; 2], bounds: Rect, max_scale: f32) -> f32 {
3111 let prox = self.mani_glow_intensity(pointer, bounds, 120.0);
3112 1.0 + (max_scale - 1.0) * prox
3113 }
3114 fn set_sdf_shape(&mut self, _shape: crate::layout::SdfShape) {}
3116
3117 fn enter_portal(&mut self, _z_index: i32) {}
3127
3128 fn exit_portal(&mut self) {}
3132
3133 fn viewport_size(&self) -> Rect {
3136 Rect::new(0.0, 0.0, 1920.0, 1080.0)
3137 }
3138
3139 fn announce(&mut self, _message: &str, _priority: AnnouncementPriority) {}
3145}
3146
3147pub mod accessibility {
3149 pub fn relative_luminance(color: [f32; 4]) -> f32 {
3151 let f = |c: f32| {
3152 if c <= 0.03928 {
3153 c / 12.92
3154 } else {
3155 ((c + 0.055) / 1.055).powf(2.4)
3156 }
3157 };
3158 0.2126 * f(color[0]) + 0.7152 * f(color[1]) + 0.0722 * f(color[2])
3159 }
3160
3161 pub fn contrast_ratio(c1: [f32; 4], c2: [f32; 4]) -> f32 {
3163 let l1 = relative_luminance(c1);
3164 let l2 = relative_luminance(c2);
3165 let (light, dark) = if l1 > l2 { (l1, l2) } else { (l2, l1) };
3166 (light + 0.05) / (dark + 0.05)
3167 }
3168}
3169#[derive(
3171 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
3172)]
3173pub enum RenderTier {
3174 Tier1GPU = 0,
3176 Tier2GPU = 1,
3178 Tier3Fallback = 2,
3180}
3181use bytemuck::{Pod, Zeroable};
3185#[repr(C)]
3187#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3188pub struct ColorTheme {
3189 pub primary_neon: [f32; 4], pub shatter_neon: [f32; 4],
3191 pub glass_base: [f32; 4],
3192 pub glass_edge: [f32; 4],
3193 pub rune_glow: [f32; 4],
3194 pub ember_core: [f32; 4],
3195 pub background_deep: [f32; 4],
3196 pub mani_glow: [f32; 4], pub glass_blur_strength: f32,
3198 pub shatter_edge_width: f32,
3199 pub neon_bloom_radius: f32,
3200 pub rune_opacity: f32,
3201 pub glass_tint_adapt: f32,
3204 pub glass_ior: f32,
3206 pub _pad0: f32,
3208 pub _pad1: f32,
3209}
3210impl ColorTheme {
3211 pub fn asgard() -> Self {
3213 Self {
3214 primary_neon: [0.0, 1.0, 0.95, 1.2],
3215 shatter_neon: [1.0, 0.0, 0.75, 1.5],
3216 glass_base: [0.04, 0.04, 0.06, 0.82],
3217 glass_edge: [0.0, 0.45, 0.55, 0.6],
3218 rune_glow: [0.75, 0.98, 1.0, 0.9],
3219 ember_core: [0.95, 0.12, 0.12, 1.0],
3220 background_deep: [0.01, 0.01, 0.03, 1.0],
3221 mani_glow: [0.7, 0.9, 1.0, 0.05],
3222 glass_blur_strength: 0.6,
3223 shatter_edge_width: 1.8,
3224 neon_bloom_radius: 0.022,
3225 rune_opacity: 0.55,
3226 glass_tint_adapt: 0.35,
3227 glass_ior: 1.45,
3228 _pad0: 0.0,
3229 _pad1: 0.0,
3230 }
3231 }
3232
3233 pub fn midgard() -> Self {
3235 Self {
3236 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],
3242 background_deep: [0.05, 0.05, 0.07, 1.0],
3243 mani_glow: [0.0, 0.0, 0.0, 0.0], glass_blur_strength: 0.0, shatter_edge_width: 1.0,
3246 neon_bloom_radius: 0.0,
3247 rune_opacity: 0.0,
3248 glass_tint_adapt: 0.0,
3249 glass_ior: 1.0,
3250 _pad0: 0.0,
3251 _pad1: 0.0,
3252 }
3253 }
3254
3255 pub fn cyberpunk_viking() -> Self {
3256 Self::asgard()
3257 }
3258 pub fn vibrant_glass() -> Self {
3259 Self {
3260 primary_neon: [0.0, 1.0, 0.95, 1.2],
3261 shatter_neon: [1.0, 0.0, 0.75, 1.5],
3262 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],
3265 ember_core: [1.0, 0.4, 0.1, 1.0],
3266 background_deep: [0.05, 0.05, 0.1, 1.0],
3267 mani_glow: [0.7, 0.9, 1.0, 0.05],
3268 glass_blur_strength: 0.9,
3269 shatter_edge_width: 1.8,
3270 neon_bloom_radius: 0.022,
3271 rune_opacity: 0.55,
3272 glass_tint_adapt: 0.65,
3273 glass_ior: 1.45,
3274 _pad0: 0.0,
3275 _pad1: 0.0,
3276 }
3277 }
3278
3279 pub fn berserker() -> Self {
3281 Self {
3282 primary_neon: [1.0, 0.08, 0.12, 1.8],
3283 shatter_neon: [0.95, 0.92, 0.88, 1.6],
3284 glass_base: [0.03, 0.02, 0.02, 0.88],
3285 glass_edge: [0.8, 0.35, 0.08, 0.7],
3286 rune_glow: [0.9, 0.72, 0.3, 1.0],
3287 ember_core: [0.98, 0.25, 0.05, 1.0],
3288 background_deep: [0.01, 0.005, 0.005, 1.0],
3289 mani_glow: [0.8, 0.2, 0.05, 0.08],
3290 glass_blur_strength: 0.85,
3291 shatter_edge_width: 2.8,
3292 neon_bloom_radius: 0.035,
3293 rune_opacity: 0.85,
3294 glass_tint_adapt: 0.15,
3295 glass_ior: 1.85,
3296 _pad0: 0.0,
3297 _pad1: 0.0,
3298 }
3299 }
3300}
3301impl Default for ColorTheme {
3302 fn default() -> Self {
3303 Self::vibrant_glass()
3304 }
3305}
3306#[repr(C)]
3308#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
3309pub struct SceneUniforms {
3310 pub view: glam::Mat4,
3311 pub proj: glam::Mat4,
3312 pub time: f32,
3313 pub delta_time: f32,
3314 pub resolution: [f32; 2],
3315 pub mouse: [f32; 2],
3316 pub mouse_velocity: [f32; 2],
3317 pub shatter_origin: [f32; 2],
3318 pub shatter_time: f32,
3319 pub shatter_force: f32,
3320 pub berzerker_rage: f32,
3321 pub berzerker_mode: u32,
3322 pub scroll_offset: f32,
3323 pub scale_factor: f32,
3324 pub scene_type: u32,
3325 pub _pad: [f32; 3], }
3327
3328pub const SCENE_AURORA: u32 = 0;
3329pub const SCENE_VOID: u32 = 1;
3330pub const SCENE_NEBULA: u32 = 2;
3331pub const SCENE_GLITCH: u32 = 3;
3332pub const SCENE_YGGDRASIL: u32 = 4;
3333
3334impl SceneUniforms {
3335 pub fn new(width: f32, height: f32) -> Self {
3336 Self {
3337 view: glam::Mat4::IDENTITY,
3338 proj: glam::Mat4::orthographic_lh(0.0, width, height, 0.0, -100.0, 100.0),
3339 time: 0.0,
3340 delta_time: 0.016,
3341 resolution: [width, height],
3342 mouse: [0.5, 0.5],
3343 mouse_velocity: [0.0, 0.0],
3344 shatter_origin: [0.5, 0.5],
3345 shatter_time: -100.0,
3346 shatter_force: 0.0,
3347 berzerker_rage: 0.0,
3348 berzerker_mode: 0,
3349 scroll_offset: 0.0,
3350 scale_factor: 1.0,
3351 scene_type: SCENE_AURORA,
3352 _pad: [0.0; 3],
3353 }
3354 }
3355}
3356#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
3358pub struct Mesh {
3359 pub vertices: Vec<[f32; 3]>,
3360 pub normals: Vec<[f32; 3]>,
3361 pub indices: Vec<u32>,
3362}
3363impl Mesh {
3364 pub fn from_obj(data: &[u8]) -> anyhow::Result<Vec<Self>> {
3365 let mut cursor = std::io::Cursor::new(data);
3366 let (models, _) = tobj::load_obj_buf(&mut cursor, &tobj::LoadOptions::default(), |_| {
3367 Ok((Vec::new(), Default::default()))
3368 })?;
3369 let mut meshes = Vec::new();
3370 for m in models {
3371 let mesh = m.mesh;
3372 let vertices: Vec<[f32; 3]> = mesh
3373 .positions
3374 .chunks(3)
3375 .map(|c| [c[0], c[1], c[2]])
3376 .collect();
3377 let normals = if mesh.normals.is_empty() {
3378 vec![[0.0, 0.0, 1.0]; vertices.len()]
3379 } else {
3380 mesh.normals.chunks(3).map(|c| [c[0], c[1], c[2]]).collect()
3381 };
3382 meshes.push(Mesh {
3383 vertices,
3384 normals,
3385 indices: mesh.indices,
3386 });
3387 }
3388 Ok(meshes)
3389 }
3390 pub fn from_stl(data: &[u8]) -> anyhow::Result<Self> {
3391 let mut cursor = std::io::Cursor::new(data);
3392 let stl = stl_io::read_stl(&mut cursor)?;
3393 let vertices: Vec<[f32; 3]> = stl.vertices.iter().map(|v| [v[0], v[1], v[2]]).collect();
3394 let mut indices = Vec::new();
3395 for face in stl.faces {
3396 indices.push(face.vertices[0] as u32);
3397 indices.push(face.vertices[1] as u32);
3398 indices.push(face.vertices[2] as u32);
3399 }
3400 let normals = vec![[0.0, 0.0, 1.0]; vertices.len()];
3401 Ok(Mesh {
3402 vertices,
3403 normals,
3404 indices,
3405 })
3406 }
3407}
3408
3409#[derive(Debug, Clone, Copy, PartialEq)]
3415pub struct Transform3D {
3416 pub position: glam::Vec3,
3417 pub rotation: glam::Quat,
3418 pub scale: glam::Vec3,
3419}
3420
3421impl Default for Transform3D {
3422 fn default() -> Self {
3423 Self {
3424 position: glam::Vec3::ZERO,
3425 rotation: glam::Quat::IDENTITY,
3426 scale: glam::Vec3::ONE,
3427 }
3428 }
3429}
3430
3431impl Transform3D {
3432 pub fn to_matrix(&self) -> glam::Mat4 {
3434 glam::Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.position)
3435 }
3436
3437 pub fn from_2d(x: f32, y: f32, rotation: f32) -> Self {
3439 Self {
3440 position: glam::Vec3::new(x, y, 0.0),
3441 rotation: glam::Quat::from_rotation_z(rotation),
3442 scale: glam::Vec3::ONE,
3443 }
3444 }
3445}
3446
3447#[derive(Debug, Clone, Copy)]
3449pub struct Camera3D {
3450 pub position: glam::Vec3,
3452 pub target: glam::Vec3,
3454 pub up: glam::Vec3,
3456 pub fov_y: f32,
3458 pub near: f32,
3460 pub far: f32,
3462 pub perspective: bool,
3464 pub aspect: f32,
3466}
3467
3468#[derive(Debug, Clone, Copy, PartialEq)]
3470pub struct Material3D {
3471 pub base_color: [f32; 4],
3473 pub metallic: f32,
3475 pub roughness: f32,
3477 pub emissive: [f32; 3],
3479 pub opacity: f32,
3481}
3482
3483impl Default for Material3D {
3484 fn default() -> Self {
3485 Self {
3486 base_color: [1.0, 1.0, 1.0, 1.0],
3487 metallic: 0.0,
3488 roughness: 0.5,
3489 emissive: [0.0, 0.0, 0.0],
3490 opacity: 1.0,
3491 }
3492 }
3493}
3494
3495impl Material3D {
3496 pub fn unlit(color: [f32; 4]) -> Self {
3498 Self {
3499 base_color: color,
3500 metallic: 0.0,
3501 roughness: 1.0,
3502 emissive: [0.0, 0.0, 0.0],
3503 opacity: color[3],
3504 }
3505 }
3506
3507 pub fn metallic(color: [f32; 4], roughness: f32) -> Self {
3509 Self {
3510 base_color: color,
3511 metallic: 1.0,
3512 roughness: roughness.clamp(0.0, 1.0),
3513 emissive: [0.0, 0.0, 0.0],
3514 opacity: color[3],
3515 }
3516 }
3517}
3518
3519impl Default for Camera3D {
3520 fn default() -> Self {
3521 Self {
3522 position: glam::Vec3::new(0.0, 0.0, 10.0),
3523 target: glam::Vec3::ZERO,
3524 up: glam::Vec3::Y,
3525 fov_y: 45.0f32.to_radians(),
3526 near: 0.1,
3527 far: 1000.0,
3528 perspective: true,
3529 aspect: 16.0 / 9.0,
3530 }
3531 }
3532}
3533
3534impl Camera3D {
3535 pub fn view_matrix(&self) -> glam::Mat4 {
3537 glam::Mat4::look_at_lh(self.position, self.target, self.up)
3538 }
3539
3540 pub fn projection_matrix(&self) -> glam::Mat4 {
3542 if self.perspective {
3543 glam::Mat4::perspective_lh(self.fov_y, self.aspect, self.near, self.far)
3544 } else {
3545 let top = self.fov_y;
3547 let right = top * self.aspect;
3548 glam::Mat4::orthographic_lh(-right, right, -top, top, self.near, self.far)
3549 }
3550 }
3551
3552 pub fn view_projection(&self) -> glam::Mat4 {
3554 self.projection_matrix() * self.view_matrix()
3555 }
3556}
3557
3558pub trait FrameRenderer<E = ()>: Renderer {
3561 fn begin_frame(&mut self) -> E;
3562 fn render_frame(&mut self) {
3563 }
3565 fn end_frame(&mut self, encoder: E);
3566}
3567use std::sync::Arc;
3568type SubscriberList<T> = Arc<std::sync::Mutex<Vec<Box<dyn Fn(&T) + Send + Sync>>>>;
3569#[derive(Clone)]
3571pub struct State<T: Clone + Send + Sync + 'static> {
3572 swap: Arc<arc_swap::ArcSwap<T>>,
3573 metadata_swap: Arc<arc_swap::ArcSwap<Option<agents::MutationMetadata>>>,
3574 #[cfg(not(target_arch = "wasm32"))]
3575 tvar: Arc<stm::TVar<T>>,
3576 #[cfg(not(target_arch = "wasm32"))]
3577 metadata_tvar: Arc<stm::TVar<Option<agents::MutationMetadata>>>,
3578 subscribers: SubscriberList<T>,
3579 version: Arc<std::sync::atomic::AtomicU64>,
3580 resolution: agents::ConflictResolution,
3581}
3582impl<T: Clone + Send + Sync + 'static> State<T> {
3583 pub fn new(value: T) -> Self {
3585 #[cfg(not(target_arch = "wasm32"))]
3586 let tvar = Arc::new(stm::TVar::new(value.clone()));
3587 #[cfg(not(target_arch = "wasm32"))]
3588 let metadata_tvar = Arc::new(stm::TVar::new(None));
3589 Self {
3590 swap: Arc::new(arc_swap::ArcSwap::from_pointee(value)),
3591 metadata_swap: Arc::new(arc_swap::ArcSwap::new(Arc::new(None))),
3592 #[cfg(not(target_arch = "wasm32"))]
3593 tvar,
3594 #[cfg(not(target_arch = "wasm32"))]
3595 metadata_tvar,
3596 subscribers: Arc::new(std::sync::Mutex::new(Vec::new())),
3597 version: Arc::new(std::sync::atomic::AtomicU64::new(0)),
3598 resolution: agents::ConflictResolution::default(),
3599 }
3600 }
3601 pub fn with_resolution(mut self, resolution: agents::ConflictResolution) -> Self {
3603 self.resolution = resolution;
3604 self
3605 }
3606 pub fn get(&self) -> T {
3608 (**self.swap.load()).clone()
3609 }
3610 pub fn set(&self, value: T) {
3612 #[cfg(not(target_arch = "wasm32"))]
3613 let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3614 let new_meta = agents::get_current_mutation_metadata();
3615 let existing_meta = self.metadata_tvar.read(tx)?;
3616 let mut skip = false;
3617 if self.resolution == agents::ConflictResolution::PriorityWins
3618 && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3619 && new_m.priority < old_m.priority
3620 {
3621 skip = true;
3622 }
3623 if !skip {
3624 self.tvar.write(tx, value.clone())?;
3625 self.metadata_tvar.write(tx, new_meta)?;
3626 Ok((false, value.clone(), new_meta))
3627 } else {
3628 Ok((true, self.tvar.read(tx)?, existing_meta))
3629 }
3630 });
3631 #[cfg(target_arch = "wasm32")]
3632 let (was_skipped, final_val, final_meta) =
3633 (false, value, agents::get_current_mutation_metadata());
3634 if was_skipped {
3635 if let (Some(new_m), Some(old_m)) =
3636 (agents::get_current_mutation_metadata(), final_meta)
3637 {
3638 agents::notify_conflict(agents::ConflictEvent {
3639 agent_id: new_m.agent_id,
3640 priority: new_m.priority,
3641 existing_agent_id: old_m.agent_id,
3642 existing_priority: old_m.priority,
3643 timestamp_ms: new_m.timestamp_ms,
3644 });
3645 }
3646 return;
3647 }
3648 self.swap.store(Arc::new(final_val.clone()));
3649 self.metadata_swap.store(Arc::new(final_meta));
3650 self.version
3651 .fetch_add(1, std::sync::atomic::Ordering::Release);
3652 let subs = Arc::clone(&self.subscribers);
3653 if crate::is_batching() {
3654 crate::enqueue_batch_task(Box::new(move || {
3655 let s = subs.lock().unwrap();
3656 for cb in s.iter() {
3657 cb(&final_val);
3658 }
3659 }));
3660 } else {
3661 let s = subs.lock().unwrap();
3662 for cb in s.iter() {
3663 cb(&final_val);
3664 }
3665 }
3666 }
3667 pub fn mutate<F: Fn(&T) -> T>(&self, f: F) {
3668 #[cfg(not(target_arch = "wasm32"))]
3669 {
3670 let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3671 let new_meta = agents::get_current_mutation_metadata();
3672 let existing_meta = self.metadata_tvar.read(tx)?;
3673 let mut skip = false;
3674 if self.resolution == agents::ConflictResolution::PriorityWins
3675 && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3676 && new_m.priority < old_m.priority
3677 {
3678 skip = true;
3679 }
3680 if !skip {
3681 let current = self.tvar.read(tx)?;
3682 let next = f(¤t);
3683 self.tvar.write(tx, next.clone())?;
3684 self.metadata_tvar.write(tx, new_meta)?;
3685 Ok((false, next, new_meta))
3686 } else {
3687 Ok((true, self.tvar.read(tx)?, existing_meta))
3688 }
3689 });
3690 if was_skipped {
3691 if let (Some(new_m), Some(old_m)) =
3692 (agents::get_current_mutation_metadata(), final_meta)
3693 {
3694 agents::notify_conflict(agents::ConflictEvent {
3695 agent_id: new_m.agent_id,
3696 priority: new_m.priority,
3697 existing_agent_id: old_m.agent_id,
3698 existing_priority: old_m.priority,
3699 timestamp_ms: new_m.timestamp_ms,
3700 });
3701 }
3702 return;
3703 }
3704 self.swap.store(Arc::new(final_val.clone()));
3705 self.metadata_swap.store(Arc::new(final_meta));
3706 self.version
3707 .fetch_add(1, std::sync::atomic::Ordering::Release);
3708 let subs = Arc::clone(&self.subscribers);
3709 if crate::is_batching() {
3710 crate::enqueue_batch_task(Box::new(move || {
3711 let s = subs.lock().unwrap();
3712 for cb in s.iter() {
3713 cb(&final_val);
3714 }
3715 }));
3716 } else {
3717 let s = subs.lock().unwrap();
3718 for cb in s.iter() {
3719 cb(&final_val);
3720 }
3721 }
3722 }
3723 #[cfg(target_arch = "wasm32")]
3724 {
3725 self.set(f(&self.get()));
3726 }
3727 }
3728 pub fn version(&self) -> u64 {
3730 self.version.load(std::sync::atomic::Ordering::Acquire)
3731 }
3732 pub fn subscribe<F: Fn(&T) + Send + Sync + 'static>(&self, callback: F) {
3734 self.subscribers.lock().unwrap().push(Box::new(callback));
3735 }
3736}
3737use crate::runtime::NodeStateSnapshot;
3738use std::sync::OnceLock;
3739use std::sync::atomic::{AtomicBool, Ordering};
3740pub static SYSTEM_STATE: OnceLock<Arc<arc_swap::ArcSwap<KnowledgeState>>> = OnceLock::new();
3742#[cfg(not(target_arch = "wasm32"))]
3743static KNOWLEDGE_TVAR: OnceLock<stm::TVar<KnowledgeState>> = OnceLock::new();
3744static IS_BATCHING: AtomicBool = AtomicBool::new(false);
3745pub static IS_RENDERING: AtomicBool = AtomicBool::new(false);
3746pub static LAYOUT_DIRTY: AtomicBool = AtomicBool::new(false);
3747type BatchQueue = OnceLock<std::sync::Mutex<Vec<Box<dyn FnOnce() + Send + Sync>>>>;
3748static BATCH_QUEUE: BatchQueue = OnceLock::new();
3749static STATE_WRITE_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
3752pub fn is_batching() -> bool {
3754 IS_BATCHING.load(Ordering::Acquire)
3755}
3756pub fn is_rendering() -> bool {
3758 IS_RENDERING.load(Ordering::Acquire)
3759}
3760pub fn begin_render_phase() {
3762 IS_RENDERING.store(true, Ordering::Release);
3763}
3764pub fn end_render_phase() {
3766 IS_RENDERING.store(false, Ordering::Release);
3767}
3768pub fn enqueue_batch_task(task: Box<dyn FnOnce() + Send + Sync>) {
3770 let mut queue = BATCH_QUEUE
3771 .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3772 .lock()
3773 .unwrap();
3774 queue.push(task);
3775}
3776pub fn batch<F: FnOnce()>(f: F) {
3780 if IS_BATCHING.swap(true, Ordering::AcqRel) {
3781 f();
3783 return;
3784 }
3785 f();
3786 IS_BATCHING.store(false, Ordering::Release);
3787 let mut queue = BATCH_QUEUE
3788 .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3789 .lock()
3790 .unwrap();
3791 let tasks: Vec<_> = queue.drain(..).collect();
3792 drop(queue);
3793 for task in tasks {
3794 task();
3795 }
3796}
3797pub fn get_system_state() -> Arc<arc_swap::ArcSwap<KnowledgeState>> {
3799 SYSTEM_STATE
3800 .get_or_init(|| Arc::new(arc_swap::ArcSwap::from_pointee(KnowledgeState::default())))
3801 .clone()
3802}
3803pub fn load_system_state() -> arc_swap::Guard<Arc<KnowledgeState>> {
3804 get_system_state().load()
3805}
3806pub fn update_system_state<F>(f: F)
3807where
3808 F: FnOnce(&KnowledgeState) -> KnowledgeState,
3809{
3810 let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3811 if is_rendering() {
3812 log::warn!(
3813 "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3814 );
3815 }
3816 LAYOUT_DIRTY.store(true, Ordering::SeqCst);
3817 let swap = get_system_state();
3818 let current = swap.load();
3819 let new_state = Arc::new(f(¤t));
3820 swap.store(Arc::clone(&new_state));
3821 #[cfg(not(target_arch = "wasm32"))]
3822 {
3823 let tvar = KNOWLEDGE_TVAR.get_or_init(|| stm::TVar::new((*new_state).clone()));
3824 stm::atomically(|tx| tvar.write(tx, (*new_state).clone()));
3825 }
3826}
3827pub fn transact_system_state<F>(f: F)
3828where
3829 F: Fn(&KnowledgeState) -> KnowledgeState,
3830{
3831 let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3832 #[cfg(not(target_arch = "wasm32"))]
3833 {
3834 if is_rendering() {
3835 log::warn!(
3836 "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3837 );
3838 }
3839 let tvar = KNOWLEDGE_TVAR
3840 .get_or_init(|| stm::TVar::new((**get_system_state().load()).clone()))
3841 .clone();
3842 let new_state = stm::atomically(move |tx| {
3843 let current = tvar.read(tx)?;
3844 let next = f(¤t);
3845 tvar.write(tx, next.clone())?;
3846 Ok(next)
3847 });
3848 get_system_state().store(Arc::new(new_state));
3849 }
3850 #[cfg(target_arch = "wasm32")]
3851 {
3852 if is_rendering() {
3853 log::warn!(
3854 "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3855 );
3856 }
3857 update_system_state(f);
3858 }
3859}
3860impl KnowledgeState {
3861 pub fn new() -> Self {
3863 Self::default()
3864 }
3865 pub fn set_component_state<T: 'static + Send + Sync>(&mut self, id: u64, state: T) {
3867 self.component_states
3868 .insert(id, Arc::new(std::sync::RwLock::new(state)));
3869 }
3870 pub fn get_component_state<T: 'static + Send + Sync>(
3872 &self,
3873 id: u64,
3874 ) -> Option<Arc<std::sync::RwLock<T>>> {
3875 let lock = self.component_states.get(&id)?;
3876 let any_ref = lock.read().ok()?;
3880 if any_ref.is::<T>() {
3881 drop(any_ref);
3883 let cloned: Arc<std::sync::RwLock<dyn std::any::Any + Send + Sync>> = Arc::clone(lock);
3884 Some(unsafe {
3887 let raw = Arc::into_raw(cloned);
3888 Arc::from_raw(raw as *const std::sync::RwLock<T>)
3889 })
3890 } else {
3891 None
3892 }
3893 }
3894 pub fn remember(&mut self, fragment: KnowledgeFragment) {
3896 self.fragments.insert(fragment.id.clone(), fragment);
3897 }
3898 pub fn process_query(&mut self, query: &str) {
3900 let query_lower = query.to_lowercase();
3901 let mut results: Vec<(f32, String)> = self
3902 .fragments
3903 .iter()
3904 .map(|(id, frag)| {
3905 let mut score = 0.0;
3906 if frag.summary.to_lowercase().contains(&query_lower) {
3907 score += 1.0;
3908 }
3909 if frag.source.to_lowercase().contains(&query_lower) {
3910 score += 0.5;
3911 }
3912 (score, id.clone())
3913 })
3914 .filter(|(score, _)| *score > 0.0)
3915 .collect();
3916 results.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
3918 self.last_query_results = results.into_iter().map(|(_, id)| id).take(5).collect();
3919 }
3920 pub fn snapshot(&self) -> Vec<NodeStateSnapshot> {
3922 let mut snapshots = Vec::new();
3923 for frag in self.fragments.values() {
3925 if let Ok(val) = serde_json::to_value(frag) {
3926 snapshots.push(NodeStateSnapshot { id: 0, state: val });
3927 }
3928 }
3929 snapshots
3930 }
3931}
3932#[derive(Clone)]
3934pub struct Binding<T: Clone + Send + Sync + 'static> {
3935 swap: Arc<arc_swap::ArcSwap<T>>,
3936 #[cfg(not(target_arch = "wasm32"))]
3937 tvar: Arc<stm::TVar<T>>,
3938 version: Arc<std::sync::atomic::AtomicU64>,
3939}
3940impl<T: Clone + Send + Sync + 'static> Binding<T> {
3941 pub fn from_state(state: &State<T>) -> Self {
3943 Self {
3944 swap: Arc::clone(&state.swap),
3945 #[cfg(not(target_arch = "wasm32"))]
3946 tvar: Arc::clone(&state.tvar),
3947 version: Arc::clone(&state.version),
3948 }
3949 }
3950 pub fn get(&self) -> T {
3952 (**self.swap.load()).clone()
3953 }
3954 pub fn set(&self, value: T) {
3956 self.swap.store(Arc::new(value.clone()));
3957 #[cfg(not(target_arch = "wasm32"))]
3958 {
3959 let tvar = Arc::clone(&self.tvar);
3960 let v = value.clone();
3961 stm::atomically(move |tx| tvar.write(tx, v.clone()));
3962 }
3963 self.version
3964 .fetch_add(1, std::sync::atomic::Ordering::Release);
3965 }
3966 pub fn version(&self) -> u64 {
3968 self.version.load(std::sync::atomic::Ordering::Acquire)
3969 }
3970}
3971#[cfg(not(target_arch = "wasm32"))]
3972pub fn transact_pair<A, B, F>(state_a: &State<A>, state_b: &State<B>, f: F)
3973where
3974 A: Clone + Send + Sync + 'static,
3975 B: Clone + Send + Sync + 'static,
3976 F: Fn(&A, &B) -> (A, B),
3977{
3978 let tvar_a = Arc::clone(&state_a.tvar);
3979 let tvar_b = Arc::clone(&state_b.tvar);
3980 let (new_a, new_b) = stm::atomically(move |tx| {
3981 let a = tvar_a.read(tx)?;
3982 let b = tvar_b.read(tx)?;
3983 let (na, nb) = f(&a, &b);
3984 tvar_a.write(tx, na.clone())?;
3985 tvar_b.write(tx, nb.clone())?;
3986 Ok((na, nb))
3987 });
3988 state_a.swap.store(Arc::new(new_a.clone()));
3989 state_b.swap.store(Arc::new(new_b.clone()));
3990 state_a
3991 .version
3992 .fetch_add(1, std::sync::atomic::Ordering::Release);
3993 state_b
3994 .version
3995 .fetch_add(1, std::sync::atomic::Ordering::Release);
3996 let subs_a = Arc::clone(&state_a.subscribers);
3997 let subs_b = Arc::clone(&state_b.subscribers);
3998 if crate::is_batching() {
3999 crate::enqueue_batch_task(Box::new(move || {
4000 {
4001 let s = subs_a.lock().unwrap();
4002 for cb in s.iter() {
4003 cb(&new_a);
4004 }
4005 }
4006 {
4007 let s = subs_b.lock().unwrap();
4008 for cb in s.iter() {
4009 cb(&new_b);
4010 }
4011 }
4012 }));
4013 } else {
4014 {
4015 let s = subs_a.lock().unwrap();
4016 for cb in s.iter() {
4017 cb(&new_a);
4018 }
4019 }
4020 {
4021 let s = subs_b.lock().unwrap();
4022 for cb in s.iter() {
4023 cb(&new_b);
4024 }
4025 }
4026 }
4027}
4028use std::any::TypeId;
4029use std::sync::Mutex;
4030pub(crate) static ENVIRONMENT: OnceLock<
4032 Mutex<HashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>>,
4033> = OnceLock::new();
4034pub trait EnvKey: 'static + Send + Sync {
4037 type Value: Clone + Send + Sync + 'static;
4039 fn default_value() -> Self::Value;
4041}
4042pub struct YggdrasilKey;
4044impl EnvKey for YggdrasilKey {
4045 type Value = YggdrasilTokens;
4046 fn default_value() -> Self::Value {
4047 default_tokens()
4048 }
4049}
4050#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4053pub enum Appearance {
4054 Light,
4055 Dark,
4056}
4057#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4059pub enum Orientation {
4060 Horizontal,
4061 Vertical,
4062}
4063#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4065pub struct GridPlacement {
4066 pub column: i32,
4068 pub column_span: u32,
4070 pub row: i32,
4072 pub row_span: u32,
4074}
4075#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
4077pub enum Alignment {
4078 #[default]
4079 Center,
4080 Leading,
4081 Trailing,
4082 Top,
4083 Bottom,
4084}
4085#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
4087pub enum Distribution {
4088 #[default]
4089 Fill,
4090 Center,
4091 Leading,
4092 Trailing,
4093 SpaceBetween,
4094 SpaceAround,
4095 SpaceEvenly,
4096}
4097#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
4099pub struct Color {
4100 pub r: f32,
4101 pub g: f32,
4102 pub b: f32,
4103 pub a: f32,
4104}
4105impl Color {
4106 pub const BLACK: Color = Color {
4107 r: 0.0,
4108 g: 0.0,
4109 b: 0.0,
4110 a: 1.0,
4111 };
4112 pub const WHITE: Color = Color {
4113 r: 1.0,
4114 g: 1.0,
4115 b: 1.0,
4116 a: 1.0,
4117 };
4118 pub const TRANSPARENT: Color = Color {
4119 r: 0.0,
4120 g: 0.0,
4121 b: 0.0,
4122 a: 0.0,
4123 };
4124 pub const RED: Color = Color {
4125 r: 1.0,
4126 g: 0.0,
4127 b: 0.0,
4128 a: 1.0,
4129 };
4130 pub const GREEN: Color = Color {
4131 r: 0.0,
4132 g: 1.0,
4133 b: 0.0,
4134 a: 1.0,
4135 };
4136 pub const BLUE: Color = Color {
4137 r: 0.0,
4138 g: 0.0,
4139 b: 1.0,
4140 a: 1.0,
4141 };
4142 pub const VIKING_GOLD: Color = Color {
4143 r: 1.0,
4144 g: 0.84,
4145 b: 0.0,
4146 a: 1.0,
4147 };
4148 pub const MAGENTA_LIQUID: Color = Color {
4149 r: 1.0,
4150 g: 0.0,
4151 b: 1.0,
4152 a: 1.0,
4153 };
4154 pub const TACTICAL_OBSIDIAN: Color = Color {
4155 r: 0.05,
4156 g: 0.05,
4157 b: 0.07,
4158 a: 1.0,
4159 };
4160 pub fn relative_luminance(&self) -> f32 {
4162 fn res(c: f32) -> f32 {
4163 if c <= 0.03928 {
4164 c / 12.92
4165 } else {
4166 ((c + 0.055) / 1.055).powf(2.4)
4167 }
4168 }
4169 0.2126 * res(self.r) + 0.7152 * res(self.g) + 0.0722 * res(self.b)
4170 }
4171 pub fn contrast_ratio(&self, other: &Color) -> f32 {
4173 let l1 = self.relative_luminance();
4174 let l2 = other.relative_luminance();
4175 if l1 > l2 {
4176 (l1 + 0.05) / (l2 + 0.05)
4177 } else {
4178 (l2 + 0.05) / (l1 + 0.05)
4179 }
4180 }
4181 pub const CYAN: Color = Color {
4182 r: 0.0,
4183 g: 1.0,
4184 b: 1.0,
4185 a: 1.0,
4186 };
4187 pub const YELLOW: Color = Color {
4188 r: 1.0,
4189 g: 1.0,
4190 b: 0.0,
4191 a: 1.0,
4192 };
4193 pub const MAGENTA: Color = Color {
4194 r: 1.0,
4195 g: 0.0,
4196 b: 1.0,
4197 a: 1.0,
4198 };
4199 pub const GRAY: Color = Color {
4200 r: 0.5,
4201 g: 0.5,
4202 b: 0.5,
4203 a: 1.0,
4204 };
4205 pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
4207 Self { r, g, b, a }
4208 }
4209 pub fn as_array(&self) -> [f32; 4] {
4211 [self.r, self.g, self.b, self.a]
4212 }
4213
4214 pub fn lighten(&self, amount: f32) -> Self {
4220 Self {
4221 r: (self.r + amount).clamp(0.0, 1.0),
4222 g: (self.g + amount).clamp(0.0, 1.0),
4223 b: (self.b + amount).clamp(0.0, 1.0),
4224 a: self.a,
4225 }
4226 }
4227
4228 pub fn darken(&self, amount: f32) -> Self {
4230 Self {
4231 r: (self.r - amount).clamp(0.0, 1.0),
4232 g: (self.g - amount).clamp(0.0, 1.0),
4233 b: (self.b - amount).clamp(0.0, 1.0),
4234 a: self.a,
4235 }
4236 }
4237}
4238impl View for Color {
4239 type Body = Never;
4240 fn body(self) -> Self::Body {
4241 unreachable!()
4242 }
4243 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
4244 renderer.fill_rect(rect, self.as_array());
4245 }
4246}
4247pub struct AppearanceKey;
4249impl EnvKey for AppearanceKey {
4250 type Value = Appearance;
4251 fn default_value() -> Self::Value {
4252 Appearance::Dark }
4254}
4255pub struct StyleResolver;
4257impl StyleResolver {
4258 pub fn color(key: &str) -> String {
4260 let tokens = Environment::<YggdrasilKey>::new().get();
4261 let appearance = Environment::<AppearanceKey>::new().get();
4262 let is_dark = appearance == Appearance::Dark;
4263 tokens
4264 .get_color(key, is_dark)
4265 .unwrap_or_else(|| "#FF00FF".to_string()) }
4267 pub fn get<T: FromStr>(category: &str, key: &str) -> Option<T> {
4269 let tokens = Environment::<YggdrasilKey>::new().get();
4270 let appearance = Environment::<AppearanceKey>::new().get();
4271 let is_dark = appearance == Appearance::Dark;
4272 tokens.get(category, key, is_dark)
4273 }
4274 pub fn color_array(key: &str) -> [f32; 4] {
4278 let hex = Self::color(key);
4279 parse_hex_color(&hex)
4280 }
4281}
4282
4283fn parse_hex_color(hex: &str) -> [f32; 4] {
4285 let hex = hex.trim_start_matches('#');
4286 if hex.len() >= 6 {
4287 let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(255) as f32 / 255.0;
4288 let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0) as f32 / 255.0;
4289 let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(255) as f32 / 255.0;
4290 let a = if hex.len() >= 8 {
4291 u8::from_str_radix(&hex[6..8], 16).unwrap_or(255) as f32 / 255.0
4292 } else {
4293 1.0
4294 };
4295 [r, g, b, a]
4296 } else {
4297 [1.0, 0.0, 1.0, 1.0] }
4299}
4300
4301pub fn default_tokens() -> YggdrasilTokens {
4303 let mut tokens = YggdrasilTokens::new();
4304 tokens.color.insert(
4306 "background".to_string(),
4307 TokenValue::Single {
4308 value: "#000000".to_string(), },
4310 );
4311 tokens.color.insert(
4312 "primary".to_string(),
4313 TokenValue::Single {
4314 value: "#00FFFF".to_string(), },
4316 );
4317 tokens.color.insert(
4318 "secondary".to_string(),
4319 TokenValue::Single {
4320 value: "#FF00FF".to_string(), },
4322 );
4323 tokens.color.insert(
4324 "surface".to_string(),
4325 TokenValue::Adaptive {
4326 light: "#FFFFFF".to_string(),
4327 dark: "#121212".to_string(),
4328 },
4329 );
4330 tokens.color.insert(
4331 "text".to_string(),
4332 TokenValue::Adaptive {
4333 light: "#000000".to_string(),
4334 dark: "#FFFFFF".to_string(),
4335 },
4336 );
4337 tokens.color.insert(
4339 "surface_elevated".to_string(),
4340 TokenValue::Adaptive {
4341 light: "#FFFFFF".to_string(),
4342 dark: "#1A1A24".to_string(),
4343 },
4344 );
4345 tokens.color.insert(
4346 "surface_overlay".to_string(),
4347 TokenValue::Adaptive {
4348 light: "#FFFFFF".to_string(),
4349 dark: "#1E1E2E".to_string(),
4350 },
4351 );
4352 tokens.color.insert(
4353 "border".to_string(),
4354 TokenValue::Adaptive {
4355 light: "#D0D0D8".to_string(),
4356 dark: "#2A2A3A".to_string(),
4357 },
4358 );
4359 tokens.color.insert(
4360 "border_strong".to_string(),
4361 TokenValue::Adaptive {
4362 light: "#A0A0B0".to_string(),
4363 dark: "#3A3A50".to_string(),
4364 },
4365 );
4366 tokens.color.insert(
4367 "text_muted".to_string(),
4368 TokenValue::Adaptive {
4369 light: "#606070".to_string(),
4370 dark: "#8080A0".to_string(),
4371 },
4372 );
4373 tokens.color.insert(
4374 "text_dim".to_string(),
4375 TokenValue::Adaptive {
4376 light: "#9090A0".to_string(),
4377 dark: "#505070".to_string(),
4378 },
4379 );
4380 tokens.color.insert(
4381 "accent".to_string(),
4382 TokenValue::Single {
4383 value: "#00FFFF".to_string(), },
4385 );
4386 tokens.color.insert(
4387 "accent_hover".to_string(),
4388 TokenValue::Single {
4389 value: "#33FFFF".to_string(),
4390 },
4391 );
4392 tokens.color.insert(
4393 "success".to_string(),
4394 TokenValue::Single {
4395 value: "#00E676".to_string(),
4396 },
4397 );
4398 tokens.color.insert(
4399 "warning".to_string(),
4400 TokenValue::Single {
4401 value: "#FFB300".to_string(),
4402 },
4403 );
4404 tokens.color.insert(
4405 "error".to_string(),
4406 TokenValue::Single {
4407 value: "#FF5252".to_string(),
4408 },
4409 );
4410 tokens.color.insert(
4411 "info".to_string(),
4412 TokenValue::Single {
4413 value: "#448AFF".to_string(),
4414 },
4415 );
4416 tokens.color.insert(
4417 "hover".to_string(),
4418 TokenValue::Adaptive {
4419 light: "#F0F0F5".to_string(),
4420 dark: "#252535".to_string(),
4421 },
4422 );
4423 tokens.color.insert(
4424 "active".to_string(),
4425 TokenValue::Adaptive {
4426 light: "#E0E0EB".to_string(),
4427 dark: "#303045".to_string(),
4428 },
4429 );
4430 tokens.color.insert(
4431 "disabled".to_string(),
4432 TokenValue::Adaptive {
4433 light: "#E8E8F0".to_string(),
4434 dark: "#1A1A28".to_string(),
4435 },
4436 );
4437 tokens.color.insert(
4438 "disabled_text".to_string(),
4439 TokenValue::Adaptive {
4440 light: "#B0B0C0".to_string(),
4441 dark: "#404060".to_string(),
4442 },
4443 );
4444 tokens.color.insert(
4445 "focus_ring".to_string(),
4446 TokenValue::Single {
4447 value: "#00FFFF".to_string(),
4448 },
4449 );
4450 tokens.color.insert(
4451 "shadow".to_string(),
4452 TokenValue::Adaptive {
4453 light: "#00000020".to_string(),
4454 dark: "#00000060".to_string(),
4455 },
4456 );
4457 tokens.color.insert(
4458 "code_bg".to_string(),
4459 TokenValue::Adaptive {
4460 light: "#F5F5FA".to_string(),
4461 dark: "#0D0D18".to_string(),
4462 },
4463 );
4464 tokens.bifrost.insert(
4466 "blur".to_string(),
4467 TokenValue::Single {
4468 value: "25.0".to_string(),
4469 },
4470 );
4471 tokens.bifrost.insert(
4472 "saturation".to_string(),
4473 TokenValue::Single {
4474 value: "1.2".to_string(),
4475 },
4476 );
4477 tokens.bifrost.insert(
4478 "opacity".to_string(),
4479 TokenValue::Single {
4480 value: "0.65".to_string(),
4481 },
4482 );
4483 tokens.gungnir.insert(
4485 "intensity".to_string(),
4486 TokenValue::Single {
4487 value: "1.0".to_string(),
4488 },
4489 );
4490 tokens.gungnir.insert(
4491 "radius".to_string(),
4492 TokenValue::Single {
4493 value: "15.0".to_string(),
4494 },
4495 );
4496 tokens.mjolnir.insert(
4498 "clip_angle".to_string(),
4499 TokenValue::Single {
4500 value: "12.0".to_string(),
4501 },
4502 );
4503 tokens.mjolnir.insert(
4504 "border_width".to_string(),
4505 TokenValue::Single {
4506 value: "2.0".to_string(),
4507 },
4508 );
4509 tokens.anim.insert(
4511 "stiffness".to_string(),
4512 TokenValue::Single {
4513 value: "170.0".to_string(),
4514 },
4515 );
4516 tokens.anim.insert(
4517 "damping".to_string(),
4518 TokenValue::Single {
4519 value: "26.0".to_string(),
4520 },
4521 );
4522 tokens.anim.insert(
4523 "mass".to_string(),
4524 TokenValue::Single {
4525 value: "1.0".to_string(),
4526 },
4527 );
4528 tokens.accessibility.insert(
4530 "reduce_motion".to_string(),
4531 TokenValue::Single {
4532 value: "false".to_string(),
4533 },
4534 );
4535 tokens
4536}
4537pub struct Environment<K: EnvKey> {
4539 _marker: std::marker::PhantomData<K>,
4540}
4541impl<K: EnvKey> Default for Environment<K> {
4542 fn default() -> Self {
4543 Self::new()
4544 }
4545}
4546impl<K: EnvKey> Environment<K> {
4547 pub fn new() -> Self {
4549 Self {
4550 _marker: std::marker::PhantomData,
4551 }
4552 }
4553 pub fn get(&self) -> K::Value {
4555 if let Some(env_store) = ENVIRONMENT.get() {
4556 let env_lock = env_store.lock().unwrap();
4557 if let Some(val) = env_lock.get(&std::any::TypeId::of::<K>()) {
4558 if let Some(typed_val) = val.downcast_ref::<K::Value>() {
4559 return typed_val.clone();
4560 } else {
4561 log::warn!(
4562 "Environment: Downcast failed for key type {:?}",
4563 std::any::type_name::<K>()
4564 );
4565 }
4566 } else {
4567 log::trace!(
4569 "Environment: Key not found: {:?}. Returning default.",
4570 std::any::type_name::<K>()
4571 );
4572 }
4573 } else {
4574 log::trace!(
4576 "Environment: Store not initialized. Key: {:?}. Returning default.",
4577 std::any::type_name::<K>()
4578 );
4579 }
4580 K::default_value()
4581 }
4582}
4583pub mod env {
4585 pub fn insert<K: super::EnvKey>(value: K::Value) {
4587 let store = super::ENVIRONMENT
4588 .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
4589 let mut env_map = store.lock().unwrap();
4590 env_map.insert(std::any::TypeId::of::<K>(), Box::new(value));
4591 }
4592 pub fn remove<K: super::EnvKey>() {
4594 if let Some(store) = super::ENVIRONMENT.get() {
4595 let mut env_map = store.lock().unwrap();
4596 env_map.remove(&std::any::TypeId::of::<K>());
4597 }
4598 }
4599}
4600#[derive(Debug, Clone, Copy, PartialEq)]
4603pub struct Size {
4604 pub width: f32,
4605 pub height: f32,
4606}
4607
4608impl Size {
4609 pub const ZERO: Self = Self {
4610 width: 0.0,
4611 height: 0.0,
4612 };
4613
4614 pub fn new(width: f32, height: f32) -> Self {
4615 Self { width, height }
4616 }
4617}
4618
4619#[derive(Debug, Clone, Copy, PartialEq)]
4621pub struct EdgeInsets {
4622 pub top: f32,
4623 pub leading: f32,
4624 pub bottom: f32,
4625 pub trailing: f32,
4626}
4627
4628impl EdgeInsets {
4629 pub fn all(value: f32) -> Self {
4631 Self {
4632 top: value,
4633 leading: value,
4634 bottom: value,
4635 trailing: value,
4636 }
4637 }
4638
4639 pub fn vertical(value: f32) -> Self {
4641 Self {
4642 top: value,
4643 leading: 0.0,
4644 bottom: value,
4645 trailing: 0.0,
4646 }
4647 }
4648
4649 pub fn horizontal(value: f32) -> Self {
4651 Self {
4652 top: 0.0,
4653 leading: value,
4654 bottom: 0.0,
4655 trailing: value,
4656 }
4657 }
4658}
4659
4660#[derive(Debug, Clone, Copy, PartialEq)]
4664pub struct FrameModifier {
4665 pub width: Option<f32>,
4667 pub height: Option<f32>,
4669 pub min_width: Option<f32>,
4671 pub max_width: Option<f32>,
4673 pub min_height: Option<f32>,
4675 pub max_height: Option<f32>,
4677 pub alignment: Alignment,
4679}
4680
4681impl Default for FrameModifier {
4682 fn default() -> Self {
4684 Self::new()
4685 }
4686}
4687
4688impl FrameModifier {
4689 pub fn new() -> Self {
4691 Self {
4692 width: None,
4693 height: None,
4694 min_width: None,
4695 max_width: None,
4696 min_height: None,
4697 max_height: None,
4698 alignment: Alignment::Center,
4699 }
4700 }
4701
4702 pub fn width(mut self, width: f32) -> Self {
4704 self.width = Some(width);
4705 self
4706 }
4707
4708 pub fn height(mut self, height: f32) -> Self {
4710 self.height = Some(height);
4711 self
4712 }
4713
4714 pub fn size(mut self, width: f32, height: f32) -> Self {
4716 self.width = Some(width);
4717 self.height = Some(height);
4718 self
4719 }
4720
4721 pub fn min_width(mut self, min_width: f32) -> Self {
4723 self.min_width = Some(min_width);
4724 self
4725 }
4726
4727 pub fn max_width(mut self, max_width: f32) -> Self {
4729 self.max_width = Some(max_width);
4730 self
4731 }
4732
4733 pub fn min_height(mut self, min_height: f32) -> Self {
4735 self.min_height = Some(min_height);
4736 self
4737 }
4738
4739 pub fn max_height(mut self, max_height: f32) -> Self {
4741 self.max_height = Some(max_height);
4742 self
4743 }
4744
4745 pub fn alignment(mut self, alignment: Alignment) -> Self {
4747 self.alignment = alignment;
4748 self
4749 }
4750}
4751
4752impl ViewModifier for FrameModifier {
4753 fn modify<V: View>(self, content: V) -> impl View {
4755 ModifiedView::new(content, self)
4756 }
4757
4758 fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
4760 let w = if let Some(width) = self.width {
4761 Some(width)
4762 } else {
4763 proposal.width.map(|pw| {
4764 pw.clamp(
4765 self.min_width.unwrap_or(0.0),
4766 self.max_width.unwrap_or(f32::INFINITY),
4767 )
4768 })
4769 };
4770 let h = if let Some(height) = self.height {
4771 Some(height)
4772 } else {
4773 proposal.height.map(|ph| {
4774 ph.clamp(
4775 self.min_height.unwrap_or(0.0),
4776 self.max_height.unwrap_or(f32::INFINITY),
4777 )
4778 })
4779 };
4780 SizeProposal {
4781 width: w,
4782 height: h,
4783 }
4784 }
4785
4786 fn transform_size(&self, child_size: Size) -> Size {
4788 let w = if let Some(width) = self.width {
4789 width
4790 } else {
4791 child_size.width.clamp(
4792 self.min_width.unwrap_or(0.0),
4793 self.max_width.unwrap_or(f32::INFINITY),
4794 )
4795 };
4796 let h = if let Some(height) = self.height {
4797 height
4798 } else {
4799 child_size.height.clamp(
4800 self.min_height.unwrap_or(0.0),
4801 self.max_height.unwrap_or(f32::INFINITY),
4802 )
4803 };
4804 Size {
4805 width: w,
4806 height: h,
4807 }
4808 }
4809
4810 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4812 self.render(renderer, rect);
4813 let child_proposal =
4814 self.transform_proposal(SizeProposal::new(Some(rect.width), Some(rect.height)));
4815 let child_size = view.intrinsic_size(renderer, child_proposal);
4816
4817 let mut child_x = rect.x;
4818 let mut child_y = rect.y;
4819
4820 match self.alignment {
4821 Alignment::Leading => {
4822 child_y = rect.y + (rect.height - child_size.height) / 2.0;
4823 }
4824 Alignment::Trailing => {
4825 child_x = rect.x + rect.width - child_size.width;
4826 child_y = rect.y + (rect.height - child_size.height) / 2.0;
4827 }
4828 Alignment::Top => {
4829 child_x = rect.x + (rect.width - child_size.width) / 2.0;
4830 }
4831 Alignment::Bottom => {
4832 child_x = rect.x + (rect.width - child_size.width) / 2.0;
4833 child_y = rect.y + rect.height - child_size.height;
4834 }
4835 Alignment::Center => {
4836 child_x = rect.x + (rect.width - child_size.width) / 2.0;
4837 child_y = rect.y + (rect.height - child_size.height) / 2.0;
4838 }
4839 }
4840
4841 let child_rect = Rect {
4842 x: child_x,
4843 y: child_y,
4844 width: child_size.width,
4845 height: child_size.height,
4846 };
4847
4848 view.render(renderer, child_rect);
4849 self.post_render(renderer, rect);
4850 }
4851}
4852
4853#[derive(Debug, Clone, Copy, PartialEq)]
4855pub struct FlexModifier {
4856 pub weight: f32,
4857}
4858
4859impl ViewModifier for FlexModifier {
4860 fn modify<V: View>(self, content: V) -> impl View {
4861 ModifiedView::new(content, self)
4862 }
4863
4864 fn child_flex_weight<V: View>(&self, _view: &V) -> f32 {
4865 self.weight
4866 }
4867}
4868
4869#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4871pub struct GridPlacementModifier {
4872 pub placement: GridPlacement,
4874}
4875
4876impl ViewModifier for GridPlacementModifier {
4877 fn modify<V: View>(self, content: V) -> impl View {
4879 ModifiedView::new(content, self)
4880 }
4881
4882 fn get_grid_placement(&self) -> Option<GridPlacement> {
4884 Some(self.placement)
4885 }
4886}
4887
4888#[derive(Clone)]
4891pub struct OverlayModifier {
4892 pub overlay: AnyView,
4894 pub alignment: Alignment,
4896 pub offset: [f32; 2],
4898 pub on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
4900}
4901
4902impl ViewModifier for OverlayModifier {
4903 fn modify<V: View>(self, content: V) -> impl View {
4905 ModifiedView::new(content, self)
4906 }
4907
4908 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4910 view.render(renderer, rect);
4912
4913 let overlay_size = self
4915 .overlay
4916 .intrinsic_size(renderer, SizeProposal::unspecified());
4917
4918 let mut overlay_x;
4920 let mut overlay_y;
4921
4922 match self.alignment {
4923 Alignment::Leading => {
4924 overlay_x = rect.x - overlay_size.width;
4925 overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4926 }
4927 Alignment::Trailing => {
4928 overlay_x = rect.x + rect.width;
4929 overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4930 }
4931 Alignment::Top => {
4932 overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4933 overlay_y = rect.y - overlay_size.height;
4934 }
4935 Alignment::Bottom => {
4936 overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4937 overlay_y = rect.y + rect.height;
4938 }
4939 Alignment::Center => {
4940 overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4941 overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4942 }
4943 }
4944
4945 overlay_x += self.offset[0];
4946 overlay_y += self.offset[1];
4947
4948 let overlay_rect = Rect {
4949 x: overlay_x,
4950 y: overlay_y,
4951 width: overlay_size.width,
4952 height: overlay_size.height,
4953 };
4954
4955 if let Some(on_dismiss) = &self.on_dismiss {
4957 let dismiss = on_dismiss.clone();
4958 renderer.register_handler(
4959 "pointerdown",
4960 Arc::new(move |event| {
4961 if let Event::PointerDown { x, y, .. } = event {
4962 let click_inside = x >= overlay_rect.x
4963 && x <= overlay_rect.x + overlay_rect.width
4964 && y >= overlay_rect.y
4965 && y <= overlay_rect.y + overlay_rect.height;
4966 if !click_inside {
4967 dismiss();
4968 }
4969 }
4970 }),
4971 );
4972 }
4973
4974 self.overlay.render(renderer, overlay_rect);
4976 }
4977}
4978
4979#[derive(Debug, Clone, Copy, PartialEq)]
4981pub struct OffsetModifier {
4982 pub x: f32,
4983 pub y: f32,
4984}
4985
4986impl OffsetModifier {
4987 pub fn new(x: f32, y: f32) -> Self {
4988 Self { x, y }
4989 }
4990}
4991
4992impl ViewModifier for OffsetModifier {
4993 fn modify<V: View>(self, content: V) -> impl View {
4994 ModifiedView::new(content, self)
4995 }
4996}
4997
4998#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5000pub struct ZIndexModifier {
5001 pub z_index: i32,
5002}
5003
5004impl ZIndexModifier {
5005 pub fn new(z_index: i32) -> Self {
5006 Self { z_index }
5007 }
5008}
5009
5010impl ViewModifier for ZIndexModifier {
5011 fn modify<V: View>(self, content: V) -> impl View {
5012 ModifiedView::new(content, self)
5013 }
5014}
5015
5016#[derive(Debug, Clone, Copy, PartialEq, Default)]
5018pub struct LayoutConstraints {
5019 pub min_width: Option<f32>,
5020 pub max_width: Option<f32>,
5021 pub min_height: Option<f32>,
5022 pub max_height: Option<f32>,
5023}
5024
5025#[derive(Debug, Clone, Copy, PartialEq)]
5027pub struct LayoutModifier {
5028 pub constraints: LayoutConstraints,
5029}
5030
5031impl LayoutModifier {
5032 pub fn new(constraints: LayoutConstraints) -> Self {
5033 Self { constraints }
5034 }
5035}
5036
5037impl ViewModifier for LayoutModifier {
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 SafeAreaModifier {
5046 pub ignores: bool,
5047}
5048
5049impl ViewModifier for SafeAreaModifier {
5050 fn modify<V: View>(self, content: V) -> impl View {
5051 ModifiedView::new(content, self)
5052 }
5053}
5054
5055#[derive(Debug, Clone, Copy, PartialEq)]
5057pub struct ElevationModifier {
5058 pub level: f32,
5059}
5060
5061impl ViewModifier for ElevationModifier {
5062 fn modify<V: View>(self, content: V) -> impl View {
5063 ModifiedView::new(content, self)
5064 }
5065
5066 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
5067 if self.level > 0.0 {
5068 let radius = self.level * 2.0;
5069 let offset_y = self.level * 0.5;
5070 let shadow_color = [0.0, 0.0, 0.0, 0.3];
5071 renderer.push_shadow(radius, shadow_color, [0.0, offset_y]);
5072 view.render(renderer, rect);
5073 renderer.pop_shadow();
5074 } else {
5075 view.render(renderer, rect);
5076 }
5077 }
5078}
5079
5080pub mod layout {
5082 use super::*;
5083
5084 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5087 pub struct LayoutKey {
5088 pub view_hash: u64,
5089 pub generation: u64,
5090 }
5091
5092 pub struct LayoutCache {
5094 pub safe_area: SafeArea,
5095 pub delta_time: f32,
5096 pub scale_factor: f32,
5098 size_cache: HashMap<(u64, u32, u32), Size>, generation: u64,
5103 pub engine: Option<Box<dyn std::any::Any + Send + Sync>>,
5105 pub animators: Option<Box<dyn std::any::Any + Send + Sync>>,
5107 pub previous_rects: HashMap<u64, Rect>,
5109 }
5110
5111 impl Default for LayoutCache {
5112 fn default() -> Self {
5113 Self::new()
5114 }
5115 }
5116
5117 impl LayoutCache {
5118 pub fn new() -> Self {
5119 Self {
5120 safe_area: SafeArea::default(),
5121 delta_time: 0.016,
5122 scale_factor: 1.0,
5123 size_cache: HashMap::new(),
5124 generation: 0,
5125 engine: None,
5126 animators: None,
5127 previous_rects: HashMap::new(),
5128 }
5129 }
5130
5131 pub fn generation(&self) -> u64 {
5133 self.generation
5134 }
5135
5136 pub fn invalidate(&mut self) {
5140 self.generation = self.generation.wrapping_add(1);
5141 }
5142
5143 pub fn is_valid(&self, key: LayoutKey, current_gen: u64) -> bool {
5146 key.generation == current_gen && key.generation == self.generation
5147 }
5148
5149 pub fn clear(&mut self) {
5150 self.safe_area = SafeArea::default();
5151 self.size_cache.clear();
5152 }
5153
5154 pub fn get_size(&self, view_hash: u64, proposal: SizeProposal) -> Option<Size> {
5155 let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
5156 let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
5157 self.size_cache.get(&(view_hash, pw, ph)).copied()
5158 }
5159
5160 pub fn set_size(&mut self, view_hash: u64, proposal: SizeProposal, size: Size) {
5161 let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
5162 let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
5163 self.size_cache.insert((view_hash, pw, ph), size);
5164 }
5165
5166 pub fn invalidate_view(&mut self, view_hash: u64) {
5168 self.size_cache.retain(|&(hash, _, _), _| hash != view_hash);
5169 }
5170 }
5171
5172 #[derive(Debug, Clone, Copy, PartialEq)]
5174 pub struct SizeProposal {
5175 pub width: Option<f32>,
5176 pub height: Option<f32>,
5177 }
5178
5179 impl SizeProposal {
5180 pub fn unspecified() -> Self {
5181 Self {
5182 width: None,
5183 height: None,
5184 }
5185 }
5186
5187 pub fn width(width: f32) -> Self {
5188 Self {
5189 width: Some(width),
5190 height: None,
5191 }
5192 }
5193
5194 pub fn height(height: f32) -> Self {
5195 Self {
5196 width: None,
5197 height: Some(height),
5198 }
5199 }
5200
5201 pub fn tight(width: f32, height: f32) -> Self {
5202 Self {
5203 width: Some(width),
5204 height: Some(height),
5205 }
5206 }
5207
5208 pub fn new(width: Option<f32>, height: Option<f32>) -> Self {
5209 Self { width, height }
5210 }
5211 }
5212
5213 pub trait LayoutView: Send {
5215 fn size_that_fits(
5217 &self,
5218 proposal: SizeProposal,
5219 subviews: &[&dyn LayoutView],
5220 cache: &mut LayoutCache,
5221 ) -> Size;
5222
5223 fn place_subviews(
5225 &self,
5226 bounds: Rect,
5227 subviews: &mut [&mut dyn LayoutView],
5228 cache: &mut LayoutCache,
5229 );
5230
5231 fn flex_weight(&self) -> f32 {
5233 0.0
5234 }
5235
5236 fn view_hash(&self) -> u64 {
5239 0
5240 }
5241
5242 fn debug_layout(&self, indent: usize) -> String {
5245 let prefix = " ".repeat(indent);
5246 format!("{}LayoutView", prefix)
5247 }
5248 }
5249 #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
5251 pub struct EdgeInsets {
5252 pub top: f32,
5253 pub leading: f32,
5254 pub bottom: f32,
5255 pub trailing: f32,
5256 }
5257
5258 impl EdgeInsets {
5259 pub fn new(top: f32, leading: f32, bottom: f32, trailing: f32) -> Self {
5260 Self {
5261 top,
5262 leading,
5263 bottom,
5264 trailing,
5265 }
5266 }
5267
5268 pub fn all(value: f32) -> Self {
5269 Self {
5270 top: value,
5271 leading: value,
5272 bottom: value,
5273 trailing: value,
5274 }
5275 }
5276 }
5277
5278 #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
5280 pub struct SafeArea {
5281 pub insets: EdgeInsets,
5282 }
5283
5284 #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
5286 pub enum SdfShape {
5287 Rect(Rect),
5288 RoundedRect { rect: Rect, radius: f32 },
5289 Circle { center: [f32; 2], radius: f32 },
5290 }
5291
5292 #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
5294 pub struct Rect {
5295 pub x: f32,
5296 pub y: f32,
5297 pub width: f32,
5298 pub height: f32,
5299 }
5300
5301 impl Rect {
5302 pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
5303 Self {
5304 x,
5305 y,
5306 width,
5307 height,
5308 }
5309 }
5310
5311 pub fn inset(&self, amount: f32) -> Self {
5312 Self {
5313 x: self.x + amount,
5314 y: self.y + amount,
5315 width: (self.width - amount * 2.0).max(0.0),
5316 height: (self.height - amount * 2.0).max(0.0),
5317 }
5318 }
5319
5320 pub fn offset(&self, dx: f32, dy: f32) -> Self {
5321 Self {
5322 x: self.x + dx,
5323 y: self.y + dy,
5324 ..*self
5325 }
5326 }
5327
5328 pub fn zero() -> Self {
5329 Self {
5330 x: 0.0,
5331 y: 0.0,
5332 width: 0.0,
5333 height: 0.0,
5334 }
5335 }
5336
5337 pub fn contains(&self, x: f32, y: f32) -> bool {
5338 x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
5339 }
5340
5341 pub fn size(&self) -> Size {
5342 Size {
5343 width: self.width,
5344 height: self.height,
5345 }
5346 }
5347
5348 pub fn split_horizontal(&self, n: usize) -> Vec<Rect> {
5350 if n == 0 {
5351 return vec![];
5352 }
5353 let item_width = self.width / n as f32;
5354 (0..n)
5355 .map(|i| Rect {
5356 x: self.x + i as f32 * item_width,
5357 y: self.y,
5358 width: item_width,
5359 height: self.height,
5360 })
5361 .collect()
5362 }
5363
5364 pub fn split_vertical(&self, n: usize) -> Vec<Rect> {
5366 if n == 0 {
5367 return vec![];
5368 }
5369 let item_height = self.height / n as f32;
5370 (0..n)
5371 .map(|i| Rect {
5372 x: self.x,
5373 y: self.y + i as f32 * item_height,
5374 width: self.width,
5375 height: item_height,
5376 })
5377 .collect()
5378 }
5379 }
5380}
5381
5382pub use layout::{LayoutCache, LayoutKey, LayoutView, Rect, SizeProposal};
5384pub mod agents;
5387pub mod animation;
5388pub mod gpu;
5389pub mod material;
5390pub mod runtime;
5391pub mod scene_graph;
5392pub mod sdf_shadow;
5393
5394pub use material::DrawMaterial;
5395pub use scene_graph::{NodeId, bifrost_registry};
5396
5397pub trait AssetManager: Send + Sync {
5401 fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>>;
5403
5404 fn preload_image(&self, url: &str);
5406}
5407
5408#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5410pub enum TouchPhase {
5411 Began,
5413 Moved,
5415 Ended,
5417 Cancelled,
5419}
5420
5421#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
5423pub enum Event {
5424 PointerDown {
5425 x: f32,
5426 y: f32,
5427 button: u32,
5428 proximity_field: f32,
5429 tilt: Option<f32>,
5430 azimuth: Option<f32>,
5431 pressure: Option<f32>,
5432 barrel_rotation: Option<f32>,
5433 pointer_precision: f32,
5434 },
5435 PointerUp {
5436 x: f32,
5437 y: f32,
5438 button: u32,
5439 tilt: Option<f32>,
5440 azimuth: Option<f32>,
5441 pressure: Option<f32>,
5442 barrel_rotation: Option<f32>,
5443 pointer_precision: f32,
5444 },
5445 PointerMove {
5446 x: f32,
5447 y: f32,
5448 proximity_field: f32,
5449 tilt: Option<f32>,
5450 azimuth: Option<f32>,
5451 pressure: Option<f32>,
5452 barrel_rotation: Option<f32>,
5453 pointer_precision: f32,
5454 },
5455 PointerClick {
5456 x: f32,
5457 y: f32,
5458 button: u32,
5459 tilt: Option<f32>,
5460 azimuth: Option<f32>,
5461 pressure: Option<f32>,
5462 barrel_rotation: Option<f32>,
5463 pointer_precision: f32,
5464 },
5465 PointerEnter,
5466 PointerLeave,
5467 PointerWheel {
5470 x: f32,
5471 y: f32,
5472 delta_x: f32,
5473 delta_y: f32,
5474 pointer_precision: f32,
5475 },
5476 PointerDoubleClick {
5478 x: f32,
5479 y: f32,
5480 button: u32,
5481 pointer_precision: f32,
5482 },
5483 DragStart {
5485 x: f32,
5486 y: f32,
5487 button: u32,
5488 pointer_precision: f32,
5489 },
5490 DragMove {
5492 x: f32,
5493 y: f32,
5494 pointer_precision: f32,
5495 },
5496 DragEnd {
5498 x: f32,
5499 y: f32,
5500 pointer_precision: f32,
5501 },
5502 KeyDown {
5503 key: String,
5504 },
5505 KeyUp {
5506 key: String,
5507 },
5508 FocusIn,
5510 FocusOut,
5512 Copy,
5514 Cut,
5516 Paste(String),
5518 Ime(String),
5520 TouchStart {
5522 x: f32,
5523 y: f32,
5524 touch_id: u64,
5525 },
5526 TouchMove {
5528 x: f32,
5529 y: f32,
5530 touch_id: u64,
5531 },
5532 TouchEnd {
5534 x: f32,
5535 y: f32,
5536 touch_id: u64,
5537 },
5538 TouchCancel {
5540 touch_id: u64,
5541 },
5542 GesturePinch {
5548 center: [f32; 2],
5549 scale: f32,
5550 velocity: f32,
5551 phase: TouchPhase,
5552 },
5553 GestureSwipe {
5558 direction: [f32; 2],
5559 velocity: f32,
5560 phase: TouchPhase,
5561 },
5562 FileDrop {
5564 x: f32,
5565 y: f32,
5566 path: String,
5567 },
5568}
5569
5570impl Event {
5571 pub fn pointer_precision(&self) -> f32 {
5577 match self {
5578 Self::PointerDown {
5579 pointer_precision, ..
5580 }
5581 | Self::PointerUp {
5582 pointer_precision, ..
5583 }
5584 | Self::PointerMove {
5585 pointer_precision, ..
5586 }
5587 | Self::PointerClick {
5588 pointer_precision, ..
5589 }
5590 | Self::PointerWheel {
5591 pointer_precision, ..
5592 }
5593 | Self::PointerDoubleClick {
5594 pointer_precision, ..
5595 }
5596 | Self::DragStart {
5597 pointer_precision, ..
5598 }
5599 | Self::DragMove {
5600 pointer_precision, ..
5601 }
5602 | Self::DragEnd {
5603 pointer_precision, ..
5604 } => *pointer_precision,
5605 _ => 0.0,
5606 }
5607 }
5608
5609 pub fn name(&self) -> &'static str {
5611 match self {
5612 Self::PointerDown { .. } => "pointerdown",
5613 Self::PointerUp { .. } => "pointerup",
5614 Self::PointerMove { .. } => "pointermove",
5615 Self::PointerClick { .. } => "pointerclick",
5616 Self::PointerEnter => "pointerenter",
5617 Self::PointerLeave => "pointerleave",
5618 Self::PointerWheel { .. } => "pointerwheel",
5619 Self::PointerDoubleClick { .. } => "pointerdoubleclick",
5620 Self::DragStart { .. } => "dragstart",
5621 Self::DragMove { .. } => "dragmove",
5622 Self::DragEnd { .. } => "dragend",
5623 Self::KeyDown { .. } => "keydown",
5624 Self::KeyUp { .. } => "keyup",
5625 Self::FocusIn => "focusin",
5626 Self::FocusOut => "focusout",
5627 Self::Copy => "copy",
5628 Self::Cut => "cut",
5629 Self::Paste(_) => "paste",
5630 Self::Ime(_) => "ime",
5631 Self::TouchStart { .. } => "touchstart",
5632 Self::TouchMove { .. } => "touchmove",
5633 Self::TouchEnd { .. } => "touchend",
5634 Self::TouchCancel { .. } => "touchcancel",
5635 Self::GesturePinch { .. } => "gesturepinch",
5636 Self::GestureSwipe { .. } => "gestureswipe",
5637 Self::FileDrop { .. } => "filedrop",
5638 }
5639 }
5640}
5641
5642#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5644pub enum EventResponse {
5645 Handled,
5646 Ignored,
5647}
5648
5649pub struct DefaultAssetManager {
5651 cache: AssetCache,
5652}
5653type AssetCache = Arc<arc_swap::ArcSwap<HashMap<String, AssetState<Arc<Vec<u8>>>>>>;
5654
5655impl Default for DefaultAssetManager {
5656 fn default() -> Self {
5657 Self::new()
5658 }
5659}
5660
5661impl DefaultAssetManager {
5662 pub fn new() -> Self {
5663 Self {
5664 cache: Arc::new(arc_swap::ArcSwap::from_pointee(HashMap::new())),
5665 }
5666 }
5667}
5668
5669impl AssetManager for DefaultAssetManager {
5670 fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>> {
5671 if let Some(state) = self.cache.load().get(url) {
5672 return state.clone();
5673 }
5674
5675 self.cache.rcu(|map| {
5676 let mut m = (**map).clone();
5677 m.entry(url.to_string()).or_insert(AssetState::Loading);
5678 m
5679 });
5680 AssetState::Loading
5681 }
5682
5683 fn preload_image(&self, _url: &str) {}
5684}
5685
5686use std::future::Future;
5687
5688pub struct Suspense<T: Clone + Send + Sync + 'static> {
5691 inner: State<AssetState<T>>,
5692}
5693
5694impl<T: Clone + Send + Sync + 'static> Default for Suspense<T> {
5695 fn default() -> Self {
5696 Self::new()
5697 }
5698}
5699
5700impl<T: Clone + Send + Sync + 'static> Suspense<T> {
5701 pub fn new() -> Self {
5702 Self {
5703 inner: State::new(AssetState::Loading),
5704 }
5705 }
5706
5707 pub fn new_async<F>(future: F) -> Self
5708 where
5709 F: Future<Output = Result<T, String>> + Send + 'static,
5710 {
5711 let suspense = Self::new();
5712 let suspense_clone = suspense.clone();
5713
5714 #[cfg(not(target_arch = "wasm32"))]
5715 {
5716 if let Ok(handle) = tokio::runtime::Handle::try_current() {
5718 handle.spawn(async move {
5719 let result = future.await;
5720 match result {
5721 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5722 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5723 }
5724 });
5725 } else {
5726 std::thread::spawn(move || {
5727 let rt = tokio::runtime::Builder::new_current_thread()
5728 .enable_all()
5729 .build()
5730 .unwrap();
5731 rt.block_on(async {
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 }
5741 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5742 {
5743 wasm_bindgen_futures::spawn_local(async move {
5744 let result = future.await;
5745 match result {
5746 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5747 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5748 }
5749 });
5750 }
5751
5752 suspense
5753 }
5754
5755 pub fn ready(value: T) -> Self {
5756 Self {
5757 inner: State::new(AssetState::Ready(value)),
5758 }
5759 }
5760
5761 pub fn error(message: impl Into<String>) -> Self {
5762 Self {
5763 inner: State::new(AssetState::Error(message.into())),
5764 }
5765 }
5766
5767 pub fn get(&self) -> AssetState<T> {
5768 self.inner.get()
5769 }
5770
5771 pub fn get_ref(&self) -> AssetState<T> {
5772 self.inner.get()
5773 }
5774
5775 pub fn is_loading(&self) -> bool {
5776 matches!(self.get(), AssetState::Loading)
5777 }
5778
5779 pub fn is_ready(&self) -> bool {
5780 matches!(self.get(), AssetState::Ready(_))
5781 }
5782
5783 pub fn is_error(&self) -> bool {
5784 matches!(self.get(), AssetState::Error(_))
5785 }
5786
5787 pub fn ready_value(&self) -> Option<T> {
5788 match self.get() {
5789 AssetState::Ready(value) => Some(value),
5790 _ => None,
5791 }
5792 }
5793
5794 pub fn error_message(&self) -> Option<String> {
5795 match self.get() {
5796 AssetState::Error(message) => Some(message),
5797 _ => None,
5798 }
5799 }
5800
5801 pub fn subscribe<F: Fn(&AssetState<T>) + Send + Sync + 'static>(&self, callback: F) {
5802 self.inner.subscribe(callback)
5803 }
5804
5805 pub fn inner_state(&self) -> &State<AssetState<T>> {
5806 &self.inner
5807 }
5808}
5809
5810impl<T: Clone + Send + Sync + 'static> Clone for Suspense<T> {
5811 fn clone(&self) -> Self {
5812 Self {
5813 inner: self.inner.clone(),
5814 }
5815 }
5816}
5817
5818impl<T: Clone + Send + Sync + 'static> From<T> for Suspense<T> {
5819 fn from(value: T) -> Self {
5820 Self::ready(value)
5821 }
5822}
5823
5824impl<T: Clone + Send + Sync + 'static> From<Result<T, String>> for Suspense<T> {
5825 fn from(result: Result<T, String>) -> Self {
5826 match result {
5827 Ok(value) => Self::ready(value),
5828 Err(error) => Self::error(error),
5829 }
5830 }
5831}
5832
5833#[cfg(test)]
5834mod phase1_test;
5835
5836#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5838pub enum BerserkerMode {
5839 Normal,
5840 Rage, Frenzy, GodMode, }
5844
5845pub trait Seer: Send + Sync {
5848 fn predict(&self, context: &str) -> String;
5850 fn whispers(&self) -> Vec<String>;
5852}
5853
5854#[cfg(test)]
5855mod vili_tests {
5856 use super::*;
5857
5858 struct DummyRenderer;
5859 impl ElapsedTime for DummyRenderer {
5860 fn elapsed_time(&self) -> f32 {
5861 0.0
5862 }
5863 fn delta_time(&self) -> f32 {
5864 0.0
5865 }
5866 }
5867 impl Renderer for DummyRenderer {
5868 fn fill_rect(&mut self, _r: Rect, _c: [f32; 4]) {}
5869 fn fill_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4]) {}
5870 fn fill_ellipse(&mut self, _r: Rect, _c: [f32; 4]) {}
5871 fn stroke_rect(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5872 fn stroke_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4], _w: f32) {}
5873 fn stroke_ellipse(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5874 fn draw_line(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, _c: [f32; 4], _w: f32) {}
5875 fn draw_text(&mut self, _t: &str, _x: f32, _y: f32, _s: f32, _c: [f32; 4]) {}
5876 fn measure_text(&mut self, _t: &str, _s: f32) -> (f32, f32) {
5877 (0.0, 0.0)
5878 }
5879 fn memoize(&mut self, _id: u64, _hash: u64, _r: &dyn Fn(&mut dyn Renderer)) {}
5880 fn draw_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {
5881 }
5882 fn set_camera_3d(&mut self, _camera: &Camera3D) {}
5883 fn push_transform_3d(&mut self, _transform: &Transform3D) {}
5884 fn pop_transform_3d(&mut self) {}
5885 }
5886
5887 #[test]
5888 fn test_magnetic_warp() {
5889 let renderer = DummyRenderer;
5890 let anchor = Rect {
5891 x: 100.0,
5892 y: 100.0,
5893 width: 50.0,
5894 height: 50.0,
5895 };
5896 let pointer = [125.0, 50.0];
5898 let warp = renderer.magnetic_warp(pointer, anchor, 1.0);
5901 assert!(warp[1] > 50.0);
5903
5904 let far_pointer = [500.0, 500.0];
5906 let far_warp = renderer.magnetic_warp(far_pointer, anchor, 1.0);
5907 assert_eq!(far_pointer, far_warp);
5908 }
5909
5910 #[test]
5911 fn test_mani_glow() {
5912 let renderer = DummyRenderer;
5913 let bounds = Rect {
5914 x: 0.0,
5915 y: 0.0,
5916 width: 100.0,
5917 height: 100.0,
5918 };
5919 let pointer_inside = [50.0, 50.0];
5920 let glow_max = renderer.mani_glow_intensity(pointer_inside, bounds, 120.0);
5921 assert_eq!(glow_max, 1.0);
5922
5923 let pointer_edge = [50.0, -10.0];
5924 let glow_partial = renderer.mani_glow_intensity(pointer_edge, bounds, 120.0);
5925 assert!(glow_partial > 0.0 && glow_partial < 1.0);
5926 }
5927
5928 #[test]
5929 fn test_fafnir_evolve() {
5930 let renderer = DummyRenderer;
5931 let bounds = Rect {
5932 x: 0.0,
5933 y: 0.0,
5934 width: 100.0,
5935 height: 100.0,
5936 };
5937 let pointer_inside = [50.0, 50.0];
5938 let scale = renderer.fafnir_evolve(pointer_inside, bounds, 1.2);
5939 assert_eq!(scale, 1.2); }
5941
5942 #[test]
5943 fn test_undo_manager_basic() {
5944 let mut manager = UndoManager::new(3, 0.5);
5945 let val = std::sync::Arc::new(std::sync::Mutex::new(0));
5946
5947 let v1 = val.clone();
5948 let v2 = val.clone();
5949 manager.push(
5950 "Add",
5951 move || *v1.lock().unwrap() -= 1,
5952 move || *v2.lock().unwrap() += 1,
5953 );
5954
5955 assert!(manager.can_undo());
5956 assert!(!manager.can_redo());
5957
5958 let undo = manager.undo().unwrap();
5959 undo();
5960 assert_eq!(*val.lock().unwrap(), -1);
5961 assert!(!manager.can_undo());
5962 assert!(manager.can_redo());
5963
5964 let redo = manager.redo().unwrap();
5965 redo();
5966 assert_eq!(*val.lock().unwrap(), 0);
5967 }
5968
5969 #[test]
5970 fn test_undo_manager_depth_limit() {
5971 let mut manager = UndoManager::new(2, 0.5);
5972 manager.push("1", || {}, || {});
5973 manager.push("2", || {}, || {});
5974 manager.push("3", || {}, || {});
5975
5976 assert_eq!(manager.stack.len(), 2);
5977 assert_eq!(manager.position, 2);
5978 }
5979
5980 #[test]
5981 fn test_undo_manager_coalescing() {
5982 let mut manager = UndoManager::new(10, 1.0);
5983 let count = std::sync::Arc::new(std::sync::Mutex::new(0));
5984
5985 let c = count.clone();
5986 manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5987
5988 let c = count.clone();
5989 manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5990
5991 assert_eq!(manager.stack.len(), 1);
5992
5993 let undo = manager.undo().unwrap();
5994 undo();
5995 assert_eq!(*count.lock().unwrap(), -2);
5996 }
5997}
5998
5999use std::cell::RefCell;
6011
6012thread_local! {
6013 static THEME_CONTEXT: RefCell<Option<color::SemanticColors>> = const { RefCell::new(None) };
6015}
6016
6017pub use color::SemanticColors;
6023
6024pub fn set_current_theme(colors: color::SemanticColors) {
6027 THEME_CONTEXT.with(|cell| {
6028 *cell.borrow_mut() = Some(colors);
6029 });
6030}
6031
6032pub fn clear_current_theme() {
6034 THEME_CONTEXT.with(|cell| {
6035 *cell.borrow_mut() = None;
6036 });
6037}
6038
6039pub fn use_theme() -> color::SemanticColors {
6054 THEME_CONTEXT.with(|cell| {
6055 cell.borrow()
6056 .clone()
6057 .unwrap_or_else(color::SemanticColors::dark)
6058 })
6059}
6060
6061pub mod color {
6070 use super::Color;
6071
6072 #[derive(Debug, Clone)]
6089 pub struct SemanticColors {
6090 pub primary: Color,
6092 pub secondary: Color,
6094 pub accent: Color,
6096 pub background: Color,
6098 pub surface: Color,
6100 pub error: Color,
6102 pub warning: Color,
6104 pub success: Color,
6106 pub text: Color,
6108 pub text_dim: Color,
6110 }
6111
6112 impl SemanticColors {
6113 pub fn dark() -> Self {
6115 Self {
6116 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), }
6127 }
6128
6129 pub fn light() -> Self {
6131 Self {
6132 primary: Color::new(0.35, 0.30, 0.70, 1.0),
6133 secondary: Color::new(0.30, 0.50, 0.30, 1.0),
6134 accent: Color::new(0.30, 0.35, 0.75, 1.0),
6135 background: Color::new(0.97, 0.97, 0.98, 1.0),
6136 surface: Color::new(0.93, 0.93, 0.95, 1.0),
6137 error: Color::new(0.75, 0.15, 0.15, 1.0),
6138 warning: Color::new(0.80, 0.60, 0.0, 1.0),
6139 success: Color::new(0.15, 0.65, 0.30, 1.0),
6140 text: Color::new(0.08, 0.08, 0.10, 1.0),
6141 text_dim: Color::new(0.40, 0.40, 0.45, 1.0),
6142 }
6143 }
6144
6145 pub fn accent_states(&self) -> InteractiveColorStates {
6150 InteractiveColorStates::from_color(self.accent)
6151 }
6152
6153 pub fn primary_states(&self) -> InteractiveColorStates {
6155 InteractiveColorStates::from_color(self.primary)
6156 }
6157
6158 pub fn error_states(&self) -> InteractiveColorStates {
6160 InteractiveColorStates::from_color(self.error)
6161 }
6162
6163 pub fn success_states(&self) -> InteractiveColorStates {
6165 InteractiveColorStates::from_color(self.success)
6166 }
6167 }
6168
6169 #[derive(Debug, Clone)]
6174 pub struct InteractiveColorStates {
6175 pub default: Color,
6176 pub hover: Color,
6177 pub active: Color,
6178 pub focus: Color,
6179 pub disabled: Color,
6180 pub focus_ring: Color,
6181 }
6182
6183 impl InteractiveColorStates {
6184 pub fn from_color(base: Color) -> Self {
6193 Self {
6194 default: base,
6195 hover: base.lighten(0.15),
6196 active: base.darken(0.15),
6197 focus: base,
6198 disabled: Color::new(base.r, base.g, base.b, base.a * 0.4),
6199 focus_ring: Color::new(base.r, base.g, base.b, base.a * 0.7),
6200 }
6201 }
6202
6203 pub fn color_for(&self, state: InteractiveState) -> Color {
6205 match state {
6206 InteractiveState::Default => self.default,
6207 InteractiveState::Hover => self.hover,
6208 InteractiveState::Active => self.active,
6209 InteractiveState::Focus => self.focus,
6210 InteractiveState::Disabled => self.disabled,
6211 }
6212 }
6213 }
6214
6215 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6217 pub enum InteractiveState {
6218 Default,
6219 Hover,
6220 Active,
6221 Focus,
6222 Disabled,
6223 }
6224}
6225
6226pub fn use_state<T: Clone + Send + Sync + 'static>(
6245 id: u64,
6246 initial: T,
6247) -> (impl Fn() -> T, impl Fn(T)) {
6248 let already_exists = load_system_state().get_component_state::<T>(id).is_some();
6250 if !already_exists {
6251 update_system_state(|s| {
6252 let mut ns = s.clone();
6253 ns.set_component_state(id, initial.clone());
6254 ns
6255 });
6256 }
6257
6258 let getter = move || -> T {
6259 load_system_state()
6260 .get_component_state::<T>(id)
6261 .map(|arc_lock| {
6262 arc_lock
6263 .read()
6264 .ok()
6265 .map(|guard| (*guard).clone())
6266 .unwrap_or_else(|| initial.clone())
6267 })
6268 .unwrap_or_else(|| initial.clone())
6269 };
6270
6271 let setter = {
6272 move |value| {
6273 update_system_state(|s| {
6274 let mut ns = s.clone();
6275 ns.set_component_state(id, value);
6276 ns
6277 });
6278 }
6279 };
6280
6281 (getter, setter)
6282}
6283
6284pub fn use_state_hash(key: &str) -> u64 {
6296 use std::hash::{Hash, Hasher};
6297 let mut s = std::collections::hash_map::DefaultHasher::new();
6298 key.hash(&mut s);
6299 s.finish()
6300}
6301
6302thread_local! {
6312 static ACCESSIBILITY_PREFS: std::cell::RefCell<AccessibilityPreferences> =
6315 std::cell::RefCell::new(AccessibilityPreferences::default());
6316}
6317
6318#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
6325pub struct AccessibilityPreferences {
6326 pub reduce_motion: bool,
6328 pub reduce_transparency: bool,
6330 pub increase_contrast: bool,
6332}
6333
6334impl AccessibilityPreferences {
6335 pub fn detect_from_system() -> Self {
6340 #[cfg(target_os = "macos")]
6341 {
6342 let reduce_motion = std::process::Command::new("defaults")
6344 .args(["read", "-g", "com.apple.universalaccess", "reduceMotion"])
6345 .output()
6346 .ok()
6347 .and_then(|o| String::from_utf8(o.stdout).ok())
6348 .map(|s| s.trim() == "1")
6349 .unwrap_or(false);
6350
6351 let reduce_transparency = std::process::Command::new("defaults")
6352 .args([
6353 "read",
6354 "-g",
6355 "com.apple.universalaccess",
6356 "reduceTransparency",
6357 ])
6358 .output()
6359 .ok()
6360 .and_then(|o| String::from_utf8(o.stdout).ok())
6361 .map(|s| s.trim() == "1")
6362 .unwrap_or(false);
6363
6364 let increase_contrast = std::process::Command::new("defaults")
6365 .args([
6366 "read",
6367 "-g",
6368 "com.apple.universalaccess",
6369 "increaseContrast",
6370 ])
6371 .output()
6372 .ok()
6373 .and_then(|o| String::from_utf8(o.stdout).ok())
6374 .map(|s| s.trim() == "1")
6375 .unwrap_or(false);
6376
6377 Self {
6378 reduce_motion,
6379 reduce_transparency,
6380 increase_contrast,
6381 }
6382 }
6383 #[cfg(not(target_os = "macos"))]
6384 {
6385 Self::default()
6386 }
6387 }
6388
6389 pub fn min_alpha(&self, requested: f32) -> f32 {
6391 if self.increase_contrast {
6392 requested.max(0.5)
6393 } else {
6394 requested
6395 }
6396 }
6397
6398 pub fn should_disable_glass(&self) -> bool {
6400 self.reduce_transparency
6401 }
6402
6403 pub fn should_reduce_motion(&self) -> bool {
6405 self.reduce_motion
6406 }
6407
6408 pub fn should_increase_contrast(&self) -> bool {
6410 self.increase_contrast
6411 }
6412}
6413
6414pub fn accessibility_preferences() -> AccessibilityPreferences {
6416 ACCESSIBILITY_PREFS.with(|p| *p.borrow())
6417}
6418
6419pub fn set_accessibility_preferences(prefs: AccessibilityPreferences) {
6424 ACCESSIBILITY_PREFS.with(|p| {
6425 *p.borrow_mut() = prefs;
6426 });
6427}
6428
6429pub trait ClipboardProvider: Send + Sync {
6438 fn read_text(&self) -> Option<String>;
6440 fn write_text(&self, text: &str);
6442}
6443
6444#[cfg(not(target_arch = "wasm32"))]
6448pub struct SystemClipboard;
6449
6450#[cfg(not(target_arch = "wasm32"))]
6451impl ClipboardProvider for SystemClipboard {
6452 fn read_text(&self) -> Option<String> {
6453 use std::process::Command;
6454 Command::new("pbpaste")
6456 .output()
6457 .ok()
6458 .and_then(|o| String::from_utf8(o.stdout).ok())
6459 }
6460
6461 fn write_text(&self, text: &str) {
6462 use std::process::Command;
6463 if let Ok(mut child) = Command::new("pbcopy")
6465 .stdin(std::process::Stdio::piped())
6466 .spawn()
6467 {
6468 if let Some(stdin) = child.stdin.as_mut() {
6469 use std::io::Write;
6470 let _ = stdin.write_all(text.as_bytes());
6471 }
6472 let _ = child.wait();
6473 }
6474 }
6475}
6476
6477#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6483pub enum TextDirection {
6484 Forward,
6485 Backward,
6486 Up,
6487 Down,
6488 LineStart,
6489 LineEnd,
6490 WordForward,
6491 WordBackward,
6492}
6493
6494#[derive(Debug, Clone, Default)]
6499pub struct TextInputState {
6500 pub text: String,
6502 pub cursor_pos: usize,
6504 pub selection_anchor: Option<usize>,
6507 pub focused: bool,
6509 pub caret_visible: bool,
6511 pub last_edit_time: f32,
6513}
6514
6515impl TextInputState {
6516 pub fn new(text: impl Into<String>) -> Self {
6518 let text = text.into();
6519 let cursor_pos = text.len();
6520 Self {
6521 text,
6522 cursor_pos,
6523 selection_anchor: None,
6524 focused: false,
6525 caret_visible: true,
6526 last_edit_time: 0.0,
6527 }
6528 }
6529
6530 pub fn selection_range(&self) -> Option<(usize, usize)> {
6533 self.selection_anchor.map(|anchor| {
6534 if anchor <= self.cursor_pos {
6535 (anchor, self.cursor_pos)
6536 } else {
6537 (self.cursor_pos, anchor)
6538 }
6539 })
6540 }
6541
6542 pub fn selected_text(&self) -> String {
6544 self.selection_range()
6545 .map(|(start, end)| self.text[start..end].to_string())
6546 .unwrap_or_default()
6547 }
6548
6549 pub fn insert(&mut self, new_text: &str) {
6551 if let Some((start, end)) = self.selection_range() {
6552 self.text.replace_range(start..end, new_text);
6553 self.cursor_pos = start + new_text.len();
6554 } else {
6555 self.text.insert_str(self.cursor_pos, new_text);
6556 self.cursor_pos += new_text.len();
6557 }
6558 self.selection_anchor = None;
6559 }
6560
6561 pub fn delete(&mut self, backward: bool, count: usize) -> String {
6564 if let Some((start, end)) = self.selection_range() {
6565 let deleted = self.text[start..end].to_string();
6566 self.text.replace_range(start..end, "");
6567 self.cursor_pos = start;
6568 self.selection_anchor = None;
6569 return deleted;
6570 }
6571
6572 if backward && self.cursor_pos > 0 {
6573 let start = self.cursor_pos.saturating_sub(count);
6574 let deleted = self.text[start..self.cursor_pos].to_string();
6575 self.text.replace_range(start..self.cursor_pos, "");
6576 self.cursor_pos = start;
6577 deleted
6578 } else if !backward && self.cursor_pos < self.text.len() {
6579 let end = (self.cursor_pos + count).min(self.text.len());
6580 let deleted = self.text[self.cursor_pos..end].to_string();
6581 self.text.replace_range(self.cursor_pos..end, "");
6582 deleted
6583 } else {
6584 String::new()
6585 }
6586 }
6587
6588 pub fn move_cursor(&mut self, direction: TextDirection, extend_selection: bool) {
6590 if !extend_selection {
6591 self.selection_anchor = None;
6592 } else if self.selection_anchor.is_none() {
6593 self.selection_anchor = Some(self.cursor_pos);
6594 }
6595
6596 match direction {
6597 TextDirection::Forward if self.cursor_pos < self.text.len() => {
6598 let next = self.text[self.cursor_pos..]
6600 .char_indices()
6601 .nth(1)
6602 .map(|(i, _)| self.cursor_pos + i)
6603 .unwrap_or(self.text.len());
6604 self.cursor_pos = next;
6605 }
6606 TextDirection::Backward if self.cursor_pos > 0 => {
6607 let prev = self.text[..self.cursor_pos]
6608 .char_indices()
6609 .next_back()
6610 .map(|(i, _)| i)
6611 .unwrap_or(0);
6612 self.cursor_pos = prev;
6613 }
6614 TextDirection::LineStart => {
6615 self.cursor_pos = 0;
6616 }
6617 TextDirection::LineEnd => {
6618 self.cursor_pos = self.text.len();
6619 }
6620 TextDirection::WordForward => {
6621 let rest = &self.text[self.cursor_pos..];
6623 let after_word = rest
6625 .char_indices()
6626 .find(|(_, c)| !c.is_alphanumeric())
6627 .map(|(i, _)| i)
6628 .unwrap_or(rest.len());
6629 let after_space = rest[after_word..]
6631 .char_indices()
6632 .find(|(_, c)| !c.is_whitespace())
6633 .map(|(i, _)| after_word + i)
6634 .unwrap_or(rest.len());
6635 self.cursor_pos = (self.cursor_pos + after_space).min(self.text.len());
6636 }
6637 TextDirection::WordBackward => {
6638 let before = &self.text[..self.cursor_pos];
6639 let before_word = before
6641 .char_indices()
6642 .rev()
6643 .find(|(_, c)| !c.is_whitespace())
6644 .map(|(i, _)| i)
6645 .unwrap_or(0);
6646 let word_start = before[..before_word]
6648 .char_indices()
6649 .rev()
6650 .find(|(_, c)| !c.is_alphanumeric())
6651 .map(|(i, _)| i)
6652 .unwrap_or(0);
6653 self.cursor_pos = word_start;
6654 }
6655 _ => {} }
6657
6658 if !extend_selection {
6659 self.selection_anchor = None;
6660 }
6661 }
6662
6663 pub fn select_all(&mut self) {
6665 self.cursor_pos = self.text.len();
6666 self.selection_anchor = Some(0);
6667 }
6668
6669 pub fn cursor_byte_pos(&self) -> usize {
6671 self.cursor_pos
6672 }
6673}
6674
6675#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6677pub struct NotificationAction {
6678 pub id: String,
6680 pub title: String,
6682 pub is_destructive: bool,
6684}
6685
6686#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6688pub enum NotificationPriority {
6689 Passive,
6691 #[default]
6693 Active,
6694 TimeSensitive,
6696}
6697
6698#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
6700pub struct Notification {
6701 pub id: String,
6703 pub app_name: Option<String>,
6705 pub title: String,
6707 pub body: String,
6709 pub icon: Option<String>,
6711 pub sound: Option<String>,
6713 pub actions: Vec<NotificationAction>,
6715 pub timeout: Option<f32>,
6717 pub priority: NotificationPriority,
6719 pub timestamp: f32,
6721 pub dismissed: bool,
6723}
6724
6725#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, thiserror::Error)]
6727pub enum NotificationError {
6728 #[error("Notification permission denied")]
6730 PermissionDenied,
6731 #[error("Failed to post notification")]
6733 PostFailed,
6734}
6735
6736#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6738pub enum NotificationPermission {
6739 Granted,
6741 Denied,
6743 #[default]
6745 NotDetermined,
6746}
6747
6748pub trait NotificationHandler: Send + Sync {
6750 fn show(&self, notification: Notification) -> Result<(), NotificationError>;
6752 fn dismiss(&self, id: &str) -> Result<(), NotificationError>;
6754 fn request_permission(&self) -> NotificationPermission;
6756}
6757
6758static NEXT_NOTIFICATION_ID: std::sync::atomic::AtomicUsize =
6759 std::sync::atomic::AtomicUsize::new(1);
6760
6761#[derive(Clone, Copy, Debug, Default)]
6763pub struct DefaultNotificationHandler;
6764
6765impl NotificationHandler for DefaultNotificationHandler {
6766 fn show(&self, notification: Notification) -> Result<(), NotificationError> {
6768 let mut notif = notification;
6769 if notif.id.is_empty() {
6770 let id = NEXT_NOTIFICATION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6771 notif.id = format!("notif_{}", id);
6772 }
6773 update_system_state(|state| {
6774 let mut new_state = state.clone();
6775 new_state.notifications.push(notif.clone());
6776 new_state
6777 });
6778 Ok(())
6779 }
6780
6781 fn dismiss(&self, id: &str) -> Result<(), NotificationError> {
6783 update_system_state(|state| {
6784 let mut new_state = state.clone();
6785 for notif in &mut new_state.notifications {
6786 if notif.id == id {
6787 notif.dismissed = true;
6788 }
6789 }
6790 new_state
6791 });
6792 Ok(())
6793 }
6794
6795 fn request_permission(&self) -> NotificationPermission {
6797 NotificationPermission::Granted
6798 }
6799}
6800
6801static NOTIFICATION_HANDLER: once_cell::sync::OnceCell<std::sync::Arc<dyn NotificationHandler>> =
6802 once_cell::sync::OnceCell::new();
6803
6804pub fn set_notification_handler(handler: std::sync::Arc<dyn NotificationHandler>) {
6806 let _ = NOTIFICATION_HANDLER.set(handler);
6807}
6808
6809pub fn get_notification_handler() -> std::sync::Arc<dyn NotificationHandler> {
6811 NOTIFICATION_HANDLER
6812 .get_or_init(|| std::sync::Arc::new(DefaultNotificationHandler))
6813 .clone()
6814}
6815
6816#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6818pub struct FileFilter {
6819 pub name: String,
6821 pub extensions: Vec<String>,
6823}
6824
6825#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
6827pub enum FileDialogMode {
6828 #[default]
6830 OpenFile,
6831 OpenDirectory,
6833 SaveFile,
6835}
6836
6837#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6839pub struct FileDialog {
6840 pub title: String,
6842 pub default_path: Option<String>,
6844 pub filters: Vec<FileFilter>,
6846 pub mode: FileDialogMode,
6848 pub allow_multiple: bool,
6850}
6851
6852#[derive(Debug, thiserror::Error)]
6854pub enum FileDialogError {
6855 #[error("File dialog cancelled")]
6857 Cancelled,
6858 #[error("I/O error: {0}")]
6860 Io(#[from] std::io::Error),
6861 #[error("Platform error: {0}")]
6863 Platform(String),
6864}
6865
6866impl FileDialog {
6867 pub fn new(mode: FileDialogMode) -> Self {
6869 Self {
6870 mode,
6871 ..Default::default()
6872 }
6873 }
6874
6875 pub fn title(mut self, title: impl Into<String>) -> Self {
6877 self.title = title.into();
6878 self
6879 }
6880
6881 pub fn add_filter(mut self, name: &str, extensions: &[&str]) -> Self {
6883 self.filters.push(FileFilter {
6884 name: name.to_string(),
6885 extensions: extensions.iter().map(|s| s.to_string()).collect(),
6886 });
6887 self
6888 }
6889
6890 pub fn default_path(mut self, path: impl Into<String>) -> Self {
6892 self.default_path = Some(path.into());
6893 self
6894 }
6895
6896 pub fn allow_multiple(mut self, allow: bool) -> Self {
6898 self.allow_multiple = allow;
6899 self
6900 }
6901}
6902
6903#[cfg(not(target_arch = "wasm32"))]
6904impl FileDialog {
6905 pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6907 let mut dialog = rfd::FileDialog::new();
6908 dialog = dialog.set_title(&self.title);
6909 if let Some(path) = &self.default_path {
6910 dialog = dialog.set_directory(path);
6911 }
6912 for filter in &self.filters {
6913 let refs: Vec<&str> = filter.extensions.iter().map(|s| s.as_str()).collect();
6914 dialog = dialog.add_filter(&filter.name, &refs);
6915 }
6916
6917 match self.mode {
6918 FileDialogMode::OpenFile => {
6919 if self.allow_multiple {
6920 dialog.pick_files().ok_or(FileDialogError::Cancelled)
6921 } else {
6922 Ok(dialog.pick_file().into_iter().collect())
6923 }
6924 }
6925 FileDialogMode::OpenDirectory => Ok(dialog.pick_folder().into_iter().collect()),
6926 FileDialogMode::SaveFile => Ok(dialog.save_file().into_iter().collect()),
6927 }
6928 }
6929
6930 pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6932 let results = self.pick()?;
6933 Ok(results.into_iter().next())
6934 }
6935}
6936
6937#[cfg(target_arch = "wasm32")]
6938impl FileDialog {
6939 pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6941 Err(FileDialogError::Platform(
6942 "FileDialog is not supported synchronously on WebAssembly".to_string(),
6943 ))
6944 }
6945
6946 pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6948 Err(FileDialogError::Platform(
6949 "FileDialog is not supported synchronously on WebAssembly".to_string(),
6950 ))
6951 }
6952}
6953
6954#[derive(Debug, thiserror::Error)]
6956pub enum DocumentError {
6957 #[error("I/O error: {0}")]
6959 Io(#[from] std::io::Error),
6960 #[error("Parse error: {0}")]
6962 Parse(String),
6963 #[error("Serialization error: {0}")]
6965 Serialize(String),
6966}
6967
6968pub trait Document: Send + Sync {
6970 fn read_from(path: &std::path::Path) -> Result<Self, DocumentError>
6972 where
6973 Self: Sized;
6974
6975 fn write_to(&self, path: &std::path::Path) -> Result<(), DocumentError>;
6977
6978 fn is_dirty(&self) -> bool;
6980
6981 fn mark_clean(&mut self);
6983}
6984
6985pub struct AutoSaveManager {
6987 pub interval: f32,
6989 pub timer: f32,
6991 pub documents: Vec<(std::path::PathBuf, Box<dyn Document>)>,
6993}
6994
6995impl AutoSaveManager {
6996 pub fn new(interval: f32) -> Self {
6998 Self {
6999 interval,
7000 timer: 0.0,
7001 documents: Vec::new(),
7002 }
7003 }
7004
7005 pub fn register(&mut self, path: std::path::PathBuf, doc: Box<dyn Document>) {
7007 self.documents.push((path, doc));
7008 }
7009
7010 pub fn tick(&mut self, dt: f32) {
7012 self.timer += dt;
7013 if self.timer >= self.interval {
7014 self.timer = 0.0;
7015 for (path, doc) in &mut self.documents {
7016 if doc.is_dirty() {
7017 match doc.write_to(path) {
7018 Ok(()) => {
7019 doc.mark_clean();
7020 log::info!("[AutoSaveManager] Auto-saved document to {:?}", path);
7021 }
7022 Err(e) => {
7023 log::error!(
7024 "[AutoSaveManager] Failed to auto-save document to {:?}: {:?}",
7025 path,
7026 e
7027 );
7028 }
7029 }
7030 }
7031 }
7032 }
7033 }
7034}
7035
7036#[derive(Debug, Clone, PartialEq, Eq, Default)]
7044pub struct Modifiers {
7045 pub cmd: bool,
7047 pub shift: bool,
7049 pub alt: bool,
7051 pub ctrl: bool,
7053}
7054
7055#[derive(Debug, Clone)]
7057pub struct KeyboardShortcut {
7058 pub key: String,
7060 pub modifiers: Modifiers,
7062}
7063
7064impl KeyboardShortcut {
7065 pub fn cmd(key: impl Into<String>) -> Self {
7067 Self {
7068 key: key.into(),
7069 modifiers: Modifiers {
7070 cmd: true,
7071 ..Default::default()
7072 },
7073 }
7074 }
7075
7076 pub fn cmd_shift(key: impl Into<String>) -> Self {
7078 Self {
7079 key: key.into(),
7080 modifiers: Modifiers {
7081 cmd: true,
7082 shift: true,
7083 ..Default::default()
7084 },
7085 }
7086 }
7087}
7088
7089pub enum MenuItem {
7095 Action {
7097 label: String,
7098 shortcut: Option<KeyboardShortcut>,
7099 action: std::sync::Arc<dyn Fn() + Send + Sync>,
7100 enabled: bool,
7101 },
7102 Submenu { label: String, items: Vec<MenuItem> },
7104 Separator,
7106}
7107
7108impl std::fmt::Debug for MenuItem {
7109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7110 match self {
7111 Self::Action { label, enabled, .. } => f
7112 .debug_struct("Action")
7113 .field("label", label)
7114 .field("enabled", enabled)
7115 .finish(),
7116 Self::Submenu { label, items } => f
7117 .debug_struct("Submenu")
7118 .field("label", label)
7119 .field("items", items)
7120 .finish(),
7121 Self::Separator => write!(f, "Separator"),
7122 }
7123 }
7124}
7125
7126pub struct MenuBar {
7131 pub items: Vec<MenuItem>,
7133}
7134
7135impl MenuBar {
7136 pub fn new() -> Self {
7138 Self { items: Vec::new() }
7139 }
7140
7141 pub fn add_item(&mut self, item: MenuItem) {
7143 self.items.push(item);
7144 }
7145
7146 #[allow(clippy::too_many_arguments)]
7158 pub fn standard(
7159 new_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7160 open_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7161 save_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7162 close_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7163 quit_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7164 undo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7165 redo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7166 cut_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7167 copy_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7168 paste_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7169 select_all_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7170 find_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7171 ) -> Self {
7172 let mut bar = Self::new();
7173
7174 bar.add_item(MenuItem::Submenu {
7176 label: "File".to_string(),
7177 items: vec![
7178 MenuItem::Action {
7179 label: "New".to_string(),
7180 shortcut: Some(KeyboardShortcut::cmd("n")),
7181 action: new_fn,
7182 enabled: true,
7183 },
7184 MenuItem::Action {
7185 label: "Open…".to_string(),
7186 shortcut: Some(KeyboardShortcut::cmd("o")),
7187 action: open_fn,
7188 enabled: true,
7189 },
7190 MenuItem::Separator,
7191 MenuItem::Action {
7192 label: "Save".to_string(),
7193 shortcut: Some(KeyboardShortcut::cmd("s")),
7194 action: save_fn,
7195 enabled: true,
7196 },
7197 MenuItem::Separator,
7198 MenuItem::Action {
7199 label: "Close".to_string(),
7200 shortcut: Some(KeyboardShortcut::cmd("w")),
7201 action: close_fn,
7202 enabled: true,
7203 },
7204 MenuItem::Separator,
7205 MenuItem::Action {
7206 label: "Quit".to_string(),
7207 shortcut: Some(KeyboardShortcut::cmd("q")),
7208 action: quit_fn,
7209 enabled: true,
7210 },
7211 ],
7212 });
7213
7214 bar.add_item(MenuItem::Submenu {
7216 label: "Edit".to_string(),
7217 items: vec![
7218 MenuItem::Action {
7219 label: "Undo".to_string(),
7220 shortcut: Some(KeyboardShortcut::cmd("z")),
7221 action: undo_fn,
7222 enabled: true,
7223 },
7224 MenuItem::Action {
7225 label: "Redo".to_string(),
7226 shortcut: Some(KeyboardShortcut::cmd_shift("z")),
7227 action: redo_fn,
7228 enabled: true,
7229 },
7230 MenuItem::Separator,
7231 MenuItem::Action {
7232 label: "Cut".to_string(),
7233 shortcut: Some(KeyboardShortcut::cmd("x")),
7234 action: cut_fn,
7235 enabled: true,
7236 },
7237 MenuItem::Action {
7238 label: "Copy".to_string(),
7239 shortcut: Some(KeyboardShortcut::cmd("c")),
7240 action: copy_fn,
7241 enabled: true,
7242 },
7243 MenuItem::Action {
7244 label: "Paste".to_string(),
7245 shortcut: Some(KeyboardShortcut::cmd("v")),
7246 action: paste_fn,
7247 enabled: true,
7248 },
7249 MenuItem::Separator,
7250 MenuItem::Action {
7251 label: "Select All".to_string(),
7252 shortcut: Some(KeyboardShortcut::cmd("a")),
7253 action: select_all_fn,
7254 enabled: true,
7255 },
7256 MenuItem::Separator,
7257 MenuItem::Action {
7258 label: "Find…".to_string(),
7259 shortcut: Some(KeyboardShortcut::cmd("f")),
7260 action: find_fn,
7261 enabled: true,
7262 },
7263 ],
7264 });
7265
7266 let noop: std::sync::Arc<dyn Fn() + Send + Sync> = std::sync::Arc::new(|| {});
7270 bar.add_item(MenuItem::Submenu {
7271 label: "View".to_string(),
7272 items: vec![
7273 MenuItem::Action {
7274 label: "Zoom In".to_string(),
7275 shortcut: Some(KeyboardShortcut::cmd("=")),
7276 action: noop.clone(),
7277 enabled: true,
7278 },
7279 MenuItem::Action {
7280 label: "Zoom Out".to_string(),
7281 shortcut: Some(KeyboardShortcut::cmd("-")),
7282 action: noop.clone(),
7283 enabled: true,
7284 },
7285 MenuItem::Separator,
7286 MenuItem::Action {
7287 label: "Toggle Fullscreen".to_string(),
7288 shortcut: Some(KeyboardShortcut {
7289 key: "f".to_string(),
7290 modifiers: Modifiers {
7291 ctrl: true,
7292 ..Default::default()
7293 },
7294 }),
7295 action: noop.clone(),
7296 enabled: true,
7297 },
7298 ],
7299 });
7300
7301 bar.add_item(MenuItem::Submenu {
7303 label: "Window".to_string(),
7304 items: vec![
7305 MenuItem::Action {
7306 label: "Minimize".to_string(),
7307 shortcut: Some(KeyboardShortcut::cmd("m")),
7308 action: noop.clone(),
7309 enabled: true,
7310 },
7311 MenuItem::Action {
7312 label: "Zoom".to_string(),
7313 shortcut: None,
7314 action: noop.clone(),
7315 enabled: true,
7316 },
7317 MenuItem::Separator,
7318 MenuItem::Action {
7319 label: "Bring All to Front".to_string(),
7320 shortcut: None,
7321 action: noop.clone(),
7322 enabled: true,
7323 },
7324 ],
7325 });
7326
7327 bar.add_item(MenuItem::Submenu {
7329 label: "Help".to_string(),
7330 items: vec![MenuItem::Action {
7331 label: "Search Help".to_string(),
7332 shortcut: None,
7333 action: noop,
7334 enabled: true,
7335 }],
7336 });
7337
7338 bar
7339 }
7340}
7341
7342impl Default for MenuBar {
7343 fn default() -> Self {
7344 Self::new()
7345 }
7346}
7347
7348use std::sync::RwLock;
7354
7355#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7357pub enum Direction {
7358 #[default]
7359 LTR,
7360 RTL,
7361 Auto,
7362}
7363
7364impl Direction {
7365 pub fn is_rtl(self) -> bool {
7366 matches!(self, Direction::RTL)
7367 }
7368}
7369#[derive(Clone, Debug)]
7370pub struct L10nBundle {
7371 pub locale: String,
7372 pub strings: HashMap<String, String>,
7373 pub is_rtl: bool,
7374}
7375
7376impl L10nBundle {
7377 pub fn new(locale: impl Into<String>) -> Self {
7378 Self {
7379 locale: locale.into(),
7380 strings: HashMap::new(),
7381 is_rtl: false,
7382 }
7383 }
7384
7385 pub fn add(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
7386 self.strings.insert(key.into(), value.into());
7387 self
7388 }
7389
7390 pub fn from_strings_format(locale: impl Into<String>, input: &str) -> Self {
7391 let mut bundle = Self::new(locale);
7392 for line in input.lines() {
7393 let line = line.trim();
7394 if line.is_empty() || line.starts_with("//") {
7395 continue;
7396 }
7397 if let Some(eq_pos) = line.find(" = ") {
7398 let key = line[..eq_pos].trim_matches('"').to_string();
7399 let val = line[eq_pos + 3..]
7400 .trim_end_matches(';')
7401 .trim_matches('"')
7402 .to_string();
7403 bundle.strings.insert(key, val);
7404 }
7405 }
7406 bundle
7407 }
7408 pub fn t(&self, key: &str) -> String {
7410 self.strings
7411 .get(key)
7412 .map(|s| s.to_string())
7413 .unwrap_or_else(|| key.to_string())
7414 }
7415
7416 pub fn tf(&self, key: &str, args: &[&str]) -> String {
7418 let mut result = self.t(key);
7419 for (i, arg) in args.iter().enumerate() {
7420 result = result.replace(&format!("{{{}}}", i), arg);
7421 }
7422 result
7423 }
7424}
7425
7426pub struct L10n {
7428 bundles: HashMap<String, L10nBundle>,
7429 current: String,
7430}
7431
7432impl L10n {
7433 pub fn new(default_locale: &str) -> Self {
7434 Self {
7435 bundles: HashMap::new(),
7436 current: default_locale.to_string(),
7437 }
7438 }
7439
7440 pub fn add_bundle(&mut self, bundle: L10nBundle) {
7441 self.bundles.insert(bundle.locale.clone(), bundle);
7442 }
7443
7444 pub fn set_locale(&mut self, locale: &str) {
7445 self.current = locale.to_string();
7446 }
7447 pub fn current_locale(&self) -> &str {
7448 &self.current
7449 }
7450
7451 pub fn is_rtl(&self) -> bool {
7452 self.bundles
7453 .get(self.current.as_str())
7454 .map(|b| b.is_rtl)
7455 .unwrap_or(false)
7456 }
7457
7458 pub fn t(&self, key: &str) -> String {
7459 self.bundles
7460 .get(self.current.as_str())
7461 .map(|b| b.t(key))
7462 .unwrap_or_else(|| key.to_string())
7463 }
7464
7465 pub fn tf(&self, key: &str, args: &[&str]) -> String {
7466 let mut result = self.t(key);
7467 for (i, arg) in args.iter().enumerate() {
7468 result = result.replace(&format!("{{{}}}", i), arg);
7469 }
7470 result
7471 }
7472
7473 pub fn direction(&self) -> Direction {
7474 if self.is_rtl() {
7475 Direction::RTL
7476 } else {
7477 Direction::LTR
7478 }
7479 }
7480}
7481
7482static L10N: once_cell::sync::Lazy<Arc<RwLock<L10n>>> =
7483 once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(L10n::new("en"))));
7484
7485pub fn init_l10n(l10n: L10n) {
7486 if let Ok(mut guard) = L10N.write() {
7487 *guard = l10n;
7488 }
7489}
7490
7491pub fn l10n() -> Arc<RwLock<L10n>> {
7492 L10N.clone()
7493}
7494
7495pub fn t(key: &str) -> String {
7496 L10N.read()
7497 .map(|g| g.t(key).to_string())
7498 .unwrap_or_else(|_| key.to_string())
7499}
7500
7501pub fn tf(key: &str, args: &[&str]) -> String {
7502 L10N.read()
7503 .map(|g| g.tf(key, args))
7504 .unwrap_or_else(|_| key.to_string())
7505}
7506
7507pub fn set_locale(locale: &str) {
7508 if let Ok(mut guard) = L10N.write() {
7509 guard.set_locale(locale);
7510 }
7511}
7512
7513pub fn current_locale() -> String {
7514 L10N.read()
7515 .map(|g| g.current_locale().to_string())
7516 .unwrap_or_else(|_| "en".to_string())
7517}
7518
7519pub fn is_rtl() -> bool {
7520 L10N.read().map(|g| g.is_rtl()).unwrap_or(false)
7521}
7522
7523#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7535pub enum SystemTheme {
7536 #[default]
7538 Dark,
7539 Light,
7541}
7542
7543pub fn detect_system_theme() -> SystemTheme {
7553 std::env::var("CVKG_THEME")
7554 .ok()
7555 .and_then(|v| match v.as_str() {
7556 "light" => Some(SystemTheme::Light),
7557 "dark" => Some(SystemTheme::Dark),
7558 _ => None,
7559 })
7560 .unwrap_or(SystemTheme::Dark)
7561}
7562
7563pub mod audio_haptic;
7569pub use audio_haptic::{
7570 AudioEngine, HapticEngine, HapticIntensity, NullAudioEngine, NullHapticEngine, haptic_error,
7571 haptic_impact, haptic_selection, haptic_success, play_sound, set_audio_engine,
7572 set_haptic_engine, sounds,
7573};
7574
7575pub mod parallax;
7580pub use parallax::{DisplayEnvironment, ParallaxModifier, PerformanceContract, Tier3Fallback};