mirror of
https://github.com/Stremio/stremio-shell-ng.git
synced 2026-01-11 22:40:32 +00:00
Better window events; pause on minimize; always on top
This commit is contained in:
parent
29817c614d
commit
e4a262c6d9
3 changed files with 126 additions and 82 deletions
|
|
@ -7,9 +7,10 @@ use std::sync::Arc;
|
|||
use std::thread;
|
||||
use winapi::shared::windef::HWND__;
|
||||
use winapi::um::winuser::{
|
||||
GetActiveWindow, GetSystemMetrics, GetWindowLongA, IsIconic, IsZoomed, SetWindowLongA,
|
||||
GWL_EXSTYLE, GWL_STYLE, SM_CXSCREEN, SM_CYSCREEN, WS_CAPTION, WS_EX_CLIENTEDGE,
|
||||
WS_EX_DLGMODALFRAME, WS_EX_STATICEDGE, WS_EX_WINDOWEDGE, WS_THICKFRAME,
|
||||
GetForegroundWindow, GetSystemMetrics, GetWindowLongA, IsIconic, IsZoomed, SetWindowLongA,
|
||||
SetWindowPos, GWL_EXSTYLE, GWL_STYLE, HWND_NOTOPMOST, HWND_TOPMOST, SM_CXSCREEN, SM_CYSCREEN,
|
||||
SWP_FRAMECHANGED, SWP_NOMOVE, SWP_NOSIZE, WS_CAPTION, WS_EX_CLIENTEDGE, WS_EX_DLGMODALFRAME,
|
||||
WS_EX_STATICEDGE, WS_EX_TOPMOST, WS_EX_WINDOWEDGE, WS_THICKFRAME,
|
||||
};
|
||||
|
||||
use crate::stremio_app::ipc::{RPCRequest, RPCResponse, RPCResponseData, RPCResponseDataTransport};
|
||||
|
|
@ -17,6 +18,7 @@ use crate::stremio_app::stremio_player::Player;
|
|||
use crate::stremio_app::stremio_wevbiew::WebView;
|
||||
use crate::stremio_app::systray::SystemTray;
|
||||
|
||||
// https://doc.qt.io/qt-5/qt.html#WindowState-enum
|
||||
bitflags! {
|
||||
struct WindowState: u8 {
|
||||
const MINIMIZED = 0x01;
|
||||
|
|
@ -37,28 +39,20 @@ pub struct WindowStyle {
|
|||
|
||||
impl WindowStyle {
|
||||
pub fn get_window_state(self, hwnd: *mut HWND__) -> u32 {
|
||||
let mut visibility: WindowState = WindowState::empty();
|
||||
visibility |= if 0 != unsafe { IsIconic(hwnd) } {
|
||||
WindowState::MINIMIZED
|
||||
} else {
|
||||
WindowState::empty()
|
||||
};
|
||||
visibility |= if 0 != unsafe { IsZoomed(hwnd) } {
|
||||
WindowState::MAXIMIZED
|
||||
} else {
|
||||
WindowState::empty()
|
||||
};
|
||||
visibility |= if hwnd == unsafe { GetActiveWindow() } {
|
||||
WindowState::ACTIVE
|
||||
} else {
|
||||
WindowState::empty()
|
||||
};
|
||||
visibility |= if self.full_screen {
|
||||
WindowState::FULL_SCREEN
|
||||
} else {
|
||||
WindowState::empty()
|
||||
};
|
||||
visibility.bits() as u32
|
||||
let mut state: WindowState = WindowState::empty();
|
||||
if 0 != unsafe { IsIconic(hwnd) } {
|
||||
state |= WindowState::MINIMIZED;
|
||||
}
|
||||
if 0 != unsafe { IsZoomed(hwnd) } {
|
||||
state |= WindowState::MAXIMIZED;
|
||||
}
|
||||
if hwnd == unsafe { GetForegroundWindow() } {
|
||||
state |= WindowState::ACTIVE
|
||||
}
|
||||
if self.full_screen {
|
||||
state |= WindowState::FULL_SCREEN;
|
||||
}
|
||||
state.bits() as u32
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,10 +65,10 @@ pub struct MainWindow {
|
|||
#[nwg_resource(source_embed: Some(&data.embed), source_embed_str: Some("MAINICON"))]
|
||||
pub window_icon: nwg::Icon,
|
||||
#[nwg_control(icon: Some(&data.window_icon), title: "Stremio", flags: "MAIN_WINDOW|VISIBLE")]
|
||||
#[nwg_events( OnWindowClose: [Self::on_quit(SELF, EVT_DATA)], OnInit: [Self::on_init], OnPaint: [Self::on_paint], OnMinMaxInfo: [Self::on_min_max(SELF, EVT_DATA)], OnWindowMaximize: [Self::on_maximize] )]
|
||||
#[nwg_events( OnWindowClose: [Self::on_quit(SELF, EVT_DATA)], OnInit: [Self::on_init], OnPaint: [Self::on_paint], OnMinMaxInfo: [Self::on_min_max(SELF, EVT_DATA)], OnWindowMaximize: [Self::transmit_window_state_change], OnWindowMinimize: [Self::transmit_window_state_change] )]
|
||||
pub window: nwg::Window,
|
||||
#[nwg_partial(parent: window)]
|
||||
#[nwg_events((tray_exit, OnMenuItemSelected): [Self::on_quit_notice], (tray_show_hide, OnMenuItemSelected): [Self::on_show_hide]) ]
|
||||
#[nwg_events((tray, MousePressLeftUp): [Self::on_show_hide], (tray_exit, OnMenuItemSelected): [Self::on_quit_notice], (tray_show_hide, OnMenuItemSelected): [Self::on_show_hide], (tray_topmost, OnMenuItemSelected): [Self::on_toggle_topmost]) ]
|
||||
pub tray: SystemTray,
|
||||
#[nwg_partial(parent: window)]
|
||||
pub webview: WebView,
|
||||
|
|
@ -101,6 +95,33 @@ impl MainWindow {
|
|||
const BG_COLOR: [u8; 3] = [27, 17, 38];
|
||||
const MIN_WIDTH: i32 = 1000;
|
||||
const MIN_HEIGHT: i32 = 600;
|
||||
fn transmit_window_full_screen_change(&self, prevent_close: bool) {
|
||||
let web_channel = self.webview.channel.borrow();
|
||||
let (web_tx, _) = web_channel
|
||||
.as_ref()
|
||||
.expect("Cannont obtain communication channel for the Web UI");
|
||||
let web_tx_app = web_tx.clone();
|
||||
let saved_style = self.saved_window_style.borrow();
|
||||
web_tx_app
|
||||
.send(RPCResponse::visibility_change(
|
||||
self.window.visible(),
|
||||
prevent_close as u32,
|
||||
saved_style.full_screen,
|
||||
))
|
||||
.ok();
|
||||
}
|
||||
fn transmit_window_state_change(&self) {
|
||||
if let Some(hwnd) = self.window.handle.hwnd() {
|
||||
let web_channel = self.webview.channel.borrow();
|
||||
let (web_tx, _) = web_channel
|
||||
.as_ref()
|
||||
.expect("Cannont obtain communication channel for the Web UI");
|
||||
let web_tx_app = web_tx.clone();
|
||||
let style = self.saved_window_style.borrow();
|
||||
let state = style.clone().get_window_state(hwnd);
|
||||
web_tx_app.send(RPCResponse::state_change(state)).ok();
|
||||
}
|
||||
}
|
||||
fn on_init(&self) {
|
||||
self.webview.endpoint.set(self.webui_url.clone()).ok();
|
||||
let small_side = cmp::min(nwg::Monitor::width(), nwg::Monitor::height()) * 70 / 100;
|
||||
|
|
@ -252,20 +273,35 @@ impl MainWindow {
|
|||
self.splash_frame.set_size(w, h);
|
||||
self.splash.set_size(s, s);
|
||||
self.splash.set_position(w as i32 / 2 - s as i32 / 2, 0);
|
||||
} else {
|
||||
self.transmit_window_state_change();
|
||||
}
|
||||
}
|
||||
fn on_toggle_fullscreen_notice(&self) {
|
||||
if let Some(hwnd) = self.window.handle.hwnd() {
|
||||
let mut saved_style = self.saved_window_style.borrow_mut();
|
||||
if saved_style.full_screen {
|
||||
let topmost = if saved_style.ex_style as u32 & WS_EX_TOPMOST == WS_EX_TOPMOST {
|
||||
HWND_TOPMOST
|
||||
} else {
|
||||
HWND_NOTOPMOST
|
||||
};
|
||||
unsafe {
|
||||
SetWindowLongA(hwnd, GWL_STYLE, saved_style.style);
|
||||
SetWindowLongA(hwnd, GWL_EXSTYLE, saved_style.ex_style);
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
topmost,
|
||||
saved_style.pos.0,
|
||||
saved_style.pos.1,
|
||||
saved_style.size.0 as i32,
|
||||
saved_style.size.1 as i32,
|
||||
SWP_FRAMECHANGED,
|
||||
);
|
||||
}
|
||||
self.window
|
||||
.set_position(saved_style.pos.0, saved_style.pos.1);
|
||||
self.window.set_size(saved_style.size.0, saved_style.size.1);
|
||||
saved_style.full_screen = false;
|
||||
self.tray.tray_topmost.set_enabled(true);
|
||||
self.tray.tray_topmost.set_checked(topmost == HWND_TOPMOST);
|
||||
} else {
|
||||
saved_style.pos = self.window.position();
|
||||
saved_style.size = self.window.size();
|
||||
|
|
@ -286,29 +322,21 @@ impl MainWindow {
|
|||
| WS_EX_CLIENTEDGE as i32
|
||||
| WS_EX_STATICEDGE as i32),
|
||||
);
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
HWND_NOTOPMOST,
|
||||
0,
|
||||
0,
|
||||
GetSystemMetrics(SM_CXSCREEN),
|
||||
GetSystemMetrics(SM_CYSCREEN),
|
||||
SWP_FRAMECHANGED,
|
||||
);
|
||||
}
|
||||
self.window.set_position(0, 0);
|
||||
self.window
|
||||
.set_size(unsafe { GetSystemMetrics(SM_CXSCREEN) as u32 }, unsafe {
|
||||
GetSystemMetrics(SM_CYSCREEN) as u32
|
||||
});
|
||||
saved_style.full_screen = true;
|
||||
self.tray.tray_topmost.set_enabled(false);
|
||||
}
|
||||
let visibility = saved_style.clone().get_window_state(hwnd);
|
||||
|
||||
let web_channel = self.webview.channel.borrow();
|
||||
let (web_tx, _) = web_channel
|
||||
.as_ref()
|
||||
.expect("Cannont obtain communication channel for the Web UI");
|
||||
let web_tx_app = web_tx.clone();
|
||||
web_tx_app
|
||||
.send(RPCResponse::visibility_change(
|
||||
true,
|
||||
visibility,
|
||||
saved_style.full_screen,
|
||||
))
|
||||
.ok();
|
||||
}
|
||||
self.transmit_window_full_screen_change(true);
|
||||
}
|
||||
fn on_quit_notice(&self) {
|
||||
nwg::stop_thread_dispatch();
|
||||
|
|
@ -316,40 +344,44 @@ impl MainWindow {
|
|||
fn on_hide_splash_notice(&self) {
|
||||
self.splash_frame.set_visible(false);
|
||||
}
|
||||
fn on_maximize(&self) {}
|
||||
fn on_toggle_topmost(&self) {
|
||||
if let Some(hwnd) = self.window.handle.hwnd() {
|
||||
let topmost = if unsafe { GetWindowLongA(hwnd, GWL_EXSTYLE) } as u32 & WS_EX_TOPMOST
|
||||
== WS_EX_TOPMOST
|
||||
{
|
||||
HWND_NOTOPMOST
|
||||
} else {
|
||||
HWND_TOPMOST
|
||||
};
|
||||
unsafe {
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
topmost,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED,
|
||||
);
|
||||
}
|
||||
let mut saved_style = self.saved_window_style.borrow_mut();
|
||||
saved_style.ex_style = unsafe { GetWindowLongA(hwnd, GWL_EXSTYLE) };
|
||||
self.tray
|
||||
.tray_topmost
|
||||
.set_checked((saved_style.ex_style as u32 & WS_EX_TOPMOST) == WS_EX_TOPMOST);
|
||||
}
|
||||
}
|
||||
fn on_show_hide(&self) {
|
||||
self.window.set_visible(!self.window.visible());
|
||||
self.tray.tray_show_hide.set_checked(self.window.visible());
|
||||
let web_channel = self.webview.channel.borrow();
|
||||
let (web_tx, _) = web_channel
|
||||
.as_ref()
|
||||
.expect("Cannont obtain communication channel for the Web UI");
|
||||
let web_tx_app = web_tx.clone();
|
||||
let saved_style = self.saved_window_style.borrow();
|
||||
web_tx_app
|
||||
.send(RPCResponse::visibility_change(
|
||||
self.window.visible(),
|
||||
1,
|
||||
saved_style.full_screen,
|
||||
))
|
||||
.ok();
|
||||
self.transmit_window_state_change();
|
||||
}
|
||||
fn on_quit(&self, data: &nwg::EventData) {
|
||||
if let Some(hwnd) = self.window.handle.hwnd() {
|
||||
if let nwg::EventData::OnWindowClose(data) = data {
|
||||
data.close(false);
|
||||
}
|
||||
self.window.set_visible(false);
|
||||
let web_channel = self.webview.channel.borrow();
|
||||
let (web_tx, _) = web_channel
|
||||
.as_ref()
|
||||
.expect("Cannont obtain communication channel for the Web UI");
|
||||
let web_tx_app = web_tx.clone();
|
||||
let saved_style = self.saved_window_style.borrow();
|
||||
let visibility = saved_style.clone().get_window_state(hwnd);
|
||||
web_tx_app
|
||||
.send(RPCResponse::visibility_change(false, visibility, false))
|
||||
.ok();
|
||||
if let nwg::EventData::OnWindowClose(data) = data {
|
||||
data.close(false);
|
||||
}
|
||||
self.window.set_visible(false);
|
||||
self.tray.tray_show_hide.set_checked(self.window.visible());
|
||||
self.transmit_window_full_screen_change(false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{self, json};
|
||||
use std::cell::RefCell;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{self, json};
|
||||
|
||||
pub type Channel = RefCell<Option<(mpsc::Sender<String>, Arc<Mutex<mpsc::Receiver<String>>>)>>;
|
||||
|
||||
|
|
@ -51,4 +51,16 @@ impl RPCResponse {
|
|||
};
|
||||
serde_json::to_string(&resp).expect("Cannot build response")
|
||||
}
|
||||
pub fn state_change(state: u32) -> String {
|
||||
let resp = RPCResponse {
|
||||
id: 1,
|
||||
object: "transport".to_string(),
|
||||
response_type: 1,
|
||||
args: Some(json!(["win-state-changed" ,{
|
||||
"state": state,
|
||||
}])),
|
||||
..Default::default()
|
||||
};
|
||||
serde_json::to_string(&resp).expect("Cannot build response")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ pub struct SystemTray {
|
|||
#[nwg_resource(source_embed: Some(&data.embed), source_embed_str: Some("MAINICON"))]
|
||||
pub tray_icon: nwg::Icon,
|
||||
#[nwg_control(icon: Some(&data.tray_icon), tip: Some("Stremio"))]
|
||||
#[nwg_events(MousePressLeftUp: [Self::show_menu], OnContextMenu: [Self::show_menu])]
|
||||
#[nwg_events(OnContextMenu: [Self::show_menu])]
|
||||
pub tray: nwg::TrayNotification,
|
||||
#[nwg_control(popup: true)]
|
||||
pub tray_menu: nwg::Menu,
|
||||
|
|
@ -25,4 +25,4 @@ impl SystemTray {
|
|||
let (x, y) = nwg::GlobalCursor::position();
|
||||
self.tray_menu.popup(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue