use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::rc::{Rc, Weak};
use incular_core::Rect;
use crate::details::PointerDeviceKind;
#[derive(Clone)]
pub struct FocusNode {
state: Rc<FocusState>,
}
impl PartialEq for FocusNode {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.state, &other.state)
}
}
impl Eq for FocusNode {}
impl std::fmt::Debug for FocusNode {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("FocusNode")
.field("has_focus", &self.has_focus())
.field("can_request_focus", &self.can_request_focus())
.finish()
}
}
struct FocusState {
focused: Cell<bool>,
enabled: Cell<bool>,
can_request_focus: Cell<bool>,
skip_traversal: Cell<bool>,
descendants_are_focusable: Cell<bool>,
descendants_are_traversable: Cell<bool>,
rect: Cell<Rect>,
traversal_order: std::cell::RefCell<Option<FocusOrder>>,
changes: incular_core::reactivity::DependencySource,
revision: Cell<u64>,
}
pub struct FocusNodeSubscription {
_subscription: incular_core::reactivity::Subscription,
}
impl FocusState {
fn notify(&self) {
self.revision.set(
self.revision
.get()
.checked_add(1)
.expect("revision exhausted"),
);
self.changes.notify();
}
fn set_focused_silent(&self, focused: bool) -> bool {
self.focused.replace(focused) != focused
}
fn set_focused(&self, focused: bool) {
if self.set_focused_silent(focused) {
self.notify();
}
}
}
impl Default for FocusNode {
fn default() -> Self {
Self {
state: Rc::new(FocusState {
focused: Cell::new(false),
enabled: Cell::new(true),
can_request_focus: Cell::new(true),
skip_traversal: Cell::new(false),
descendants_are_focusable: Cell::new(true),
descendants_are_traversable: Cell::new(true),
rect: Cell::new(Rect::default()),
traversal_order: std::cell::RefCell::new(None),
changes: incular_core::reactivity::DependencySource::default(),
revision: Cell::new(0),
}),
}
}
}
impl FocusNode {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn observe(&self, callback: impl Fn() + 'static) -> FocusNodeSubscription {
FocusNodeSubscription {
_subscription: self.state.changes.subscribe((), callback),
}
}
#[must_use]
pub fn revision(&self) -> u64 {
self.state.revision.get()
}
pub fn request_focus(&self) {
if self.can_request_focus() {
self.state.set_focused(true);
}
}
pub fn unfocus(&self) {
self.state.set_focused(false);
}
#[must_use]
pub fn has_focus(&self) -> bool {
self.state.changes.track();
self.state.focused.get()
}
#[must_use]
pub fn has_primary_focus(&self) -> bool {
self.state.changes.track();
self.state.focused.get()
}
pub fn set_can_request_focus(&self, can_request_focus: bool) {
if self.state.can_request_focus.replace(can_request_focus) != can_request_focus {
if !self.can_request_focus() {
self.unfocus();
}
self.state.notify();
} else if !can_request_focus {
self.unfocus();
}
}
#[must_use]
pub fn can_request_focus(&self) -> bool {
self.state.changes.track();
self.state.enabled.get() && self.state.can_request_focus.get()
}
pub fn set_enabled(&self, enabled: bool) {
if self.state.enabled.replace(enabled) != enabled {
if !enabled {
self.unfocus();
}
self.state.notify();
}
}
#[must_use]
pub fn is_enabled(&self) -> bool {
self.state.enabled.get()
}
pub fn set_skip_traversal(&self, skip: bool) {
if self.state.skip_traversal.replace(skip) != skip {
self.state.notify();
}
}
#[must_use]
pub fn skip_traversal(&self) -> bool {
self.state.skip_traversal.get()
}
pub fn set_descendants_are_focusable(&self, focusable: bool) {
if self.state.descendants_are_focusable.replace(focusable) != focusable {
self.state.notify();
}
}
#[must_use]
pub fn descendants_are_focusable(&self) -> bool {
self.state.descendants_are_focusable.get()
}
pub fn set_descendants_are_traversable(&self, traversable: bool) {
if self.state.descendants_are_traversable.replace(traversable) != traversable {
self.state.notify();
}
}
#[must_use]
pub fn descendants_are_traversable(&self) -> bool {
self.state.descendants_are_traversable.get()
}
pub fn set_rect(&self, rect: Rect) {
self.state.rect.set(rect);
}
#[must_use]
pub fn rect(&self) -> Rect {
self.state.rect.get()
}
pub fn set_traversal_order(&self, order: Option<f64>) {
self.set_focus_order(order.map(FocusOrder::Numeric));
}
#[must_use]
pub fn traversal_order(&self) -> Option<f64> {
match *self.state.traversal_order.borrow() {
Some(FocusOrder::Numeric(order)) => Some(order),
_ => None,
}
}
pub fn set_focus_order(&self, order: Option<FocusOrder>) {
*self.state.traversal_order.borrow_mut() = order.filter(FocusOrder::is_valid);
}
#[must_use]
pub fn focus_order(&self) -> Option<FocusOrder> {
self.state.traversal_order.borrow().clone()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum FocusHighlightMode {
Touch,
#[default]
Traditional,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum FocusHighlightStrategy {
#[default]
Automatic,
AlwaysTouch,
AlwaysTraditional,
}
struct HighlightModeState {
strategy: FocusHighlightStrategy,
last_input_mode: Option<FocusHighlightMode>,
mode: FocusHighlightMode,
observers: Vec<Weak<HighlightModeObserverEntry>>,
pending: VecDeque<HighlightNotification>,
dispatching: bool,
}
struct HighlightNotification {
mode: FocusHighlightMode,
observers: Vec<Weak<HighlightModeObserverEntry>>,
}
impl HighlightModeState {
fn commit_mode(&mut self, mode: FocusHighlightMode) {
if self.mode == mode {
return;
}
self.mode = mode;
self.observers
.retain(|observer| observer.strong_count() != 0);
self.pending.push_back(HighlightNotification {
mode,
observers: self.observers.clone(),
});
}
}
struct HighlightModeObserverEntry {
active: Cell<bool>,
callback: Rc<dyn Fn(FocusHighlightMode)>,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct FocusHighlightManager;
pub struct FocusHighlightSubscription {
entry: Rc<HighlightModeObserverEntry>,
}
impl Drop for FocusHighlightSubscription {
fn drop(&mut self) {
self.entry.active.set(false);
}
}
thread_local! {
static HIGHLIGHT_MODE: RefCell<HighlightModeState> = const { RefCell::new(HighlightModeState {
strategy: FocusHighlightStrategy::Automatic,
last_input_mode: None,
mode: FocusHighlightMode::Traditional,
observers: Vec::new(),
pending: VecDeque::new(),
dispatching: false,
}) };
}
struct HighlightDispatchGuard;
impl Drop for HighlightDispatchGuard {
fn drop(&mut self) {
let abandoned = HIGHLIGHT_MODE.with(|state| {
let mut state = state.borrow_mut();
state.dispatching = false;
std::mem::take(&mut state.pending)
});
drop(abandoned);
}
}
impl FocusHighlightManager {
#[must_use]
pub const fn new() -> Self {
Self
}
#[must_use]
pub fn mode(self) -> FocusHighlightMode {
HIGHLIGHT_MODE.with(|state| state.borrow().mode)
}
#[must_use]
pub fn strategy(self) -> FocusHighlightStrategy {
HIGHLIGHT_MODE.with(|state| state.borrow().strategy)
}
pub fn set_strategy(self, strategy: FocusHighlightStrategy) {
Self::update(|state| {
if state.strategy == strategy {
return;
}
state.strategy = strategy;
let mode = match strategy {
FocusHighlightStrategy::Automatic => state.last_input_mode.unwrap_or(state.mode),
FocusHighlightStrategy::AlwaysTouch => FocusHighlightMode::Touch,
FocusHighlightStrategy::AlwaysTraditional => FocusHighlightMode::Traditional,
};
state.commit_mode(mode);
});
}
pub fn set_mode(self, mode: FocusHighlightMode) {
Self::update(|state| state.commit_mode(mode));
}
pub fn note_keyboard_input(self) {
Self::update(|state| {
if state.last_input_mode != Some(FocusHighlightMode::Traditional) {
state.last_input_mode = Some(FocusHighlightMode::Traditional);
if state.strategy == FocusHighlightStrategy::Automatic {
state.commit_mode(FocusHighlightMode::Traditional);
}
}
});
}
pub fn note_pointer_input(self, kind: PointerDeviceKind) {
let requires_touch_highlights = matches!(
kind,
PointerDeviceKind::Touch
| PointerDeviceKind::Stylus
| PointerDeviceKind::InvertedStylus
);
if !requires_touch_highlights {
return;
}
Self::update(|state| {
if state.last_input_mode != Some(FocusHighlightMode::Touch) {
state.last_input_mode = Some(FocusHighlightMode::Touch);
if state.strategy == FocusHighlightStrategy::Automatic {
state.commit_mode(FocusHighlightMode::Touch);
}
}
});
}
#[must_use]
pub fn observe(
self,
callback: impl Fn(FocusHighlightMode) + 'static,
) -> FocusHighlightSubscription {
let entry = Rc::new(HighlightModeObserverEntry {
active: Cell::new(true),
callback: Rc::new(callback),
});
HIGHLIGHT_MODE.with(|state| {
let mut state = state.borrow_mut();
state
.observers
.retain(|observer| observer.strong_count() != 0);
state.observers.push(Rc::downgrade(&entry));
});
FocusHighlightSubscription { entry }
}
fn update(update: impl FnOnce(&mut HighlightModeState)) {
HIGHLIGHT_MODE.with(|state| update(&mut state.borrow_mut()));
let owns_dispatch = HIGHLIGHT_MODE.with(|state| {
let mut state = state.borrow_mut();
if state.dispatching || state.pending.is_empty() {
return false;
}
state.dispatching = true;
true
});
if !owns_dispatch {
return;
}
let _dispatch = HighlightDispatchGuard;
loop {
let next = HIGHLIGHT_MODE.with(|state| state.borrow_mut().pending.pop_front());
let Some(notification) = next else {
break;
};
for observer in notification.observers {
if let Some(observer) = observer.upgrade()
&& observer.active.get()
{
(observer.callback)(notification.mode);
}
}
}
}
}
pub struct FocusBehavior {
node: FocusNode,
enabled: Cell<bool>,
hovering: Cell<bool>,
focused: Cell<bool>,
can_show_highlight: Cell<bool>,
focus_highlight_visible: Cell<bool>,
hover_highlight_visible: Cell<bool>,
on_focus_change: Option<Rc<dyn Fn(bool)>>,
on_show_focus_highlight: Option<Rc<dyn Fn(bool)>>,
on_show_hover_highlight: Option<Rc<dyn Fn(bool)>>,
focus_subscription: RefCell<Option<FocusNodeSubscription>>,
highlight_subscription: RefCell<Option<FocusHighlightSubscription>>,
}
impl FocusBehavior {
#[must_use]
pub fn new(
node: FocusNode,
enabled: bool,
on_focus_change: Option<Rc<dyn Fn(bool)>>,
on_show_focus_highlight: Option<Rc<dyn Fn(bool)>>,
on_show_hover_highlight: Option<Rc<dyn Fn(bool)>>,
) -> Rc<Self> {
node.set_enabled(enabled);
let can_show_highlight =
FocusHighlightManager::new().mode() == FocusHighlightMode::Traditional;
let focused = node.has_focus();
let behavior = Rc::new(Self {
focused: Cell::new(focused),
node,
enabled: Cell::new(enabled),
hovering: Cell::new(false),
can_show_highlight: Cell::new(can_show_highlight),
focus_highlight_visible: Cell::new(focused && enabled && can_show_highlight),
hover_highlight_visible: Cell::new(false),
on_focus_change,
on_show_focus_highlight,
on_show_hover_highlight,
focus_subscription: RefCell::new(None),
highlight_subscription: RefCell::new(None),
});
let weak = Rc::downgrade(&behavior);
let focus_subscription = behavior.node.observe(move || {
if let Some(behavior) = weak.upgrade() {
behavior.handle_focus_change();
}
});
*behavior.focus_subscription.borrow_mut() = Some(focus_subscription);
let weak = Rc::downgrade(&behavior);
let highlight_subscription = FocusHighlightManager::new().observe(move |mode| {
if let Some(behavior) = weak.upgrade() {
behavior.update_highlight_mode(mode);
}
});
*behavior.highlight_subscription.borrow_mut() = Some(highlight_subscription);
behavior
}
#[must_use]
pub fn node(&self) -> FocusNode {
self.node.clone()
}
#[must_use]
pub fn is_hovering(&self) -> bool {
self.hovering.get()
}
#[must_use]
pub fn is_focused(&self) -> bool {
self.focused.get()
}
pub fn mouse_enter(&self) {
if !self.hovering.replace(true) {
self.trigger_highlight_callbacks();
}
}
pub fn mouse_exit(&self) {
if self.hovering.replace(false) {
self.trigger_highlight_callbacks();
}
}
pub fn restore_hovering(&self, hovering: bool) {
self.hovering.set(hovering);
self.hover_highlight_visible
.set(self.hover_highlight_visible());
}
fn handle_focus_change(&self) {
let focused = self.node.has_focus();
if self.focused.replace(focused) != focused {
self.trigger_highlight_callbacks();
if let Some(callback) = &self.on_focus_change {
callback(focused);
}
}
}
fn update_highlight_mode(&self, mode: FocusHighlightMode) {
self.can_show_highlight
.set(mode == FocusHighlightMode::Traditional);
self.trigger_highlight_callbacks();
}
fn focus_highlight_visible(&self) -> bool {
self.focused.get() && self.enabled.get() && self.can_show_highlight.get()
}
fn hover_highlight_visible(&self) -> bool {
self.hovering.get() && self.enabled.get() && self.can_show_highlight.get()
}
fn trigger_highlight_callbacks(&self) {
let focus_after = self.focus_highlight_visible();
let hover_after = self.hover_highlight_visible();
if self.focus_highlight_visible.replace(focus_after) != focus_after
&& let Some(callback) = &self.on_show_focus_highlight
{
callback(focus_after);
}
if self.hover_highlight_visible.replace(hover_after) != hover_after
&& let Some(callback) = &self.on_show_hover_highlight
{
callback(hover_after);
}
}
}
pub trait FocusTraversalPolicy {
fn find_first_focus(&self, nodes: &[FocusNode]) -> Option<FocusNode>;
fn find_last_focus(&self, nodes: &[FocusNode]) -> Option<FocusNode>;
fn next(&self, current: &FocusNode, nodes: &[FocusNode]) -> Option<FocusNode>;
fn previous(&self, current: &FocusNode, nodes: &[FocusNode]) -> Option<FocusNode>;
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum FocusTraversalPolicyKind {
#[default]
WidgetOrder,
ReadingOrder,
Ordered,
}
fn traversable(nodes: &[FocusNode]) -> Vec<FocusNode> {
nodes
.iter()
.filter(|node| node.can_request_focus() && !node.skip_traversal())
.cloned()
.collect()
}
fn reading_order(mut nodes: Vec<FocusNode>) -> Vec<FocusNode> {
nodes.sort_by(|left, right| {
let left_rect = left.rect();
let right_rect = right.rect();
let row_tolerance = left_rect
.size
.height
.max(right_rect.size.height)
.mul_add(0.5, 1.0);
let vertical = left_rect.origin.y - right_rect.origin.y;
if vertical.abs() > row_tolerance {
vertical
.partial_cmp(&0.0)
.unwrap_or(std::cmp::Ordering::Equal)
} else {
left_rect
.origin
.x
.partial_cmp(&right_rect.origin.x)
.unwrap_or(std::cmp::Ordering::Equal)
}
});
nodes
}
fn explicit_order(mut nodes: Vec<FocusNode>) -> Vec<FocusNode> {
nodes.sort_by(
|left, right| match (left.focus_order(), right.focus_order()) {
(Some(left), Some(right)) => left.compare(&right),
(Some(_), None) => std::cmp::Ordering::Less,
(None, Some(_)) => std::cmp::Ordering::Greater,
(None, None) => std::cmp::Ordering::Equal,
},
);
nodes
}
#[derive(Clone, Debug, PartialEq)]
pub enum FocusOrder {
Numeric(f64),
Lexical(String),
}
impl FocusOrder {
#[must_use]
pub fn numeric(order: f64) -> Self {
Self::Numeric(order)
}
#[must_use]
pub fn lexical(order: impl Into<String>) -> Self {
Self::Lexical(order.into())
}
#[must_use]
pub fn is_valid(&self) -> bool {
match self {
Self::Numeric(order) => order.is_finite(),
Self::Lexical(_) => true,
}
}
#[must_use]
pub fn compare(&self, other: &Self) -> std::cmp::Ordering {
match (self, other) {
(Self::Numeric(left), Self::Numeric(right)) => {
left.partial_cmp(right).unwrap_or(std::cmp::Ordering::Equal)
}
(Self::Lexical(left), Self::Lexical(right)) => {
left.encode_utf16().cmp(right.encode_utf16())
}
(Self::Numeric(_), Self::Lexical(_)) => std::cmp::Ordering::Less,
(Self::Lexical(_), Self::Numeric(_)) => std::cmp::Ordering::Greater,
}
}
}
fn policy_nodes(policy: FocusTraversalPolicyKind, nodes: &[FocusNode]) -> Vec<FocusNode> {
let nodes = traversable(nodes);
match policy {
FocusTraversalPolicyKind::WidgetOrder => nodes,
FocusTraversalPolicyKind::ReadingOrder => reading_order(nodes),
FocusTraversalPolicyKind::Ordered => explicit_order(nodes),
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct WidgetOrderTraversalPolicy;
impl FocusTraversalPolicy for WidgetOrderTraversalPolicy {
fn find_first_focus(&self, nodes: &[FocusNode]) -> Option<FocusNode> {
traversable(nodes).into_iter().next()
}
fn find_last_focus(&self, nodes: &[FocusNode]) -> Option<FocusNode> {
traversable(nodes).into_iter().next_back()
}
fn next(&self, current: &FocusNode, nodes: &[FocusNode]) -> Option<FocusNode> {
let traversable = traversable(nodes);
if let Some(pos) = traversable.iter().position(|n| n == current) {
traversable.get(pos + 1).cloned()
} else {
traversable.into_iter().next()
}
}
fn previous(&self, current: &FocusNode, nodes: &[FocusNode]) -> Option<FocusNode> {
let traversable = traversable(nodes);
if let Some(pos) = traversable.iter().position(|n| n == current) {
if pos > 0 {
traversable.get(pos - 1).cloned()
} else {
None
}
} else {
traversable.into_iter().next_back()
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ReadingOrderTraversalPolicy;
impl FocusTraversalPolicy for ReadingOrderTraversalPolicy {
fn find_first_focus(&self, nodes: &[FocusNode]) -> Option<FocusNode> {
reading_order(traversable(nodes)).into_iter().next()
}
fn find_last_focus(&self, nodes: &[FocusNode]) -> Option<FocusNode> {
reading_order(traversable(nodes)).into_iter().next_back()
}
fn next(&self, current: &FocusNode, nodes: &[FocusNode]) -> Option<FocusNode> {
WidgetOrderTraversalPolicy.next(current, &reading_order(traversable(nodes)))
}
fn previous(&self, current: &FocusNode, nodes: &[FocusNode]) -> Option<FocusNode> {
WidgetOrderTraversalPolicy.previous(current, &reading_order(traversable(nodes)))
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct OrderedTraversalPolicy;
impl FocusTraversalPolicy for OrderedTraversalPolicy {
fn find_first_focus(&self, nodes: &[FocusNode]) -> Option<FocusNode> {
explicit_order(traversable(nodes)).into_iter().next()
}
fn find_last_focus(&self, nodes: &[FocusNode]) -> Option<FocusNode> {
explicit_order(traversable(nodes)).into_iter().next_back()
}
fn next(&self, current: &FocusNode, nodes: &[FocusNode]) -> Option<FocusNode> {
WidgetOrderTraversalPolicy.next(current, &explicit_order(traversable(nodes)))
}
fn previous(&self, current: &FocusNode, nodes: &[FocusNode]) -> Option<FocusNode> {
WidgetOrderTraversalPolicy.previous(current, &explicit_order(traversable(nodes)))
}
}
#[derive(Default)]
pub struct FocusManager {
nodes: Vec<Weak<FocusState>>,
policy: FocusTraversalPolicyKind,
}
#[derive(Default)]
struct FocusTransition {
changed: Vec<Rc<FocusState>>,
}
impl FocusTransition {
fn set(&mut self, state: Rc<FocusState>, focused: bool) {
if state.set_focused_silent(focused)
&& !self.changed.iter().any(|known| Rc::ptr_eq(known, &state))
{
self.changed.push(state);
}
}
fn notify(self) {
for state in self.changed {
state.notify();
}
}
}
impl FocusManager {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_policy(policy: FocusTraversalPolicyKind) -> Self {
Self {
nodes: Vec::new(),
policy,
}
}
pub fn set_policy(&mut self, policy: FocusTraversalPolicyKind) {
self.policy = policy;
}
#[must_use]
pub const fn policy(&self) -> FocusTraversalPolicyKind {
self.policy
}
pub fn register(&mut self, node: &FocusNode) {
self.prune();
if !self.nodes.iter().any(|known| {
known
.upgrade()
.is_some_and(|known| Rc::ptr_eq(&known, &node.state))
}) {
self.nodes.push(Rc::downgrade(&node.state));
}
}
pub fn unregister(&mut self, node: &FocusNode) {
self.unregister_silent(node).notify();
}
fn unregister_silent(&mut self, node: &FocusNode) -> FocusTransition {
self.nodes.retain(|known| {
known
.upgrade()
.is_some_and(|known| !Rc::ptr_eq(&known, &node.state))
});
let mut transition = FocusTransition::default();
transition.set(node.state.clone(), false);
transition
}
#[must_use]
pub fn request_focus(&mut self, node: &FocusNode) -> bool {
let (requested, transition) = self.request_focus_silent(node);
transition.notify();
requested
}
fn request_focus_silent(&mut self, node: &FocusNode) -> (bool, FocusTransition) {
self.register(node);
if !node.can_request_focus() {
return (false, FocusTransition::default());
}
let mut transition = FocusTransition::default();
for known in self.nodes.iter().filter_map(Weak::upgrade) {
transition.set(known, false);
}
transition.set(node.state.clone(), true);
(true, transition)
}
pub fn clear_focus(&mut self) {
self.clear_focus_silent().notify();
}
fn clear_focus_silent(&mut self) -> FocusTransition {
let mut transition = FocusTransition::default();
for node in self.nodes.iter().filter_map(Weak::upgrade) {
transition.set(node, false);
}
self.prune();
transition
}
#[must_use]
pub fn focused(&mut self) -> Option<FocusNode> {
self.prune();
self.nodes
.iter()
.filter_map(Weak::upgrade)
.find_map(|state| state.focused.get().then_some(FocusNode { state }))
}
#[must_use]
pub fn focus_next(&mut self, reverse: bool) -> Option<FocusNode> {
let (next, transition) = self.focus_next_silent(reverse);
transition.notify();
next
}
fn focus_next_silent(&mut self, reverse: bool) -> (Option<FocusNode>, FocusTransition) {
self.prune();
let nodes: Vec<_> = self
.nodes
.iter()
.filter_map(Weak::upgrade)
.map(|state| FocusNode { state })
.collect();
let nodes = policy_nodes(self.policy, &nodes);
let Some(first) = nodes.first().cloned() else {
return (None, FocusTransition::default());
};
let current = nodes.iter().position(|node| node.state.focused.get());
let next = match current {
Some(index) if reverse => nodes[(index + nodes.len() - 1) % nodes.len()].clone(),
Some(index) => nodes[(index + 1) % nodes.len()].clone(),
None if reverse => nodes.last().cloned().unwrap_or(first),
None => first,
};
let mut transition = FocusTransition::default();
for node in self.nodes.iter().filter_map(Weak::upgrade) {
transition.set(node, false);
}
transition.set(next.state.clone(), true);
(Some(next), transition)
}
#[must_use]
pub fn registered_count(&mut self) -> usize {
self.prune();
self.nodes.len()
}
fn prune(&mut self) {
self.nodes.retain(|node| node.strong_count() > 0);
}
}
#[derive(Clone)]
pub struct FocusScopeNode {
state: Rc<FocusScopeState>,
}
pub struct FocusScopeSubscription {
_subscription: incular_core::reactivity::Subscription,
}
struct FocusScopeState {
manager: RefCell<FocusManager>,
last_focused: RefCell<Option<FocusNode>>,
parent: RefCell<Option<Weak<FocusScopeState>>>,
changes: incular_core::reactivity::DependencySource,
revision: Cell<u64>,
}
impl FocusScopeState {
fn bump_revision(&self) {
self.revision.set(
self.revision
.get()
.checked_add(1)
.expect("revision exhausted"),
);
self.changes.notify();
}
}
impl Default for FocusScopeNode {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for FocusScopeNode {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut manager = self.state.manager.borrow_mut();
formatter
.debug_struct("FocusScopeNode")
.field("registered_count", &manager.registered_count())
.field("has_focus", &manager.focused().is_some())
.finish()
}
}
impl FocusScopeNode {
#[must_use]
pub fn new() -> Self {
Self {
state: Rc::new(FocusScopeState {
manager: RefCell::new(FocusManager::new()),
last_focused: RefCell::new(None),
parent: RefCell::new(None),
changes: incular_core::reactivity::DependencySource::default(),
revision: Cell::new(0),
}),
}
}
#[must_use]
pub fn nested(parent: &Self) -> Self {
let scope = Self::new();
scope.set_parent(Some(parent));
scope
}
pub fn set_parent(&self, parent: Option<&Self>) {
let parent = parent.filter(|scope| !Rc::ptr_eq(&scope.state, &self.state));
*self.state.parent.borrow_mut() = parent.map(|scope| Rc::downgrade(&scope.state));
self.state.bump_revision();
}
#[must_use]
pub fn observe(&self, callback: impl Fn() + 'static) -> FocusScopeSubscription {
FocusScopeSubscription {
_subscription: self.state.changes.subscribe((), callback),
}
}
#[must_use]
pub fn revision(&self) -> u64 {
self.state.revision.get()
}
pub fn register(&self, node: &FocusNode) {
self.state.manager.borrow_mut().register(node);
self.state.bump_revision();
}
pub fn set_policy(&self, policy: FocusTraversalPolicyKind) {
self.state.manager.borrow_mut().set_policy(policy);
self.state.bump_revision();
}
#[must_use]
pub fn policy(&self) -> FocusTraversalPolicyKind {
self.state.manager.borrow().policy()
}
pub fn unregister(&self, node: &FocusNode) {
let transition = self.state.manager.borrow_mut().unregister_silent(node);
let clear_last = self
.state
.last_focused
.borrow()
.as_ref()
.is_some_and(|focused| focused == node);
if clear_last {
self.state.last_focused.borrow_mut().take();
}
self.state.bump_revision();
transition.notify();
}
#[must_use]
pub fn request_focus(&self, node: &FocusNode) -> bool {
if !node.can_request_focus() {
return false;
}
let parent = self
.state
.parent
.borrow()
.as_ref()
.and_then(Weak::upgrade)
.map(|state| Self { state });
if let Some(parent) = &parent {
let mut parent_manager = parent.state.manager.borrow_mut();
let parent_focused = parent_manager.focused();
let parent_transition = parent_focused
.as_ref()
.map(|_| parent_manager.clear_focus_silent());
drop(parent_manager);
if let Some(parent_focused) = parent_focused {
*parent.state.last_focused.borrow_mut() = Some(parent_focused);
}
let mut manager = self.state.manager.borrow_mut();
let previous = manager.focused();
let (requested, transition) = manager.request_focus_silent(node);
drop(manager);
if !requested {
return false;
}
*self.state.last_focused.borrow_mut() = previous;
if parent_transition.is_some() {
parent.state.bump_revision();
}
self.state.bump_revision();
if let Some(parent_transition) = parent_transition {
parent_transition.notify();
}
transition.notify();
return true;
}
let mut manager = self.state.manager.borrow_mut();
let previous = manager.focused();
let (requested, transition) = manager.request_focus_silent(node);
if requested {
*self.state.last_focused.borrow_mut() = previous;
drop(manager);
self.state.bump_revision();
transition.notify();
}
requested
}
pub fn clear_focus(&self) {
let mut manager = self.state.manager.borrow_mut();
let previous = manager.focused();
let transition = manager.clear_focus_silent();
drop(manager);
*self.state.last_focused.borrow_mut() = previous;
self.state.bump_revision();
transition.notify();
}
pub fn unfocus(&self) {
self.clear_focus();
}
#[must_use]
pub fn focused(&self) -> Option<FocusNode> {
self.state.manager.borrow_mut().focused()
}
#[must_use]
pub fn focus_next(&self, reverse: bool) -> Option<FocusNode> {
let mut manager = self.state.manager.borrow_mut();
let previous = manager.focused();
let (next, transition) = manager.focus_next_silent(reverse);
if next.is_some() {
*self.state.last_focused.borrow_mut() = previous;
drop(manager);
self.state.bump_revision();
transition.notify();
}
next
}
#[must_use]
pub fn restore_focus(&self) -> bool {
let Some(node) = self.state.last_focused.borrow().clone() else {
return false;
};
let mut manager = self.state.manager.borrow_mut();
let (restored, transition) = manager.request_focus_silent(&node);
if restored {
*self.state.last_focused.borrow_mut() = Some(node);
drop(manager);
self.state.bump_revision();
transition.notify();
}
restored
}
#[must_use]
pub fn restore_parent_focus(&self) -> bool {
self.state
.parent
.borrow()
.as_ref()
.and_then(Weak::upgrade)
.is_some_and(|parent| {
let scope = Self { state: parent };
scope.restore_focus()
})
}
#[must_use]
pub fn registered_count(&self) -> usize {
self.state.manager.borrow_mut().registered_count()
}
}