mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-04 16:57:00 +01:00
Settings > Appearance offers 90% to 150%, applied as egui's zoom factor so controls grow with the text. Ctrl+plus and Ctrl+minus already zoomed through egui; what they settle on is now kept too, so Settings shows it and the next launch opens at it. It lives in the desktop-only settings: what reads well depends on the screen, not on the viewer's other devices.
142 lines
4.7 KiB
Rust
142 lines
4.7 KiB
Rust
//! Settings only the desktop app has.
|
|
//!
|
|
//! Kept out of [`pstr_core::prefs::PlaybackPrefs`] on purpose: that one is
|
|
//! shared with Android through a bridge record, and a field Android neither
|
|
//! shows nor keeps would be reset to its default every time Android saved.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// What the settings page offers for the streaming cache, in GiB.
|
|
pub const CACHE_CHOICES: [u32; 5] = [1, 2, 4, 8, 16];
|
|
|
|
/// What the settings page offers for the size of everything, in percent.
|
|
pub const TEXT_SIZE_CHOICES: [u16; 5] = [90, 100, 115, 130, 150];
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
|
// A file from an older version, or edited by hand, missing a field: the rest
|
|
// still counts, and the missing one takes its default.
|
|
#[serde(default)]
|
|
pub struct DesktopPrefs {
|
|
/// Let mpv decode on the GPU. Off is for a driver that shows green frames
|
|
/// or nothing; it costs CPU, not correctness.
|
|
pub hardware_decoding: bool,
|
|
/// How much decrypted video the disk cache may keep.
|
|
pub cache_gib: u32,
|
|
/// How the library was last arranged, so it opens that way.
|
|
pub filter: crate::app::Filter,
|
|
pub sort: pstr_core::browse::Sort,
|
|
pub grouped: bool,
|
|
/// The library's offer of posters and synopses was turned down.
|
|
pub metadata_offer_dismissed: bool,
|
|
/// How large text and controls are drawn, in percent. Per computer, not
|
|
/// synced: what reads well depends on the screen and how far away it is.
|
|
pub text_size: u16,
|
|
}
|
|
|
|
impl Default for DesktopPrefs {
|
|
fn default() -> Self {
|
|
Self {
|
|
hardware_decoding: true,
|
|
cache_gib: 4,
|
|
filter: crate::app::Filter::default(),
|
|
sort: pstr_core::browse::Sort::default(),
|
|
// On: a share holding a series and its three films is four tiles
|
|
// of the same picture otherwise.
|
|
grouped: true,
|
|
metadata_offer_dismissed: false,
|
|
text_size: 100,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DesktopPrefs {
|
|
/// [`Self::text_size`] as egui's zoom factor, kept to what is offered.
|
|
pub fn zoom(&self) -> f32 {
|
|
f32::from(self.text_size.clamp(50, 200)) / 100.0
|
|
}
|
|
|
|
pub fn cache_bytes(&self) -> u64 {
|
|
u64::from(self.cache_gib.clamp(1, 64)) << 30
|
|
}
|
|
}
|
|
|
|
/// The prefs and where they live.
|
|
pub struct DesktopPrefsFile {
|
|
path: PathBuf,
|
|
/// False when the file was there and would not parse. Such a file is never
|
|
/// overwritten — it is far more likely a bug than something to replace —
|
|
/// so changes then last only for the session.
|
|
writable: bool,
|
|
}
|
|
|
|
impl DesktopPrefsFile {
|
|
pub fn load(path: PathBuf) -> (Self, DesktopPrefs) {
|
|
match pstr_core::config::read_json::<DesktopPrefs>(&path) {
|
|
Ok(prefs) => (
|
|
Self {
|
|
path,
|
|
writable: true,
|
|
},
|
|
prefs.unwrap_or_default(),
|
|
),
|
|
Err(error) => {
|
|
tracing::warn!("read the desktop settings: {error}");
|
|
(
|
|
Self {
|
|
path,
|
|
writable: false,
|
|
},
|
|
DesktopPrefs::default(),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn save(&self, prefs: &DesktopPrefs) -> pstr_core::Result<()> {
|
|
if !self.writable {
|
|
tracing::warn!(
|
|
"not saving the desktop settings over {}, which would not parse",
|
|
self.path.display()
|
|
);
|
|
return Ok(());
|
|
}
|
|
pstr_core::config::write_json(&self.path, prefs)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn a_file_missing_a_field_keeps_the_rest() {
|
|
let prefs: DesktopPrefs = serde_json::from_str(r#"{ "cache_gib": 8 }"#).unwrap();
|
|
assert_eq!(prefs.cache_gib, 8);
|
|
assert!(prefs.hardware_decoding);
|
|
}
|
|
|
|
#[test]
|
|
fn a_text_size_from_an_older_file_is_full_size_and_a_wild_one_is_kept_in_range() {
|
|
let prefs: DesktopPrefs = serde_json::from_str(r#"{ "cache_gib": 8 }"#).unwrap();
|
|
assert_eq!(prefs.zoom(), 1.0);
|
|
let wild = DesktopPrefs {
|
|
text_size: 900,
|
|
..DesktopPrefs::default()
|
|
};
|
|
assert_eq!(wild.zoom(), 2.0);
|
|
}
|
|
|
|
#[test]
|
|
fn an_unparseable_file_is_never_overwritten() {
|
|
let path = std::env::temp_dir().join(format!("pstr-desktop-{}.json", std::process::id()));
|
|
std::fs::write(&path, "{ not json").unwrap();
|
|
let (file, prefs) = DesktopPrefsFile::load(path.clone());
|
|
assert_eq!(prefs, DesktopPrefs::default());
|
|
file.save(&DesktopPrefs::default()).unwrap();
|
|
assert_eq!(std::fs::read_to_string(&path).unwrap(), "{ not json");
|
|
let _ = std::fs::remove_file(path);
|
|
}
|
|
}
|