use core::ptr;
use crate::{
LogicalRect, LogicalSize, PhysicalRect, PopupDesc, PopupInput, ShellError, WindowDesc, WindowId,
};
use super::ffi::{self, Point, style, value};
use super::geometry;
use super::proc;
use super::shell::Win32Shell;
use super::window::{Created, class_name};
const WHOLE_DESKTOP: PhysicalRect = PhysicalRect::new(i32::MIN, i32::MIN, u32::MAX, u32::MAX);
impl Win32Shell {
fn popup_rect_for(
&self,
popup: &PopupDesc,
size: LogicalSize,
) -> Result<PhysicalRect, ShellError> {
let parent = self.window(popup.parent)?;
let (parent_hwnd, scale) = (parent.raw(), parent.scale_factor);
let mut origin = Point::default();
unsafe { ffi::ClientToScreen(parent_hwnd, &raw mut origin) };
let anchor = PhysicalRect::new(
origin
.x
.saturating_add(geometry::pixels(popup.anchor.origin.x, scale)),
origin
.y
.saturating_add(geometry::pixels(popup.anchor.origin.y, scale)),
geometry::pixels(popup.anchor.size.width, scale)
.max(1)
.unsigned_abs(),
geometry::pixels(popup.anchor.size.height, scale)
.max(1)
.unsigned_abs(),
);
let area = self
.monitor_of(parent_hwnd)
.and_then(|id| self.monitors.iter().find(|monitor| monitor.id == id))
.or_else(|| self.monitors.iter().find(|monitor| monitor.is_primary))
.or_else(|| self.monitors.first())
.map_or(WHOLE_DESKTOP, |monitor| monitor.work_area);
Ok(crate::role::popup_rect(
anchor,
size.to_physical(scale),
popup.placement,
area,
))
}
pub(super) fn move_popup(
&mut self,
window: WindowId,
anchor: LogicalRect,
size: LogicalSize,
) -> Result<(), ShellError> {
let state = self.window(window)?;
let hwnd = state.raw();
let mut popup = state
.popup
.ok_or_else(|| crate::role::not_a_popup(window))?;
popup.anchor = anchor;
let rect = self.popup_rect_for(&popup, size)?;
let moved = 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,
)
};
if moved == 0 {
let error = unsafe { ffi::GetLastError() };
return Err(ShellError::Backend(format!(
"SetWindowPos failed with Win32 error {error}"
)));
}
let state = self.window_mut(window)?;
state.popup = Some(popup);
state.requested_size = size;
Ok(())
}
pub(super) fn create_popup(
&self,
desc: &WindowDesc<'_>,
popup: &PopupDesc,
) -> Result<Created, ShellError> {
let parent_hwnd = self.window(popup.parent)?.raw();
let title = ffi::wide(desc.title).ok_or_else(|| {
ShellError::InvalidDescriptor("title contains a NUL byte".to_string())
})?;
let rect = self.popup_rect_for(popup, desc.size)?;
let parent_ex_style =
unsafe { ffi::GetWindowLongPtrW(parent_hwnd, value::GWL_EX_STYLE) } as u32;
let styles = geometry::with_transparency(
geometry::popup_styles(popup.input, parent_ex_style & style::EX_TOPMOST != 0),
desc.transparent,
);
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),
parent_hwnd,
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 popup.input == PopupInput::Passthrough {
self.shared().set_passthrough(proc::key(hwnd));
}
Ok(Created {
hwnd,
dpi: Self::dpi_of(hwnd),
size: Self::client_size_of(hwnd),
})
}
}