diff --git a/Cargo.lock b/Cargo.lock index f280f87..e1186c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1010,6 +1010,7 @@ dependencies = [ "log", "nohash-hasher", "profiling", + "ron", "serde", "smallvec", "unicode-segmentation", @@ -3766,6 +3767,20 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ron" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4147b952f3f819eca0e99527022f7d6a8d05f111aeb0a62960c74eb283bec8fc" +dependencies = [ + "bitflags 2.11.0", + "once_cell", + "serde", + "serde_derive", + "typeid", + "unicode-ident", +] + [[package]] name = "rtrb" version = "0.3.3" @@ -4753,6 +4768,12 @@ dependencies = [ "rustc-hash 2.1.2", ] +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" version = "1.19.0" diff --git a/Cargo.toml b/Cargo.toml index 2e1b795..ffb00b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ bytemuck = { version = "1", features = ["derive"] } clap = { version = "4", features = ["derive"] } cpal = { git = "https://github.com/sidit77/cpal.git", rev = "66ed6be" } directories = "6" -egui = { version = "0.34", features = ["serde"] } +egui = { version = "0.34", features = ["persistence", "serde"] } egui_extras = { version = "0.34", features = ["image"] } egui-notify = "0.22" egui-winit = "0.34" diff --git a/src/app.rs b/src/app.rs index b5decd9..11b1a53 100644 --- a/src/app.rs +++ b/src/app.rs @@ -33,6 +33,8 @@ use crate::{ window::{AppWindow, ChildWindow, GameScreen, GameWindow, InitArgs}, }; +const EGUI_FILENAME: &str = "egui"; + fn load_icon() -> anyhow::Result { let bytes = include_bytes!("../assets/lemur-256x256.png"); let img = image::load_from_memory_with_format(bytes, image::ImageFormat::Png)?; @@ -52,6 +54,7 @@ struct SharedViewportState { pub struct Application { client: EmulatorClient, + persistence: Persistence, ctx: Context, shared: SharedViewportState, icon: Option>, @@ -95,6 +98,8 @@ impl Application { ); let ctx = Context::default(); + let data = persistence.load_config(EGUI_FILENAME).unwrap_or_default(); + ctx.data_mut(|d| *d = data); let mut fonts = FontDefinitions::default(); fonts.font_data.insert( "Selawik".into(), @@ -175,6 +180,7 @@ impl Application { Self { client, + persistence, ctx, shared: SharedViewportState { viewport_info: ViewportIdMap::default(), @@ -454,6 +460,12 @@ impl ApplicationHandler for Application { } fn exiting(&mut self, _event_loop: &ActiveEventLoop) { + if let Err(error) = self + .ctx + .data(|d| self.persistence.save_config(EGUI_FILENAME, d)) + { + error!(%error, "could not save egui state."); + } let (sender, receiver) = oneshot::channel(); if self.client.send_command(EmulatorCommand::Exit(sender)) && let Err(error) = receiver.recv_timeout(Duration::from_secs(5)) diff --git a/src/window/game.rs b/src/window/game.rs index 8cbca38..6600393 100644 --- a/src/window/game.rs +++ b/src/window/game.rs @@ -1,5 +1,4 @@ use std::{ - collections::HashMap, ops::{Deref, DerefMut}, sync::{Arc, Mutex, atomic::AtomicBool, mpsc}, time::Duration, @@ -22,7 +21,7 @@ use crate::{ use anyhow::Context as _; use egui::{ Align2, Button, CentralPanel, Color32, Context, Frame, MenuBar, Panel, Ui, Vec2, - ViewportBuilder, ViewportCommand, ViewportId, Window, + ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, Window, }; use egui_notify::{Anchor, Toast, Toasts}; use winit::{event::KeyEvent, event_loop::EventLoopProxy}; @@ -50,7 +49,7 @@ pub struct GameWindow { memory: Arc, images: Arc, mappings: MappingProvider, - children: HashMap, + children: ViewportIdMap, } impl GameWindow { @@ -89,7 +88,7 @@ impl GameWindow { memory: memory.clone(), images: images.clone(), mappings: mappings.clone(), - children: HashMap::new(), + children: ViewportIdMap::default(), } } diff --git a/src/window/utils.rs b/src/window/utils.rs index d25e167..b2dc2d0 100644 --- a/src/window/utils.rs +++ b/src/window/utils.rs @@ -1,6 +1,6 @@ use std::{ fmt::{Display, UpperHex}, - ops::{Bound, RangeBounds}, + ops::{Bound, Deref, DerefMut, RangeBounds}, str::FromStr, }; @@ -8,7 +8,7 @@ use atoi::FromRadix16; use egui::{ Align, Color32, CornerRadius, CursorIcon, Event, Frame, Key, Layout, Margin, Rect, Response, RichText, Sense, Shape, Stroke, StrokeKind, TextEdit, Ui, UiBuilder, Vec2, Widget, WidgetText, - ecolor::HexColor, + ecolor::HexColor, util::id_type_map::SerializableAny, }; use num_traits::{CheckedAdd, CheckedSub, One}; @@ -409,3 +409,51 @@ impl ResponseExt for Response { self.clicked() || self.dragged() } } + +pub struct UiData { + current: T, + prev: T, + loaded: bool, +} + +impl UiData +where + T: Default + PartialEq + SerializableAny, +{ + pub fn new() -> Self { + Self { + current: T::default(), + prev: T::default(), + loaded: false, + } + } + + pub fn load(&mut self, ui: &Ui) { + if !self.loaded { + self.current = ui + .data_mut(|d| d.get_persisted(ui.id())) + .unwrap_or_default(); + self.loaded = true; + } + } + + pub fn save(&mut self, ui: &Ui) { + if self.current != self.prev { + ui.data_mut(|d| d.insert_persisted(ui.id(), self.current.clone())); + self.prev = self.current.clone(); + } + } +} + +impl Deref for UiData { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.current + } +} + +impl DerefMut for UiData { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.current + } +} diff --git a/src/window/vip/bgmap.rs b/src/window/vip/bgmap.rs index 86891b9..182f7da 100644 --- a/src/window/vip/bgmap.rs +++ b/src/window/vip/bgmap.rs @@ -5,6 +5,7 @@ use egui::{ TextureOptions, Ui, ViewportBuilder, }; use egui_extras::{Column, Size, StripBuilder, TableBuilder}; +use serde::{Deserialize, Serialize}; use crate::{ emulator::SimId, @@ -12,36 +13,56 @@ use crate::{ memory::{MemoryClient, MemoryView}, window::{ AppWindow, - utils::{NumberEdit, UiExt}, + utils::{NumberEdit, UiData, UiExt}, }, }; use super::utils::{self, CellData, CharacterGrid}; +#[derive(Clone, PartialEq, Serialize, Deserialize)] +struct State { + cell_index: usize, + generic_palette: bool, + scale: f32, + show_grid: bool, +} +impl Default for State { + fn default() -> Self { + Self { + cell_index: 0, + generic_palette: false, + scale: 1.0, + show_grid: false, + } + } +} + pub struct BgMapWindow { sim_id: SimId, memory: Arc, bgmaps: MemoryView, - cell_index: usize, - generic_palette: bool, params: ImageParams, - scale: f32, - show_grid: bool, + state: UiData, } impl BgMapWindow { pub fn new(sim_id: SimId, memory: &Arc, images: &ImageTextureLoader) -> Self { + let state: UiData = UiData::new(); let renderer = BgMapRenderer::new(sim_id, memory); - let params = images.add(sim_id, renderer, BgMapParams::default()); + let params = images.add( + sim_id, + renderer, + BgMapParams { + cell_index: state.cell_index, + generic_palette: state.generic_palette, + }, + ); Self { sim_id, memory: memory.clone(), bgmaps: memory.watch(sim_id, 0x00020000, 0x20000), - cell_index: params.cell_index, - generic_palette: params.generic_palette, params, - scale: 1.0, - show_grid: false, + state, } } @@ -57,10 +78,11 @@ impl BgMapWindow { ui.label("Map"); }); row.col(|ui| { - let mut bgmap_index = self.cell_index / 4096; + let mut bgmap_index = self.state.cell_index / 4096; ui.add(NumberEdit::new(&mut bgmap_index).range(0..16)); - if bgmap_index != self.cell_index / 4096 { - self.cell_index = (bgmap_index * 4096) + (self.cell_index % 4096); + if bgmap_index != self.state.cell_index / 4096 { + self.state.cell_index = + (bgmap_index * 4096) + (self.state.cell_index % 4096); } }); }); @@ -69,7 +91,7 @@ impl BgMapWindow { ui.label("Cell"); }); row.col(|ui| { - ui.add(NumberEdit::new(&mut self.cell_index).range(0..16 * 4096)); + ui.add(NumberEdit::new(&mut self.state.cell_index).range(0..16 * 4096)); }); }); body.row(row_height, |mut row| { @@ -77,7 +99,7 @@ impl BgMapWindow { ui.label("Address"); }); row.col(|ui| { - let address = 0x00020000 + (self.cell_index * 2); + let address = 0x00020000 + (self.state.cell_index * 2); let mut address_str = format!("{address:08x}"); ui.add_enabled( false, @@ -91,7 +113,7 @@ impl BgMapWindow { .texture_options(TextureOptions::NEAREST); ui.add(image); ui.section("Cell", |ui| { - let mut data = self.bgmaps.borrow().read::(self.cell_index); + let mut data = self.bgmaps.borrow().read::(self.state.cell_index); let mut cell = CellData::parse(data); TableBuilder::new(ui) .column(Column::remainder()) @@ -134,7 +156,7 @@ impl BgMapWindow { }); }); if cell.update(&mut data) { - let address = 0x00020000 + (self.cell_index * 2); + let address = 0x00020000 + (self.state.cell_index * 2); self.memory.write(self.sim_id, address as u32, &data); } }); @@ -142,28 +164,28 @@ impl BgMapWindow { ui.horizontal(|ui| { ui.label("Scale"); ui.spacing_mut().slider_width = ui.available_width(); - let slider = Slider::new(&mut self.scale, 1.0..=10.0) + let slider = Slider::new(&mut self.state.scale, 1.0..=10.0) .step_by(1.0) .show_value(false); ui.add(slider); }); - ui.checkbox(&mut self.show_grid, "Show grid"); - ui.checkbox(&mut self.generic_palette, "Generic palette"); + ui.checkbox(&mut self.state.show_grid, "Show grid"); + ui.checkbox(&mut self.state.generic_palette, "Generic palette"); }); }); self.params.write(BgMapParams { - cell_index: self.cell_index, - generic_palette: self.generic_palette, + cell_index: self.state.cell_index, + generic_palette: self.state.generic_palette, }); } fn show_bgmap(&mut self, ui: &mut Ui) { let grid = CharacterGrid::new(self.image_url("bgmap")) - .with_scale(self.scale) - .with_grid(self.show_grid) - .with_selected(self.cell_index % 4096); + .with_scale(self.state.scale) + .with_grid(self.state.show_grid) + .with_selected(self.state.cell_index % 4096); if let Some(selected) = grid.show(ui) { - self.cell_index = (self.cell_index / 4096 * 4096) + selected; + self.state.cell_index = (self.state.cell_index / 4096 * 4096) + selected; } } } @@ -180,6 +202,7 @@ impl AppWindow for BgMapWindow { } fn show(&mut self, ui: &mut Ui) { + self.state.load(ui); CentralPanel::default().show_inside(ui, |ui| { ui.horizontal_top(|ui| { StripBuilder::new(ui) @@ -195,6 +218,7 @@ impl AppWindow for BgMapWindow { }) }); }); + self.state.save(ui); } } diff --git a/src/window/vip/chardata.rs b/src/window/vip/chardata.rs index 7ed2fc5..e70e7ad 100644 --- a/src/window/vip/chardata.rs +++ b/src/window/vip/chardata.rs @@ -13,7 +13,7 @@ use crate::{ memory::{MemoryClient, MemoryView}, window::{ AppWindow, - utils::{NumberEdit, UiExt as _}, + utils::{NumberEdit, UiData, UiExt as _}, }, }; @@ -79,30 +79,50 @@ impl Display for Palette { } } +#[derive(Clone, PartialEq, Serialize, Deserialize)] +struct State { + palette: Palette, + index: usize, + scale: f32, + show_grid: bool, +} +impl Default for State { + fn default() -> Self { + Self { + palette: Palette::default(), + index: 0, + scale: 4.0, + show_grid: true, + } + } +} + pub struct CharacterDataWindow { sim_id: SimId, brightness: MemoryView, palettes: MemoryView, - palette: Palette, - index: usize, params: ImageParams, - scale: f32, - show_grid: bool, + state: UiData, } impl CharacterDataWindow { pub fn new(sim_id: SimId, memory: &MemoryClient, images: &ImageTextureLoader) -> Self { + let state: UiData = UiData::new(); let renderer = CharDataRenderer::new(sim_id, memory); - let params = images.add(sim_id, renderer, CharDataParams::default()); + let params = images.add( + sim_id, + renderer, + CharDataParams { + palette: state.palette, + index: state.index, + }, + ); Self { sim_id, brightness: memory.watch(sim_id, 0x0005f824, 8), palettes: memory.watch(sim_id, 0x0005f860, 16), - palette: params.palette, - index: params.index, params, - scale: 4.0, - show_grid: true, + state, } } @@ -118,7 +138,7 @@ impl CharacterDataWindow { ui.label("Index"); }); row.col(|ui| { - ui.add(NumberEdit::new(&mut self.index).range(0..2048)); + ui.add(NumberEdit::new(&mut self.state.index).range(0..2048)); }); }); body.row(row_height, |mut row| { @@ -126,11 +146,11 @@ impl CharacterDataWindow { ui.label("Address"); }); row.col(|ui| { - let address = match self.index { - 0x000..0x200 => 0x00060000 + self.index * 16, - 0x200..0x400 => 0x000e0000 + (self.index - 0x200) * 16, - 0x400..0x600 => 0x00160000 + (self.index - 0x400) * 16, - 0x600..0x800 => 0x001e0000 + (self.index - 0x600) * 16, + let address = match self.state.index { + 0x000..0x200 => 0x00060000 + self.state.index * 16, + 0x200..0x400 => 0x000e0000 + (self.state.index - 0x200) * 16, + 0x400..0x600 => 0x00160000 + (self.state.index - 0x400) * 16, + 0x600..0x800 => 0x001e0000 + (self.state.index - 0x600) * 16, _ => unreachable!("can't happen"), }; let mut address_str = format!("{address:08x}"); @@ -145,7 +165,7 @@ impl CharacterDataWindow { ui.label("Mirror"); }); row.col(|ui| { - let mirror = 0x00078000 + (self.index * 16); + let mirror = 0x00078000 + (self.state.index * 16); let mut mirror_str = format!("{mirror:08x}"); ui.add_enabled( false, @@ -162,12 +182,12 @@ impl CharacterDataWindow { ui.horizontal(|ui| { ui.label("Palette"); ComboBox::from_id_salt("palette") - .selected_text(self.palette.to_string()) + .selected_text(self.state.palette.to_string()) .width(ui.available_width()) .show_ui(ui, |ui| { for palette in Palette::values() { ui.selectable_value( - &mut self.palette, + &mut self.state.palette, palette, palette.to_string(), ); @@ -194,23 +214,23 @@ impl CharacterDataWindow { ui.horizontal(|ui| { ui.label("Scale"); ui.spacing_mut().slider_width = ui.available_width(); - let slider = Slider::new(&mut self.scale, 1.0..=10.0) + let slider = Slider::new(&mut self.state.scale, 1.0..=10.0) .step_by(1.0) .show_value(false); ui.add(slider); }); - ui.checkbox(&mut self.show_grid, "Show grid"); + ui.checkbox(&mut self.state.show_grid, "Show grid"); }); }); self.params.write(CharDataParams { - palette: self.palette, - index: self.index, + palette: self.state.palette, + index: self.state.index, }); } fn load_palette_colors(&self) -> [Color32; 4] { - let Some(offset) = self.palette.offset() else { + let Some(offset) = self.state.palette.offset() else { return utils::generic_palette(Color32::RED); }; let palette = self.palettes.borrow().read(offset); @@ -221,11 +241,11 @@ impl CharacterDataWindow { fn show_chardata(&mut self, ui: &mut Ui) { let grid = CharacterGrid::new(self.image_url("chardata")) - .with_scale(self.scale) - .with_grid(self.show_grid) - .with_selected(self.index); + .with_scale(self.state.scale) + .with_grid(self.state.show_grid) + .with_selected(self.state.index); if let Some(selected) = grid.show(ui) { - self.index = selected; + self.state.index = selected; } } } @@ -242,6 +262,7 @@ impl AppWindow for CharacterDataWindow { } fn show(&mut self, ui: &mut Ui) { + self.state.load(ui); CentralPanel::default().show_inside(ui, |ui| { ui.horizontal_top(|ui| { StripBuilder::new(ui) @@ -257,10 +278,11 @@ impl AppWindow for CharacterDataWindow { }); }); }); + self.state.save(ui); } } -#[derive(Clone, Default, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] struct CharDataParams { palette: Palette, index: usize, diff --git a/src/window/vip/framebuffer.rs b/src/window/vip/framebuffer.rs index 86a673c..59015ba 100644 --- a/src/window/vip/framebuffer.rs +++ b/src/window/vip/framebuffer.rs @@ -3,6 +3,7 @@ use egui::{ ViewportBuilder, }; use egui_extras::{Column, Size, StripBuilder, TableBuilder}; +use serde::{Deserialize, Serialize}; use crate::{ emulator::SimId, @@ -10,42 +11,58 @@ use crate::{ memory::{MemoryClient, MemoryView}, window::{ AppWindow, - utils::{NumberEdit, UiExt as _}, + utils::{NumberEdit, UiData, UiExt as _}, }, }; use super::utils; -pub struct FrameBufferWindow { - sim_id: SimId, +#[derive(Clone, PartialEq, Serialize, Deserialize)] +struct State { index: usize, left: bool, right: bool, generic_palette: bool, - params: ImageParams, scale: f32, } - -impl FrameBufferWindow { - pub fn new(sim_id: SimId, memory: &MemoryClient, images: &ImageTextureLoader) -> Self { - let initial_params = FrameBufferParams { +impl Default for State { + fn default() -> Self { + Self { index: 0, left: true, right: true, generic_palette: false, - left_color: Color32::from_rgb(0xff, 0x00, 0x00), - right_color: Color32::from_rgb(0x00, 0xc6, 0xf0), - }; + scale: 2.0, + } + } +} + +pub struct FrameBufferWindow { + sim_id: SimId, + params: ImageParams, + state: UiData, +} + +impl FrameBufferWindow { + pub fn new(sim_id: SimId, memory: &MemoryClient, images: &ImageTextureLoader) -> Self { + let state: UiData = UiData::new(); let renderer = FrameBufferRenderer::new(sim_id, memory); - let params = images.add(sim_id, renderer, initial_params); + let params = images.add( + sim_id, + renderer, + FrameBufferParams { + index: state.index, + left: state.left, + right: state.right, + generic_palette: state.generic_palette, + left_color: Color32::from_rgb(0xff, 0x00, 0x00), + right_color: Color32::from_rgb(0x00, 0xc6, 0xf0), + }, + ); Self { sim_id, - index: params.index, - left: params.left, - right: params.right, - generic_palette: params.generic_palette, params, - scale: 2.0, + state, } } @@ -61,7 +78,7 @@ impl FrameBufferWindow { ui.label("Index"); }); row.col(|ui| { - ui.add(NumberEdit::new(&mut self.index).range(0..2)); + ui.add(NumberEdit::new(&mut self.state.index).range(0..2)); }); }); body.row(row_height, |mut row| { @@ -69,7 +86,7 @@ impl FrameBufferWindow { ui.label("Left"); }); row.col(|ui| { - let address = self.index * 0x00008000; + let address = self.state.index * 0x00008000; let mut address_str = format!("{address:08x}"); ui.add_enabled( false, @@ -82,7 +99,7 @@ impl FrameBufferWindow { ui.label("Right"); }); row.col(|ui| { - let address = self.index * 0x00008000 + 0x00010000; + let address = self.state.index * 0x00008000 + 0x00010000; let mut address_str = format!("{address:08x}"); ui.add_enabled( false, @@ -95,7 +112,7 @@ impl FrameBufferWindow { ui.horizontal(|ui| { ui.label("Scale"); ui.spacing_mut().slider_width = ui.available_width(); - let slider = Slider::new(&mut self.scale, 1.0..=10.0) + let slider = Slider::new(&mut self.state.scale, 1.0..=10.0) .step_by(1.0) .show_value(false); ui.add(slider); @@ -105,29 +122,29 @@ impl FrameBufferWindow { .body(|mut body| { body.row(row_height, |mut row| { row.col(|ui| { - ui.checkbox(&mut self.left, "Left"); + ui.checkbox(&mut self.state.left, "Left"); }); row.col(|ui| { - ui.checkbox(&mut self.right, "Right"); + ui.checkbox(&mut self.state.right, "Right"); }); }); }); - ui.checkbox(&mut self.generic_palette, "Generic colors"); + ui.checkbox(&mut self.state.generic_palette, "Generic colors"); }); }); self.params.write(FrameBufferParams { - index: self.index, - left: self.left, - right: self.right, - generic_palette: self.generic_palette, + index: self.state.index, + left: self.state.left, + right: self.state.right, + generic_palette: self.state.generic_palette, ..*self.params }); } fn show_buffers(&mut self, ui: &mut Ui) { let image = Image::new(self.image_url("buffer")) - .fit_to_original_size(self.scale) + .fit_to_original_size(self.state.scale) .texture_options(TextureOptions::NEAREST); ui.add(image); } @@ -145,6 +162,7 @@ impl AppWindow for FrameBufferWindow { } fn show(&mut self, ui: &mut Ui) { + self.state.load(ui); CentralPanel::default().show_inside(ui, |ui| { ui.horizontal_top(|ui| { StripBuilder::new(ui) @@ -160,6 +178,7 @@ impl AppWindow for FrameBufferWindow { }); }); }); + self.state.save(ui); } } diff --git a/src/window/vip/object.rs b/src/window/vip/object.rs index 32bc58d..869230d 100644 --- a/src/window/vip/object.rs +++ b/src/window/vip/object.rs @@ -5,6 +5,7 @@ use egui::{ TextureOptions, Ui, ViewportBuilder, }; use egui_extras::{Column, Size, StripBuilder, TableBuilder}; +use serde::{Deserialize, Serialize}; use crate::{ emulator::SimId, @@ -12,40 +13,56 @@ use crate::{ memory::{MemoryClient, MemoryView}, window::{ AppWindow, - utils::{NumberEdit, UiExt as _}, + utils::{NumberEdit, UiData, UiExt as _}, }, }; use super::utils::{self, Object}; +#[derive(Clone, PartialEq, Serialize, Deserialize)] +struct State { + index: usize, + generic_palette: bool, + scale: f32, +} +impl Default for State { + fn default() -> Self { + Self { + index: 0, + generic_palette: false, + scale: 1.0, + } + } +} + pub struct ObjectWindow { sim_id: SimId, memory: Arc, objects: MemoryView, - index: usize, - generic_palette: bool, params: ImageParams, - scale: f32, + state: UiData, } impl ObjectWindow { pub fn new(sim_id: SimId, memory: &Arc, images: &ImageTextureLoader) -> Self { - let initial_params = ObjectParams { - index: 0, - generic_palette: false, - left_color: Color32::from_rgb(0xff, 0x00, 0x00), - right_color: Color32::from_rgb(0x00, 0xc6, 0xf0), - }; + let state: UiData = UiData::new(); let renderer = ObjectRenderer::new(sim_id, memory); - let params = images.add(sim_id, renderer, initial_params); + let params = images.add( + sim_id, + renderer, + ObjectParams { + index: state.index, + generic_palette: state.generic_palette, + left_color: Color32::from_rgb(0xff, 0x00, 0x00), + right_color: Color32::from_rgb(0x00, 0xc6, 0xf0), + }, + ); Self { sim_id, memory: memory.clone(), objects: memory.watch(sim_id, 0x0003e000, 0x2000), - index: params.index, - generic_palette: params.generic_palette, params, - scale: 1.0, + state, } } @@ -61,7 +78,7 @@ impl ObjectWindow { ui.label("Index"); }); row.col(|ui| { - ui.add(NumberEdit::new(&mut self.index).range(0..1024)); + ui.add(NumberEdit::new(&mut self.state.index).range(0..1024)); }); }); body.row(row_height, |mut row| { @@ -69,7 +86,7 @@ impl ObjectWindow { ui.label("Address"); }); row.col(|ui| { - let address = 0x3e000 + self.index * 8; + let address = 0x3e000 + self.state.index * 8; let mut address_str = format!("{address:08x}"); ui.add_enabled( false, @@ -83,7 +100,7 @@ impl ObjectWindow { .texture_options(TextureOptions::NEAREST); ui.add(image); ui.section("Properties", |ui| { - let mut object = self.objects.borrow().read::<[u16; 4]>(self.index); + let mut object = self.objects.borrow().read::<[u16; 4]>(self.state.index); let mut obj = Object::parse(object); TableBuilder::new(ui) .column(Column::remainder()) @@ -158,7 +175,7 @@ impl ObjectWindow { }); }); if obj.update(&mut object) { - let address = 0x3e000 + self.index * 8; + let address = 0x3e000 + self.state.index * 8; self.memory.write(self.sim_id, address as u32, &object); } }); @@ -166,24 +183,24 @@ impl ObjectWindow { ui.horizontal(|ui| { ui.label("Scale"); ui.spacing_mut().slider_width = ui.available_width(); - let slider = Slider::new(&mut self.scale, 1.0..=10.0) + let slider = Slider::new(&mut self.state.scale, 1.0..=10.0) .step_by(1.0) .show_value(false); ui.add(slider); }); - ui.checkbox(&mut self.generic_palette, "Generic palette"); + ui.checkbox(&mut self.state.generic_palette, "Generic palette"); }); }); self.params.write(ObjectParams { - index: self.index, - generic_palette: self.generic_palette, + index: self.state.index, + generic_palette: self.state.generic_palette, ..*self.params }); } fn show_object(&mut self, ui: &mut Ui) { let image = Image::new(self.image_url("object-full")) - .fit_to_original_size(self.scale) + .fit_to_original_size(self.state.scale) .texture_options(TextureOptions::NEAREST); ui.add(image); } @@ -201,6 +218,7 @@ impl AppWindow for ObjectWindow { } fn show(&mut self, ui: &mut Ui) { + self.state.load(ui); CentralPanel::default().show_inside(ui, |ui| { ui.horizontal_top(|ui| { StripBuilder::new(ui) @@ -216,6 +234,7 @@ impl AppWindow for ObjectWindow { }); }); }); + self.state.save(ui); } } diff --git a/src/window/vip/world.rs b/src/window/vip/world.rs index 1c32071..dcf24bd 100644 --- a/src/window/vip/world.rs +++ b/src/window/vip/world.rs @@ -11,6 +11,7 @@ use fixed::{ }; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; +use serde::{Deserialize, Serialize}; use crate::{ emulator::SimId, @@ -18,46 +19,62 @@ use crate::{ memory::{MemoryClient, MemoryRef, MemoryView}, window::{ AppWindow, - utils::{NumberEdit, UiExt as _}, + utils::{NumberEdit, UiData, UiExt as _}, }, }; use super::utils::{self, CellData, Object, shade}; +#[derive(Clone, PartialEq, Serialize, Deserialize)] +struct State { + index: usize, + param_index: usize, + generic_palette: bool, + show_extents: bool, + scale: f32, +} +impl Default for State { + fn default() -> Self { + Self { + index: 31, + param_index: 0, + generic_palette: false, + show_extents: false, + scale: 1.0, + } + } +} + pub struct WorldWindow { sim_id: SimId, memory: Arc, worlds: MemoryView, bgmaps: MemoryView, - index: usize, - param_index: usize, - generic_palette: bool, - show_extents: bool, params: ImageParams, - scale: f32, + state: UiData, } impl WorldWindow { pub fn new(sim_id: SimId, memory: &Arc, images: &ImageTextureLoader) -> Self { - let initial_params = WorldParams { - index: 31, - generic_palette: false, - left_color: Color32::from_rgb(0xff, 0x00, 0x00), - right_color: Color32::from_rgb(0x00, 0xc6, 0xf0), - }; + let state: UiData = UiData::new(); let renderer = WorldRenderer::new(sim_id, memory); - let params = images.add(sim_id, renderer, initial_params); + let params = images.add( + sim_id, + renderer, + WorldParams { + index: state.index, + generic_palette: state.generic_palette, + left_color: Color32::from_rgb(0xff, 0x00, 0x00), + right_color: Color32::from_rgb(0x00, 0xc6, 0xf0), + }, + ); Self { sim_id, memory: memory.clone(), worlds: memory.watch(sim_id, 0x0003d800, 0x400), bgmaps: memory.watch(sim_id, 0x00020000, 0x20000), - index: params.index, - param_index: 0, - generic_palette: params.generic_palette, - show_extents: false, params, - scale: 1.0, + state, } } @@ -73,7 +90,7 @@ impl WorldWindow { ui.label("Index"); }); row.col(|ui| { - ui.add(NumberEdit::new(&mut self.index).range(0..32)); + ui.add(NumberEdit::new(&mut self.state.index).range(0..32)); }); }); body.row(row_height, |mut row| { @@ -81,7 +98,7 @@ impl WorldWindow { ui.label("Address"); }); row.col(|ui| { - let address = 0x0003d800 + self.index * 32; + let address = 0x0003d800 + self.state.index * 32; let mut address_str = format!("{address:08x}"); ui.add_enabled( false, @@ -92,7 +109,7 @@ impl WorldWindow { }); let mut data = { let worlds = self.worlds.borrow(); - worlds.read(self.index) + worlds.read(self.state.index) }; let mut world = World::parse(&data); ui.section("Properties", |ui| { @@ -272,7 +289,7 @@ impl WorldWindow { }); }); if world.update(&mut data) { - let address = 0x0003d800 + self.index * 32; + let address = 0x0003d800 + self.state.index * 32; self.memory.write(self.sim_id, address as u32, &data); } if world.header.mode == WorldMode::HBias { @@ -287,10 +304,12 @@ impl WorldWindow { }); row.col(|ui| { let max = world.height.max(8) as usize; - ui.add(NumberEdit::new(&mut self.param_index).range(0..max)); + ui.add( + NumberEdit::new(&mut self.state.param_index).range(0..max), + ); }); }); - let base = (world.param_base + self.param_index * 2) & 0x1ffff; + let base = (world.param_base + self.state.param_index * 2) & 0x1ffff; let mut param = HBiasParam::load(&self.bgmaps.borrow(), base); body.row(row_height, |mut row| { row.col(|ui| { @@ -337,10 +356,12 @@ impl WorldWindow { }); row.col(|ui| { let max = world.height.max(1) as usize; - ui.add(NumberEdit::new(&mut self.param_index).range(0..max)); + ui.add( + NumberEdit::new(&mut self.state.param_index).range(0..max), + ); }); }); - let base = (world.param_base + self.param_index * 8) & 0x1ffff; + let base = (world.param_base + self.state.param_index * 8) & 0x1ffff; let mut param = AffineParam::load(&self.bgmaps.borrow(), base); body.row(row_height, |mut row| { row.col(|ui| { @@ -400,49 +421,49 @@ impl WorldWindow { }); }); } else { - self.param_index = 0; + self.state.param_index = 0; } ui.section("Display", |ui| { ui.horizontal(|ui| { ui.label("Scale"); ui.spacing_mut().slider_width = ui.available_width(); - let slider = Slider::new(&mut self.scale, 1.0..=10.0) + let slider = Slider::new(&mut self.state.scale, 1.0..=10.0) .step_by(1.0) .show_value(false); ui.add(slider); }); - ui.checkbox(&mut self.generic_palette, "Generic palette"); - ui.checkbox(&mut self.show_extents, "Show extents"); + ui.checkbox(&mut self.state.generic_palette, "Generic palette"); + ui.checkbox(&mut self.state.show_extents, "Show extents"); }); }); self.params.write(WorldParams { - index: self.index, - generic_palette: self.generic_palette, + index: self.state.index, + generic_palette: self.state.generic_palette, ..*self.params }); } fn show_world(&mut self, ui: &mut Ui) { let image = Image::new(self.image_url("world")) - .fit_to_original_size(self.scale) + .fit_to_original_size(self.state.scale) .texture_options(TextureOptions::NEAREST); let res = ui.add(image); - if self.show_extents { + if self.state.show_extents { let world = { let worlds = self.worlds.borrow(); - let data = worlds.read(self.index); + let data = worlds.read(self.state.index); World::parse(&data) }; if world.header.mode == WorldMode::Object { return; } - let lx1 = (world.dst_x - world.dst_parallax) as f32 * self.scale; - let lx2 = lx1 + world.width as f32 * self.scale; - let rx1 = (world.dst_x + world.dst_parallax) as f32 * self.scale; - let rx2 = rx1 + world.width as f32 * self.scale; - let y1 = world.dst_y as f32 * self.scale; - let y2 = y1 + world.height as f32 * self.scale; + let lx1 = (world.dst_x - world.dst_parallax) as f32 * self.state.scale; + let lx2 = lx1 + world.width as f32 * self.state.scale; + let rx1 = (world.dst_x + world.dst_parallax) as f32 * self.state.scale; + let rx2 = rx1 + world.width as f32 * self.state.scale; + let y1 = world.dst_y as f32 * self.state.scale; + let y2 = y1 + world.height as f32 * self.state.scale; let left_color = self.params.left_color; let right_color = self.params.right_color; @@ -517,6 +538,7 @@ impl AppWindow for WorldWindow { } fn show(&mut self, ui: &mut Ui) { + self.state.load(ui); CentralPanel::default().show_inside(ui, |ui| { ui.horizontal_top(|ui| { StripBuilder::new(ui) @@ -532,6 +554,7 @@ impl AppWindow for WorldWindow { }); }); }); + self.state.save(ui); } }