mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-03 18:29:09 +00:00

Some checks are pending
autofix.ci / format_fix (push) Waiting to run
autofix.ci / lint_typecheck (push) Waiting to run
CI / files-changed (push) Waiting to run
CI / build_and_test (--exclude bevy-example, ubuntu-22.04, 1.82) (push) Blocked by required conditions
CI / build_and_test (--exclude ffmpeg --exclude gstreamer-player, --exclude bevy-example, windows-2022, 1.82) (push) Blocked by required conditions
CI / build_and_test (--exclude ffmpeg --exclude gstreamer-player, macos-14, stable) (push) Blocked by required conditions
CI / build_and_test (--exclude ffmpeg --exclude gstreamer-player, windows-2022, beta) (push) Blocked by required conditions
CI / build_and_test (--exclude ffmpeg --exclude gstreamer-player, windows-2022, stable) (push) Blocked by required conditions
CI / build_and_test (ubuntu-22.04, nightly) (push) Blocked by required conditions
CI / node_test (macos-14) (push) Blocked by required conditions
CI / node_test (ubuntu-22.04) (push) Blocked by required conditions
CI / node_test (windows-2022) (push) Blocked by required conditions
CI / python_test (macos-14) (push) Blocked by required conditions
CI / python_test (ubuntu-22.04) (push) Blocked by required conditions
CI / python_test (windows-2022) (push) Blocked by required conditions
CI / cpp_test_driver (macos-13) (push) Blocked by required conditions
CI / cpp_test_driver (ubuntu-22.04) (push) Blocked by required conditions
CI / cpp_test_driver (windows-2022) (push) Blocked by required conditions
CI / cpp_cmake (macos-14, 1.82) (push) Blocked by required conditions
CI / cpp_cmake (ubuntu-22.04, stable) (push) Blocked by required conditions
CI / cpp_cmake (windows-2022, nightly) (push) Blocked by required conditions
CI / cpp_package_test (push) Blocked by required conditions
CI / vsce_build_test (push) Blocked by required conditions
CI / mcu (pico-st7789, thumbv6m-none-eabi) (push) Blocked by required conditions
CI / mcu (pico2-st7789, thumbv8m.main-none-eabihf) (push) Blocked by required conditions
CI / mcu (stm32h735g, thumbv7em-none-eabihf) (push) Blocked by required conditions
CI / mcu-embassy (push) Blocked by required conditions
CI / ffi_32bit_build (push) Blocked by required conditions
CI / docs (push) Blocked by required conditions
CI / wasm (push) Blocked by required conditions
CI / wasm_demo (push) Blocked by required conditions
CI / tree-sitter (push) Blocked by required conditions
CI / updater_test (0.3.0) (push) Blocked by required conditions
CI / fmt_test (push) Blocked by required conditions
CI / esp-idf-quick (push) Blocked by required conditions
CI / android (push) Blocked by required conditions
CI / miri (push) Blocked by required conditions
CI / test-figma-inspector (push) Blocked by required conditions
This fixes the contents of the `n`-th repeater inside the `ComponentContainer` to also show up in the `n`-th repeater *after* the `ComponentContainer`. The ComponentContainer is lowered to a Comonent Container and a dynamic tree node. The tree node is managed manually and I messed up the repeater indices since there were no entries in the repeater array for the embedded components. That mad things hard to keep consistent as components get merged. This chnages that: It lowers a Component Container to Something like this: ```slint ComponentContainer { if false: Emtpy {} } ``` This way the standard mechanismns make sure we have something to put into the repeater list and that unscrews the indices, saving a bit of code along the way. The inserted repeated node is still marked as `is_component_placeholder`, so that we can wire it up as needed.
484 lines
17 KiB
Rust
484 lines
17 KiB
Rust
// 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
|
|
|
|
use super::{EvaluationContext, Expression, ParentCtx};
|
|
use crate::langtype::{NativeClass, Type};
|
|
use smol_str::SmolStr;
|
|
use std::cell::{Cell, RefCell};
|
|
use std::collections::{BTreeMap, HashMap};
|
|
use std::num::NonZeroUsize;
|
|
use std::rc::Rc;
|
|
use typed_index_collections::TiVec;
|
|
|
|
#[derive(
|
|
Debug, Clone, Copy, derive_more::Into, derive_more::From, Hash, PartialEq, Eq, PartialOrd, Ord,
|
|
)]
|
|
pub struct PropertyIdx(usize);
|
|
#[derive(Debug, Clone, Copy, derive_more::Into, derive_more::From, Hash, PartialEq, Eq)]
|
|
pub struct FunctionIdx(usize);
|
|
#[derive(Debug, Clone, Copy, derive_more::Into, derive_more::From)]
|
|
pub struct SubComponentIdx(usize);
|
|
#[derive(Debug, Clone, Copy, derive_more::Into, derive_more::From, Hash, PartialEq, Eq)]
|
|
pub struct GlobalIdx(usize);
|
|
#[derive(Debug, Clone, Copy, derive_more::Into, derive_more::From, Hash, PartialEq, Eq)]
|
|
pub struct SubComponentInstanceIdx(usize);
|
|
#[derive(Debug, Clone, Copy, derive_more::Into, derive_more::From, Hash, PartialEq, Eq)]
|
|
pub struct ItemInstanceIdx(usize);
|
|
#[derive(Debug, Clone, Copy, derive_more::Into, derive_more::From, Hash, PartialEq, Eq)]
|
|
pub struct RepeatedElementIdx(usize);
|
|
|
|
#[derive(Debug, Clone, derive_more::Deref)]
|
|
pub struct MutExpression(RefCell<Expression>);
|
|
|
|
impl From<Expression> for MutExpression {
|
|
fn from(e: Expression) -> Self {
|
|
Self(e.into())
|
|
}
|
|
}
|
|
|
|
impl MutExpression {
|
|
pub fn ty(&self, ctx: &dyn super::TypeResolutionContext) -> Type {
|
|
self.0.borrow().ty(ctx)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum Animation {
|
|
/// The expression is a Struct with the animation fields
|
|
Static(Expression),
|
|
Transition(Expression),
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct BindingExpression {
|
|
pub expression: MutExpression,
|
|
pub animation: Option<Animation>,
|
|
/// When true, we can initialize the property with `set` otherwise, `set_binding` must be used
|
|
pub is_constant: bool,
|
|
/// When true, the expression is a "state binding". Despite the type of the expression being a integer
|
|
/// the property is of type StateInfo and the `set_state_binding` need to be used on the property
|
|
pub is_state_info: bool,
|
|
|
|
/// The amount of time this binding is used
|
|
/// This property is only valid after the [`count_property_use`](super::optim_passes::count_property_use) pass
|
|
pub use_count: Cell<usize>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct GlobalComponent {
|
|
pub name: SmolStr,
|
|
pub properties: TiVec<PropertyIdx, Property>,
|
|
pub functions: TiVec<FunctionIdx, Function>,
|
|
/// One entry per property
|
|
pub init_values: TiVec<PropertyIdx, Option<BindingExpression>>,
|
|
// maps property to its changed callback
|
|
pub change_callbacks: BTreeMap<PropertyIdx, MutExpression>,
|
|
pub const_properties: TiVec<PropertyIdx, bool>,
|
|
pub public_properties: PublicProperties,
|
|
pub private_properties: PrivateProperties,
|
|
/// true if we should expose the global in the generated API
|
|
pub exported: bool,
|
|
/// The extra names under which this component should be accessible
|
|
/// if it is exported several time.
|
|
pub aliases: Vec<SmolStr>,
|
|
/// True when this is a built-in global that does not need to be generated
|
|
pub is_builtin: bool,
|
|
|
|
/// Analysis for each properties
|
|
pub prop_analysis: TiVec<PropertyIdx, crate::object_tree::PropertyAnalysis>,
|
|
}
|
|
|
|
impl GlobalComponent {
|
|
pub fn must_generate(&self) -> bool {
|
|
!self.is_builtin
|
|
&& (self.exported
|
|
|| !self.functions.is_empty()
|
|
|| self.properties.iter().any(|p| p.use_count.get() > 0))
|
|
}
|
|
}
|
|
|
|
/// a Reference to a property, in the context of a SubComponent
|
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
|
pub enum PropertyReference {
|
|
/// A property relative to this SubComponent
|
|
Local { sub_component_path: Vec<SubComponentInstanceIdx>, property_index: PropertyIdx },
|
|
/// A property in a Native item
|
|
InNativeItem {
|
|
sub_component_path: Vec<SubComponentInstanceIdx>,
|
|
item_index: ItemInstanceIdx,
|
|
prop_name: String,
|
|
},
|
|
/// The properties is a property relative to a parent ItemTree (`level` level deep)
|
|
InParent { level: NonZeroUsize, parent_reference: Box<PropertyReference> },
|
|
/// The property within a GlobalComponent
|
|
Global { global_index: GlobalIdx, property_index: PropertyIdx },
|
|
|
|
/// A function in a sub component.
|
|
Function { sub_component_path: Vec<SubComponentInstanceIdx>, function_index: FunctionIdx },
|
|
/// A function in a global.
|
|
GlobalFunction { global_index: GlobalIdx, function_index: FunctionIdx },
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct Property {
|
|
pub name: SmolStr,
|
|
pub ty: Type,
|
|
/// The amount of time this property is used of another property
|
|
/// This property is only valid after the [`count_property_use`](super::optim_passes::count_property_use) pass
|
|
pub use_count: Cell<usize>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Function {
|
|
pub name: SmolStr,
|
|
pub ret_ty: Type,
|
|
pub args: Vec<Type>,
|
|
pub code: Expression,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
/// The property references might be either in the parent context, or in the
|
|
/// repeated's component context
|
|
pub struct ListViewInfo {
|
|
pub viewport_y: PropertyReference,
|
|
pub viewport_height: PropertyReference,
|
|
pub viewport_width: PropertyReference,
|
|
/// The ListView's inner visible height (not counting eventual scrollbar)
|
|
pub listview_height: PropertyReference,
|
|
/// The ListView's inner visible width (not counting eventual scrollbar)
|
|
pub listview_width: PropertyReference,
|
|
|
|
// In the repeated component context
|
|
pub prop_y: PropertyReference,
|
|
// In the repeated component context
|
|
pub prop_height: PropertyReference,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct RepeatedElement {
|
|
pub model: MutExpression,
|
|
/// Within the sub_tree's root component. None for `if`
|
|
pub index_prop: Option<PropertyIdx>,
|
|
/// Within the sub_tree's root component. None for `if`
|
|
pub data_prop: Option<PropertyIdx>,
|
|
pub sub_tree: ItemTree,
|
|
/// The index of the item node in the parent tree
|
|
pub index_in_tree: u32,
|
|
|
|
pub listview: Option<ListViewInfo>,
|
|
|
|
/// Access through this in case of the element being a `is_component_placeholder`
|
|
pub container_item_index: Option<ItemInstanceIdx>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ComponentContainerElement {
|
|
/// The index of the `ComponentContainer` in the enclosing components `item_tree` array
|
|
pub component_container_item_tree_index: u32,
|
|
/// The index of the `ComponentContainer` item in the enclosing components `items` array
|
|
pub component_container_items_index: ItemInstanceIdx,
|
|
/// The index to a dynamic tree node where the component is supposed to be embedded at
|
|
pub component_placeholder_item_tree_index: u32,
|
|
}
|
|
|
|
pub struct Item {
|
|
pub ty: Rc<NativeClass>,
|
|
pub name: SmolStr,
|
|
/// Index in the item tree array
|
|
pub index_in_tree: u32,
|
|
}
|
|
|
|
impl std::fmt::Debug for Item {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("Item")
|
|
.field("ty", &self.ty.class_name)
|
|
.field("name", &self.name)
|
|
.field("index_in_tree", &self.index_in_tree)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct TreeNode {
|
|
pub sub_component_path: Vec<SubComponentInstanceIdx>,
|
|
/// Either an index in the items, or the local dynamic index for repeater or component container
|
|
pub item_index: itertools::Either<ItemInstanceIdx, u32>,
|
|
pub children: Vec<TreeNode>,
|
|
pub is_accessible: bool,
|
|
}
|
|
|
|
impl TreeNode {
|
|
fn children_count(&self) -> usize {
|
|
let mut count = self.children.len();
|
|
for c in &self.children {
|
|
count += c.children_count();
|
|
}
|
|
count
|
|
}
|
|
|
|
/// Visit this, and the children.
|
|
/// `children_offset` must be set to `1` for the root
|
|
pub fn visit_in_array(
|
|
&self,
|
|
visitor: &mut dyn FnMut(
|
|
&TreeNode,
|
|
/*children_offset: */ usize,
|
|
/*parent_index: */ usize,
|
|
),
|
|
) {
|
|
visitor(self, 1, 0);
|
|
visit_in_array_recursive(self, 1, 0, visitor);
|
|
|
|
fn visit_in_array_recursive(
|
|
node: &TreeNode,
|
|
children_offset: usize,
|
|
current_index: usize,
|
|
visitor: &mut dyn FnMut(&TreeNode, usize, usize),
|
|
) {
|
|
let mut offset = children_offset + node.children.len();
|
|
for c in &node.children {
|
|
visitor(c, offset, current_index);
|
|
offset += c.children_count();
|
|
}
|
|
|
|
let mut offset = children_offset + node.children.len();
|
|
for (i, c) in node.children.iter().enumerate() {
|
|
visit_in_array_recursive(c, offset, children_offset + i, visitor);
|
|
offset += c.children_count();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct SubComponent {
|
|
pub name: SmolStr,
|
|
pub properties: TiVec<PropertyIdx, Property>,
|
|
pub functions: TiVec<FunctionIdx, Function>,
|
|
pub items: TiVec<ItemInstanceIdx, Item>,
|
|
pub repeated: TiVec<RepeatedElementIdx, RepeatedElement>,
|
|
pub component_containers: Vec<ComponentContainerElement>,
|
|
pub popup_windows: Vec<PopupWindow>,
|
|
/// The MenuItem trees. The index is stored in a Expression::NumberLiteral in the arguments of BuiltinFunction::ShowPopupMenu and BuiltinFunction::SetupNativeMenuBar
|
|
pub menu_item_trees: Vec<ItemTree>,
|
|
pub timers: Vec<Timer>,
|
|
pub sub_components: TiVec<SubComponentInstanceIdx, SubComponentInstance>,
|
|
/// The initial value or binding for properties.
|
|
/// This is ordered in the order they must be set.
|
|
pub property_init: Vec<(PropertyReference, BindingExpression)>,
|
|
pub change_callbacks: Vec<(PropertyReference, MutExpression)>,
|
|
/// The animation for properties which are animated
|
|
pub animations: HashMap<PropertyReference, Expression>,
|
|
pub two_way_bindings: Vec<(PropertyReference, PropertyReference)>,
|
|
pub const_properties: Vec<PropertyReference>,
|
|
/// Code that is run in the sub component constructor, after property initializations
|
|
pub init_code: Vec<MutExpression>,
|
|
|
|
/// For each node, an expression that returns a `{x: length, y: length, width: length, height: length}`
|
|
pub geometries: Vec<Option<MutExpression>>,
|
|
|
|
pub layout_info_h: MutExpression,
|
|
pub layout_info_v: MutExpression,
|
|
|
|
/// Maps (item_index, property) to an expression
|
|
pub accessible_prop: BTreeMap<(u32, String), MutExpression>,
|
|
|
|
/// Maps item index to a list of encoded element infos of the element (type name, qualified ids).
|
|
pub element_infos: BTreeMap<u32, String>,
|
|
|
|
pub prop_analysis: HashMap<PropertyReference, PropAnalysis>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct PopupWindow {
|
|
pub item_tree: ItemTree,
|
|
pub position: MutExpression,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct PopupMenu {
|
|
pub item_tree: ItemTree,
|
|
pub sub_menu: PropertyReference,
|
|
pub activated: PropertyReference,
|
|
pub close: PropertyReference,
|
|
pub entries: PropertyReference,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Timer {
|
|
pub interval: MutExpression,
|
|
pub running: MutExpression,
|
|
pub triggered: MutExpression,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct PropAnalysis {
|
|
/// Index in SubComponent::property_init for this property
|
|
pub property_init: Option<usize>,
|
|
pub analysis: crate::object_tree::PropertyAnalysis,
|
|
}
|
|
|
|
impl SubComponent {
|
|
/// total count of repeater, including in sub components
|
|
pub fn repeater_count(&self, cu: &CompilationUnit) -> u32 {
|
|
let mut count = (self.repeated.len() + self.component_containers.len()) as u32;
|
|
for x in self.sub_components.iter() {
|
|
count += cu.sub_components[x.ty].repeater_count(cu);
|
|
}
|
|
count
|
|
}
|
|
|
|
/// total count of items, including in sub components
|
|
pub fn child_item_count(&self, cu: &CompilationUnit) -> u32 {
|
|
let mut count = self.items.len() as u32;
|
|
for x in self.sub_components.iter() {
|
|
count += cu.sub_components[x.ty].child_item_count(cu);
|
|
}
|
|
count
|
|
}
|
|
|
|
/// Return if a local property is used. (unused property shouldn't be generated)
|
|
pub fn prop_used(&self, prop: &PropertyReference, cu: &CompilationUnit) -> bool {
|
|
if let PropertyReference::Local { property_index, sub_component_path } = prop {
|
|
let mut sc = self;
|
|
for i in sub_component_path {
|
|
sc = &cu.sub_components[sc.sub_components[*i].ty];
|
|
}
|
|
if sc.properties[*property_index].use_count.get() == 0 {
|
|
return false;
|
|
}
|
|
}
|
|
true
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct SubComponentInstance {
|
|
pub ty: SubComponentIdx,
|
|
pub name: SmolStr,
|
|
pub index_in_tree: u32,
|
|
pub index_of_first_child_in_tree: u32,
|
|
pub repeater_offset: u32,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ItemTree {
|
|
pub root: SubComponentIdx,
|
|
pub tree: TreeNode,
|
|
/// This tree has a parent. e.g: it is a Repeater or a PopupWindow whose property can access
|
|
/// the parent ItemTree.
|
|
/// The String is the type of the parent ItemTree
|
|
pub parent_context: Option<SmolStr>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct PublicComponent {
|
|
pub public_properties: PublicProperties,
|
|
pub private_properties: PrivateProperties,
|
|
pub item_tree: ItemTree,
|
|
pub name: SmolStr,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct CompilationUnit {
|
|
pub public_components: Vec<PublicComponent>,
|
|
/// Storage for all sub-components
|
|
pub sub_components: TiVec<SubComponentIdx, SubComponent>,
|
|
/// The sub-components that are not item-tree root
|
|
pub used_sub_components: Vec<SubComponentIdx>,
|
|
pub globals: TiVec<GlobalIdx, GlobalComponent>,
|
|
pub popup_menu: Option<PopupMenu>,
|
|
pub has_debug_info: bool,
|
|
#[cfg(feature = "bundle-translations")]
|
|
pub translations: Option<crate::translations::Translations>,
|
|
}
|
|
|
|
impl CompilationUnit {
|
|
pub fn for_each_sub_components<'a>(
|
|
&'a self,
|
|
visitor: &mut dyn FnMut(&'a SubComponent, &EvaluationContext<'_>),
|
|
) {
|
|
fn visit_component<'a>(
|
|
root: &'a CompilationUnit,
|
|
c: SubComponentIdx,
|
|
visitor: &mut dyn FnMut(&'a SubComponent, &EvaluationContext<'_>),
|
|
parent: Option<ParentCtx<'_>>,
|
|
) {
|
|
let ctx = EvaluationContext::new_sub_component(root, c, (), parent);
|
|
let sc = &root.sub_components[c];
|
|
visitor(sc, &ctx);
|
|
for (idx, r) in sc.repeated.iter_enumerated() {
|
|
visit_component(
|
|
root,
|
|
r.sub_tree.root,
|
|
visitor,
|
|
Some(ParentCtx::new(&ctx, Some(idx))),
|
|
);
|
|
}
|
|
for popup in &sc.popup_windows {
|
|
visit_component(
|
|
root,
|
|
popup.item_tree.root,
|
|
visitor,
|
|
Some(ParentCtx::new(&ctx, None)),
|
|
);
|
|
}
|
|
for menu_tree in &sc.menu_item_trees {
|
|
visit_component(root, menu_tree.root, visitor, Some(ParentCtx::new(&ctx, None)));
|
|
}
|
|
}
|
|
for c in &self.used_sub_components {
|
|
visit_component(self, *c, visitor, None);
|
|
}
|
|
for p in &self.public_components {
|
|
visit_component(self, p.item_tree.root, visitor, None);
|
|
}
|
|
if let Some(p) = &self.popup_menu {
|
|
visit_component(self, p.item_tree.root, visitor, None);
|
|
}
|
|
}
|
|
|
|
pub fn for_each_expression<'a>(
|
|
&'a self,
|
|
visitor: &mut dyn FnMut(&'a super::MutExpression, &EvaluationContext<'_>),
|
|
) {
|
|
self.for_each_sub_components(&mut |sc, ctx| {
|
|
for e in &sc.init_code {
|
|
visitor(e, ctx);
|
|
}
|
|
for (_, e) in &sc.property_init {
|
|
visitor(&e.expression, ctx);
|
|
}
|
|
visitor(&sc.layout_info_h, ctx);
|
|
visitor(&sc.layout_info_v, ctx);
|
|
for e in sc.accessible_prop.values() {
|
|
visitor(e, ctx);
|
|
}
|
|
for i in sc.geometries.iter().flatten() {
|
|
visitor(i, ctx);
|
|
}
|
|
for (_, e) in sc.change_callbacks.iter() {
|
|
visitor(e, ctx);
|
|
}
|
|
});
|
|
for (idx, g) in self.globals.iter_enumerated() {
|
|
let ctx = EvaluationContext::new_global(self, idx, ());
|
|
for e in g.init_values.iter().filter_map(|x| x.as_ref()) {
|
|
visitor(&e.expression, &ctx)
|
|
}
|
|
for e in g.change_callbacks.values() {
|
|
visitor(e, &ctx)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct PublicProperty {
|
|
pub name: SmolStr,
|
|
pub ty: Type,
|
|
pub prop: PropertyReference,
|
|
pub read_only: bool,
|
|
}
|
|
pub type PublicProperties = Vec<PublicProperty>;
|
|
pub type PrivateProperties = Vec<(SmolStr, Type)>;
|