Better window events; pause on minimize; always on top

This commit is contained in:
Vladimir Borisov 2021-07-27 11:42:42 +03:00
parent 29817c614d
commit e4a262c6d9
3 changed files with 126 additions and 82 deletions

View file

@ -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);
}
}

View file

@ -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")
}
}

View file

@ -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);
}
}
}