This commit is contained in:
Olivier Goffart 2025-07-07 17:56:14 +00:00 committed by GitHub
commit 1a500fe862
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 1071 additions and 40 deletions

View file

@ -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 load .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")

View file

@ -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"]

View file

@ -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*;
}",
)

View file

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

View file

@ -0,0 +1,349 @@
// 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"
#define SLINT_FEATURE_LIVE_RELOAD
#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(&notify, index, count);
}
void row_changed(size_t index) override
{
cbindgen_private::slint_interpreter_model_notify_row_changed(&notify, index);
}
void row_removed(size_t index, size_t count) override
{
cbindgen_private::slint_interpreter_model_notify_row_removed(&notify, index, count);
}
void reset() override { cbindgen_private::slint_interpreter_model_notify_reset(&notify); }
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(&notify); }
virtual ~LiveReloadModelWrapperBase()
{
cbindgen_private::slint_interpreter_model_notify_destructor(&notify);
}
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

View file

@ -101,6 +101,7 @@ public:
{
if (!--inner->strong_ref) {
Layout layout = inner->vtable->drop_in_place({ inner->vtable, &inner->data });
layout.size = std::max<size_t>(layout.size, sizeof(Layout)); // because of the union
layout.size += inner->data_offset;
layout.align = std::max<size_t>(layout.align, alignof(VRcInner<VTable, Dyn>));
inner->layout = layout;

View file

@ -21,7 +21,8 @@ use crate::CompilerConfiguration;
#[cfg(feature = "cpp")]
pub mod cpp;
#[cfg(feature = "cpp")]
pub mod cpp_live_reload;
#[cfg(feature = "rust")]
pub mod rust;
#[cfg(feature = "rust")]

View file

@ -47,7 +47,7 @@ fn is_cpp_keyword(word: &str) -> bool {
keywords.contains(word)
}
fn ident(ident: &str) -> SmolStr {
pub fn ident(ident: &str) -> SmolStr {
let mut new_ident = SmolStr::from(ident);
if ident.contains('-') {
new_ident = ident.replace_smolstr("-", "_");
@ -58,7 +58,7 @@ fn ident(ident: &str) -> SmolStr {
new_ident
}
fn concatenate_ident(ident: &str) -> SmolStr {
pub fn concatenate_ident(ident: &str) -> SmolStr {
if ident.contains('-') {
ident.replace_smolstr("-", "_")
} else {
@ -109,7 +109,7 @@ fn access_item_rc(pr: &llr::PropertyReference, ctx: &EvaluationContext) -> Strin
/// This module contains some data structure that helps represent a C++ code.
/// It is then rendered into an actual C++ text using the Display trait
mod cpp_ast {
pub mod cpp_ast {
use std::cell::Cell;
use std::fmt::{Display, Error, Formatter};
@ -681,33 +681,16 @@ pub fn generate(
config: Config,
compiler_config: &CompilerConfiguration,
) -> std::io::Result<impl std::fmt::Display> {
let mut file = File { namespace: config.namespace.clone(), ..Default::default() };
if std::env::var("SLINT_LIVE_RELOAD").is_ok() {
return super::cpp_live_reload::generate(doc, config, compiler_config);
}
file.includes.push("<array>".into());
file.includes.push("<limits>".into());
file.includes.push("<slint.h>".into());
let mut file = generate_types(&doc.used_types.borrow().structs_and_enums, &config);
for (path, er) in doc.embedded_file_resources.borrow().iter() {
embed_resource(er, path, &mut file.resources);
}
for ty in doc.used_types.borrow().structs_and_enums.iter() {
match ty {
Type::Struct(s) if s.name.is_some() && s.node.is_some() => {
generate_struct(
&mut file,
s.name.as_ref().unwrap(),
&s.fields,
s.node.as_ref().unwrap(),
);
}
Type::Enumeration(en) => {
generate_enum(&mut file, en);
}
_ => (),
}
}
let llr = llr::lower_to_item_tree::lower_to_item_tree(doc, compiler_config)?;
#[cfg(feature = "bundle-translations")]
@ -874,15 +857,6 @@ pub fn generate(
generate_type_aliases(&mut file, doc);
file.after_includes = format!(
"static_assert({x} == SLINT_VERSION_MAJOR && {y} == SLINT_VERSION_MINOR && {z} == SLINT_VERSION_PATCH, \
\"This file was generated with Slint compiler version {x}.{y}.{z}, but the Slint library used is \" \
SLINT_VERSION_STRING \". The version numbers must match exactly.\");",
x = env!("CARGO_PKG_VERSION_MAJOR"),
y = env!("CARGO_PKG_VERSION_MINOR"),
z = env!("CARGO_PKG_VERSION_PATCH")
);
if conditional_includes.iostream.get() {
file.includes.push("<iostream>".into());
}
@ -905,6 +879,42 @@ pub fn generate(
Ok(file)
}
pub fn generate_types(used_types: &[Type], config: &Config) -> File {
let mut file = File { namespace: config.namespace.clone(), ..Default::default() };
file.includes.push("<array>".into());
file.includes.push("<limits>".into());
file.includes.push("<slint.h>".into());
file.after_includes = format!(
"static_assert({x} == SLINT_VERSION_MAJOR && {y} == SLINT_VERSION_MINOR && {z} == SLINT_VERSION_PATCH, \
\"This file was generated with Slint compiler version {x}.{y}.{z}, but the Slint library used is \" \
SLINT_VERSION_STRING \". The version numbers must match exactly.\");",
x = env!("CARGO_PKG_VERSION_MAJOR"),
y = env!("CARGO_PKG_VERSION_MINOR"),
z = env!("CARGO_PKG_VERSION_PATCH")
);
for ty in used_types {
match ty {
Type::Struct(s) if s.name.is_some() && s.node.is_some() => {
generate_struct(
&mut file,
s.name.as_ref().unwrap(),
&s.fields,
s.node.as_ref().unwrap(),
);
}
Type::Enumeration(en) => {
generate_enum(&mut file, en);
}
_ => (),
}
}
file
}
fn embed_resource(
resource: &crate::embedded_resources::EmbeddedResources,
path: &SmolStr,
@ -4089,7 +4099,7 @@ fn return_compile_expression(
}
}
fn generate_type_aliases(file: &mut File, doc: &Document) {
pub fn generate_type_aliases(file: &mut File, doc: &Document) {
let type_aliases = doc
.exports
.iter()

View file

@ -0,0 +1,518 @@
// 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
use super::cpp::{concatenate_ident, cpp_ast::*, ident, Config};
use crate::langtype::Type;
use crate::llr;
use crate::object_tree::Document;
use crate::CompilerConfiguration;
use itertools::Itertools as _;
use smol_str::format_smolstr;
use std::io::BufWriter;
pub fn generate(
doc: &Document,
config: Config,
compiler_config: &CompilerConfiguration,
) -> std::io::Result<File> {
let mut file = super::cpp::generate_types(&doc.used_types.borrow().structs_and_enums, &config);
file.includes.push("<slint_live_reload.h>".into());
generate_value_conversions(&mut file, &doc.used_types.borrow().structs_and_enums);
let llr = crate::llr::lower_to_item_tree::lower_to_item_tree(doc, compiler_config)?;
let main_file = doc
.node
.as_ref()
.ok_or_else(|| std::io::Error::other("Cannot determine path of the main file"))?
.source_file
.path()
.to_string_lossy();
for p in &llr.public_components {
generate_public_component(&mut file, p, &llr, compiler_config, &main_file);
}
for glob in &llr.globals {
if glob.must_generate() {
generate_global(&mut file, glob);
file.definitions.extend(glob.aliases.iter().map(|name| {
Declaration::TypeAlias(TypeAlias {
old_name: ident(&glob.name),
new_name: ident(name),
})
}));
};
}
super::cpp::generate_type_aliases(&mut file, doc);
let cpp_files = file.split_off_cpp_files(config.header_include, config.cpp_files.len());
for (cpp_file_name, cpp_file) in config.cpp_files.iter().zip(cpp_files) {
use std::io::Write;
write!(&mut BufWriter::new(std::fs::File::create(&cpp_file_name)?), "{cpp_file}")?;
}
Ok(file)
}
fn generate_public_component(
file: &mut File,
component: &llr::PublicComponent,
unit: &llr::CompilationUnit,
compiler_config: &CompilerConfiguration,
main_file: &str,
) {
let component_id = ident(&component.name);
let mut component_struct = Struct { name: component_id.clone(), ..Default::default() };
component_struct.members.push((
Access::Private,
Declaration::Var(Var {
ty: "slint::private_api::live_reload::LiveReloadingComponent".into(),
name: "live_reload".into(),
..Default::default()
}),
));
let mut global_accessor_function_body = Vec::new();
for glob in unit.globals.iter().filter(|glob| glob.exported && glob.must_generate()) {
let accessor_statement = format!(
"{0}if constexpr(std::is_same_v<T, {1}>) {{ return T(live_reload); }}",
if global_accessor_function_body.is_empty() { "" } else { "else " },
concatenate_ident(&glob.name),
);
global_accessor_function_body.push(accessor_statement);
}
if !global_accessor_function_body.is_empty() {
global_accessor_function_body.push(
"else { static_assert(!sizeof(T*), \"The type is not global/or exported\"); }".into(),
);
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: "global".into(),
signature: "() const -> T".into(),
statements: Some(global_accessor_function_body),
template_parameters: Some("typename T".into()),
..Default::default()
}),
));
}
generate_public_api_for_properties(
"",
&mut component_struct.members,
&component.public_properties,
&component.private_properties,
);
component_struct.members.push((
Access::Public,
Declaration::Var(Var {
ty: "static const slint::private_api::ItemTreeVTable".into(),
name: "static_vtable".into(),
..Default::default()
}),
));
file.definitions.push(Declaration::Var(Var {
ty: "const slint::private_api::ItemTreeVTable".into(),
name: format_smolstr!("{component_id}::static_vtable"),
init: Some(format!(
"{{ nullptr, nullptr, nullptr, nullptr, \
nullptr, nullptr, nullptr, nullptr, nullptr, \
nullptr, nullptr, nullptr, nullptr, \
nullptr, nullptr, nullptr, \
slint::private_api::drop_in_place<{component_id}>, slint::private_api::dealloc }}"
)),
..Default::default()
}));
let create_code = vec![
format!("slint::SharedVector<slint::SharedString> include_paths{{ {} }};", compiler_config.include_paths.iter().map(|p| format!("\"{}\"", escape_string(&p.to_string_lossy()))).join(", ")),
format!("slint::SharedVector<slint::SharedString> library_paths{{ {} }};", compiler_config.library_paths.iter().map(|(l, p)| format!("\"{l}={}\"", p.to_string_lossy())).join(", ")),
format!("auto live_reload = slint::private_api::live_reload::LiveReloadingComponent({main_file:?}, {:?}, include_paths, library_paths, \"{}\");", component.name, compiler_config.style.as_ref().unwrap_or(&String::new())),
format!("auto self_rc = vtable::VRc<slint::private_api::ItemTreeVTable, {component_id}>::make(std::move(live_reload));"),
format!("return slint::ComponentHandle<{component_id}>(self_rc);"),
];
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: "create".into(),
signature: format!("() -> slint::ComponentHandle<{component_id}>"),
statements: Some(create_code),
is_static: true,
..Default::default()
}),
));
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
is_constructor_or_destructor: true,
name: ident(&component_struct.name),
signature: "(slint::private_api::live_reload::LiveReloadingComponent live_reload)"
.into(),
constructor_member_initializers: vec!["live_reload(std::move(live_reload))".into()],
statements: Some(vec![]),
..Default::default()
}),
));
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: "show".into(),
signature: "() -> void".into(),
statements: Some(vec!["window().show();".into()]),
..Default::default()
}),
));
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: "hide".into(),
signature: "() -> void".into(),
statements: Some(vec!["window().hide();".into()]),
..Default::default()
}),
));
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: "window".into(),
signature: "() const -> slint::Window&".into(),
statements: Some(vec!["return live_reload.window();".into()]),
..Default::default()
}),
));
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: "run".into(),
signature: "() -> void".into(),
statements: Some(vec![
"show();".into(),
"slint::run_event_loop();".into(),
"hide();".into(),
]),
..Default::default()
}),
));
file.definitions.extend(component_struct.extract_definitions().collect::<Vec<_>>());
file.declarations.push(Declaration::Struct(component_struct));
}
fn generate_global(file: &mut File, global: &llr::GlobalComponent) {
let mut global_struct = Struct { name: ident(&global.name), ..Default::default() };
global_struct.members.push((
Access::Private,
Declaration::Var(Var {
ty: "const slint::private_api::live_reload::LiveReloadingComponent&".into(),
name: "live_reload".into(),
..Default::default()
}),
));
global_struct.members.push((
Access::Public,
Declaration::Function(Function {
is_constructor_or_destructor: true,
name: ident(&global.name),
signature:
"(const slint::private_api::live_reload::LiveReloadingComponent &live_reload)"
.into(),
constructor_member_initializers: vec!["live_reload(live_reload)".into()],
statements: Some(vec![]),
..Default::default()
}),
));
generate_public_api_for_properties(
&format!("{}.", global.name),
&mut global_struct.members,
&global.public_properties,
&global.private_properties,
);
file.definitions.extend(global_struct.extract_definitions().collect::<Vec<_>>());
file.declarations.push(Declaration::Struct(global_struct));
}
fn generate_public_api_for_properties(
prefix: &str,
declarations: &mut Vec<(Access, Declaration)>,
public_properties: &llr::PublicProperties,
private_properties: &llr::PrivateProperties,
) {
for p in public_properties {
let prop_name = &p.name;
let prop_ident = concatenate_ident(prop_name);
if let Type::Callback(callback) = &p.ty {
let ret = callback.return_type.cpp_type().unwrap();
let param_types =
callback.args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
let callback_emitter = vec![format!(
"return {}(live_reload.invoke(\"{prefix}{prop_name}\" {}));",
convert_from_value_fn(&callback.return_type),
(0..callback.args.len()).map(|i| format!(", arg_{i}")).join(""),
)];
declarations.push((
Access::Public,
Declaration::Function(Function {
name: format_smolstr!("invoke_{prop_ident}"),
signature: format!(
"({}) const -> {ret}",
param_types
.iter()
.enumerate()
.map(|(i, ty)| format!("{ty} arg_{i}"))
.join(", "),
),
statements: Some(callback_emitter),
..Default::default()
}),
));
let args = callback
.args
.iter()
.enumerate()
.map(|(i, t)| format!("{}(args[{i}])", convert_from_value_fn(t)))
.join(", ");
let return_statement = if callback.return_type == Type::Void {
format!("callback_handler({args}); return slint::interpreter::Value();",)
} else {
format!(
"return {}(callback_handler({args}));",
convert_to_value_fn(&callback.return_type),
)
};
declarations.push((
Access::Public,
Declaration::Function(Function {
name: format_smolstr!("on_{}", concatenate_ident(&p.name)),
template_parameters: Some(format!(
"std::invocable<{}> Functor",
param_types.join(", "),
)),
signature: "(Functor && callback_handler) const".into(),
statements: Some(vec![
"using slint::private_api::live_reload::into_slint_value;".into(),
format!(
"live_reload.set_callback(\"{prefix}{prop_name}\", [callback_handler]([[maybe_unused]] auto args) {{ {return_statement} }});",
),
]),
..Default::default()
}),
));
} else if let Type::Function(function) = &p.ty {
let param_types =
function.args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
let ret = function.return_type.cpp_type().unwrap();
let call_code = vec![format!(
"return {}(live_reload.invoke(\"{prefix}{prop_name}\"{}));",
convert_from_value_fn(&function.return_type),
(0..function.args.len()).map(|i| format!(", arg_{i}")).join("")
)];
declarations.push((
Access::Public,
Declaration::Function(Function {
name: format_smolstr!("invoke_{}", concatenate_ident(&p.name)),
signature: format!(
"({}) const -> {ret}",
param_types
.iter()
.enumerate()
.map(|(i, ty)| format!("{ty} arg_{i}"))
.join(", "),
),
statements: Some(call_code),
..Default::default()
}),
));
} else {
let cpp_property_type = p.ty.cpp_type().expect("Invalid type in public properties");
let prop_getter: Vec<String> = vec![format!(
"return {}(live_reload.get_property(\"{prefix}{prop_name}\"));",
convert_from_value_fn(&p.ty)
)];
declarations.push((
Access::Public,
Declaration::Function(Function {
name: format_smolstr!("get_{}", &prop_ident),
signature: format!("() const -> {cpp_property_type}"),
statements: Some(prop_getter),
..Default::default()
}),
));
if !p.read_only {
let prop_setter: Vec<String> = vec![
"using slint::private_api::live_reload::into_slint_value;".into(),
format!(
"live_reload.set_property(\"{prefix}{prop_name}\", {}(value));",
convert_to_value_fn(&p.ty)
),
];
declarations.push((
Access::Public,
Declaration::Function(Function {
name: format_smolstr!("set_{}", &prop_ident),
signature: format!("(const {} &value) const -> void", cpp_property_type),
statements: Some(prop_setter),
..Default::default()
}),
));
} else {
declarations.push((
Access::Private,
Declaration::Function(Function {
name: format_smolstr!("set_{}", &prop_ident),
signature: format!(
"(const {cpp_property_type} &) const = delete /* property '{}' is declared as 'out' (read-only). Declare it as 'in' or 'in-out' to enable the setter */", p.name
),
..Default::default()
}),
));
}
}
}
for (name, ty) in private_properties {
let prop_ident = concatenate_ident(name);
if let Type::Function(function) = &ty {
let param_types = function.args.iter().map(|t| t.cpp_type().unwrap()).join(", ");
declarations.push((
Access::Private,
Declaration::Function(Function {
name: format_smolstr!("invoke_{prop_ident}"),
signature: format!(
"({param_types}) const = delete /* the function '{name}' is declared as private. Declare it as 'public' */",
),
..Default::default()
}),
));
} else {
declarations.push((
Access::Private,
Declaration::Function(Function {
name: format_smolstr!("get_{prop_ident}"),
signature: format!(
"() const = delete /* the property '{name}' is declared as private. Declare it as 'in', 'out', or 'in-out' to make it public */",
),
..Default::default()
}),
));
declarations.push((
Access::Private,
Declaration::Function(Function {
name: format_smolstr!("set_{}", &prop_ident),
signature: format!(
"(const auto &) const = delete /* property '{name}' is declared as private. Declare it as 'in' or 'in-out' to make it public */",
),
..Default::default()
}),
));
}
}
}
fn convert_to_value_fn(ty: &Type) -> String {
match ty {
Type::Struct(s) if s.name.is_none() => {
let mut init = s.fields.iter().enumerate().map(|(i, (name, ty))| {
format!(
"s.set_field(\"{name}\", {}(std::get<{i}>(tuple))); ",
convert_to_value_fn(ty)
)
});
format!("([](const auto &tuple) {{ slint::interpreter::Struct s; {}return slint::interpreter::Value(s); }})", init.join(""))
}
_ => "into_slint_value".into(),
}
}
fn convert_from_value_fn(ty: &Type) -> String {
match ty {
Type::Struct(s) if s.name.is_none() => {
let mut init = s.fields.iter().map(|(name, ty)| {
format!("slint::private_api::live_reload::from_slint_value<{}>(s.get_field(\"{name}\").value())", ty.cpp_type().unwrap())
});
format!(
"([](const slint::interpreter::Value &v) {{ auto s = v.to_struct().value(); return std::make_tuple({}); }})",
init.join(", ")
)
}
_ => format!(
"slint::private_api::live_reload::from_slint_value<{}>",
ty.cpp_type().unwrap_or_default()
),
}
}
fn generate_value_conversions(file: &mut File, structs_and_enums: &[Type]) {
for ty in structs_and_enums {
match ty {
Type::Struct(s) if s.name.is_some() && s.node.is_some() => {
let name = ident(&s.name.as_ref().unwrap());
let mut to_statements = vec![
"using slint::private_api::live_reload::into_slint_value;".into(),
"slint::interpreter::Struct s;".into(),
];
let mut from_statements = vec![
"using slint::private_api::live_reload::from_slint_value;".into(),
"slint::interpreter::Struct s = val.to_struct().value();".into(),
format!("{name} self;"),
];
for (f, t) in &s.fields {
to_statements.push(format!(
"s.set_field(\"{f}\", into_slint_value(self.{}));",
ident(f)
));
from_statements.push(format!(
"self.{} = slint::private_api::live_reload::from_slint_value<{}>(s.get_field(\"{f}\").value());",
ident(f),
t.cpp_type().unwrap()
));
}
to_statements.push("return s;".into());
from_statements.push("return self;".into());
file.declarations.push(Declaration::Function(Function {
name: "into_slint_value".into(),
signature: format!(
"([[maybe_unused]] const {name} &self) -> slint::interpreter::Value"
),
statements: Some(to_statements),
is_inline: true,
..Function::default()
}));
file.declarations.push(Declaration::Function(Function {
name: "from_slint_value".into(),
signature: format!(
"(const slint::interpreter::Value &val, const {name} *) -> {name}"
),
statements: Some(from_statements),
is_inline: true,
..Function::default()
}));
}
Type::Enumeration(_) => {
// todo;
}
_ => (),
}
}
}

