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
39namespace slint {
40
41namespace private_api {
42// Bring opaque structure in scope
43using namespace cbindgen_private;
44using ItemTreeRef = vtable::VRef<private_api::ItemTreeVTable>;
45using IndexRange = cbindgen_private::IndexRange;
46using ItemRef = vtable::VRef<private_api::ItemVTable>;
47using ItemVisitorRefMut = vtable::VRefMut<cbindgen_private::ItemVisitorVTable>;
48using ItemTreeNode = cbindgen_private::ItemTreeNode;
49using ItemArrayEntry =
50 vtable::VOffset<uint8_t, slint::cbindgen_private::ItemVTable, vtable::AllowPin>;
51using ItemArray = slint::cbindgen_private::Slice<ItemArrayEntry>;
52
53constexpr 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
62constexpr 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
68inline 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
78inline 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
85inline 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
97template<typename T>
98inline 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
119template<typename T>
120class 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>`
125template<typename T>
126class ComponentHandle
127{
128 vtable::VRc<private_api::ItemTreeVTable, T> inner;
129 friend class ComponentWeakHandle<T>;
130
131public:
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>`
165template<typename T>
166class ComponentWeakHandle
167{
168 vtable::VWeak<private_api::ItemTreeVTable, T> inner;
169
170public:
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
189namespace cbindgen_private {
190inline 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}
200inline 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
211namespace private_api {
212
213inline 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
221inline 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
231inline 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
238inline cbindgen_private::LayoutInfo
239grid_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
245inline cbindgen_private::LayoutInfo
246box_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
253inline cbindgen_private::LayoutInfo
254box_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
261inline 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
268struct 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};
276using ModelPeer = std::weak_ptr<ModelChangeListener>;
277
278template<typename M>
279auto 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
290template<typename M>
291long 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
307template<typename ModelData>
308class Model
309{
310public:
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
371protected:
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
406private:
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
427namespace private_api {
428/// A Model backed by a std::array of constant size
429/// \private
430template<int Count, typename ModelData>
431class ArrayModel : public Model<ModelData>
432{
433 std::array<ModelData, Count> data;
434
435public:
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
458template<>
459class ArrayModel<0, void> : public Model<int>
460{
461public:
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.
467struct 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
485template<typename ModelData>
486class VectorModel : public Model<ModelData>
487{
488 std::vector<ModelData> data;
489
490public:
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
548template<typename ModelData>
549class FilterModel;
550
551namespace private_api {
552template<typename ModelData>
553struct 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.
660template<typename ModelData>
661class FilterModel : public Model<ModelData>
662{
663 friend struct private_api::FilterModelInner<ModelData>;
664
665public:
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
702private:
703 std::shared_ptr<private_api::FilterModelInner<ModelData>> inner;
704};
705
706template<typename SourceModelData, typename MappedModelData>
707class MapModel;
708
709namespace private_api {
710template<typename SourceModelData, typename MappedModelData>
711struct 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.
739template<typename SourceModelData, typename MappedModelData = SourceModelData>
740class MapModel : public Model<MappedModelData>
741{
742 friend struct private_api::MapModelInner<SourceModelData, MappedModelData>;
743
744public:
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
770private:
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
776template<typename ModelData>
777class SortModel;
778
779namespace private_api {
780template<typename ModelData>
781struct 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.
910template<typename ModelData>
911class SortModel : public Model<ModelData>
912{
913 friend struct private_api::SortModelInner<ModelData>;
914
915public:
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
954private:
955 std::shared_ptr<private_api::SortModelInner<ModelData>> inner;
956};
957
958template<typename ModelData>
959class ReverseModel;
960
961namespace private_api {
962template<typename ModelData>
963struct 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.
1000template<typename ModelData>
1001class ReverseModel : public Model<ModelData>
1002{
1003 friend struct private_api::ReverseModelInner<ModelData>;
1004
1005public:
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
1031private:
1032 std::shared_ptr<private_api::ReverseModelInner<ModelData>> inner;
1033};
1034
1035namespace private_api {
1036
1037template<typename C, typename ModelData>
1038class 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
1090public:
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
1213inline 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)
1226cbindgen_private::Flickable::Flickable()
1227{
1228 slint_flickable_data_init(data: &data);
1229}
1230cbindgen_private::Flickable::~Flickable()
1231{
1232 slint_flickable_data_free(data: &data);
1233}
1234
1235cbindgen_private::NativeStyleMetrics::NativeStyleMetrics(void *)
1236{
1237 slint_native_style_metrics_init(self_: this);
1238}
1239
1240cbindgen_private::NativeStyleMetrics::~NativeStyleMetrics()
1241{
1242 slint_native_style_metrics_deinit(self_: this);
1243}
1244
1245cbindgen_private::NativePalette::NativePalette(void *)
1246{
1247 slint_native_palette_init(self_: this);
1248}
1249
1250cbindgen_private::NativePalette::~NativePalette()
1251{
1252 slint_native_palette_deinit(self_: this);
1253}
1254#endif // !defined(DOXYGEN)
1255
1256namespace private_api {
1257// Was used in Slint <= 1.1.0 to have an error message in case of mismatch
1258template<int Major, int Minor, int Patch>
1259struct [[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.
1266enum 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.
1283inline 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.
1293inline 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
1335template<std::invocable Functor>
1336void 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/// ```
1380template<std::invocable Functor>
1381auto 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
1399template<std::invocable Functor>
1400 requires(std::is_void_v<std::invoke_result_t<Functor>>)
1401void 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

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