use core::ptr;
use crate::{
LogicalSize, Margins, MonitorId, MonitorInfo, PanelAnchor, PanelDesc, PanelKeyboard,
PanelLayer, PhysicalRect, Reserve, ShellError, WindowDesc, WindowId,
};
use super::ffi::{self, AppBarData, Handle, Point, appbar, msg, value};
use super::geometry::{self, Insets};
use super::shell::Win32Shell;
use super::window::{Created, class_name};
#[derive(Clone, Copy, Debug, PartialEq)]
pub(super) struct Panel {
pub monitor: Option<MonitorId>,
pub layer: PanelLayer,
pub anchor: PanelAnchor,
pub reserve: Reserve,
pub margins: Margins,
pub keyboard: PanelKeyboard,
pub size: LogicalSize,
pub edge: Option<PanelAnchor>,
pub appbar: bool,
}
impl Panel {
pub(super) fn new(desc: &PanelDesc<'_>, size: LogicalSize) -> Self {
Self {
monitor: desc.monitor,
layer: desc.layer,
anchor: desc.anchor,
reserve: desc.reserve,
margins: desc.margins,
keyboard: desc.keyboard,
size,
edge: match desc.reserve {
Reserve::OwnExtent => desc.reserved_edge(),
Reserve::None | Reserve::IgnoreOthers => None,
},
appbar: false,
}
}
fn depth(&self, edge: PanelAnchor, scale: f64) -> i32 {
let (extent, margin) = match edge {
PanelAnchor::BOTTOM => (self.size.height, self.margins.bottom),
PanelAnchor::LEFT => (self.size.width, self.margins.left),
PanelAnchor::RIGHT => (self.size.width, self.margins.right),
_ => (self.size.height, self.margins.top),
};
geometry::pixels(extent, scale).saturating_add(geometry::pixels(margin, scale))
}
fn insets(&self, scale: f64) -> Insets {
Insets {
top: geometry::pixels(self.margins.top, scale),
right: geometry::pixels(self.margins.right, scale),
bottom: geometry::pixels(self.margins.bottom, scale),
left: geometry::pixels(self.margins.left, scale),
}
}
pub(super) fn rect_on(&self, monitor: &MonitorInfo) -> PhysicalRect {
let area = match self.reserve {
Reserve::IgnoreOthers => monitor.bounds,
Reserve::None | Reserve::OwnExtent => monitor.work_area,
};
let scale = monitor.scale_factor;
geometry::panel_rect(
area,
self.anchor,
self.size.to_physical(scale),
self.insets(scale),
)
}
}
impl Win32Shell {
fn panel_monitor(&self, panel: &Panel) -> Option<MonitorInfo> {
if let Some(id) = panel.monitor {
return self.monitors.iter().find(|m| m.id == id).cloned();
}
let mut cursor = Point::default();
let under = if unsafe { ffi::GetCursorPos(&raw mut cursor) } == 0 {
None
} else {
let handle =
unsafe { ffi::MonitorFromPoint(cursor, value::MONITOR_DEFAULT_TO_NEAREST) };
super::monitors::device_name(handle).and_then(|device| self.monitor_id_of(&device))
};
under
.and_then(|id| self.monitors.iter().find(|m| m.id == id))
.or_else(|| self.monitors.iter().find(|m| m.is_primary))
.or_else(|| self.monitors.first())
.cloned()
}
pub(super) fn create_panel(
&self,
desc: &WindowDesc<'_>,
panel: &mut Panel,
) -> Result<Created, ShellError> {
let title = ffi::wide(desc.title).ok_or_else(|| {
ShellError::InvalidDescriptor("title contains a NUL byte".to_string())
})?;
let monitor = self.panel_monitor(panel).ok_or_else(|| {
ShellError::WindowCreation("there is no monitor to place a panel on".to_string())
})?;
let rect = panel.rect_on(&monitor);
let styles = geometry::panel_styles(panel.layer, panel.keyboard);
let name = class_name();
let hwnd = unsafe {
ffi::CreateWindowExW(
styles.ex_style,
name.as_ptr(),
title.as_ptr(),
styles.style,
rect.x,
rect.y,
i32::try_from(rect.width).unwrap_or(i32::MAX),
i32::try_from(rect.height).unwrap_or(i32::MAX),
ptr::null_mut(),
ptr::null_mut(),
self.instance(),
self.shared_ptr(),
)
};
if hwnd.is_null() {
let error = unsafe { ffi::GetLastError() };
return Err(ShellError::WindowCreation(format!(
"CreateWindowExW failed with Win32 error {error}"
)));
}
if let Some(edge) = panel.edge {
panel.appbar = Self::register_appbar(hwnd);
if panel.appbar {
Self::place_appbar(hwnd, panel, &monitor, edge);
}
}
if matches!(panel.layer, PanelLayer::Bottom | PanelLayer::Background) {
unsafe {
ffi::SetWindowPos(
hwnd,
value::HWND_BOTTOM as Handle,
0,
0,
0,
0,
value::SWP_NO_MOVE | value::SWP_NO_SIZE | value::SWP_NO_ACTIVATE,
);
}
}
Ok(Created {
hwnd,
dpi: Self::dpi_of(hwnd),
size: Self::client_size_of(hwnd),
})
}
#[must_use]
pub(super) fn show_panel(hwnd: Handle, keyboard: PanelKeyboard) -> bool {
let command = match keyboard {
PanelKeyboard::Exclusive => value::SW_SHOW_NORMAL,
PanelKeyboard::None | PanelKeyboard::OnDemand => value::SW_SHOW_NO_ACTIVATE,
};
unsafe { ffi::ShowWindow(hwnd, command) };
if keyboard == PanelKeyboard::Exclusive {
let answered = unsafe { ffi::SetForegroundWindow(hwnd) };
let foreground = unsafe { ffi::GetForegroundWindow() };
return foreground_granted(hwnd, answered, foreground);
}
true
}
pub(super) fn place_panels(&mut self, removed: &[MonitorId]) {
let panels: Vec<WindowId> = self
.windows_iter()
.filter(|(_, window)| window.panel.is_some())
.map(|(id, _)| id)
.collect();
for window in panels {
let Ok(state) = self.window(window) else {
continue;
};
let Some(panel) = state.panel else { continue };
if panel
.monitor
.is_some_and(|monitor| removed.contains(&monitor))
{
self.close_requested(window);
continue;
}
if !panel.appbar {
self.place_panel(window);
}
}
}
pub(super) fn place_panel(&self, window: WindowId) {
let Ok(state) = self.window(window) else {
return;
};
let Some(panel) = state.panel else { return };
let hwnd = state.raw();
let monitor = match panel.monitor {
Some(_) => self.panel_monitor(&panel),
None => self
.monitor_of(hwnd)
.and_then(|id| self.monitors.iter().find(|m| m.id == id).cloned()),
};
let Some(monitor) = monitor else { return };
if let (true, Some(edge)) = (panel.appbar, panel.edge) {
Self::place_appbar(hwnd, &panel, &monitor, edge);
return;
}
let rect = panel.rect_on(&monitor);
unsafe {
ffi::SetWindowPos(
hwnd,
ptr::null_mut(),
rect.x,
rect.y,
i32::try_from(rect.width).unwrap_or(i32::MAX),
i32::try_from(rect.height).unwrap_or(i32::MAX),
value::SWP_NO_Z_ORDER | value::SWP_NO_ACTIVATE,
);
}
}
fn register_appbar(hwnd: Handle) -> bool {
let mut data = AppBarData::new(hwnd);
data.callback_message = msg::APP_BAR;
let registered = unsafe { ffi::SHAppBarMessage(appbar::NEW, &raw mut data) } != 0;
if !registered {
crcbl_core::log::warn!(
"win32: the shell refused a reserving panel as an appbar; it reserves nothing"
);
}
registered
}
fn place_appbar(hwnd: Handle, panel: &Panel, monitor: &MonitorInfo, edge: PanelAnchor) {
let scale = monitor.scale_factor;
let depth = panel.depth(edge, scale);
let mut data = AppBarData::new(hwnd);
data.edge = match edge {
PanelAnchor::BOTTOM => appbar::EDGE_BOTTOM,
PanelAnchor::LEFT => appbar::EDGE_LEFT,
PanelAnchor::RIGHT => appbar::EDGE_RIGHT,
_ => appbar::EDGE_TOP,
};
data.rc = geometry::with_depth(geometry::rect_from(monitor.bounds), edge, depth);
unsafe {
ffi::SHAppBarMessage(appbar::QUERY_POS, &raw mut data);
data.rc = geometry::with_depth(data.rc, edge, depth);
ffi::SHAppBarMessage(appbar::SET_POS, &raw mut data);
}
let rect = geometry::panel_rect(
geometry::desktop_rect(data.rc),
panel.anchor,
panel.size.to_physical(scale),
panel.insets(scale),
);
unsafe {
ffi::SetWindowPos(
hwnd,
ptr::null_mut(),
rect.x,
rect.y,
i32::try_from(rect.width).unwrap_or(i32::MAX),
i32::try_from(rect.height).unwrap_or(i32::MAX),
value::SWP_NO_Z_ORDER | value::SWP_NO_ACTIVATE,
);
}
}
pub(super) fn remove_appbar(hwnd: Handle) {
let mut data = AppBarData::new(hwnd);
unsafe { ffi::SHAppBarMessage(appbar::REMOVE, &raw mut data) };
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum AppBarNotice {
Moved,
FullScreenApp(bool),
}
pub(super) fn foreground_granted(hwnd: Handle, answered: ffi::Bool32, foreground: Handle) -> bool {
answered != 0 && ptr::eq(foreground, hwnd)
}
pub(super) const fn appbar_notice(notification: u32, param: isize) -> Option<AppBarNotice> {
match notification {
appbar::POS_CHANGED => Some(AppBarNotice::Moved),
appbar::FULL_SCREEN_APP => Some(AppBarNotice::FullScreenApp(param != 0)),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
const STATE_CHANGE: u32 = 0;
const WINDOW_ARRANGE: u32 = 3;
#[test]
fn an_appbar_notice_is_decoded_from_its_code_and_lparam() {
assert_eq!(
appbar_notice(appbar::POS_CHANGED, 0),
Some(AppBarNotice::Moved)
);
assert_eq!(
appbar_notice(appbar::FULL_SCREEN_APP, 1),
Some(AppBarNotice::FullScreenApp(true))
);
assert_eq!(
appbar_notice(appbar::FULL_SCREEN_APP, 0),
Some(AppBarNotice::FullScreenApp(false))
);
assert_eq!(
appbar_notice(appbar::FULL_SCREEN_APP, -1),
Some(AppBarNotice::FullScreenApp(true)),
"any nonzero BOOL is TRUE"
);
assert_eq!(appbar_notice(STATE_CHANGE, 1), None);
assert_eq!(appbar_notice(WINDOW_ARRANGE, 1), None);
}
#[test]
fn the_foreground_is_granted_only_when_the_panel_holds_it_afterwards() {
let mut cells = [0_u8; 2];
let panel: Handle = (&raw mut cells[0]).cast();
let other: Handle = (&raw mut cells[1]).cast();
let none: Handle = ptr::null_mut();
assert!(foreground_granted(panel, 1, panel));
assert!(
foreground_granted(panel, -1, panel),
"any nonzero BOOL is TRUE"
);
assert!(
!foreground_granted(panel, 1, other),
"success while another window kept the foreground"
);
assert!(
!foreground_granted(panel, 1, none),
"success with no foreground window at all"
);
assert!(!foreground_granted(panel, 0, other));
assert!(
!foreground_granted(panel, 0, panel),
"a refusal is taken at its word"
);
}
}