use std::cell::{Cell, RefCell};
use std::rc::Rc;
use crate::base::{Point, Rect, Rgba, Size};
use crate::layout::{solve, LayoutId, LayoutTree};
use crate::reactive::{request_frame, GenArena, Key as ArenaKey, Scope, Signal};
use super::mount::{mount_view, remove_subtree};
use super::view::{DrawFn, Handler, Shortcut, View};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct ViewId(pub(crate) ArenaKey);
pub(super) enum InstPayload {
Element {
draw: Option<Rc<RefCell<DrawFn>>>,
handlers: Rc<RefCell<Vec<Handler>>>,
shortcuts: Rc<RefCell<Vec<Shortcut>>>,
},
Text {
content: String,
},
Dyn,
}
pub(super) struct Inst {
pub(super) parent: Option<ViewId>,
pub(super) children: Vec<ViewId>,
pub(super) layout: LayoutId,
pub(super) focusable: bool,
pub(super) focus_trap: bool,
pub(super) focus_memory: bool,
pub(super) probe_when_culled: bool,
pub(super) drag_zone: Option<super::view::DragZoneFn>,
pub(super) access: super::access::AccessProps,
pub(super) payload: InstPayload,
}
pub(super) struct TreeCore {
pub(super) insts: GenArena<Inst>,
pub(super) layout: LayoutTree,
pub(super) root: Option<ViewId>,
pub(super) viewport: Size,
pub(super) damage: Vec<Rect>,
pub(super) needs_layout: bool,
pub(super) focus: Option<ViewId>,
pub(super) text_fg: Rgba,
pub(super) hovered_path: Vec<ViewId>,
pub(super) capture: Option<ViewId>,
pub(super) capture_pos: Option<Point>,
pub(super) last_hover: Option<(Point, u64)>,
pub(super) layout_epoch: u64,
pub(super) dirty_subtrees: Vec<LayoutId>,
pub(super) focus_memory: std::collections::HashMap<ViewId, ViewId>,
pub(super) pending_autofocus: Option<ViewId>,
pub(super) click_chain: super::click::ClickChain,
pub(super) layer_origin: Point,
pub(super) rect_probes: Vec<RectProbe>,
}
pub(super) struct PendingRectPublish {
sig: Signal<Option<Rect>>,
seen: Rc<Cell<Option<Rect>>>,
pending: Rc<Cell<bool>>,
alive: Rc<Cell<bool>>,
}
impl PendingRectPublish {
fn schedule(self) {
let PendingRectPublish {
sig,
seen,
pending,
alive,
} = self;
crate::reactive::after(std::time::Duration::ZERO, move || {
pending.set(false);
if !alive.get() || sig.try_get_untracked().is_none() {
return;
}
sig.set_if_changed(seen.get().filter(|r| !r.is_empty()));
});
}
}
fn collect_rect_publishes(core: &mut TreeCore) -> Vec<PendingRectPublish> {
if core.rect_probes.is_empty() {
return Vec::new();
}
core.rect_probes.retain(|p| p.alive.get());
let mut out = Vec::new();
for probe in &core.rect_probes {
let Some(inst) = core.insts.get(probe.view.0) else {
continue;
};
let rect = core.layout.rect(inst.layout);
if probe.seen.get() == Some(rect) {
continue; }
probe.seen.set(Some(rect));
if probe.pending.replace(true) {
continue; }
out.push(PendingRectPublish {
sig: probe.sig,
seen: probe.seen.clone(),
pending: probe.pending.clone(),
alive: probe.alive.clone(),
});
}
out
}
pub(super) struct RectProbe {
pub(super) view: ViewId,
pub(super) sig: Signal<Option<Rect>>,
pub(super) seen: Rc<Cell<Option<Rect>>>,
pub(super) pending: Rc<Cell<bool>>,
pub(super) alive: Rc<Cell<bool>>,
}
impl TreeCore {
pub(super) fn damage_rect(&mut self, rect: Rect) {
if rect.is_empty() {
return;
}
if self.damage.iter().any(|r| r.intersect(rect) == rect) {
return; }
self.damage.retain(|r| rect.intersect(*r) != *r); self.damage.push(rect);
}
pub(super) fn damage_all(&mut self) {
let full = Rect::from_size(self.viewport);
self.damage.push(full);
}
}
pub struct UiTree {
pub(super) core: Rc<RefCell<TreeCore>>,
}
impl UiTree {
pub fn new(viewport: Size) -> UiTree {
UiTree {
core: Rc::new(RefCell::new(TreeCore {
insts: GenArena::new(),
layout: LayoutTree::new(),
root: None,
viewport,
damage: Vec::new(),
needs_layout: false,
focus: None,
text_fg: Rgba::WHITE,
hovered_path: Vec::new(),
capture: None,
capture_pos: None,
last_hover: None,
layout_epoch: 0,
dirty_subtrees: Vec::new(),
focus_memory: std::collections::HashMap::new(),
pending_autofocus: None,
click_chain: super::click::ClickChain::new(),
layer_origin: Point::ZERO,
rect_probes: Vec::new(),
})),
}
}
pub fn set_text_fg(&mut self, fg: Rgba) {
self.core.borrow_mut().text_fg = fg;
}
pub fn handle(&self) -> UiTree {
UiTree {
core: self.core.clone(),
}
}
pub fn viewport_size(&self) -> Size {
self.core.borrow().viewport
}
pub fn accessibility_tree(&mut self) -> super::access::AccessSnapshot {
self.layout(); let core = self.core.borrow();
let mut snapshot = super::access::AccessSnapshot::default();
let Some(root) = core.root else {
return snapshot;
};
let focus_carrier = core.focus.map(|f| {
let mut cur = f;
loop {
let Some(inst) = core.insts.get(cur.0) else {
break cur;
};
let annotated =
!inst.access.is_empty() || matches!(inst.payload, InstPayload::Text { .. });
if annotated {
break cur;
}
match inst.parent {
Some(p) => cur = p,
None => break cur,
}
}
});
let mut stack: Vec<(ViewId, usize)> = vec![(root, 0)];
while let Some((id, depth)) = stack.pop() {
let Some(inst) = core.insts.get(id.0) else {
continue;
};
let mut child_depth = depth;
let entry = match &inst.payload {
InstPayload::Text { content } if !content.is_empty() => {
Some(super::access::AccessEntry {
role: super::access::Role::Text,
label: content.clone(),
value: None,
focused: focus_carrier == Some(id),
bounds: core.layout.rect(inst.layout),
depth,
})
}
_ if !inst.access.is_empty() => {
let a = &inst.access;
let value = a.value.as_ref().map(|f| {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f()))
.unwrap_or_else(|_| "<stale>".into())
});
Some(super::access::AccessEntry {
role: a.role.unwrap_or(super::access::Role::Region),
label: a.label.clone().unwrap_or_default(),
value,
focused: focus_carrier == Some(id),
bounds: core.layout.rect(inst.layout),
depth,
})
}
_ => None,
};
if let Some(e) = entry {
snapshot.entries.push(e);
child_depth += 1;
}
for &child in inst.children.iter().rev() {
stack.push((child, child_depth));
}
}
snapshot
}
pub fn accessibility_tree_text(&mut self) -> String {
self.accessibility_tree().to_text()
}
pub fn a11y_tree(&mut self) -> super::access::AccessSnapshot {
self.accessibility_tree()
}
pub fn focus_announcement(&mut self) -> Option<String> {
let snapshot = self.accessibility_tree();
let e = snapshot.focused()?;
let mut out = e.role.as_str().to_string();
if !e.label.is_empty() {
out.push_str(&format!(" \"{}\"", e.label));
}
if let Some(v) = &e.value {
out.push_str(&format!(" = \"{v}\""));
}
Some(out)
}
pub fn keymap_of_focus_path(&self) -> Vec<(super::event::KeyChord, Option<String>)> {
let core = self.core.borrow();
let start = core.focus.or(core.root);
let mut out = Vec::new();
let mut cur = start;
while let Some(id) = cur {
let Some(inst) = core.insts.get(id.0) else {
break;
};
if let InstPayload::Element { shortcuts, .. } = &inst.payload {
for s in shortcuts.borrow().iter() {
out.push((s.chord, s.label.clone()));
}
}
cur = inst.parent;
}
out
}
pub fn invalidator(&self) -> impl Fn() + 'static {
let core = Rc::downgrade(&self.core);
move || {
if let Some(core) = core.upgrade() {
let mut c = core.borrow_mut();
c.damage_all();
c.needs_layout = true;
drop(c);
request_frame();
}
}
}
pub fn mount(&mut self, cx: Scope, view: View) -> ViewId {
let id = mount_view(&self.core, cx, view, None);
let core_for_cleanup = self.core.clone();
cx.on_cleanup(move || remove_subtree(&core_for_cleanup, id));
{
let mut core = self.core.borrow_mut();
core.root = Some(id);
core.needs_layout = true;
core.damage_all();
}
self.deliver_pending_autofocus();
request_frame();
id
}
pub fn set_viewport(&mut self, size: Size) {
let mut core = self.core.borrow_mut();
core.viewport = size;
core.needs_layout = true;
core.damage_all();
drop(core);
request_frame();
}
pub(crate) fn take_collapse_notices(&mut self) -> Vec<String> {
self.core.borrow_mut().layout.take_collapse_notices()
}
pub fn layout(&mut self) {
self.deliver_pending_autofocus();
let mut core = self.core.borrow_mut();
let full = core.needs_layout;
let dirty: Vec<LayoutId> = std::mem::take(&mut core.dirty_subtrees);
if !full && dirty.is_empty() {
return;
}
core.layout_epoch += 1; core.needs_layout = false;
let Some(root) = core.root else { return };
let root_layout = match core.insts.get(root.0) {
Some(inst) => inst.layout,
None => return,
};
let viewport = Rect::from_size(core.viewport);
if full {
solve(&mut core.layout, root_layout, viewport);
} else {
for anchor in dirty {
if core.layout.is_alive(anchor) {
crate::layout::resolve_subtree(&mut core.layout, anchor);
}
}
}
for rect in core.layout.take_geometry_damage() {
core.damage_rect(rect);
}
let scheduled = collect_rect_publishes(&mut core);
drop(core);
for probe in scheduled {
probe.schedule();
}
}
pub fn take_damage(&mut self) -> Vec<Rect> {
std::mem::take(&mut self.core.borrow_mut().damage)
}
pub fn has_pending_work(&self) -> bool {
let core = self.core.borrow();
!core.damage.is_empty() || core.needs_layout
}
pub fn needs_layout(&self) -> bool {
self.core.borrow().needs_layout
}
pub fn instance_count(&self) -> usize {
self.core.borrow().insts.live()
}
pub fn rect_of(&self, id: ViewId) -> Rect {
let core = self.core.borrow();
core.insts
.get(id.0)
.map(|i| core.layout.rect(i.layout))
.unwrap_or(Rect::ZERO)
}
pub fn layer_origin(&self) -> Point {
self.core.borrow().layer_origin
}
pub fn set_layer_origin(&mut self, origin: Point) {
self.core.borrow_mut().layer_origin = origin;
}
pub fn focused(&self) -> Option<ViewId> {
self.core.borrow().focus
}
}
#[path = "tree_dispatch.rs"]
mod dispatch;
pub use dispatch::PressProbe;
thread_local! {
static AMBIENT_LAYER_ORIGIN: std::cell::Cell<Point> = const { std::cell::Cell::new(Point::ZERO) };
}
pub fn layer_origin() -> Point {
AMBIENT_LAYER_ORIGIN.with(|c| c.get())
}
pub(crate) fn publish_layer_origin(origin: Point) -> LayerOriginGuard {
AMBIENT_LAYER_ORIGIN.with(|c| LayerOriginGuard {
prev: c.replace(origin),
})
}
pub(crate) struct LayerOriginGuard {
prev: Point,
}
impl Drop for LayerOriginGuard {
fn drop(&mut self) {
AMBIENT_LAYER_ORIGIN.with(|c| c.set(self.prev));
}
}