1pub mod glyph;
40pub mod view;
41
42use crate::prelude::*;
43pub use view::{
44 ChoicesView, GroupView, KeyBindingsView, RestoreView, RowView, SlotView, SwitchView,
45};
46
47use core::fmt;
48
49use shuyi_assets::keymap::{ActionSet, Button, ButtonChord, Chord, Layout, SLOTS, UiAction};
50
51use super::layout_picker;
52use crate::audio::MenuSound;
53use crate::ctx::{CHORD_JOIN_TEXT, Ctx, GameData, Keyboard};
54use crate::frame::glyph_buffer::GlyphBuffer;
55use crate::input::bindings::{LayoutBindings, PadBindings};
56use crate::input::fixed::{
57 capture_abort_key_name, clear_slot_key_name, is_capture_abort, is_clear_slot,
58};
59use crate::input::help_keys::{HelpKeys, SEPARATOR};
60use crate::input::keymap::Keymap;
61use crate::input::pad::PadKind;
62use crate::input::state::Device;
63use crate::screen::{FrameInput, Screen, Transition};
64use crate::widgets::{Ask, AskView};
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum RowGroup {
69 Required,
72 Optional,
74 Shared(&'static str),
77}
78
79pub type KeyRow<A> = (A, &'static str, RowGroup);
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum Side {
88 Keyboard,
90 Controller,
92}
93
94impl Side {
95 fn prompt(self) -> &'static str {
97 match self {
98 Self::Keyboard => CAPTURE_PROMPT,
99 Self::Controller => CAPTURE_BUTTON_PROMPT,
100 }
101 }
102}
103
104pub const CAPTURE_PROMPT: &str = "key_bindings.press_a_key";
106
107pub const CAPTURE_BUTTON_PROMPT: &str = "key_bindings.press_a_button";
110
111pub const TITLE: &str = "key_bindings.title";
113pub const KEYBOARD_SIDE: &str = "key_bindings.keyboard";
116pub const CONTROLLER_SIDE: &str = "key_bindings.controller";
118pub const REQUIRED_HEADING: &str = "key_bindings.must_have_key";
121pub const REQUIRED_BUTTON_HEADING: &str = "key_bindings.must_have_button";
124pub const OPTIONAL_HEADING: &str = "key_bindings.optional";
126pub const RESTORE_DEFAULTS: &str = "key_bindings.restore_defaults";
129pub const CAPTURE_HELP: &str = "key_bindings.capture_help";
131pub const HOLD_TO_CANCEL: &str = "key_bindings.hold_to_cancel";
134pub const CHOICES: [&str; 2] = ["key_bindings.change", "key_bindings.clear"];
137pub const RESERVED_MESSAGE: &str = "key_bindings.reserved";
139pub const MOVED_FLASH_SECS: f32 = 1.5;
142pub const HOLD_TO_CANCEL_SECS: f32 = 1.0;
145
146pub type Paint<G> = fn(&Ctx<G>, &KeyBindingsView<<G as GameData>::Action>, &mut GlyphBuffer);
149
150#[derive(Debug, Clone, Copy, PartialEq)]
154struct Held {
155 first: Button,
156 secs: f32,
157 second: Option<Button>,
158}
159
160impl Held {
161 fn chord(self) -> Option<ButtonChord> {
163 match self.second {
164 Some(second) => ButtonChord::pair(self.first, second),
165 None => Some(ButtonChord::single(self.first)),
166 }
167 }
168
169 fn has(self, button: Button) -> bool {
171 self.first == button || self.second == Some(button)
172 }
173}
174
175fn is_cursor<A: ActionSet>(action: A) -> bool {
178 matches!(
179 action.ui(),
180 Some(UiAction::Up | UiAction::Down | UiAction::Left | UiAction::Right)
181 )
182}
183
184pub fn label<'a, G: GameData>(
187 ctx: &'a Ctx<G>,
188 rows: &[KeyRow<G::Action>],
189 action: G::Action,
190) -> &'a str {
191 rows.iter()
192 .find(|&&(a, ..)| a == action)
193 .map_or(action.name(), |&(_, key, _)| ctx.text(key))
194}
195
196pub fn blocked_message<G: GameData>(
199 ctx: &Ctx<G>,
200 rows: &[KeyRow<G::Action>],
201 side: Side,
202 action: G::Action,
203) -> String {
204 let key = match side {
205 Side::Keyboard => "key_bindings.blocked_key",
206 Side::Controller => "key_bindings.blocked_button",
207 };
208 ctx.text_with(key, &[("action", &label(ctx, rows, action))])
209}
210
211pub struct KeyBindingsScreen<G: GameData> {
214 rows: &'static [KeyRow<G::Action>],
216 paint: Paint<G>,
218 keyboard: Keyboard,
221 layout: Layout,
223 opened_with: Keymap<G::Action>,
227 side: Side,
229 bindings: LayoutBindings<G::Action>,
231 pad: PadBindings<G::Action>,
233 row: usize,
237 slot: usize,
239 capturing: bool,
241 held: Option<Held>,
243 releasing: Vec<Button>,
246 choice: Option<usize>,
248 message: Option<String>,
250 moved: Option<(G::Action, f32)>,
253 restoring: Option<Ask<bool>>,
255 await_release: bool,
260}
261
262impl<G: GameData> fmt::Debug for KeyBindingsScreen<G> {
263 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264 f.debug_struct("KeyBindingsScreen")
266 .field("layout", &self.layout)
267 .field("side", &self.side)
268 .field("row", &self.row)
269 .field("slot", &self.slot)
270 .field("capturing", &self.capturing)
271 .field("choice", &self.choice)
272 .field("restoring", &self.restoring.as_ref().map(Ask::key))
273 .finish_non_exhaustive()
274 }
275}
276
277impl<G: GameData> Clone for KeyBindingsScreen<G> {
278 fn clone(&self) -> Self {
279 Self {
280 rows: self.rows,
281 paint: self.paint,
282 keyboard: self.keyboard,
283 layout: self.layout,
284 opened_with: self.opened_with.clone(),
285 side: self.side,
286 bindings: self.bindings.clone(),
287 pad: self.pad.clone(),
288 row: self.row,
289 slot: self.slot,
290 capturing: self.capturing,
291 held: self.held,
292 releasing: self.releasing.clone(),
293 choice: self.choice,
294 message: self.message.clone(),
295 moved: self.moved,
296 restoring: self.restoring.clone(),
297 await_release: self.await_release,
298 }
299 }
300}
301
302impl<G: GameData> KeyBindingsScreen<G> {
303 pub const NAME: &'static str = "key_bindings";
305
306 pub fn new(ctx: &Ctx<G>, rows: &'static [KeyRow<G::Action>]) -> Self {
312 let layout = ctx.shown_layout().unwrap_or(Layout::named("none"));
314 let side = match ctx.device {
315 _ if ctx.platform.keyboard == Keyboard::Absent => Side::Controller,
316 Device::Keyboard => Side::Keyboard,
317 Device::Pad(_) => Side::Controller,
318 };
319 Self {
320 rows,
321 paint: glyph::paint,
322 keyboard: ctx.platform.keyboard,
323 layout,
324 opened_with: ctx.keymap.clone(),
325 side,
326 bindings: ctx.layout_bindings(layout),
327 pad: ctx.pad_bindings(),
328 row: 0,
329 slot: 0,
330 capturing: false,
331 held: None,
332 releasing: Vec::new(),
333 choice: None,
334 message: None,
335 moved: None,
336 restoring: None,
337 await_release: false,
338 }
339 }
340
341 #[must_use]
344 pub fn painted_by(mut self, paint: Paint<G>) -> Self {
345 self.paint = paint;
346 self
347 }
348
349 pub fn is_controller_only(&self) -> bool {
352 self.keyboard == Keyboard::Absent
353 }
354
355 #[must_use]
357 pub fn with_bindings(mut self, bindings: LayoutBindings<G::Action>) -> Self {
358 self.bindings = bindings;
359 self
360 }
361
362 pub fn bindings(&self) -> &LayoutBindings<G::Action> {
364 &self.bindings
365 }
366
367 pub fn pad_bindings(&self) -> &PadBindings<G::Action> {
369 &self.pad
370 }
371
372 pub fn side(&self) -> Side {
374 self.side
375 }
376
377 pub fn focus(&self) -> Option<(G::Action, usize)> {
380 self.rows
381 .get(self.row)
382 .map(|&(action, ..)| (action, self.slot))
383 }
384
385 pub fn is_on_switch(&self) -> bool {
387 !self.is_controller_only() && self.row == self.switch_row()
388 }
389
390 fn restore_row(&self) -> usize {
392 self.rows.len()
393 }
394
395 fn switch_row(&self) -> usize {
398 self.rows.len() + 1
399 }
400
401 pub fn is_capturing(&self) -> bool {
403 self.capturing
404 }
405
406 pub fn choice(&self) -> Option<usize> {
408 self.choice
409 }
410
411 pub fn is_asking_restore(&self) -> bool {
413 self.restoring.is_some()
414 }
415
416 pub fn restore_question(&self, ctx: &Ctx<G>) -> String {
418 Self::restore_view(ctx, &self.restore_ask(), self.layout, ctx.help_keys()).text
419 }
420
421 fn restore_ask(&self) -> Ask<bool> {
424 let key = match self.side {
425 Side::Keyboard => "key_bindings.restore_keys",
426 Side::Controller => "key_bindings.restore_buttons",
427 };
428 Ask::yes_no(key, "key_bindings.restore_answers")
429 }
430
431 fn restore_view(
434 ctx: &Ctx<G>,
435 ask: &Ask<bool>,
436 layout: Layout,
437 keys: HelpKeys<'_, G::Action>,
438 ) -> AskView<bool> {
439 let layout = layout_picker::label(ctx, layout);
440 ask.view_with_keys(ctx, keys, &[("layout", &layout)])
441 }
442
443 pub fn message(&self) -> Option<&str> {
445 self.message.as_deref()
446 }
447
448 fn slot_names(&self, ctx: &Ctx<G>, action: G::Action) -> [Option<String>; SLOTS] {
452 match self.side {
453 Side::Keyboard => self
454 .bindings
455 .slots(action)
456 .map(|c| c.map(|c| c.to_string())),
457 Side::Controller => {
458 let kind = match ctx.device {
459 Device::Pad(kind) => kind,
460 Device::Keyboard => PadKind::default(),
461 };
462 let join = ctx.text(CHORD_JOIN_TEXT);
463 let name = |b| kind.chord_name(b, join);
464 self.pad.slots(action).map(|b| b.map(name))
465 }
466 }
467 }
468
469 fn is_unmapped(&self, action: G::Action) -> bool {
471 match self.side {
472 Side::Keyboard => self.bindings.is_unmapped(action),
473 Side::Controller => self.pad.is_unmapped(action),
474 }
475 }
476
477 fn first_unmapped(&self, side: Side) -> Option<G::Action> {
480 let unmapped = match side {
481 Side::Keyboard => self.bindings.unmapped_required(),
482 Side::Controller => self.pad.unmapped_required(),
483 };
484 let mut rows = self.rows.iter().map(|&(action, ..)| action);
485 rows.find(|action| unmapped.contains(action))
486 }
487
488 fn tick(&mut self, dt: f32) {
491 if let Some((_, left)) = &mut self.moved {
492 *left -= dt;
493 if *left <= 0.0 {
494 self.moved = None;
495 }
496 }
497 }
498
499 fn move_row(&mut self, ctx: &mut Ctx<G>, down: bool) {
503 let rows = self.rows.len() + if self.is_controller_only() { 1 } else { 2 };
504 self.row = if down {
505 (self.row + 1) % rows
506 } else {
507 (self.row + rows - 1) % rows
508 };
509 self.message = None;
510 ctx.audio.menu(MenuSound::Move);
511 }
512
513 fn move_across(&mut self, ctx: &mut Ctx<G>, right: bool) {
517 if self.is_on_switch() {
518 let side = if right {
519 Side::Controller
520 } else {
521 Side::Keyboard
522 };
523 self.show(ctx, side);
524 return;
525 }
526 if self.focus().is_none() {
527 return;
528 }
529 let to = if right {
530 (self.slot + 1).min(SLOTS - 1)
531 } else {
532 self.slot.saturating_sub(1)
533 };
534 if to != self.slot {
535 self.slot = to;
536 self.message = None;
537 ctx.audio.menu(MenuSound::Move);
538 }
539 }
540
541 fn show(&mut self, ctx: &mut Ctx<G>, side: Side) {
543 if side != self.side {
544 self.side = side;
545 self.message = None;
546 self.moved = None;
547 ctx.audio.menu(MenuSound::Move);
548 }
549 }
550
551 fn confirm(&mut self, ctx: &mut Ctx<G>) {
555 self.message = None;
556 if self.is_on_switch() {
557 let other = match self.side {
558 Side::Keyboard => Side::Controller,
559 Side::Controller => Side::Keyboard,
560 };
561 self.show(ctx, other);
562 return;
563 }
564 if self.focus().is_none() {
565 self.restoring = Some(self.restore_ask());
566 } else if matches!(ctx.device, Device::Pad(_)) {
567 self.choice = Some(0);
568 } else {
569 self.capturing = true;
570 }
571 ctx.audio.menu(MenuSound::Select);
572 }
573
574 fn answer_choice(&mut self, ctx: &mut Ctx<G>, action: G::Action) -> bool {
578 let Some(line) = self.choice else {
579 return true;
580 };
581 match action.ui() {
582 Some(UiAction::Up | UiAction::Down) => {
583 self.choice = Some((line + 1) % CHOICES.len());
584 ctx.audio.menu(MenuSound::Move);
585 return false;
586 }
587 Some(UiAction::Confirm) if line == 0 => {
588 self.capturing = true;
589 ctx.audio.menu(MenuSound::Select);
590 }
591 Some(UiAction::Confirm) => {
592 if !self.clear_slot(ctx) {
593 ctx.audio.menu(MenuSound::Select);
594 }
595 }
596 Some(UiAction::Cancel) => ctx.audio.menu(MenuSound::Cancel),
597 _ => return false,
598 }
599 self.choice = None;
600 true
601 }
602
603 fn answer_restore(&mut self, ctx: &mut Ctx<G>, action: G::Action) -> bool {
607 let Some(ask) = &mut self.restoring else {
608 return true;
609 };
610 match ask.handle(action, &mut ctx.audio) {
611 Some(true) => {
612 let def = ctx.game.keymap_def();
613 match self.side {
614 Side::Keyboard => self.bindings = LayoutBindings::defaults(def, self.layout),
615 Side::Controller => self.pad = PadBindings::defaults(def),
616 }
617 self.moved = None;
618 ctx.audio.menu(MenuSound::Select);
619 }
620 Some(false) => ctx.audio.menu(MenuSound::Cancel),
621 None => return false,
622 }
623 self.restoring = None;
624 true
625 }
626
627 fn clear_slot(&mut self, ctx: &mut Ctx<G>) -> bool {
630 let Some((action, slot)) = self.focus() else {
631 return false;
632 };
633 if self.slot_names(ctx, action)[slot].is_none() {
634 return false;
635 }
636 match self.side {
637 Side::Keyboard => self.bindings.clear(action, slot),
638 Side::Controller => self.pad.clear(action, slot),
639 }
640 self.message = None;
641 ctx.audio.menu(MenuSound::Select);
642 true
643 }
644
645 fn abort_capture(&mut self, ctx: &mut Ctx<G>) {
647 self.end_capture();
648 ctx.audio.menu(MenuSound::Cancel);
649 }
650
651 fn end_capture(&mut self) {
653 self.capturing = false;
654 self.held = None;
655 self.message = None;
656 }
657
658 fn captured(&mut self, ctx: &mut Ctx<G>, from: Option<(G::Action, usize)>) {
661 self.end_capture();
662 self.moved = from.map(|(lost, _)| (lost, MOVED_FLASH_SECS));
663 ctx.audio.menu(MenuSound::Select);
664 }
665
666 fn capture(&mut self, ctx: &mut Ctx<G>, chord: Chord) -> bool {
668 if is_clear_slot(chord) {
669 return false;
670 }
671 if is_capture_abort(chord) {
672 self.abort_capture(ctx);
673 return true;
674 }
675 let Some((action, slot)) = self.focus() else {
676 self.end_capture();
677 return true;
678 };
679 if self.side == Side::Controller {
680 return false;
682 }
683 let Ok(from) = self.bindings.bind(action, slot, chord) else {
684 self.message = Some(ctx.text(RESERVED_MESSAGE).to_owned());
685 ctx.audio.menu(MenuSound::Denied);
686 return false;
687 };
688 self.captured(ctx, from);
689 true
690 }
691
692 fn capture_buttons(&mut self, ctx: &mut Ctx<G>, input: &FrameInput<G::Action>) {
699 let fresh = self.held.is_none();
700 let mut pressed = input.pressed_buttons().iter().copied();
701 if fresh {
702 self.held = pressed.next().map(|first| Held {
703 first,
704 secs: 0.0,
705 second: None,
706 });
707 }
708 let Some(mut held) = self.held else {
709 return;
710 };
711 if held.second.is_none() {
712 held.second = pressed.find(|&b| b != held.first);
713 }
714 let released = input.released_buttons();
715 if released.iter().any(|&b| held.has(b)) {
716 self.held = None;
717 let still_down = [Some(held.first), held.second].into_iter().flatten();
718 self.releasing = still_down.filter(|b| !released.contains(b)).collect();
719 if let Some(chord) = held.chord() {
720 self.tapped(ctx, chord);
721 }
722 return;
723 }
724 if !fresh {
726 held.secs += input.dt();
727 }
728 self.held = Some(held);
729 if held.second.is_none() && held.secs >= HOLD_TO_CANCEL_SECS {
730 self.abort_capture(ctx);
731 self.releasing = vec![held.first];
732 }
733 }
734
735 fn tapped(&mut self, ctx: &mut Ctx<G>, chord: ButtonChord) {
738 let (Side::Controller, Some((action, slot))) = (self.side, self.focus()) else {
739 return;
740 };
741 if let Ok(from) = self.pad.bind(action, slot, chord) {
743 self.captured(ctx, from);
744 }
745 }
746
747 fn leave(&mut self, ctx: &mut Ctx<G>) -> bool {
752 let other = match self.side {
753 Side::Keyboard => Side::Controller,
754 Side::Controller => Side::Keyboard,
755 };
756 let sides = if self.is_controller_only() {
757 &[Side::Controller][..]
758 } else {
759 &[self.side, other][..]
760 };
761 let blocked = sides
762 .iter()
763 .find_map(|&side| Some((side, self.first_unmapped(side)?)));
764 if let Some((side, action)) = blocked {
765 self.side = side;
766 self.message = Some(blocked_message(ctx, self.rows, side, action));
767 ctx.audio.menu(MenuSound::Denied);
768 return false;
769 }
770 if self.bindings != ctx.layout_bindings(self.layout) {
772 let _ = ctx.set_layout_bindings(self.layout, self.bindings.clone());
773 }
774 if self.pad != ctx.pad_bindings() {
775 let _ = ctx.set_pad_bindings(self.pad.clone());
776 }
777 ctx.audio.menu(MenuSound::Cancel);
778 true
779 }
780
781 pub fn help(&self, ctx: &Ctx<G>) -> String {
785 let km = ctx.help_keys().of(&self.opened_with);
786 let on_pad = matches!(ctx.device, Device::Pad(_));
787 if self.capturing {
788 let what = ctx.text(match self.side {
789 Side::Keyboard => CAPTURE_HELP,
790 Side::Controller => CAPTURE_BUTTON_PROMPT,
791 });
792 let hold = on_pad || self.side == Side::Controller;
793 let hold = hold.then(|| ctx.text(HOLD_TO_CANCEL));
794 let back = (!on_pad).then(|| {
795 let key = capture_abort_key_name();
796 ctx.text_with("key_bindings.help_capture_back", &[("key", &key)])
797 });
798 let hints = [Some(what), hold, back.as_deref()];
799 return hints
800 .into_iter()
801 .flatten()
802 .collect::<Vec<_>>()
803 .join(SEPARATOR);
804 }
805 if self.restoring.is_some() {
806 return ctx.text_with_keys("key_bindings.help_restore", km, &[]);
807 }
808 if self.choice.is_some() {
809 return ctx.text_with("key_bindings.help_choice", &[]);
810 }
811 if self.is_on_switch() {
812 return ctx.text_with("key_bindings.help_switch", &[]);
813 }
814 let on_slot = self.focus().is_some();
815 if on_slot && on_pad {
816 return ctx.text_with("key_bindings.help_slot_pad", &[]);
817 }
818 if on_slot {
819 let clear = clear_slot_key_name();
820 return ctx.text_with_keys("key_bindings.help_slot", km, &[("clear", &clear)]);
821 }
822 ctx.text_with_keys("key_bindings.help_restore_row", km, &[])
823 }
824
825 pub fn view(&self, ctx: &Ctx<G>) -> KeyBindingsView<G::Action> {
831 let columns = (0..SLOTS).map(|j| match self.side {
832 Side::Keyboard => ctx.text_with("key_bindings.key_column", &[("n", &(j + 1))]),
833 Side::Controller => ctx.text_with("key_bindings.button_column", &[("n", &(j + 1))]),
834 });
835 let mut groups: Vec<GroupView<G::Action>> = Vec::new();
836 let mut last = None;
837 for (i, &(_, _, group)) in self.rows.iter().enumerate() {
838 if last != Some(group) {
839 last = Some(group);
840 groups.push(GroupView {
841 heading: self.group_heading(ctx, group),
842 required: group == RowGroup::Required,
843 rows: Vec::new(),
844 });
845 }
846 if let Some(group) = groups.last_mut() {
847 group.rows.push(self.row_view(ctx, i));
848 }
849 }
850 let km = ctx.help_keys().of(&self.opened_with);
851 KeyBindingsView {
852 title: ctx.text(TITLE).to_owned(),
853 switch: (!self.is_controller_only()).then(|| SwitchView {
854 keyboard: format!(
855 "{}{SEPARATOR}{}",
856 ctx.text(KEYBOARD_SIDE),
857 layout_picker::label(ctx, self.layout)
858 ),
859 controller: ctx.text(CONTROLLER_SIDE).to_owned(),
860 shown: self.side,
861 focused: self.is_on_switch(),
862 }),
863 columns: columns.collect(),
864 groups,
865 restore: RestoreView {
866 label: ctx.text(RESTORE_DEFAULTS).to_owned(),
867 focused: self.row == self.restore_row(),
868 },
869 not_mapped: ctx.help_keys().not_mapped().to_owned(),
870 message: self.message.clone(),
871 question: self
872 .restoring
873 .as_ref()
874 .map(|ask| Self::restore_view(ctx, ask, self.layout, km)),
875 choices: self.choice.map(|focused| ChoicesView {
876 lines: CHOICES.map(|key| ctx.text(key).to_owned()).to_vec(),
877 focused,
878 slot: self.slot,
879 }),
880 help: self.help(ctx),
881 }
882 }
883
884 fn group_heading(&self, ctx: &Ctx<G>, group: RowGroup) -> String {
886 let key = match (group, self.side) {
887 (RowGroup::Shared(heading), _) => heading,
888 (RowGroup::Required, Side::Keyboard) => REQUIRED_HEADING,
889 (RowGroup::Required, Side::Controller) => REQUIRED_BUTTON_HEADING,
890 (RowGroup::Optional, _) => OPTIONAL_HEADING,
891 };
892 ctx.text(key).to_owned()
893 }
894
895 fn row_view(&self, ctx: &Ctx<G>, i: usize) -> RowView<G::Action> {
897 let (action, label, _) = self.rows[i];
898 let label = ctx.text(label);
899 let focused = self.row == i;
900 let fixed = match self.side {
901 Side::Keyboard => Keymap::fixed_chords_for(action)
902 .iter()
903 .map(ToString::to_string)
904 .collect(),
905 Side::Controller => Vec::new(),
906 };
907 let slots = self.slot_names(ctx, action).into_iter().enumerate();
908 let slots = slots.map(|(j, name)| match name {
909 _ if focused && self.slot == j && self.capturing => {
910 SlotView::Capturing(ctx.text(self.side.prompt()).to_owned())
911 }
912 Some(name) => SlotView::Bound(name),
913 None => SlotView::Empty,
914 });
915 let unmapped = self.is_unmapped(action);
916 RowView {
917 action,
918 label: label.to_owned(),
919 fixed,
920 slots: slots.collect(),
921 focus: focused.then_some(self.slot),
922 unmapped,
923 blocks_leaving: unmapped && action.is_required(),
924 lost_key: self.moved.is_some_and(|(lost, _)| lost == action),
925 }
926 }
927}
928
929impl<G: GameData> Screen<G> for KeyBindingsScreen<G> {
930 fn name(&self) -> &'static str {
931 Self::NAME
932 }
933
934 fn actions(&self) -> Option<Vec<G::Action>> {
936 Some(crate::input::ui_actions(crate::input::NAVIGATION))
937 }
938
939 fn update(&mut self, ctx: &mut Ctx<G>, input: &FrameInput<G::Action>) -> Transition<G> {
940 self.tick(input.dt());
941 if !self.releasing.is_empty() {
942 let up = input.released_buttons();
944 self.releasing.retain(|b| !up.contains(b));
945 self.await_release &= <G::Action as ActionSet>::ALL
946 .iter()
947 .any(|&a| is_cursor(a) && input.is_held(a));
948 return Transition::None;
949 }
950 if self.capturing {
951 if !input.pressed_chords().iter().any(|&c| self.capture(ctx, c)) {
954 self.capture_buttons(ctx, input);
955 }
956 if !self.capturing {
957 self.await_release = <G::Action as ActionSet>::ALL
958 .iter()
959 .any(|&a| is_cursor(a) && input.is_held(a));
960 }
961 return Transition::None;
962 }
963 if self.restoring.is_some() {
964 for &action in &input.actions {
965 if self.answer_restore(ctx, action) {
966 break;
967 }
968 }
969 return Transition::None;
970 }
971 if self.choice.is_some() {
972 for &action in &input.actions {
973 if self.answer_choice(ctx, action) {
974 break;
975 }
976 }
977 return Transition::None;
978 }
979 if input.pressed_chords().iter().any(|&c| is_clear_slot(c)) {
980 self.clear_slot(ctx);
981 }
982 self.await_release &= <G::Action as ActionSet>::ALL
983 .iter()
984 .any(|&a| is_cursor(a) && input.is_held(a));
985 for &action in &input.actions {
986 match action.ui() {
987 _ if self.await_release && is_cursor(action) => {}
988 Some(UiAction::Up) => self.move_row(ctx, false),
989 Some(UiAction::Down) => self.move_row(ctx, true),
990 Some(UiAction::Left) => self.move_across(ctx, false),
991 Some(UiAction::Right) => self.move_across(ctx, true),
992 Some(UiAction::Confirm) => {
993 self.confirm(ctx);
994 if self.capturing || self.restoring.is_some() || self.choice.is_some() {
995 break;
999 }
1000 }
1001 Some(UiAction::Cancel) if self.leave(ctx) => return Transition::Pop,
1002 _ => {}
1003 }
1004 }
1005 Transition::None
1006 }
1007
1008 fn draw(&self, ctx: &Ctx<G>, buf: &mut GlyphBuffer) {
1009 (self.paint)(ctx, &self.view(ctx), buf);
1010 }
1011
1012 fn as_any(&self) -> Option<&dyn core::any::Any> {
1013 Some(self)
1014 }
1015}
1016
1017#[cfg(test)]
1018mod tests;