/* LICENSE BEGIN This file is part of the SixtyFPS Project -- https://sixtyfps.io Copyright (c) 2020 Olivier Goffart Copyright (c) 2020 Simon Hausmann SPDX-License-Identifier: GPL-3.0-only This file is also available under commercial licensing terms. Please contact info@sixtyfps.io for more information. LICENSE END */ #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 namespace sixtyfps::cbindgen_private { // Workaround https://github.com/eqrion/cbindgen/issues/43 struct ComponentVTable; struct ItemVTable; } #include "sixtyfps_internal.h" #include "sixtyfps_backend_internal.h" #include "sixtyfps_qt_internal.h" namespace sixtyfps { // Bring opaque structure in scope namespace private_api { using cbindgen_private::ComponentVTable; using cbindgen_private::ItemVTable; using ComponentRef = vtable::VRef; using ItemRef = vtable::VRef; using ItemVisitorRefMut = vtable::VRefMut; using cbindgen_private::ItemWeak; } using cbindgen_private::ComponentRc; using cbindgen_private::EasingCurve; using cbindgen_private::EventResult; using cbindgen_private::FillRule; using cbindgen_private::ImageFit; using cbindgen_private::KeyboardModifiers; using cbindgen_private::KeyEvent; using cbindgen_private::PropertyAnimation; using cbindgen_private::Slice; using cbindgen_private::TextHorizontalAlignment; using cbindgen_private::TextOverflow; using cbindgen_private::TextVerticalAlignment; using cbindgen_private::TextWrap; using cbindgen_private::TraversalOrder; namespace private_api { using ItemTreeNode = cbindgen_private::ItemTreeNode; class ComponentWindow { public: ComponentWindow() { cbindgen_private::sixtyfps_component_window_init(&inner); } ~ComponentWindow() { cbindgen_private::sixtyfps_component_window_drop(&inner); } ComponentWindow(const ComponentWindow &other) { cbindgen_private::sixtyfps_component_window_clone(&other.inner, &inner); } ComponentWindow(ComponentWindow &&) = delete; ComponentWindow &operator=(const ComponentWindow &) = delete; void show() const { sixtyfps_component_window_show(&inner); } void hide() const { sixtyfps_component_window_hide(&inner); } float scale_factor() const { return sixtyfps_component_window_get_scale_factor(&inner); } void set_scale_factor(float value) const { sixtyfps_component_window_set_scale_factor(&inner, value); } void free_graphics_resources(const sixtyfps::Slice &items) const { cbindgen_private::sixtyfps_component_window_free_graphics_resources(&inner, &items); } void set_focus_item(const ComponentRc &component_rc, uintptr_t item_index) { cbindgen_private::ItemRc item_rc { component_rc, item_index }; cbindgen_private::sixtyfps_component_window_set_focus_item(&inner, &item_rc); } template void init_items(Component *c, ItemTree items) const { cbindgen_private::sixtyfps_component_init_items( 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(); sixtyfps_component_window_set_component(&inner, &self_rc); } template void show_popup(const Parent *parent_component, cbindgen_private::Point p) const { auto popup = Component::create(parent_component).into_dyn(); cbindgen_private::sixtyfps_component_window_show_popup(&inner, &popup, p); } private: cbindgen_private::ComponentWindowOpaque inner; }; } using cbindgen_private::BorderRectangle; using cbindgen_private::BoxShadow; using cbindgen_private::Clip; using cbindgen_private::ClippedImage; using cbindgen_private::Flickable; using cbindgen_private::FocusScope; using cbindgen_private::ImageItem; using cbindgen_private::Opacity; using cbindgen_private::Path; using cbindgen_private::Rectangle; using cbindgen_private::Rotate; using cbindgen_private::Text; using cbindgen_private::TextInput; using cbindgen_private::TouchArea; using cbindgen_private::Window; using cbindgen_private::NativeButton; using cbindgen_private::NativeCheckBox; using cbindgen_private::NativeComboBox; using cbindgen_private::NativeGroupBox; using cbindgen_private::NativeLineEdit; using cbindgen_private::NativeScrollView; using cbindgen_private::NativeSlider; using cbindgen_private::NativeSpinBox; using cbindgen_private::NativeStandardListViewItem; using cbindgen_private::NativeStyleMetrics; namespace private_api { constexpr inline ItemTreeNode make_item_node(std::uintptr_t offset, const cbindgen_private::ItemVTable *vtable, uint32_t child_count, uint32_t child_index, uint32_t parent_index) { return ItemTreeNode { ItemTreeNode::Item_Body { ItemTreeNode::Tag::Item, { vtable, offset }, child_count, child_index, parent_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, Slice item_tree, int index) { const auto &item = item_tree.ptr[index].item.item; return ItemRef { item.vtable, reinterpret_cast(component.instance) + item.offset }; } inline ItemWeak parent_item(cbindgen_private::ComponentWeak component, Slice item_tree, int index) { const auto &node = item_tree.ptr[index]; if (node.tag == ItemTreeNode::Tag::Item) { return { component, node.item.parent_index }; } else { return { component, node.dynamic_tree.parent_index }; } } } using cbindgen_private::FocusEvent; using cbindgen_private::InputEventResult; using cbindgen_private::KeyEvent; using cbindgen_private::KeyEventResult; using cbindgen_private::MouseEvent; using cbindgen_private::sixtyfps_visit_item_tree; namespace private_api { 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 SIXTYFPS_GET_ITEM_VTABLE(VTableName) \ sixtyfps::private_api::sixtyfps_get_##VTableName() # else # define SIXTYFPS_GET_ITEM_VTABLE(VTableName) (&sixtyfps::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 .60 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 { return inner.operator->(); } /// Dereference operator that implements pointer semantics. const T &operator*() const { 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 { if (auto l = inner.lock()) { return { ComponentHandle(*l) }; } else { return {}; } } }; /// 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 timer which will repeat the callback every `duration` milliseconds until /// the destructor of the timer is called. template Timer(std::chrono::milliseconds duration, F callback) : id(cbindgen_private::sixtyfps_timer_start( duration.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::sixtyfps_timer_stop(id); } /// Call the callback after the given duration. template static void single_shot(std::chrono::milliseconds duration, F callback) { cbindgen_private::sixtyfps_timer_singleshot( duration.count(), [](void *data) { (*reinterpret_cast(data))(); }, new F(std::move(callback)), [](void *data) { delete reinterpret_cast(data); }); } private: int64_t id; }; // layouts: using cbindgen_private::BoxLayoutCellData; using cbindgen_private::BoxLayoutData; using cbindgen_private::GridLayoutCellData; using cbindgen_private::GridLayoutData; using cbindgen_private::LayoutAlignment; using cbindgen_private::LayoutInfo; using cbindgen_private::Orientation; using cbindgen_private::Padding; using cbindgen_private::PathLayoutData; using cbindgen_private::Rect; using cbindgen_private::sixtyfps_box_layout_info; using cbindgen_private::sixtyfps_box_layout_info_ortho; using cbindgen_private::sixtyfps_grid_layout_info; using cbindgen_private::sixtyfps_solve_box_layout; using cbindgen_private::sixtyfps_solve_grid_layout; using cbindgen_private::sixtyfps_solve_path_layout; #if !defined(DOXYGEN) inline LayoutInfo LayoutInfo::merge(const LayoutInfo &other) const { // Note: This "logic" is duplicated from LayoutInfo::merge in layout.rs. return LayoutInfo { std::max(min, other.min), std::min(max, other.max), std::max(min_percent, other.min_percent), std::min(max_percent, other.max_percent), std::max(preferred, other.preferred), std::min(stretch, other.stretch) }; } #endif /// FIXME! this should be done by cbindgen namespace cbindgen_private { inline bool operator==(const LayoutInfo &a, const LayoutInfo &b) { return a.min == b.min && a.max == b.max && a.min_percent == b.min_percent && a.max_percent == b.max_percent && a.preferred == b.preferred && a.stretch == b.stretch; } inline bool operator!=(const LayoutInfo &a, const LayoutInfo &b) { return !(a == b); } } namespace private_api { // 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; }; using ModelPeer = std::weak_ptr; } // namespace private_api /// A Model is providing Data for the Repeater or ListView elements of the `.60` language 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 ModelData row_data(int i) const = 0; /// Sets the data for a particular row. This function should be called with `row < row_count()`. /// If the model cannot support data changes, then it is ok to do nothing (default /// implementation). If the model can update the data, the implementation should also call /// row_changed. virtual void set_row_data(int, const ModelData &) {}; /// \private /// Internal function called by the view to register itself void attach_peer(private_api::ModelPeer p) { peers.push_back(std::move(p)); } protected: /// Notify the views that a specific row was changed void row_changed(int row) { for_each_peers([=](auto peer) { peer->row_changed(row); }); } /// Notify the views that rows were added void row_added(int index, int count) { for_each_peers([=](auto peer) { peer->row_added(index, count); }); } /// Notify the views that rows were removed void row_removed(int index, int count) { for_each_peers([=](auto peer) { peer->row_removed(index, count); }); } private: template void for_each_peers(const F &f) { 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; }; /// A Model backed by a std::array of constant size 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; } ModelData row_data(int i) const override { return data[i]; } void set_row_data(int i, const ModelData &value) override { 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; } int row_data(int value) const override { return value; } }; /// 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 data.size(); } ModelData row_data(int i) const override { return data[i]; } void set_row_data(int i, const ModelData &value) override { 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(int index) { data.erase(data.begin() + index); this->row_removed(index, 1); } }; namespace private_api { template class Repeater { private_api::Property>> model; struct RepeaterInner : AbstractRepeaterView { 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; } } }; 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); } intptr_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) != -1) { return index; } } return -1; } vtable::VRef item_at(int i) const { const auto &x = inner->data.at(i); return { &C::static_vtable, const_cast(&(**x.ptr)) }; } 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()) { 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) Flickable::Flickable() { sixtyfps_flickable_data_init(&data); } Flickable::~Flickable() { sixtyfps_flickable_data_free(&data); } NativeStyleMetrics::NativeStyleMetrics() { sixtyfps_init_native_style_metrics(this); } #endif // !defined(DOXYGEN) using cbindgen_private::StandardListViewItem; namespace cbindgen_private { inline bool operator==(const StandardListViewItem &a, const StandardListViewItem &b) { static_assert(sizeof(StandardListViewItem) == sizeof(std::tuple), "must update to cover all fields"); return a.text == b.text; } inline bool operator!=(const StandardListViewItem &a, const StandardListViewItem &b) { return !(a == b); } } namespace private_api { template struct VersionCheckHelper { }; } inline void run_event_loop() { cbindgen_private::sixtyfps_run_event_loop(); } inline void quit_event_loop() { cbindgen_private::sixtyfps_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 SixtyFPS APIs from other threads, /// by collecting the code in a functor and queuing it up for invocation within the event loop. template void invoke_from_event_loop(Functor f) { cbindgen_private::sixtyfps_post_event( [](void *data) { (*reinterpret_cast(data))(); }, new Functor(std::move(f)), [](void *data) { delete reinterpret_cast(data); }); } namespace private_api { /// Registers a font by the specified path. The path must refer to an existing /// TrueType font 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::sixtyfps_register_font_from_path(&path, &maybe_err); if (!maybe_err.empty()) { return maybe_err; } else { return {}; } } } } // namespace sixtyfps