use densa_core::Point;
use crate::access::{AccessAction, AccessId, AccessNode, AccessRequest, AccessTree, Role};
use crate::{EventCx, Handled, Overflow, PaintCx, Ui, WidgetId};
const SCROLL_STEP: f32 = 48.0;
impl Ui {
pub fn access_id(&self, id: WidgetId) -> Option<AccessId> {
self.state
.node(id)
.map(|node| AccessId::widget(node.access))
}
pub fn access_tree(&mut self) -> AccessTree {
self.layout();
let mut nodes = Vec::new();
let root = self.state.root;
let root_id = self.access_node(root, &mut nodes);
for index in 0..self.state.nodes[self.state.layer.slot()].children.len() {
let popup = self.state.nodes[self.state.layer.slot()].children[index];
if !self.state.nodes[popup.slot()].style.hidden {
let id = self.access_node(popup, &mut nodes);
if let Some((_, window)) = nodes.iter_mut().find(|(i, _)| *i == root_id) {
window.push_child(id);
}
}
}
let focus = match self.state.focus.filter(|f| self.state.is_shown(*f)) {
Some(widget) => {
let id = AccessId::widget(self.state.nodes[widget.slot()].access);
let part = nodes
.iter()
.find(|(i, _)| *i == id)
.and_then(|(_, node)| node.focused_part())
.map(|part| id.part(part));
part.filter(|p| nodes.iter().any(|(i, _)| i == p))
.unwrap_or(id)
}
None => root_id,
};
AccessTree {
nodes,
root: root_id,
focus,
}
}
fn access_node(&self, id: WidgetId, out: &mut Vec<(AccessId, AccessNode)>) -> AccessId {
let state = &self.state.nodes[id.slot()];
let node_id = AccessId::widget(state.access);
let role = if id == self.state.root {
Role::Window
} else {
Role::Group
};
let mut node = AccessNode::new(role);
node.set_bounds(state.rect);
node.set_clips_children(state.style.overflow != Overflow::Visible);
if let Some(widget) = self.widgets.get(id.slot()).and_then(Option::as_ref) {
widget.accessibility(
&PaintCx {
state: &self.state,
id,
},
&mut node,
);
}
if self.accepts_focus(id) {
node.add_action(AccessAction::Focus);
}
node.add_action(AccessAction::ScrollIntoView);
let at = out.len();
out.push((node_id, AccessNode::default()));
let mut seen = Vec::new();
for (part, sub) in node.take_parts() {
if let Some(child) = Self::access_part(node_id, part, sub, &mut seen, out) {
node.push_child(child);
}
}
for index in 0..self.state.nodes[id.slot()].children.len() {
let child = self.state.nodes[id.slot()].children[index];
if !self.state.nodes[child.slot()].style.hidden {
let child = self.access_node(child, out);
node.push_child(child);
}
}
out[at].1 = node;
node_id
}
fn access_part(
widget: AccessId,
part: u32,
mut node: AccessNode,
seen: &mut Vec<u32>,
out: &mut Vec<(AccessId, AccessNode)>,
) -> Option<AccessId> {
if part > crate::MAX_PART || seen.contains(&part) {
return None;
}
seen.push(part);
let node_id = widget.part(part);
let at = out.len();
out.push((node_id, AccessNode::default()));
for (inner, sub) in node.take_parts() {
if let Some(child) = Self::access_part(widget, inner, sub, seen, out) {
node.push_child(child);
}
}
out[at].1 = node;
Some(node_id)
}
pub fn access_action(
&mut self,
target: AccessId,
action: AccessAction,
value: Option<&str>,
) -> bool {
let (serial, part) = target.split();
let Some(id) = self.widget_numbered(serial) else {
return false;
};
self.layout();
let request = AccessRequest {
action,
part,
value,
};
let done = match action {
AccessAction::Focus if part.is_none() && self.accepts_focus(id) => {
self.set_focus(Some(id));
true
}
AccessAction::ScrollIntoView if part.is_none() => {
self.scroll_into_view(id);
true
}
AccessAction::ScrollUp
| AccessAction::ScrollDown
| AccessAction::ScrollLeft
| AccessAction::ScrollRight => self.access_scroll(id, &request),
_ => self.deliver_access(id, &request) == Handled::Yes,
};
self.settle();
done
}
fn widget_numbered(&self, serial: u64) -> Option<WidgetId> {
let slot = self
.state
.nodes
.iter()
.position(|node| node.access == serial)?;
let id = WidgetId {
index: u32::try_from(slot).ok()?,
generation: self.state.nodes[slot].generation,
};
self.state.is_shown(id).then_some(id)
}
fn deliver_access(&mut self, id: WidgetId, request: &AccessRequest<'_>) -> Handled {
let Some(widget) = self.widgets.get_mut(id.slot()).and_then(Option::as_mut) else {
return Handled::No;
};
let mut cx = EventCx {
state: &mut self.state,
id,
};
widget.access_action(&mut cx, request)
}
fn access_scroll(&mut self, id: WidgetId, request: &AccessRequest<'_>) -> bool {
let mut at = Some(id);
while let Some(current) = at {
if self.deliver_access(current, request) == Handled::Yes {
return true;
}
let node = &self.state.nodes[current.slot()];
if node.style.overflow == Overflow::Scroll {
let step = SCROLL_STEP * self.state.scale;
let (dx, dy) = match request.action {
AccessAction::ScrollUp => (0.0, -step),
AccessAction::ScrollDown => (0.0, step),
AccessAction::ScrollLeft => (-step, 0.0),
_ => (step, 0.0),
};
let to = Point::new(node.scroll.x + dx, node.scroll.y + dy);
let before = node.scroll;
return self.state.scroll_to(current, to) != before;
}
at = node.parent;
}
false
}
fn scroll_into_view(&mut self, id: WidgetId) {
let mut at = self.state.nodes[id.slot()].parent;
while let Some(container) = at {
self.layout();
let target = self.state.nodes[id.slot()].rect;
let node = &self.state.nodes[container.slot()];
if node.style.overflow == Overflow::Scroll {
let view = node.content;
let shift = |start: f32, end: f32, view_start: f32, view_end: f32| {
if start < view_start {
start - view_start
} else if end > view_end {
(end - view_end).min(start - view_start)
} else {
0.0
}
};
let dx = shift(target.left(), target.right(), view.left(), view.right());
let dy = shift(target.top(), target.bottom(), view.top(), view.bottom());
if dx != 0.0 || dy != 0.0 {
let to = Point::new(node.scroll.x + dx, node.scroll.y + dy);
self.state.scroll_to(container, to);
}
}
at = self.state.nodes[container.slot()].parent;
}
}
}