'changed' event for the content of array/model
Currently, changed callback on a property of type model is only run if the model pointer is changed.
It would also be useful to have changed event called if anything inside the model is changed.
Suggested syntac: changed my-array[] => { ... } or changed[] my-array => { ... }
Original Description
For any property that is an 'array' the changed event is never dispatched, when any data of the array is modified or size of the array is changed.
struct AStruct {
a-value: int,
}
export component AppWindow inherits Window {
property <[AStruct]> a-struct: [{a-value:1}, {a-value:2}, {a-value:3}];
changed a-struct => {
// Due to that a-struct is an array, whatever we do, the 'changed' event is never dispatched
}
}
The changed event will only be called if the model pointer is changed. Not if the content of the model is changed.
For example, in that example, the first button will cause the changed callback to be called but not the second button.
import { Button, VerticalBox } from "std-widgets.slint";
struct AStruct {
a-value: int,
}
export component AppWindow inherits Window {
property <int> xxx: 1;
property <[AStruct]> a-struct: [{a-value:xxx}, {a-value:2}, {a-value:3}];
changed a-struct => {
debug("CHANGED", a-struct[0]);
}
VerticalBox {
alignment: start;
Button {
clicked => {
// This force re-evaluation of the binding and create a new model
xxx += 1;
}
text: "xxx += 1";
}
Button {
clicked => {
// This doesn't
a-struct[0].a-value += 1;
}
text: "a-struct[0].a-value += 1";
}
Text {
text: a-struct[0].a-value;
}
}
}
From what I understand from the description, you wish that the callback is called for any change in the content of the model as well.
I wonder
- Is that really what we want?
- How to implement that?
- Would that be a breaking change?
I do think this is really needed, let me expand the example a bit with the real use case how this would be used.
struct XYData {
x: float,
x: float,
}
struct PlotData {
data: [XYData],
}
export component Chart {
property <[PlotData]> plot-data: [{x:1,y:1}, {x:2,y:2}, {x:3,y:3}, {x:4,y:2}, {x:5,y:1}];
changed plot-data => {
// Whenever plot-data is changed, update and render the chart
update();
}
public function udpate() {
// Update and render the Chart
}
}
As far I understand, the array property is a the slint::Model already provides the 'ModelTracker', which is already used in other parts to track all the changes to a slint::Model, so it should be possible to also integrate this with the 'changed' event for a property that is an [array] Using the ChangeTracker would probably not work, or be very complicated, but maybe implement a ChangeArrayTracker - that will be used for [array] properties could be possible way.
I'm not sure that we should change the semantics of changed plot-data, but we could have changed plot-data[] for example.
That said, I wonder if the answer is closer to the use-site itself. How is update() implemented? How does it use the model?
If it's native code, is it perhaps in there that we should ways of registering a property dependency?
Me, I like the idea off changed plot-data[] That makes it explicit that it's a changed event for an array.
The update() will trigger native code, that renders the Chart to a SharedPixelBuffer, that's how it's implemented for the moment being
Great, so today one way of solving this is like this:
export component Chart {
property <[PlotData]> plot-data: [{x:1,y:1}, {x:2,y:2}, {x:3,y:3}, {x:4,y:2}, {x:5,y:1}];
callback render(data: [PlotData]) -> image;
Image {
source: render(plot-data);
}
}
and inside your callback:
fn render(model: slint::Model<PlotData) -> slint::Image {
let length = model.row_count();
...
let row = model.row_data_tracked(some_index); // <-- This registers a dependency
...
}
This should work today, but it's one dependency for every row. We could have a fn track_model_changes(); function perhaps that solely serves the purpose to register for any changes without the overhead of tracking each row. (name and existence to be discussed, so just brainstorming here).
Thank you @tronical That actually almost works, only one little detail, it's not possible to force an update, needed for functions like Zoom-In and Zoom-Out, etc. the plot-data has not changed but it needs to re-render. However I can get around this by having a 'dummy' value inside the PlotData struct.
Also from what I understand all the functionality tracking changes, and dependencies is already in place, from here to also have a 'changed' / 'changed[]' callback dispatched
Perhaps you can make the zoom an parameter of the render function. If the value comes from a property, the render function will also automatically be called if the zoom changes.
I've rewritten the issue to be a feature request for the changed[] syntax.