use std::cell::Cell;
use crate::{
DisplayBounds, EventRegistry, WindowHandle, WindowOptions, WindowState, WindowStateStore,
};
use webview2_com::Microsoft::Web::WebView2::Win32::{
ICoreWebView2, ICoreWebView2Controller, ICoreWebView2NavigationCompletedEventHandler,
ICoreWebView2NavigationStartingEventHandler, ICoreWebView2WebMessageReceivedEventHandler,
ICoreWebView2WebResourceRequestedEventHandler,
};
use windows::Win32::Foundation::{HWND, LPARAM, RECT};
use windows::Win32::Graphics::Gdi;
use windows::Win32::UI::WindowsAndMessaging::{self, WINDOW_LONG_PTR_INDEX};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum WindowSizeState {
Normal,
Maximized,
Minimized,
}
pub(super) struct FrameState {
pub(super) app_window: super::native_frame::WindowFrame,
pub(super) content: HWND,
pub(super) application_tasks: std::rc::Rc<super::tasks::ApplicationTasks>,
#[cfg(feature = "application-ipc")]
pub(super) ipc: Option<std::rc::Rc<super::ipc::WindowsIpc>>,
pub(super) controller: ICoreWebView2Controller,
#[cfg(feature = "native-picker")]
pub(super) picker: Option<crate::NativeServices>,
#[cfg(feature = "native-dialogs")]
pub(super) dialogs: Option<std::sync::Arc<crate::native_dialogs::DialogState>>,
#[cfg(feature = "native-clipboard")]
pub(super) clipboard: Option<std::sync::Arc<crate::clipboard::ClipboardState>>,
#[cfg(feature = "local-server")]
pub(super) local_controls: Option<std::rc::Rc<super::local_controls::LocalControls>>,
#[cfg(feature = "native-capture")]
pub(super) capture_registration: Option<super::capture::Registration>,
pub(super) _navigation_starting: ICoreWebView2NavigationStartingEventHandler,
#[cfg(feature = "local-server")]
pub(super) _local_navigation: Option<super::webview::LocalNavigationGuards>,
#[cfg(feature = "local-server")]
pub(super) _owner_close_registration: Option<crate::local_server::HostCloseRegistration>,
#[cfg(feature = "local-server")]
pub(super) local_lifetime: Option<crate::HostLifetime>,
#[cfg(feature = "local-server")]
pub(super) owner_close_cookie: Option<usize>,
pub(super) _navigation_completed: ICoreWebView2NavigationCompletedEventHandler,
pub(super) _web_message_received: Option<ICoreWebView2WebMessageReceivedEventHandler>,
pub(super) _web_resource_requested: Option<ICoreWebView2WebResourceRequestedEventHandler>,
pub(super) events: EventRegistry,
pub(super) window_handle: WindowHandle,
pub(super) options: WindowOptions,
pub(super) webview: ICoreWebView2,
pub(super) store: Option<WindowStateStore>,
pub(super) fullscreen: Cell<bool>,
pub(super) window_state: Cell<WindowSizeState>,
}
pub(super) fn initial_window_size_state(hwnd: HWND) -> WindowSizeState {
if unsafe { WindowsAndMessaging::IsIconic(hwnd) }.as_bool() {
WindowSizeState::Minimized
} else if unsafe { WindowsAndMessaging::IsZoomed(hwnd) }.as_bool() {
WindowSizeState::Maximized
} else {
WindowSizeState::Normal
}
}
pub(super) fn display_bounds() -> Vec<DisplayBounds> {
let mut displays: Vec<DisplayBounds> = Vec::with_capacity(4);
let pointer = LPARAM(std::ptr::from_mut(&mut displays) as isize);
let _ = unsafe { Gdi::EnumDisplayMonitors(None, None, Some(enumerate_display), pointer) };
displays
}
unsafe extern "system" fn enumerate_display(
_monitor: Gdi::HMONITOR,
_hdc: Gdi::HDC,
rect: *mut RECT,
data: LPARAM,
) -> windows::core::BOOL {
if rect.is_null() || data.0 == 0 {
return true.into();
}
unsafe {
let rect = *rect;
let displays = &mut *(data.0 as *mut Vec<DisplayBounds>);
let width = rect.right.saturating_sub(rect.left);
let height = rect.bottom.saturating_sub(rect.top);
if let (Ok(width), Ok(height)) = (u32::try_from(width), u32::try_from(height)) {
displays.push(DisplayBounds {
x: rect.left,
y: rect.top,
width,
height,
});
}
}
true.into()
}
pub(super) fn load_saved_state(store: Option<&WindowStateStore>) -> Option<WindowState> {
let store = store?;
match store.load_valid(&display_bounds()) {
Ok(state) => state,
Err(error) => {
eprintln!("WebUI: failed to restore window state: {error}");
None
}
}
}
pub(super) fn save_window_state(hwnd: HWND, state: &FrameState) {
let Some(store) = state.store.as_ref() else {
return;
};
if state.fullscreen.get() {
return;
}
if unsafe { WindowsAndMessaging::IsIconic(hwnd) }.as_bool() {
return;
}
let mut rect = RECT::default();
if unsafe { WindowsAndMessaging::GetWindowRect(hwnd, &mut rect) }.is_err() {
return;
}
let maximized = unsafe { WindowsAndMessaging::IsZoomed(hwnd) }.as_bool();
let (Ok(width), Ok(height)) = (
u32::try_from(rect.right.saturating_sub(rect.left)),
u32::try_from(rect.bottom.saturating_sub(rect.top)),
) else {
return;
};
if let Err(error) = store.save(&WindowState {
x: rect.left,
y: rect.top,
width,
height,
maximized,
}) {
eprintln!("WebUI: failed to persist window state: {error}");
}
}
pub(super) fn set_window_state(
hwnd: HWND,
state: Option<Box<FrameState>>,
) -> Option<Box<FrameState>> {
let value = state.map_or(0_isize, |state| Box::into_raw(state) as isize);
let previous = unsafe { set_window_long(hwnd, WindowsAndMessaging::GWLP_USERDATA, value) };
if previous == 0 {
None
} else {
Some(unsafe { Box::from_raw(previous as *mut FrameState) })
}
}
pub(super) fn with_window_state_result<T>(
hwnd: HWND,
f: impl FnOnce(&FrameState) -> T,
) -> Option<T> {
let data = unsafe { get_window_long(hwnd, WindowsAndMessaging::GWLP_USERDATA) };
if data == 0 {
return None;
}
Some(f(unsafe { &*(data as *const FrameState) }))
}
pub(super) fn with_window_state(hwnd: HWND, f: impl FnOnce(&FrameState)) {
let _ = with_window_state_result(hwnd, f);
}
pub(super) fn window_style_bits(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX) -> u32 {
let bits = unsafe { WindowsAndMessaging::GetWindowLongW(hwnd, index) };
bits.cast_unsigned()
}
#[allow(non_snake_case)]
#[cfg(target_pointer_width = "32")]
unsafe fn set_window_long(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX, value: isize) -> isize {
unsafe { WindowsAndMessaging::SetWindowLongW(hwnd, index, value as _) as _ }
}
#[allow(non_snake_case)]
#[cfg(target_pointer_width = "64")]
unsafe fn set_window_long(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX, value: isize) -> isize {
unsafe { WindowsAndMessaging::SetWindowLongPtrW(hwnd, index, value) }
}
#[allow(non_snake_case)]
#[cfg(target_pointer_width = "32")]
unsafe fn get_window_long(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX) -> isize {
unsafe { WindowsAndMessaging::GetWindowLongW(hwnd, index) as _ }
}
#[allow(non_snake_case)]
#[cfg(target_pointer_width = "64")]
unsafe fn get_window_long(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX) -> isize {
unsafe { WindowsAndMessaging::GetWindowLongPtrW(hwnd, index) }
}