Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions api/node/rust/types/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,108 @@ impl Model for JsModel {
}
}

fn push_row(&self, data: Self::Data) {
let Ok(model_unknown) = self.js_impl.get_unknown() else {
eprintln!("Node.js: JavaScript Model<T>'s pushRow threw an exception");
return;
};

let Ok(model) = model_unknown.coerce_to_object() else {
eprintln!("Node.js: JavaScript Model<T> is not an object");
return;
};

let push_row_fn: Function<Unknown<'_>, Unknown> = match model.get_named_property("pushRow")
{
Ok(f) => f,
Err(e) => {
eprintln!("{}", e.to_string());
eprintln!(
"Node.js: JavaScript Model<T> implementation is missing pushRow property"
);
return;
}
};

let Ok(js_data) = to_js_unknown(&self.env, &data) else {
eprintln!(
"Node.js: Model<T>'s push_row called by Rust with data type that can't be represented in JavaScript"
);
return;
};

if let Err(exception) = push_row_fn.apply(model, js_data) {
eprintln!(
"Node.js: JavaScript Model<T>'s pushRow function threw an exception: {exception}"
);
}
}

fn remove_row(&self, row: usize) {
let Ok(model_unknown) = self.js_impl.get_unknown() else {
eprintln!("Node.js: JavaScript Model<T>'s removeRow threw an exception");
return;
};

let Ok(model) = model_unknown.coerce_to_object() else {
eprintln!("Node.js: JavaScript Model<T> is not an object");
return;
};

let remove_row_fn: Function<f64, Unknown> = match model.get_named_property("removeRow") {
Ok(f) => f,
Err(e) => {
eprintln!("{}", e.to_string());
eprintln!(
"Node.js: JavaScript Model<T> implementation is missing removeRow property"
);
return;
}
};

if let Err(exception) = remove_row_fn.apply(model, row as f64) {
eprintln!(
"Node.js: JavaScript Model<T>'s removeRow function threw an exception: {exception}"
);
}
}

fn insert_row(&self, row: usize, data: Self::Data) {
let Ok(model_unknown) = self.js_impl.get_unknown() else {
eprintln!("Node.js: JavaScript Model<T>'s insertRow threw an exception");
return;
};

let Ok(model) = model_unknown.coerce_to_object() else {
eprintln!("Node.js: JavaScript Model<T> is not an object");
return;
};

let insert_row_fn: Function<FnArgs<(f64, Unknown<'_>)>, Unknown> =
match model.get_named_property("insertRow") {
Ok(f) => f,
Err(_) => {
eprintln!(
"Node.js: JavaScript Model<T> implementation is missing insertRow property"
);
return;
}
};

let Ok(js_data) = to_js_unknown(&self.env, &data) else {
eprintln!(
"Node.js: Model<T>'s insert_row called by Rust with data type that can't be represented in JavaScript"
);
return;
};

if let Err(exception) = insert_row_fn.apply(model, FnArgs::from((row as f64, js_data))) {
eprintln!(
"Node.js: JavaScript Model<T>'s insertRow function threw an exception: {exception}"
);
}
}

fn model_tracker(&self) -> &dyn i_slint_core::model::ModelTracker {
&**self.shared_model_notify
}
Expand Down
60 changes: 60 additions & 0 deletions api/node/typescript/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,38 @@ export abstract class Model<T> implements Iterable<T> {
);
}

/**
* Implementations of this function must add a line to the model with the provided data.
* @param _data new data item to store in a new row.
*/
pushRow(_data: T): void {
console.log(
"pushRow called on a model which does not re-implement this method. This happens when trying to modify a read-only model",
);
}

/**
* Implementations of this function must remove the row at the specified index.
* @param _index index of the row to remove.
*/
removeRow(_index: number): void {
console.log(
"removeRow called on a model which does not re-implement this method. This happens when trying to modify a read-only model",
);
}

/**
* Implementations of this function must add a row at the specified index, pushing all next
* rows to the right.
* @param _index index of the row to insert.
* @param _data new data item to store in a new row.
*/
insertRow(_index: number, _data: T): void {
console.log(
"insertRow called on a model which does not re-implement this method. This happens when trying to modify a read-only model",
);
}

[Symbol.iterator](): Iterator<T> {
return new ModelIterator(this);
}
Expand Down Expand Up @@ -226,6 +258,34 @@ export class ArrayModel<T> extends Model<T> {
this.notifyRowDataChanged(row);
}

