#[cfg(feature = "raw_input")]
use crate::raw_input;
use crate::DEFAULT_DPI;
use crate::{
api::*,
context::*,
device::Cursor,
error::*,
event::EventHandler,
geometry::*,
ime,
procedure::{window_proc, UserMessage},
resource::*,
};
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, Win32WindowHandle};
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::{Arc, RwLock};
use windows::core::{HSTRING, PCWSTR};
use windows::Win32::{
Foundation::*, Graphics::Gdi::*, System::LibraryLoader::*, UI::HiDpi::*, UI::Shell::*,
UI::WindowsAndMessaging::*,
};
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct WindowHandle(HWND);
unsafe impl Send for WindowHandle {}
unsafe impl Sync for WindowHandle {}
pub trait Style {
fn value(&self) -> WINDOW_STYLE;
fn is_borderless(&self) -> bool {
self.value() == WS_POPUP
}
}
pub struct BorderlessStyle;
impl Style for BorderlessStyle {
#[inline]
fn value(&self) -> WINDOW_STYLE {
WS_POPUP
}
}
pub struct WindowStyle(WINDOW_STYLE);
impl WindowStyle {
#[inline]
pub fn dialog() -> Self {
Self(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU)
}
#[inline]
pub fn borderless() -> BorderlessStyle {
BorderlessStyle
}
#[inline]
pub fn resizable(mut self, resizable: bool) -> Self {
if resizable {
self.0 |= WS_THICKFRAME;
} else {
self.0 &= !WS_THICKFRAME;
}
self
}
#[inline]
pub fn has_minimize_box(mut self, has_minimize_box: bool) -> Self {
if has_minimize_box {
self.0 |= WS_MINIMIZEBOX;
} else {
self.0 &= !WS_MINIMIZEBOX;
}
self
}
#[inline]
pub fn has_maximize_box(mut self, has_maximize_box: bool) -> Self {
if has_maximize_box {
self.0 |= WS_MAXIMIZEBOX;
} else {
self.0 &= !WS_MAXIMIZEBOX;
}
self
}
#[inline]
pub fn is_borderless(&self) -> bool {
self.value() == WS_POPUP
}
}
impl Default for WindowStyle {
#[inline]
fn default() -> Self {
Self(
WS_OVERLAPPED
| WS_CAPTION
| WS_SYSMENU
| WS_THICKFRAME
| WS_MINIMIZEBOX
| WS_MAXIMIZEBOX,
)
}
}
impl Style for WindowStyle {
#[inline]
fn value(&self) -> WINDOW_STYLE {
self.0
}
}
const WINDOW_CLASS_NAME: &str = "wita_window_class";
pub(crate) fn register_class<T: EventHandler + 'static>() {
unsafe {
let class_name = WINDOW_CLASS_NAME
.encode_utf16()
.chain(Some(0))
.collect::<Vec<_>>();
let wc = WNDCLASSEXW {
cbSize: std::mem::size_of::<WNDCLASSEXW>() as _,
style: CS_VREDRAW | CS_HREDRAW,
lpfnWndProc: Some(window_proc::<T>),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: GetModuleHandleW(PCWSTR::null()).unwrap(),
hIcon: HICON(0),
hCursor: HCURSOR(0),
hbrBackground: HBRUSH(GetStockObject(WHITE_BRUSH).0),
lpszMenuName: PCWSTR::null(),
lpszClassName: PCWSTR(class_name.as_ptr() as _),
hIconSm: HICON(0),
};
if RegisterClassExW(&wc) == 0 {
panic!("cannot register the window class");
}
}
}
pub struct WindowBuilder<Ti, S> {
title: Ti,
position: ScreenPosition,
inner_size: S,
visibility: bool,
style: WINDOW_STYLE,
enabled_ime: bool,
visible_ime_composition_window: bool,
visible_ime_candidate_window: bool,
parent: Option<Window>,
children: Vec<Window>,
accept_drag_files: bool,
icon: Option<Icon>,
cursor: Cursor,
no_redirection_bitmap: bool,
#[cfg(feature = "raw_input")]
raw_input_window_state: raw_input::WindowState,
}
impl WindowBuilder<(), ()> {
#[allow(clippy::new_ret_no_self)]
pub fn new() -> WindowBuilder<&'static str, LogicalSize<u32>> {
WindowBuilder {
title: "",
position: ScreenPosition::new(0, 0),
inner_size: LogicalSize::new(640, 480),
style: WindowStyle::default().value(),
visibility: true,
enabled_ime: false,
visible_ime_composition_window: false,
visible_ime_candidate_window: true,
parent: None,
children: Vec::new(),
accept_drag_files: false,
icon: None,
cursor: Cursor::default(),
no_redirection_bitmap: false,
#[cfg(feature = "raw_input")]
raw_input_window_state: raw_input::WindowState::Foreground,
}
}
}
impl<Ti, S> WindowBuilder<Ti, S> {
#[inline]
pub fn title<T>(self, title: T) -> WindowBuilder<T, S> {
WindowBuilder {
title,
position: self.position,
inner_size: self.inner_size,
style: self.style,
visibility: self.visibility,
enabled_ime: self.enabled_ime,
visible_ime_composition_window: self.visible_ime_composition_window,
visible_ime_candidate_window: self.visible_ime_candidate_window,
parent: self.parent,
children: self.children,
accept_drag_files: self.accept_drag_files,
icon: self.icon,
cursor: self.cursor,
no_redirection_bitmap: self.no_redirection_bitmap,
#[cfg(feature = "raw_input")]
raw_input_window_state: self.raw_input_window_state,
}
}
#[inline]
pub fn position(mut self, position: impl Into<ScreenPosition>) -> Self {
self.position = position.into();
self
}
#[inline]
pub fn inner_size<T>(self, inner_size: T) -> WindowBuilder<Ti, T> {
WindowBuilder {
title: self.title,
position: self.position,
inner_size,
style: self.style,
visibility: self.visibility,
enabled_ime: self.enabled_ime,
visible_ime_composition_window: self.visible_ime_composition_window,
visible_ime_candidate_window: self.visible_ime_candidate_window,
parent: self.parent,
children: self.children,
accept_drag_files: self.accept_drag_files,
icon: self.icon,
cursor: self.cursor,
no_redirection_bitmap: self.no_redirection_bitmap,
#[cfg(feature = "raw_input")]
raw_input_window_state: self.raw_input_window_state,
}
}
#[inline]
pub fn style(mut self, style: impl Style) -> WindowBuilder<Ti, S> {
self.style = style.value();
self
}
#[inline]
pub fn visible(mut self, visibility: bool) -> WindowBuilder<Ti, S> {
self.visibility = visibility;
self
}
#[inline]
pub fn visible_ime_composition_window(
mut self,
show_ime_composition_window: bool,
) -> WindowBuilder<Ti, S> {
self.visible_ime_composition_window = show_ime_composition_window;
self
}
#[inline]
pub fn visible_ime_candidate_window(
mut self,
show_ime_cadidate_window: bool,
) -> WindowBuilder<Ti, S> {
self.visible_ime_candidate_window = show_ime_cadidate_window;
self
}
#[inline]
pub fn parent(mut self, parent: &Window) -> WindowBuilder<Ti, S> {
self.parent = Some(parent.clone());
self
}
#[inline]
pub fn child(mut self, child: &Window) -> WindowBuilder<Ti, S> {
self.children.push(child.clone());
self
}
#[inline]
pub fn children(mut self, children: &[&Window]) -> WindowBuilder<Ti, S> {
for &c in children {
self.children.push(c.clone());
}
self
}
#[inline]
pub fn accept_drag_files(mut self, enabled: bool) -> WindowBuilder<Ti, S> {
self.accept_drag_files = enabled;
self
}
#[inline]
pub fn icon(mut self, icon: Icon) -> WindowBuilder<Ti, S> {
self.icon = Some(icon);
self
}
#[inline]
pub fn cursor(mut self, cursor: Cursor) -> WindowBuilder<Ti, S> {
self.cursor = cursor;
self
}
#[inline]
pub fn ime(mut self, enable: bool) -> WindowBuilder<Ti, S> {
self.enabled_ime = enable;
self
}
#[inline]
pub fn no_redirection_bitmap(mut self, enable: bool) -> WindowBuilder<Ti, S> {
self.no_redirection_bitmap = enable;
self
}
#[cfg(feature = "raw_input")]
#[inline]
pub fn raw_input_window_state(mut self, state: raw_input::WindowState) -> WindowBuilder<Ti, S> {
self.raw_input_window_state = state;
self
}
}
impl<Ti, S> WindowBuilder<Ti, S>
where
Ti: AsRef<str>,
S: ToPhysicalSize<u32>,
{
pub fn build(self) -> Result<Window, ApiError> {
if is_context_null() {
panic!("The window can be created after run");
}
unsafe {
let dpi = get_dpi_from_point(self.position);
let inner_size = self.inner_size.to_physical(dpi);
let rc = adjust_window_rect(inner_size, self.style, WINDOW_EX_STYLE(0), dpi);
let hinst = GetModuleHandleW(PCWSTR::null()).unwrap();
let hwnd = CreateWindowExW(
if self.no_redirection_bitmap {
WS_EX_NOREDIRECTIONBITMAP
} else {
WINDOW_EX_STYLE(0)
},
&HSTRING::from(WINDOW_CLASS_NAME),
&HSTRING::from(self.title.as_ref()),
self.style,
self.position.x,
self.position.y,
(rc.right - rc.left) as i32,
(rc.bottom - rc.top) as i32,
HWND(0),
HMENU(0),
hinst,
None,
);
if hwnd == HWND(0) {
return Err(ApiError::new());
}
let window = LocalWindow::new(
hwnd,
WindowState {
title: self.title.as_ref().to_string(),
style: self.style,
set_position: (self.position.x, self.position.y),
set_inner_size: inner_size,
enabled_ime: self.enabled_ime,
visible_ime_composition_window: self.visible_ime_composition_window,
visible_ime_candidate_window: self.visible_ime_candidate_window,
ime_position: PhysicalPosition::new(0, 0),
children: self.children,
closed: false,
cursor: self.cursor,
},
);
self.cursor.set();
let handle = window.handle.clone();
if let Some(parent) = self.parent {
let mut state = parent.state.write().unwrap();
state.children.push(handle.clone());
}
if self.visibility {
window.handle.show();
}
if self.accept_drag_files {
DragAcceptFiles(hwnd, true);
}
if let Some(icon) = self.icon {
let big = load_icon(&icon, hinst);
let small = load_small_icon(&icon, hinst);
SendMessageW(
HWND(handle.raw_handle() as _),
WM_SETICON,
WPARAM(ICON_BIG as _),
LPARAM(big.0 as _),
);
SendMessageW(
HWND(handle.raw_handle() as _),
WM_SETICON,
WPARAM(ICON_SMALL as _),
LPARAM(small.0 as _),
);
}
if self.enabled_ime {
window.handle.ime(self.enabled_ime);
}
#[cfg(feature = "raw_input")]
raw_input::register_devices(&window.handle, self.raw_input_window_state);
push_window(hwnd, window);
Ok(handle)
}
}
}
pub struct InnerWindowBuilder<W = (), P = (), S = ()> {
parent: W,
position: P,
size: S,
visibility: bool,
visible_ime_composition_window: bool,
visible_ime_candidate_window: bool,
accept_drag_files: bool,
cursor: Cursor,
#[cfg(feature = "raw_input")]
raw_input_window_state: raw_input::WindowState,
}
impl InnerWindowBuilder<(), (), ()> {
#[allow(clippy::new_ret_no_self)]
#[inline]
pub fn new() -> InnerWindowBuilder<(), LogicalPosition<f32>, ()> {
InnerWindowBuilder {
parent: (),
position: LogicalPosition::new(0.0, 0.0),
size: (),
visibility: true,
visible_ime_composition_window: false,
visible_ime_candidate_window: true,
accept_drag_files: false,
cursor: Cursor::Arrow,
#[cfg(feature = "raw_input")]
raw_input_window_state: raw_input::WindowState::Foreground,
}
}
}
impl<W, P, S> InnerWindowBuilder<W, P, S> {
#[inline]
pub fn parent(self, parent: &Window) -> InnerWindowBuilder<Window, P, S> {
InnerWindowBuilder {
parent: parent.clone(),
position: self.position,
size: self.size,
visibility: self.visibility,
visible_ime_composition_window: self.visible_ime_composition_window,
visible_ime_candidate_window: self.visible_ime_candidate_window,
accept_drag_files: self.accept_drag_files,
cursor: self.cursor,
#[cfg(feature = "raw_input")]
raw_input_window_state: self.raw_input_window_state,
}
}
#[inline]
pub fn position<T>(self, position: T) -> InnerWindowBuilder<W, T, S> {
InnerWindowBuilder {
parent: self.parent,
position,
size: self.size,
visibility: self.visibility,
visible_ime_composition_window: self.visible_ime_composition_window,
visible_ime_candidate_window: self.visible_ime_candidate_window,
accept_drag_files: self.accept_drag_files,
cursor: self.cursor,
#[cfg(feature = "raw_input")]
raw_input_window_state: self.raw_input_window_state,
}
}
#[inline]
pub fn size<T>(self, size: T) -> InnerWindowBuilder<W, P, T> {
InnerWindowBuilder {
parent: self.parent,
position: self.position,
size,
visibility: self.visibility,
visible_ime_composition_window: self.visible_ime_composition_window,
visible_ime_candidate_window: self.visible_ime_candidate_window,
accept_drag_files: self.accept_drag_files,
cursor: self.cursor,
#[cfg(feature = "raw_input")]
raw_input_window_state: self.raw_input_window_state,
}
}
#[inline]
pub fn visible(mut self, visibility: bool) -> Self {
self.visibility = visibility;
self
}
#[inline]
pub fn accept_drag_files(mut self) -> Self {
self.accept_drag_files = true;
self
}
}
impl<P, S> InnerWindowBuilder<Window, P, S>
where
P: ToPhysicalPosition<i32>,
S: ToPhysicalSize<u32>,
{
pub fn build(self) -> Result<Window, ApiError> {
unsafe {
let dpi = self.parent.dpi();
let position = self.position.to_physical(dpi as i32);
let size = self.size.to_physical(dpi);
let rc = adjust_window_rect(size, WS_CHILD, WINDOW_EX_STYLE(0), dpi);
let hinst = GetModuleHandleW(PCWSTR::null()).unwrap();
let hwnd = CreateWindowExW(
WINDOW_EX_STYLE(0),
&HSTRING::from(WINDOW_CLASS_NAME),
PCWSTR::null(),
WS_CHILD,
position.x,
position.y,
(rc.right - rc.left) as i32,
(rc.bottom - rc.top) as i32,
HWND(self.parent.raw_handle() as _),
HMENU(0),
hinst,
None,
);
if hwnd == HWND(0) {
return Err(ApiError::new());
}
let window = LocalWindow::new(
hwnd,
WindowState {
title: String::new(),
style: WS_CHILD,
set_position: (position.x, position.y),
set_inner_size: size,
enabled_ime: self.parent.is_enabled_ime(),
visible_ime_composition_window: self.visible_ime_composition_window,
visible_ime_candidate_window: self.visible_ime_candidate_window,
ime_position: PhysicalPosition::new(0, 0),
children: vec![],
cursor: self.cursor,
closed: false,
},
);
let handle = window.handle.clone();
if self.visibility {
window.handle.show();
}
if self.accept_drag_files {
DragAcceptFiles(hwnd, true);
}
#[cfg(feature = "raw_input")]
raw_input::register_devices(&window.handle, self.raw_input_window_state);
push_window(hwnd, window);
Ok(handle)
}
}
}
pub(crate) struct WindowState {
pub title: String,
pub style: WINDOW_STYLE,
pub set_position: (i32, i32),
pub set_inner_size: PhysicalSize<u32>,
pub enabled_ime: bool,
pub visible_ime_composition_window: bool,
pub visible_ime_candidate_window: bool,
pub ime_position: PhysicalPosition<i32>,
pub children: Vec<Window>,
pub closed: bool,
pub cursor: Cursor,
}
#[derive(Clone)]
pub(crate) struct LocalWindow {
pub handle: Window,
pub ime_context: Rc<RefCell<ime::ImmContext>>,
}
impl LocalWindow {
pub(crate) fn new(hwnd: HWND, state: WindowState) -> Self {
Self {
handle: Window {
hwnd: WindowHandle(hwnd),
state: Arc::new(RwLock::new(state)),
},
ime_context: Rc::new(RefCell::new(ime::ImmContext::new(hwnd))),
}
}
}
#[derive(Clone)]
pub struct Window {
pub(crate) hwnd: WindowHandle,
pub(crate) state: Arc<RwLock<WindowState>>,
}
impl Window {
#[inline]
pub fn builder() -> WindowBuilder<&'static str, Size<u32, Logical>> {
WindowBuilder::new()
}
#[inline]
pub fn inner_builder() -> InnerWindowBuilder<(), LogicalPosition<f32>, ()> {
InnerWindowBuilder::new()
}
#[inline]
pub fn title(&self) -> String {
let state = self.state.read().unwrap();
state.title.clone()
}
#[inline]
pub fn set_title(&self, title: impl AsRef<str>) {
let mut state = self.state.write().unwrap();
state.title = title.as_ref().to_string();
unsafe {
PostMessageW(
self.hwnd.0,
WM_USER,
WPARAM(UserMessage::SetTitle as _),
LPARAM(0),
);
}
}
#[inline]
pub fn position(&self) -> ScreenPosition {
unsafe {
let mut rc = RECT::default();
GetWindowRect(self.hwnd.0, &mut rc);
ScreenPosition::new(rc.left, rc.top)
}
}
#[inline]
pub fn set_position(&self, position: ScreenPosition) {
unsafe {
let mut state = self.state.write().unwrap();
state.set_position = (position.x, position.y);
PostMessageW(
self.hwnd.0,
WM_USER,
WPARAM(UserMessage::SetPosition as _),
LPARAM(0),
);
}
}
#[inline]
pub fn inner_size(&self) -> PhysicalSize<u32> {
unsafe {
let mut rc = RECT::default();
GetClientRect(self.hwnd.0, &mut rc);
PhysicalSize::new((rc.right - rc.left) as u32, (rc.bottom - rc.top) as u32)
}
}
#[inline]
pub fn set_inner_size(&self, size: impl ToPhysicalSize<u32>) {
unsafe {
let mut state = self.state.write().unwrap();
state.set_inner_size = size.to_physical(self.dpi());
PostMessageW(
self.hwnd.0,
WM_USER,
WPARAM(UserMessage::SetInnerSize as _),
LPARAM(0),
);
}
}
#[inline]
pub fn dpi(&self) -> u32 {
unsafe { GetDpiForWindow(self.hwnd.0) }
}
#[inline]
pub fn scale_factor(&self) -> f32 {
unsafe { GetDpiForWindow(self.hwnd.0) as f32 / DEFAULT_DPI as f32 }
}
#[inline]
pub fn show(&self) {
unsafe {
if self.is_minimized() {
self.restore();
}
ShowWindowAsync(self.hwnd.0, SW_SHOW);
}
}
#[inline]
pub fn hide(&self) {
unsafe {
ShowWindowAsync(self.hwnd.0, SW_HIDE);
}
}
#[inline]
pub fn minimize(&self) {
unsafe {
ShowWindowAsync(self.hwnd.0, SW_MINIMIZE);
}
}
#[inline]
pub fn maximize(&self) {
unsafe {
ShowWindowAsync(self.hwnd.0, SW_MAXIMIZE);
}
}
#[inline]
pub fn restore(&self) {
unsafe {
ShowWindowAsync(self.hwnd.0, SW_RESTORE);
}
}
#[inline]
pub fn is_minimized(&self) -> bool {
unsafe { IsIconic(self.hwnd.0).as_bool() }
}
#[inline]
pub fn is_maximized(&self) -> bool {
unsafe { IsZoomed(self.hwnd.0).as_bool() }
}
#[inline]
pub fn redraw(&self) {
unsafe {
RedrawWindow(self.hwnd.0, None, HRGN(0), RDW_INTERNALPAINT);
}
}
#[inline]
pub fn is_closed(&self) -> bool {
let state = self.state.read().unwrap();
state.closed
}
#[inline]
pub fn close(&self) {
unsafe {
if !self.is_closed() {
PostMessageW(self.hwnd.0, WM_CLOSE, WPARAM(0), LPARAM(0));
}
}
}
#[inline]
pub fn ime_position(&self) -> PhysicalPosition<i32> {
let state = self.state.read().unwrap();
PhysicalPosition::new(state.ime_position.x, state.ime_position.y)
}
#[inline]
pub fn ime(&self, enable: bool) {
unsafe {
if enable {
PostMessageW(
self.hwnd.0,
WM_USER,
WPARAM(UserMessage::EnableIme as _),
LPARAM(0),
);
} else {
PostMessageW(
self.hwnd.0,
WM_USER,
WPARAM(UserMessage::DisableIme as _),
LPARAM(0),
);
}
}
let mut state = self.state.write().unwrap();
state.enabled_ime = enable;
}
#[inline]
pub fn set_ime_position(&self, position: impl ToPhysicalPosition<i32>) {
let mut state = self.state.write().unwrap();
let position = position.to_physical(self.dpi() as i32);
state.ime_position.x = position.x;
state.ime_position.y = position.y;
let imc = ime::Imc::get(self.hwnd.0);
if state.visible_ime_composition_window {
imc.set_composition_window_position(state.ime_position);
}
if state.visible_ime_candidate_window {
imc.set_candidate_window_position(
state.ime_position,
state.visible_ime_composition_window,
);
}
}
#[inline]
pub fn is_enabled_ime(&self) -> bool {
let state = self.state.read().unwrap();
state.enabled_ime
}
#[inline]
pub fn style(&self) -> WindowStyle {
let state = self.state.read().unwrap();
WindowStyle(state.style)
}
#[inline]
pub fn set_style(&self, style: impl Style) {
unsafe {
let mut state = self.state.write().unwrap();
state.style = style.value();
PostMessageW(
self.hwnd.0,
WM_USER,
WPARAM(UserMessage::SetStyle as _),
LPARAM(0),
);
}
}
#[inline]
pub fn accept_drag_files(&self, enabled: bool) {
unsafe {
PostMessageW(
self.hwnd.0,
WM_USER,
WPARAM(UserMessage::AcceptDragFiles as _),
LPARAM(if enabled { 1 } else { 0 }),
);
}
}
#[inline]
pub fn set_cursor(&self, cursor: Cursor) {
let mut state = self.state.write().unwrap();
state.cursor = cursor;
cursor.set();
}
#[inline]
pub fn raw_handle(&self) -> *mut std::ffi::c_void {
self.hwnd.0 .0 as _
}
}
impl PartialEq for Window {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.hwnd == other.hwnd
}
}
impl Eq for Window {}
unsafe impl HasRawWindowHandle for Window {
#[inline]
fn raw_window_handle(&self) -> RawWindowHandle {
let mut handle = Win32WindowHandle::empty();
handle.hinstance =
unsafe { GetModuleHandleW(PCWSTR::null()).unwrap().0 as *mut std::ffi::c_void };
handle.hwnd = self.hwnd.0 .0 as *mut std::ffi::c_void;
RawWindowHandle::Win32(handle)
}
}