// Copyright © SixtyFPS GmbH // 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 #include #include #include // FIXME: remove: iostream always bring it lots of code so we should not have it in this header #include #include #include #include #include #include #include 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; using ComponentRef = vtable::VRef; using IndexRange = cbindgen_private::IndexRange; using ItemRef = vtable::VRef; using ItemVisitorRefMut = vtable::VRefMut; using cbindgen_private::ComponentWeak; using cbindgen_private::ItemWeak; using cbindgen_private::TraversalOrder; } namespace private_api { using ItemTreeNode = cbindgen_private::ItemTreeNode; using ItemArrayEntry = vtable::VOffset; using ItemArray = slint::cbindgen_private::Slice; using cbindgen_private::KeyboardModifiers; using cbindgen_private::KeyEvent; using cbindgen_private::PointerEvent; using cbindgen_private::StandardListViewItem; /// Internal function that checks that the API that must be called from the main /// thread is indeed called from the main thread, or abort the program otherwise /// /// Most API should be called from the main thread. When using thread one must /// use slint::invoke_from_event_loop inline void assert_main_thread() { #ifndef NDEBUG static auto main_thread_id = std::this_thread::get_id(); if (main_thread_id != std::this_thread::get_id()) { std::cerr << "A function that should be only called from the main thread was called from a " "thread." << std::endl; std::cerr << "Most API should be called from the main thread. When using thread one must " "use slint::invoke_from_event_loop." << std::endl; std::abort(); } #endif } class WindowAdapterRc { public: explicit WindowAdapterRc(cbindgen_private::WindowAdapterRcOpaque adopted_inner) : inner(adopted_inner) { } WindowAdapterRc() { cbindgen_private::slint_windowrc_init(&inner); } ~WindowAdapterRc() { cbindgen_private::slint_windowrc_drop(&inner); } WindowAdapterRc(const WindowAdapterRc &other) { assert_main_thread(); cbindgen_private::slint_windowrc_clone(&other.inner, &inner); } WindowAdapterRc(WindowAdapterRc &&) = delete; WindowAdapterRc &operator=(WindowAdapterRc &&) = delete; WindowAdapterRc &operator=(const WindowAdapterRc &other) { assert_main_thread(); if (this != &other) { cbindgen_private::slint_windowrc_drop(&inner); cbindgen_private::slint_windowrc_clone(&other.inner, &inner); } return *this; } void show() const { slint_windowrc_show(&inner); } void hide() const { slint_windowrc_hide(&inner); } float scale_factor() const { return slint_windowrc_get_scale_factor(&inner); } void set_scale_factor(float value) const { slint_windowrc_set_scale_factor(&inner, value); } bool dark_color_scheme() const { return slint_windowrc_dark_color_scheme(&inner); } template void unregister_component(Component *c, ItemArray items) const { cbindgen_private::slint_unregister_component( vtable::VRef { &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 void register_component(Component *c, ItemArray items) const { cbindgen_private::slint_register_component( vtable::VRef { &Component::static_vtable, c }, items, &inner); } template 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 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::optional set_rendering_notifier(F callback) const { auto actual_cb = [](RenderingState state, GraphicsAPI graphics_api, void *data) { (*reinterpret_cast(data))(state, graphics_api); }; SetRenderingNotifierError err; if (cbindgen_private::slint_windowrc_set_rendering_notifier( &inner, actual_cb, [](void *user_data) { delete reinterpret_cast(user_data); }, new F(std::move(callback)), &err)) { return {}; } else { return err; } } template void on_close_requested(F callback) const { auto actual_cb = [](void *data) { return (*reinterpret_cast(data))(); }; cbindgen_private::slint_windowrc_on_close_requested( &inner, actual_cb, [](void *user_data) { delete reinterpret_cast(user_data); }, new F(std::move(callback))); } void request_redraw() const { cbindgen_private::slint_windowrc_request_redraw(&inner); } slint::PhysicalPosition position() const { slint::PhysicalPosition pos; cbindgen_private::slint_windowrc_position(&inner, &pos); return pos; } void set_logical_position(const slint::LogicalPosition &pos) { cbindgen_private::slint_windowrc_set_logical_position(&inner, &pos); } void set_physical_position(const slint::PhysicalPosition &pos) { cbindgen_private::slint_windowrc_set_physical_position(&inner, &pos); } slint::PhysicalSize size() const { return slint::PhysicalSize(cbindgen_private::slint_windowrc_size(&inner)); } void set_logical_size(const slint::LogicalSize &size) { cbindgen_private::slint_windowrc_set_logical_size(&inner, &size); } void set_physical_size(const slint::PhysicalSize &size) { cbindgen_private::slint_windowrc_set_physical_size(&inner, &size); } /// Registers a font by the specified path. The path must refer to an existing /// TrueType font. /// \returns an empty optional on success, otherwise an error string inline std::optional 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 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(data), len }, &maybe_err); if (!maybe_err.empty()) { return maybe_err; } else { return {}; } } private: cbindgen_private::WindowAdapterRcOpaque inner; }; constexpr inline ItemTreeNode make_item_node(uint32_t child_count, uint32_t child_index, uint32_t parent_index, uint32_t item_array_index, bool is_accessible) { return ItemTreeNode { ItemTreeNode::Item_Body { ItemTreeNode::Tag::Item, is_accessible, child_count, child_index, parent_index, item_array_index } }; } constexpr inline ItemTreeNode make_dyn_node(std::uintptr_t offset, std::uint32_t parent_index) { return ItemTreeNode { ItemTreeNode::DynamicTree_Body { ItemTreeNode::Tag::DynamicTree, offset, parent_index } }; } inline ItemRef get_item_ref(ComponentRef component, const cbindgen_private::Slice 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(component.instance) + item.offset }; } inline void dealloc(const ComponentVTable *, uint8_t *ptr, vtable::Layout layout) { #ifdef __cpp_sized_deallocation ::operator delete(reinterpret_cast(ptr), layout.size, static_cast(layout.align)); #else ::operator delete(reinterpret_cast(ptr), static_cast(layout.align)); #endif } template inline vtable::Layout drop_in_place(ComponentRef component) { reinterpret_cast(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 struct ReturnWrapper { ReturnWrapper(T val) : value(std::move(val)) { } T value; }; template<> struct ReturnWrapper { }; } // namespace private_api template 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` template class ComponentHandle { vtable::VRc inner; friend class ComponentWeakHandle; public: /// internal constructor ComponentHandle(const vtable::VRc &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 into_dyn() const { return inner.into_dyn(); } }; /// A weak reference to the component. Can be constructed from a `ComponentHandle` template class ComponentWeakHandle { vtable::VWeak inner; public: /// Constructs a null ComponentWeakHandle. lock() will always return empty. ComponentWeakHandle() = default; /// Copy-constructs a new ComponentWeakHandle from \a other. ComponentWeakHandle(const ComponentHandle &other) : inner(other.inner) { } /// Returns a new strong ComponentHandle if the component the weak handle points to is /// still referenced by any other ComponentHandle. An empty std::optional is returned /// otherwise. std::optional> lock() const { private_api::assert_main_thread(); if (auto l = inner.lock()) { return { ComponentHandle(*l) }; } else { return {}; } } }; /// This class represents a window towards the windowing system, that's used to render the /// scene of a component. It provides API to control windowing system specific aspects such /// as the position on the screen. class Window { public: /// \private /// Internal function used by the generated code to construct a new instance of this /// public API wrapper. explicit Window(const private_api::WindowAdapterRc &windowrc) : inner(windowrc) { } Window(const Window &other) = delete; Window &operator=(const Window &other) = delete; Window(Window &&other) = delete; Window &operator=(Window &&other) = delete; /// Destroys this window. Window instances are explicitly shared and reference counted. /// If this window instance is the last one referencing the window towards the windowing /// system, then it will also become hidden and destroyed. ~Window() = default; /// Registers the window with the windowing system in order to make it visible on the screen. void show() { inner.show(); } /// De-registers the window from the windowing system, therefore hiding it. void hide() { inner.hide(); } /// This function allows registering a callback that's invoked during the different phases of /// rendering. This allows custom rendering on top or below of the scene. /// On success, the function returns a std::optional without value. On error, the function /// returns the error code as value in the std::optional. template std::optional set_rendering_notifier(F &&callback) const { return inner.set_rendering_notifier(std::forward(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 void on_close_requested(F &&callback) const { static_assert(std::is_invocable_v, "Functor callback must be callable"); static_assert(std::is_same_v, CloseRequestResponse>, "Functor callback must return CloseRequestResponse"); return inner.on_close_requested(std::forward(callback)); } /// This function issues a request to the windowing system to redraw the contents of the window. void request_redraw() const { inner.request_redraw(); } /// Returns the position of the window on the screen, in physical screen coordinates and /// including a window frame (if present). slint::PhysicalPosition position() const { return inner.position(); } /// Sets the position of the window on the screen, in physical screen coordinates and including /// a window frame (if present). /// Note that on some windowing systems, such as Wayland, this functionality is not available. void set_position(const slint::LogicalPosition &pos) { inner.set_logical_position(pos); } /// Sets the position of the window on the screen, in physical screen coordinates and including /// a window frame (if present). /// Note that on some windowing systems, such as Wayland, this functionality is not available. void set_position(const slint::PhysicalPosition &pos) { inner.set_physical_position(pos); } /// Returns the size of the window on the screen, in physical screen coordinates and excluding /// a window frame (if present). slint::PhysicalSize size() const { return inner.size(); } /// Resizes the window to the specified size on the screen, in logical pixels and excluding /// a window frame (if present). void set_size(const slint::LogicalSize &size) { inner.set_logical_size(size); } /// Resizes the window to the specified size on the screen, in physical pixels and excluding /// a window frame (if present). void set_size(const slint::PhysicalSize &size) { inner.set_physical_size(size); } /// \private private_api::WindowAdapterRc &window_handle() { return inner; } /// \private const private_api::WindowAdapterRc &window_handle() const { return inner; } private: private_api::WindowAdapterRc inner; }; /// A Timer that can call a callback at repeated interval /// /// Use the static single_shot function to make a single shot timer struct Timer { /// Construct a null timer. Use the start() method to activate the timer with a mode, interval /// and callback. Timer() : id(-1) { } /// Construct a timer which will repeat the callback every `interval` milliseconds until /// the destructor of the timer is called. /// /// This is a convenience function and equivalent to calling /// `start(slint::TimerMode::Repeated, interval, callback);` on a default constructed Timer. template Timer(std::chrono::milliseconds interval, F callback) : id(cbindgen_private::slint_timer_start( -1, TimerMode::Repeated, interval.count(), [](void *data) { (*reinterpret_cast(data))(); }, new F(std::move(callback)), [](void *data) { delete reinterpret_cast(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 void start(TimerMode mode, std::chrono::milliseconds interval, F callback) { id = cbindgen_private::slint_timer_start( id, mode, interval.count(), [](void *data) { (*reinterpret_cast(data))(); }, new F(std::move(callback)), [](void *data) { delete reinterpret_cast(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 static void single_shot(std::chrono::milliseconds duration, F callback) { cbindgen_private::slint_timer_singleshot( duration.count(), [](void *data) { (*reinterpret_cast(data))(); }, new F(std::move(callback)), [](void *data) { delete reinterpret_cast(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 solve_box_layout(const cbindgen_private::BoxLayoutData &data, cbindgen_private::Slice repeater_indexes) { SharedVector result; cbindgen_private::Slice ri { reinterpret_cast(repeater_indexes.ptr), repeater_indexes.len }; cbindgen_private::slint_solve_box_layout(&data, ri, &result); return result; } inline SharedVector solve_grid_layout(const cbindgen_private::GridLayoutData &data) { SharedVector result; cbindgen_private::slint_solve_grid_layout(&data, &result); return result; } inline cbindgen_private::LayoutInfo grid_layout_info(cbindgen_private::Slice 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 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 cells, const cbindgen_private::Padding &padding) { return cbindgen_private::slint_box_layout_info_ortho(cells, &padding); } inline SharedVector solve_path_layout(const cbindgen_private::PathLayoutData &data, cbindgen_private::Slice repeater_indexes) { SharedVector result; cbindgen_private::Slice ri { reinterpret_cast(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 &cache, int offset, int repeater_index) { size_t idx = size_t(cache[offset]) + repeater_index * 2; return idx < cache.size() ? cache[idx] : 0; } // models struct ModelChangeListener { virtual ~ModelChangeListener() = default; virtual void row_added(int index, int count) = 0; virtual void row_removed(int index, int count) = 0; virtual void row_changed(int index) = 0; virtual void reset() = 0; }; using ModelPeer = std::weak_ptr; template 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 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 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 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 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 peers; private_api::Property model_row_count_dirty_property; private_api::Property model_row_data_dirty_property; mutable std::vector tracked_rows; }; namespace private_api { /// A Model backed by a std::array of constant size /// \private template class ArrayModel : public Model { std::array data; public: /// Constructs a new ArrayModel by forwarding \a to the std::array constructor. template ArrayModel(A &&...a) : data { std::forward(a)... } { } int row_count() const override { return Count; } std::optional 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 { /// 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 row_data(int value) const override { if (value >= row_count()) return {}; return value; } }; } // namespace private_api /// A Model backed by a SharedVector template class VectorModel : public Model { std::vector data; public: /// Constructs a new empty VectorModel. VectorModel() = default; /// Constructs a new VectorModel from \a array. VectorModel(std::vector array) : data(std::move(array)) { } int row_count() const override { return int(data.size()); } std::optional row_data(int i) const override { if (i >= row_count()) return {}; return std::optional { data[i] }; } void set_row_data(int i, const ModelData &value) override { if (i < row_count()) { data[i] = value; this->row_changed(i); } } /// Append a new row with the given value void push_back(const ModelData &value) { data.push_back(value); this->row_added(int(data.size()) - 1, 1); } /// Remove the row at the given index from the model void erase(int index) { data.erase(data.begin() + index); this->row_removed(index, 1); } /// Inserts the given value as a new row at the specified index void insert(size_t index, const ModelData &value) { data.insert(data.begin() + index, value); this->row_added(int(index), 1); } }; template class FilterModel; namespace private_api { template struct FilterModelInner : private_api::ModelChangeListener { FilterModelInner(std::shared_ptr> source_model, std::function filter_fn, slint::FilterModel &target_model) : source_model(source_model), filter_fn(filter_fn), target_model(target_model) { update_mapping(); } void row_added(int index, int count) override { if (count == 0) { return; } std::vector added_accepted_rows; for (int i = index; i < index + count; ++i) { if (auto data = source_model->row_data(i)) { if (filter_fn(*data)) { added_accepted_rows.push_back(i); } } } if (added_accepted_rows.empty()) { return; } auto insertion_point = std::lower_bound(accepted_rows.begin(), accepted_rows.end(), index); insertion_point = accepted_rows.insert(insertion_point, added_accepted_rows.begin(), added_accepted_rows.end()); for (auto it = insertion_point + added_accepted_rows.size(); it != accepted_rows.end(); ++it) (*it) += count; target_model.row_added(insertion_point - accepted_rows.begin(), added_accepted_rows.size()); } void row_changed(int index) override { auto existing_row = std::lower_bound(accepted_rows.begin(), accepted_rows.end(), index); auto existing_row_index = std::distance(accepted_rows.begin(), existing_row); bool is_contained = existing_row != accepted_rows.end() && *existing_row == index; auto accepted_updated_row = filter_fn(*source_model->row_data(index)); if (is_contained && accepted_updated_row) { target_model.row_changed(existing_row_index); } else if (!is_contained && accepted_updated_row) { accepted_rows.insert(existing_row, index); target_model.row_added(existing_row_index, 1); } else if (is_contained && !accepted_updated_row) { accepted_rows.erase(existing_row); target_model.row_removed(existing_row_index, 1); } } void row_removed(int index, int count) override { auto mapped_row_start = std::lower_bound(accepted_rows.begin(), accepted_rows.end(), index); auto mapped_row_end = std::lower_bound(accepted_rows.begin(), accepted_rows.end(), index + count); auto mapped_removed_len = std::distance(mapped_row_start, mapped_row_end); auto mapped_removed_index = (mapped_row_start != accepted_rows.end() && *mapped_row_start == index) ? std::optional(mapped_row_start - accepted_rows.begin()) : std::nullopt; auto it = accepted_rows.erase(mapped_row_start, mapped_row_end); for (; it != accepted_rows.end(); ++it) { *it -= count; } if (mapped_removed_index) { target_model.row_removed(*mapped_removed_index, mapped_removed_len); } } void reset() override { update_mapping(); target_model.reset(); } void update_mapping() { accepted_rows.clear(); for (int i = 0, count = source_model->row_count(); i < count; ++i) { if (auto data = source_model->row_data(i)) { if (filter_fn(*data)) { accepted_rows.push_back(i); } } } } std::shared_ptr> source_model; std::function filter_fn; std::vector accepted_rows; slint::FilterModel &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 class FilterModel : public Model { friend struct private_api::FilterModelInner; 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> source_model, std::function filter_fn) : inner(std::make_shared>( std::move(source_model), std::move(filter_fn), *this)) { inner->source_model->attach_peer(inner); } int row_count() const override { return inner->accepted_rows.size(); } std::optional row_data(int i) const override { if (i < 0 || size_t(i) >= inner->accepted_rows.size()) return {}; return inner->source_model->row_data(inner->accepted_rows[i]); } void set_row_data(int i, const ModelData &value) override { inner->source_model->set_row_data(inner->accepted_rows[i], value); } /// Re-applies the model's filter function on each row of the source model. Use this if state /// external to the filter function has changed. void reset() { inner->reset(); } /// Given the \a filtered_row index, this function returns the corresponding row index in the /// source model. int unfiltered_row(int filtered_row) const { return inner->accepted_rows[filtered_row]; } /// Returns the source model of this filter model. std::shared_ptr> source_model() const { return inner->source_model; } private: std::shared_ptr> inner; }; template class MapModel; namespace private_api { template struct MapModelInner : private_api::ModelChangeListener { MapModelInner(slint::MapModel &target_model) : target_model(target_model) { } void row_added(int index, int count) override { target_model.row_added(index, count); } void row_changed(int index) override { target_model.row_changed(index); } void row_removed(int index, int count) override { target_model.row_removed(index, count); } void reset() override { target_model.reset(); } slint::MapModel &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 class MapModel : public Model { friend struct private_api::MapModelInner; 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> source_model, std::function map_fn) : inner(std::make_shared>( *this)), model(source_model), map_fn(map_fn) { model->attach_peer(inner); } int row_count() const override { return model->row_count(); } std::optional row_data(int i) const override { if (auto source_data = model->row_data(i)) return map_fn(*source_data); else return {}; } /// Returns the source model of this filter model. std::shared_ptr> source_model() const { return model; } private: std::shared_ptr> inner; std::shared_ptr> model; std::function map_fn; }; template class SortModel; namespace private_api { template struct SortModelInner : private_api::ModelChangeListener { SortModelInner(std::shared_ptr> source_model, std::function comp, slint::SortModel &target_model) : source_model(source_model), comp(comp), target_model(target_model) { } void row_added(int first_inserted_row, int count) override { if (sorted_rows_dirty) { reset(); return; } // Adjust the existing sorted row indices to match the updated source model for (auto &row : sorted_rows) { if (row >= first_inserted_row) row += count; } for (int row = first_inserted_row; row < first_inserted_row + count; ++row) { ModelData inserted_value = *source_model->row_data(row); auto insertion_point = std::lower_bound(sorted_rows.begin(), sorted_rows.end(), inserted_value, [this](int sorted_row, const ModelData &inserted_value) { auto sorted_elem = source_model->row_data(sorted_row); return comp(*sorted_elem, inserted_value); }); insertion_point = sorted_rows.insert(insertion_point, row); target_model.row_added(std::distance(sorted_rows.begin(), insertion_point), 1); } } void row_changed(int changed_row) override { if (sorted_rows_dirty) { reset(); return; } auto removed_row_it = sorted_rows.erase(std::find(sorted_rows.begin(), sorted_rows.end(), changed_row)); auto removed_row = std::distance(sorted_rows.begin(), removed_row_it); ModelData changed_value = *source_model->row_data(changed_row); auto insertion_point = std::lower_bound(sorted_rows.begin(), sorted_rows.end(), changed_value, [this](int sorted_row, const ModelData &changed_value) { auto sorted_elem = source_model->row_data(sorted_row); return comp(*sorted_elem, changed_value); }); insertion_point = sorted_rows.insert(insertion_point, changed_row); auto inserted_row = std::distance(sorted_rows.begin(), insertion_point); if (inserted_row == removed_row) { target_model.row_changed(removed_row); } else { target_model.row_removed(removed_row, 1); target_model.row_added(inserted_row, 1); } } void row_removed(int first_removed_row, int count) override { if (sorted_rows_dirty) { reset(); return; } std::vector 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> source_model; std::function comp; slint::SortModel &target_model; std::vector 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 class SortModel : public Model { friend struct private_api::SortModelInner; 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> source_model, std::function comp) : inner(std::make_shared>(std::move(source_model), std::move(comp), *this)) { inner->source_model->attach_peer(inner); } int row_count() const override { return inner->source_model->row_count(); } std::optional row_data(int i) const override { inner->ensure_sorted(); return inner->source_model->row_data(inner->sorted_rows[i]); } void set_row_data(int i, const ModelData &value) override { inner->source_model->set_row_data(inner->sorted_rows[i], value); } /// Re-applies the model's sort function on each row of the source model. Use this if state /// external to the sort function has changed. void reset() { inner->reset(); } /// Given the \a sorted_row_index, this function returns the corresponding row index in the /// source model. int unsorted_row(int sorted_row_index) const { inner->ensure_sorted(); return inner->sorted_rows[sorted_row_index]; } /// Returns the source model of this filter model. std::shared_ptr> source_model() const { return inner->source_model; } private: std::shared_ptr> inner; }; namespace private_api { template class Repeater { private_api::Property>> model; struct RepeaterInner : ModelChangeListener { enum class State { Clean, Dirty }; struct ComponentWithState { State state = State::Dirty; std::optional> ptr; }; std::vector data; private_api::Property 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 inner; template void set_model_binding(F &&binding) const { model.set_binding(std::forward(binding)); } template void ensure_updated(const Parent *parent) const { if (model.is_dirty()) { inner = std::make_shared(); 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 void ensure_updated_listview(const Parent *parent, const private_api::Property *viewport_width, const private_api::Property *viewport_height, [[maybe_unused]] const private_api::Property *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::max()) { return index; } } return std::numeric_limits::max(); } vtable::VRef item_at(int i) const { const auto &x = inner->data.at(i); return { &C::static_vtable, const_cast(&(**x.ptr)) }; } vtable::VWeak component_at(int i) const { const auto &x = inner->data.at(i); return vtable::VWeak { 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 *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 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 /// /// int main(int argc, char **argv) /// { /// auto ui = NetworkStatusUI::create(); /// ui->set_status_label("Pending"); /// /// slint::ComponentWeakHandle 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 void invoke_from_event_loop(Functor f) { cbindgen_private::slint_post_event( [](void *data) { (*reinterpret_cast(data))(); }, new Functor(std::move(f)), [](void *data) { delete reinterpret_cast(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 /// /// 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>>> auto blocking_invoke_from_event_loop(Functor f) -> std::invoke_result_t { std::optional> 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>>> auto blocking_invoke_from_event_loop(Functor f) -> void { std::mutex mtx; std::condition_variable cv; bool ok = false; invoke_from_event_loop([&] { f(); std::unique_lock lock(mtx); ok = true; cv.notify_one(); }); std::unique_lock lock(mtx); cv.wait(lock, [&] { return ok; }); } } // namespace slint