This commit is contained in:
Kayleigh McMillan 2026-04-28 19:56:40 +03:00 committed by GitHub
commit 5fa83fa493
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 118 additions and 31 deletions

View file

@ -3,7 +3,7 @@ use native_windows_gui as nwg;
use rand::Rng; use rand::Rng;
use serde_json; use serde_json;
use std::{ use std::{
cell::RefCell, cell::{Cell, RefCell},
io::Read, io::Read,
os::windows::process::CommandExt, os::windows::process::CommandExt,
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -31,6 +31,7 @@ use super::stremio_server::StremioServer;
#[derive(Default, NwgUi)] #[derive(Default, NwgUi)]
pub struct MainWindow { pub struct MainWindow {
pub pip_mode: Cell<bool>,
pub command: String, pub command: String,
pub commands_path: Option<String>, pub commands_path: Option<String>,
pub webui_url: String, pub webui_url: String,
@ -63,6 +64,7 @@ pub struct MainWindow {
(tray_exit, OnMenuItemSelected): [nwg::stop_thread_dispatch()], (tray_exit, OnMenuItemSelected): [nwg::stop_thread_dispatch()],
(tray_show_hide, OnMenuItemSelected): [Self::on_show_hide], (tray_show_hide, OnMenuItemSelected): [Self::on_show_hide],
(tray_topmost, OnMenuItemSelected): [Self::on_toggle_topmost], (tray_topmost, OnMenuItemSelected): [Self::on_toggle_topmost],
(tray_pip, OnMenuItemSelected): [Self::on_toggle_pip_notice],
)] )]
pub tray: SystemTray, pub tray: SystemTray,
#[nwg_partial(parent: window)] #[nwg_partial(parent: window)]
@ -77,6 +79,9 @@ pub struct MainWindow {
#[nwg_events(OnNotice: [Self::on_toggle_fullscreen_notice] )] #[nwg_events(OnNotice: [Self::on_toggle_fullscreen_notice] )]
pub toggle_fullscreen_notice: nwg::Notice, pub toggle_fullscreen_notice: nwg::Notice,
#[nwg_control] #[nwg_control]
#[nwg_events(OnNotice: [Self::on_toggle_pip_notice] )]
pub toggle_pip_notice: nwg::Notice,
#[nwg_control]
#[nwg_events(OnNotice: [nwg::stop_thread_dispatch()] )] #[nwg_events(OnNotice: [nwg::stop_thread_dispatch()] )]
pub quit_notice: nwg::Notice, pub quit_notice: nwg::Notice,
#[nwg_control] #[nwg_control]
@ -243,6 +248,7 @@ impl MainWindow {
}); // thread }); // thread
let toggle_fullscreen_sender = self.toggle_fullscreen_notice.sender(); let toggle_fullscreen_sender = self.toggle_fullscreen_notice.sender();
let toggle_pip_sender = self.toggle_pip_notice.sender();
let quit_sender = self.quit_notice.sender(); let quit_sender = self.quit_notice.sender();
let hide_splash_sender = self.hide_splash_notice.sender(); let hide_splash_sender = self.hide_splash_notice.sender();
let focus_sender = self.focus_notice.sender(); let focus_sender = self.focus_notice.sender();
@ -259,6 +265,7 @@ impl MainWindow {
web_tx_web.send(RPCResponse::get_handshake()).ok(); web_tx_web.send(RPCResponse::get_handshake()).ok();
} }
Some("win-set-visibility") => toggle_fullscreen_sender.notice(), Some("win-set-visibility") => toggle_fullscreen_sender.notice(),
Some("win-pip-toggle") => toggle_pip_sender.notice(),
Some("quit") => quit_sender.notice(), Some("quit") => quit_sender.notice(),
Some("app-ready") => { Some("app-ready") => {
hide_splash_sender.notice(); hide_splash_sender.notice();
@ -353,6 +360,10 @@ impl MainWindow {
} }
fn on_min_max(&self, data: &nwg::EventData) { fn on_min_max(&self, data: &nwg::EventData) {
let data = data.on_min_max(); let data = data.on_min_max();
if self.pip_mode.get() {
data.set_min_size(320, 180);
return;
}
data.set_min_size(WINDOW_MIN_WIDTH, WINDOW_MIN_HEIGHT); data.set_min_size(WINDOW_MIN_WIDTH, WINDOW_MIN_HEIGHT);
} }
fn on_paint(&self) { fn on_paint(&self) {
@ -372,6 +383,16 @@ impl MainWindow {
} }
self.transmit_window_visibility_change(); self.transmit_window_visibility_change();
} }
fn on_toggle_pip_notice(&self) {
if let Some(hwnd) = self.window.handle.hwnd() {
if let Ok(mut saved_style) = self.saved_window_style.try_borrow_mut() {
self.pip_mode.set(!saved_style.pip);
saved_style.toggle_pip(hwnd);
self.tray.tray_pip.set_checked(saved_style.pip);
self.webview.fit_to_window(self.window.handle.hwnd());
}
}
}
fn on_hide_splash_notice(&self) { fn on_hide_splash_notice(&self) {
self.splash_screen.hide(); self.splash_screen.hide();
} }

