Skip to main content

shuyi_ui/debug/
view.rs

1//! The "Debug view" screen of the debug menu (ADR-0085): every channel the
2//! running screens have offered, each on or off, toggled with Confirm.
3//! The choice is saved ([`Ctx::set_debug_channel`]); the game shows the
4//! channels on over its screens once the menu is closed.
5
6use core::fmt;
7
8use shuyi_assets::keymap::{ActionSet, UiAction};
9
10use super::glyph;
11use crate::ctx::{Ctx, GameData};
12use crate::frame::glyph_buffer::GlyphBuffer;
13use crate::input::help_keys::{cursor_keys_name, help_line, key_name};
14use crate::log::{LogCategory, LogLevel};
15use crate::prelude::*;
16use crate::screen::{FrameInput, Screen, Transition};
17use crate::widgets::menu::SuffixKind;
18use crate::widgets::{Menu, MenuEvent, MenuItem, MenuView};
19
20/// What the "Debug view" screen shows.
21#[derive(Debug, Clone, PartialEq, Eq)]
22#[non_exhaustive]
23pub struct DebugViewView {
24    /// Its title.
25    pub title: String,
26    /// The channels, each with on or off after its name.
27    pub menu: MenuView,
28    /// What it says when no screen has offered a channel yet; `None` when
29    /// some have.
30    pub empty: Option<String>,
31    /// The help line.
32    pub help: String,
33}
34
35/// The channels offered, on or off: Confirm turns the focused one on or
36/// off, Cancel closes the screen.
37pub struct DebugViewScreen {
38    /// Each item stands for its channel's id.
39    menu: Menu<&'static str>,
40}
41
42impl fmt::Debug for DebugViewScreen {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        f.debug_struct("DebugViewScreen")
45            .field("menu", &self.menu)
46            .finish()
47    }
48}
49
50impl DebugViewScreen {
51    /// Name reported by [`Screen::name`].
52    pub const NAME: &'static str = "debug_view";
53
54    /// The channels `ctx`'s screens have offered, the first focused.
55    pub fn new<G: GameData>(ctx: &Ctx<G>) -> Self {
56        Self {
57            menu: Self::menu_of(ctx),
58        }
59    }
60
61    /// The menu of the channels offered now, each on or off.
62    fn menu_of<G: GameData>(ctx: &Ctx<G>) -> Menu<&'static str> {
63        let view = &ctx.debug_view;
64        let items = view
65            .offered()
66            .iter()
67            .map(|channel| {
68                // check-text: not player text (debug tools are English only)
69                let state = if view.is_on(channel.id()) {
70                    "  on"
71                } else {
72                    "  off"
73                };
74                MenuItem::new(channel.label())
75                    .with_suffix(state, SuffixKind::Aside)
76                    .with_value(channel.id())
77            })
78            .collect();
79        Menu::new(items)
80    }
81
82    /// The menu: its items, each standing for its channel's id, and focus.
83    pub fn menu(&self) -> &Menu<&'static str> {
84        &self.menu
85    }
86
87    /// What the screen shows, naming the keys of the device used last.
88    pub fn view<G: GameData>(&self, ctx: &Ctx<G>) -> DebugViewView {
89        let km = ctx.help_keys();
90        let key = |ui| G::Action::for_ui(ui).map(|a| key_name(km, a));
91        // check-text: not player text (debug tools are English only)
92        let help = help_line(&[
93            (Some(cursor_keys_name(km)), "move"),
94            (key(UiAction::Confirm), "on/off"),
95            (key(UiAction::Cancel), "back"),
96        ]);
97        let empty = self
98            .menu
99            .items()
100            .is_empty()
101            // check-text: not player text (debug tools are English only)
102            .then(|| "No screen offers a channel yet".to_owned());
103        DebugViewView {
104            // check-text: not player text (debug tools are English only)
105            title: "Debug view".to_owned(),
106            menu: self.menu.view(),
107            empty,
108            help,
109        }
110    }
111}
112
113impl<G: GameData> Screen<G> for DebugViewScreen {
114    fn name(&self) -> &'static str {
115        Self::NAME
116    }
117
118    /// Its menu's.
119    fn actions(&self) -> Option<Vec<G::Action>> {
120        Some(crate::input::ui_actions(Menu::<()>::ACTIONS))
121    }
122
123    fn update(&mut self, ctx: &mut Ctx<G>, input: &FrameInput<G::Action>) -> Transition<G> {
124        for &action in &input.actions {
125            match self.menu.handle_with_sound(action, &mut ctx.audio) {
126                Some(MenuEvent::Cancelled) => return Transition::Pop,
127                Some(MenuEvent::Chosen(id)) => {
128                    let on = !ctx.debug_view.is_on(id);
129                    if let Err(e) = ctx.set_debug_channel(id, on) {
130                        let (level, category) = (LogLevel::Warn, LogCategory::Storage);
131                        // check-text: not player text (a warning for the log)
132                        ctx.log(level, category, format!("debug view not saved: {e}"));
133                    }
134                    let focus = self.menu.focus();
135                    self.menu = Self::menu_of(ctx).focused(focus);
136                }
137                None => {}
138            }
139        }
140        Transition::None
141    }
142
143    fn draw(&self, ctx: &Ctx<G>, buf: &mut GlyphBuffer) {
144        glyph::paint_view(ctx, &self.view(ctx), buf);
145    }
146}