mirror of
https://github.com/slint-ui/slint.git
synced 2025-07-19 02:55:53 +00:00

* Rust SortModel: Rename parameter `S` to `F` * Rust SortModel: Rename parameter `apply_sorting` to `reset` * Rust FilterModel: Rename parameter `apply_filter` to `reset` * C++ SortModel: Rename parameter `sort_fn` to `comp` * C++ SortModel: Add pub reset function * C++ FilterModel: Rename parameter `apply_filter` to `reset`
1569 lines
56 KiB
C++
1569 lines
56 KiB
C++
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
|
// SPDX-License-Identifier: GPL-3.0-only 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 <vector>
|
|
#include <memory>
|
|
#include <algorithm>
|
|
#include <iostream> // FIXME: remove: iostream always bring it lots of code so we should not have it in this header
|
|
#include <chrono>
|
|
#include <optional>
|
|
#include <thread>
|
|
#include <mutex>
|
|
#include <condition_variable>
|
|
#include <span>
|
|
#include <functional>
|
|
|
|
namespace slint::cbindgen_private {
|
|
// Workaround https://github.com/eqrion/cbindgen/issues/43
|
|
struct ComponentVTable;
|
|
struct ItemVTable;
|
|
}
|
|
#include "slint_internal.h"
|
|
#include "slint_size.h"
|
|
#include "slint_point.h"
|
|
#include "slint_backend_internal.h"
|
|
#include "slint_qt_internal.h"
|
|
|
|
/// \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::ComponentVTable;
|
|
using cbindgen_private::ItemVTable;
|
|
using ComponentRc = vtable::VRc<private_api::ComponentVTable>;
|
|
using ComponentRef = vtable::VRef<private_api::ComponentVTable>;
|
|
using IndexRange = cbindgen_private::IndexRange;
|
|
using ItemRef = vtable::VRef<private_api::ItemVTable>;
|
|
using ItemVisitorRefMut = vtable::VRefMut<cbindgen_private::ItemVisitorVTable>;
|
|
using cbindgen_private::ComponentWeak;
|
|
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::StandardListViewItem;
|
|
|
|
/// Internal function that checks that the API that must be called from the main
|
|
/// thread is indeed called from the main thread, or abort the program otherwise
|
|
///
|
|
/// Most API should be called from the main thread. When using thread one must
|
|
/// use slint::invoke_from_event_loop
|
|
inline void assert_main_thread()
|
|
{
|
|
#ifndef NDEBUG
|
|
static auto main_thread_id = std::this_thread::get_id();
|
|
if (main_thread_id != std::this_thread::get_id()) {
|
|
std::cerr << "A function that should be only called from the main thread was called from a "
|
|
"thread."
|
|
<< std::endl;
|
|
std::cerr << "Most API should be called from the main thread. When using thread one must "
|
|
"use slint::invoke_from_event_loop."
|
|
<< std::endl;
|
|
std::abort();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
class WindowAdapterRc
|
|
{
|
|
public:
|
|
explicit WindowAdapterRc(cbindgen_private::WindowAdapterRcOpaque adopted_inner)
|
|
: inner(adopted_inner)
|
|
{
|
|
}
|
|
WindowAdapterRc() { cbindgen_private::slint_windowrc_init(&inner); }
|
|
~WindowAdapterRc() { cbindgen_private::slint_windowrc_drop(&inner); }
|
|
WindowAdapterRc(const WindowAdapterRc &other)
|
|
{
|
|
assert_main_thread();
|
|
cbindgen_private::slint_windowrc_clone(&other.inner, &inner);
|
|
}
|
|
WindowAdapterRc(WindowAdapterRc &&) = delete;
|
|
WindowAdapterRc &operator=(WindowAdapterRc &&) = delete;
|
|
WindowAdapterRc &operator=(const WindowAdapterRc &other)
|
|
{
|
|
assert_main_thread();
|
|
if (this != &other) {
|
|
cbindgen_private::slint_windowrc_drop(&inner);
|
|
cbindgen_private::slint_windowrc_clone(&other.inner, &inner);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
void show() const { slint_windowrc_show(&inner); }
|
|
void hide() const { slint_windowrc_hide(&inner); }
|
|
|
|
float scale_factor() const { return slint_windowrc_get_scale_factor(&inner); }
|
|
void set_scale_factor(float value) const { slint_windowrc_set_scale_factor(&inner, value); }
|
|
|
|
bool dark_color_scheme() const { return slint_windowrc_dark_color_scheme(&inner); }
|
|
|
|
template<typename Component, typename ItemArray>
|
|
void unregister_component(Component *c, ItemArray items) const
|
|
{
|
|
cbindgen_private::slint_unregister_component(
|
|
vtable::VRef<ComponentVTable> { &Component::static_vtable, c }, items, &inner);
|
|
}
|
|
|
|
void set_focus_item(const ComponentRc &component_rc, uintptr_t item_index)
|
|
{
|
|
cbindgen_private::ItemRc item_rc { component_rc, item_index };
|
|
cbindgen_private::slint_windowrc_set_focus_item(&inner, &item_rc);
|
|
}
|
|
|
|
template<typename Component, typename ItemArray>
|
|
void register_component(Component *c, ItemArray items) const
|
|
{
|
|
cbindgen_private::slint_register_component(
|
|
vtable::VRef<ComponentVTable> { &Component::static_vtable, c }, items, &inner);
|
|
}
|
|
|
|
template<typename Component>
|
|
void set_component(const Component &c) const
|
|
{
|
|
auto self_rc = c.self_weak.lock().value().into_dyn();
|
|
slint_windowrc_set_component(&inner, &self_rc);
|
|
}
|
|
|
|
template<typename Component, typename Parent>
|
|
void show_popup(const Parent *parent_component, cbindgen_private::Point p,
|
|
cbindgen_private::ItemRc parent_item) const
|
|
{
|
|
auto popup = Component::create(parent_component).into_dyn();
|
|
cbindgen_private::slint_windowrc_show_popup(&inner, &popup, p, &parent_item);
|
|
}
|
|
|
|
template<typename F>
|
|
std::optional<SetRenderingNotifierError> set_rendering_notifier(F callback) const
|
|
{
|
|
auto actual_cb = [](RenderingState state, GraphicsAPI graphics_api, void *data) {
|
|
(*reinterpret_cast<F *>(data))(state, graphics_api);
|
|
};
|
|
SetRenderingNotifierError err;
|
|
if (cbindgen_private::slint_windowrc_set_rendering_notifier(
|
|
&inner, actual_cb,
|
|
[](void *user_data) { delete reinterpret_cast<F *>(user_data); },
|
|
new F(std::move(callback)), &err)) {
|
|
return {};
|
|
} else {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
template<typename F>
|
|
void on_close_requested(F callback) const
|
|
{
|
|
auto actual_cb = [](void *data) { return (*reinterpret_cast<F *>(data))(); };
|
|
cbindgen_private::slint_windowrc_on_close_requested(
|
|
&inner, actual_cb, [](void *user_data) { delete reinterpret_cast<F *>(user_data); },
|
|
new F(std::move(callback)));
|
|
}
|
|
|
|
void request_redraw() const { cbindgen_private::slint_windowrc_request_redraw(&inner); }
|
|
|
|
slint::PhysicalPosition position() const
|
|
{
|
|
slint::PhysicalPosition pos;
|
|
cbindgen_private::slint_windowrc_position(&inner, &pos);
|
|
return pos;
|
|
}
|
|
|
|
void set_logical_position(const slint::LogicalPosition &pos)
|
|
{
|
|
cbindgen_private::slint_windowrc_set_logical_position(&inner, &pos);
|
|
}
|
|
|
|
void set_physical_position(const slint::PhysicalPosition &pos)
|
|
{
|
|
cbindgen_private::slint_windowrc_set_physical_position(&inner, &pos);
|
|
}
|
|
|
|
slint::PhysicalSize size() const
|
|
{
|
|
return slint::PhysicalSize(cbindgen_private::slint_windowrc_size(&inner));
|
|
}
|
|
|
|
void set_logical_size(const slint::LogicalSize &size)
|
|
{
|
|
cbindgen_private::slint_windowrc_set_logical_size(&inner, &size);
|
|
}
|
|
|
|
void set_physical_size(const slint::PhysicalSize &size)
|
|
{
|
|
cbindgen_private::slint_windowrc_set_physical_size(&inner, &size);
|
|
}
|
|
|
|
/// Registers a font by the specified path. The path must refer to an existing
|
|
/// TrueType font.
|
|
/// \returns an empty optional on success, otherwise an error string
|
|
inline std::optional<SharedString> register_font_from_path(const SharedString &path)
|
|
{
|
|
SharedString maybe_err;
|
|
cbindgen_private::slint_register_font_from_path(&inner, &path, &maybe_err);
|
|
if (!maybe_err.empty()) {
|
|
return maybe_err;
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
/// Registers a font by the data. The data must be valid TrueType font data.
|
|
/// \returns an empty optional on success, otherwise an error string
|
|
inline std::optional<SharedString> register_font_from_data(const uint8_t *data, std::size_t len)
|
|
{
|
|
SharedString maybe_err;
|
|
cbindgen_private::slint_register_font_from_data(
|
|
&inner, { const_cast<uint8_t *>(data), len }, &maybe_err);
|
|
if (!maybe_err.empty()) {
|
|
return maybe_err;
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
private:
|
|
cbindgen_private::WindowAdapterRcOpaque inner;
|
|
};
|
|
|
|
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::uintptr_t offset, std::uint32_t parent_index)
|
|
{
|
|
return ItemTreeNode { ItemTreeNode::DynamicTree_Body { ItemTreeNode::Tag::DynamicTree, offset,
|
|
parent_index } };
|
|
}
|
|
|
|
inline ItemRef get_item_ref(ComponentRef component,
|
|
const cbindgen_private::Slice<ItemTreeNode> item_tree,
|
|
const private_api::ItemArray item_array, int index)
|
|
{
|
|
const auto item_array_index = item_tree.ptr[index].item.item_array_index;
|
|
const auto item = item_array[item_array_index];
|
|
return ItemRef { item.vtable, reinterpret_cast<char *>(component.instance) + item.offset };
|
|
}
|
|
|
|
inline void dealloc(const ComponentVTable *, uint8_t *ptr, vtable::Layout layout)
|
|
{
|
|
#ifdef __cpp_sized_deallocation
|
|
::operator delete(reinterpret_cast<void *>(ptr), layout.size,
|
|
static_cast<std::align_val_t>(layout.align));
|
|
#else
|
|
::operator delete(reinterpret_cast<void *>(ptr), static_cast<std::align_val_t>(layout.align));
|
|
#endif
|
|
}
|
|
|
|
template<typename T>
|
|
inline vtable::Layout drop_in_place(ComponentRef component)
|
|
{
|
|
reinterpret_cast<T *>(component.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)
|
|
|
|
template<typename T>
|
|
struct ReturnWrapper
|
|
{
|
|
ReturnWrapper(T val) : value(std::move(val)) { }
|
|
T value;
|
|
};
|
|
template<>
|
|
struct ReturnWrapper<void>
|
|
{
|
|
};
|
|
} // 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::ComponentVTable, T> inner;
|
|
friend class ComponentWeakHandle<T>;
|
|
|
|
public:
|
|
/// internal constructor
|
|
ComponentHandle(const vtable::VRc<private_api::ComponentVTable, 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::ComponentVTable> 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::ComponentVTable, 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 {};
|
|
}
|
|
}
|
|
};
|
|
|
|
/// This class represents a window towards the windowing system, that's used to render the
|
|
/// scene of a component. It provides API to control windowing system specific aspects such
|
|
/// as the position on the screen.
|
|
class Window
|
|
{
|
|
public:
|
|
/// \private
|
|
/// Internal function used by the generated code to construct a new instance of this
|
|
/// public API wrapper.
|
|
explicit Window(const private_api::WindowAdapterRc &windowrc) : inner(windowrc) { }
|
|
Window(const Window &other) = delete;
|
|
Window &operator=(const Window &other) = delete;
|
|
Window(Window &&other) = delete;
|
|
Window &operator=(Window &&other) = delete;
|
|
/// Destroys this window. Window instances are explicitly shared and reference counted.
|
|
/// If this window instance is the last one referencing the window towards the windowing
|
|
/// system, then it will also become hidden and destroyed.
|
|
~Window() = default;
|
|
|
|
/// Registers the window with the windowing system in order to make it visible on the screen.
|
|
void show() { inner.show(); }
|
|
/// De-registers the window from the windowing system, therefore hiding it.
|
|
void hide() { inner.hide(); }
|
|
|
|
/// This function allows registering a callback that's invoked during the different phases of
|
|
/// rendering. This allows custom rendering on top or below of the scene.
|
|
/// On success, the function returns a std::optional without value. On error, the function
|
|
/// returns the error code as value in the std::optional.
|
|
template<typename F>
|
|
std::optional<SetRenderingNotifierError> set_rendering_notifier(F &&callback) const
|
|
{
|
|
return inner.set_rendering_notifier(std::forward<F>(callback));
|
|
}
|
|
|
|
/// This function allows registering a callback that's invoked when the user tries to close
|
|
/// a window.
|
|
/// The callback has to return a CloseRequestResponse.
|
|
template<typename F>
|
|
void on_close_requested(F &&callback) const
|
|
{
|
|
static_assert(std::is_invocable_v<F>, "Functor callback must be callable");
|
|
static_assert(std::is_same_v<std::invoke_result_t<F>, CloseRequestResponse>,
|
|
"Functor callback must return CloseRequestResponse");
|
|
return inner.on_close_requested(std::forward<F>(callback));
|
|
}
|
|
|
|
/// This function issues a request to the windowing system to redraw the contents of the window.
|
|
void request_redraw() const { inner.request_redraw(); }
|
|
|
|
/// Returns the position of the window on the screen, in physical screen coordinates and
|
|
/// including a window frame (if present).
|
|
slint::PhysicalPosition position() const { return inner.position(); }
|
|
|
|
/// Sets the position of the window on the screen, in physical screen coordinates and including
|
|
/// a window frame (if present).
|
|
/// Note that on some windowing systems, such as Wayland, this functionality is not available.
|
|
void set_position(const slint::LogicalPosition &pos) { inner.set_logical_position(pos); }
|
|
/// Sets the position of the window on the screen, in physical screen coordinates and including
|
|
/// a window frame (if present).
|
|
/// Note that on some windowing systems, such as Wayland, this functionality is not available.
|
|
void set_position(const slint::PhysicalPosition &pos) { inner.set_physical_position(pos); }
|
|
|
|
/// Returns the size of the window on the screen, in physical screen coordinates and excluding
|
|
/// a window frame (if present).
|
|
slint::PhysicalSize size() const { return inner.size(); }
|
|
|
|
/// Resizes the window to the specified size on the screen, in logical pixels and excluding
|
|
/// a window frame (if present).
|
|
void set_size(const slint::LogicalSize &size) { inner.set_logical_size(size); }
|
|
/// Resizes the window to the specified size on the screen, in physical pixels and excluding
|
|
/// a window frame (if present).
|
|
void set_size(const slint::PhysicalSize &size) { inner.set_physical_size(size); }
|
|
|
|
/// \private
|
|
private_api::WindowAdapterRc &window_handle() { return inner; }
|
|
/// \private
|
|
const private_api::WindowAdapterRc &window_handle() const { return inner; }
|
|
|
|
private:
|
|
private_api::WindowAdapterRc inner;
|
|
};
|
|
|
|
/// A Timer that can call a callback at repeated interval
|
|
///
|
|
/// Use the static single_shot function to make a single shot timer
|
|
struct Timer
|
|
{
|
|
/// Construct a null timer. Use the start() method to activate the timer with a mode, interval
|
|
/// and callback.
|
|
Timer() : id(-1) { }
|
|
/// Construct a timer which will repeat the callback every `interval` milliseconds until
|
|
/// the destructor of the timer is called.
|
|
///
|
|
/// This is a convenience function and equivalent to calling
|
|
/// `start(slint::TimerMode::Repeated, interval, callback);` on a default constructed Timer.
|
|
template<typename F>
|
|
Timer(std::chrono::milliseconds interval, F callback)
|
|
: id(cbindgen_private::slint_timer_start(
|
|
-1, TimerMode::Repeated, interval.count(),
|
|
[](void *data) { (*reinterpret_cast<F *>(data))(); }, new F(std::move(callback)),
|
|
[](void *data) { delete reinterpret_cast<F *>(data); }))
|
|
{
|
|
}
|
|
Timer(const Timer &) = delete;
|
|
Timer &operator=(const Timer &) = delete;
|
|
~Timer() { cbindgen_private::slint_timer_destroy(id); }
|
|
|
|
/// Starts the timer with the given \a mode and \a interval, in order for the \a callback to
|
|
/// called when the timer fires. If the timer has been started previously and not fired yet,
|
|
/// then it will be restarted.
|
|
template<typename F>
|
|
void start(TimerMode mode, std::chrono::milliseconds interval, F callback)
|
|
{
|
|
id = cbindgen_private::slint_timer_start(
|
|
id, mode, interval.count(), [](void *data) { (*reinterpret_cast<F *>(data))(); },
|
|
new F(std::move(callback)), [](void *data) { delete reinterpret_cast<F *>(data); });
|
|
}
|
|
/// Stops the previously started timer. Does nothing if the timer has never been started. A
|
|
/// stopped timer cannot be restarted with restart() -- instead you need to call start().
|
|
void stop() { cbindgen_private::slint_timer_stop(id); }
|
|
/// Restarts the timer. If the timer was previously started by calling [`Self::start()`]
|
|
/// with a duration and callback, then the time when the callback will be next invoked
|
|
/// is re-calculated to be in the specified duration relative to when this function is called.
|
|
///
|
|
/// Does nothing if the timer was never started.
|
|
void restart() { cbindgen_private::slint_timer_restart(id); }
|
|
/// Returns true if the timer is running; false otherwise.
|
|
bool running() const { return cbindgen_private::slint_timer_running(id); }
|
|
|
|
/// Call the callback after the given duration.
|
|
template<typename F>
|
|
static void single_shot(std::chrono::milliseconds duration, F callback)
|
|
{
|
|
cbindgen_private::slint_timer_singleshot(
|
|
duration.count(), [](void *data) { (*reinterpret_cast<F *>(data))(); },
|
|
new F(std::move(callback)), [](void *data) { delete reinterpret_cast<F *>(data); });
|
|
}
|
|
|
|
private:
|
|
int64_t id;
|
|
};
|
|
|
|
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) };
|
|
}
|
|
}
|
|
|
|
namespace private_api {
|
|
|
|
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);
|
|
}
|
|
|
|
inline SharedVector<float> solve_path_layout(const cbindgen_private::PathLayoutData &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_path_layout(&data, ri, &result);
|
|
return result;
|
|
}
|
|
|
|
/// 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(int index, int count) = 0;
|
|
virtual void row_removed(int index, int count) = 0;
|
|
virtual void row_changed(int index) = 0;
|
|
virtual void reset() = 0;
|
|
};
|
|
using ModelPeer = std::weak_ptr<ModelChangeListener>;
|
|
|
|
template<typename M>
|
|
auto access_array_index(const M &model, int index)
|
|
{
|
|
if (const auto v = model->row_data_tracked(index)) {
|
|
return *v;
|
|
} else {
|
|
return decltype(*v) {};
|
|
}
|
|
}
|
|
|
|
} // 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 int 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(int 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(int, const ModelData &)
|
|
{
|
|
std::cerr << "Model::set_row_data was called on a read-only model" << std::endl;
|
|
};
|
|
|
|
/// \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(int 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(int row) const
|
|
{
|
|
track_row_data_changes(row);
|
|
return row_data(row);
|
|
}
|
|
|
|
protected:
|
|
/// Notify the views that a specific row was changed
|
|
void row_changed(int 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(int index, int 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(int index, int 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<int> 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)... }
|
|
{
|
|
}
|
|
int row_count() const override { return Count; }
|
|
std::optional<ModelData> row_data(int i) const override
|
|
{
|
|
if (i >= row_count())
|
|
return {};
|
|
return data[i];
|
|
}
|
|
void set_row_data(int i, const ModelData &value) override
|
|
{
|
|
if (i < row_count()) {
|
|
data[i] = value;
|
|
this->row_changed(i);
|
|
}
|
|
}
|
|
};
|
|
|
|
/// Model to be used when we just want to repeat without data.
|
|
struct IntModel : Model<int>
|
|
{
|
|
/// Constructs a new IntModel with \a d rows.
|
|
IntModel(int d) : data(d) { }
|
|
/// \private
|
|
int data;
|
|
/// \copydoc Model::row_count
|
|
int row_count() const override { return data; }
|
|
std::optional<int> row_data(int value) const override
|
|
{
|
|
if (value >= row_count())
|
|
return {};
|
|
return 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)) { }
|
|
int row_count() const override { return int(data.size()); }
|
|
std::optional<ModelData> row_data(int i) const override
|
|
{
|
|
if (i >= row_count())
|
|
return {};
|
|
return std::optional<ModelData> { data[i] };
|
|
}
|
|
void set_row_data(int 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(int(data.size()) - 1, 1);
|
|
}
|
|
|
|
/// Remove the row at the given index from the model
|
|
void erase(int 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(int(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(int index, int count) override
|
|
{
|
|
if (count == 0) {
|
|
return;
|
|
}
|
|
|
|
std::vector<int> added_accepted_rows;
|
|
for (int 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(int 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(int index, int 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 (int 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<int> 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);
|
|
}
|
|
|
|
int row_count() const override { return inner->accepted_rows.size(); }
|
|
|
|
std::optional<ModelData> row_data(int i) const override
|
|
{
|
|
if (i < 0 || size_t(i) >= inner->accepted_rows.size())
|
|
return {};
|
|
return inner->source_model->row_data(inner->accepted_rows[i]);
|
|
}
|
|
|
|
void set_row_data(int 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(int index, int count) override { target_model.row_added(index, count); }
|
|
void row_changed(int index) override { target_model.row_changed(index); }
|
|
void row_removed(int index, int 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);
|
|
}
|
|
|
|
int row_count() const override { return model->row_count(); }
|
|
|
|
std::optional<MappedModelData> row_data(int 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(int first_inserted_row, int 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 (int 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](int 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(int 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](int 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(int first_removed_row, int count) override
|
|
{
|
|
if (sorted_rows_dirty) {
|
|
reset();
|
|
return;
|
|
}
|
|
|
|
std::vector<int> 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 (int 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](int lhs_index, int 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<int> 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);
|
|
}
|
|
|
|
int row_count() const override { return inner->source_model->row_count(); }
|
|
|
|
std::optional<ModelData> row_data(int i) const override
|
|
{
|
|
inner->ensure_sorted();
|
|
return inner->source_model->row_data(inner->sorted_rows[i]);
|
|
}
|
|
|
|
void set_row_data(int 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;
|
|
};
|
|
|
|
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 ComponentWithState
|
|
{
|
|
State state = State::Dirty;
|
|
std::optional<ComponentHandle<C>> ptr;
|
|
};
|
|
std::vector<ComponentWithState> data;
|
|
private_api::Property<bool> is_dirty { true };
|
|
|
|
void row_added(int index, int count) override
|
|
{
|
|
is_dirty.set(true);
|
|
data.resize(data.size() + count);
|
|
std::rotate(data.begin() + index, data.end() - count, data.end());
|
|
}
|
|
void row_changed(int index) override
|
|
{
|
|
is_dirty.set(true);
|
|
data[index].state = State::Dirty;
|
|
}
|
|
void row_removed(int index, int 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()) {
|
|
m->attach_peer(inner);
|
|
}
|
|
}
|
|
|
|
if (inner && inner->is_dirty.get()) {
|
|
inner->is_dirty.set(false);
|
|
if (auto m = model.get()) {
|
|
int count = m->row_count();
|
|
inner->data.resize(count);
|
|
for (int i = 0; i < count; ++i) {
|
|
auto &c = inner->data[i];
|
|
if (!c.ptr) {
|
|
c.ptr = C::create(parent);
|
|
}
|
|
if (c.state == RepeaterInner::State::Dirty) {
|
|
(*c.ptr)->update_data(i, *m->row_data(i));
|
|
}
|
|
}
|
|
} 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);
|
|
}
|
|
|
|
uintptr_t visit(TraversalOrder order, private_api::ItemVisitorRefMut visitor) const
|
|
{
|
|
for (std::size_t i = 0; i < inner->data.size(); ++i) {
|
|
int index = order == TraversalOrder::BackToFront ? i : inner->data.size() - 1 - i;
|
|
auto ref = item_at(index);
|
|
if (ref.vtable->visit_children_item(ref, -1, order, visitor)
|
|
!= std::numeric_limits<uint64_t>::max()) {
|
|
return index;
|
|
}
|
|
}
|
|
return std::numeric_limits<uint64_t>::max();
|
|
}
|
|
|
|
vtable::VRef<private_api::ComponentVTable> 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::ComponentVTable> component_at(int i) const
|
|
{
|
|
const auto &x = inner->data.at(i);
|
|
return vtable::VWeak<private_api::ComponentVTable> { 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(int 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);
|
|
if (inner && inner->is_dirty.get()) {
|
|
auto &c = inner->data[row];
|
|
if (c.state == RepeaterInner::State::Dirty && c.ptr) {
|
|
(*c.ptr)->update_data(row, *m->row_data(row));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
} // 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 {
|
|
// Code generated by Slint <= 0.1.5 uses this enum with VersionCheckHelper
|
|
enum class [[deprecated]] VersionCheck { Major = SLINT_VERSION_MAJOR, Minor = SLINT_VERSION_MINOR,
|
|
Patch = SLINT_VERSION_PATCH };
|
|
template<int Major, int Minor, int Patch>
|
|
struct 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<typename 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); });
|
|
}
|
|
|
|
/// 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<typename Functor,
|
|
typename = std::enable_if_t<!std::is_void_v<std::invoke_result_t<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);
|
|
}
|
|
|
|
template<typename Functor,
|
|
typename = std::enable_if_t<std::is_void_v<std::invoke_result_t<Functor>>>>
|
|
auto blocking_invoke_from_event_loop(Functor f) -> void
|
|
{
|
|
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; });
|
|
}
|
|
|
|
} // namespace slint
|