use std::cell::RefCell;
use std::collections::VecDeque;
use std::ffi::c_void;
use std::io;
use std::mem::ManuallyDrop;
use std::rc::Rc;
use windows::Win32::Foundation::LPARAM;
use super::config::{MAX_WINDOW_EVENTS, allocation_error};
use super::event::{CompositionPhase, ModifierState, WindowEvent};
use super::hotkey::MAX_PENDING_HOTKEY_PRESSES;
use super::input::update_modifier;
use super::menu_bar::MAX_PENDING_MENU_COMMANDS;
use super::tray::{MAX_PENDING_TRAY_EVENTS, TrayEvent, decode_tray_event};
#[derive(Debug)]
pub(super) struct PresentedFrame {
pub(super) width: u32,
pub(super) height: u32,
pub(super) pixels: Vec<u32>,
}
#[derive(Debug)]
pub(super) struct WindowState {
pub(super) events: VecDeque<WindowEvent>,
pub(super) hotkey_presses: VecDeque<u16>,
pub(super) menu_commands: VecDeque<u16>,
pub(super) tray_events: VecDeque<TrayEvent>,
pub(super) frame: Option<PresentedFrame>,
pending_high_surrogate: Option<u16>,
pub(super) composition_active: bool,
pub(super) modifiers: ModifierState,
pub(super) overflowed: bool,
pub(super) error: Option<io::Error>,
}
impl WindowState {
pub(super) fn new() -> io::Result<Self> {
let mut events = VecDeque::new();
events
.try_reserve(MAX_WINDOW_EVENTS)
.map_err(|_| allocation_error())?;
let mut hotkey_presses = VecDeque::new();
hotkey_presses
.try_reserve(MAX_PENDING_HOTKEY_PRESSES)
.map_err(|_| allocation_error())?;
let mut menu_commands = VecDeque::new();
menu_commands
.try_reserve(MAX_PENDING_MENU_COMMANDS)
.map_err(|_| allocation_error())?;
let mut tray_events = VecDeque::new();
tray_events
.try_reserve(MAX_PENDING_TRAY_EVENTS)
.map_err(|_| allocation_error())?;
Ok(Self {
events,
hotkey_presses,
menu_commands,
tray_events,
frame: None,
pending_high_surrogate: None,
composition_active: false,
modifiers: ModifierState::NONE,
overflowed: false,
error: None,
})
}
pub(super) fn push(&mut self, event: WindowEvent) {
if self.events.len() >= MAX_WINDOW_EVENTS {
self.overflowed = true;
} else {
self.events.push_back(event);
}
}
pub(super) fn push_hotkey(&mut self, id: usize) {
if let Ok(id) = u16::try_from(id)
&& self.hotkey_presses.len() < MAX_PENDING_HOTKEY_PRESSES
{
self.hotkey_presses.push_back(id);
}
}
pub(super) fn push_menu_command(&mut self, wparam: usize, lparam: isize) {
let code = (wparam >> 16) & 0xffff;
if code == 0 && lparam == 0 && self.menu_commands.len() < MAX_PENDING_MENU_COMMANDS {
self.menu_commands.push_back((wparam & 0xffff) as u16);
}
}
pub(super) fn push_tray(&mut self, wparam: usize, lparam: isize) {
if let Some(event) = decode_tray_event(wparam, lparam)
&& self.tray_events.len() < MAX_PENDING_TRAY_EVENTS
{
self.tray_events.push_back(event);
}
}
pub(super) fn push_composition(&mut self, phase: CompositionPhase, text: String) {
self.composition_active =
matches!(phase, CompositionPhase::Started | CompositionPhase::Updated);
self.push(WindowEvent::TextComposition { phase, text });
}
pub(super) fn record_error(&mut self, error: io::Error) {
if self.error.is_none() {
self.error = Some(error);
}
}
pub(super) fn update_modifier(&mut self, virtual_key: u32, lparam: LPARAM, pressed: bool) {
self.modifiers = update_modifier(self.modifiers, virtual_key, lparam, pressed);
}
pub(super) fn clear_modifiers(&mut self) {
self.modifiers = ModifierState::NONE;
}
pub(super) fn push_text_unit(&mut self, unit: u16) {
match (self.pending_high_surrogate.take(), unit) {
(Some(high), low @ 0xdc00..=0xdfff) => {
let scalar =
0x1_0000 + ((u32::from(high) - 0xd800) << 10) + (u32::from(low) - 0xdc00);
if let Some(character) = char::from_u32(scalar) {
self.push(WindowEvent::TextInput { character });
} else {
self.push(WindowEvent::TextInput {
character: '\u{fffd}',
});
}
}
(Some(_), _) => {
self.push(WindowEvent::TextInput {
character: '\u{fffd}',
});
self.push_text_unit(unit);
}
(None, high @ 0xd800..=0xdbff) => {
self.pending_high_surrogate = Some(high);
}
(None, 0xdc00..=0xdfff) => {
self.push(WindowEvent::TextInput {
character: '\u{fffd}',
});
}
(None, unit) => {
if let Some(character) = char::from_u32(u32::from(unit)) {
self.push(WindowEvent::TextInput { character });
}
}
}
}
pub(super) fn finish_text(&mut self) {
if self.pending_high_surrogate.take().is_some() {
self.push(WindowEvent::TextInput {
character: '\u{fffd}',
});
}
}
}
#[derive(Clone, Debug)]
pub(super) struct SharedWindowState(Rc<RefCell<WindowState>>);
impl SharedWindowState {
pub(super) fn new(state: WindowState) -> Self {
Self(Rc::new(RefCell::new(state)))
}
pub(super) fn with<R>(&self, operation: impl FnOnce(&mut WindowState) -> R) -> R {
operation(&mut self.0.borrow_mut())
}
pub(super) fn create_param(&self) -> *const c_void {
Rc::as_ptr(&self.0).cast()
}
pub(super) unsafe fn adopt_create_param(create_param: *const c_void) -> isize {
let cell = create_param.cast::<RefCell<WindowState>>();
unsafe { Rc::increment_strong_count(cell) };
cell as isize
}
pub(super) unsafe fn borrow_userdata(userdata: isize) -> Option<ManuallyDrop<Self>> {
let cell = userdata as *const RefCell<WindowState>;
if cell.is_null() {
return None;
}
Some(ManuallyDrop::new(Self(unsafe { Rc::from_raw(cell) })))
}
pub(super) fn release_userdata(state: ManuallyDrop<Self>) {
drop(ManuallyDrop::into_inner(state));
}
#[cfg(test)]
pub(super) fn holders(&self) -> usize {
Rc::strong_count(&self.0)
}
}
pub(super) fn decode_composition(units: &[u16]) -> io::Result<String> {
if units.len() > super::config::MAX_COMPOSITION_UNITS {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"native IME composition exceeds the bounded UTF-16 limit",
));
}
String::from_utf16(units).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"native IME composition contains invalid UTF-16",
)
})
}