mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 18:58:36 +00:00
WIP: live-reload for C++
Missing feature: - conversion between Value and enums - conversion from value to Model - Compatibility with the testing framework (get the `VRc<ItemTreeTable>` from an instance)
This commit is contained in:
parent
73e970b8ca
commit
43b436a89f
12 changed files with 1070 additions and 40 deletions
|
@ -92,6 +92,7 @@ define_renderer_winit_compat_option(skia-vulkan)
|
|||
define_renderer_winit_compat_option(software)
|
||||
|
||||
define_cargo_dependent_feature(interpreter "Enable support for the Slint interpreter to load .slint files at run-time" ON "NOT SLINT_FEATURE_FREESTANDING")
|
||||
define_cargo_dependent_feature(live-reload "Enable support for the Slint live-reload to re-load changed .slint files at run-time" OFF "SLINT_FEATURE_INTERPRETER")
|
||||
|
||||
define_cargo_dependent_feature(backend-winit "Enable support for the winit crate to interaction with all windowing systems." ON "NOT SLINT_FEATURE_FREESTANDING")
|
||||
define_cargo_dependent_feature(backend-winit-x11 "Enable support for the winit create to interact only with the X11 windowing system on Unix. Enable this option and turn off SLINT_FEATURE_BACKEND_WINIT for a smaller build with just X11 support on Unix." OFF "NOT SLINT_FEATURE_FREESTANDING")
|
||||
|
|
|
@ -26,6 +26,7 @@ name = "slint_cpp"
|
|||
# the C++ crate's CMakeLists.txt as well as cbindgen.rs
|
||||
[features]
|
||||
interpreter = ["slint-interpreter", "std"]
|
||||
live-reload = ["interpreter", "slint-interpreter/internal-live-reload"]
|
||||
# Enable some function used by the integration tests
|
||||
testing = ["dep:i-slint-backend-testing"]
|
||||
|
||||
|
|
|
@ -896,6 +896,7 @@ fn gen_interpreter(
|
|||
"Diagnostic",
|
||||
"PropertyDescriptor",
|
||||
"Box",
|
||||
"LiveReloadingComponentInner",
|
||||
])
|
||||
.map(String::from)
|
||||
.collect();
|
||||
|
@ -942,6 +943,7 @@ fn gen_interpreter(
|
|||
using slint::interpreter::ValueType;
|
||||
using slint::interpreter::PropertyDescriptor;
|
||||
using slint::interpreter::Diagnostic;
|
||||
struct LiveReloadingComponentInner;
|
||||
template <typename T> using Box = T*;
|
||||
}",
|
||||
)
|
||||
|
@ -985,6 +987,7 @@ macro_rules! declare_features {
|
|||
|
||||
declare_features! {
|
||||
interpreter
|
||||
live_reload
|
||||
testing
|
||||
backend_qt
|
||||
backend_winit
|
||||
|
|
|
@ -26,6 +26,10 @@ struct ErasedItemTreeBox : vtable::Dyn
|
|||
ErasedItemTreeBox(ErasedItemTreeBox &) = delete;
|
||||
};
|
||||
}
|
||||
namespace slint::private_api::live_reload {
|
||||
class LiveReloadingComponent;
|
||||
class LiveReloadModelWrapperBase;
|
||||
}
|
||||
|
||||
/// The types in this namespace allow you to load a .slint file at runtime and show its UI.
|
||||
///
|
||||
|
@ -396,6 +400,8 @@ private:
|
|||
slint::cbindgen_private::Value *inner;
|
||||
friend struct Struct;
|
||||
friend class ComponentInstance;
|
||||
friend class slint::private_api::live_reload::LiveReloadingComponent;
|
||||
friend class slint::private_api::live_reload::LiveReloadModelWrapperBase;
|
||||
// Internal constructor that takes ownership of the value
|
||||
explicit Value(slint::cbindgen_private::Value *&&inner) : inner(inner) { }
|
||||
};
|
||||
|
|
347
api/cpp/include/slint_live_reload.h
Normal file
347
api/cpp/include/slint_live_reload.h
Normal file
|
@ -0,0 +1,347 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "slint.h"
|
||||
|
||||
#ifndef SLINT_FEATURE_LIVE_RELOAD
|
||||
# error SLINT_FEATURE_LIVE_RELOAD must be activated
|
||||
#else
|
||||
|
||||
# include "slint-interpreter.h"
|
||||
|
||||
/// Internal API to support the live-reload generated code
|
||||
namespace slint::private_api::live_reload {
|
||||
|
||||
template<typename T>
|
||||
requires(std::convertible_to<T, slint::interpreter::Value>)
|
||||
slint::interpreter::Value into_slint_value(const T &val)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires requires(T val) { val.into_slint_value(); }
|
||||
slint::interpreter::Value into_slint_value(const T &val)
|
||||
{
|
||||
return val.into_slint_value();
|
||||
}
|
||||
|
||||
inline slint::interpreter::Value into_slint_value(const slint::interpreter::Value &val)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires std::is_same_v<T, void>
|
||||
inline void from_slint_value(const slint::interpreter::Value &, const T *)
|
||||
{
|
||||
}
|
||||
inline bool from_slint_value(const slint::interpreter::Value &val, const bool *)
|
||||
{
|
||||
return val.to_bool().value();
|
||||
}
|
||||
inline slint::SharedString from_slint_value(const slint::interpreter::Value &val,
|
||||
const slint::SharedString *)
|
||||
{
|
||||
return val.to_string().value();
|
||||
}
|
||||
inline int from_slint_value(const slint::interpreter::Value &val, const int *)
|
||||
{
|
||||
return val.to_number().value();
|
||||
}
|
||||
inline float from_slint_value(const slint::interpreter::Value &val, const float *)
|
||||
{
|
||||
return val.to_number().value();
|
||||
}
|
||||
inline slint::Color from_slint_value(const slint::interpreter::Value &val, const slint::Color *)
|
||||
{
|
||||
return val.to_brush().value().color();
|
||||
}
|
||||
inline interpreter::Value into_slint_value(const slint::Color &val)
|
||||
{
|
||||
return slint::Brush(val);
|
||||
}
|
||||
inline slint::Brush from_slint_value(const slint::interpreter::Value &val, const slint::Brush *)
|
||||
{
|
||||
return val.to_brush().value();
|
||||
}
|
||||
inline slint::Image from_slint_value(const slint::interpreter::Value &val, const slint::Image *)
|
||||
{
|
||||
return val.to_image().value();
|
||||
}
|
||||
/// duration
|
||||
inline long int from_slint_value(const slint::interpreter::Value &val, const long int *)
|
||||
{
|
||||
return val.to_number().value();
|
||||
}
|
||||
inline interpreter::Value into_slint_value(const long int &val)
|
||||
{
|
||||
return double(val);
|
||||
}
|
||||
|
||||
template<typename ModelData>
|
||||
inline std::shared_ptr<slint::Model<ModelData>>
|
||||
from_slint_value(const slint::interpreter::Value &,
|
||||
const std::shared_ptr<slint::Model<ModelData>> *)
|
||||
{
|
||||
std::cout << "NOT IMPLEMENTED " << __PRETTY_FUNCTION__ << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename ModelData>
|
||||
slint::interpreter::Value into_slint_value(const std::shared_ptr<slint::Model<ModelData>> &val);
|
||||
|
||||
inline slint::interpreter::Value into_slint_value(const slint::StandardListViewItem &val)
|
||||
{
|
||||
slint::interpreter::Struct s;
|
||||
s.set_field("text", val.text);
|
||||
return s;
|
||||
}
|
||||
|
||||
inline slint::StandardListViewItem from_slint_value(const slint::interpreter::Value &val,
|
||||
const slint::StandardListViewItem *)
|
||||
{
|
||||
auto s = val.to_struct().value();
|
||||
return slint::StandardListViewItem { .text = s.get_field("text").value().to_string().value() };
|
||||
}
|
||||
|
||||
inline slint::interpreter::Value into_slint_value(const slint::LogicalPosition &val)
|
||||
{
|
||||
slint::interpreter::Struct s;
|
||||
s.set_field("x", val.x);
|
||||
s.set_field("y", val.y);
|
||||
return s;
|
||||
}
|
||||
|
||||
inline slint::LogicalPosition from_slint_value(const slint::interpreter::Value &val,
|
||||
const slint::LogicalPosition *)
|
||||
{
|
||||
auto s = val.to_struct().value();
|
||||
return slint::LogicalPosition({ float(s.get_field("x").value().to_number().value()),
|
||||
float(s.get_field("y").value().to_number().value()) });
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T from_slint_value(const slint::interpreter::Value &v)
|
||||
{
|
||||
return from_slint_value(v, static_cast<const T *>(nullptr));
|
||||
}
|
||||
|
||||
class LiveReloadingComponent
|
||||
{
|
||||
const cbindgen_private::LiveReloadingComponentInner *inner;
|
||||
|
||||
public:
|
||||
/// Libraries is an array of string that have in the form `lib=...`
|
||||
LiveReloadingComponent(std::string_view file_name, std::string_view component_name,
|
||||
const slint::SharedVector<slint::SharedString> &include_paths,
|
||||
const slint::SharedVector<slint::SharedString> &libraries,
|
||||
std::string_view style)
|
||||
{
|
||||
assert_main_thread();
|
||||
inner = cbindgen_private::slint_live_reload_new(
|
||||
string_to_slice(file_name), string_to_slice(component_name), &include_paths,
|
||||
&libraries, string_to_slice(style));
|
||||
}
|
||||
|
||||
LiveReloadingComponent(const LiveReloadingComponent &other) : inner(other.inner)
|
||||
{
|
||||
assert_main_thread();
|
||||
cbindgen_private::slint_live_reload_clone(other.inner);
|
||||
}
|
||||
LiveReloadingComponent &operator=(const LiveReloadingComponent &other)
|
||||
{
|
||||
assert_main_thread();
|
||||
if (this == &other)
|
||||
return *this;
|
||||
cbindgen_private::slint_live_reload_drop(inner);
|
||||
inner = other.inner;
|
||||
cbindgen_private::slint_live_reload_clone(inner);
|
||||
return *this;
|
||||
}
|
||||
~LiveReloadingComponent()
|
||||
{
|
||||
assert_main_thread();
|
||||
cbindgen_private::slint_live_reload_drop(inner);
|
||||
}
|
||||
|
||||
void set_property(std::string_view name, const interpreter::Value &value) const
|
||||
{
|
||||
assert_main_thread();
|
||||
return cbindgen_private::slint_live_reload_set_property(inner, string_to_slice(name),
|
||||
value.inner);
|
||||
}
|
||||
|
||||
interpreter::Value get_property(std::string_view name) const
|
||||
{
|
||||
assert_main_thread();
|
||||
auto val = slint::interpreter::Value(
|
||||
cbindgen_private::slint_live_reload_get_property(inner, string_to_slice(name)));
|
||||
return val;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
interpreter::Value invoke(std::string_view name, Args &...args) const
|
||||
{
|
||||
assert_main_thread();
|
||||
std::array<interpreter::Value, sizeof...(Args)> args_values { into_slint_value(args)... };
|
||||
cbindgen_private::Slice<cbindgen_private::Value *> args_slice {
|
||||
reinterpret_cast<cbindgen_private::Value **>(args_values.data()), args_values.size()
|
||||
};
|
||||
interpreter::Value val(cbindgen_private::slint_live_reload_invoke(
|
||||
inner, string_to_slice(name), args_slice));
|
||||
return val;
|
||||
}
|
||||
|
||||
template<std::invocable<std::span<const interpreter::Value>> F>
|
||||
requires(std::is_convertible_v<std::invoke_result_t<F, std::span<const interpreter::Value>>,
|
||||
interpreter::Value>)
|
||||
void set_callback(std::string_view name, F &&callback) const
|
||||
{
|
||||
assert_main_thread();
|
||||
auto actual_cb =
|
||||
[](void *data,
|
||||
cbindgen_private::Slice<cbindgen_private::Box<cbindgen_private::Value>> arg) {
|
||||
std::span<const interpreter::Value> args_view {
|
||||
reinterpret_cast<const interpreter::Value *>(arg.ptr), arg.len
|
||||
};
|
||||
interpreter::Value r = (*reinterpret_cast<F *>(data))(args_view);
|
||||
auto inner = r.inner;
|
||||
r.inner = cbindgen_private::slint_interpreter_value_new();
|
||||
return inner;
|
||||
};
|
||||
return cbindgen_private::slint_live_reload_set_callback(
|
||||
inner, slint::private_api::string_to_slice(name), actual_cb,
|
||||
new F(std::move(callback)), [](void *data) { delete reinterpret_cast<F *>(data); });
|
||||
}
|
||||
|
||||
slint::Window &window() const
|
||||
{
|
||||
const cbindgen_private::WindowAdapterRcOpaque *win_ptr = nullptr;
|
||||
cbindgen_private::slint_live_reload_window(inner, &win_ptr);
|
||||
return const_cast<slint::Window &>(*reinterpret_cast<const slint::Window *>(win_ptr));
|
||||
}
|
||||
};
|
||||
|
||||
class LiveReloadModelWrapperBase : public private_api::ModelChangeListener
|
||||
{
|
||||
cbindgen_private::ModelNotifyOpaque notify;
|
||||
// This means that the rust code has ownership of "this" until the drop function is called
|
||||
std::shared_ptr<ModelChangeListener> self = nullptr;
|
||||
|
||||
void row_added(size_t index, size_t count) override
|
||||
{
|
||||
cbindgen_private::slint_interpreter_model_notify_row_added(¬ify, index, count);
|
||||
}
|
||||
void row_changed(size_t index) override
|
||||
{
|
||||
cbindgen_private::slint_interpreter_model_notify_row_changed(¬ify, index);
|
||||
}
|
||||
void row_removed(size_t index, size_t count) override
|
||||
{
|
||||
cbindgen_private::slint_interpreter_model_notify_row_removed(¬ify, index, count);
|
||||
}
|
||||
void reset() override { cbindgen_private::slint_interpreter_model_notify_reset(¬ify); }
|
||||
|
||||
static const ModelAdaptorVTable *vtable()
|
||||
{
|
||||
auto row_count = [](VRef<ModelAdaptorVTable> self) -> uintptr_t {
|
||||
return reinterpret_cast<LiveReloadModelWrapperBase *>(self.instance)->row_count();
|
||||
};
|
||||
auto row_data = [](VRef<ModelAdaptorVTable> self,
|
||||
uintptr_t row) -> slint::cbindgen_private::Value * {
|
||||
std::optional<interpreter::Value> v =
|
||||
reinterpret_cast<LiveReloadModelWrapperBase *>(self.instance)
|
||||
->row_data(int(row));
|
||||
if (v.has_value()) {
|
||||
slint::cbindgen_private::Value *rval = v->inner;
|
||||
v->inner = cbindgen_private::slint_interpreter_value_new();
|
||||
return rval;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
auto set_row_data = [](VRef<ModelAdaptorVTable> self, uintptr_t row,
|
||||
slint::cbindgen_private::Value *value) {
|
||||
interpreter::Value v(std::move(value));
|
||||
reinterpret_cast<LiveReloadModelWrapperBase *>(self.instance)->set_row_data(row, v);
|
||||
};
|
||||
auto get_notify =
|
||||
[](VRef<ModelAdaptorVTable> self) -> const cbindgen_private::ModelNotifyOpaque * {
|
||||
return &reinterpret_cast<LiveReloadModelWrapperBase *>(self.instance)->notify;
|
||||
};
|
||||
auto drop = [](vtable::VRefMut<ModelAdaptorVTable> self) {
|
||||
reinterpret_cast<LiveReloadModelWrapperBase *>(self.instance)->self = nullptr;
|
||||
};
|
||||
|
||||
static const ModelAdaptorVTable vt { row_count, row_data, set_row_data, get_notify, drop };
|
||||
return &vt;
|
||||
}
|
||||
|
||||
protected:
|
||||
LiveReloadModelWrapperBase() { cbindgen_private::slint_interpreter_model_notify_new(¬ify); }
|
||||
virtual ~LiveReloadModelWrapperBase()
|
||||
{
|
||||
cbindgen_private::slint_interpreter_model_notify_destructor(¬ify);
|
||||
}
|
||||
|
||||
virtual int row_count() const = 0;
|
||||
virtual std::optional<slint::interpreter::Value> row_data(int i) const = 0;
|
||||
virtual void set_row_data(int i, const slint::interpreter::Value &value) = 0;
|
||||
|
||||
static interpreter::Value wrap(std::shared_ptr<LiveReloadModelWrapperBase> wrapper)
|
||||
{
|
||||
wrapper->self = wrapper;
|
||||
return interpreter::Value(cbindgen_private::slint_interpreter_value_new_model(
|
||||
reinterpret_cast<uint8_t *>(wrapper.get()), vtable()));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename ModelData>
|
||||
class LiveReloadModelWrapper : public LiveReloadModelWrapperBase
|
||||
{
|
||||
std::shared_ptr<slint::Model<ModelData>> model = nullptr;
|
||||
|
||||
int row_count() const override { return model->row_count(); }
|
||||
|
||||
std::optional<slint::interpreter::Value> row_data(int i) const override
|
||||
{
|
||||
if (auto v = model->row_data(i))
|
||||
return into_slint_value(*v);
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
void set_row_data(int i, const slint::interpreter::Value &value) override
|
||||
{
|
||||
model->set_row_data(i, from_slint_value<ModelData>(value));
|
||||
}
|
||||
|
||||
public:
|
||||
LiveReloadModelWrapper(std::shared_ptr<slint::Model<ModelData>> model) : model(std::move(model))
|
||||
{
|
||||
}
|
||||
|
||||
static slint::interpreter::Value wrap(std::shared_ptr<slint::Model<ModelData>> model)
|
||||
{
|
||||
auto self = std::make_shared<LiveReloadModelWrapper<ModelData>>(model);
|
||||
auto peer = std::weak_ptr<LiveReloadModelWrapperBase>(self);
|
||||
model->attach_peer(peer);
|
||||
return LiveReloadModelWrapperBase::wrap(self);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename ModelData>
|
||||
slint::interpreter::Value into_slint_value(const std::shared_ptr<slint::Model<ModelData>> &val)
|
||||
{
|
||||
if (!val) {
|
||||
return {};
|
||||
}
|
||||
return LiveReloadModelWrapper<ModelData>::wrap(val);
|
||||
}
|
||||
|
||||
} // namespace slint::private_api::live_reload
|
||||
|
||||
#endif // SLINT_FEATURE_LIVE_RELOAD
|
Loading…
Add table
Add a link
Reference in a new issue