From 0da516922895ac752da882fa7a76b6a29d42ec7d Mon Sep 17 00:00:00 2001 From: ImFeH2 Date: Tue, 2 Jun 2026 15:42:07 +0800 Subject: [PATCH] fix: support global property animations in llr --- internal/compiler/llr/expression.rs | 5 +- internal/compiler/llr/item_tree.rs | 2 + internal/compiler/llr/lower_to_item_tree.rs | 12 ++++- .../llr/optim_passes/remove_unused.rs | 11 ++++ internal/compiler/llr/pretty_print.rs | 14 ++++++ ...issue_9961_animation_alias_to_global.slint | 50 +++++++++++++++++++ 6 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 tests/cases/issues/issue_9961_animation_alias_to_global.slint diff --git a/internal/compiler/llr/expression.rs b/internal/compiler/llr/expression.rs index 6e3f4293388..2420138454a 100644 --- a/internal/compiler/llr/expression.rs +++ b/internal/compiler/llr/expression.rs @@ -717,14 +717,15 @@ impl<'a, T> EvaluationContext<'a, T> { r: &'_ LocalMemberIndex, map: ContextMap, ) -> PropertyInfoResult<'a> { - let binding = g.init_values.get(r).map(|b| (b, map)); + let binding = g.init_values.get(r).map(|b| (b, map.clone())); + let animation = g.animations.get(r).map(|a| (a, map)); match r { LocalMemberIndex::Property(index) => { let property_decl = &g.properties[*index]; PropertyInfoResult { analysis: Some(&g.prop_analysis[*index]), binding, - animation: None, + animation, ty: property_decl.ty.clone(), use_count: Some(&property_decl.use_count), } diff --git a/internal/compiler/llr/item_tree.rs b/internal/compiler/llr/item_tree.rs index f52590c6725..bdff8b6b329 100644 --- a/internal/compiler/llr/item_tree.rs +++ b/internal/compiler/llr/item_tree.rs @@ -127,6 +127,8 @@ pub struct GlobalComponent { pub functions: TiVec, /// One entry per property pub init_values: BTreeMap, + /// The animation for properties which are animated + pub animations: BTreeMap, // maps property to its changed callback pub change_callbacks: BTreeMap, pub const_properties: TiVec, diff --git a/internal/compiler/llr/lower_to_item_tree.rs b/internal/compiler/llr/lower_to_item_tree.rs index a2144e041fa..4be562b6843 100644 --- a/internal/compiler/llr/lower_to_item_tree.rs +++ b/internal/compiler/llr/lower_to_item_tree.rs @@ -985,6 +985,7 @@ fn lower_global( GlobalComponent { name: global.root_element.borrow().id.clone(), init_values: BTreeMap::new(), + animations: BTreeMap::new(), properties, callbacks, functions, @@ -1012,9 +1013,13 @@ fn lower_global_expressions( for (prop, binding) in &global.root_element.borrow().bindings { assert!(binding.borrow().two_way_bindings.is_empty()); - assert!(binding.borrow().animation.is_none()); let expression = super::lower_expression::lower_expression(&binding.borrow().expression, &mut ctx); + let animation = binding + .borrow() + .animation + .as_ref() + .map(|a| super::lower_expression::lower_animation(a, &mut ctx)); let nr = NamedReference::new(&global.root_element, prop.clone()); let member_index = match &ctx.state.global_properties[&nr] { @@ -1027,12 +1032,15 @@ fn lower_global_expressions( MemberReference::Global { member, .. } => member.clone(), _ => unreachable!(), }; + if let Some(Animation::Static(animation)) = animation.as_ref() { + lowered.animations.insert(member_index.clone(), animation.clone()); + } let is_constant = binding.borrow().analysis.as_ref().is_some_and(|a| a.is_const); lowered.init_values.insert( member_index, BindingExpression { expression: expression.into(), - animation: None, + animation, is_constant, is_state_info: false, use_count: 0.into(), diff --git a/internal/compiler/llr/optim_passes/remove_unused.rs b/internal/compiler/llr/optim_passes/remove_unused.rs index 86c6be3099a..0c949074d2a 100644 --- a/internal/compiler/llr/optim_passes/remove_unused.rs +++ b/internal/compiler/llr/optim_passes/remove_unused.rs @@ -83,6 +83,7 @@ pub fn remove_unused(root: &mut CompilationUnit) { } for (idx, g) in root.globals.iter_mut_enumerated() { g.init_values.retain(|x, _| mappings.glob_mappings[idx].keep(x)); + g.animations.retain(|x, _| mappings.glob_mappings[idx].keep(x)); } impl visitor::Visitor for &RemoveUnusedMappings { @@ -423,6 +424,7 @@ mod visitor { callbacks: _, functions, init_values, + animations, change_callbacks, const_properties: _, public_properties, @@ -450,6 +452,15 @@ mod visitor { }) .collect(); + *animations = std::mem::take(animations) + .into_iter() + .map(|(mut k, mut v)| { + visit_member_index(&mut k, &scope, state, visitor); + visit_expression(&mut v, &scope, state, visitor); + (k, v) + }) + .collect(); + *change_callbacks = std::mem::take(change_callbacks) .into_iter() .map(|(mut k, mut v)| { diff --git a/internal/compiler/llr/pretty_print.rs b/internal/compiler/llr/pretty_print.rs index fc9cd037913..38236f19cc7 100644 --- a/internal/compiler/llr/pretty_print.rs +++ b/internal/compiler/llr/pretty_print.rs @@ -187,6 +187,20 @@ impl PrettyPrinter<'_> { _ => unreachable!(), } } + for (p, animation) in &global.animations { + self.indent()?; + match p { + LocalMemberIndex::Property(p) => { + writeln!( + self.writer, + "animate {} {{ {} }}", + global.properties[*p].name, + DisplayExpression(animation, &ctx), + )?; + } + _ => unreachable!(), + } + } for (p, e) in &global.change_callbacks { self.indent()?; diff --git a/tests/cases/issues/issue_9961_animation_alias_to_global.slint b/tests/cases/issues/issue_9961_animation_alias_to_global.slint new file mode 100644 index 00000000000..ec92108f81a --- /dev/null +++ b/tests/cases/issues/issue_9961_animation_alias_to_global.slint @@ -0,0 +1,50 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +export global GlobalInfo { + in-out property animated: 8; +} + +export component TestCase inherits Window { + property animated <=> GlobalInfo.animated; + animate animated { duration: 300ms; } +} + +/* + +```rust +let instance = TestCase::new().unwrap(); +let global = instance.global::>(); +assert_eq!(global.get_animated(), 8); +global.set_animated(108); +assert_eq!(global.get_animated(), 8); +slint_testing::mock_elapsed_time(150); +assert_eq!(global.get_animated(), 58); +slint_testing::mock_elapsed_time(150); +assert_eq!(global.get_animated(), 108); +``` + +```cpp +auto handle = TestCase::create(); +const TestCase &instance = *handle; +assert_eq(instance.global().get_animated(), 8); +instance.global().set_animated(108); +assert_eq(instance.global().get_animated(), 8); +slint_testing::mock_elapsed_time(150); +assert_eq(instance.global().get_animated(), 58); +slint_testing::mock_elapsed_time(150); +assert_eq(instance.global().get_animated(), 108); +``` + +```js +var instance = new slint.TestCase(); +assert.equal(instance.GlobalInfo.animated, 8); +instance.GlobalInfo.animated = 108; +assert.equal(instance.GlobalInfo.animated, 8); +slintlib.private_api.mock_elapsed_time(150); +assert.equal(instance.GlobalInfo.animated, 58); +slintlib.private_api.mock_elapsed_time(150); +assert.equal(instance.GlobalInfo.animated, 108); +``` + +*/