#![allow(non_snake_case, clippy::cast_lossless)]
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::mem;
use std::ptr::{null, null_mut};
use std::rc::{Rc, Weak};
use std::sync::{Arc, Mutex};
use log::{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::d2d1::*;
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::unknwnbase::*;
use winapi::um::winnt::*;
use winapi::um::winuser::*;
use piet_common::d2d::{D2DFactory, DeviceContext};
use piet_common::dwrite::DwriteFactory;
use crate::platform::windows::HwndRenderTarget;
use crate::kurbo::{Point, Size, Vec2};
use crate::piet::{Piet, RenderContext};
use super::accels::register_accel;
use super::dcomp::{D3D11Device, DCompositionDevice, DCompositionTarget, DCompositionVisual};
use super::dialog::get_file_dialog_path;
use super::error::Error;
use super::menu::Menu;
use super::paint;
use super::timers::TimerSlots;
use super::util::{as_result, FromWide, ToWide, OPTIONAL_FUNCTIONS};
use crate::common_util::IdleCallback;
use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo};
use crate::keyboard::{KeyEvent, KeyModifiers};
use crate::keycodes::KeyCode;
use crate::mouse::{Cursor, MouseButton, MouseEvent};
use crate::window::{IdleToken, Text, TimerToken, WinHandler};
extern "system" {
pub fn DwmFlush();
}
pub struct WindowBuilder {
handler: Option<Box<dyn WinHandler>>,
title: String,
menu: Option<Menu>,
present_strategy: PresentStrategy,
resizable: bool,
show_titlebar: bool,
size: Size,
min_size: Option<Size>,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum PresentStrategy {
Hwnd,
#[allow(dead_code)]
Sequential,
Flip,
FlipRedirect,
}
pub struct WindowHandle {
dwrite_factory: DwriteFactory,
state: Weak<WindowState>,
}
#[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>,
dpi: Cell<f32>,
wndproc: Box<dyn WndProc>,
idle_queue: Arc<Mutex<Vec<IdleKind>>>,
timers: Arc<Mutex<TimerSlots>>,
}
trait WndProc {
fn connect(&self, handle: &WindowHandle, state: WndState);
fn window_proc(&self, hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM)
-> Option<LRESULT>;
}
struct MyWndProc {
handle: RefCell<WindowHandle>,
d2d_factory: D2DFactory,
dwrite_factory: DwriteFactory,
state: RefCell<Option<WndState>>,
present_strategy: PresentStrategy,
}
struct WndState {
handler: Box<dyn WinHandler>,
render_target: Option<DeviceContext>,
dcomp_state: Option<DCompState>,
dpi: f32,
min_size: Option<Size>,
stashed_key_code: KeyCode,
stashed_char: Option<char>,
captured_mouse_buttons: u32,
}
struct DCompState {
swap_chain: *mut IDXGISwapChain1,
dcomp_device: DCompositionDevice,
dcomp_target: DCompositionTarget,
swapchain_visual: DCompositionVisual,
sizing: bool,
}
const XI_RUN_IDLE: UINT = WM_USER;
const XI_REQUEST_DESTROY: UINT = WM_USER + 1;
impl Default for PresentStrategy {
fn default() -> PresentStrategy {
PresentStrategy::FlipRedirect
}
}
fn get_mod_state() -> KeyModifiers {
unsafe {
let mut mod_state = KeyModifiers::default();
if GetKeyState(VK_MENU) < 0 {
mod_state.alt = true;
}
if GetKeyState(VK_CONTROL) < 0 {
mod_state.ctrl = true;
}
if GetKeyState(VK_SHIFT) < 0 {
mod_state.shift = true;
}
mod_state
}
}
impl WndState {
fn rebuild_render_target(&mut self, d2d: &D2DFactory) {
unsafe {
let swap_chain = self.dcomp_state.as_ref().unwrap().swap_chain;
let rt = paint::create_render_target_dxgi(d2d, swap_chain, self.dpi)
.map(|rt| rt.as_device_context().expect("TODO remove this expect"));
self.render_target = rt.ok();
}
}
fn render(&mut self, d2d: &D2DFactory, dw: &DwriteFactory, handle: &RefCell<WindowHandle>) {
let rt = self.render_target.as_mut().unwrap();
rt.begin_draw();
let anim;
{
let mut piet_ctx = Piet::new(d2d, dw, rt);
anim = self.handler.paint(&mut piet_ctx);
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);
}
if anim {
let handle = handle.borrow().get_idle_handle().unwrap();
let handle2 = handle.clone();
handle.add_idle_callback(move |_| handle2.invalidate());
}
}
fn enter_mouse_capture(&mut self, hwnd: HWND, button: MouseButton) {
if self.captured_mouse_buttons == 0 {
unsafe {
SetCapture(hwnd);
}
}
self.captured_mouse_buttons |= 1 << (button as u32);
}
fn exit_mouse_capture(&mut self, button: MouseButton) {
self.captured_mouse_buttons &= !(1 << (button as u32));
if self.captured_mouse_buttons == 0 {
unsafe {
if ReleaseCapture() == FALSE {
warn!(
"failed to release mouse capture: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
}
}
}
}
}
impl MyWndProc {
fn log_dropped_msg(&self, hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) {
error!(
"dropped message 0x{:x}, hwnd={:?}, wparam=0x{:x}, lparam=0x{:x}",
msg, hwnd, wparam, lparam
);
}
}
impl WndProc for MyWndProc {
fn connect(&self, handle: &WindowHandle, state: WndState) {
*self.handle.borrow_mut() = handle.clone();
*self.state.borrow_mut() = Some(state);
}
#[allow(clippy::cognitive_complexity)]
fn window_proc(
&self,
hwnd: HWND,
msg: UINT,
wparam: WPARAM,
lparam: LPARAM,
) -> Option<LRESULT> {
match msg {
WM_CREATE => {
let dcomp_state = unsafe {
create_dcomp_state(self.present_strategy, hwnd).unwrap_or_else(|e| {
warn!("Creating swapchain failed, falling back to hwnd: {:?}", e);
None
})
};
self.state.borrow_mut().as_mut().unwrap().dcomp_state = dcomp_state;
if let Some(state) = self.handle.borrow().state.upgrade() {
state.hwnd.set(hwnd);
}
let handle = self.handle.borrow().to_owned();
if let Some(state) = self.state.borrow_mut().as_mut() {
state.handler.connect(&handle.into());
}
Some(0)
}
WM_ERASEBKGND => Some(0),
WM_SETFOCUS => {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
s.handler.got_focus();
} else {
self.log_dropped_msg(hwnd, msg, wparam, lparam);
}
Some(0)
}
WM_PAINT => unsafe {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
if s.render_target.is_none() {
let rt = paint::create_render_target(&self.d2d_factory, hwnd);
s.render_target = rt.ok();
}
s.handler.rebuild_resources();
s.render(&self.d2d_factory, &self.dwrite_factory, &self.handle);
if let Some(ref mut ds) = s.dcomp_state {
if !ds.sizing {
(*ds.swap_chain).Present(1, 0);
let _ = ds.dcomp_device.commit();
}
}
ValidateRect(hwnd, null_mut());
} else {
self.log_dropped_msg(hwnd, msg, wparam, lparam);
}
Some(0)
},
WM_ENTERSIZEMOVE => unsafe {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
if s.dcomp_state.is_some() {
let rt = paint::create_render_target(&self.d2d_factory, hwnd);
s.render_target = rt.ok();
{
s.handler.rebuild_resources();
s.render(&self.d2d_factory, &self.dwrite_factory, &self.handle);
}
if let Some(ref mut ds) = s.dcomp_state {
let _ = ds.dcomp_target.clear_root();
let _ = ds.dcomp_device.commit();
ds.sizing = true;
}
}
} else {
self.log_dropped_msg(hwnd, msg, wparam, lparam);
}
None
},
WM_EXITSIZEMOVE => unsafe {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
if s.dcomp_state.is_some() {
let mut rect: RECT = mem::zeroed();
if GetClientRect(hwnd, &mut rect) == 0 {
warn!("GetClientRect failed.");
return None;
}
let width = (rect.right - rect.left) as u32;
let height = (rect.bottom - rect.top) as u32;
let res = (*s.dcomp_state.as_mut().unwrap().swap_chain).ResizeBuffers(
2,
width,
height,
DXGI_FORMAT_UNKNOWN,
0,
);
if SUCCEEDED(res) {
s.handler.rebuild_resources();
s.rebuild_render_target(&self.d2d_factory);
s.render(&self.d2d_factory, &self.dwrite_factory, &self.handle);
(*s.dcomp_state.as_ref().unwrap().swap_chain).Present(0, 0);
} else {
error!("ResizeBuffers failed: 0x{:x}", res);
}
DwmFlush();
if let Some(ref mut ds) = s.dcomp_state {
let _ = ds.dcomp_target.set_root(&mut ds.swapchain_visual);
let _ = ds.dcomp_device.commit();
ds.sizing = false;
}
}
} else {
self.log_dropped_msg(hwnd, msg, wparam, lparam);
}
None
},
WM_SIZE => unsafe {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
let width = LOWORD(lparam as u32) as u32;
let height = HIWORD(lparam as u32) as u32;
s.handler.size(width, height);
let use_hwnd = if let Some(ref dcomp_state) = s.dcomp_state {
dcomp_state.sizing
} else {
true
};
if use_hwnd {
if let Some(ref mut rt) = s.render_target {
if let Some(hrt) = cast_to_hwnd(rt) {
let width = LOWORD(lparam as u32) as u32;
let height = HIWORD(lparam as u32) as u32;
let size = D2D1_SIZE_U { width, height };
let _ = hrt.ptr.Resize(&size);
}
}
InvalidateRect(hwnd, null_mut(), FALSE);
} else {
let res;
{
s.render_target = None;
res = (*s.dcomp_state.as_mut().unwrap().swap_chain).ResizeBuffers(
0,
width,
height,
DXGI_FORMAT_UNKNOWN,
0,
);
}
if SUCCEEDED(res) {
s.rebuild_render_target(&self.d2d_factory);
s.render(&self.d2d_factory, &self.dwrite_factory, &self.handle);
if let Some(ref mut dcomp_state) = s.dcomp_state {
(*dcomp_state.swap_chain).Present(0, 0);
let _ = dcomp_state.dcomp_device.commit();
}
ValidateRect(hwnd, null_mut());
} else {
error!("ResizeBuffers failed: 0x{:x}", res);
}
}
} else {
self.log_dropped_msg(hwnd, msg, wparam, lparam);
}
Some(0)
},
WM_COMMAND => {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
s.handler.command(LOWORD(wparam as u32) as u32);
} else {
self.log_dropped_msg(hwnd, msg, wparam, lparam);
}
Some(0)
}
WM_CHAR => {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
let key_code = s.stashed_key_code;
s.stashed_char = std::char::from_u32(wparam as u32);
let text = match s.stashed_char {
Some(c) => c,
None => {
warn!("failed to convert WM_CHAR to char: {:#X}", wparam);
return None;
}
};
let modifiers = get_mod_state();
let is_repeat = (lparam & 0xFFFF) > 0;
let event = KeyEvent::new(key_code, is_repeat, modifiers, text, text);
if s.handler.key_down(event) {
Some(0)
} else {
None
}
} else {
self.log_dropped_msg(hwnd, msg, wparam, lparam);
None
}
}
WM_KEYDOWN | WM_SYSKEYDOWN => {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
let key_code: KeyCode = (wparam as i32).into();
s.stashed_key_code = key_code;
if key_code.is_printable() || key_code == KeyCode::Backspace {
return None;
}
let modifiers = get_mod_state();
let is_repeat = (lparam & 0xFFFF) > 0;
let event = KeyEvent::new(key_code, is_repeat, modifiers, "", "");
if s.handler.key_down(event) {
Some(0)
} else {
None
}
} else {
self.log_dropped_msg(hwnd, msg, wparam, lparam);
None
}
}
WM_KEYUP => {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
let key_code: KeyCode = (wparam as i32).into();
let modifiers = get_mod_state();
let is_repeat = false;
let text = s.stashed_char.take();
let event = KeyEvent::new(key_code, is_repeat, modifiers, text, text);
s.handler.key_up(event);
} else {
self.log_dropped_msg(hwnd, msg, wparam, lparam);
}
Some(0)
}
WM_MOUSEWHEEL => {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
let delta_y = HIWORD(wparam as u32) as i16 as f64;
let mods = get_mod_state();
let delta = if mods.shift {
Vec2::new(-delta_y, 0.)
} else {
Vec2::new(0., -delta_y)
};
s.handler.wheel(delta, mods);
} else {
self.log_dropped_msg(hwnd, msg, wparam, lparam);
}
Some(0)
}
WM_MOUSEHWHEEL => {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
let delta_x = HIWORD(wparam as u32) as i16 as f64;
let delta = Vec2::new(delta_x, 0.0);
let mods = get_mod_state();
s.handler.wheel(delta, mods);
} else {
self.log_dropped_msg(hwnd, msg, wparam, lparam);
}
Some(0)
}
WM_MOUSEMOVE => {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
let x = LOWORD(lparam as u32) as i16 as i32;
let y = HIWORD(lparam as u32) as i16 as i32;
let (px, py) = self.handle.borrow().pixels_to_px_xy(x, y);
let pos = Point::new(px as f64, py as f64);
let mods = get_mod_state();
let button = match wparam {
w if (w & 1) > 0 => MouseButton::Left,
w if (w & 1 << 1) > 0 => MouseButton::Right,
w if (w & 1 << 5) > 0 => MouseButton::Middle,
w if (w & 1 << 6) > 0 => MouseButton::X1,
w if (w & 1 << 7) > 0 => MouseButton::X2,
_ => MouseButton::Left,
};
let event = MouseEvent {
pos,
mods,
button,
count: 0,
};
s.handler.mouse_move(&event);
} else {
self.log_dropped_msg(hwnd, msg, wparam, lparam);
}
Some(0)
}
WM_LBUTTONDBLCLK | WM_LBUTTONDOWN | WM_LBUTTONUP | WM_MBUTTONDBLCLK
| WM_MBUTTONDOWN | WM_MBUTTONUP | WM_RBUTTONDBLCLK | WM_RBUTTONDOWN | WM_RBUTTONUP
| WM_XBUTTONDBLCLK | WM_XBUTTONDOWN | WM_XBUTTONUP => {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
let button = match msg {
WM_LBUTTONDBLCLK | WM_LBUTTONDOWN | WM_LBUTTONUP => MouseButton::Left,
WM_MBUTTONDBLCLK | WM_MBUTTONDOWN | WM_MBUTTONUP => MouseButton::Middle,
WM_RBUTTONDBLCLK | WM_RBUTTONDOWN | WM_RBUTTONUP => MouseButton::Right,
WM_XBUTTONDBLCLK | WM_XBUTTONDOWN | WM_XBUTTONUP => {
match HIWORD(wparam as u32) {
1 => MouseButton::X1,
2 => MouseButton::X2,
_ => {
warn!("unexpected X button event");
return None;
}
}
}
_ => unreachable!(),
};
let count = match msg {
WM_LBUTTONDOWN | WM_MBUTTONDOWN | WM_RBUTTONDOWN | WM_XBUTTONDOWN => 1,
WM_LBUTTONDBLCLK | WM_MBUTTONDBLCLK | WM_RBUTTONDBLCLK
| WM_XBUTTONDBLCLK => 2,
WM_LBUTTONUP | WM_MBUTTONUP | WM_RBUTTONUP | WM_XBUTTONUP => 0,
_ => unreachable!(),
};
let x = LOWORD(lparam as u32) as i16 as i32;
let y = HIWORD(lparam as u32) as i16 as i32;
let (px, py) = self.handle.borrow().pixels_to_px_xy(x, y);
let pos = Point::new(px as f64, py as f64);
let mods = get_mod_state();
let event = MouseEvent {
pos,
mods,
button,
count,
};
if count > 0 {
s.enter_mouse_capture(hwnd, button);
s.handler.mouse_down(&event);
} else {
s.handler.mouse_up(&event);
s.exit_mouse_capture(button);
}
} else {
self.log_dropped_msg(hwnd, msg, wparam, lparam);
}
Some(0)
}
XI_REQUEST_DESTROY => {
unsafe {
DestroyWindow(hwnd);
}
Some(0)
}
WM_DESTROY => {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
s.handler.destroy();
} else {
self.log_dropped_msg(hwnd, msg, wparam, lparam);
}
None
}
WM_TIMER => {
let id = wparam;
unsafe {
KillTimer(hwnd, id);
}
let token = TimerToken::from_raw(id as u64);
self.handle.borrow().free_timer_slot(token);
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
s.handler.timer(token);
}
Some(1)
}
WM_CAPTURECHANGED => {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
s.captured_mouse_buttons = 0;
}
Some(0)
}
WM_GETMINMAXINFO => {
let min_max_info = unsafe { &mut *(lparam as *mut MINMAXINFO) };
if let Ok(s) = self.state.try_borrow() {
let s = s.as_ref().unwrap();
if let Some(min_size) = s.min_size {
min_max_info.ptMinTrackSize.x =
(min_size.width * (f64::from(s.dpi) / 96.0)) as i32;
min_max_info.ptMinTrackSize.y =
(min_size.height * (f64::from(s.dpi) / 96.0)) as i32;
}
} else {
self.log_dropped_msg(hwnd, msg, wparam, lparam);
}
Some(0)
}
XI_RUN_IDLE => {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
let queue = self.handle.borrow().take_idle_queue();
for callback in queue {
match callback {
IdleKind::Callback(it) => it.call(s.handler.as_any()),
IdleKind::Token(token) => s.handler.idle(token),
}
}
Some(0)
} else {
None
}
}
_ => None,
}
}
}
fn clone_dwrite(dwrite: &DwriteFactory) -> DwriteFactory {
unsafe {
(*dwrite.get_raw()).AddRef();
DwriteFactory::from_raw(dwrite.get_raw())
}
}
impl WindowBuilder {
pub fn new() -> WindowBuilder {
WindowBuilder {
handler: None,
title: String::new(),
menu: None,
resizable: true,
show_titlebar: true,
present_strategy: Default::default(),
size: Size::new(500.0, 400.0),
min_size: None,
}
}
pub fn set_handler(&mut self, handler: Box<dyn WinHandler>) {
self.handler = Some(handler);
}
pub fn set_size(&mut self, size: Size) {
self.size = 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_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 build(self) -> Result<WindowHandle, Error> {
unsafe {
let class_name = super::util::CLASS_NAME.to_wide();
let dwrite_factory = DwriteFactory::new().unwrap();
let dw_clone = clone_dwrite(&dwrite_factory);
let wndproc = MyWndProc {
handle: Default::default(),
d2d_factory: D2DFactory::new().unwrap(),
dwrite_factory: dw_clone,
state: RefCell::new(None),
present_strategy: self.present_strategy,
};
let window = WindowState {
hwnd: Cell::new(0 as HWND),
dpi: Cell::new(0.0),
wndproc: Box::new(wndproc),
idle_queue: Default::default(),
timers: Arc::new(Mutex::new(TimerSlots::new(1))),
};
let win = Rc::new(window);
let handle = WindowHandle {
dwrite_factory,
state: Rc::downgrade(&win),
};
let dpi = if let Some(func) = OPTIONAL_FUNCTIONS.GetDpiForSystem {
func() as f32
} else {
96.0
};
win.dpi.set(dpi);
let state = WndState {
handler: self.handler.unwrap(),
render_target: None,
dcomp_state: None,
dpi,
min_size: self.min_size,
stashed_key_code: KeyCode::Unknown(0),
stashed_char: None,
captured_mouse_buttons: 0,
};
win.wndproc.connect(&handle, state);
let width = (self.size.width * (f64::from(dpi) / 96.0)) as i32;
let height = (self.size.height * (f64::from(dpi) / 96.0)) as i32;
let (hmenu, accels) = match self.menu {
Some(menu) => {
let accels = menu.accels();
(menu.into_hmenu(), accels)
}
None => (0 as HMENU, None),
};
let mut dwStyle = WS_OVERLAPPEDWINDOW;
if !self.resizable {
dwStyle &= !(WS_THICKFRAME | WS_MAXIMIZEBOX);
}
let mut dwExStyle = 0;
if self.present_strategy == PresentStrategy::Flip {
dwExStyle |= WS_EX_NOREDIRECTIONBITMAP;
}
let hwnd = create_window(
dwExStyle,
class_name.as_ptr(),
self.title.to_wide().as_ptr(),
dwStyle,
CW_USEDEFAULT,
CW_USEDEFAULT,
width,
height,
0 as HWND,
hmenu,
0 as HINSTANCE,
win,
);
if hwnd.is_null() {
return Err(Error::NullHwnd);
}
if let Some(accels) = accels {
register_accel(hwnd, &accels);
}
Ok(handle)
}
}
}
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).from_wide(),
desc.DedicatedVideoMemory
);
i += 1;
}
best_adapter
}
unsafe fn create_dcomp_state(
present_strategy: PresentStrategy,
hwnd: HWND,
) -> Result<Option<DCompState>, Error> {
if present_strategy == PresentStrategy::Hwnd {
return Ok(None);
}
if let Some(create_dxgi_factory2) = OPTIONAL_FUNCTIONS.CreateDXGIFactory2 {
let mut factory: *mut IDXGIFactory2 = null_mut();
as_result(create_dxgi_factory2(
0,
&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 mut d2d1_device = d3d11_device.create_d2d1_device()?;
let mut dcomp_device = d2d1_device.create_composition_device()?;
let mut dcomp_target = dcomp_device.create_target_for_hwnd(hwnd, true)?;
let (swap_effect, bufs) = match present_strategy {
PresentStrategy::Hwnd => unreachable!(),
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: DXGI_ALPHA_MODE_IGNORE,
Flags: 0,
};
let mut swap_chain: *mut IDXGISwapChain1 = null_mut();
let res = (*factory).CreateSwapChainForComposition(
d3d11_device.raw_ptr() as *mut IUnknown,
&desc,
null_mut(),
&mut swap_chain,
);
debug!("swap chain res = 0x{:x}, pointer = {:?}", res, swap_chain);
let mut swapchain_visual = dcomp_device.create_visual()?;
swapchain_visual.set_content_raw(swap_chain as *mut IUnknown)?;
dcomp_target.set_root(&mut swapchain_visual)?;
Ok(Some(DCompState {
swap_chain,
dcomp_device,
dcomp_target,
swapchain_visual,
sizing: false,
}))
} else {
Ok(None)
}
}
#[cfg(target_arch = "x86_64")]
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() {
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_lpcwstr(&self) -> LPCWSTR {
match self {
Cursor::Arrow => IDC_ARROW,
Cursor::IBeam => IDC_IBEAM,
Cursor::Crosshair => IDC_CROSS,
Cursor::OpenHand => IDC_HAND,
Cursor::NotAllowed => IDC_NO,
Cursor::ResizeLeftRight => IDC_SIZEWE,
Cursor::ResizeUpDown => IDC_SIZENS,
}
}
}
impl Clone for WindowHandle {
fn clone(&self) -> WindowHandle {
let dwrite_factory = clone_dwrite(&self.dwrite_factory);
WindowHandle {
dwrite_factory,
state: self.state.clone(),
}
}
}
impl WindowHandle {
pub fn show(&self) {
if let Some(w) = self.state.upgrade() {
let hwnd = w.hwnd.get();
unsafe {
ShowWindow(hwnd, SW_SHOWNORMAL);
UpdateWindow(hwnd);
}
}
}
pub fn close(&self) {
if let Some(w) = self.state.upgrade() {
let hwnd = w.hwnd.get();
unsafe {
PostMessageW(hwnd, XI_REQUEST_DESTROY, 0, 0);
}
}
}
pub fn bring_to_front_and_focus(&self) {
log::warn!("bring_to_front_and_focus not yet implemented on windows");
}
pub fn invalidate(&self) {
if let Some(w) = self.state.upgrade() {
let hwnd = w.hwnd.get();
unsafe {
InvalidateRect(hwnd, null(), FALSE);
}
}
}
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) {}
pub fn resizable(&self, resizable: bool) {
if let Some(w) = self.state.upgrade() {
let hwnd = w.hwnd.get();
unsafe {
let mut style = GetWindowLongPtrW(hwnd, GWL_STYLE);
if style == 0 {
warn!(
"failed to get window style: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
return;
}
if resizable {
style |= (WS_THICKFRAME | WS_MAXIMIZEBOX) as WindowLongPtr;
} else {
style &= !(WS_THICKFRAME | WS_MAXIMIZEBOX) as WindowLongPtr;
}
if SetWindowLongPtrW(hwnd, GWL_STYLE, style) == 0 {
warn!(
"failed to set the window style: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
}
}
}
}
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 {
DestroyMenu(old_menu);
}
if let Some(accels) = accels {
register_accel(hwnd, &accels);
}
}
}
}
pub fn show_context_menu(&self, menu: Menu, pos: Point) {
let hmenu = menu.into_hmenu();
if let Some(w) = self.state.upgrade() {
let hwnd = w.hwnd.get();
let (x, y) = self.px_to_pixels_xy(pos.x as f32, pos.y as f32);
unsafe {
let mut point = POINT { x, y };
ClientToScreen(hwnd, &mut point);
if TrackPopupMenu(hmenu, TPM_LEFTALIGN, point.x, point.y, 0, hwnd, null()) == FALSE
{
warn!("failed to track popup menu");
}
}
}
}
pub fn text(&self) -> Text {
Text::new(&self.dwrite_factory)
}
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 {
let cursor = LoadCursorW(0 as HINSTANCE, cursor.get_lpcwstr());
SetCursor(cursor);
}
}
pub fn open_file_sync(&mut self, options: FileDialogOptions) -> Option<FileInfo> {
let hwnd = self.get_hwnd()?;
unsafe {
get_file_dialog_path(hwnd, FileDialogType::Open, options)
.ok()
.map(|s| FileInfo { path: s.into() })
}
}
pub fn save_as_sync(&mut self, options: FileDialogOptions) -> Option<FileInfo> {
let hwnd = self.get_hwnd()?;
unsafe {
get_file_dialog_path(hwnd, FileDialogType::Save, options)
.ok()
.map(|os_str| FileInfo {
path: os_str.into(),
})
}
}
pub fn get_hwnd(&self) -> Option<HWND> {
self.state.upgrade().map(|w| w.hwnd.get())
}
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::replace(&mut w.idle_queue.lock().unwrap(), Vec::new())
} else {
Vec::new()
}
}
pub fn get_dpi(&self) -> f32 {
if let Some(w) = self.state.upgrade() {
w.dpi.get()
} else {
96.0
}
}
pub fn px_to_pixels(&self, x: f32) -> i32 {
(x * self.get_dpi() * (1.0 / 96.0)).round() as i32
}
pub fn px_to_pixels_xy(&self, x: f32, y: f32) -> (i32, i32) {
let scale = self.get_dpi() * (1.0 / 96.0);
((x * scale).round() as i32, (y * scale).round() as i32)
}
pub fn pixels_to_px<T: Into<f64>>(&self, x: T) -> f32 {
(x.into() as f32) * 96.0 / self.get_dpi()
}
pub fn pixels_to_px_xy<T: Into<f64>>(&self, x: T, y: T) -> (f32, f32) {
let scale = 96.0 / self.get_dpi();
((x.into() as f32) * scale, (y.into() as f32) * scale)
}
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 {}
impl IdleHandle {
pub fn add_idle_callback<F>(&self, callback: F)
where
F: FnOnce(&dyn Any) + Send + 'static,
{
let mut queue = self.queue.lock().unwrap();
if queue.is_empty() {
unsafe {
PostMessageW(self.hwnd, XI_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, XI_RUN_IDLE, 0, 0);
}
}
queue.push(IdleKind::Token(token));
}
fn invalidate(&self) {
unsafe {
InvalidateRect(self.hwnd, null(), FALSE);
}
}
}
unsafe fn cast_to_hwnd(dc: &DeviceContext) -> Option<HwndRenderTarget> {
dc.get_comptr()
.cast()
.ok()
.map(|com_ptr| HwndRenderTarget::from_ptr(com_ptr))
}
impl Default for WindowHandle {
fn default() -> Self {
WindowHandle {
state: Default::default(),
dwrite_factory: DwriteFactory::new().unwrap(),
}
}
}