1use serde::{Deserialize, Serialize};
35use std::collections::HashMap;
36use std::str::FromStr;
37
38pub mod error_types;
39
40pub mod security;
41
42#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
44pub struct ComponentErrorState {
45 pub has_error: bool,
46 pub error_message: Option<String>,
47 pub error_location: Option<String>,
48}
49impl ComponentErrorState {
50 pub fn clear() -> Self {
51 Self::default()
52 }
53
54 pub fn error(message: impl Into<String>, location: impl Into<String>) -> Self {
55 Self {
56 has_error: true,
57 error_message: Some(message.into()),
58 error_location: Some(location.into()),
59 }
60 }
61}
62
63#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
65pub struct KnowledgeState {
66 pub thoughts: Vec<String>,
67 pub actions: Vec<String>,
68 pub context: HashMap<String, String>,
69 pub last_query_results: Vec<KnowledgeId>,
70 #[serde(alias = "items")]
71 pub fragments: std::collections::HashMap<KnowledgeId, KnowledgeFragment>,
72 pub nodes: Vec<TemporalNode>,
74 pub edges: Vec<TemporalEdge>,
76 pub realm: Realm,
78 pub last_pointer_pos: [f32; 2],
80 pub pointer_velocity: [f32; 2],
82 pub odin_focus: Option<String>,
84 pub agent_attention: HashMap<String, f32>,
86 #[serde(skip)]
88 pub component_states: HashMap<u64, Arc<std::sync::RwLock<dyn std::any::Any + Send + Sync>>>,
89 #[serde(skip)]
91 pub undo_manager: UndoManager,
92 #[serde(default)]
94 pub notifications: Vec<Notification>,
95 #[serde(default)]
97 pub notification_center_visible: bool,
98 #[serde(default)]
100 pub modifiers_shift: bool,
101 #[serde(default)]
103 pub modifiers_ctrl: bool,
104 #[serde(default)]
106 pub modifiers_alt: bool,
107 #[serde(default)]
109 pub modifiers_logo: bool,
110 #[serde(default)]
112 pub performance_overlay_visible: bool,
113}
114
115impl KnowledgeState {
116 pub fn apply_decay(&mut self, decay_factor: f32) {
120 for node in &mut self.nodes {
121 node.weight *= decay_factor;
122 }
123
124 for state in self.component_states.values() {
126 if let Ok(mut lock) = state.write()
127 && let Some(v) = lock.downcast_mut::<f32>()
128 {
129 *v = (*v * decay_factor).max(1.0);
130 }
131 }
132 }
133
134 pub fn reinforce(&mut self, node_ids: &[String], boost: f32) {
136 for node in &mut self.nodes {
137 if node_ids.contains(&node.id) {
138 node.weight += boost;
139 }
140 }
141 }
142
143 pub fn update_pointer(&mut self, new_pos: [f32; 2]) {
145 self.pointer_velocity = [
146 new_pos[0] - self.last_pointer_pos[0],
147 new_pos[1] - self.last_pointer_pos[1],
148 ];
149 self.last_pointer_pos = new_pos;
150 }
151}
152pub type KnowledgeId = String;
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct KnowledgeFragment {
159 pub id: String,
161 pub summary: String,
163 pub source: String,
165 pub created_at: u64,
167 pub accessed_count: u32,
169 pub content: Option<String>,
171}
172
173impl KnowledgeFragment {
174 pub fn new(id: String, summary: String, source: String) -> Self {
175 Self {
176 id,
177 summary,
178 source,
179 created_at: 0,
180 accessed_count: 0,
181 content: None,
182 }
183 }
184}
185
186#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
188pub enum MemoryLayer {
189 Episodic,
191 Semantic,
193 Procedural,
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
201pub enum Realm {
202 Midgard,
203 #[default]
204 Asgard,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct TemporalNode {
210 pub id: String,
212 pub fragment_id: KnowledgeId,
214 pub timestamp: u64,
216 pub layer: MemoryLayer,
218 pub weight: f32,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct TemporalEdge {
225 pub source: String,
227 pub target: String,
229 pub relation: String,
231 pub weight: f32,
233}
234
235pub struct UndoGroup {
237 pub label: String,
239 pub timestamp: f32,
241 pub undo: Arc<dyn Fn() + Send + Sync>,
243 pub redo: Arc<dyn Fn() + Send + Sync>,
245}
246
247impl Clone for UndoGroup {
248 fn clone(&self) -> Self {
250 Self {
251 label: self.label.clone(),
252 timestamp: self.timestamp,
253 undo: Arc::clone(&self.undo),
254 redo: Arc::clone(&self.redo),
255 }
256 }
257}
258
259impl std::fmt::Debug for UndoGroup {
260 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262 f.debug_struct("UndoGroup")
263 .field("label", &self.label)
264 .field("timestamp", &self.timestamp)
265 .finish()
266 }
267}
268
269pub struct UndoManager {
272 stack: Vec<UndoGroup>,
274 position: usize,
276 max_depth: usize,
278 coalesce_window: f32,
280}
281
282impl Default for UndoManager {
283 fn default() -> Self {
285 Self {
286 stack: Vec::new(),
287 position: 0,
288 max_depth: 100,
289 coalesce_window: 0.5,
290 }
291 }
292}
293
294impl Clone for UndoManager {
295 fn clone(&self) -> Self {
297 Self {
298 stack: self.stack.clone(),
299 position: self.position,
300 max_depth: self.max_depth,
301 coalesce_window: self.coalesce_window,
302 }
303 }
304}
305
306impl std::fmt::Debug for UndoManager {
307 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309 f.debug_struct("UndoManager")
310 .field("stack_len", &self.stack.len())
311 .field("position", &self.position)
312 .field("max_depth", &self.max_depth)
313 .field("coalesce_window", &self.coalesce_window)
314 .finish()
315 }
316}
317
318impl UndoManager {
319 pub fn new(max_depth: usize, coalesce_window: f32) -> Self {
321 Self {
322 stack: Vec::new(),
323 position: 0,
324 max_depth,
325 coalesce_window,
326 }
327 }
328
329 pub fn push(
331 &mut self,
332 label: &str,
333 undo: impl Fn() + Send + Sync + 'static,
334 redo: impl Fn() + Send + Sync + 'static,
335 ) {
336 if self.position < self.stack.len() {
337 self.stack.truncate(self.position);
338 }
339
340 let timestamp = std::time::SystemTime::now()
341 .duration_since(std::time::UNIX_EPOCH)
342 .unwrap_or_default()
343 .as_secs_f32();
344
345 self.stack.push(UndoGroup {
346 label: label.to_string(),
347 timestamp,
348 undo: Arc::new(undo),
349 redo: Arc::new(redo),
350 });
351
352 if self.stack.len() > self.max_depth {
353 self.stack.remove(0);
354 }
355 self.position = self.stack.len();
356 }
357
358 pub fn undo(&mut self) -> Option<Arc<dyn Fn() + Send + Sync>> {
361 if self.can_undo() {
362 self.position -= 1;
363 Some(Arc::clone(&self.stack[self.position].undo))
364 } else {
365 None
366 }
367 }
368
369 pub fn redo(&mut self) -> Option<Arc<dyn Fn() + Send + Sync>> {
372 if self.can_redo() {
373 let group = &self.stack[self.position];
374 self.position += 1;
375 Some(Arc::clone(&group.redo))
376 } else {
377 None
378 }
379 }
380
381 pub fn can_undo(&self) -> bool {
383 self.position > 0
384 }
385
386 pub fn can_redo(&self) -> bool {
388 self.position < self.stack.len()
389 }
390
391 pub fn clear(&mut self) {
393 self.stack.clear();
394 self.position = 0;
395 }
396
397 pub fn push_coalesceable(
401 &mut self,
402 label: &str,
403 undo: impl Fn() + Send + Sync + 'static,
404 redo: impl Fn() + Send + Sync + 'static,
405 ) {
406 let now = std::time::SystemTime::now()
407 .duration_since(std::time::UNIX_EPOCH)
408 .unwrap_or_default()
409 .as_secs_f32();
410
411 if self.position == self.stack.len() && !self.stack.is_empty() {
412 let last_idx = self.stack.len() - 1;
413 let last = &self.stack[last_idx];
414 if last.label == label && (now - last.timestamp).abs() <= self.coalesce_window {
415 let old_undo = Arc::clone(&last.undo);
416 let old_redo = Arc::clone(&last.redo);
417 let new_undo = Arc::new(undo);
418 let new_redo = Arc::new(redo);
419
420 self.stack[last_idx].undo = Arc::new(move || {
421 new_undo();
422 old_undo();
423 });
424 self.stack[last_idx].redo = Arc::new(move || {
425 old_redo();
426 new_redo();
427 });
428 self.stack[last_idx].timestamp = now;
429 return;
430 }
431 }
432
433 self.push(label, undo, redo);
434 }
435}
436
437#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
439pub struct WindowId(pub u64);
440
441#[derive(
443 Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default,
444)]
445pub enum WindowLevel {
446 #[default]
448 Normal,
449 AlwaysOnTop,
451 PopUpMenu,
453}
454
455#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
457pub struct WindowConfig {
458 pub title: String,
460 pub size: (f32, f32),
462 pub min_size: Option<(f32, f32)>,
464 pub max_size: Option<(f32, f32)>,
466 pub resizable: bool,
468 pub transparent: bool,
470 pub decorations: bool,
472 pub level: WindowLevel,
474}
475
476impl Default for WindowConfig {
477 fn default() -> Self {
479 Self {
480 title: "CVKG Window".to_string(),
481 size: (800.0, 600.0),
482 min_size: None,
483 max_size: None,
484 resizable: true,
485 transparent: false,
486 decorations: true,
487 level: WindowLevel::Normal,
488 }
489 }
490}
491
492pub trait Window: Send + Sync {
495 fn close(&self);
497 fn set_title(&self, title: &str);
499 fn set_size(&self, width: f32, height: f32);
501 fn is_key(&self) -> bool;
503 fn is_main(&self) -> bool;
505 fn is_visible(&self) -> bool;
507 fn set_visible(&self, visible: bool);
509 fn bring_to_front(&self);
511}
512
513#[derive(Clone)]
515pub struct WindowHandle {
516 pub id: WindowId,
518 pub inner: Arc<dyn Window>,
520}
521
522impl std::fmt::Debug for WindowHandle {
523 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
524 f.debug_struct("WindowHandle")
525 .field("id", &self.id)
526 .finish()
527 }
528}
529
530impl WindowHandle {
531 pub fn new(id: WindowId, inner: Arc<dyn Window>) -> Self {
533 Self { id, inner }
534 }
535 pub fn close(self) {
537 self.inner.close();
538 }
539 pub fn set_title(&self, title: &str) {
541 self.inner.set_title(title);
542 }
543 pub fn set_size(&self, width: f32, height: f32) {
545 self.inner.set_size(width, height);
546 }
547 pub fn is_key(&self) -> bool {
549 self.inner.is_key()
550 }
551 pub fn is_main(&self) -> bool {
553 self.inner.is_main()
554 }
555 pub fn is_visible(&self) -> bool {
557 self.inner.is_visible()
558 }
559 pub fn set_visible(&self, visible: bool) {
561 self.inner.set_visible(visible);
562 }
563 pub fn bring_to_front(&self) {
565 self.inner.bring_to_front();
566 }
567}
568
569#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
571pub enum WindowCloseAction {
572 Allow,
574 Confirm,
576 Deny,
578}
579
580#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
581pub struct AssetKey(pub String);
582
583impl EnvKey for AssetKey {
584 type Value = Arc<dyn AssetManager>;
585 fn default_value() -> Self::Value {
586 Arc::new(DefaultAssetManager::new())
587 }
588}
589
590#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
592pub enum AssetState<T> {
593 Loading,
594 Ready(T),
595 Error(String),
596}
597
598#[derive(Debug, Clone, Serialize, Deserialize)]
600#[serde(untagged)]
601pub enum TokenValue {
602 Single { value: String },
604 Adaptive { light: String, dark: String },
606}
607
608#[derive(Debug, Clone, Serialize, Deserialize)]
610pub struct YggdrasilTokens {
611 pub color: HashMap<String, TokenValue>,
612 pub font: HashMap<String, TokenValue>,
613 pub spacing: HashMap<String, TokenValue>,
614 pub radius: HashMap<String, TokenValue>,
615 pub shadow: HashMap<String, TokenValue>,
616 pub border: HashMap<String, TokenValue>,
617 pub anim: HashMap<String, TokenValue>,
618 pub bifrost: HashMap<String, TokenValue>,
619 pub gungnir: HashMap<String, TokenValue>,
620 pub mjolnir: HashMap<String, TokenValue>,
621 pub accessibility: HashMap<String, TokenValue>,
622}
623
624impl Default for YggdrasilTokens {
625 fn default() -> Self {
626 Self::new()
627 }
628}
629
630impl YggdrasilTokens {
631 pub fn new() -> Self {
632 Self {
633 color: HashMap::new(),
634 font: HashMap::new(),
635 spacing: HashMap::new(),
636 radius: HashMap::new(),
637 shadow: HashMap::new(),
638 border: HashMap::new(),
639 anim: HashMap::new(),
640 bifrost: HashMap::new(),
641 gungnir: HashMap::new(),
642 mjolnir: HashMap::new(),
643 accessibility: HashMap::new(),
644 }
645 }
646
647 pub fn get_color(&self, key: &str, is_dark: bool) -> Option<String> {
649 self.color.get(key).map(|token| match token {
650 TokenValue::Single { value } => value.clone(),
651 TokenValue::Adaptive { light, dark } => {
652 if is_dark {
653 dark.clone()
654 } else {
655 light.clone()
656 }
657 }
658 })
659 }
660
661 pub fn get<T: FromStr>(&self, category: &str, key: &str, is_dark: bool) -> Option<T> {
663 let map = match category {
664 "color" => &self.color,
665 "font" => &self.font,
666 "spacing" => &self.spacing,
667 "radius" => &self.radius,
668 "shadow" => &self.shadow,
669 "border" => &self.border,
670 "anim" => &self.anim,
671 "bifrost" => &self.bifrost,
672 "gungnir" => &self.gungnir,
673 "mjolnir" => &self.mjolnir,
674 "accessibility" => &self.accessibility,
675 _ => return None,
676 };
677
678 map.get(key).and_then(|token| match token {
679 TokenValue::Single { value } => value.parse().ok(),
680 TokenValue::Adaptive { light, dark } => {
681 let value = if is_dark { dark } else { light };
682 value.parse().ok()
683 }
684 })
685 }
686}
687
688pub trait View: Sized + Send {
689 type Body: View;
692
693 fn body(self) -> Self::Body;
694
695 fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
698
699 fn intrinsic_size(&self, _renderer: &mut dyn Renderer, _proposal: SizeProposal) -> Size {
702 Size::ZERO
703 }
704
705 fn layout(&self) -> Option<&dyn layout::LayoutView> {
707 None
708 }
709
710 fn flex_weight(&self) -> f32 {
712 0.0
713 }
714
715 fn get_grid_placement(&self) -> Option<GridPlacement> {
717 None
718 }
719
720 fn modifier<M: ViewModifier>(self, m: M) -> ModifiedView<Self, M> {
722 ModifiedView::new(self, m)
723 }
724
725 fn bifrost(
727 self,
728 blur: f32,
729 saturation: f32,
730 opacity: f32,
731 ) -> ModifiedView<Self, BifrostModifier> {
732 self.modifier(BifrostModifier {
733 blur,
734 saturation,
735 opacity,
736 })
737 }
738
739 fn gungnir(
741 self,
742 color: impl Into<String>,
743 radius: f32,
744 intensity: f32,
745 ) -> ModifiedView<Self, GungnirModifier> {
746 self.modifier(GungnirModifier {
747 color: color.into(),
748 radius,
749 intensity,
750 })
751 }
752
753 fn mjolnir_slice(self, angle: f32, offset: f32) -> ModifiedView<Self, MjolnirSliceModifier> {
755 self.modifier(MjolnirSliceModifier { angle, offset })
756 }
757
758 fn mjolnir_shatter(
760 self,
761 pieces: u32,
762 force: f32,
763 ) -> ModifiedView<Self, MjolnirShatterModifier> {
764 self.modifier(MjolnirShatterModifier { pieces, force })
765 }
766
767 fn bifrost_bridge(self, id: impl Into<String>) -> ModifiedView<Self, BifrostBridgeModifier> {
769 self.modifier(BifrostBridgeModifier { id: id.into() })
770 }
771
772 fn background(self, color: [f32; 4]) -> ModifiedView<Self, BackgroundModifier> {
774 self.modifier(BackgroundModifier { color })
775 }
776
777 fn padding(self, amount: f32) -> ModifiedView<Self, PaddingModifier> {
779 self.modifier(PaddingModifier { amount })
780 }
781
782 fn opacity(self, opacity: f32) -> ModifiedView<Self, OpacityModifier> {
784 self.modifier(OpacityModifier {
785 opacity: opacity.clamp(0.0, 1.0),
786 })
787 }
788
789 fn foreground_color(self, color: [f32; 4]) -> ModifiedView<Self, ForegroundColorModifier> {
791 self.modifier(ForegroundColorModifier { color })
792 }
793
794 fn frame(self, width: Option<f32>, height: Option<f32>) -> ModifiedView<Self, FrameModifier> {
797 self.modifier(FrameModifier {
798 width,
799 height,
800 min_width: None,
801 max_width: None,
802 min_height: None,
803 max_height: None,
804 alignment: Alignment::Center,
805 })
806 }
807
808 fn flex(self, weight: f32) -> ModifiedView<Self, FlexModifier> {
810 self.modifier(FlexModifier { weight })
811 }
812
813 fn grid_placement(self, placement: GridPlacement) -> ModifiedView<Self, GridPlacementModifier> {
815 self.modifier(GridPlacementModifier { placement })
816 }
817
818 fn overlay<O: View + Clone + 'static>(
820 self,
821 overlay: O,
822 alignment: Alignment,
823 offset: [f32; 2],
824 on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
825 ) -> ModifiedView<Self, OverlayModifier> {
826 self.modifier(OverlayModifier {
827 overlay: overlay.erase(),
828 alignment,
829 offset,
830 on_dismiss,
831 })
832 }
833
834 fn safe_area_padding(self) -> ModifiedView<Self, SafeAreaModifier> {
836 self.modifier(SafeAreaModifier { ignores: false })
837 }
838
839 fn ignores_safe_area(self) -> ModifiedView<Self, SafeAreaModifier> {
841 self.modifier(SafeAreaModifier { ignores: true })
842 }
843
844 fn clip_to_bounds(self) -> ModifiedView<Self, ClipModifier> {
846 self.modifier(ClipModifier)
847 }
848
849 fn border(self, color: [f32; 4], width: f32) -> ModifiedView<Self, BorderModifier> {
851 self.modifier(BorderModifier { color, width })
852 }
853
854 fn elevation(self, level: f32) -> ModifiedView<Self, ElevationModifier> {
856 self.modifier(ElevationModifier { level })
857 }
858
859 fn magnetic(self, radius: f32, intensity: f32) -> ModifiedView<Self, MagneticModifier> {
861 self.modifier(MagneticModifier { radius, intensity })
862 }
863
864 fn mani_glow(self, color: [f32; 4], radius: f32) -> ModifiedView<Self, ManiGlowModifier> {
866 self.modifier(ManiGlowModifier { color, radius })
867 }
868
869 fn memory_layer(self, layer: MemoryLayer) -> ModifiedView<Self, BifrostLayerModifier> {
871 self.modifier(BifrostLayerModifier { layer })
872 }
873
874 fn fafnir_evolve(self, id: u64) -> ModifiedView<Self, FafnirModifier> {
876 self.modifier(FafnirModifier { id })
877 }
878
879 fn mimir_intent(self) -> ModifiedView<Self, MimirIntentModifier> {
881 self.modifier(MimirIntentModifier)
882 }
883
884 fn kvasir_vibes(self, complexity: f32) -> ModifiedView<Self, KvasirVibeModifier> {
886 self.modifier(KvasirVibeModifier { complexity })
887 }
888
889 fn odins_eye(self) -> ModifiedView<Self, OdinsEyeModifier> {
891 self.modifier(OdinsEyeModifier)
892 }
893
894 fn on_appear<F: Fn() + Send + Sync + 'static>(
896 self,
897 action: F,
898 ) -> ModifiedView<Self, LifecycleModifier> {
899 self.modifier(LifecycleModifier {
900 on_appear: Some(Arc::new(action)),
901 on_disappear: None,
902 })
903 }
904
905 fn on_disappear<F: Fn() + Send + Sync + 'static>(
907 self,
908 action: F,
909 ) -> ModifiedView<Self, LifecycleModifier> {
910 self.modifier(LifecycleModifier {
911 on_appear: None,
912 on_disappear: Some(Arc::new(action)),
913 })
914 }
915
916 fn on_click<F: Fn() + Send + Sync + 'static>(
918 self,
919 action: F,
920 ) -> ModifiedView<Self, OnClickModifier> {
921 self.modifier(OnClickModifier {
922 action: Arc::new(action),
923 })
924 }
925
926 fn on_pointer_enter<F: Fn() + Send + Sync + 'static>(
928 self,
929 action: F,
930 ) -> ModifiedView<Self, OnPointerEnterModifier> {
931 self.modifier(OnPointerEnterModifier {
932 action: Arc::new(action),
933 })
934 }
935
936 fn on_pointer_leave<F: Fn() + Send + Sync + 'static>(
938 self,
939 action: F,
940 ) -> ModifiedView<Self, OnPointerLeaveModifier> {
941 self.modifier(OnPointerLeaveModifier {
942 action: Arc::new(action),
943 })
944 }
945
946 fn on_pointer_move<F: Fn(f32, f32) + Send + Sync + 'static>(
948 self,
949 action: F,
950 ) -> ModifiedView<Self, OnPointerMoveModifier> {
951 self.modifier(OnPointerMoveModifier {
952 action: Arc::new(action),
953 })
954 }
955
956 fn on_pointer_down<F: Fn() + Send + Sync + 'static>(
958 self,
959 action: F,
960 ) -> ModifiedView<Self, OnPointerDownModifier> {
961 self.modifier(OnPointerDownModifier {
962 action: Arc::new(action),
963 })
964 }
965
966 fn on_pointer_up<F: Fn() + Send + Sync + 'static>(
968 self,
969 action: F,
970 ) -> ModifiedView<Self, OnPointerUpModifier> {
971 self.modifier(OnPointerUpModifier {
972 action: Arc::new(action),
973 })
974 }
975
976 fn erase(self) -> AnyView
978 where
979 Self: Clone + 'static,
980 {
981 AnyView::new(self)
982 }
983}
984
985pub trait ErasedView: Send {
987 fn render_erased(&self, renderer: &mut dyn Renderer, rect: Rect);
988 fn name(&self) -> &'static str;
989 fn flex_weight_erased(&self) -> f32;
990 fn layout_erased(&self) -> Option<&dyn layout::LayoutView>;
991 fn grid_placement_erased(&self) -> Option<GridPlacement>;
992 fn clone_box(&self) -> Box<dyn ErasedView>;
993}
994
995impl<V: View + Clone + 'static> ErasedView for V {
996 fn render_erased(&self, renderer: &mut dyn Renderer, rect: Rect) {
997 self.render(renderer, rect);
998 }
999
1000 fn name(&self) -> &'static str {
1001 std::any::type_name::<V>()
1002 }
1003
1004 fn flex_weight_erased(&self) -> f32 {
1005 self.flex_weight()
1006 }
1007
1008 fn layout_erased(&self) -> Option<&dyn layout::LayoutView> {
1009 self.layout()
1010 }
1011
1012 fn grid_placement_erased(&self) -> Option<GridPlacement> {
1013 self.get_grid_placement()
1014 }
1015
1016 fn clone_box(&self) -> Box<dyn ErasedView> {
1017 Box::new(self.clone())
1018 }
1019}
1020
1021pub struct MemoView<V, F> {
1024 id: u64,
1025 data_hash: u64,
1026 builder: F,
1027 _v: std::marker::PhantomData<V>,
1028}
1029
1030impl<V: View, F: Fn() -> V + Send + Sync> MemoView<V, F> {
1031 pub fn new(id: u64, data_hash: u64, builder: F) -> Self {
1033 Self {
1034 id,
1035 data_hash,
1036 builder,
1037 _v: std::marker::PhantomData,
1038 }
1039 }
1040}
1041
1042impl<V: View + 'static, F: Fn() -> V + Send + Sync + 'static> View for MemoView<V, F> {
1043 type Body = Never;
1044 fn body(self) -> Self::Body {
1045 unreachable!("MemoView does not have a body")
1046 }
1047
1048 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1049 renderer.memoize(self.id, self.data_hash, &|r| {
1050 let view = (self.builder)();
1051 view.render(r, rect);
1052 });
1053 }
1054}
1055
1056pub struct AnyView {
1058 inner: Box<dyn ErasedView>,
1059}
1060
1061impl Clone for AnyView {
1062 fn clone(&self) -> Self {
1063 Self {
1064 inner: self.inner.clone_box(),
1065 }
1066 }
1067}
1068
1069impl AnyView {
1070 pub fn new<V: View + Clone + 'static>(view: V) -> Self {
1071 Self {
1072 inner: Box::new(view),
1073 }
1074 }
1075}
1076
1077impl View for AnyView {
1078 type Body = Never;
1079 fn body(self) -> Self::Body {
1080 unreachable!()
1081 }
1082
1083 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1084 renderer.push_vnode(rect, self.inner.name());
1085 self.inner.render_erased(renderer, rect);
1086 renderer.pop_vnode();
1087 }
1088
1089 fn flex_weight(&self) -> f32 {
1090 self.inner.flex_weight_erased()
1091 }
1092
1093 fn layout(&self) -> Option<&dyn layout::LayoutView> {
1094 self.inner.layout_erased()
1095 }
1096
1097 fn get_grid_placement(&self) -> Option<GridPlacement> {
1098 self.inner.grid_placement_erased()
1099 }
1100}
1101
1102#[derive(Debug, Clone, PartialEq)]
1106pub struct BifrostBridgeModifier {
1107 pub id: String,
1108}
1109
1110impl ViewModifier for BifrostBridgeModifier {
1111 fn modify<V: View>(self, content: V) -> impl View {
1112 ModifiedView::new(content, self)
1113 }
1114
1115 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1116 renderer.register_shared_element(&self.id, rect);
1118 }
1119}
1120
1121#[derive(Debug, Clone, Copy, PartialEq)]
1124pub struct MjolnirSliceModifier {
1125 pub angle: f32,
1126 pub offset: f32,
1127}
1128
1129impl ViewModifier for MjolnirSliceModifier {
1130 fn modify<V: View>(self, content: V) -> impl View {
1131 ModifiedView::new(content, self)
1132 }
1133
1134 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1135 renderer.push_mjolnir_slice(self.angle, self.offset);
1136 }
1137
1138 fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1139 renderer.pop_mjolnir_slice();
1140 }
1141}
1142
1143#[derive(Debug, Clone, Copy, PartialEq)]
1146pub struct MjolnirShatterModifier {
1147 pub pieces: u32,
1148 pub force: f32,
1149}
1150
1151impl ViewModifier for MjolnirShatterModifier {
1152 fn modify<V: View>(self, content: V) -> impl View {
1153 ModifiedView::new(content, self)
1154 }
1155
1156 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1157 let pieces = self.pieces.max(1);
1159 for i in 0..pieces {
1160 let progress = i as f32 / pieces as f32;
1161 let next_progress = (i + 1) as f32 / pieces as f32;
1162
1163 let angle_start = progress * 360.0;
1164 let angle_end = next_progress * 360.0;
1165
1166 renderer.push_mjolnir_slice(angle_start, 0.0);
1168 renderer.push_mjolnir_slice(angle_end + 180.0, 0.0);
1169
1170 let mid_angle = (angle_start + angle_end) / 2.0;
1172 let rad = mid_angle.to_radians();
1173 let dx = rad.cos() * self.force;
1174 let dy = rad.sin() * self.force;
1175
1176 let shard_rect = Rect {
1177 x: rect.x + dx,
1178 y: rect.y + dy,
1179 ..rect
1180 };
1181
1182 view.render(renderer, shard_rect);
1183
1184 renderer.pop_mjolnir_slice();
1185 renderer.pop_mjolnir_slice();
1186 }
1187 }
1188}
1189
1190#[derive(Debug, Clone, Copy, PartialEq)]
1193pub struct BifrostModifier {
1194 pub blur: f32,
1195 pub saturation: f32,
1196 pub opacity: f32,
1197}
1198
1199impl ViewModifier for BifrostModifier {
1200 fn modify<V: View>(self, content: V) -> impl View {
1201 ModifiedView::new(content, self)
1202 }
1203
1204 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1205 if renderer.is_over_budget() {
1206 renderer.bifrost(rect, self.blur * 0.5, self.saturation, self.opacity);
1208 } else {
1209 renderer.bifrost(rect, self.blur, self.saturation, self.opacity);
1210 }
1211 }
1212}
1213
1214#[derive(Debug, Clone, Copy, PartialEq)]
1216pub struct BackgroundModifier {
1217 pub color: [f32; 4],
1218}
1219
1220impl ViewModifier for BackgroundModifier {
1221 fn modify<V: View>(self, content: V) -> impl View {
1222 ModifiedView::new(content, self)
1223 }
1224
1225 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1226 renderer.fill_rect(rect, self.color);
1227 }
1228}
1229
1230#[derive(Debug, Clone, Copy, PartialEq)]
1232pub struct PaddingModifier {
1233 pub amount: f32,
1234}
1235
1236impl ViewModifier for PaddingModifier {
1237 fn modify<V: View>(self, content: V) -> impl View {
1238 ModifiedView::new(content, self)
1239 }
1240
1241 fn transform_rect(&self, rect: Rect) -> Rect {
1242 Rect {
1243 x: rect.x + self.amount,
1244 y: rect.y + self.amount,
1245 width: (rect.width - 2.0 * self.amount).max(0.0),
1246 height: (rect.height - 2.0 * self.amount).max(0.0),
1247 }
1248 }
1249
1250 fn transform_proposal(&self, mut proposal: SizeProposal) -> SizeProposal {
1251 if let Some(w) = proposal.width {
1252 proposal.width = Some((w - 2.0 * self.amount).max(0.0));
1253 }
1254 if let Some(h) = proposal.height {
1255 proposal.height = Some((h - 2.0 * self.amount).max(0.0));
1256 }
1257 proposal
1258 }
1259
1260 fn transform_size(&self, mut size: Size) -> Size {
1261 size.width += 2.0 * self.amount;
1262 size.height += 2.0 * self.amount;
1263 size
1264 }
1265}
1266
1267#[derive(Debug, Clone, PartialEq)]
1270pub struct GungnirModifier {
1271 pub color: String,
1272 pub radius: f32,
1273 pub intensity: f32,
1274}
1275
1276impl ViewModifier for GungnirModifier {
1277 fn modify<V: View>(self, content: V) -> impl View {
1278 ModifiedView::new(content, self)
1279 }
1280
1281 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1282 renderer.stroke_rect(rect, [0.0, 1.0, 1.0, self.intensity], self.radius / 10.0);
1284 }
1285}
1286
1287#[derive(Debug, Clone, Copy, PartialEq)]
1289pub struct GungnirPulseModifier {
1290 pub color: [f32; 4],
1291 pub radius: f32,
1292 pub speed: f32,
1293}
1294
1295impl ViewModifier for GungnirPulseModifier {
1296 fn modify<V: View>(self, content: V) -> impl View {
1297 ModifiedView::new(content, self)
1298 }
1299
1300 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1301 let time = std::time::SystemTime::now()
1302 .duration_since(std::time::UNIX_EPOCH)
1303 .unwrap_or_default()
1304 .as_secs_f32();
1305
1306 let intensity = (time * self.speed).sin() * 0.5 + 0.5;
1309 let mut color = self.color;
1310 color[3] *= intensity;
1311
1312 renderer.stroke_rect(rect, color, self.radius);
1314 }
1315}
1316
1317#[derive(Debug, Clone, Copy, PartialEq)]
1319pub struct MagneticModifier {
1320 pub radius: f32,
1321 pub intensity: f32,
1322}
1323
1324impl ViewModifier for MagneticModifier {
1325 fn modify<V: View>(self, content: V) -> impl View {
1326 ModifiedView::new(content, self)
1327 }
1328
1329 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1330 let [px, py] = renderer.get_pointer_position();
1331 let center_x = rect.x + rect.width / 2.0;
1332 let center_y = rect.y + rect.height / 2.0;
1333
1334 let dx = px - center_x;
1335 let dy = py - center_y;
1336 let dist = (dx * dx + dy * dy).sqrt();
1337
1338 let mut offset_x = 0.0;
1339 let mut offset_y = 0.0;
1340
1341 if dist < self.radius && dist > 0.0 {
1342 let force = (1.0 - dist / self.radius) * self.intensity;
1343 offset_x = dx * force;
1344 offset_y = dy * force;
1345 }
1346
1347 let magnetic_rect = Rect {
1348 x: rect.x + offset_x,
1349 y: rect.y + offset_y,
1350 ..rect
1351 };
1352
1353 view.render(renderer, magnetic_rect);
1354 }
1355}
1356
1357#[derive(Debug, Clone, Copy, PartialEq)]
1360pub struct ManiGlowModifier {
1361 pub color: [f32; 4],
1362 pub radius: f32,
1363}
1364
1365impl ViewModifier for ManiGlowModifier {
1366 fn modify<V: View>(self, content: V) -> impl View {
1367 ModifiedView::new(content, self)
1368 }
1369
1370 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1371 if crate::load_system_state().realm == Realm::Asgard {
1372 renderer.mani_glow(rect, self.color, self.radius);
1373 }
1374 view.render(renderer, rect);
1375 }
1376}
1377
1378#[derive(Debug, Clone, Copy, PartialEq)]
1383pub struct BifrostLayerModifier {
1384 pub layer: MemoryLayer,
1385}
1386
1387impl ViewModifier for BifrostLayerModifier {
1388 fn modify<V: View>(self, content: V) -> impl View {
1389 ModifiedView::new(content, self)
1390 }
1391
1392 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1393 let realm = crate::load_system_state().realm;
1394 match self.layer {
1395 MemoryLayer::Episodic => {
1396 if realm == Realm::Asgard {
1397 renderer.bifrost(rect, 40.0, 1.2, 0.7);
1398 } else {
1399 renderer.fill_rect(rect, [0.1, 0.12, 0.15, 0.8]);
1400 }
1401 }
1402 MemoryLayer::Semantic => {
1403 if realm == Realm::Asgard {
1404 renderer.gungnir(rect, [1.0, 0.84, 0.0, 1.0], 15.0, 0.6);
1405 } else {
1406 renderer.stroke_rect(rect, [0.4, 0.4, 0.4, 1.0], 1.5);
1407 }
1408 }
1409 MemoryLayer::Procedural => {
1410 renderer.fill_rect(rect, [0.05, 0.05, 0.07, 0.95]);
1411 let stroke_color = if realm == Realm::Asgard {
1412 [0.3, 0.3, 0.3, 1.0]
1413 } else {
1414 [0.2, 0.2, 0.2, 1.0]
1415 };
1416 renderer.stroke_rect(rect, stroke_color, 2.0);
1417 }
1418 }
1419 view.render(renderer, rect);
1420 }
1421}
1422
1423#[derive(Debug, Clone, Copy, PartialEq)]
1427pub struct FafnirModifier {
1428 pub id: u64,
1430}
1431
1432impl ViewModifier for FafnirModifier {
1433 fn modify<V: View>(self, content: V) -> impl View {
1434 ModifiedView::new(content, self)
1435 }
1436
1437 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1438 let state = crate::load_system_state();
1439 let vitality = state
1440 .get_component_state::<f32>(self.id)
1441 .map(|v| *v.read().unwrap())
1442 .unwrap_or(1.0);
1443
1444 let growth = (vitality - 1.0).clamp(0.0, 4.0);
1447 let scale = 1.0 + growth * 0.12;
1448 let glow_intensity = growth * 0.25;
1449
1450 let id = self.id;
1452 renderer.register_handler(
1453 "pointermove",
1454 std::sync::Arc::new(move |_| {
1455 crate::update_system_state(|s| {
1456 let mut s = s.clone();
1457 let v = s
1458 .get_component_state::<f32>(id)
1459 .map(|v| *v.read().unwrap())
1460 .unwrap_or(1.0);
1461 s.set_component_state(id, (v + 0.05).min(5.0)); s
1463 });
1464 }),
1465 );
1466
1467 if scale > 1.01 {
1468 renderer.push_transform([0.0, 0.0], [scale, scale], 0.0);
1469 }
1470
1471 if glow_intensity > 0.1 && state.realm == Realm::Asgard {
1472 renderer.gungnir(rect, [1.0, 0.84, 0.0, 1.0], 15.0 * vitality, glow_intensity);
1473 }
1474
1475 view.render(renderer, rect);
1476
1477 if scale > 1.01 {
1478 renderer.pop_transform();
1479 }
1480 }
1481}
1482
1483#[derive(Debug, Clone, Copy, PartialEq)]
1485pub struct MimirIntentModifier;
1486
1487impl ViewModifier for MimirIntentModifier {
1488 fn modify<V: View>(self, content: V) -> impl View {
1489 ModifiedView::new(content, self)
1490 }
1491
1492 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1493 let state = crate::load_system_state();
1494 let pos = state.last_pointer_pos;
1495 let vel = state.pointer_velocity;
1496
1497 let center = [rect.x + rect.width / 2.0, rect.y + rect.height / 2.0];
1499 let dx = center[0] - pos[0];
1500 let dy = center[1] - pos[1];
1501
1502 let dot = vel[0] * dx + vel[1] * dy;
1504 let speed_sq = vel[0] * vel[0] + vel[1] * vel[1];
1505 let dist_sq = dx * dx + dy * dy;
1506
1507 if dot > 0.0 && dist_sq < 250.0 * 250.0 && speed_sq > 0.5 && state.realm == Realm::Asgard {
1508 let intent_strength = (dot / (speed_sq.sqrt() * dist_sq.sqrt())).clamp(0.0, 1.0);
1510 renderer.stroke_rect(rect, [0.0, 0.9, 1.0, 0.3 * intent_strength], 1.5);
1511 }
1512
1513 view.render(renderer, rect);
1514 }
1515}
1516
1517#[derive(Debug, Clone, Copy, PartialEq)]
1519pub struct KvasirVibeModifier {
1520 pub complexity: f32,
1521}
1522
1523impl ViewModifier for KvasirVibeModifier {
1524 fn modify<V: View>(self, content: V) -> impl View {
1525 ModifiedView::new(content, self)
1526 }
1527
1528 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1529 if crate::load_system_state().realm == Realm::Asgard {
1530 let t = renderer.elapsed_time();
1531 let c = self.complexity.clamp(0.0, 1.0);
1532
1533 let blur = 20.0 + c * 40.0;
1536 let turbulence_x = (t * (1.0 + c * 2.0)).sin() * 8.0 * c;
1537 let turbulence_y = (t * (0.8 + c * 1.5)).cos() * 5.0 * c;
1538 renderer.bifrost(
1539 rect.offset(turbulence_x, turbulence_y),
1540 blur,
1541 0.8 + c * 0.4,
1542 0.25,
1543 );
1544
1545 if c > 0.2 {
1547 let pulse = (t * (3.0 + c * 5.0)).sin().abs() * c;
1548 let color = [0.0, 0.9, 1.0, 0.4 * pulse]; renderer.gungnir(rect, color, 12.0 + c * 24.0, 0.6 * pulse);
1550 }
1551
1552 if c > 0.7 {
1554 let instability = (t * 15.0).cos().abs() * (c - 0.7) * 3.3;
1555 let warning_color = [1.0, 0.0, 0.4, 0.12 * instability];
1556 renderer.fill_rect(rect, warning_color);
1557 renderer.stroke_rect(rect, [1.0, 0.0, 0.2, 0.45 * instability], 1.8);
1558 }
1559 }
1560 view.render(renderer, rect);
1561 }
1562}
1563
1564#[derive(Debug, Clone, Copy, PartialEq)]
1566pub struct OdinsEyeModifier;
1567
1568impl ViewModifier for OdinsEyeModifier {
1569 fn modify<V: View>(self, content: V) -> impl View {
1570 ModifiedView::new(content, self)
1571 }
1572
1573 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1574 let state = crate::load_system_state();
1575 let t = renderer.elapsed_time();
1576
1577 view.render(renderer, rect);
1579
1580 if state.realm == Realm::Asgard {
1581 let eye_pulse = (t * 0.5).sin().abs() * 0.05;
1584 renderer.draw_radial_gradient(
1585 rect,
1586 [0.0, 0.6, 0.8, 0.08 + eye_pulse], [0.0, 0.0, 0.0, 0.0], );
1589
1590 let hugin_rect = Rect {
1592 x: rect.x + 20.0,
1593 y: rect.y + 40.0,
1594 width: 200.0,
1595 height: rect.height - 80.0,
1596 };
1597 renderer.draw_text(
1598 "HUGIN: THOUGHT",
1599 hugin_rect.x,
1600 hugin_rect.y,
1601 10.0,
1602 [0.0, 1.0, 1.0, 0.6],
1603 );
1604 for (i, thought) in state.thoughts.iter().rev().take(10).enumerate() {
1605 renderer.draw_text(
1606 thought,
1607 hugin_rect.x,
1608 hugin_rect.y + 20.0 + i as f32 * 14.0,
1609 9.0,
1610 [1.0, 1.0, 1.0, 0.4],
1611 );
1612 }
1613
1614 let munin_rect = Rect {
1616 x: rect.x + rect.width - 220.0,
1617 y: rect.y + 40.0,
1618 width: 200.0,
1619 height: rect.height - 80.0,
1620 };
1621 renderer.draw_text(
1622 "MUNIN: MEMORY",
1623 munin_rect.x,
1624 munin_rect.y,
1625 10.0,
1626 [1.0, 0.84, 0.0, 0.6],
1627 );
1628 for (i, node) in state.nodes.iter().take(10).enumerate() {
1629 let opacity = (node.weight.min(1.0)) * 0.5;
1630 renderer.draw_text(
1631 &node.id,
1632 munin_rect.x,
1633 munin_rect.y + 20.0 + i as f32 * 14.0,
1634 9.0,
1635 [1.0, 1.0, 1.0, opacity],
1636 );
1637 }
1638
1639 if let Some(focus_id) = &state.odin_focus {
1641 renderer.draw_text(
1643 &format!("EYE FOCUS: {}", focus_id),
1644 rect.x + rect.width / 2.0 - 50.0,
1645 rect.y + 20.0,
1646 12.0,
1647 [0.0, 1.0, 1.0, 0.8],
1648 );
1649
1650 renderer.gungnir(
1653 Rect {
1654 x: rect.x + rect.width / 2.0 - 1.0,
1655 y: rect.y,
1656 width: 2.0,
1657 height: rect.height,
1658 },
1659 [0.0, 1.0, 1.0, 1.0],
1660 20.0,
1661 0.4,
1662 );
1663 }
1664 }
1665 }
1666}
1667
1668#[derive(Debug, Clone, Copy, PartialEq)]
1670pub struct SleipnirParams {
1671 pub stiffness: f32,
1672 pub damping: f32,
1673 pub mass: f32,
1674}
1675
1676impl SleipnirParams {
1677 pub fn snappy() -> Self {
1678 Self {
1679 stiffness: 230.0,
1680 damping: 22.0,
1681 mass: 1.0,
1682 }
1683 }
1684 pub fn fluid() -> Self {
1685 Self {
1686 stiffness: 170.0,
1687 damping: 26.0,
1688 mass: 1.0,
1689 }
1690 }
1691 pub fn heavy() -> Self {
1692 Self {
1693 stiffness: 90.0,
1694 damping: 20.0,
1695 mass: 1.0,
1696 }
1697 }
1698 pub fn bouncy() -> Self {
1699 Self {
1700 stiffness: 190.0,
1701 damping: 14.0,
1702 mass: 1.0,
1703 }
1704 }
1705}
1706
1707impl Default for SleipnirParams {
1708 fn default() -> Self {
1709 Self::fluid()
1710 }
1711}
1712
1713#[derive(Debug, Clone, Copy, PartialEq)]
1714struct SolverState {
1715 x: f32,
1716 v: f32,
1717}
1718
1719#[derive(Debug, Clone, Copy, PartialEq)]
1722pub struct SleipnirSolver {
1723 params: SleipnirParams,
1724 target: f32,
1725 state: SolverState,
1726}
1727
1728impl SleipnirSolver {
1729 pub fn new(params: SleipnirParams, target: f32, current: f32) -> Self {
1731 Self {
1732 params,
1733 target,
1734 state: SolverState { x: current, v: 0.0 },
1735 }
1736 }
1737
1738 pub fn tick(&mut self, dt: f32) -> f32 {
1740 if dt <= 0.0 {
1741 return self.state.x;
1742 }
1743
1744 let mut remaining = dt;
1746 let step = 1.0 / 120.0;
1747
1748 while remaining > 0.0 {
1749 let d = remaining.min(step);
1750 self.step(d);
1751 remaining -= d;
1752 }
1753
1754 self.state.x
1755 }
1756
1757 fn step(&mut self, dt: f32) {
1758 let a = self.evaluate(self.state, 0.0, SolverState { x: 0.0, v: 0.0 });
1759 let b = self.evaluate(self.state, dt * 0.5, a);
1760 let c = self.evaluate(self.state, dt * 0.5, b);
1761 let d = self.evaluate(self.state, dt, c);
1762
1763 let dxdt = 1.0 / 6.0 * (a.x + 2.0 * (b.x + c.x) + d.x);
1764 let dvdt = 1.0 / 6.0 * (a.v + 2.0 * (b.v + c.v) + d.v);
1765
1766 self.state.x += dxdt * dt;
1767 self.state.v += dvdt * dt;
1768 }
1769
1770 fn evaluate(&self, initial: SolverState, dt: f32, d: SolverState) -> SolverState {
1771 let state = SolverState {
1772 x: initial.x + d.x * dt,
1773 v: initial.v + d.v * dt,
1774 };
1775 let force =
1776 -self.params.stiffness * (state.x - self.target) - self.params.damping * state.v;
1777 let mass = self.params.mass.max(0.001);
1778 SolverState {
1779 x: state.v,
1780 v: force / mass,
1781 }
1782 }
1783
1784 pub fn is_settled(&self) -> bool {
1785 (self.state.x - self.target).abs() < 0.001 && self.state.v.abs() < 0.001
1786 }
1787
1788 pub fn set_target(&mut self, target: f32) {
1789 self.target = target;
1790 }
1791
1792 pub fn current_value(&self) -> f32 {
1793 self.state.x
1794 }
1795}
1796
1797#[derive(Debug, Clone, PartialEq)]
1799pub struct SleipnirModifier {
1800 pub id: u64,
1801 pub target: f32,
1802 pub params: SleipnirParams,
1803}
1804
1805impl ViewModifier for SleipnirModifier {
1806 fn modify<V: View>(self, content: V) -> impl View {
1807 ModifiedView::new(content, self)
1808 }
1809
1810 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1811 let state = load_system_state();
1812
1813 let solver_lock_opt = state.get_component_state::<SleipnirSolver>(self.id);
1815
1816 let current_val;
1817
1818 if let Some(lock) = solver_lock_opt {
1819 let mut solver = lock.write().unwrap();
1821 solver.set_target(self.target);
1822 current_val = solver.tick(renderer.delta_time());
1823
1824 if !solver.is_settled() {
1826 renderer.request_redraw();
1827 }
1828 } else {
1829 let solver = SleipnirSolver::new(
1831 self.params,
1832 self.target,
1833 self.target, );
1835
1836 get_system_state().rcu(|old| {
1838 let mut new_state = (**old).clone();
1839 new_state.set_component_state(self.id, solver);
1840 new_state
1841 });
1842
1843 current_val = self.target;
1844 }
1845
1846 renderer.push_transform([0.0, current_val], [1.0, 1.0], 0.0);
1848 view.render(renderer, rect);
1849 renderer.pop_transform();
1850 }
1851}
1852
1853#[derive(Debug, Clone, Copy, PartialEq)]
1856pub struct TransformModifier {
1857 pub translation: [f32; 2],
1858 pub scale: [f32; 2],
1859 pub rotation: f32,
1860}
1861
1862impl Default for TransformModifier {
1863 fn default() -> Self {
1864 Self::new()
1865 }
1866}
1867
1868impl TransformModifier {
1869 pub fn new() -> Self {
1870 Self {
1871 translation: [0.0, 0.0],
1872 scale: [1.0, 1.0],
1873 rotation: 0.0,
1874 }
1875 }
1876
1877 pub fn translate(mut self, x: f32, y: f32) -> Self {
1878 self.translation = [x, y];
1879 self
1880 }
1881
1882 pub fn scale(mut self, x: f32, y: f32) -> Self {
1883 self.scale = [x, y];
1884 self
1885 }
1886
1887 pub fn rotate(mut self, radians: f32) -> Self {
1888 self.rotation = radians;
1889 self
1890 }
1891}
1892
1893impl ViewModifier for TransformModifier {
1894 fn modify<V: View>(self, content: V) -> impl View {
1895 ModifiedView::new(content, self)
1896 }
1897
1898 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1899 renderer.push_transform(self.translation, self.scale, self.rotation);
1900 view.render(renderer, rect);
1901 renderer.pop_transform();
1902 }
1903}
1904
1905#[derive(Clone)]
1908pub struct LifecycleModifier {
1909 pub on_appear: Option<Arc<dyn Fn() + Send + Sync>>,
1910 pub on_disappear: Option<Arc<dyn Fn() + Send + Sync>>,
1911}
1912
1913impl ViewModifier for LifecycleModifier {
1914 fn modify<V: View>(self, content: V) -> impl View {
1915 ModifiedView::new(content, self)
1916 }
1917}
1918
1919#[derive(Debug, Clone, Copy, PartialEq)]
1922pub struct OpacityModifier {
1923 pub opacity: f32,
1924}
1925
1926impl ViewModifier for OpacityModifier {
1927 fn modify<V: View>(self, content: V) -> impl View {
1928 ModifiedView::new(content, self)
1929 }
1930
1931 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1932 renderer.push_opacity(self.opacity);
1933 }
1934
1935 fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1936 renderer.pop_opacity();
1937 }
1938}
1939
1940#[derive(Clone)]
1942pub struct OnClickModifier {
1943 pub action: Arc<dyn Fn() + Send + Sync>,
1944}
1945
1946impl ViewModifier for OnClickModifier {
1947 fn modify<V: View>(self, content: V) -> impl View {
1948 ModifiedView::new(content, self)
1949 }
1950
1951 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1952 let action = self.action.clone();
1953 renderer.register_handler(
1954 "pointerclick",
1955 std::sync::Arc::new(move |event| {
1956 if let Event::PointerClick { .. } = event {
1957 (action)();
1958 }
1959 }),
1960 );
1961 }
1962}
1963
1964#[derive(Clone)]
1966pub struct OnPointerEnterModifier {
1967 pub action: Arc<dyn Fn() + Send + Sync>,
1968}
1969
1970impl ViewModifier for OnPointerEnterModifier {
1971 fn modify<V: View>(self, content: V) -> impl View {
1972 ModifiedView::new(content, self)
1973 }
1974
1975 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1976 let action = self.action.clone();
1977 renderer.register_handler(
1978 "pointerenter",
1979 std::sync::Arc::new(move |event| {
1980 if let Event::PointerEnter = event {
1981 (action)();
1982 }
1983 }),
1984 );
1985 }
1986}
1987
1988#[derive(Clone)]
1990pub struct OnPointerLeaveModifier {
1991 pub action: Arc<dyn Fn() + Send + Sync>,
1992}
1993
1994impl ViewModifier for OnPointerLeaveModifier {
1995 fn modify<V: View>(self, content: V) -> impl View {
1996 ModifiedView::new(content, self)
1997 }
1998
1999 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2000 let action = self.action.clone();
2001 renderer.register_handler(
2002 "pointerleave",
2003 std::sync::Arc::new(move |event| {
2004 if let Event::PointerLeave = event {
2005 (action)();
2006 }
2007 }),
2008 );
2009 }
2010}
2011
2012#[derive(Clone)]
2014pub struct OnPointerMoveModifier {
2015 pub action: Arc<dyn Fn(f32, f32) + Send + Sync>,
2016}
2017
2018impl ViewModifier for OnPointerMoveModifier {
2019 fn modify<V: View>(self, content: V) -> impl View {
2020 ModifiedView::new(content, self)
2021 }
2022
2023 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2024 let action = self.action.clone();
2025 renderer.register_handler(
2026 "pointermove",
2027 std::sync::Arc::new(move |event| {
2028 if let Event::PointerMove { x, y, .. } = event {
2029 (action)(x, y);
2030 }
2031 }),
2032 );
2033 }
2034}
2035
2036#[derive(Clone)]
2038pub struct OnPointerDownModifier {
2039 pub action: Arc<dyn Fn() + Send + Sync>,
2040}
2041
2042impl ViewModifier for OnPointerDownModifier {
2043 fn modify<V: View>(self, content: V) -> impl View {
2044 ModifiedView::new(content, self)
2045 }
2046
2047 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2048 let action = self.action.clone();
2049 renderer.register_handler(
2050 "pointerdown",
2051 std::sync::Arc::new(move |event| {
2052 if let Event::PointerDown { .. } = event {
2053 (action)();
2054 }
2055 }),
2056 );
2057 }
2058}
2059
2060#[derive(Clone)]
2062pub struct OnPointerUpModifier {
2063 pub action: Arc<dyn Fn() + Send + Sync>,
2064}
2065
2066impl ViewModifier for OnPointerUpModifier {
2067 fn modify<V: View>(self, content: V) -> impl View {
2068 ModifiedView::new(content, self)
2069 }
2070
2071 fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2072 let action = self.action.clone();
2073 renderer.register_handler(
2074 "pointerup",
2075 std::sync::Arc::new(move |event| {
2076 if let Event::PointerUp { .. } = event {
2077 (action)();
2078 }
2079 }),
2080 );
2081 }
2082}
2083
2084#[derive(Debug, Clone, Copy, PartialEq)]
2087pub struct ForegroundColorModifier {
2088 pub color: [f32; 4],
2089}
2090
2091impl ViewModifier for ForegroundColorModifier {
2092 fn modify<V: View>(self, content: V) -> impl View {
2093 ModifiedView::new(content, self)
2094 }
2095}
2096
2097#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2100pub struct ClipModifier;
2101
2102impl ViewModifier for ClipModifier {
2103 fn modify<V: View>(self, content: V) -> impl View {
2104 ModifiedView::new(content, self)
2105 }
2106
2107 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2108 renderer.push_clip_rect(rect);
2109 }
2110
2111 fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
2112 renderer.pop_clip_rect();
2113 }
2114}
2115
2116#[derive(Debug, Clone, Copy, PartialEq)]
2118pub struct BorderModifier {
2119 pub color: [f32; 4],
2120 pub width: f32,
2121}
2122
2123impl ViewModifier for BorderModifier {
2124 fn modify<V: View>(self, content: V) -> impl View {
2125 ModifiedView::new(content, self)
2126 }
2127
2128 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2129 renderer.stroke_rect(rect, self.color, self.width);
2130 }
2131}
2132
2133#[doc(hidden)]
2135pub enum Never {}
2136
2137impl View for Never {
2138 type Body = Never;
2139 fn body(self) -> Never {
2140 unreachable!()
2141 }
2142}
2143
2144#[derive(Debug, Clone, Copy, Default)]
2146pub struct EmptyView;
2147
2148impl View for EmptyView {
2149 type Body = Never;
2150 fn body(self) -> Self::Body {
2151 unreachable!()
2152 }
2153 fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2154 fn intrinsic_size(&self, _renderer: &mut dyn Renderer, _proposal: SizeProposal) -> Size {
2155 Size {
2156 width: 0.0,
2157 height: 0.0,
2158 }
2159 }
2160}
2161
2162#[derive(Clone)]
2165pub struct ModifiedView<V, M> {
2166 view: V,
2167 modifier: M,
2168}
2169
2170impl<V: View, M: ViewModifier> ModifiedView<V, M> {
2171 #[doc(hidden)]
2172 pub fn new(view: V, modifier: M) -> Self {
2173 Self { view, modifier }
2174 }
2175}
2176
2177impl<V: View, M: ViewModifier> View for ModifiedView<V, M> {
2178 type Body = ModifiedView<V::Body, M>;
2179
2180 fn body(self) -> Self::Body {
2181 ModifiedView {
2182 view: self.view.body(),
2183 modifier: self.modifier.clone(),
2184 }
2185 }
2186
2187 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
2188 self.modifier.render_view(&self.view, renderer, rect);
2189 }
2190
2191 fn intrinsic_size(&self, renderer: &mut dyn Renderer, proposal: SizeProposal) -> Size {
2192 self.modifier.measure_view(&self.view, renderer, proposal)
2193 }
2194
2195 fn flex_weight(&self) -> f32 {
2196 self.modifier.child_flex_weight(&self.view)
2197 }
2198
2199 fn layout(&self) -> Option<&dyn layout::LayoutView> {
2200 self.modifier.layout().or_else(|| self.view.layout())
2201 }
2202
2203 fn get_grid_placement(&self) -> Option<GridPlacement> {
2204 self.modifier
2205 .get_grid_placement()
2206 .or_else(|| self.view.get_grid_placement())
2207 }
2208}
2209
2210pub trait ViewModifier: Send + Clone {
2211 fn modify<V: View>(self, content: V) -> impl View;
2212
2213 fn get_grid_placement(&self) -> Option<GridPlacement> {
2215 None
2216 }
2217
2218 fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2220
2221 fn post_render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2223
2224 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2227 self.render(renderer, rect);
2228 let child_rect = self.transform_rect(rect);
2229 view.render(renderer, child_rect);
2230 self.post_render(renderer, rect);
2231 }
2232
2233 fn transform_rect(&self, rect: Rect) -> Rect {
2234 rect
2235 }
2236
2237 fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
2239 proposal
2240 }
2241
2242 fn transform_size(&self, size: Size) -> Size {
2244 size
2245 }
2246
2247 fn measure_view<V: View>(
2249 &self,
2250 view: &V,
2251 renderer: &mut dyn Renderer,
2252 proposal: SizeProposal,
2253 ) -> Size {
2254 let child_proposal = self.transform_proposal(proposal);
2255 let child_size = view.intrinsic_size(renderer, child_proposal);
2256 self.transform_size(child_size)
2257 }
2258
2259 fn child_flex_weight<V: View>(&self, view: &V) -> f32 {
2261 view.flex_weight()
2262 }
2263
2264 fn layout(&self) -> Option<&dyn layout::LayoutView> {
2265 None
2266 }
2267}
2268
2269#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
2271pub struct TelemetryData {
2272 pub frame_time_ms: f32,
2273 pub p99_frame_time_ms: f32,
2275 pub frame_jitter_ms: f32,
2277 pub hardware_stall_detected: bool,
2279
2280 pub input_time_ms: f32,
2282 pub state_flush_time_ms: f32,
2283 pub layout_time_ms: f32,
2284 pub draw_time_ms: f32,
2285 pub gpu_submit_time_ms: f32,
2286
2287 pub draw_calls: u32,
2288 pub vertices: u32,
2289
2290 pub berserker_rage: f32,
2292
2293 pub vram_usage_mb: f32,
2295 pub vram_textures_mb: f32,
2296 pub vram_buffers_mb: f32,
2297 pub vram_pipelines_mb: f32,
2298 pub vram_exhausted: bool,
2300}
2301
2302#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2304pub struct FrameBudget {
2305 pub target_ms: f32,
2307 pub allow_degradation: bool,
2310}
2311
2312impl Default for FrameBudget {
2313 fn default() -> Self {
2314 Self {
2315 target_ms: 16.0,
2316 allow_degradation: true,
2317 }
2318 }
2319}
2320
2321pub trait ElapsedTime {
2329 fn elapsed_time(&self) -> f32;
2331
2332 fn delta_time(&self) -> f32;
2334}
2335
2336pub trait Renderer: ElapsedTime + Send {
2343 fn request_redraw(&mut self) {}
2346
2347 fn is_over_budget(&self) -> bool {
2350 false
2351 }
2352
2353 fn fill_rect(&mut self, rect: Rect, color: [f32; 4]);
2355 fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]);
2356 fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]);
2358
2359 fn draw_3d_cube(&mut self, _rect: Rect, _color: [f32; 4], _rotation: [f32; 3]) {}
2362
2363 fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
2365 fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32);
2366 fn stroke_ellipse(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
2368 fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: [f32; 4], stroke_width: f32);
2370 fn fill_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4]) {}
2372 fn stroke_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4], _stroke_width: f32) {}
2374
2375 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]);
2377 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32);
2379
2380 fn shape_rich_text(
2381 &mut self,
2382 _spans: &[cvkg_runic_text::TextSpan],
2383 _max_width: Option<f32>,
2384 _align: cvkg_runic_text::TextAlign,
2385 _overflow: cvkg_runic_text::TextOverflow,
2386 ) -> Option<cvkg_runic_text::ShapedText> {
2387 None
2388 }
2389
2390 fn draw_shaped_text(&mut self, _text: &cvkg_runic_text::ShapedText, _x: f32, _y: f32) {}
2391
2392 fn draw_texture(&mut self, _texture_id: u32, _rect: Rect) {}
2395 fn draw_image(&mut self, _image_name: &str, _rect: Rect) {}
2397 fn load_image(&mut self, _name: &str, _data: &[u8]) {}
2399 fn prewarm_vram(&mut self, _assets: Vec<(String, Vec<u8>)>) {}
2402
2403 fn get_pointer_position(&self) -> [f32; 2] {
2405 [0.0, 0.0]
2406 }
2407
2408 fn upload_data_texture(&mut self, _id: &str, _data: &[f32], _width: u32, _height: u32) {}
2411 fn draw_heatmap(&mut self, _texture_id: &str, _rect: Rect, _palette: &str) {}
2413
2414 fn draw_mesh(&mut self, _mesh: &Mesh, _color: [f32; 4], _transform: glam::Mat4) {}
2417
2418 fn draw_linear_gradient(
2421 &mut self,
2422 _rect: Rect,
2423 _start_color: [f32; 4],
2424 _end_color: [f32; 4],
2425 _angle: f32,
2426 ) {
2427 }
2428 fn draw_radial_gradient(
2430 &mut self,
2431 _rect: Rect,
2432 _inner_color: [f32; 4],
2433 _outer_color: [f32; 4],
2434 ) {
2435 }
2436 fn draw_drop_shadow(
2438 &mut self,
2439 _rect: Rect,
2440 _radius: f32,
2441 _color: [f32; 4],
2442 _blur: f32,
2443 _spread: f32,
2444 ) {
2445 }
2446 fn stroke_dashed_rounded_rect(
2448 &mut self,
2449 _rect: Rect,
2450 _radius: f32,
2451 _color: [f32; 4],
2452 _width: f32,
2453 _dash: f32,
2454 _gap: f32,
2455 ) {
2456 }
2457 fn draw_9slice(
2459 &mut self,
2460 _image_name: &str,
2461 _rect: Rect,
2462 _left: f32,
2463 _top: f32,
2464 _right: f32,
2465 _bottom: f32,
2466 ) {
2467 }
2468
2469 fn push_clip_rect(&mut self, _rect: Rect) {}
2473 fn pop_clip_rect(&mut self) {}
2475 fn current_clip_rect(&self) -> Rect {
2478 Rect::new(-10000.0, -10000.0, 20000.0, 20000.0)
2479 }
2480
2481 fn push_opacity(&mut self, _opacity: f32) {}
2485 fn pop_opacity(&mut self) {}
2487
2488 fn set_theme(&mut self, _theme: ColorTheme) {}
2490 fn set_rage(&mut self, _rage: f32) {}
2491 fn set_berserker_mode(&mut self, _state: BerserkerMode) {}
2492 fn trigger_shatter_event(&mut self, _origin: [f32; 2], _force: f32) {}
2493 fn set_scene(&mut self, _scene: &str) {}
2495
2496 fn capture_png(&mut self) -> Vec<u8> {
2499 Vec::new()
2500 }
2501 fn print(&mut self) {}
2503
2504 fn set_scene_preset(&mut self, _preset: u32) {}
2505
2506 fn bifrost(&mut self, _rect: Rect, _blur: f32, _saturation: f32, _opacity: f32) {}
2509 fn gungnir(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32, _intensity: f32) {}
2511 fn mani_glow(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32) {}
2513 fn push_mjolnir_slice(&mut self, _angle: f32, _offset: f32) {}
2515 fn pop_mjolnir_slice(&mut self) {}
2516 fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer));
2520 fn mjolnir_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2522 fn mjolnir_fluid_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2523 fn draw_mjolnir_bolt(&mut self, _from: [f32; 2], _to: [f32; 2], _color: [f32; 4]) {}
2525
2526 fn set_aria_role(&mut self, _role: &str) {}
2528 fn set_aria_label(&mut self, _label: &str) {}
2529
2530 fn register_shared_element(&mut self, _id: &str, _rect: Rect) {}
2532
2533 fn set_key(&mut self, _key: &str) {}
2535
2536 fn get_telemetry(&self) -> TelemetryData {
2539 TelemetryData::default()
2540 }
2541
2542 fn push_shadow(&mut self, _radius: f32, _color: [f32; 4], _offset: [f32; 2]) {}
2545 fn pop_shadow(&mut self) {}
2547
2548 fn push_vnode(&mut self, _rect: Rect, _name: &'static str) {}
2551 fn pop_vnode(&mut self) {}
2553 fn register_handler(
2555 &mut self,
2556 _event_type: &str,
2557 _handler: std::sync::Arc<dyn Fn(Event) + Send + Sync>,
2558 ) {
2559 }
2560
2561 fn set_z_index(&mut self, _z: f32) {}
2565 fn get_z_index(&self) -> f32 {
2567 0.0
2568 }
2569
2570 fn load_svg(&mut self, _name: &str, _svg_data: &[u8]) {}
2573 fn draw_svg(&mut self, _name: &str, _rect: Rect) {}
2575 fn serialize_svg(&mut self, _name: &str) -> Result<String, String> {
2579 Err("SVG serialization not supported by this renderer".into())
2580 }
2581 fn apply_svg_filter(
2585 &mut self,
2586 _name: &str,
2587 _filter_id: &str,
2588 _region: Rect,
2589 ) -> Result<String, String> {
2590 Err("SVG filter not supported by this renderer".into())
2591 }
2592
2593 fn push_transform(&mut self, _translation: [f32; 2], _scale: [f32; 2], _rotation: f32) {}
2598 fn push_affine(&mut self, _transform: [f32; 6]) {}
2601 fn pop_transform(&mut self) {}
2603 fn query_layout(&self, _node_id: scene_graph::NodeId) -> Option<Rect> {
2605 None
2606 }
2607 fn set_debug_layout(&mut self, _enabled: bool) {}
2609 fn get_debug_layout(&self) -> bool {
2611 false
2612 }
2613
2614 fn set_material(&mut self, _material: crate::material::DrawMaterial) {}
2618 fn current_material(&self) -> crate::material::DrawMaterial {
2620 crate::material::DrawMaterial::Opaque
2621 }
2622
2623 fn mimir_intent(&self) -> [f32; 2] {
2626 [0.0, 0.0]
2627 }
2628 fn magnetic_warp(&self, pointer: [f32; 2], anchor_rect: Rect, strength: f32) -> [f32; 2] {
2630 if strength <= 0.0 {
2631 return pointer;
2632 }
2633 let cx = anchor_rect.x + anchor_rect.width / 2.0;
2634 let cy = anchor_rect.y + anchor_rect.height / 2.0;
2635 let dx = pointer[0] - cx;
2636 let dy = pointer[1] - cy;
2637 let dist = (dx * dx + dy * dy).sqrt();
2638 let radius = 120.0;
2639 if dist < radius && dist > 0.0 {
2640 let force = (1.0 - dist / radius) * strength;
2641 [pointer[0] - dx * force, pointer[1] - dy * force]
2642 } else {
2643 pointer
2644 }
2645 }
2646 fn mani_glow_intensity(&self, pointer: [f32; 2], bounds: Rect, radius: f32) -> f32 {
2648 let cx = bounds.x + bounds.width / 2.0;
2649 let cy = bounds.y + bounds.height / 2.0;
2650 let dist = ((pointer[0] - cx).powi(2) + (pointer[1] - cy).powi(2)).sqrt();
2651 if dist < radius {
2652 (1.0 - dist / radius).clamp(0.0, 1.0)
2653 } else {
2654 0.0
2655 }
2656 }
2657 fn fafnir_evolve(&self, pointer: [f32; 2], bounds: Rect, max_scale: f32) -> f32 {
2659 let prox = self.mani_glow_intensity(pointer, bounds, 120.0);
2660 1.0 + (max_scale - 1.0) * prox
2661 }
2662 fn set_sdf_shape(&mut self, _shape: crate::layout::SdfShape) {}
2664}
2665
2666pub mod accessibility {
2668 pub fn relative_luminance(color: [f32; 4]) -> f32 {
2670 let f = |c: f32| {
2671 if c <= 0.03928 {
2672 c / 12.92
2673 } else {
2674 ((c + 0.055) / 1.055).powf(2.4)
2675 }
2676 };
2677 0.2126 * f(color[0]) + 0.7152 * f(color[1]) + 0.0722 * f(color[2])
2678 }
2679
2680 pub fn contrast_ratio(c1: [f32; 4], c2: [f32; 4]) -> f32 {
2682 let l1 = relative_luminance(c1);
2683 let l2 = relative_luminance(c2);
2684 let (light, dark) = if l1 > l2 { (l1, l2) } else { (l2, l1) };
2685 (light + 0.05) / (dark + 0.05)
2686 }
2687}
2688#[derive(
2690 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
2691)]
2692pub enum RenderTier {
2693 Tier1GPU = 0,
2695 Tier2GPU = 1,
2697 Tier3Fallback = 2,
2699}
2700use bytemuck::{Pod, Zeroable};
2704#[repr(C)]
2706#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
2707pub struct ColorTheme {
2708 pub primary_neon: [f32; 4], pub shatter_neon: [f32; 4],
2710 pub glass_base: [f32; 4],
2711 pub glass_edge: [f32; 4],
2712 pub rune_glow: [f32; 4],
2713 pub ember_core: [f32; 4],
2714 pub background_deep: [f32; 4],
2715 pub mani_glow: [f32; 4], pub glass_blur_strength: f32,
2717 pub shatter_edge_width: f32,
2718 pub neon_bloom_radius: f32,
2719 pub rune_opacity: f32,
2720}
2721impl ColorTheme {
2722 pub fn asgard() -> Self {
2724 Self {
2725 primary_neon: [0.0, 1.0, 0.95, 1.2],
2726 shatter_neon: [1.0, 0.0, 0.75, 1.5],
2727 glass_base: [0.04, 0.04, 0.06, 0.82],
2728 glass_edge: [0.0, 0.45, 0.55, 0.6],
2729 rune_glow: [0.75, 0.98, 1.0, 0.9],
2730 ember_core: [0.95, 0.12, 0.12, 1.0],
2731 background_deep: [0.01, 0.01, 0.03, 1.0],
2732 mani_glow: [0.7, 0.9, 1.0, 0.05],
2733 glass_blur_strength: 0.6,
2734 shatter_edge_width: 1.8,
2735 neon_bloom_radius: 0.022,
2736 rune_opacity: 0.55,
2737 }
2738 }
2739
2740 pub fn midgard() -> Self {
2742 Self {
2743 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],
2749 background_deep: [0.05, 0.05, 0.07, 1.0],
2750 mani_glow: [0.0, 0.0, 0.0, 0.0], glass_blur_strength: 0.0, shatter_edge_width: 1.0,
2753 neon_bloom_radius: 0.0,
2754 rune_opacity: 0.0,
2755 }
2756 }
2757
2758 pub fn cyberpunk_viking() -> Self {
2759 Self::asgard()
2760 }
2761 pub fn vibrant_glass() -> Self {
2762 Self {
2763 primary_neon: [0.0, 1.0, 0.95, 1.2],
2764 shatter_neon: [1.0, 0.0, 0.75, 1.5],
2765 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],
2768 ember_core: [1.0, 0.4, 0.1, 1.0],
2769 background_deep: [0.05, 0.05, 0.1, 1.0],
2770 mani_glow: [0.7, 0.9, 1.0, 0.05],
2771 glass_blur_strength: 0.9,
2772 shatter_edge_width: 1.8,
2773 neon_bloom_radius: 0.022,
2774 rune_opacity: 0.55,
2775 }
2776 }
2777}
2778impl Default for ColorTheme {
2779 fn default() -> Self {
2780 Self::vibrant_glass()
2781 }
2782}
2783#[repr(C)]
2785#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
2786pub struct SceneUniforms {
2787 pub view: glam::Mat4,
2788 pub proj: glam::Mat4,
2789 pub time: f32,
2790 pub delta_time: f32,
2791 pub resolution: [f32; 2],
2792 pub mouse: [f32; 2],
2793 pub mouse_velocity: [f32; 2],
2794 pub shatter_origin: [f32; 2],
2795 pub shatter_time: f32,
2796 pub shatter_force: f32,
2797 pub berzerker_rage: f32,
2798 pub berzerker_mode: u32,
2799 pub scroll_offset: f32,
2800 pub scale_factor: f32,
2801 pub scene_type: u32,
2802 pub _pad: [f32; 3], }
2804
2805pub const SCENE_AURORA: u32 = 0;
2806pub const SCENE_VOID: u32 = 1;
2807pub const SCENE_NEBULA: u32 = 2;
2808pub const SCENE_GLITCH: u32 = 3;
2809pub const SCENE_YGGDRASIL: u32 = 4;
2810
2811impl SceneUniforms {
2812 pub fn new(width: f32, height: f32) -> Self {
2813 Self {
2814 view: glam::Mat4::IDENTITY,
2815 proj: glam::Mat4::orthographic_lh(0.0, width, height, 0.0, -100.0, 100.0),
2816 time: 0.0,
2817 delta_time: 0.016,
2818 resolution: [width, height],
2819 mouse: [0.5, 0.5],
2820 mouse_velocity: [0.0, 0.0],
2821 shatter_origin: [0.5, 0.5],
2822 shatter_time: -100.0,
2823 shatter_force: 0.0,
2824 berzerker_rage: 0.0,
2825 berzerker_mode: 0,
2826 scroll_offset: 0.0,
2827 scale_factor: 1.0,
2828 scene_type: SCENE_AURORA,
2829 _pad: [0.0; 3],
2830 }
2831 }
2832}
2833#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2835pub struct Mesh {
2836 pub vertices: Vec<[f32; 3]>,
2837 pub normals: Vec<[f32; 3]>,
2838 pub indices: Vec<u32>,
2839}
2840impl Mesh {
2841 pub fn from_obj(data: &[u8]) -> anyhow::Result<Vec<Self>> {
2842 let mut cursor = std::io::Cursor::new(data);
2843 let (models, _) = tobj::load_obj_buf(&mut cursor, &tobj::LoadOptions::default(), |_| {
2844 Ok((Vec::new(), Default::default()))
2845 })?;
2846 let mut meshes = Vec::new();
2847 for m in models {
2848 let mesh = m.mesh;
2849 let vertices: Vec<[f32; 3]> = mesh
2850 .positions
2851 .chunks(3)
2852 .map(|c| [c[0], c[1], c[2]])
2853 .collect();
2854 let normals = if mesh.normals.is_empty() {
2855 vec![[0.0, 0.0, 1.0]; vertices.len()]
2856 } else {
2857 mesh.normals.chunks(3).map(|c| [c[0], c[1], c[2]]).collect()
2858 };
2859 meshes.push(Mesh {
2860 vertices,
2861 normals,
2862 indices: mesh.indices,
2863 });
2864 }
2865 Ok(meshes)
2866 }
2867 pub fn from_stl(data: &[u8]) -> anyhow::Result<Self> {
2868 let mut cursor = std::io::Cursor::new(data);
2869 let stl = stl_io::read_stl(&mut cursor)?;
2870 let vertices: Vec<[f32; 3]> = stl.vertices.iter().map(|v| [v[0], v[1], v[2]]).collect();
2871 let mut indices = Vec::new();
2872 for face in stl.faces {
2873 indices.push(face.vertices[0] as u32);
2874 indices.push(face.vertices[1] as u32);
2875 indices.push(face.vertices[2] as u32);
2876 }
2877 let normals = vec![[0.0, 0.0, 1.0]; vertices.len()];
2878 Ok(Mesh {
2879 vertices,
2880 normals,
2881 indices,
2882 })
2883 }
2884}
2885pub trait FrameRenderer<E = ()>: Renderer {
2888 fn begin_frame(&mut self) -> E;
2889 fn render_frame(&mut self) {
2890 }
2892 fn end_frame(&mut self, encoder: E);
2893}
2894use std::sync::Arc;
2895type SubscriberList<T> = Arc<std::sync::Mutex<Vec<Box<dyn Fn(&T) + Send + Sync>>>>;
2896#[derive(Clone)]
2898pub struct State<T: Clone + Send + Sync + 'static> {
2899 swap: Arc<arc_swap::ArcSwap<T>>,
2900 metadata_swap: Arc<arc_swap::ArcSwap<Option<agents::MutationMetadata>>>,
2901 #[cfg(not(target_arch = "wasm32"))]
2902 tvar: Arc<stm::TVar<T>>,
2903 #[cfg(not(target_arch = "wasm32"))]
2904 metadata_tvar: Arc<stm::TVar<Option<agents::MutationMetadata>>>,
2905 subscribers: SubscriberList<T>,
2906 version: Arc<std::sync::atomic::AtomicU64>,
2907 resolution: agents::ConflictResolution,
2908}
2909impl<T: Clone + Send + Sync + 'static> State<T> {
2910 pub fn new(value: T) -> Self {
2912 #[cfg(not(target_arch = "wasm32"))]
2913 let tvar = Arc::new(stm::TVar::new(value.clone()));
2914 #[cfg(not(target_arch = "wasm32"))]
2915 let metadata_tvar = Arc::new(stm::TVar::new(None));
2916 Self {
2917 swap: Arc::new(arc_swap::ArcSwap::from_pointee(value)),
2918 metadata_swap: Arc::new(arc_swap::ArcSwap::new(Arc::new(None))),
2919 #[cfg(not(target_arch = "wasm32"))]
2920 tvar,
2921 #[cfg(not(target_arch = "wasm32"))]
2922 metadata_tvar,
2923 subscribers: Arc::new(std::sync::Mutex::new(Vec::new())),
2924 version: Arc::new(std::sync::atomic::AtomicU64::new(0)),
2925 resolution: agents::ConflictResolution::default(),
2926 }
2927 }
2928 pub fn with_resolution(mut self, resolution: agents::ConflictResolution) -> Self {
2930 self.resolution = resolution;
2931 self
2932 }
2933 pub fn get(&self) -> T {
2935 (**self.swap.load()).clone()
2936 }
2937 pub fn set(&self, value: T) {
2939 #[cfg(not(target_arch = "wasm32"))]
2940 let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
2941 let new_meta = agents::get_current_mutation_metadata();
2942 let existing_meta = self.metadata_tvar.read(tx)?;
2943 let mut skip = false;
2944 if self.resolution == agents::ConflictResolution::PriorityWins
2945 && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
2946 && new_m.priority < old_m.priority
2947 {
2948 skip = true;
2949 }
2950 if !skip {
2951 self.tvar.write(tx, value.clone())?;
2952 self.metadata_tvar.write(tx, new_meta)?;
2953 Ok((false, value.clone(), new_meta))
2954 } else {
2955 Ok((true, self.tvar.read(tx)?, existing_meta))
2956 }
2957 });
2958 #[cfg(target_arch = "wasm32")]
2959 let (was_skipped, final_val, final_meta) =
2960 (false, value, agents::get_current_mutation_metadata());
2961 if was_skipped {
2962 if let (Some(new_m), Some(old_m)) =
2963 (agents::get_current_mutation_metadata(), final_meta)
2964 {
2965 agents::notify_conflict(agents::ConflictEvent {
2966 agent_id: new_m.agent_id,
2967 priority: new_m.priority,
2968 existing_agent_id: old_m.agent_id,
2969 existing_priority: old_m.priority,
2970 timestamp_ms: new_m.timestamp_ms,
2971 });
2972 }
2973 return;
2974 }
2975 self.swap.store(Arc::new(final_val.clone()));
2976 self.metadata_swap.store(Arc::new(final_meta));
2977 self.version
2978 .fetch_add(1, std::sync::atomic::Ordering::Release);
2979 let subs = Arc::clone(&self.subscribers);
2980 if crate::is_batching() {
2981 crate::enqueue_batch_task(Box::new(move || {
2982 let s = subs.lock().unwrap();
2983 for cb in s.iter() {
2984 cb(&final_val);
2985 }
2986 }));
2987 } else {
2988 let s = subs.lock().unwrap();
2989 for cb in s.iter() {
2990 cb(&final_val);
2991 }
2992 }
2993 }
2994 pub fn mutate<F: Fn(&T) -> T>(&self, f: F) {
2995 #[cfg(not(target_arch = "wasm32"))]
2996 {
2997 let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
2998 let new_meta = agents::get_current_mutation_metadata();
2999 let existing_meta = self.metadata_tvar.read(tx)?;
3000 let mut skip = false;
3001 if self.resolution == agents::ConflictResolution::PriorityWins
3002 && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3003 && new_m.priority < old_m.priority
3004 {
3005 skip = true;
3006 }
3007 if !skip {
3008 let current = self.tvar.read(tx)?;
3009 let next = f(¤t);
3010 self.tvar.write(tx, next.clone())?;
3011 self.metadata_tvar.write(tx, new_meta)?;
3012 Ok((false, next, new_meta))
3013 } else {
3014 Ok((true, self.tvar.read(tx)?, existing_meta))
3015 }
3016 });
3017 if was_skipped {
3018 if let (Some(new_m), Some(old_m)) =
3019 (agents::get_current_mutation_metadata(), final_meta)
3020 {
3021 agents::notify_conflict(agents::ConflictEvent {
3022 agent_id: new_m.agent_id,
3023 priority: new_m.priority,
3024 existing_agent_id: old_m.agent_id,
3025 existing_priority: old_m.priority,
3026 timestamp_ms: new_m.timestamp_ms,
3027 });
3028 }
3029 return;
3030 }
3031 self.swap.store(Arc::new(final_val.clone()));
3032 self.metadata_swap.store(Arc::new(final_meta));
3033 self.version
3034 .fetch_add(1, std::sync::atomic::Ordering::Release);
3035 let subs = Arc::clone(&self.subscribers);
3036 if crate::is_batching() {
3037 crate::enqueue_batch_task(Box::new(move || {
3038 let s = subs.lock().unwrap();
3039 for cb in s.iter() {
3040 cb(&final_val);
3041 }
3042 }));
3043 } else {
3044 let s = subs.lock().unwrap();
3045 for cb in s.iter() {
3046 cb(&final_val);
3047 }
3048 }
3049 }
3050 #[cfg(target_arch = "wasm32")]
3051 {
3052 self.set(f(&self.get()));
3053 }
3054 }
3055 pub fn version(&self) -> u64 {
3057 self.version.load(std::sync::atomic::Ordering::Acquire)
3058 }
3059 pub fn subscribe<F: Fn(&T) + Send + Sync + 'static>(&self, callback: F) {
3061 self.subscribers.lock().unwrap().push(Box::new(callback));
3062 }
3063}
3064use crate::runtime::NodeStateSnapshot;
3065use std::sync::OnceLock;
3066use std::sync::atomic::{AtomicBool, Ordering};
3067pub static SYSTEM_STATE: OnceLock<Arc<arc_swap::ArcSwap<KnowledgeState>>> = OnceLock::new();
3069#[cfg(not(target_arch = "wasm32"))]
3070static KNOWLEDGE_TVAR: OnceLock<stm::TVar<KnowledgeState>> = OnceLock::new();
3071static IS_BATCHING: AtomicBool = AtomicBool::new(false);
3072pub static IS_RENDERING: AtomicBool = AtomicBool::new(false);
3073pub static LAYOUT_DIRTY: AtomicBool = AtomicBool::new(false);
3074type BatchQueue = OnceLock<std::sync::Mutex<Vec<Box<dyn FnOnce() + Send + Sync>>>>;
3075static BATCH_QUEUE: BatchQueue = OnceLock::new();
3076static STATE_WRITE_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
3079pub fn is_batching() -> bool {
3081 IS_BATCHING.load(Ordering::Acquire)
3082}
3083pub fn is_rendering() -> bool {
3085 IS_RENDERING.load(Ordering::Acquire)
3086}
3087pub fn begin_render_phase() {
3089 IS_RENDERING.store(true, Ordering::Release);
3090}
3091pub fn end_render_phase() {
3093 IS_RENDERING.store(false, Ordering::Release);
3094}
3095pub fn enqueue_batch_task(task: Box<dyn FnOnce() + Send + Sync>) {
3097 let mut queue = BATCH_QUEUE
3098 .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3099 .lock()
3100 .unwrap();
3101 queue.push(task);
3102}
3103pub fn batch<F: FnOnce()>(f: F) {
3107 if IS_BATCHING.swap(true, Ordering::AcqRel) {
3108 f();
3110 return;
3111 }
3112 f();
3113 IS_BATCHING.store(false, Ordering::Release);
3114 let mut queue = BATCH_QUEUE
3115 .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3116 .lock()
3117 .unwrap();
3118 let tasks: Vec<_> = queue.drain(..).collect();
3119 drop(queue);
3120 for task in tasks {
3121 task();
3122 }
3123}
3124pub fn get_system_state() -> Arc<arc_swap::ArcSwap<KnowledgeState>> {
3126 SYSTEM_STATE
3127 .get_or_init(|| Arc::new(arc_swap::ArcSwap::from_pointee(KnowledgeState::default())))
3128 .clone()
3129}
3130pub fn load_system_state() -> arc_swap::Guard<Arc<KnowledgeState>> {
3131 get_system_state().load()
3132}
3133pub fn update_system_state<F>(f: F)
3134where
3135 F: FnOnce(&KnowledgeState) -> KnowledgeState,
3136{
3137 let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3138 if is_rendering() {
3139 log::warn!(
3140 "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3141 );
3142 }
3143 LAYOUT_DIRTY.store(true, Ordering::SeqCst);
3144 let swap = get_system_state();
3145 let current = swap.load();
3146 let new_state = Arc::new(f(¤t));
3147 swap.store(Arc::clone(&new_state));
3148 #[cfg(not(target_arch = "wasm32"))]
3149 {
3150 let tvar = KNOWLEDGE_TVAR.get_or_init(|| stm::TVar::new((*new_state).clone()));
3151 stm::atomically(|tx| tvar.write(tx, (*new_state).clone()));
3152 }
3153}
3154pub fn transact_system_state<F>(f: F)
3155where
3156 F: Fn(&KnowledgeState) -> KnowledgeState,
3157{
3158 let _lock = STATE_WRITE_MUTEX.lock().unwrap();
3159 #[cfg(not(target_arch = "wasm32"))]
3160 {
3161 if is_rendering() {
3162 log::warn!(
3163 "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3164 );
3165 }
3166 let tvar = KNOWLEDGE_TVAR
3167 .get_or_init(|| stm::TVar::new((**get_system_state().load()).clone()))
3168 .clone();
3169 let new_state = stm::atomically(move |tx| {
3170 let current = tvar.read(tx)?;
3171 let next = f(¤t);
3172 tvar.write(tx, next.clone())?;
3173 Ok(next)
3174 });
3175 get_system_state().store(Arc::new(new_state));
3176 }
3177 #[cfg(target_arch = "wasm32")]
3178 {
3179 if is_rendering() {
3180 log::warn!(
3181 "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3182 );
3183 }
3184 update_system_state(f);
3185 }
3186}
3187impl KnowledgeState {
3188 pub fn new() -> Self {
3190 Self::default()
3191 }
3192 pub fn set_component_state<T: 'static + Send + Sync>(&mut self, id: u64, state: T) {
3194 self.component_states
3195 .insert(id, Arc::new(std::sync::RwLock::new(state)));
3196 }
3197 pub fn get_component_state<T: 'static + Send + Sync>(
3199 &self,
3200 id: u64,
3201 ) -> Option<Arc<std::sync::RwLock<T>>> {
3202 let lock = self.component_states.get(&id)?;
3203 let any_ref = lock.read().ok()?;
3207 if any_ref.is::<T>() {
3208 drop(any_ref);
3210 let cloned: Arc<std::sync::RwLock<dyn std::any::Any + Send + Sync>> = Arc::clone(lock);
3211 Some(unsafe {
3214 let raw = Arc::into_raw(cloned);
3215 Arc::from_raw(raw as *const std::sync::RwLock<T>)
3216 })
3217 } else {
3218 None
3219 }
3220 }
3221 pub fn remember(&mut self, fragment: KnowledgeFragment) {
3223 self.fragments.insert(fragment.id.clone(), fragment);
3224 }
3225 pub fn process_query(&mut self, query: &str) {
3227 let query_lower = query.to_lowercase();
3228 let mut results: Vec<(f32, String)> = self
3229 .fragments
3230 .iter()
3231 .map(|(id, frag)| {
3232 let mut score = 0.0;
3233 if frag.summary.to_lowercase().contains(&query_lower) {
3234 score += 1.0;
3235 }
3236 if frag.source.to_lowercase().contains(&query_lower) {
3237 score += 0.5;
3238 }
3239 (score, id.clone())
3240 })
3241 .filter(|(score, _)| *score > 0.0)
3242 .collect();
3243 results.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
3245 self.last_query_results = results.into_iter().map(|(_, id)| id).take(5).collect();
3246 }
3247 pub fn snapshot(&self) -> Vec<NodeStateSnapshot> {
3249 let mut snapshots = Vec::new();
3250 for frag in self.fragments.values() {
3252 if let Ok(val) = serde_json::to_value(frag) {
3253 snapshots.push(NodeStateSnapshot { id: 0, state: val });
3254 }
3255 }
3256 snapshots
3257 }
3258}
3259#[derive(Clone)]
3261pub struct Binding<T: Clone + Send + Sync + 'static> {
3262 swap: Arc<arc_swap::ArcSwap<T>>,
3263 #[cfg(not(target_arch = "wasm32"))]
3264 tvar: Arc<stm::TVar<T>>,
3265 version: Arc<std::sync::atomic::AtomicU64>,
3266}
3267impl<T: Clone + Send + Sync + 'static> Binding<T> {
3268 pub fn from_state(state: &State<T>) -> Self {
3270 Self {
3271 swap: Arc::clone(&state.swap),
3272 #[cfg(not(target_arch = "wasm32"))]
3273 tvar: Arc::clone(&state.tvar),
3274 version: Arc::clone(&state.version),
3275 }
3276 }
3277 pub fn get(&self) -> T {
3279 (**self.swap.load()).clone()
3280 }
3281 pub fn set(&self, value: T) {
3283 self.swap.store(Arc::new(value.clone()));
3284 #[cfg(not(target_arch = "wasm32"))]
3285 {
3286 let tvar = Arc::clone(&self.tvar);
3287 let v = value.clone();
3288 stm::atomically(move |tx| tvar.write(tx, v.clone()));
3289 }
3290 self.version
3291 .fetch_add(1, std::sync::atomic::Ordering::Release);
3292 }
3293 pub fn version(&self) -> u64 {
3295 self.version.load(std::sync::atomic::Ordering::Acquire)
3296 }
3297}
3298#[cfg(not(target_arch = "wasm32"))]
3299pub fn transact_pair<A, B, F>(state_a: &State<A>, state_b: &State<B>, f: F)
3300where
3301 A: Clone + Send + Sync + 'static,
3302 B: Clone + Send + Sync + 'static,
3303 F: Fn(&A, &B) -> (A, B),
3304{
3305 let tvar_a = Arc::clone(&state_a.tvar);
3306 let tvar_b = Arc::clone(&state_b.tvar);
3307 let (new_a, new_b) = stm::atomically(move |tx| {
3308 let a = tvar_a.read(tx)?;
3309 let b = tvar_b.read(tx)?;
3310 let (na, nb) = f(&a, &b);
3311 tvar_a.write(tx, na.clone())?;
3312 tvar_b.write(tx, nb.clone())?;
3313 Ok((na, nb))
3314 });
3315 state_a.swap.store(Arc::new(new_a.clone()));
3316 state_b.swap.store(Arc::new(new_b.clone()));
3317 state_a
3318 .version
3319 .fetch_add(1, std::sync::atomic::Ordering::Release);
3320 state_b
3321 .version
3322 .fetch_add(1, std::sync::atomic::Ordering::Release);
3323 let subs_a = Arc::clone(&state_a.subscribers);
3324 let subs_b = Arc::clone(&state_b.subscribers);
3325 if crate::is_batching() {
3326 crate::enqueue_batch_task(Box::new(move || {
3327 {
3328 let s = subs_a.lock().unwrap();
3329 for cb in s.iter() {
3330 cb(&new_a);
3331 }
3332 }
3333 {
3334 let s = subs_b.lock().unwrap();
3335 for cb in s.iter() {
3336 cb(&new_b);
3337 }
3338 }
3339 }));
3340 } else {
3341 {
3342 let s = subs_a.lock().unwrap();
3343 for cb in s.iter() {
3344 cb(&new_a);
3345 }
3346 }
3347 {
3348 let s = subs_b.lock().unwrap();
3349 for cb in s.iter() {
3350 cb(&new_b);
3351 }
3352 }
3353 }
3354}
3355use std::any::TypeId;
3356use std::sync::Mutex;
3357pub(crate) static ENVIRONMENT: OnceLock<
3359 Mutex<HashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>>,
3360> = OnceLock::new();
3361pub trait EnvKey: 'static + Send + Sync {
3364 type Value: Clone + Send + Sync + 'static;
3366 fn default_value() -> Self::Value;
3368}
3369pub struct YggdrasilKey;
3371impl EnvKey for YggdrasilKey {
3372 type Value = YggdrasilTokens;
3373 fn default_value() -> Self::Value {
3374 default_tokens()
3375 }
3376}
3377#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3380pub enum Appearance {
3381 Light,
3382 Dark,
3383}
3384#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3386pub enum Orientation {
3387 Horizontal,
3388 Vertical,
3389}
3390#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3392pub struct GridPlacement {
3393 pub column: i32,
3395 pub column_span: u32,
3397 pub row: i32,
3399 pub row_span: u32,
3401}
3402#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3404pub enum Alignment {
3405 #[default]
3406 Center,
3407 Leading,
3408 Trailing,
3409 Top,
3410 Bottom,
3411}
3412#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3414pub enum Distribution {
3415 #[default]
3416 Fill,
3417 Center,
3418 Leading,
3419 Trailing,
3420 SpaceBetween,
3421 SpaceAround,
3422 SpaceEvenly,
3423}
3424#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
3426pub struct Color {
3427 pub r: f32,
3428 pub g: f32,
3429 pub b: f32,
3430 pub a: f32,
3431}
3432impl Color {
3433 pub const BLACK: Color = Color {
3434 r: 0.0,
3435 g: 0.0,
3436 b: 0.0,
3437 a: 1.0,
3438 };
3439 pub const WHITE: Color = Color {
3440 r: 1.0,
3441 g: 1.0,
3442 b: 1.0,
3443 a: 1.0,
3444 };
3445 pub const TRANSPARENT: Color = Color {
3446 r: 0.0,
3447 g: 0.0,
3448 b: 0.0,
3449 a: 0.0,
3450 };
3451 pub const RED: Color = Color {
3452 r: 1.0,
3453 g: 0.0,
3454 b: 0.0,
3455 a: 1.0,
3456 };
3457 pub const GREEN: Color = Color {
3458 r: 0.0,
3459 g: 1.0,
3460 b: 0.0,
3461 a: 1.0,
3462 };
3463 pub const BLUE: Color = Color {
3464 r: 0.0,
3465 g: 0.0,
3466 b: 1.0,
3467 a: 1.0,
3468 };
3469 pub const VIKING_GOLD: Color = Color {
3470 r: 1.0,
3471 g: 0.84,
3472 b: 0.0,
3473 a: 1.0,
3474 };
3475 pub const MAGENTA_LIQUID: Color = Color {
3476 r: 1.0,
3477 g: 0.0,
3478 b: 1.0,
3479 a: 1.0,
3480 };
3481 pub const TACTICAL_OBSIDIAN: Color = Color {
3482 r: 0.05,
3483 g: 0.05,
3484 b: 0.07,
3485 a: 1.0,
3486 };
3487 pub fn relative_luminance(&self) -> f32 {
3489 fn res(c: f32) -> f32 {
3490 if c <= 0.03928 {
3491 c / 12.92
3492 } else {
3493 ((c + 0.055) / 1.055).powf(2.4)
3494 }
3495 }
3496 0.2126 * res(self.r) + 0.7152 * res(self.g) + 0.0722 * res(self.b)
3497 }
3498 pub fn contrast_ratio(&self, other: &Color) -> f32 {
3500 let l1 = self.relative_luminance();
3501 let l2 = other.relative_luminance();
3502 if l1 > l2 {
3503 (l1 + 0.05) / (l2 + 0.05)
3504 } else {
3505 (l2 + 0.05) / (l1 + 0.05)
3506 }
3507 }
3508 pub const CYAN: Color = Color {
3509 r: 0.0,
3510 g: 1.0,
3511 b: 1.0,
3512 a: 1.0,
3513 };
3514 pub const YELLOW: Color = Color {
3515 r: 1.0,
3516 g: 1.0,
3517 b: 0.0,
3518 a: 1.0,
3519 };
3520 pub const MAGENTA: Color = Color {
3521 r: 1.0,
3522 g: 0.0,
3523 b: 1.0,
3524 a: 1.0,
3525 };
3526 pub const GRAY: Color = Color {
3527 r: 0.5,
3528 g: 0.5,
3529 b: 0.5,
3530 a: 1.0,
3531 };
3532 pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
3534 Self { r, g, b, a }
3535 }
3536 pub fn as_array(&self) -> [f32; 4] {
3538 [self.r, self.g, self.b, self.a]
3539 }
3540
3541 pub fn lighten(&self, amount: f32) -> Self {
3547 Self {
3548 r: (self.r + amount).clamp(0.0, 1.0),
3549 g: (self.g + amount).clamp(0.0, 1.0),
3550 b: (self.b + amount).clamp(0.0, 1.0),
3551 a: self.a,
3552 }
3553 }
3554
3555 pub fn darken(&self, amount: f32) -> Self {
3557 Self {
3558 r: (self.r - amount).clamp(0.0, 1.0),
3559 g: (self.g - amount).clamp(0.0, 1.0),
3560 b: (self.b - amount).clamp(0.0, 1.0),
3561 a: self.a,
3562 }
3563 }
3564}
3565impl View for Color {
3566 type Body = Never;
3567 fn body(self) -> Self::Body {
3568 unreachable!()
3569 }
3570 fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
3571 renderer.fill_rect(rect, self.as_array());
3572 }
3573}
3574pub struct AppearanceKey;
3576impl EnvKey for AppearanceKey {
3577 type Value = Appearance;
3578 fn default_value() -> Self::Value {
3579 Appearance::Dark }
3581}
3582pub struct StyleResolver;
3584impl StyleResolver {
3585 pub fn color(key: &str) -> String {
3587 let tokens = Environment::<YggdrasilKey>::new().get();
3588 let appearance = Environment::<AppearanceKey>::new().get();
3589 let is_dark = appearance == Appearance::Dark;
3590 tokens
3591 .get_color(key, is_dark)
3592 .unwrap_or_else(|| "#FF00FF".to_string()) }
3594 pub fn get<T: FromStr>(category: &str, key: &str) -> Option<T> {
3596 let tokens = Environment::<YggdrasilKey>::new().get();
3597 let appearance = Environment::<AppearanceKey>::new().get();
3598 let is_dark = appearance == Appearance::Dark;
3599 tokens.get(category, key, is_dark)
3600 }
3601 pub fn color_array(key: &str) -> [f32; 4] {
3605 let hex = Self::color(key);
3606 parse_hex_color(&hex)
3607 }
3608}
3609
3610fn parse_hex_color(hex: &str) -> [f32; 4] {
3612 let hex = hex.trim_start_matches('#');
3613 if hex.len() >= 6 {
3614 let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(255) as f32 / 255.0;
3615 let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0) as f32 / 255.0;
3616 let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(255) as f32 / 255.0;
3617 let a = if hex.len() >= 8 {
3618 u8::from_str_radix(&hex[6..8], 16).unwrap_or(255) as f32 / 255.0
3619 } else {
3620 1.0
3621 };
3622 [r, g, b, a]
3623 } else {
3624 [1.0, 0.0, 1.0, 1.0] }
3626}
3627
3628pub fn default_tokens() -> YggdrasilTokens {
3630 let mut tokens = YggdrasilTokens::new();
3631 tokens.color.insert(
3633 "background".to_string(),
3634 TokenValue::Single {
3635 value: "#000000".to_string(), },
3637 );
3638 tokens.color.insert(
3639 "primary".to_string(),
3640 TokenValue::Single {
3641 value: "#00FFFF".to_string(), },
3643 );
3644 tokens.color.insert(
3645 "secondary".to_string(),
3646 TokenValue::Single {
3647 value: "#FF00FF".to_string(), },
3649 );
3650 tokens.color.insert(
3651 "surface".to_string(),
3652 TokenValue::Adaptive {
3653 light: "#FFFFFF".to_string(),
3654 dark: "#121212".to_string(),
3655 },
3656 );
3657 tokens.color.insert(
3658 "text".to_string(),
3659 TokenValue::Adaptive {
3660 light: "#000000".to_string(),
3661 dark: "#FFFFFF".to_string(),
3662 },
3663 );
3664 tokens.color.insert(
3666 "surface_elevated".to_string(),
3667 TokenValue::Adaptive {
3668 light: "#FFFFFF".to_string(),
3669 dark: "#1A1A24".to_string(),
3670 },
3671 );
3672 tokens.color.insert(
3673 "surface_overlay".to_string(),
3674 TokenValue::Adaptive {
3675 light: "#FFFFFF".to_string(),
3676 dark: "#1E1E2E".to_string(),
3677 },
3678 );
3679 tokens.color.insert(
3680 "border".to_string(),
3681 TokenValue::Adaptive {
3682 light: "#D0D0D8".to_string(),
3683 dark: "#2A2A3A".to_string(),
3684 },
3685 );
3686 tokens.color.insert(
3687 "border_strong".to_string(),
3688 TokenValue::Adaptive {
3689 light: "#A0A0B0".to_string(),
3690 dark: "#3A3A50".to_string(),
3691 },
3692 );
3693 tokens.color.insert(
3694 "text_muted".to_string(),
3695 TokenValue::Adaptive {
3696 light: "#606070".to_string(),
3697 dark: "#8080A0".to_string(),
3698 },
3699 );
3700 tokens.color.insert(
3701 "text_dim".to_string(),
3702 TokenValue::Adaptive {
3703 light: "#9090A0".to_string(),
3704 dark: "#505070".to_string(),
3705 },
3706 );
3707 tokens.color.insert(
3708 "accent".to_string(),
3709 TokenValue::Single {
3710 value: "#00FFFF".to_string(), },
3712 );
3713 tokens.color.insert(
3714 "accent_hover".to_string(),
3715 TokenValue::Single {
3716 value: "#33FFFF".to_string(),
3717 },
3718 );
3719 tokens.color.insert(
3720 "success".to_string(),
3721 TokenValue::Single {
3722 value: "#00E676".to_string(),
3723 },
3724 );
3725 tokens.color.insert(
3726 "warning".to_string(),
3727 TokenValue::Single {
3728 value: "#FFB300".to_string(),
3729 },
3730 );
3731 tokens.color.insert(
3732 "error".to_string(),
3733 TokenValue::Single {
3734 value: "#FF5252".to_string(),
3735 },
3736 );
3737 tokens.color.insert(
3738 "info".to_string(),
3739 TokenValue::Single {
3740 value: "#448AFF".to_string(),
3741 },
3742 );
3743 tokens.color.insert(
3744 "hover".to_string(),
3745 TokenValue::Adaptive {
3746 light: "#F0F0F5".to_string(),
3747 dark: "#252535".to_string(),
3748 },
3749 );
3750 tokens.color.insert(
3751 "active".to_string(),
3752 TokenValue::Adaptive {
3753 light: "#E0E0EB".to_string(),
3754 dark: "#303045".to_string(),
3755 },
3756 );
3757 tokens.color.insert(
3758 "disabled".to_string(),
3759 TokenValue::Adaptive {
3760 light: "#E8E8F0".to_string(),
3761 dark: "#1A1A28".to_string(),
3762 },
3763 );
3764 tokens.color.insert(
3765 "disabled_text".to_string(),
3766 TokenValue::Adaptive {
3767 light: "#B0B0C0".to_string(),
3768 dark: "#404060".to_string(),
3769 },
3770 );
3771 tokens.color.insert(
3772 "focus_ring".to_string(),
3773 TokenValue::Single {
3774 value: "#00FFFF".to_string(),
3775 },
3776 );
3777 tokens.color.insert(
3778 "shadow".to_string(),
3779 TokenValue::Adaptive {
3780 light: "#00000020".to_string(),
3781 dark: "#00000060".to_string(),
3782 },
3783 );
3784 tokens.color.insert(
3785 "code_bg".to_string(),
3786 TokenValue::Adaptive {
3787 light: "#F5F5FA".to_string(),
3788 dark: "#0D0D18".to_string(),
3789 },
3790 );
3791 tokens.bifrost.insert(
3793 "blur".to_string(),
3794 TokenValue::Single {
3795 value: "25.0".to_string(),
3796 },
3797 );
3798 tokens.bifrost.insert(
3799 "saturation".to_string(),
3800 TokenValue::Single {
3801 value: "1.2".to_string(),
3802 },
3803 );
3804 tokens.bifrost.insert(
3805 "opacity".to_string(),
3806 TokenValue::Single {
3807 value: "0.65".to_string(),
3808 },
3809 );
3810 tokens.gungnir.insert(
3812 "intensity".to_string(),
3813 TokenValue::Single {
3814 value: "1.0".to_string(),
3815 },
3816 );
3817 tokens.gungnir.insert(
3818 "radius".to_string(),
3819 TokenValue::Single {
3820 value: "15.0".to_string(),
3821 },
3822 );
3823 tokens.mjolnir.insert(
3825 "clip_angle".to_string(),
3826 TokenValue::Single {
3827 value: "12.0".to_string(),
3828 },
3829 );
3830 tokens.mjolnir.insert(
3831 "border_width".to_string(),
3832 TokenValue::Single {
3833 value: "2.0".to_string(),
3834 },
3835 );
3836 tokens.anim.insert(
3838 "stiffness".to_string(),
3839 TokenValue::Single {
3840 value: "170.0".to_string(),
3841 },
3842 );
3843 tokens.anim.insert(
3844 "damping".to_string(),
3845 TokenValue::Single {
3846 value: "26.0".to_string(),
3847 },
3848 );
3849 tokens.anim.insert(
3850 "mass".to_string(),
3851 TokenValue::Single {
3852 value: "1.0".to_string(),
3853 },
3854 );
3855 tokens.accessibility.insert(
3857 "reduce_motion".to_string(),
3858 TokenValue::Single {
3859 value: "false".to_string(),
3860 },
3861 );
3862 tokens
3863}
3864pub struct Environment<K: EnvKey> {
3866 _marker: std::marker::PhantomData<K>,
3867}
3868impl<K: EnvKey> Default for Environment<K> {
3869 fn default() -> Self {
3870 Self::new()
3871 }
3872}
3873impl<K: EnvKey> Environment<K> {
3874 pub fn new() -> Self {
3876 Self {
3877 _marker: std::marker::PhantomData,
3878 }
3879 }
3880 pub fn get(&self) -> K::Value {
3882 if let Some(env_store) = ENVIRONMENT.get() {
3883 let env_lock = env_store.lock().unwrap();
3884 if let Some(val) = env_lock.get(&std::any::TypeId::of::<K>()) {
3885 if let Some(typed_val) = val.downcast_ref::<K::Value>() {
3886 return typed_val.clone();
3887 } else {
3888 log::warn!(
3889 "Environment: Downcast failed for key type {:?}",
3890 std::any::type_name::<K>()
3891 );
3892 }
3893 } else {
3894 log::debug!(
3895 "Environment: Key not found: {:?}. Returning default.",
3896 std::any::type_name::<K>()
3897 );
3898 }
3899 } else {
3900 log::debug!(
3901 "Environment: Store not initialized. Key: {:?}. Returning default.",
3902 std::any::type_name::<K>()
3903 );
3904 }
3905 K::default_value()
3906 }
3907}
3908pub mod env {
3910 pub fn insert<K: super::EnvKey>(value: K::Value) {
3912 let store = super::ENVIRONMENT
3913 .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
3914 let mut env_map = store.lock().unwrap();
3915 env_map.insert(std::any::TypeId::of::<K>(), Box::new(value));
3916 }
3917 pub fn remove<K: super::EnvKey>() {
3919 if let Some(store) = super::ENVIRONMENT.get() {
3920 let mut env_map = store.lock().unwrap();
3921 env_map.remove(&std::any::TypeId::of::<K>());
3922 }
3923 }
3924}
3925#[derive(Debug, Clone, Copy, PartialEq)]
3928pub struct Size {
3929 pub width: f32,
3930 pub height: f32,
3931}
3932
3933impl Size {
3934 pub const ZERO: Self = Self {
3935 width: 0.0,
3936 height: 0.0,
3937 };
3938
3939 pub fn new(width: f32, height: f32) -> Self {
3940 Self { width, height }
3941 }
3942}
3943
3944#[derive(Debug, Clone, Copy, PartialEq)]
3946pub struct EdgeInsets {
3947 pub top: f32,
3948 pub leading: f32,
3949 pub bottom: f32,
3950 pub trailing: f32,
3951}
3952
3953impl EdgeInsets {
3954 pub fn all(value: f32) -> Self {
3956 Self {
3957 top: value,
3958 leading: value,
3959 bottom: value,
3960 trailing: value,
3961 }
3962 }
3963
3964 pub fn vertical(value: f32) -> Self {
3966 Self {
3967 top: value,
3968 leading: 0.0,
3969 bottom: value,
3970 trailing: 0.0,
3971 }
3972 }
3973
3974 pub fn horizontal(value: f32) -> Self {
3976 Self {
3977 top: 0.0,
3978 leading: value,
3979 bottom: 0.0,
3980 trailing: value,
3981 }
3982 }
3983}
3984
3985#[derive(Debug, Clone, Copy, PartialEq)]
3989pub struct FrameModifier {
3990 pub width: Option<f32>,
3992 pub height: Option<f32>,
3994 pub min_width: Option<f32>,
3996 pub max_width: Option<f32>,
3998 pub min_height: Option<f32>,
4000 pub max_height: Option<f32>,
4002 pub alignment: Alignment,
4004}
4005
4006impl Default for FrameModifier {
4007 fn default() -> Self {
4009 Self::new()
4010 }
4011}
4012
4013impl FrameModifier {
4014 pub fn new() -> Self {
4016 Self {
4017 width: None,
4018 height: None,
4019 min_width: None,
4020 max_width: None,
4021 min_height: None,
4022 max_height: None,
4023 alignment: Alignment::Center,
4024 }
4025 }
4026
4027 pub fn width(mut self, width: f32) -> Self {
4029 self.width = Some(width);
4030 self
4031 }
4032
4033 pub fn height(mut self, height: f32) -> Self {
4035 self.height = Some(height);
4036 self
4037 }
4038
4039 pub fn size(mut self, width: f32, height: f32) -> Self {
4041 self.width = Some(width);
4042 self.height = Some(height);
4043 self
4044 }
4045
4046 pub fn min_width(mut self, min_width: f32) -> Self {
4048 self.min_width = Some(min_width);
4049 self
4050 }
4051
4052 pub fn max_width(mut self, max_width: f32) -> Self {
4054 self.max_width = Some(max_width);
4055 self
4056 }
4057
4058 pub fn min_height(mut self, min_height: f32) -> Self {
4060 self.min_height = Some(min_height);
4061 self
4062 }
4063
4064 pub fn max_height(mut self, max_height: f32) -> Self {
4066 self.max_height = Some(max_height);
4067 self
4068 }
4069
4070 pub fn alignment(mut self, alignment: Alignment) -> Self {
4072 self.alignment = alignment;
4073 self
4074 }
4075}
4076
4077impl ViewModifier for FrameModifier {
4078 fn modify<V: View>(self, content: V) -> impl View {
4080 ModifiedView::new(content, self)
4081 }
4082
4083 fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
4085 let w = if let Some(width) = self.width {
4086 Some(width)
4087 } else {
4088 proposal.width.map(|pw| {
4089 pw.clamp(
4090 self.min_width.unwrap_or(0.0),
4091 self.max_width.unwrap_or(f32::INFINITY),
4092 )
4093 })
4094 };
4095 let h = if let Some(height) = self.height {
4096 Some(height)
4097 } else {
4098 proposal.height.map(|ph| {
4099 ph.clamp(
4100 self.min_height.unwrap_or(0.0),
4101 self.max_height.unwrap_or(f32::INFINITY),
4102 )
4103 })
4104 };
4105 SizeProposal {
4106 width: w,
4107 height: h,
4108 }
4109 }
4110
4111 fn transform_size(&self, child_size: Size) -> Size {
4113 let w = if let Some(width) = self.width {
4114 width
4115 } else {
4116 child_size.width.clamp(
4117 self.min_width.unwrap_or(0.0),
4118 self.max_width.unwrap_or(f32::INFINITY),
4119 )
4120 };
4121 let h = if let Some(height) = self.height {
4122 height
4123 } else {
4124 child_size.height.clamp(
4125 self.min_height.unwrap_or(0.0),
4126 self.max_height.unwrap_or(f32::INFINITY),
4127 )
4128 };
4129 Size {
4130 width: w,
4131 height: h,
4132 }
4133 }
4134
4135 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4137 self.render(renderer, rect);
4138 let child_proposal =
4139 self.transform_proposal(SizeProposal::new(Some(rect.width), Some(rect.height)));
4140 let child_size = view.intrinsic_size(renderer, child_proposal);
4141
4142 let mut child_x = rect.x;
4143 let mut child_y = rect.y;
4144
4145 match self.alignment {
4146 Alignment::Leading => {
4147 child_y = rect.y + (rect.height - child_size.height) / 2.0;
4148 }
4149 Alignment::Trailing => {
4150 child_x = rect.x + rect.width - child_size.width;
4151 child_y = rect.y + (rect.height - child_size.height) / 2.0;
4152 }
4153 Alignment::Top => {
4154 child_x = rect.x + (rect.width - child_size.width) / 2.0;
4155 }
4156 Alignment::Bottom => {
4157 child_x = rect.x + (rect.width - child_size.width) / 2.0;
4158 child_y = rect.y + rect.height - child_size.height;
4159 }
4160 Alignment::Center => {
4161 child_x = rect.x + (rect.width - child_size.width) / 2.0;
4162 child_y = rect.y + (rect.height - child_size.height) / 2.0;
4163 }
4164 }
4165
4166 let child_rect = Rect {
4167 x: child_x,
4168 y: child_y,
4169 width: child_size.width,
4170 height: child_size.height,
4171 };
4172
4173 view.render(renderer, child_rect);
4174 self.post_render(renderer, rect);
4175 }
4176}
4177
4178#[derive(Debug, Clone, Copy, PartialEq)]
4180pub struct FlexModifier {
4181 pub weight: f32,
4182}
4183
4184impl ViewModifier for FlexModifier {
4185 fn modify<V: View>(self, content: V) -> impl View {
4186 ModifiedView::new(content, self)
4187 }
4188
4189 fn child_flex_weight<V: View>(&self, _view: &V) -> f32 {
4190 self.weight
4191 }
4192}
4193
4194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4196pub struct GridPlacementModifier {
4197 pub placement: GridPlacement,
4199}
4200
4201impl ViewModifier for GridPlacementModifier {
4202 fn modify<V: View>(self, content: V) -> impl View {
4204 ModifiedView::new(content, self)
4205 }
4206
4207 fn get_grid_placement(&self) -> Option<GridPlacement> {
4209 Some(self.placement)
4210 }
4211}
4212
4213#[derive(Clone)]
4216pub struct OverlayModifier {
4217 pub overlay: AnyView,
4219 pub alignment: Alignment,
4221 pub offset: [f32; 2],
4223 pub on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
4225}
4226
4227impl ViewModifier for OverlayModifier {
4228 fn modify<V: View>(self, content: V) -> impl View {
4230 ModifiedView::new(content, self)
4231 }
4232
4233 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4235 view.render(renderer, rect);
4237
4238 let overlay_size = self
4240 .overlay
4241 .intrinsic_size(renderer, SizeProposal::unspecified());
4242
4243 let mut overlay_x;
4245 let mut overlay_y;
4246
4247 match self.alignment {
4248 Alignment::Leading => {
4249 overlay_x = rect.x - overlay_size.width;
4250 overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4251 }
4252 Alignment::Trailing => {
4253 overlay_x = rect.x + rect.width;
4254 overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4255 }
4256 Alignment::Top => {
4257 overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4258 overlay_y = rect.y - overlay_size.height;
4259 }
4260 Alignment::Bottom => {
4261 overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4262 overlay_y = rect.y + rect.height;
4263 }
4264 Alignment::Center => {
4265 overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4266 overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4267 }
4268 }
4269
4270 overlay_x += self.offset[0];
4271 overlay_y += self.offset[1];
4272
4273 let overlay_rect = Rect {
4274 x: overlay_x,
4275 y: overlay_y,
4276 width: overlay_size.width,
4277 height: overlay_size.height,
4278 };
4279
4280 if let Some(on_dismiss) = &self.on_dismiss {
4282 let dismiss = on_dismiss.clone();
4283 renderer.register_handler(
4284 "pointerdown",
4285 Arc::new(move |event| {
4286 if let Event::PointerDown { x, y, .. } = event {
4287 let click_inside = x >= overlay_rect.x
4288 && x <= overlay_rect.x + overlay_rect.width
4289 && y >= overlay_rect.y
4290 && y <= overlay_rect.y + overlay_rect.height;
4291 if !click_inside {
4292 dismiss();
4293 }
4294 }
4295 }),
4296 );
4297 }
4298
4299 self.overlay.render(renderer, overlay_rect);
4301 }
4302}
4303
4304#[derive(Debug, Clone, Copy, PartialEq)]
4306pub struct OffsetModifier {
4307 pub x: f32,
4308 pub y: f32,
4309}
4310
4311impl OffsetModifier {
4312 pub fn new(x: f32, y: f32) -> Self {
4313 Self { x, y }
4314 }
4315}
4316
4317impl ViewModifier for OffsetModifier {
4318 fn modify<V: View>(self, content: V) -> impl View {
4319 ModifiedView::new(content, self)
4320 }
4321}
4322
4323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4325pub struct ZIndexModifier {
4326 pub z_index: i32,
4327}
4328
4329impl ZIndexModifier {
4330 pub fn new(z_index: i32) -> Self {
4331 Self { z_index }
4332 }
4333}
4334
4335impl ViewModifier for ZIndexModifier {
4336 fn modify<V: View>(self, content: V) -> impl View {
4337 ModifiedView::new(content, self)
4338 }
4339}
4340
4341#[derive(Debug, Clone, Copy, PartialEq, Default)]
4343pub struct LayoutConstraints {
4344 pub min_width: Option<f32>,
4345 pub max_width: Option<f32>,
4346 pub min_height: Option<f32>,
4347 pub max_height: Option<f32>,
4348}
4349
4350#[derive(Debug, Clone, Copy, PartialEq)]
4352pub struct LayoutModifier {
4353 pub constraints: LayoutConstraints,
4354}
4355
4356impl LayoutModifier {
4357 pub fn new(constraints: LayoutConstraints) -> Self {
4358 Self { constraints }
4359 }
4360}
4361
4362impl ViewModifier for LayoutModifier {
4363 fn modify<V: View>(self, content: V) -> impl View {
4364 ModifiedView::new(content, self)
4365 }
4366}
4367
4368#[derive(Debug, Clone, Copy, PartialEq)]
4370pub struct SafeAreaModifier {
4371 pub ignores: bool,
4372}
4373
4374impl ViewModifier for SafeAreaModifier {
4375 fn modify<V: View>(self, content: V) -> impl View {
4376 ModifiedView::new(content, self)
4377 }
4378}
4379
4380#[derive(Debug, Clone, Copy, PartialEq)]
4382pub struct ElevationModifier {
4383 pub level: f32,
4384}
4385
4386impl ViewModifier for ElevationModifier {
4387 fn modify<V: View>(self, content: V) -> impl View {
4388 ModifiedView::new(content, self)
4389 }
4390
4391 fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4392 if self.level > 0.0 {
4393 let radius = self.level * 2.0;
4394 let offset_y = self.level * 0.5;
4395 let shadow_color = [0.0, 0.0, 0.0, 0.3];
4396 renderer.push_shadow(radius, shadow_color, [0.0, offset_y]);
4397 view.render(renderer, rect);
4398 renderer.pop_shadow();
4399 } else {
4400 view.render(renderer, rect);
4401 }
4402 }
4403}
4404
4405pub mod layout {
4407 use super::*;
4408
4409 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4412 pub struct LayoutKey {
4413 pub view_hash: u64,
4414 pub generation: u64,
4415 }
4416
4417 pub struct LayoutCache {
4419 pub safe_area: SafeArea,
4420 size_cache: HashMap<(u64, u32, u32), Size>, generation: u64,
4425 }
4426
4427 impl Default for LayoutCache {
4428 fn default() -> Self {
4429 Self::new()
4430 }
4431 }
4432
4433 impl LayoutCache {
4434 pub fn new() -> Self {
4435 Self {
4436 safe_area: SafeArea::default(),
4437 size_cache: HashMap::new(),
4438 generation: 0,
4439 }
4440 }
4441
4442 pub fn generation(&self) -> u64 {
4444 self.generation
4445 }
4446
4447 pub fn invalidate(&mut self) {
4451 self.generation = self.generation.wrapping_add(1);
4452 }
4453
4454 pub fn is_valid(&self, key: LayoutKey, current_gen: u64) -> bool {
4457 key.generation == current_gen && key.generation == self.generation
4458 }
4459
4460 pub fn clear(&mut self) {
4461 self.safe_area = SafeArea::default();
4462 self.size_cache.clear();
4463 }
4464
4465 pub fn get_size(&self, view_hash: u64, proposal: SizeProposal) -> Option<Size> {
4466 let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
4467 let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
4468 self.size_cache.get(&(view_hash, pw, ph)).copied()
4469 }
4470
4471 pub fn set_size(&mut self, view_hash: u64, proposal: SizeProposal, size: Size) {
4472 let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
4473 let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
4474 self.size_cache.insert((view_hash, pw, ph), size);
4475 }
4476
4477 pub fn invalidate_view(&mut self, view_hash: u64) {
4479 self.size_cache.retain(|&(hash, _, _), _| hash != view_hash);
4480 }
4481 }
4482
4483 #[derive(Debug, Clone, Copy, PartialEq)]
4485 pub struct SizeProposal {
4486 pub width: Option<f32>,
4487 pub height: Option<f32>,
4488 }
4489
4490 impl SizeProposal {
4491 pub fn unspecified() -> Self {
4492 Self {
4493 width: None,
4494 height: None,
4495 }
4496 }
4497
4498 pub fn width(width: f32) -> Self {
4499 Self {
4500 width: Some(width),
4501 height: None,
4502 }
4503 }
4504
4505 pub fn height(height: f32) -> Self {
4506 Self {
4507 width: None,
4508 height: Some(height),
4509 }
4510 }
4511
4512 pub fn tight(width: f32, height: f32) -> Self {
4513 Self {
4514 width: Some(width),
4515 height: Some(height),
4516 }
4517 }
4518
4519 pub fn new(width: Option<f32>, height: Option<f32>) -> Self {
4520 Self { width, height }
4521 }
4522 }
4523
4524 pub trait LayoutView: Send {
4526 fn size_that_fits(
4528 &self,
4529 proposal: SizeProposal,
4530 subviews: &[&dyn LayoutView],
4531 cache: &mut LayoutCache,
4532 ) -> Size;
4533
4534 fn place_subviews(
4536 &self,
4537 bounds: Rect,
4538 subviews: &mut [&mut dyn LayoutView],
4539 cache: &mut LayoutCache,
4540 );
4541
4542 fn flex_weight(&self) -> f32 {
4544 0.0
4545 }
4546
4547 fn debug_layout(&self, indent: usize) -> String {
4550 let prefix = " ".repeat(indent);
4551 format!("{}LayoutView", prefix)
4552 }
4553 }
4554 #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
4556 pub struct EdgeInsets {
4557 pub top: f32,
4558 pub leading: f32,
4559 pub bottom: f32,
4560 pub trailing: f32,
4561 }
4562
4563 impl EdgeInsets {
4564 pub fn new(top: f32, leading: f32, bottom: f32, trailing: f32) -> Self {
4565 Self {
4566 top,
4567 leading,
4568 bottom,
4569 trailing,
4570 }
4571 }
4572
4573 pub fn all(value: f32) -> Self {
4574 Self {
4575 top: value,
4576 leading: value,
4577 bottom: value,
4578 trailing: value,
4579 }
4580 }
4581 }
4582
4583 #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
4585 pub struct SafeArea {
4586 pub insets: EdgeInsets,
4587 }
4588
4589 #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
4591 pub enum SdfShape {
4592 Rect(Rect),
4593 RoundedRect { rect: Rect, radius: f32 },
4594 Circle { center: [f32; 2], radius: f32 },
4595 }
4596
4597 #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
4599 pub struct Rect {
4600 pub x: f32,
4601 pub y: f32,
4602 pub width: f32,
4603 pub height: f32,
4604 }
4605
4606 impl Rect {
4607 pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
4608 Self {
4609 x,
4610 y,
4611 width,
4612 height,
4613 }
4614 }
4615
4616 pub fn inset(&self, amount: f32) -> Self {
4617 Self {
4618 x: self.x + amount,
4619 y: self.y + amount,
4620 width: (self.width - amount * 2.0).max(0.0),
4621 height: (self.height - amount * 2.0).max(0.0),
4622 }
4623 }
4624
4625 pub fn offset(&self, dx: f32, dy: f32) -> Self {
4626 Self {
4627 x: self.x + dx,
4628 y: self.y + dy,
4629 ..*self
4630 }
4631 }
4632
4633 pub fn zero() -> Self {
4634 Self {
4635 x: 0.0,
4636 y: 0.0,
4637 width: 0.0,
4638 height: 0.0,
4639 }
4640 }
4641
4642 pub fn contains(&self, x: f32, y: f32) -> bool {
4643 x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
4644 }
4645
4646 pub fn size(&self) -> Size {
4647 Size {
4648 width: self.width,
4649 height: self.height,
4650 }
4651 }
4652
4653 pub fn split_horizontal(&self, n: usize) -> Vec<Rect> {
4655 if n == 0 {
4656 return vec![];
4657 }
4658 let item_width = self.width / n as f32;
4659 (0..n)
4660 .map(|i| Rect {
4661 x: self.x + i as f32 * item_width,
4662 y: self.y,
4663 width: item_width,
4664 height: self.height,
4665 })
4666 .collect()
4667 }
4668
4669 pub fn split_vertical(&self, n: usize) -> Vec<Rect> {
4671 if n == 0 {
4672 return vec![];
4673 }
4674 let item_height = self.height / n as f32;
4675 (0..n)
4676 .map(|i| Rect {
4677 x: self.x,
4678 y: self.y + i as f32 * item_height,
4679 width: self.width,
4680 height: item_height,
4681 })
4682 .collect()
4683 }
4684 }
4685}
4686
4687pub use layout::{LayoutCache, LayoutKey, LayoutView, Rect, SizeProposal};
4689pub mod agents;
4692pub mod animation;
4693pub mod gpu;
4694pub mod material;
4695pub mod runtime;
4696pub mod scene_graph;
4697pub mod sdf_shadow;
4698
4699pub use material::DrawMaterial;
4700pub use scene_graph::{NodeId, bifrost_registry};
4701
4702pub trait AssetManager: Send + Sync {
4706 fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>>;
4708
4709 fn preload_image(&self, url: &str);
4711}
4712
4713#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
4715pub enum TouchPhase {
4716 Began,
4718 Moved,
4720 Ended,
4722 Cancelled,
4724}
4725
4726#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
4728pub enum Event {
4729 PointerDown {
4730 x: f32,
4731 y: f32,
4732 button: u32,
4733 proximity_field: f32,
4734 },
4735 PointerUp {
4736 x: f32,
4737 y: f32,
4738 button: u32,
4739 },
4740 PointerMove {
4741 x: f32,
4742 y: f32,
4743 proximity_field: f32,
4744 },
4745 PointerClick {
4746 x: f32,
4747 y: f32,
4748 button: u32,
4749 },
4750 PointerEnter,
4751 PointerLeave,
4752 PointerWheel {
4755 x: f32,
4756 y: f32,
4757 delta_x: f32,
4758 delta_y: f32,
4759 },
4760 PointerDoubleClick {
4762 x: f32,
4763 y: f32,
4764 button: u32,
4765 },
4766 DragStart {
4768 x: f32,
4769 y: f32,
4770 button: u32,
4771 },
4772 DragMove {
4774 x: f32,
4775 y: f32,
4776 },
4777 DragEnd {
4779 x: f32,
4780 y: f32,
4781 },
4782 KeyDown {
4783 key: String,
4784 },
4785 KeyUp {
4786 key: String,
4787 },
4788 FocusIn,
4790 FocusOut,
4792 Copy,
4794 Cut,
4796 Paste(String),
4798 Ime(String),
4800 TouchStart {
4802 x: f32,
4803 y: f32,
4804 touch_id: u64,
4805 },
4806 TouchMove {
4808 x: f32,
4809 y: f32,
4810 touch_id: u64,
4811 },
4812 TouchEnd {
4814 x: f32,
4815 y: f32,
4816 touch_id: u64,
4817 },
4818 TouchCancel {
4820 touch_id: u64,
4821 },
4822 GesturePinch {
4828 center: [f32; 2],
4829 scale: f32,
4830 velocity: f32,
4831 phase: TouchPhase,
4832 },
4833 GestureSwipe {
4838 direction: [f32; 2],
4839 velocity: f32,
4840 phase: TouchPhase,
4841 },
4842 FileDrop {
4844 path: String,
4845 },
4846}
4847
4848impl Event {
4849 pub fn name(&self) -> &'static str {
4851 match self {
4852 Self::PointerDown { .. } => "pointerdown",
4853 Self::PointerUp { .. } => "pointerup",
4854 Self::PointerMove { .. } => "pointermove",
4855 Self::PointerClick { .. } => "pointerclick",
4856 Self::PointerEnter => "pointerenter",
4857 Self::PointerLeave => "pointerleave",
4858 Self::PointerWheel { .. } => "pointerwheel",
4859 Self::PointerDoubleClick { .. } => "pointerdoubleclick",
4860 Self::DragStart { .. } => "dragstart",
4861 Self::DragMove { .. } => "dragmove",
4862 Self::DragEnd { .. } => "dragend",
4863 Self::KeyDown { .. } => "keydown",
4864 Self::KeyUp { .. } => "keyup",
4865 Self::FocusIn => "focusin",
4866 Self::FocusOut => "focusout",
4867 Self::Copy => "copy",
4868 Self::Cut => "cut",
4869 Self::Paste(_) => "paste",
4870 Self::Ime(_) => "ime",
4871 Self::TouchStart { .. } => "touchstart",
4872 Self::TouchMove { .. } => "touchmove",
4873 Self::TouchEnd { .. } => "touchend",
4874 Self::TouchCancel { .. } => "touchcancel",
4875 Self::GesturePinch { .. } => "gesturepinch",
4876 Self::GestureSwipe { .. } => "gestureswipe",
4877 Self::FileDrop { .. } => "filedrop",
4878 }
4879 }
4880}
4881
4882#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4884pub enum EventResponse {
4885 Handled,
4886 Ignored,
4887}
4888
4889pub struct DefaultAssetManager {
4891 cache: AssetCache,
4892}
4893type AssetCache = Arc<arc_swap::ArcSwap<HashMap<String, AssetState<Arc<Vec<u8>>>>>>;
4894
4895impl Default for DefaultAssetManager {
4896 fn default() -> Self {
4897 Self::new()
4898 }
4899}
4900
4901impl DefaultAssetManager {
4902 pub fn new() -> Self {
4903 Self {
4904 cache: Arc::new(arc_swap::ArcSwap::from_pointee(HashMap::new())),
4905 }
4906 }
4907}
4908
4909impl AssetManager for DefaultAssetManager {
4910 fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>> {
4911 if let Some(state) = self.cache.load().get(url) {
4912 return state.clone();
4913 }
4914
4915 self.cache.rcu(|map| {
4916 let mut m = (**map).clone();
4917 m.entry(url.to_string()).or_insert(AssetState::Loading);
4918 m
4919 });
4920 AssetState::Loading
4921 }
4922
4923 fn preload_image(&self, _url: &str) {}
4924}
4925
4926use std::future::Future;
4927
4928pub struct Suspense<T: Clone + Send + Sync + 'static> {
4931 inner: State<AssetState<T>>,
4932}
4933
4934impl<T: Clone + Send + Sync + 'static> Default for Suspense<T> {
4935 fn default() -> Self {
4936 Self::new()
4937 }
4938}
4939
4940impl<T: Clone + Send + Sync + 'static> Suspense<T> {
4941 pub fn new() -> Self {
4942 Self {
4943 inner: State::new(AssetState::Loading),
4944 }
4945 }
4946
4947 pub fn new_async<F>(future: F) -> Self
4948 where
4949 F: Future<Output = Result<T, String>> + Send + 'static,
4950 {
4951 let suspense = Self::new();
4952 let suspense_clone = suspense.clone();
4953
4954 #[cfg(not(target_arch = "wasm32"))]
4955 {
4956 if let Ok(handle) = tokio::runtime::Handle::try_current() {
4958 handle.spawn(async move {
4959 let result = future.await;
4960 match result {
4961 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
4962 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
4963 }
4964 });
4965 } else {
4966 std::thread::spawn(move || {
4967 let rt = tokio::runtime::Builder::new_current_thread()
4968 .enable_all()
4969 .build()
4970 .unwrap();
4971 rt.block_on(async {
4972 let result = future.await;
4973 match result {
4974 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
4975 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
4976 }
4977 });
4978 });
4979 }
4980 }
4981 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
4982 {
4983 wasm_bindgen_futures::spawn_local(async move {
4984 let result = future.await;
4985 match result {
4986 Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
4987 Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
4988 }
4989 });
4990 }
4991
4992 suspense
4993 }
4994
4995 pub fn ready(value: T) -> Self {
4996 Self {
4997 inner: State::new(AssetState::Ready(value)),
4998 }
4999 }
5000
5001 pub fn error(message: impl Into<String>) -> Self {
5002 Self {
5003 inner: State::new(AssetState::Error(message.into())),
5004 }
5005 }
5006
5007 pub fn get(&self) -> AssetState<T> {
5008 self.inner.get()
5009 }
5010
5011 pub fn get_ref(&self) -> AssetState<T> {
5012 self.inner.get()
5013 }
5014
5015 pub fn is_loading(&self) -> bool {
5016 matches!(self.get(), AssetState::Loading)
5017 }
5018
5019 pub fn is_ready(&self) -> bool {
5020 matches!(self.get(), AssetState::Ready(_))
5021 }
5022
5023 pub fn is_error(&self) -> bool {
5024 matches!(self.get(), AssetState::Error(_))
5025 }
5026
5027 pub fn ready_value(&self) -> Option<T> {
5028 match self.get() {
5029 AssetState::Ready(value) => Some(value),
5030 _ => None,
5031 }
5032 }
5033
5034 pub fn error_message(&self) -> Option<String> {
5035 match self.get() {
5036 AssetState::Error(message) => Some(message),
5037 _ => None,
5038 }
5039 }
5040
5041 pub fn subscribe<F: Fn(&AssetState<T>) + Send + Sync + 'static>(&self, callback: F) {
5042 self.inner.subscribe(callback)
5043 }
5044
5045 pub fn inner_state(&self) -> &State<AssetState<T>> {
5046 &self.inner
5047 }
5048}
5049
5050impl<T: Clone + Send + Sync + 'static> Clone for Suspense<T> {
5051 fn clone(&self) -> Self {
5052 Self {
5053 inner: self.inner.clone(),
5054 }
5055 }
5056}
5057
5058impl<T: Clone + Send + Sync + 'static> From<T> for Suspense<T> {
5059 fn from(value: T) -> Self {
5060 Self::ready(value)
5061 }
5062}
5063
5064impl<T: Clone + Send + Sync + 'static> From<Result<T, String>> for Suspense<T> {
5065 fn from(result: Result<T, String>) -> Self {
5066 match result {
5067 Ok(value) => Self::ready(value),
5068 Err(error) => Self::error(error),
5069 }
5070 }
5071}
5072
5073#[cfg(test)]
5074mod phase1_test;
5075
5076#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5078pub enum BerserkerMode {
5079 Normal,
5080 Rage, Frenzy, GodMode, }
5084
5085pub trait Seer: Send + Sync {
5088 fn predict(&self, context: &str) -> String;
5090 fn whispers(&self) -> Vec<String>;
5092}
5093
5094#[cfg(test)]
5095mod vili_tests {
5096 use super::*;
5097
5098 struct DummyRenderer;
5099 impl ElapsedTime for DummyRenderer {
5100 fn elapsed_time(&self) -> f32 {
5101 0.0
5102 }
5103 fn delta_time(&self) -> f32 {
5104 0.0
5105 }
5106 }
5107 impl Renderer for DummyRenderer {
5108 fn fill_rect(&mut self, _r: Rect, _c: [f32; 4]) {}
5109 fn fill_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4]) {}
5110 fn fill_ellipse(&mut self, _r: Rect, _c: [f32; 4]) {}
5111 fn stroke_rect(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5112 fn stroke_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4], _w: f32) {}
5113 fn stroke_ellipse(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5114 fn draw_line(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, _c: [f32; 4], _w: f32) {}
5115 fn draw_text(&mut self, _t: &str, _x: f32, _y: f32, _s: f32, _c: [f32; 4]) {}
5116 fn measure_text(&mut self, _t: &str, _s: f32) -> (f32, f32) {
5117 (0.0, 0.0)
5118 }
5119 fn memoize(&mut self, _id: u64, _hash: u64, _r: &dyn Fn(&mut dyn Renderer)) {}
5120 }
5121
5122 #[test]
5123 fn test_magnetic_warp() {
5124 let renderer = DummyRenderer;
5125 let anchor = Rect {
5126 x: 100.0,
5127 y: 100.0,
5128 width: 50.0,
5129 height: 50.0,
5130 };
5131 let pointer = [125.0, 50.0];
5133 let warp = renderer.magnetic_warp(pointer, anchor, 1.0);
5136 assert!(warp[1] > 50.0);
5138
5139 let far_pointer = [500.0, 500.0];
5141 let far_warp = renderer.magnetic_warp(far_pointer, anchor, 1.0);
5142 assert_eq!(far_pointer, far_warp);
5143 }
5144
5145 #[test]
5146 fn test_mani_glow() {
5147 let renderer = DummyRenderer;
5148 let bounds = Rect {
5149 x: 0.0,
5150 y: 0.0,
5151 width: 100.0,
5152 height: 100.0,
5153 };
5154 let pointer_inside = [50.0, 50.0];
5155 let glow_max = renderer.mani_glow_intensity(pointer_inside, bounds, 120.0);
5156 assert_eq!(glow_max, 1.0);
5157
5158 let pointer_edge = [50.0, -10.0];
5159 let glow_partial = renderer.mani_glow_intensity(pointer_edge, bounds, 120.0);
5160 assert!(glow_partial > 0.0 && glow_partial < 1.0);
5161 }
5162
5163 #[test]
5164 fn test_fafnir_evolve() {
5165 let renderer = DummyRenderer;
5166 let bounds = Rect {
5167 x: 0.0,
5168 y: 0.0,
5169 width: 100.0,
5170 height: 100.0,
5171 };
5172 let pointer_inside = [50.0, 50.0];
5173 let scale = renderer.fafnir_evolve(pointer_inside, bounds, 1.2);
5174 assert_eq!(scale, 1.2); }
5176
5177 #[test]
5178 fn test_undo_manager_basic() {
5179 let mut manager = UndoManager::new(3, 0.5);
5180 let val = std::sync::Arc::new(std::sync::Mutex::new(0));
5181
5182 let v1 = val.clone();
5183 let v2 = val.clone();
5184 manager.push(
5185 "Add",
5186 move || *v1.lock().unwrap() -= 1,
5187 move || *v2.lock().unwrap() += 1,
5188 );
5189
5190 assert!(manager.can_undo());
5191 assert!(!manager.can_redo());
5192
5193 let undo = manager.undo().unwrap();
5194 undo();
5195 assert_eq!(*val.lock().unwrap(), -1);
5196 assert!(!manager.can_undo());
5197 assert!(manager.can_redo());
5198
5199 let redo = manager.redo().unwrap();
5200 redo();
5201 assert_eq!(*val.lock().unwrap(), 0);
5202 }
5203
5204 #[test]
5205 fn test_undo_manager_depth_limit() {
5206 let mut manager = UndoManager::new(2, 0.5);
5207 manager.push("1", || {}, || {});
5208 manager.push("2", || {}, || {});
5209 manager.push("3", || {}, || {});
5210
5211 assert_eq!(manager.stack.len(), 2);
5212 assert_eq!(manager.position, 2);
5213 }
5214
5215 #[test]
5216 fn test_undo_manager_coalescing() {
5217 let mut manager = UndoManager::new(10, 1.0);
5218 let count = std::sync::Arc::new(std::sync::Mutex::new(0));
5219
5220 let c = count.clone();
5221 manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5222
5223 let c = count.clone();
5224 manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
5225
5226 assert_eq!(manager.stack.len(), 1);
5227
5228 let undo = manager.undo().unwrap();
5229 undo();
5230 assert_eq!(*count.lock().unwrap(), -2);
5231 }
5232}
5233
5234use std::cell::RefCell;
5246
5247thread_local! {
5248 static THEME_CONTEXT: RefCell<Option<color::SemanticColors>> = const { RefCell::new(None) };
5250}
5251
5252pub use color::SemanticColors;
5258
5259pub fn set_current_theme(colors: color::SemanticColors) {
5262 THEME_CONTEXT.with(|cell| {
5263 *cell.borrow_mut() = Some(colors);
5264 });
5265}
5266
5267pub fn clear_current_theme() {
5269 THEME_CONTEXT.with(|cell| {
5270 *cell.borrow_mut() = None;
5271 });
5272}
5273
5274pub fn use_theme() -> color::SemanticColors {
5289 THEME_CONTEXT.with(|cell| {
5290 cell.borrow()
5291 .clone()
5292 .unwrap_or_else(color::SemanticColors::dark)
5293 })
5294}
5295
5296pub mod color {
5305 use super::Color;
5306
5307 #[derive(Debug, Clone)]
5324 pub struct SemanticColors {
5325 pub primary: Color,
5327 pub secondary: Color,
5329 pub accent: Color,
5331 pub background: Color,
5333 pub surface: Color,
5335 pub error: Color,
5337 pub warning: Color,
5339 pub success: Color,
5341 pub text: Color,
5343 pub text_dim: Color,
5345 }
5346
5347 impl SemanticColors {
5348 pub fn dark() -> Self {
5350 Self {
5351 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), }
5362 }
5363
5364 pub fn light() -> Self {
5366 Self {
5367 primary: Color::new(0.35, 0.30, 0.70, 1.0),
5368 secondary: Color::new(0.30, 0.50, 0.30, 1.0),
5369 accent: Color::new(0.30, 0.35, 0.75, 1.0),
5370 background: Color::new(0.97, 0.97, 0.98, 1.0),
5371 surface: Color::new(0.93, 0.93, 0.95, 1.0),
5372 error: Color::new(0.75, 0.15, 0.15, 1.0),
5373 warning: Color::new(0.80, 0.60, 0.0, 1.0),
5374 success: Color::new(0.15, 0.65, 0.30, 1.0),
5375 text: Color::new(0.08, 0.08, 0.10, 1.0),
5376 text_dim: Color::new(0.40, 0.40, 0.45, 1.0),
5377 }
5378 }
5379
5380 pub fn accent_states(&self) -> InteractiveColorStates {
5385 InteractiveColorStates::from_color(self.accent)
5386 }
5387
5388 pub fn primary_states(&self) -> InteractiveColorStates {
5390 InteractiveColorStates::from_color(self.primary)
5391 }
5392
5393 pub fn error_states(&self) -> InteractiveColorStates {
5395 InteractiveColorStates::from_color(self.error)
5396 }
5397
5398 pub fn success_states(&self) -> InteractiveColorStates {
5400 InteractiveColorStates::from_color(self.success)
5401 }
5402 }
5403
5404 #[derive(Debug, Clone)]
5409 pub struct InteractiveColorStates {
5410 pub default: Color,
5411 pub hover: Color,
5412 pub active: Color,
5413 pub focus: Color,
5414 pub disabled: Color,
5415 pub focus_ring: Color,
5416 }
5417
5418 impl InteractiveColorStates {
5419 pub fn from_color(base: Color) -> Self {
5428 Self {
5429 default: base,
5430 hover: base.lighten(0.15),
5431 active: base.darken(0.15),
5432 focus: base,
5433 disabled: Color::new(base.r, base.g, base.b, base.a * 0.4),
5434 focus_ring: Color::new(base.r, base.g, base.b, base.a * 0.7),
5435 }
5436 }
5437
5438 pub fn color_for(&self, state: InteractiveState) -> Color {
5440 match state {
5441 InteractiveState::Default => self.default,
5442 InteractiveState::Hover => self.hover,
5443 InteractiveState::Active => self.active,
5444 InteractiveState::Focus => self.focus,
5445 InteractiveState::Disabled => self.disabled,
5446 }
5447 }
5448 }
5449
5450 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5452 pub enum InteractiveState {
5453 Default,
5454 Hover,
5455 Active,
5456 Focus,
5457 Disabled,
5458 }
5459}
5460
5461pub fn use_state<T: Clone + Send + Sync + 'static>(
5480 id: u64,
5481 initial: T,
5482) -> (impl Fn() -> T, impl Fn(T)) {
5483 let already_exists = load_system_state().get_component_state::<T>(id).is_some();
5485 if !already_exists {
5486 update_system_state(|s| {
5487 let mut ns = s.clone();
5488 ns.set_component_state(id, initial.clone());
5489 ns
5490 });
5491 }
5492
5493 let getter = move || -> T {
5494 load_system_state()
5495 .get_component_state::<T>(id)
5496 .map(|arc_lock| {
5497 arc_lock
5498 .read()
5499 .ok()
5500 .map(|guard| (*guard).clone())
5501 .unwrap_or_else(|| initial.clone())
5502 })
5503 .unwrap_or_else(|| initial.clone())
5504 };
5505
5506 let setter = {
5507 move |value| {
5508 update_system_state(|s| {
5509 let mut ns = s.clone();
5510 ns.set_component_state(id, value);
5511 ns
5512 });
5513 }
5514 };
5515
5516 (getter, setter)
5517}
5518
5519pub fn use_state_hash(key: &str) -> u64 {
5531 use std::hash::{Hash, Hasher};
5532 let mut s = std::collections::hash_map::DefaultHasher::new();
5533 key.hash(&mut s);
5534 s.finish()
5535}
5536
5537thread_local! {
5547 static ACCESSIBILITY_PREFS: std::cell::RefCell<AccessibilityPreferences> =
5550 std::cell::RefCell::new(AccessibilityPreferences::default());
5551}
5552
5553#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5560pub struct AccessibilityPreferences {
5561 pub reduce_motion: bool,
5563 pub reduce_transparency: bool,
5565 pub increase_contrast: bool,
5567}
5568
5569impl AccessibilityPreferences {
5570 pub fn detect_from_system() -> Self {
5575 #[cfg(target_os = "macos")]
5576 {
5577 let reduce_motion = std::process::Command::new("defaults")
5579 .args(["read", "-g", "com.apple.universalaccess", "reduceMotion"])
5580 .output()
5581 .ok()
5582 .and_then(|o| String::from_utf8(o.stdout).ok())
5583 .map(|s| s.trim() == "1")
5584 .unwrap_or(false);
5585
5586 let reduce_transparency = std::process::Command::new("defaults")
5587 .args([
5588 "read",
5589 "-g",
5590 "com.apple.universalaccess",
5591 "reduceTransparency",
5592 ])
5593 .output()
5594 .ok()
5595 .and_then(|o| String::from_utf8(o.stdout).ok())
5596 .map(|s| s.trim() == "1")
5597 .unwrap_or(false);
5598
5599 let increase_contrast = std::process::Command::new("defaults")
5600 .args([
5601 "read",
5602 "-g",
5603 "com.apple.universalaccess",
5604 "increaseContrast",
5605 ])
5606 .output()
5607 .ok()
5608 .and_then(|o| String::from_utf8(o.stdout).ok())
5609 .map(|s| s.trim() == "1")
5610 .unwrap_or(false);
5611
5612 Self {
5613 reduce_motion,
5614 reduce_transparency,
5615 increase_contrast,
5616 }
5617 }
5618 #[cfg(not(target_os = "macos"))]
5619 {
5620 Self::default()
5621 }
5622 }
5623
5624 pub fn min_alpha(&self, requested: f32) -> f32 {
5626 if self.increase_contrast {
5627 requested.max(0.5)
5628 } else {
5629 requested
5630 }
5631 }
5632
5633 pub fn should_disable_glass(&self) -> bool {
5635 self.reduce_transparency
5636 }
5637
5638 pub fn should_reduce_motion(&self) -> bool {
5640 self.reduce_motion
5641 }
5642
5643 pub fn should_increase_contrast(&self) -> bool {
5645 self.increase_contrast
5646 }
5647}
5648
5649pub fn accessibility_preferences() -> AccessibilityPreferences {
5651 ACCESSIBILITY_PREFS.with(|p| *p.borrow())
5652}
5653
5654pub fn set_accessibility_preferences(prefs: AccessibilityPreferences) {
5659 ACCESSIBILITY_PREFS.with(|p| {
5660 *p.borrow_mut() = prefs;
5661 });
5662}
5663
5664pub trait ClipboardProvider: Send + Sync {
5673 fn read_text(&self) -> Option<String>;
5675 fn write_text(&self, text: &str);
5677}
5678
5679#[cfg(not(target_arch = "wasm32"))]
5683pub struct SystemClipboard;
5684
5685#[cfg(not(target_arch = "wasm32"))]
5686impl ClipboardProvider for SystemClipboard {
5687 fn read_text(&self) -> Option<String> {
5688 use std::process::Command;
5689 Command::new("pbpaste")
5691 .output()
5692 .ok()
5693 .and_then(|o| String::from_utf8(o.stdout).ok())
5694 }
5695
5696 fn write_text(&self, text: &str) {
5697 use std::process::Command;
5698 if let Ok(mut child) = Command::new("pbcopy")
5700 .stdin(std::process::Stdio::piped())
5701 .spawn()
5702 {
5703 if let Some(stdin) = child.stdin.as_mut() {
5704 use std::io::Write;
5705 let _ = stdin.write_all(text.as_bytes());
5706 }
5707 let _ = child.wait();
5708 }
5709 }
5710}
5711
5712#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5718pub enum TextDirection {
5719 Forward,
5720 Backward,
5721 Up,
5722 Down,
5723 LineStart,
5724 LineEnd,
5725 WordForward,
5726 WordBackward,
5727}
5728
5729#[derive(Debug, Clone, Default)]
5734pub struct TextInputState {
5735 pub text: String,
5737 pub cursor_pos: usize,
5739 pub selection_anchor: Option<usize>,
5742 pub focused: bool,
5744 pub caret_visible: bool,
5746 pub last_edit_time: f32,
5748}
5749
5750impl TextInputState {
5751 pub fn new(text: impl Into<String>) -> Self {
5753 let text = text.into();
5754 let cursor_pos = text.len();
5755 Self {
5756 text,
5757 cursor_pos,
5758 selection_anchor: None,
5759 focused: false,
5760 caret_visible: true,
5761 last_edit_time: 0.0,
5762 }
5763 }
5764
5765 pub fn selection_range(&self) -> Option<(usize, usize)> {
5768 self.selection_anchor.map(|anchor| {
5769 if anchor <= self.cursor_pos {
5770 (anchor, self.cursor_pos)
5771 } else {
5772 (self.cursor_pos, anchor)
5773 }
5774 })
5775 }
5776
5777 pub fn selected_text(&self) -> String {
5779 self.selection_range()
5780 .map(|(start, end)| self.text[start..end].to_string())
5781 .unwrap_or_default()
5782 }
5783
5784 pub fn insert(&mut self, new_text: &str) {
5786 if let Some((start, end)) = self.selection_range() {
5787 self.text.replace_range(start..end, new_text);
5788 self.cursor_pos = start + new_text.len();
5789 } else {
5790 self.text.insert_str(self.cursor_pos, new_text);
5791 self.cursor_pos += new_text.len();
5792 }
5793 self.selection_anchor = None;
5794 }
5795
5796 pub fn delete(&mut self, backward: bool, count: usize) -> String {
5799 if let Some((start, end)) = self.selection_range() {
5800 let deleted = self.text[start..end].to_string();
5801 self.text.replace_range(start..end, "");
5802 self.cursor_pos = start;
5803 self.selection_anchor = None;
5804 return deleted;
5805 }
5806
5807 if backward && self.cursor_pos > 0 {
5808 let start = self.cursor_pos.saturating_sub(count);
5809 let deleted = self.text[start..self.cursor_pos].to_string();
5810 self.text.replace_range(start..self.cursor_pos, "");
5811 self.cursor_pos = start;
5812 deleted
5813 } else if !backward && self.cursor_pos < self.text.len() {
5814 let end = (self.cursor_pos + count).min(self.text.len());
5815 let deleted = self.text[self.cursor_pos..end].to_string();
5816 self.text.replace_range(self.cursor_pos..end, "");
5817 deleted
5818 } else {
5819 String::new()
5820 }
5821 }
5822
5823 pub fn move_cursor(&mut self, direction: TextDirection, extend_selection: bool) {
5825 if !extend_selection {
5826 self.selection_anchor = None;
5827 } else if self.selection_anchor.is_none() {
5828 self.selection_anchor = Some(self.cursor_pos);
5829 }
5830
5831 match direction {
5832 TextDirection::Forward if self.cursor_pos < self.text.len() => {
5833 let next = self.text[self.cursor_pos..]
5835 .char_indices()
5836 .nth(1)
5837 .map(|(i, _)| self.cursor_pos + i)
5838 .unwrap_or(self.text.len());
5839 self.cursor_pos = next;
5840 }
5841 TextDirection::Backward if self.cursor_pos > 0 => {
5842 let prev = self.text[..self.cursor_pos]
5843 .char_indices()
5844 .next_back()
5845 .map(|(i, _)| i)
5846 .unwrap_or(0);
5847 self.cursor_pos = prev;
5848 }
5849 TextDirection::LineStart => {
5850 self.cursor_pos = 0;
5851 }
5852 TextDirection::LineEnd => {
5853 self.cursor_pos = self.text.len();
5854 }
5855 TextDirection::WordForward => {
5856 let rest = &self.text[self.cursor_pos..];
5858 let after_word = rest
5860 .char_indices()
5861 .find(|(_, c)| !c.is_alphanumeric())
5862 .map(|(i, _)| i)
5863 .unwrap_or(rest.len());
5864 let after_space = rest[after_word..]
5866 .char_indices()
5867 .find(|(_, c)| !c.is_whitespace())
5868 .map(|(i, _)| after_word + i)
5869 .unwrap_or(rest.len());
5870 self.cursor_pos = (self.cursor_pos + after_space).min(self.text.len());
5871 }
5872 TextDirection::WordBackward => {
5873 let before = &self.text[..self.cursor_pos];
5874 let before_word = before
5876 .char_indices()
5877 .rev()
5878 .find(|(_, c)| !c.is_whitespace())
5879 .map(|(i, _)| i)
5880 .unwrap_or(0);
5881 let word_start = before[..before_word]
5883 .char_indices()
5884 .rev()
5885 .find(|(_, c)| !c.is_alphanumeric())
5886 .map(|(i, _)| i)
5887 .unwrap_or(0);
5888 self.cursor_pos = word_start;
5889 }
5890 _ => {} }
5892
5893 if !extend_selection {
5894 self.selection_anchor = None;
5895 }
5896 }
5897
5898 pub fn select_all(&mut self) {
5900 self.cursor_pos = self.text.len();
5901 self.selection_anchor = Some(0);
5902 }
5903
5904 pub fn cursor_byte_pos(&self) -> usize {
5906 self.cursor_pos
5907 }
5908}
5909
5910#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
5912pub struct NotificationAction {
5913 pub id: String,
5915 pub title: String,
5917 pub is_destructive: bool,
5919}
5920
5921#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
5923pub enum NotificationPriority {
5924 Passive,
5926 #[default]
5928 Active,
5929 TimeSensitive,
5931}
5932
5933#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
5935pub struct Notification {
5936 pub id: String,
5938 pub app_name: Option<String>,
5940 pub title: String,
5942 pub body: String,
5944 pub icon: Option<String>,
5946 pub sound: Option<String>,
5948 pub actions: Vec<NotificationAction>,
5950 pub timeout: Option<f32>,
5952 pub priority: NotificationPriority,
5954 pub timestamp: f32,
5956 pub dismissed: bool,
5958}
5959
5960#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, thiserror::Error)]
5962pub enum NotificationError {
5963 #[error("Notification permission denied")]
5965 PermissionDenied,
5966 #[error("Failed to post notification")]
5968 PostFailed,
5969}
5970
5971#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
5973pub enum NotificationPermission {
5974 Granted,
5976 Denied,
5978 #[default]
5980 NotDetermined,
5981}
5982
5983pub trait NotificationHandler: Send + Sync {
5985 fn show(&self, notification: Notification) -> Result<(), NotificationError>;
5987 fn dismiss(&self, id: &str) -> Result<(), NotificationError>;
5989 fn request_permission(&self) -> NotificationPermission;
5991}
5992
5993static NEXT_NOTIFICATION_ID: std::sync::atomic::AtomicUsize =
5994 std::sync::atomic::AtomicUsize::new(1);
5995
5996#[derive(Clone, Copy, Debug, Default)]
5998pub struct DefaultNotificationHandler;
5999
6000impl NotificationHandler for DefaultNotificationHandler {
6001 fn show(&self, notification: Notification) -> Result<(), NotificationError> {
6003 let mut notif = notification;
6004 if notif.id.is_empty() {
6005 let id = NEXT_NOTIFICATION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
6006 notif.id = format!("notif_{}", id);
6007 }
6008 update_system_state(|state| {
6009 let mut new_state = state.clone();
6010 new_state.notifications.push(notif.clone());
6011 new_state
6012 });
6013 Ok(())
6014 }
6015
6016 fn dismiss(&self, id: &str) -> Result<(), NotificationError> {
6018 update_system_state(|state| {
6019 let mut new_state = state.clone();
6020 for notif in &mut new_state.notifications {
6021 if notif.id == id {
6022 notif.dismissed = true;
6023 }
6024 }
6025 new_state
6026 });
6027 Ok(())
6028 }
6029
6030 fn request_permission(&self) -> NotificationPermission {
6032 NotificationPermission::Granted
6033 }
6034}
6035
6036static NOTIFICATION_HANDLER: once_cell::sync::OnceCell<std::sync::Arc<dyn NotificationHandler>> =
6037 once_cell::sync::OnceCell::new();
6038
6039pub fn set_notification_handler(handler: std::sync::Arc<dyn NotificationHandler>) {
6041 let _ = NOTIFICATION_HANDLER.set(handler);
6042}
6043
6044pub fn get_notification_handler() -> std::sync::Arc<dyn NotificationHandler> {
6046 NOTIFICATION_HANDLER
6047 .get_or_init(|| std::sync::Arc::new(DefaultNotificationHandler))
6048 .clone()
6049}
6050
6051#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6053pub struct FileFilter {
6054 pub name: String,
6056 pub extensions: Vec<String>,
6058}
6059
6060#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
6062pub enum FileDialogMode {
6063 #[default]
6065 OpenFile,
6066 OpenDirectory,
6068 SaveFile,
6070}
6071
6072#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
6074pub struct FileDialog {
6075 pub title: String,
6077 pub default_path: Option<String>,
6079 pub filters: Vec<FileFilter>,
6081 pub mode: FileDialogMode,
6083 pub allow_multiple: bool,
6085}
6086
6087#[derive(Debug, thiserror::Error)]
6089pub enum FileDialogError {
6090 #[error("File dialog cancelled")]
6092 Cancelled,
6093 #[error("I/O error: {0}")]
6095 Io(#[from] std::io::Error),
6096 #[error("Platform error: {0}")]
6098 Platform(String),
6099}
6100
6101impl FileDialog {
6102 pub fn new(mode: FileDialogMode) -> Self {
6104 Self {
6105 mode,
6106 ..Default::default()
6107 }
6108 }
6109
6110 pub fn title(mut self, title: impl Into<String>) -> Self {
6112 self.title = title.into();
6113 self
6114 }
6115
6116 pub fn add_filter(mut self, name: &str, extensions: &[&str]) -> Self {
6118 self.filters.push(FileFilter {
6119 name: name.to_string(),
6120 extensions: extensions.iter().map(|s| s.to_string()).collect(),
6121 });
6122 self
6123 }
6124
6125 pub fn default_path(mut self, path: impl Into<String>) -> Self {
6127 self.default_path = Some(path.into());
6128 self
6129 }
6130
6131 pub fn allow_multiple(mut self, allow: bool) -> Self {
6133 self.allow_multiple = allow;
6134 self
6135 }
6136}
6137
6138#[cfg(not(target_arch = "wasm32"))]
6139impl FileDialog {
6140 pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6142 let mut dialog = rfd::FileDialog::new();
6143 dialog = dialog.set_title(&self.title);
6144 if let Some(path) = &self.default_path {
6145 dialog = dialog.set_directory(path);
6146 }
6147 for filter in &self.filters {
6148 let refs: Vec<&str> = filter.extensions.iter().map(|s| s.as_str()).collect();
6149 dialog = dialog.add_filter(&filter.name, &refs);
6150 }
6151
6152 match self.mode {
6153 FileDialogMode::OpenFile => {
6154 if self.allow_multiple {
6155 dialog.pick_files().ok_or(FileDialogError::Cancelled)
6156 } else {
6157 Ok(dialog.pick_file().into_iter().collect())
6158 }
6159 }
6160 FileDialogMode::OpenDirectory => Ok(dialog.pick_folder().into_iter().collect()),
6161 FileDialogMode::SaveFile => Ok(dialog.save_file().into_iter().collect()),
6162 }
6163 }
6164
6165 pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6167 let results = self.pick()?;
6168 Ok(results.into_iter().next())
6169 }
6170}
6171
6172#[cfg(target_arch = "wasm32")]
6173impl FileDialog {
6174 pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
6176 Err(FileDialogError::Platform(
6177 "FileDialog is not supported synchronously on WebAssembly".to_string(),
6178 ))
6179 }
6180
6181 pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
6183 Err(FileDialogError::Platform(
6184 "FileDialog is not supported synchronously on WebAssembly".to_string(),
6185 ))
6186 }
6187}
6188
6189#[derive(Debug, thiserror::Error)]
6191pub enum DocumentError {
6192 #[error("I/O error: {0}")]
6194 Io(#[from] std::io::Error),
6195 #[error("Parse error: {0}")]
6197 Parse(String),
6198 #[error("Serialization error: {0}")]
6200 Serialize(String),
6201}
6202
6203pub trait Document: Send + Sync {
6205 fn read_from(path: &std::path::Path) -> Result<Self, DocumentError>
6207 where
6208 Self: Sized;
6209
6210 fn write_to(&self, path: &std::path::Path) -> Result<(), DocumentError>;
6212
6213 fn is_dirty(&self) -> bool;
6215
6216 fn mark_clean(&mut self);
6218}
6219
6220pub struct AutoSaveManager {
6222 pub interval: f32,
6224 pub timer: f32,
6226 pub documents: Vec<(std::path::PathBuf, Box<dyn Document>)>,
6228}
6229
6230impl AutoSaveManager {
6231 pub fn new(interval: f32) -> Self {
6233 Self {
6234 interval,
6235 timer: 0.0,
6236 documents: Vec::new(),
6237 }
6238 }
6239
6240 pub fn register(&mut self, path: std::path::PathBuf, doc: Box<dyn Document>) {
6242 self.documents.push((path, doc));
6243 }
6244
6245 pub fn tick(&mut self, dt: f32) {
6247 self.timer += dt;
6248 if self.timer >= self.interval {
6249 self.timer = 0.0;
6250 for (path, doc) in &mut self.documents {
6251 if doc.is_dirty() {
6252 match doc.write_to(path) {
6253 Ok(()) => {
6254 doc.mark_clean();
6255 log::info!("[AutoSaveManager] Auto-saved document to {:?}", path);
6256 }
6257 Err(e) => {
6258 log::error!(
6259 "[AutoSaveManager] Failed to auto-save document to {:?}: {:?}",
6260 path,
6261 e
6262 );
6263 }
6264 }
6265 }
6266 }
6267 }
6268 }
6269}
6270
6271#[derive(Debug, Clone, PartialEq, Eq, Default)]
6279pub struct Modifiers {
6280 pub cmd: bool,
6282 pub shift: bool,
6284 pub alt: bool,
6286 pub ctrl: bool,
6288}
6289
6290#[derive(Debug, Clone)]
6292pub struct KeyboardShortcut {
6293 pub key: String,
6295 pub modifiers: Modifiers,
6297}
6298
6299impl KeyboardShortcut {
6300 pub fn cmd(key: impl Into<String>) -> Self {
6302 Self {
6303 key: key.into(),
6304 modifiers: Modifiers {
6305 cmd: true,
6306 ..Default::default()
6307 },
6308 }
6309 }
6310
6311 pub fn cmd_shift(key: impl Into<String>) -> Self {
6313 Self {
6314 key: key.into(),
6315 modifiers: Modifiers {
6316 cmd: true,
6317 shift: true,
6318 ..Default::default()
6319 },
6320 }
6321 }
6322}
6323
6324pub enum MenuItem {
6330 Action {
6332 label: String,
6333 shortcut: Option<KeyboardShortcut>,
6334 action: std::sync::Arc<dyn Fn() + Send + Sync>,
6335 enabled: bool,
6336 },
6337 Submenu { label: String, items: Vec<MenuItem> },
6339 Separator,
6341}
6342
6343impl std::fmt::Debug for MenuItem {
6344 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6345 match self {
6346 Self::Action { label, enabled, .. } => f
6347 .debug_struct("Action")
6348 .field("label", label)
6349 .field("enabled", enabled)
6350 .finish(),
6351 Self::Submenu { label, items } => f
6352 .debug_struct("Submenu")
6353 .field("label", label)
6354 .field("items", items)
6355 .finish(),
6356 Self::Separator => write!(f, "Separator"),
6357 }
6358 }
6359}
6360
6361pub struct MenuBar {
6366 pub items: Vec<MenuItem>,
6368}
6369
6370impl MenuBar {
6371 pub fn new() -> Self {
6373 Self { items: Vec::new() }
6374 }
6375
6376 pub fn add_item(&mut self, item: MenuItem) {
6378 self.items.push(item);
6379 }
6380
6381 #[allow(clippy::too_many_arguments)]
6393 pub fn standard(
6394 new_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6395 open_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6396 save_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6397 close_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6398 quit_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6399 undo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6400 redo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6401 cut_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6402 copy_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6403 paste_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6404 select_all_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6405 find_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
6406 ) -> Self {
6407 let mut bar = Self::new();
6408
6409 bar.add_item(MenuItem::Submenu {
6411 label: "File".to_string(),
6412 items: vec![
6413 MenuItem::Action {
6414 label: "New".to_string(),
6415 shortcut: Some(KeyboardShortcut::cmd("n")),
6416 action: new_fn,
6417 enabled: true,
6418 },
6419 MenuItem::Action {
6420 label: "Open…".to_string(),
6421 shortcut: Some(KeyboardShortcut::cmd("o")),
6422 action: open_fn,
6423 enabled: true,
6424 },
6425 MenuItem::Separator,
6426 MenuItem::Action {
6427 label: "Save".to_string(),
6428 shortcut: Some(KeyboardShortcut::cmd("s")),
6429 action: save_fn,
6430 enabled: true,
6431 },
6432 MenuItem::Separator,
6433 MenuItem::Action {
6434 label: "Close".to_string(),
6435 shortcut: Some(KeyboardShortcut::cmd("w")),
6436 action: close_fn,
6437 enabled: true,
6438 },
6439 MenuItem::Separator,
6440 MenuItem::Action {
6441 label: "Quit".to_string(),
6442 shortcut: Some(KeyboardShortcut::cmd("q")),
6443 action: quit_fn,
6444 enabled: true,
6445 },
6446 ],
6447 });
6448
6449 bar.add_item(MenuItem::Submenu {
6451 label: "Edit".to_string(),
6452 items: vec![
6453 MenuItem::Action {
6454 label: "Undo".to_string(),
6455 shortcut: Some(KeyboardShortcut::cmd("z")),
6456 action: undo_fn,
6457 enabled: true,
6458 },
6459 MenuItem::Action {
6460 label: "Redo".to_string(),
6461 shortcut: Some(KeyboardShortcut::cmd_shift("z")),
6462 action: redo_fn,
6463 enabled: true,
6464 },
6465 MenuItem::Separator,
6466 MenuItem::Action {
6467 label: "Cut".to_string(),
6468 shortcut: Some(KeyboardShortcut::cmd("x")),
6469 action: cut_fn,
6470 enabled: true,
6471 },
6472 MenuItem::Action {
6473 label: "Copy".to_string(),
6474 shortcut: Some(KeyboardShortcut::cmd("c")),
6475 action: copy_fn,
6476 enabled: true,
6477 },
6478 MenuItem::Action {
6479 label: "Paste".to_string(),
6480 shortcut: Some(KeyboardShortcut::cmd("v")),
6481 action: paste_fn,
6482 enabled: true,
6483 },
6484 MenuItem::Separator,
6485 MenuItem::Action {
6486 label: "Select All".to_string(),
6487 shortcut: Some(KeyboardShortcut::cmd("a")),
6488 action: select_all_fn,
6489 enabled: true,
6490 },
6491 MenuItem::Separator,
6492 MenuItem::Action {
6493 label: "Find…".to_string(),
6494 shortcut: Some(KeyboardShortcut::cmd("f")),
6495 action: find_fn,
6496 enabled: true,
6497 },
6498 ],
6499 });
6500
6501 let noop: std::sync::Arc<dyn Fn() + Send + Sync> = std::sync::Arc::new(|| {});
6505 bar.add_item(MenuItem::Submenu {
6506 label: "View".to_string(),
6507 items: vec![
6508 MenuItem::Action {
6509 label: "Zoom In".to_string(),
6510 shortcut: Some(KeyboardShortcut::cmd("=")),
6511 action: noop.clone(),
6512 enabled: true,
6513 },
6514 MenuItem::Action {
6515 label: "Zoom Out".to_string(),
6516 shortcut: Some(KeyboardShortcut::cmd("-")),
6517 action: noop.clone(),
6518 enabled: true,
6519 },
6520 MenuItem::Separator,
6521 MenuItem::Action {
6522 label: "Toggle Fullscreen".to_string(),
6523 shortcut: Some(KeyboardShortcut {
6524 key: "f".to_string(),
6525 modifiers: Modifiers {
6526 ctrl: true,
6527 ..Default::default()
6528 },
6529 }),
6530 action: noop.clone(),
6531 enabled: true,
6532 },
6533 ],
6534 });
6535
6536 bar.add_item(MenuItem::Submenu {
6538 label: "Window".to_string(),
6539 items: vec![
6540 MenuItem::Action {
6541 label: "Minimize".to_string(),
6542 shortcut: Some(KeyboardShortcut::cmd("m")),
6543 action: noop.clone(),
6544 enabled: true,
6545 },
6546 MenuItem::Action {
6547 label: "Zoom".to_string(),
6548 shortcut: None,
6549 action: noop.clone(),
6550 enabled: true,
6551 },
6552 MenuItem::Separator,
6553 MenuItem::Action {
6554 label: "Bring All to Front".to_string(),
6555 shortcut: None,
6556 action: noop.clone(),
6557 enabled: true,
6558 },
6559 ],
6560 });
6561
6562 bar.add_item(MenuItem::Submenu {
6564 label: "Help".to_string(),
6565 items: vec![MenuItem::Action {
6566 label: "Search Help".to_string(),
6567 shortcut: None,
6568 action: noop,
6569 enabled: true,
6570 }],
6571 });
6572
6573 bar
6574 }
6575}
6576
6577impl Default for MenuBar {
6578 fn default() -> Self {
6579 Self::new()
6580 }
6581}
6582
6583use std::sync::RwLock;
6589
6590#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
6592pub enum Direction {
6593 #[default]
6594 LTR,
6595 RTL,
6596 Auto,
6597}
6598
6599impl Direction {
6600 pub fn is_rtl(self) -> bool {
6601 matches!(self, Direction::RTL)
6602 }
6603}
6604#[derive(Clone, Debug)]
6605pub struct L10nBundle {
6606 pub locale: String,
6607 pub strings: HashMap<String, String>,
6608 pub is_rtl: bool,
6609}
6610
6611impl L10nBundle {
6612 pub fn new(locale: impl Into<String>) -> Self {
6613 Self {
6614 locale: locale.into(),
6615 strings: HashMap::new(),
6616 is_rtl: false,
6617 }
6618 }
6619
6620 pub fn add(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
6621 self.strings.insert(key.into(), value.into());
6622 self
6623 }
6624
6625 pub fn from_strings_format(locale: impl Into<String>, input: &str) -> Self {
6626 let mut bundle = Self::new(locale);
6627 for line in input.lines() {
6628 let line = line.trim();
6629 if line.is_empty() || line.starts_with("//") {
6630 continue;
6631 }
6632 if let Some(eq_pos) = line.find(" = ") {
6633 let key = line[..eq_pos].trim_matches('"').to_string();
6634 let val = line[eq_pos + 3..]
6635 .trim_end_matches(';')
6636 .trim_matches('"')
6637 .to_string();
6638 bundle.strings.insert(key, val);
6639 }
6640 }
6641 bundle
6642 }
6643 pub fn t(&self, key: &str) -> String {
6645 self.strings
6646 .get(key)
6647 .map(|s| s.to_string())
6648 .unwrap_or_else(|| key.to_string())
6649 }
6650
6651 pub fn tf(&self, key: &str, args: &[&str]) -> String {
6653 let mut result = self.t(key);
6654 for (i, arg) in args.iter().enumerate() {
6655 result = result.replace(&format!("{{{}}}", i), arg);
6656 }
6657 result
6658 }
6659}
6660
6661pub struct L10n {
6663 bundles: HashMap<String, L10nBundle>,
6664 current: String,
6665}
6666
6667impl L10n {
6668 pub fn new(default_locale: &str) -> Self {
6669 Self {
6670 bundles: HashMap::new(),
6671 current: default_locale.to_string(),
6672 }
6673 }
6674
6675 pub fn add_bundle(&mut self, bundle: L10nBundle) {
6676 self.bundles.insert(bundle.locale.clone(), bundle);
6677 }
6678
6679 pub fn set_locale(&mut self, locale: &str) {
6680 self.current = locale.to_string();
6681 }
6682 pub fn current_locale(&self) -> &str {
6683 &self.current
6684 }
6685
6686 pub fn is_rtl(&self) -> bool {
6687 self.bundles
6688 .get(self.current.as_str())
6689 .map(|b| b.is_rtl)
6690 .unwrap_or(false)
6691 }
6692
6693 pub fn t(&self, key: &str) -> String {
6694 self.bundles
6695 .get(self.current.as_str())
6696 .map(|b| b.t(key))
6697 .unwrap_or_else(|| key.to_string())
6698 }
6699
6700 pub fn tf(&self, key: &str, args: &[&str]) -> String {
6701 let mut result = self.t(key);
6702 for (i, arg) in args.iter().enumerate() {
6703 result = result.replace(&format!("{{{}}}", i), arg);
6704 }
6705 result
6706 }
6707
6708 pub fn direction(&self) -> Direction {
6709 if self.is_rtl() {
6710 Direction::RTL
6711 } else {
6712 Direction::LTR
6713 }
6714 }
6715}
6716
6717static L10N: once_cell::sync::Lazy<Arc<RwLock<L10n>>> =
6718 once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(L10n::new("en"))));
6719
6720pub fn init_l10n(l10n: L10n) {
6721 if let Ok(mut guard) = L10N.write() {
6722 *guard = l10n;
6723 }
6724}
6725
6726pub fn l10n() -> Arc<RwLock<L10n>> {
6727 L10N.clone()
6728}
6729
6730pub fn t(key: &str) -> String {
6731 L10N.read()
6732 .map(|g| g.t(key).to_string())
6733 .unwrap_or_else(|_| key.to_string())
6734}
6735
6736pub fn tf(key: &str, args: &[&str]) -> String {
6737 L10N.read()
6738 .map(|g| g.tf(key, args))
6739 .unwrap_or_else(|_| key.to_string())
6740}
6741
6742pub fn set_locale(locale: &str) {
6743 if let Ok(mut guard) = L10N.write() {
6744 guard.set_locale(locale);
6745 }
6746}
6747
6748pub fn current_locale() -> String {
6749 L10N.read()
6750 .map(|g| g.current_locale().to_string())
6751 .unwrap_or_else(|_| "en".to_string())
6752}
6753
6754pub fn is_rtl() -> bool {
6755 L10N.read().map(|g| g.is_rtl()).unwrap_or(false)
6756}
6757
6758#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
6770pub enum SystemTheme {
6771 #[default]
6773 Dark,
6774 Light,
6776}
6777
6778pub fn detect_system_theme() -> SystemTheme {
6788 std::env::var("CVKG_THEME")
6789 .ok()
6790 .and_then(|v| match v.as_str() {
6791 "light" => Some(SystemTheme::Light),
6792 "dark" => Some(SystemTheme::Dark),
6793 _ => None,
6794 })
6795 .unwrap_or(SystemTheme::Dark)
6796}
6797
6798pub mod audio_haptic;
6804pub use audio_haptic::{
6805 AudioEngine, HapticEngine, HapticIntensity, NullAudioEngine, NullHapticEngine, haptic_error,
6806 haptic_impact, haptic_selection, haptic_success, play_sound, set_audio_engine,
6807 set_haptic_engine, sounds,
6808};