fret_runtime/
window_input_context.rs1use std::collections::HashMap;
2
3use fret_core::AppWindowId;
4
5use crate::{GlobalsHost, InputContext, WindowCommandAvailabilityService};
6
7#[derive(Debug, Default)]
12pub struct WindowInputContextService {
13 by_window: HashMap<AppWindowId, InputContext>,
14}
15
16impl WindowInputContextService {
17 pub fn window_count(&self) -> usize {
18 self.by_window.len()
19 }
20
21 pub fn snapshot(&self, window: AppWindowId) -> Option<&InputContext> {
22 self.by_window.get(&window)
23 }
24
25 pub fn set_snapshot(&mut self, window: AppWindowId, input_ctx: InputContext) {
26 self.by_window.insert(window, input_ctx);
27 }
28
29 pub fn remove_window(&mut self, window: AppWindowId) {
30 self.by_window.remove(&window);
31 }
32}
33
34fn apply_window_command_availability(
35 app: &impl GlobalsHost,
36 window: AppWindowId,
37 mut input_ctx: InputContext,
38) -> InputContext {
39 if let Some(availability) = app
40 .global::<WindowCommandAvailabilityService>()
41 .and_then(|svc| svc.snapshot(window))
42 .copied()
43 {
44 input_ctx.edit_can_undo = availability.edit_can_undo;
45 input_ctx.edit_can_redo = availability.edit_can_redo;
46 input_ctx.router_can_back = availability.router_can_back;
47 input_ctx.router_can_forward = availability.router_can_forward;
48 }
49 input_ctx
50}
51
52pub fn best_effort_input_context_for_window(
55 app: &impl GlobalsHost,
56 window: AppWindowId,
57) -> Option<InputContext> {
58 app.global::<WindowInputContextService>()
59 .and_then(|svc| svc.snapshot(window))
60 .cloned()
61 .map(|input_ctx| apply_window_command_availability(app, window, input_ctx))
62}
63
64pub fn best_effort_input_context_for_window_with_fallback(
68 app: &impl GlobalsHost,
69 window: AppWindowId,
70 fallback_input_ctx: InputContext,
71) -> InputContext {
72 let input_ctx = app
73 .global::<WindowInputContextService>()
74 .and_then(|svc| svc.snapshot(window))
75 .cloned()
76 .unwrap_or(fallback_input_ctx);
77 apply_window_command_availability(app, window, input_ctx)
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 use std::any::{Any, TypeId};
85 use std::collections::HashMap;
86
87 #[derive(Default)]
88 struct TestGlobalsHost {
89 globals: HashMap<TypeId, Box<dyn Any>>,
90 }
91
92 impl GlobalsHost for TestGlobalsHost {
93 fn set_global<T: Any>(&mut self, value: T) {
94 self.globals.insert(TypeId::of::<T>(), Box::new(value));
95 }
96
97 fn global<T: Any>(&self) -> Option<&T> {
98 self.globals
99 .get(&TypeId::of::<T>())
100 .and_then(|value| value.downcast_ref::<T>())
101 }
102
103 fn with_global_mut<T: Any, R>(
104 &mut self,
105 init: impl FnOnce() -> T,
106 f: impl FnOnce(&mut T, &mut Self) -> R,
107 ) -> R {
108 let type_id = TypeId::of::<T>();
109 let mut value = match self.globals.remove(&type_id) {
110 Some(value) => *value
111 .downcast::<T>()
112 .expect("TestGlobalsHost stored wrong type"),
113 None => init(),
114 };
115
116 let out = f(&mut value, self);
117 self.globals.insert(type_id, Box::new(value));
118 out
119 }
120 }
121
122 #[test]
123 fn best_effort_input_context_overlays_authoritative_command_availability() {
124 let mut host = TestGlobalsHost::default();
125 let window = AppWindowId::default();
126
127 host.with_global_mut(WindowInputContextService::default, |svc, _host| {
128 svc.set_snapshot(
129 window,
130 InputContext {
131 edit_can_undo: false,
132 edit_can_redo: false,
133 router_can_back: false,
134 router_can_forward: false,
135 ..Default::default()
136 },
137 );
138 });
139 host.with_global_mut(WindowCommandAvailabilityService::default, |svc, _host| {
140 svc.set_router_availability(window, true, false);
141 svc.set_edit_availability(window, true, false);
142 });
143
144 let input_ctx =
145 best_effort_input_context_for_window(&host, window).expect("published input context");
146 assert!(input_ctx.edit_can_undo);
147 assert!(!input_ctx.edit_can_redo);
148 assert!(input_ctx.router_can_back);
149 assert!(!input_ctx.router_can_forward);
150 }
151
152 #[test]
153 fn best_effort_input_context_fallback_inherits_command_availability() {
154 let mut host = TestGlobalsHost::default();
155 let window = AppWindowId::default();
156
157 host.with_global_mut(WindowCommandAvailabilityService::default, |svc, _host| {
158 svc.set_router_availability(window, true, true);
159 svc.set_edit_availability(window, false, true);
160 });
161
162 let input_ctx = best_effort_input_context_for_window_with_fallback(
163 &host,
164 window,
165 InputContext::default(),
166 );
167 assert!(!input_ctx.edit_can_undo);
168 assert!(input_ctx.edit_can_redo);
169 assert!(input_ctx.router_can_back);
170 assert!(input_ctx.router_can_forward);
171 }
172}