use std::sync::{Arc, Mutex};
use accesskit::{ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, TreeUpdate};
use martensite_core::WidgetArena;
use crate::actions::ActionHandler as MartensiteActionHandler;
use crate::AccessKitAdapter;
pub struct MartensiteAccessBridge {
inner: Mutex<BridgeInner>,
}
struct BridgeInner {
arena: WidgetArena,
adapter: AccessKitAdapter,
action_handler: Option<Box<dyn MartensiteActionHandler + Send>>,
activated: bool,
}
impl MartensiteAccessBridge {
pub fn new(arena: WidgetArena, adapter: AccessKitAdapter) -> Self {
Self {
inner: Mutex::new(BridgeInner {
arena,
adapter,
action_handler: None,
activated: false,
}),
}
}
pub fn set_action_handler<H: MartensiteActionHandler + Send + 'static>(&self, handler: H) {
let mut inner = self.inner.lock().unwrap();
inner.action_handler = Some(Box::new(handler));
}
pub fn shared(self) -> Arc<Self> {
Arc::new(self)
}
pub fn with_arena_mut<R>(
&self,
f: impl FnOnce(&mut WidgetArena, &mut AccessKitAdapter) -> R,
) -> R {
let mut guard = self.inner.lock().unwrap();
let inner = &mut *guard;
f(&mut inner.arena, &mut inner.adapter)
}
pub fn build_full_update(&self) -> TreeUpdate {
let mut guard = self.inner.lock().unwrap();
let inner = &mut *guard;
inner.adapter.build_update(&mut inner.arena)
}
pub fn build_incremental_update(&self) -> Option<TreeUpdate> {
let mut guard = self.inner.lock().unwrap();
let inner = &mut *guard;
inner.adapter.build_incremental_update(&mut inner.arena)
}
}
impl ActivationHandler for MartensiteAccessBridge {
fn request_initial_tree(&mut self) -> Option<TreeUpdate> {
let mut guard = self.inner.lock().unwrap();
let inner = &mut *guard;
inner.activated = true;
Some(inner.adapter.build_update(&mut inner.arena))
}
}
impl ActionHandler for MartensiteAccessBridge {
fn do_action(&mut self, request: ActionRequest) {
let mut guard = self.inner.lock().unwrap();
let inner = &mut *guard;
let tree_id = inner.adapter.tree_id();
let action = crate::actions::decode_action_request(&inner.arena, &request, &tree_id);
if let Some(action) = action {
if let Some(ref mut handler) = inner.action_handler {
handler.handle_action(&mut inner.arena, &action);
}
}
}
}
impl DeactivationHandler for MartensiteAccessBridge {
fn deactivate_accessibility(&mut self) {
let mut guard = self.inner.lock().unwrap();
let inner = &mut *guard;
inner.activated = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
use martensite_core::{ColdNode, HotNode, NodeFlags};
#[test]
fn bridge_builds_full_update() {
let mut arena = WidgetArena::new();
let root = arena.insert(HotNode::default(), ColdNode::default());
let adapter = AccessKitAdapter::new(root);
let bridge = MartensiteAccessBridge::new(arena, adapter);
let update = bridge.build_full_update();
assert!(!update.nodes.is_empty());
}
#[test]
fn bridge_activation_returns_initial_tree() {
use accesskit::ActivationHandler;
let mut arena = WidgetArena::new();
let root = arena.insert(HotNode::default(), ColdNode::default());
let adapter = AccessKitAdapter::new(root);
let mut bridge = MartensiteAccessBridge::new(arena, adapter);
let update = bridge.request_initial_tree();
assert!(update.is_some());
let update = update.unwrap();
assert!(!update.nodes.is_empty());
}
#[test]
fn bridge_action_handler_receives_decoded_action() {
use accesskit::ActionHandler as _;
let mut arena = WidgetArena::new();
let hot = HotNode {
flags: NodeFlags::FOCUSABLE | NodeFlags::VISIBLE,
..Default::default()
};
let id = arena.insert(hot, ColdNode::default());
let adapter = AccessKitAdapter::new(id);
let bridge = MartensiteAccessBridge::new(arena, adapter);
bridge.set_action_handler(crate::actions::QueuedActionDispatcher::new());
let request = ActionRequest {
action: accesskit::Action::Focus,
target_node: crate::widget_id_to_node_id(id),
target_tree: accesskit::TreeId::ROOT,
data: None,
};
let mut bridge_mut = bridge;
bridge_mut.do_action(request);
let _ = id;
}
#[test]
fn bridge_deactivation_sets_activated_false() {
use accesskit::DeactivationHandler as _;
let mut arena = WidgetArena::new();
let root = arena.insert(HotNode::default(), ColdNode::default());
let adapter = AccessKitAdapter::new(root);
let mut bridge = MartensiteAccessBridge::new(arena, adapter);
use accesskit::ActivationHandler as _;
let _ = bridge.request_initial_tree();
bridge.deactivate_accessibility();
let inner = bridge.inner.lock().unwrap();
assert!(!inner.activated);
}
#[test]
fn bridge_with_arena_mut_allows_mutation() {
let mut arena = WidgetArena::new();
let root = arena.insert(HotNode::default(), ColdNode::default());
let adapter = AccessKitAdapter::new(root);
let bridge = MartensiteAccessBridge::new(arena, adapter);
bridge.with_arena_mut(|arena, adapter| {
adapter.mark_dirty(arena, root);
});
let inner = bridge.inner.lock().unwrap();
let hot = inner.arena.get_hot(root).unwrap();
assert!(hot.flags.contains(NodeFlags::DIRTY_A11Y));
}
}