1use accesskit::{
12 Action, ActionData, ActionRequest, Affine, Live, NodeId, Orientation, Point, Rect, Role,
13 Toggled,
14};
15use accesskit_consumer::{FilterResult, Node, TreeState};
16use atspi_common::{
17 CoordType, Granularity, Interface, InterfaceSet, Layer, Politeness, RelationType,
18 Role as AtspiRole, ScrollType, State, StateSet,
19};
20use std::{
21 collections::HashMap,
22 hash::{Hash, Hasher},
23 iter::FusedIterator,
24 sync::{Arc, RwLock, RwLockReadGuard, Weak},
25};
26
27use crate::{
28 adapter::Adapter,
29 context::{AppContext, Context},
30 filters::filter,
31 util::*,
32 Action as AtspiAction, Error, ObjectEvent, Property, Rect as AtspiRect, Result,
33};
34
35pub(crate) struct NodeWrapper<'a>(pub(crate) &'a Node<'a>);
36
37impl NodeWrapper<'_> {
38 pub(crate) fn name(&self) -> Option<String> {
39 if self.0.label_comes_from_value() {
40 self.0.value()
41 } else {
42 self.0.label()
43 }
44 }
45
46 pub(crate) fn description(&self) -> Option<String> {
47 self.0.description()
48 }
49
50 pub(crate) fn parent_id(&self) -> Option<NodeId> {
51 self.0.parent_id()
52 }
53
54 pub(crate) fn id(&self) -> NodeId {
55 self.0.id()
56 }
57
58 fn filtered_child_ids(
59 &self,
60 ) -> impl DoubleEndedIterator<Item = NodeId> + FusedIterator<Item = NodeId> + '_ {
61 self.0.filtered_children(&filter).map(|child| child.id())
62 }
63
64 pub(crate) fn role(&self) -> AtspiRole {
65 if self.0.has_role_description() {
66 return AtspiRole::Extended;
67 }
68
69 match self.0.role() {
70 Role::Alert => AtspiRole::Notification,
71 Role::AlertDialog => AtspiRole::Alert,
72 Role::Comment | Role::Suggestion => AtspiRole::Section,
73 Role::Application => AtspiRole::Embedded,
75 Role::Article => AtspiRole::Article,
76 Role::Audio => AtspiRole::Audio,
77 Role::Banner | Role::Header => AtspiRole::Landmark,
78 Role::Blockquote => AtspiRole::BlockQuote,
79 Role::Caret => AtspiRole::Unknown,
80 Role::Button => {
81 if self.0.toggled().is_some() {
82 AtspiRole::ToggleButton
83 } else {
84 AtspiRole::Button
85 }
86 }
87 Role::DefaultButton => AtspiRole::Button,
88 Role::Canvas => AtspiRole::Canvas,
89 Role::Caption => AtspiRole::Caption,
90 Role::Cell => AtspiRole::TableCell,
91 Role::CheckBox => AtspiRole::CheckBox,
92 Role::Switch => AtspiRole::ToggleButton,
93 Role::ColorWell => AtspiRole::Button,
94 Role::ColumnHeader => AtspiRole::ColumnHeader,
95 Role::ComboBox | Role::EditableComboBox => AtspiRole::ComboBox,
96 Role::Complementary => AtspiRole::Landmark,
97 Role::ContentDeletion => AtspiRole::ContentDeletion,
98 Role::ContentInsertion => AtspiRole::ContentInsertion,
99 Role::ContentInfo | Role::Footer => AtspiRole::Landmark,
100 Role::Definition | Role::DescriptionListDetail => AtspiRole::DescriptionValue,
101 Role::DescriptionList => AtspiRole::DescriptionList,
102 Role::DescriptionListTerm => AtspiRole::DescriptionTerm,
103 Role::Details => AtspiRole::Panel,
104 Role::Dialog => AtspiRole::Dialog,
105 Role::Directory => AtspiRole::List,
106 Role::DisclosureTriangle => AtspiRole::ToggleButton,
107 Role::DocCover => AtspiRole::Image,
108 Role::DocBackLink | Role::DocBiblioRef | Role::DocGlossRef | Role::DocNoteRef => {
109 AtspiRole::Link
110 }
111 Role::DocBiblioEntry | Role::DocEndnote => AtspiRole::ListItem,
112 Role::DocNotice | Role::DocTip => AtspiRole::Comment,
113 Role::DocFootnote => AtspiRole::Footnote,
114 Role::DocPageBreak => AtspiRole::Separator,
115 Role::DocPageFooter => AtspiRole::Footer,
116 Role::DocPageHeader => AtspiRole::Header,
117 Role::DocAcknowledgements
118 | Role::DocAfterword
119 | Role::DocAppendix
120 | Role::DocBibliography
121 | Role::DocChapter
122 | Role::DocConclusion
123 | Role::DocCredits
124 | Role::DocEndnotes
125 | Role::DocEpilogue
126 | Role::DocErrata
127 | Role::DocForeword
128 | Role::DocGlossary
129 | Role::DocIndex
130 | Role::DocIntroduction
131 | Role::DocPageList
132 | Role::DocPart
133 | Role::DocPreface
134 | Role::DocPrologue
135 | Role::DocToc => AtspiRole::Landmark,
136 Role::DocAbstract
137 | Role::DocColophon
138 | Role::DocCredit
139 | Role::DocDedication
140 | Role::DocEpigraph
141 | Role::DocExample
142 | Role::DocPullquote
143 | Role::DocQna => AtspiRole::Section,
144 Role::DocSubtitle => AtspiRole::Heading,
145 Role::Document => AtspiRole::DocumentFrame,
146 Role::EmbeddedObject => AtspiRole::Embedded,
147 Role::Form => AtspiRole::Form,
151 Role::Figure | Role::Feed => AtspiRole::Panel,
152 Role::GenericContainer
153 | Role::FooterAsNonLandmark
154 | Role::HeaderAsNonLandmark
155 | Role::Ruby => AtspiRole::Section,
156 Role::GraphicsDocument => AtspiRole::DocumentFrame,
157 Role::GraphicsObject => AtspiRole::Panel,
158 Role::GraphicsSymbol => AtspiRole::Image,
159 Role::Grid => AtspiRole::Table,
160 Role::Group => AtspiRole::Panel,
161 Role::Heading => AtspiRole::Heading,
162 Role::Iframe | Role::IframePresentational => AtspiRole::InternalFrame,
163 Role::Image => AtspiRole::Image,
165 Role::TextRun => AtspiRole::Static,
166 Role::Legend => AtspiRole::Label,
167 Role::LayoutTable => AtspiRole::Section,
169 Role::LayoutTableCell => AtspiRole::Section,
170 Role::LayoutTableRow => AtspiRole::Section,
171 Role::LineBreak => AtspiRole::Static,
174 Role::Link => AtspiRole::Link,
175 Role::List => AtspiRole::List,
176 Role::ListBox => AtspiRole::ListBox,
177 Role::ListBoxOption => AtspiRole::ListItem,
179 Role::ListGrid => AtspiRole::Table,
180 Role::ListItem => AtspiRole::ListItem,
181 Role::ListMarker => AtspiRole::Static,
190 Role::Log => AtspiRole::Log,
191 Role::Main => AtspiRole::Landmark,
192 Role::Mark => AtspiRole::Static,
193 Role::Math => AtspiRole::Math,
194 Role::Marquee => AtspiRole::Marquee,
195 Role::Menu | Role::MenuListPopup => AtspiRole::Menu,
196 Role::MenuBar => AtspiRole::MenuBar,
197 Role::MenuItem | Role::MenuListOption => AtspiRole::MenuItem,
198 Role::MenuItemCheckBox => AtspiRole::CheckMenuItem,
199 Role::MenuItemRadio => AtspiRole::RadioMenuItem,
200 Role::Meter => AtspiRole::LevelBar,
201 Role::Navigation => AtspiRole::Landmark,
202 Role::Note => AtspiRole::Comment,
203 Role::Pane | Role::ScrollView => AtspiRole::Panel,
204 Role::Paragraph => AtspiRole::Paragraph,
205 Role::PdfActionableHighlight => AtspiRole::Button,
206 Role::PdfRoot => AtspiRole::DocumentFrame,
207 Role::PluginObject => AtspiRole::Embedded,
208 Role::Portal => AtspiRole::Button,
209 Role::Pre => AtspiRole::Section,
210 Role::ProgressIndicator => AtspiRole::ProgressBar,
211 Role::RadioButton => AtspiRole::RadioButton,
212 Role::RadioGroup => AtspiRole::Panel,
213 Role::Region => AtspiRole::Landmark,
214 Role::RootWebArea => AtspiRole::DocumentWeb,
215 Role::Row => AtspiRole::TableRow,
216 Role::RowGroup => AtspiRole::Panel,
217 Role::RowHeader => AtspiRole::RowHeader,
218 Role::RubyAnnotation => AtspiRole::Static,
226 Role::Section => AtspiRole::Section,
227 Role::ScrollBar => AtspiRole::ScrollBar,
228 Role::Search => AtspiRole::Landmark,
229 Role::Slider => AtspiRole::Slider,
230 Role::SpinButton => AtspiRole::SpinButton,
231 Role::Splitter => AtspiRole::Separator,
232 Role::Label => AtspiRole::Label,
233 Role::Status => AtspiRole::StatusBar,
234 Role::SvgRoot => AtspiRole::DocumentFrame,
235 Role::Tab => AtspiRole::PageTab,
236 Role::Table => AtspiRole::Table,
237 Role::TabList => AtspiRole::PageTabList,
238 Role::TabPanel => AtspiRole::ScrollPane,
239 Role::Term => AtspiRole::DescriptionTerm,
242 Role::TitleBar => AtspiRole::TitleBar,
243 Role::TextInput
244 | Role::MultilineTextInput
245 | Role::SearchInput
246 | Role::EmailInput
247 | Role::NumberInput
248 | Role::PhoneNumberInput
249 | Role::UrlInput => AtspiRole::Entry,
250 Role::DateInput
251 | Role::DateTimeInput
252 | Role::WeekInput
253 | Role::MonthInput
254 | Role::TimeInput => AtspiRole::DateEditor,
255 Role::PasswordInput => AtspiRole::PasswordText,
256 Role::Abbr | Role::Code | Role::Emphasis | Role::Strong | Role::Time => {
257 AtspiRole::Static
258 }
259 Role::Timer => AtspiRole::Timer,
260 Role::Toolbar => AtspiRole::ToolBar,
261 Role::Tooltip => AtspiRole::ToolTip,
262 Role::Tree => AtspiRole::Tree,
263 Role::TreeItem => AtspiRole::TreeItem,
264 Role::TreeGrid => AtspiRole::TreeTable,
265 Role::Video => AtspiRole::Video,
266 Role::Window => AtspiRole::Frame,
270 Role::WebView => AtspiRole::Panel,
271 Role::FigureCaption => AtspiRole::Caption,
272 Role::Unknown => AtspiRole::Unknown,
274 Role::ImeCandidate | Role::Keyboard => AtspiRole::RedundantObject,
275 Role::Terminal => AtspiRole::Terminal,
276 }
277 }
278
279 fn is_focused(&self) -> bool {
280 self.0.is_focused()
281 }
282
283 pub(crate) fn state(&self, is_window_focused: bool) -> StateSet {
284 let state = self.0;
285 let atspi_role = self.role();
286 let mut atspi_state = StateSet::empty();
287 if state.parent_id().is_none() && state.role() == Role::Window && is_window_focused {
288 atspi_state.insert(State::Active);
289 }
290 if state.is_text_input() && !state.is_read_only() {
291 atspi_state.insert(State::Editable);
292 }
293 if state.is_focusable(&filter) {
295 atspi_state.insert(State::Focusable);
296 }
297 let filter_result = filter(self.0);
298 if filter_result == FilterResult::Include {
299 atspi_state.insert(State::Visible | State::Showing);
300 }
301 if state.is_required() {
302 atspi_state.insert(State::Required);
303 }
304 if state.is_multiselectable() {
305 atspi_state.insert(State::Multiselectable);
306 }
307 if let Some(orientation) = state.orientation() {
308 atspi_state.insert(if orientation == Orientation::Horizontal {
309 State::Horizontal
310 } else {
311 State::Vertical
312 });
313 }
314 if atspi_role != AtspiRole::ToggleButton && state.toggled().is_some() {
315 atspi_state.insert(State::Checkable);
316 }
317 if let Some(selected) = state.is_selected() {
318 if !state.is_disabled() {
319 atspi_state.insert(State::Selectable);
320 }
321 if selected {
322 atspi_state.insert(State::Selected);
323 }
324 }
325 if state.is_text_input() {
326 atspi_state.insert(State::SelectableText);
327 atspi_state.insert(match state.is_multiline() {
328 true => State::MultiLine,
329 false => State::SingleLine,
330 });
331 }
332
333 if state.role() == Role::ProgressIndicator && state.numeric_value().is_none() {
335 atspi_state.insert(State::Indeterminate);
336 }
337
338 match state.toggled() {
340 Some(Toggled::Mixed) => atspi_state.insert(State::Indeterminate),
341 Some(Toggled::True) if atspi_role == AtspiRole::ToggleButton => {
342 atspi_state.insert(State::Pressed)
343 }
344 Some(Toggled::True) => atspi_state.insert(State::Checked),
345 _ => {}
346 }
347
348 if state.is_read_only_supported() && state.is_read_only_or_disabled() {
349 atspi_state.insert(State::ReadOnly);
350 } else {
351 atspi_state.insert(State::Enabled | State::Sensitive);
352 }
353
354 if self.is_focused() {
355 atspi_state.insert(State::Focused);
356 }
357
358 atspi_state
359 }
360
361 fn placeholder(&self) -> Option<&str> {
362 self.0.placeholder()
363 }
364
365 fn position_in_set(&self) -> Option<String> {
366 self.0.position_in_set().map(|p| (p + 1).to_string())
367 }
368
369 fn size_of_set(&self) -> Option<String> {
370 self.0
371 .size_of_set_from_container(&filter)
372 .map(|s| s.to_string())
373 }
374
375 fn attributes(&self) -> HashMap<&'static str, String> {
376 let mut attributes = HashMap::new();
377 if let Some(placeholder) = self.placeholder() {
378 attributes.insert("placeholder-text", placeholder.to_string());
379 }
380 if let Some(position_in_set) = self.position_in_set() {
381 attributes.insert("posinset", position_in_set);
382 }
383 if let Some(size_of_set) = self.size_of_set() {
384 attributes.insert("setsize", size_of_set);
385 }
386
387 attributes
388 }
389
390 fn is_root(&self) -> bool {
391 self.0.is_root()
392 }
393
394 fn supports_action(&self) -> bool {
395 self.0.is_clickable(&filter)
396 }
397
398 fn supports_component(&self) -> bool {
399 self.0.raw_bounds().is_some() || self.is_root()
400 }
401
402 fn supports_selection(&self) -> bool {
403 self.0.is_container_with_selectable_children()
404 }
405
406 fn supports_text(&self) -> bool {
407 self.0.supports_text_ranges()
408 }
409
410 fn supports_value(&self) -> bool {
411 self.current_value().is_some()
412 }
413
414 pub(crate) fn interfaces(&self) -> InterfaceSet {
415 let mut interfaces = InterfaceSet::new(Interface::Accessible);
416 if self.supports_action() {
417 interfaces.insert(Interface::Action);
418 }
419 if self.supports_component() {
420 interfaces.insert(Interface::Component);
421 }
422 if self.supports_selection() {
423 interfaces.insert(Interface::Selection);
424 }
425 if self.supports_text() {
426 interfaces.insert(Interface::Text);
427 }
428 if self.supports_value() {
429 interfaces.insert(Interface::Value);
430 }
431 interfaces
432 }
433
434 pub(crate) fn live(&self) -> Politeness {
435 let live = self.0.live();
436 match live {
437 Live::Off => Politeness::None,
438 Live::Polite => Politeness::Polite,
439 Live::Assertive => Politeness::Assertive,
440 }
441 }
442
443 fn n_actions(&self) -> i32 {
444 if self.0.is_clickable(&filter) {
445 1
446 } else {
447 0
448 }
449 }
450
451 fn get_action_name(&self, index: i32) -> String {
452 if index != 0 {
453 return String::new();
454 }
455 String::from(if self.0.is_clickable(&filter) {
456 "click"
457 } else {
458 ""
459 })
460 }
461
462 fn raw_bounds_and_transform(&self) -> (Option<Rect>, Affine) {
463 let state = self.0;
464 (state.raw_bounds(), state.direct_transform())
465 }
466
467 fn extents(&self, window_bounds: &WindowBounds, coord_type: CoordType) -> Option<Rect> {
468 let mut bounds = self.0.bounding_box();
469 if self.is_root() {
470 let window_bounds = window_bounds.inner.with_origin(Point::ZERO);
471 if !window_bounds.is_empty() {
472 if let Some(bounds) = &mut bounds {
473 bounds.x0 = bounds.x0.min(window_bounds.x1);
474 bounds.y0 = bounds.y0.min(window_bounds.y1);
475 bounds.x1 = bounds.x1.min(window_bounds.x1);
476 bounds.y1 = bounds.y1.min(window_bounds.y1);
477 } else {
478 bounds = Some(window_bounds);
479 }
480 }
481 }
482 bounds.map(|bounds| {
483 let new_origin = window_bounds.accesskit_point_to_atspi_point(
484 bounds.origin(),
485 self.0.filtered_parent(&filter),
486 coord_type,
487 );
488 bounds.with_origin(new_origin)
489 })
490 }
491
492 fn current_value(&self) -> Option<f64> {
493 self.0.numeric_value()
494 }
495
496 pub(crate) fn notify_changes(
497 &self,
498 window_bounds: &WindowBounds,
499 adapter: &Adapter,
500 old: &NodeWrapper<'_>,
501 ) {
502 self.notify_state_changes(adapter, old);
503 self.notify_property_changes(adapter, old);
504 self.notify_bounds_changes(window_bounds, adapter, old);
505 self.notify_children_changes(adapter, old);
506 }
507
508 fn notify_state_changes(&self, adapter: &Adapter, old: &NodeWrapper<'_>) {
509 let old_state = old.state(true);
510 let new_state = self.state(true);
511 let changed_states = old_state ^ new_state;
512 for state in changed_states.iter() {
513 if state == State::Focused {
514 continue;
516 }
517 adapter.emit_object_event(
518 self.id(),
519 ObjectEvent::StateChanged(state, new_state.contains(state)),
520 );
521 }
522 }
523
524 fn notify_property_changes(&self, adapter: &Adapter, old: &NodeWrapper<'_>) {
525 let name = self.name();
526 if name != old.name() {
527 let name = name.unwrap_or_default();
528 adapter.emit_object_event(
529 self.id(),
530 ObjectEvent::PropertyChanged(Property::Name(name.clone())),
531 );
532
533 let live = self.live();
534 if live != Politeness::None {
535 adapter.emit_object_event(self.id(), ObjectEvent::Announcement(name, live));
536 }
537 }
538 let description = self.description();
539 if description != old.description() {
540 adapter.emit_object_event(
541 self.id(),
542 ObjectEvent::PropertyChanged(Property::Description(
543 description.unwrap_or_default(),
544 )),
545 );
546 }
547 let parent_id = self.parent_id();
548 if parent_id != old.parent_id() {
549 let parent = self
550 .0
551 .filtered_parent(&filter)
552 .map_or(NodeIdOrRoot::Root, |node| NodeIdOrRoot::Node(node.id()));
553 adapter.emit_object_event(
554 self.id(),
555 ObjectEvent::PropertyChanged(Property::Parent(parent)),
556 );
557 }
558 let role = self.role();
559 if role != old.role() {
560 adapter.emit_object_event(
561 self.id(),
562 ObjectEvent::PropertyChanged(Property::Role(role)),
563 );
564 }
565 if let Some(value) = self.current_value() {
566 if Some(value) != old.current_value() {
567 adapter.emit_object_event(
568 self.id(),
569 ObjectEvent::PropertyChanged(Property::Value(value)),
570 );
571 }
572 }
573 }
574
575 fn notify_bounds_changes(
576 &self,
577 window_bounds: &WindowBounds,
578 adapter: &Adapter,
579 old: &NodeWrapper<'_>,
580 ) {
581 if self.raw_bounds_and_transform() != old.raw_bounds_and_transform() {
582 if let Some(extents) = self.extents(window_bounds, CoordType::Window) {
583 adapter.emit_object_event(self.id(), ObjectEvent::BoundsChanged(extents.into()));
584 }
585 }
586 }
587
588 fn notify_children_changes(&self, adapter: &Adapter, old: &NodeWrapper<'_>) {
589 let old_filtered_children = old.filtered_child_ids().collect::<Vec<NodeId>>();
590 let new_filtered_children = self.filtered_child_ids().collect::<Vec<NodeId>>();
591 for (index, child) in new_filtered_children.iter().enumerate() {
592 if !old_filtered_children.contains(child) {
593 adapter.emit_object_event(self.id(), ObjectEvent::ChildAdded(index, *child));
594 }
595 }
596 for child in old_filtered_children.into_iter() {
597 if !new_filtered_children.contains(&child) {
598 adapter.emit_object_event(self.id(), ObjectEvent::ChildRemoved(child));
599 }
600 }
601 }
602}
603
604#[derive(Clone)]
605pub struct PlatformNode {
606 context: Weak<Context>,
607 adapter_id: usize,
608 id: NodeId,
609}
610
611impl PlatformNode {
612 pub(crate) fn new(context: &Arc<Context>, adapter_id: usize, id: NodeId) -> Self {
613 Self {
614 context: Arc::downgrade(context),
615 adapter_id,
616 id,
617 }
618 }
619
620 fn from_adapter_root(adapter_id_and_context: &(usize, Arc<Context>)) -> Self {
621 let (adapter_id, context) = adapter_id_and_context;
622 Self::new(context, *adapter_id, context.read_tree().state().root_id())
623 }
624
625 fn upgrade_context(&self) -> Result<Arc<Context>> {
626 if let Some(context) = self.context.upgrade() {
627 Ok(context)
628 } else {
629 Err(Error::Defunct)
630 }
631 }
632
633 fn with_tree_state<F, T>(&self, f: F) -> Result<T>
634 where
635 F: FnOnce(&TreeState) -> Result<T>,
636 {
637 let context = self.upgrade_context()?;
638 let tree = context.read_tree();
639 f(tree.state())
640 }
641
642 fn with_tree_state_and_context<F, T>(&self, f: F) -> Result<T>
643 where
644 F: FnOnce(&TreeState, &Context) -> Result<T>,
645 {
646 let context = self.upgrade_context()?;
647 let tree = context.read_tree();
648 f(tree.state(), &context)
649 }
650
651 fn resolve_with_context<F, T>(&self, f: F) -> Result<T>
652 where
653 for<'a> F: FnOnce(Node<'a>, &Context) -> Result<T>,
654 {
655 self.with_tree_state_and_context(|state, context| {
656 if let Some(node) = state.node_by_id(self.id) {
657 f(node, context)
658 } else {
659 Err(Error::Defunct)
660 }
661 })
662 }
663
664 fn resolve_for_selection_with_context<F, T>(&self, f: F) -> Result<T>
665 where
666 for<'a> F: FnOnce(Node<'a>, &Context) -> Result<T>,
667 {
668 self.resolve_with_context(|node, context| {
669 let wrapper = NodeWrapper(&node);
670 if wrapper.supports_selection() {
671 f(node, context)
672 } else {
673 Err(Error::UnsupportedInterface)
674 }
675 })
676 }
677
678 fn resolve_for_text_with_context<F, T>(&self, f: F) -> Result<T>
679 where
680 for<'a> F: FnOnce(Node<'a>, &Context) -> Result<T>,
681 {
682 self.resolve_with_context(|node, context| {
683 let wrapper = NodeWrapper(&node);
684 if wrapper.supports_text() {
685 f(node, context)
686 } else {
687 Err(Error::UnsupportedInterface)
688 }
689 })
690 }
691
692 fn resolve<F, T>(&self, f: F) -> Result<T>
693 where
694 for<'a> F: FnOnce(Node<'a>) -> Result<T>,
695 {
696 self.resolve_with_context(|node, _| f(node))
697 }
698
699 fn resolve_for_selection<F, T>(&self, f: F) -> Result<T>
700 where
701 for<'a> F: FnOnce(Node<'a>) -> Result<T>,
702 {
703 self.resolve(|node| {
704 let wrapper = NodeWrapper(&node);
705 if wrapper.supports_selection() {
706 f(node)
707 } else {
708 Err(Error::UnsupportedInterface)
709 }
710 })
711 }
712
713 fn resolve_for_text<F, T>(&self, f: F) -> Result<T>
714 where
715 for<'a> F: FnOnce(Node<'a>) -> Result<T>,
716 {
717 self.resolve_for_text_with_context(|node, _| f(node))
718 }
719
720 fn do_action_internal<F>(&self, f: F) -> Result<()>
721 where
722 F: FnOnce(&TreeState, &Context) -> ActionRequest,
723 {
724 let context = self.upgrade_context()?;
725 let tree = context.read_tree();
726 if tree.state().has_node(self.id) {
727 let request = f(tree.state(), &context);
728 drop(tree);
729 context.do_action(request);
730 Ok(())
731 } else {
732 Err(Error::Defunct)
733 }
734 }
735
736 pub fn name(&self) -> Result<String> {
737 self.resolve(|node| {
738 let wrapper = NodeWrapper(&node);
739 Ok(wrapper.name().unwrap_or_default())
740 })
741 }
742
743 pub fn description(&self) -> Result<String> {
744 self.resolve(|node| {
745 let wrapper = NodeWrapper(&node);
746 Ok(wrapper.description().unwrap_or_default())
747 })
748 }
749
750 pub fn relative(&self, id: NodeId) -> Self {
751 Self {
752 context: self.context.clone(),
753 adapter_id: self.adapter_id,
754 id,
755 }
756 }
757
758 pub fn root(&self) -> Result<PlatformRoot> {
759 let context = self.upgrade_context()?;
760 Ok(PlatformRoot::new(&context.app_context))
761 }
762
763 pub fn toolkit_name(&self) -> Result<String> {
764 self.with_tree_state(|state| Ok(state.toolkit_name().unwrap_or_default().to_string()))
765 }
766
767 pub fn toolkit_version(&self) -> Result<String> {
768 self.with_tree_state(|state| Ok(state.toolkit_version().unwrap_or_default().to_string()))
769 }
770
771 pub fn parent(&self) -> Result<NodeIdOrRoot> {
772 self.resolve(|node| {
773 let parent = node
774 .filtered_parent(&filter)
775 .map_or(NodeIdOrRoot::Root, |node| NodeIdOrRoot::Node(node.id()));
776 Ok(parent)
777 })
778 }
779
780 pub fn child_count(&self) -> Result<i32> {
781 self.resolve(|node| {
782 i32::try_from(node.filtered_children(&filter).count())
783 .map_err(|_| Error::TooManyChildren)
784 })
785 }
786
787 pub fn adapter_id(&self) -> usize {
788 self.adapter_id
789 }
790
791 pub fn id(&self) -> NodeId {
792 self.id
793 }
794
795 pub fn accessible_id(&self) -> Result<String> {
796 self.resolve(|node| {
797 if let Some(author_id) = node.author_id() {
798 Ok(author_id.to_string())
799 } else {
800 Ok(String::new())
801 }
802 })
803 }
804
805 pub fn child_at_index(&self, index: usize) -> Result<Option<NodeId>> {
806 self.resolve(|node| {
807 let child = node
808 .filtered_children(&filter)
809 .nth(index)
810 .map(|child| child.id());
811 Ok(child)
812 })
813 }
814
815 pub fn map_children<T, I>(&self, f: impl Fn(NodeId) -> I) -> Result<T>
816 where
817 T: FromIterator<I>,
818 {
819 self.resolve(|node| {
820 let children = node
821 .filtered_children(&filter)
822 .map(|child| child.id())
823 .map(f)
824 .collect();
825 Ok(children)
826 })
827 }
828
829 pub fn index_in_parent(&self) -> Result<i32> {
830 self.resolve(|node| {
831 i32::try_from(node.preceding_filtered_siblings(&filter).count())
832 .map_err(|_| Error::IndexOutOfRange)
833 })
834 }
835
836 pub fn relation_set<T>(
837 &self,
838 f: impl Fn(NodeId) -> T,
839 ) -> Result<HashMap<RelationType, Vec<T>>> {
840 self.resolve(|node| {
841 let mut relations = HashMap::new();
842 let controls: Vec<_> = node
843 .controls()
844 .filter(|controlled| filter(controlled) == FilterResult::Include)
845 .map(|controlled| controlled.id())
846 .map(f)
847 .collect();
848 if !controls.is_empty() {
849 relations.insert(RelationType::ControllerFor, controls);
850 }
851 Ok(relations)
852 })
853 }
854
855 pub fn role(&self) -> Result<AtspiRole> {
856 self.resolve(|node| {
857 let wrapper = NodeWrapper(&node);
858 Ok(wrapper.role())
859 })
860 }
861
862 pub fn localized_role_name(&self) -> Result<String> {
863 self.resolve(|node| Ok(node.role_description().unwrap_or_default().to_string()))
864 }
865
866 pub fn state(&self) -> StateSet {
867 self.resolve_with_context(|node, context| {
868 let wrapper = NodeWrapper(&node);
869 Ok(wrapper.state(context.read_tree().state().focus_id().is_some()))
870 })
871 .unwrap_or(State::Defunct.into())
872 }
873
874 pub fn attributes(&self) -> Result<HashMap<&'static str, String>> {
875 self.resolve(|node| {
876 let wrapper = NodeWrapper(&node);
877 Ok(wrapper.attributes())
878 })
879 }
880
881 pub fn supports_action(&self) -> Result<bool> {
882 self.resolve(|node| {
883 let wrapper = NodeWrapper(&node);
884 Ok(wrapper.supports_action())
885 })
886 }
887
888 pub fn supports_component(&self) -> Result<bool> {
889 self.resolve(|node| {
890 let wrapper = NodeWrapper(&node);
891 Ok(wrapper.supports_component())
892 })
893 }
894
895 pub fn supports_selection(&self) -> Result<bool> {
896 self.resolve(|node| {
897 let wrapper = NodeWrapper(&node);
898 Ok(wrapper.supports_selection())
899 })
900 }
901
902 pub fn supports_text(&self) -> Result<bool> {
903 self.resolve(|node| {
904 let wrapper = NodeWrapper(&node);
905 Ok(wrapper.supports_text())
906 })
907 }
908
909 pub fn supports_value(&self) -> Result<bool> {
910 self.resolve(|node| {
911 let wrapper = NodeWrapper(&node);
912 Ok(wrapper.supports_value())
913 })
914 }
915
916 pub fn interfaces(&self) -> Result<InterfaceSet> {
917 self.resolve(|node| {
918 let wrapper = NodeWrapper(&node);
919 Ok(wrapper.interfaces())
920 })
921 }
922
923 pub fn n_actions(&self) -> Result<i32> {
924 self.resolve(|node| {
925 let wrapper = NodeWrapper(&node);
926 Ok(wrapper.n_actions())
927 })
928 }
929
930 pub fn action_name(&self, index: i32) -> Result<String> {
931 self.resolve(|node| {
932 let wrapper = NodeWrapper(&node);
933 Ok(wrapper.get_action_name(index))
934 })
935 }
936
937 pub fn actions(&self) -> Result<Vec<AtspiAction>> {
938 self.resolve(|node| {
939 let wrapper = NodeWrapper(&node);
940 let n_actions = wrapper.n_actions() as usize;
941 let mut actions = Vec::with_capacity(n_actions);
942 for i in 0..n_actions {
943 actions.push(AtspiAction {
944 localized_name: wrapper.get_action_name(i as i32),
945 description: "".into(),
946 key_binding: "".into(),
947 });
948 }
949 Ok(actions)
950 })
951 }
952
953 pub fn do_action(&self, index: i32) -> Result<bool> {
954 if index != 0 {
955 return Ok(false);
956 }
957 self.do_action_internal(|_, _| ActionRequest {
958 action: Action::Click,
959 target: self.id,
960 data: None,
961 })?;
962 Ok(true)
963 }
964
965 pub fn contains(&self, x: i32, y: i32, coord_type: CoordType) -> Result<bool> {
966 self.resolve_with_context(|node, context| {
967 let window_bounds = context.read_root_window_bounds();
968 let wrapper = NodeWrapper(&node);
969 if let Some(extents) = wrapper.extents(&window_bounds, coord_type) {
970 Ok(extents.contains(Point::new(x.into(), y.into())))
971 } else {
972 Ok(false)
973 }
974 })
975 }
976
977 pub fn accessible_at_point(
978 &self,
979 x: i32,
980 y: i32,
981 coord_type: CoordType,
982 ) -> Result<Option<NodeId>> {
983 self.resolve_with_context(|node, context| {
984 let window_bounds = context.read_root_window_bounds();
985 let point = window_bounds.atspi_point_to_accesskit_point(
986 Point::new(x.into(), y.into()),
987 Some(node),
988 coord_type,
989 );
990 let point = node.transform().inverse() * point;
991 Ok(node.node_at_point(point, &filter).map(|node| node.id()))
992 })
993 }
994
995 pub fn extents(&self, coord_type: CoordType) -> Result<AtspiRect> {
996 self.resolve_with_context(|node, context| {
997 let window_bounds = context.read_root_window_bounds();
998 let wrapper = NodeWrapper(&node);
999 Ok(wrapper
1000 .extents(&window_bounds, coord_type)
1001 .map_or(AtspiRect::INVALID, AtspiRect::from))
1002 })
1003 }
1004
1005 pub fn layer(&self) -> Result<Layer> {
1006 self.resolve(|node| {
1007 let wrapper = NodeWrapper(&node);
1008 if wrapper.role() == AtspiRole::Window {
1009 Ok(Layer::Window)
1010 } else {
1011 Ok(Layer::Widget)
1012 }
1013 })
1014 }
1015
1016 pub fn grab_focus(&self) -> Result<bool> {
1017 self.do_action_internal(|_, _| ActionRequest {
1018 action: Action::Focus,
1019 target: self.id,
1020 data: None,
1021 })?;
1022 Ok(true)
1023 }
1024
1025 pub fn scroll_to(&self, scroll_type: ScrollType) -> Result<bool> {
1026 self.do_action_internal(|_, _| ActionRequest {
1027 action: Action::ScrollIntoView,
1028 target: self.id,
1029 data: atspi_scroll_type_to_scroll_hint(scroll_type).map(ActionData::ScrollHint),
1030 })?;
1031 Ok(true)
1032 }
1033
1034 pub fn scroll_to_point(&self, coord_type: CoordType, x: i32, y: i32) -> Result<bool> {
1035 self.resolve_with_context(|node, context| {
1036 let window_bounds = context.read_root_window_bounds();
1037 let point = window_bounds.atspi_point_to_accesskit_point(
1038 Point::new(x.into(), y.into()),
1039 node.filtered_parent(&filter),
1040 coord_type,
1041 );
1042 context.do_action(ActionRequest {
1043 action: Action::ScrollToPoint,
1044 target: self.id,
1045 data: Some(ActionData::ScrollToPoint(point)),
1046 });
1047 Ok(())
1048 })?;
1049 Ok(true)
1050 }
1051
1052 pub fn n_selected_children(&self) -> Result<i32> {
1053 self.resolve_for_selection(|node| {
1054 node.items(filter)
1055 .filter(|item| item.is_selected() == Some(true))
1056 .count()
1057 .try_into()
1058 .map_err(|_| Error::TooManyChildren)
1059 })
1060 }
1061
1062 pub fn selected_child(&self, selected_child_index: usize) -> Result<Option<NodeId>> {
1063 self.resolve_for_selection(|node| {
1064 Ok(node
1065 .items(filter)
1066 .filter(|item| item.is_selected() == Some(true))
1067 .nth(selected_child_index)
1068 .map(|node| node.id()))
1069 })
1070 }
1071
1072 pub fn select_child(&self, child_index: usize) -> Result<bool> {
1073 self.resolve_for_selection_with_context(|node, context| {
1074 if let Some(child) = node.filtered_children(filter).nth(child_index) {
1075 if let Some(true) = child.is_selected() {
1076 Ok(true)
1077 } else if child.is_selectable() && child.is_clickable(&filter) {
1078 context.do_action(ActionRequest {
1079 action: Action::Click,
1080 target: child.id(),
1081 data: None,
1082 });
1083 Ok(true)
1084 } else {
1085 Ok(false)
1086 }
1087 } else {
1088 Err(Error::Defunct)
1089 }
1090 })
1091 }
1092
1093 pub fn deselect_selected_child(&self, selected_child_index: usize) -> Result<bool> {
1094 self.resolve_for_selection_with_context(|node, context| {
1095 if let Some(child) = node
1096 .items(filter)
1097 .filter(|c| c.is_selected() == Some(true))
1098 .nth(selected_child_index)
1099 {
1100 if child.is_clickable(&filter) {
1101 context.do_action(ActionRequest {
1102 action: Action::Click,
1103 target: child.id(),
1104 data: None,
1105 });
1106 Ok(true)
1107 } else {
1108 Ok(false)
1109 }
1110 } else {
1111 Err(Error::Defunct)
1112 }
1113 })
1114 }
1115
1116 pub fn is_child_selected(&self, child_index: usize) -> Result<bool> {
1117 self.resolve_for_selection(|node| {
1118 node.filtered_children(filter)
1119 .nth(child_index)
1120 .map(|child| child.is_item_like() && child.is_selected() == Some(true))
1121 .ok_or(Error::Defunct)
1122 })
1123 }
1124
1125 pub fn select_all(&self) -> Result<bool> {
1126 Ok(false)
1128 }
1129
1130 pub fn clear_selection(&self) -> Result<bool> {
1131 Ok(false)
1133 }
1134
1135 pub fn deselect_child(&self, child_index: usize) -> Result<bool> {
1136 self.resolve_for_selection_with_context(|node, context| {
1137 if let Some(child) = node.filtered_children(filter).nth(child_index) {
1138 if let Some(false) = child.is_selected() {
1139 Ok(true)
1140 } else if child.is_selectable() && child.is_clickable(&filter) {
1141 context.do_action(ActionRequest {
1142 action: Action::Click,
1143 target: child.id(),
1144 data: None,
1145 });
1146 Ok(true)
1147 } else {
1148 Ok(false)
1149 }
1150 } else {
1151 Err(Error::Defunct)
1152 }
1153 })
1154 }
1155
1156 pub fn character_count(&self) -> Result<i32> {
1157 self.resolve_for_text(|node| {
1158 node.document_range()
1159 .end()
1160 .to_global_usv_index()
1161 .try_into()
1162 .map_err(|_| Error::TooManyCharacters)
1163 })
1164 }
1165
1166 pub fn caret_offset(&self) -> Result<i32> {
1167 self.resolve_for_text(|node| {
1168 node.text_selection_focus().map_or(Ok(-1), |focus| {
1169 focus
1170 .to_global_usv_index()
1171 .try_into()
1172 .map_err(|_| Error::TooManyCharacters)
1173 })
1174 })
1175 }
1176
1177 pub fn string_at_offset(
1178 &self,
1179 offset: i32,
1180 granularity: Granularity,
1181 ) -> Result<(String, i32, i32)> {
1182 self.resolve_for_text(|node| {
1183 let range = text_range_from_offset(&node, offset, granularity)?;
1184 let text = range.text();
1185 let start = range
1186 .start()
1187 .to_global_usv_index()
1188 .try_into()
1189 .map_err(|_| Error::TooManyCharacters)?;
1190 let end = range
1191 .end()
1192 .to_global_usv_index()
1193 .try_into()
1194 .map_err(|_| Error::TooManyCharacters)?;
1195
1196 Ok((text, start, end))
1197 })
1198 }
1199
1200 pub fn text(&self, start_offset: i32, end_offset: i32) -> Result<String> {
1201 self.resolve_for_text(|node| {
1202 let range = text_range_from_offsets(&node, start_offset, end_offset)
1203 .ok_or(Error::IndexOutOfRange)?;
1204 Ok(range.text())
1205 })
1206 }
1207
1208 pub fn set_caret_offset(&self, offset: i32) -> Result<bool> {
1209 self.resolve_for_text_with_context(|node, context| {
1210 let offset = text_position_from_offset(&node, offset).ok_or(Error::IndexOutOfRange)?;
1211 context.do_action(ActionRequest {
1212 action: Action::SetTextSelection,
1213 target: node.id(),
1214 data: Some(ActionData::SetTextSelection(
1215 offset.to_degenerate_range().to_text_selection(),
1216 )),
1217 });
1218 Ok(true)
1219 })
1220 }
1221
1222 pub fn text_attribute_value(&self, _offset: i32, _attribute_name: &str) -> Result<String> {
1223 Err(Error::UnsupportedInterface)
1225 }
1226
1227 pub fn text_attributes(&self, _offset: i32) -> Result<(HashMap<String, String>, i32, i32)> {
1228 Err(Error::UnsupportedInterface)
1230 }
1231
1232 pub fn default_text_attributes(&self) -> Result<HashMap<String, String>> {
1233 Err(Error::UnsupportedInterface)
1235 }
1236
1237 pub fn character_extents(&self, offset: i32, coord_type: CoordType) -> Result<AtspiRect> {
1238 self.resolve_for_text_with_context(|node, context| {
1239 let range = text_range_from_offset(&node, offset, Granularity::Char)?;
1240 if let Some(bounds) = range.bounding_boxes().first() {
1241 let window_bounds = context.read_root_window_bounds();
1242 let new_origin = window_bounds.accesskit_point_to_atspi_point(
1243 bounds.origin(),
1244 Some(node),
1245 coord_type,
1246 );
1247 Ok(bounds.with_origin(new_origin).into())
1248 } else {
1249 Ok(AtspiRect::INVALID)
1250 }
1251 })
1252 }
1253
1254 pub fn offset_at_point(&self, x: i32, y: i32, coord_type: CoordType) -> Result<i32> {
1255 self.resolve_for_text_with_context(|node, context| {
1256 let window_bounds = context.read_root_window_bounds();
1257 let point = window_bounds.atspi_point_to_accesskit_point(
1258 Point::new(x.into(), y.into()),
1259 Some(node),
1260 coord_type,
1261 );
1262 let point = node.transform().inverse() * point;
1263 node.text_position_at_point(point)
1264 .to_global_usv_index()
1265 .try_into()
1266 .map_err(|_| Error::TooManyCharacters)
1267 })
1268 }
1269
1270 pub fn n_selections(&self) -> Result<i32> {
1271 self.resolve_for_text(|node| {
1272 match node.text_selection().filter(|range| !range.is_degenerate()) {
1273 Some(_) => Ok(1),
1274 None => Ok(0),
1275 }
1276 })
1277 }
1278
1279 pub fn selection(&self, selection_num: i32) -> Result<(i32, i32)> {
1280 if selection_num != 0 {
1281 return Ok((-1, -1));
1282 }
1283
1284 self.resolve_for_text(|node| {
1285 node.text_selection()
1286 .filter(|range| !range.is_degenerate())
1287 .map_or(Ok((-1, -1)), |range| {
1288 let start = range
1289 .start()
1290 .to_global_usv_index()
1291 .try_into()
1292 .map_err(|_| Error::TooManyCharacters)?;
1293 let end = range
1294 .end()
1295 .to_global_usv_index()
1296 .try_into()
1297 .map_err(|_| Error::TooManyCharacters)?;
1298
1299 Ok((start, end))
1300 })
1301 })
1302 }
1303
1304 pub fn add_selection(&self, start_offset: i32, end_offset: i32) -> Result<bool> {
1305 self.set_selection(0, start_offset, end_offset)
1307 }
1308
1309 pub fn remove_selection(&self, selection_num: i32) -> Result<bool> {
1310 if selection_num != 0 {
1311 return Ok(false);
1312 }
1313
1314 self.resolve_for_text_with_context(|node, context| {
1315 let selection_end = node
1318 .text_selection_focus()
1319 .unwrap_or_else(|| node.document_range().start());
1320 context.do_action(ActionRequest {
1321 action: Action::SetTextSelection,
1322 target: node.id(),
1323 data: Some(ActionData::SetTextSelection(
1324 selection_end.to_degenerate_range().to_text_selection(),
1325 )),
1326 });
1327 Ok(true)
1328 })
1329 }
1330
1331 pub fn set_selection(
1332 &self,
1333 selection_num: i32,
1334 start_offset: i32,
1335 end_offset: i32,
1336 ) -> Result<bool> {
1337 if selection_num != 0 {
1338 return Ok(false);
1339 }
1340
1341 self.resolve_for_text_with_context(|node, context| {
1342 let range = text_range_from_offsets(&node, start_offset, end_offset)
1343 .ok_or(Error::IndexOutOfRange)?;
1344 context.do_action(ActionRequest {
1345 action: Action::SetTextSelection,
1346 target: node.id(),
1347 data: Some(ActionData::SetTextSelection(range.to_text_selection())),
1348 });
1349 Ok(true)
1350 })
1351 }
1352
1353 pub fn range_extents(
1354 &self,
1355 start_offset: i32,
1356 end_offset: i32,
1357 coord_type: CoordType,
1358 ) -> Result<AtspiRect> {
1359 self.resolve_for_text_with_context(|node, context| {
1360 if let Some(rect) = text_range_bounds_from_offsets(&node, start_offset, end_offset) {
1361 let window_bounds = context.read_root_window_bounds();
1362 let new_origin = window_bounds.accesskit_point_to_atspi_point(
1363 rect.origin(),
1364 Some(node),
1365 coord_type,
1366 );
1367 Ok(rect.with_origin(new_origin).into())
1368 } else {
1369 Ok(AtspiRect::INVALID)
1370 }
1371 })
1372 }
1373
1374 pub fn text_attribute_run(
1375 &self,
1376 _offset: i32,
1377 _include_defaults: bool,
1378 ) -> Result<(HashMap<String, String>, i32, i32)> {
1379 let character_count = self.character_count()?;
1383 Ok((HashMap::new(), 0, character_count))
1384 }
1385
1386 pub fn scroll_substring_to(
1387 &self,
1388 start_offset: i32,
1389 end_offset: i32,
1390 scroll_type: ScrollType,
1391 ) -> Result<bool> {
1392 self.resolve_for_text_with_context(|node, context| {
1393 if let Some(range) = text_range_from_offsets(&node, start_offset, end_offset) {
1394 let position = if matches!(
1395 scroll_type,
1396 ScrollType::BottomRight | ScrollType::BottomEdge | ScrollType::RightEdge
1397 ) {
1398 range.end()
1399 } else {
1400 range.start()
1401 };
1402 context.do_action(ActionRequest {
1403 action: Action::ScrollIntoView,
1404 target: position.inner_node().id(),
1405 data: atspi_scroll_type_to_scroll_hint(scroll_type).map(ActionData::ScrollHint),
1406 });
1407 Ok(true)
1408 } else {
1409 Ok(false)
1410 }
1411 })
1412 }
1413
1414 pub fn scroll_substring_to_point(
1415 &self,
1416 start_offset: i32,
1417 end_offset: i32,
1418 coord_type: CoordType,
1419 x: i32,
1420 y: i32,
1421 ) -> Result<bool> {
1422 self.resolve_for_text_with_context(|node, context| {
1423 let window_bounds = context.read_root_window_bounds();
1424 let target_point = window_bounds.atspi_point_to_accesskit_point(
1425 Point::new(x.into(), y.into()),
1426 Some(node),
1427 coord_type,
1428 );
1429
1430 if let Some(rect) = text_range_bounds_from_offsets(&node, start_offset, end_offset) {
1431 let point = Point::new(target_point.x - rect.x0, target_point.y - rect.y0);
1432 context.do_action(ActionRequest {
1433 action: Action::ScrollToPoint,
1434 target: node.id(),
1435 data: Some(ActionData::ScrollToPoint(point)),
1436 });
1437 return Ok(true);
1438 }
1439 Ok(false)
1440 })
1441 }
1442
1443 pub fn minimum_value(&self) -> Result<f64> {
1444 self.resolve(|node| Ok(node.min_numeric_value().unwrap_or(f64::MIN)))
1445 }
1446
1447 pub fn maximum_value(&self) -> Result<f64> {
1448 self.resolve(|node| Ok(node.max_numeric_value().unwrap_or(f64::MAX)))
1449 }
1450
1451 pub fn minimum_increment(&self) -> Result<f64> {
1452 self.resolve(|node| Ok(node.numeric_value_step().unwrap_or(0.0)))
1453 }
1454
1455 pub fn current_value(&self) -> Result<f64> {
1456 self.resolve(|node| {
1457 let wrapper = NodeWrapper(&node);
1458 Ok(wrapper.current_value().unwrap_or(0.0))
1459 })
1460 }
1461
1462 pub fn set_current_value(&self, value: f64) -> Result<()> {
1463 self.do_action_internal(|_, _| ActionRequest {
1464 action: Action::SetValue,
1465 target: self.id,
1466 data: Some(ActionData::NumericValue(value)),
1467 })
1468 }
1469}
1470
1471impl PartialEq for PlatformNode {
1472 fn eq(&self, other: &Self) -> bool {
1473 self.adapter_id == other.adapter_id && self.id == other.id
1474 }
1475}
1476
1477impl Eq for PlatformNode {}
1478
1479impl Hash for PlatformNode {
1480 fn hash<H: Hasher>(&self, state: &mut H) {
1481 self.adapter_id.hash(state);
1482 self.id.hash(state);
1483 }
1484}
1485
1486#[derive(Clone)]
1487pub struct PlatformRoot {
1488 app_context: Weak<RwLock<AppContext>>,
1489}
1490
1491impl PlatformRoot {
1492 pub fn new(app_context: &Arc<RwLock<AppContext>>) -> Self {
1493 Self {
1494 app_context: Arc::downgrade(app_context),
1495 }
1496 }
1497
1498 fn resolve_app_context<F, T>(&self, f: F) -> Result<T>
1499 where
1500 for<'a> F: FnOnce(RwLockReadGuard<'a, AppContext>) -> Result<T>,
1501 {
1502 let app_context = match self.app_context.upgrade() {
1503 Some(context) => context,
1504 None => return Err(Error::Defunct),
1505 };
1506 let app_context = app_context.read().unwrap();
1507 f(app_context)
1508 }
1509
1510 pub fn name(&self) -> Result<String> {
1511 self.resolve_app_context(|context| Ok(context.name.clone().unwrap_or_default()))
1512 }
1513
1514 pub fn child_count(&self) -> Result<i32> {
1515 self.resolve_app_context(|context| {
1516 i32::try_from(context.adapters.len()).map_err(|_| Error::TooManyChildren)
1517 })
1518 }
1519
1520 pub fn child_at_index(&self, index: usize) -> Result<Option<PlatformNode>> {
1521 self.resolve_app_context(|context| {
1522 let child = context
1523 .adapters
1524 .get(index)
1525 .map(PlatformNode::from_adapter_root);
1526 Ok(child)
1527 })
1528 }
1529
1530 pub fn child_id_at_index(&self, index: usize) -> Result<Option<(usize, NodeId)>> {
1531 self.resolve_app_context(|context| {
1532 let child = context
1533 .adapters
1534 .get(index)
1535 .map(|(adapter_id, context)| (*adapter_id, context.read_tree().state().root_id()));
1536 Ok(child)
1537 })
1538 }
1539
1540 pub fn map_children<T, I>(&self, f: impl Fn(PlatformNode) -> I) -> Result<T>
1541 where
1542 T: FromIterator<I>,
1543 {
1544 self.resolve_app_context(|context| {
1545 let children = context
1546 .adapters
1547 .iter()
1548 .map(PlatformNode::from_adapter_root)
1549 .map(f)
1550 .collect();
1551 Ok(children)
1552 })
1553 }
1554
1555 pub fn map_child_ids<T, I>(&self, f: impl Fn((usize, NodeId)) -> I) -> Result<T>
1556 where
1557 T: FromIterator<I>,
1558 {
1559 self.resolve_app_context(|context| {
1560 let children = context
1561 .adapters
1562 .iter()
1563 .map(|(adapter_id, context)| (*adapter_id, context.read_tree().state().root_id()))
1564 .map(f)
1565 .collect();
1566 Ok(children)
1567 })
1568 }
1569
1570 pub fn toolkit_name(&self) -> Result<String> {
1571 self.resolve_app_context(|context| Ok(context.toolkit_name.clone().unwrap_or_default()))
1572 }
1573
1574 pub fn toolkit_version(&self) -> Result<String> {
1575 self.resolve_app_context(|context| Ok(context.toolkit_version.clone().unwrap_or_default()))
1576 }
1577
1578 pub fn id(&self) -> Result<i32> {
1579 self.resolve_app_context(|context| Ok(context.id.unwrap_or(-1)))
1580 }
1581
1582 pub fn set_id(&mut self, id: i32) -> Result<()> {
1583 let app_context = match self.app_context.upgrade() {
1584 Some(context) => context,
1585 None => return Err(Error::Defunct),
1586 };
1587 let mut app_context = app_context.write().unwrap();
1588 app_context.id = Some(id);
1589 Ok(())
1590 }
1591}
1592
1593impl PartialEq for PlatformRoot {
1594 fn eq(&self, other: &Self) -> bool {
1595 self.app_context.ptr_eq(&other.app_context)
1596 }
1597}
1598
1599impl Hash for PlatformRoot {
1600 fn hash<H: Hasher>(&self, state: &mut H) {
1601 self.app_context.as_ptr().hash(state);
1602 }
1603}
1604
1605#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1606pub enum NodeIdOrRoot {
1607 Node(NodeId),
1608 Root,
1609}