Program Listing for File sixtyfps.h

Return to documentation for file (/home/runner/work/sixtyfps/sixtyfps/api/sixtyfps-cpp/include/sixtyfps.h)

/* LICENSE BEGIN
    This file is part of the SixtyFPS Project -- https://sixtyfps.io
    Copyright (c) 2021 Olivier Goffart <olivier.goffart@sixtyfps.io>
    Copyright (c) 2021 Simon Hausmann <simon.hausmann@sixtyfps.io>

    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 <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>

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<private_api::ComponentVTable>;
using ItemRef = vtable::VRef<private_api::ItemVTable>;
using ItemVisitorRefMut = vtable::VRefMut<cbindgen_private::ItemVisitorVTable>;
using cbindgen_private::ComponentRc;
using cbindgen_private::ItemWeak;
using cbindgen_private::TraversalOrder;
}

// FIXME: this should not be public API
using cbindgen_private::Slice;

namespace private_api {
using ItemTreeNode = cbindgen_private::ItemTreeNode<uint8_t>;
using cbindgen_private::KeyboardModifiers;
using cbindgen_private::KeyEvent;
using cbindgen_private::PointerEvent;

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 sixtyfps::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::sixtyfps_windowrc_init(&inner); }
    ~WindowRc() { cbindgen_private::sixtyfps_windowrc_drop(&inner); }
    WindowRc(const WindowRc &other)
    {
        assert_main_thread();
        cbindgen_private::sixtyfps_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::sixtyfps_windowrc_drop(&inner);
            cbindgen_private::sixtyfps_windowrc_clone(&other.inner, &inner);
        }
        return *this;
    }

    void show() const { sixtyfps_windowrc_show(&inner); }
    void hide() const { sixtyfps_windowrc_hide(&inner); }

    float scale_factor() const { return sixtyfps_windowrc_get_scale_factor(&inner); }
    void set_scale_factor(float value) const { sixtyfps_windowrc_set_scale_factor(&inner, value); }

    void free_graphics_resources(const sixtyfps::Slice<ItemRef> &items) const
    {
        cbindgen_private::sixtyfps_windowrc_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_windowrc_set_focus_item(&inner, &item_rc);
    }

    template<typename Component, typename ItemTree>
    void init_items(Component *c, ItemTree items) const
    {
        cbindgen_private::sixtyfps_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();
        sixtyfps_windowrc_set_component(&inner, &self_rc);
    }

    template<typename Component, typename Parent>
    void show_popup(const Parent *parent_component, cbindgen_private::Point p) const
    {
        auto popup = Component::create(parent_component).into_dyn();
        cbindgen_private::sixtyfps_windowrc_show_popup(&inner, &popup, p);
    }

private:
    cbindgen_private::WindowRcOpaque inner;
};

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<ItemTreeNode> item_tree, int index)
{
    const auto &item = item_tree.ptr[index].item.item;
    return ItemRef { item.vtable, reinterpret_cast<char *>(component.instance) + item.offset };
}

inline ItemWeak parent_item(cbindgen_private::ComponentWeak component,
                            Slice<ItemTreeNode> 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 };
    }
}

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 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<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;

template<typename T>
class ComponentHandle
{
    vtable::VRc<private_api::ComponentVTable, T> inner;
    friend class ComponentWeakHandle<T>;

public:
    ComponentHandle(const vtable::VRc<private_api::ComponentVTable, T> &inner) : inner(inner) { }

    const T *operator->() const
    {
        private_api::assert_main_thread();
        return inner.operator->();
    }
    const T &operator*() const
    {
        private_api::assert_main_thread();
        return inner.operator*();
    }
    T *operator->()
    {
        private_api::assert_main_thread();
        return inner.operator->();
    }
    T &operator*()
    {
        private_api::assert_main_thread();
        return inner.operator*();
    }

    vtable::VRc<private_api::ComponentVTable> into_dyn() const { return inner.into_dyn(); }
};

template<typename T>
class ComponentWeakHandle
{
    vtable::VWeak<private_api::ComponentVTable, T> inner;

public:
    ComponentWeakHandle() = default;
    ComponentWeakHandle(const ComponentHandle<T> &other) : inner(other.inner) { }
    std::optional<ComponentHandle<T>> lock() const
    {
        private_api::assert_main_thread();
        if (auto l = inner.lock()) {
            return { ComponentHandle(*l) };
        } else {
            return {};
        }
    }
};

class Window
{
public:
    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;
    ~Window() = default;

    void show() { inner.show(); }
    void hide() { inner.hide(); }

    private_api::WindowRc &window_handle() { return inner; }
    const private_api::WindowRc &window_handle() const { return inner; }

private:
    private_api::WindowRc inner;
};

struct Timer
{
    template<typename F>
    Timer(std::chrono::milliseconds duration, F callback)
        : id(cbindgen_private::sixtyfps_timer_start(
                duration.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::sixtyfps_timer_stop(id); }

    template<typename F>
    static void single_shot(std::chrono::milliseconds duration, F callback)
    {
        cbindgen_private::sixtyfps_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;
};

// 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::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) };
}
#endif

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 {
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;
};
using ModelPeer = std::weak_ptr<AbstractRepeaterView>;

} // namespace private_api

template<typename ModelData>
class Model
{
public:
    virtual ~Model() = default;
    Model() = default;
    Model(const Model &) = delete;
    Model &operator=(const Model &) = delete;

    virtual int row_count() const = 0;
    virtual ModelData row_data(int i) const = 0;
    virtual void set_row_data(int, const ModelData &) {
        std::cerr << "Model::set_row_data was called on a read-only model" << std::endl;
    };

    void attach_peer(private_api::ModelPeer p) { peers.push_back(std::move(p)); }

protected:
    void row_changed(int row)
    {
        for_each_peers([=](auto peer) { peer->row_changed(row); });
    }
    void row_added(int index, int count)
    {
        for_each_peers([=](auto peer) { peer->row_added(index, count); });
    }
    void row_removed(int index, int count)
    {
        for_each_peers([=](auto peer) { peer->row_removed(index, count); });
    }

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;
};

namespace private_api {
template<int Count, typename ModelData>
class ArrayModel : public Model<ModelData>
{
    std::array<ModelData, Count> data;

public:
    template<typename... A>
    ArrayModel(A &&...a) : data { std::forward<A>(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);
    }
};

struct IntModel : Model<int>
{
    IntModel(int d) : data(d) { }
    int data;
    int row_count() const override { return data; }
    int row_data(int value) const override { return value; }
};
} // namespace private_api

template<typename ModelData>
class VectorModel : public Model<ModelData>
{
    std::vector<ModelData> data;

public:
    VectorModel() = default;
    VectorModel(std::vector<ModelData> array) : data(std::move(array)) { }
    int row_count() const override { return int(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);
    }

    void push_back(const ModelData &value)
    {
        data.push_back(value);
        this->row_added(int(data.size()) - 1, 1);
    }

    void erase(int index)
    {
        data.erase(data.begin() + index);
        this->row_removed(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;
            }
        }
    };

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);
    }

    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<private_api::ComponentVTable> item_at(int i) const
    {
        const auto &x = inner->data.at(i);
        return { &C::static_vtable, const_cast<C *>(&(**x.ptr)) };
    }

    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()) {
            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()
{
    sixtyfps_flickable_data_init(&data);
}
cbindgen_private::Flickable::~Flickable()
{
    sixtyfps_flickable_data_free(&data);
}

cbindgen_private::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<SharedString>),
                  "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<int Major, int Minor, int Patch>
struct VersionCheckHelper
{
};
}

inline void run_event_loop()
{
    private_api::assert_main_thread();
    cbindgen_private::sixtyfps_run_event_loop();
}

inline void quit_event_loop()
{
    cbindgen_private::sixtyfps_quit_event_loop();
}

template<typename Functor>
void invoke_from_event_loop(Functor f)
{
    cbindgen_private::sixtyfps_post_event(
            [](void *data) { (*reinterpret_cast<Functor *>(data))(); }, new Functor(std::move(f)),
            [](void *data) { delete reinterpret_cast<Functor *>(data); });
}

template<typename 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);
}

namespace private_api {

inline std::optional<SharedString> 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 {};
    }
}

inline std::optional<SharedString> register_font_from_data(const uint8_t *data, std::size_t len)
{
    SharedString maybe_err;
    cbindgen_private::sixtyfps_register_font_from_data({ const_cast<uint8_t *>(data), len },
                                                       &maybe_err);
    if (!maybe_err.empty()) {
        return maybe_err;
    } else {
        return {};
    }
}

}

} // namespace sixtyfps