Skip to content

Commit 1d3b189

Browse files
feat: Support tree views on Windows (#698)
1 parent d37b2f9 commit 1d3b189

File tree

3 files changed

+77
-3
lines changed

3 files changed

+77
-3
lines changed

consumer/src/node.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -609,8 +609,14 @@ impl<'a> Node<'a> {
609609
&self,
610610
filter: &impl Fn(&Node) -> FilterResult,
611611
) -> Option<usize> {
612-
self.selection_container(filter)
613-
.and_then(|c| c.size_of_set())
612+
let mut parent = self.filtered_parent(filter);
613+
while let Some(node) = parent {
614+
if let Some(size_of_set) = node.size_of_set() {
615+
return Some(size_of_set);
616+
}
617+
parent = node.filtered_parent(filter);
618+
}
619+
None
614620
}
615621

616622
pub fn size_of_set(&self) -> Option<usize> {
@@ -632,7 +638,12 @@ impl<'a> Node<'a> {
632638
}
633639

634640
pub fn supports_expand_collapse(&self) -> bool {
635-
self.data().is_expanded().is_some()
641+
self.has_popup().is_some()
642+
|| self.data().is_expanded().is_some()
643+
|| matches!(
644+
self.role(),
645+
Role::ComboBox | Role::EditableComboBox | Role::DisclosureTriangle | Role::TreeItem
646+
)
636647
}
637648

638649
pub fn is_invocable(&self, parent_filter: &impl Fn(&Node) -> FilterResult) -> bool {

platforms/windows/src/node.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,13 @@ impl NodeWrapper<'_> {
675675
.and_then(|s| s.try_into().ok())
676676
}
677677

678+
fn level(&self) -> Option<i32> {
679+
self.0
680+
.level()
681+
.and_then(|level| level.checked_add(1))
682+
.and_then(|level| level.try_into().ok())
683+
}
684+
678685
fn is_selection_pattern_supported(&self) -> bool {
679686
self.0.is_container_with_selectable_children()
680687
}
@@ -687,6 +694,19 @@ impl NodeWrapper<'_> {
687694
self.0.supports_text_ranges()
688695
}
689696

697+
fn is_expand_collapse_pattern_supported(&self) -> bool {
698+
self.0.supports_expand_collapse()
699+
}
700+
701+
fn expand_collapse_state(&self) -> ExpandCollapseState {
702+
match self.0.data().is_expanded() {
703+
Some(true) => ExpandCollapseState_Expanded,
704+
Some(false) => ExpandCollapseState_Collapsed,
705+
// TODO: Handle the menu button case. (#27)
706+
None => ExpandCollapseState_LeafNode,
707+
}
708+
}
709+
690710
fn is_password(&self) -> bool {
691711
self.0.role() == Role::PasswordInput
692712
}
@@ -763,6 +783,7 @@ impl NodeWrapper<'_> {
763783
ISelectionItemProvider,
764784
ISelectionProvider,
765785
ITextProvider,
786+
IExpandCollapseProvider,
766787
IWindowProvider
767788
)]
768789
pub(crate) struct PlatformNode {
@@ -921,6 +942,30 @@ impl PlatformNode {
921942
self.do_action(|| (Action::Click, None))
922943
}
923944

945+
fn set_expanded(&self, expanded: bool) -> Result<()> {
946+
self.do_complex_action(|node, target_node, target_tree| {
947+
if node.is_disabled() {
948+
return Err(element_not_enabled());
949+
}
950+
let Some(current) = node.data().is_expanded() else {
951+
return Err(invalid_operation());
952+
};
953+
if current == expanded {
954+
return Err(invalid_operation());
955+
}
956+
Ok(Some(ActionRequest {
957+
action: if expanded {
958+
Action::Expand
959+
} else {
960+
Action::Collapse
961+
},
962+
target_tree,
963+
target_node,
964+
data: None,
965+
}))
966+
})
967+
}
968+
924969
fn set_selected(&self, selected: bool) -> Result<()> {
925970
self.do_complex_action(|node, target_node, target_tree| {
926971
if node.is_disabled() {
@@ -1251,6 +1296,7 @@ properties! {
12511296
(UIA_OrientationPropertyId, orientation),
12521297
(UIA_IsRequiredForFormPropertyId, is_required),
12531298
(UIA_IsPasswordPropertyId, is_password),
1299+
(UIA_LevelPropertyId, level),
12541300
(UIA_PositionInSetPropertyId, position_in_set),
12551301
(UIA_SizeOfSetPropertyId, size_of_set),
12561302
(UIA_AriaPropertiesPropertyId, aria_properties),
@@ -1407,6 +1453,17 @@ patterns! {
14071453
})
14081454
}
14091455
)),
1456+
(UIA_ExpandCollapsePatternId, IExpandCollapseProvider, IExpandCollapseProvider_Impl, is_expand_collapse_pattern_supported, (
1457+
(UIA_ExpandCollapseExpandCollapseStatePropertyId, ExpandCollapseState, expand_collapse_state, ExpandCollapseState)
1458+
), (
1459+
fn Expand(&self) -> Result<()> {
1460+
self.set_expanded(true)
1461+
},
1462+
1463+
fn Collapse(&self) -> Result<()> {
1464+
self.set_expanded(false)
1465+
}
1466+
)),
14101467
(UIA_WindowPatternId, IWindowProvider, IWindowProvider_Impl, is_window_pattern_supported, (
14111468
(UIA_WindowIsModalPropertyId, IsModal, is_modal, BOOL)
14121469
), (

platforms/windows/src/util.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ impl From<ToggleState> for Variant {
122122
}
123123
}
124124

125+
impl From<ExpandCollapseState> for Variant {
126+
fn from(value: ExpandCollapseState) -> Self {
127+
Self(value.0.into())
128+
}
129+
}
130+
125131
impl From<LiveSetting> for Variant {
126132
fn from(value: LiveSetting) -> Self {
127133
Self(value.0.into())

0 commit comments

Comments
 (0)