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) -> ⟪
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;