use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::rc::Rc;
use crate::scope::Scope;
use crate::{Rect, Scene, View, semantics::Role};
thread_local! {
pub static COMPOSER: RefCell<Composer> = RefCell::new(Composer::default());
static ROOT_SCOPE: RefCell<Option<Scope>> = const { RefCell::new(None) };
static FOCUS_REQUEST: Cell<Option<u64>> = const { Cell::new(None) };
}
pub fn take_focus_request() -> Option<u64> {
FOCUS_REQUEST.with(|r| r.replace(None))
}
#[derive(Clone)]
pub struct FocusRequester {
pub target: Rc<RefCell<Option<u64>>>,
}
impl FocusRequester {
pub fn new() -> Self {
Self {
target: Rc::new(RefCell::new(None)),
}
}
pub fn request_focus(&self) {
if let Some(id) = *self.target.borrow() {
FOCUS_REQUEST.with(|r| r.set(Some(id)));
}
}
}
impl Default for FocusRequester {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FocusDirection {
Next,
Previous,
Left,
Right,
Up,
Down,
}
#[derive(Clone)]
pub struct FocusManager {
pub chain: Vec<u64>,
pub focused: Option<u64>,
}
impl FocusManager {
pub fn new(chain: Vec<u64>, focused: Option<u64>) -> Self {
Self { chain, focused }
}
pub fn move_focus(&mut self, dir: FocusDirection) -> Option<u64> {
match dir {
FocusDirection::Next | FocusDirection::Previous => {
self.move_tab(dir == FocusDirection::Previous)
}
_ => None, }
}
pub fn move_focus_spatial(
&mut self,
dir: FocusDirection,
hit_regions: &[HitRegion],
) -> Option<u64> {
let next = spatial_focus_next(&self.chain, hit_regions, self.focused, dir)?;
self.focused = Some(next);
Some(next)
}
pub fn move_tab(&mut self, reverse: bool) -> Option<u64> {
if self.chain.is_empty() {
return None;
}
let next = if let Some(cur) = self.focused {
if let Some(idx) = self.chain.iter().position(|&id| id == cur) {
if reverse {
if idx == 0 {
self.chain[self.chain.len() - 1]
} else {
self.chain[idx - 1]
}
} else {
self.chain[(idx + 1) % self.chain.len()]
}
} else {
self.chain[0]
}
} else {
self.chain[0]
};
self.focused = Some(next);
Some(next)
}
pub fn set_requester_target(requester: &FocusRequester, id: u64) {
*requester.target.borrow_mut() = Some(id);
}
}
pub fn spatial_focus_next(
chain: &[u64],
hit_regions: &[HitRegion],
current: Option<u64>,
dir: FocusDirection,
) -> Option<u64> {
if chain.is_empty() {
return None;
}
let current_rect = current.and_then(|id| hit_regions.iter().find(|h| h.id == id).map(|h| h.rect));
match dir {
FocusDirection::Next | FocusDirection::Previous => {
let mut fm = FocusManager {
chain: chain.to_vec(),
focused: current,
};
return fm.move_tab(dir == FocusDirection::Previous);
}
_ => {}
}
let (cx, cy) = match current_rect {
Some(r) => (r.x + r.w / 2.0, r.y + r.h / 2.0),
None => return chain.first().copied(),
};
let mut best: Option<(u64, f32)> = None;
for &id in chain {
if Some(id) == current {
continue;
}
let Some(hr) = hit_regions.iter().find(|h| h.id == id) else {
continue;
};
let r = hr.rect;
let other_cx = r.x + r.w / 2.0;
let other_cy = r.y + r.h / 2.0;
let dx = other_cx - cx;
let dy = other_cy - cy;
let in_direction = match dir {
FocusDirection::Left => dx < 0.0 && dy.abs() <= r.h.max(1.0),
FocusDirection::Right => dx > 0.0 && dy.abs() <= r.h.max(1.0),
FocusDirection::Up => dy < 0.0 && dx.abs() <= r.w.max(1.0),
FocusDirection::Down => dy > 0.0 && dx.abs() <= r.w.max(1.0),
_ => false,
};
if !in_direction {
continue;
}
let dist = dx * dx + dy * dy;
let weight = dist / (r.w * r.h + 1.0).max(1.0);
match best {
Some((_, best_weight)) if weight >= best_weight => {}
_ => best = Some((id, weight)),
}
}
best.map(|(id, _)| id)
}
#[derive(Default)]
pub struct Composer {
pub slots: Vec<Box<dyn Any>>,
pub cursor: usize,
pub keyed_slots: HashMap<String, Box<dyn Any>>,
}
pub struct ComposeGuard {
scope: Scope,
}
impl ComposeGuard {
pub fn begin() -> Self {
COMPOSER.with(|c| c.borrow_mut().cursor = 0);
let scope = ROOT_SCOPE.with(|rs| {
if let Some(existing) = rs.borrow().clone() {
existing
} else {
let s = Scope::new();
*rs.borrow_mut() = Some(s.clone());
s
}
});
ComposeGuard { scope }
}
pub fn scope(&self) -> &Scope {
&self.scope
}
}
impl Drop for ComposeGuard {
fn drop(&mut self) {
}
}
pub fn remember<T: 'static>(init: impl FnOnce() -> T) -> Rc<T> {
COMPOSER.with(|c| {
let mut c = c.borrow_mut();
let cursor = c.cursor;
c.cursor += 1;
if cursor >= c.slots.len() {
let rc: Rc<T> = Rc::new(init());
c.slots.push(Box::new(rc.clone()));
return rc;
}
if let Some(rc) = c.slots[cursor].downcast_ref::<Rc<T>>() {
rc.clone()
} else {
log::warn!(
"remember: slot {} type changed; replacing. \
If this is due to conditional composition, prefer remember_with_key.",
cursor
);
let rc: Rc<T> = Rc::new(init());
c.slots[cursor] = Box::new(rc.clone());
rc
}
})
}
pub fn remember_with_key<T: 'static>(key: impl Into<String>, init: impl FnOnce() -> T) -> Rc<T> {
COMPOSER.with(|c| {
let mut c = c.borrow_mut();
let key = key.into();
if let Some(existing) = c.keyed_slots.get(&key) {
if let Some(rc) = existing.downcast_ref::<Rc<T>>() {
return rc.clone();
} else {
log::warn!(
"remember_with_key: key '{}' reused with a different type; replacing.",
key
);
}
}
if cfg!(debug_assertions) && c.keyed_slots.len() > 10_000 {
log::warn!(
"remember_with_key: more than 10k keys stored; \
are you generating unbounded dynamic keys (e.g., using timestamps)?"
);
}
let rc: Rc<T> = Rc::new(init());
c.keyed_slots.insert(key, Box::new(rc.clone()));
rc
})
}
pub fn remember_state<T: 'static>(init: impl FnOnce() -> T) -> Rc<RefCell<T>> {
remember(|| RefCell::new(init()))
}
pub fn remember_state_with_key<T: 'static>(
key: impl Into<String>,
init: impl FnOnce() -> T,
) -> Rc<RefCell<T>> {
remember_with_key(key, || RefCell::new(init()))
}
pub struct Frame {
pub scene: Scene,
pub hit_regions: Vec<HitRegion>,
pub semantics_nodes: Vec<SemNode>,
pub focus_chain: Vec<u64>,
}
#[derive(Clone, Default)]
pub struct HitRegion {
pub id: u64,
pub rect: Rect,
pub on_click: Option<Rc<dyn Fn()>>,
pub on_scroll: Option<Rc<dyn Fn(crate::Vec2) -> crate::Vec2>>,
pub focusable: bool,
pub on_pointer_down: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
pub on_pointer_move: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
pub on_pointer_up: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
pub on_pointer_enter: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
pub on_pointer_leave: Option<Rc<dyn Fn(crate::input::PointerEvent)>>,
pub z_index: f32,
pub on_text_change: Option<Rc<dyn Fn(String)>>,
pub on_text_submit: Option<Rc<dyn Fn(String)>>,
pub tf_state_key: Option<u64>,
pub tf_multiline: bool,
pub on_drag_start: Option<Rc<dyn Fn(crate::dnd::DragStart) -> Option<crate::dnd::DragPayload>>>,
pub on_drag_end: Option<Rc<dyn Fn(crate::dnd::DragEnd)>>,
pub on_drag_enter: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
pub on_drag_over: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
pub on_drag_leave: Option<Rc<dyn Fn(crate::dnd::DragOver)>>,
pub on_drop: Option<Rc<dyn Fn(crate::dnd::DropEvent) -> bool>>,
pub on_action: Option<Rc<dyn Fn(crate::shortcuts::Action) -> bool>>,
pub cursor: Option<crate::CursorIcon>,
}
impl HitRegion {
pub fn from_modifier(id: u64, rect: Rect, m: &crate::modifier::Modifier) -> Self {
Self {
id,
rect,
z_index: m.z_index,
on_pointer_down: m.on_pointer_down.clone(),
on_pointer_move: m.on_pointer_move.clone(),
on_pointer_up: m.on_pointer_up.clone(),
on_pointer_enter: m.on_pointer_enter.clone(),
on_pointer_leave: m.on_pointer_leave.clone(),
on_action: m.on_action.clone(),
cursor: m.cursor,
on_drag_start: m.on_drag_start.clone(),
on_drag_end: m.on_drag_end.clone(),
on_drag_enter: m.on_drag_enter.clone(),
on_drag_over: m.on_drag_over.clone(),
on_drag_leave: m.on_drag_leave.clone(),
on_drop: m.on_drop.clone(),
..Default::default()
}
}
}
#[derive(Clone)]
pub struct SemNode {
pub id: u64,
pub parent: Option<u64>,
pub role: Role,
pub label: Option<String>,
pub rect: Rect,
pub focused: bool,
pub enabled: bool,
}
pub struct Scheduler {
next_id: u64,
pub focused: Option<u64>,
pub size: (u32, u32),
}
impl Default for Scheduler {
fn default() -> Self {
Self::new()
}
}
impl Scheduler {
pub fn new() -> Self {
Self {
next_id: 1,
focused: None,
size: (1280, 800),
}
}
pub fn id(&mut self) -> u64 {
let id = self.next_id;
self.next_id += 1;
id
}
pub fn id_count(&self) -> u64 {
self.next_id - 1
}
pub fn repose<F>(
&mut self,
mut build_root: F,
layout_paint: impl Fn(&View, (u32, u32)) -> (Scene, Vec<HitRegion>, Vec<SemNode>),
) -> Frame
where
F: FnMut(&mut Scheduler) -> View,
{
let guard = ComposeGuard::begin();
let root = guard.scope.run(|| build_root(self));
let (scene, hits, sem) = layout_paint(&root, self.size);
let focus_chain: Vec<u64> = hits.iter().filter(|h| h.focusable).map(|h| h.id).collect();
Frame {
scene,
hit_regions: hits,
semantics_nodes: sem,
focus_chain,
}
}
}
#[cfg(test)]
pub fn clear_composer() {
COMPOSER.with(|c| {
let mut c = c.borrow_mut();
c.slots.clear();
c.keyed_slots.clear();
c.cursor = 0;
});
ROOT_SCOPE.with(|rs| {
*rs.borrow_mut() = None;
});
}