mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-06 00:20:23 +00:00

In the compiler this is still very primitive, but an attempt to start a generic interface. The basic assumption is that all item functions will eventually need access to the window adapter and itemrc. Support for additional arguments is still missing. Also missing is support for the function access via rtti in the interpreter, hence the hardcoding at the moment.
1637 lines
59 KiB
C++
1637 lines
59 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>
|
|
#include <concepts>
|
|
|
|
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;
|
|
}
|
|
|
|
#if !defined(DOXYGEN)
|
|
namespace experimental {
|
|
namespace platform {
|
|
class SkiaRenderer;
|
|
class SoftwareRenderer;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
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::TableColumn;
|
|
|
|
/// 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)
|
|
{
|
|
assert_main_thread();
|
|
cbindgen_private::slint_windowrc_clone(&adopted_inner, &inner);
|
|
}
|
|
WindowAdapterRc() { cbindgen_private::slint_windowrc_init(&inner); }
|
|
~WindowAdapterRc() { cbindgen_private::slint_windowrc_drop(&inner); }
|
|
WindowAdapterRc(const WindowAdapterRc &other) : WindowAdapterRc(other.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); }
|
|
bool is_visible() const { return slint_windowrc_is_visible(&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); }
|
|
|
|
bool text_input_focused() const { return slint_windowrc_get_text_input_focused(&inner); }
|
|
void set_text_input_focused(bool value) const
|
|
{
|
|
slint_windowrc_set_text_input_focused(&inner, value);
|
|
}
|
|
|
|
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()).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<std::invocable<RenderingState, GraphicsAPI> 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;
|
|
}
|
|
}
|
|
|
|
// clang-format off
|
|
template<std::invocable F>
|
|
requires(std::is_convertible_v<std::invoke_result_t<F>, CloseRequestResponse>)
|
|
void on_close_requested(F callback) const
|
|
// clang-format on
|
|
{
|
|
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);
|
|
}
|
|
|
|
void dispatch_key_event(const cbindgen_private::KeyInputEvent &event)
|
|
{
|
|
private_api::assert_main_thread();
|
|
cbindgen_private::slint_windowrc_dispatch_key_event(&inner, &event);
|
|
}
|
|
|
|
/// 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
|
|
const cbindgen_private::WindowAdapterRcOpaque &handle() { return inner; }
|
|
|
|
private:
|
|
friend class slint::experimental::platform::SkiaRenderer;
|
|
friend class slint::experimental::platform::SoftwareRenderer;
|
|
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));
|
|
#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(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(); }
|
|
|
|
/// Returns the visibility state of the window. This function can return false even if you
|
|
/// previously called show() on it, for example if the user minimized the window.
|
|
bool is_visible() const { return inner.is_visible(); }
|
|
|
|
/// 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.
|
|
///
|
|
/// The provided callback must be callable with a slint::RenderingState and the
|
|
/// slint::GraphicsAPI argument.
|
|
///
|
|
/// 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<std::invocable<RenderingState, GraphicsAPI> 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.
|
|
// clang-format off
|
|
template<std::invocable F>
|
|
requires(std::is_convertible_v<std::invoke_result_t<F>, CloseRequestResponse>)
|
|
void on_close_requested(F &&callback) const
|
|
// clang-format on
|
|
{
|
|
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); }
|
|
|
|
/// Dispatch a key press event to the scene.
|
|
///
|
|
/// Use this when you're implementing your own backend and want to forward user input events.
|
|
///
|
|
/// The \a text is the unicode representation of the key.
|
|
void dispatch_key_press_event(const SharedString &text)
|
|
{
|
|
cbindgen_private::KeyInputEvent event { text, cbindgen_private::KeyEventType::KeyPressed, 0,
|
|
0 };
|
|
inner.dispatch_key_event(event);
|
|
}
|
|
|
|
/// Dispatch a key release event to the scene.
|
|
///
|
|
/// Use this when you're implementing your own backend and want to forward user input events.
|
|
///
|
|
/// The \a text is the unicode representation of the key.
|
|
void dispatch_key_release_event(const SharedString &text)
|
|
{
|
|
cbindgen_private::KeyInputEvent event { text, cbindgen_private::KeyEventType::KeyReleased,
|
|
0, 0 };
|
|
inner.dispatch_key_event(event);
|
|
}
|
|
|
|
/// \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<std::invocable 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<std::invocable 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(). Use start() instead.
|
|
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<std::invocable 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);
|
|
}
|
|
|
|
/// 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 M &model, size_t 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
|
|
/// `for - in<../../slint/src/reference/repetitions.html>`_ repetitions or
|
|
/// `ListView<../../slint/src/builtins/widgets.html#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 &)
|
|
{
|
|
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(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);
|
|
}
|
|
}
|
|
};
|
|
|
|
/// 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<int> 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](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(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](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(size_t first_removed_row, size_t 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<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;
|
|
};
|
|
|
|
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(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());
|
|
}
|
|
void row_changed(size_t index) override
|
|
{
|
|
is_dirty.set(true);
|
|
data[index].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()) {
|
|
auto preserved_data = inner ? std::make_optional(std::move(inner->data)) : std::nullopt;
|
|
inner = std::make_shared<RepeaterInner>();
|
|
if (auto data = preserved_data) {
|
|
inner->data = std::move(*data);
|
|
for (auto &&compo_with_state : inner->data) {
|
|
compo_with_state.state = RepeaterInner::State::Dirty;
|
|
}
|
|
}
|
|
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()) {
|
|
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::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(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);
|
|
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 {
|
|
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<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); });
|
|
}
|
|
|
|
/// 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
|
|
|
|
} // namespace slint
|