1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial |
3 | |
4 | #pragma once |
5 | |
6 | #if defined(__GNUC__) || defined(__clang__) |
7 | // In C++17, it is conditionally supported, but still valid for all compiler we care |
8 | # pragma GCC diagnostic ignored "-Winvalid-offsetof" |
9 | #endif |
10 | |
11 | #include "slint_internal.h" |
12 | #include "slint_size.h" |
13 | #include "slint_point.h" |
14 | #include "slint_platform_internal.h" |
15 | #include "slint_qt_internal.h" |
16 | #include "slint_window.h" |
17 | |
18 | #include <vector> |
19 | #include <memory> |
20 | #include <algorithm> |
21 | #include <chrono> |
22 | #include <optional> |
23 | #include <span> |
24 | #include <functional> |
25 | #include <concepts> |
26 | |
27 | #ifndef SLINT_FEATURE_FREESTANDING |
28 | # include <mutex> |
29 | # include <condition_variable> |
30 | #endif |
31 | |
32 | /// \rst |
33 | /// The :code:`slint` namespace is the primary entry point into the Slint C++ API. |
34 | /// All available types are in this namespace. |
35 | /// |
36 | /// See the :doc:`Overview <../overview>` documentation for the C++ integration how |
37 | /// to load :code:`.slint` designs. |
38 | /// \endrst |
39 | namespace slint { |
40 | |
41 | namespace private_api { |
42 | // Bring opaque structure in scope |
43 | using namespace cbindgen_private; |
44 | using ItemTreeRef = vtable::VRef<private_api::ItemTreeVTable>; |
45 | using IndexRange = cbindgen_private::IndexRange; |
46 | using ItemRef = vtable::VRef<private_api::ItemVTable>; |
47 | using ItemVisitorRefMut = vtable::VRefMut<cbindgen_private::ItemVisitorVTable>; |
48 | using ItemTreeNode = cbindgen_private::ItemTreeNode; |
49 | using ItemArrayEntry = |
50 | vtable::VOffset<uint8_t, slint::cbindgen_private::ItemVTable, vtable::AllowPin>; |
51 | using ItemArray = slint::cbindgen_private::Slice<ItemArrayEntry>; |
52 | |
53 | constexpr inline ItemTreeNode make_item_node(uint32_t child_count, uint32_t child_index, |
54 | uint32_t parent_index, uint32_t item_array_index, |
55 | bool is_accessible) |
56 | { |
57 | return ItemTreeNode { ItemTreeNode::Item_Body { .tag: ItemTreeNode::Tag::Item, .is_accessible: is_accessible, |
58 | .children_count: child_count, .children_index: child_index, .parent_index: parent_index, |
59 | .item_array_index: item_array_index } }; |
60 | } |
61 | |
62 | constexpr inline ItemTreeNode make_dyn_node(std::uint32_t offset, std::uint32_t parent_index) |
63 | { |
64 | return ItemTreeNode { ItemTreeNode::DynamicTree_Body { .tag: ItemTreeNode::Tag::DynamicTree, .index: offset, |
65 | .parent_index: parent_index } }; |
66 | } |
67 | |
68 | inline ItemRef get_item_ref(ItemTreeRef item_tree, |
69 | const cbindgen_private::Slice<ItemTreeNode> item_tree_array, |
70 | const private_api::ItemArray item_array, int index) |
71 | { |
72 | const auto item_array_index = item_tree_array.ptr[index].item.item_array_index; |
73 | const auto item = item_array[item_array_index]; |
74 | return ItemRef { .vtable: item.vtable, .instance: reinterpret_cast<char *>(item_tree.instance) + item.offset }; |
75 | } |
76 | |
77 | /// Convert a slint `{height: length, width: length, x: length, y: length}` to a Rect |
78 | inline cbindgen_private::Rect convert_anonymous_rect(std::tuple<float, float, float, float> tuple) |
79 | { |
80 | // alphabetical order |
81 | auto [h, w, x, y] = tuple; |
82 | return cbindgen_private::Rect { .x = x, .y = y, .width = w, .height = h }; |
83 | } |
84 | |
85 | inline void dealloc(const ItemTreeVTable *, uint8_t *ptr, [[maybe_unused]] vtable::Layout layout) |
86 | { |
87 | #ifdef __cpp_sized_deallocation |
88 | ::operator delete(reinterpret_cast<void *>(ptr), layout.size, |
89 | static_cast<std::align_val_t>(layout.align)); |
90 | #elif !defined(__APPLE__) || MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_14 |
91 | ::operator delete(reinterpret_cast<void *>(ptr), static_cast<std::align_val_t>(layout.align)); |
92 | #else |
93 | ::operator delete(reinterpret_cast<void *>(ptr)); |
94 | #endif |
95 | } |
96 | |
97 | template<typename T> |
98 | inline vtable::Layout drop_in_place(ItemTreeRef item_tree) |
99 | { |
100 | reinterpret_cast<T *>(item_tree.instance)->~T(); |
101 | return vtable::Layout { .size: sizeof(T), .align: alignof(T) }; |
102 | } |
103 | |
104 | #if !defined(DOXYGEN) |
105 | # if defined(_WIN32) || defined(_WIN64) |
106 | // On Windows cross-dll data relocations are not supported: |
107 | // https://docs.microsoft.com/en-us/cpp/c-language/rules-and-limitations-for-dllimport-dllexport?view=msvc-160 |
108 | // so we have a relocation to a function that returns the address we seek. That |
109 | // relocation will be resolved to the locally linked stub library, the implementation of |
110 | // which will be patched. |
111 | # define SLINT_GET_ITEM_VTABLE(VTableName) slint::private_api::slint_get_##VTableName() |
112 | # else |
113 | # define SLINT_GET_ITEM_VTABLE(VTableName) (&slint::private_api::VTableName) |
114 | # endif |
115 | #endif // !defined(DOXYGEN) |
116 | |
117 | } // namespace private_api |
118 | |
119 | template<typename T> |
120 | class ComponentWeakHandle; |
121 | |
122 | /// The component handle is like a shared pointer to a component in the generated code. |
123 | /// In order to get a component, use `T::create()` where T is the name of the component |
124 | /// in the .slint file. This give you a `ComponentHandle<T>` |
125 | template<typename T> |
126 | class ComponentHandle |
127 | { |
128 | vtable::VRc<private_api::ItemTreeVTable, T> inner; |
129 | friend class ComponentWeakHandle<T>; |
130 | |
131 | public: |
132 | /// internal constructor |
133 | ComponentHandle(const vtable::VRc<private_api::ItemTreeVTable, T> &inner) : inner(inner) { } |
134 | |
135 | /// Arrow operator that implements pointer semantics. |
136 | const T *operator->() const |
137 | { |
138 | private_api::assert_main_thread(); |
139 | return inner.operator->(); |
140 | } |
141 | /// Dereference operator that implements pointer semantics. |
142 | const T &operator*() const |
143 | { |
144 | private_api::assert_main_thread(); |
145 | return inner.operator*(); |
146 | } |
147 | /// Arrow operator that implements pointer semantics. |
148 | T *operator->() |
149 | { |
150 | private_api::assert_main_thread(); |
151 | return inner.operator->(); |
152 | } |
153 | /// Dereference operator that implements pointer semantics. |
154 | T &operator*() |
155 | { |
156 | private_api::assert_main_thread(); |
157 | return inner.operator*(); |
158 | } |
159 | |
160 | /// internal function that returns the internal handle |
161 | vtable::VRc<private_api::ItemTreeVTable> into_dyn() const { return inner.into_dyn(); } |
162 | }; |
163 | |
164 | /// A weak reference to the component. Can be constructed from a `ComponentHandle<T>` |
165 | template<typename T> |
166 | class ComponentWeakHandle |
167 | { |
168 | vtable::VWeak<private_api::ItemTreeVTable, T> inner; |
169 | |
170 | public: |
171 | /// Constructs a null ComponentWeakHandle. lock() will always return empty. |
172 | ComponentWeakHandle() = default; |
173 | /// Copy-constructs a new ComponentWeakHandle from \a other. |
174 | ComponentWeakHandle(const ComponentHandle<T> &other) : inner(other.inner) { } |
175 | /// Returns a new strong ComponentHandle<T> if the component the weak handle points to is |
176 | /// still referenced by any other ComponentHandle<T>. An empty std::optional is returned |
177 | /// otherwise. |
178 | std::optional<ComponentHandle<T>> lock() const |
179 | { |
180 | private_api::assert_main_thread(); |
181 | if (auto l = inner.lock()) { |
182 | return { ComponentHandle(*l) }; |
183 | } else { |
184 | return {}; |
185 | } |
186 | } |
187 | }; |
188 | |
189 | namespace cbindgen_private { |
190 | inline LayoutInfo LayoutInfo::merge(const LayoutInfo &other) const |
191 | { |
192 | // Note: This "logic" is duplicated from LayoutInfo::merge in layout.rs. |
193 | return LayoutInfo { .max: std::min(a: max, b: other.max), |
194 | .max_percent: std::min(a: max_percent, b: other.max_percent), |
195 | .min: std::max(a: min, b: other.min), |
196 | .min_percent: std::max(a: min_percent, b: other.min_percent), |
197 | .preferred: std::max(a: preferred, b: other.preferred), |
198 | .stretch: std::min(a: stretch, b: other.stretch) }; |
199 | } |
200 | inline bool operator==(const EasingCurve &a, const EasingCurve &b) |
201 | { |
202 | if (a.tag != b.tag) { |
203 | return false; |
204 | } else if (a.tag == EasingCurve::Tag::CubicBezier) { |
205 | return std::equal(first1: a.cubic_bezier._0, last1: a.cubic_bezier._0 + 4, first2: b.cubic_bezier._0); |
206 | } |
207 | return true; |
208 | } |
209 | } |
210 | |
211 | namespace private_api { |
212 | |
213 | inline static void register_item_tree(const vtable::VRc<ItemTreeVTable> *c, |
214 | const std::optional<slint::Window> &maybe_window) |
215 | { |
216 | const cbindgen_private::WindowAdapterRcOpaque *window_ptr = |
217 | maybe_window.has_value() ? &maybe_window->window_handle().handle() : nullptr; |
218 | cbindgen_private::slint_register_item_tree(item_tree_rc: c, window_handle: window_ptr); |
219 | } |
220 | |
221 | inline SharedVector<float> solve_box_layout(const cbindgen_private::BoxLayoutData &data, |
222 | cbindgen_private::Slice<int> repeater_indexes) |
223 | { |
224 | SharedVector<float> result; |
225 | cbindgen_private::Slice<uint32_t> ri { reinterpret_cast<uint32_t *>(repeater_indexes.ptr), |
226 | repeater_indexes.len }; |
227 | cbindgen_private::slint_solve_box_layout(data: &data, repeater_indexes: ri, result: &result); |
228 | return result; |
229 | } |
230 | |
231 | inline SharedVector<float> solve_grid_layout(const cbindgen_private::GridLayoutData &data) |
232 | { |
233 | SharedVector<float> result; |
234 | cbindgen_private::slint_solve_grid_layout(data: &data, result: &result); |
235 | return result; |
236 | } |
237 | |
238 | inline cbindgen_private::LayoutInfo |
239 | grid_layout_info(cbindgen_private::Slice<cbindgen_private::GridLayoutCellData> cells, float spacing, |
240 | const cbindgen_private::Padding &padding) |
241 | { |
242 | return cbindgen_private::slint_grid_layout_info(cells, spacing, padding: &padding); |
243 | } |
244 | |
245 | inline cbindgen_private::LayoutInfo |
246 | box_layout_info(cbindgen_private::Slice<cbindgen_private::BoxLayoutCellData> cells, float spacing, |
247 | const cbindgen_private::Padding &padding, |
248 | cbindgen_private::LayoutAlignment alignment) |
249 | { |
250 | return cbindgen_private::slint_box_layout_info(cells, spacing, padding: &padding, alignment); |
251 | } |
252 | |
253 | inline cbindgen_private::LayoutInfo |
254 | box_layout_info_ortho(cbindgen_private::Slice<cbindgen_private::BoxLayoutCellData> cells, |
255 | const cbindgen_private::Padding &padding) |
256 | { |
257 | return cbindgen_private::slint_box_layout_info_ortho(cells, padding: &padding); |
258 | } |
259 | |
260 | /// Access the layout cache of an item within a repeater |
261 | inline float layout_cache_access(const SharedVector<float> &cache, int offset, int repeater_index) |
262 | { |
263 | size_t idx = size_t(cache[offset]) + repeater_index * 2; |
264 | return idx < cache.size() ? cache[idx] : 0; |
265 | } |
266 | |
267 | // models |
268 | struct ModelChangeListener |
269 | { |
270 | virtual ~ModelChangeListener() = default; |
271 | virtual void row_added(size_t index, size_t count) = 0; |
272 | virtual void row_removed(size_t index, size_t count) = 0; |
273 | virtual void row_changed(size_t index) = 0; |
274 | virtual void reset() = 0; |
275 | }; |
276 | using ModelPeer = std::weak_ptr<ModelChangeListener>; |
277 | |
278 | template<typename M> |
279 | auto access_array_index(const std::shared_ptr<M> &model, size_t index) |
280 | { |
281 | if (!model) { |
282 | return decltype(*model->row_data_tracked(index)) {}; |
283 | } else if (const auto v = model->row_data_tracked(index)) { |
284 | return *v; |
285 | } else { |
286 | return decltype(*v) {}; |
287 | } |
288 | } |
289 | |
290 | template<typename M> |
291 | long int model_length(const std::shared_ptr<M> &model) |
292 | { |
293 | if (!model) { |
294 | return 0; |
295 | } else { |
296 | model->track_row_count_changes(); |
297 | return model->row_count(); |
298 | } |
299 | } |
300 | |
301 | } // namespace private_api |
302 | |
303 | /// \rst |
304 | /// A Model is providing Data for |Repetition|_ repetitions or |ListView|_ elements of the |
305 | /// :code:`.slint` language |
306 | /// \endrst |
307 | template<typename ModelData> |
308 | class Model |
309 | { |
310 | public: |
311 | virtual ~Model() = default; |
312 | Model() = default; |
313 | Model(const Model &) = delete; |
314 | Model &operator=(const Model &) = delete; |
315 | |
316 | /// The amount of row in the model |
317 | virtual size_t row_count() const = 0; |
318 | /// Returns the data for a particular row. This function should be called with `row < |
319 | /// row_count()`. |
320 | virtual std::optional<ModelData> row_data(size_t i) const = 0; |
321 | /// Sets the data for a particular row. |
322 | /// |
323 | /// This function should only be called with `row < row_count()`. |
324 | /// |
325 | /// If the model cannot support data changes, then it is ok to do nothing. |
326 | /// The default implementation will print a warning to stderr. |
327 | /// |
328 | /// If the model can update the data, it should also call `row_changed` |
329 | virtual void set_row_data(size_t, const ModelData &) |
330 | { |
331 | #ifndef SLINT_FEATURE_FREESTANDING |
332 | std::cerr << "Model::set_row_data was called on a read-only model" << std::endl; |
333 | #endif |
334 | }; |
335 | |
336 | /// \private |
337 | /// Internal function called by the view to register itself |
338 | void attach_peer(private_api::ModelPeer p) |
339 | { |
340 | peers.push_back(x: std::move(p)); |
341 | } |
342 | |
343 | /// \private |
344 | /// Internal function called from within bindings to register with the currently |
345 | /// evaluating dependency and get notified when this model's row count changes. |
346 | void track_row_count_changes() const |
347 | { |
348 | model_row_count_dirty_property.get(); |
349 | } |
350 | |
351 | /// \private |
352 | /// Internal function called from within bindings to register with the currently |
353 | /// evaluating dependency and get notified when this model's row data changes. |
354 | void track_row_data_changes(size_t row) const |
355 | { |
356 | auto it = std::lower_bound(first: tracked_rows.begin(), last: tracked_rows.end(), val: row); |
357 | if (it == tracked_rows.end() || row < *it) { |
358 | tracked_rows.insert(position: it, x: row); |
359 | } |
360 | model_row_data_dirty_property.get(); |
361 | } |
362 | |
363 | /// \private |
364 | /// Convenience function that calls `track_row_data_changes` before returning `row_data` |
365 | std::optional<ModelData> row_data_tracked(size_t row) const |
366 | { |
367 | track_row_data_changes(row); |
368 | return row_data(i: row); |
369 | } |
370 | |
371 | protected: |
372 | /// Notify the views that a specific row was changed |
373 | void row_changed(size_t row) |
374 | { |
375 | if (std::binary_search(first: tracked_rows.begin(), last: tracked_rows.end(), val: row)) { |
376 | model_row_data_dirty_property.mark_dirty(); |
377 | } |
378 | for_each_peers([=](auto peer) { peer->row_changed(row); }); |
379 | } |
380 | /// Notify the views that rows were added |
381 | void row_added(size_t index, size_t count) |
382 | { |
383 | model_row_count_dirty_property.mark_dirty(); |
384 | tracked_rows.clear(); |
385 | model_row_data_dirty_property.mark_dirty(); |
386 | for_each_peers([=](auto peer) { peer->row_added(index, count); }); |
387 | } |
388 | /// Notify the views that rows were removed |
389 | void row_removed(size_t index, size_t count) |
390 | { |
391 | model_row_count_dirty_property.mark_dirty(); |
392 | tracked_rows.clear(); |
393 | model_row_data_dirty_property.mark_dirty(); |
394 | for_each_peers([=](auto peer) { peer->row_removed(index, count); }); |
395 | } |
396 | |
397 | /// Notify the views that the model has been changed and that everything needs to be reloaded |
398 | void reset() |
399 | { |
400 | model_row_count_dirty_property.mark_dirty(); |
401 | tracked_rows.clear(); |
402 | model_row_data_dirty_property.mark_dirty(); |
403 | for_each_peers([=](auto peer) { peer->reset(); }); |
404 | } |
405 | |
406 | private: |
407 | template<typename F> |
408 | void for_each_peers(const F &f) |
409 | { |
410 | private_api::assert_main_thread(); |
411 | peers.erase(std::remove_if(peers.begin(), peers.end(), |
412 | [&](const auto &p) { |
413 | if (auto pp = p.lock()) { |
414 | f(pp); |
415 | return false; |
416 | } |
417 | return true; |
418 | }), |
419 | peers.end()); |
420 | } |
421 | std::vector<private_api::ModelPeer> peers; |
422 | private_api::Property<bool> model_row_count_dirty_property; |
423 | private_api::Property<bool> model_row_data_dirty_property; |
424 | mutable std::vector<size_t> tracked_rows; |
425 | }; |
426 | |
427 | namespace private_api { |
428 | /// A Model backed by a std::array of constant size |
429 | /// \private |
430 | template<int Count, typename ModelData> |
431 | class ArrayModel : public Model<ModelData> |
432 | { |
433 | std::array<ModelData, Count> data; |
434 | |
435 | public: |
436 | /// Constructs a new ArrayModel by forwarding \a to the std::array constructor. |
437 | template<typename... A> |
438 | ArrayModel(A &&...a) : data { std::forward<A>(a)... } |
439 | { |
440 | } |
441 | size_t row_count() const override { return Count; } |
442 | std::optional<ModelData> row_data(size_t i) const override |
443 | { |
444 | if (i >= row_count()) |
445 | return {}; |
446 | return data[i]; |
447 | } |
448 | void set_row_data(size_t i, const ModelData &value) override |
449 | { |
450 | if (i < row_count()) { |
451 | data[i] = value; |
452 | this->row_changed(i); |
453 | } |
454 | } |
455 | }; |
456 | |
457 | // Specialize for the empty array. We can't have a Model<void>, but `int` will work for our purpose |
458 | template<> |
459 | class ArrayModel<0, void> : public Model<int> |
460 | { |
461 | public: |
462 | size_t row_count() const override { return 0; } |
463 | std::optional<int> row_data(size_t) const override { return {}; } |
464 | }; |
465 | |
466 | /// Model to be used when we just want to repeat without data. |
467 | struct UIntModel : Model<int> |
468 | { |
469 | /// Constructs a new IntModel with \a d rows. |
470 | UIntModel(uint32_t d) : data(d) { } |
471 | /// \private |
472 | uint32_t data; |
473 | /// \copydoc Model::row_count |
474 | size_t row_count() const override { return data; } |
475 | std::optional<int> row_data(size_t value) const override |
476 | { |
477 | if (value >= row_count()) |
478 | return {}; |
479 | return static_cast<int>(value); |
480 | } |
481 | }; |
482 | } // namespace private_api |
483 | |
484 | /// A Model backed by a SharedVector |
485 | template<typename ModelData> |
486 | class VectorModel : public Model<ModelData> |
487 | { |
488 | std::vector<ModelData> data; |
489 | |
490 | public: |
491 | /// Constructs a new empty VectorModel. |
492 | VectorModel() = default; |
493 | /// Constructs a new VectorModel from \a array. |
494 | VectorModel(std::vector<ModelData> array) : data(std::move(array)) { } |
495 | size_t row_count() const override { return data.size(); } |
496 | std::optional<ModelData> row_data(size_t i) const override |
497 | { |
498 | if (i >= row_count()) |
499 | return {}; |
500 | return std::optional<ModelData> { data[i] }; |
501 | } |
502 | void set_row_data(size_t i, const ModelData &value) override |
503 | { |
504 | if (i < row_count()) { |
505 | data[i] = value; |
506 | this->row_changed(i); |
507 | } |
508 | } |
509 | |
510 | /// Append a new row with the given value |
511 | void push_back(const ModelData &value) |
512 | { |
513 | data.push_back(value); |
514 | this->row_added(data.size() - 1, 1); |
515 | } |
516 | |
517 | /// Remove the row at the given index from the model |
518 | void erase(size_t index) |
519 | { |
520 | data.erase(data.begin() + index); |
521 | this->row_removed(index, 1); |
522 | } |
523 | |
524 | /// Inserts the given value as a new row at the specified index |
525 | void insert(size_t index, const ModelData &value) |
526 | { |
527 | data.insert(data.begin() + index, value); |
528 | this->row_added(index, 1); |
529 | } |
530 | |
531 | /// Erases all rows from the VectorModel. |
532 | void clear() |
533 | { |
534 | if (!data.empty()) { |
535 | data.clear(); |
536 | this->reset(); |
537 | } |
538 | } |
539 | |
540 | /// Replaces the underlying VectorModel's vector with \a array. |
541 | void set_vector(std::vector<ModelData> array) |
542 | { |
543 | data = std::move(array); |
544 | this->reset(); |
545 | } |
546 | }; |
547 | |
548 | template<typename ModelData> |
549 | class FilterModel; |
550 | |
551 | namespace private_api { |
552 | template<typename ModelData> |
553 | struct FilterModelInner : private_api::ModelChangeListener |
554 | { |
555 | FilterModelInner(std::shared_ptr<slint::Model<ModelData>> source_model, |
556 | std::function<bool(const ModelData &)> filter_fn, |
557 | slint::FilterModel<ModelData> &target_model) |
558 | : source_model(source_model), filter_fn(filter_fn), target_model(target_model) |
559 | { |
560 | update_mapping(); |
561 | } |
562 | |
563 | void row_added(size_t index, size_t count) override |
564 | { |
565 | if (count == 0) { |
566 | return; |
567 | } |
568 | |
569 | std::vector<size_t> added_accepted_rows; |
570 | for (auto i = index; i < index + count; ++i) { |
571 | if (auto data = source_model->row_data(i)) { |
572 | if (filter_fn(*data)) { |
573 | added_accepted_rows.push_back(x: i); |
574 | } |
575 | } |
576 | } |
577 | |
578 | if (added_accepted_rows.empty()) { |
579 | return; |
580 | } |
581 | |
582 | auto insertion_point = std::lower_bound(first: accepted_rows.begin(), last: accepted_rows.end(), val: index); |
583 | |
584 | insertion_point = accepted_rows.insert(position: insertion_point, first: added_accepted_rows.begin(), |
585 | last: added_accepted_rows.end()); |
586 | |
587 | for (auto it = insertion_point + added_accepted_rows.size(); it != accepted_rows.end(); |
588 | ++it) |
589 | (*it) += count; |
590 | |
591 | target_model.row_added(insertion_point - accepted_rows.begin(), added_accepted_rows.size()); |
592 | } |
593 | void row_changed(size_t index) override |
594 | { |
595 | auto existing_row = std::lower_bound(first: accepted_rows.begin(), last: accepted_rows.end(), val: index); |
596 | auto existing_row_index = std::distance(first: accepted_rows.begin(), last: existing_row); |
597 | bool is_contained = existing_row != accepted_rows.end() && *existing_row == index; |
598 | auto accepted_updated_row = filter_fn(*source_model->row_data(index)); |
599 | |
600 | if (is_contained && accepted_updated_row) { |
601 | target_model.row_changed(existing_row_index); |
602 | } else if (!is_contained && accepted_updated_row) { |
603 | accepted_rows.insert(position: existing_row, x: index); |
604 | target_model.row_added(existing_row_index, 1); |
605 | } else if (is_contained && !accepted_updated_row) { |
606 | accepted_rows.erase(position: existing_row); |
607 | target_model.row_removed(existing_row_index, 1); |
608 | } |
609 | } |
610 | void row_removed(size_t index, size_t count) override |
611 | { |
612 | auto mapped_row_start = std::lower_bound(first: accepted_rows.begin(), last: accepted_rows.end(), val: index); |
613 | auto mapped_row_end = |
614 | std::lower_bound(first: accepted_rows.begin(), last: accepted_rows.end(), val: index + count); |
615 | |
616 | auto mapped_removed_len = std::distance(first: mapped_row_start, last: mapped_row_end); |
617 | |
618 | auto mapped_removed_index = |
619 | (mapped_row_start != accepted_rows.end() && *mapped_row_start == index) |
620 | ? std::optional<int>(mapped_row_start - accepted_rows.begin()) |
621 | : std::nullopt; |
622 | |
623 | auto it = accepted_rows.erase(first: mapped_row_start, last: mapped_row_end); |
624 | for (; it != accepted_rows.end(); ++it) { |
625 | *it -= count; |
626 | } |
627 | |
628 | if (mapped_removed_index) { |
629 | target_model.row_removed(*mapped_removed_index, mapped_removed_len); |
630 | } |
631 | } |
632 | void reset() override |
633 | { |
634 | update_mapping(); |
635 | target_model.reset(); |
636 | } |
637 | |
638 | void update_mapping() |
639 | { |
640 | accepted_rows.clear(); |
641 | for (size_t i = 0, count = source_model->row_count(); i < count; ++i) { |
642 | if (auto data = source_model->row_data(i)) { |
643 | if (filter_fn(*data)) { |
644 | accepted_rows.push_back(x: i); |
645 | } |
646 | } |
647 | } |
648 | } |
649 | |
650 | std::shared_ptr<slint::Model<ModelData>> source_model; |
651 | std::function<bool(const ModelData &)> filter_fn; |
652 | std::vector<size_t> accepted_rows; |
653 | slint::FilterModel<ModelData> &target_model; |
654 | }; |
655 | } |
656 | |
657 | /// The FilterModel acts as an adapter model for a given source model by applying a filter |
658 | /// function. The filter function is called for each row on the source model and if the |
659 | /// filter accepts the row (i.e. returns true), the row is also visible in the FilterModel. |
660 | template<typename ModelData> |
661 | class FilterModel : public Model<ModelData> |
662 | { |
663 | friend struct private_api::FilterModelInner<ModelData>; |
664 | |
665 | public: |
666 | /// Constructs a new FilterModel that provides a limited view on the \a source_model by applying |
667 | /// \a filter_fn on each row. If the provided function returns true, the row is exposed by the |
668 | /// FilterModel. |
669 | FilterModel(std::shared_ptr<Model<ModelData>> source_model, |
670 | std::function<bool(const ModelData &)> filter_fn) |
671 | : inner(std::make_shared<private_api::FilterModelInner<ModelData>>( |
672 | std::move(source_model), std::move(filter_fn), *this)) |
673 | { |
674 | inner->source_model->attach_peer(inner); |
675 | } |
676 | |
677 | size_t row_count() const override { return inner->accepted_rows.size(); } |
678 | |
679 | std::optional<ModelData> row_data(size_t i) const override |
680 | { |
681 | if (i >= inner->accepted_rows.size()) |
682 | return {}; |
683 | return inner->source_model->row_data(inner->accepted_rows[i]); |
684 | } |
685 | |
686 | void set_row_data(size_t i, const ModelData &value) override |
687 | { |
688 | inner->source_model->set_row_data(inner->accepted_rows[i], value); |
689 | } |
690 | |
691 | /// Re-applies the model's filter function on each row of the source model. Use this if state |
692 | /// external to the filter function has changed. |
693 | void reset() { inner->reset(); } |
694 | |
695 | /// Given the \a filtered_row index, this function returns the corresponding row index in the |
696 | /// source model. |
697 | int unfiltered_row(int filtered_row) const { return inner->accepted_rows[filtered_row]; } |
698 | |
699 | /// Returns the source model of this filter model. |
700 | std::shared_ptr<Model<ModelData>> source_model() const { return inner->source_model; } |
701 | |
702 | private: |
703 | std::shared_ptr<private_api::FilterModelInner<ModelData>> inner; |
704 | }; |
705 | |
706 | template<typename SourceModelData, typename MappedModelData> |
707 | class MapModel; |
708 | |
709 | namespace private_api { |
710 | template<typename SourceModelData, typename MappedModelData> |
711 | struct MapModelInner : private_api::ModelChangeListener |
712 | { |
713 | MapModelInner(slint::MapModel<SourceModelData, MappedModelData> &target_model) |
714 | : target_model(target_model) |
715 | { |
716 | } |
717 | |
718 | void row_added(size_t index, size_t count) override { target_model.row_added(index, count); } |
719 | void row_changed(size_t index) override { target_model.row_changed(index); } |
720 | void row_removed(size_t index, size_t count) override |
721 | { |
722 | target_model.row_removed(index, count); |
723 | } |
724 | void reset() override { target_model.reset(); } |
725 | |
726 | slint::MapModel<SourceModelData, MappedModelData> &target_model; |
727 | }; |
728 | } |
729 | |
730 | /// The MapModel acts as an adapter model for a given source model by applying a mapping |
731 | /// function. The mapping function is called for each row on the source model and allows |
732 | /// transforming the values on the fly. The MapModel has two template parameters: The |
733 | /// SourceModelData specifies the data type of the underlying source model, and the |
734 | /// MappedModelData the data type of this MapModel. This permits not only changing the |
735 | /// values of the underlying source model, but also changing the data type itself. For |
736 | /// example a MapModel can be used to adapt a model that provides numbers to be a model |
737 | /// that exposes all numbers converted to strings, by calling `std::to_string` on each |
738 | /// value given in the mapping lambda expression. |
739 | template<typename SourceModelData, typename MappedModelData = SourceModelData> |
740 | class MapModel : public Model<MappedModelData> |
741 | { |
742 | friend struct private_api::MapModelInner<SourceModelData, MappedModelData>; |
743 | |
744 | public: |
745 | /// Constructs a new MapModel that provides an altered view on the \a source_model by applying |
746 | /// \a map_fn on the data in each row. |
747 | MapModel(std::shared_ptr<Model<SourceModelData>> source_model, |
748 | std::function<MappedModelData(const SourceModelData &)> map_fn) |
749 | : inner(std::make_shared<private_api::MapModelInner<SourceModelData, MappedModelData>>( |
750 | *this)), |
751 | model(source_model), |
752 | map_fn(map_fn) |
753 | { |
754 | model->attach_peer(inner); |
755 | } |
756 | |
757 | size_t row_count() const override { return model->row_count(); } |
758 | |
759 | std::optional<MappedModelData> row_data(size_t i) const override |
760 | { |
761 | if (auto source_data = model->row_data(i)) |
762 | return map_fn(*source_data); |
763 | else |
764 | return {}; |
765 | } |
766 | |
767 | /// Returns the source model of this filter model. |
768 | std::shared_ptr<Model<SourceModelData>> source_model() const { return model; } |
769 | |
770 | private: |
771 | std::shared_ptr<private_api::MapModelInner<SourceModelData, MappedModelData>> inner; |
772 | std::shared_ptr<slint::Model<SourceModelData>> model; |
773 | std::function<MappedModelData(const SourceModelData &)> map_fn; |
774 | }; |
775 | |
776 | template<typename ModelData> |
777 | class SortModel; |
778 | |
779 | namespace private_api { |
780 | template<typename ModelData> |
781 | struct SortModelInner : private_api::ModelChangeListener |
782 | { |
783 | SortModelInner(std::shared_ptr<slint::Model<ModelData>> source_model, |
784 | std::function<bool(const ModelData &, const ModelData &)> comp, |
785 | slint::SortModel<ModelData> &target_model) |
786 | : source_model(source_model), comp(comp), target_model(target_model) |
787 | { |
788 | } |
789 | |
790 | void row_added(size_t first_inserted_row, size_t count) override |
791 | { |
792 | if (sorted_rows_dirty) { |
793 | reset(); |
794 | return; |
795 | } |
796 | |
797 | // Adjust the existing sorted row indices to match the updated source model |
798 | for (auto &row : sorted_rows) { |
799 | if (row >= first_inserted_row) |
800 | row += count; |
801 | } |
802 | |
803 | for (size_t row = first_inserted_row; row < first_inserted_row + count; ++row) { |
804 | |
805 | ModelData inserted_value = *source_model->row_data(row); |
806 | auto insertion_point = |
807 | std::lower_bound(sorted_rows.begin(), sorted_rows.end(), inserted_value, |
808 | [this](size_t sorted_row, const ModelData &inserted_value) { |
809 | auto sorted_elem = source_model->row_data(sorted_row); |
810 | return comp(*sorted_elem, inserted_value); |
811 | }); |
812 | |
813 | insertion_point = sorted_rows.insert(insertion_point, row); |
814 | target_model.row_added(std::distance(sorted_rows.begin(), insertion_point), 1); |
815 | } |
816 | } |
817 | void row_changed(size_t changed_row) override |
818 | { |
819 | if (sorted_rows_dirty) { |
820 | reset(); |
821 | return; |
822 | } |
823 | |
824 | auto removed_row_it = |
825 | sorted_rows.erase(position: std::find(first: sorted_rows.begin(), last: sorted_rows.end(), val: changed_row)); |
826 | auto removed_row = std::distance(first: sorted_rows.begin(), last: removed_row_it); |
827 | |
828 | ModelData changed_value = *source_model->row_data(changed_row); |
829 | auto insertion_point = |
830 | std::lower_bound(sorted_rows.begin(), sorted_rows.end(), changed_value, |
831 | [this](size_t sorted_row, const ModelData &changed_value) { |
832 | auto sorted_elem = source_model->row_data(sorted_row); |
833 | return comp(*sorted_elem, changed_value); |
834 | }); |
835 | |
836 | insertion_point = sorted_rows.insert(insertion_point, changed_row); |
837 | auto inserted_row = std::distance(sorted_rows.begin(), insertion_point); |
838 | |
839 | if (inserted_row == removed_row) { |
840 | target_model.row_changed(removed_row); |
841 | } else { |
842 | target_model.row_removed(removed_row, 1); |
843 | target_model.row_added(inserted_row, 1); |
844 | } |
845 | } |
846 | void row_removed(size_t first_removed_row, size_t count) override |
847 | { |
848 | if (sorted_rows_dirty) { |
849 | reset(); |
850 | return; |
851 | } |
852 | |
853 | std::vector<size_t> removed_rows; |
854 | removed_rows.reserve(n: count); |
855 | |
856 | for (auto it = sorted_rows.begin(); it != sorted_rows.end();) { |
857 | if (*it >= first_removed_row) { |
858 | if (*it < first_removed_row + count) { |
859 | removed_rows.push_back(x: std::distance(first: sorted_rows.begin(), last: it)); |
860 | it = sorted_rows.erase(position: it); |
861 | continue; |
862 | } else { |
863 | *it -= count; |
864 | } |
865 | } |
866 | ++it; |
867 | } |
868 | |
869 | for (auto removed_row : removed_rows) { |
870 | target_model.row_removed(removed_row, 1); |
871 | } |
872 | } |
873 | |
874 | void reset() override |
875 | { |
876 | sorted_rows_dirty = true; |
877 | target_model.reset(); |
878 | } |
879 | |
880 | void ensure_sorted() |
881 | { |
882 | if (!sorted_rows_dirty) { |
883 | return; |
884 | } |
885 | |
886 | sorted_rows.resize(source_model->row_count()); |
887 | for (size_t i = 0; i < sorted_rows.size(); ++i) |
888 | sorted_rows[i] = i; |
889 | |
890 | std::sort(sorted_rows.begin(), sorted_rows.end(), [this](auto lhs_index, auto rhs_index) { |
891 | auto lhs_elem = source_model->row_data(lhs_index); |
892 | auto rhs_elem = source_model->row_data(rhs_index); |
893 | return comp(*lhs_elem, *rhs_elem); |
894 | }); |
895 | |
896 | sorted_rows_dirty = false; |
897 | } |
898 | |
899 | std::shared_ptr<slint::Model<ModelData>> source_model; |
900 | std::function<bool(const ModelData &, const ModelData &)> comp; |
901 | slint::SortModel<ModelData> &target_model; |
902 | std::vector<size_t> sorted_rows; |
903 | bool sorted_rows_dirty = true; |
904 | }; |
905 | } |
906 | |
907 | /// The SortModel acts as an adapter model for a given source model by sorting all rows |
908 | /// with by order provided by the given sorting function. The sorting function is called for |
909 | /// pairs of elements of the source model. |
910 | template<typename ModelData> |
911 | class SortModel : public Model<ModelData> |
912 | { |
913 | friend struct private_api::SortModelInner<ModelData>; |
914 | |
915 | public: |
916 | /// Constructs a new SortModel that provides a sorted view on the \a source_model by applying |
917 | /// the order given by the specified \a comp. |
918 | SortModel(std::shared_ptr<Model<ModelData>> source_model, |
919 | std::function<bool(const ModelData &, const ModelData &)> comp) |
920 | : inner(std::make_shared<private_api::SortModelInner<ModelData>>(std::move(source_model), |
921 | std::move(comp), *this)) |
922 | { |
923 | inner->source_model->attach_peer(inner); |
924 | } |
925 | |
926 | size_t row_count() const override { return inner->source_model->row_count(); } |
927 | |
928 | std::optional<ModelData> row_data(size_t i) const override |
929 | { |
930 | inner->ensure_sorted(); |
931 | return inner->source_model->row_data(inner->sorted_rows[i]); |
932 | } |
933 | |
934 | void set_row_data(size_t i, const ModelData &value) override |
935 | { |
936 | inner->source_model->set_row_data(inner->sorted_rows[i], value); |
937 | } |
938 | |
939 | /// Re-applies the model's sort function on each row of the source model. Use this if state |
940 | /// external to the sort function has changed. |
941 | void reset() { inner->reset(); } |
942 | |
943 | /// Given the \a sorted_row_index, this function returns the corresponding row index in the |
944 | /// source model. |
945 | int unsorted_row(int sorted_row_index) const |
946 | { |
947 | inner->ensure_sorted(); |
948 | return inner->sorted_rows[sorted_row_index]; |
949 | } |
950 | |
951 | /// Returns the source model of this filter model. |
952 | std::shared_ptr<Model<ModelData>> source_model() const { return inner->source_model; } |
953 | |
954 | private: |
955 | std::shared_ptr<private_api::SortModelInner<ModelData>> inner; |
956 | }; |
957 | |
958 | template<typename ModelData> |
959 | class ReverseModel; |
960 | |
961 | namespace private_api { |
962 | template<typename ModelData> |
963 | struct ReverseModelInner : private_api::ModelChangeListener |
964 | { |
965 | ReverseModelInner(std::shared_ptr<slint::Model<ModelData>> source_model, |
966 | slint::ReverseModel<ModelData> &target_model) |
967 | : source_model(source_model), target_model(target_model) |
968 | { |
969 | } |
970 | |
971 | void row_added(size_t first_inserted_row, size_t count) override |
972 | { |
973 | auto row_count = source_model->row_count(); |
974 | auto old_row_count = row_count - count; |
975 | auto row = old_row_count - first_inserted_row; |
976 | |
977 | target_model.row_added(row, count); |
978 | } |
979 | |
980 | void row_changed(size_t changed_row) override |
981 | { |
982 | target_model.row_changed(source_model->row_count() - 1 - changed_row); |
983 | } |
984 | |
985 | void row_removed(size_t first_removed_row, size_t count) override |
986 | { |
987 | target_model.row_removed(source_model->row_count() - first_removed_row, count); |
988 | } |
989 | |
990 | void reset() override { source_model.reset(); } |
991 | |
992 | std::shared_ptr<slint::Model<ModelData>> source_model; |
993 | slint::ReverseModel<ModelData> &target_model; |
994 | }; |
995 | } |
996 | |
997 | /// The ReverseModel acts as an adapter model for a given source model by reserving all rows. |
998 | /// This means that the first row in the source model is the last row of this model, the second |
999 | /// row is the second last, and so on. |
1000 | template<typename ModelData> |
1001 | class ReverseModel : public Model<ModelData> |
1002 | { |
1003 | friend struct private_api::ReverseModelInner<ModelData>; |
1004 | |
1005 | public: |
1006 | /// Constructs a new ReverseModel that provides a reversed view on the \a source_model. |
1007 | ReverseModel(std::shared_ptr<Model<ModelData>> source_model) |
1008 | : inner(std::make_shared<private_api::ReverseModelInner<ModelData>>(std::move(source_model), |
1009 | *this)) |
1010 | { |
1011 | inner->source_model->attach_peer(inner); |
1012 | } |
1013 | |
1014 | size_t row_count() const override { return inner->source_model->row_count(); } |
1015 | |
1016 | std::optional<ModelData> row_data(size_t i) const override |
1017 | { |
1018 | auto count = inner->source_model->row_count(); |
1019 | return inner->source_model->row_data(count - i - 1); |
1020 | } |
1021 | |
1022 | void set_row_data(size_t i, const ModelData &value) override |
1023 | { |
1024 | auto count = inner->source_model->row_count(); |
1025 | inner->source_model->set_row_data(count - i - 1, value); |
1026 | } |
1027 | |
1028 | /// Returns the source model of this reserve model. |
1029 | std::shared_ptr<Model<ModelData>> source_model() const { return inner->source_model; } |
1030 | |
1031 | private: |
1032 | std::shared_ptr<private_api::ReverseModelInner<ModelData>> inner; |
1033 | }; |
1034 | |
1035 | namespace private_api { |
1036 | |
1037 | template<typename C, typename ModelData> |
1038 | class Repeater |
1039 | { |
1040 | private_api::Property<std::shared_ptr<Model<ModelData>>> model; |
1041 | |
1042 | struct RepeaterInner : ModelChangeListener |
1043 | { |
1044 | enum class State { Clean, Dirty }; |
1045 | struct RepeatedInstanceWithState |
1046 | { |
1047 | State state = State::Dirty; |
1048 | std::optional<ComponentHandle<C>> ptr; |
1049 | }; |
1050 | std::vector<RepeatedInstanceWithState> data; |
1051 | private_api::Property<bool> is_dirty { true }; |
1052 | std::shared_ptr<Model<ModelData>> model; |
1053 | |
1054 | void row_added(size_t index, size_t count) override |
1055 | { |
1056 | is_dirty.set(true); |
1057 | data.resize(data.size() + count); |
1058 | std::rotate(data.begin() + index, data.end() - count, data.end()); |
1059 | for (std::size_t i = index; i < data.size(); ++i) { |
1060 | // all the indexes are dirty |
1061 | data[i].state = State::Dirty; |
1062 | } |
1063 | } |
1064 | void row_changed(size_t index) override |
1065 | { |
1066 | auto &c = data[index]; |
1067 | if (model && c.ptr) { |
1068 | (*c.ptr)->update_data(index, *model->row_data(index)); |
1069 | c.state = State::Clean; |
1070 | } else { |
1071 | c.state = State::Dirty; |
1072 | } |
1073 | } |
1074 | void row_removed(size_t index, size_t count) override |
1075 | { |
1076 | is_dirty.set(true); |
1077 | data.erase(data.begin() + index, data.begin() + index + count); |
1078 | for (std::size_t i = index; i < data.size(); ++i) { |
1079 | // all the indexes are dirty |
1080 | data[i].state = State::Dirty; |
1081 | } |
1082 | } |
1083 | void reset() override |
1084 | { |
1085 | is_dirty.set(true); |
1086 | data.clear(); |
1087 | } |
1088 | }; |
1089 | |
1090 | public: |
1091 | // FIXME: should be private, but layouting code uses it. |
1092 | mutable std::shared_ptr<RepeaterInner> inner; |
1093 | |
1094 | template<typename F> |
1095 | void set_model_binding(F &&binding) const |
1096 | { |
1097 | model.set_binding(std::forward<F>(binding)); |
1098 | } |
1099 | |
1100 | template<typename Parent> |
1101 | void ensure_updated(const Parent *parent) const |
1102 | { |
1103 | if (model.is_dirty()) { |
1104 | inner = std::make_shared<RepeaterInner>(); |
1105 | if (auto m = model.get()) { |
1106 | inner->model = m; |
1107 | m->attach_peer(inner); |
1108 | } |
1109 | } |
1110 | |
1111 | if (inner && inner->is_dirty.get()) { |
1112 | inner->is_dirty.set(false); |
1113 | if (auto m = model.get()) { |
1114 | auto count = m->row_count(); |
1115 | inner->data.resize(count); |
1116 | for (size_t i = 0; i < count; ++i) { |
1117 | auto &c = inner->data[i]; |
1118 | bool created = false; |
1119 | if (!c.ptr) { |
1120 | c.ptr = C::create(parent); |
1121 | created = true; |
1122 | } |
1123 | if (c.state == RepeaterInner::State::Dirty) { |
1124 | (*c.ptr)->update_data(i, *m->row_data(i)); |
1125 | } |
1126 | if (created) { |
1127 | (*c.ptr)->init(); |
1128 | } |
1129 | } |
1130 | } else { |
1131 | inner->data.clear(); |
1132 | } |
1133 | } else { |
1134 | // just do a get() on the model to register dependencies so that, for example, the |
1135 | // layout property tracker becomes dirty. |
1136 | model.get(); |
1137 | } |
1138 | } |
1139 | |
1140 | template<typename Parent> |
1141 | void ensure_updated_listview(const Parent *parent, |
1142 | const private_api::Property<float> *viewport_width, |
1143 | const private_api::Property<float> *viewport_height, |
1144 | [[maybe_unused]] const private_api::Property<float> *viewport_y, |
1145 | float listview_width, [[maybe_unused]] float listview_height) const |
1146 | { |
1147 | // TODO: the rust code in model.rs try to only allocate as many items as visible items |
1148 | ensure_updated(parent); |
1149 | |
1150 | float h = compute_layout_listview(viewport_width, listview_width); |
1151 | viewport_height->set(h); |
1152 | } |
1153 | |
1154 | uint64_t visit(TraversalOrder order, private_api::ItemVisitorRefMut visitor) const |
1155 | { |
1156 | for (std::size_t i = 0; i < inner->data.size(); ++i) { |
1157 | auto index = order == TraversalOrder::BackToFront ? i : inner->data.size() - 1 - i; |
1158 | auto ref = item_at(i: index); |
1159 | if (ref.vtable->visit_children_item(ref, -1, order, visitor) |
1160 | != std::numeric_limits<uint64_t>::max()) { |
1161 | return index; |
1162 | } |
1163 | } |
1164 | return std::numeric_limits<uint64_t>::max(); |
1165 | } |
1166 | |
1167 | vtable::VRef<private_api::ItemTreeVTable> item_at(int i) const |
1168 | { |
1169 | const auto &x = inner->data.at(i); |
1170 | return { &C::static_vtable, const_cast<C *>(&(**x.ptr)) }; |
1171 | } |
1172 | |
1173 | vtable::VWeak<private_api::ItemTreeVTable> instance_at(std::size_t i) const |
1174 | { |
1175 | if (i >= inner->data.size()) { |
1176 | return {}; |
1177 | } |
1178 | const auto &x = inner->data.at(i); |
1179 | return vtable::VWeak<private_api::ItemTreeVTable> { x.ptr->into_dyn() }; |
1180 | } |
1181 | |
1182 | private_api::IndexRange index_range() const |
1183 | { |
1184 | return private_api::IndexRange { 0, inner->data.size() }; |
1185 | } |
1186 | |
1187 | float compute_layout_listview(const private_api::Property<float> *viewport_width, |
1188 | float listview_width) const |
1189 | { |
1190 | float offset = 0; |
1191 | viewport_width->set(listview_width); |
1192 | if (!inner) |
1193 | return offset; |
1194 | for (auto &x : inner->data) { |
1195 | (*x.ptr)->listview_layout(&offset, viewport_width); |
1196 | } |
1197 | return offset; |
1198 | } |
1199 | |
1200 | void model_set_row_data(size_t row, const ModelData &data) const |
1201 | { |
1202 | if (model.is_dirty()) { |
1203 | std::abort(); |
1204 | } |
1205 | if (auto m = model.get()) { |
1206 | if (row < m->row_count()) { |
1207 | m->set_row_data(row, data); |
1208 | } |
1209 | } |
1210 | } |
1211 | }; |
1212 | |
1213 | inline SharedString translate(const SharedString &original, const SharedString &context, |
1214 | const SharedString &domain, |
1215 | cbindgen_private::Slice<SharedString> arguments, int n, |
1216 | const SharedString &plural) |
1217 | { |
1218 | SharedString result = original; |
1219 | cbindgen_private::slint_translate(to_translate: &result, context: &context, domain: &domain, arguments, n, plural: &plural); |
1220 | return result; |
1221 | } |
1222 | |
1223 | } // namespace private_api |
1224 | |
1225 | #if !defined(DOXYGEN) |
1226 | cbindgen_private::Flickable::Flickable() |
1227 | { |
1228 | slint_flickable_data_init(data: &data); |
1229 | } |
1230 | cbindgen_private::Flickable::~Flickable() |
1231 | { |
1232 | slint_flickable_data_free(data: &data); |
1233 | } |
1234 | |
1235 | cbindgen_private::NativeStyleMetrics::NativeStyleMetrics(void *) |
1236 | { |
1237 | slint_native_style_metrics_init(self_: this); |
1238 | } |
1239 | |
1240 | cbindgen_private::NativeStyleMetrics::~NativeStyleMetrics() |
1241 | { |
1242 | slint_native_style_metrics_deinit(self_: this); |
1243 | } |
1244 | |
1245 | cbindgen_private::NativePalette::NativePalette(void *) |
1246 | { |
1247 | slint_native_palette_init(self_: this); |
1248 | } |
1249 | |
1250 | cbindgen_private::NativePalette::~NativePalette() |
1251 | { |
1252 | slint_native_palette_deinit(self_: this); |
1253 | } |
1254 | #endif // !defined(DOXYGEN) |
1255 | |
1256 | namespace private_api { |
1257 | // Was used in Slint <= 1.1.0 to have an error message in case of mismatch |
1258 | template<int Major, int Minor, int Patch> |
1259 | struct [[deprecated]] VersionCheckHelper |
1260 | { |
1261 | }; |
1262 | } |
1263 | |
1264 | /// Enum for the event loop mode parameter of the slint::run_event_loop() function. |
1265 | /// It is used to determine when the event loop quits. |
1266 | enum class EventLoopMode { |
1267 | /// The event loop will quit when the last window is closed |
1268 | /// or when slint::quit_event_loop() is called. |
1269 | QuitOnLastWindowClosed, |
1270 | |
1271 | /// The event loop will keep running until slint::quit_event_loop() is called, |
1272 | /// even when all windows are closed. |
1273 | RunUntilQuit |
1274 | }; |
1275 | |
1276 | /// Enters the main event loop. This is necessary in order to receive |
1277 | /// events from the windowing system in order to render to the screen |
1278 | /// and react to user input. |
1279 | /// |
1280 | /// The mode parameter determines the behavior of the event loop when all windows are closed. |
1281 | /// By default, it is set to QuitOnLastWindowClose, which means the event loop will |
1282 | /// quit when the last window is closed. |
1283 | inline void run_event_loop(EventLoopMode mode = EventLoopMode::QuitOnLastWindowClosed) |
1284 | { |
1285 | private_api::assert_main_thread(); |
1286 | cbindgen_private::slint_run_event_loop(quit_on_last_window_closed: mode == EventLoopMode::QuitOnLastWindowClosed); |
1287 | } |
1288 | |
1289 | /// Schedules the main event loop for termination. This function is meant |
1290 | /// to be called from callbacks triggered by the UI. After calling the function, |
1291 | /// it will return immediately and once control is passed back to the event loop, |
1292 | /// the initial call to slint::run_event_loop() will return. |
1293 | inline void quit_event_loop() |
1294 | { |
1295 | cbindgen_private::slint_quit_event_loop(); |
1296 | } |
1297 | |
1298 | /// Adds the specified functor to an internal queue, notifies the event loop to wake up. |
1299 | /// Once woken up, any queued up functors will be invoked. |
1300 | /// This function is thread-safe and can be called from any thread, including the one |
1301 | /// running the event loop. The provided functors will only be invoked from the thread |
1302 | /// that started the event loop. |
1303 | /// |
1304 | /// You can use this to set properties or use any other Slint APIs from other threads, |
1305 | /// by collecting the code in a functor and queuing it up for invocation within the event loop. |
1306 | /// |
1307 | /// The following example assumes that a status message received from a network thread is |
1308 | /// shown in the UI: |
1309 | /// |
1310 | /// ``` |
1311 | /// #include "my_application_ui.h" |
1312 | /// #include <thread> |
1313 | /// |
1314 | /// int main(int argc, char **argv) |
1315 | /// { |
1316 | /// auto ui = NetworkStatusUI::create(); |
1317 | /// ui->set_status_label("Pending"); |
1318 | /// |
1319 | /// slint::ComponentWeakHandle<NetworkStatusUI> weak_ui_handle(ui); |
1320 | /// std::thread network_thread([=]{ |
1321 | /// std::string message = read_message_blocking_from_network(); |
1322 | /// slint::invoke_from_event_loop([&]() { |
1323 | /// if (auto ui = weak_ui_handle.lock()) { |
1324 | /// ui->set_status_label(message); |
1325 | /// } |
1326 | /// }); |
1327 | /// }); |
1328 | /// ... |
1329 | /// ui->run(); |
1330 | /// ... |
1331 | /// } |
1332 | /// ``` |
1333 | /// |
1334 | /// See also blocking_invoke_from_event_loop() for a blocking version of this function |
1335 | template<std::invocable Functor> |
1336 | void invoke_from_event_loop(Functor f) |
1337 | { |
1338 | cbindgen_private::slint_post_event( |
1339 | event: [](void *data) { (*reinterpret_cast<Functor *>(data))(); }, user_data: new Functor(std::move(f)), |
1340 | drop_user_data: [](void *data) { delete reinterpret_cast<Functor *>(data); }); |
1341 | } |
1342 | |
1343 | #ifndef SLINT_FEATURE_FREESTANDING |
1344 | |
1345 | /// Blocking version of invoke_from_event_loop() |
1346 | /// |
1347 | /// Just like invoke_from_event_loop(), this will run the specified functor from the thread running |
1348 | /// the slint event loop. But it will block until the execution of the functor is finished, |
1349 | /// and return that value. |
1350 | /// |
1351 | /// This function must be called from a different thread than the thread that runs the event loop |
1352 | /// otherwise it will result in a deadlock. Calling this function if the event loop is not running |
1353 | /// will also block forever or until the event loop is started in another thread. |
1354 | /// |
1355 | /// The following example is reading the message property from a thread |
1356 | /// |
1357 | /// ``` |
1358 | /// #include "my_application_ui.h" |
1359 | /// #include <thread> |
1360 | /// |
1361 | /// int main(int argc, char **argv) |
1362 | /// { |
1363 | /// auto ui = MyApplicationUI::create(); |
1364 | /// ui->set_status_label("Pending"); |
1365 | /// |
1366 | /// std::thread worker_thread([ui]{ |
1367 | /// while (...) { |
1368 | /// auto message = slint::blocking_invoke_from_event_loop([ui]() { |
1369 | /// return ui->get_message(); |
1370 | /// } |
1371 | /// do_something(message); |
1372 | /// ... |
1373 | /// }); |
1374 | /// }); |
1375 | /// ... |
1376 | /// ui->run(); |
1377 | /// ... |
1378 | /// } |
1379 | /// ``` |
1380 | template<std::invocable Functor> |
1381 | auto blocking_invoke_from_event_loop(Functor f) -> std::invoke_result_t<Functor> |
1382 | { |
1383 | std::optional<std::invoke_result_t<Functor>> result; |
1384 | std::mutex mtx; |
1385 | std::condition_variable cv; |
1386 | invoke_from_event_loop([&] { |
1387 | auto r = f(); |
1388 | std::unique_lock lock(mtx); |
1389 | result = std::move(r); |
1390 | cv.notify_one(); |
1391 | }); |
1392 | std::unique_lock lock(mtx); |
1393 | cv.wait(lock, [&] { return result.has_value(); }); |
1394 | return std::move(*result); |
1395 | } |
1396 | |
1397 | # if !defined(DOXYGEN) // Doxygen doesn't see this as an overload of the previous one |
1398 | // clang-format off |
1399 | template<std::invocable Functor> |
1400 | requires(std::is_void_v<std::invoke_result_t<Functor>>) |
1401 | void blocking_invoke_from_event_loop(Functor f) |
1402 | // clang-format on |
1403 | { |
1404 | std::mutex mtx; |
1405 | std::condition_variable cv; |
1406 | bool ok = false; |
1407 | invoke_from_event_loop([&] { |
1408 | f(); |
1409 | std::unique_lock lock(mtx); |
1410 | ok = true; |
1411 | cv.notify_one(); |
1412 | }); |
1413 | std::unique_lock lock(mtx); |
1414 | cv.wait(lock, [&] { return ok; }); |
1415 | } |
1416 | # endif |
1417 | #endif |
1418 | |
1419 | } // namespace slint |
1420 | |