1use 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#[derive(Debug, Clone, PartialEq, Eq)]
22#[non_exhaustive]
23pub struct DebugViewView {
24 pub title: String,
26 pub menu: MenuView,
28 pub empty: Option<String>,
31 pub help: String,
33}
34
35pub struct DebugViewScreen {
38 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 pub const NAME: &'static str = "debug_view";
53
54 pub fn new<G: GameData>(ctx: &Ctx<G>) -> Self {
56 Self {
57 menu: Self::menu_of(ctx),
58 }
59 }
60
61 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 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 pub fn menu(&self) -> &Menu<&'static str> {
84 &self.menu
85 }
86
87 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 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 .then(|| "No screen offers a channel yet".to_owned());
103 DebugViewView {
104 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 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 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}