diff --git a/src/config.rs b/src/config.rs index f493476..fc623af 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ use anyhow::Result; use clap::Parser; -use egui::{Color32, Vec2}; +use egui::{Color32, Pos2, Vec2}; use serde::{Deserialize, Serialize}; use crate::{emulator::SimId, persistence::Persistence, window::DisplayMode}; @@ -75,6 +75,8 @@ pub struct SimConfig { pub dimensions: Vec2, #[serde(default = "default_audio_enabled")] pub audio_enabled: bool, + #[serde(default)] + pub position: Option, } impl SimConfig { @@ -87,6 +89,7 @@ impl SimConfig { colors: COLOR_PRESETS[0], dimensions: DisplayMode::Anaglyph.proportions() + Vec2::new(0.0, 22.0), audio_enabled: true, + position: None, } } diff --git a/src/window/game.rs b/src/window/game.rs index 48f140e..f67e2e9 100644 --- a/src/window/game.rs +++ b/src/window/game.rs @@ -20,10 +20,11 @@ use crate::{ }; use anyhow::Context as _; 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, }; use egui_notify::{Anchor, Toast, Toasts}; +use serde::{Deserialize, Serialize}; use winit::{event::KeyEvent, event_loop::EventLoopProxy}; use super::{ @@ -50,7 +51,7 @@ pub struct GameWindow { images: Arc, mappings: MappingProvider, children: ViewportIdMap, - child_sizes: UiData>, + child_states: UiData>, queued_children: Vec, } @@ -91,7 +92,7 @@ impl GameWindow { images: images.clone(), mappings: mappings.clone(), children: ViewportIdMap::default(), - child_sizes: UiData::new(), + child_states: UiData::new(), queued_children: vec![], } } @@ -175,8 +176,9 @@ impl GameWindow { }; let mut viewport = child.initial_viewport(); - if let Some(size) = self.child_sizes.get(&viewport_id) { - viewport.inner_size = Some(*size); + if let Some(state) = self.child_states.get(&viewport_id) { + viewport.position = Some(state.position); + viewport.inner_size = Some(state.size); } self.children.insert( viewport_id, @@ -626,18 +628,27 @@ impl AppWindow for GameWindow { } fn initial_viewport(&self) -> ViewportBuilder { - ViewportBuilder::default() + let builder = ViewportBuilder::default() .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) { - self.child_sizes.load(ui); + self.child_states.load(ui); let dimensions = { let bounds = ui.content_rect(); 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() { self.toasts.add(toast); @@ -679,8 +690,15 @@ impl AppWindow for GameWindow { let app = child.app.clone(); let viewport_builder = child.updates.take().unwrap_or_default(); let close_requested = child.close_requested.clone(); - let size = ui.input_for(*id, |v| v.viewport_rect().size()); - self.child_sizes.insert(*id, size); + if let Some(rect) = ui.input_for(*id, |inp| inp.viewport().outer_rect) { + self.child_states.insert( + *id, + ChildState { + position: rect.min, + size: rect.size(), + }, + ); + } ui.show_viewport_deferred(*id, viewport_builder, move |ui, _| { app.lock().unwrap().show(ui); if ui.input(|s| s.viewport().close_requested()) { @@ -689,7 +707,7 @@ impl AppWindow for GameWindow { }); true }); - self.child_sizes.save(ui); + self.child_states.save(ui); 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, +}