diff --git a/Cargo.toml b/Cargo.toml index 2321a28..c9a1c31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ winapi = { version = "0.3.9", features = [ "wincon", "winuser", "namedpipeapi", + "dwmapi", ] } webview2 = "0.1.4" webview2-sys = "0.1.1" diff --git a/src/stremio_app/app.rs b/src/stremio_app/app.rs index 024272e..a7ea4fb 100644 --- a/src/stremio_app/app.rs +++ b/src/stremio_app/app.rs @@ -130,7 +130,11 @@ impl MainWindow { self.webview.dev_tools.set(self.dev_tools).ok(); if let Some(hwnd) = self.window.handle.hwnd() { if let Ok(mut saved_style) = self.saved_window_style.try_borrow_mut() { - saved_style.center_window(hwnd, WINDOW_MIN_WIDTH, WINDOW_MIN_HEIGHT); + if !saved_style.restore_window_state(hwnd) { + saved_style.center_window(hwnd, WINDOW_MIN_WIDTH, WINDOW_MIN_HEIGHT); + } + // Set title bar to Stremio purple (#6B5B95 -> COLORREF 0x00955B6B) + saved_style.set_title_bar_color(hwnd, 0x00955B6B); } } @@ -419,6 +423,12 @@ impl MainWindow { } } fn on_quit(&self, data: &nwg::EventData) { + if let (Some(hwnd), Ok(saved_style)) = ( + self.window.handle.hwnd(), + self.saved_window_style.try_borrow(), + ) { + saved_style.save_window_state(hwnd); + } if let nwg::EventData::OnWindowClose(data) = data { data.close(false); } diff --git a/src/stremio_app/window_helper.rs b/src/stremio_app/window_helper.rs index 68c5547..ce9357e 100644 --- a/src/stremio_app/window_helper.rs +++ b/src/stremio_app/window_helper.rs @@ -1,4 +1,49 @@ use std::{cmp, mem}; +use std::path::PathBuf; +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize)] +struct SavedWindowGeometry { + x: i32, + y: i32, + width: i32, + height: i32, + maximized: bool, +} + +impl SavedWindowGeometry { + fn config_path() -> Option { + std::env::current_exe().ok().and_then(|p| p.parent().map(|d| d.join("window_state.json"))) + } + + pub fn save(hwnd: HWND) { + use winapi::um::winuser::{GetWindowPlacement, WINDOWPLACEMENT, IsZoomed}; + let maximized = unsafe { IsZoomed(hwnd) } != 0; + let mut wp: WINDOWPLACEMENT = unsafe { mem::zeroed() }; + wp.length = mem::size_of::() as u32; + if unsafe { GetWindowPlacement(hwnd, &mut wp) } == 0 { + return; + } + let state = SavedWindowGeometry { + x: wp.rcNormalPosition.left, + y: wp.rcNormalPosition.top, + width: wp.rcNormalPosition.right - wp.rcNormalPosition.left, + height: wp.rcNormalPosition.bottom - wp.rcNormalPosition.top, + maximized, + }; + if let Some(path) = Self::config_path() { + if let Ok(json) = serde_json::to_string_pretty(&state) { + std::fs::write(path, json).ok(); + } + } + } + + pub fn load() -> Option { + let path = Self::config_path()?; + let json = std::fs::read_to_string(path).ok()?; + serde_json::from_str(&json).ok() + } +} use winapi::shared::windef::HWND; use winapi::um::winuser::{ GetForegroundWindow, GetMonitorInfoA, GetSystemMetrics, GetWindowLongA, GetWindowRect, @@ -148,6 +193,42 @@ impl WindowStyle { } self.ex_style = unsafe { GetWindowLongA(hwnd, GWL_EXSTYLE) }; } + /// Restores window position from saved state. Returns true if restored. + pub fn restore_window_state(&mut self, hwnd: HWND) -> bool { + if let Some(state) = SavedWindowGeometry::load() { + self.pos = (state.x, state.y); + self.size = (state.width, state.height); + self.show_window_at(hwnd, HWND_NOTOPMOST); + if state.maximized { + use winapi::um::winuser::{ShowWindow, SW_SHOWMAXIMIZED}; + unsafe { ShowWindow(hwnd, SW_SHOWMAXIMIZED); } + } + true + } else { + false + } + } + + /// Saves current window position and state to disk. + pub fn save_window_state(&self, hwnd: HWND) { + SavedWindowGeometry::save(hwnd); + } + + /// Sets the window title bar color using DWM API (Windows 10 build 22000+). + /// Color format is COLORREF (0x00BBGGRR). + pub fn set_title_bar_color(&self, hwnd: HWND, color: u32) { + use winapi::um::dwmapi::DwmSetWindowAttribute; + const DWMWA_CAPTION_COLOR: u32 = 35; + unsafe { + DwmSetWindowAttribute( + hwnd, + DWMWA_CAPTION_COLOR, + &color as *const u32 as *const _, + std::mem::size_of::() as u32, + ); + } + } + pub fn set_active(&mut self, hwnd: HWND) { unsafe { SetForegroundWindow(hwnd);