mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-30 13:51:13 +00:00
parent
2643a327e8
commit
a9f526491a
25 changed files with 599 additions and 14 deletions
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <slint_timer_internal.h>
|
#include <slint_timer_internal.h>
|
||||||
|
|
||||||
namespace slint {
|
namespace slint {
|
||||||
|
@ -59,6 +60,16 @@ struct Timer
|
||||||
void restart() { cbindgen_private::slint_timer_restart(id); }
|
void restart() { cbindgen_private::slint_timer_restart(id); }
|
||||||
/// Returns true if the timer is running; false otherwise.
|
/// Returns true if the timer is running; false otherwise.
|
||||||
bool running() const { return cbindgen_private::slint_timer_running(id); }
|
bool running() const { return cbindgen_private::slint_timer_running(id); }
|
||||||
|
/// Returns the interval of the timer.
|
||||||
|
/// Returns `nullopt` if the timer is not running.
|
||||||
|
std::optional<std::chrono::milliseconds> interval() const
|
||||||
|
{
|
||||||
|
int64_t val = cbindgen_private::slint_timer_interval(id);
|
||||||
|
if (val < 0) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return std::chrono::milliseconds(val);
|
||||||
|
}
|
||||||
|
|
||||||
/// Call the callback after the given duration.
|
/// Call the callback after the given duration.
|
||||||
template<std::invocable F>
|
template<std::invocable F>
|
||||||
|
|
|
@ -207,6 +207,7 @@ pub mod re_exports {
|
||||||
set_state_binding, ChangeTracker, Property, PropertyTracker, StateInfo,
|
set_state_binding, ChangeTracker, Property, PropertyTracker, StateInfo,
|
||||||
};
|
};
|
||||||
pub use i_slint_core::slice::Slice;
|
pub use i_slint_core::slice::Slice;
|
||||||
|
pub use i_slint_core::timers::{Timer, TimerMode};
|
||||||
pub use i_slint_core::window::{
|
pub use i_slint_core::window::{
|
||||||
InputMethodRequest, WindowAdapter, WindowAdapterRc, WindowInner,
|
InputMethodRequest, WindowAdapter, WindowAdapterRc, WindowInner,
|
||||||
};
|
};
|
||||||
|
|
|
@ -778,6 +778,57 @@ export component Example inherits Window {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `Timer`
|
||||||
|
<!-- FIXME: Timer is not really an element so it doesn't really belong in the `Builtin Elements` section. -->
|
||||||
|
|
||||||
|
Timer is not an actual element visible in the tree, therefore it doesn't have the common properties such as `x`, `y`, `width`, `height`, etc.
|
||||||
|
It doesn't take room in a layout and cannot have any children or be inherited from.
|
||||||
|
|
||||||
|
The Timer pseudo-element is used to schedule a callback at a given interval.
|
||||||
|
The timer is only running when the `running` property is set to `true`. To stop or start the timer, simply set that property to `true` or `false`.
|
||||||
|
It can be also set to a binding.
|
||||||
|
When already running, the timer will be restarted if the `interval` property is changed.
|
||||||
|
|
||||||
|
Note that the default value for `running` is `true`, so if you don't specify it, it will be running.
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
- **`interval`** (_in_ _duration_): The interval between timer ticks. (default value: `0ms`)
|
||||||
|
- **`running`** (_in_ _bool_): `true` if the timer is running. (default value: `true`)
|
||||||
|
|
||||||
|
### Callbacks
|
||||||
|
|
||||||
|
- **`triggered()`**: Invoked every time the timer ticks (every `interval`)
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
This example shows a timer that counts down from 10 to 0 every second:
|
||||||
|
|
||||||
|
```slint
|
||||||
|
import { Button } from "std-widgets.slint";
|
||||||
|
export component Example inherits Window {
|
||||||
|
property <int> value: 10;
|
||||||
|
timer := Timer {
|
||||||
|
interval: 1s;
|
||||||
|
running: true;
|
||||||
|
triggered() => {
|
||||||
|
value -= 1;
|
||||||
|
if (value == 0) {
|
||||||
|
self.running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HorizontalLayout {
|
||||||
|
Text { text: value; }
|
||||||
|
Button {
|
||||||
|
text: "Reset";
|
||||||
|
clicked() => { value = 10; timer.running = true; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## `TouchArea`
|
## `TouchArea`
|
||||||
|
|
||||||
Use `TouchArea` to control what happens when the region it covers is touched or interacted with
|
Use `TouchArea` to control what happens when the region it covers is touched or interacted with
|
||||||
|
|
|
@ -325,7 +325,6 @@ component Close {
|
||||||
//-rust_type_constructor:slint::re_exports::PathElement::Close
|
//-rust_type_constructor:slint::re_exports::PathElement::Close
|
||||||
//-cpp_type:slint::private_api::PathClose
|
//-cpp_type:slint::private_api::PathClose
|
||||||
//-is_non_item_type
|
//-is_non_item_type
|
||||||
//-is_non_item_type
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export component Path {
|
export component Path {
|
||||||
|
@ -379,6 +378,15 @@ export component PopupWindow {
|
||||||
//show() is hardcoded in typeregister.rs
|
//show() is hardcoded in typeregister.rs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also not a real Item. Actually not an element at all
|
||||||
|
export component Timer {
|
||||||
|
in property <duration> interval;
|
||||||
|
callback triggered;
|
||||||
|
in property <bool> running: true;
|
||||||
|
//-is_non_item_type
|
||||||
|
//-disallow_global_types_as_child_elements
|
||||||
|
}
|
||||||
|
|
||||||
export component Dialog inherits WindowItem {}
|
export component Dialog inherits WindowItem {}
|
||||||
|
|
||||||
component PropertyAnimation {
|
component PropertyAnimation {
|
||||||
|
|
|
@ -75,6 +75,7 @@ pub enum BuiltinFunction {
|
||||||
RegisterCustomFontByMemory,
|
RegisterCustomFontByMemory,
|
||||||
RegisterBitmapFont,
|
RegisterBitmapFont,
|
||||||
Translate,
|
Translate,
|
||||||
|
UpdateTimers,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -307,6 +308,9 @@ impl BuiltinFunction {
|
||||||
BuiltinFunction::Use24HourFormat => {
|
BuiltinFunction::Use24HourFormat => {
|
||||||
Type::Function { return_type: Box::new(Type::Bool), args: vec![] }
|
Type::Function { return_type: Box::new(Type::Bool), args: vec![] }
|
||||||
}
|
}
|
||||||
|
BuiltinFunction::UpdateTimers => {
|
||||||
|
Type::Function { return_type: Box::new(Type::Void), args: vec![] }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,6 +375,7 @@ impl BuiltinFunction {
|
||||||
| BuiltinFunction::RegisterBitmapFont => false,
|
| BuiltinFunction::RegisterBitmapFont => false,
|
||||||
BuiltinFunction::Translate => false,
|
BuiltinFunction::Translate => false,
|
||||||
BuiltinFunction::Use24HourFormat => false,
|
BuiltinFunction::Use24HourFormat => false,
|
||||||
|
BuiltinFunction::UpdateTimers => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,6 +433,7 @@ impl BuiltinFunction {
|
||||||
| BuiltinFunction::RegisterBitmapFont => false,
|
| BuiltinFunction::RegisterBitmapFont => false,
|
||||||
BuiltinFunction::Translate => true,
|
BuiltinFunction::Translate => true,
|
||||||
BuiltinFunction::Use24HourFormat => true,
|
BuiltinFunction::Use24HourFormat => true,
|
||||||
|
BuiltinFunction::UpdateTimers => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1918,6 +1918,38 @@ fn generate_sub_component(
|
||||||
format!("self->change_tracker{idx}.init(self, [](auto self) {{ return {prop}; }}, []([[maybe_unused]] auto self, auto) {{ {code}; }});")
|
format!("self->change_tracker{idx}.init(self, [](auto self) {{ return {prop}; }}, []([[maybe_unused]] auto self, auto) {{ {code}; }});")
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if !component.timers.is_empty() {
|
||||||
|
let mut update_timers = vec!["auto self = this;".into()];
|
||||||
|
for (i, tmr) in component.timers.iter().enumerate() {
|
||||||
|
user_init.push(format!("self->update_timers();"));
|
||||||
|
let name = format!("timer{}", i);
|
||||||
|
let running = compile_expression(&tmr.running.borrow(), &ctx);
|
||||||
|
let interval = compile_expression(&tmr.interval.borrow(), &ctx);
|
||||||
|
let callback = compile_expression(&tmr.triggered.borrow(), &ctx);
|
||||||
|
update_timers.push(format!("if ({running}) {{"));
|
||||||
|
update_timers
|
||||||
|
.push(format!(" auto interval = std::chrono::milliseconds({interval});"));
|
||||||
|
update_timers.push(format!(
|
||||||
|
" if (!self->{name}.running() || *self->{name}.interval() != interval)"
|
||||||
|
));
|
||||||
|
update_timers.push(format!(" self->{name}.start(slint::TimerMode::Repeated, interval, [self] {{ {callback}; }});"));
|
||||||
|
update_timers.push(format!("}} else {{ self->{name}.stop(); }}").into());
|
||||||
|
target_struct.members.push((
|
||||||
|
field_access,
|
||||||
|
Declaration::Var(Var { ty: "slint::Timer".into(), name, ..Default::default() }),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
target_struct.members.push((
|
||||||
|
field_access,
|
||||||
|
Declaration::Function(Function {
|
||||||
|
name: "update_timers".to_owned(),
|
||||||
|
signature: "() -> void".into(),
|
||||||
|
statements: Some(update_timers),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
target_struct
|
target_struct
|
||||||
.members
|
.members
|
||||||
.extend(generate_functions(&component.functions, &ctx).map(|x| (Access::Public, x)));
|
.extend(generate_functions(&component.functions, &ctx).map(|x| (Access::Public, x)));
|
||||||
|
@ -3445,6 +3477,9 @@ fn compile_builtin_function_call(
|
||||||
BuiltinFunction::Translate => {
|
BuiltinFunction::Translate => {
|
||||||
format!("slint::private_api::translate({})", a.join(","))
|
format!("slint::private_api::translate({})", a.join(","))
|
||||||
}
|
}
|
||||||
|
BuiltinFunction::UpdateTimers => {
|
||||||
|
"self->update_timers()".into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1064,6 +1064,41 @@ fn generate_sub_component(
|
||||||
quote!(usize::MAX)
|
quote!(usize::MAX)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let timer_names =
|
||||||
|
component.timers.iter().enumerate().map(|(idx, _)| format_ident!("timer{idx}"));
|
||||||
|
let update_timers = (!component.timers.is_empty()).then(|| {
|
||||||
|
let updt = component.timers.iter().enumerate().map(|(idx, tmr)| {
|
||||||
|
let ident = format_ident!("timer{idx}");
|
||||||
|
let interval = compile_expression(&tmr.interval.borrow(), &ctx);
|
||||||
|
let running = compile_expression(&tmr.running.borrow(), &ctx);
|
||||||
|
let callback = compile_expression(&tmr.triggered.borrow(), &ctx);
|
||||||
|
quote!(
|
||||||
|
if #running {
|
||||||
|
let interval = core::time::Duration::from_millis(#interval as u64);
|
||||||
|
let old_interval = self.#ident.interval();
|
||||||
|
if old_interval != Some(interval) || !self.#ident.running() {
|
||||||
|
let self_weak = self.self_weak.get().unwrap().clone();
|
||||||
|
self.#ident.start(sp::TimerMode::Repeated, interval, move || {
|
||||||
|
if let Some(self_rc) = self_weak.upgrade() {
|
||||||
|
let _self = self_rc.as_pin_ref();
|
||||||
|
#callback
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.#ident.stop();
|
||||||
|
}
|
||||||
|
)
|
||||||
|
});
|
||||||
|
user_init_code.push(quote!(_self.update_timers();));
|
||||||
|
quote!(
|
||||||
|
fn update_timers(self: ::core::pin::Pin<&Self>) {
|
||||||
|
let _self = self;
|
||||||
|
#(#updt)*
|
||||||
|
}
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let pin_macro = if pinned_drop { quote!(#[pin_drop]) } else { quote!(#[pin]) };
|
let pin_macro = if pinned_drop { quote!(#[pin_drop]) } else { quote!(#[pin]) };
|
||||||
|
|
||||||
quote!(
|
quote!(
|
||||||
|
@ -1079,6 +1114,7 @@ fn generate_sub_component(
|
||||||
#(#declared_callbacks : sp::Callback<(#(#declared_callbacks_types,)*), #declared_callbacks_ret>,)*
|
#(#declared_callbacks : sp::Callback<(#(#declared_callbacks_types,)*), #declared_callbacks_ret>,)*
|
||||||
#(#repeated_element_names : sp::Repeater<#repeated_element_components>,)*
|
#(#repeated_element_names : sp::Repeater<#repeated_element_components>,)*
|
||||||
#(#change_tracker_names : sp::ChangeTracker,)*
|
#(#change_tracker_names : sp::ChangeTracker,)*
|
||||||
|
#(#timer_names : sp::Timer,)*
|
||||||
self_weak : sp::OnceCell<sp::VWeakMapped<sp::ItemTreeVTable, #inner_component_id>>,
|
self_weak : sp::OnceCell<sp::VWeakMapped<sp::ItemTreeVTable, #inner_component_id>>,
|
||||||
#(parent : #parent_component_type,)*
|
#(parent : #parent_component_type,)*
|
||||||
globals: sp::OnceCell<sp::Rc<SharedGlobals>>,
|
globals: sp::OnceCell<sp::Rc<SharedGlobals>>,
|
||||||
|
@ -1214,6 +1250,8 @@ fn generate_sub_component(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#update_timers
|
||||||
|
|
||||||
#(#declared_functions)*
|
#(#declared_functions)*
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2816,6 +2854,9 @@ fn compile_builtin_function_call(
|
||||||
panic!("internal error: invalid args to MapPointToWindow {:?}", arguments)
|
panic!("internal error: invalid args to MapPointToWindow {:?}", arguments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BuiltinFunction::UpdateTimers => {
|
||||||
|
quote!(_self.update_timers())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -507,12 +507,18 @@ impl ElementType {
|
||||||
builtin.additional_accepted_child_types.keys().cloned().collect();
|
builtin.additional_accepted_child_types.keys().cloned().collect();
|
||||||
valid_children.sort();
|
valid_children.sort();
|
||||||
|
|
||||||
return Err(format!(
|
let err = if valid_children.is_empty() {
|
||||||
"{} is not allowed within {}. Only {} are valid children",
|
format!("{} cannot have children elements", builtin.native_class.class_name,)
|
||||||
name,
|
} else {
|
||||||
builtin.native_class.class_name,
|
format!(
|
||||||
valid_children.join(" ")
|
"{} is not allowed within {}. Only {} are valid children",
|
||||||
));
|
name,
|
||||||
|
builtin.native_class.class_name,
|
||||||
|
valid_children.join(" ")
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
return Err(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
|
@ -241,6 +241,7 @@ pub struct SubComponent {
|
||||||
pub repeated: Vec<RepeatedElement>,
|
pub repeated: Vec<RepeatedElement>,
|
||||||
pub component_containers: Vec<ComponentContainerElement>,
|
pub component_containers: Vec<ComponentContainerElement>,
|
||||||
pub popup_windows: Vec<PopupWindow>,
|
pub popup_windows: Vec<PopupWindow>,
|
||||||
|
pub timers: Vec<Timer>,
|
||||||
pub sub_components: Vec<SubComponentInstance>,
|
pub sub_components: Vec<SubComponentInstance>,
|
||||||
/// The initial value or binding for properties.
|
/// The initial value or binding for properties.
|
||||||
/// This is ordered in the order they must be set.
|
/// This is ordered in the order they must be set.
|
||||||
|
@ -274,6 +275,13 @@ pub struct PopupWindow {
|
||||||
pub position: MutExpression,
|
pub position: MutExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Timer {
|
||||||
|
pub interval: MutExpression,
|
||||||
|
pub running: MutExpression,
|
||||||
|
pub triggered: MutExpression,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PropAnalysis {
|
pub struct PropAnalysis {
|
||||||
/// Index in SubComponent::property_init for this property
|
/// Index in SubComponent::property_init for this property
|
||||||
|
|
|
@ -4,12 +4,11 @@
|
||||||
use by_address::ByAddress;
|
use by_address::ByAddress;
|
||||||
|
|
||||||
use super::lower_expression::ExpressionContext;
|
use super::lower_expression::ExpressionContext;
|
||||||
use super::PopupWindow as llr_PopupWindow;
|
|
||||||
use crate::expression_tree::Expression as tree_Expression;
|
use crate::expression_tree::Expression as tree_Expression;
|
||||||
use crate::langtype::{ElementType, Type};
|
use crate::langtype::{ElementType, Type};
|
||||||
use crate::llr::item_tree::*;
|
use crate::llr::item_tree::*;
|
||||||
use crate::namedreference::NamedReference;
|
use crate::namedreference::NamedReference;
|
||||||
use crate::object_tree::{Component, ElementRc, PopupWindow, PropertyAnalysis, PropertyVisibility};
|
use crate::object_tree::{self, Component, ElementRc, PropertyAnalysis, PropertyVisibility};
|
||||||
use crate::CompilerConfiguration;
|
use crate::CompilerConfiguration;
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
@ -206,6 +205,7 @@ fn lower_sub_component(
|
||||||
repeated: Default::default(),
|
repeated: Default::default(),
|
||||||
component_containers: Default::default(),
|
component_containers: Default::default(),
|
||||||
popup_windows: Default::default(),
|
popup_windows: Default::default(),
|
||||||
|
timers: Default::default(),
|
||||||
sub_components: Default::default(),
|
sub_components: Default::default(),
|
||||||
property_init: Default::default(),
|
property_init: Default::default(),
|
||||||
change_callbacks: Default::default(),
|
change_callbacks: Default::default(),
|
||||||
|
@ -447,6 +447,9 @@ fn lower_sub_component(
|
||||||
.map(|popup| lower_popup_component(&popup, &ctx, &compiler_config))
|
.map(|popup| lower_popup_component(&popup, &ctx, &compiler_config))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
sub_component.timers =
|
||||||
|
component.timers.borrow().iter().map(|t| lower_timer(&t, &ctx)).collect();
|
||||||
|
|
||||||
crate::generator::for_each_const_properties(component, |elem, n| {
|
crate::generator::for_each_const_properties(component, |elem, n| {
|
||||||
let x = ctx.map_property_reference(&NamedReference::new(elem, n));
|
let x = ctx.map_property_reference(&NamedReference::new(elem, n));
|
||||||
// ensure that all const properties have analysis
|
// ensure that all const properties have analysis
|
||||||
|
@ -645,10 +648,10 @@ fn lower_component_container(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lower_popup_component(
|
fn lower_popup_component(
|
||||||
popup: &PopupWindow,
|
popup: &object_tree::PopupWindow,
|
||||||
ctx: &ExpressionContext,
|
ctx: &ExpressionContext,
|
||||||
compiler_config: &CompilerConfiguration,
|
compiler_config: &CompilerConfiguration,
|
||||||
) -> llr_PopupWindow {
|
) -> PopupWindow {
|
||||||
let sc = lower_sub_component(&popup.component, ctx.state, Some(ctx), compiler_config);
|
let sc = lower_sub_component(&popup.component, ctx.state, Some(ctx), compiler_config);
|
||||||
let item_tree = ItemTree {
|
let item_tree = ItemTree {
|
||||||
tree: make_tree(ctx.state, &popup.component.root_element, &sc, &[]),
|
tree: make_tree(ctx.state, &popup.component.root_element, &sc, &[]),
|
||||||
|
@ -677,7 +680,22 @@ fn lower_popup_component(
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
llr_PopupWindow { item_tree, position: position.into() }
|
PopupWindow { item_tree, position: position.into() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lower_timer(timer: &object_tree::Timer, ctx: &ExpressionContext) -> Timer {
|
||||||
|
Timer {
|
||||||
|
interval: super::Expression::PropertyReference(ctx.map_property_reference(&timer.interval))
|
||||||
|
.into(),
|
||||||
|
running: super::Expression::PropertyReference(ctx.map_property_reference(&timer.running))
|
||||||
|
.into(),
|
||||||
|
// TODO: this calls a callback instead of inlining the callback code directly
|
||||||
|
triggered: super::Expression::CallBackCall {
|
||||||
|
callback: ctx.map_property_reference(&timer.triggered),
|
||||||
|
arguments: vec![],
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lower_global(
|
fn lower_global(
|
||||||
|
|
|
@ -129,6 +129,12 @@ pub fn count_property_use(root: &CompilationUnit) {
|
||||||
);
|
);
|
||||||
popup.position.borrow().visit_recursive(&mut |e| visit_expression(e, &popup_ctx))
|
popup.position.borrow().visit_recursive(&mut |e| visit_expression(e, &popup_ctx))
|
||||||
}
|
}
|
||||||
|
// 11. timer
|
||||||
|
for timer in &sc.timers {
|
||||||
|
timer.interval.borrow().visit_recursive(&mut |e| visit_expression(e, &ctx));
|
||||||
|
timer.running.borrow().visit_recursive(&mut |e| visit_expression(e, &ctx));
|
||||||
|
timer.triggered.borrow().visit_recursive(&mut |e| visit_expression(e, &ctx));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: only visit used function
|
// TODO: only visit used function
|
||||||
|
|
|
@ -123,6 +123,7 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize {
|
||||||
BuiltinFunction::TextInputFocused => PROPERTY_ACCESS_COST,
|
BuiltinFunction::TextInputFocused => PROPERTY_ACCESS_COST,
|
||||||
BuiltinFunction::Translate => 2 * ALLOC_COST + PROPERTY_ACCESS_COST,
|
BuiltinFunction::Translate => 2 * ALLOC_COST + PROPERTY_ACCESS_COST,
|
||||||
BuiltinFunction::Use24HourFormat => 2 * ALLOC_COST + PROPERTY_ACCESS_COST,
|
BuiltinFunction::Use24HourFormat => 2 * ALLOC_COST + PROPERTY_ACCESS_COST,
|
||||||
|
BuiltinFunction::UpdateTimers => 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -277,6 +277,13 @@ pub struct PopupWindow {
|
||||||
pub parent_element: ElementRc,
|
pub parent_element: ElementRc,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Timer {
|
||||||
|
pub interval: NamedReference,
|
||||||
|
pub triggered: NamedReference,
|
||||||
|
pub running: NamedReference,
|
||||||
|
}
|
||||||
|
|
||||||
type ChildrenInsertionPoint = (ElementRc, usize, syntax_nodes::ChildrenPlaceholder);
|
type ChildrenInsertionPoint = (ElementRc, usize, syntax_nodes::ChildrenPlaceholder);
|
||||||
|
|
||||||
/// Used sub types for a root component
|
/// Used sub types for a root component
|
||||||
|
@ -347,6 +354,7 @@ pub struct Component {
|
||||||
pub init_code: RefCell<InitCode>,
|
pub init_code: RefCell<InitCode>,
|
||||||
|
|
||||||
pub popup_windows: RefCell<Vec<PopupWindow>>,
|
pub popup_windows: RefCell<Vec<PopupWindow>>,
|
||||||
|
pub timers: RefCell<Vec<Timer>>,
|
||||||
|
|
||||||
/// This component actually inherits PopupWindow (although that has been changed to a Window by the lower_popups pass)
|
/// This component actually inherits PopupWindow (although that has been changed to a Window by the lower_popups pass)
|
||||||
pub inherits_popup_window: Cell<bool>,
|
pub inherits_popup_window: Cell<bool>,
|
||||||
|
@ -2321,6 +2329,11 @@ pub fn visit_all_named_references(
|
||||||
vis(&mut p.x);
|
vis(&mut p.x);
|
||||||
vis(&mut p.y);
|
vis(&mut p.y);
|
||||||
});
|
});
|
||||||
|
compo.timers.borrow_mut().iter_mut().for_each(|t| {
|
||||||
|
vis(&mut t.interval);
|
||||||
|
vis(&mut t.triggered);
|
||||||
|
vis(&mut t.running);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
compo
|
compo
|
||||||
},
|
},
|
||||||
|
|
|
@ -36,6 +36,7 @@ mod lower_shadows;
|
||||||
mod lower_states;
|
mod lower_states;
|
||||||
mod lower_tabwidget;
|
mod lower_tabwidget;
|
||||||
mod lower_text_input_interface;
|
mod lower_text_input_interface;
|
||||||
|
mod lower_timers;
|
||||||
pub mod materialize_fake_properties;
|
pub mod materialize_fake_properties;
|
||||||
pub mod move_declarations;
|
pub mod move_declarations;
|
||||||
mod optimize_useless_rectangles;
|
mod optimize_useless_rectangles;
|
||||||
|
@ -98,6 +99,7 @@ pub async fn run_passes(
|
||||||
repeater_component::process_repeater_components(component);
|
repeater_component::process_repeater_components(component);
|
||||||
lower_popups::lower_popups(component, &doc.local_registry, diag);
|
lower_popups::lower_popups(component, &doc.local_registry, diag);
|
||||||
collect_init_code::collect_init_code(component);
|
collect_init_code::collect_init_code(component);
|
||||||
|
lower_timers::lower_timers(component, diag);
|
||||||
});
|
});
|
||||||
|
|
||||||
inlining::inline(doc, inlining::InlineSelection::InlineOnlyRequiredComponents, diag);
|
inlining::inline(doc, inlining::InlineSelection::InlineOnlyRequiredComponents, diag);
|
||||||
|
|
|
@ -282,7 +282,11 @@ fn inline_element(
|
||||||
fixup_reference(&mut p.x, &mapping);
|
fixup_reference(&mut p.x, &mapping);
|
||||||
fixup_reference(&mut p.y, &mapping);
|
fixup_reference(&mut p.y, &mapping);
|
||||||
}
|
}
|
||||||
|
for t in root_component.timers.borrow_mut().iter_mut() {
|
||||||
|
fixup_reference(&mut t.interval, &mapping);
|
||||||
|
fixup_reference(&mut t.running, &mapping);
|
||||||
|
fixup_reference(&mut t.triggered, &mapping);
|
||||||
|
}
|
||||||
// If some element were moved into PopupWindow, we need to report error if they are used outside of the popup window.
|
// If some element were moved into PopupWindow, we need to report error if they are used outside of the popup window.
|
||||||
if !moved_into_popup.is_empty() {
|
if !moved_into_popup.is_empty() {
|
||||||
recurse_elem_no_borrow(&root_component.root_element.clone(), &(), &mut |e, _| {
|
recurse_elem_no_borrow(&root_component.root_element.clone(), &(), &mut |e, _| {
|
||||||
|
@ -393,6 +397,7 @@ fn duplicate_sub_component(
|
||||||
child_insertion_point: component_to_duplicate.child_insertion_point.clone(),
|
child_insertion_point: component_to_duplicate.child_insertion_point.clone(),
|
||||||
init_code: component_to_duplicate.init_code.clone(),
|
init_code: component_to_duplicate.init_code.clone(),
|
||||||
popup_windows: Default::default(),
|
popup_windows: Default::default(),
|
||||||
|
timers: component_to_duplicate.timers.clone(),
|
||||||
exported_global_names: component_to_duplicate.exported_global_names.clone(),
|
exported_global_names: component_to_duplicate.exported_global_names.clone(),
|
||||||
private_properties: Default::default(),
|
private_properties: Default::default(),
|
||||||
inherits_popup_window: core::cell::Cell::new(false),
|
inherits_popup_window: core::cell::Cell::new(false),
|
||||||
|
@ -413,6 +418,11 @@ fn duplicate_sub_component(
|
||||||
fixup_reference(&mut p.x, mapping);
|
fixup_reference(&mut p.x, mapping);
|
||||||
fixup_reference(&mut p.y, mapping);
|
fixup_reference(&mut p.y, mapping);
|
||||||
}
|
}
|
||||||
|
for t in new_component.timers.borrow_mut().iter_mut() {
|
||||||
|
fixup_reference(&mut t.interval, &mapping);
|
||||||
|
fixup_reference(&mut t.running, &mapping);
|
||||||
|
fixup_reference(&mut t.triggered, &mapping);
|
||||||
|
}
|
||||||
new_component
|
new_component
|
||||||
.root_constraints
|
.root_constraints
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
|
|
68
internal/compiler/passes/lower_timers.rs
Normal file
68
internal/compiler/passes/lower_timers.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
//! Passe that transform the Timer element into a timer in the Component
|
||||||
|
|
||||||
|
use crate::diagnostics::BuildDiagnostics;
|
||||||
|
use crate::expression_tree::{BuiltinFunction, Expression, NamedReference};
|
||||||
|
use crate::langtype::ElementType;
|
||||||
|
use crate::object_tree::*;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
pub fn lower_timers(component: &Rc<Component>, diag: &mut BuildDiagnostics) {
|
||||||
|
recurse_elem_including_sub_components_no_borrow(
|
||||||
|
component,
|
||||||
|
&None,
|
||||||
|
&mut |elem, parent_element: &Option<ElementRc>| {
|
||||||
|
let is_timer = matches!(&elem.borrow().base_type, ElementType::Builtin(base_type) if base_type.name == "Timer");
|
||||||
|
if is_timer {
|
||||||
|
lower_timer(elem, parent_element.as_ref(), diag);
|
||||||
|
}
|
||||||
|
Some(elem.clone())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lower_timer(
|
||||||
|
timer_element: &ElementRc,
|
||||||
|
parent_element: Option<&ElementRc>,
|
||||||
|
diag: &mut BuildDiagnostics,
|
||||||
|
) {
|
||||||
|
let parent_component = timer_element.borrow().enclosing_component.upgrade().unwrap();
|
||||||
|
let Some(parent_element) = parent_element else {
|
||||||
|
diag.push_error("A component cannot inherit from Timer".into(), &*timer_element.borrow());
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if Rc::ptr_eq(&parent_component.root_element, timer_element) {
|
||||||
|
diag.push_error(
|
||||||
|
"Timer cannot be directly repeated or conditional".into(),
|
||||||
|
&*timer_element.borrow(),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the timer_element from its parent
|
||||||
|
let old_size = parent_element.borrow().children.len();
|
||||||
|
parent_element.borrow_mut().children.retain(|child| !Rc::ptr_eq(child, timer_element));
|
||||||
|
debug_assert_eq!(
|
||||||
|
parent_element.borrow().children.len() + 1,
|
||||||
|
old_size,
|
||||||
|
"Exactly one child must be removed (the timer itself)"
|
||||||
|
);
|
||||||
|
parent_component.optimized_elements.borrow_mut().push(timer_element.clone());
|
||||||
|
|
||||||
|
parent_component.timers.borrow_mut().push(Timer {
|
||||||
|
interval: NamedReference::new(timer_element, "interval"),
|
||||||
|
running: NamedReference::new(timer_element, "running"),
|
||||||
|
triggered: NamedReference::new(timer_element, "triggered"),
|
||||||
|
});
|
||||||
|
let update_timers = Expression::FunctionCall {
|
||||||
|
function: Expression::BuiltinFunctionReference(BuiltinFunction::UpdateTimers, None).into(),
|
||||||
|
arguments: vec![],
|
||||||
|
source_location: None,
|
||||||
|
};
|
||||||
|
let change_callbacks = &mut timer_element.borrow_mut().change_callbacks;
|
||||||
|
change_callbacks.entry("running".into()).or_default().borrow_mut().push(update_timers.clone());
|
||||||
|
change_callbacks.entry("interval".into()).or_default().borrow_mut().push(update_timers);
|
||||||
|
}
|
|
@ -96,6 +96,11 @@ fn do_move_declarations(component: &Rc<Component>) {
|
||||||
fixup_reference(&mut p.y);
|
fixup_reference(&mut p.y);
|
||||||
visit_all_named_references(&p.component, &mut fixup_reference)
|
visit_all_named_references(&p.component, &mut fixup_reference)
|
||||||
});
|
});
|
||||||
|
component.timers.borrow_mut().iter_mut().for_each(|t| {
|
||||||
|
fixup_reference(&mut t.interval);
|
||||||
|
fixup_reference(&mut t.running);
|
||||||
|
fixup_reference(&mut t.triggered);
|
||||||
|
});
|
||||||
component.init_code.borrow_mut().iter_mut().for_each(|expr| {
|
component.init_code.borrow_mut().iter_mut().for_each(|expr| {
|
||||||
visit_named_references_in_expression(expr, &mut fixup_reference);
|
visit_named_references_in_expression(expr, &mut fixup_reference);
|
||||||
});
|
});
|
||||||
|
|
25
internal/compiler/tests/syntax/elements/timer.slint
Normal file
25
internal/compiler/tests/syntax/elements/timer.slint
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
export component Def {
|
||||||
|
Timer {
|
||||||
|
interval: 100ms;
|
||||||
|
Rectangle {}
|
||||||
|
// ^error{Timer cannot have children elements}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 200ms;
|
||||||
|
width: 500px;
|
||||||
|
// ^error{Unknown property width in Timer}
|
||||||
|
y: 12px;
|
||||||
|
// ^error{Unknown property y in Timer}
|
||||||
|
|
||||||
|
opacity: 0.5;
|
||||||
|
// ^error{Unknown property opacity in Timer}
|
||||||
|
visible: false;
|
||||||
|
// ^error{Unknown property visible in Timer}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
14
internal/compiler/tests/syntax/elements/timer2.slint
Normal file
14
internal/compiler/tests/syntax/elements/timer2.slint
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
export component Abc inherits Timer {
|
||||||
|
// ^error{A component cannot inherit from Timer}
|
||||||
|
interval: 50ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
export component Def {
|
||||||
|
if true: Timer { interval: 32ms; }
|
||||||
|
// ^error{Timer cannot be directly repeated or conditional}
|
||||||
|
|
||||||
|
if false: Abc {}
|
||||||
|
}
|
|
@ -298,6 +298,9 @@ impl Snapshotter {
|
||||||
.map(|p| self.snapshot_popup_window(p))
|
.map(|p| self.snapshot_popup_window(p))
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
|
let timers = RefCell::new(
|
||||||
|
component.timers.borrow().iter().map(|p| self.snapshot_timer(p)).collect(),
|
||||||
|
);
|
||||||
let root_constraints = RefCell::new(
|
let root_constraints = RefCell::new(
|
||||||
self.snapshot_layout_constraints(&component.root_constraints.borrow()),
|
self.snapshot_layout_constraints(&component.root_constraints.borrow()),
|
||||||
);
|
);
|
||||||
|
@ -314,6 +317,7 @@ impl Snapshotter {
|
||||||
optimized_elements,
|
optimized_elements,
|
||||||
parent_element,
|
parent_element,
|
||||||
popup_windows,
|
popup_windows,
|
||||||
|
timers,
|
||||||
private_properties: RefCell::new(component.private_properties.borrow().clone()),
|
private_properties: RefCell::new(component.private_properties.borrow().clone()),
|
||||||
root_constraints,
|
root_constraints,
|
||||||
root_element,
|
root_element,
|
||||||
|
@ -588,6 +592,14 @@ impl Snapshotter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn snapshot_timer(&mut self, popup_window: &object_tree::Timer) -> object_tree::Timer {
|
||||||
|
object_tree::Timer {
|
||||||
|
interval: popup_window.interval.snapshot(self),
|
||||||
|
running: popup_window.running.snapshot(self),
|
||||||
|
triggered: popup_window.triggered.snapshot(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn snapshot_layout_constraints(
|
fn snapshot_layout_constraints(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout_constraints: &layout::LayoutConstraints,
|
layout_constraints: &layout::LayoutConstraints,
|
||||||
|
|
|
@ -169,6 +169,13 @@ impl Timer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the interval of the timer.
|
||||||
|
/// Returns `None` if the timer is not running.
|
||||||
|
pub fn interval(&self) -> Option<core::time::Duration> {
|
||||||
|
self.id()
|
||||||
|
.map(|timer_id| CURRENT_TIMERS.with(|timers| timers.borrow().timers[timer_id].duration))
|
||||||
|
}
|
||||||
|
|
||||||
fn id(&self) -> Option<usize> {
|
fn id(&self) -> Option<usize> {
|
||||||
self.id.get().map(|v| usize::from(v) - 1)
|
self.id.get().map(|v| usize::from(v) - 1)
|
||||||
}
|
}
|
||||||
|
@ -479,7 +486,12 @@ pub(crate) mod ffi {
|
||||||
if id != 0 {
|
if id != 0 {
|
||||||
timer.id.set(NonZeroUsize::new(id));
|
timer.id.set(NonZeroUsize::new(id));
|
||||||
}
|
}
|
||||||
timer.start(mode, core::time::Duration::from_millis(duration), move || wrap.call());
|
if duration > i64::MAX as u64 {
|
||||||
|
// negative duration? stop the timer
|
||||||
|
timer.stop();
|
||||||
|
} else {
|
||||||
|
timer.start(mode, core::time::Duration::from_millis(duration), move || wrap.call());
|
||||||
|
}
|
||||||
timer.id.take().map(|x| usize::from(x)).unwrap_or(0)
|
timer.id.take().map(|x| usize::from(x)).unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -538,6 +550,18 @@ pub(crate) mod ffi {
|
||||||
timer.id.take(); // Make sure that dropping the Timer doesn't unregister it. C++ will call destroy() in the destructor.
|
timer.id.take(); // Make sure that dropping the Timer doesn't unregister it. C++ will call destroy() in the destructor.
|
||||||
running
|
running
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the interval in milliseconds if it is running, or -1 otherwise
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn slint_timer_interval(id: usize) -> i64 {
|
||||||
|
if id == 0 {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
let timer = Timer { id: Cell::new(NonZeroUsize::new(id)), _phantom: Default::default() };
|
||||||
|
let val = timer.interval().map_or(-1, |d| d.as_millis() as i64);
|
||||||
|
timer.id.take(); // Make sure that dropping the Timer doesn't unregister it. C++ will call destroy() in the destructor.
|
||||||
|
val
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -32,6 +32,7 @@ use i_slint_core::platform::PlatformError;
|
||||||
use i_slint_core::properties::{ChangeTracker, InterpolatedPropertyValue};
|
use i_slint_core::properties::{ChangeTracker, InterpolatedPropertyValue};
|
||||||
use i_slint_core::rtti::{self, AnimatedBindingKind, FieldOffset, PropertyInfo};
|
use i_slint_core::rtti::{self, AnimatedBindingKind, FieldOffset, PropertyInfo};
|
||||||
use i_slint_core::slice::Slice;
|
use i_slint_core::slice::Slice;
|
||||||
|
use i_slint_core::timers::Timer;
|
||||||
use i_slint_core::window::{WindowAdapterRc, WindowInner};
|
use i_slint_core::window::{WindowAdapterRc, WindowInner};
|
||||||
use i_slint_core::{Brush, Color, Property, SharedString, SharedVector};
|
use i_slint_core::{Brush, Color, Property, SharedString, SharedVector};
|
||||||
#[cfg(feature = "internal")]
|
#[cfg(feature = "internal")]
|
||||||
|
@ -407,6 +408,7 @@ pub struct ItemTreeDescription<'id> {
|
||||||
FieldOffset<Instance<'id>, OnceCell<Vec<ChangeTracker>>>,
|
FieldOffset<Instance<'id>, OnceCell<Vec<ChangeTracker>>>,
|
||||||
Vec<(NamedReference, Expression)>,
|
Vec<(NamedReference, Expression)>,
|
||||||
)>,
|
)>,
|
||||||
|
timers: Vec<FieldOffset<Instance<'id>, Timer>>,
|
||||||
|
|
||||||
/// The collection of compiled globals
|
/// The collection of compiled globals
|
||||||
compiled_globals: Option<Rc<CompiledGlobalCollection>>,
|
compiled_globals: Option<Rc<CompiledGlobalCollection>>,
|
||||||
|
@ -1241,6 +1243,12 @@ pub(crate) fn generate_item_tree<'id>(
|
||||||
builder.change_callbacks,
|
builder.change_callbacks,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
let timers = component
|
||||||
|
.timers
|
||||||
|
.borrow()
|
||||||
|
.iter()
|
||||||
|
.map(|_| builder.type_builder.add_field_type::<Timer>())
|
||||||
|
.collect();
|
||||||
|
|
||||||
let public_properties = component.root_element.borrow().property_declarations.clone();
|
let public_properties = component.root_element.borrow().property_declarations.clone();
|
||||||
|
|
||||||
|
@ -1283,6 +1291,7 @@ pub(crate) fn generate_item_tree<'id>(
|
||||||
public_properties,
|
public_properties,
|
||||||
compiled_globals,
|
compiled_globals,
|
||||||
change_trackers,
|
change_trackers,
|
||||||
|
timers,
|
||||||
#[cfg(feature = "highlight")]
|
#[cfg(feature = "highlight")]
|
||||||
type_loader: std::cell::OnceCell::new(),
|
type_loader: std::cell::OnceCell::new(),
|
||||||
#[cfg(feature = "highlight")]
|
#[cfg(feature = "highlight")]
|
||||||
|
@ -1612,6 +1621,8 @@ pub fn instantiate(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_timers(instance_ref);
|
||||||
|
|
||||||
self_rc
|
self_rc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2302,3 +2313,41 @@ pub fn show_popup(
|
||||||
parent_item,
|
parent_item,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_timers(instance: InstanceRef) {
|
||||||
|
let ts = instance.description.original.timers.borrow();
|
||||||
|
for (desc, offset) in ts.iter().zip(&instance.description.timers) {
|
||||||
|
let timer = offset.apply(instance.as_ref());
|
||||||
|
let running =
|
||||||
|
eval::load_property(instance, &desc.running.element(), desc.running.name()).unwrap();
|
||||||
|
if matches!(running, Value::Bool(true)) {
|
||||||
|
let millis: i64 =
|
||||||
|
eval::load_property(instance, &desc.interval.element(), desc.interval.name())
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.expect("interval must be a duration");
|
||||||
|
if millis < 0 {
|
||||||
|
timer.stop();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let interval = core::time::Duration::from_millis(millis as _);
|
||||||
|
let old_interval = timer.interval();
|
||||||
|
if old_interval != Some(interval) || !timer.running() {
|
||||||
|
let callback = desc.triggered.clone();
|
||||||
|
let self_weak = instance.self_weak().get().unwrap().clone();
|
||||||
|
timer.start(i_slint_core::timers::TimerMode::Repeated, interval, move || {
|
||||||
|
if let Some(instance) = self_weak.upgrade() {
|
||||||
|
generativity::make_guard!(guard);
|
||||||
|
let c = instance.unerase(guard);
|
||||||
|
let c = c.borrow_instance();
|
||||||
|
let inst = eval::ComponentInstance::InstanceRef(c);
|
||||||
|
eval::invoke_callback(inst, &callback.element(), callback.name(), &[])
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
timer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1118,6 +1118,15 @@ fn call_builtin_function(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
BuiltinFunction::Use24HourFormat => Value::Bool(corelib::date_time::use_24_hour_format()),
|
BuiltinFunction::Use24HourFormat => Value::Bool(corelib::date_time::use_24_hour_format()),
|
||||||
|
BuiltinFunction::UpdateTimers => match local_context.component_instance {
|
||||||
|
ComponentInstance::InstanceRef(component) => {
|
||||||
|
crate::dynamic_item_tree::update_timers(component);
|
||||||
|
Value::Void
|
||||||
|
}
|
||||||
|
ComponentInstance::GlobalComponent(_) => {
|
||||||
|
panic!("timer in global?")
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
153
tests/cases/elements/timer.slint
Normal file
153
tests/cases/elements/timer.slint
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
// 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 FizBuzz {
|
||||||
|
out property <string> result;
|
||||||
|
|
||||||
|
}*/
|
||||||
|
|
||||||
|
export component TestCase inherits Window {
|
||||||
|
|
||||||
|
out property <string> result;
|
||||||
|
|
||||||
|
in property <int> tm2duration;
|
||||||
|
in property <bool> tm2running <=> tm2.running;
|
||||||
|
|
||||||
|
vl := VerticalLayout {
|
||||||
|
if true: HorizontalLayout {
|
||||||
|
Timer {
|
||||||
|
interval: 1s;
|
||||||
|
triggered => { result += "1"; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out property <bool> test: vl.max-width == 0;
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
tm2 := Timer {
|
||||||
|
running: false;
|
||||||
|
interval: tm2duration * 1ms;
|
||||||
|
triggered => { result += "2"; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
oops := Timer {
|
||||||
|
interval: -5ms;
|
||||||
|
triggered => { result += "oops"; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let instance = TestCase::new().unwrap();
|
||||||
|
assert!(instance.get_test());
|
||||||
|
assert_eq!(instance.get_result(), "");
|
||||||
|
slint_testing::mock_elapsed_time(991);
|
||||||
|
assert_eq!(instance.get_result(), "");
|
||||||
|
slint_testing::mock_elapsed_time(10);
|
||||||
|
assert_eq!(instance.get_result(), "1");
|
||||||
|
instance.set_tm2running(true);
|
||||||
|
assert_eq!(instance.get_result(), "1");
|
||||||
|
slint_testing::mock_elapsed_time(500);
|
||||||
|
// despite we say to ellapse 500ms, the changed callback is only called once
|
||||||
|
slint_testing::mock_elapsed_time(510);
|
||||||
|
// Same, the timer event are only called onced
|
||||||
|
assert_eq!(instance.get_result(), "121");
|
||||||
|
slint_testing::mock_elapsed_time(0);
|
||||||
|
assert_eq!(instance.get_result(), "1212");
|
||||||
|
slint_testing::mock_elapsed_time(0);
|
||||||
|
assert_eq!(instance.get_result(), "12122");
|
||||||
|
instance.set_tm2duration(50);
|
||||||
|
slint_testing::mock_elapsed_time(8);
|
||||||
|
// even though we changed the duration, the timer fires before the changed callback
|
||||||
|
assert_eq!(instance.get_result(), "121222");
|
||||||
|
slint_testing::mock_elapsed_time(49);
|
||||||
|
assert_eq!(instance.get_result(), "121222");
|
||||||
|
slint_testing::mock_elapsed_time(2);
|
||||||
|
assert_eq!(instance.get_result(), "1212222");
|
||||||
|
slint_testing::mock_elapsed_time(47);
|
||||||
|
assert_eq!(instance.get_result(), "1212222");
|
||||||
|
instance.set_tm2duration(18);
|
||||||
|
slint_testing::mock_elapsed_time(2);
|
||||||
|
assert_eq!(instance.get_result(), "1212222");
|
||||||
|
slint_testing::mock_elapsed_time(19);
|
||||||
|
assert_eq!(instance.get_result(), "12122222");
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto handle = TestCase::create();
|
||||||
|
const TestCase &instance = *handle;
|
||||||
|
assert(instance.get_test());
|
||||||
|
assert_eq(instance.get_result(), "");
|
||||||
|
slint_testing::mock_elapsed_time(991);
|
||||||
|
assert_eq(instance.get_result(), "");
|
||||||
|
slint_testing::mock_elapsed_time(10);
|
||||||
|
assert_eq(instance.get_result(), "1");
|
||||||
|
instance.set_tm2running(true);
|
||||||
|
assert_eq(instance.get_result(), "1");
|
||||||
|
slint_testing::mock_elapsed_time(500);
|
||||||
|
// despite we say to ellapse 500ms, the changed callback is only called once
|
||||||
|
slint_testing::mock_elapsed_time(510);
|
||||||
|
// Same, the timer event are only called onced
|
||||||
|
assert_eq(instance.get_result(), "121");
|
||||||
|
slint_testing::mock_elapsed_time(0);
|
||||||
|
assert_eq(instance.get_result(), "1212");
|
||||||
|
slint_testing::mock_elapsed_time(0);
|
||||||
|
assert_eq(instance.get_result(), "12122");
|
||||||
|
instance.set_tm2duration(50);
|
||||||
|
slint_testing::mock_elapsed_time(8);
|
||||||
|
// even though we changed the duration, the timer fires before the changed callback
|
||||||
|
assert_eq(instance.get_result(), "121222");
|
||||||
|
slint_testing::mock_elapsed_time(49);
|
||||||
|
assert_eq(instance.get_result(), "121222");
|
||||||
|
slint_testing::mock_elapsed_time(2);
|
||||||
|
assert_eq(instance.get_result(), "1212222");
|
||||||
|
slint_testing::mock_elapsed_time(47);
|
||||||
|
assert_eq(instance.get_result(), "1212222");
|
||||||
|
instance.set_tm2duration(18);
|
||||||
|
slint_testing::mock_elapsed_time(2);
|
||||||
|
assert_eq(instance.get_result(), "1212222");
|
||||||
|
slint_testing::mock_elapsed_time(19);
|
||||||
|
assert_eq(instance.get_result(), "12122222");
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
var instance = new slint.TestCase({});
|
||||||
|
assert(instance.test);
|
||||||
|
assert.equal(instance.result, "");
|
||||||
|
slintlib.private_api.mock_elapsed_time(991);
|
||||||
|
assert.equal(instance.result, "");
|
||||||
|
slintlib.private_api.mock_elapsed_time(10);
|
||||||
|
assert.equal(instance.result, "1");
|
||||||
|
instance.tm2running = true;
|
||||||
|
assert.equal(instance.result, "1");
|
||||||
|
slintlib.private_api.mock_elapsed_time(500);
|
||||||
|
// despite we say to ellapse 500ms, the changed callback is only called once
|
||||||
|
slintlib.private_api.mock_elapsed_time(510);
|
||||||
|
// Same, the timer event are only called onced
|
||||||
|
assert.equal(instance.result, "121");
|
||||||
|
slintlib.private_api.mock_elapsed_time(0);
|
||||||
|
assert.equal(instance.result, "1212");
|
||||||
|
slintlib.private_api.mock_elapsed_time(0);
|
||||||
|
assert.equal(instance.result, "12122");
|
||||||
|
instance.tm2duration = 50;
|
||||||
|
slintlib.private_api.mock_elapsed_time(8);
|
||||||
|
// even though we changed the duration, the timer fires before the changed callback
|
||||||
|
assert.equal(instance.result, "121222");
|
||||||
|
slintlib.private_api.mock_elapsed_time(49);
|
||||||
|
assert.equal(instance.result, "121222");
|
||||||
|
slintlib.private_api.mock_elapsed_time(2);
|
||||||
|
assert.equal(instance.result, "1212222");
|
||||||
|
slintlib.private_api.mock_elapsed_time(47);
|
||||||
|
assert.equal(instance.result, "1212222");
|
||||||
|
instance.tm2duration = 18;
|
||||||
|
slintlib.private_api.mock_elapsed_time(2);
|
||||||
|
assert.equal(instance.result, "1212222");
|
||||||
|
slintlib.private_api.mock_elapsed_time(19);
|
||||||
|
assert.equal(instance.result, "12122222");
|
||||||
|
```
|
||||||
|
*/
|
|
@ -89,6 +89,15 @@ export component TestCase inherits Window {
|
||||||
changed sub-result => {
|
changed sub-result => {
|
||||||
result += "||" + sub-result;
|
result += "||" + sub-result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
probably-optimized := Rectangle {
|
||||||
|
property <int> foo: other;
|
||||||
|
changed foo => {
|
||||||
|
result += "foo,";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue