mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 10:50:00 +00:00
WIP: Search by qualified element id
This commit is contained in:
parent
5bfa6044b5
commit
73b5136f98
12 changed files with 193 additions and 16 deletions
|
@ -58,6 +58,19 @@ impl ElementHandle {
|
|||
result.into_iter().map(ElementHandle)
|
||||
}
|
||||
|
||||
/// This function searches through the entire tree of elements of `component`, looks for
|
||||
/// elements by their name.
|
||||
pub fn find_by_element_id(
|
||||
component: &impl i_slint_core::api::ComponentHandle,
|
||||
id: &str,
|
||||
) -> impl Iterator<Item = Self> {
|
||||
// dirty way to get the ItemTreeRc:
|
||||
let item_tree = WindowInner::from_pub(component.window()).component();
|
||||
let result =
|
||||
search_item(&item_tree, |item| item.element_ids().iter().find(|&i| *i == id).is_some());
|
||||
result.into_iter().map(|x| ElementHandle(x))
|
||||
}
|
||||
|
||||
/// Invokes the default accessible action on the element. For example a `MyButton` element might declare
|
||||
/// an accessible default action that simulates a click, as in the following example:
|
||||
///
|
||||
|
|
|
@ -23,7 +23,7 @@ use crate::object_tree::Document;
|
|||
use itertools::Either;
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
use std::num::NonZeroUsize;
|
||||
use std::str::FromStr;
|
||||
|
||||
|
@ -832,6 +832,35 @@ fn generate_sub_component(
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut sub_components_by_local_tree_index = component
|
||||
.sub_components
|
||||
.iter()
|
||||
.map(|sub| {
|
||||
let local_tree_index: u32 = sub.index_in_tree as _;
|
||||
(local_tree_index, sub.ty.clone())
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut collect_item_element_ids_branch = component
|
||||
.element_ids
|
||||
.iter()
|
||||
.map(|(item_index, ids)| {
|
||||
let mut ids = ids.clone();
|
||||
if let Some(sub_compo) = sub_components_by_local_tree_index.remove(&item_index) {
|
||||
ids.extend_from_slice(&sub_compo.root_item_element_ids());
|
||||
}
|
||||
let ids = ids.join(";");
|
||||
quote!(#item_index => { return #ids.into(); })
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
collect_item_element_ids_branch.extend(sub_components_by_local_tree_index.into_iter().map(
|
||||
|(sub_compo_index, sc)| {
|
||||
let compo_ids = sc.root_item_element_ids().join(";");
|
||||
quote!(#sub_compo_index => { return #compo_ids.into(); })
|
||||
},
|
||||
));
|
||||
|
||||
let mut user_init_code: Vec<TokenStream> = Vec::new();
|
||||
|
||||
let mut sub_component_names: Vec<Ident> = vec![];
|
||||
|
@ -920,6 +949,9 @@ fn generate_sub_component(
|
|||
supported_accessibility_actions_branch.push(quote!(
|
||||
#range_begin..=#range_end => #sub_compo_field.apply_pin(_self).supported_accessibility_actions(index - #range_begin + 1),
|
||||
));
|
||||
collect_item_element_ids_branch.push(quote!(
|
||||
#range_begin..=#range_end => #sub_compo_field.apply_pin(_self).item_element_ids(index - #range_begin + 1),
|
||||
));
|
||||
}
|
||||
|
||||
sub_component_names.push(field_name);
|
||||
|
@ -1147,6 +1179,14 @@ fn generate_sub_component(
|
|||
}
|
||||
}
|
||||
|
||||
fn item_element_ids(self: ::core::pin::Pin<&Self>, index: u32) -> sp::SharedString {
|
||||
#![allow(unused)]
|
||||
let _self = self;
|
||||
match index {
|
||||
#(#collect_item_element_ids_branch)*
|
||||
_ => { ::core::default::Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
#(#declared_functions)*
|
||||
}
|
||||
|
@ -1605,6 +1645,13 @@ fn generate_item_tree(
|
|||
self.supported_accessibility_actions(index)
|
||||
}
|
||||
|
||||
fn item_element_ids(
|
||||
self: ::core::pin::Pin<&Self>,
|
||||
index: u32,
|
||||
) -> sp::SharedString {
|
||||
self.item_element_ids(index)
|
||||
}
|
||||
|
||||
fn window_adapter(
|
||||
self: ::core::pin::Pin<&Self>,
|
||||
do_create: bool,
|
||||
|
|
|
@ -253,6 +253,9 @@ pub struct SubComponent {
|
|||
/// Maps (item_index, property) to an expression
|
||||
pub accessible_prop: BTreeMap<(u32, String), MutExpression>,
|
||||
|
||||
/// Maps item index to a list of qualified ids of the element.
|
||||
pub element_ids: BTreeMap<u32, Vec<String>>,
|
||||
|
||||
pub prop_analysis: HashMap<PropertyReference, PropAnalysis>,
|
||||
}
|
||||
|
||||
|
@ -281,6 +284,17 @@ impl SubComponent {
|
|||
}
|
||||
count
|
||||
}
|
||||
|
||||
pub fn root_item_element_ids(&self) -> Vec<String> {
|
||||
let mut result = Vec::new();
|
||||
if let Some(root_item_ids) = self.element_ids.get(&0) {
|
||||
result.extend_from_slice(root_item_ids);
|
||||
}
|
||||
if let Some(sub_compo) = self.sub_components.iter().find(|c| c.index_in_tree == 0) {
|
||||
result.extend_from_slice(&sub_compo.ty.root_item_element_ids());
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SubComponentInstance {
|
||||
|
|
|
@ -201,6 +201,7 @@ fn lower_sub_component(
|
|||
layout_info_h: super::Expression::BoolLiteral(false).into(),
|
||||
layout_info_v: super::Expression::BoolLiteral(false).into(),
|
||||
accessible_prop: Default::default(),
|
||||
element_ids: Default::default(),
|
||||
prop_analysis: Default::default(),
|
||||
};
|
||||
let mut mapping = LoweredSubComponentMapping::default();
|
||||
|
@ -324,6 +325,10 @@ fn lower_sub_component(
|
|||
for (prop, expr) in &elem.change_callbacks {
|
||||
change_callbacks.push((NamedReference::new(element, prop), expr.borrow().clone()));
|
||||
}
|
||||
let element_infos = elem.element_infos();
|
||||
if !element_infos.is_empty() {
|
||||
sub_component.element_ids.insert(*elem.item_index.get().unwrap(), element_infos);
|
||||
}
|
||||
|
||||
Some(element.clone())
|
||||
});
|
||||
|
|
|
@ -504,7 +504,7 @@ impl LookupType {
|
|||
.borrow()
|
||||
.debug
|
||||
.get(0)
|
||||
.and_then(|x| x.0.source_file())
|
||||
.and_then(|x| x.node.source_file())
|
||||
.map_or(false, |x| x.path().starts_with("builtin:")))
|
||||
.then(|| "Palette".to_string()),
|
||||
})
|
||||
|
|
|
@ -383,7 +383,12 @@ impl Component {
|
|||
let c = Rc::new(c);
|
||||
let weak = Rc::downgrade(&c);
|
||||
recurse_elem(&c.root_element, &(), &mut |e, _| {
|
||||
e.borrow_mut().enclosing_component = weak.clone()
|
||||
e.borrow_mut().enclosing_component = weak.clone();
|
||||
if let Some(ref mut qualified_id) =
|
||||
e.borrow_mut().debug.first_mut().unwrap().qualified_id
|
||||
{
|
||||
*qualified_id = format!("{}::{}", c.id, qualified_id);
|
||||
}
|
||||
});
|
||||
c
|
||||
}
|
||||
|
@ -589,6 +594,16 @@ impl GeometryProps {
|
|||
|
||||
pub type BindingsMap = BTreeMap<String, RefCell<BindingExpression>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ElementDebugInfo {
|
||||
// The id qualified with the enclosing component name. Given `foo := Bar {}` this is `EnclosingComponent::foo`
|
||||
pub qualified_id: Option<String>,
|
||||
pub node: syntax_nodes::Element,
|
||||
// Field to indicate wether this element was a layout that had
|
||||
// been lowered into a rectangle in the lower_layouts pass.
|
||||
pub layout: Option<crate::layout::Layout>,
|
||||
}
|
||||
|
||||
/// An Element is an instantiation of a Component
|
||||
#[derive(Default)]
|
||||
pub struct Element {
|
||||
|
@ -656,21 +671,19 @@ pub struct Element {
|
|||
|
||||
/// Debug information about this element.
|
||||
///
|
||||
/// Contains the AST node if available, as well as wether this element was a layout that had
|
||||
/// been lowered into a rectangle in the lower_layouts pass.
|
||||
/// There can be several in case of inlining or optimization (child merged into their parent).
|
||||
///
|
||||
/// The order in the list is first the parent, and then the removed children.
|
||||
pub debug: Vec<(syntax_nodes::Element, Option<crate::layout::Layout>)>,
|
||||
pub debug: Vec<ElementDebugInfo>,
|
||||
}
|
||||
|
||||
impl Spanned for Element {
|
||||
fn span(&self) -> crate::diagnostics::Span {
|
||||
self.debug.first().map(|n| n.0.span()).unwrap_or_default()
|
||||
self.debug.first().map(|n| n.node.span()).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn source_file(&self) -> Option<&crate::diagnostics::SourceFile> {
|
||||
self.debug.first().map(|n| &n.0.source_file)
|
||||
self.debug.first().map(|n| &n.node.source_file)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -910,10 +923,12 @@ impl Element {
|
|||
} else {
|
||||
tr.empty_type()
|
||||
};
|
||||
// This isn't truly qualified yet, the enclosing component is added at the end of Component::from_node
|
||||
let qualified_id = (!id.is_empty()).then(|| id.clone());
|
||||
let mut r = Element {
|
||||
id,
|
||||
base_type,
|
||||
debug: vec![(node.clone(), None)],
|
||||
debug: vec![ElementDebugInfo { qualified_id, node: node.clone(), layout: None }],
|
||||
is_legacy_syntax,
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -1690,7 +1705,7 @@ impl Element {
|
|||
pub fn original_name(&self) -> String {
|
||||
self.debug
|
||||
.first()
|
||||
.and_then(|n| n.0.child_token(parser::SyntaxKind::Identifier))
|
||||
.and_then(|n| n.node.child_token(parser::SyntaxKind::Identifier))
|
||||
.map(|n| n.to_string())
|
||||
.unwrap_or_else(|| self.id.clone())
|
||||
}
|
||||
|
@ -1748,6 +1763,10 @@ impl Element {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_infos(&self) -> Vec<String> {
|
||||
self.debug.iter().flat_map(|debug_info| debug_info.qualified_id.clone()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply default property values defined in `builtins.slint` to the element.
|
||||
|
@ -2201,7 +2220,7 @@ pub fn visit_all_named_references_in_element(
|
|||
elem.borrow_mut().layout_info_prop = layout_info_prop;
|
||||
let mut debug = std::mem::take(&mut elem.borrow_mut().debug);
|
||||
for d in debug.iter_mut() {
|
||||
d.1.as_mut().map(|l| l.visit_named_references(&mut vis));
|
||||
d.layout.as_mut().map(|l| l.visit_named_references(&mut vis));
|
||||
}
|
||||
elem.borrow_mut().debug = debug;
|
||||
|
||||
|
|
|
@ -254,7 +254,7 @@ fn lower_grid_layout(
|
|||
grid_layout_element.borrow_mut().layout_info_prop =
|
||||
Some((layout_info_prop_h, layout_info_prop_v));
|
||||
for d in grid_layout_element.borrow_mut().debug.iter_mut() {
|
||||
d.1 = Some(Layout::GridLayout(grid.clone()));
|
||||
d.layout = Some(Layout::GridLayout(grid.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -444,7 +444,7 @@ fn lower_box_layout(
|
|||
);
|
||||
layout_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
|
||||
for d in layout_element.borrow_mut().debug.iter_mut() {
|
||||
d.1 = Some(Layout::BoxLayout(layout.clone()));
|
||||
d.layout = Some(Layout::BoxLayout(layout.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -657,7 +657,7 @@ fn lower_dialog_layout(
|
|||
);
|
||||
dialog_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
|
||||
for d in dialog_element.borrow_mut().debug.iter_mut() {
|
||||
d.1 = Some(Layout::GridLayout(grid.clone()));
|
||||
d.layout = Some(Layout::GridLayout(grid.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ use crate::lengths::{LogicalPoint, LogicalRect};
|
|||
use crate::slice::Slice;
|
||||
use crate::window::WindowAdapterRc;
|
||||
use crate::SharedString;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
use core::pin::Pin;
|
||||
use vtable::*;
|
||||
|
||||
|
@ -128,6 +130,10 @@ pub struct ItemTreeVTable {
|
|||
item_index: u32,
|
||||
) -> SupportedAccessibilityAction,
|
||||
|
||||
/// Add the `ElementName::id` entries of the given item
|
||||
pub item_element_ids:
|
||||
extern "C" fn(core::pin::Pin<VRef<ItemTreeVTable>>, item_index: u32) -> SharedString,
|
||||
|
||||
/// Returns a Window, creating a fresh one if `do_create` is true.
|
||||
pub window_adapter: extern "C" fn(
|
||||
core::pin::Pin<VRef<ItemTreeVTable>>,
|
||||
|
@ -360,6 +366,17 @@ impl ItemRc {
|
|||
comp_ref_pin.as_ref().supported_accessibility_actions(self.index)
|
||||
}
|
||||
|
||||
pub fn element_ids(&self) -> Vec<String> {
|
||||
let comp_ref_pin = vtable::VRc::borrow_pin(&self.item_tree);
|
||||
comp_ref_pin
|
||||
.as_ref()
|
||||
.item_element_ids(self.index)
|
||||
.as_str()
|
||||
.split(";")
|
||||
.map(ToString::to_string)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn geometry(&self) -> LogicalRect {
|
||||
let comp_ref_pin = vtable::VRc::borrow_pin(&self.item_tree);
|
||||
comp_ref_pin.as_ref().item_geometry(self.index)
|
||||
|
@ -1152,6 +1169,10 @@ mod tests {
|
|||
false
|
||||
}
|
||||
|
||||
fn item_element_ids(self: Pin<&Self>, _: u32) -> SharedString {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn window_adapter(
|
||||
self: Pin<&Self>,
|
||||
_do_create: bool,
|
||||
|
|
|
@ -255,6 +255,10 @@ impl ItemTree for ErasedItemTreeBox {
|
|||
) -> SupportedAccessibilityAction {
|
||||
self.borrow().as_ref().supported_accessibility_actions(index)
|
||||
}
|
||||
|
||||
fn item_element_ids(self: core::pin::Pin<&Self>, index: u32) -> SharedString {
|
||||
self.borrow().as_ref().item_element_ids(index)
|
||||
}
|
||||
}
|
||||
|
||||
i_slint_core::ItemTreeVTable_static!(static COMPONENT_BOX_VT for ErasedItemTreeBox);
|
||||
|
@ -1193,6 +1197,7 @@ pub(crate) fn generate_item_tree<'id>(
|
|||
accessible_string_property,
|
||||
accessibility_action,
|
||||
supported_accessibility_actions,
|
||||
item_element_ids,
|
||||
window_adapter,
|
||||
drop_in_place,
|
||||
dealloc,
|
||||
|
@ -2009,6 +2014,17 @@ extern "C" fn supported_accessibility_actions(
|
|||
val
|
||||
}
|
||||
|
||||
extern "C" fn item_element_ids(component: ItemTreeRefPin, item_index: u32) -> SharedString {
|
||||
generativity::make_guard!(guard);
|
||||
let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) };
|
||||
let val = instance_ref.description.original_elements[item_index as usize]
|
||||
.borrow()
|
||||
.element_infos()
|
||||
.join(";")
|
||||
.into();
|
||||
val
|
||||
}
|
||||
|
||||
extern "C" fn window_adapter(
|
||||
component: ItemTreeRefPin,
|
||||
do_create: bool,
|
||||
|
|
42
tests/cases/testing/find_by_element_id.slint
Normal file
42
tests/cases/testing/find_by_element_id.slint
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||
|
||||
component ButtonBase inherits Text { }
|
||||
|
||||
component Button inherits ButtonBase {
|
||||
accessible-role: button;
|
||||
accessible-label: "optimized";
|
||||
extra-label := Text { }
|
||||
}
|
||||
|
||||
export component TestCase {
|
||||
first := Button { }
|
||||
|
||||
second := Button {
|
||||
accessible-label: "plain";
|
||||
}
|
||||
|
||||
// third
|
||||
Button {
|
||||
accessible-label: "third";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
```rust
|
||||
let instance = TestCase::new().unwrap();
|
||||
let mut button_search = slint_testing::ElementHandle::find_by_element_id(&instance, "Button::root");
|
||||
let mut button = button_search.next().unwrap();
|
||||
assert!(button.is_valid());
|
||||
assert_eq!(button.accessible_label().unwrap(), "optimized");
|
||||
button = button_search.next().unwrap();
|
||||
assert!(button.is_valid());
|
||||
assert_eq!(button.accessible_label().unwrap(), "plain");
|
||||
button = button_search.next().unwrap();
|
||||
assert!(button.is_valid());
|
||||
assert_eq!(button.accessible_label().unwrap(), "third");
|
||||
assert!(button_search.next().is_none());
|
||||
|
||||
assert_eq!(slint_testing::ElementHandle::find_by_element_id(&instance, "TestCase::second").count(), 1);
|
||||
```
|
||||
*/
|
|
@ -149,7 +149,7 @@ impl ElementRcNode {
|
|||
/// Run with the SyntaxNode incl. any id, condition, etc.
|
||||
pub fn with_decorated_node<R>(&self, func: impl Fn(SyntaxNode) -> R) -> R {
|
||||
let elem = self.element.borrow();
|
||||
func(find_element_with_decoration(&elem.debug.get(self.debug_index).unwrap().0))
|
||||
func(find_element_with_decoration(&elem.debug.get(self.debug_index).unwrap().node))
|
||||
}
|
||||
|
||||
pub fn path_and_offset(&self) -> (PathBuf, u32) {
|
||||
|
|
|
@ -222,7 +222,7 @@ fn visit_node(
|
|||
.borrow()
|
||||
.children
|
||||
.iter()
|
||||
.find(|c| c.borrow().debug.first().map_or(false, |n| n.0.node == node.node))
|
||||
.find(|c| c.borrow().debug.first().map_or(false, |n| n.node.node == node.node))
|
||||
.cloned()
|
||||
} else if let Some(parent_co) = &state.current_component {
|
||||
if node.parent().map_or(false, |n| n.kind() == SyntaxKind::Component) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue