1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4#pragma once
5
6#include "slint_item_tree.h"
7
8#include <algorithm>
9#include <functional>
10#include <memory>
11#include <optional>
12
13namespace slint {
14
15namespace private_api {
16
17struct ModelChangeListener
18{
19 virtual ~ModelChangeListener() = default;
20 virtual void row_added(size_t index, size_t count) = 0;
21 virtual void row_removed(size_t index, size_t count) = 0;
22 virtual void row_changed(size_t index) = 0;
23 virtual void reset() = 0;
24};
25using ModelPeer = std::weak_ptr<ModelChangeListener>;
26
27template<typename M>
28auto access_array_index(const std::shared_ptr<M> &model, std::ptrdiff_t index)
29{
30 if (!model || index < 0) {
31 return decltype(*model->row_data_tracked(index)) {};
32 } else if (const auto v = model->row_data_tracked(index)) {
33 return *v;
34 } else {
35 return decltype(*v) {};
36 }
37}
38
39template<typename M>
40long int model_length(const std::shared_ptr<M> &model)
41{
42 if (!model) {
43 return 0;
44 } else {
45 model->track_row_count_changes();
46 return model->row_count();
47 }
48}
49
50} // namespace private_api
51
52/// \rst
53/// A Model is providing Data for Slint |Models|_ or |ListView|_ elements of the
54/// :code:`.slint` language
55/// \endrst
56///
57/// This is typically used in a `std::shared_ptr<slint::Model>`.
58/// Model is an abstract class and you can derive from it to provide your own data model,
59/// or use one of the provided models such as `slint::VectorModel`
60///
61/// An implementation of the Model can provide data to slint by re-implementing the `row_count` and
62/// `row_data` functions. It is the responsibility of the Model implementation to call the
63/// `Model::notify_row_changed()`, `Model::notify_row_added()`, `Model::notify_row_removed()`, or
64/// `Model::notify_reset()` functions when the underlying data changes.
65///
66/// Note that the Model is not thread-safe. All Model operations need to be done in the main thread.
67/// If you need to update the model data from another thread, use the
68/// `slint::invoke_from_event_loop()` function to send the data to the main thread and update the
69/// model.
70template<typename ModelData>
71class Model
72{
73public:
74 virtual ~Model() = default;
75 Model() = default;
76 Model(const Model &) = delete;
77 Model &operator=(const Model &) = delete;
78
79 /// The amount of row in the model
80 virtual size_t row_count() const = 0;
81 /// Returns the data for a particular row. This function should be called with `row <
82 /// row_count()`.
83 virtual std::optional<ModelData> row_data(size_t i) const = 0;
84 /// Sets the data for a particular row.
85 ///
86 /// This function should only be called with `row < row_count()`.
87 ///
88 /// If the model cannot support data changes, then it is ok to do nothing.
89 /// The default implementation will print a warning to stderr.
90 ///
91 /// If the model can update the data, it should also call `row_changed`
92 virtual void set_row_data(size_t, const ModelData &)
93 {
94#ifndef SLINT_FEATURE_FREESTANDING
95 std::cerr << "Model::set_row_data was called on a read-only model" << std::endl;
96#endif
97 };
98
99 /// \private
100 /// Internal function called by the view to register itself
101 void attach_peer(private_api::ModelPeer p) { peers.push_back(x: std::move(p)); }
102
103 /// \private
104 /// Internal function called from within bindings to register with the currently
105 /// evaluating dependency and get notified when this model's row count changes.
106 void track_row_count_changes() const { model_row_count_dirty_property.get(); }
107
108 /// \private
109 /// Internal function called from within bindings to register with the currently
110 /// evaluating dependency and get notified when this model's row data changes.
111 void track_row_data_changes(size_t row) const
112 {
113 auto it = std::lower_bound(first: tracked_rows.begin(), last: tracked_rows.end(), val: row);
114 if (it == tracked_rows.end() || row < *it) {
115 tracked_rows.insert(position: it, x: row);
116 }
117 model_row_data_dirty_property.get();
118 }
119
120 /// \private
121 /// Convenience function that calls `track_row_data_changes` before returning `row_data`
122 std::optional<ModelData> row_data_tracked(size_t row) const
123 {
124 track_row_data_changes(row);
125 return row_data(i: row);
126 }
127
128protected:
129 /// Notify the views that a specific row was changed
130 ///
131 /// Your model implementation should call this function after the data of a row changes.
132 void notify_row_changed(size_t row)
133 {
134 private_api::assert_main_thread();
135 if (std::binary_search(first: tracked_rows.begin(), last: tracked_rows.end(), val: row)) {
136 model_row_data_dirty_property.mark_dirty();
137 }
138 for_each_peers([=](auto peer) { peer->row_changed(row); });
139 }
140 /// Notify the views that rows were added
141 ///
142 /// Your model implementation should call this function after the row were added.
143 void notify_row_added(size_t index, size_t count)
144 {
145 private_api::assert_main_thread();
146 model_row_count_dirty_property.mark_dirty();
147 tracked_rows.clear();
148 model_row_data_dirty_property.mark_dirty();
149 for_each_peers([=](auto peer) { peer->row_added(index, count); });
150 }
151 /// Notify the views that rows were removed
152 ///
153 /// Your model implementation should call this function after the row were removed.
154 void notify_row_removed(size_t index, size_t count)
155 {
156 private_api::assert_main_thread();
157 model_row_count_dirty_property.mark_dirty();
158 tracked_rows.clear();
159 model_row_data_dirty_property.mark_dirty();
160 for_each_peers([=](auto peer) { peer->row_removed(index, count); });
161 }
162
163 /// Notify the views that the model has been changed and that everything needs to be reloaded
164 ///
165 /// Your model implementation should call this function after the model has been changed.
166 void notify_reset()
167 {
168 private_api::assert_main_thread();
169 model_row_count_dirty_property.mark_dirty();
170 tracked_rows.clear();
171 model_row_data_dirty_property.mark_dirty();
172 for_each_peers([=](auto peer) { peer->reset(); });
173 }
174
175 /// \deprecated
176 [[deprecated("Renamed to notify_row_changed")]] void row_changed(size_t row)
177 {
178 notify_row_changed(row);
179 }
180 /// \deprecated
181 [[deprecated("Renamed to notify_row_added")]] void row_added(size_t index, size_t count)
182 {
183 notify_row_added(index, count);
184 }
185 /// \deprecated
186 [[deprecated("Renamed to notify_row_removed")]] void row_removed(size_t index, size_t count)
187 {
188 notify_row_removed(index, count);
189 }
190 /// \deprecated
191 [[deprecated("Renamed to notify_reset")]] void reset() { notify_reset(); }
192
193private:
194 template<typename F>
195 void for_each_peers(const F &f)
196 {
197 peers.erase(std::remove_if(peers.begin(), peers.end(),
198 [&](const auto &p) {
199 if (auto pp = p.lock()) {
200 f(pp);
201 return false;
202 }
203 return true;
204 }),
205 peers.end());
206 }
207 std::vector<private_api::ModelPeer> peers;
208 private_api::Property<bool> model_row_count_dirty_property;
209 private_api::Property<bool> model_row_data_dirty_property;
210 mutable std::vector<size_t> tracked_rows;
211};
212
213namespace private_api {
214/// A Model backed by a std::array of constant size
215/// \private
216template<int Count, typename ModelData>
217class ArrayModel : public Model<ModelData>
218{
219 std::array<ModelData, Count> data;
220
221public:
222 /// Constructs a new ArrayModel by forwarding \a to the std::array constructor.
223 template<typename... A>
224 ArrayModel(A &&...a) : data { std::forward<A>(a)... }
225 {
226 }
227 size_t row_count() const override { return Count; }
228 std::optional<ModelData> row_data(size_t i) const override
229 {
230 if (i >= row_count())
231 return {};
232 return data[i];
233 }
234 void set_row_data(size_t i, const ModelData &value) override
235 {
236 if (i < row_count()) {
237 data[i] = value;
238 this->notify_row_changed(i);
239 }
240 }
241};
242
243// Specialize for the empty array. We can't have a Model<void>, but `int` will work for our purpose
244template<>
245class ArrayModel<0, void> : public Model<int>
246{
247public:
248 size_t row_count() const override { return 0; }
249 std::optional<int> row_data(size_t) const override { return {}; }
250};
251
252/// Model to be used when we just want to repeat without data.
253struct UIntModel : Model<int>
254{
255 /// Constructs a new IntModel with \a d rows.
256 UIntModel(uint32_t d) : data(d) { }
257 /// \private
258 uint32_t data;
259 /// \copydoc Model::row_count
260 size_t row_count() const override { return data; }
261 std::optional<int> row_data(size_t value) const override
262 {
263 if (value >= row_count())
264 return {};
265 return static_cast<int>(value);
266 }
267};
268} // namespace private_api
269
270/// A Model backed by a SharedVector
271template<typename ModelData>
272class VectorModel : public Model<ModelData>
273{
274 std::vector<ModelData> data;
275
276public:
277 /// Constructs a new empty VectorModel.
278 VectorModel() = default;
279 /// Constructs a new VectorModel from \a array.
280 VectorModel(std::vector<ModelData> array) : data(std::move(array)) { }
281 size_t row_count() const override { return data.size(); }
282 std::optional<ModelData> row_data(size_t i) const override
283 {
284 if (i >= row_count())
285 return {};
286 return std::optional<ModelData> { data[i] };
287 }
288 void set_row_data(size_t i, const ModelData &value) override
289 {
290 if (i < row_count()) {
291 data[i] = value;
292 this->notify_row_changed(i);
293 }
294 }
295
296 /// Append a new row with the given value
297 void push_back(const ModelData &value)
298 {
299 data.push_back(value);
300 this->notify_row_added(data.size() - 1, 1);
301 }
302
303 /// Remove the row at the given index from the model
304 void erase(size_t index)
305 {
306 data.erase(data.begin() + index);
307 this->notify_row_removed(index, 1);
308 }
309
310 /// Inserts the given value as a new row at the specified index
311 void insert(size_t index, const ModelData &value)
312 {
313 data.insert(data.begin() + index, value);
314 this->notify_row_added(index, 1);
315 }
316
317 /// Erases all rows from the VectorModel.
318 void clear()
319 {
320 if (!data.empty()) {
321 data.clear();
322 this->notify_reset();
323 }
324 }
325
326 /// Replaces the underlying VectorModel's vector with \a array.
327 void set_vector(std::vector<ModelData> array)
328 {
329 data = std::move(array);
330 this->notify_reset();
331 }
332};
333
334template<typename ModelData>
335class FilterModel;
336
337namespace private_api {
338template<typename ModelData>
339struct FilterModelInner : private_api::ModelChangeListener
340{
341 FilterModelInner(std::shared_ptr<slint::Model<ModelData>> source_model,
342 std::function<bool(const ModelData &)> filter_fn,
343 slint::FilterModel<ModelData> &target_model)
344 : source_model(source_model), filter_fn(filter_fn), target_model(target_model)
345 {
346 }
347
348 void row_added(size_t index, size_t count) override
349 {
350 if (filtered_rows_dirty) {
351 reset();
352 return;
353 }
354
355 if (count == 0) {
356 return;
357 }
358
359 std::vector<size_t> added_accepted_rows;
360 for (auto i = index; i < index + count; ++i) {
361 if (auto data = source_model->row_data(i)) {
362 if (filter_fn(*data)) {
363 added_accepted_rows.push_back(x: i);
364 }
365 }
366 }
367
368 if (added_accepted_rows.empty()) {
369 return;
370 }
371
372 auto insertion_point = std::lower_bound(first: accepted_rows.begin(), last: accepted_rows.end(), val: index);
373
374 insertion_point = accepted_rows.insert(position: insertion_point, first: added_accepted_rows.begin(),
375 last: added_accepted_rows.end());
376
377 for (auto it = insertion_point + added_accepted_rows.size(); it != accepted_rows.end();
378 ++it)
379 (*it) += count;
380
381 target_model.notify_row_added(insertion_point - accepted_rows.begin(),
382 added_accepted_rows.size());
383 }
384 void row_changed(size_t index) override
385 {
386 if (filtered_rows_dirty) {
387 reset();
388 return;
389 }
390
391 auto existing_row = std::lower_bound(first: accepted_rows.begin(), last: accepted_rows.end(), val: index);
392 auto existing_row_index = std::distance(first: accepted_rows.begin(), last: existing_row);
393 bool is_contained = existing_row != accepted_rows.end() && *existing_row == index;
394 auto accepted_updated_row = filter_fn(*source_model->row_data(index));
395
396 if (is_contained && accepted_updated_row) {
397 target_model.notify_row_changed(existing_row_index);
398 } else if (!is_contained && accepted_updated_row) {
399 accepted_rows.insert(position: existing_row, x: index);
400 target_model.notify_row_added(existing_row_index, 1);
401 } else if (is_contained && !accepted_updated_row) {
402 accepted_rows.erase(position: existing_row);
403 target_model.notify_row_removed(existing_row_index, 1);
404 }
405 }
406 void row_removed(size_t index, size_t count) override
407 {
408 if (filtered_rows_dirty) {
409 reset();
410 return;
411 }
412
413 auto mapped_row_start = std::lower_bound(first: accepted_rows.begin(), last: accepted_rows.end(), val: index);
414 auto mapped_row_end =
415 std::lower_bound(first: accepted_rows.begin(), last: accepted_rows.end(), val: index + count);
416
417 auto mapped_removed_len = std::distance(first: mapped_row_start, last: mapped_row_end);
418
419 auto mapped_removed_index =
420 (mapped_row_start != accepted_rows.end() && *mapped_row_start == index)
421 ? std::optional<int>(mapped_row_start - accepted_rows.begin())
422 : std::nullopt;
423
424 auto it = accepted_rows.erase(first: mapped_row_start, last: mapped_row_end);
425 for (; it != accepted_rows.end(); ++it) {
426 *it -= count;
427 }
428
429 if (mapped_removed_index) {
430 target_model.notify_row_removed(*mapped_removed_index, mapped_removed_len);
431 }
432 }
433 void reset() override
434 {
435 filtered_rows_dirty = true;
436 update_mapping();
437 target_model.notify_reset();
438 }
439
440 void update_mapping()
441 {
442 if (!filtered_rows_dirty) {
443 return;
444 }
445
446 accepted_rows.clear();
447 for (size_t i = 0, count = source_model->row_count(); i < count; ++i) {
448 if (auto data = source_model->row_data(i)) {
449 if (filter_fn(*data)) {
450 accepted_rows.push_back(x: i);
451 }
452 }
453 }
454
455 filtered_rows_dirty = false;
456 }
457
458 bool filtered_rows_dirty = true;
459 std::shared_ptr<slint::Model<ModelData>> source_model;
460 std::function<bool(const ModelData &)> filter_fn;
461 std::vector<size_t> accepted_rows;
462 slint::FilterModel<ModelData> &target_model;
463};
464}
465
466/// The FilterModel acts as an adapter model for a given source model by applying a filter
467/// function. The filter function is called for each row on the source model and if the
468/// filter accepts the row (i.e. returns true), the row is also visible in the FilterModel.
469template<typename ModelData>
470class FilterModel : public Model<ModelData>
471{
472 friend struct private_api::FilterModelInner<ModelData>;
473
474public:
475 /// Constructs a new FilterModel that provides a limited view on the \a source_model by applying
476 /// \a filter_fn on each row. If the provided function returns true, the row is exposed by the
477 /// FilterModel.
478 FilterModel(std::shared_ptr<Model<ModelData>> source_model,
479 std::function<bool(const ModelData &)> filter_fn)
480 : inner(std::make_shared<private_api::FilterModelInner<ModelData>>(
481 std::move(source_model), std::move(filter_fn), *this))
482 {
483 inner->source_model->attach_peer(inner);
484 }
485
486 size_t row_count() const override
487 {
488 inner->update_mapping();
489 return inner->accepted_rows.size();
490 }
491
492 std::optional<ModelData> row_data(size_t i) const override
493 {
494 inner->update_mapping();
495 if (i >= inner->accepted_rows.size())
496 return {};
497 return inner->source_model->row_data(inner->accepted_rows[i]);
498 }
499
500 void set_row_data(size_t i, const ModelData &value) override
501 {
502 inner->update_mapping();
503 inner->source_model->set_row_data(inner->accepted_rows[i], value);
504 }
505
506 /// Re-applies the model's filter function on each row of the source model. Use this if state
507 /// external to the filter function has changed.
508 void reset() { inner->reset(); }
509
510 /// Given the \a filtered_row index, this function returns the corresponding row index in the
511 /// source model.
512 int unfiltered_row(int filtered_row) const
513 {
514 inner->update_mapping();
515 return inner->accepted_rows[filtered_row];
516 }
517
518 /// Returns the source model of this filter model.
519 std::shared_ptr<Model<ModelData>> source_model() const { return inner->source_model; }
520
521private:
522 std::shared_ptr<private_api::FilterModelInner<ModelData>> inner;
523};
524
525template<typename SourceModelData, typename MappedModelData>
526class MapModel;
527
528namespace private_api {
529template<typename SourceModelData, typename MappedModelData>
530struct MapModelInner : private_api::ModelChangeListener
531{
532 MapModelInner(slint::MapModel<SourceModelData, MappedModelData> &target_model)
533 : target_model(target_model)
534 {
535 }
536
537 void row_added(size_t index, size_t count) override
538 {
539 target_model.notify_row_added(index, count);
540 }
541 void row_changed(size_t index) override { target_model.notify_row_changed(index); }
542 void row_removed(size_t index, size_t count) override
543 {
544 target_model.notify_row_removed(index, count);
545 }
546 void reset() override { target_model.notify_reset(); }
547
548 slint::MapModel<SourceModelData, MappedModelData> &target_model;
549};
550}
551
552/// The MapModel acts as an adapter model for a given source model by applying a mapping
553/// function. The mapping function is called for each row on the source model and allows
554/// transforming the values on the fly. The MapModel has two template parameters: The
555/// SourceModelData specifies the data type of the underlying source model, and the
556/// MappedModelData the data type of this MapModel. This permits not only changing the
557/// values of the underlying source model, but also changing the data type itself. For
558/// example a MapModel can be used to adapt a model that provides numbers to be a model
559/// that exposes all numbers converted to strings, by calling `std::to_string` on each
560/// value given in the mapping lambda expression.
561///
562/// \code
563/// auto source_model = std::make_shared<slint::VectorModel<Person>>(...);
564/// auto mapped_model = std::make_shared<slint::MapModel<Person, SharedString>>(
565/// source_model, [](const Person &person) {
566// return fmt::format("{} {}", person.first, person.last);
567// });
568/// \endcode
569template<typename SourceModelData, typename MappedModelData = SourceModelData>
570class MapModel : public Model<MappedModelData>
571{
572 friend struct private_api::MapModelInner<SourceModelData, MappedModelData>;
573
574public:
575 /// Constructs a new MapModel that provides an altered view on the \a source_model by applying
576 /// \a map_fn on the data in each row.
577 MapModel(std::shared_ptr<Model<SourceModelData>> source_model,
578 std::function<MappedModelData(const SourceModelData &)> map_fn)
579 : inner(std::make_shared<private_api::MapModelInner<SourceModelData, MappedModelData>>(
580 *this)),
581 model(source_model),
582 map_fn(map_fn)
583 {
584 model->attach_peer(inner);
585 }
586
587 size_t row_count() const override { return model->row_count(); }
588
589 std::optional<MappedModelData> row_data(size_t i) const override
590 {
591 if (auto source_data = model->row_data(i))
592 return map_fn(*source_data);
593 else
594 return {};
595 }
596
597 /// Returns the source model of this filter model.
598 std::shared_ptr<Model<SourceModelData>> source_model() const { return model; }
599
600 /// Re-applies the model's mapping function on each row of the source model. Use this if state
601 /// external to the mapping function has changed.
602 void reset() { inner->reset(); }
603
604private:
605 std::shared_ptr<private_api::MapModelInner<SourceModelData, MappedModelData>> inner;
606 std::shared_ptr<slint::Model<SourceModelData>> model;
607 std::function<MappedModelData(const SourceModelData &)> map_fn;
608};
609
610template<typename ModelData>
611class SortModel;
612
613namespace private_api {
614template<typename ModelData>
615struct SortModelInner : private_api::ModelChangeListener
616{
617 SortModelInner(std::shared_ptr<slint::Model<ModelData>> source_model,
618 std::function<bool(const ModelData &, const ModelData &)> comp,
619 slint::SortModel<ModelData> &target_model)
620 : source_model(source_model), comp(comp), target_model(target_model)
621 {
622 }
623
624 void row_added(size_t first_inserted_row, size_t count) override
625 {
626 if (sorted_rows_dirty) {
627 reset();
628 return;
629 }
630
631 // Adjust the existing sorted row indices to match the updated source model
632 for (auto &row : sorted_rows) {
633 if (row >= first_inserted_row)
634 row += count;
635 }
636
637 for (size_t row = first_inserted_row; row < first_inserted_row + count; ++row) {
638
639 ModelData inserted_value = *source_model->row_data(row);
640 auto insertion_point =
641 std::lower_bound(sorted_rows.begin(), sorted_rows.end(), inserted_value,
642 [this](size_t sorted_row, const ModelData &inserted_value) {
643 auto sorted_elem = source_model->row_data(sorted_row);
644 return comp(*sorted_elem, inserted_value);
645 });
646
647 insertion_point = sorted_rows.insert(insertion_point, row);
648 target_model.notify_row_added(std::distance(sorted_rows.begin(), insertion_point), 1);
649 }
650 }
651 void row_changed(size_t changed_row) override
652 {
653 if (sorted_rows_dirty) {
654 reset();
655 return;
656 }
657
658 auto removed_row_it =
659 sorted_rows.erase(position: std::find(first: sorted_rows.begin(), last: sorted_rows.end(), val: changed_row));
660 auto removed_row = std::distance(first: sorted_rows.begin(), last: removed_row_it);
661
662 ModelData changed_value = *source_model->row_data(changed_row);
663 auto insertion_point =
664 std::lower_bound(sorted_rows.begin(), sorted_rows.end(), changed_value,
665 [this](size_t sorted_row, const ModelData &changed_value) {
666 auto sorted_elem = source_model->row_data(sorted_row);
667 return comp(*sorted_elem, changed_value);
668 });
669
670 insertion_point = sorted_rows.insert(insertion_point, changed_row);
671 auto inserted_row = std::distance(sorted_rows.begin(), insertion_point);
672
673 if (inserted_row == removed_row) {
674 target_model.notify_row_changed(removed_row);
675 } else {
676 target_model.notify_row_removed(removed_row, 1);
677 target_model.notify_row_added(inserted_row, 1);
678 }
679 }
680 void row_removed(size_t first_removed_row, size_t count) override
681 {
682 if (sorted_rows_dirty) {
683 reset();
684 return;
685 }
686
687 std::vector<size_t> removed_rows;
688 removed_rows.reserve(n: count);
689
690 for (auto it = sorted_rows.begin(); it != sorted_rows.end();) {
691 if (*it >= first_removed_row) {
692 if (*it < first_removed_row + count) {
693 removed_rows.push_back(x: std::distance(first: sorted_rows.begin(), last: it));
694 it = sorted_rows.erase(position: it);
695 continue;
696 } else {
697 *it -= count;
698 }
699 }
700 ++it;
701 }
702
703 for (auto removed_row : removed_rows) {
704 target_model.notify_row_removed(removed_row, 1);
705 }
706 }
707
708 void reset() override
709 {
710 sorted_rows_dirty = true;
711 target_model.notify_reset();
712 }
713
714 void ensure_sorted()
715 {
716 if (!sorted_rows_dirty) {
717 return;
718 }
719
720 sorted_rows.resize(source_model->row_count());
721 for (size_t i = 0; i < sorted_rows.size(); ++i)
722 sorted_rows[i] = i;
723
724 std::sort(sorted_rows.begin(), sorted_rows.end(), [this](auto lhs_index, auto rhs_index) {
725 auto lhs_elem = source_model->row_data(lhs_index);
726 auto rhs_elem = source_model->row_data(rhs_index);
727 return comp(*lhs_elem, *rhs_elem);
728 });
729
730 sorted_rows_dirty = false;
731 }
732
733 std::shared_ptr<slint::Model<ModelData>> source_model;
734 std::function<bool(const ModelData &, const ModelData &)> comp;
735 slint::SortModel<ModelData> &target_model;
736 std::vector<size_t> sorted_rows;
737 bool sorted_rows_dirty = true;
738};
739}
740
741/// The SortModel acts as an adapter model for a given source model by sorting all rows
742/// with by order provided by the given sorting function. The sorting function is called for
743/// pairs of elements of the source model.
744///
745/// \code
746/// auto source_model = std::make_shared<slint::VectorModel<SharedString>>(
747// std::vector<SharedString> { "lorem", "ipsum", "dolor" });
748/// auto sorted_model = std::make_shared<slint::SortModel<SharedString>>(
749/// source_model, [](auto lhs, auto rhs) { return lhs < rhs; }));
750/// \endcode
751
752template<typename ModelData>
753class SortModel : public Model<ModelData>
754{
755 friend struct private_api::SortModelInner<ModelData>;
756
757public:
758 /// Constructs a new SortModel that provides a sorted view on the \a source_model by applying
759 /// the order given by the specified \a comp.
760 SortModel(std::shared_ptr<Model<ModelData>> source_model,
761 std::function<bool(const ModelData &, const ModelData &)> comp)
762 : inner(std::make_shared<private_api::SortModelInner<ModelData>>(std::move(source_model),
763 std::move(comp), *this))
764 {
765 inner->source_model->attach_peer(inner);
766 }
767
768 size_t row_count() const override { return inner->source_model->row_count(); }
769
770 std::optional<ModelData> row_data(size_t i) const override
771 {
772 inner->ensure_sorted();
773 return inner->source_model->row_data(inner->sorted_rows[i]);
774 }
775
776 void set_row_data(size_t i, const ModelData &value) override
777 {
778 inner->source_model->set_row_data(inner->sorted_rows[i], value);
779 }
780
781 /// Re-applies the model's sort function on each row of the source model. Use this if state
782 /// external to the sort function has changed.
783 void reset() { inner->reset(); }
784
785 /// Given the \a sorted_row_index, this function returns the corresponding row index in the
786 /// source model.
787 int unsorted_row(int sorted_row_index) const
788 {
789 inner->ensure_sorted();
790 return inner->sorted_rows[sorted_row_index];
791 }
792
793 /// Returns the source model of this filter model.
794 std::shared_ptr<Model<ModelData>> source_model() const { return inner->source_model; }
795
796private:
797 std::shared_ptr<private_api::SortModelInner<ModelData>> inner;
798};
799
800template<typename ModelData>
801class ReverseModel;
802
803namespace private_api {
804template<typename ModelData>
805struct ReverseModelInner : private_api::ModelChangeListener
806{
807 ReverseModelInner(std::shared_ptr<slint::Model<ModelData>> source_model,
808 slint::ReverseModel<ModelData> &target_model)
809 : source_model(source_model), target_model(target_model)
810 {
811 }
812
813 void row_added(size_t first_inserted_row, size_t count) override
814 {
815 auto row_count = source_model->row_count();
816 auto old_row_count = row_count - count;
817 auto row = old_row_count - first_inserted_row;
818
819 target_model.notify_row_added(row, count);
820 }
821
822 void row_changed(size_t changed_row) override
823 {
824 target_model.notify_row_changed(source_model->row_count() - 1 - changed_row);
825 }
826
827 void row_removed(size_t first_removed_row, size_t count) override
828 {
829 target_model.notify_row_removed(source_model->row_count() - first_removed_row, count);
830 }
831
832 void reset() override { target_model.notify_reset(); }
833
834 std::shared_ptr<slint::Model<ModelData>> source_model;
835 slint::ReverseModel<ModelData> &target_model;
836};
837}
838
839/// The ReverseModel acts as an adapter model for a given source model by reserving all rows.
840/// This means that the first row in the source model is the last row of this model, the second
841/// row is the second last, and so on.
842///
843/// \code
844/// auto source_model = std::make_shared<slint::VectorModel<int>>(
845// std::vector<int> { 1, 2, 3, 4, 5 });
846/// auto reversed_model = std::make_shared<slint::ReverseModel<int>>(source_model);
847/// \endcode
848template<typename ModelData>
849class ReverseModel : public Model<ModelData>
850{
851 friend struct private_api::ReverseModelInner<ModelData>;
852
853public:
854 /// Constructs a new ReverseModel that provides a reversed view on the \a source_model.
855 ReverseModel(std::shared_ptr<Model<ModelData>> source_model)
856 : inner(std::make_shared<private_api::ReverseModelInner<ModelData>>(std::move(source_model),
857 *this))
858 {
859 inner->source_model->attach_peer(inner);
860 }
861
862 size_t row_count() const override { return inner->source_model->row_count(); }
863
864 std::optional<ModelData> row_data(size_t i) const override
865 {
866 auto count = inner->source_model->row_count();
867 return inner->source_model->row_data(count - i - 1);
868 }
869
870 void set_row_data(size_t i, const ModelData &value) override
871 {
872 auto count = inner->source_model->row_count();
873 inner->source_model->set_row_data(count - i - 1, value);
874 }
875
876 /// Returns the source model of this reserve model.
877 std::shared_ptr<Model<ModelData>> source_model() const { return inner->source_model; }
878
879private:
880 std::shared_ptr<private_api::ReverseModelInner<ModelData>> inner;
881};
882
883namespace private_api {
884
885template<typename C, typename ModelData>
886class Repeater
887{
888 struct RepeaterInner : ModelChangeListener
889 {
890 enum class State { Clean, Dirty };
891 struct RepeatedInstanceWithState
892 {
893 State state = State::Dirty;
894 std::optional<ComponentHandle<C>> ptr;
895 };
896 std::vector<RepeatedInstanceWithState> data;
897 private_api::Property<bool> is_dirty { true };
898 std::shared_ptr<Model<ModelData>> model;
899
900 void row_added(size_t index, size_t count) override
901 {
902 if (index > data.size()) {
903 // Can happen before ensure_updated was called
904 return;
905 }
906 is_dirty.set(true);
907 data.resize(data.size() + count);
908 std::rotate(data.begin() + index, data.end() - count, data.end());
909 for (std::size_t i = index; i < data.size(); ++i) {
910 // all the indexes are dirty
911 data[i].state = State::Dirty;
912 }
913 }
914 void row_changed(size_t index) override
915 {
916 if (index >= data.size()) {
917 return;
918 }
919 auto &c = data[index];
920 if (model && c.ptr) {
921 (*c.ptr)->update_data(index, *model->row_data(index));
922 c.state = State::Clean;
923 } else {
924 c.state = State::Dirty;
925 }
926 }
927 void row_removed(size_t index, size_t count) override
928 {
929 if (index + count > data.size()) {
930 // Can happen before ensure_updated was called
931 return;
932 }
933 is_dirty.set(true);
934 data.erase(data.begin() + index, data.begin() + index + count);
935 for (std::size_t i = index; i < data.size(); ++i) {
936 // all the indexes are dirty
937 data[i].state = State::Dirty;
938 }
939 }
940 void reset() override
941 {
942 is_dirty.set(true);
943 data.clear();
944 }
945 };
946
947 private_api::Property<std::shared_ptr<Model<ModelData>>> model;
948 mutable std::shared_ptr<RepeaterInner> inner;
949
950 vtable::VRef<private_api::ItemTreeVTable> item_at(int i) const
951 {
952 const auto &x = inner->data.at(i);
953 return { &C::static_vtable, const_cast<C *>(&(**x.ptr)) };
954 }
955
956public:
957 template<typename F>
958 void set_model_binding(F &&binding) const
959 {
960 model.set_binding(std::forward<F>(binding));
961 }
962
963 template<typename Parent>
964 void ensure_updated(const Parent *parent) const
965 {
966 if (model.is_dirty()) {
967 auto old_model = model.get_internal();
968 auto m = model.get();
969 if (!inner || old_model != m) {
970 inner = std::make_shared<RepeaterInner>();
971 if (m) {
972 inner->model = m;
973 m->attach_peer(inner);
974 }
975 }
976 }
977
978 if (inner && inner->is_dirty.get()) {
979 inner->is_dirty.set(false);
980 if (auto m = model.get()) {
981 auto count = m->row_count();
982 inner->data.resize(count);
983 for (size_t i = 0; i < count; ++i) {
984 auto &c = inner->data[i];
985 bool created = false;
986 if (!c.ptr) {
987 c.ptr = C::create(parent);
988 created = true;
989 }
990 if (c.state == RepeaterInner::State::Dirty) {
991 (*c.ptr)->update_data(i, *m->row_data(i));
992 }
993 if (created) {
994 (*c.ptr)->init();
995 }
996 }
997 } else {
998 inner->data.clear();
999 }
1000 } else {
1001 // just do a get() on the model to register dependencies so that, for example, the
1002 // layout property tracker becomes dirty.
1003 model.get();
1004 }
1005 }
1006
1007 template<typename Parent>
1008 void ensure_updated_listview(const Parent *parent,
1009 const private_api::Property<float> *viewport_width,
1010 const private_api::Property<float> *viewport_height,
1011 const private_api::Property<float> *viewport_y,
1012 float listview_width, [[maybe_unused]] float listview_height) const
1013 {
1014 // TODO: the rust code in model.rs try to only allocate as many items as visible items
1015 ensure_updated(parent);
1016
1017 float h = compute_layout_listview(viewport_width, listview_width, viewport_y: viewport_y->get());
1018 viewport_height->set(h);
1019 }
1020
1021 uint64_t visit(TraversalOrder order, private_api::ItemVisitorRefMut visitor) const
1022 {
1023 for (std::size_t i = 0; i < inner->data.size(); ++i) {
1024 auto index = order == TraversalOrder::BackToFront ? i : inner->data.size() - 1 - i;
1025 auto ref = item_at(i: index);
1026 if (ref.vtable->visit_children_item(ref, -1, order, visitor)
1027 != std::numeric_limits<uint64_t>::max()) {
1028 return index;
1029 }
1030 }
1031 return std::numeric_limits<uint64_t>::max();
1032 }
1033
1034 vtable::VWeak<private_api::ItemTreeVTable> instance_at(std::size_t i) const
1035 {
1036 if (i >= inner->data.size()) {
1037 return {};
1038 }
1039 const auto &x = inner->data.at(i);
1040 return vtable::VWeak<private_api::ItemTreeVTable> { x.ptr->into_dyn() };
1041 }
1042
1043 private_api::IndexRange index_range() const
1044 {
1045 return private_api::IndexRange { 0, inner->data.size() };
1046 }
1047
1048 std::size_t len() const { return inner ? inner->data.size() : 0; }
1049
1050 float compute_layout_listview(const private_api::Property<float> *viewport_width,
1051 float listview_width, float viewport_y) const
1052 {
1053 float offset = viewport_y;
1054 auto vp_width = listview_width;
1055 if (!inner)
1056 return offset;
1057 for (auto &x : inner->data) {
1058 vp_width = std::max(vp_width, (*x.ptr)->listview_layout(&offset));
1059 }
1060 viewport_width->set(vp_width);
1061 return offset - viewport_y;
1062 }
1063
1064 void model_set_row_data(size_t row, const ModelData &data) const
1065 {
1066 if (model.is_dirty()) {
1067 std::abort();
1068 }
1069 if (auto m = model.get()) {
1070 if (row < m->row_count()) {
1071 m->set_row_data(row, data);
1072 }
1073 }
1074 }
1075
1076 void for_each(auto &&f) const
1077 {
1078 if (inner) {
1079 for (auto &&x : inner->data) {
1080 f(*x.ptr);
1081 }
1082 }
1083 }
1084};
1085
1086template<typename C>
1087class Conditional
1088{
1089 private_api::Property<bool> model;
1090 mutable std::optional<ComponentHandle<C>> instance;
1091
1092public:
1093 template<typename F>
1094 void set_model_binding(F &&binding) const
1095 {
1096 model.set_binding(std::forward<F>(binding));
1097 }
1098
1099 template<typename Parent>
1100 void ensure_updated(const Parent *parent) const
1101 {
1102 if (!model.get()) {
1103 instance = std::nullopt;
1104 } else if (!instance) {
1105 instance = C::create(parent);
1106 (*instance)->init();
1107 }
1108 }
1109
1110 uint64_t visit(TraversalOrder order, private_api::ItemVisitorRefMut visitor) const
1111 {
1112 if (instance) {
1113 vtable::VRef<private_api::ItemTreeVTable> ref { &C::static_vtable,
1114 const_cast<C *>(&(**instance)) };
1115 if (ref.vtable->visit_children_item(ref, -1, order, visitor)
1116 != std::numeric_limits<uint64_t>::max()) {
1117 return 0;
1118 }
1119 }
1120 return std::numeric_limits<uint64_t>::max();
1121 }
1122
1123 vtable::VWeak<private_api::ItemTreeVTable> instance_at(std::size_t i) const
1124 {
1125 if (i != 0 || !instance) {
1126 return {};
1127 }
1128 return vtable::VWeak<private_api::ItemTreeVTable> { instance->into_dyn() };
1129 }
1130
1131 private_api::IndexRange index_range() const { return private_api::IndexRange { 0, len() }; }
1132
1133 std::size_t len() const { return instance ? 1 : 0; }
1134
1135 void for_each(auto &&f) const
1136 {
1137 if (instance) {
1138 f(*instance);
1139 }
1140 }
1141};
1142
1143} // namespace private_api
1144
1145} // namespace slint
1146

source code of slint/api/cpp/include/slint_models.h