mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-03 02:13:21 +00:00
Python: Add support for exporting multiple components
This commit is contained in:
parent
a3435d218f
commit
0c7d2062a5
10 changed files with 76 additions and 51 deletions
|
@ -112,7 +112,7 @@ The exported component is exposed as a Python class. To access this class, you h
|
||||||
options:
|
options:
|
||||||
|
|
||||||
1. Call `slint.load_file("app.slint")`. The returned object is a [namespace](https://docs.python.org/3/library/types.html#types.SimpleNamespace),
|
1. Call `slint.load_file("app.slint")`. The returned object is a [namespace](https://docs.python.org/3/library/types.html#types.SimpleNamespace),
|
||||||
that provides the `MainWindow` class:
|
that provides the `MainWindow` class as well as any other explicitly exported component that inherits `Window`:
|
||||||
```python
|
```python
|
||||||
import slint
|
import slint
|
||||||
components = slint.load_file("app.slint")
|
components = slint.load_file("app.slint")
|
||||||
|
@ -128,7 +128,8 @@ options:
|
||||||
|
|
||||||
Any attribute lookup in `slint.loader` is searched for in `sys.path`. If a directory with the name exists, it is returned as a loader object, and subsequent
|
Any attribute lookup in `slint.loader` is searched for in `sys.path`. If a directory with the name exists, it is returned as a loader object, and subsequent
|
||||||
attribute lookups follow the same logic. If the name matches a file with the `.slint` extension, it is automatically loaded with `load_file` and the
|
attribute lookups follow the same logic. If the name matches a file with the `.slint` extension, it is automatically loaded with `load_file` and the
|
||||||
[namespace](https://docs.python.org/3/library/types.html#types.SimpleNamespace) is returned.
|
[namespace](https://docs.python.org/3/library/types.html#types.SimpleNamespace) is returned, which contains classes for each exported component that
|
||||||
|
inherits `Window`.
|
||||||
|
|
||||||
### Accessing Properties
|
### Accessing Properties
|
||||||
|
|
||||||
|
|
|
@ -20,12 +20,12 @@ use crate::errors::{
|
||||||
use crate::value::PyValue;
|
use crate::value::PyValue;
|
||||||
|
|
||||||
#[pyclass(unsendable)]
|
#[pyclass(unsendable)]
|
||||||
pub struct ComponentCompiler {
|
pub struct Compiler {
|
||||||
compiler: slint_interpreter::ComponentCompiler,
|
compiler: slint_interpreter::Compiler,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl ComponentCompiler {
|
impl Compiler {
|
||||||
#[new]
|
#[new]
|
||||||
fn py_new() -> PyResult<Self> {
|
fn py_new() -> PyResult<Self> {
|
||||||
Ok(Self { compiler: Default::default() })
|
Ok(Self { compiler: Default::default() })
|
||||||
|
@ -61,28 +61,19 @@ impl ComponentCompiler {
|
||||||
self.compiler.set_library_paths(libraries)
|
self.compiler.set_library_paths(libraries)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[getter]
|
|
||||||
fn get_diagnostics(&self) -> Vec<PyDiagnostic> {
|
|
||||||
self.compiler.diagnostics().iter().map(|diag| PyDiagnostic(diag.clone())).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[setter]
|
#[setter]
|
||||||
fn set_translation_domain(&mut self, domain: String) {
|
fn set_translation_domain(&mut self, domain: String) {
|
||||||
self.compiler.set_translation_domain(domain)
|
self.compiler.set_translation_domain(domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_from_path(&mut self, path: PathBuf) -> Option<ComponentDefinition> {
|
fn build_from_path(&mut self, path: PathBuf) -> CompilationResult {
|
||||||
spin_on::spin_on(self.compiler.build_from_path(path))
|
CompilationResult { result: spin_on::spin_on(self.compiler.build_from_path(path)) }
|
||||||
.map(|definition| ComponentDefinition { definition })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_from_source(
|
fn build_from_source(&mut self, source_code: String, path: PathBuf) -> CompilationResult {
|
||||||
&mut self,
|
CompilationResult {
|
||||||
source_code: String,
|
result: spin_on::spin_on(self.compiler.build_from_source(source_code, path)),
|
||||||
path: PathBuf,
|
}
|
||||||
) -> Option<ComponentDefinition> {
|
|
||||||
spin_on::spin_on(self.compiler.build_from_source(source_code, path))
|
|
||||||
.map(|definition| ComponentDefinition { definition })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,6 +123,28 @@ pub enum PyDiagnosticLevel {
|
||||||
Warning,
|
Warning,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pyclass(unsendable)]
|
||||||
|
pub struct CompilationResult {
|
||||||
|
result: slint_interpreter::CompilationResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl CompilationResult {
|
||||||
|
#[getter]
|
||||||
|
fn component_names(&self) -> Vec<String> {
|
||||||
|
self.result.component_names().map(ToString::to_string).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn component(&self, name: &str) -> Option<ComponentDefinition> {
|
||||||
|
self.result.component(name).map(|definition| ComponentDefinition { definition })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter]
|
||||||
|
fn get_diagnostics(&self) -> Vec<PyDiagnostic> {
|
||||||
|
self.result.diagnostics().map(|diag| PyDiagnostic(diag.clone())).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[pyclass(unsendable)]
|
#[pyclass(unsendable)]
|
||||||
struct ComponentDefinition {
|
struct ComponentDefinition {
|
||||||
definition: slint_interpreter::ComponentDefinition,
|
definition: slint_interpreter::ComponentDefinition,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
mod image;
|
mod image;
|
||||||
mod interpreter;
|
mod interpreter;
|
||||||
use interpreter::{ComponentCompiler, PyDiagnostic, PyDiagnosticLevel, PyValueType};
|
use interpreter::{CompilationResult, Compiler, PyDiagnostic, PyDiagnosticLevel, PyValueType};
|
||||||
mod brush;
|
mod brush;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod models;
|
mod models;
|
||||||
|
@ -30,7 +30,8 @@ fn slint(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
})
|
})
|
||||||
.map_err(|e| errors::PyPlatformError(e))?;
|
.map_err(|e| errors::PyPlatformError(e))?;
|
||||||
|
|
||||||
m.add_class::<ComponentCompiler>()?;
|
m.add_class::<Compiler>()?;
|
||||||
|
m.add_class::<CompilationResult>()?;
|
||||||
m.add_class::<image::PyImage>()?;
|
m.add_class::<image::PyImage>()?;
|
||||||
m.add_class::<PyValueType>()?;
|
m.add_class::<PyValueType>()?;
|
||||||
m.add_class::<PyDiagnosticLevel>()?;
|
m.add_class::<PyDiagnosticLevel>()?;
|
||||||
|
|
|
@ -188,7 +188,7 @@ def _build_class(compdef):
|
||||||
|
|
||||||
|
|
||||||
def load_file(path, quiet=False, style=None, include_paths=None, library_paths=None, translation_domain=None):
|
def load_file(path, quiet=False, style=None, include_paths=None, library_paths=None, translation_domain=None):
|
||||||
compiler = native.ComponentCompiler()
|
compiler = native.Compiler()
|
||||||
|
|
||||||
if style is not None:
|
if style is not None:
|
||||||
compiler.style = style
|
compiler.style = style
|
||||||
|
@ -199,9 +199,9 @@ def load_file(path, quiet=False, style=None, include_paths=None, library_paths=N
|
||||||
if translation_domain is not None:
|
if translation_domain is not None:
|
||||||
compiler.translation_domain = translation_domain
|
compiler.translation_domain = translation_domain
|
||||||
|
|
||||||
compdef = compiler.build_from_path(path)
|
result = compiler.build_from_path(path)
|
||||||
|
|
||||||
diagnostics = compiler.diagnostics
|
diagnostics = result.diagnostics
|
||||||
if diagnostics:
|
if diagnostics:
|
||||||
if not quiet:
|
if not quiet:
|
||||||
for diag in diagnostics:
|
for diag in diagnostics:
|
||||||
|
@ -213,10 +213,11 @@ def load_file(path, quiet=False, style=None, include_paths=None, library_paths=N
|
||||||
if errors:
|
if errors:
|
||||||
raise CompileError(f"Could not compile {path}", diagnostics)
|
raise CompileError(f"Could not compile {path}", diagnostics)
|
||||||
|
|
||||||
wrapper_class = _build_class(compdef)
|
|
||||||
|
|
||||||
module = types.SimpleNamespace()
|
module = types.SimpleNamespace()
|
||||||
setattr(module, compdef.name, wrapper_class)
|
for comp_name in result.component_names:
|
||||||
|
wrapper_class = _build_class(result.component(comp_name))
|
||||||
|
|
||||||
|
setattr(module, comp_name, wrapper_class)
|
||||||
|
|
||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
|
@ -7,15 +7,15 @@ from slint.slint import ValueType
|
||||||
|
|
||||||
|
|
||||||
def test_basic_compiler():
|
def test_basic_compiler():
|
||||||
compiler = native.ComponentCompiler()
|
compiler = native.Compiler()
|
||||||
|
|
||||||
assert compiler.include_paths == []
|
assert compiler.include_paths == []
|
||||||
compiler.include_paths = ["testing"]
|
compiler.include_paths = ["testing"]
|
||||||
assert compiler.include_paths == ["testing"]
|
assert compiler.include_paths == ["testing"]
|
||||||
|
|
||||||
assert compiler.build_from_source("Garbage", "") == None
|
assert len(compiler.build_from_source("Garbage", "").component_names) == 0
|
||||||
|
|
||||||
compdef = compiler.build_from_source("""
|
result = compiler.build_from_source("""
|
||||||
export global TestGlobal {
|
export global TestGlobal {
|
||||||
in property <string> theglobalprop;
|
in property <string> theglobalprop;
|
||||||
callback globallogic();
|
callback globallogic();
|
||||||
|
@ -36,6 +36,9 @@ def test_basic_compiler():
|
||||||
public function ff() {}
|
public function ff() {}
|
||||||
}
|
}
|
||||||
""", "")
|
""", "")
|
||||||
|
assert result.component_names == ["Test"]
|
||||||
|
compdef = result.component("Test")
|
||||||
|
|
||||||
assert compdef != None
|
assert compdef != None
|
||||||
|
|
||||||
assert compdef.name == "Test"
|
assert compdef.name == "Test"
|
||||||
|
@ -64,12 +67,12 @@ def test_basic_compiler():
|
||||||
|
|
||||||
|
|
||||||
def test_compiler_build_from_path():
|
def test_compiler_build_from_path():
|
||||||
compiler = native.ComponentCompiler()
|
compiler = native.Compiler()
|
||||||
|
|
||||||
assert len(compiler.diagnostics) == 0
|
result = compiler.build_from_path("Nonexistent.slint")
|
||||||
|
assert len(result.component_names) == 0
|
||||||
|
|
||||||
assert compiler.build_from_path("Nonexistent.slint") == None
|
diags = result.diagnostics
|
||||||
diags = compiler.diagnostics
|
|
||||||
assert len(diags) == 1
|
assert len(diags) == 1
|
||||||
|
|
||||||
assert diags[0].level == native.DiagnosticLevel.Error
|
assert diags[0].level == native.DiagnosticLevel.Error
|
||||||
|
|
|
@ -7,14 +7,14 @@ import gc
|
||||||
|
|
||||||
|
|
||||||
def test_callback_gc():
|
def test_callback_gc():
|
||||||
compiler = native.ComponentCompiler()
|
compiler = native.Compiler()
|
||||||
|
|
||||||
compdef = compiler.build_from_source("""
|
compdef = compiler.build_from_source("""
|
||||||
export component Test {
|
export component Test {
|
||||||
out property <string> test-value: "Ok";
|
out property <string> test-value: "Ok";
|
||||||
callback test-callback(string) -> string;
|
callback test-callback(string) -> string;
|
||||||
}
|
}
|
||||||
""", "")
|
""", "").component("Test")
|
||||||
assert compdef != None
|
assert compdef != None
|
||||||
|
|
||||||
instance = compdef.create()
|
instance = compdef.create()
|
||||||
|
|
|
@ -11,7 +11,7 @@ Brush = native.PyBrush
|
||||||
|
|
||||||
|
|
||||||
def test_property_access():
|
def test_property_access():
|
||||||
compiler = native.ComponentCompiler()
|
compiler = native.Compiler()
|
||||||
|
|
||||||
compdef = compiler.build_from_source("""
|
compdef = compiler.build_from_source("""
|
||||||
export global TestGlobal {
|
export global TestGlobal {
|
||||||
|
@ -41,7 +41,7 @@ def test_property_access():
|
||||||
|
|
||||||
callback test-callback();
|
callback test-callback();
|
||||||
}
|
}
|
||||||
""", os.path.join(os.path.dirname(__file__), "main.slint"))
|
""", os.path.join(os.path.dirname(__file__), "main.slint")).component("Test")
|
||||||
assert compdef != None
|
assert compdef != None
|
||||||
|
|
||||||
instance = compdef.create()
|
instance = compdef.create()
|
||||||
|
@ -121,7 +121,7 @@ def test_property_access():
|
||||||
|
|
||||||
|
|
||||||
def test_callbacks():
|
def test_callbacks():
|
||||||
compiler = native.ComponentCompiler()
|
compiler = native.Compiler()
|
||||||
|
|
||||||
compdef = compiler.build_from_source("""
|
compdef = compiler.build_from_source("""
|
||||||
export global TestGlobal {
|
export global TestGlobal {
|
||||||
|
@ -138,7 +138,7 @@ def test_callbacks():
|
||||||
}
|
}
|
||||||
callback void-callback();
|
callback void-callback();
|
||||||
}
|
}
|
||||||
""", "")
|
""", "").component("Test")
|
||||||
assert compdef != None
|
assert compdef != None
|
||||||
|
|
||||||
instance = compdef.create()
|
instance = compdef.create()
|
||||||
|
|
|
@ -12,9 +12,13 @@ def test_load_file(caplog):
|
||||||
|
|
||||||
assert "The property 'color' has been deprecated. Please use 'background' instead" in caplog.text
|
assert "The property 'color' has been deprecated. Please use 'background' instead" in caplog.text
|
||||||
|
|
||||||
assert list(module.__dict__.keys()) == ["App"]
|
assert len(list(module.__dict__.keys())) == 2
|
||||||
|
assert "App" in module.__dict__
|
||||||
|
assert "Diag" in module.__dict__
|
||||||
instance = module.App()
|
instance = module.App()
|
||||||
del instance
|
del instance
|
||||||
|
instance = module.Diag()
|
||||||
|
del instance
|
||||||
|
|
||||||
|
|
||||||
def test_load_file_fail():
|
def test_load_file_fail():
|
||||||
|
|
|
@ -9,7 +9,7 @@ export global MyGlobal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export component App {
|
export component App inherits Window {
|
||||||
in-out property <string> hello: "World";
|
in-out property <string> hello: "World";
|
||||||
callback say-hello(string) -> string;
|
callback say-hello(string) -> string;
|
||||||
callback say_hello_again(string) -> string;
|
callback say_hello_again(string) -> string;
|
||||||
|
@ -37,3 +37,5 @@ export component App {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export component Diag inherits Window { }
|
||||||
|
|
|
@ -6,7 +6,7 @@ from slint import models as models
|
||||||
|
|
||||||
|
|
||||||
def test_model_notify():
|
def test_model_notify():
|
||||||
compiler = native.ComponentCompiler()
|
compiler = native.Compiler()
|
||||||
|
|
||||||
compdef = compiler.build_from_source("""
|
compdef = compiler.build_from_source("""
|
||||||
export component App {
|
export component App {
|
||||||
|
@ -28,7 +28,7 @@ def test_model_notify():
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
""", "")
|
""", "").component("App")
|
||||||
assert compdef != None
|
assert compdef != None
|
||||||
|
|
||||||
instance = compdef.create()
|
instance = compdef.create()
|
||||||
|
@ -52,13 +52,13 @@ def test_model_notify():
|
||||||
|
|
||||||
|
|
||||||
def test_model_from_list():
|
def test_model_from_list():
|
||||||
compiler = native.ComponentCompiler()
|
compiler = native.Compiler()
|
||||||
|
|
||||||
compdef = compiler.build_from_source("""
|
compdef = compiler.build_from_source("""
|
||||||
export component App {
|
export component App {
|
||||||
in-out property<[int]> data: [1, 2, 3, 4];
|
in-out property<[int]> data: [1, 2, 3, 4];
|
||||||
}
|
}
|
||||||
""", "")
|
""", "").component("App")
|
||||||
assert compdef != None
|
assert compdef != None
|
||||||
|
|
||||||
instance = compdef.create()
|
instance = compdef.create()
|
||||||
|
@ -97,13 +97,13 @@ def test_python_model_iterable():
|
||||||
|
|
||||||
|
|
||||||
def test_rust_model_sequence():
|
def test_rust_model_sequence():
|
||||||
compiler = native.ComponentCompiler()
|
compiler = native.Compiler()
|
||||||
|
|
||||||
compdef = compiler.build_from_source("""
|
compdef = compiler.build_from_source("""
|
||||||
export component App {
|
export component App {
|
||||||
in-out property<[int]> data: [1, 2, 3, 4, 5];
|
in-out property<[int]> data: [1, 2, 3, 4, 5];
|
||||||
}
|
}
|
||||||
""", "")
|
""", "").component("App")
|
||||||
assert compdef != None
|
assert compdef != None
|
||||||
|
|
||||||
instance = compdef.create()
|
instance = compdef.create()
|
||||||
|
@ -117,7 +117,7 @@ def test_rust_model_sequence():
|
||||||
|
|
||||||
|
|
||||||
def test_model_writeback():
|
def test_model_writeback():
|
||||||
compiler = native.ComponentCompiler()
|
compiler = native.Compiler()
|
||||||
|
|
||||||
compdef = compiler.build_from_source("""
|
compdef = compiler.build_from_source("""
|
||||||
export component App {
|
export component App {
|
||||||
|
@ -131,7 +131,7 @@ def test_model_writeback():
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
""", "")
|
""", "").component("App")
|
||||||
assert compdef != None
|
assert compdef != None
|
||||||
|
|
||||||
instance = compdef.create()
|
instance = compdef.create()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue