diff --git a/api/sixtyfps-cpp/include/sixtyfps.h b/api/sixtyfps-cpp/include/sixtyfps.h index 3433841bb..97e6838ce 100644 --- a/api/sixtyfps-cpp/include/sixtyfps.h +++ b/api/sixtyfps-cpp/include/sixtyfps.h @@ -262,7 +262,13 @@ using cbindgen_private::solve_grid_layout; using cbindgen_private::solve_path_layout; // models -using ModelPeer = std::weak_ptr; +struct AbstractRepeaterView { + ~AbstractRepeaterView() = default; + virtual void row_added(int index, int count) = 0; + virtual void row_removed(int index, int count) = 0; + virtual void row_changed(int index) = 0; +}; +using ModelPeer = std::weak_ptr; template class Model @@ -291,29 +297,27 @@ protected: /// Notify the views that a specific row was changed void row_changed(int row) { - (void)row; - notify(); + for_each_peers([=](auto peer) { peer->row_changed(row); }); } /// Notify the views that rows were added void row_added(int index, int count) { - (void)(index + count); - notify(); + for_each_peers([=](auto peer) { peer->row_added(index, count); }); } /// Notify the views that rows were removed void row_removed(int index, int count) { - (void)(index + count); - notify(); + for_each_peers([=](auto peer) { peer->row_removed(index, count); }); } private: - void notify() + template + void for_each_peers(const F &f) { peers.erase(std::remove_if(peers.begin(), peers.end(), - [](const auto &p) { + [&](const auto &p) { if (auto pp = p.lock()) { - *pp = true; + f(pp); return false; } return true; @@ -376,12 +380,34 @@ public: template class Repeater { - mutable std::shared_ptr is_dirty; Property>> model; + struct RepeaterInner : AbstractRepeaterView { + enum class State { Clean, Dirty }; + struct ComponentWithState { + State state = State::Dirty; + std::unique_ptr ptr; + }; + std::vector data; + bool is_dirty = true; + + void row_added(int index, int count) override { + is_dirty = true; + data.resize(data.size() + count); + std::rotate(data.begin() + index, data.end() - count, data.end()); + } + void row_changed(int index) override { + is_dirty = true; + data[index].state = State::Dirty; + } + void row_removed(int index, int count) override { + data.erase(data.begin() + index, data.begin() + index + count); + } + }; + public: // FIXME: should be private, but compute_layout uses it. - std::vector> data; + mutable std::shared_ptr inner; template void set_model_binding(F &&binding) const @@ -393,30 +419,37 @@ public: void ensure_updated(const Parent *parent) const { if (model.is_dirty()) { - is_dirty = std::make_shared(true); + inner = std::make_shared(); if (auto m = model.get()) { - m->attach_peer(is_dirty); + m->attach_peer(inner); } } - if (is_dirty && *is_dirty) { - auto &data = const_cast(this)->data; - data.clear(); - auto m = model.get(); - auto count = m->row_count(); - for (auto i = 0; i < count; ++i) { - auto x = std::make_unique(); - x->parent = parent; - x->update_data(i, m->row_data(i)); - data.push_back(std::move(x)); + + if (inner && inner->is_dirty) { + inner->is_dirty = false; + if (auto m = model.get()) { + int count = m->row_count(); + inner->data.resize(count); + for (int i = 0; i < count; ++i) { + auto &c = inner->data[i]; + if (c.state == RepeaterInner::State::Dirty) { + if (!c.ptr) { + c.ptr = std::make_unique(); + c.ptr->parent = parent; + } + c.ptr->update_data(i, m->row_data(i)); + } + } + } else { + inner->data.clear(); } - *is_dirty = false; } } intptr_t visit(TraversalOrder order, private_api::ItemVisitorRefMut visitor) const { - for (std::size_t i = 0; i < data.size(); ++i) { - int index = order == TraversalOrder::BackToFront ? i : data.size() - 1 - i; + for (std::size_t i = 0; i < inner->data.size(); ++i) { + int index = order == TraversalOrder::BackToFront ? i : inner->data.size() - 1 - i; auto ref = item_at(index); if (ref.vtable->visit_children_item(ref, -1, order, visitor) != -1) { return index; @@ -427,14 +460,16 @@ public: VRef item_at(int i) const { - const auto &x = data.at(i); - return { &C::component_type, x.get() }; + const auto &x = inner->data.at(i); + return { &C::component_type, x.ptr.get() }; } void compute_layout() const { - for (auto &x : data) { - x->compute_layout({ &C::component_type, x.get() }); + if (!inner) + return; + for (auto &x : inner->data) { + x.ptr->compute_layout({ &C::component_type, x.ptr.get() }); } } }; diff --git a/sixtyfps_compiler/generator/cpp.rs b/sixtyfps_compiler/generator/cpp.rs index 93c8cb393..c8c745396 100644 --- a/sixtyfps_compiler/generator/cpp.rs +++ b/sixtyfps_compiler/generator/cpp.rs @@ -1483,15 +1483,15 @@ impl<'a> LayoutTreeItem<'a> { let root_element = elem.borrow().base_type.as_component().root_element.clone(); code_stream.push(format!( - " for (auto &&sub_comp : self->repeater_{}.data)", + " for (auto &&sub_comp : self->repeater_{}.inner->data)", elem.borrow().id )); code_stream.push(format!( " items.push_back({});", path_layout_item_data( &root_element, - &format!("sub_comp->{}", root_element.borrow().id), - "sub_comp", + &format!("sub_comp.ptr->{}", root_element.borrow().id), + "sub_comp.ptr", ) )); } else { diff --git a/tests/cases/model.60 b/tests/cases/model.60 index 2ddd26ea0..e27acfe8f 100644 --- a/tests/cases/model.60 +++ b/tests/cases/model.60 @@ -99,9 +99,11 @@ TestCase instance; // there should be nothing there sixtyfps::testing::send_mouse_click(instance, 25., 5.); assert_eq(instance.get_clicked_score(), 0); +assert_eq(instance.get_clicked_internal_state(), 0); sixtyfps::testing::send_mouse_click(instance, 15., 5.); assert_eq(instance.get_clicked_score(), 789000); +assert_eq(instance.get_clicked_internal_state(), 1); using ModelData = std::tuple; sixtyfps::SharedArray array; @@ -113,20 +115,24 @@ instance.set_model(another_model); sixtyfps::testing::send_mouse_click(instance, 25., 5.); assert_eq(instance.get_clicked_score(), 333000); +assert_eq(instance.get_clicked_internal_state(), 1); sixtyfps::testing::send_mouse_click(instance, 15., 5.); assert_eq(instance.get_clicked_score(), 222000); assert_eq(instance.get_clicked_name(), "cruel"); +assert_eq(instance.get_clicked_internal_state(), 1); another_model->push_back({"a4", "!", 444.}); sixtyfps::testing::send_mouse_click(instance, 35., 5.); assert_eq(instance.get_clicked_score(), 444000); assert_eq(instance.get_clicked_name(), "!"); +assert_eq(instance.get_clicked_internal_state(), 1); another_model->set_row_data(1, {"a2", "idyllic", 555.}); sixtyfps::testing::send_mouse_click(instance, 15., 5.); assert_eq(instance.get_clicked_score(), 555000); assert_eq(instance.get_clicked_name(), "idyllic"); +assert_eq(instance.get_clicked_internal_state(), 2); ``` */ \ No newline at end of file