View file

@ -16,6 +16,8 @@ pub struct SystemTray {
pub tray_show_hide: nwg::MenuItem, pub tray_show_hide: nwg::MenuItem,
#[nwg_control(parent: tray_menu, text: "Always on &top")] #[nwg_control(parent: tray_menu, text: "Always on &top")]
pub tray_topmost: nwg::MenuItem, pub tray_topmost: nwg::MenuItem,
#[nwg_control(parent: tray_menu, text: "&Picture in Picture")]
pub tray_pip: nwg::MenuItem,
#[nwg_control(parent: tray_menu, text: "&Quit")] #[nwg_control(parent: tray_menu, text: "&Quit")]
pub tray_exit: nwg::MenuItem, pub tray_exit: nwg::MenuItem,
} }

View file

@ -21,6 +21,9 @@ bitflags! {
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub struct WindowStyle { pub struct WindowStyle {
pub full_screen: bool, pub full_screen: bool,
pub pip: bool,
pub pip_pos: (i32, i32),
pub pip_size: (i32, i32),
pub pos: (i32, i32), pub pos: (i32, i32),
pub size: (i32, i32), pub size: (i32, i32),
pub style: i32, pub style: i32,
@ -148,6 +151,67 @@ impl WindowStyle {
} }
self.ex_style = unsafe { GetWindowLongA(hwnd, GWL_EXSTYLE) }; self.ex_style = unsafe { GetWindowLongA(hwnd, GWL_EXSTYLE) };
} }
pub fn toggle_pip(&mut self, hwnd: HWND) {
if self.pip {
// Restore from PiP
unsafe {
SetWindowLongA(hwnd, GWL_STYLE, self.style);
SetWindowLongA(hwnd, GWL_EXSTYLE, self.ex_style);
}
let topmost = if self.ex_style as u32 & WS_EX_TOPMOST == WS_EX_TOPMOST {
HWND_TOPMOST
} else {
HWND_NOTOPMOST
};
self.show_window_at(hwnd, topmost);
self.pip = false;
} else {
// Enter PiP — save current state
unsafe {
let mut rect = mem::zeroed();
GetWindowRect(hwnd, &mut rect);
self.pos = (rect.left, rect.top);
self.size = ((rect.right - rect.left), (rect.bottom - rect.top));
self.style = GetWindowLongA(hwnd, GWL_STYLE);
self.ex_style = GetWindowLongA(hwnd, GWL_EXSTYLE);
}
// Small window, no caption, keep thick frame for resize, always on top
let pip_style = self.style & !(WS_THICKFRAME as i32);
let pip_ex_style = self.ex_style
& !(WS_EX_DLGMODALFRAME as i32
| WS_EX_WINDOWEDGE as i32
| WS_EX_CLIENTEDGE as i32
| WS_EX_STATICEDGE as i32)
| WS_EX_TOPMOST as i32;
unsafe {
SetWindowLongA(hwnd, GWL_STYLE, pip_style);
SetWindowLongA(hwnd, GWL_EXSTYLE, pip_ex_style);
}
// Position bottom-right corner, 400x225 (16:9)
let monitor_w = unsafe { GetSystemMetrics(SM_CXSCREEN) };
let monitor_h = unsafe { GetSystemMetrics(SM_CYSCREEN) };
let pip_w = 400;
let pip_h = 225;
let pip_x = monitor_w - pip_w - 20;
let pip_y = monitor_h - pip_h - 60;
unsafe {
SetWindowPos(
hwnd,
HWND_TOPMOST,
pip_x,
pip_y,
pip_w,
pip_h,
SWP_FRAMECHANGED,
);
}
self.pip = true;
}
}
pub fn set_active(&mut self, hwnd: HWND) { pub fn set_active(&mut self, hwnd: HWND) {
unsafe { unsafe {
SetForegroundWindow(hwnd); SetForegroundWindow(hwnd);