use std::cell::Cell;
use std::thread::LocalKey;
use super::key::EditCommand;
use super::pointer::CursorIcon;
thread_local! {
pub(super) static CURSOR_REQUEST: Cell<Option<CursorIcon>> = const { Cell::new(None) };
pub(super) static CLIPBOARD_WRITE: Cell<Option<String>> = const { Cell::new(None) };
pub(super) static PASTE_REQUEST: Cell<bool> = const { Cell::new(false) };
pub(super) static EDIT_COMMAND_QUEUE: Cell<Vec<EditCommand>> = const { Cell::new(Vec::new()) };
static REQUEST_PASS_OPEN: Cell<bool> = const { Cell::new(false) };
}
pub(crate) struct PassSlot<T: Default + 'static> {
slot: &'static LocalKey<Cell<T>>,
restore: T,
}
impl<T: Default + 'static> PassSlot<T> {
pub(crate) fn enter(slot: &'static LocalKey<Cell<T>>, nested: bool) -> Self {
let stashed = slot.with(|cell| cell.take());
Self {
slot,
restore: if nested { stashed } else { T::default() },
}
}
pub(crate) fn take(&self) -> T {
self.slot.with(|cell| cell.take())
}
}
impl<T: Default + 'static> Drop for PassSlot<T> {
fn drop(&mut self) {
let restore = std::mem::take(&mut self.restore);
self.slot.with(|cell| cell.set(restore));
}
}
pub(crate) struct PassBracket<T: Default + 'static> {
slot: PassSlot<T>,
open: &'static LocalKey<Cell<bool>>,
was_open: bool,
}
impl<T: Default + 'static> PassBracket<T> {
pub(crate) fn enter(
slot: &'static LocalKey<Cell<T>>,
open: &'static LocalKey<Cell<bool>>,
) -> Self {
let was_open = open.with(|flag| flag.replace(true));
Self {
slot: PassSlot::enter(slot, was_open),
open,
was_open,
}
}
pub(crate) fn take(&self) -> T {
self.slot.take()
}
}
impl<T: Default + 'static> Drop for PassBracket<T> {
fn drop(&mut self) {
self.open.with(|flag| flag.set(self.was_open));
}
}
pub(crate) struct PassRequests {
pub(crate) cursor: Option<CursorIcon>,
pub(crate) clipboard_write: Option<String>,
pub(crate) paste_request: bool,
}
pub(crate) struct RequestPass {
cursor: PassSlot<Option<CursorIcon>>,
clipboard_write: PassSlot<Option<String>>,
paste_request: PassSlot<bool>,
edit_commands: PassSlot<Vec<EditCommand>>,
was_open: bool,
}
impl RequestPass {
pub(crate) fn enter() -> Self {
let was_open = REQUEST_PASS_OPEN.with(|open| open.replace(true));
Self {
cursor: PassSlot::enter(&CURSOR_REQUEST, was_open),
clipboard_write: PassSlot::enter(&CLIPBOARD_WRITE, was_open),
paste_request: PassSlot::enter(&PASTE_REQUEST, was_open),
edit_commands: PassSlot::enter(&EDIT_COMMAND_QUEUE, was_open),
was_open,
}
}
pub(crate) fn take(self) -> PassRequests {
let leaked = self.edit_commands.take();
#[cfg(debug_assertions)]
if !leaked.is_empty() {
eprintln!(
"frust-core: {} edit command(s) dispatched but never taken in this \
pass ({leaked:?}); clearing — the dispatching widget's consumer \
must call EventCtx::take_edit_commands in the same pass",
leaked.len()
);
}
drop(leaked);
PassRequests {
cursor: self.cursor.take(),
clipboard_write: self.clipboard_write.take(),
paste_request: self.paste_request.take(),
}
}
}
impl Drop for RequestPass {
fn drop(&mut self) {
REQUEST_PASS_OPEN.with(|open| open.set(self.was_open));
}
}
#[cfg(test)]
pub(crate) fn clear_cursor_request() {
CURSOR_REQUEST.with(|slot| slot.set(None));
}
#[cfg(test)]
pub(crate) fn take_cursor_request() -> Option<CursorIcon> {
CURSOR_REQUEST.with(|slot| slot.take())
}