use std::cell::{Cell, RefCell}; use std::rc::Rc; use thunderdome::{Arena, Index}; use windows::core::{w, PCWSTR}; use windows::Win32::Foundation::{HMODULE, HWND, LPARAM, LRESULT, WPARAM}; use windows::Win32::Graphics::Gdi::{BeginPaint, EndPaint, FillRect, COLOR_WINDOWFRAME, HBRUSH, PAINTSTRUCT}; use windows::Win32::System::Performance::QueryPerformanceCounter; use windows::Win32::UI::Input::KeyboardAndMouse::{ReleaseCapture, SetCapture}; use windows::Win32::UI::WindowsAndMessaging::{ CreateWindowExW, DefWindowProcW, GetWindowLongPtrW, LoadCursorW, PostQuitMessage, RegisterClassW, SetWindowLongPtrW, CREATESTRUCTW, CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, GWLP_USERDATA, IDC_ARROW, WINDOW_EX_STYLE, WM_CHAR, WM_CREATE, WM_DESTROY, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_STARTCOMPOSITION, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCCREATE, WM_PAINT, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SIZE, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_OVERLAPPEDWINDOW, WS_SIZEBOX, WS_VISIBLE, }; use crate::win::WinEvent; use crate::{hiword, loword}; use super::{Platform, WinHandle, WinRef}; pub type RcDynWinDelegate = Rc>; pub struct Win { _key: String, delegate: RcDynWinDelegate, hwnd: Cell, } impl Win { unsafe fn proc(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option { let hwnd = self.hwnd.get(); let winref = WinRef(&hwnd); match msg { WM_CREATE => { self.delegate.on_create(winref); } WM_DESTROY => { self.delegate.on_destroy(winref); PostQuitMessage(0); } WM_PAINT => { let mut nanos = 0; _ = QueryPerformanceCounter(&mut nanos); self.delegate.req_draw(winref, nanos); // TODO remove let mut ps = PAINTSTRUCT::default(); let hdc = BeginPaint(hwnd, &mut ps); FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOWFRAME.0 as _)); _ = EndPaint(hwnd, &ps); } WM_SIZE => { let w: u32 = loword(lparam.0 as _) as _; let h: u32 = hiword(lparam.0 as _) as _; self.delegate.on_resize(winref, (w, h)); use types::WinRef as _; winref.fresh("WM_SIZE"); } WM_USER => { self.delegate.on_proxy(winref, lparam.0 as _); } WM_LBUTTONDOWN | WM_LBUTTONUP | WM_RBUTTONDOWN | WM_RBUTTONUP | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_XBUTTONDOWN | WM_XBUTTONUP | WM_MOUSEMOVE => { match msg { WM_LBUTTONDOWN | WM_RBUTTONDOWN | WM_MBUTTONDOWN | WM_XBUTTONDOWN => _ = SetCapture(hwnd), WM_LBUTTONUP | WM_RBUTTONUP | WM_MBUTTONUP | WM_XBUTTONUP => _ = ReleaseCapture(), _ => {} } self.delegate.on_mouse(winref, WinEvent(&(), msg, wparam, lparam)); } WM_KEYDOWN | WM_KEYUP => { self.delegate.on_key(winref, WinEvent(&(), msg, wparam, lparam)); } WM_SYSKEYDOWN | WM_SYSKEYUP => { self.delegate.on_key(winref, WinEvent(&(), msg, wparam, lparam)); return DefWindowProcW(hwnd, msg, wparam, lparam).into(); } WM_MOUSEWHEEL | WM_MOUSEHWHEEL => { self.delegate.on_wheel(winref, WinEvent(&(), msg, wparam, lparam)); } WM_IME_STARTCOMPOSITION => { // println!("WM_IME_STARTCOMPOSITION {wparam:?}"); self.delegate.on_commit(winref, &types::EvtCommit::begin()); return DefWindowProcW(hwnd, msg, wparam, lparam).into(); } WM_IME_COMPOSITION => { // println!("WM_IME_COMPOSITION {wparam:?}"); let ime_context = unsafe { ImeContext::current(hwnd) }; if let Some((s, _, _)) = ime_context.get_composing_text_and_cursor() { self.delegate.on_commit(winref, &types::EvtCommit::update(s.into())); } } WM_IME_ENDCOMPOSITION => { // println!("WM_IME_ENDCOMPOSITION {wparam:?}"); let ime_context = unsafe { ImeContext::current(hwnd) }; if let Some(s) = ime_context.get_composed_text() { self.delegate.on_commit(winref, &types::EvtCommit::end(s.into())); } return DefWindowProcW(hwnd, msg, wparam, lparam).into(); } WM_CHAR => { // println!("WM_CHAR {wparam:?}"); match wparam.0 { 8 | 127 => _ = self.delegate.on_commit(winref, &types::EvtCommit::back()), v => { let s = char::from_u32(v as _).map(|v| v.to_string()).unwrap_or_default(); self.delegate.on_commit(winref, &types::EvtCommit::end(s.into())); } } } _ => return DefWindowProcW(hwnd, msg, wparam, lparam).into(), } None } } pub type Key = Index; thread_local! { static CACHE:RefCell> = Default::default(); static WCLS:RefCell> = Default::default(); } pub(crate) fn _with_win_mut(key: Key, mut fun: impl FnMut(&mut Win) -> Option) -> Option { CACHE.with_borrow_mut(|vars| vars.get_mut(key).map(|win| fun(win)).flatten()) } pub(crate) fn with_win(key: Key, mut fun: impl FnMut(&Win) -> Option) -> Option { CACHE.with_borrow(|vars| vars.get(key).map(|win| fun(win)).flatten()) } pub(crate) fn _with_wins(mut fun: impl FnMut(&Arena) -> T) -> T { CACHE.with_borrow(|vars: &Arena| fun(vars)) } impl WinHandle { pub unsafe fn reg(instance: HMODULE) -> Option { WCLS.with_borrow_mut(|cls| { let window_class = w!("XLoopWindow"); if cls.is_none() { let wc = WNDCLASSW { hCursor: LoadCursorW(None, IDC_ARROW).ok()?, hInstance: instance.into(), lpszClassName: window_class, style: CS_HREDRAW | CS_VREDRAW, lpfnWndProc: Some(wnd_proc), ..Default::default() }; let atom = RegisterClassW(&wc); debug_assert!(atom != 0); *cls = Some(atom); } Some(window_class) }) } pub unsafe fn new(key: impl AsRef, delegate: impl types::WinDelegate + 'static) -> Option { use types::AppHandle; let instance = crate::app::AppHandle::singleton()?.0; let window_class = Self::reg(instance)?; let idx = CACHE.with_borrow_mut(|wins| { wins.insert(Win { _key: key.as_ref().to_string(), delegate: Rc::new(delegate), hwnd: Cell::new(HWND(0)), }) }); let hwnd = CreateWindowExW( WINDOW_EX_STYLE::default(), window_class, w!("This is a window"), WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_SIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, 350, 700, None, None, instance, Some(idx.to_bits() as _), ); Some(WinHandle(hwnd)) } } extern "system" fn wnd_proc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { unsafe { let userdata = GetWindowLongPtrW(hwnd, GWLP_USERDATA); let Some(key) = (match (userdata, msg) { (0, WM_NCCREATE) => { let createstruct = &mut *(lparam.0 as *mut CREATESTRUCTW); SetWindowLongPtrW(hwnd, GWLP_USERDATA, createstruct.lpCreateParams as _); // log::info!("WM_NCCREATE"); return DefWindowProcW(hwnd, msg, wparam, lparam); } (0, _) => { // log::info!("BEFORE WM_NCCREATE : {}", msg); return DefWindowProcW(hwnd, msg, wparam, lparam); } _ => Key::from_bits(userdata as _), }) else { // log::warn!("EMPTY KEY : {}", msg); return DefWindowProcW(hwnd, msg, wparam, lparam); }; with_win(key, |win| { if win.hwnd.get() == HWND(0) { win.hwnd.set(hwnd); } win.proc(msg, wparam, lparam) }) .unwrap_or(LRESULT(0)) } } mod ime { use std::ffi::c_void; use windows::Win32::{ Foundation::{HWND, POINT, RECT}, Globalization::HIMC, UI::{ Input::Ime::{ ImmAssociateContextEx, ImmGetCompositionStringW, ImmGetContext, ImmReleaseContext, ImmSetCandidateWindow, ImmSetCompositionWindow, ATTR_TARGET_CONVERTED, ATTR_TARGET_NOTCONVERTED, CANDIDATEFORM, CFS_EXCLUDE, CFS_POINT, COMPOSITIONFORM, GCS_COMPATTR, GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, IACE_CHILDREN, IACE_DEFAULT, IME_COMPOSITION_STRING, }, WindowsAndMessaging::{GetSystemMetrics, SM_IMMENABLED}, }, }; pub struct ImeContext { hwnd: HWND, himc: HIMC, } impl ImeContext { pub unsafe fn current(hwnd: HWND) -> Self { let himc = unsafe { ImmGetContext(hwnd) }; ImeContext { hwnd, himc } } pub unsafe fn get_composing_text_and_cursor(&self) -> Option<(String, Option, Option)> { let text = unsafe { self.get_composition_string(GCS_COMPSTR.0) }?; let attrs = unsafe { self.get_composition_data(GCS_COMPATTR.0) }.unwrap_or_default(); let mut first = None; let mut last = None; let mut boundary_before_char = 0; for (attr, chr) in attrs.into_iter().zip(text.chars()) { let char_is_targeted = attr as u32 == ATTR_TARGET_CONVERTED || attr as u32 == ATTR_TARGET_NOTCONVERTED; if first.is_none() && char_is_targeted { first = Some(boundary_before_char); } else if first.is_some() && last.is_none() && !char_is_targeted { last = Some(boundary_before_char); } boundary_before_char += chr.len_utf8(); } if first.is_some() && last.is_none() { last = Some(text.len()); } else if first.is_none() { // IME haven't split words and select any clause yet, so trying to retrieve normal // cursor. let cursor = unsafe { self.get_composition_cursor(&text) }; first = cursor; last = cursor; } Some((text, first, last)) } pub unsafe fn get_composed_text(&self) -> Option { unsafe { self.get_composition_string(GCS_RESULTSTR.0) } } unsafe fn get_composition_cursor(&self, text: &str) -> Option { let cursor = unsafe { ImmGetCompositionStringW(self.himc, GCS_CURSORPOS, None, 0) }; (cursor >= 0).then(|| text.chars().take(cursor as _).map(|c| c.len_utf8()).sum()) } unsafe fn get_composition_string(&self, gcs_mode: u32) -> Option { let data = unsafe { self.get_composition_data(gcs_mode) }?; let (prefix, _shorts, suffix) = unsafe { data.align_to::() }; if prefix.is_empty() && suffix.is_empty() { #[cfg(target_os = "windows")] use std::os::windows::prelude::OsStringExt; #[cfg(target_os = "windows")] return std::ffi::OsString::from_wide(_shorts).into_string().ok(); } None } unsafe fn get_composition_data(&self, gcs_mode: u32) -> Option> { let size = match unsafe { ImmGetCompositionStringW(self.himc, IME_COMPOSITION_STRING(gcs_mode), None, 0) } { 0 => return Some(Vec::new()), size if size < 0 => return None, size => size, }; let mut buf = Vec::::with_capacity(size as _); let size = unsafe { ImmGetCompositionStringW( self.himc, IME_COMPOSITION_STRING(gcs_mode), Some(buf.as_mut_ptr() as *mut c_void), size as _, ) }; if size < 0 { None } else { unsafe { buf.set_len(size as _) }; Some(buf) } } pub unsafe fn set_ime_cursor_area(&self, spot: (i32, i32), size: (i32, i32), _scale_factor: f64) { if !unsafe { ImeContext::system_has_ime() } { return; } let (x, y) = spot; //.to_physical::(scale_factor).into(); let (width, height): (i32, i32) = size; //.to_physical::(scale_factor).into(); let rc_area = RECT { left: x, top: y, right: x + width, bottom: y + height, }; let candidate_form = CANDIDATEFORM { dwIndex: 0, dwStyle: CFS_EXCLUDE, ptCurrentPos: POINT { x, y }, rcArea: rc_area, }; let composition_form = COMPOSITIONFORM { dwStyle: CFS_POINT, ptCurrentPos: POINT { x, y: y + height }, rcArea: rc_area, }; unsafe { _ = ImmSetCompositionWindow(self.himc, &composition_form); _ = ImmSetCandidateWindow(self.himc, &candidate_form); } } pub unsafe fn set_ime_allowed(hwnd: HWND, allowed: bool) { if !unsafe { ImeContext::system_has_ime() } { return; } let context = Self::current(hwnd); if allowed { _ = unsafe { ImmAssociateContextEx(hwnd, context.himc, IACE_DEFAULT) }; } else { _ = unsafe { ImmAssociateContextEx(hwnd, context.himc, IACE_CHILDREN) }; } } unsafe fn system_has_ime() -> bool { unsafe { GetSystemMetrics(SM_IMMENABLED) != 0 } } } impl Drop for ImeContext { fn drop(&mut self) { _ = unsafe { ImmReleaseContext(self.hwnd, self.himc) }; } } } pub use ime::*;