Skip to main content

shuyi_ui/
ctx.rs

1//! [`Ctx`]: what every screen shares, passed `&mut` to
2//! [`Screen::update`](crate::Screen::update) and `&` to
3//! [`Screen::draw`](crate::Screen::draw). The engine's services (text by
4//! key in the player's language, the keys and the names help text gives
5//! them, the player's settings and profiles, sounds, music and voices,
6//! storage) and the game's own part, [`Ctx::game`], whose type says what
7//! the engine reads from the game's data ([`GameData`]).
8//!
9//! A plain struct: no service locator, no globals. A game names its own
10//! with an alias (`type Ctx = shuyi_ui::Ctx<MyGame>`). The engine's
11//! services are fields; a module's state is found by its type
12//! ([`Ctx::module_state`]), put there by its
13//! [`install`](crate::module::Module::install) and listed.
14
15use crate::prelude::*;
16use alloc::collections::BTreeMap;
17use core::any::Any;
18use core::fmt::{Debug, Display};
19
20use shuyi_assets::audio::AudioManifest;
21use shuyi_assets::dialogue::LineId;
22use shuyi_assets::font::CellWidths;
23use shuyi_assets::hot::Reload;
24use shuyi_assets::keymap::{ActionSet, KeymapDef, Layout};
25use shuyi_assets::lang::{Lang, LangCode};
26use shuyi_assets::palette::PaletteDef;
27use shuyi_assets::voice::{ClipVariant, Playable};
28
29use crate::audio::{AudioConfig, AudioQueue, MusicClock, pick_from_pool};
30use crate::debug::gizmo::DebugView;
31use crate::frame::color::{ColorSet, EngineColor, Palette};
32use crate::frame::console::ConsoleSpec;
33use crate::frame::glyph_buffer::Cell;
34use crate::frame_cap::{CapRequest, CapStatus, FrameCap, Pacing};
35use crate::input::bindings::{LayoutBindings, PadBindings, PlayerKeys};
36use crate::input::help_keys::HelpKeys;
37use crate::input::keymap::Keymap;
38use crate::input::state::Device;
39use crate::log::{LOG_LINES, LogCategory, LogLevel, LogLine, LogRing};
40use crate::module;
41use crate::perf::{FrameRecord, MemoryScopes, PerfClock, PerfState, TagClass};
42use crate::platform_facts::PlatformFacts;
43use crate::profiles::{self, ProfileError, ProfileId, ProfileStore, Profiles};
44use crate::settings::{GameSettings, SETTINGS_KEY, Settings};
45use crate::storage::{Storage, StorageError};
46use crate::system_keyboard::SystemKeyboard;
47
48/// [`Storage`] key under which the player's key and controller-button
49/// bindings are saved ([`PlayerKeys::to_ron`]).
50pub const KEYBINDINGS_KEY: &str = "keybindings";
51
52/// Key in the computer's [`Storage`] (not a profile's) under which the
53/// debug view's channels that are on are saved ([`DebugView::to_ron`]),
54/// only with debug tools on.
55pub const DEBUG_VIEW_KEY: &str = "debug_view";
56
57/// The text key of what help text shows for an action with no key.
58pub const NOT_MAPPED_TEXT: &str = "keys.not_mapped";
59
60/// The text key of the arrow keys' name in help text
61/// ([`ARROWS`](crate::input::help_keys::ARROWS)).
62pub const ARROWS_TEXT: &str = "keys.arrows";
63
64/// The text key of what joins a button chord's two buttons in help text
65/// (`A+B`).
66pub const CHORD_JOIN_TEXT: &str = "keys.chord_join";
67
68/// The text key of a profile's name when the player hasn't named it:
69/// `Player {n}`, made on a first launch.
70pub const PROFILE_NUMBERED_TEXT: &str = "profile.numbered";
71
72/// [`Ctx::music_seed`] until the platform sets it.
73pub const DEFAULT_MUSIC_SEED: u64 = 0;
74
75/// The game's part of the [`Ctx`], and what the engine reads from the
76/// game's data through it: its actions, colors, text, keys, audio
77/// manifest and palettes, and the hooks where the game has a say.
78pub trait GameData: Debug + Sized + 'static {
79    /// The game's input actions.
80    type Action: ActionSet;
81    /// The colors the game's code draws with.
82    type Colors: ColorSet;
83    /// Which of a voiced line's clips plays.
84    type Voice: ClipVariant;
85    /// The settings the game adds to the engine's, and their saved form.
86    type Settings: GameSettings;
87
88    /// The screen text by key, in every language.
89    fn lang(&self) -> &Lang;
90
91    /// The keymap file: every layout's default keys, the layout picker's
92    /// and the controller buttons.
93    fn keymap_def(&self) -> &KeymapDef<Self::Action>;
94
95    /// The sound and music cues.
96    fn audio_manifest(&self) -> &AudioManifest;
97
98    /// The palette the game starts with, before the settings are read.
99    fn palette_def(&self) -> &PaletteDef;
100
101    /// The console the game starts on ([`Ctx::console`]).
102    fn console(&self) -> ConsoleSpec;
103
104    /// The palette of a color `theme`; `None` keeps the palette there is.
105    /// By default the game has one palette: always `None`.
106    fn theme_def(&self, theme: <Self::Settings as GameSettings>::Theme) -> Option<&PaletteDef> {
107        let _ = theme;
108        None
109    }
110
111    /// Which of the game's sound cues the engine's widgets and screens
112    /// play for a menu's moves and choices and a cursor's steps: by
113    /// default the cues named after what they are
114    /// ([`AudioConfig::DEFAULT`]).
115    fn audio_config(&self) -> AudioConfig {
116        AudioConfig::DEFAULT
117    }
118
119    /// The language only a build with debug tools shows (a test pack), if
120    /// the game has one: without debug tools it is English.
121    fn debug_lang(&self) -> Option<&str> {
122        None
123    }
124
125    /// The text of a `{name}` placeholder that names a key
126    /// ([`Ctx::text_with`]): by default the cursor keys for `{Cursor}` and
127    /// the key of the action called `name` ([`text::key_text`]); `None`
128    /// leaves it as written.
129    ///
130    /// [`text::key_text`]: crate::text::key_text
131    fn key_text(&self, name: &str, keys: HelpKeys<'_, Self::Action>) -> Option<String> {
132        crate::text::key_text(name, keys)
133    }
134
135    /// Which clip of a line plays now, where a line has several (who the
136    /// player made the hero, say). By default the variant's default: a
137    /// line's only clip.
138    fn voice_variant(&self) -> Self::Voice {
139        Self::Voice::default()
140    }
141
142    /// The clips that may be played, from the text of the voice manifest
143    /// the platform read: those whose line still says what they say. `Err`
144    /// says why the manifest can't be used. By default the game has no
145    /// voices, and a manifest is refused.
146    // check-text: not player text (warnings for the log)
147    fn voices(&self, _manifest: &str) -> Result<Playable<Self::Voice>, String> {
148        Err("this game has no voices".to_owned())
149    }
150
151    /// Takes what the settings say that the game draws with (a map's look,
152    /// say): `before` is what they were (`None` when they were just
153    /// loaded). The engine applies the theme itself.
154    fn apply_settings(
155        &mut self,
156        _now: &Settings<Self::Settings>,
157        _before: Option<&Settings<Self::Settings>>,
158    ) {
159    }
160
161    /// The cell the frame is cleared to before the screens draw: by
162    /// default a blank in the engine's text color on its panel color.
163    fn blank_cell(&self, palette: &Palette) -> Cell {
164        let text = palette.engine(EngineColor::Text);
165        Cell::new(' ', text, palette.engine(EngineColor::Panel))
166    }
167
168    /// The [`Storage`] keys the game kept a player's data under before it
169    /// had profiles: a first launch with profiles moves them into the
170    /// first one ([`profiles::migrate`]). None by default.
171    fn keys_before_profiles(&self) -> Vec<String> {
172        vec![]
173    }
174
175    /// The [`Storage`] key a layout's name was saved under before the
176    /// settings held it, if the game had one: still read when the settings
177    /// name none, never written.
178    fn old_layout_key(&self) -> Option<&'static str> {
179        None
180    }
181}
182
183/// Whether screens show their idle motion ([`Ctx::idle_motion`]).
184#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
185pub enum IdleMotion {
186    /// Nothing moves while a screen is left alone.
187    #[default]
188    Off,
189    /// Decoration moves.
190    On,
191}
192
193/// Whether the platform has a keyboard
194/// ([`PlatformFacts::keyboard`](crate::PlatformFacts::keyboard)).
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
196pub enum Keyboard {
197    /// It does (a computer, a browser): a first key press opens the
198    /// game's first-key screen until a layout is chosen
199    /// ([`GameHooks::first_key`](crate::GameHooks::first_key)).
200    #[default]
201    Present,
202    /// It has only its own buttons (a console): that screen never opens.
203    Absent,
204}
205
206/// The "press any key or button" wait ([`Ctx::key_prompt`]): browsers
207/// block sound until the player presses a key.
208#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
209pub enum KeyPrompt {
210    /// No prompt (tests that start at a menu).
211    #[default]
212    Off,
213    /// Nothing pressed yet: a screen shows the prompt.
214    Waiting,
215    /// A key or a controller button has been pressed (on any screen).
216    Pressed,
217}
218
219/// The platform's session record, as the debug tools see it.
220#[derive(Debug, Clone, Copy, Default)]
221struct SessionState {
222    /// Whether the platform records this run, or plays a record.
223    recording: bool,
224    /// Whether a debug tool has asked for the session to be saved.
225    save: bool,
226}
227
228/// Resources shared by every screen: the engine's, and the game's in
229/// [`game`](Self::game).
230#[derive(Debug)]
231pub struct Ctx<G: GameData> {
232    /// The game's data and state, which its screens share.
233    pub game: G,
234    /// Named colors, from the game's palette and the color theme in the
235    /// settings.
236    pub palette: Palette,
237    /// The console the frame is drawn on: the game's
238    /// ([`GameData::console`]) unless a front end sets its own before the
239    /// [`Game`](crate::Game) starts.
240    pub console: ConsoleSpec,
241    /// Which characters take two cells of the console: those the font
242    /// atlas has wide, where no language says more. Nothing is wide here;
243    /// the platform sets the atlas's when the game has loaded
244    /// ([`Game::set_widths`](crate::Game::set_widths)).
245    widths: CellWidths,
246    /// [`widths`](Self::widths) in each language, the source's and each
247    /// pack's: its wide set and its punctuation's width ([`Lang::widths`]).
248    lang_widths: BTreeMap<LangCode, CellWidths>,
249    /// The active key bindings, for help text that names keys: the chosen
250    /// layout's with the player's changes, or the layout picker's
251    /// ([`Keymap::layout_picker`]) until one is chosen. Change it with
252    /// [`use_layout`](Self::use_layout),
253    /// [`choose_layout`](Self::choose_layout) or
254    /// [`set_layout_bindings`](Self::set_layout_bindings), never directly.
255    pub keymap: Keymap<G::Action>,
256    /// What the player pressed last, the keyboard or a controller, so help
257    /// text names keys or that pad's buttons ([`help_keys`]). `Game` keeps
258    /// it up to date from the input, starting from the one set here when
259    /// it starts (the keyboard, unless a front end without one sets its
260    /// pad).
261    ///
262    /// [`help_keys`]: Self::help_keys
263    pub device: Device,
264    /// The layout in use; `None` until the player has picked one.
265    layout: Option<Layout>,
266    /// The player's key bindings for every layout and their controller
267    /// buttons, loaded from `storage`.
268    player_keys: PlayerKeys<G::Action>,
269    /// The lines logged and not yet taken ([`log`](Self::log)).
270    log_lines: LogRing,
271    /// The clock and what this frame measured ([`span`](Self::span)).
272    perf: PerfState,
273    /// Where saves and settings persist: a
274    /// [`MemoryStorage`](crate::storage::MemoryStorage) until the platform
275    /// swaps in its own with [`with_storage`](Self::with_storage).
276    ///
277    /// It reads and writes the keys of the profile in use: its settings,
278    /// key bindings and whatever the game keeps per player.
279    /// [`computer`](ProfileStore::computer) is the storage of what stays
280    /// with the computer.
281    pub storage: ProfileStore,
282    /// The player profiles and the one in use, loaded from the computer's
283    /// storage. Change them with [`switch_profile`](Self::switch_profile),
284    /// [`add_profile`](Self::add_profile),
285    /// [`rename_profile`](Self::rename_profile) and
286    /// [`delete_profile`](Self::delete_profile), which save them.
287    profiles: Profiles,
288    /// The player's settings, loaded from `storage`. Change them with
289    /// [`change_settings`](Self::change_settings), which saves them, or
290    /// with [`preview_settings`](Self::preview_settings), which doesn't,
291    /// then [`keep_settings`](Self::keep_settings) or
292    /// [`restore_settings`](Self::restore_settings).
293    settings: Settings<G::Settings>,
294    /// The settings as `storage` holds them: what was loaded or last
295    /// written. [`settings`](Self::settings) differs from it while a
296    /// previewed change is neither kept nor put back.
297    stored_settings: Settings<G::Settings>,
298    /// The serial of the latest change of the frame cap in the settings
299    /// ([`frame_cap_status`](Self::frame_cap_status)): 0 until one.
300    frame_cap_serial: u32,
301    /// The platform's last report of its pacing; `None` until one.
302    pacing: Option<Pacing>,
303    /// Whether debug tools are offered: the Debug action
304    /// ([`GameHooks::debug`](crate::GameHooks::debug)) and whatever else
305    /// the game offers only to developers. On in debug builds and with the
306    /// `debug-tools` feature ([`DEBUG_TOOLS`](crate::DEBUG_TOOLS)).
307    pub debug_tools: bool,
308    /// The platform's session record, as the debug tools see it.
309    session: SessionState,
310    /// Which channels of the debug view are on (none at first), and which
311    /// the running screens have offered (ADR-0085): read from the
312    /// computer's storage by [`with_storage`](Self::with_storage) with
313    /// debug tools on. Turn one on with
314    /// [`set_debug_channel`](Self::set_debug_channel), which saves it; a
315    /// change made here directly isn't saved.
316    pub debug_view: DebugView,
317    /// Whether screens show their idle motion. Off here, so a screen left
318    /// alone in a test looks the same a moment later; the platform turns
319    /// it on.
320    pub idle_motion: IdleMotion,
321    /// Sounds and music screens ask for this frame, e.g.
322    /// `ctx.audio.play_sound("click")`. `Game` passes them to the
323    /// platform.
324    pub audio: AudioQueue<G::Voice>,
325    /// Where the music that is sounding is in its track, for a screen that
326    /// keeps time with it. `None` in silence: nothing asked for, the track
327    /// still loading, its file missing, or a track played once that has
328    /// ended. Of two tracks sounding (a crossfade) it is the newest:
329    /// the old one until the new one has loaded. The platform
330    /// reports it each frame through
331    /// [`Game::set_music_playing`](crate::Game::set_music_playing).
332    pub music_clock: Option<MusicClock>,
333    /// The voice clips that may be played: none until the platform hands
334    /// over a manifest ([`set_voice_manifest`](Self::set_voice_manifest)).
335    voices: Playable<G::Voice>,
336    /// The line whose voice clip the platform last said is sounding
337    /// ([`voice_playing`](Self::voice_playing)).
338    pub(crate) voice_playing: Option<LineId>,
339    /// Seeds random music picks (e.g. a track from a pool), kept apart
340    /// from any simulation's randomness. A fixed [`DEFAULT_MUSIC_SEED`]
341    /// here, so tests are repeatable; the platform sets it from the clock
342    /// at startup so each launch picks differently.
343    pub music_seed: u64,
344    /// Random music picks made so far, mixed into
345    /// [`music_seed`](Self::music_seed) so each pick rolls afresh
346    /// ([`pick_music`](Self::pick_music)).
347    music_picks: u64,
348    /// Seconds the game has been running: the sum of every frame's time
349    /// (`Game` adds it).
350    pub clock_s: f64,
351    /// Whether a screen waits for a key press before going on (and before
352    /// playing music). Off here; the platform sets
353    /// [`KeyPrompt::Waiting`], and `Game` moves it on to
354    /// [`KeyPrompt::Pressed`] at the first key or button press.
355    pub key_prompt: KeyPrompt,
356    /// Whether the device's crank is folded away, as the platform said
357    /// last: a screen whose required action is on the crank then gets a
358    /// hint to unfold it.
359    pub crank_docked: bool,
360    /// Where the game runs: the target's id, whether the game may quit,
361    /// whether its window goes full screen and back, and the keyboard,
362    /// pointer and touch screen the device has. The `desktop` target's
363    /// here ([`PlatformFacts::default`]); the platform hands over its own
364    /// before the [`Game`](crate::Game) starts (a front end without a
365    /// keyboard sets the [`device`](Self::device) it has too). Screens
366    /// read it; a game's rules never do.
367    pub platform: PlatformFacts,
368    /// The system's own keyboard for a text box (a console's, Steam's on
369    /// the Steam Deck), if the platform has one: absent here, and the
370    /// platform's `App` says so before every frame.
371    pub system_keyboard: SystemKeyboard,
372    /// The language screen text is shown in: the game's source language
373    /// ([`Lang::source_lang`]) until the player picks another. The game's
374    /// debug language ([`GameData::debug_lang`]) counts only with
375    /// [`debug_tools`](Self::debug_tools) on; anywhere else it is the
376    /// source.
377    pub lang: LangCode,
378    /// The installed modules' states and debug menu entries.
379    pub(crate) modules: module::States<G>,
380    /// The hot reloads that failed ([`note_reload`](Self::note_reload)).
381    reload_errors: module::ReloadErrors,
382}
383
384impl<G: GameData> Ctx<G> {
385    /// The shared context over the game's part. Fails with the names of
386    /// the colors the game's palette lacks. Starts with a
387    /// [`MemoryStorage`](crate::storage::MemoryStorage), the default
388    /// settings and no layout chosen (so the layout picker's keys are
389    /// active).
390    pub fn new(game: G) -> Result<Self, Vec<&'static str>> {
391        let palette = Palette::new::<G::Colors>(game.palette_def())?;
392        let keymap = Keymap::layout_picker(game.keymap_def());
393        let settings = Settings::default();
394        let mut audio = AudioQueue::new(game.audio_config());
395        audio.set_manifest(game.audio_manifest());
396        let profiles = Profiles::first(&numbered_profile(game.lang(), ProfileId::FIRST.get()));
397        let lang = game.lang().source_lang().code.clone();
398        Ok(Self {
399            console: game.console(),
400            widths: CellWidths::NARROW,
401            lang_widths: lang_widths(game.lang(), &CellWidths::NARROW),
402            game,
403            palette,
404            keymap,
405            device: Device::default(),
406            layout: None,
407            player_keys: PlayerKeys::default(),
408            log_lines: LogRing::new(LOG_LINES),
409            perf: PerfState::default(),
410            storage: ProfileStore::default(),
411            profiles,
412            stored_settings: settings.clone(),
413            settings,
414            frame_cap_serial: 0,
415            pacing: None,
416            debug_tools: crate::DEBUG_TOOLS,
417            session: SessionState::default(),
418            debug_view: DebugView::default(),
419            idle_motion: IdleMotion::Off,
420            audio,
421            music_clock: None,
422            voices: Playable::default(),
423            voice_playing: None,
424            music_seed: DEFAULT_MUSIC_SEED,
425            music_picks: 0,
426            clock_s: 0.0,
427            key_prompt: KeyPrompt::Off,
428            crank_docked: false,
429            platform: PlatformFacts::default(),
430            system_keyboard: SystemKeyboard::default(),
431            lang,
432            modules: module::States::default(),
433            reload_errors: module::ReloadErrors::default(),
434        })
435    }
436
437    /// Which characters take two cells of the console, for a screen that
438    /// measures text before it has the frame to draw it in
439    /// ([`text_width`](shuyi_assets::lang::text_width)): the font atlas's
440    /// in the language text is shown in ([`effective_lang`]), as that
441    /// language has them ([`Lang::widths`]: its wide set, and its
442    /// punctuation wide or not, a pack's or the source's). The frame's
443    /// ([`GlyphBuffer::widths`](crate::frame::GlyphBuffer::widths)) follow
444    /// them at each drawing.
445    ///
446    /// [`effective_lang`]: Self::effective_lang
447    pub fn widths(&self) -> &CellWidths {
448        self.lang_widths
449            .get(self.effective_lang())
450            .unwrap_or(&self.widths)
451    }
452
453    /// Sets the font atlas's widths, which [`widths`](Self::widths) are
454    /// made of in each language: only with the frame's, by
455    /// [`Game::set_widths`](crate::Game::set_widths).
456    pub(crate) fn set_widths(&mut self, widths: CellWidths) {
457        self.lang_widths = lang_widths(self.game.lang(), &widths);
458        self.widths = widths;
459    }
460
461    /// Takes how the asset `id` reloaded (`Hot::refresh`'s answer): a
462    /// failure's errors show at the top of the frame, where the backend has
463    /// a debug overlay, until `id`'s next good reload. Called from
464    /// [`Module::reloaded`](module::Module::reloaded).
465    pub fn note_reload(&mut self, id: &str, reload: &Reload) {
466        self.reload_errors.note(id, reload);
467    }
468
469    /// The errors of the reloads that failed and haven't reloaded since,
470    /// each with its file and line, one a line.
471    pub fn reload_errors(&self) -> &[String] {
472        self.reload_errors.lines()
473    }
474
475    /// The state of type `T` a module put here
476    /// ([`Install::state`](module::Install::state)), or `None` if no
477    /// installed module did.
478    pub fn module_state<T: Any>(&self) -> Option<&T> {
479        self.modules.get()
480    }
481
482    /// [`module_state`](Self::module_state), mutable.
483    pub fn module_state_mut<T: Any>(&mut self) -> Option<&mut T> {
484        self.modules.get_mut()
485    }
486
487    /// The screen text for `key` in the player's language, placeholders
488    /// and all: the first text along its fallback chain
489    /// ([`Lang::chain`]) made from today's source text, else the source's.
490    ///
491    /// A key the source lacks is a bug in the screen: it panics in debug
492    /// builds, and shows as the key itself in release builds.
493    pub fn text<'a>(&'a self, key: &'a str) -> &'a str {
494        let lang = self.game.lang();
495        debug_assert!(lang.has(key), "text key not in the source text: {key:?}");
496        lang.text(self.effective_lang(), key)
497    }
498
499    /// [`text`](Self::text) with its placeholders filled in: each `{name}`
500    /// that `args` names by its value, and each that names a key (an
501    /// action's name, [`GameData::key_text`]) by the key the player has
502    /// for it ([`help_keys`](Self::help_keys)).
503    pub fn text_with(&self, key: &str, args: &[(&str, &dyn Display)]) -> String {
504        self.text_with_keys(key, self.help_keys(), args)
505    }
506
507    /// What `label` says to the player: a key's text filled in by
508    /// [`text_with`](Self::text_with), or the text as it is. What a skin
509    /// calls on the labels of the view it paints.
510    pub fn label(&self, label: &crate::text::Label) -> String {
511        match label {
512            crate::text::Label::Key { key, args } => {
513                let args: Vec<(&str, &dyn Display)> = args
514                    .iter()
515                    .map(|(name, value)| (name.as_str(), value as &dyn Display))
516                    .collect();
517                self.text_with(key, &args)
518            }
519            crate::text::Label::Text(text) => text.clone(),
520        }
521    }
522
523    /// [`text_with`](Self::text_with), naming the keys `keys` has rather
524    /// than the active ones: for a screen steered by the keys it was
525    /// opened with while the player changes them.
526    pub fn text_with_keys(
527        &self,
528        key: &str,
529        keys: HelpKeys<'_, G::Action>,
530        args: &[(&str, &dyn Display)],
531    ) -> String {
532        crate::text::fill(self.text(key), args, |name| self.game.key_text(name, keys))
533    }
534
535    /// The language text is shown in: [`lang`](Self::lang), but the
536    /// game's source language for its debug language without
537    /// [`debug_tools`](Self::debug_tools).
538    pub fn effective_lang(&self) -> &LangCode {
539        let debug = self.game.debug_lang();
540        if debug == Some(self.lang.as_str()) && !self.debug_tools {
541            return &self.game.lang().source_lang().code;
542        }
543        &self.lang
544    }
545
546    /// What help text names keys from: the active bindings on the device
547    /// the player pressed last, and the words of a key's name in the
548    /// player's language.
549    pub fn help_keys(&self) -> HelpKeys<'_, G::Action> {
550        let not_mapped = self.text(NOT_MAPPED_TEXT);
551        let arrows = self.text(ARROWS_TEXT);
552        let join = self.text(CHORD_JOIN_TEXT);
553        HelpKeys::new(&self.keymap, self.device)
554            .worded(not_mapped, arrows)
555            .chord_joined(join)
556    }
557
558    /// A music cue picked at random from the audio manifest's `pool`
559    /// ([`pick_from_pool`]): the n-th pick of this run uses
560    /// [`music_seed`](Self::music_seed) mixed with n, so a fight started
561    /// again may get another track. `None` if there is no such pool or it
562    /// is empty.
563    pub fn pick_music(&mut self, pool: &str) -> Option<String> {
564        let seed = self.music_seed ^ self.music_picks;
565        self.music_picks = self.music_picks.wrapping_add(1);
566        pick_from_pool(self.game.audio_manifest(), pool, seed).map(str::to_owned)
567    }
568
569    /// Takes the text of the voice manifest the platform read, and keeps
570    /// the clips that may be played ([`GameData::voices`]). A manifest the
571    /// game can't use leaves no voices and a warning in the log
572    /// ([`log`](Self::log)).
573    pub fn set_voice_manifest(&mut self, source: &str) {
574        self.voices = match self.game.voices(source) {
575            Ok(playable) => playable,
576            Err(why) => {
577                self.log(
578                    LogLevel::Warn,
579                    LogCategory::Audio,
580                    format!("no voices: {why}"),
581                );
582                Playable::default()
583            }
584        };
585    }
586
587    /// The clip `line` would play now, if voices are on and it has one
588    /// that may be played.
589    fn voice_for(&self, line: &LineId) -> Option<G::Voice> {
590        if !self.settings.voices {
591            return None;
592        }
593        self.voices.variant_for(line, self.game.voice_variant())
594    }
595
596    /// Whether `line` has a voice clip that would play now: voices are on
597    /// and the line has a clip that isn't stale.
598    pub fn has_voice(&self, line: &LineId) -> bool {
599        self.voice_for(line).is_some()
600    }
601
602    /// Says `line`: plays its voice clip (the one for
603    /// [`GameData::voice_variant`], where the line has several), stopping
604    /// the voice that is playing. Does nothing when voices are off or the
605    /// line has no clip that may be played.
606    pub fn play_voice(&mut self, line: &LineId) {
607        if let Some(variant) = self.voice_for(line) {
608            self.audio.play_voice(line, variant);
609        }
610    }
611
612    /// The line whose voice clip is sounding, or asked for and still
613    /// loading; `None` in silence. The platform reports it each frame
614    /// through [`Game::set_voice_playing`](crate::Game::set_voice_playing),
615    /// before the screens run: a clip that ended, was stopped or replaced,
616    /// whose file failed or that loaded too late to be played is `None`. A
617    /// clip paused with the game still sounds.
618    ///
619    /// A screen sees its own [`play_voice`](Self::play_voice) from the next
620    /// frame, once the platform has read the request: in the frame it asks,
621    /// and on a platform with no voices, this is still `None`. So a screen
622    /// that waits for a voice to end waits for its line to sound first,
623    /// and takes `None` after that as the end. A clip that never sounds
624    /// (its file missing or slow) gives up within half a second: a screen
625    /// that hasn't seen its line by then may move on.
626    pub fn voice_playing(&self) -> Option<&LineId> {
627        self.voice_playing.as_ref()
628    }
629
630    /// Stops the voice that is playing, if any.
631    pub fn stop_voice(&mut self) {
632        self.audio.stop_voice();
633    }
634
635    /// Tells the platform the lines a scene is about to say, in script
636    /// order, so it loads their clips a few ahead. Lines without a
637    /// playable clip are left out; nothing is sent when voices are off or
638    /// none has one.
639    pub fn preload_voices(&mut self, lines: &[LineId]) {
640        let clips: Vec<_> = lines
641            .iter()
642            .filter_map(|line| Some((line.clone(), self.voice_for(line)?)))
643            .collect();
644        if !clips.is_empty() {
645            self.audio.preload_voices(clips);
646        }
647    }
648
649    /// Whether any line has a voice clip that may be played: the platform
650    /// found the clips and the game can use them. Voices off in the
651    /// settings doesn't change it, so a game can ask whether to play them.
652    pub fn has_voices(&self) -> bool {
653        !self.voices.is_empty()
654    }
655
656    /// The layout in use, or `None` if the player hasn't picked one yet.
657    pub fn layout(&self) -> Option<Layout> {
658        self.layout
659    }
660
661    /// Switches to `layout`'s bindings (the player's own for that layout,
662    /// else its defaults) for this session, without saving the choice.
663    pub fn use_layout(&mut self, layout: Layout) {
664        self.layout = Some(layout);
665        self.refresh_keymap();
666    }
667
668    /// Rebuilds [`keymap`](Self::keymap) from the layout in use (the
669    /// layout picker's keys while there is none) and the player's
670    /// bindings: also after the game changed its keymap file
671    /// ([`GameData::keymap_def`]), such as a console's own buttons.
672    pub fn refresh_keymap(&mut self) {
673        self.keymap = match self.layout {
674            Some(layout) => self.keymap_for(layout),
675            None => self
676                .player_keys
677                .layout_picker_keymap(self.game.keymap_def()),
678        };
679    }
680
681    /// The keymap `layout` would have: the player's bindings for it, else
682    /// its defaults (a layout picker draws every layout with this).
683    pub fn keymap_for(&self, layout: Layout) -> Keymap<G::Action> {
684        self.player_keys.keymap(self.game.keymap_def(), layout)
685    }
686
687    /// The player's key bindings for every layout.
688    pub fn player_keys(&self) -> &PlayerKeys<G::Action> {
689        &self.player_keys
690    }
691
692    /// `layout`'s current bindings, for a key bindings screen to edit and
693    /// hand back to [`set_layout_bindings`](Self::set_layout_bindings).
694    pub fn layout_bindings(&self, layout: Layout) -> LayoutBindings<G::Action> {
695        self.player_keys.bindings(self.game.keymap_def(), layout)
696    }
697
698    /// Replaces `layout`'s bindings and saves every layout's under
699    /// [`KEYBINDINGS_KEY`]. If `layout` is in use, its keys work from the
700    /// next key press (`Game` hands the new keymap to its input at once).
701    /// The change applies even if saving fails (it is then lost on quit).
702    pub fn set_layout_bindings(
703        &mut self,
704        layout: Layout,
705        bindings: LayoutBindings<G::Action>,
706    ) -> Result<(), StorageError> {
707        self.player_keys
708            .set(self.game.keymap_def(), layout, bindings);
709        if self.layout == Some(layout) {
710            self.use_layout(layout);
711        }
712        self.storage
713            .write(KEYBINDINGS_KEY, &self.player_keys.to_ron())
714    }
715
716    /// The player's controller buttons (the defaults until they change
717    /// them), for a key bindings screen to edit and hand back to
718    /// [`set_pad_bindings`](Self::set_pad_bindings). The same in every
719    /// layout.
720    pub fn pad_bindings(&self) -> PadBindings<G::Action> {
721        self.player_keys.pad_bindings(self.game.keymap_def())
722    }
723
724    /// Replaces the controller buttons and saves them under
725    /// [`KEYBINDINGS_KEY`]. They work from the next press, whatever the
726    /// layout (and before one is chosen). The change applies even if
727    /// saving fails (it is then lost on quit).
728    pub fn set_pad_bindings(
729        &mut self,
730        bindings: PadBindings<G::Action>,
731    ) -> Result<(), StorageError> {
732        self.player_keys.set_pad(self.game.keymap_def(), bindings);
733        self.refresh_keymap();
734        self.storage
735            .write(KEYBINDINGS_KEY, &self.player_keys.to_ron())
736    }
737
738    /// Loads the player's key bindings from `storage` (repairing what it
739    /// must, with a warning per fix) and re-applies the layout in use.
740    fn load_player_keys(&mut self) {
741        let (keys, warnings) = match self.storage.read(KEYBINDINGS_KEY) {
742            Ok(Some(text)) => PlayerKeys::from_ron(&text, self.game.keymap_def()),
743            Ok(None) => (PlayerKeys::default(), Vec::new()),
744            Err(e) => (
745                PlayerKeys::default(),
746                // check-text: not player text (a warning for the log)
747                vec![format!("can't be read, using the default keys: {e}")],
748            ),
749        };
750        self.player_keys = keys;
751        for warning in warnings {
752            let (level, category) = (LogLevel::Warn, LogCategory::Input);
753            self.log(level, category, format!("{KEYBINDINGS_KEY}: {warning}"));
754        }
755        self.refresh_keymap();
756    }
757
758    /// Logs `text` at `level` about `category`: kept, with frame 0, until
759    /// the platform takes it ([`take_log`](Self::take_log)) and stamps it
760    /// with its frame. Past [`LOG_LINES`] waiting, the oldest is dropped
761    /// and counted. English, never translated: no player reads it.
762    pub fn log(&mut self, level: LogLevel, category: LogCategory, text: impl Into<String>) {
763        self.log_lines.push(LogLine {
764            frame: 0,
765            level,
766            category,
767            text: text.into(),
768        });
769    }
770
771    /// Takes the lines waiting, oldest first, and how many were dropped
772    /// before they were taken: for the platform's `App`.
773    pub fn take_log(&mut self) -> (Vec<LogLine>, u32) {
774        self.log_lines.take()
775    }
776
777    /// Runs `f` as a span called `name` and gives back what it returns. In
778    /// a frame the platform measures (debug builds, with
779    /// [`debug_tools`](Self::debug_tools) on), the span's start, length and
780    /// depth go into the frame's record ([`crate::perf`]); past
781    /// [`MAX_SPANS`](crate::perf::MAX_SPANS) in a frame it is only
782    /// counted. With no clock, or outside a measured frame, it calls `f`
783    /// and reads nothing. Nothing reads a span's time to decide anything.
784    ///
785    /// With memory scopes ([`set_memory_scopes`](Self::set_memory_scopes))
786    /// what `f` allocates counts under a tag named `name`
787    /// ([`TagClass::Span`]).
788    pub fn span<R>(&mut self, name: &'static str, f: impl FnOnce(&mut Self) -> R) -> R {
789        let tagged = self.enter_tag(TagClass::Span, name);
790        let result = match self.perf.open_span() {
791            Some(open) => {
792                let result = f(self);
793                self.perf.close_span(name, open);
794                result
795            }
796            None => f(self),
797        };
798        crate::perf::leave_tag(tagged);
799        result
800    }
801
802    /// Hands the context the backend's memory tags, or takes them away
803    /// (`None`): for the platform's `App`, which moves them here when the
804    /// game runs, and for tests. With them the frame's `update` and `draw`
805    /// and each span count what they allocate under a tag of their own
806    /// ([`crate::perf::MemoryScopes`]).
807    pub fn set_memory_scopes(&mut self, scopes: Option<MemoryScopes>) {
808        self.perf.scopes = scopes;
809    }
810
811    /// The memory tags, if the context has them.
812    pub fn memory_scopes(&self) -> Option<MemoryScopes> {
813        self.perf.scopes
814    }
815
816    /// Enters the tag of `class`'s `name`, with memory scopes: the tag
817    /// before and the scopes, for [`crate::perf::leave_tag`], which
818    /// leaves it even if the scopes were taken away since.
819    pub(crate) fn enter_tag(
820        &self,
821        class: TagClass,
822        name: &'static str,
823    ) -> Option<(MemoryScopes, u16)> {
824        let scopes = self.perf.scopes?;
825        Some((scopes, scopes.enter(class, name)))
826    }
827
828    /// Whether the platform is recording this run as a session, or playing
829    /// one back, so [`ask_session_save`](Self::ask_session_save) means
830    /// something: not until it says.
831    pub fn session_recording(&self) -> bool {
832        self.session.recording
833    }
834
835    /// Says whether the platform is recording this run, or playing a record:
836    /// for the platform's `App`. The same in a session and its playback, so
837    /// it is no value a record must hold.
838    pub fn set_session_recording(&mut self, on: bool) {
839        self.session.recording = on;
840    }
841
842    /// Asks the platform to save the session so far, after this frame.
843    pub fn ask_session_save(&mut self) {
844        self.session.save = true;
845    }
846
847    /// Takes the request of [`ask_session_save`](Self::ask_session_save): for
848    /// the platform's `App`.
849    pub fn take_session_save(&mut self) -> bool {
850        core::mem::take(&mut self.session.save)
851    }
852
853    /// Hands the context a clock to measure frames with, or takes it away
854    /// (`None`): for the platform's `App`, which moves the backend's clock
855    /// here when the game runs, and for tests.
856    pub fn set_perf_clock(&mut self, clock: Option<PerfClock>) {
857        self.perf.set_clock(clock);
858    }
859
860    /// The clock frames are measured with, if the context has one: for the
861    /// platform's `App` and tests (a fake clock's reads).
862    pub fn perf_clock(&self) -> Option<PerfClock> {
863        self.perf.clock()
864    }
865
866    /// Reads the clock, if the context has one, in microseconds: for the
867    /// platform's `App`, which times its own parts of a frame with it, and
868    /// for tests.
869    pub fn perf_now(&mut self) -> Option<u64> {
870        self.perf.now()
871    }
872
873    /// Starts measuring a frame, if the context has a clock: reads the
874    /// frame's start (and gives it) and forgets the last frame's spans.
875    /// For the platform's `App`, at the start of a frame it measures.
876    pub fn begin_perf_frame(&mut self) -> Option<u64> {
877        self.perf.begin_frame()
878    }
879
880    /// Ends the frame being measured, reading nothing: its update's and
881    /// drawing's times and its spans, in a record of frame 0 that `App`
882    /// finishes. `None` if no frame was being measured.
883    pub fn end_perf_frame(&mut self) -> Option<FrameRecord> {
884        self.perf.end_frame()
885    }
886
887    /// Reads the clock in a measured frame only, for `Game::frame`.
888    pub(crate) fn perf_now_in_frame(&mut self) -> Option<u64> {
889        self.perf.now_in_frame()
890    }
891
892    /// Keeps the frame's update and drawing times, for `Game::frame`.
893    pub(crate) fn set_perf_frame_times(
894        &mut self,
895        start: Option<u64>,
896        drawing: Option<u64>,
897        drawn: Option<u64>,
898    ) {
899        self.perf.set_frame_times(start, drawing, drawn);
900    }
901
902    /// Takes every line waiting, and returns the text of the warnings and
903    /// errors among them (e.g. repaired key bindings): for a front end
904    /// without the platform's `App`.
905    pub fn take_warnings(&mut self) -> Vec<String> {
906        let (lines, _) = self.take_log();
907        lines
908            .into_iter()
909            .filter(|line| line.level <= LogLevel::Warn)
910            .map(|line| line.text)
911            .collect()
912    }
913
914    /// Builder form of [`use_layout`](Self::use_layout).
915    #[must_use]
916    pub fn with_layout(mut self, layout: Layout) -> Self {
917        self.use_layout(layout);
918        self
919    }
920
921    /// The player picked `layout`: switches to it (its own keys if the
922    /// player changed them, else its defaults) and saves it in the
923    /// settings. The switch happens even if saving fails (the player is
924    /// then asked again next launch).
925    pub fn choose_layout(&mut self, layout: Layout) -> Result<(), StorageError> {
926        self.use_layout(layout);
927        self.settings.layout = Some(layout);
928        // Only the layout is saved: a change an options screen hasn't
929        // kept yet stays unsaved.
930        let mut stored = self.stored_settings.clone();
931        stored.layout = Some(layout);
932        self.write_settings(stored)
933    }
934
935    /// The layout saved by an earlier [`choose_layout`](Self::choose_layout):
936    /// the saved settings', else the one under the game's
937    /// [old key](GameData::old_layout_key). `None` if nothing is saved, or
938    /// if the saved value can't be read or isn't a layout the keymap file
939    /// lists (the player is simply asked again).
940    pub fn saved_layout(&self) -> Option<Layout> {
941        let read = |key| self.storage.read(key).ok().flatten();
942        let known = |l: &Layout| self.game.keymap_def().has_layout(*l);
943        let in_settings = read(SETTINGS_KEY)
944            .and_then(|text| Settings::<G::Settings>::from_ron(&text).ok())
945            .and_then(|s| s.layout())
946            .filter(known);
947        in_settings.or_else(|| {
948            let old = read(self.game.old_layout_key()?)?;
949            Layout::from_name(old.trim()).filter(known)
950        })
951    }
952
953    /// The layout a screen about the keys shows: the one in use, else the
954    /// keymap file's first (before a layout is chosen). `None` only for a
955    /// keymap without layouts, which no loaded one is.
956    pub fn shown_layout(&self) -> Option<Layout> {
957        self.layout
958            .or_else(|| self.game.keymap_def().first_layout())
959    }
960
961    /// The player's settings.
962    pub fn settings(&self) -> &Settings<G::Settings> {
963        &self.settings
964    }
965
966    /// Lets `change` edit the settings and, if it changed anything, saves
967    /// them under [`SETTINGS_KEY`]. The change applies even if saving
968    /// fails (it is then lost on quit).
969    pub fn change_settings(
970        &mut self,
971        change: impl FnOnce(&mut Settings<G::Settings>),
972    ) -> Result<(), StorageError> {
973        if !self.preview_settings(change) {
974            return Ok(());
975        }
976        self.write_settings(self.settings.clone())
977    }
978
979    /// Lets `change` edit the settings without saving them: the change
980    /// applies at once (the look, the sound), and is saved only by
981    /// [`keep_settings`](Self::keep_settings) or the next
982    /// [`change_settings`](Self::change_settings). Returns whether it
983    /// changed anything. The layout changes only with the keys
984    /// ([`choose_layout`](Self::choose_layout)).
985    pub fn preview_settings(&mut self, change: impl FnOnce(&mut Settings<G::Settings>)) -> bool {
986        let before = self.settings.clone();
987        change(&mut self.settings);
988        self.settings.layout = before.layout;
989        if self.settings == before {
990            return false;
991        }
992        if self.settings.frame_cap != before.frame_cap {
993            self.next_frame_cap_serial();
994        }
995        if before.voices && !self.settings.voices {
996            self.stop_voice();
997        }
998        self.game.apply_settings(&self.settings, Some(&before));
999        if self.settings.theme != before.theme {
1000            self.apply_theme();
1001        }
1002        true
1003    }
1004
1005    /// Saves the settings if they differ from what `storage` holds (a
1006    /// previewed change, [`preview_settings`](Self::preview_settings)).
1007    pub fn keep_settings(&mut self) -> Result<(), StorageError> {
1008        if self.settings == self.stored_settings {
1009            return Ok(());
1010        }
1011        self.write_settings(self.settings.clone())
1012    }
1013
1014    /// Puts the settings back to `to` (all but the layout, which only the
1015    /// keys change), as a previewed change thrown away. It saves only if
1016    /// `storage` holds something else, which is so only if another change
1017    /// was saved meanwhile (a fullscreen shortcut, a layout picked).
1018    pub fn restore_settings(&mut self, to: &Settings<G::Settings>) -> Result<(), StorageError> {
1019        let to = to.clone();
1020        self.preview_settings(|s| *s = to);
1021        self.keep_settings()
1022    }
1023
1024    /// Asks for `cap` as the frame cap: it becomes the setting and is
1025    /// saved, as [`change_settings`](Self::change_settings) does, and takes
1026    /// the next serial even if it is the cap already, so the request is
1027    /// confirmed either way. The platform applies it from the next frame;
1028    /// check where it is with [`frame_cap_status`](Self::frame_cap_status).
1029    /// A save that fails is logged, and the cap applies all the same.
1030    pub fn request_frame_cap(&mut self, cap: FrameCap) -> CapRequest {
1031        let serial = self.frame_cap_serial;
1032        if let Err(e) = self.change_settings(|s| s.frame_cap = cap) {
1033            let (level, category) = (LogLevel::Warn, LogCategory::Storage);
1034            self.log(level, category, format!("{SETTINGS_KEY}: {e}"));
1035        }
1036        if self.frame_cap_serial == serial {
1037            self.next_frame_cap_serial();
1038        }
1039        CapRequest::new(self.frame_cap_serial)
1040    }
1041
1042    /// Where `request` is: [`CapStatus::Superseded`] once a later change of
1043    /// the cap has taken the next serial; [`CapStatus::Applied`], with the
1044    /// report, once the platform has reported its serial
1045    /// ([`Game::set_pacing`](crate::Game::set_pacing)); else
1046    /// [`CapStatus::Pending`].
1047    pub fn frame_cap_status(&self, request: CapRequest) -> CapStatus {
1048        if self.frame_cap_serial > request.serial() {
1049            return CapStatus::Superseded;
1050        }
1051        match self.pacing {
1052            Some(pacing) if pacing.serial >= request.serial() => CapStatus::Applied(pacing),
1053            _ => CapStatus::Pending,
1054        }
1055    }
1056
1057    /// The serial of the latest change of the frame cap: what the platform
1058    /// is handed with the cap, and reports once it has applied it. 0 until
1059    /// the cap changes.
1060    pub fn frame_cap_serial(&self) -> u32 {
1061        self.frame_cap_serial
1062    }
1063
1064    /// The platform's last report of its pacing (the cap it applied, the
1065    /// frames a second it measured), for a screen that shows the frame
1066    /// rate; `None` until the platform reports, and on a platform that
1067    /// never does.
1068    pub fn pacing(&self) -> Option<Pacing> {
1069        self.pacing
1070    }
1071
1072    /// Takes the platform's report ([`Game::set_pacing`](crate::Game::set_pacing)).
1073    pub(crate) fn set_pacing(&mut self, pacing: Pacing) {
1074        self.pacing = Some(pacing);
1075    }
1076
1077    /// The frame cap changed: it takes the next serial.
1078    fn next_frame_cap_serial(&mut self) {
1079        self.frame_cap_serial = self.frame_cap_serial.saturating_add(1);
1080    }
1081
1082    /// Builds [`palette`](Self::palette) from the color theme in the
1083    /// settings. Keeps the palette if the theme has none, or one that
1084    /// lacks colors.
1085    fn apply_theme(&mut self) {
1086        let def = self.game.theme_def(self.settings.theme);
1087        if let Some(palette) = def.and_then(|d| Palette::new::<G::Colors>(d).ok()) {
1088            self.palette = palette;
1089        }
1090    }
1091
1092    /// Writes `settings` under [`SETTINGS_KEY`]; once written they are
1093    /// what `storage` holds.
1094    fn write_settings(&mut self, settings: Settings<G::Settings>) -> Result<(), StorageError> {
1095        self.storage.write(SETTINGS_KEY, &settings.to_ron())?;
1096        self.stored_settings = settings;
1097        Ok(())
1098    }
1099
1100    /// Loads the settings from `storage`: the defaults if none are saved,
1101    /// or (with a warning) if they can't be read. A layout saved under the
1102    /// game's old key is taken over ([`saved_layout`](Self::saved_layout)).
1103    fn load_settings(&mut self) {
1104        let cap_before = self.settings.frame_cap;
1105        let loaded = match self.storage.read(SETTINGS_KEY) {
1106            Ok(Some(text)) => Settings::from_ron(&text),
1107            Ok(None) => Ok(Settings::default()),
1108            // check-text: not player text (a warning for the log)
1109            Err(e) => Err(format!("can't be read: {e}")),
1110        };
1111        self.settings = loaded.unwrap_or_else(|why| {
1112            let (level, category) = (LogLevel::Warn, LogCategory::Storage);
1113            self.log(
1114                level,
1115                category,
1116                format!("{SETTINGS_KEY}: {why}, using the defaults"),
1117            );
1118            Settings::default()
1119        });
1120        self.settings.layout = self.saved_layout();
1121        self.stored_settings = self.settings.clone();
1122        if self.settings.frame_cap != cap_before {
1123            self.next_frame_cap_serial();
1124        }
1125        self.game.apply_settings(&self.settings, None);
1126        self.apply_theme();
1127    }
1128
1129    /// Replaces the storage backend (tests keep the
1130    /// [`MemoryStorage`](crate::storage::MemoryStorage); the platform
1131    /// installs its own), loads the profiles from it ([`profiles::load`]:
1132    /// the last one used, the first made on a first launch, a player's
1133    /// keys from before profiles moved into it) and that profile's key
1134    /// bindings and settings.
1135    #[must_use]
1136    pub fn with_storage(mut self, mut storage: Box<dyn Storage>) -> Self {
1137        let old_keys = self.game.keys_before_profiles();
1138        let lang = self.game.lang();
1139        let (profiles, warnings) =
1140            profiles::load(&mut *storage, &old_keys, |n| numbered_profile(lang, n));
1141        for warning in warnings {
1142            self.log(LogLevel::Warn, LogCategory::Storage, warning);
1143        }
1144        self.storage = ProfileStore::new(storage, profiles.current());
1145        self.profiles = profiles;
1146        self.load_player_keys();
1147        self.load_settings();
1148        if self.debug_tools {
1149            self.load_debug_view();
1150        }
1151        self
1152    }
1153
1154    /// Reads the debug view's channels that are on from the computer's
1155    /// storage; a value that can't be read leaves them off, with a
1156    /// warning.
1157    fn load_debug_view(&mut self) {
1158        let read = match self.storage.computer().read(DEBUG_VIEW_KEY) {
1159            Ok(Some(text)) => self.debug_view.read_ron(&text),
1160            Ok(None) => Ok(()),
1161            // check-text: not player text (a warning for the log)
1162            Err(e) => Err(format!("can't be read: {e}")),
1163        };
1164        if let Err(why) = read {
1165            let (level, category) = (LogLevel::Warn, LogCategory::Storage);
1166            self.log(level, category, format!("{DEBUG_VIEW_KEY}: {why}"));
1167        }
1168    }
1169
1170    /// Turns the debug view's channel `id` on or off (ADR-0085) and, with
1171    /// debug tools on, saves the channels on under [`DEBUG_VIEW_KEY`] in
1172    /// the computer's storage, for the next launch. The change applies
1173    /// even if saving fails. With debug tools off nothing is saved (and
1174    /// nothing is drawn).
1175    pub fn set_debug_channel(&mut self, id: &str, on: bool) -> Result<(), StorageError> {
1176        self.debug_view.set(id, on);
1177        if !self.debug_tools {
1178            return Ok(());
1179        }
1180        self.storage
1181            .computer_mut()
1182            .write(DEBUG_VIEW_KEY, &self.debug_view.to_ron())
1183    }
1184
1185    /// The player profiles and the one in use.
1186    pub fn profiles(&self) -> &Profiles {
1187        &self.profiles
1188    }
1189
1190    /// Makes `id` the profile in use: its settings, key bindings and layout
1191    /// replace these (`None` if it has none yet: the layout picker's keys),
1192    /// and whatever else it keeps is its own from the next read
1193    /// ([`storage`](Self::storage)). It is the one the next launch opens
1194    /// on. The switch happens even if the list can't be saved (the next
1195    /// launch then opens on the profile used before).
1196    pub fn switch_profile(&mut self, id: ProfileId) -> Result<(), ProfileError> {
1197        self.profiles.set_current(id)?;
1198        self.load_profile();
1199        self.profiles
1200            .save(self.storage.computer_mut())
1201            .map_err(ProfileError::Storage)
1202    }
1203
1204    /// Loads everything the profile in use holds.
1205    fn load_profile(&mut self) {
1206        self.storage.set_profile(self.profiles.current());
1207        self.load_player_keys();
1208        self.load_settings();
1209        self.layout = self.settings.layout();
1210        self.refresh_keymap();
1211    }
1212
1213    /// Adds a profile called `name`, starting from the default options and
1214    /// keys, and returns its id; the one in use stays. Refused (and
1215    /// nothing changes) for a name that's empty, too long or taken, past
1216    /// [`MAX_PROFILES`](profiles::MAX_PROFILES), or if the list can't be
1217    /// saved.
1218    pub fn add_profile(&mut self, name: &str) -> Result<ProfileId, ProfileError> {
1219        let mut changed = self.profiles.clone();
1220        let id = changed.add(name)?;
1221        self.keep_profiles(changed)?;
1222        Ok(id)
1223    }
1224
1225    /// Renames the profile `id`. Refused (and nothing changes) as
1226    /// [`add_profile`](Self::add_profile) is.
1227    pub fn rename_profile(&mut self, id: ProfileId, name: &str) -> Result<(), ProfileError> {
1228        let mut changed = self.profiles.clone();
1229        changed.rename(id, name)?;
1230        self.keep_profiles(changed)
1231    }
1232
1233    /// Deletes the profile `id` and every key it holds, and no other
1234    /// profile's. If it was in use, the first profile left is
1235    /// ([`switch_profile`](Self::switch_profile)). Refused (and nothing
1236    /// changes) for the only profile, or if the list can't be saved; once
1237    /// the list is saved, an error deleting its keys is returned with the
1238    /// profile gone (they are never read again).
1239    pub fn delete_profile(&mut self, id: ProfileId) -> Result<(), ProfileError> {
1240        let mut changed = self.profiles.clone();
1241        changed.remove(id)?;
1242        let was_current = self.profiles.current() == id;
1243        self.keep_profiles(changed)?;
1244        if was_current {
1245            self.load_profile();
1246        }
1247        profiles::delete_keys(self.storage.computer_mut(), id).map_err(ProfileError::Storage)
1248    }
1249
1250    /// Saves `changed` and, once it is saved, takes it as the list.
1251    fn keep_profiles(&mut self, changed: Profiles) -> Result<(), ProfileError> {
1252        changed
1253            .save(self.storage.computer_mut())
1254            .map_err(ProfileError::Storage)?;
1255        self.profiles = changed;
1256        Ok(())
1257    }
1258}
1259
1260/// `base`, a font atlas's widths, in each of `lang`'s languages, the
1261/// source's and each pack's ([`Lang::widths`]).
1262fn lang_widths(lang: &Lang, base: &CellWidths) -> BTreeMap<LangCode, CellWidths> {
1263    let source = &lang.source_lang().code;
1264    core::iter::once(source)
1265        .chain(lang.codes())
1266        .map(|code| (code.clone(), lang.widths(code, base)))
1267        .collect()
1268}
1269
1270/// The name of the profile numbered `n` when the player hasn't named it
1271/// ([`PROFILE_NUMBERED_TEXT`], in the source language).
1272fn numbered_profile(lang: &Lang, n: u32) -> String {
1273    let text = lang.text(&lang.source_lang().code, PROFILE_NUMBERED_TEXT);
1274    crate::text::fill_values(text, &[("n", &n)])
1275}
1276
1277#[cfg(test)]
1278mod tests;