1use crate::*;
2use alux_ext::ext;
3use rsynko_manager::*;
4
5pub type ManagerKeyBinding<Manager> = KeyBinding<
7 <Manager as ManagerSorts>::Id,
8 <Manager as ManagerSorts>::Source,
9 <Manager as ManagerSorts>::Format,
10 <Manager as ManagerSorts>::Change,
11>;
12
13pub struct KeyBinding<Id, Source, Format, Change> {
18 pub action: Option<ManagerAction>,
20 pub keys: Vec<Keystroke>,
22 pub intent: ManagerIntentOp<Id, Source, Format, Change>,
24}
25
26impl<Id, Source, Format, Change> KeyBinding<Id, Source, Format, Change> {
27 fn action(
29 action: ManagerAction,
30 keys: impl IntoIterator<Item = Key>,
31 intent: ManagerIntentOp<Id, Source, Format, Change>,
32 ) -> Self {
33 Self {
34 action: Some(action),
35 keys: keys.into_iter().map(Keystroke::plain).collect(),
36 intent,
37 }
38 }
39
40 fn ungated(
42 keys: impl IntoIterator<Item = Key>,
43 intent: ManagerIntentOp<Id, Source, Format, Change>,
44 ) -> Self {
45 Self {
46 action: None,
47 keys: keys.into_iter().map(Keystroke::plain).collect(),
48 intent,
49 }
50 }
51
52 fn binds(&self, stroke: Keystroke) -> bool {
54 self.keys.contains(&stroke)
55 }
56}
57
58#[ext(name = KeyBindingExt)]
60pub impl<This> This
61where
62 This: NavigationStateAlg,
63{
64 fn page_bindings(&self) -> impl Iterator<Item = ManagerKeyBinding<This>> {
69 let bindings = match self.page() {
70 ManagerPage::Collection => vec![
71 KeyBinding::action(
72 ManagerAction::Previous,
73 [Key::Up, Key::Character('k')],
74 ManagerIntentOp::SelectPrevious {},
75 ),
76 KeyBinding::action(
77 ManagerAction::Next,
78 [Key::Down, Key::Character('j')],
79 ManagerIntentOp::SelectNext {},
80 ),
81 KeyBinding::action(
82 ManagerAction::Activate,
83 [Key::Enter],
84 ManagerIntentOp::OpenSelected {},
85 ),
86 KeyBinding::action(
87 ManagerAction::AddSources,
88 [Key::Character('a')],
89 ManagerIntentOp::OpenAddSources {},
90 ),
91 KeyBinding::action(
92 ManagerAction::Space,
93 [Key::Character(' ')],
94 ManagerIntentOp::ApplySelectedSpace {},
95 ),
96 KeyBinding::action(
97 ManagerAction::Remove,
98 [Key::Delete],
99 ManagerIntentOp::RemoveSelected {},
100 ),
101 ],
102 ManagerPage::Details(_) => vec![
103 KeyBinding::action(
104 ManagerAction::Previous,
105 [Key::Up, Key::Character('k')],
106 ManagerIntentOp::SelectPreviousDetail {},
107 ),
108 KeyBinding::action(
109 ManagerAction::Next,
110 [Key::Down, Key::Character('j')],
111 ManagerIntentOp::SelectNextDetail {},
112 ),
113 KeyBinding::action(
114 ManagerAction::Activate,
115 [Key::Enter],
116 ManagerIntentOp::ActivateDetail {},
117 ),
118 KeyBinding::action(
119 ManagerAction::Space,
120 [Key::Character(' ')],
121 ManagerIntentOp::ApplySelectedSpace {},
122 ),
123 KeyBinding::action(
124 ManagerAction::Back,
125 [Key::Escape, Key::Backspace],
126 ManagerIntentOp::Back {},
127 ),
128 ],
129 ManagerPage::Formats(_) => vec![
130 KeyBinding::action(
131 ManagerAction::Previous,
132 [Key::Up, Key::Character('k')],
133 ManagerIntentOp::SelectPreviousFormat {},
134 ),
135 KeyBinding::action(
136 ManagerAction::Next,
137 [Key::Down, Key::Character('j')],
138 ManagerIntentOp::SelectNextFormat {},
139 ),
140 KeyBinding::action(
142 ManagerAction::Activate,
143 [Key::Enter],
144 ManagerIntentOp::Back {},
145 ),
146 KeyBinding::action(
147 ManagerAction::Back,
148 [Key::Escape, Key::Backspace],
149 ManagerIntentOp::Back {},
150 ),
151 ],
152 ManagerPage::Log(_) | ManagerPage::Report(_) | ManagerPage::Command(_) => {
153 vec![KeyBinding::action(
154 ManagerAction::Back,
155 [Key::Escape, Key::Enter],
156 ManagerIntentOp::Back {},
157 )]
158 }
159 ManagerPage::AddSources => editor_bindings(ManagerIntentOp::SubmitDraft {}),
160 ManagerPage::Output(_) => editor_bindings(ManagerIntentOp::SubmitOutput {}),
161 ManagerPage::Input(_) => editor_bindings(ManagerIntentOp::SubmitInput {}),
162 };
163 bindings.into_iter()
164 }
165
166 fn keystroke_meaning(&self, stroke: Keystroke) -> Option<ManagerKeyBinding<This>> {
171 if stroke == EXIT_KEYSTROKE {
172 return Some(KeyBinding::action(
173 ManagerAction::Exit,
174 [EXIT_KEYSTROKE.key],
175 ManagerIntentOp::SafeExitRequested {},
176 ));
177 }
178 if stroke.modified() {
179 return None;
180 }
181 self.page_bindings()
182 .find(|binding| binding.binds(stroke))
183 .or_else(|| self.typed_meaning(stroke.key.typed()?))
184 }
185
186 fn typed_meaning(&self, typed: char) -> Option<ManagerKeyBinding<This>> {
188 self.page_accepts_text().then(|| {
189 KeyBinding::ungated(
190 [Key::Character(typed)],
191 ManagerIntentOp::InsertText {
192 text: typed.to_string(),
193 },
194 )
195 })
196 }
197
198 fn paste_meaning(&self, text: &str) -> Option<ManagerKeyBinding<This>> {
200 self.page_accepts_text().then(|| {
201 KeyBinding::ungated(
202 [],
203 ManagerIntentOp::InsertText {
204 text: text.to_owned(),
205 },
206 )
207 })
208 }
209
210 fn page_accepts_text(&self) -> bool {
212 matches!(
213 self.page(),
214 ManagerPage::AddSources | ManagerPage::Output(_) | ManagerPage::Input(_)
215 )
216 }
217
218 fn action_keys(&self, action: ManagerAction) -> impl Iterator<Item = Keystroke> {
220 if action == ManagerAction::Exit {
221 return vec![EXIT_KEYSTROKE].into_iter();
222 }
223 self.page_bindings()
224 .find(|binding| binding.action == Some(action))
225 .map(|binding| binding.keys)
226 .unwrap_or_default()
227 .into_iter()
228 }
229}
230
231#[ext(name = KeyInputExt)]
233pub impl<This> This
234where
235 This: NavigationStateAlg
236 + QueueCatalogAlg
237 + QueueAppendAlg
238 + QueueRemoveAlg
239 + QueuePauseAlg
240 + QueueDuplicateAlg
241 + QueueFormatEditAlg
242 + FormatCatalogStateAlg
243 + QueueDryRunAlg
244 + RehearsalStateAlg
245 + TransferStateAlg
246 + DetailSelectionAlg
247 + DraftStateAlg
248 + OutputDraftAlg
249 + InputDraftAlg
250 + QueueOutputAlg
251 + QueueSourceAlg
252 + SourceMetadataAlg
253 + TextEditorStateAlg
254 + ManagerStatusAlg
255 + SafeExitAlg
256 + SourceRequestAlg
257 + SubmissionAlg
258 + MediaOptionsAlg
259 + OutputChoiceAlg,
260 This::Entry: QueueEntryAlg + RequestOptionsAlg,
261 This::Id: Copy + Eq,
262{
263 fn apply_keystroke(&mut self, stroke: Keystroke) -> bool {
268 self.keystroke_meaning(stroke)
269 .is_some_and(|binding| self.apply_binding(binding))
270 }
271
272 fn apply_paste(&mut self, text: &str) -> bool {
274 self.paste_meaning(text)
275 .is_some_and(|binding| self.apply_binding(binding))
276 }
277
278 fn apply_binding(&mut self, binding: ManagerKeyBinding<This>) -> bool {
280 let enabled = binding
281 .action
282 .is_none_or(|action| self.action_availability(action).is_enabled());
283 if enabled {
284 self.apply_manager_event(binding.intent);
285 }
286 enabled
287 }
288}
289
290fn editor_bindings<Id, Source, Format, Change>(
292 submit: ManagerIntentOp<Id, Source, Format, Change>,
293) -> Vec<KeyBinding<Id, Source, Format, Change>> {
294 vec![
295 KeyBinding::action(
296 ManagerAction::Previous,
297 [Key::Left],
298 ManagerIntentOp::MoveCursorLeft {},
299 ),
300 KeyBinding::action(
301 ManagerAction::Next,
302 [Key::Right],
303 ManagerIntentOp::MoveCursorRight {},
304 ),
305 KeyBinding::action(ManagerAction::Activate, [Key::Enter], submit),
306 KeyBinding::action(ManagerAction::Back, [Key::Escape], ManagerIntentOp::Back {}),
307 KeyBinding::ungated([Key::Backspace], ManagerIntentOp::DeleteBeforeCursor {}),
308 KeyBinding::ungated([Key::Delete], ManagerIntentOp::DeleteAtCursor {}),
309 KeyBinding::ungated([Key::Home], ManagerIntentOp::MoveCursorHome {}),
310 KeyBinding::ungated([Key::End], ManagerIntentOp::MoveCursorEnd {}),
311 ]
312}