#![allow(non_snake_case, clippy::cast_lossless)]
use std::cell::{Cell, RefCell};
use std::mem;
use std::panic::Location;
use std::ptr::{null, null_mut};
use std::rc::{Rc, Weak};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use scopeguard::defer;
use tracing::{debug, error, warn};
use winapi::ctypes::{c_int, c_void};
use winapi::shared::dxgi::*;
use winapi::shared::dxgi1_2::*;
use winapi::shared::dxgiformat::*;
use winapi::shared::dxgitype::*;
use winapi::shared::minwindef::*;
use winapi::shared::windef::*;
use winapi::shared::winerror::*;
use winapi::um::dcomp::{IDCompositionDevice, IDCompositionTarget, IDCompositionVisual};
use winapi::um::dwmapi::{DwmExtendFrameIntoClientArea, DwmSetWindowAttribute};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::shellscalingapi::MDT_EFFECTIVE_DPI;
use winapi::um::unknwnbase::*;
use winapi::um::uxtheme::*;
use winapi::um::wingdi::*;
use winapi::um::winnt::*;
use winapi::um::winreg::{RegGetValueW, HKEY_CURRENT_USER, RRF_RT_REG_DWORD};
use winapi::um::winuser::*;
use winapi::Interface;
use wio::com::ComPtr;
#[cfg(feature = "raw-win-handle")]
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, Win32WindowHandle};
use piet_common::d2d::{D2DFactory, DeviceContext};
use piet_common::dwrite::DwriteFactory;
use crate::kurbo::{Insets, Point, Rect, Size, Vec2};
use crate::piet::{Piet, PietText, RenderContext};
use super::accels::register_accel;
use super::application::Application;
use super::dcomp::D3D11Device;
use super::dialog::get_file_dialog_path;
use super::error::Error;
use super::keyboard::{self, KeyboardState};
use super::menu::Menu;
use super::paint;
use super::timers::TimerSlots;
use super::util::{self, as_result, FromWide, ToWide, OPTIONAL_FUNCTIONS};
use crate::common_util::IdleCallback;
use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo};
use crate::error::Error as ShellError;
use crate::keyboard::{KbKey, KeyState};
use crate::mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent};
use crate::region::Region;
use crate::scale::{Scalable, Scale, ScaledArea};
use crate::text::{simulate_input, Event};
use crate::window;
use crate::window::{
FileDialogToken, IdleToken, TextFieldToken, TimerToken, WinHandler, WindowLevel,
};
pub(crate) const SCALE_TARGET_DPI: f64 = 96.0;
pub(crate) struct WindowBuilder {
app: Application,
handler: Option<Box<dyn WinHandler>>,
title: String,
menu: Option<Menu>,
present_strategy: PresentStrategy,
resizable: bool,
show_titlebar: bool,
size: Option<Size>,
transparent: bool,
min_size: Option<Size>,
position: Option<Point>,
level: Option<WindowLevel>,
state: window::WindowState,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[allow(dead_code)]
pub enum PresentStrategy {
Sequential,
Flip,
FlipRedirect,
}
enum DeferredOp {
SaveAs(FileDialogOptions, FileDialogToken),
Open(FileDialogOptions, FileDialogToken),
ContextMenu(Menu, Point),
ShowTitlebar(bool),
SetPosition(Point),
SetSize(Size),
SetResizable(bool),
SetWindowState(window::WindowState),
ReleaseMouseCapture,
}
#[derive(Clone, Debug)]
pub struct WindowHandle {
text: PietText,
state: Weak<WindowState>,
}
impl PartialEq for WindowHandle {
fn eq(&self, other: &Self) -> bool {
match (self.state.upgrade(), other.state.upgrade()) {
(None, None) => true,
(Some(s), Some(o)) => std::rc::Rc::ptr_eq(&s, &o),
(_, _) => false,
}
}
}
impl Eq for WindowHandle {}
#[cfg(feature = "raw-win-handle")]
unsafe impl HasRawWindowHandle for WindowHandle {
fn raw_window_handle(&self) -> RawWindowHandle {
if let Some(hwnd) = self.get_hwnd() {
let mut handle = Win32WindowHandle::empty();
handle.hwnd = hwnd as *mut core::ffi::c_void;
handle.hinstance = unsafe {
winapi::um::libloaderapi::GetModuleHandleW(0 as winapi::um::winnt::LPCWSTR)
as *mut core::ffi::c_void
};
RawWindowHandle::Win32(handle)
} else {
error!("Cannot retrieved HWND for window.");
RawWindowHandle::Win32(Win32WindowHandle::empty())
}
}
}
#[derive(Clone)]
pub struct IdleHandle {
pub(crate) hwnd: HWND,
queue: Arc<Mutex<Vec<IdleKind>>>,
}
enum IdleKind {
Callback(Box<dyn IdleCallback>),
Token(IdleToken),
}
struct WindowState {
hwnd: Cell<HWND>,
scale: Cell<Scale>,
area: Cell<ScaledArea>,
invalid: RefCell<Region>,
has_menu: Cell<bool>,
wndproc: Box<dyn WndProc>,
idle_queue: Arc<Mutex<Vec<IdleKind>>>,
timers: Arc<Mutex<TimerSlots>>,
deferred_queue: RefCell<Vec<DeferredOp>>,
has_titlebar: Cell<bool>,
is_transparent: Cell<bool>,
is_resizable: Cell<bool>,
handle_titlebar: Cell<bool>,
active_text_input: Cell<Option<TextFieldToken>>,
is_focusable: bool,
window_level: WindowLevel,
}
impl std::fmt::Debug for WindowState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str("WindowState{\n")?;
f.write_str(format!("{:p}", self.hwnd.get()).as_str())?;
f.write_str("}")?;
Ok(())
}
}
trait WndProc {
fn connect(&self, handle: &WindowHandle, state: WndState);
fn cleanup(&self, hwnd: HWND);
fn window_proc(&self, hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM)
-> Option<LRESULT>;
}
struct MyWndProc {
app: Application,
handle: RefCell<WindowHandle>,
d2d_factory: D2DFactory,
text: PietText,
state: RefCell<Option<WndState>>,
present_strategy: PresentStrategy,
}
struct WndState {
handler: Box<dyn WinHandler>,
render_target: Option<DeviceContext>,
dxgi_state: Option<DxgiState>,
min_size: Option<Size>,
keyboard_state: KeyboardState,
captured_mouse_buttons: MouseButtons,
transparent: bool,
has_mouse_focus: bool,
last_click_time: Instant,
last_click_pos: (i32, i32),
click_count: u8,
}
struct DxgiState {
swap_chain: *mut IDXGISwapChain1,
#[allow(dead_code)]
composition_device: Option<ComPtr<IDCompositionDevice>>,
#[allow(dead_code)]
composition_target: Option<ComPtr<IDCompositionTarget>>,
#[allow(dead_code)]
composition_visual: Option<ComPtr<IDCompositionVisual>>,
}
#[derive(Clone, PartialEq, Eq)]
pub struct CustomCursor(Arc<HCursor>);
#[derive(PartialEq, Eq)]
struct HCursor(HCURSOR);
impl Drop for HCursor {
fn drop(&mut self) {
unsafe {
DestroyIcon(self.0);
}
}
}
const DS_RUN_IDLE: UINT = WM_USER;
pub(crate) const DS_REQUEST_DESTROY: UINT = WM_USER + 1;
impl Default for PresentStrategy {
fn default() -> PresentStrategy {
PresentStrategy::Sequential
}
}
fn get_buttons(wparam: WPARAM) -> MouseButtons {
let mut buttons = MouseButtons::new();
if wparam & MK_LBUTTON != 0 {
buttons.insert(MouseButton::Left);
}
if wparam & MK_RBUTTON != 0 {
buttons.insert(MouseButton::Right);
}
if wparam & MK_MBUTTON != 0 {
buttons.insert(MouseButton::Middle);
}
if wparam & MK_XBUTTON1 != 0 {
buttons.insert(MouseButton::X1);
}
if wparam & MK_XBUTTON2 != 0 {
buttons.insert(MouseButton::X2);
}
buttons
}
fn is_point_in_client_rect(hwnd: HWND, x: i32, y: i32) -> bool {
unsafe {
let mut client_rect = mem::MaybeUninit::uninit();
if GetClientRect(hwnd, client_rect.as_mut_ptr()) == FALSE {
warn!(
"failed to get client rect: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
return false;
}
let client_rect = client_rect.assume_init();
let mouse_point = POINT { x, y };
PtInRect(&client_rect, mouse_point) != FALSE
}
}
fn set_style(hwnd: HWND, resizable: bool, titlebar: bool) {
unsafe {
let mut style = GetWindowLongPtrW(hwnd, GWL_STYLE) as u32;
if style == 0 {
warn!(
"failed to get window style: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
return;
}
if !resizable {
style &= !(WS_THICKFRAME | WS_MAXIMIZEBOX);
} else {
style |= WS_THICKFRAME | WS_MAXIMIZEBOX;
}
if !titlebar {
style &= !(WS_SYSMENU | WS_OVERLAPPED);
} else {
style |= WS_MINIMIZEBOX | WS_SYSMENU | WS_OVERLAPPED;
}
if SetWindowLongPtrW(hwnd, GWL_STYLE, style as _) == 0 {
warn!(
"failed to set the window style: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
}
if SetWindowPos(
hwnd,
HWND_TOPMOST,
0,
0,
0,
0,
SWP_SHOWWINDOW
| SWP_NOMOVE
| SWP_NOZORDER
| SWP_FRAMECHANGED
| SWP_NOSIZE
| SWP_NOOWNERZORDER
| SWP_NOACTIVATE,
) == 0
{
warn!(
"failed to update window style: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
};
}
}
impl WndState {
fn rebuild_render_target(&mut self, d2d: &D2DFactory, scale: Scale) -> Result<(), Error> {
unsafe {
let swap_chain = self.dxgi_state.as_ref().unwrap().swap_chain;
match paint::create_render_target_dxgi(d2d, swap_chain, scale, self.transparent) {
Ok(rt) => {
self.render_target =
Some(rt.as_device_context().expect("TODO remove this expect"));
Ok(())
}
Err(e) => Err(e),
}
}
}
fn render(&mut self, d2d: &D2DFactory, text: &PietText, invalid: &Region) {
let rt = self.render_target.as_mut().unwrap();
rt.begin_draw();
{
let mut piet_ctx = Piet::new(d2d, text.clone(), rt);
piet_ctx.clip(invalid.to_bez_path());
self.handler.paint(&mut piet_ctx, invalid);
if let Err(e) = piet_ctx.finish() {
error!("piet error on render: {:?}", e);
}
}
let res = rt.end_draw();
if let Err(e) = res {
error!("EndDraw error: {:?}", e);
}
}
fn enter_mouse_capture(&mut self, hwnd: HWND, button: MouseButton) {
if self.captured_mouse_buttons.is_empty() {
unsafe {
SetCapture(hwnd);
}
}
self.captured_mouse_buttons.insert(button);
}
fn exit_mouse_capture(&mut self, button: MouseButton) -> bool {
self.captured_mouse_buttons.remove(button);
self.captured_mouse_buttons.is_empty()
}
}
impl MyWndProc {
fn with_window_state<F, R>(&self, f: F) -> R
where
F: FnOnce(Rc<WindowState>) -> R,
{
f(self
.handle
.borrow()
.state
.upgrade()
.unwrap()) }
#[track_caller]
fn with_wnd_state<F, R>(&self, f: F) -> Option<R>
where
F: FnOnce(&mut WndState) -> R,
{
let ret = if let Ok(mut s) = self.state.try_borrow_mut() {
(*s).as_mut().map(f)
} else {
error!("failed to borrow WndState at {}", Location::caller());
None
};
if ret.is_some() {
self.handle_deferred_queue();
}
ret
}
fn scale(&self) -> Scale {
self.with_window_state(|state| state.scale.get())
}
fn set_scale(&self, scale: Scale) {
self.with_window_state(move |state| state.scale.set(scale))
}
fn take_invalid(&self) -> Region {
self.with_window_state(|state| {
std::mem::replace(&mut *state.invalid.borrow_mut(), Region::EMPTY)
})
}
fn invalidate_rect(&self, rect: Rect) {
self.with_window_state(|state| state.invalid.borrow_mut().add_rect(rect));
}
fn set_area(&self, area: ScaledArea) {
self.with_window_state(move |state| state.area.set(area))
}
fn has_menu(&self) -> bool {
self.with_window_state(|state| state.has_menu.get())
}
fn has_titlebar(&self) -> bool {
self.with_window_state(|state| state.has_titlebar.get())
}
fn resizable(&self) -> bool {
self.with_window_state(|state| state.is_resizable.get())
}
fn is_transparent(&self) -> bool {
self.with_window_state(|state| state.is_transparent.get())
}
fn handle_deferred_queue(&self) {
let q = self.with_window_state(move |state| state.deferred_queue.replace(Vec::new()));
for op in q {
self.handle_deferred(op);
}
}
fn handle_deferred(&self, op: DeferredOp) {
if let Some(hwnd) = self.handle.borrow().get_hwnd() {
match op {
DeferredOp::SetSize(size_dp) => unsafe {
let area = ScaledArea::from_dp(size_dp, self.scale());
let size_px = area.size_px();
if SetWindowPos(
hwnd,
HWND_TOPMOST,
0,
0,
size_px.width as i32,
size_px.height as i32,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE,
) == 0
{
warn!(
"failed to resize window: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
};
},
DeferredOp::SetPosition(pos_dp) => unsafe {
let pos_px = pos_dp.to_px(self.scale());
if SetWindowPos(
hwnd,
HWND_TOPMOST,
pos_px.x.round() as i32,
pos_px.y.round() as i32,
0,
0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE,
) == 0
{
warn!(
"failed to move window: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
};
},
DeferredOp::ShowTitlebar(titlebar) => {
self.with_window_state(|s| s.has_titlebar.set(titlebar));
set_style(hwnd, self.resizable(), titlebar);
}
DeferredOp::SetResizable(resizable) => {
self.with_window_state(|s| s.is_resizable.set(resizable));
set_style(hwnd, resizable, self.has_titlebar());
}
DeferredOp::SetWindowState(val) => {
let show = if self.handle.borrow().is_focusable() {
match val {
window::WindowState::Maximized => SW_MAXIMIZE,
window::WindowState::Minimized => SW_MINIMIZE,
window::WindowState::Restored => SW_RESTORE,
}
} else {
SW_SHOWNOACTIVATE
};
unsafe {
ShowWindow(hwnd, show);
}
}
DeferredOp::SaveAs(options, token) => {
let info = unsafe {
get_file_dialog_path(hwnd, FileDialogType::Save, options)
.ok()
.map(|os_str| FileInfo {
path: os_str.into(),
format: None,
})
};
self.with_wnd_state(|s| s.handler.save_as(token, info));
}
DeferredOp::Open(options, token) => {
let info = unsafe {
get_file_dialog_path(hwnd, FileDialogType::Open, options)
.ok()
.map(|s| FileInfo {
path: s.into(),
format: None,
})
};
self.with_wnd_state(|s| s.handler.open_file(token, info));
}
DeferredOp::ContextMenu(menu, pos) => {
let hmenu = menu.into_hmenu();
let pos = pos.to_px(self.scale()).round();
unsafe {
let mut point = POINT {
x: pos.x as i32,
y: pos.y as i32,
};
ClientToScreen(hwnd, &mut point);
if TrackPopupMenu(hmenu, TPM_LEFTALIGN, point.x, point.y, 0, hwnd, null())
== FALSE
{
warn!("failed to track popup menu");
}
}
}
DeferredOp::ReleaseMouseCapture => unsafe {
if ReleaseCapture() == FALSE {
let result = HRESULT_FROM_WIN32(GetLastError());
if result != 0 {
warn!("failed to release mouse capture: {}", Error::Hr(result));
}
}
},
}
} else {
warn!("Could not get HWND");
}
}
fn get_system_metric(&self, metric: c_int) -> i32 {
unsafe {
if let Some(func) = OPTIONAL_FUNCTIONS.GetSystemMetricsForDpi {
let dpi = self.scale().x() * SCALE_TARGET_DPI;
func(metric, dpi as u32)
}
else {
GetSystemMetrics(metric)
}
}
}
}
impl WndProc for MyWndProc {
fn connect(&self, handle: &WindowHandle, state: WndState) {
*self.handle.borrow_mut() = handle.clone();
*self.state.borrow_mut() = Some(state);
self.state
.borrow_mut()
.as_mut()
.unwrap()
.handler
.scale(self.scale());
}
fn cleanup(&self, hwnd: HWND) {
self.app.remove_window(hwnd);
}
#[allow(clippy::cognitive_complexity)]
fn window_proc(
&self,
hwnd: HWND,
msg: UINT,
wparam: WPARAM,
lparam: LPARAM,
) -> Option<LRESULT> {
match msg {
WM_CREATE => {
let scale_factor = if let Some(func) = OPTIONAL_FUNCTIONS.GetDpiForWindow {
unsafe { func(hwnd) as f64 / SCALE_TARGET_DPI }
}
else if let Some(func) = OPTIONAL_FUNCTIONS.GetDpiForMonitor {
unsafe {
let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
let mut dpiX = 0;
let mut dpiY = 0;
func(monitor, MDT_EFFECTIVE_DPI, &mut dpiX, &mut dpiY);
dpiX as f64 / SCALE_TARGET_DPI
}
} else {
1.0
};
let scale = Scale::new(scale_factor, scale_factor);
self.set_scale(scale);
if let Some(state) = self.handle.borrow().state.upgrade() {
state.hwnd.set(hwnd);
}
if let Some(state) = self.state.borrow_mut().as_mut() {
let dxgi_state = unsafe {
create_dxgi_state(self.present_strategy, hwnd, self.is_transparent())
.unwrap_or_else(|e| {
error!("Creating swapchain failed: {:?}", e);
None
})
};
state.dxgi_state = dxgi_state;
let handle = self.handle.borrow().to_owned();
state.handler.connect(&handle.into());
if let Err(e) = state.rebuild_render_target(&self.d2d_factory, scale) {
error!("error building render target: {}", e);
}
}
Some(0)
}
WM_ACTIVATE => {
if LOWORD(wparam as u32) as u32 != 0 {
unsafe {
if !self.has_titlebar() && !self.is_transparent() {
let margins = MARGINS {
cxLeftWidth: 0,
cxRightWidth: 0,
cyTopHeight: 1,
cyBottomHeight: 0,
};
DwmExtendFrameIntoClientArea(hwnd, &margins);
}
if SetWindowPos(
hwnd,
HWND_TOPMOST,
0,
0,
0,
0,
SWP_SHOWWINDOW
| SWP_NOMOVE
| SWP_NOZORDER
| SWP_FRAMECHANGED
| SWP_NOSIZE
| SWP_NOOWNERZORDER
| SWP_NOACTIVATE,
) == 0
{
warn!(
"SetWindowPos failed with error: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
};
}
}
Some(0)
}
WM_ERASEBKGND => Some(0),
WM_SETFOCUS => {
self.with_wnd_state(|s| s.handler.got_focus());
Some(0)
}
WM_KILLFOCUS => {
self.with_wnd_state(|s| s.handler.lost_focus());
Some(0)
}
WM_PAINT => unsafe {
self.with_wnd_state(|s| {
s.handler.prepare_paint();
let mut rect: RECT = mem::zeroed();
GetUpdateRect(hwnd, &mut rect, FALSE);
ValidateRect(hwnd, null_mut());
let rect_dp = util::recti_to_rect(rect).to_dp(self.scale());
if rect_dp.area() != 0.0 {
self.invalidate_rect(rect_dp);
}
let invalid = self.take_invalid();
if !invalid.rects().is_empty() {
s.handler.rebuild_resources();
s.render(&self.d2d_factory, &self.text, &invalid);
if let Some(ref mut ds) = s.dxgi_state {
let mut dirty_rects = util::region_to_rectis(&invalid, self.scale());
let params = DXGI_PRESENT_PARAMETERS {
DirtyRectsCount: dirty_rects.len() as u32,
pDirtyRects: dirty_rects.as_mut_ptr(),
pScrollRect: null_mut(),
pScrollOffset: null_mut(),
};
(*ds.swap_chain).Present1(1, 0, ¶ms);
}
}
});
Some(0)
},
WM_DPICHANGED => unsafe {
let x = HIWORD(wparam as u32) as f64 / SCALE_TARGET_DPI;
let y = LOWORD(wparam as u32) as f64 / SCALE_TARGET_DPI;
let scale = Scale::new(x, y);
self.set_scale(scale);
let rect: *mut RECT = lparam as *mut RECT;
SetWindowPos(
hwnd,
HWND_TOPMOST,
(*rect).left,
(*rect).top,
(*rect).right - (*rect).left,
(*rect).bottom - (*rect).top,
SWP_NOZORDER
| SWP_FRAMECHANGED
| SWP_DRAWFRAME
| SWP_NOOWNERZORDER
| SWP_NOACTIVATE,
);
Some(0)
},
WM_NCCALCSIZE => unsafe {
if wparam != 0 && !self.has_titlebar() {
if let Ok(handle) = self.handle.try_borrow() {
if handle.get_window_state() == window::WindowState::Maximized {
let s: *mut NCCALCSIZE_PARAMS = lparam as *mut NCCALCSIZE_PARAMS;
if let Some(mut s) = s.as_mut() {
let border = self.get_system_metric(SM_CXPADDEDBORDER);
let frame = self.get_system_metric(SM_CYSIZEFRAME);
s.rgrc[0].top += border + frame;
s.rgrc[0].right -= border + frame;
s.rgrc[0].left += border + frame;
s.rgrc[0].bottom -= border + frame;
}
}
}
return Some(0);
}
None
},
WM_NCHITTEST => unsafe {
let mut hit = DefWindowProcW(hwnd, msg, wparam, lparam);
if !self.has_titlebar() && self.resizable() {
if let Ok(handle) = self.handle.try_borrow() {
if handle.get_window_state() != window::WindowState::Maximized {
let mut rect = RECT {
left: 0,
top: 0,
right: 0,
bottom: 0,
};
if GetWindowRect(hwnd, &mut rect) == 0 {
warn!(
"failed to get window rect: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
};
let y_cord = HIWORD(lparam as u32) as i16 as i32;
let x_cord = LOWORD(lparam as u32) as i16 as i32;
let HIT_SIZE = self.get_system_metric(SM_CYSIZEFRAME)
+ self.get_system_metric(SM_CXPADDEDBORDER);
if y_cord - rect.top <= HIT_SIZE {
if x_cord - rect.left <= HIT_SIZE {
hit = HTTOPLEFT;
} else if rect.right - x_cord <= HIT_SIZE {
hit = HTTOPRIGHT;
} else {
hit = HTTOP;
}
} else if rect.bottom - y_cord <= HIT_SIZE {
if x_cord - rect.left <= HIT_SIZE {
hit = HTBOTTOMLEFT;
} else if rect.right - x_cord <= HIT_SIZE {
hit = HTBOTTOMRIGHT;
} else {
hit = HTBOTTOM;
}
} else if x_cord - rect.left <= HIT_SIZE {
hit = HTLEFT;
} else if rect.right - x_cord <= HIT_SIZE {
hit = HTRIGHT;
}
}
}
}
let mouseDown = GetAsyncKeyState(VK_LBUTTON) < 0;
if self.with_window_state(|state| state.handle_titlebar.get()) && !mouseDown {
self.with_window_state(move |state| state.handle_titlebar.set(false));
};
if self.with_window_state(|state| state.handle_titlebar.get()) && hit == HTCLIENT {
hit = HTCAPTION;
}
Some(hit)
},
WM_SIZE => unsafe {
let width = LOWORD(lparam as u32) as u32;
let height = HIWORD(lparam as u32) as u32;
if width == 0 || height == 0 {
return Some(0);
}
self.with_wnd_state(|s| {
let scale = self.scale();
let area = ScaledArea::from_px((width as f64, height as f64), scale);
let size_dp = area.size_dp();
self.set_area(area);
s.handler.size(size_dp);
let res;
{
s.render_target = None;
res = (*s.dxgi_state.as_mut().unwrap().swap_chain).ResizeBuffers(
0,
width,
height,
DXGI_FORMAT_UNKNOWN,
0,
);
}
if SUCCEEDED(res) {
if let Err(e) = s.rebuild_render_target(&self.d2d_factory, scale) {
error!("error building render target: {}", e);
}
s.render(&self.d2d_factory, &self.text, &size_dp.to_rect().into());
let present_after = match self.present_strategy {
PresentStrategy::Sequential => 1,
_ => 0,
};
if let Some(ref mut dxgi_state) = s.dxgi_state {
(*dxgi_state.swap_chain).Present(present_after, 0);
}
ValidateRect(hwnd, null_mut());
} else {
error!("ResizeBuffers failed: 0x{:x}", res);
}
})
.map(|_| 0)
},
WM_COMMAND => {
self.with_wnd_state(|s| s.handler.command(LOWORD(wparam as u32) as u32));
Some(0)
}
WM_CHAR | WM_SYSCHAR | WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP
| WM_INPUTLANGCHANGE => {
unsafe {
let is_last = keyboard::is_last_message(hwnd, msg, lparam);
let handled = self.with_wnd_state(|s| {
if let Some(event) = s
.keyboard_state
.process_message(msg, wparam, lparam, is_last)
{
let handle_menu = !self.has_menu()
&& (event.key == KbKey::Alt || event.key == KbKey::F10);
match event.state {
KeyState::Down => {
let keydown_handled = self.with_window_state(|window_state| {
simulate_input(
&mut *s.handler,
window_state.active_text_input.get(),
event,
)
});
if keydown_handled || handle_menu {
return true;
}
}
KeyState::Up => {
s.handler.key_up(event);
if handle_menu {
return true;
}
}
}
}
false
});
if handled == Some(true) {
Some(0)
} else {
None
}
}
}
WM_MOUSEWHEEL | WM_MOUSEHWHEEL => {
let handled = self.with_wnd_state(|s| {
let system_delta = HIWORD(wparam as u32) as i16 as f64;
let down_state = LOWORD(wparam as u32) as usize;
let mods = s.keyboard_state.get_modifiers();
let is_shift = mods.shift();
let wheel_delta = match msg {
WM_MOUSEWHEEL if is_shift => Vec2::new(-system_delta, 0.),
WM_MOUSEWHEEL => Vec2::new(0., -system_delta),
WM_MOUSEHWHEEL => Vec2::new(system_delta, 0.),
_ => unreachable!(),
};
let mut p = POINT {
x: LOWORD(lparam as u32) as i16 as i32,
y: HIWORD(lparam as u32) as i16 as i32,
};
unsafe {
if ScreenToClient(hwnd, &mut p) == FALSE {
warn!(
"ScreenToClient failed: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
return false;
}
}
let pos = Point::new(p.x as f64, p.y as f64).to_dp(self.scale());
let buttons = get_buttons(down_state);
let event = MouseEvent {
pos,
buttons,
mods,
count: 0,
focus: false,
button: MouseButton::None,
wheel_delta,
};
s.handler.wheel(&event);
true
});
if handled == Some(false) {
None
} else {
Some(0)
}
}
WM_MOUSEMOVE => {
self.with_wnd_state(|s| {
let x = LOWORD(lparam as u32) as i16 as i32;
let y = HIWORD(lparam as u32) as i16 as i32;
if !s.has_mouse_focus && is_point_in_client_rect(hwnd, x, y) {
let mut desc = TRACKMOUSEEVENT {
cbSize: mem::size_of::<TRACKMOUSEEVENT>() as DWORD,
dwFlags: TME_LEAVE,
hwndTrack: hwnd,
dwHoverTime: HOVER_DEFAULT,
};
unsafe {
if TrackMouseEvent(&mut desc) != FALSE {
s.has_mouse_focus = true;
} else {
warn!(
"failed to TrackMouseEvent: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
}
}
}
let pos = Point::new(x as f64, y as f64).to_dp(self.scale());
let mods = s.keyboard_state.get_modifiers();
let buttons = get_buttons(wparam);
let event = MouseEvent {
pos,
buttons,
mods,
count: 0,
focus: false,
button: MouseButton::None,
wheel_delta: Vec2::ZERO,
};
s.handler.mouse_move(&event);
});
Some(0)
}
WM_MOUSELEAVE => {
self.with_wnd_state(|s| {
s.has_mouse_focus = false;
s.handler.mouse_leave();
});
Some(0)
}
WM_LBUTTONDBLCLK | WM_LBUTTONDOWN | WM_LBUTTONUP | WM_RBUTTONDBLCLK
| WM_RBUTTONDOWN | WM_RBUTTONUP | WM_MBUTTONDBLCLK | WM_MBUTTONDOWN | WM_MBUTTONUP
| WM_XBUTTONDBLCLK | WM_XBUTTONDOWN | WM_XBUTTONUP => {
if let Some(button) = match msg {
WM_LBUTTONDBLCLK | WM_LBUTTONDOWN | WM_LBUTTONUP => Some(MouseButton::Left),
WM_RBUTTONDBLCLK | WM_RBUTTONDOWN | WM_RBUTTONUP => Some(MouseButton::Right),
WM_MBUTTONDBLCLK | WM_MBUTTONDOWN | WM_MBUTTONUP => Some(MouseButton::Middle),
WM_XBUTTONDBLCLK | WM_XBUTTONDOWN | WM_XBUTTONUP => {
match HIWORD(wparam as u32) {
XBUTTON1 => Some(MouseButton::X1),
XBUTTON2 => Some(MouseButton::X2),
w => {
warn!("Received an unknown XBUTTON event ({})", w);
None
}
}
}
_ => unreachable!(),
} {
self.with_wnd_state(|s| {
let down = matches!(
msg,
WM_LBUTTONDOWN
| WM_MBUTTONDOWN
| WM_RBUTTONDOWN
| WM_XBUTTONDOWN
| WM_LBUTTONDBLCLK
| WM_MBUTTONDBLCLK
| WM_RBUTTONDBLCLK
| WM_XBUTTONDBLCLK
);
let x = LOWORD(lparam as u32) as i16 as i32;
let y = HIWORD(lparam as u32) as i16 as i32;
let pos = Point::new(x as f64, y as f64).to_dp(self.scale());
let mods = s.keyboard_state.get_modifiers();
let buttons = get_buttons(wparam);
let dct = unsafe { GetDoubleClickTime() };
let count = if down {
let this_click = Instant::now();
let thresh_x = self.get_system_metric(SM_CXDOUBLECLK);
let thresh_y = self.get_system_metric(SM_CYDOUBLECLK);
let in_box = (x - s.last_click_pos.0).abs() <= thresh_x / 2
&& (y - s.last_click_pos.1).abs() <= thresh_y / 2;
let threshold = Duration::from_millis(dct as u64);
if this_click - s.last_click_time >= threshold || !in_box {
s.click_count = 0;
}
s.click_count = s.click_count.saturating_add(1);
s.last_click_time = this_click;
s.last_click_pos = (x, y);
s.click_count
} else {
0
};
let event = MouseEvent {
pos,
buttons,
mods,
count,
focus: false,
button,
wheel_delta: Vec2::ZERO,
};
if count > 0 {
s.enter_mouse_capture(hwnd, button);
s.handler.mouse_down(&event);
} else {
s.handler.mouse_up(&event);
if s.exit_mouse_capture(button) {
self.handle.borrow().defer(DeferredOp::ReleaseMouseCapture);
}
}
});
}
Some(0)
}
WM_CLOSE => self
.with_wnd_state(|s| s.handler.request_close())
.map(|_| 0),
DS_REQUEST_DESTROY => {
unsafe {
DestroyWindow(hwnd);
}
Some(0)
}
WM_DESTROY => {
self.with_wnd_state(|s| s.handler.destroy());
Some(0)
}
WM_TIMER => {
let id = wparam;
unsafe {
KillTimer(hwnd, id);
}
let token = TimerToken::from_raw(id as u64);
self.handle.borrow().free_timer_slot(token);
self.with_wnd_state(|s| s.handler.timer(token));
Some(1)
}
WM_CAPTURECHANGED => {
self.with_wnd_state(|s| s.captured_mouse_buttons.clear());
Some(0)
}
WM_GETMINMAXINFO => {
let min_max_info = unsafe { &mut *(lparam as *mut MINMAXINFO) };
self.with_wnd_state(|s| {
if let Some(min_size_dp) = s.min_size {
let min_area = ScaledArea::from_dp(min_size_dp, self.scale());
let min_size_px = min_area.size_px();
min_max_info.ptMinTrackSize.x = min_size_px.width as i32;
min_max_info.ptMinTrackSize.y = min_size_px.height as i32;
}
});
Some(0)
}
DS_RUN_IDLE => self
.with_wnd_state(|s| {
let queue = self.handle.borrow().take_idle_queue();
for callback in queue {
match callback {
IdleKind::Callback(it) => it.call(&mut *s.handler),
IdleKind::Token(token) => s.handler.idle(token),
}
}
})
.map(|_| 0),
_ => None,
}
}
}
impl WindowBuilder {
pub fn new(app: Application) -> WindowBuilder {
WindowBuilder {
app,
handler: None,
title: String::new(),
menu: None,
resizable: true,
show_titlebar: true,
transparent: false,
present_strategy: Default::default(),
size: None,
min_size: None,
position: None,
level: None,
state: window::WindowState::Restored,
}
}
pub fn set_handler(&mut self, handler: Box<dyn WinHandler>) {
self.handler = Some(handler);
}
pub fn set_size(&mut self, size: Size) {
self.size = Some(size);
}
pub fn set_min_size(&mut self, size: Size) {
self.min_size = Some(size);
}
pub fn resizable(&mut self, resizable: bool) {
self.resizable = resizable;
}
pub fn show_titlebar(&mut self, show_titlebar: bool) {
self.show_titlebar = show_titlebar;
}
pub fn set_transparent(&mut self, transparent: bool) {
if transparent {
if OPTIONAL_FUNCTIONS.DCompositionCreateDevice.is_some() {
self.present_strategy = PresentStrategy::Flip;
self.transparent = true;
} else {
tracing::warn!("Transparency requires Windows 8 or newer");
}
}
}
pub fn set_title<S: Into<String>>(&mut self, title: S) {
self.title = title.into();
}
pub fn set_menu(&mut self, menu: Menu) {
self.menu = Some(menu);
}
pub fn set_position(&mut self, position: Point) {
self.position = Some(position);
}
pub fn set_window_state(&mut self, state: window::WindowState) {
self.state = state;
}
pub fn set_level(&mut self, level: WindowLevel) {
self.level = Some(level)
}
pub fn build(self) -> Result<WindowHandle, Error> {
unsafe {
let class_name = super::util::CLASS_NAME.to_wide();
let dwrite_factory = DwriteFactory::new().unwrap();
let fonts = self.app.fonts.clone();
let text = PietText::new_with_shared_fonts(dwrite_factory, Some(fonts));
let wndproc = MyWndProc {
app: self.app.clone(),
handle: Default::default(),
d2d_factory: D2DFactory::new().unwrap(),
text: text.clone(),
state: RefCell::new(None),
present_strategy: self.present_strategy,
};
let mut scale = Scale::new(1.0, 1.0);
let (mut width, mut height) = (CW_USEDEFAULT, CW_USEDEFAULT);
let (mut pos_x, mut pos_y) = (CW_USEDEFAULT, CW_USEDEFAULT);
let mut parent_pos_dp = None; let mut dwStyle = WS_OVERLAPPEDWINDOW;
let mut dwExStyle: DWORD = 0;
let mut focusable = true;
let mut parent_hwnd = None;
let window_level;
if let Some(level) = self.level {
window_level = level.clone();
match level {
WindowLevel::AppWindow => (),
WindowLevel::Tooltip(parent_window_handle)
| WindowLevel::DropDown(parent_window_handle)
| WindowLevel::Modal(parent_window_handle) => {
scale = parent_window_handle.get_scale().unwrap_or_default();
parent_pos_dp = Some(parent_window_handle.get_position());
parent_hwnd = parent_window_handle.0.get_hwnd();
dwStyle = WS_POPUP;
dwExStyle = WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW;
focusable = false;
}
}
} else {
window_level = WindowLevel::AppWindow;
}
if let Some(pos_dp) = self.position {
(pos_x, pos_y) = calculate_window_pos(parent_pos_dp, pos_dp, scale);
}
let mut area = ScaledArea::default();
if let Some(size_dp) = self.size {
area = ScaledArea::from_dp(size_dp, scale);
let size_px = area.size_px();
(width, height) = (size_px.width as i32, size_px.height as i32);
}
let (hmenu, accels, has_menu) = match self.menu {
Some(menu) => {
let accels = menu.accels();
(menu.into_hmenu(), accels, true)
}
None => (0 as HMENU, None, false),
};
let window = WindowState {
hwnd: Cell::new(0 as HWND),
scale: Cell::new(scale),
area: Cell::new(area),
invalid: RefCell::new(Region::EMPTY),
has_menu: Cell::new(has_menu),
wndproc: Box::new(wndproc),
idle_queue: Default::default(),
timers: Arc::new(Mutex::new(TimerSlots::new(1))),
deferred_queue: RefCell::new(Vec::new()),
has_titlebar: Cell::new(self.show_titlebar),
is_resizable: Cell::new(self.resizable),
is_transparent: Cell::new(self.transparent),
handle_titlebar: Cell::new(false),
active_text_input: Cell::new(None),
is_focusable: focusable,
window_level,
};
let win = Rc::new(window);
let handle = WindowHandle {
text,
state: Rc::downgrade(&win),
};
let state = WndState {
handler: self.handler.unwrap(),
render_target: None,
dxgi_state: None,
min_size: self.min_size,
keyboard_state: KeyboardState::new(),
captured_mouse_buttons: MouseButtons::new(),
has_mouse_focus: false,
transparent: self.transparent,
last_click_time: Instant::now(),
last_click_pos: (0, 0),
click_count: 0,
};
win.wndproc.connect(&handle, state);
if !self.resizable {
dwStyle &= !(WS_THICKFRAME | WS_MAXIMIZEBOX);
}
if !self.show_titlebar {
dwStyle &= !(WS_SYSMENU | WS_OVERLAPPED);
}
if self.present_strategy == PresentStrategy::Flip {
dwExStyle |= WS_EX_NOREDIRECTIONBITMAP;
}
match self.state {
window::WindowState::Maximized => dwStyle |= WS_MAXIMIZE,
window::WindowState::Minimized => dwStyle |= WS_MINIMIZE,
_ => (),
};
let hwnd = create_window(
dwExStyle,
class_name.as_ptr(),
self.title.to_wide().as_ptr(),
dwStyle,
pos_x,
pos_y,
width,
height,
parent_hwnd.unwrap_or(0 as HWND),
hmenu,
0 as HINSTANCE,
win,
);
if hwnd.is_null() {
return Err(Error::NullHwnd);
}
if let Ok(new_scale) = handle.get_scale() {
if new_scale != scale {
scale = new_scale;
let mut flags =
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER;
let mut needs_any_change = false;
if let Some(pos_dp) = self.position {
(pos_x, pos_y) = calculate_window_pos(parent_pos_dp, pos_dp, scale);
flags &= !SWP_NOMOVE;
needs_any_change = true;
}
if let Some(size_dp) = self.size {
area = ScaledArea::from_dp(size_dp, scale);
let size_px = area.size_px();
(width, height) = (size_px.width as i32, size_px.height as i32);
flags &= !SWP_NOSIZE;
needs_any_change = true;
}
if needs_any_change
&& SetWindowPos(hwnd, HWND_TOPMOST, pos_x, pos_y, width, height, flags) == 0
{
warn!(
"failed to update window size/pos: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
}
}
}
const DWMWA_USE_IMMERSIVE_DARK_MODE: u32 = 20;
let value = should_use_light_theme() as BOOL;
let value_ptr = &value as *const _ as *const c_void;
DwmSetWindowAttribute(
hwnd,
DWMWA_USE_IMMERSIVE_DARK_MODE,
value_ptr,
std::mem::size_of::<BOOL>() as u32,
);
self.app.add_window(hwnd);
if let Some(accels) = accels {
register_accel(hwnd, &accels);
}
Ok(handle)
}
}
}
fn calculate_window_pos(parent_pos_dp: Option<Point>, pos_dp: Point, scale: Scale) -> (i32, i32) {
let pos_px = if let Some(parent_pos_dp) = parent_pos_dp {
(parent_pos_dp + pos_dp.to_vec2()).to_px(scale)
} else {
pos_dp.to_px(scale)
};
(pos_px.x.round() as i32, pos_px.y.round() as i32)
}
pub fn should_use_light_theme() -> bool {
let mut data: [u8; 4] = [0; 4];
let mut cb_data: u32 = data.len() as u32;
let res = unsafe {
RegGetValueW(
HKEY_CURRENT_USER,
r#"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"#
.to_wide()
.as_ptr(),
"AppsUseLightTheme".to_wide().as_ptr(),
RRF_RT_REG_DWORD,
std::ptr::null_mut(),
data.as_mut_ptr() as _,
&mut cb_data as *mut _,
)
};
if res == 0 {
i32::from_le_bytes(data) == 1
} else {
true }
}
unsafe fn choose_adapter(factory: *mut IDXGIFactory2) -> *mut IDXGIAdapter {
let mut i = 0;
let mut best_adapter = null_mut();
let mut best_vram = 0;
loop {
let mut adapter: *mut IDXGIAdapter = null_mut();
if !SUCCEEDED((*factory).EnumAdapters(i, &mut adapter)) {
break;
}
let mut desc = mem::MaybeUninit::uninit();
let hr = (*adapter).GetDesc(desc.as_mut_ptr());
if !SUCCEEDED(hr) {
error!("Failed to get adapter description: {:?}", Error::Hr(hr));
break;
}
let mut desc: DXGI_ADAPTER_DESC = desc.assume_init();
let vram = desc.DedicatedVideoMemory;
if i == 0 || vram > best_vram {
best_vram = vram;
best_adapter = adapter;
}
debug!(
"{:?}: desc = {:?}, vram = {}",
adapter,
(&mut desc.Description[0] as LPWSTR).to_string(),
desc.DedicatedVideoMemory
);
i += 1;
}
best_adapter
}
unsafe fn create_dxgi_state(
present_strategy: PresentStrategy,
hwnd: HWND,
transparent: bool,
) -> Result<Option<DxgiState>, Error> {
let mut factory: *mut IDXGIFactory2 = null_mut();
as_result(CreateDXGIFactory1(
&IID_IDXGIFactory2,
&mut factory as *mut *mut IDXGIFactory2 as *mut *mut c_void,
))?;
debug!("dxgi factory pointer = {:?}", factory);
let adapter = choose_adapter(factory);
debug!("adapter = {:?}", adapter);
let mut d3d11_device = D3D11Device::new_simple()?;
let (swap_effect, bufs) = match present_strategy {
PresentStrategy::Sequential => (DXGI_SWAP_EFFECT_SEQUENTIAL, 1),
PresentStrategy::Flip | PresentStrategy::FlipRedirect => {
(DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, 2)
}
};
let desc = DXGI_SWAP_CHAIN_DESC1 {
Width: 1024,
Height: 768,
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
Stereo: FALSE,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT,
BufferCount: bufs,
Scaling: DXGI_SCALING_STRETCH,
SwapEffect: swap_effect,
AlphaMode: if transparent {
DXGI_ALPHA_MODE_PREMULTIPLIED
} else {
DXGI_ALPHA_MODE_IGNORE
},
Flags: 0,
};
let mut swap_chain: *mut IDXGISwapChain1 = null_mut();
let swap_chain_res = if transparent {
(*factory).CreateSwapChainForComposition(
d3d11_device.raw_ptr() as *mut IUnknown,
&desc,
null_mut(),
&mut swap_chain,
)
} else {
(*factory).CreateSwapChainForHwnd(
d3d11_device.raw_ptr() as *mut IUnknown,
hwnd,
&desc,
null_mut(),
null_mut(),
&mut swap_chain,
)
};
debug!(
"swap chain res = 0x{:x}, pointer = {:?}",
swap_chain_res, swap_chain
);
let (composition_device, composition_target, composition_visual) = if transparent {
let DCompositionCreateDevice = OPTIONAL_FUNCTIONS.DCompositionCreateDevice.unwrap();
let mut ptr: *mut c_void = null_mut();
DCompositionCreateDevice(
d3d11_device.raw_ptr() as *mut IDXGIDevice,
&IDCompositionDevice::uuidof(),
&mut ptr,
);
let composition_device = ComPtr::<IDCompositionDevice>::from_raw(ptr as _);
let mut ptr: *mut IDCompositionTarget = null_mut();
composition_device.CreateTargetForHwnd(hwnd as _, 1, &mut ptr);
let composition_target = ComPtr::from_raw(ptr);
let mut ptr: *mut IDCompositionVisual = null_mut();
composition_device.CreateVisual(&mut ptr);
let composition_visual = ComPtr::from_raw(ptr);
composition_visual.SetContent(swap_chain as *mut IUnknown);
composition_target.SetRoot(composition_visual.as_raw());
composition_device.Commit();
(
Some(composition_device),
Some(composition_target),
Some(composition_visual),
)
} else {
(None, None, None)
};
Ok(Some(DxgiState {
swap_chain,
composition_device,
composition_target,
composition_visual,
}))
}
#[cfg(target_arch = "x86_64")]
type WindowLongPtr = winapi::shared::basetsd::LONG_PTR;
#[cfg(target_arch = "aarch64")]
type WindowLongPtr = winapi::shared::basetsd::LONG_PTR;
#[cfg(target_arch = "x86")]
type WindowLongPtr = LONG;
pub(crate) unsafe extern "system" fn win_proc_dispatch(
hwnd: HWND,
msg: UINT,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
if msg == WM_CREATE {
let create_struct = &*(lparam as *const CREATESTRUCTW);
let wndproc_ptr = create_struct.lpCreateParams;
SetWindowLongPtrW(hwnd, GWLP_USERDATA, wndproc_ptr as WindowLongPtr);
}
let window_ptr = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *const WindowState;
let result = {
if window_ptr.is_null() {
None
} else {
(*window_ptr).wndproc.window_proc(hwnd, msg, wparam, lparam)
}
};
if msg == WM_NCDESTROY && !window_ptr.is_null() {
(*window_ptr).wndproc.cleanup(hwnd);
SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
mem::drop(Rc::from_raw(window_ptr));
}
match result {
Some(lresult) => lresult,
None => DefWindowProcW(hwnd, msg, wparam, lparam),
}
}
#[allow(clippy::too_many_arguments)]
unsafe fn create_window(
dwExStyle: DWORD,
lpClassName: LPCWSTR,
lpWindowName: LPCWSTR,
dwStyle: DWORD,
x: c_int,
y: c_int,
nWidth: c_int,
nHeight: c_int,
hWndParent: HWND,
hMenu: HMENU,
hInstance: HINSTANCE,
wndproc: Rc<WindowState>,
) -> HWND {
CreateWindowExW(
dwExStyle,
lpClassName,
lpWindowName,
dwStyle,
x,
y,
nWidth,
nHeight,
hWndParent,
hMenu,
hInstance,
Rc::into_raw(wndproc) as LPVOID,
)
}
impl Cursor {
fn get_hcursor(&self) -> HCURSOR {
#[allow(deprecated)]
let name = match self {
Cursor::Arrow => IDC_ARROW,
Cursor::IBeam => IDC_IBEAM,
Cursor::Pointer => IDC_HAND,
Cursor::Crosshair => IDC_CROSS,
Cursor::OpenHand => {
warn!("Cursor::OpenHand not available on windows");
IDC_ARROW
}
Cursor::NotAllowed => IDC_NO,
Cursor::ResizeLeftRight => IDC_SIZEWE,
Cursor::ResizeUpDown => IDC_SIZENS,
Cursor::Custom(c) => {
return (c.0).0;
}
};
unsafe { LoadCursorW(0 as HINSTANCE, name) }
}
}
impl WindowHandle {
pub fn show(&self) {
if let Some(w) = self.state.upgrade() {
let hwnd = w.hwnd.get();
let show = if w.is_focusable {
match self.get_window_state() {
window::WindowState::Maximized => SW_MAXIMIZE,
window::WindowState::Minimized => SW_MINIMIZE,
_ => SW_SHOWNORMAL,
}
} else {
SW_SHOWNOACTIVATE
};
unsafe {
ShowWindow(hwnd, show);
UpdateWindow(hwnd);
}
}
}
pub fn close(&self) {
if let Some(w) = self.state.upgrade() {
let hwnd = w.hwnd.get();
unsafe {
PostMessageW(hwnd, DS_REQUEST_DESTROY, 0, 0);
}
}
}
pub fn bring_to_front_and_focus(&self) {
warn!("bring_to_front_and_focus not yet implemented on windows");
}
pub fn request_anim_frame(&self) {
if let Some(w) = self.state.upgrade() {
let hwnd = w.hwnd.get();
unsafe {
if RedrawWindow(hwnd, null(), null_mut(), RDW_INTERNALPAINT) == 0 {
warn!(
"RedrawWindow failed: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
}
}
}
}
pub fn invalidate(&self) {
if let Some(w) = self.state.upgrade() {
w.invalid
.borrow_mut()
.set_rect(w.area.get().size_dp().to_rect());
}
self.request_anim_frame();
}
pub fn invalidate_rect(&self, rect: Rect) {
if let Some(w) = self.state.upgrade() {
let scale = w.scale.get();
w.invalid
.borrow_mut()
.add_rect(rect.to_px(scale).expand().to_dp(scale));
}
self.request_anim_frame();
}
fn defer(&self, op: DeferredOp) {
if let Some(w) = self.state.upgrade() {
w.deferred_queue.borrow_mut().push(op);
}
}
pub fn set_title(&self, title: &str) {
if let Some(w) = self.state.upgrade() {
let hwnd = w.hwnd.get();
unsafe {
if SetWindowTextW(hwnd, title.to_wide().as_ptr()) == FALSE {
warn!("failed to set window title '{}'", title);
}
}
}
}
pub fn show_titlebar(&self, show_titlebar: bool) {
self.defer(DeferredOp::ShowTitlebar(show_titlebar));
}
pub fn set_position(&self, position: Point) {
self.defer(DeferredOp::SetWindowState(window::WindowState::Restored));
if let Some(w) = self.state.upgrade() {
match &w.window_level {
WindowLevel::Tooltip(parent_window_handle)
| WindowLevel::DropDown(parent_window_handle)
| WindowLevel::Modal(parent_window_handle) => {
let screen_position = parent_window_handle.get_position() + position.to_vec2();
self.defer(DeferredOp::SetPosition(screen_position));
}
WindowLevel::AppWindow => {
self.defer(DeferredOp::SetPosition(position));
}
}
}
}
pub fn get_position(&self) -> Point {
if let Some(w) = self.state.upgrade() {
let hwnd = w.hwnd.get();
unsafe {
let mut rect = RECT {
left: 0,
top: 0,
right: 0,
bottom: 0,
};
if GetWindowRect(hwnd, &mut rect) == 0 {
warn!(
"failed to get window rect: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
};
return Point::new(rect.left as f64, rect.top as f64)
.to_dp(self.get_scale().unwrap());
}
}
Point::new(0.0, 0.0)
}
pub fn content_insets(&self) -> Insets {
if let Some(w) = self.state.upgrade() {
let hwnd = w.hwnd.get();
unsafe {
let mut info: WINDOWINFO = mem::zeroed();
info.cbSize = mem::size_of::<WINDOWINFO>() as u32;
if GetWindowInfo(hwnd, &mut info) == 0 {
warn!(
"failed to get window info: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
};
let window_frame = Rect::from_points(
(info.rcWindow.left as f64, info.rcWindow.top as f64),
(info.rcWindow.right as f64, info.rcWindow.bottom as f64),
);
let content_frame = Rect::from_points(
(info.rcClient.left as f64, info.rcClient.top as f64),
(info.rcClient.right as f64, info.rcClient.bottom as f64),
);
return (window_frame - content_frame).to_dp(w.scale.get());
}
}
Insets::ZERO
}
pub fn set_size(&self, size: Size) {
self.defer(DeferredOp::SetSize(size));
}
pub fn get_size(&self) -> Size {
if let Some(w) = self.state.upgrade() {
let hwnd = w.hwnd.get();
unsafe {
let mut rect = RECT {
left: 0,
top: 0,
right: 0,
bottom: 0,
};
if GetWindowRect(hwnd, &mut rect) == 0 {
warn!(
"failed to get window rect: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
};
let width = rect.right - rect.left;
let height = rect.bottom - rect.top;
return Size::new(width as f64, height as f64).to_dp(w.scale.get());
}
}
Size::new(0.0, 0.0)
}
pub fn resizable(&self, resizable: bool) {
self.defer(DeferredOp::SetResizable(resizable));
}
pub fn set_window_state(&self, state: window::WindowState) {
self.defer(DeferredOp::SetWindowState(state));
}
pub fn get_window_state(&self) -> window::WindowState {
if let Some(w) = self.state.upgrade() {
let hwnd = w.hwnd.get();
unsafe {
let style = GetWindowLongPtrW(hwnd, GWL_STYLE) as u32;
if style == 0 {
warn!(
"failed to get window style: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
}
if (style & WS_MAXIMIZE) != 0 {
window::WindowState::Maximized
} else if (style & WS_MINIMIZE) != 0 {
window::WindowState::Minimized
} else {
window::WindowState::Restored
}
}
} else {
window::WindowState::Restored
}
}
pub fn handle_titlebar(&self, val: bool) {
if let Some(w) = self.state.upgrade() {
w.handle_titlebar.set(val);
}
}
pub fn set_menu(&self, menu: Menu) {
let accels = menu.accels();
let hmenu = menu.into_hmenu();
if let Some(w) = self.state.upgrade() {
let hwnd = w.hwnd.get();
unsafe {
let old_menu = GetMenu(hwnd);
if SetMenu(hwnd, hmenu) == FALSE {
warn!("failed to set window menu");
} else {
w.has_menu.set(true);
DestroyMenu(old_menu);
}
if let Some(accels) = accels {
register_accel(hwnd, &accels);
}
}
}
}
pub fn show_context_menu(&self, menu: Menu, pos: Point) {
self.defer(DeferredOp::ContextMenu(menu, pos));
}
pub fn text(&self) -> PietText {
self.text.clone()
}
pub fn add_text_field(&self) -> TextFieldToken {
TextFieldToken::next()
}
pub fn remove_text_field(&self, token: TextFieldToken) {
if let Some(state) = self.state.upgrade() {
if state.active_text_input.get() == Some(token) {
state.active_text_input.set(None);
}
}
}
pub fn set_focused_text_field(&self, active_field: Option<TextFieldToken>) {
if let Some(state) = self.state.upgrade() {
state.active_text_input.set(active_field);
}
}
pub fn update_text_field(&self, _token: TextFieldToken, _update: Event) {
}
pub fn request_timer(&self, deadline: std::time::Instant) -> TimerToken {
let (id, elapse) = self.get_timer_slot(deadline);
let id = self
.get_hwnd()
.map(|hwnd| unsafe { SetTimer(hwnd, id.into_raw() as usize, elapse, None) as u64 })
.unwrap_or(0);
TimerToken::from_raw(id)
}
pub fn set_cursor(&mut self, cursor: &Cursor) {
unsafe {
SetCursor(cursor.get_hcursor());
}
}
pub fn make_cursor(&self, cursor_desc: &CursorDesc) -> Option<Cursor> {
if let Some(hwnd) = self.get_hwnd() {
unsafe {
let hdc = GetDC(hwnd);
if hdc.is_null() {
return None;
}
defer!(ReleaseDC(null_mut(), hdc););
let mask_dc = CreateCompatibleDC(hdc);
if mask_dc.is_null() {
return None;
}
defer!(DeleteDC(mask_dc););
let bmp_dc = CreateCompatibleDC(hdc);
if bmp_dc.is_null() {
return None;
}
defer!(DeleteDC(bmp_dc););
let width = cursor_desc.image.width();
let height = cursor_desc.image.height();
let mask = CreateCompatibleBitmap(hdc, width as c_int, height as c_int);
if mask.is_null() {
return None;
}
defer!(DeleteObject(mask as _););
let bmp = CreateCompatibleBitmap(hdc, width as c_int, height as c_int);
if bmp.is_null() {
return None;
}
defer!(DeleteObject(bmp as _););
let old_mask = SelectObject(mask_dc, mask as *mut c_void);
let old_bmp = SelectObject(bmp_dc, bmp as *mut c_void);
for (row_idx, row) in cursor_desc.image.pixel_colors().enumerate() {
for (col_idx, p) in row.enumerate() {
let (r, g, b, a) = p.as_rgba8();
let mask_px = RGB(255 - a, 255 - a, 255 - a);
let bmp_px = RGB(r, g, b);
SetPixel(mask_dc, col_idx as i32, row_idx as i32, mask_px);
SetPixel(bmp_dc, col_idx as i32, row_idx as i32, bmp_px);
}
}
SelectObject(mask_dc, old_mask);
SelectObject(bmp_dc, old_bmp);
let mut icon_info = ICONINFO {
fIcon: 0,
xHotspot: cursor_desc.hot.x as DWORD,
yHotspot: cursor_desc.hot.y as DWORD,
hbmMask: mask,
hbmColor: bmp,
};
let icon = CreateIconIndirect(&mut icon_info);
Some(Cursor::Custom(CustomCursor(Arc::new(HCursor(icon)))))
}
} else {
None
}
}
pub fn open_file(&mut self, options: FileDialogOptions) -> Option<FileDialogToken> {
let tok = FileDialogToken::next();
self.defer(DeferredOp::Open(options, tok));
Some(tok)
}
pub fn save_as(&mut self, options: FileDialogOptions) -> Option<FileDialogToken> {
let tok = FileDialogToken::next();
self.defer(DeferredOp::SaveAs(options, tok));
Some(tok)
}
pub fn get_hwnd(&self) -> Option<HWND> {
self.state.upgrade().map(|w| w.hwnd.get())
}
pub fn is_focusable(&self) -> bool {
self.state.upgrade().map(|w| w.is_focusable).unwrap_or(true)
}
pub fn get_idle_handle(&self) -> Option<IdleHandle> {
self.state.upgrade().map(|w| IdleHandle {
hwnd: w.hwnd.get(),
queue: w.idle_queue.clone(),
})
}
fn take_idle_queue(&self) -> Vec<IdleKind> {
if let Some(w) = self.state.upgrade() {
mem::take(&mut w.idle_queue.lock().unwrap())
} else {
Vec::new()
}
}
pub fn get_scale(&self) -> Result<Scale, ShellError> {
Ok(self
.state
.upgrade()
.ok_or(ShellError::WindowDropped)?
.scale
.get())
}
fn get_timer_slot(&self, deadline: std::time::Instant) -> (TimerToken, u32) {
if let Some(w) = self.state.upgrade() {
let mut timers = w.timers.lock().unwrap();
let id = timers.alloc();
let elapsed = timers.compute_elapsed(deadline);
(id, elapsed)
} else {
(TimerToken::INVALID, 0)
}
}
fn free_timer_slot(&self, token: TimerToken) {
if let Some(w) = self.state.upgrade() {
w.timers.lock().unwrap().free(token)
}
}
}
unsafe impl Send for IdleHandle {}
unsafe impl Sync for IdleHandle {}
impl IdleHandle {
pub fn add_idle_callback<F>(&self, callback: F)
where
F: FnOnce(&mut dyn WinHandler) + Send + 'static,
{
let mut queue = self.queue.lock().unwrap();
if queue.is_empty() {
unsafe {
PostMessageW(self.hwnd, DS_RUN_IDLE, 0, 0);
}
}
queue.push(IdleKind::Callback(Box::new(callback)));
}
pub fn add_idle_token(&self, token: IdleToken) {
let mut queue = self.queue.lock().unwrap();
if queue.is_empty() {
unsafe {
PostMessageW(self.hwnd, DS_RUN_IDLE, 0, 0);
}
}
queue.push(IdleKind::Token(token));
}
}
impl Default for WindowHandle {
fn default() -> Self {
WindowHandle {
state: Default::default(),
text: PietText::new_with_shared_fonts(DwriteFactory::new().unwrap(), None),
}
}
}