slint/api/cpp/include/slint.h
Tobias Hunger 07ad20a09c
Basic Slint accessibility support (#1294)
Implement basic accessibility (a11y) support, using the Qt backend.

_This should get us started, but accessibility support is an additional way to interact with UIs that is very different from the "graphical way" most users will interact with the UI. No single PR will "make a toolkit accessibility", this needs to be an ongoing effort!_

Parts of this PR:

* Add functions to access a11y-related properties to Component
* Add helper functions to Item struct 
* Handle accessible- properties in the compiler
* Add documentation, add description, enforce some basic rules
* Make the Text element accessible by default
* Don't optimize away accessibility property in the LLR
* Ensure that accessibility property are marked as used
* Add some accessibility properties to the native style widgets
* Support for bool and integer `accessible` properties
* Implement basic support for accessibility
* Make basic widgets accessible by default
* Make slider focus-able and interactable with keyboard
* Tell a11y layer about value changes
* Generate QAccessible constants using bindgen
* Don't expose the `accessible` properties when using the MCU backend: There is no backend to make use of them
* Handle focus change based on keyboard focus of the window
* Report accessible widgets at correct positions
* Allow for (virtual) focus delegation at the a11y level
* Calculate value step size dynamically
* Make sure to not send notifications to a11y backend about dead objects
2022-06-08 20:42:10 +02:00

1102 lines
39 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>
namespace slint::cbindgen_private {
// Workaround https://github.com/eqrion/cbindgen/issues/43
struct ComponentVTable;
struct ItemVTable;
}
#include "slint_internal.h"
#include "slint_backend_internal.h"
#include "slint_qt_internal.h"
#include "slint_selector_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 WindowRc
{
public:
explicit WindowRc(cbindgen_private::WindowRcOpaque adopted_inner) : inner(adopted_inner) { }
WindowRc() { cbindgen_private::slint_windowrc_init(&inner); }
~WindowRc() { cbindgen_private::slint_windowrc_drop(&inner); }
WindowRc(const WindowRc &other)
{
assert_main_thread();
cbindgen_private::slint_windowrc_clone(&other.inner, &inner);
}
WindowRc(WindowRc &&) = delete;
WindowRc &operator=(WindowRc &&) = delete;
WindowRc &operator=(const WindowRc &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); }
template<typename Component, typename ItemArray>
void free_graphics_resources(Component *c, ItemArray items) const
{
cbindgen_private::slint_component_free_item_array_graphics_resources(
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 init_items(Component *c, ItemArray items) const
{
cbindgen_private::slint_component_init_items(
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); }
private:
cbindgen_private::WindowRcOpaque 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::WindowRc &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(); }
/// \private
private_api::WindowRc &window_handle() { return inner; }
/// \private
const private_api::WindowRc &window_handle() const { return inner; }
private:
private_api::WindowRc 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 AbstractRepeaterView
{
virtual ~AbstractRepeaterView() = 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<AbstractRepeaterView>;
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);
}
};
namespace private_api {
template<typename C, typename ModelData>
class Repeater
{
private_api::Property<std::shared_ptr<Model<ModelData>>> model;
struct RepeaterInner : AbstractRepeaterView
{
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 private_api {
/// 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(&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({ const_cast<uint8_t *>(data), len },
&maybe_err);
if (!maybe_err.empty()) {
return maybe_err;
} else {
return {};
}
}
}
} // namespace slint