mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-28 12:54:45 +00:00
Optional C++ namespaces (#4759)
Co-authored-by: Michael Winkelmann <michael@winkelmann.site>
This commit is contained in:
parent
43266f7f76
commit
bef532b5fc
11 changed files with 148 additions and 19 deletions
|
@ -11,6 +11,19 @@ set_property(CACHE DEFAULT_SLINT_EMBED_RESOURCES PROPERTY STRINGS
|
|||
# INITIALIZE_FROM_VARIABLE DEFAULT_SLINT_EMBED_RESOURCES)
|
||||
|
||||
function(SLINT_TARGET_SOURCES target)
|
||||
# Parse the NAMESPACE argument
|
||||
cmake_parse_arguments(SLINT_TARGET_SOURCES "" "NAMESPACE" "" ${ARGN})
|
||||
|
||||
if (DEFINED SLINT_TARGET_SOURCES_NAMESPACE)
|
||||
# Remove the NAMESPACE argument from the list
|
||||
list(FIND ARGN "NAMESPACE" _index)
|
||||
list(REMOVE_AT ARGN ${_index})
|
||||
list(FIND ARGN "${SLINT_TARGET_SOURCES_NAMESPACE}" _index)
|
||||
list(REMOVE_AT ARGN ${_index})
|
||||
# If the namespace is not empty, add the --cpp-namespace argument
|
||||
set(_SLINT_CPP_NAMESPACE_ARG "--cpp-namespace=${SLINT_TARGET_SOURCES_NAMESPACE}")
|
||||
endif()
|
||||
|
||||
foreach (it IN ITEMS ${ARGN})
|
||||
get_filename_component(_SLINT_BASE_NAME ${it} NAME_WE)
|
||||
get_filename_component(_SLINT_ABSOLUTE ${it} REALPATH BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
@ -28,6 +41,7 @@ function(SLINT_TARGET_SOURCES target)
|
|||
--style ${_SLINT_STYLE}
|
||||
--embed-resources=${embed}
|
||||
--translation-domain="${target}"
|
||||
${_SLINT_CPP_NAMESPACE_ARG}
|
||||
DEPENDS Slint::slint-compiler ${_SLINT_ABSOLUTE}
|
||||
COMMENT "Generating ${_SLINT_BASE_NAME}.h"
|
||||
DEPFILE ${CMAKE_CURRENT_BINARY_DIR}/${_SLINT_BASE_NAME}.d
|
||||
|
|
|
@ -5,23 +5,26 @@
|
|||
## `slint_target_sources`
|
||||
|
||||
```
|
||||
slint_target_sources(<target> <files>....)
|
||||
slint_target_sources(<target> <files>.... [NAMESPACE namespace])
|
||||
```
|
||||
|
||||
Use this function to tell cmake about the .slint files of your application, similar to the builtin cmake [target_sources](https://cmake.org/cmake/help/latest/command/target_sources.html) function.
|
||||
The function takes care of running the slint-compiler to convert `.slint` files to `.h` files in the build directory,
|
||||
and extend the include directories of your target so that the generated file is found when including it in your application.
|
||||
|
||||
The optional NAMESPACE argument will put the generated components in the given C++ namespace.
|
||||
|
||||
Given a file called `the_window.slint`, the following example will create a file called `the_window.h` that can
|
||||
be included from your .cpp file.
|
||||
be included from your .cpp file. Assuming the `the_window.slint` contains a component `TheWindow`, the output
|
||||
C++ class will be put in the namespace `ui`, resulting to `ui::TheWindow`.
|
||||
|
||||
```cmake
|
||||
add_executable(my_application main.cpp)
|
||||
target_link_libraries(my_application PRIVATE Slint::Slint)
|
||||
slint_target_sources(my_application the_window.slint)
|
||||
slint_target_sources(my_application the_window.slint NAMESPACE ui)
|
||||
```
|
||||
|
||||
|
||||
## Resource Embedding
|
||||
|
||||
By default, images from [`@image-url()`](slint-reference:src/language/syntax/types#images) or fonts that your Slint files reference are loaded from disk at run-time. This minimises build times, but requires that the directory structure with the files remains stable. If you want to build a program that runs anywhere, then you can configure the Slint compiler to embed such sources into the binary.
|
||||
|
|
|
@ -10,4 +10,4 @@ endif()
|
|||
|
||||
add_executable(todo main.cpp)
|
||||
target_link_libraries(todo PRIVATE Slint::Slint)
|
||||
slint_target_sources(todo ../ui/todo.slint)
|
||||
slint_target_sources(todo ../ui/todo.slint NAMESPACE todo_ui)
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
|
||||
int main()
|
||||
{
|
||||
auto demo = MainWindow::create();
|
||||
auto demo = todo_ui::MainWindow::create();
|
||||
using todo_ui::TodoItem;
|
||||
|
||||
auto todo_model = std::make_shared<slint::VectorModel<TodoItem>>(std::vector {
|
||||
TodoItem { "Implement the .slint file", true },
|
||||
|
|
|
@ -18,15 +18,15 @@ use crate::namedreference::NamedReference;
|
|||
use crate::object_tree::{Component, Document, ElementRc};
|
||||
|
||||
#[cfg(feature = "cpp")]
|
||||
mod cpp;
|
||||
pub mod cpp;
|
||||
|
||||
#[cfg(feature = "rust")]
|
||||
pub mod rust;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum OutputFormat {
|
||||
#[cfg(feature = "cpp")]
|
||||
Cpp,
|
||||
Cpp(cpp::Config),
|
||||
#[cfg(feature = "rust")]
|
||||
Rust,
|
||||
Interpreter,
|
||||
|
@ -37,7 +37,9 @@ impl OutputFormat {
|
|||
pub fn guess_from_extension(path: &std::path::Path) -> Option<Self> {
|
||||
match path.extension().and_then(|ext| ext.to_str()) {
|
||||
#[cfg(feature = "cpp")]
|
||||
Some("cpp") | Some("cxx") | Some("h") | Some("hpp") => Some(Self::Cpp),
|
||||
Some("cpp") | Some("cxx") | Some("h") | Some("hpp") => {
|
||||
Some(Self::Cpp(cpp::Config::default()))
|
||||
}
|
||||
#[cfg(feature = "rust")]
|
||||
Some("rs") => Some(Self::Rust),
|
||||
_ => None,
|
||||
|
@ -50,7 +52,7 @@ impl std::str::FromStr for OutputFormat {
|
|||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
#[cfg(feature = "cpp")]
|
||||
"cpp" => Ok(Self::Cpp),
|
||||
"cpp" => Ok(Self::Cpp(cpp::Config::default())),
|
||||
#[cfg(feature = "rust")]
|
||||
"rust" => Ok(Self::Rust),
|
||||
"llr" => Ok(Self::Llr),
|
||||
|
@ -74,8 +76,8 @@ pub fn generate(
|
|||
|
||||
match format {
|
||||
#[cfg(feature = "cpp")]
|
||||
OutputFormat::Cpp => {
|
||||
let output = cpp::generate(doc);
|
||||
OutputFormat::Cpp(config) => {
|
||||
let output = cpp::generate(doc, config);
|
||||
write!(destination, "{}", output)?;
|
||||
}
|
||||
#[cfg(feature = "rust")]
|
||||
|
|
|
@ -8,6 +8,12 @@
|
|||
|
||||
use std::fmt::Write;
|
||||
|
||||
/// The configuration for the C++ code generator
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct Config {
|
||||
pub namespace: Option<String>,
|
||||
}
|
||||
|
||||
fn ident(ident: &str) -> String {
|
||||
if ident.contains('-') {
|
||||
ident.replace('-', "_")
|
||||
|
@ -76,6 +82,7 @@ mod cpp_ast {
|
|||
pub struct File {
|
||||
pub includes: Vec<String>,
|
||||
pub after_includes: String,
|
||||
pub namespace: Option<String>,
|
||||
pub declarations: Vec<Declaration>,
|
||||
pub definitions: Vec<Declaration>,
|
||||
}
|
||||
|
@ -85,6 +92,11 @@ mod cpp_ast {
|
|||
for i in &self.includes {
|
||||
writeln!(f, "#include {}", i)?;
|
||||
}
|
||||
if let Some(namespace) = &self.namespace {
|
||||
writeln!(f, "namespace {} {{", namespace)?;
|
||||
INDENTATION.with(|x| x.set(x.get() + 1));
|
||||
}
|
||||
|
||||
write!(f, "{}", self.after_includes)?;
|
||||
for d in &self.declarations {
|
||||
write!(f, "\n{}", d)?;
|
||||
|
@ -92,6 +104,11 @@ mod cpp_ast {
|
|||
for d in &self.definitions {
|
||||
write!(f, "\n{}", d)?;
|
||||
}
|
||||
if let Some(namespace) = &self.namespace {
|
||||
writeln!(f, "}} // namespace {}", namespace)?;
|
||||
INDENTATION.with(|x| x.set(x.get() - 1));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -515,8 +532,8 @@ fn handle_property_init(
|
|||
}
|
||||
|
||||
/// Returns the text of the C++ code produced by the given root component
|
||||
pub fn generate(doc: &Document) -> impl std::fmt::Display {
|
||||
let mut file = File::default();
|
||||
pub fn generate(doc: &Document, config: Config) -> impl std::fmt::Display {
|
||||
let mut file = File { namespace: config.namespace.clone(), ..Default::default() };
|
||||
|
||||
file.includes.push("<array>".into());
|
||||
file.includes.push("<limits>".into());
|
||||
|
|
|
@ -94,6 +94,9 @@ pub struct CompilerConfiguration {
|
|||
|
||||
/// The domain used as one of the parameter to the translate function
|
||||
pub translation_domain: Option<String>,
|
||||
|
||||
/// C++ namespace
|
||||
pub cpp_namespace: Option<String>,
|
||||
}
|
||||
|
||||
impl CompilerConfiguration {
|
||||
|
@ -142,6 +145,18 @@ impl CompilerConfiguration {
|
|||
|
||||
let enable_component_containers = enable_experimental_features;
|
||||
|
||||
let cpp_namespace = match output_format {
|
||||
#[cfg(feature = "cpp")]
|
||||
crate::generator::OutputFormat::Cpp(config) => match config.namespace {
|
||||
Some(namespace) => Some(namespace),
|
||||
None => match std::env::var("SLINT_CPP_NAMESPACE") {
|
||||
Ok(namespace) => Some(namespace),
|
||||
Err(_) => None,
|
||||
},
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Self {
|
||||
embed_resources,
|
||||
include_paths: Default::default(),
|
||||
|
@ -154,6 +169,7 @@ impl CompilerConfiguration {
|
|||
accessibility: true,
|
||||
enable_component_containers,
|
||||
translation_domain: None,
|
||||
cpp_namespace,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
36
tests/cases/exports/cpp_namespace.slint
Normal file
36
tests/cases/exports/cpp_namespace.slint
Normal file
|
@ -0,0 +1,36 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
//cpp-namespace: my::ui
|
||||
//ignore: rust,js
|
||||
struct TestStruct {
|
||||
condition: bool,
|
||||
}
|
||||
|
||||
enum TestEnum {
|
||||
A,
|
||||
B,
|
||||
C
|
||||
}
|
||||
|
||||
export component TestCase inherits Rectangle {
|
||||
in-out property <TestStruct> test_struct;
|
||||
in-out property <TestEnum> test_enum;
|
||||
}
|
||||
|
||||
/*
|
||||
```cpp
|
||||
auto handle = my::ui::TestCase::create();
|
||||
const my::ui::TestCase &instance = *handle;
|
||||
my::ui::TestStruct test_struct {.condition = false};
|
||||
test_struct.condition = true;
|
||||
instance.set_test_struct(test_struct);
|
||||
assert(instance.get_test_struct() == test_struct);
|
||||
instance.set_test_enum(my::ui::TestEnum::A);
|
||||
auto test_enum = instance.get_test_enum();
|
||||
test_enum = my::ui::TestEnum::B;
|
||||
instance.set_test_enum(test_enum);
|
||||
assert(instance.get_test_enum() == my::ui::TestEnum::B);
|
||||
```
|
||||
|
||||
*/
|
|
@ -16,9 +16,14 @@ pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>>
|
|||
.map(|(k, v)| (k.to_string(), std::path::PathBuf::from(v)))
|
||||
.collect::<std::collections::HashMap<_, _>>();
|
||||
|
||||
let cpp_namespace = test_driver_lib::extract_cpp_namespace(&source);
|
||||
|
||||
let mut diag = BuildDiagnostics::default();
|
||||
let syntax_node = parser::parse(source.clone(), Some(&testcase.absolute_path), None, &mut diag);
|
||||
let mut compiler_config = CompilerConfiguration::new(generator::OutputFormat::Cpp);
|
||||
let output_format =
|
||||
generator::OutputFormat::Cpp(generator::cpp::Config { namespace: cpp_namespace });
|
||||
|
||||
let mut compiler_config = CompilerConfiguration::new(output_format.clone());
|
||||
compiler_config.include_paths = include_paths;
|
||||
compiler_config.library_paths = library_paths;
|
||||
let (root_component, diag, _) =
|
||||
|
@ -31,7 +36,7 @@ pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>>
|
|||
|
||||
let mut generated_cpp: Vec<u8> = Vec::new();
|
||||
|
||||
generator::generate(generator::OutputFormat::Cpp, &mut generated_cpp, &root_component)?;
|
||||
generator::generate(output_format, &mut generated_cpp, &root_component)?;
|
||||
|
||||
if diag.has_error() {
|
||||
let vec = diag.to_string_vec();
|
||||
|
|
|
@ -238,3 +238,23 @@ fn test_extract_ignores() {
|
|||
let r = extract_ignores(source).collect::<Vec<_>>();
|
||||
assert_eq!(r, ["cpp", "rust", "nodejs"]);
|
||||
}
|
||||
|
||||
pub fn extract_cpp_namespace(source: &str) -> Option<String> {
|
||||
lazy_static::lazy_static! {
|
||||
static ref RX: Regex = Regex::new(r"//cpp-namespace:\s*(.+)\s*\n").unwrap();
|
||||
}
|
||||
RX.captures(source).map(|mat| mat.get(1).unwrap().as_str().trim().to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_cpp_namespace() {
|
||||
assert!(extract_cpp_namespace("something").is_none());
|
||||
|
||||
let source = r"
|
||||
//cpp-namespace: ui
|
||||
Blah {}
|
||||
";
|
||||
|
||||
let r = extract_cpp_namespace(source);
|
||||
assert_eq!(r, Some("ui".to_string()));
|
||||
}
|
||||
|
|
|
@ -62,6 +62,10 @@ struct Cli {
|
|||
/// Translation domain
|
||||
#[arg(long = "translation-domain", action)]
|
||||
translation_domain: Option<String>,
|
||||
|
||||
/// C++ namespace
|
||||
#[arg(long = "cpp-namespace", name = "C++ namespace")]
|
||||
cpp_namespace: Option<String>,
|
||||
}
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
|
@ -74,7 +78,18 @@ fn main() -> std::io::Result<()> {
|
|||
diag.print();
|
||||
std::process::exit(-1);
|
||||
}
|
||||
let mut compiler_config = CompilerConfiguration::new(args.format);
|
||||
|
||||
let mut format = args.format.clone();
|
||||
|
||||
if args.cpp_namespace.is_some() {
|
||||
if !matches!(format, generator::OutputFormat::Cpp(..)) {
|
||||
eprintln!("C++ namespace option was set. Output format will be C++.");
|
||||
}
|
||||
format =
|
||||
generator::OutputFormat::Cpp(generator::cpp::Config { namespace: args.cpp_namespace });
|
||||
}
|
||||
|
||||
let mut compiler_config = CompilerConfiguration::new(format.clone());
|
||||
compiler_config.translation_domain = args.translation_domain;
|
||||
|
||||
// Override defaults from command line:
|
||||
|
@ -102,10 +117,10 @@ fn main() -> std::io::Result<()> {
|
|||
let diag = diag.check_and_exit_on_error();
|
||||
|
||||
if args.output == std::path::Path::new("-") {
|
||||
generator::generate(args.format, &mut std::io::stdout(), &doc)?;
|
||||
generator::generate(format, &mut std::io::stdout(), &doc)?;
|
||||
} else {
|
||||
generator::generate(
|
||||
args.format,
|
||||
format,
|
||||
&mut BufWriter::new(std::fs::File::create(&args.output)?),
|
||||
&doc,
|
||||
)?;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue