mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-03 18:29:09 +00:00

We have a nice FFI-compatible wrapper areound rust timers in C++, so allow to use that in other types exported to C++. This also makes sure the rust and C++ side agree on the size of the type, with the nice side effect that the rust Timer now uses half as much space now as it did before.
1393 lines
48 KiB
C++
1393 lines
48 KiB
C++
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
|
|
|
#pragma once
|
|
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
// In C++17, it is conditionally supported, but still valid for all compiler we care
|
|
# pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
|
#endif
|
|
|
|
#include "slint_internal.h"
|
|
#include "slint_size.h"
|
|
#include "slint_point.h"
|
|
#include "slint_platform_internal.h"
|
|
#include "slint_qt_internal.h"
|
|
#include "slint_window.h"
|
|
|
|
#include <vector>
|
|
#include <memory>
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <optional>
|
|
#include <span>
|
|
#include <functional>
|
|
#include <concepts>
|
|
|
|
#ifndef SLINT_FEATURE_FREESTANDING
|
|
# include <mutex>
|
|
# include <condition_variable>
|
|
#endif
|
|
|
|
/// \rst
|
|
/// The :code:`slint` namespace is the primary entry point into the Slint C++ API.
|
|
/// All available types are in this namespace.
|
|
///
|
|
/// See the :doc:`Overview <../overview>` documentation for the C++ integration how
|
|
/// to load :code:`.slint` designs.
|
|
/// \endrst
|
|
namespace slint {
|
|
|
|
// Bring opaque structure in scope
|
|
namespace private_api {
|
|
using cbindgen_private::ItemTreeVTable;
|
|
using cbindgen_private::ItemVTable;
|
|
using ItemTreeRef = vtable::VRef<private_api::ItemTreeVTable>;
|
|
using IndexRange = cbindgen_private::IndexRange;
|
|
using ItemRef = vtable::VRef<private_api::ItemVTable>;
|
|
using ItemVisitorRefMut = vtable::VRefMut<cbindgen_private::ItemVisitorVTable>;
|
|
using cbindgen_private::ItemTreeWeak;
|
|
using cbindgen_private::ItemWeak;
|
|
using cbindgen_private::TraversalOrder;
|
|
}
|
|
|
|
namespace private_api {
|
|
using ItemTreeNode = cbindgen_private::ItemTreeNode;
|
|
using ItemArrayEntry =
|
|
vtable::VOffset<uint8_t, slint::cbindgen_private::ItemVTable, vtable::AllowPin>;
|
|
using ItemArray = slint::cbindgen_private::Slice<ItemArrayEntry>;
|
|
using cbindgen_private::KeyboardModifiers;
|
|
using cbindgen_private::KeyEvent;
|
|
using cbindgen_private::PointerEvent;
|
|
using cbindgen_private::PointerScrollEvent;
|
|
using cbindgen_private::TableColumn;
|
|
|
|
constexpr inline ItemTreeNode make_item_node(uint32_t child_count, uint32_t child_index,
|
|
uint32_t parent_index, uint32_t item_array_index,
|
|
bool is_accessible)
|
|
{
|
|
return ItemTreeNode { ItemTreeNode::Item_Body { ItemTreeNode::Tag::Item, is_accessible,
|
|
child_count, child_index, parent_index,
|
|
item_array_index } };
|
|
}
|
|
|
|
constexpr inline ItemTreeNode make_dyn_node(std::uint32_t offset, std::uint32_t parent_index)
|
|
{
|
|
return ItemTreeNode { ItemTreeNode::DynamicTree_Body { ItemTreeNode::Tag::DynamicTree, offset,
|
|
parent_index } };
|
|
}
|
|
|
|
inline ItemRef get_item_ref(ItemTreeRef item_tree,
|
|
const cbindgen_private::Slice<ItemTreeNode> item_tree_array,
|
|
const private_api::ItemArray item_array, int index)
|
|
{
|
|
const auto item_array_index = item_tree_array.ptr[index].item.item_array_index;
|
|
const auto item = item_array[item_array_index];
|
|
return ItemRef { item.vtable, reinterpret_cast<char *>(item_tree.instance) + item.offset };
|
|
}
|
|
|
|
/// Convert a slint `{height: length, width: length, x: length, y: length}` to a Rect
|
|
inline cbindgen_private::Rect convert_anonymous_rect(std::tuple<float, float, float, float> tuple)
|
|
{
|
|
// alphabetical order
|
|
auto [h, w, x, y] = tuple;
|
|
return cbindgen_private::Rect { .x = x, .y = y, .width = w, .height = h };
|
|
}
|
|
|
|
inline void dealloc(const ItemTreeVTable *, uint8_t *ptr, [[maybe_unused]] vtable::Layout layout)
|
|
{
|
|
#ifdef __cpp_sized_deallocation
|
|
::operator delete(reinterpret_cast<void *>(ptr), layout.size,
|
|
static_cast<std::align_val_t>(layout.align));
|
|
#elif !defined(__APPLE__) || MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_14
|
|
::operator delete(reinterpret_cast<void *>(ptr), static_cast<std::align_val_t>(layout.align));
|
|
#else
|
|
::operator delete(reinterpret_cast<void *>(ptr));
|
|
#endif
|
|
}
|
|
|
|
template<typename T>
|
|
inline vtable::Layout drop_in_place(ItemTreeRef item_tree)
|
|
{
|
|
reinterpret_cast<T *>(item_tree.instance)->~T();
|
|
return vtable::Layout { sizeof(T), alignof(T) };
|
|
}
|
|
|
|
#if !defined(DOXYGEN)
|
|
# if defined(_WIN32) || defined(_WIN64)
|
|
// On Windows cross-dll data relocations are not supported:
|
|
// https://docs.microsoft.com/en-us/cpp/c-language/rules-and-limitations-for-dllimport-dllexport?view=msvc-160
|
|
// so we have a relocation to a function that returns the address we seek. That
|
|
// relocation will be resolved to the locally linked stub library, the implementation of
|
|
// which will be patched.
|
|
# define SLINT_GET_ITEM_VTABLE(VTableName) slint::private_api::slint_get_##VTableName()
|
|
# else
|
|
# define SLINT_GET_ITEM_VTABLE(VTableName) (&slint::private_api::VTableName)
|
|
# endif
|
|
#endif // !defined(DOXYGEN)
|
|
|
|
} // namespace private_api
|
|
|
|
template<typename T>
|
|
class ComponentWeakHandle;
|
|
|
|
/// The component handle is like a shared pointer to a component in the generated code.
|
|
/// In order to get a component, use `T::create()` where T is the name of the component
|
|
/// in the .slint file. This give you a `ComponentHandle<T>`
|
|
template<typename T>
|
|
class ComponentHandle
|
|
{
|
|
vtable::VRc<private_api::ItemTreeVTable, T> inner;
|
|
friend class ComponentWeakHandle<T>;
|
|
|
|
public:
|
|
/// internal constructor
|
|
ComponentHandle(const vtable::VRc<private_api::ItemTreeVTable, T> &inner) : inner(inner) { }
|
|
|
|
/// Arrow operator that implements pointer semantics.
|
|
const T *operator->() const
|
|
{
|
|
private_api::assert_main_thread();
|
|
return inner.operator->();
|
|
}
|
|
/// Dereference operator that implements pointer semantics.
|
|
const T &operator*() const
|
|
{
|
|
private_api::assert_main_thread();
|
|
return inner.operator*();
|
|
}
|
|
/// Arrow operator that implements pointer semantics.
|
|
T *operator->()
|
|
{
|
|
private_api::assert_main_thread();
|
|
return inner.operator->();
|
|
}
|
|
/// Dereference operator that implements pointer semantics.
|
|
T &operator*()
|
|
{
|
|
private_api::assert_main_thread();
|
|
return inner.operator*();
|
|
}
|
|
|
|
/// internal function that returns the internal handle
|
|
vtable::VRc<private_api::ItemTreeVTable> into_dyn() const { return inner.into_dyn(); }
|
|
};
|
|
|
|
/// A weak reference to the component. Can be constructed from a `ComponentHandle<T>`
|
|
template<typename T>
|
|
class ComponentWeakHandle
|
|
{
|
|
vtable::VWeak<private_api::ItemTreeVTable, T> inner;
|
|
|
|
public:
|
|
/// Constructs a null ComponentWeakHandle. lock() will always return empty.
|
|
ComponentWeakHandle() = default;
|
|
/// Copy-constructs a new ComponentWeakHandle from \a other.
|
|
ComponentWeakHandle(const ComponentHandle<T> &other) : inner(other.inner) { }
|
|
/// Returns a new strong ComponentHandle<T> if the component the weak handle points to is
|
|
/// still referenced by any other ComponentHandle<T>. An empty std::optional is returned
|
|
/// otherwise.
|
|
std::optional<ComponentHandle<T>> lock() const
|
|
{
|
|
private_api::assert_main_thread();
|
|
if (auto l = inner.lock()) {
|
|
return { ComponentHandle(*l) };
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
};
|
|
|
|
namespace cbindgen_private {
|
|
inline LayoutInfo LayoutInfo::merge(const LayoutInfo &other) const
|
|
{
|
|
// Note: This "logic" is duplicated from LayoutInfo::merge in layout.rs.
|
|
return LayoutInfo { std::min(max, other.max),
|
|
std::min(max_percent, other.max_percent),
|
|
std::max(min, other.min),
|
|
std::max(min_percent, other.min_percent),
|
|
std::max(preferred, other.preferred),
|
|
std::min(stretch, other.stretch) };
|
|
}
|
|
inline bool operator==(const EasingCurve &a, const EasingCurve &b)
|
|
{
|
|
if (a.tag != b.tag) {
|
|
return false;
|
|
} else if (a.tag == EasingCurve::Tag::CubicBezier) {
|
|
return std::equal(a.cubic_bezier._0, a.cubic_bezier._0 + 4, b.cubic_bezier._0);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
namespace private_api {
|
|
|
|
inline static void register_item_tree(const vtable::VRc<ItemTreeVTable> *c,
|
|
const std::optional<slint::Window> &maybe_window)
|
|
{
|
|
const cbindgen_private::WindowAdapterRcOpaque *window_ptr =
|
|
maybe_window.has_value() ? &maybe_window->window_handle().handle() : nullptr;
|
|
cbindgen_private::slint_register_item_tree(c, window_ptr);
|
|
}
|
|
|
|
inline SharedVector<float> solve_box_layout(const cbindgen_private::BoxLayoutData &data,
|
|
cbindgen_private::Slice<int> repeater_indexes)
|
|
{
|
|
SharedVector<float> result;
|
|
cbindgen_private::Slice<uint32_t> ri { reinterpret_cast<uint32_t *>(repeater_indexes.ptr),
|
|
repeater_indexes.len };
|
|
cbindgen_private::slint_solve_box_layout(&data, ri, &result);
|
|
return result;
|
|
}
|
|
|
|
inline SharedVector<float> solve_grid_layout(const cbindgen_private::GridLayoutData &data)
|
|
{
|
|
SharedVector<float> result;
|
|
cbindgen_private::slint_solve_grid_layout(&data, &result);
|
|
return result;
|
|
}
|
|
|
|
inline cbindgen_private::LayoutInfo
|
|
grid_layout_info(cbindgen_private::Slice<cbindgen_private::GridLayoutCellData> cells, float spacing,
|
|
const cbindgen_private::Padding &padding)
|
|
{
|
|
return cbindgen_private::slint_grid_layout_info(cells, spacing, &padding);
|
|
}
|
|
|
|
inline cbindgen_private::LayoutInfo
|
|
box_layout_info(cbindgen_private::Slice<cbindgen_private::BoxLayoutCellData> cells, float spacing,
|
|
const cbindgen_private::Padding &padding,
|
|
cbindgen_private::LayoutAlignment alignment)
|
|
{
|
|
return cbindgen_private::slint_box_layout_info(cells, spacing, &padding, alignment);
|
|
}
|
|
|
|
inline cbindgen_private::LayoutInfo
|
|
box_layout_info_ortho(cbindgen_private::Slice<cbindgen_private::BoxLayoutCellData> cells,
|
|
const cbindgen_private::Padding &padding)
|
|
{
|
|
return cbindgen_private::slint_box_layout_info_ortho(cells, &padding);
|
|
}
|
|
|
|
/// Access the layout cache of an item within a repeater
|
|
inline float layout_cache_access(const SharedVector<float> &cache, int offset, int repeater_index)
|
|
{
|
|
size_t idx = size_t(cache[offset]) + repeater_index * 2;
|
|
return idx < cache.size() ? cache[idx] : 0;
|
|
}
|
|
|
|
// models
|
|
struct ModelChangeListener
|
|
{
|
|
virtual ~ModelChangeListener() = default;
|
|
virtual void row_added(size_t index, size_t count) = 0;
|
|
virtual void row_removed(size_t index, size_t count) = 0;
|
|
virtual void row_changed(size_t index) = 0;
|
|
virtual void reset() = 0;
|
|
};
|
|
using ModelPeer = std::weak_ptr<ModelChangeListener>;
|
|
|
|
template<typename M>
|
|
auto access_array_index(const std::shared_ptr<M> &model, size_t index)
|
|
{
|
|
if (!model) {
|
|
return decltype(*model->row_data_tracked(index)) {};
|
|
} else if (const auto v = model->row_data_tracked(index)) {
|
|
return *v;
|
|
} else {
|
|
return decltype(*v) {};
|
|
}
|
|
}
|
|
|
|
template<typename M>
|
|
long int model_length(const std::shared_ptr<M> &model)
|
|
{
|
|
if (!model) {
|
|
return 0;
|
|
} else {
|
|
model->track_row_count_changes();
|
|
return model->row_count();
|
|
}
|
|
}
|
|
|
|
} // namespace private_api
|
|
|
|
/// \rst
|
|
/// A Model is providing Data for |Repetition|_ repetitions or |ListView|_ elements of the
|
|
/// :code:`.slint` language
|
|
/// \endrst
|
|
template<typename ModelData>
|
|
class Model
|
|
{
|
|
public:
|
|
virtual ~Model() = default;
|
|
Model() = default;
|
|
Model(const Model &) = delete;
|
|
Model &operator=(const Model &) = delete;
|
|
|
|
/// The amount of row in the model
|
|
virtual size_t row_count() const = 0;
|
|
/// Returns the data for a particular row. This function should be called with `row <
|
|
/// row_count()`.
|
|
virtual std::optional<ModelData> row_data(size_t i) const = 0;
|
|
/// Sets the data for a particular row.
|
|
///
|
|
/// This function should only be called with `row < row_count()`.
|
|
///
|
|
/// If the model cannot support data changes, then it is ok to do nothing.
|
|
/// The default implementation will print a warning to stderr.
|
|
///
|
|
/// If the model can update the data, it should also call `row_changed`
|
|
virtual void set_row_data(size_t, const ModelData &)
|
|
{
|
|
#ifndef SLINT_FEATURE_FREESTANDING
|
|
std::cerr << "Model::set_row_data was called on a read-only model" << std::endl;
|
|
#endif
|
|
};
|
|
|
|
/// \private
|
|
/// Internal function called by the view to register itself
|
|
void attach_peer(private_api::ModelPeer p)
|
|
{
|
|
peers.push_back(std::move(p));
|
|
}
|
|
|
|
/// \private
|
|
/// Internal function called from within bindings to register with the currently
|
|
/// evaluating dependency and get notified when this model's row count changes.
|
|
void track_row_count_changes() const
|
|
{
|
|
model_row_count_dirty_property.get();
|
|
}
|
|
|
|
/// \private
|
|
/// Internal function called from within bindings to register with the currently
|
|
/// evaluating dependency and get notified when this model's row data changes.
|
|
void track_row_data_changes(size_t row) const
|
|
{
|
|
auto it = std::lower_bound(tracked_rows.begin(), tracked_rows.end(), row);
|
|
if (it == tracked_rows.end() || row < *it) {
|
|
tracked_rows.insert(it, row);
|
|
}
|
|
model_row_data_dirty_property.get();
|
|
}
|
|
|
|
/// \private
|
|
/// Convenience function that calls `track_row_data_changes` before returning `row_data`
|
|
std::optional<ModelData> row_data_tracked(size_t row) const
|
|
{
|
|
track_row_data_changes(row);
|
|
return row_data(row);
|
|
}
|
|
|
|
protected:
|
|
/// Notify the views that a specific row was changed
|
|
void row_changed(size_t row)
|
|
{
|
|
if (std::binary_search(tracked_rows.begin(), tracked_rows.end(), row)) {
|
|
model_row_data_dirty_property.mark_dirty();
|
|
}
|
|
for_each_peers([=](auto peer) { peer->row_changed(row); });
|
|
}
|
|
/// Notify the views that rows were added
|
|
void row_added(size_t index, size_t count)
|
|
{
|
|
model_row_count_dirty_property.mark_dirty();
|
|
tracked_rows.clear();
|
|
model_row_data_dirty_property.mark_dirty();
|
|
for_each_peers([=](auto peer) { peer->row_added(index, count); });
|
|
}
|
|
/// Notify the views that rows were removed
|
|
void row_removed(size_t index, size_t count)
|
|
{
|
|
model_row_count_dirty_property.mark_dirty();
|
|
tracked_rows.clear();
|
|
model_row_data_dirty_property.mark_dirty();
|
|
for_each_peers([=](auto peer) { peer->row_removed(index, count); });
|
|
}
|
|
|
|
/// Notify the views that the model has been changed and that everything needs to be reloaded
|
|
void reset()
|
|
{
|
|
model_row_count_dirty_property.mark_dirty();
|
|
tracked_rows.clear();
|
|
model_row_data_dirty_property.mark_dirty();
|
|
for_each_peers([=](auto peer) { peer->reset(); });
|
|
}
|
|
|
|
private:
|
|
template<typename F>
|
|
void for_each_peers(const F &f)
|
|
{
|
|
private_api::assert_main_thread();
|
|
peers.erase(std::remove_if(peers.begin(), peers.end(),
|
|
[&](const auto &p) {
|
|
if (auto pp = p.lock()) {
|
|
f(pp);
|
|
return false;
|
|
}
|
|
return true;
|
|
}),
|
|
peers.end());
|
|
}
|
|
std::vector<private_api::ModelPeer> peers;
|
|
private_api::Property<bool> model_row_count_dirty_property;
|
|
private_api::Property<bool> model_row_data_dirty_property;
|
|
mutable std::vector<size_t> tracked_rows;
|
|
};
|
|
|
|
namespace private_api {
|
|
/// A Model backed by a std::array of constant size
|
|
/// \private
|
|
template<int Count, typename ModelData>
|
|
class ArrayModel : public Model<ModelData>
|
|
{
|
|
std::array<ModelData, Count> data;
|
|
|
|
public:
|
|
/// Constructs a new ArrayModel by forwarding \a to the std::array constructor.
|
|
template<typename... A>
|
|
ArrayModel(A &&...a) : data { std::forward<A>(a)... }
|
|
{
|
|
}
|
|
size_t row_count() const override { return Count; }
|
|
std::optional<ModelData> row_data(size_t i) const override
|
|
{
|
|
if (i >= row_count())
|
|
return {};
|
|
return data[i];
|
|
}
|
|
void set_row_data(size_t i, const ModelData &value) override
|
|
{
|
|
if (i < row_count()) {
|
|
data[i] = value;
|
|
this->row_changed(i);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Specialize for the empty array. We can't have a Model<void>, but `int` will work for our purpose
|
|
template<>
|
|
class ArrayModel<0, void> : public Model<int>
|
|
{
|
|
public:
|
|
size_t row_count() const override { return 0; }
|
|
std::optional<int> row_data(size_t) const override { return {}; }
|
|
};
|
|
|
|
/// Model to be used when we just want to repeat without data.
|
|
struct UIntModel : Model<int>
|
|
{
|
|
/// Constructs a new IntModel with \a d rows.
|
|
UIntModel(uint32_t d) : data(d) { }
|
|
/// \private
|
|
uint32_t data;
|
|
/// \copydoc Model::row_count
|
|
size_t row_count() const override { return data; }
|
|
std::optional<int> row_data(size_t value) const override
|
|
{
|
|
if (value >= row_count())
|
|
return {};
|
|
return static_cast<int>(value);
|
|
}
|
|
};
|
|
} // namespace private_api
|
|
|
|
/// A Model backed by a SharedVector
|
|
template<typename ModelData>
|
|
class VectorModel : public Model<ModelData>
|
|
{
|
|
std::vector<ModelData> data;
|
|
|
|
public:
|
|
/// Constructs a new empty VectorModel.
|
|
VectorModel() = default;
|
|
/// Constructs a new VectorModel from \a array.
|
|
VectorModel(std::vector<ModelData> array) : data(std::move(array)) { }
|
|
size_t row_count() const override { return data.size(); }
|
|
std::optional<ModelData> row_data(size_t i) const override
|
|
{
|
|
if (i >= row_count())
|
|
return {};
|
|
return std::optional<ModelData> { data[i] };
|
|
}
|
|
void set_row_data(size_t i, const ModelData &value) override
|
|
{
|
|
if (i < row_count()) {
|
|
data[i] = value;
|
|
this->row_changed(i);
|
|
}
|
|
}
|
|
|
|
/// Append a new row with the given value
|
|
void push_back(const ModelData &value)
|
|
{
|
|
data.push_back(value);
|
|
this->row_added(data.size() - 1, 1);
|
|
}
|
|
|
|
/// Remove the row at the given index from the model
|
|
void erase(size_t index)
|
|
{
|
|
data.erase(data.begin() + index);
|
|
this->row_removed(index, 1);
|
|
}
|
|
|
|
/// Inserts the given value as a new row at the specified index
|
|
void insert(size_t index, const ModelData &value)
|
|
{
|
|
data.insert(data.begin() + index, value);
|
|
this->row_added(index, 1);
|
|
}
|
|
};
|
|
|
|
template<typename ModelData>
|
|
class FilterModel;
|
|
|
|
namespace private_api {
|
|
template<typename ModelData>
|
|
struct FilterModelInner : private_api::ModelChangeListener
|
|
{
|
|
FilterModelInner(std::shared_ptr<slint::Model<ModelData>> source_model,
|
|
std::function<bool(const ModelData &)> filter_fn,
|
|
slint::FilterModel<ModelData> &target_model)
|
|
: source_model(source_model), filter_fn(filter_fn), target_model(target_model)
|
|
{
|
|
update_mapping();
|
|
}
|
|
|
|
void row_added(size_t index, size_t count) override
|
|
{
|
|
if (count == 0) {
|
|
return;
|
|
}
|
|
|
|
std::vector<size_t> added_accepted_rows;
|
|
for (auto i = index; i < index + count; ++i) {
|
|
if (auto data = source_model->row_data(i)) {
|
|
if (filter_fn(*data)) {
|
|
added_accepted_rows.push_back(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (added_accepted_rows.empty()) {
|
|
return;
|
|
}
|
|
|
|
auto insertion_point = std::lower_bound(accepted_rows.begin(), accepted_rows.end(), index);
|
|
|
|
insertion_point = accepted_rows.insert(insertion_point, added_accepted_rows.begin(),
|
|
added_accepted_rows.end());
|
|
|
|
for (auto it = insertion_point + added_accepted_rows.size(); it != accepted_rows.end();
|
|
++it)
|
|
(*it) += count;
|
|
|
|
target_model.row_added(insertion_point - accepted_rows.begin(), added_accepted_rows.size());
|
|
}
|
|
void row_changed(size_t index) override
|
|
{
|
|
auto existing_row = std::lower_bound(accepted_rows.begin(), accepted_rows.end(), index);
|
|
auto existing_row_index = std::distance(accepted_rows.begin(), existing_row);
|
|
bool is_contained = existing_row != accepted_rows.end() && *existing_row == index;
|
|
auto accepted_updated_row = filter_fn(*source_model->row_data(index));
|
|
|
|
if (is_contained && accepted_updated_row) {
|
|
target_model.row_changed(existing_row_index);
|
|
} else if (!is_contained && accepted_updated_row) {
|
|
accepted_rows.insert(existing_row, index);
|
|
target_model.row_added(existing_row_index, 1);
|
|
} else if (is_contained && !accepted_updated_row) {
|
|
accepted_rows.erase(existing_row);
|
|
target_model.row_removed(existing_row_index, 1);
|
|
}
|
|
}
|
|
void row_removed(size_t index, size_t count) override
|
|
{
|
|
auto mapped_row_start = std::lower_bound(accepted_rows.begin(), accepted_rows.end(), index);
|
|
auto mapped_row_end =
|
|
std::lower_bound(accepted_rows.begin(), accepted_rows.end(), index + count);
|
|
|
|
auto mapped_removed_len = std::distance(mapped_row_start, mapped_row_end);
|
|
|
|
auto mapped_removed_index =
|
|
(mapped_row_start != accepted_rows.end() && *mapped_row_start == index)
|
|
? std::optional<int>(mapped_row_start - accepted_rows.begin())
|
|
: std::nullopt;
|
|
|
|
auto it = accepted_rows.erase(mapped_row_start, mapped_row_end);
|
|
for (; it != accepted_rows.end(); ++it) {
|
|
*it -= count;
|
|
}
|
|
|
|
if (mapped_removed_index) {
|
|
target_model.row_removed(*mapped_removed_index, mapped_removed_len);
|
|
}
|
|
}
|
|
void reset() override
|
|
{
|
|
update_mapping();
|
|
target_model.reset();
|
|
}
|
|
|
|
void update_mapping()
|
|
{
|
|
accepted_rows.clear();
|
|
for (size_t i = 0, count = source_model->row_count(); i < count; ++i) {
|
|
if (auto data = source_model->row_data(i)) {
|
|
if (filter_fn(*data)) {
|
|
accepted_rows.push_back(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<slint::Model<ModelData>> source_model;
|
|
std::function<bool(const ModelData &)> filter_fn;
|
|
std::vector<size_t> accepted_rows;
|
|
slint::FilterModel<ModelData> &target_model;
|
|
};
|
|
}
|
|
|
|
/// The FilterModel acts as an adapter model for a given source model by applying a filter
|
|
/// function. The filter function is called for each row on the source model and if the
|
|
/// filter accepts the row (i.e. returns true), the row is also visible in the FilterModel.
|
|
template<typename ModelData>
|
|
class FilterModel : public Model<ModelData>
|
|
{
|
|
friend struct private_api::FilterModelInner<ModelData>;
|
|
|
|
public:
|
|
/// Constructs a new FilterModel that provides a limited view on the \a source_model by applying
|
|
/// \a filter_fn on each row. If the provided function returns true, the row is exposed by the
|
|
/// FilterModel.
|
|
FilterModel(std::shared_ptr<Model<ModelData>> source_model,
|
|
std::function<bool(const ModelData &)> filter_fn)
|
|
: inner(std::make_shared<private_api::FilterModelInner<ModelData>>(
|
|
std::move(source_model), std::move(filter_fn), *this))
|
|
{
|
|
inner->source_model->attach_peer(inner);
|
|
}
|
|
|
|
size_t row_count() const override { return inner->accepted_rows.size(); }
|
|
|
|
std::optional<ModelData> row_data(size_t i) const override
|
|
{
|
|
if (i >= inner->accepted_rows.size())
|
|
return {};
|
|
return inner->source_model->row_data(inner->accepted_rows[i]);
|
|
}
|
|
|
|
void set_row_data(size_t i, const ModelData &value) override
|
|
{
|
|
inner->source_model->set_row_data(inner->accepted_rows[i], value);
|
|
}
|
|
|
|
/// Re-applies the model's filter function on each row of the source model. Use this if state
|
|
/// external to the filter function has changed.
|
|
void reset() { inner->reset(); }
|
|
|
|
/// Given the \a filtered_row index, this function returns the corresponding row index in the
|
|
/// source model.
|
|
int unfiltered_row(int filtered_row) const { return inner->accepted_rows[filtered_row]; }
|
|
|
|
/// Returns the source model of this filter model.
|
|
std::shared_ptr<Model<ModelData>> source_model() const { return inner->source_model; }
|
|
|
|
private:
|
|
std::shared_ptr<private_api::FilterModelInner<ModelData>> inner;
|
|
};
|
|
|
|
template<typename SourceModelData, typename MappedModelData>
|
|
class MapModel;
|
|
|
|
namespace private_api {
|
|
template<typename SourceModelData, typename MappedModelData>
|
|
struct MapModelInner : private_api::ModelChangeListener
|
|
{
|
|
MapModelInner(slint::MapModel<SourceModelData, MappedModelData> &target_model)
|
|
: target_model(target_model)
|
|
{
|
|
}
|
|
|
|
void row_added(size_t index, size_t count) override { target_model.row_added(index, count); }
|
|
void row_changed(size_t index) override { target_model.row_changed(index); }
|
|
void row_removed(size_t index, size_t count) override
|
|
{
|
|
target_model.row_removed(index, count);
|
|
}
|
|
void reset() override { target_model.reset(); }
|
|
|
|
slint::MapModel<SourceModelData, MappedModelData> &target_model;
|
|
};
|
|
}
|
|
|
|
/// The MapModel acts as an adapter model for a given source model by applying a mapping
|
|
/// function. The mapping function is called for each row on the source model and allows
|
|
/// transforming the values on the fly. The MapModel has two template parameters: The
|
|
/// SourceModelData specifies the data type of the underlying source model, and the
|
|
/// MappedModelData the data type of this MapModel. This permits not only changing the
|
|
/// values of the underlying source model, but also changing the data type itself. For
|
|
/// example a MapModel can be used to adapt a model that provides numbers to be a model
|
|
/// that exposes all numbers converted to strings, by calling `std::to_string` on each
|
|
/// value given in the mapping lambda expression.
|
|
template<typename SourceModelData, typename MappedModelData = SourceModelData>
|
|
class MapModel : public Model<MappedModelData>
|
|
{
|
|
friend struct private_api::MapModelInner<SourceModelData, MappedModelData>;
|
|
|
|
public:
|
|
/// Constructs a new MapModel that provides an altered view on the \a source_model by applying
|
|
/// \a map_fn on the data in each row.
|
|
MapModel(std::shared_ptr<Model<SourceModelData>> source_model,
|
|
std::function<MappedModelData(const SourceModelData &)> map_fn)
|
|
: inner(std::make_shared<private_api::MapModelInner<SourceModelData, MappedModelData>>(
|
|
*this)),
|
|
model(source_model),
|
|
map_fn(map_fn)
|
|
{
|
|
model->attach_peer(inner);
|
|
}
|
|
|
|
size_t row_count() const override { return model->row_count(); }
|
|
|
|
std::optional<MappedModelData> row_data(size_t i) const override
|
|
{
|
|
if (auto source_data = model->row_data(i))
|
|
return map_fn(*source_data);
|
|
else
|
|
return {};
|
|
}
|
|
|
|
/// Returns the source model of this filter model.
|
|
std::shared_ptr<Model<SourceModelData>> source_model() const { return model; }
|
|
|
|
private:
|
|
std::shared_ptr<private_api::MapModelInner<SourceModelData, MappedModelData>> inner;
|
|
std::shared_ptr<slint::Model<SourceModelData>> model;
|
|
std::function<MappedModelData(const SourceModelData &)> map_fn;
|
|
};
|
|
|
|
template<typename ModelData>
|
|
class SortModel;
|
|
|
|
namespace private_api {
|
|
template<typename ModelData>
|
|
struct SortModelInner : private_api::ModelChangeListener
|
|
{
|
|
SortModelInner(std::shared_ptr<slint::Model<ModelData>> source_model,
|
|
std::function<bool(const ModelData &, const ModelData &)> comp,
|
|
slint::SortModel<ModelData> &target_model)
|
|
: source_model(source_model), comp(comp), target_model(target_model)
|
|
{
|
|
}
|
|
|
|
void row_added(size_t first_inserted_row, size_t count) override
|
|
{
|
|
if (sorted_rows_dirty) {
|
|
reset();
|
|
return;
|
|
}
|
|
|
|
// Adjust the existing sorted row indices to match the updated source model
|
|
for (auto &row : sorted_rows) {
|
|
if (row >= first_inserted_row)
|
|
row += count;
|
|
}
|
|
|
|
for (size_t row = first_inserted_row; row < first_inserted_row + count; ++row) {
|
|
|
|
ModelData inserted_value = *source_model->row_data(row);
|
|
auto insertion_point =
|
|
std::lower_bound(sorted_rows.begin(), sorted_rows.end(), inserted_value,
|
|
[this](size_t sorted_row, const ModelData &inserted_value) {
|
|
auto sorted_elem = source_model->row_data(sorted_row);
|
|
return comp(*sorted_elem, inserted_value);
|
|
});
|
|
|
|
insertion_point = sorted_rows.insert(insertion_point, row);
|
|
target_model.row_added(std::distance(sorted_rows.begin(), insertion_point), 1);
|
|
}
|
|
}
|
|
void row_changed(size_t changed_row) override
|
|
{
|
|
if (sorted_rows_dirty) {
|
|
reset();
|
|
return;
|
|
}
|
|
|
|
auto removed_row_it =
|
|
sorted_rows.erase(std::find(sorted_rows.begin(), sorted_rows.end(), changed_row));
|
|
auto removed_row = std::distance(sorted_rows.begin(), removed_row_it);
|
|
|
|
ModelData changed_value = *source_model->row_data(changed_row);
|
|
auto insertion_point =
|
|
std::lower_bound(sorted_rows.begin(), sorted_rows.end(), changed_value,
|
|
[this](size_t sorted_row, const ModelData &changed_value) {
|
|
auto sorted_elem = source_model->row_data(sorted_row);
|
|
return comp(*sorted_elem, changed_value);
|
|
});
|
|
|
|
insertion_point = sorted_rows.insert(insertion_point, changed_row);
|
|
auto inserted_row = std::distance(sorted_rows.begin(), insertion_point);
|
|
|
|
if (inserted_row == removed_row) {
|
|
target_model.row_changed(removed_row);
|
|
} else {
|
|
target_model.row_removed(removed_row, 1);
|
|
target_model.row_added(inserted_row, 1);
|
|
}
|
|
}
|
|
void row_removed(size_t first_removed_row, size_t count) override
|
|
{
|
|
if (sorted_rows_dirty) {
|
|
reset();
|
|
return;
|
|
}
|
|
|
|
std::vector<size_t> removed_rows;
|
|
removed_rows.reserve(count);
|
|
|
|
for (auto it = sorted_rows.begin(); it != sorted_rows.end();) {
|
|
if (*it >= first_removed_row) {
|
|
if (*it < first_removed_row + count) {
|
|
removed_rows.push_back(std::distance(sorted_rows.begin(), it));
|
|
it = sorted_rows.erase(it);
|
|
continue;
|
|
} else {
|
|
*it -= count;
|
|
}
|
|
}
|
|
++it;
|
|
}
|
|
|
|
for (auto removed_row : removed_rows) {
|
|
target_model.row_removed(removed_row, 1);
|
|
}
|
|
}
|
|
|
|
void reset() override
|
|
{
|
|
sorted_rows_dirty = true;
|
|
target_model.reset();
|
|
}
|
|
|
|
void ensure_sorted()
|
|
{
|
|
if (!sorted_rows_dirty) {
|
|
return;
|
|
}
|
|
|
|
sorted_rows.resize(source_model->row_count());
|
|
for (size_t i = 0; i < sorted_rows.size(); ++i)
|
|
sorted_rows[i] = i;
|
|
|
|
std::sort(sorted_rows.begin(), sorted_rows.end(), [this](auto lhs_index, auto rhs_index) {
|
|
auto lhs_elem = source_model->row_data(lhs_index);
|
|
auto rhs_elem = source_model->row_data(rhs_index);
|
|
return comp(*lhs_elem, *rhs_elem);
|
|
});
|
|
|
|
sorted_rows_dirty = false;
|
|
}
|
|
|
|
std::shared_ptr<slint::Model<ModelData>> source_model;
|
|
std::function<bool(const ModelData &, const ModelData &)> comp;
|
|
slint::SortModel<ModelData> &target_model;
|
|
std::vector<size_t> sorted_rows;
|
|
bool sorted_rows_dirty = true;
|
|
};
|
|
}
|
|
|
|
/// The SortModel acts as an adapter model for a given source model by sorting all rows
|
|
/// with by order provided by the given sorting function. The sorting function is called for
|
|
/// pairs of elements of the source model.
|
|
template<typename ModelData>
|
|
class SortModel : public Model<ModelData>
|
|
{
|
|
friend struct private_api::SortModelInner<ModelData>;
|
|
|
|
public:
|
|
/// Constructs a new SortModel that provides a sorted view on the \a source_model by applying
|
|
/// the order given by the specified \a comp.
|
|
SortModel(std::shared_ptr<Model<ModelData>> source_model,
|
|
std::function<bool(const ModelData &, const ModelData &)> comp)
|
|
: inner(std::make_shared<private_api::SortModelInner<ModelData>>(std::move(source_model),
|
|
std::move(comp), *this))
|
|
{
|
|
inner->source_model->attach_peer(inner);
|
|
}
|
|
|
|
size_t row_count() const override { return inner->source_model->row_count(); }
|
|
|
|
std::optional<ModelData> row_data(size_t i) const override
|
|
{
|
|
inner->ensure_sorted();
|
|
return inner->source_model->row_data(inner->sorted_rows[i]);
|
|
}
|
|
|
|
void set_row_data(size_t i, const ModelData &value) override
|
|
{
|
|
inner->source_model->set_row_data(inner->sorted_rows[i], value);
|
|
}
|
|
|
|
/// Re-applies the model's sort function on each row of the source model. Use this if state
|
|
/// external to the sort function has changed.
|
|
void reset() { inner->reset(); }
|
|
|
|
/// Given the \a sorted_row_index, this function returns the corresponding row index in the
|
|
/// source model.
|
|
int unsorted_row(int sorted_row_index) const
|
|
{
|
|
inner->ensure_sorted();
|
|
return inner->sorted_rows[sorted_row_index];
|
|
}
|
|
|
|
/// Returns the source model of this filter model.
|
|
std::shared_ptr<Model<ModelData>> source_model() const { return inner->source_model; }
|
|
|
|
private:
|
|
std::shared_ptr<private_api::SortModelInner<ModelData>> inner;
|
|
};
|
|
|
|
template<typename ModelData>
|
|
class ReverseModel;
|
|
|
|
namespace private_api {
|
|
template<typename ModelData>
|
|
struct ReverseModelInner : private_api::ModelChangeListener
|
|
{
|
|
ReverseModelInner(std::shared_ptr<slint::Model<ModelData>> source_model,
|
|
slint::ReverseModel<ModelData> &target_model)
|
|
: source_model(source_model), target_model(target_model)
|
|
{
|
|
}
|
|
|
|
void row_added(size_t first_inserted_row, size_t count) override
|
|
{
|
|
auto row_count = source_model->row_count();
|
|
auto old_row_count = row_count - count;
|
|
auto row = old_row_count - first_inserted_row;
|
|
|
|
target_model.row_added(row, count);
|
|
}
|
|
|
|
void row_changed(size_t changed_row) override
|
|
{
|
|
target_model.row_changed(source_model->row_count() - 1 - changed_row);
|
|
}
|
|
|
|
void row_removed(size_t first_removed_row, size_t count) override
|
|
{
|
|
auto row_count = source_model->row_count();
|
|
auto old_row_count = row_count + count;
|
|
auto row = old_row_count - first_removed_row - 1;
|
|
|
|
target_model.row_removed(row, count);
|
|
}
|
|
|
|
void reset() override { source_model.reset(); }
|
|
|
|
std::shared_ptr<slint::Model<ModelData>> source_model;
|
|
slint::ReverseModel<ModelData> &target_model;
|
|
};
|
|
}
|
|
|
|
/// The ReverseModel acts as an adapter model for a given source model by reserving all rows.
|
|
/// This means that the first row in the source model is the last row of this model, the second
|
|
/// row is the second last, and so on.
|
|
template<typename ModelData>
|
|
class ReverseModel : public Model<ModelData>
|
|
{
|
|
friend struct private_api::ReverseModelInner<ModelData>;
|
|
|
|
public:
|
|
/// Constructs a new ReverseModel that provides a reversed view on the \a source_model.
|
|
ReverseModel(std::shared_ptr<Model<ModelData>> source_model)
|
|
: inner(std::make_shared<private_api::ReverseModelInner<ModelData>>(std::move(source_model),
|
|
*this))
|
|
{
|
|
inner->source_model->attach_peer(inner);
|
|
}
|
|
|
|
size_t row_count() const override { return inner->source_model->row_count(); }
|
|
|
|
std::optional<ModelData> row_data(size_t i) const override
|
|
{
|
|
auto count = inner->source_model->row_count();
|
|
return inner->source_model->row_data(count - i - 1);
|
|
}
|
|
|
|
void set_row_data(size_t i, const ModelData &value) override
|
|
{
|
|
auto count = inner->source_model->row_count();
|
|
inner->source_model->set_row_data(count - i - 1, value);
|
|
}
|
|
|
|
/// Returns the source model of this reserve model.
|
|
std::shared_ptr<Model<ModelData>> source_model() const { return inner->source_model; }
|
|
|
|
private:
|
|
std::shared_ptr<private_api::ReverseModelInner<ModelData>> inner;
|
|
};
|
|
|
|
namespace private_api {
|
|
|
|
template<typename C, typename ModelData>
|
|
class Repeater
|
|
{
|
|
private_api::Property<std::shared_ptr<Model<ModelData>>> model;
|
|
|
|
struct RepeaterInner : ModelChangeListener
|
|
{
|
|
enum class State { Clean, Dirty };
|
|
struct RepeatedInstanceWithState
|
|
{
|
|
State state = State::Dirty;
|
|
std::optional<ComponentHandle<C>> ptr;
|
|
};
|
|
std::vector<RepeatedInstanceWithState> data;
|
|
private_api::Property<bool> is_dirty { true };
|
|
std::shared_ptr<Model<ModelData>> model;
|
|
|
|
void row_added(size_t index, size_t count) override
|
|
{
|
|
is_dirty.set(true);
|
|
data.resize(data.size() + count);
|
|
std::rotate(data.begin() + index, data.end() - count, data.end());
|
|
for (std::size_t i = index; i < data.size(); ++i) {
|
|
// all the indexes are dirty
|
|
data[i].state = State::Dirty;
|
|
}
|
|
}
|
|
void row_changed(size_t index) override
|
|
{
|
|
auto &c = data[index];
|
|
if (model && c.ptr) {
|
|
(*c.ptr)->update_data(index, *model->row_data(index));
|
|
c.state = State::Clean;
|
|
} else {
|
|
c.state = State::Dirty;
|
|
}
|
|
}
|
|
void row_removed(size_t index, size_t count) override
|
|
{
|
|
is_dirty.set(true);
|
|
data.erase(data.begin() + index, data.begin() + index + count);
|
|
for (std::size_t i = index; i < data.size(); ++i) {
|
|
// all the indexes are dirty
|
|
data[i].state = State::Dirty;
|
|
}
|
|
}
|
|
void reset() override
|
|
{
|
|
is_dirty.set(true);
|
|
data.clear();
|
|
}
|
|
};
|
|
|
|
public:
|
|
// FIXME: should be private, but layouting code uses it.
|
|
mutable std::shared_ptr<RepeaterInner> inner;
|
|
|
|
template<typename F>
|
|
void set_model_binding(F &&binding) const
|
|
{
|
|
model.set_binding(std::forward<F>(binding));
|
|
}
|
|
|
|
template<typename Parent>
|
|
void ensure_updated(const Parent *parent) const
|
|
{
|
|
if (model.is_dirty()) {
|
|
inner = std::make_shared<RepeaterInner>();
|
|
if (auto m = model.get()) {
|
|
inner->model = m;
|
|
m->attach_peer(inner);
|
|
}
|
|
}
|
|
|
|
if (inner && inner->is_dirty.get()) {
|
|
inner->is_dirty.set(false);
|
|
if (auto m = model.get()) {
|
|
auto count = m->row_count();
|
|
inner->data.resize(count);
|
|
for (size_t i = 0; i < count; ++i) {
|
|
auto &c = inner->data[i];
|
|
bool created = false;
|
|
if (!c.ptr) {
|
|
c.ptr = C::create(parent);
|
|
created = true;
|
|
}
|
|
if (c.state == RepeaterInner::State::Dirty) {
|
|
(*c.ptr)->update_data(i, *m->row_data(i));
|
|
}
|
|
if (created) {
|
|
(*c.ptr)->init();
|
|
}
|
|
}
|
|
} else {
|
|
inner->data.clear();
|
|
}
|
|
} else {
|
|
// just do a get() on the model to register dependencies so that, for example, the
|
|
// layout property tracker becomes dirty.
|
|
model.get();
|
|
}
|
|
}
|
|
|
|
template<typename Parent>
|
|
void ensure_updated_listview(const Parent *parent,
|
|
const private_api::Property<float> *viewport_width,
|
|
const private_api::Property<float> *viewport_height,
|
|
[[maybe_unused]] const private_api::Property<float> *viewport_y,
|
|
float listview_width, [[maybe_unused]] float listview_height) const
|
|
{
|
|
// TODO: the rust code in model.rs try to only allocate as many items as visible items
|
|
ensure_updated(parent);
|
|
|
|
float h = compute_layout_listview(viewport_width, listview_width);
|
|
viewport_height->set(h);
|
|
}
|
|
|
|
uint64_t visit(TraversalOrder order, private_api::ItemVisitorRefMut visitor) const
|
|
{
|
|
for (std::size_t i = 0; i < inner->data.size(); ++i) {
|
|
auto index = order == TraversalOrder::BackToFront ? i : inner->data.size() - 1 - i;
|
|
auto ref = item_at(index);
|
|
if (ref.vtable->visit_children_item(ref, -1, order, visitor)
|
|
!= std::numeric_limits<uint64_t>::max()) {
|
|
return index;
|
|
}
|
|
}
|
|
return std::numeric_limits<uint64_t>::max();
|
|
}
|
|
|
|
vtable::VRef<private_api::ItemTreeVTable> item_at(int i) const
|
|
{
|
|
const auto &x = inner->data.at(i);
|
|
return { &C::static_vtable, const_cast<C *>(&(**x.ptr)) };
|
|
}
|
|
|
|
vtable::VWeak<private_api::ItemTreeVTable> instance_at(std::size_t i) const
|
|
{
|
|
if (i >= inner->data.size()) {
|
|
return {};
|
|
}
|
|
const auto &x = inner->data.at(i);
|
|
return vtable::VWeak<private_api::ItemTreeVTable> { x.ptr->into_dyn() };
|
|
}
|
|
|
|
private_api::IndexRange index_range() const
|
|
{
|
|
return private_api::IndexRange { 0, inner->data.size() };
|
|
}
|
|
|
|
float compute_layout_listview(const private_api::Property<float> *viewport_width,
|
|
float listview_width) const
|
|
{
|
|
float offset = 0;
|
|
viewport_width->set(listview_width);
|
|
if (!inner)
|
|
return offset;
|
|
for (auto &x : inner->data) {
|
|
(*x.ptr)->listview_layout(&offset, viewport_width);
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
void model_set_row_data(size_t row, const ModelData &data) const
|
|
{
|
|
if (model.is_dirty()) {
|
|
std::abort();
|
|
}
|
|
if (auto m = model.get()) {
|
|
if (row < m->row_count()) {
|
|
m->set_row_data(row, data);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
inline SharedString translate(const SharedString &original, const SharedString &context,
|
|
const SharedString &domain,
|
|
cbindgen_private::Slice<SharedString> arguments, int n,
|
|
const SharedString &plural)
|
|
{
|
|
SharedString result = original;
|
|
cbindgen_private::slint_translate(&result, &context, &domain, arguments, n, &plural);
|
|
return result;
|
|
}
|
|
|
|
} // namespace private_api
|
|
|
|
#if !defined(DOXYGEN)
|
|
cbindgen_private::Flickable::Flickable()
|
|
{
|
|
slint_flickable_data_init(&data);
|
|
}
|
|
cbindgen_private::Flickable::~Flickable()
|
|
{
|
|
slint_flickable_data_free(&data);
|
|
}
|
|
|
|
cbindgen_private::NativeStyleMetrics::NativeStyleMetrics(void *)
|
|
{
|
|
slint_native_style_metrics_init(this);
|
|
}
|
|
|
|
cbindgen_private::NativeStyleMetrics::~NativeStyleMetrics()
|
|
{
|
|
slint_native_style_metrics_deinit(this);
|
|
}
|
|
#endif // !defined(DOXYGEN)
|
|
|
|
namespace private_api {
|
|
// Was used in Slint <= 1.1.0 to have an error message in case of mismatch
|
|
template<int Major, int Minor, int Patch>
|
|
struct [[deprecated]] VersionCheckHelper
|
|
{
|
|
};
|
|
}
|
|
|
|
/// Enters the main event loop. This is necessary in order to receive
|
|
/// events from the windowing system in order to render to the screen
|
|
/// and react to user input.
|
|
inline void run_event_loop()
|
|
{
|
|
private_api::assert_main_thread();
|
|
cbindgen_private::slint_run_event_loop();
|
|
}
|
|
|
|
/// Schedules the main event loop for termination. This function is meant
|
|
/// to be called from callbacks triggered by the UI. After calling the function,
|
|
/// it will return immediately and once control is passed back to the event loop,
|
|
/// the initial call to slint::run_event_loop() will return.
|
|
inline void quit_event_loop()
|
|
{
|
|
cbindgen_private::slint_quit_event_loop();
|
|
}
|
|
|
|
/// Adds the specified functor to an internal queue, notifies the event loop to wake up.
|
|
/// Once woken up, any queued up functors will be invoked.
|
|
/// This function is thread-safe and can be called from any thread, including the one
|
|
/// running the event loop. The provided functors will only be invoked from the thread
|
|
/// that started the event loop.
|
|
///
|
|
/// You can use this to set properties or use any other Slint APIs from other threads,
|
|
/// by collecting the code in a functor and queuing it up for invocation within the event loop.
|
|
///
|
|
/// The following example assumes that a status message received from a network thread is
|
|
/// shown in the UI:
|
|
///
|
|
/// ```
|
|
/// #include "my_application_ui.h"
|
|
/// #include <thread>
|
|
///
|
|
/// int main(int argc, char **argv)
|
|
/// {
|
|
/// auto ui = NetworkStatusUI::create();
|
|
/// ui->set_status_label("Pending");
|
|
///
|
|
/// slint::ComponentWeakHandle<NetworkStatusUI> weak_ui_handle(ui);
|
|
/// std::thread network_thread([=]{
|
|
/// std::string message = read_message_blocking_from_network();
|
|
/// slint::invoke_from_event_loop([&]() {
|
|
/// if (auto ui = weak_ui_handle.lock()) {
|
|
/// ui->set_status_label(message);
|
|
/// }
|
|
/// });
|
|
/// });
|
|
/// ...
|
|
/// ui->run();
|
|
/// ...
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// See also blocking_invoke_from_event_loop() for a blocking version of this function
|
|
template<std::invocable Functor>
|
|
void invoke_from_event_loop(Functor f)
|
|
{
|
|
cbindgen_private::slint_post_event(
|
|
[](void *data) { (*reinterpret_cast<Functor *>(data))(); }, new Functor(std::move(f)),
|
|
[](void *data) { delete reinterpret_cast<Functor *>(data); });
|
|
}
|
|
|
|
#ifndef SLINT_FEATURE_FREESTANDING
|
|
|
|
/// Blocking version of invoke_from_event_loop()
|
|
///
|
|
/// Just like invoke_from_event_loop(), this will run the specified functor from the thread running
|
|
/// the slint event loop. But it will block until the execution of the functor is finished,
|
|
/// and return that value.
|
|
///
|
|
/// This function must be called from a different thread than the thread that runs the event loop
|
|
/// otherwise it will result in a deadlock. Calling this function if the event loop is not running
|
|
/// will also block forever or until the event loop is started in another thread.
|
|
///
|
|
/// The following example is reading the message property from a thread
|
|
///
|
|
/// ```
|
|
/// #include "my_application_ui.h"
|
|
/// #include <thread>
|
|
///
|
|
/// int main(int argc, char **argv)
|
|
/// {
|
|
/// auto ui = MyApplicationUI::create();
|
|
/// ui->set_status_label("Pending");
|
|
///
|
|
/// std::thread worker_thread([ui]{
|
|
/// while (...) {
|
|
/// auto message = slint::blocking_invoke_from_event_loop([ui]() {
|
|
/// return ui->get_message();
|
|
/// }
|
|
/// do_something(message);
|
|
/// ...
|
|
/// });
|
|
/// });
|
|
/// ...
|
|
/// ui->run();
|
|
/// ...
|
|
/// }
|
|
/// ```
|
|
template<std::invocable Functor>
|
|
auto blocking_invoke_from_event_loop(Functor f) -> std::invoke_result_t<Functor>
|
|
{
|
|
std::optional<std::invoke_result_t<Functor>> result;
|
|
std::mutex mtx;
|
|
std::condition_variable cv;
|
|
invoke_from_event_loop([&] {
|
|
auto r = f();
|
|
std::unique_lock lock(mtx);
|
|
result = std::move(r);
|
|
cv.notify_one();
|
|
});
|
|
std::unique_lock lock(mtx);
|
|
cv.wait(lock, [&] { return result.has_value(); });
|
|
return std::move(*result);
|
|
}
|
|
|
|
# if !defined(DOXYGEN) // Doxygen doesn't see this as an overload of the previous one
|
|
// clang-format off
|
|
template<std::invocable Functor>
|
|
requires(std::is_void_v<std::invoke_result_t<Functor>>)
|
|
void blocking_invoke_from_event_loop(Functor f)
|
|
// clang-format on
|
|
{
|
|
std::mutex mtx;
|
|
std::condition_variable cv;
|
|
bool ok = false;
|
|
invoke_from_event_loop([&] {
|
|
f();
|
|
std::unique_lock lock(mtx);
|
|
ok = true;
|
|
cv.notify_one();
|
|
});
|
|
std::unique_lock lock(mtx);
|
|
cv.wait(lock, [&] { return ok; });
|
|
}
|
|
# endif
|
|
#endif
|
|
|
|
} // namespace slint
|