From e31269368db3f5e875b7d54975be8cbab29330a8 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Sat, 14 Dec 2024 23:08:44 -0500 Subject: [PATCH] Save/load more config --- Cargo.lock | 27 +++++++ Cargo.toml | 2 +- src/app.rs | 18 ++++- src/window/game.rs | 150 ++++++++++++++++++++++++++------------ src/window/game_screen.rs | 3 +- 5 files changed, 148 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c24fdf3..6e9bcab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,16 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +[[package]] +name = "accesskit" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b76d84ee70e30a4a7e39ab9018e2b17a6a09e31084176cc7c0b2dec036ba45" +dependencies = [ + "enumn", + "serde", +] + [[package]] name = "adler2" version = "2.0.0" @@ -33,6 +43,7 @@ dependencies = [ "cfg-if", "getrandom", "once_cell", + "serde", "version_check", "zerocopy", ] @@ -923,6 +934,7 @@ checksum = "775cfde491852059e386c4e1deb4aef381c617dc364184c6f6afee99b87c402b" dependencies = [ "bytemuck", "emath", + "serde", ] [[package]] @@ -931,11 +943,13 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53eafabcce0cb2325a59a98736efe0bf060585b437763f8c476957fb274bb974" dependencies = [ + "accesskit", "ahash", "emath", "epaint", "log", "nohash-hasher", + "serde", ] [[package]] @@ -1009,6 +1023,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1fe0049ce51d0fb414d029e668dd72eb30bc2b739bf34296ed97bd33df544f3" dependencies = [ "bytemuck", + "serde", ] [[package]] @@ -1059,6 +1074,17 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "epaint" version = "0.29.1" @@ -1074,6 +1100,7 @@ dependencies = [ "log", "nohash-hasher", "parking_lot", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9741f2b..b4e617f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ bytemuck = { version = "1", features = ["derive"] } clap = { version = "4", features = ["derive"] } cpal = { git = "https://github.com/sidit77/cpal.git", rev = "66ed6be" } directories = "5" -egui = "0.29" +egui = { version = "0.29", features = ["serde"] } egui_extras = "0.29" egui-toast = "0.15" egui-winit = "0.29" diff --git a/src/app.rs b/src/app.rs index 9e112d3..6151db5 100644 --- a/src/app.rs +++ b/src/app.rs @@ -38,6 +38,7 @@ pub struct Application { proxy: EventLoopProxy, mappings: MappingProvider, controllers: ControllerManager, + persistence: Persistence, viewports: HashMap, focused: Option, } @@ -46,7 +47,7 @@ impl Application { pub fn new(client: EmulatorClient, proxy: EventLoopProxy) -> Self { let icon = load_icon().ok().map(Arc::new); let persistence = Persistence::new(); - let mappings = MappingProvider::new(persistence); + let mappings = MappingProvider::new(persistence.clone()); let controllers = ControllerManager::new(client.clone(), &mappings); { let mappings = mappings.clone(); @@ -59,6 +60,7 @@ impl Application { proxy, mappings, controllers, + persistence, viewports: HashMap::new(), focused: None, } @@ -78,7 +80,12 @@ impl Application { impl ApplicationHandler for Application { fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let app = GameWindow::new(self.client.clone(), self.proxy.clone(), SimId::Player1); + let app = GameWindow::new( + self.client.clone(), + self.proxy.clone(), + self.persistence.clone(), + SimId::Player1, + ); let wrapper = Viewport::new(event_loop, self.icon.clone(), Box::new(app)); self.focused = Some(wrapper.id()); self.viewports.insert(wrapper.id(), wrapper); @@ -177,7 +184,12 @@ impl ApplicationHandler for Application { self.open(event_loop, Box::new(input)); } UserEvent::OpenPlayer2 => { - let p2 = GameWindow::new(self.client.clone(), self.proxy.clone(), SimId::Player2); + let p2 = GameWindow::new( + self.client.clone(), + self.proxy.clone(), + self.persistence.clone(), + SimId::Player2, + ); self.open(event_loop, Box::new(p2)); } } diff --git a/src/window/game.rs b/src/window/game.rs index 4b83ca0..c028579 100644 --- a/src/window/game.rs +++ b/src/window/game.rs @@ -3,6 +3,7 @@ use std::sync::mpsc; use crate::{ app::UserEvent, emulator::{EmulatorClient, EmulatorCommand, SimId}, + persistence::Persistence, }; use egui::{ ecolor::HexColor, menu, Align2, Button, CentralPanel, Color32, Context, Direction, Frame, @@ -10,6 +11,7 @@ use egui::{ ViewportId, WidgetText, Window, }; use egui_toast::{Toast, Toasts}; +use serde::{Deserialize, Serialize}; use winit::event_loop::EventLoopProxy; use super::{ @@ -35,22 +37,28 @@ const COLOR_PRESETS: [[Color32; 2]; 3] = [ pub struct GameWindow { client: EmulatorClient, proxy: EventLoopProxy, + persistence: Persistence, sim_id: SimId, - display_mode: DisplayMode, - colors: [Color32; 2], + config: GameConfig, screen: Option, messages: Option>, color_picker: Option, } impl GameWindow { - pub fn new(client: EmulatorClient, proxy: EventLoopProxy, sim_id: SimId) -> Self { + pub fn new( + client: EmulatorClient, + proxy: EventLoopProxy, + persistence: Persistence, + sim_id: SimId, + ) -> Self { + let config = load_config(&persistence, sim_id); Self { client, proxy, + persistence, sim_id, - display_mode: DisplayMode::Anaglyph, - colors: COLOR_PRESETS[0], + config, screen: None, messages: None, color_picker: None, @@ -99,7 +107,7 @@ impl GameWindow { let label = format!("x{scale}"); let scale = scale as f32; let dims = { - let Vec2 { x, y } = self.display_mode.proportions(); + let Vec2 { x, y } = self.config.display_mode.proportions(); Vec2::new(x * scale, y * scale + 22.0) }; if ui @@ -113,49 +121,46 @@ impl GameWindow { }); ui.menu_button("Display Mode", |ui| { - let old_proportions = self.display_mode.proportions(); - if ui - .selectable_option(&mut self.display_mode, DisplayMode::Anaglyph, "Anaglyph") - .clicked() - { - ui.close_menu(); - } - if ui - .selectable_option(&mut self.display_mode, DisplayMode::LeftEye, "Left Eye") - .clicked() - { - ui.close_menu(); - } - if ui - .selectable_option(&mut self.display_mode, DisplayMode::RightEye, "Right Eye") - .clicked() - { - ui.close_menu(); - } - if ui - .selectable_option( - &mut self.display_mode, - DisplayMode::SideBySide, - "Side by Side", - ) - .clicked() - { - ui.close_menu(); + let old_proportions = self.config.display_mode.proportions(); + let mut changed = false; + let mut display_mode = self.config.display_mode; + changed |= ui + .selectable_option(&mut display_mode, DisplayMode::Anaglyph, "Anaglyph") + .clicked(); + changed |= ui + .selectable_option(&mut display_mode, DisplayMode::LeftEye, "Left Eye") + .clicked(); + changed |= ui + .selectable_option(&mut display_mode, DisplayMode::RightEye, "Right Eye") + .clicked(); + changed |= ui + .selectable_option(&mut display_mode, DisplayMode::SideBySide, "Side by Side") + .clicked(); + + if !changed { + return; } - let new_proportions = self.display_mode.proportions(); + let current_dims = { + let viewport = ctx.input(|i| i.viewport().inner_rect.unwrap()); + viewport.max - viewport.min + }; + let new_proportions = display_mode.proportions(); let scale = new_proportions / old_proportions; if scale != Vec2::new(1.0, 1.0) { - let current_dims = ctx.input(|i| i.viewport().inner_rect.unwrap()); - let current_dims = current_dims.max - current_dims.min; - ctx.send_viewport_cmd(ViewportCommand::InnerSize(current_dims * scale)); } + + self.update_config(|c| { + c.display_mode = display_mode; + c.dimensions = current_dims * scale; + }); + ui.close_menu(); }); ui.menu_button("Colors", |ui| { for preset in COLOR_PRESETS { if ui.color_pair_button(preset[0], preset[1]).clicked() { - self.colors = preset; + self.update_config(|c| c.colors = preset); ui.close_menu(); } } @@ -168,8 +173,12 @@ impl GameWindow { if is_running { self.client.send_command(EmulatorCommand::Pause); } + let color_codes = [ + color_str(self.config.colors[0]), + color_str(self.config.colors[1]), + ]; self.color_picker = Some(ColorPickerState { - color_codes: [color_str(self.colors[0]), color_str(self.colors[1])], + color_codes, just_opened: true, unpause_on_close: is_running, }); @@ -223,18 +232,21 @@ impl GameWindow { } fn show_color_picker(&mut self, ui: &mut Ui) { + let mut colors = self.config.colors; let Some(state) = self.color_picker.as_mut() else { return; }; - let open = ui + let (open, updated) = ui .horizontal(|ui| { - let left_color = ui.color_picker(&mut self.colors[0], &mut state.color_codes[0]); + let left_color = ui.color_picker(&mut colors[0], &mut state.color_codes[0]); if state.just_opened { left_color.request_focus(); state.just_opened = false; } - let right_color = ui.color_picker(&mut self.colors[1], &mut state.color_codes[1]); - left_color.has_focus() || right_color.has_focus() + let right_color = ui.color_picker(&mut colors[1], &mut state.color_codes[1]); + let open = left_color.has_focus() || right_color.has_focus(); + let updated = left_color.changed() || right_color.changed(); + (open, updated) }) .inner; if !open { @@ -243,6 +255,38 @@ impl GameWindow { } self.color_picker = None; } + if updated { + self.update_config(|c| c.colors = colors); + } + } + + fn update_config(&mut self, update: impl FnOnce(&mut GameConfig)) { + let mut new_config = self.config.clone(); + update(&mut new_config); + if self.config != new_config { + let _ = self + .persistence + .save_config(config_filename(self.sim_id), &new_config); + } + self.config = new_config; + } +} + +fn config_filename(sim_id: SimId) -> &'static str { + match sim_id { + SimId::Player1 => "config_p1", + SimId::Player2 => "config_p2", + } +} + +fn load_config(persistence: &Persistence, sim_id: SimId) -> GameConfig { + if let Ok(config) = persistence.load_config(config_filename(sim_id)) { + return config; + } + GameConfig { + display_mode: DisplayMode::Anaglyph, + colors: COLOR_PRESETS[0], + dimensions: DisplayMode::Anaglyph.proportions() + Vec2::new(0.0, 22.0), } } @@ -255,13 +299,18 @@ impl AppWindow for GameWindow { } fn initial_viewport(&self) -> ViewportBuilder { - let dimensions = self.display_mode.proportions() + Vec2::new(0.0, 22.0); ViewportBuilder::default() .with_title("Lemur") - .with_inner_size(dimensions) + .with_inner_size(self.config.dimensions) } fn show(&mut self, ctx: &Context) { + let dimensions = { + let bounds = ctx.input(|i| i.viewport().inner_rect.unwrap()); + bounds.max - bounds.min + }; + self.update_config(|c| c.dimensions = dimensions); + let mut toasts = Toasts::new() .anchor(Align2::LEFT_BOTTOM, (10.0, 10.0)) .direction(Direction::BottomUp); @@ -289,7 +338,7 @@ impl AppWindow for GameWindow { let frame = Frame::central_panel(&ctx.style()).fill(Color32::BLACK); CentralPanel::default().frame(frame).show(ctx, |ui| { if let Some(screen) = self.screen.as_mut() { - screen.update(self.display_mode, self.colors); + screen.update(self.config.display_mode, self.config.colors); ui.add(screen); } }); @@ -383,3 +432,10 @@ struct ColorPickerState { just_opened: bool, unpause_on_close: bool, } + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] +struct GameConfig { + display_mode: DisplayMode, + colors: [Color32; 2], + dimensions: Vec2, +} diff --git a/src/window/game_screen.rs b/src/window/game_screen.rs index 10d8426..407a74a 100644 --- a/src/window/game_screen.rs +++ b/src/window/game_screen.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, sync::Arc}; use egui::{Color32, Rgba, Vec2, Widget}; +use serde::{Deserialize, Serialize}; use wgpu::{util::DeviceExt as _, BindGroup, BindGroupLayout, Buffer, RenderPipeline}; use crate::graphics::TextureSink; @@ -260,7 +261,7 @@ impl Colors { } } -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub enum DisplayMode { Anaglyph, LeftEye,