mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-31 15:47:26 +00:00
Change Model::row_data to return an Option<T> (#873)
Change Model::row_data to return an Option<T> (rust) or std::optional<T> (c++) Co-authored-by: Olivier Goffart <olivier@woboq.com> Co-authored-by: Simon Hausmann <hausmann@gmail.com>
This commit is contained in:
parent
e2ec76f9ef
commit
e3c4209b1f
24 changed files with 229 additions and 102 deletions
|
@ -10,6 +10,7 @@ This version changes some APIs in incompatible ways. For details how to migrate
|
|||
- Minimum rust version is now 1.56
|
||||
- C++ compiler requires C++20
|
||||
- In the C++ interpreter API `std::span` is used for callbacks arguments, instead of `sixtyfps::Slice`
|
||||
- `Model::row_data` will now return a `Option<T>` / `std::optional<T>` instead of a plain `T`.
|
||||
|
||||
### Added
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ This guide lists all API incompatible changes between major versions and describ
|
|||
|
||||
## Migrating from Version 0.1.x to 0.2.0
|
||||
|
||||
In the 0.2.x series we have increased the minimum version of C++. You need to have a C++ compiler installed that supports C++ 20 or newer.
|
||||
In version 0.2.0 we have increased the minimum version of C++. You need to have a C++ compiler installed that supports C++ 20 or newer.
|
||||
|
||||
If you are building SixtyFPS from source, you need to make sure that your Rust installation is up-to-date. If you have installed Rust using `rustup`, then you can upgrade to the latest Version of Rust by running `rustup update`.
|
||||
|
||||
|
@ -29,3 +29,28 @@ New code:
|
|||
sixtyfps::Value args[] = { SharedString("Hello"), 42. };
|
||||
instance->invoke_callback("foo", args);
|
||||
```
|
||||
|
||||
#### Models
|
||||
|
||||
`Model::row_data` returns now a `std::optional<ModelData>` and can thus be used with indices that are out of bounds.
|
||||
|
||||
This also means that `Model`s must handle invalid indices and may not crash when a invalid index is passed in.
|
||||
|
||||
Old code:
|
||||
|
||||
```cpp
|
||||
float value = another_model->row_data(2);
|
||||
do_something(value)
|
||||
```
|
||||
|
||||
New code:
|
||||
|
||||
```cpp
|
||||
// `another_model` is a model that contains floats.
|
||||
std::optional<float> value = another_model->row_data(2);
|
||||
if (value.has_value()) {
|
||||
do_something(*value);
|
||||
} else {
|
||||
// row index 2 is out of bounds
|
||||
}
|
||||
```
|
||||
|
|
|
@ -481,7 +481,7 @@ public:
|
|||
virtual int row_count() const = 0;
|
||||
/// Returns the data for a particular row. This function should be called with `row <
|
||||
/// row_count()`.
|
||||
virtual ModelData row_data(int i) const = 0;
|
||||
virtual std::optional<ModelData> row_data(int i) const = 0;
|
||||
/// Sets the data for a particular row.
|
||||
///
|
||||
/// This function should only be called with `row < row_count()`.
|
||||
|
@ -578,11 +578,18 @@ public:
|
|||
{
|
||||
}
|
||||
int row_count() const override { return Count; }
|
||||
ModelData row_data(int i) const override { return data[i]; }
|
||||
std::optional<ModelData> row_data(int i) const override
|
||||
{
|
||||
if (i >= row_count())
|
||||
return {};
|
||||
return data[i];
|
||||
}
|
||||
void set_row_data(int i, const ModelData &value) override
|
||||
{
|
||||
data[i] = value;
|
||||
this->row_changed(i);
|
||||
if (i < row_count()) {
|
||||
data[i] = value;
|
||||
this->row_changed(i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -595,7 +602,12 @@ struct IntModel : Model<int>
|
|||
int data;
|
||||
/// \copydoc Model::row_count
|
||||
int row_count() const override { return data; }
|
||||
int row_data(int value) const override { return value; }
|
||||
std::optional<int> row_data(int value) const override
|
||||
{
|
||||
if (value >= row_count())
|
||||
return {};
|
||||
return value;
|
||||
}
|
||||
};
|
||||
} // namespace private_api
|
||||
|
||||
|
@ -611,11 +623,18 @@ public:
|
|||
/// Constructs a new VectorModel from \a array.
|
||||
VectorModel(std::vector<ModelData> array) : data(std::move(array)) { }
|
||||
int row_count() const override { return int(data.size()); }
|
||||
ModelData row_data(int i) const override { return data[i]; }
|
||||
std::optional<ModelData> row_data(int i) const override
|
||||
{
|
||||
if (i >= row_count())
|
||||
return {};
|
||||
return std::optional<ModelData> { data[i] };
|
||||
}
|
||||
void set_row_data(int i, const ModelData &value) override
|
||||
{
|
||||
data[i] = value;
|
||||
this->row_changed(i);
|
||||
if (i < row_count()) {
|
||||
data[i] = value;
|
||||
this->row_changed(i);
|
||||
}
|
||||
}
|
||||
|
||||
/// Append a new row with the given value
|
||||
|
@ -711,7 +730,7 @@ public:
|
|||
c.ptr = C::create(parent);
|
||||
}
|
||||
if (c.state == RepeaterInner::State::Dirty) {
|
||||
(*c.ptr)->update_data(i, m->row_data(i));
|
||||
(*c.ptr)->update_data(i, *m->row_data(i));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -776,11 +795,13 @@ public:
|
|||
std::abort();
|
||||
}
|
||||
if (auto m = model.get()) {
|
||||
m->set_row_data(row, data);
|
||||
if (inner && inner->is_dirty.get()) {
|
||||
auto &c = inner->data[row];
|
||||
if (c.state == RepeaterInner::State::Dirty && c.ptr) {
|
||||
(*c.ptr)->update_data(row, m->row_data(row));
|
||||
if (row < m->row_count()) {
|
||||
m->set_row_data(row, data);
|
||||
if (inner && inner->is_dirty.get()) {
|
||||
auto &c = inner->data[row];
|
||||
if (c.state == RepeaterInner::State::Dirty && c.ptr) {
|
||||
(*c.ptr)->update_data(row, *m->row_data(row));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -462,9 +462,13 @@ inline Value::Value(const std::shared_ptr<sixtyfps::Model<Value>> &model)
|
|||
return reinterpret_cast<ModelWrapper *>(self.instance)->model->row_count();
|
||||
};
|
||||
auto row_data = [](VRef<ModelAdaptorVTable> self, uintptr_t row, ValueOpaque *out) {
|
||||
Value v = reinterpret_cast<ModelWrapper *>(self.instance)->model->row_data(int(row));
|
||||
*out = v.inner;
|
||||
cbindgen_private::sixtyfps_interpreter_value_new(&v.inner);
|
||||
std::optional<Value> v = reinterpret_cast<ModelWrapper *>(self.instance)->model->row_data(int(row));
|
||||
if (v.has_value()) {
|
||||
*out = v->inner;
|
||||
cbindgen_private::sixtyfps_interpreter_value_new(&v->inner);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
auto set_row_data = [](VRef<ModelAdaptorVTable> self, uintptr_t row, const ValueOpaque *value) {
|
||||
Value v = *reinterpret_cast<const Value *>(value);
|
||||
|
|
|
@ -64,22 +64,27 @@ impl Model for JsModel {
|
|||
r.get()
|
||||
}
|
||||
|
||||
fn row_data(&self, row: usize) -> Self::Data {
|
||||
let r = Cell::new(sixtyfps_interpreter::Value::default());
|
||||
crate::run_with_global_context(&|cx, persistent_context| {
|
||||
let row = JsNumber::new(cx, row as f64);
|
||||
let obj = self.get_object(cx, persistent_context).unwrap();
|
||||
let _ = obj
|
||||
.get(cx, "rowData")
|
||||
.ok()
|
||||
.and_then(|func| func.downcast::<JsFunction>().ok())
|
||||
.and_then(|func| func.call(cx, obj, std::iter::once(row)).ok())
|
||||
.and_then(|res| {
|
||||
crate::to_eval_value(res, self.data_type.clone(), cx, persistent_context).ok()
|
||||
})
|
||||
.map(|res| r.set(res));
|
||||
});
|
||||
r.into_inner()
|
||||
fn row_data(&self, row: usize) -> Option<Self::Data> {
|
||||
if row >= self.row_count() {
|
||||
None
|
||||
} else {
|
||||
let r = Cell::new(sixtyfps_interpreter::Value::default());
|
||||
crate::run_with_global_context(&|cx, persistent_context| {
|
||||
let row = JsNumber::new(cx, row as f64);
|
||||
let obj = self.get_object(cx, persistent_context).unwrap();
|
||||
let _ = obj
|
||||
.get(cx, "rowData")
|
||||
.ok()
|
||||
.and_then(|func| func.downcast::<JsFunction>().ok())
|
||||
.and_then(|func| func.call(cx, obj, std::iter::once(row)).ok())
|
||||
.and_then(|res| {
|
||||
crate::to_eval_value(res, self.data_type.clone(), cx, persistent_context)
|
||||
.ok()
|
||||
})
|
||||
.map(|res| r.set(res));
|
||||
});
|
||||
Some(r.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
fn model_tracker(&self) -> &dyn sixtyfps_corelib::model::ModelTracker {
|
||||
|
|
29
api/sixtyfps-rs/migration.md
Normal file
29
api/sixtyfps-rs/migration.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Migrating from Older Versions
|
||||
|
||||
The Rust library is versioned according to the principles of [Semantic Versioning](https://semver.org). We define that the left-most non-zero component of the version is the major version, followed by the minor and optionally patch version. That means releases in the "0.y.z" series treat changes in "y" as a major release, which can contain incompatible API changes, while changes in just "z" are minor. For example the release 0.1.6 is fully backwards compatible to 0.1.5, but it contains new functionality. The release 0.2.0 however is a new major version compared to 0.1.x and may contain API incompatible changes.
|
||||
|
||||
This guide lists all API incompatible changes between major versions and describes how you can migrate your application's source code.
|
||||
|
||||
## Migrating from Version 0.1.x to 0.2.0
|
||||
|
||||
In 0.2.0 we have increased the minimum version of rust. You need to have rust compiler version >= 1.56 installed.
|
||||
|
||||
### Rust API
|
||||
|
||||
#### Models
|
||||
|
||||
`Model::row_data` now returns an `Option<T>` instead of a simple `T`.
|
||||
|
||||
This implies that `Model`s must handle invalid indices and may not panic when they encounter one.
|
||||
|
||||
Old code:
|
||||
|
||||
```rust
|
||||
let row_five = model.row_data(5);
|
||||
```
|
||||
|
||||
New code:
|
||||
|
||||
```rust,ignore
|
||||
let row_five = model.row_data(5).unwrap_or_default();
|
||||
```
|
|
@ -15,8 +15,8 @@ int main()
|
|||
std::vector<TileData> new_tiles;
|
||||
new_tiles.reserve(old_tiles->row_count() * 2);
|
||||
for (int i = 0; i < old_tiles->row_count(); ++i) {
|
||||
new_tiles.push_back(old_tiles->row_data(i));
|
||||
new_tiles.push_back(old_tiles->row_data(i));
|
||||
new_tiles.push_back(*old_tiles->row_data(i));
|
||||
new_tiles.push_back(*old_tiles->row_data(i));
|
||||
}
|
||||
std::default_random_engine rng {};
|
||||
std::shuffle(new_tiles.begin(), new_tiles.end(), rng);
|
||||
|
@ -33,7 +33,7 @@ int main()
|
|||
int first_visible_index = -1;
|
||||
TileData first_visible_tile;
|
||||
for (int i = 0; i < tiles_model->row_count(); ++i) {
|
||||
auto tile = tiles_model->row_data(i);
|
||||
auto tile = *tiles_model->row_data(i);
|
||||
if (!tile.image_visible || tile.solved)
|
||||
continue;
|
||||
if (first_visible_index == -1) {
|
||||
|
|
|
@ -16,8 +16,8 @@ int main()
|
|||
std::vector<TileData> new_tiles;
|
||||
new_tiles.reserve(old_tiles->row_count() * 2);
|
||||
for (int i = 0; i < old_tiles->row_count(); ++i) {
|
||||
new_tiles.push_back(old_tiles->row_data(i));
|
||||
new_tiles.push_back(old_tiles->row_data(i));
|
||||
new_tiles.push_back(*old_tiles->row_data(i));
|
||||
new_tiles.push_back(*old_tiles->row_data(i));
|
||||
}
|
||||
std::default_random_engine rng {};
|
||||
std::shuffle(new_tiles.begin(), new_tiles.end(), rng);
|
||||
|
|
|
@ -64,8 +64,8 @@ impl sixtyfps::Model for Filters {
|
|||
self.0.len()
|
||||
}
|
||||
|
||||
fn row_data(&self, row: usize) -> Self::Data {
|
||||
self.0[row].name.clone()
|
||||
fn row_data(&self, row: usize) -> Option<Self::Data> {
|
||||
self.0.get(row).map(|x| x.name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ int main()
|
|||
std::vector<TileData> new_tiles;
|
||||
new_tiles.reserve(old_tiles->row_count() * 2);
|
||||
for (int i = 0; i < old_tiles->row_count(); ++i) {
|
||||
new_tiles.push_back(old_tiles->row_data(i));
|
||||
new_tiles.push_back(old_tiles->row_data(i));
|
||||
new_tiles.push_back(*old_tiles->row_data(i));
|
||||
new_tiles.push_back(*old_tiles->row_data(i));
|
||||
}
|
||||
std::default_random_engine rng{};
|
||||
std::shuffle(std::begin(new_tiles), std::end(new_tiles), rng);
|
||||
|
@ -25,7 +25,7 @@ int main()
|
|||
int first_visible_index = -1;
|
||||
TileData first_visible_tile;
|
||||
for (int i = 0; i < tiles_model->row_count(); ++i) {
|
||||
auto tile = tiles_model->row_data(i);
|
||||
auto tile = *tiles_model->row_data(i);
|
||||
if (!tile.image_visible || tile.solved)
|
||||
continue;
|
||||
if (first_visible_index == -1) {
|
||||
|
|
|
@ -8,7 +8,11 @@
|
|||
struct InkLevelModel : sixtyfps::Model<InkLevel>
|
||||
{
|
||||
int row_count() const override { return m_data.size(); }
|
||||
InkLevel row_data(int i) const override { return m_data[i]; }
|
||||
std::optional<InkLevel> row_data(int i) const override {
|
||||
if (i < row_count())
|
||||
return { m_data[i] };
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<InkLevel> m_data = { { sixtyfps::Color::from_rgb_uint8(255, 255, 0), 0.9 },
|
||||
{ sixtyfps::Color::from_rgb_uint8(0, 255, 255), 0.5 },
|
||||
|
@ -26,7 +30,7 @@ int main()
|
|||
auto printer_queue = std::make_shared<sixtyfps::VectorModel<PrinterQueueItem>>();
|
||||
auto default_queue = printer_demo->global<PrinterQueue>().get_printer_queue();
|
||||
for (int i = 0; i < default_queue->row_count(); ++i) {
|
||||
printer_queue->push_back(default_queue->row_data(i));
|
||||
printer_queue->push_back(*default_queue->row_data(i));
|
||||
}
|
||||
printer_demo->global<PrinterQueue>().set_printer_queue(printer_queue);
|
||||
|
||||
|
@ -50,7 +54,7 @@ int main()
|
|||
|
||||
sixtyfps::Timer printer_queue_progress_timer(std::chrono::seconds(1), [=]() {
|
||||
if (printer_queue->row_count() > 0) {
|
||||
auto top_item = printer_queue->row_data(0);
|
||||
auto top_item = *printer_queue->row_data(0);
|
||||
top_item.progress += 1;
|
||||
if (top_item.progress > 100) {
|
||||
printer_queue->erase(0);
|
||||
|
|
|
@ -10,7 +10,11 @@ using sixtyfps::interpreter::Value;
|
|||
struct InkLevelModel : sixtyfps::Model<Value>
|
||||
{
|
||||
int row_count() const override { return m_data.size(); }
|
||||
Value row_data(int i) const override { return m_data[i]; }
|
||||
std::optional<Value> row_data(int i) const override {
|
||||
if (i < m_data.size())
|
||||
return { m_data[i] };
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
static Value make_inklevel_value(sixtyfps::Color color, float level)
|
||||
|
@ -93,7 +97,7 @@ int main()
|
|||
|
||||
sixtyfps::Timer printer_queue_progress_timer(std::chrono::seconds(1), [=]() {
|
||||
if (printer_queue->row_count() > 0) {
|
||||
auto top_item = *printer_queue->row_data(0).to_struct();
|
||||
auto top_item = *(*printer_queue->row_data(0)).to_struct();
|
||||
auto progress = *top_item.get_field("progress")->to_number() + 1.;
|
||||
top_item.set_field("progress", progress);
|
||||
top_item.set_field("status", sixtyfps::SharedString("PRINTING"));
|
||||
|
|
|
@ -83,7 +83,7 @@ pub fn main() {
|
|||
move || {
|
||||
if let Some(printer_queue) = printer_queue_weak.upgrade() {
|
||||
if printer_queue.data.row_count() > 0 {
|
||||
let mut top_item = printer_queue.data.row_data(0);
|
||||
let mut top_item = printer_queue.data.row_data(0).unwrap();
|
||||
top_item.progress += 1;
|
||||
top_item.status = "PRINTING".into();
|
||||
if top_item.progress > 100 {
|
||||
|
@ -91,7 +91,7 @@ pub fn main() {
|
|||
if printer_queue.data.row_count() == 0 {
|
||||
return;
|
||||
}
|
||||
top_item = printer_queue.data.row_data(0);
|
||||
top_item = printer_queue.data.row_data(0).unwrap();
|
||||
}
|
||||
printer_queue.data.set_row_data(0, top_item);
|
||||
} else {
|
||||
|
|
|
@ -6,7 +6,12 @@
|
|||
struct InkLevelModel : sixtyfps::Model<InkLevel>
|
||||
{
|
||||
int row_count() const override { return m_data.size(); }
|
||||
InkLevel row_data(int i) const override { return m_data[i]; }
|
||||
std::optional<InkLevel> row_data(int i) const override
|
||||
{
|
||||
if (i < row_count())
|
||||
return { m_data[i] };
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<InkLevel> m_data = { { sixtyfps::Color::from_rgb_uint8(255, 255, 0), 0.9 },
|
||||
{ sixtyfps::Color::from_rgb_uint8(0, 255, 255), 0.5 },
|
||||
|
@ -21,7 +26,7 @@ int main()
|
|||
printer_demo->on_quit([] { std::exit(0); });
|
||||
|
||||
printer_demo->on_fax_number_erase([printer_demo = sixtyfps::ComponentWeakHandle(printer_demo)] {
|
||||
std::string fax_number{(*printer_demo.lock())->get_fax_number()};
|
||||
std::string fax_number { (*printer_demo.lock())->get_fax_number() };
|
||||
fax_number.pop_back();
|
||||
(*printer_demo.lock())->set_fax_number(fax_number.data());
|
||||
});
|
||||
|
|
|
@ -74,7 +74,7 @@ impl AppState {
|
|||
}
|
||||
|
||||
fn piece_clicked(&mut self, p: i8) -> bool {
|
||||
let piece = self.pieces.row_data(p as usize);
|
||||
let piece = self.pieces.row_data(p as usize).unwrap_or_default();
|
||||
assert_eq!(self.positions[(piece.pos_x * 4 + piece.pos_y) as usize], p);
|
||||
|
||||
// find the coordinate of the hole.
|
||||
|
@ -148,7 +148,7 @@ impl AppState {
|
|||
|
||||
let mut has_animation = false;
|
||||
for idx in 0..15 {
|
||||
let mut p = self.pieces.row_data(idx);
|
||||
let mut p = self.pieces.row_data(idx).unwrap_or_default();
|
||||
let ax = spring_animation(&mut p.offset_x, &mut self.speed_for_kick_animation[idx].0);
|
||||
let ay = spring_animation(&mut p.offset_y, &mut self.speed_for_kick_animation[idx].1);
|
||||
if ax || ay {
|
||||
|
|
|
@ -27,7 +27,7 @@ int main()
|
|||
int offset = 0;
|
||||
int count = todo_model->row_count();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (todo_model->row_data(i - offset).checked) {
|
||||
if (todo_model->row_data(i - offset)->checked) {
|
||||
todo_model->erase(i - offset);
|
||||
offset += 1;
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ pub fn main() {
|
|||
move || {
|
||||
let mut offset = 0;
|
||||
for i in 0..todo_model.row_count() {
|
||||
if todo_model.row_data(i - offset).checked {
|
||||
if todo_model.row_data(i - offset).unwrap().checked {
|
||||
todo_model.remove(i - offset);
|
||||
offset += 1;
|
||||
}
|
||||
|
|
|
@ -1768,7 +1768,8 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String
|
|||
_ => panic!("Expression::ObjectAccess's base expression is not an Object type"),
|
||||
},
|
||||
Expression::ArrayIndex { array, index } => {
|
||||
format!("[&](const auto &model, const auto &index){{ model->track_row_data_changes(index); return model->row_data(index); }}({}, {})", compile_expression(array, ctx), compile_expression(index, ctx))
|
||||
format!("[&]<typename D>(const std::shared_ptr<sixtyfps::Model<D>> &model, const auto &index) -> D {{ model->track_row_data_changes(index); if (const auto v = model->row_data(index)) return *v; return D(); }}({}, {})",
|
||||
compile_expression(array, ctx), compile_expression(index, ctx))
|
||||
},
|
||||
Expression::Cast { from, to } => {
|
||||
let f = compile_expression(&*from, ctx);
|
||||
|
|
|
@ -1398,7 +1398,7 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream
|
|||
quote!(match &#base_e { x => {
|
||||
let index = (#index_e) as usize;
|
||||
x.model_tracker().track_row_data_changes(index);
|
||||
x.row_data(index)
|
||||
x.row_data(index).unwrap_or_default()
|
||||
}})
|
||||
}
|
||||
Expression::CodeBlock(sub) => {
|
||||
|
|
|
@ -170,8 +170,8 @@ impl ModelTracker for ModelNotify {
|
|||
/// self.array.borrow().len()
|
||||
/// }
|
||||
///
|
||||
/// fn row_data(&self, row: usize) -> Self::Data {
|
||||
/// self.array.borrow()[row].clone()
|
||||
/// fn row_data(&self, row: usize) -> Option<Self::Data> {
|
||||
/// self.array.borrow().get(row).cloned()
|
||||
/// }
|
||||
///
|
||||
/// fn set_row_data(&self, row: usize, data: Self::Data) {
|
||||
|
@ -212,7 +212,7 @@ pub trait Model {
|
|||
/// The amount of row in the model
|
||||
fn row_count(&self) -> usize;
|
||||
/// Returns the data for a particular row. This function should be called with `row < row_count()`.
|
||||
fn row_data(&self, row: usize) -> Self::Data;
|
||||
fn row_data(&self, row: usize) -> Option<Self::Data>;
|
||||
/// Sets the data for a particular row.
|
||||
///
|
||||
/// This function should be called with `row < row_count()`, otherwise the implementation can panic.
|
||||
|
@ -261,7 +261,7 @@ pub trait Model {
|
|||
/// let handle = ModelHandle::from(vec_model as Rc<dyn Model<Data = i32>>);
|
||||
/// // later:
|
||||
/// handle.as_any().downcast_ref::<VecModel<i32>>().unwrap().push(4);
|
||||
/// assert_eq!(handle.row_data(3), 4);
|
||||
/// assert_eq!(handle.row_data(3).unwrap(), 4);
|
||||
/// ```
|
||||
///
|
||||
/// Note: the default implementation returns nothing interesting. this method should be
|
||||
|
@ -285,13 +285,11 @@ impl<'a, T> Iterator for ModelIterator<'a, T> {
|
|||
type Item = T;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let row = self.row;
|
||||
if self.row < self.model.row_count() {
|
||||
let row = self.row;
|
||||
self.row += 1;
|
||||
Some(self.model.row_data(row))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
self.model.row_data(row)
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
|
@ -351,13 +349,15 @@ impl<T: Clone + 'static> Model for VecModel<T> {
|
|||
self.array.borrow().len()
|
||||
}
|
||||
|
||||
fn row_data(&self, row: usize) -> Self::Data {
|
||||
self.array.borrow()[row].clone()
|
||||
fn row_data(&self, row: usize) -> Option<Self::Data> {
|
||||
self.array.borrow().get(row).cloned()
|
||||
}
|
||||
|
||||
fn set_row_data(&self, row: usize, data: Self::Data) {
|
||||
self.array.borrow_mut()[row] = data;
|
||||
self.notify.row_changed(row);
|
||||
if row < self.row_count() {
|
||||
self.array.borrow_mut()[row] = data;
|
||||
self.notify.row_changed(row);
|
||||
}
|
||||
}
|
||||
|
||||
fn model_tracker(&self) -> &dyn ModelTracker {
|
||||
|
@ -376,8 +376,8 @@ impl Model for usize {
|
|||
*self
|
||||
}
|
||||
|
||||
fn row_data(&self, row: usize) -> Self::Data {
|
||||
row as i32
|
||||
fn row_data(&self, row: usize) -> Option<Self::Data> {
|
||||
(row < self.row_count()).then(|| row as i32)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
|
@ -396,7 +396,9 @@ impl Model for bool {
|
|||
}
|
||||
}
|
||||
|
||||
fn row_data(&self, _row: usize) -> Self::Data {}
|
||||
fn row_data(&self, row: usize) -> Option<Self::Data> {
|
||||
(row < self.row_count()).then(|| ())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
|
@ -459,12 +461,14 @@ impl<T> Model for ModelHandle<T> {
|
|||
self.0.as_ref().map_or(0, |model| model.row_count())
|
||||
}
|
||||
|
||||
fn row_data(&self, row: usize) -> Self::Data {
|
||||
self.0.as_ref().unwrap().row_data(row)
|
||||
fn row_data(&self, row: usize) -> Option<Self::Data> {
|
||||
self.0.as_ref().and_then(|model| model.row_data(row))
|
||||
}
|
||||
|
||||
fn set_row_data(&self, row: usize, data: Self::Data) {
|
||||
self.0.as_ref().unwrap().set_row_data(row, data)
|
||||
if let Some(model) = self.0.as_ref() {
|
||||
model.set_row_data(row, data)
|
||||
}
|
||||
}
|
||||
|
||||
fn attach_peer(&self, peer: ModelPeer) {
|
||||
|
@ -692,7 +696,7 @@ impl<C: RepeatedComponent + 'static> Repeater<C> {
|
|||
created = true;
|
||||
c.1 = Some(init());
|
||||
}
|
||||
c.1.as_ref().unwrap().update(i + offset, model.row_data(i + offset));
|
||||
c.1.as_ref().unwrap().update(i + offset, model.row_data(i + offset).unwrap());
|
||||
c.0 = RepeatedComponentState::Clean;
|
||||
}
|
||||
}
|
||||
|
@ -856,7 +860,7 @@ impl<C: RepeatedComponent + 'static> Repeater<C> {
|
|||
if let Some(c) = self.inner.borrow_mut().components.get_mut(row) {
|
||||
if c.0 == RepeatedComponentState::Dirty {
|
||||
if let Some(comp) = c.1.as_ref() {
|
||||
comp.update(row, model.row_data(row));
|
||||
comp.update(row, model.row_data(row).unwrap());
|
||||
c.0 = RepeatedComponentState::Clean;
|
||||
}
|
||||
}
|
||||
|
@ -980,7 +984,7 @@ fn test_data_tracking() {
|
|||
assert_eq!(
|
||||
tracker.as_ref().evaluate(|| {
|
||||
handle.model_tracker().track_row_data_changes(1);
|
||||
handle.row_data(1)
|
||||
handle.row_data(1).unwrap()
|
||||
}),
|
||||
1
|
||||
);
|
||||
|
@ -994,7 +998,7 @@ fn test_data_tracking() {
|
|||
assert_eq!(
|
||||
tracker.as_ref().evaluate(|| {
|
||||
handle.model_tracker().track_row_data_changes(1);
|
||||
handle.row_data(1)
|
||||
handle.row_data(1).unwrap()
|
||||
}),
|
||||
100
|
||||
);
|
||||
|
@ -1008,7 +1012,7 @@ fn test_data_tracking() {
|
|||
assert_eq!(
|
||||
tracker.as_ref().evaluate(|| {
|
||||
handle.model_tracker().track_row_data_changes(1);
|
||||
handle.row_data(1)
|
||||
handle.row_data(1).unwrap()
|
||||
}),
|
||||
100
|
||||
);
|
||||
|
|
|
@ -180,9 +180,9 @@ pub fn eval_expression(expression: &Expression, local_context: &mut EvalLocalCon
|
|||
(Value::Model(model), Value::Number(index)) => {
|
||||
if (index as usize) < model.row_count() {
|
||||
model.model_tracker().track_row_data_changes(index as usize);
|
||||
model.row_data(index as usize)
|
||||
model.row_data(index as usize).unwrap_or_else(|| default_value_for_type(&expression.ty()))
|
||||
} else {
|
||||
Value::Void
|
||||
default_value_for_type(&expression.ty())
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
@ -747,7 +747,14 @@ fn eval_assignment(lhs: &Expression, op: char, rhs: Value, local_context: &mut E
|
|||
if op == '=' {
|
||||
model.set_row_data(index, rhs);
|
||||
} else {
|
||||
model.set_row_data(index, eval(model.row_data(index)));
|
||||
model.set_row_data(
|
||||
index,
|
||||
eval(
|
||||
model
|
||||
.row_data(index)
|
||||
.unwrap_or_else(|| default_value_for_type(&lhs.ty())),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -568,7 +568,8 @@ pub unsafe extern "C" fn sixtyfps_interpreter_component_instance_create(
|
|||
#[repr(C)]
|
||||
pub struct ModelAdaptorVTable {
|
||||
pub row_count: extern "C" fn(VRef<ModelAdaptorVTable>) -> usize,
|
||||
pub row_data: unsafe extern "C" fn(VRef<ModelAdaptorVTable>, row: usize, out: *mut ValueOpaque),
|
||||
pub row_data:
|
||||
unsafe extern "C" fn(VRef<ModelAdaptorVTable>, row: usize, out: *mut ValueOpaque) -> bool,
|
||||
pub set_row_data: extern "C" fn(VRef<ModelAdaptorVTable>, row: usize, value: &ValueOpaque),
|
||||
pub get_notify: extern "C" fn(VRef<ModelAdaptorVTable>) -> &ModelNotifyOpaque,
|
||||
pub drop: extern "C" fn(VRefMut<ModelAdaptorVTable>),
|
||||
|
@ -582,11 +583,14 @@ impl Model for ModelAdaptorWrapper {
|
|||
self.0.row_count()
|
||||
}
|
||||
|
||||
fn row_data(&self, row: usize) -> Value {
|
||||
fn row_data(&self, row: usize) -> Option<Value> {
|
||||
unsafe {
|
||||
let mut v = std::mem::MaybeUninit::<Value>::uninit();
|
||||
self.0.row_data(row, v.as_mut_ptr() as *mut ValueOpaque);
|
||||
v.assume_init()
|
||||
if self.0.row_data(row, v.as_mut_ptr() as *mut ValueOpaque) {
|
||||
Some(v.assume_init())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -62,13 +62,17 @@ impl Model for ValueModel {
|
|||
}
|
||||
}
|
||||
|
||||
fn row_data(&self, row: usize) -> Self::Data {
|
||||
match &*self.value.borrow() {
|
||||
Value::Bool(_) => Value::Void,
|
||||
Value::Number(_) => Value::Number(row as _),
|
||||
Value::Array(a) => a[row].clone(),
|
||||
Value::Model(model_ptr) => model_ptr.row_data(row),
|
||||
x => panic!("Invalid model {:?}", x),
|
||||
fn row_data(&self, row: usize) -> Option<Self::Data> {
|
||||
if row >= self.row_count() {
|
||||
None
|
||||
} else {
|
||||
Some(match &*self.value.borrow() {
|
||||
Value::Bool(_) => Value::Void,
|
||||
Value::Number(_) => Value::Number(row as _),
|
||||
Value::Array(a) => a[row].clone(),
|
||||
Value::Model(model_ptr) => model_ptr.row_data(row)?,
|
||||
x => panic!("Invalid model {:?}", x),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -61,13 +61,16 @@ instance.set_model(sixtyfps::ModelHandle::new(another_model.clone()));
|
|||
|
||||
sixtyfps::testing::send_mouse_click(&instance, 25., 5.);
|
||||
assert_eq!(instance.get_clicked_score(), 336);
|
||||
assert_eq!(another_model.row_data(2).2, 336.);
|
||||
assert_eq!(another_model.row_data(2).unwrap().2, 336.);
|
||||
|
||||
instance.invoke_manual_score_update(2, 100);
|
||||
sixtyfps::testing::send_mouse_click(&instance, 25., 5.);
|
||||
assert_eq!(instance.get_clicked_score(), 439);
|
||||
assert_eq!(another_model.row_data(2).2, 439.);
|
||||
assert_eq!(another_model.row_data(2).1, sixtyfps::SharedString::from("world"));
|
||||
assert_eq!(another_model.row_data(2).unwrap().2, 439.);
|
||||
assert_eq!(another_model.row_data(2).unwrap().1, sixtyfps::SharedString::from("world"));
|
||||
|
||||
instance.invoke_manual_score_update(200, 100);
|
||||
assert_eq!(another_model.row_count(), 3);
|
||||
```
|
||||
|
||||
```cpp
|
||||
|
@ -86,13 +89,16 @@ instance.set_model(another_model);
|
|||
|
||||
sixtyfps::testing::send_mouse_click(&instance, 25., 5.);
|
||||
assert_eq(instance.get_clicked_score(), 336);
|
||||
assert_eq(std::get<2>(another_model->row_data(2)), 336.);
|
||||
assert_eq(std::get<2>(*another_model->row_data(2)), 336.);
|
||||
|
||||
instance.invoke_manual_score_update(2, 100);
|
||||
sixtyfps::testing::send_mouse_click(&instance, 25., 5.);
|
||||
assert_eq(instance.get_clicked_score(), 439);
|
||||
assert_eq(std::get<2>(another_model->row_data(2)), 439.);
|
||||
assert_eq(std::get<1>(another_model->row_data(2)), "world");
|
||||
assert_eq(std::get<2>(*another_model->row_data(2)), 439.);
|
||||
assert_eq(std::get<1>(*another_model->row_data(2)), "world");
|
||||
|
||||
instance.invoke_manual_score_update(200, 100);
|
||||
assert_eq(another_model->row_count(), 3);
|
||||
```
|
||||
|
||||
|
||||
|
@ -117,6 +123,9 @@ instance.send_mouse_click(25., 5.);
|
|||
assert.equal(instance.clicked_score, 439);
|
||||
assert.equal(another_model.rowData(2).score, 439.);
|
||||
assert.equal(another_model.rowData(2).name, "world");
|
||||
|
||||
instance.manual_score_update(200, 100); // should do nothing
|
||||
assert.equal(another_model.length, 3);
|
||||
```
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue