Desktop: global UI scale (egui zoom_factor), persisted
The card-density slider only resized the channel-grid cards — the sidebar, settings, detail panel, and all text stayed fixed. Add a real global scale that grows/shrinks the *entire* UI crisply. egui already handles Ctrl + / Ctrl - / Ctrl 0 via zoom_factor (it re-rasterizes fonts rather than bitmap-stretching), but it wasn't persisted and had no visible control. Now: - New `[ui] ui_scale` config (default 1.0), applied with set_zoom_factor at startup. - update() mirrors ctx.zoom_factor() back into config each frame and saves on a 500ms debounce, so both the slider and the built-in keyboard zoom persist across restarts without writing config.toml every frame of a drag. - A "UI scale" slider (0.5–3.0×) + Reset button in the Settings screen, driving set_zoom_factor live. The card-density "Size:" slider stays as a separate grid-density preference (it composes with the global zoom). 95 tests pass; GUI verified running with the per-frame zoom-sync path. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
fd9063b5cb
commit
b8a742535b
2 changed files with 61 additions and 0 deletions
52
src/app.rs
52
src/app.rs
|
|
@ -138,6 +138,10 @@ pub struct App {
|
|||
plex_status: String,
|
||||
db: Database,
|
||||
card_density: f32,
|
||||
/// When set, the global UI zoom changed and config should be saved at
|
||||
/// this instant (debounced so a slider drag / key-repeat doesn't write
|
||||
/// config.toml every frame). Synced from `ctx.zoom_factor()` in update().
|
||||
scale_save_at: Option<Instant>,
|
||||
sort_mode: SortMode,
|
||||
watched: HashSet<String>,
|
||||
/// Per-video flag sets (bookmark/favourite/waiting/archive). Loaded from
|
||||
|
|
@ -283,6 +287,10 @@ impl App {
|
|||
};
|
||||
|
||||
theme::apply(&cc.egui_ctx, &config.ui.theme);
|
||||
// Restore the persisted global UI zoom. egui's built-in Ctrl +/-/0
|
||||
// also drives zoom_factor; update() keeps config.ui.ui_scale synced
|
||||
// and persists changes so the size survives restarts.
|
||||
cc.egui_ctx.set_zoom_factor(config.ui.ui_scale.clamp(0.5, 3.0));
|
||||
|
||||
let channels_root = config.backup.directory.clone();
|
||||
let settings_dir = channels_root.display().to_string();
|
||||
|
|
@ -433,6 +441,7 @@ impl App {
|
|||
plex_status: String::new(),
|
||||
db,
|
||||
card_density: 1.0,
|
||||
scale_save_at: None,
|
||||
sort_mode: SortMode::Title,
|
||||
watched,
|
||||
flags,
|
||||
|
|
@ -2450,6 +2459,29 @@ impl App {
|
|||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label("UI scale:");
|
||||
ui.horizontal(|ui| {
|
||||
// Drive zoom_factor directly; update() mirrors it
|
||||
// back into config.ui.ui_scale and persists it.
|
||||
let mut scale = ctx.zoom_factor();
|
||||
let resp = ui.add(
|
||||
egui::Slider::new(&mut scale, 0.5_f32..=3.0_f32)
|
||||
.step_by(0.1)
|
||||
.suffix("×"),
|
||||
);
|
||||
if resp.changed() {
|
||||
ctx.set_zoom_factor(scale);
|
||||
}
|
||||
if ui.button("Reset").on_hover_text("Back to 1.0× (Ctrl+0)").clicked() {
|
||||
ctx.set_zoom_factor(1.0);
|
||||
}
|
||||
ui.label(
|
||||
egui::RichText::new("scales the whole UI · also Ctrl + / Ctrl - / Ctrl 0")
|
||||
.small().weak(),
|
||||
);
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Minimize to tray:");
|
||||
ui.checkbox(&mut self.config.ui.minimize_to_tray, "hide window on close instead of quitting")
|
||||
.on_hover_text("Requires a working StatusNotifier host (KDE native, GNOME with AppIndicator extension). Ctrl+Q always quits.");
|
||||
|
|
@ -3581,6 +3613,26 @@ impl App {
|
|||
|
||||
impl eframe::App for App {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
// ── Global UI zoom sync + persistence ───────────────────────────
|
||||
// egui's built-in Ctrl +/-/0 (and our Settings slider) mutate
|
||||
// ctx.zoom_factor() directly. Mirror it into config and persist on
|
||||
// a short debounce so the chosen scale survives restarts without
|
||||
// writing config.toml on every frame of a slider drag / key repeat.
|
||||
let zoom = ctx.zoom_factor();
|
||||
if (zoom - self.config.ui.ui_scale).abs() > f32::EPSILON {
|
||||
self.config.ui.ui_scale = zoom;
|
||||
self.scale_save_at =
|
||||
Some(Instant::now() + std::time::Duration::from_millis(500));
|
||||
// Ensure the debounce fires even if the UI would otherwise idle.
|
||||
ctx.request_repaint_after(std::time::Duration::from_millis(550));
|
||||
}
|
||||
if let Some(at) = self.scale_save_at {
|
||||
if Instant::now() >= at {
|
||||
let _ = self.config.save(&self.config_path);
|
||||
self.scale_save_at = None;
|
||||
}
|
||||
}
|
||||
|
||||
// ── System-tray event drain ─────────────────────────────────────
|
||||
// Tray menu activations arrive on a background-thread channel.
|
||||
// Drain them all each frame and translate into viewport commands.
|
||||
|
|
|
|||
|
|
@ -161,13 +161,22 @@ pub struct UiSection {
|
|||
/// hidden window.
|
||||
#[serde(default)]
|
||||
pub minimize_to_tray: bool,
|
||||
/// Global egui zoom factor — scales the *entire* desktop UI (fonts,
|
||||
/// spacing, widgets), not just the card grid. 1.0 = native. Persisted
|
||||
/// here so the chosen size survives restarts and is kept in sync with
|
||||
/// the built-in Ctrl + / Ctrl - / Ctrl 0 keyboard zoom.
|
||||
#[serde(default = "default_ui_scale")]
|
||||
pub ui_scale: f32,
|
||||
}
|
||||
|
||||
fn default_ui_scale() -> f32 { 1.0 }
|
||||
|
||||
impl Default for UiSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
theme: default_theme(),
|
||||
minimize_to_tray: false,
|
||||
ui_scale: default_ui_scale(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue