Persist window positions when possible

This commit is contained in:
Simon Gellis 2026-04-07 22:32:43 -04:00
parent c96368da0e
commit ec1fe6e0d3
No known key found for this signature in database
GPG Key ID: DA576912FED9577B
2 changed files with 40 additions and 13 deletions

View File

@ -1,6 +1,6 @@
use anyhow::Result; use anyhow::Result;
use clap::Parser; use clap::Parser;
use egui::{Color32, Vec2}; use egui::{Color32, Pos2, Vec2};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{emulator::SimId, persistence::Persistence, window::DisplayMode}; use crate::{emulator::SimId, persistence::Persistence, window::DisplayMode};
@ -75,6 +75,8 @@ pub struct SimConfig {
pub dimensions: Vec2, pub dimensions: Vec2,
#[serde(default = "default_audio_enabled")] #[serde(default = "default_audio_enabled")]
pub audio_enabled: bool, pub audio_enabled: bool,
#[serde(default)]
pub position: Option<Pos2>,
} }
impl SimConfig { impl SimConfig {
@ -87,6 +89,7 @@ impl SimConfig {
colors: COLOR_PRESETS[0], colors: COLOR_PRESETS[0],
dimensions: DisplayMode::Anaglyph.proportions() + Vec2::new(0.0, 22.0), dimensions: DisplayMode::Anaglyph.proportions() + Vec2::new(0.0, 22.0),
audio_enabled: true, audio_enabled: true,
position: None,
} }
} }

View File

@ -20,10 +20,11 @@ use crate::{
}; };
use anyhow::Context as _; use anyhow::Context as _;
use egui::{ use egui::{
Align2, Button, CentralPanel, Color32, Context, Frame, MenuBar, Panel, Ui, Vec2, Align2, Button, CentralPanel, Color32, Context, Frame, MenuBar, Panel, Pos2, Ui, Vec2,
ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, Window, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, Window,
}; };
use egui_notify::{Anchor, Toast, Toasts}; use egui_notify::{Anchor, Toast, Toasts};
use serde::{Deserialize, Serialize};
use winit::{event::KeyEvent, event_loop::EventLoopProxy}; use winit::{event::KeyEvent, event_loop::EventLoopProxy};
use super::{ use super::{
@ -50,7 +51,7 @@ pub struct GameWindow {
images: Arc<ImageTextureLoader>, images: Arc<ImageTextureLoader>,
mappings: MappingProvider, mappings: MappingProvider,
children: ViewportIdMap<ChildWindowWrapper>, children: ViewportIdMap<ChildWindowWrapper>,
child_sizes: UiData<ViewportIdMap<Vec2>>, child_states: UiData<ViewportIdMap<ChildState>>,
queued_children: Vec<ChildWindow>, queued_children: Vec<ChildWindow>,
} }
@ -91,7 +92,7 @@ impl GameWindow {
images: images.clone(), images: images.clone(),
mappings: mappings.clone(), mappings: mappings.clone(),
children: ViewportIdMap::default(), children: ViewportIdMap::default(),
child_sizes: UiData::new(), child_states: UiData::new(),
queued_children: vec![], queued_children: vec![],
} }
} }
@ -175,8 +176,9 @@ impl GameWindow {
}; };
let mut viewport = child.initial_viewport(); let mut viewport = child.initial_viewport();
if let Some(size) = self.child_sizes.get(&viewport_id) { if let Some(state) = self.child_states.get(&viewport_id) {
viewport.inner_size = Some(*size); viewport.position = Some(state.position);
viewport.inner_size = Some(state.size);
} }
self.children.insert( self.children.insert(
viewport_id, viewport_id,
@ -626,18 +628,27 @@ impl AppWindow for GameWindow {
} }
fn initial_viewport(&self) -> ViewportBuilder { fn initial_viewport(&self) -> ViewportBuilder {
ViewportBuilder::default() let builder = ViewportBuilder::default()
.with_title("Lemur") .with_title("Lemur")
.with_inner_size(self.config.dimensions) .with_inner_size(self.config.dimensions);
if let Some(position) = self.config.position {
builder.with_position(position)
} else {
builder
}
} }
fn show(&mut self, ui: &mut Ui) { fn show(&mut self, ui: &mut Ui) {
self.child_sizes.load(ui); self.child_states.load(ui);
let dimensions = { let dimensions = {
let bounds = ui.content_rect(); let bounds = ui.content_rect();
bounds.max - bounds.min bounds.max - bounds.min
}; };
self.update_config(|c| c.dimensions = dimensions); let position = ui.input(|i| i.viewport().outer_rect.map(|r| r.min));
self.update_config(|c| {
c.dimensions = dimensions;
c.position = position;
});
while let Ok(toast) = self.messages.try_recv() { while let Ok(toast) = self.messages.try_recv() {
self.toasts.add(toast); self.toasts.add(toast);
@ -679,8 +690,15 @@ impl AppWindow for GameWindow {
let app = child.app.clone(); let app = child.app.clone();
let viewport_builder = child.updates.take().unwrap_or_default(); let viewport_builder = child.updates.take().unwrap_or_default();
let close_requested = child.close_requested.clone(); let close_requested = child.close_requested.clone();
let size = ui.input_for(*id, |v| v.viewport_rect().size()); if let Some(rect) = ui.input_for(*id, |inp| inp.viewport().outer_rect) {
self.child_sizes.insert(*id, size); self.child_states.insert(
*id,
ChildState {
position: rect.min,
size: rect.size(),
},
);
}
ui.show_viewport_deferred(*id, viewport_builder, move |ui, _| { ui.show_viewport_deferred(*id, viewport_builder, move |ui, _| {
app.lock().unwrap().show(ui); app.lock().unwrap().show(ui);
if ui.input(|s| s.viewport().close_requested()) { if ui.input(|s| s.viewport().close_requested()) {
@ -689,7 +707,7 @@ impl AppWindow for GameWindow {
}); });
true true
}); });
self.child_sizes.save(ui); self.child_states.save(ui);
ui.request_repaint_after(Duration::from_millis(10)); ui.request_repaint_after(Duration::from_millis(10));
} }
@ -795,3 +813,9 @@ impl ChildWindow {
} }
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
struct ChildState {
position: Pos2,
size: Vec2,
}