Detailed changes
@@ -1,14 +1,18 @@
mod direct_write;
mod dispatcher;
mod display;
+mod events;
mod platform;
+mod system_settings;
mod util;
mod window;
pub(crate) use direct_write::*;
pub(crate) use dispatcher::*;
pub(crate) use display::*;
+pub(crate) use events::*;
pub(crate) use platform::*;
+pub(crate) use system_settings::*;
pub(crate) use util::*;
pub(crate) use window::*;
@@ -9,7 +9,7 @@ use windows::{
use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay, Point, Size};
-#[derive(Debug)]
+#[derive(Debug, Clone, Copy)]
pub(crate) struct WindowsDisplay {
pub handle: HMONITOR,
pub display_id: DisplayId,
@@ -0,0 +1,1274 @@
+use std::rc::Rc;
+
+use ::util::ResultExt;
+use anyhow::Context;
+use windows::Win32::{
+ Foundation::*,
+ Graphics::Gdi::*,
+ System::SystemServices::*,
+ UI::{
+ HiDpi::*,
+ Input::{Ime::*, KeyboardAndMouse::*},
+ WindowsAndMessaging::*,
+ },
+};
+
+use crate::*;
+
+pub(crate) const CURSOR_STYLE_CHANGED: u32 = WM_USER + 1;
+pub(crate) const MOUSE_WHEEL_SETTINGS_CHANGED: u32 = WM_USER + 2;
+pub(crate) const MOUSE_WHEEL_SETTINGS_SCROLL_CHARS_CHANGED: isize = 1;
+pub(crate) const MOUSE_WHEEL_SETTINGS_SCROLL_LINES_CHANGED: isize = 2;
+pub(crate) const CLOSE_ONE_WINDOW: u32 = WM_USER + 3;
+const SIZE_MOVE_LOOP_TIMER_ID: usize = 1;
+
+pub(crate) fn handle_msg(
+ handle: HWND,
+ msg: u32,
+ wparam: WPARAM,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> LRESULT {
+ let handled = match msg {
+ WM_ACTIVATE => handle_activate_msg(handle, wparam, state_ptr),
+ WM_CREATE => handle_create_msg(handle, state_ptr),
+ WM_MOVE => handle_move_msg(handle, lparam, state_ptr),
+ WM_SIZE => handle_size_msg(lparam, state_ptr),
+ WM_ENTERSIZEMOVE | WM_ENTERMENULOOP => handle_size_move_loop(handle),
+ WM_EXITSIZEMOVE | WM_EXITMENULOOP => handle_size_move_loop_exit(handle),
+ WM_TIMER => handle_timer_msg(handle, wparam, state_ptr),
+ WM_NCCALCSIZE => handle_calc_client_size(handle, wparam, lparam, state_ptr),
+ WM_DPICHANGED => handle_dpi_changed_msg(handle, wparam, lparam, state_ptr),
+ WM_NCHITTEST => handle_hit_test_msg(handle, msg, wparam, lparam, state_ptr),
+ WM_PAINT => handle_paint_msg(handle, state_ptr),
+ WM_CLOSE => handle_close_msg(state_ptr),
+ WM_DESTROY => handle_destroy_msg(handle, state_ptr),
+ WM_MOUSEMOVE => handle_mouse_move_msg(lparam, wparam, state_ptr),
+ WM_NCMOUSEMOVE => handle_nc_mouse_move_msg(handle, lparam, state_ptr),
+ WM_NCLBUTTONDOWN => {
+ handle_nc_mouse_down_msg(handle, MouseButton::Left, wparam, lparam, state_ptr)
+ }
+ WM_NCRBUTTONDOWN => {
+ handle_nc_mouse_down_msg(handle, MouseButton::Right, wparam, lparam, state_ptr)
+ }
+ WM_NCMBUTTONDOWN => {
+ handle_nc_mouse_down_msg(handle, MouseButton::Middle, wparam, lparam, state_ptr)
+ }
+ WM_NCLBUTTONUP => {
+ handle_nc_mouse_up_msg(handle, MouseButton::Left, wparam, lparam, state_ptr)
+ }
+ WM_NCRBUTTONUP => {
+ handle_nc_mouse_up_msg(handle, MouseButton::Right, wparam, lparam, state_ptr)
+ }
+ WM_NCMBUTTONUP => {
+ handle_nc_mouse_up_msg(handle, MouseButton::Middle, wparam, lparam, state_ptr)
+ }
+ WM_LBUTTONDOWN => handle_mouse_down_msg(MouseButton::Left, lparam, state_ptr),
+ WM_RBUTTONDOWN => handle_mouse_down_msg(MouseButton::Right, lparam, state_ptr),
+ WM_MBUTTONDOWN => handle_mouse_down_msg(MouseButton::Middle, lparam, state_ptr),
+ WM_XBUTTONDOWN => handle_xbutton_msg(wparam, lparam, handle_mouse_down_msg, state_ptr),
+ WM_LBUTTONUP => handle_mouse_up_msg(MouseButton::Left, lparam, state_ptr),
+ WM_RBUTTONUP => handle_mouse_up_msg(MouseButton::Right, lparam, state_ptr),
+ WM_MBUTTONUP => handle_mouse_up_msg(MouseButton::Middle, lparam, state_ptr),
+ WM_XBUTTONUP => handle_xbutton_msg(wparam, lparam, handle_mouse_up_msg, state_ptr),
+ WM_MOUSEWHEEL => handle_mouse_wheel_msg(handle, wparam, lparam, state_ptr),
+ WM_MOUSEHWHEEL => handle_mouse_horizontal_wheel_msg(handle, wparam, lparam, state_ptr),
+ WM_SYSKEYDOWN => handle_syskeydown_msg(handle, wparam, lparam, state_ptr),
+ WM_SYSKEYUP => handle_syskeyup_msg(handle, wparam, state_ptr),
+ WM_KEYDOWN => handle_keydown_msg(handle, wparam, lparam, state_ptr),
+ WM_KEYUP => handle_keyup_msg(handle, wparam, state_ptr),
+ WM_CHAR => handle_char_msg(handle, wparam, lparam, state_ptr),
+ WM_IME_STARTCOMPOSITION => handle_ime_position(handle, state_ptr),
+ WM_IME_COMPOSITION => handle_ime_composition(handle, lparam, state_ptr),
+ WM_SETCURSOR => handle_set_cursor(lparam, state_ptr),
+ CURSOR_STYLE_CHANGED => handle_cursor_changed(lparam, state_ptr),
+ MOUSE_WHEEL_SETTINGS_CHANGED => handle_mouse_wheel_settings_msg(wparam, lparam, state_ptr),
+ _ => None,
+ };
+ if let Some(n) = handled {
+ LRESULT(n)
+ } else {
+ unsafe { DefWindowProcW(handle, msg, wparam, lparam) }
+ }
+}
+
+fn handle_move_msg(
+ handle: HWND,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ let x = lparam.signed_loword() as i32;
+ let y = lparam.signed_hiword() as i32;
+ let mut lock = state_ptr.state.borrow_mut();
+ lock.origin = point(x.into(), y.into());
+ let size = lock.physical_size;
+ let center_x = x + size.width.0 / 2;
+ let center_y = y + size.height.0 / 2;
+ let monitor_bounds = lock.display.bounds();
+ if center_x < monitor_bounds.left().0
+ || center_x > monitor_bounds.right().0
+ || center_y < monitor_bounds.top().0
+ || center_y > monitor_bounds.bottom().0
+ {
+ // center of the window may have moved to another monitor
+ let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };
+ if !monitor.is_invalid() && lock.display.handle != monitor {
+ // we will get the same monitor if we only have one
+ lock.display = WindowsDisplay::new_with_handle(monitor);
+ }
+ }
+ if let Some(mut callback) = lock.callbacks.moved.take() {
+ drop(lock);
+ callback();
+ state_ptr.state.borrow_mut().callbacks.moved = Some(callback);
+ }
+ Some(0)
+}
+
+fn handle_size_msg(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
+ let width = lparam.loword().max(1) as i32;
+ let height = lparam.hiword().max(1) as i32;
+ let new_physical_size = size(width.into(), height.into());
+ let mut lock = state_ptr.state.borrow_mut();
+ let scale_factor = lock.scale_factor;
+ lock.physical_size = new_physical_size;
+ lock.renderer.update_drawable_size(Size {
+ width: width as f64,
+ height: height as f64,
+ });
+ if let Some(mut callback) = lock.callbacks.resize.take() {
+ drop(lock);
+ let logical_size = logical_size(new_physical_size, scale_factor);
+ callback(logical_size, scale_factor);
+ state_ptr.state.borrow_mut().callbacks.resize = Some(callback);
+ }
+ Some(0)
+}
+
+fn handle_size_move_loop(handle: HWND) -> Option<isize> {
+ unsafe {
+ let ret = SetTimer(handle, SIZE_MOVE_LOOP_TIMER_ID, USER_TIMER_MINIMUM, None);
+ if ret == 0 {
+ log::error!(
+ "unable to create timer: {}",
+ std::io::Error::last_os_error()
+ );
+ }
+ }
+ None
+}
+
+fn handle_size_move_loop_exit(handle: HWND) -> Option<isize> {
+ unsafe {
+ KillTimer(handle, SIZE_MOVE_LOOP_TIMER_ID).log_err();
+ }
+ None
+}
+
+fn handle_timer_msg(
+ handle: HWND,
+ wparam: WPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID {
+ for runnable in state_ptr.main_receiver.drain() {
+ runnable.run();
+ }
+ handle_paint_msg(handle, state_ptr)
+ } else {
+ None
+ }
+}
+
+fn handle_paint_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
+ let mut paint_struct = PAINTSTRUCT::default();
+ let _hdc = unsafe { BeginPaint(handle, &mut paint_struct) };
+ let mut lock = state_ptr.state.borrow_mut();
+ if let Some(mut request_frame) = lock.callbacks.request_frame.take() {
+ drop(lock);
+ request_frame();
+ state_ptr.state.borrow_mut().callbacks.request_frame = Some(request_frame);
+ }
+ unsafe { EndPaint(handle, &paint_struct) };
+ Some(0)
+}
+
+fn handle_close_msg(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
+ let mut lock = state_ptr.state.borrow_mut();
+ if let Some(mut callback) = lock.callbacks.should_close.take() {
+ drop(lock);
+ let should_close = callback();
+ state_ptr.state.borrow_mut().callbacks.should_close = Some(callback);
+ if should_close {
+ None
+ } else {
+ Some(0)
+ }
+ } else {
+ None
+ }
+}
+
+fn handle_destroy_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
+ let callback = {
+ let mut lock = state_ptr.state.borrow_mut();
+ lock.callbacks.close.take()
+ };
+ if let Some(callback) = callback {
+ callback();
+ }
+ unsafe {
+ PostMessageW(None, CLOSE_ONE_WINDOW, None, LPARAM(handle.0)).log_err();
+ }
+ Some(0)
+}
+
+fn handle_mouse_move_msg(
+ lparam: LPARAM,
+ wparam: WPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ let mut lock = state_ptr.state.borrow_mut();
+ if let Some(mut callback) = lock.callbacks.input.take() {
+ let scale_factor = lock.scale_factor;
+ drop(lock);
+ let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
+ flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
+ flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
+ flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
+ flags if flags.contains(MK_XBUTTON1) => {
+ Some(MouseButton::Navigate(NavigationDirection::Back))
+ }
+ flags if flags.contains(MK_XBUTTON2) => {
+ Some(MouseButton::Navigate(NavigationDirection::Forward))
+ }
+ _ => None,
+ };
+ let x = lparam.signed_loword() as f32;
+ let y = lparam.signed_hiword() as f32;
+ let event = MouseMoveEvent {
+ position: logical_point(x, y, scale_factor),
+ pressed_button,
+ modifiers: current_modifiers(),
+ };
+ let result = if callback(PlatformInput::MouseMove(event)).default_prevented {
+ Some(0)
+ } else {
+ Some(1)
+ };
+ state_ptr.state.borrow_mut().callbacks.input = Some(callback);
+ return result;
+ }
+ Some(1)
+}
+
+fn handle_syskeydown_msg(
+ handle: HWND,
+ wparam: WPARAM,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
+ // shortcuts.
+ let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else {
+ return None;
+ };
+ let mut lock = state_ptr.state.borrow_mut();
+ let Some(mut func) = lock.callbacks.input.take() else {
+ return None;
+ };
+ drop(lock);
+ let event = KeyDownEvent {
+ keystroke,
+ is_held: lparam.0 & (0x1 << 30) > 0,
+ };
+ let result = if func(PlatformInput::KeyDown(event)).default_prevented {
+ invalidate_client_area(handle);
+ Some(0)
+ } else {
+ None
+ };
+ state_ptr.state.borrow_mut().callbacks.input = Some(func);
+
+ result
+}
+
+fn handle_syskeyup_msg(
+ handle: HWND,
+ wparam: WPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
+ // shortcuts.
+ let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else {
+ return None;
+ };
+ let mut lock = state_ptr.state.borrow_mut();
+ let Some(mut func) = lock.callbacks.input.take() else {
+ return None;
+ };
+ drop(lock);
+ let event = KeyUpEvent { keystroke };
+ let result = if func(PlatformInput::KeyUp(event)).default_prevented {
+ invalidate_client_area(handle);
+ Some(0)
+ } else {
+ Some(1)
+ };
+ state_ptr.state.borrow_mut().callbacks.input = Some(func);
+
+ result
+}
+
+fn handle_keydown_msg(
+ handle: HWND,
+ wparam: WPARAM,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ let Some(keystroke) = parse_keydown_msg_keystroke(wparam) else {
+ return Some(1);
+ };
+ let mut lock = state_ptr.state.borrow_mut();
+ let Some(mut func) = lock.callbacks.input.take() else {
+ return Some(1);
+ };
+ drop(lock);
+ let event = KeyDownEvent {
+ keystroke,
+ is_held: lparam.0 & (0x1 << 30) > 0,
+ };
+ let result = if func(PlatformInput::KeyDown(event)).default_prevented {
+ invalidate_client_area(handle);
+ Some(0)
+ } else {
+ Some(1)
+ };
+ state_ptr.state.borrow_mut().callbacks.input = Some(func);
+
+ result
+}
+
+fn handle_keyup_msg(
+ handle: HWND,
+ wparam: WPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ let Some(keystroke) = parse_keydown_msg_keystroke(wparam) else {
+ return Some(1);
+ };
+ let mut lock = state_ptr.state.borrow_mut();
+ let Some(mut func) = lock.callbacks.input.take() else {
+ return Some(1);
+ };
+ drop(lock);
+ let event = KeyUpEvent { keystroke };
+ let result = if func(PlatformInput::KeyUp(event)).default_prevented {
+ invalidate_client_area(handle);
+ Some(0)
+ } else {
+ Some(1)
+ };
+ state_ptr.state.borrow_mut().callbacks.input = Some(func);
+
+ result
+}
+
+fn handle_char_msg(
+ handle: HWND,
+ wparam: WPARAM,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ let Some(keystroke) = parse_char_msg_keystroke(wparam) else {
+ return Some(1);
+ };
+ let mut lock = state_ptr.state.borrow_mut();
+ let Some(mut func) = lock.callbacks.input.take() else {
+ return Some(1);
+ };
+ drop(lock);
+ let ime_key = keystroke.ime_key.clone();
+ let event = KeyDownEvent {
+ keystroke,
+ is_held: lparam.0 & (0x1 << 30) > 0,
+ };
+
+ let dispatch_event_result = func(PlatformInput::KeyDown(event));
+ let mut lock = state_ptr.state.borrow_mut();
+ lock.callbacks.input = Some(func);
+ if dispatch_event_result.default_prevented || !dispatch_event_result.propagate {
+ invalidate_client_area(handle);
+ return Some(0);
+ }
+ let Some(ime_char) = ime_key else {
+ return Some(1);
+ };
+ let Some(mut input_handler) = lock.input_handler.take() else {
+ return Some(1);
+ };
+ drop(lock);
+ input_handler.replace_text_in_range(None, &ime_char);
+ invalidate_client_area(handle);
+ state_ptr.state.borrow_mut().input_handler = Some(input_handler);
+
+ Some(0)
+}
+
+fn handle_mouse_down_msg(
+ button: MouseButton,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ let mut lock = state_ptr.state.borrow_mut();
+ if let Some(mut callback) = lock.callbacks.input.take() {
+ let x = lparam.signed_loword() as f32;
+ let y = lparam.signed_hiword() as f32;
+ let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
+ let click_count = lock.click_state.update(button, physical_point);
+ let scale_factor = lock.scale_factor;
+ drop(lock);
+
+ let event = MouseDownEvent {
+ button,
+ position: logical_point(x, y, scale_factor),
+ modifiers: current_modifiers(),
+ click_count,
+ first_mouse: false,
+ };
+ let result = if callback(PlatformInput::MouseDown(event)).default_prevented {
+ Some(0)
+ } else {
+ Some(1)
+ };
+ state_ptr.state.borrow_mut().callbacks.input = Some(callback);
+
+ result
+ } else {
+ Some(1)
+ }
+}
+
+fn handle_mouse_up_msg(
+ button: MouseButton,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ let mut lock = state_ptr.state.borrow_mut();
+ if let Some(mut callback) = lock.callbacks.input.take() {
+ let x = lparam.signed_loword() as f32;
+ let y = lparam.signed_hiword() as f32;
+ let click_count = lock.click_state.current_count;
+ let scale_factor = lock.scale_factor;
+ drop(lock);
+
+ let event = MouseUpEvent {
+ button,
+ position: logical_point(x, y, scale_factor),
+ modifiers: current_modifiers(),
+ click_count,
+ };
+ let result = if callback(PlatformInput::MouseUp(event)).default_prevented {
+ Some(0)
+ } else {
+ Some(1)
+ };
+ state_ptr.state.borrow_mut().callbacks.input = Some(callback);
+
+ result
+ } else {
+ Some(1)
+ }
+}
+
+fn handle_xbutton_msg(
+ wparam: WPARAM,
+ lparam: LPARAM,
+ handler: impl Fn(MouseButton, LPARAM, Rc<WindowsWindowStatePtr>) -> Option<isize>,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ let nav_dir = match wparam.hiword() {
+ XBUTTON1 => NavigationDirection::Back,
+ XBUTTON2 => NavigationDirection::Forward,
+ _ => return Some(1),
+ };
+ handler(MouseButton::Navigate(nav_dir), lparam, state_ptr)
+}
+
+fn handle_mouse_wheel_msg(
+ handle: HWND,
+ wparam: WPARAM,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ let mut lock = state_ptr.state.borrow_mut();
+ if let Some(mut callback) = lock.callbacks.input.take() {
+ let scale_factor = lock.scale_factor;
+ let wheel_scroll_lines = lock.mouse_wheel_settings.wheel_scroll_lines;
+ drop(lock);
+ let wheel_distance =
+ (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_lines as f32;
+ let mut cursor_point = POINT {
+ x: lparam.signed_loword().into(),
+ y: lparam.signed_hiword().into(),
+ };
+ unsafe { ScreenToClient(handle, &mut cursor_point) };
+ let event = ScrollWheelEvent {
+ position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
+ delta: ScrollDelta::Lines(Point {
+ x: 0.0,
+ y: wheel_distance,
+ }),
+ modifiers: current_modifiers(),
+ touch_phase: TouchPhase::Moved,
+ };
+ let result = if callback(PlatformInput::ScrollWheel(event)).default_prevented {
+ Some(0)
+ } else {
+ Some(1)
+ };
+ state_ptr.state.borrow_mut().callbacks.input = Some(callback);
+
+ result
+ } else {
+ Some(1)
+ }
+}
+
+fn handle_mouse_horizontal_wheel_msg(
+ handle: HWND,
+ wparam: WPARAM,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ let mut lock = state_ptr.state.borrow_mut();
+ if let Some(mut callback) = lock.callbacks.input.take() {
+ let scale_factor = lock.scale_factor;
+ let wheel_scroll_chars = lock.mouse_wheel_settings.wheel_scroll_chars;
+ drop(lock);
+ let wheel_distance =
+ (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_chars as f32;
+ let mut cursor_point = POINT {
+ x: lparam.signed_loword().into(),
+ y: lparam.signed_hiword().into(),
+ };
+ unsafe { ScreenToClient(handle, &mut cursor_point) };
+ let event = ScrollWheelEvent {
+ position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
+ delta: ScrollDelta::Lines(Point {
+ x: wheel_distance,
+ y: 0.0,
+ }),
+ modifiers: current_modifiers(),
+ touch_phase: TouchPhase::Moved,
+ };
+ let result = if callback(PlatformInput::ScrollWheel(event)).default_prevented {
+ Some(0)
+ } else {
+ Some(1)
+ };
+ state_ptr.state.borrow_mut().callbacks.input = Some(callback);
+
+ result
+ } else {
+ Some(1)
+ }
+}
+
+fn handle_ime_position(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
+ unsafe {
+ let mut lock = state_ptr.state.borrow_mut();
+ let ctx = ImmGetContext(handle);
+ let Some(mut input_handler) = lock.input_handler.take() else {
+ return Some(1);
+ };
+ let scale_factor = lock.scale_factor;
+ drop(lock);
+
+ let caret_range = input_handler.selected_text_range().unwrap_or_default();
+ let caret_position = input_handler.bounds_for_range(caret_range).unwrap();
+ state_ptr.state.borrow_mut().input_handler = Some(input_handler);
+ let config = CANDIDATEFORM {
+ dwStyle: CFS_CANDIDATEPOS,
+ // logical to physical
+ ptCurrentPos: POINT {
+ x: (caret_position.origin.x.0 * scale_factor) as i32,
+ y: (caret_position.origin.y.0 * scale_factor) as i32
+ + ((caret_position.size.height.0 * scale_factor) as i32 / 2),
+ },
+ ..Default::default()
+ };
+ ImmSetCandidateWindow(ctx, &config as _);
+ ImmReleaseContext(handle, ctx);
+ Some(0)
+ }
+}
+
+fn handle_ime_composition(
+ handle: HWND,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ let mut ime_input = None;
+ if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
+ let Some((string, string_len)) = parse_ime_compostion_string(handle) else {
+ return None;
+ };
+ let mut lock = state_ptr.state.borrow_mut();
+ let Some(mut input_handler) = lock.input_handler.take() else {
+ return None;
+ };
+ drop(lock);
+ input_handler.replace_and_mark_text_in_range(None, string.as_str(), Some(0..string_len));
+ state_ptr.state.borrow_mut().input_handler = Some(input_handler);
+ ime_input = Some(string);
+ }
+ if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
+ let Some(ref comp_string) = ime_input else {
+ return None;
+ };
+ let caret_pos = retrieve_composition_cursor_position(handle);
+ let mut lock = state_ptr.state.borrow_mut();
+ let Some(mut input_handler) = lock.input_handler.take() else {
+ return None;
+ };
+ drop(lock);
+ input_handler.replace_and_mark_text_in_range(None, comp_string, Some(0..caret_pos));
+ state_ptr.state.borrow_mut().input_handler = Some(input_handler);
+ }
+ if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 {
+ let Some(comp_result) = parse_ime_compostion_result(handle) else {
+ return None;
+ };
+ let mut lock = state_ptr.state.borrow_mut();
+ let Some(mut input_handler) = lock.input_handler.take() else {
+ return Some(1);
+ };
+ drop(lock);
+ input_handler.replace_text_in_range(None, &comp_result);
+ state_ptr.state.borrow_mut().input_handler = Some(input_handler);
+ invalidate_client_area(handle);
+ return Some(0);
+ }
+ // currently, we don't care other stuff
+ None
+}
+
+/// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
+fn handle_calc_client_size(
+ handle: HWND,
+ wparam: WPARAM,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ if !state_ptr.hide_title_bar || state_ptr.state.borrow().is_fullscreen() {
+ return None;
+ }
+
+ if wparam.0 == 0 {
+ return None;
+ }
+
+ let dpi = unsafe { GetDpiForWindow(handle) };
+
+ let frame_x = unsafe { GetSystemMetricsForDpi(SM_CXFRAME, dpi) };
+ let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
+ let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
+
+ // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
+ let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
+ let mut requested_client_rect = unsafe { &mut ((*params).rgrc) };
+
+ requested_client_rect[0].right -= frame_x + padding;
+ requested_client_rect[0].left += frame_x + padding;
+ requested_client_rect[0].bottom -= frame_y + padding;
+
+ Some(0)
+}
+
+fn handle_activate_msg(
+ handle: HWND,
+ wparam: WPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ let activated = wparam.loword() > 0;
+ if state_ptr.hide_title_bar {
+ if let Some(titlebar_rect) = state_ptr.state.borrow().get_titlebar_rect().log_err() {
+ unsafe { InvalidateRect(handle, Some(&titlebar_rect), FALSE) };
+ }
+ }
+ let this = state_ptr.clone();
+ state_ptr
+ .executor
+ .spawn(async move {
+ let mut lock = this.state.borrow_mut();
+ if let Some(mut cb) = lock.callbacks.active_status_change.take() {
+ drop(lock);
+ cb(activated);
+ this.state.borrow_mut().callbacks.active_status_change = Some(cb);
+ }
+ })
+ .detach();
+
+ None
+}
+
+fn handle_create_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
+ let mut size_rect = RECT::default();
+ unsafe { GetWindowRect(handle, &mut size_rect).log_err() };
+
+ let width = size_rect.right - size_rect.left;
+ let height = size_rect.bottom - size_rect.top;
+
+ if state_ptr.hide_title_bar {
+ unsafe {
+ SetWindowPos(
+ handle,
+ None,
+ size_rect.left,
+ size_rect.top,
+ width,
+ height,
+ SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE,
+ )
+ .log_err()
+ };
+ }
+
+ Some(0)
+}
+
+fn handle_dpi_changed_msg(
+ handle: HWND,
+ wparam: WPARAM,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ let new_dpi = wparam.loword() as f32;
+ state_ptr.state.borrow_mut().scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
+
+ let rect = unsafe { &*(lparam.0 as *const RECT) };
+ let width = rect.right - rect.left;
+ let height = rect.bottom - rect.top;
+ // this will emit `WM_SIZE` and `WM_MOVE` right here
+ // even before this function returns
+ // the new size is handled in `WM_SIZE`
+ unsafe {
+ SetWindowPos(
+ handle,
+ None,
+ rect.left,
+ rect.top,
+ width,
+ height,
+ SWP_NOZORDER | SWP_NOACTIVATE,
+ )
+ .context("unable to set window position after dpi has changed")
+ .log_err();
+ }
+ invalidate_client_area(handle);
+
+ Some(0)
+}
+
+fn handle_hit_test_msg(
+ handle: HWND,
+ msg: u32,
+ wparam: WPARAM,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ if !state_ptr.hide_title_bar {
+ return None;
+ }
+
+ // default handler for resize areas
+ let hit = unsafe { DefWindowProcW(handle, msg, wparam, lparam) };
+ if matches!(
+ hit.0 as u32,
+ HTNOWHERE
+ | HTRIGHT
+ | HTLEFT
+ | HTTOPLEFT
+ | HTTOP
+ | HTTOPRIGHT
+ | HTBOTTOMRIGHT
+ | HTBOTTOM
+ | HTBOTTOMLEFT
+ ) {
+ return Some(hit.0);
+ }
+
+ if state_ptr.state.borrow().is_fullscreen() {
+ return Some(HTCLIENT as _);
+ }
+
+ let dpi = unsafe { GetDpiForWindow(handle) };
+ let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
+ let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
+
+ let mut cursor_point = POINT {
+ x: lparam.signed_loword().into(),
+ y: lparam.signed_hiword().into(),
+ };
+ unsafe { ScreenToClient(handle, &mut cursor_point) };
+ if cursor_point.y > 0 && cursor_point.y < frame_y + padding {
+ return Some(HTTOP as _);
+ }
+
+ let titlebar_rect = state_ptr.state.borrow().get_titlebar_rect();
+ if let Ok(titlebar_rect) = titlebar_rect {
+ if cursor_point.y < titlebar_rect.bottom {
+ let caption_btn_width = (state_ptr.state.borrow().caption_button_width().0
+ * state_ptr.state.borrow().scale_factor) as i32;
+ if cursor_point.x >= titlebar_rect.right - caption_btn_width {
+ return Some(HTCLOSE as _);
+ } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 {
+ return Some(HTMAXBUTTON as _);
+ } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 {
+ return Some(HTMINBUTTON as _);
+ }
+
+ return Some(HTCAPTION as _);
+ }
+ }
+
+ Some(HTCLIENT as _)
+}
+
+fn handle_nc_mouse_move_msg(
+ handle: HWND,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ if !state_ptr.hide_title_bar {
+ return None;
+ }
+
+ let mut lock = state_ptr.state.borrow_mut();
+ if let Some(mut callback) = lock.callbacks.input.take() {
+ let scale_factor = lock.scale_factor;
+ drop(lock);
+ let mut cursor_point = POINT {
+ x: lparam.signed_loword().into(),
+ y: lparam.signed_hiword().into(),
+ };
+ unsafe { ScreenToClient(handle, &mut cursor_point) };
+ let event = MouseMoveEvent {
+ position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
+ pressed_button: None,
+ modifiers: current_modifiers(),
+ };
+ let result = if callback(PlatformInput::MouseMove(event)).default_prevented {
+ Some(0)
+ } else {
+ Some(1)
+ };
+ state_ptr.state.borrow_mut().callbacks.input = Some(callback);
+
+ result
+ } else {
+ None
+ }
+}
+
+fn handle_nc_mouse_down_msg(
+ handle: HWND,
+ button: MouseButton,
+ wparam: WPARAM,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ if !state_ptr.hide_title_bar {
+ return None;
+ }
+
+ let mut lock = state_ptr.state.borrow_mut();
+ let result = if let Some(mut callback) = lock.callbacks.input.take() {
+ let scale_factor = lock.scale_factor;
+ let mut cursor_point = POINT {
+ x: lparam.signed_loword().into(),
+ y: lparam.signed_hiword().into(),
+ };
+ unsafe { ScreenToClient(handle, &mut cursor_point) };
+ let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
+ let click_count = lock.click_state.update(button, physical_point);
+ drop(lock);
+ let event = MouseDownEvent {
+ button,
+ position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
+ modifiers: current_modifiers(),
+ click_count,
+ first_mouse: false,
+ };
+ let result = if callback(PlatformInput::MouseDown(event)).default_prevented {
+ Some(0)
+ } else {
+ None
+ };
+ state_ptr.state.borrow_mut().callbacks.input = Some(callback);
+
+ result
+ } else {
+ None
+ };
+
+ // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
+ result.or_else(|| matches!(wparam.0 as u32, HTMINBUTTON | HTMAXBUTTON | HTCLOSE).then_some(0))
+}
+
+fn handle_nc_mouse_up_msg(
+ handle: HWND,
+ button: MouseButton,
+ wparam: WPARAM,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ if !state_ptr.hide_title_bar {
+ return None;
+ }
+
+ let mut lock = state_ptr.state.borrow_mut();
+ if let Some(mut callback) = lock.callbacks.input.take() {
+ let scale_factor = lock.scale_factor;
+ drop(lock);
+ let mut cursor_point = POINT {
+ x: lparam.signed_loword().into(),
+ y: lparam.signed_hiword().into(),
+ };
+ unsafe { ScreenToClient(handle, &mut cursor_point) };
+ let event = MouseUpEvent {
+ button,
+ position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
+ modifiers: current_modifiers(),
+ click_count: 1,
+ };
+ let result = if callback(PlatformInput::MouseUp(event)).default_prevented {
+ Some(0)
+ } else {
+ None
+ };
+ state_ptr.state.borrow_mut().callbacks.input = Some(callback);
+ if result.is_some() {
+ return result;
+ }
+ } else {
+ drop(lock);
+ }
+
+ if button == MouseButton::Left {
+ match wparam.0 as u32 {
+ HTMINBUTTON => unsafe {
+ ShowWindowAsync(handle, SW_MINIMIZE);
+ },
+ HTMAXBUTTON => unsafe {
+ if state_ptr.state.borrow().is_maximized() {
+ ShowWindowAsync(handle, SW_NORMAL);
+ } else {
+ ShowWindowAsync(handle, SW_MAXIMIZE);
+ }
+ },
+ HTCLOSE => unsafe {
+ PostMessageW(handle, WM_CLOSE, WPARAM::default(), LPARAM::default()).log_err();
+ },
+ _ => return None,
+ };
+ return Some(0);
+ }
+
+ None
+}
+
+fn handle_cursor_changed(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
+ state_ptr.state.borrow_mut().current_cursor = HCURSOR(lparam.0);
+ Some(0)
+}
+
+fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
+ if matches!(
+ lparam.loword() as u32,
+ HTLEFT | HTRIGHT | HTTOP | HTTOPLEFT | HTTOPRIGHT | HTBOTTOM | HTBOTTOMLEFT | HTBOTTOMRIGHT
+ ) {
+ return None;
+ }
+ unsafe { SetCursor(state_ptr.state.borrow().current_cursor) };
+ Some(1)
+}
+
+fn handle_mouse_wheel_settings_msg(
+ wparam: WPARAM,
+ lparam: LPARAM,
+ state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
+ match lparam.0 {
+ 1 => {
+ state_ptr
+ .state
+ .borrow_mut()
+ .mouse_wheel_settings
+ .wheel_scroll_chars = wparam.0 as u32
+ }
+ 2 => {
+ state_ptr
+ .state
+ .borrow_mut()
+ .mouse_wheel_settings
+ .wheel_scroll_lines = wparam.0 as u32
+ }
+ _ => unreachable!(),
+ }
+ Some(0)
+}
+
+fn parse_syskeydown_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
+ let modifiers = current_modifiers();
+ if !modifiers.alt {
+ // on Windows, F10 can trigger this event, not just the alt key
+ // and we just don't care about F10
+ return None;
+ }
+
+ let vk_code = wparam.loword();
+ let basic_key = basic_vkcode_to_string(vk_code, modifiers);
+ if basic_key.is_some() {
+ return basic_key;
+ }
+
+ let key = match VIRTUAL_KEY(vk_code) {
+ VK_BACK => Some("backspace"),
+ VK_RETURN => Some("enter"),
+ VK_TAB => Some("tab"),
+ VK_UP => Some("up"),
+ VK_DOWN => Some("down"),
+ VK_RIGHT => Some("right"),
+ VK_LEFT => Some("left"),
+ VK_HOME => Some("home"),
+ VK_END => Some("end"),
+ VK_PRIOR => Some("pageup"),
+ VK_NEXT => Some("pagedown"),
+ VK_ESCAPE => Some("escape"),
+ VK_INSERT => Some("insert"),
+ _ => None,
+ };
+
+ if let Some(key) = key {
+ Some(Keystroke {
+ modifiers,
+ key: key.to_string(),
+ ime_key: None,
+ })
+ } else {
+ None
+ }
+}
+
+fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
+ let vk_code = wparam.loword();
+
+ let modifiers = current_modifiers();
+ if modifiers.control || modifiers.alt {
+ let basic_key = basic_vkcode_to_string(vk_code, modifiers);
+ if basic_key.is_some() {
+ return basic_key;
+ }
+ }
+
+ if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 {
+ let offset = vk_code - VK_F1.0;
+ return Some(Keystroke {
+ modifiers,
+ key: format!("f{}", offset + 1),
+ ime_key: None,
+ });
+ }
+
+ let key = match VIRTUAL_KEY(vk_code) {
+ VK_BACK => Some("backspace"),
+ VK_RETURN => Some("enter"),
+ VK_TAB => Some("tab"),
+ VK_UP => Some("up"),
+ VK_DOWN => Some("down"),
+ VK_RIGHT => Some("right"),
+ VK_LEFT => Some("left"),
+ VK_HOME => Some("home"),
+ VK_END => Some("end"),
+ VK_PRIOR => Some("pageup"),
+ VK_NEXT => Some("pagedown"),
+ VK_ESCAPE => Some("escape"),
+ VK_INSERT => Some("insert"),
+ VK_DELETE => Some("delete"),
+ _ => None,
+ };
+
+ if let Some(key) = key {
+ Some(Keystroke {
+ modifiers,
+ key: key.to_string(),
+ ime_key: None,
+ })
+ } else {
+ None
+ }
+}
+
+fn parse_char_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
+ let src = [wparam.0 as u16];
+ let Ok(first_char) = char::decode_utf16(src).collect::<Vec<_>>()[0] else {
+ return None;
+ };
+ if first_char.is_control() {
+ None
+ } else {
+ let mut modifiers = current_modifiers();
+ // for characters that use 'shift' to type it is expected that the
+ // shift is not reported if the uppercase/lowercase are the same and instead only the key is reported
+ if first_char.to_lowercase().to_string() == first_char.to_uppercase().to_string() {
+ modifiers.shift = false;
+ }
+ let key = match first_char {
+ ' ' => "space".to_string(),
+ first_char => first_char.to_lowercase().to_string(),
+ };
+ Some(Keystroke {
+ modifiers,
+ key,
+ ime_key: Some(first_char.to_string()),
+ })
+ }
+}
+
+/// mark window client rect to be re-drawn
+/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-invalidaterect
+pub(crate) fn invalidate_client_area(handle: HWND) {
+ unsafe { InvalidateRect(handle, None, FALSE) };
+}
+
+fn parse_ime_compostion_string(handle: HWND) -> Option<(String, usize)> {
+ unsafe {
+ let ctx = ImmGetContext(handle);
+ let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0);
+ let result = if string_len >= 0 {
+ let mut buffer = vec![0u8; string_len as usize + 2];
+ ImmGetCompositionStringW(
+ ctx,
+ GCS_COMPSTR,
+ Some(buffer.as_mut_ptr() as _),
+ string_len as _,
+ );
+ let wstring = std::slice::from_raw_parts::<u16>(
+ buffer.as_mut_ptr().cast::<u16>(),
+ string_len as usize / 2,
+ );
+ let string = String::from_utf16_lossy(wstring);
+ Some((string, string_len as usize / 2))
+ } else {
+ None
+ };
+ ImmReleaseContext(handle, ctx);
+ result
+ }
+}
+
+fn retrieve_composition_cursor_position(handle: HWND) -> usize {
+ unsafe {
+ let ctx = ImmGetContext(handle);
+ let ret = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0);
+ ImmReleaseContext(handle, ctx);
+ ret as usize
+ }
+}
+
+fn parse_ime_compostion_result(handle: HWND) -> Option<String> {
+ unsafe {
+ let ctx = ImmGetContext(handle);
+ let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0);
+ let result = if string_len >= 0 {
+ let mut buffer = vec![0u8; string_len as usize + 2];
+ ImmGetCompositionStringW(
+ ctx,
+ GCS_RESULTSTR,
+ Some(buffer.as_mut_ptr() as _),
+ string_len as _,
+ );
+ let wstring = std::slice::from_raw_parts::<u16>(
+ buffer.as_mut_ptr().cast::<u16>(),
+ string_len as usize / 2,
+ );
+ let string = String::from_utf16_lossy(wstring);
+ Some(string)
+ } else {
+ None
+ };
+ ImmReleaseContext(handle, ctx);
+ result
+ }
+}
+
+fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
+ match code {
+ // VK_0 - VK_9
+ 48..=57 => Some(Keystroke {
+ modifiers,
+ key: format!("{}", code - VK_0.0),
+ ime_key: None,
+ }),
+ // VK_A - VK_Z
+ 65..=90 => Some(Keystroke {
+ modifiers,
+ key: format!("{}", (b'a' + code as u8 - VK_A.0 as u8) as char),
+ ime_key: None,
+ }),
+ // VK_F1 - VK_F24
+ 112..=135 => Some(Keystroke {
+ modifiers,
+ key: format!("f{}", code - VK_F1.0 + 1),
+ ime_key: None,
+ }),
+ // OEM3: `/~, OEM_MINUS: -/_, OEM_PLUS: =/+, ...
+ _ => {
+ if let Some(key) = oemkey_vkcode_to_string(code) {
+ Some(Keystroke {
+ modifiers,
+ key,
+ ime_key: None,
+ })
+ } else {
+ None
+ }
+ }
+ }
+}
+
+fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
+ match code {
+ 186 => Some(";".to_string()), // VK_OEM_1
+ 187 => Some("=".to_string()), // VK_OEM_PLUS
+ 188 => Some(",".to_string()), // VK_OEM_COMMA
+ 189 => Some("-".to_string()), // VK_OEM_MINUS
+ 190 => Some(".".to_string()), // VK_OEM_PERIOD
+ // https://kbdlayout.info/features/virtualkeys/VK_ABNT_C1
+ 191 | 193 => Some("/".to_string()), // VK_OEM_2 VK_ABNT_C1
+ 192 => Some("`".to_string()), // VK_OEM_3
+ 219 => Some("[".to_string()), // VK_OEM_4
+ 220 => Some("\\".to_string()), // VK_OEM_5
+ 221 => Some("]".to_string()), // VK_OEM_6
+ 222 => Some("'".to_string()), // VK_OEM_7
+ _ => None,
+ }
+}
+
+#[inline]
+fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool {
+ unsafe { GetKeyState(vkey.0 as i32) < 0 }
+}
+
+#[inline]
+fn current_modifiers() -> Modifiers {
+ Modifiers {
+ control: is_virtual_key_pressed(VK_CONTROL),
+ alt: is_virtual_key_pressed(VK_MENU),
+ shift: is_virtual_key_pressed(VK_SHIFT),
+ platform: is_virtual_key_pressed(VK_LWIN) || is_virtual_key_pressed(VK_RWIN),
+ function: false,
+ }
+}
@@ -3,8 +3,7 @@
use std::{
cell::{Cell, RefCell},
- ffi::{c_uint, c_void, OsString},
- iter::once,
+ ffi::{c_void, OsString},
mem::transmute,
os::windows::ffi::{OsStrExt, OsStringExt},
path::{Path, PathBuf},
@@ -18,7 +17,7 @@ use async_task::Runnable;
use copypasta::{ClipboardContext, ClipboardProvider};
use futures::channel::oneshot::{self, Receiver};
use itertools::Itertools;
-use parking_lot::{Mutex, RwLock};
+use parking_lot::RwLock;
use semantic_version::SemanticVersion;
use smallvec::SmallVec;
use time::UtcOffset;
@@ -39,56 +38,26 @@ use windows::{
use crate::*;
pub(crate) struct WindowsPlatform {
- inner: Rc<WindowsPlatformInner>,
-}
-
-/// Windows settings pulled from SystemParametersInfo
-/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfow
-#[derive(Default, Debug)]
-pub(crate) struct WindowsPlatformSystemSettings {
- /// SEE: SPI_GETWHEELSCROLLCHARS
- pub(crate) wheel_scroll_chars: u32,
-
- /// SEE: SPI_GETWHEELSCROLLLINES
- pub(crate) wheel_scroll_lines: u32,
-}
-
-pub(crate) struct WindowsPlatformInner {
- background_executor: BackgroundExecutor,
- pub(crate) foreground_executor: ForegroundExecutor,
+ state: RefCell<WindowsPlatformState>,
+ raw_window_handles: RwLock<SmallVec<[HWND; 4]>>,
+ // The below members will never change throughout the entire lifecycle of the app.
+ icon: HICON,
main_receiver: flume::Receiver<Runnable>,
+ background_executor: BackgroundExecutor,
+ foreground_executor: ForegroundExecutor,
text_system: Arc<dyn PlatformTextSystem>,
- callbacks: Mutex<Callbacks>,
- pub raw_window_handles: RwLock<SmallVec<[HWND; 4]>>,
- pub(crate) dispatch_event: OwnedHandle,
- pub(crate) settings: RefCell<WindowsPlatformSystemSettings>,
- pub icon: HICON,
- // NOTE: standard cursor handles don't need to close.
- pub(crate) current_cursor: Cell<HCURSOR>,
+ dispatch_event: OwnedHandle,
}
-impl WindowsPlatformInner {
- pub(crate) fn try_get_windows_inner_from_hwnd(
- &self,
- hwnd: HWND,
- ) -> Option<Rc<WindowsWindowInner>> {
- self.raw_window_handles
- .read()
- .iter()
- .find(|entry| *entry == &hwnd)
- .and_then(|hwnd| try_get_window_inner(*hwnd))
- }
-
- #[inline]
- pub fn run_foreground_tasks(&self) {
- for runnable in self.main_receiver.drain() {
- runnable.run();
- }
- }
+pub(crate) struct WindowsPlatformState {
+ callbacks: PlatformCallbacks,
+ pub(crate) settings: WindowsPlatformSystemSettings,
+ // NOTE: standard cursor handles don't need to close.
+ pub(crate) current_cursor: HCURSOR,
}
#[derive(Default)]
-struct Callbacks {
+struct PlatformCallbacks {
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
quit: Option<Box<dyn FnMut()>>,
reopen: Option<Box<dyn FnMut()>>,
@@ -97,53 +66,16 @@ struct Callbacks {
validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
}
-enum WindowsMessageWaitResult {
- ForegroundExecution,
- WindowsMessage(MSG),
- Error,
-}
-
-impl WindowsPlatformSystemSettings {
+impl WindowsPlatformState {
fn new() -> Self {
- let mut settings = Self::default();
- settings.update_all();
- settings
- }
-
- pub(crate) fn update_all(&mut self) {
- self.update_wheel_scroll_lines();
- self.update_wheel_scroll_chars();
- }
+ let callbacks = PlatformCallbacks::default();
+ let settings = WindowsPlatformSystemSettings::new();
+ let current_cursor = load_cursor(CursorStyle::Arrow);
- pub(crate) fn update_wheel_scroll_lines(&mut self) {
- let mut value = c_uint::default();
- let result = unsafe {
- SystemParametersInfoW(
- SPI_GETWHEELSCROLLLINES,
- 0,
- Some((&mut value) as *mut c_uint as *mut c_void),
- SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS::default(),
- )
- };
-
- if result.log_err() != None {
- self.wheel_scroll_lines = value;
- }
- }
-
- pub(crate) fn update_wheel_scroll_chars(&mut self) {
- let mut value = c_uint::default();
- let result = unsafe {
- SystemParametersInfoW(
- SPI_GETWHEELSCROLLCHARS,
- 0,
- Some((&mut value) as *mut c_uint as *mut c_void),
- SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS::default(),
- )
- };
-
- if result.log_err() != None {
- self.wheel_scroll_chars = value;
+ Self {
+ callbacks,
+ settings,
+ current_cursor,
}
}
}
@@ -166,33 +98,31 @@ impl WindowsPlatform {
log::info!("Using cosmic text system.");
Arc::new(CosmicTextSystem::new()) as Arc<dyn PlatformTextSystem>
};
- let callbacks = Mutex::new(Callbacks::default());
- let raw_window_handles = RwLock::new(SmallVec::new());
- let settings = RefCell::new(WindowsPlatformSystemSettings::new());
let icon = load_icon().unwrap_or_default();
- let current_cursor = Cell::new(load_cursor(CursorStyle::Arrow));
- let inner = Rc::new(WindowsPlatformInner {
+ let state = RefCell::new(WindowsPlatformState::new());
+ let raw_window_handles = RwLock::new(SmallVec::new());
+
+ Self {
+ state,
+ raw_window_handles,
+ icon,
+ main_receiver,
background_executor,
foreground_executor,
- main_receiver,
text_system,
- callbacks,
- raw_window_handles,
dispatch_event,
- settings,
- icon,
- current_cursor,
- });
- Self { inner }
+ }
}
#[inline]
fn run_foreground_tasks(&self) {
- self.inner.run_foreground_tasks();
+ for runnable in self.main_receiver.drain() {
+ runnable.run();
+ }
}
fn redraw_all(&self) {
- for handle in self.inner.raw_window_handles.read().iter() {
+ for handle in self.raw_window_handles.read().iter() {
unsafe {
RedrawWindow(
*handle,
@@ -203,24 +133,75 @@ impl WindowsPlatform {
}
}
}
+
+ pub fn try_get_windows_inner_from_hwnd(&self, hwnd: HWND) -> Option<Rc<WindowsWindowStatePtr>> {
+ self.raw_window_handles
+ .read()
+ .iter()
+ .find(|entry| *entry == &hwnd)
+ .and_then(|hwnd| try_get_window_inner(*hwnd))
+ }
+
+ #[inline]
+ fn post_message(&self, message: u32, wparam: WPARAM, lparam: LPARAM) {
+ self.raw_window_handles
+ .read()
+ .iter()
+ .for_each(|handle| unsafe {
+ PostMessageW(*handle, message, wparam, lparam).log_err();
+ });
+ }
+
+ fn close_one_window(&self, target_window: HWND) -> bool {
+ let mut lock = self.raw_window_handles.write();
+ let index = lock
+ .iter()
+ .position(|handle| *handle == target_window)
+ .unwrap();
+ lock.remove(index);
+
+ lock.is_empty()
+ }
+
+ fn update_system_settings(&self) {
+ let mut lock = self.state.borrow_mut();
+ // mouse wheel
+ {
+ let (scroll_chars, scroll_lines) = lock.settings.mouse_wheel_settings.update();
+ if let Some(scroll_chars) = scroll_chars {
+ self.post_message(
+ MOUSE_WHEEL_SETTINGS_CHANGED,
+ WPARAM(scroll_chars as usize),
+ LPARAM(MOUSE_WHEEL_SETTINGS_SCROLL_CHARS_CHANGED),
+ );
+ }
+ if let Some(scroll_lines) = scroll_lines {
+ self.post_message(
+ MOUSE_WHEEL_SETTINGS_CHANGED,
+ WPARAM(scroll_lines as usize),
+ LPARAM(MOUSE_WHEEL_SETTINGS_SCROLL_LINES_CHANGED),
+ );
+ }
+ }
+ }
}
impl Platform for WindowsPlatform {
fn background_executor(&self) -> BackgroundExecutor {
- self.inner.background_executor.clone()
+ self.background_executor.clone()
}
fn foreground_executor(&self) -> ForegroundExecutor {
- self.inner.foreground_executor.clone()
+ self.foreground_executor.clone()
}
fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
- self.inner.text_system.clone()
+ self.text_system.clone()
}
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
on_finish_launching();
- let dispatch_event = self.inner.dispatch_event.to_raw();
+ let dispatch_event = self.dispatch_event.to_raw();
let vsync_event = create_event().unwrap();
let timer_stop_event = create_event().unwrap();
let raw_timer_stop_event = timer_stop_event.to_raw();
@@ -248,16 +229,20 @@ impl Platform for WindowsPlatform {
WAIT_EVENT(2) => {
let mut msg = MSG::default();
unsafe {
- while PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE).as_bool() {
- if msg.message == WM_QUIT {
- break 'a;
- }
- if msg.message == WM_SETTINGCHANGE {
- self.inner.settings.borrow_mut().update_all();
- continue;
+ while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
+ match msg.message {
+ WM_QUIT => break 'a,
+ CLOSE_ONE_WINDOW => {
+ if self.close_one_window(HWND(msg.lParam.0)) {
+ break 'a;
+ }
+ }
+ WM_SETTINGCHANGE => self.update_system_settings(),
+ _ => {
+ TranslateMessage(&msg);
+ DispatchMessageW(&msg);
+ }
}
- TranslateMessage(&msg);
- DispatchMessageW(&msg);
}
}
@@ -272,9 +257,8 @@ impl Platform for WindowsPlatform {
}
end_vsync_timer(raw_timer_stop_event);
- let mut callbacks = self.inner.callbacks.lock();
- if let Some(callback) = callbacks.quit.as_mut() {
- callback()
+ if let Some(ref mut callback) = self.state.borrow_mut().callbacks.quit {
+ callback();
}
}
@@ -340,17 +324,12 @@ impl Platform for WindowsPlatform {
}
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
- if let Some(display) = WindowsDisplay::primary_monitor() {
- Some(Rc::new(display) as Rc<dyn PlatformDisplay>)
- } else {
- None
- }
+ WindowsDisplay::primary_monitor().map(|display| Rc::new(display) as Rc<dyn PlatformDisplay>)
}
fn active_window(&self) -> Option<AnyWindowHandle> {
let active_window_hwnd = unsafe { GetActiveWindow() };
- self.inner
- .try_get_windows_inner_from_hwnd(active_window_hwnd)
+ self.try_get_windows_inner_from_hwnd(active_window_hwnd)
.map(|inner| inner.handle)
}
@@ -359,7 +338,21 @@ impl Platform for WindowsPlatform {
handle: AnyWindowHandle,
options: WindowParams,
) -> Box<dyn PlatformWindow> {
- Box::new(WindowsWindow::new(self.inner.clone(), handle, options))
+ let lock = self.state.borrow();
+ let window = WindowsWindow::new(
+ handle,
+ options,
+ self.icon,
+ self.foreground_executor.clone(),
+ self.main_receiver.clone(),
+ lock.settings.mouse_wheel_settings,
+ lock.current_cursor,
+ );
+ drop(lock);
+ let handle = window.get_raw_handle();
+ self.raw_window_handles.write().push(handle);
+
+ Box::new(window)
}
// todo(windows)
@@ -379,9 +372,8 @@ impl Platform for WindowsPlatform {
.detach();
}
- // todo(windows)
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
- self.inner.callbacks.lock().open_urls = Some(callback);
+ self.state.borrow_mut().callbacks.open_urls = Some(callback);
}
fn prompt_for_paths(&self, options: PathPromptOptions) -> Receiver<Option<Vec<PathBuf>>> {
@@ -501,26 +493,26 @@ impl Platform for WindowsPlatform {
}
fn on_quit(&self, callback: Box<dyn FnMut()>) {
- self.inner.callbacks.lock().quit = Some(callback);
+ self.state.borrow_mut().callbacks.quit = Some(callback);
}
fn on_reopen(&self, callback: Box<dyn FnMut()>) {
- self.inner.callbacks.lock().reopen = Some(callback);
+ self.state.borrow_mut().callbacks.reopen = Some(callback);
}
// todo(windows)
fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {}
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
- self.inner.callbacks.lock().app_menu_action = Some(callback);
+ self.state.borrow_mut().callbacks.app_menu_action = Some(callback);
}
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
- self.inner.callbacks.lock().will_open_app_menu = Some(callback);
+ self.state.borrow_mut().callbacks.will_open_app_menu = Some(callback);
}
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
- self.inner.callbacks.lock().validate_app_menu_command = Some(callback);
+ self.state.borrow_mut().callbacks.validate_app_menu_command = Some(callback);
}
fn os_name(&self) -> &'static str {
@@ -667,7 +659,9 @@ impl Platform for WindowsPlatform {
}
fn set_cursor_style(&self, style: CursorStyle) {
- self.inner.current_cursor.set(load_cursor(style));
+ let hcursor = load_cursor(style);
+ self.post_message(CURSOR_STYLE_CHANGED, WPARAM(0), LPARAM(hcursor.0));
+ self.state.borrow_mut().current_cursor = hcursor;
}
// todo(windows)
@@ -699,10 +693,10 @@ impl Platform for WindowsPlatform {
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
let mut password = password.to_vec();
- let mut username = username.encode_utf16().chain(once(0)).collect_vec();
+ let mut username = username.encode_utf16().chain(Some(0)).collect_vec();
let mut target_name = windows_credentials_target_name(url)
.encode_utf16()
- .chain(once(0))
+ .chain(Some(0))
.collect_vec();
self.foreground_executor().spawn(async move {
let credentials = CREDENTIALW {
@@ -724,7 +718,7 @@ impl Platform for WindowsPlatform {
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
let mut target_name = windows_credentials_target_name(url)
.encode_utf16()
- .chain(once(0))
+ .chain(Some(0))
.collect_vec();
self.foreground_executor().spawn(async move {
let mut credentials: *mut CREDENTIALW = std::ptr::null_mut();
@@ -757,7 +751,7 @@ impl Platform for WindowsPlatform {
fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
let mut target_name = windows_credentials_target_name(url)
.encode_utf16()
- .chain(once(0))
+ .chain(Some(0))
.collect_vec();
self.foreground_executor().spawn(async move {
unsafe { CredDeleteW(PCWSTR::from_raw(target_name.as_ptr()), CRED_TYPE_GENERIC, 0)? };
@@ -0,0 +1,81 @@
+use std::ffi::{c_uint, c_void};
+
+use util::ResultExt;
+use windows::Win32::UI::WindowsAndMessaging::{
+ SystemParametersInfoW, SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES,
+ SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS,
+};
+
+/// Windows settings pulled from SystemParametersInfo
+/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfow
+#[derive(Default, Debug)]
+pub(crate) struct WindowsPlatformSystemSettings {
+ pub(crate) mouse_wheel_settings: MouseWheelSettings,
+}
+
+#[derive(Default, Debug, Clone, Copy)]
+pub(crate) struct MouseWheelSettings {
+ /// SEE: SPI_GETWHEELSCROLLCHARS
+ pub(crate) wheel_scroll_chars: u32,
+ /// SEE: SPI_GETWHEELSCROLLLINES
+ pub(crate) wheel_scroll_lines: u32,
+}
+
+impl WindowsPlatformSystemSettings {
+ pub(crate) fn new() -> Self {
+ let mut settings = Self::default();
+ settings.init();
+ settings
+ }
+
+ fn init(&mut self) {
+ self.mouse_wheel_settings.update();
+ }
+}
+
+impl MouseWheelSettings {
+ pub(crate) fn update(&mut self) -> (Option<u32>, Option<u32>) {
+ (
+ self.update_wheel_scroll_chars(),
+ self.update_wheel_scroll_lines(),
+ )
+ }
+
+ fn update_wheel_scroll_chars(&mut self) -> Option<u32> {
+ let mut value = c_uint::default();
+ let result = unsafe {
+ SystemParametersInfoW(
+ SPI_GETWHEELSCROLLCHARS,
+ 0,
+ Some((&mut value) as *mut c_uint as *mut c_void),
+ SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS::default(),
+ )
+ };
+
+ if result.log_err() != None && self.wheel_scroll_chars != value {
+ self.wheel_scroll_chars = value;
+ Some(value)
+ } else {
+ None
+ }
+ }
+
+ fn update_wheel_scroll_lines(&mut self) -> Option<u32> {
+ let mut value = c_uint::default();
+ let result = unsafe {
+ SystemParametersInfoW(
+ SPI_GETWHEELSCROLLLINES,
+ 0,
+ Some((&mut value) as *mut c_uint as *mut c_void),
+ SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS::default(),
+ )
+ };
+
+ if result.log_err() != None && self.wheel_scroll_lines != value {
+ self.wheel_scroll_lines = value;
+ Some(value)
+ } else {
+ None
+ }
+ }
+}
@@ -74,6 +74,7 @@ pub(crate) unsafe fn set_window_long(
}
}
+#[derive(Debug, Clone)]
pub(crate) struct OwnedHandle(HANDLE);
impl OwnedHandle {
@@ -135,3 +136,19 @@ pub(crate) fn load_cursor(style: CursorStyle) -> HCURSOR {
)
})
}
+
+#[inline]
+pub(crate) fn logical_size(physical_size: Size<DevicePixels>, scale_factor: f32) -> Size<Pixels> {
+ Size {
+ width: px(physical_size.width.0 as f32 / scale_factor),
+ height: px(physical_size.height.0 as f32 / scale_factor),
+ }
+}
+
+#[inline]
+pub(crate) fn logical_point(x: f32, y: f32, scale_factor: f32) -> Point<Pixels> {
+ Point {
+ x: px(x / scale_factor),
+ y: px(y / scale_factor),
+ }
+}
@@ -1,8 +1,7 @@
#![deny(unsafe_op_in_unsafe_fn)]
use std::{
- cell::{Cell, RefCell},
- iter::once,
+ cell::RefCell,
num::NonZeroIsize,
path::PathBuf,
rc::{Rc, Weak},
@@ -13,197 +12,122 @@ use std::{
use ::util::ResultExt;
use anyhow::Context;
-use blade_graphics as gpu;
+use async_task::Runnable;
use futures::channel::oneshot::{self, Receiver};
use itertools::Itertools;
use raw_window_handle as rwh;
use smallvec::SmallVec;
-use std::result::Result;
use windows::{
core::*,
Win32::{
Foundation::*,
Graphics::Gdi::*,
System::{Com::*, LibraryLoader::*, Ole::*, SystemServices::*},
- UI::{
- Controls::*,
- HiDpi::*,
- Input::{Ime::*, KeyboardAndMouse::*},
- Shell::*,
- WindowsAndMessaging::*,
- },
+ UI::{Controls::*, HiDpi::*, Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
},
};
-use crate::platform::blade::{BladeRenderer, BladeSurfaceConfig};
+use crate::platform::blade::BladeRenderer;
use crate::*;
-pub(crate) struct WindowsWindowInner {
+pub(crate) struct WindowsWindow(pub Rc<WindowsWindowStatePtr>);
+
+pub struct WindowsWindowState {
+ pub origin: Point<DevicePixels>,
+ pub physical_size: Size<DevicePixels>,
+ pub scale_factor: f32,
+
+ pub callbacks: Callbacks,
+ pub input_handler: Option<PlatformInputHandler>,
+
+ pub renderer: BladeRenderer,
+
+ pub click_state: ClickState,
+ pub mouse_wheel_settings: MouseWheelSettings,
+ pub current_cursor: HCURSOR,
+
+ pub display: WindowsDisplay,
+ fullscreen: Option<StyleAndBounds>,
+ hwnd: HWND,
+}
+
+pub(crate) struct WindowsWindowStatePtr {
hwnd: HWND,
- origin: Cell<Point<DevicePixels>>,
- physical_size: Cell<Size<DevicePixels>>,
- scale_factor: Cell<f32>,
- input_handler: Cell<Option<PlatformInputHandler>>,
- renderer: RefCell<BladeRenderer>,
- callbacks: RefCell<Callbacks>,
- platform_inner: Rc<WindowsPlatformInner>,
+ pub(crate) state: RefCell<WindowsWindowState>,
pub(crate) handle: AnyWindowHandle,
- hide_title_bar: bool,
- display: RefCell<Rc<WindowsDisplay>>,
- click_state: RefCell<ClickState>,
- fullscreen: Cell<Option<StyleAndBounds>>,
+ pub(crate) hide_title_bar: bool,
+ pub(crate) executor: ForegroundExecutor,
+ pub(crate) main_receiver: flume::Receiver<Runnable>,
}
-impl WindowsWindowInner {
+impl WindowsWindowState {
fn new(
hwnd: HWND,
- cs: &CREATESTRUCTW,
- platform_inner: Rc<WindowsPlatformInner>,
- handle: AnyWindowHandle,
- hide_title_bar: bool,
- display: Rc<WindowsDisplay>,
transparent: bool,
+ cs: &CREATESTRUCTW,
+ mouse_wheel_settings: MouseWheelSettings,
+ current_cursor: HCURSOR,
+ display: WindowsDisplay,
) -> Self {
- let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
- let origin = Cell::new(Point {
- x: DevicePixels(cs.x),
- y: DevicePixels(cs.y),
- });
- let physical_size = Cell::new(Size {
- width: DevicePixels(cs.cx),
- height: DevicePixels(cs.cy),
- });
- let scale_factor = Cell::new(monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32);
- let input_handler = Cell::new(None);
- struct RawWindow {
- hwnd: isize,
- }
- impl rwh::HasWindowHandle for RawWindow {
- fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
- Ok(unsafe {
- let hwnd = NonZeroIsize::new_unchecked(self.hwnd);
- let mut handle = rwh::Win32WindowHandle::new(hwnd);
- let hinstance = get_window_long(HWND(self.hwnd), GWLP_HINSTANCE);
- handle.hinstance = NonZeroIsize::new(hinstance);
- rwh::WindowHandle::borrow_raw(handle.into())
- })
- }
- }
- impl rwh::HasDisplayHandle for RawWindow {
- fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
- let handle = rwh::WindowsDisplayHandle::new();
- Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
- }
- }
+ let origin = point(cs.x.into(), cs.y.into());
+ let physical_size = size(cs.cx.into(), cs.cy.into());
+ let scale_factor = {
+ let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
+ monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32
+ };
+ let renderer = windows_renderer::windows_renderer(hwnd, transparent);
+ let callbacks = Callbacks::default();
+ let input_handler = None;
+ let click_state = ClickState::new();
+ let fullscreen = None;
- let raw = RawWindow { hwnd: hwnd.0 };
- let gpu = Arc::new(
- unsafe {
- gpu::Context::init_windowed(
- &raw,
- gpu::ContextDesc {
- validation: false,
- capture: false,
- overlay: false,
- },
- )
- }
- .unwrap(),
- );
- let config = BladeSurfaceConfig {
- size: gpu::Extent::default(),
- transparent,
- };
- let renderer = RefCell::new(BladeRenderer::new(gpu, config));
- let callbacks = RefCell::new(Callbacks::default());
- let display = RefCell::new(display);
- let click_state = RefCell::new(ClickState::new());
- let fullscreen = Cell::new(None);
Self {
- hwnd,
origin,
physical_size,
scale_factor,
+ callbacks,
input_handler,
renderer,
- callbacks,
- platform_inner,
- handle,
- hide_title_bar,
- display,
click_state,
+ mouse_wheel_settings,
+ current_cursor,
+ display,
fullscreen,
+ hwnd,
}
}
- fn is_maximized(&self) -> bool {
- !self.is_fullscreen() && unsafe { IsZoomed(self.hwnd) }.as_bool()
+ #[inline]
+ pub(crate) fn is_fullscreen(&self) -> bool {
+ self.fullscreen.is_some()
}
- fn is_minimized(&self) -> bool {
- unsafe { IsIconic(self.hwnd) }.as_bool()
+ pub(crate) fn is_maximized(&self) -> bool {
+ !self.is_fullscreen() && unsafe { IsZoomed(self.hwnd) }.as_bool()
}
- fn is_fullscreen(&self) -> bool {
- let fullscreen = self.fullscreen.take();
- let is_fullscreen = fullscreen.is_some();
- self.fullscreen.set(fullscreen);
- is_fullscreen
- }
-
- async fn toggle_fullscreen(self: Rc<Self>) {
- let StyleAndBounds {
- style,
- x,
- y,
- cx,
- cy,
- } = if let Some(state) = self.fullscreen.take() {
- state
- } else {
- let style = WINDOW_STYLE(unsafe { get_window_long(self.hwnd, GWL_STYLE) } as _);
- let mut rc = RECT::default();
- unsafe { GetWindowRect(self.hwnd, &mut rc) }.log_err();
- self.fullscreen.set(Some(StyleAndBounds {
- style,
- x: rc.left,
- y: rc.top,
- cx: rc.right - rc.left,
- cy: rc.bottom - rc.top,
- }));
- let style = style
- & !(WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CAPTION);
- let bounds = self.display.borrow().clone().bounds();
- StyleAndBounds {
- style,
- x: bounds.left().0,
- y: bounds.top().0,
- cx: bounds.size.width.0,
- cy: bounds.size.height.0,
- }
- };
- unsafe { set_window_long(self.hwnd, GWL_STYLE, style.0 as isize) };
- unsafe {
- SetWindowPos(
- self.hwnd,
- HWND::default(),
- x,
- y,
- cx,
- cy,
- SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOZORDER,
- )
+ fn bounds(&self) -> Bounds<DevicePixels> {
+ Bounds {
+ origin: self.origin,
+ size: self.physical_size,
}
- .log_err();
}
- pub(crate) fn title_bar_padding(&self) -> Pixels {
+ /// get the logical size of the app's drawable area.
+ ///
+ /// Currently, GPUI uses logical size of the app to handle mouse interactions (such as
+ /// whether the mouse collides with other elements of GPUI).
+ fn content_size(&self) -> Size<Pixels> {
+ logical_size(self.physical_size, self.scale_factor)
+ }
+
+ fn title_bar_padding(&self) -> Pixels {
// using USER_DEFAULT_SCREEN_DPI because GPUI handles the scale with the scale factor
let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI) };
px(padding as f32)
}
- pub(crate) fn title_bar_top_offset(&self) -> Pixels {
+ fn title_bar_top_offset(&self) -> Pixels {
if self.is_maximized() {
self.title_bar_padding() * 2
} else {
@@ -211,7 +135,7 @@ impl WindowsWindowInner {
}
}
- pub(crate) fn title_bar_height(&self) -> Pixels {
+ fn title_bar_height(&self) -> Pixels {
// todo(windows) this is hard set to match the ui title bar
// in the future the ui title bar component will report the size
px(32.) + self.title_bar_top_offset()
@@ -223,1032 +147,76 @@ impl WindowsWindowInner {
px(36.)
}
- fn get_titlebar_rect(&self) -> anyhow::Result<RECT> {
+ pub(crate) fn get_titlebar_rect(&self) -> anyhow::Result<RECT> {
let height = self.title_bar_height();
let mut rect = RECT::default();
unsafe { GetClientRect(self.hwnd, &mut rect) }?;
- rect.bottom = rect.top + ((height.0 * self.scale_factor.get()).round() as i32);
+ rect.bottom = rect.top + ((height.0 * self.scale_factor).round() as i32);
Ok(rect)
}
+}
- fn is_virtual_key_pressed(&self, vkey: VIRTUAL_KEY) -> bool {
- unsafe { GetKeyState(vkey.0 as i32) < 0 }
- }
-
- fn current_modifiers(&self) -> Modifiers {
- Modifiers {
- control: self.is_virtual_key_pressed(VK_CONTROL),
- alt: self.is_virtual_key_pressed(VK_MENU),
- shift: self.is_virtual_key_pressed(VK_SHIFT),
- platform: self.is_virtual_key_pressed(VK_LWIN) || self.is_virtual_key_pressed(VK_RWIN),
- function: false,
- }
- }
-
- /// mark window client rect to be re-drawn
- /// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-invalidaterect
- pub(crate) fn invalidate_client_area(&self) {
- unsafe { InvalidateRect(self.hwnd, None, FALSE) };
- }
-
- fn handle_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
- let handled = match msg {
- WM_ACTIVATE => self.handle_activate_msg(wparam),
- WM_CREATE => self.handle_create_msg(lparam),
- WM_MOVE => self.handle_move_msg(lparam),
- WM_SIZE => self.handle_size_msg(lparam),
- WM_ENTERSIZEMOVE | WM_ENTERMENULOOP => self.handle_size_move_loop(),
- WM_EXITSIZEMOVE | WM_EXITMENULOOP => self.handle_size_move_loop_exit(),
- WM_TIMER => self.handle_timer_msg(wparam),
- WM_NCCALCSIZE => self.handle_calc_client_size(wparam, lparam),
- WM_DPICHANGED => self.handle_dpi_changed_msg(wparam, lparam),
- WM_NCHITTEST => self.handle_hit_test_msg(msg, wparam, lparam),
- WM_PAINT => self.handle_paint_msg(),
- WM_CLOSE => self.handle_close_msg(),
- WM_DESTROY => self.handle_destroy_msg(),
- WM_MOUSEMOVE => self.handle_mouse_move_msg(lparam, wparam),
- WM_NCMOUSEMOVE => self.handle_nc_mouse_move_msg(lparam),
- WM_NCLBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Left, wparam, lparam),
- WM_NCRBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Right, wparam, lparam),
- WM_NCMBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Middle, wparam, lparam),
- WM_NCLBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Left, wparam, lparam),
- WM_NCRBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Right, wparam, lparam),
- WM_NCMBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Middle, wparam, lparam),
- WM_LBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Left, lparam),
- WM_RBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Right, lparam),
- WM_MBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Middle, lparam),
- WM_XBUTTONDOWN => self.handle_xbutton_msg(wparam, lparam, Self::handle_mouse_down_msg),
- WM_LBUTTONUP => self.handle_mouse_up_msg(MouseButton::Left, lparam),
- WM_RBUTTONUP => self.handle_mouse_up_msg(MouseButton::Right, lparam),
- WM_MBUTTONUP => self.handle_mouse_up_msg(MouseButton::Middle, lparam),
- WM_XBUTTONUP => self.handle_xbutton_msg(wparam, lparam, Self::handle_mouse_up_msg),
- WM_MOUSEWHEEL => self.handle_mouse_wheel_msg(wparam, lparam),
- WM_MOUSEHWHEEL => self.handle_mouse_horizontal_wheel_msg(wparam, lparam),
- WM_SYSKEYDOWN => self.handle_syskeydown_msg(wparam, lparam),
- WM_SYSKEYUP => self.handle_syskeyup_msg(wparam),
- WM_KEYDOWN => self.handle_keydown_msg(msg, wparam, lparam),
- WM_KEYUP => self.handle_keyup_msg(msg, wparam),
- WM_CHAR => self.handle_char_msg(msg, wparam, lparam),
- WM_IME_STARTCOMPOSITION => self.handle_ime_position(),
- WM_IME_COMPOSITION => self.handle_ime_composition(lparam),
- WM_SETCURSOR => self.handle_set_cursor(lparam),
- _ => None,
- };
- if let Some(n) = handled {
- LRESULT(n)
- } else {
- unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }
- }
- }
-
- fn handle_move_msg(&self, lparam: LPARAM) -> Option<isize> {
- let x = lparam.signed_loword() as i32;
- let y = lparam.signed_hiword() as i32;
- self.origin.set(Point {
- x: DevicePixels(x),
- y: DevicePixels(y),
- });
- let size = self.physical_size.get();
- let center_x = x + size.width.0 / 2;
- let center_y = y + size.height.0 / 2;
- let monitor_bounds = self.display.borrow().bounds();
- if center_x < monitor_bounds.left().0
- || center_x > monitor_bounds.right().0
- || center_y < monitor_bounds.top().0
- || center_y > monitor_bounds.bottom().0
- {
- // center of the window may have moved to another monitor
- let monitor = unsafe { MonitorFromWindow(self.hwnd, MONITOR_DEFAULTTONULL) };
- if !monitor.is_invalid() && self.display.borrow().handle != monitor {
- // we will get the same monitor if we only have one
- (*self.display.borrow_mut()) = Rc::new(WindowsDisplay::new_with_handle(monitor));
- }
- }
- let mut callbacks = self.callbacks.borrow_mut();
- if let Some(callback) = callbacks.moved.as_mut() {
- callback()
- }
- Some(0)
- }
-
- fn handle_size_msg(&self, lparam: LPARAM) -> Option<isize> {
- let width = lparam.loword().max(1) as i32;
- let height = lparam.hiword().max(1) as i32;
- let scale_factor = self.scale_factor.get();
- let new_physical_size = Size {
- width: DevicePixels(width),
- height: DevicePixels(height),
- };
- self.physical_size.set(new_physical_size);
- self.renderer.borrow_mut().update_drawable_size(Size {
- width: width as f64,
- height: height as f64,
- });
- let mut callbacks = self.callbacks.borrow_mut();
- if let Some(callback) = callbacks.resize.as_mut() {
- let logical_size = logical_size(new_physical_size, scale_factor);
- callback(logical_size, scale_factor);
- }
- Some(0)
- }
-
- fn handle_size_move_loop(&self) -> Option<isize> {
- unsafe {
- let ret = SetTimer(self.hwnd, SIZE_MOVE_LOOP_TIMER_ID, USER_TIMER_MINIMUM, None);
- if ret == 0 {
- log::error!(
- "unable to create timer: {}",
- std::io::Error::last_os_error()
- );
- }
- }
- None
- }
-
- fn handle_size_move_loop_exit(&self) -> Option<isize> {
- unsafe {
- KillTimer(self.hwnd, SIZE_MOVE_LOOP_TIMER_ID).log_err();
- }
- None
- }
-
- fn handle_timer_msg(&self, wparam: WPARAM) -> Option<isize> {
- if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID {
- self.platform_inner.run_foreground_tasks();
- self.handle_paint_msg();
- return Some(0);
- }
- None
- }
-
- fn handle_paint_msg(&self) -> Option<isize> {
- let mut paint_struct = PAINTSTRUCT::default();
- let _hdc = unsafe { BeginPaint(self.hwnd, &mut paint_struct) };
- let mut callbacks = self.callbacks.borrow_mut();
- if let Some(request_frame) = callbacks.request_frame.as_mut() {
- request_frame();
- }
- unsafe { EndPaint(self.hwnd, &paint_struct) };
- Some(0)
- }
-
- fn handle_close_msg(&self) -> Option<isize> {
- let mut callbacks = self.callbacks.borrow_mut();
- if let Some(callback) = callbacks.should_close.as_mut() {
- if callback() {
- return Some(0);
- }
- }
- None
- }
-
- fn handle_destroy_msg(&self) -> Option<isize> {
- let mut callbacks = self.callbacks.borrow_mut();
- if let Some(callback) = callbacks.close.take() {
- callback()
- }
- let index = self
- .platform_inner
- .raw_window_handles
- .read()
- .iter()
- .position(|handle| *handle == self.hwnd)
- .unwrap();
- self.platform_inner.raw_window_handles.write().remove(index);
- if self.platform_inner.raw_window_handles.read().is_empty() {
- self.platform_inner
- .foreground_executor
- .spawn(async {
- unsafe { PostQuitMessage(0) };
- })
- .detach();
- }
- Some(1)
- }
-
- fn handle_mouse_move_msg(&self, lparam: LPARAM, wparam: WPARAM) -> Option<isize> {
- let mut callbacks = self.callbacks.borrow_mut();
- if let Some(callback) = callbacks.input.as_mut() {
- let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
- flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
- flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
- flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
- flags if flags.contains(MK_XBUTTON1) => {
- Some(MouseButton::Navigate(NavigationDirection::Back))
- }
- flags if flags.contains(MK_XBUTTON2) => {
- Some(MouseButton::Navigate(NavigationDirection::Forward))
- }
- _ => None,
- };
- let x = lparam.signed_loword() as f32;
- let y = lparam.signed_hiword() as f32;
- let scale_factor = self.scale_factor.get();
- let event = MouseMoveEvent {
- position: logical_point(x, y, scale_factor),
- pressed_button,
- modifiers: self.current_modifiers(),
- };
- if callback(PlatformInput::MouseMove(event)).default_prevented {
- return Some(0);
- }
- }
- Some(1)
- }
-
- fn parse_syskeydown_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
- let modifiers = self.current_modifiers();
- if !modifiers.alt {
- // on Windows, F10 can trigger this event, not just the alt key
- // and we just don't care about F10
- return None;
- }
-
- let vk_code = wparam.loword();
- let basic_key = basic_vkcode_to_string(vk_code, modifiers);
- if basic_key.is_some() {
- return basic_key;
- }
-
- let key = match VIRTUAL_KEY(vk_code) {
- VK_BACK => Some("backspace"),
- VK_RETURN => Some("enter"),
- VK_TAB => Some("tab"),
- VK_UP => Some("up"),
- VK_DOWN => Some("down"),
- VK_RIGHT => Some("right"),
- VK_LEFT => Some("left"),
- VK_HOME => Some("home"),
- VK_END => Some("end"),
- VK_PRIOR => Some("pageup"),
- VK_NEXT => Some("pagedown"),
- VK_ESCAPE => Some("escape"),
- VK_INSERT => Some("insert"),
- _ => None,
- };
-
- if let Some(key) = key {
- Some(Keystroke {
- modifiers,
- key: key.to_string(),
- ime_key: None,
- })
- } else {
- None
- }
- }
-
- fn parse_keydown_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
- let vk_code = wparam.loword();
-
- let modifiers = self.current_modifiers();
- if modifiers.control || modifiers.alt {
- let basic_key = basic_vkcode_to_string(vk_code, modifiers);
- if basic_key.is_some() {
- return basic_key;
- }
- }
-
- if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 {
- let offset = vk_code - VK_F1.0;
- return Some(Keystroke {
- modifiers,
- key: format!("f{}", offset + 1),
- ime_key: None,
- });
- }
-
- let key = match VIRTUAL_KEY(vk_code) {
- VK_BACK => Some("backspace"),
- VK_RETURN => Some("enter"),
- VK_TAB => Some("tab"),
- VK_UP => Some("up"),
- VK_DOWN => Some("down"),
- VK_RIGHT => Some("right"),
- VK_LEFT => Some("left"),
- VK_HOME => Some("home"),
- VK_END => Some("end"),
- VK_PRIOR => Some("pageup"),
- VK_NEXT => Some("pagedown"),
- VK_ESCAPE => Some("escape"),
- VK_INSERT => Some("insert"),
- VK_DELETE => Some("delete"),
- _ => None,
- };
-
- if let Some(key) = key {
- Some(Keystroke {
- modifiers,
- key: key.to_string(),
- ime_key: None,
- })
- } else {
- None
- }
- }
-
- fn parse_char_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
- let src = [wparam.0 as u16];
- let Ok(first_char) = char::decode_utf16(src).collect::<Vec<_>>()[0] else {
- return None;
- };
- if first_char.is_control() {
- None
- } else {
- let mut modifiers = self.current_modifiers();
- // for characters that use 'shift' to type it is expected that the
- // shift is not reported if the uppercase/lowercase are the same and instead only the key is reported
- if first_char.to_lowercase().to_string() == first_char.to_uppercase().to_string() {
- modifiers.shift = false;
- }
- let key = match first_char {
- ' ' => "space".to_string(),
- first_char => first_char.to_lowercase().to_string(),
- };
- Some(Keystroke {
- modifiers,
- key,
- ime_key: Some(first_char.to_string()),
- })
- }
- }
-
- fn handle_syskeydown_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
- // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
- // shortcuts.
- let Some(keystroke) = self.parse_syskeydown_msg_keystroke(wparam) else {
- return None;
- };
- let Some(ref mut func) = self.callbacks.borrow_mut().input else {
- return None;
- };
- let event = KeyDownEvent {
- keystroke,
- is_held: lparam.0 & (0x1 << 30) > 0,
- };
- if func(PlatformInput::KeyDown(event)).default_prevented {
- self.invalidate_client_area();
- return Some(0);
- }
- None
- }
-
- fn handle_syskeyup_msg(&self, wparam: WPARAM) -> Option<isize> {
- // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
- // shortcuts.
- let Some(keystroke) = self.parse_syskeydown_msg_keystroke(wparam) else {
- return None;
- };
- let Some(ref mut func) = self.callbacks.borrow_mut().input else {
- return None;
- };
- let event = KeyUpEvent { keystroke };
- if func(PlatformInput::KeyUp(event)).default_prevented {
- self.invalidate_client_area();
- return Some(0);
- }
- None
- }
-
- fn handle_keydown_msg(&self, _msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
- let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else {
- return Some(1);
- };
- let Some(ref mut func) = self.callbacks.borrow_mut().input else {
- return Some(1);
- };
- let event = KeyDownEvent {
- keystroke,
- is_held: lparam.0 & (0x1 << 30) > 0,
- };
- if func(PlatformInput::KeyDown(event)).default_prevented {
- self.invalidate_client_area();
- return Some(0);
- }
- Some(1)
- }
-
- fn handle_keyup_msg(&self, _msg: u32, wparam: WPARAM) -> Option<isize> {
- let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else {
- return Some(1);
- };
- let Some(ref mut func) = self.callbacks.borrow_mut().input else {
- return Some(1);
- };
- let event = KeyUpEvent { keystroke };
- if func(PlatformInput::KeyUp(event)).default_prevented {
- self.invalidate_client_area();
- return Some(0);
- }
- Some(1)
- }
-
- fn handle_char_msg(&self, _msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
- let Some(keystroke) = self.parse_char_msg_keystroke(wparam) else {
- return Some(1);
- };
- let mut callbacks = self.callbacks.borrow_mut();
- let Some(ref mut func) = callbacks.input else {
- return Some(1);
- };
- let ime_key = keystroke.ime_key.clone();
- let event = KeyDownEvent {
- keystroke,
- is_held: lparam.0 & (0x1 << 30) > 0,
- };
-
- let dispatch_event_result = func(PlatformInput::KeyDown(event));
- if dispatch_event_result.default_prevented || !dispatch_event_result.propagate {
- self.invalidate_client_area();
- return Some(0);
- }
- drop(callbacks);
- let Some(ime_char) = ime_key else {
- return Some(1);
- };
- let Some(mut input_handler) = self.input_handler.take() else {
- return Some(1);
- };
- input_handler.replace_text_in_range(None, &ime_char);
- self.input_handler.set(Some(input_handler));
- self.invalidate_client_area();
- Some(0)
- }
-
- fn handle_mouse_down_msg(&self, button: MouseButton, lparam: LPARAM) -> Option<isize> {
- let mut callbacks = self.callbacks.borrow_mut();
- if let Some(callback) = callbacks.input.as_mut() {
- let x = lparam.signed_loword() as f32;
- let y = lparam.signed_hiword() as f32;
- let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
- let click_count = self.click_state.borrow_mut().update(button, physical_point);
- let scale_factor = self.scale_factor.get();
- let event = MouseDownEvent {
- button,
- position: logical_point(x, y, scale_factor),
- modifiers: self.current_modifiers(),
- click_count,
- first_mouse: false,
- };
- if callback(PlatformInput::MouseDown(event)).default_prevented {
- return Some(0);
- }
- }
- Some(1)
- }
-
- fn handle_mouse_up_msg(&self, button: MouseButton, lparam: LPARAM) -> Option<isize> {
- let mut callbacks = self.callbacks.borrow_mut();
- if let Some(callback) = callbacks.input.as_mut() {
- let x = lparam.signed_loword() as f32;
- let y = lparam.signed_hiword() as f32;
- let click_count = self.click_state.borrow().current_count;
- let scale_factor = self.scale_factor.get();
- let event = MouseUpEvent {
- button,
- position: logical_point(x, y, scale_factor),
- modifiers: self.current_modifiers(),
- click_count,
- };
- if callback(PlatformInput::MouseUp(event)).default_prevented {
- return Some(0);
- }
- }
- Some(1)
- }
-
- fn handle_xbutton_msg(
- &self,
- wparam: WPARAM,
- lparam: LPARAM,
- handler: impl Fn(&Self, MouseButton, LPARAM) -> Option<isize>,
- ) -> Option<isize> {
- let nav_dir = match wparam.hiword() {
- XBUTTON1 => NavigationDirection::Back,
- XBUTTON2 => NavigationDirection::Forward,
- _ => return Some(1),
- };
- handler(self, MouseButton::Navigate(nav_dir), lparam)
- }
-
- fn handle_mouse_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
- let mut callbacks = self.callbacks.borrow_mut();
- if let Some(callback) = callbacks.input.as_mut() {
- let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32)
- * self.platform_inner.settings.borrow().wheel_scroll_lines as f32;
- let mut cursor_point = POINT {
- x: lparam.signed_loword().into(),
- y: lparam.signed_hiword().into(),
- };
- unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
- let scale_factor = self.scale_factor.get();
- let event = crate::ScrollWheelEvent {
- position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
- delta: ScrollDelta::Lines(Point {
- x: 0.0,
- y: wheel_distance,
- }),
- modifiers: self.current_modifiers(),
- touch_phase: TouchPhase::Moved,
- };
- callback(PlatformInput::ScrollWheel(event));
- return Some(0);
- }
- Some(1)
- }
-
- fn handle_mouse_horizontal_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
- let mut callbacks = self.callbacks.borrow_mut();
- if let Some(callback) = callbacks.input.as_mut() {
- let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32)
- * self.platform_inner.settings.borrow().wheel_scroll_chars as f32;
- let mut cursor_point = POINT {
- x: lparam.signed_loword().into(),
- y: lparam.signed_hiword().into(),
- };
- unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
- let scale_factor = self.scale_factor.get();
- let event = crate::ScrollWheelEvent {
- position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
- delta: ScrollDelta::Lines(Point {
- x: wheel_distance,
- y: 0.0,
- }),
- modifiers: self.current_modifiers(),
- touch_phase: TouchPhase::Moved,
- };
- if callback(PlatformInput::ScrollWheel(event)).default_prevented {
- return Some(0);
- }
- }
- Some(1)
- }
-
- fn handle_ime_position(&self) -> Option<isize> {
- unsafe {
- let ctx = ImmGetContext(self.hwnd);
- let Some(mut input_handler) = self.input_handler.take() else {
- return Some(1);
- };
- let caret_range = input_handler.selected_text_range().unwrap_or_default();
- let caret_position = input_handler.bounds_for_range(caret_range).unwrap();
- self.input_handler.set(Some(input_handler));
- let scale_factor = self.scale_factor.get();
- let config = CANDIDATEFORM {
- dwStyle: CFS_CANDIDATEPOS,
- // logical to physical
- ptCurrentPos: POINT {
- x: (caret_position.origin.x.0 * scale_factor) as i32,
- y: (caret_position.origin.y.0 * scale_factor) as i32
- + ((caret_position.size.height.0 * scale_factor) as i32 / 2),
- },
- ..Default::default()
- };
- ImmSetCandidateWindow(ctx, &config as _);
- ImmReleaseContext(self.hwnd, ctx);
- Some(0)
- }
- }
-
- fn parse_ime_compostion_string(&self) -> Option<(String, usize)> {
- unsafe {
- let ctx = ImmGetContext(self.hwnd);
- let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0);
- let result = if string_len >= 0 {
- let mut buffer = vec![0u8; string_len as usize + 2];
- ImmGetCompositionStringW(
- ctx,
- GCS_COMPSTR,
- Some(buffer.as_mut_ptr() as _),
- string_len as _,
- );
- let wstring = std::slice::from_raw_parts::<u16>(
- buffer.as_mut_ptr().cast::<u16>(),
- string_len as usize / 2,
- );
- let string = String::from_utf16_lossy(wstring);
- Some((string, string_len as usize / 2))
- } else {
- None
- };
- ImmReleaseContext(self.hwnd, ctx);
- result
- }
- }
-
- fn retrieve_composition_cursor_position(&self) -> usize {
- unsafe {
- let ctx = ImmGetContext(self.hwnd);
- let ret = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0);
- ImmReleaseContext(self.hwnd, ctx);
- ret as usize
- }
- }
-
- fn parse_ime_compostion_result(&self) -> Option<String> {
- unsafe {
- let ctx = ImmGetContext(self.hwnd);
- let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0);
- let result = if string_len >= 0 {
- let mut buffer = vec![0u8; string_len as usize + 2];
- ImmGetCompositionStringW(
- ctx,
- GCS_RESULTSTR,
- Some(buffer.as_mut_ptr() as _),
- string_len as _,
- );
- let wstring = std::slice::from_raw_parts::<u16>(
- buffer.as_mut_ptr().cast::<u16>(),
- string_len as usize / 2,
- );
- let string = String::from_utf16_lossy(wstring);
- Some(string)
- } else {
- None
- };
- ImmReleaseContext(self.hwnd, ctx);
- result
- }
- }
-
- fn handle_ime_composition(&self, lparam: LPARAM) -> Option<isize> {
- let mut ime_input = None;
- if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
- let Some((string, string_len)) = self.parse_ime_compostion_string() else {
- return None;
- };
- let Some(mut input_handler) = self.input_handler.take() else {
- return None;
- };
- input_handler.replace_and_mark_text_in_range(
- None,
- string.as_str(),
- Some(0..string_len),
- );
- self.input_handler.set(Some(input_handler));
- ime_input = Some(string);
- }
- if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
- let Some(ref comp_string) = ime_input else {
- return None;
- };
- let caret_pos = self.retrieve_composition_cursor_position();
- let Some(mut input_handler) = self.input_handler.take() else {
- return None;
- };
- input_handler.replace_and_mark_text_in_range(None, comp_string, Some(0..caret_pos));
- self.input_handler.set(Some(input_handler));
- }
- if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 {
- let Some(comp_result) = self.parse_ime_compostion_result() else {
- return None;
- };
- let Some(mut input_handler) = self.input_handler.take() else {
- return Some(1);
- };
- input_handler.replace_text_in_range(None, &comp_result);
- self.input_handler.set(Some(input_handler));
- self.invalidate_client_area();
- return Some(0);
- }
- // currently, we don't care other stuff
- None
- }
-
- fn handle_drag_drop(&self, input: PlatformInput) {
- let mut callbacks = self.callbacks.borrow_mut();
- let Some(ref mut func) = callbacks.input else {
- return;
- };
- func(input);
- }
-
- /// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
- fn handle_calc_client_size(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
- if !self.hide_title_bar || self.is_fullscreen() {
- return None;
- }
-
- if wparam.0 == 0 {
- return None;
- }
-
- let dpi = unsafe { GetDpiForWindow(self.hwnd) };
-
- let frame_x = unsafe { GetSystemMetricsForDpi(SM_CXFRAME, dpi) };
- let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
- let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
-
- // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
- let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
- let mut requested_client_rect = unsafe { &mut ((*params).rgrc) };
-
- requested_client_rect[0].right -= frame_x + padding;
- requested_client_rect[0].left += frame_x + padding;
- requested_client_rect[0].bottom -= frame_y + padding;
-
- Some(0)
- }
-
- fn handle_activate_msg(&self, wparam: WPARAM) -> Option<isize> {
- if self.hide_title_bar {
- if let Some(titlebar_rect) = self.get_titlebar_rect().log_err() {
- unsafe { InvalidateRect(self.hwnd, Some(&titlebar_rect), FALSE) };
- }
- }
- let activated = wparam.loword() > 0;
- let mut callbacks = self.callbacks.borrow_mut();
- if let Some(mut cb) = callbacks.active_status_change.as_mut() {
- cb(activated);
- }
- None
- }
-
- fn handle_create_msg(&self, _lparam: LPARAM) -> Option<isize> {
- let mut size_rect = RECT::default();
- unsafe { GetWindowRect(self.hwnd, &mut size_rect).log_err() };
-
- let width = size_rect.right - size_rect.left;
- let height = size_rect.bottom - size_rect.top;
-
- self.physical_size.set(Size {
- width: DevicePixels(width),
- height: DevicePixels(height),
- });
-
- if self.hide_title_bar {
- // Inform the application of the frame change to force redrawing with the new
- // client area that is extended into the title bar
- unsafe {
- SetWindowPos(
- self.hwnd,
- HWND::default(),
- size_rect.left,
- size_rect.top,
- width,
- height,
- SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE,
- )
- .log_err()
- };
- }
-
- Some(0)
- }
-
- fn handle_dpi_changed_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
- let new_dpi = wparam.loword() as f32;
- let scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
- self.scale_factor.set(scale_factor);
- let rect = unsafe { &*(lparam.0 as *const RECT) };
- let width = rect.right - rect.left;
- let height = rect.bottom - rect.top;
- // this will emit `WM_SIZE` and `WM_MOVE` right here
- // even before this function returns
- // the new size is handled in `WM_SIZE`
- unsafe {
- SetWindowPos(
- self.hwnd,
- None,
- rect.left,
- rect.top,
- width,
- height,
- SWP_NOZORDER | SWP_NOACTIVATE,
- )
- .context("unable to set window position after dpi has changed")
- .log_err();
- }
- self.invalidate_client_area();
- Some(0)
- }
-
- fn handle_hit_test_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
- if !self.hide_title_bar {
- return None;
- }
-
- // default handler for resize areas
- let hit = unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) };
- if matches!(
- hit.0 as u32,
- HTNOWHERE
- | HTRIGHT
- | HTLEFT
- | HTTOPLEFT
- | HTTOP
- | HTTOPRIGHT
- | HTBOTTOMRIGHT
- | HTBOTTOM
- | HTBOTTOMLEFT
- ) {
- return Some(hit.0);
- }
-
- if self.is_fullscreen() {
- return Some(HTCLIENT as _);
- }
-
- let dpi = unsafe { GetDpiForWindow(self.hwnd) };
- let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
- let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
-
- let mut cursor_point = POINT {
- x: lparam.signed_loword().into(),
- y: lparam.signed_hiword().into(),
- };
- unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
- if cursor_point.y > 0 && cursor_point.y < frame_y + padding {
- return Some(HTTOP as _);
- }
-
- let titlebar_rect = self.get_titlebar_rect();
- if let Ok(titlebar_rect) = titlebar_rect {
- if cursor_point.y < titlebar_rect.bottom {
- let caption_btn_width =
- (self.caption_button_width().0 * self.scale_factor.get()) as i32;
- if cursor_point.x >= titlebar_rect.right - caption_btn_width {
- return Some(HTCLOSE as _);
- } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 {
- return Some(HTMAXBUTTON as _);
- } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 {
- return Some(HTMINBUTTON as _);
- }
-
- return Some(HTCAPTION as _);
- }
- }
-
- Some(HTCLIENT as _)
- }
-
- fn handle_nc_mouse_move_msg(&self, lparam: LPARAM) -> Option<isize> {
- if !self.hide_title_bar {
- return None;
- }
-
- let mut callbacks = self.callbacks.borrow_mut();
- if let Some(callback) = callbacks.input.as_mut() {
- let mut cursor_point = POINT {
- x: lparam.signed_loword().into(),
- y: lparam.signed_hiword().into(),
- };
- unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
- let scale_factor = self.scale_factor.get();
- let event = MouseMoveEvent {
- position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
- pressed_button: None,
- modifiers: self.current_modifiers(),
- };
- if callback(PlatformInput::MouseMove(event)).default_prevented {
- return Some(0);
- }
- }
- None
- }
-
- fn handle_nc_mouse_down_msg(
- &self,
- button: MouseButton,
- wparam: WPARAM,
- lparam: LPARAM,
- ) -> Option<isize> {
- if !self.hide_title_bar {
- return None;
- }
-
- let mut callbacks = self.callbacks.borrow_mut();
- if let Some(callback) = callbacks.input.as_mut() {
- let mut cursor_point = POINT {
- x: lparam.signed_loword().into(),
- y: lparam.signed_hiword().into(),
- };
- unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
- let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
- let click_count = self.click_state.borrow_mut().update(button, physical_point);
- let scale_factor = self.scale_factor.get();
- let event = MouseDownEvent {
- button,
- position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
- modifiers: self.current_modifiers(),
- click_count,
- first_mouse: false,
- };
- if callback(PlatformInput::MouseDown(event)).default_prevented {
- return Some(0);
- }
- }
-
- // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
- matches!(wparam.0 as u32, HTMINBUTTON | HTMAXBUTTON | HTCLOSE).then_some(0)
- }
-
- fn handle_nc_mouse_up_msg(
- &self,
- button: MouseButton,
- wparam: WPARAM,
- lparam: LPARAM,
- ) -> Option<isize> {
- if !self.hide_title_bar {
- return None;
- }
-
- let mut callbacks = self.callbacks.borrow_mut();
- if let Some(callback) = callbacks.input.as_mut() {
- let mut cursor_point = POINT {
- x: lparam.signed_loword().into(),
- y: lparam.signed_hiword().into(),
- };
- unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
- let scale_factor = self.scale_factor.get();
- let event = MouseUpEvent {
- button,
- position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
- modifiers: self.current_modifiers(),
- click_count: 1,
- };
- if callback(PlatformInput::MouseUp(event)).default_prevented {
- return Some(0);
- }
- }
- drop(callbacks);
-
- if button == MouseButton::Left {
- match wparam.0 as u32 {
- HTMINBUTTON => unsafe {
- ShowWindowAsync(self.hwnd, SW_MINIMIZE);
- },
- HTMAXBUTTON => unsafe {
- if self.is_maximized() {
- ShowWindowAsync(self.hwnd, SW_NORMAL);
- } else {
- ShowWindowAsync(self.hwnd, SW_MAXIMIZE);
- }
- },
- HTCLOSE => unsafe {
- PostMessageW(self.hwnd, WM_CLOSE, WPARAM::default(), LPARAM::default())
- .log_err();
- },
- _ => return None,
- };
- return Some(0);
- }
+impl WindowsWindowStatePtr {
+ fn new(context: &WindowCreateContext, hwnd: HWND, cs: &CREATESTRUCTW) -> Rc<Self> {
+ let state = RefCell::new(WindowsWindowState::new(
+ hwnd,
+ context.transparent,
+ cs,
+ context.mouse_wheel_settings,
+ context.current_cursor,
+ context.display,
+ ));
- None
+ Rc::new(Self {
+ state,
+ hwnd,
+ handle: context.handle,
+ hide_title_bar: context.hide_title_bar,
+ executor: context.executor.clone(),
+ main_receiver: context.main_receiver.clone(),
+ })
}
- fn handle_set_cursor(&self, lparam: LPARAM) -> Option<isize> {
- if matches!(
- lparam.loword() as u32,
- HTLEFT
- | HTRIGHT
- | HTTOP
- | HTTOPLEFT
- | HTTOPRIGHT
- | HTBOTTOM
- | HTBOTTOMLEFT
- | HTBOTTOMRIGHT
- ) {
- return None;
- }
- unsafe { SetCursor(self.platform_inner.current_cursor.get()) };
- Some(1)
+ fn is_minimized(&self) -> bool {
+ unsafe { IsIconic(self.hwnd) }.as_bool()
}
}
#[derive(Default)]
-struct Callbacks {
- request_frame: Option<Box<dyn FnMut()>>,
- input: Option<Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>>,
- active_status_change: Option<Box<dyn FnMut(bool)>>,
- resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
- moved: Option<Box<dyn FnMut()>>,
- should_close: Option<Box<dyn FnMut() -> bool>>,
- close: Option<Box<dyn FnOnce()>>,
- appearance_changed: Option<Box<dyn FnMut()>>,
-}
-
-pub(crate) struct WindowsWindow {
- inner: Rc<WindowsWindowInner>,
- drag_drop_handler: IDropTarget,
+pub(crate) struct Callbacks {
+ pub(crate) request_frame: Option<Box<dyn FnMut()>>,
+ pub(crate) input: Option<Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>>,
+ pub(crate) active_status_change: Option<Box<dyn FnMut(bool)>>,
+ pub(crate) resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
+ pub(crate) moved: Option<Box<dyn FnMut()>>,
+ pub(crate) should_close: Option<Box<dyn FnMut() -> bool>>,
+ pub(crate) close: Option<Box<dyn FnOnce()>>,
+ pub(crate) appearance_changed: Option<Box<dyn FnMut()>>,
}
struct WindowCreateContext {
- inner: Option<Rc<WindowsWindowInner>>,
- platform_inner: Rc<WindowsPlatformInner>,
+ inner: Option<Rc<WindowsWindowStatePtr>>,
handle: AnyWindowHandle,
hide_title_bar: bool,
- display: Rc<WindowsDisplay>,
+ display: WindowsDisplay,
transparent: bool,
+ executor: ForegroundExecutor,
+ main_receiver: flume::Receiver<Runnable>,
+ mouse_wheel_settings: MouseWheelSettings,
+ current_cursor: HCURSOR,
}
impl WindowsWindow {
pub(crate) fn new(
- platform_inner: Rc<WindowsPlatformInner>,
handle: AnyWindowHandle,
options: WindowParams,
+ icon: HICON,
+ executor: ForegroundExecutor,
+ main_receiver: flume::Receiver<Runnable>,
+ mouse_wheel_settings: MouseWheelSettings,
+ current_cursor: HCURSOR,
) -> Self {
- let classname = register_wnd_class(platform_inner.icon);
+ let classname = register_wnd_class(icon);
let hide_title_bar = options
.titlebar
.as_ref()