use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::rc::{Rc, Weak};
use windows::System::DispatcherQueueController;
use windows::Win32::Foundation::{HINSTANCE, HWND, LPARAM, WPARAM};
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::System::WinRT::{
CreateDispatcherQueueController, DQTAT_COM_STA, DQTYPE_THREAD_CURRENT, DispatcherQueueOptions,
RO_INIT_SINGLETHREADED, RoInitialize,
};
use windows::Win32::UI::Controls::{
ICC_STANDARD_CLASSES, INITCOMMONCONTROLSEX, InitCommonControlsEx,
};
use windows::Win32::UI::HiDpi::{
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, SetProcessDpiAwarenessContext,
};
use windows::Win32::UI::WindowsAndMessaging::{IsDialogMessageW, MSG, PostMessageW, WM_NULL};
use crate::error::{Call, Result};
thread_local! {
static CURRENT: RefCell<Weak<Inner>> = const { RefCell::new(Weak::new()) };
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Notice {
Popup(u32),
Dialog(u32),
}
struct Inner {
hinstance: HINSTANCE,
_dispatcher: DispatcherQueueController,
next_id: Cell<u32>,
notices: RefCell<VecDeque<Notice>>,
dialogs: RefCell<Vec<HWND>>,
}
#[derive(Clone)]
pub struct Ui {
inner: Rc<Inner>,
}
impl Ui {
pub fn new() -> Result<Self> {
if let Some(inner) = CURRENT.with_borrow(Weak::upgrade) {
return Ok(Self { inner });
}
unsafe {
let _ = SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
let _ = RoInitialize(RO_INIT_SINGLETHREADED);
let _ = InitCommonControlsEx(&INITCOMMONCONTROLSEX {
dwSize: size_of::<INITCOMMONCONTROLSEX>() as u32,
dwICC: ICC_STANDARD_CLASSES,
});
let hinstance: HINSTANCE = GetModuleHandleW(None).call("GetModuleHandleW")?.into();
let dispatcher = CreateDispatcherQueueController(DispatcherQueueOptions {
dwSize: size_of::<DispatcherQueueOptions>() as u32,
threadType: DQTYPE_THREAD_CURRENT,
apartmentType: DQTAT_COM_STA,
})
.call("CreateDispatcherQueueController")?;
let inner = Rc::new(Inner {
hinstance,
_dispatcher: dispatcher,
next_id: Cell::new(1),
notices: RefCell::new(VecDeque::new()),
dialogs: RefCell::new(Vec::new()),
});
CURRENT.set(Rc::downgrade(&inner));
Ok(Self { inner })
}
}
pub(crate) fn hinstance(&self) -> HINSTANCE {
self.inner.hinstance
}
pub(crate) fn new_id(&self) -> u32 {
let id = self.inner.next_id.get();
self.inner.next_id.set(id + 1);
id
}
pub(crate) fn take_notice(&self) -> Option<Notice> {
self.inner.notices.borrow_mut().pop_front()
}
pub(crate) fn add_dialog(&self, hwnd: HWND) {
self.inner.dialogs.borrow_mut().push(hwnd);
}
pub(crate) fn remove_dialog(&self, hwnd: HWND) {
self.inner.dialogs.borrow_mut().retain(|&h| h != hwnd);
}
pub(crate) fn dialog_message(&self, msg: &MSG) -> bool {
let dialogs = self.inner.dialogs.borrow().clone();
dialogs
.into_iter()
.any(|hwnd| unsafe { IsDialogMessageW(hwnd, msg) }.as_bool())
}
}
pub(crate) fn notify(notice: Notice) {
if let Some(inner) = CURRENT.with_borrow(Weak::upgrade) {
let mut notices = inner.notices.borrow_mut();
if !notices.contains(¬ice) {
notices.push_back(notice);
}
}
unsafe {
let _ = PostMessageW(None, WM_NULL, WPARAM(0), LPARAM(0));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_thread_shares_one_setup() {
let a = Ui::new().unwrap();
let b = Ui::new().unwrap();
assert!(Rc::ptr_eq(&a.inner, &b.inner));
}
#[test]
fn setup_ends_with_the_last_copy_and_can_start_again() {
let first = Ui::new().unwrap();
let weak = Rc::downgrade(&first.inner);
drop(first);
assert!(weak.upgrade().is_none());
assert!(Ui::new().is_ok());
}
}
#[cfg(test)]
mod notice_tests {
use super::*;
#[test]
fn notices_come_once_in_order() {
let ui = Ui::new().unwrap();
notify(Notice::Popup(1));
notify(Notice::Dialog(2));
notify(Notice::Popup(1));
assert_eq!(ui.take_notice(), Some(Notice::Popup(1)));
assert_eq!(ui.take_notice(), Some(Notice::Dialog(2)));
assert_eq!(ui.take_notice(), None);
}
#[test]
fn ids_are_new_each_time() {
let ui = Ui::new().unwrap();
assert_ne!(ui.new_id(), ui.new_id());
}
}