/**
* Add a new row to the array backing the model and notifies run-time about the added row.
* @param data new data item to store in a new row.
*/
pushRow(data: T) {
this.#array.push(data);
this.notifyRowAdded(this.#array.length - 1, 1);
}

/**
* Remove a row from the array backing the model and notifies run-time about the removed row.
* @param _index index of the row to remove.
*/
removeRow(_index: number) {
this.#array.splice(_index, 1);
this.notifyRowRemoved(_index, 1);
}

/**
* Insert a new row into the array backing the model at the specified index and notifies run-time about the added row.
* @param _index index at which to insert the new row.
* @param _data data item to store in the new row.
*/
insertRow(_index: number, _data: T) {
this.#array.splice(_index, 0, _data);
this.notifyRowAdded(_index, 1);
}

/**
* Pushes new values to the array that's backing the model and notifies
* the run-time about the added rows.
Expand Down
74 changes: 74 additions & 0 deletions api/python/slint/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,80 @@ impl i_slint_core::model::Model for PyModelShared {
});
}

fn push_row(&self, data: Self::Data) {
Python::try_attach(|py| {
let obj = self.self_ref.borrow();
let Some(obj) = obj.as_ref() else {
eprintln!("Python: Model implementation is lacking self object (in push_row)");
return;
};

let Some(type_collection) = self.type_collection.borrow().as_ref().cloned() else {
eprintln!("Python: Model implementation is lacking type collection (in push_row)");
return;
};

let element_type = self.element_type.borrow().clone();
if let Err(err) =
obj.call_method1(py, "push_row", (type_collection.to_py_value(data, element_type),))
{
crate::handle_unraisable(
py,
"Python: Model implementation of push_row() threw an exception".into(),
err,
);
};
});
}

fn remove_row(&self, row: usize) {
Python::try_attach(|py| {
let obj = self.self_ref.borrow();
let Some(obj) = obj.as_ref() else {
eprintln!("Python: Model implementation is lacking self object (in remove_row)");
return;
};

if let Err(err) = obj.call_method1(py, "remove_row", (row,)) {
crate::handle_unraisable(
py,
"Python: Model implementation of remove_row() threw an exception".into(),
err,
);
};
});
}

fn insert_row(&self, row: usize, data: Self::Data) {
Python::try_attach(|py| {
let obj = self.self_ref.borrow();
let Some(obj) = obj.as_ref() else {
eprintln!("Python: Model implementation is lacking self object (in insert_row)");
return;
};

let Some(type_collection) = self.type_collection.borrow().as_ref().cloned() else {
eprintln!(
"Python: Model implementation is lacking type collection (in insert_row)"
);
return;
};

let element_type = self.element_type.borrow().clone();
if let Err(err) = obj.call_method1(
py,
"insert_row",
(row, type_collection.to_py_value(data, element_type)),
) {
crate::handle_unraisable(
py,
"Python: Model implementation of insert_row() threw an exception".into(),
err,
);
};
});
}

fn model_tracker(&self) -> &dyn i_slint_core::model::ModelTracker {
&self.notify
}
Expand Down
27 changes: 27 additions & 0 deletions api/python/slint/slint/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@ def row_data(self, row: int) -> typing.Optional[T]:
Re-implement this method in a sub-class to provide the data."""
return cast(T, super().row_data(row))

def push_row(self, value: T) -> None:
"""Add a new row to the model with the provided value.
Re-implement this method in a sub-class to handle the change."""
super().push_row(value)

def remove_row(self, row: int) -> None:
"""Remove the row at the given index.
Re-implement this method in a sub-class to handle the change."""
super().remove_row(row)

def insert_row(self, row: int, value: T) -> None:
"""Insert a new row at the given index.
Re-implement this method in a sub-class to handle the change."""
super().insert_row(row, value)

def notify_row_changed(self, row: int) -> None:
"""Call this method from a sub-class to notify the views that a row has changed."""
super().notify_row_changed(row)
Expand Down Expand Up @@ -91,6 +106,18 @@ def set_row_data(self, row: int, value: T) -> None:
self.list[row] = value
super().notify_row_changed(row)

def push_row(self, value: T) -> None:
self.list.append(value)
super().notify_row_added(len(self.list) - 1, 1)

def remove_row(self, row: int) -> None:
del self.list[row]
super().notify_row_removed(row, 1)

def insert_row(self, row: int, value: T) -> None:
self.list.insert(row, value)
self.notify_row_added(row, 1)

def __delitem__(self, key: int | slice) -> None:
if isinstance(key, slice):
start, stop, step = key.indices(len(self.list))
Expand Down
3 changes: 3 additions & 0 deletions api/python/slint/slint/slint.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ class PyModelBase:
def row_count(self) -> int: ...
def row_data(self, row: int) -> typing.Optional[Any]: ...
def set_row_data(self, row: int, value: Any) -> None: ...
def push_row(self, value: Any) -> None: ...
def remove_row(self, row: int) -> None: ...
def insert_row(self, row: int, value: Any) -> None: ...
def notify_row_changed(self, row: int) -> None: ...
def notify_row_removed(self, row: int, count: int) -> None: ...
def notify_row_added(self, row: int, count: int) -> None: ...
Expand Down
37 changes: 37 additions & 0 deletions api/python/slint/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,40 @@ def test_model_writeback() -> None:
assert list(instance.get_property("model")) == [100, 42]
instance.invoke("write-to-model", 0, 25)
assert list(instance.get_property("model")) == [25, 42]


def test_model_modifications() -> None:
compiler = native.Compiler()
compdef = compiler.build_from_source(
"""
export component App {
in-out property<[int]> ints;
public function push-one(value: int) { ints.push(value) }
public function remove-one(index: int) { ints.remove(index) }
public function insert-one(index: int, value: int) { ints.insert(index, value) }
}
""",
Path(""),
).component("App")

assert compdef is not None

instance = compdef.create()
assert instance is not None

model = models.ListModel([1, 2, 3])
instance.set_property("ints", model)

assert instance.get_property("ints").row_count() == 3

instance.invoke("push-one", 10)
assert instance.get_property("ints").row_count() == 4
assert instance.get_property("ints").row_data(3) == 10

instance.invoke("remove-one", 1)
assert instance.get_property("ints").row_count() == 3
assert instance.get_property("ints").row_data(2) == 10

instance.invoke("insert-one", 1, 20)
assert instance.get_property("ints").row_count() == 4
assert instance.get_property("ints").row_data(1) == 20
Loading
Loading