View file

@ -1227,7 +1227,7 @@ pub fn print_diagnostics(diagnostics: &[Diagnostic]) {
/// An instance can be put on screen with the [`ComponentInstance::run`] function.
#[repr(C)]
pub struct ComponentInstance {
inner: crate::dynamic_item_tree::DynamicComponentVRc,
pub(crate) inner: crate::dynamic_item_tree::DynamicComponentVRc,
}
impl ComponentInstance {

View file

@ -327,7 +327,7 @@ pub unsafe extern "C" fn slint_interpreter_component_instance_invoke(
///
/// Safety: user_data must be a pointer that can be destroyed by the drop_user_data function.
/// callback must be a valid callback that initialize the `ret`
struct CallbackUserData {
pub struct CallbackUserData {
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
callback: extern "C" fn(user_data: *mut c_void, arg: Slice<Box<Value>>) -> Box<Value>,
@ -342,7 +342,15 @@ impl Drop for CallbackUserData {
}
impl CallbackUserData {
fn call(&self, args: &[Value]) -> Value {
pub unsafe fn new(
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
callback: extern "C" fn(user_data: *mut c_void, arg: Slice<Box<Value>>) -> Box<Value>,
) -> Self {
Self { user_data, drop_user_data, callback }
}
pub fn call(&self, args: &[Value]) -> Value {
let args = args.iter().map(|v| v.clone().into()).collect::<Vec<_>>();
(self.callback)(self.user_data, Slice::from_slice(args.as_ref())).as_ref().clone()
}
@ -358,7 +366,7 @@ pub unsafe extern "C" fn slint_interpreter_component_instance_set_callback(
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
) -> bool {
let ud = CallbackUserData { user_data, drop_user_data, callback };
let ud = CallbackUserData::new(user_data, drop_user_data, callback);
generativity::make_guard!(guard);
let comp = inst.unerase(guard);
@ -424,7 +432,7 @@ pub unsafe extern "C" fn slint_interpreter_component_instance_set_global_callbac
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
) -> bool {
let ud = CallbackUserData { user_data, drop_user_data, callback };
let ud = CallbackUserData::new(user_data, drop_user_data, callback);
generativity::make_guard!(guard);
let comp = inst.unerase(guard);

View file

@ -303,3 +303,137 @@ impl Watcher {
self.files.insert(path.into());
}
}
#[cfg(feature = "ffi")]
mod ffi {
use super::*;
use core::ffi::c_void;
use i_slint_core::window::WindowAdapter;
use i_slint_core::{slice::Slice, SharedString, SharedVector};
type LiveReloadingComponentInner = RefCell<LiveReloadingComponent>;
#[no_mangle]
/// LibraryPath is an array of string that have in the form `lib=...`
pub extern "C" fn slint_live_reload_new(
file_name: Slice<u8>,
component_name: Slice<u8>,
include_paths: &SharedVector<SharedString>,
library_paths: &SharedVector<SharedString>,
style: Slice<u8>,
) -> *const LiveReloadingComponentInner {
let mut compiler = Compiler::default();
compiler.set_include_paths(
include_paths.iter().map(|path| PathBuf::from(path.as_str())).collect(),
);
compiler.set_library_paths(
library_paths
.iter()
.map(|path| path.as_str().split_once('=').expect("library path must have an '='"))
.map(|(lib, path)| (lib.into(), PathBuf::from(path)))
.collect(),
);
if !style.is_empty() {
compiler.set_style(std::str::from_utf8(&style).unwrap().into());
}
Rc::into_raw(
LiveReloadingComponent::new(
compiler,
std::path::PathBuf::from(std::str::from_utf8(&file_name).unwrap()),
std::str::from_utf8(&component_name).unwrap().into(),
)
.expect("Creating the component failed"),
)
}
#[no_mangle]
pub unsafe extern "C" fn slint_live_reload_clone(
component: *const LiveReloadingComponentInner,
) {
Rc::increment_strong_count(component);
}
#[no_mangle]
pub unsafe extern "C" fn slint_live_reload_drop(component: *const LiveReloadingComponentInner) {
Rc::decrement_strong_count(component);
}
#[no_mangle]
pub extern "C" fn slint_live_reload_set_property(
component: &LiveReloadingComponentInner,
property: Slice<u8>,
value: &Value,
) {
let property = std::str::from_utf8(&property).unwrap();
if let Some((global, prop)) = property.split_once('.') {
component.borrow_mut().set_global_property(global, prop, value.clone());
} else {
component.borrow_mut().set_property(property, value.clone());
}
}
#[no_mangle]
pub extern "C" fn slint_live_reload_get_property(
component: &LiveReloadingComponentInner,
property: Slice<u8>,
) -> *mut Value {
let property = std::str::from_utf8(&property).unwrap();
let val = if let Some((global, prop)) = property.split_once('.') {
component.borrow().get_global_property(global, prop)
} else {
component.borrow().get_property(property)
};
Box::into_raw(Box::new(val))
}
#[no_mangle]
pub extern "C" fn slint_live_reload_invoke(
component: &LiveReloadingComponentInner,
callback: Slice<u8>,
args: Slice<Box<Value>>,
) -> *mut Value {
let callback = std::str::from_utf8(&callback).unwrap();
let args = args.iter().map(|vb| vb.as_ref().clone()).collect::<Vec<_>>();
let val = if let Some((global, prop)) = callback.split_once('.') {
component.borrow().invoke_global(global, prop, &args)
} else {
component.borrow().invoke(callback, &args)
};
Box::into_raw(Box::new(val))
}
#[no_mangle]
pub unsafe extern "C" fn slint_live_reload_set_callback(
component: &LiveReloadingComponentInner,
callback: Slice<u8>,
callback_handler: extern "C" fn(
user_data: *mut c_void,
arg: Slice<Box<Value>>,
) -> Box<Value>,
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
) {
let ud = crate::ffi::CallbackUserData::new(user_data, drop_user_data, callback_handler);
let handler = Rc::new(move |args: &[Value]| ud.call(args));
let callback = std::str::from_utf8(&callback).unwrap();
if let Some((global, prop)) = callback.split_once('.') {
component.borrow_mut().set_global_callback(global, prop, handler);
} else {
component.borrow_mut().set_callback(callback, handler);
}
}
/// Same precondition as slint_interpreter_component_instance_window
#[unsafe(no_mangle)]
pub unsafe extern "C" fn slint_live_reload_window(
component: &LiveReloadingComponentInner,
out: *mut *const i_slint_core::window::ffi::WindowAdapterRcOpaque,
) {
assert_eq!(
core::mem::size_of::<Rc<dyn WindowAdapter>>(),
core::mem::size_of::<i_slint_core::window::ffi::WindowAdapterRcOpaque>()
);
let borrow = component.borrow();
let adapter = borrow.instance().inner.window_adapter_ref().unwrap();
core::ptr::write(out as *mut *const Rc<dyn WindowAdapter>, adapter as *const _)
}
}