slint
slint copied to clipboard
Component Library
Hey Is it possible to write a component library for sixtyfps as of the current state? Meaning a Rust lib, that exports component to be used in other packages. Primarily for Rust (both lib and consumer).
Didn't find any documentation/issues/examples talking about that
This is currently not supported but it is on our radar.
Could you explain your use-case a little bit? What kind of components are you thinking about?
It's pretty important for separation of concerns and distribution. Let's take web development as an example, where this is pretty common or even standard. You create a lib for your UI components so that you can reuse them for different applications and to have a better structure.
Once that's possible we could also create a library for standard UI components for different systems like material design for example.
Yes, we do have a module system which allow to put components in different files to be re-used. https://github.com/sixtyfpsui/sixtyfps/blob/master/docs/langref.md#modules and we use that for the standard widget library.
Now, we haven't anything in place regarding how to distribute theses, especially if they need to mix with native code.
Okay, thank you :) I think it would be beneficial if distributing these modules would be as easy as possible, cause that's one of the reasons why modern web frameworks are so damn popular, even for desktop apps. They make it as easy as possible so that the ecosystem can grow. And therefore more people use it because they can reuse component frameworks that are already pretty slick
Hi, is there any update on that point. I think this is also necessary if you want to create a library with resources / themes or a custom widget library that you want to use in different projects / crates.
There is still no progress. right now you'd just have to put your files in a .zip and share that? (or, better, in a git repository)
It would be also possible to put these files in a crate.io and somehow add ~/.cargo/registry/src/github.com-1ecc6299db9ec823/foobar-1.2.3 to the .60 include path. Or what better option could there be?
There is still no progress. right now you'd just have to put your files in a .zip and share that? (or, better, in a git repository)
It would be also possible to put these files in a crate.io and somehow add
~/.cargo/registry/src/github.com-1ecc6299db9ec823/foobar-1.2.3to the .60 include path. Or what better option could there be?
I'd recommend using the big web frameworks as an inspiration, as close as possible. Would definitely help adoption if it's easily reusable. In JavaScript you usually just include the package as a dependency and then import the components you intend to use.
I managed to easily include .slint files from a Cargo crate without any changes in Slint itself.
The only issue here is that internal Cargo paths change and I have to manually update the LSP configuration all the time, so IDE integration works properly. Currently experimenting with using symlinks that are maintained by the build script to create a stable path for the LSP to reference.
Official support from Slint's side probably would be better compared to such hacky solutions.
I found a solution to reference Slint files from my custom Slint widget library crate with a little build magic.
First in the build.rs of my crate all new or changed .slint files are copied to the output directory of the crate. On the lib.rs file there is a create_include method that you can call from your build.rs file. This creates a slint file in your project that exports all stuff of the .slint output files. You can add this include file to .gitignore.
I have to cleanup the code a little but after that I want to provide a cargo generate template.
It's a solution until there is a build in solution in slint :-)
Result
- include file
import { co } from "$PROJECT_PATH/target/release/build/co_widgets-580ab606e89d424c/out/co_widgets/co/co.slint";
import { mi } from "$PROJECT_PATH/target/release/build/co_widgets-580ab606e89d424c/out/co_widgets/assets/icons/material_icons.slint";
import { MaterialIcon } from "$PROJECT_PATH/target/release/build/co_widgets-580ab606e89d424c/out/co_widgets/assets/icons/material_icon.slint";
import { CoWindow } from "$PROJECT_PATH/target/release/build/co_widgets-580ab606e89d424c/out/co_widgets/components/components.slint";
import { Button, OutlineButton, IconButton, TextLine } from "$PROJECT_PATH/target/release/build/co_widgets-580ab606e89d424c/out/co_widgets/widgets/widgets.slint";
import { KeyboardPage, Keyboard } from "$PROJECT_PATH/target/release/build/co_widgets-580ab606e89d424c/out/co_widgets/keyboard/keyboard.slint";
export { co }
export { mi, MaterialIcon }
export { CoWindow }
export { Button, OutlineButton, IconButton, TextLine }
export { KeyboardPage, Keyboard }
I haven't tested this, but i propose the following approach:
In the crates that provide slint files, the build.rs would contains something like (named the mylib crate in this example)
// (in mylib/build.rs 's main)
println!("cargo:SLINT_INCLUDE_PATH={}/ui", env!("CARGO_MANIFEST_DIR"))
Then, the application will need to add the include path in the build script such as
// (in app/build.rs)
slint_build::compile_with_config("ui/main.slint",
slint_build::CompilerConfiguration::new().with_include_path(vec![
env!("DEP_MYLIB_SLINT_INCLUDE_PATH").into()
]));
And that's it if there is only slint file.
If there is also native code, maybe the library provides a global or some defined callback, and then the application cde would need to do:
//...
app.global::<MyLibLogic>().on_render(mylib::render);
app.global::<MyLibLogic>().on_foo_bar(mylib::foo_bar);
//...
This should all works now with the current release and has worked for a long time.
But it would be nice if there was some automated way to do this so the use would be easier. and in the app's build.rs we could just do something like slint_build::CompilerConfiguration::new().add_libary("mylib") and somehow this would take care also of initializing the global or so with code form ::mylib if mylib added some metadata on how to do it.
@ogoffart I tried this
lint_build::compile_with_config("ui/main.slint",
slint_build::CompilerConfiguration::new().with_include_path(vec![
env!("DEP_MYLIB_SLINT_INCLUDE_PATH").into()
]));
I get
environment variable `SLINT_INCLUDE_GENERATED` not defined
--> examples/widgets/src/main.rs:10:1
|
10 | slint::include_modules!();
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `env` which comes from the expansion of the macro `slint::include_modules` (in Nightly builds, run with -Z macro-backtrace for more info)
error: could not compile `widgets` due to previous error
The path that is read by usign env! in the build.rs is correct
Error from the compiler
Err(CompileError(["$PROJECT_PATH/examples/widgets/ui/widgets.slint:1: Cannot find requested import \"co_widgets/co_widgets.slint\" in the include search path", "$PROJECT_PATH/examples/widgets/ui/widgets.slint:3: Unknown type CoWindow", "$PROJECT_PATH/examples/widgets/ui/widgets.slint:25: Unknown type KeyboardPage", "$PROJECT_PATH/examples/widgets/ui/widgets.slint:35: Unknown type OutlineButton", "$PROJECT_PATH/examples/widgets/ui/widgets.slint:38: 'clicked' is not a callback in <error>", "$PROJECT_PATH/examples/widgets/ui/widgets.slint:44: Unknown type OutlineButton", "$PROJECT_PATH/examples/widgets/ui/widgets.slint:47: 'clicked' is not a callback in <error>", "$PROJECT_PATH/examples/widgets/ui/widgets.slint:53: Unknown type Button", "$PROJECT_PATH/examples/widgets/ui/widgets.slint:55: 'clicked' is not a callback in <error>", "$PROJECT_PATH/examples/widgets/ui/widgets.slint:58: Unknown type Button", "$PROJECT_PATH/examples/widgets/ui/widgets.slint:65: Unknown type Button", "$PROJECT_PATH/examples/widgets/ui/widgets.slint:69: 'clicked' is not a callback in <error>", "$PROJECT_PATH/examples/widgets/ui/widgets.slint:72: Unknown type IconButton", "$PROJECT_PATH/examples/widgets/ui/widgets.slint:79: Unknown type TextLine", "$PROJECT_PATH/examples/widgets/ui/widgets.slint:86: 'clicked' is not a callback in <error>"]))
Looks like there is a problem with imports import { co } from "co/co.slint";
Here you can find a template that makes it possible to ship your slint files as crate with lsp and viewer support: https://codeberg.org/flovansl/slint_lib_template and I've crated also an example https://codeberg.org/flovansl/slint_lib_template_example.
Any ETA for this feature?
In the crates that provide slint files, the build.rs would contains something like (named the
mylibcrate in this example)// (in mylib/build.rs 's main) println!("cargo:SLINT_INCLUDE_PATH={}/ui", env!("CARGO_MANIFEST_DIR"))
DEP_FOO_KEY=value metadata is only passed when using package.links. With this added to mylib/Cargo.toml, DEP_MYLIB_SLINT_INCLUDE_PATH is visible in the app's build script:
[package]
# ...
links = "mylib" # <==
Not sure if it would be a problem in practice but with this approach one could only have .slint files provided by an immediate dependency because the metadata is not passed to transitive dependents.
What if slint-build would use cargo_metadata to find any dependencies that depend on slint and automatically add them to include paths? It could default to <dep-manifest-dir>/ui by convention, and allow specifying the value in mylib/Cargo.toml metadata if desired:
[package.metadata.slint]
include_path = "foo" # or ["ui", "foo", "bar"]
Could slint_build::compile() do something like this out of the box?
// app/build.rs
use cargo_metadata::{MetadataCommand, Package};
use serde_json::Value;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")?;
let metadata = MetadataCommand::new()
.manifest_path(std::path::PathBuf::from(manifest_dir).join("Cargo.toml"))
.exec()?;
let dependencies = metadata
.packages
.iter()
.filter(|p| p.dependencies.iter().any(|d| d.name == "slint"))
.collect::<Vec<_>>();
let include_paths = dependencies
.into_iter()
.map(resolve_slint_include_path)
.collect::<Vec<_>>()
.concat();
slint_build::compile_with_config(
"ui/main.slint",
slint_build::CompilerConfiguration::new().with_include_paths(include_paths),
)
.unwrap();
Ok(())
}
fn resolve_slint_include_path(package: &Package) -> Vec<std::path::PathBuf> {
let manifest_dir = package.manifest_path.parent().unwrap();
let include_path = package
.metadata
.get("slint")
.and_then(|s| s.get("include_path"));
match include_path {
Some(Value::String(s)) => vec![manifest_dir.join(s).into()],
Some(Value::Array(a)) => a
.iter()
.map(|s| manifest_dir.join(s.as_str().unwrap()).into())
.collect(),
_ => vec![manifest_dir.join("ui").into()],
}
}