mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 02:39:28 +00:00
Begin wrapping the component compiler
This commit is contained in:
parent
a2054e7ebd
commit
73024beb98
9 changed files with 646 additions and 0 deletions
|
@ -25,6 +25,8 @@ i-slint-renderer-skia = { version = "=1.4.0", path="../../internal/renderers/ski
|
|||
i-slint-core = { version = "=1.4.0", path="../../internal/core", features = ["ffi"] }
|
||||
slint-interpreter = { workspace = true, features = ["default", "display-diagnostics", "internal"] }
|
||||
pyo3 = { version = "0.20.0", features = ["extension-module", "indexmap"] }
|
||||
indexmap = { version = "2.1.0" }
|
||||
spin_on = "0.1"
|
||||
|
||||
[build-dependencies]
|
||||
i-slint-common = { version = "=1.4.0", path="../../internal/common" }
|
||||
|
|
432
api/python/interpreter.rs
Normal file
432
api/python/interpreter.rs
Normal file
|
@ -0,0 +1,432 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::PyTuple;
|
||||
|
||||
#[pyclass(unsendable)]
|
||||
pub struct ComponentCompiler {
|
||||
compiler: slint_interpreter::ComponentCompiler,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl ComponentCompiler {
|
||||
#[new]
|
||||
fn py_new() -> PyResult<Self> {
|
||||
Ok(Self { compiler: Default::default() })
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn get_include_paths(&self) -> PyResult<Vec<PathBuf>> {
|
||||
Ok(self.compiler.include_paths().clone())
|
||||
}
|
||||
|
||||
#[setter]
|
||||
fn set_include_paths(&mut self, paths: Vec<PathBuf>) {
|
||||
self.compiler.set_include_paths(paths)
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn get_style(&self) -> PyResult<Option<String>> {
|
||||
Ok(self.compiler.style().cloned())
|
||||
}
|
||||
|
||||
#[setter]
|
||||
fn set_style(&mut self, style: String) {
|
||||
self.compiler.set_style(style)
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn get_library_paths(&self) -> PyResult<HashMap<String, PathBuf>> {
|
||||
Ok(self.compiler.library_paths().clone())
|
||||
}
|
||||
|
||||
#[setter]
|
||||
fn set_library_paths(&mut self, libraries: HashMap<String, PathBuf>) {
|
||||
self.compiler.set_library_paths(libraries)
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn get_diagnostics(&self) -> Vec<PyDiagnostic> {
|
||||
self.compiler.diagnostics().iter().map(|diag| PyDiagnostic(diag.clone())).collect()
|
||||
}
|
||||
|
||||
#[setter]
|
||||
fn set_translation_domain(&mut self, domain: String) {
|
||||
self.compiler.set_translation_domain(domain)
|
||||
}
|
||||
|
||||
fn build_from_path(&mut self, path: PathBuf) -> Option<ComponentDefinition> {
|
||||
spin_on::spin_on(self.compiler.build_from_path(path))
|
||||
.map(|definition| ComponentDefinition { definition })
|
||||
}
|
||||
|
||||
fn build_from_source(
|
||||
&mut self,
|
||||
source_code: String,
|
||||
path: PathBuf,
|
||||
) -> Option<ComponentDefinition> {
|
||||
spin_on::spin_on(self.compiler.build_from_source(source_code, path))
|
||||
.map(|definition| ComponentDefinition { definition })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[pyclass(unsendable)]
|
||||
pub struct PyDiagnostic(slint_interpreter::Diagnostic);
|
||||
|
||||
#[pymethods]
|
||||
impl PyDiagnostic {
|
||||
#[getter]
|
||||
fn level(&self) -> PyDiagnosticLevel {
|
||||
match self.0.level() {
|
||||
slint_interpreter::DiagnosticLevel::Error => PyDiagnosticLevel::Error,
|
||||
slint_interpreter::DiagnosticLevel::Warning => PyDiagnosticLevel::Warning,
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn message(&self) -> &str {
|
||||
self.0.message()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn column_number(&self) -> usize {
|
||||
self.0.line_column().0
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn line_number(&self) -> usize {
|
||||
self.0.line_column().1
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn source_file(&self) -> Option<PathBuf> {
|
||||
self.0.source_file().map(|path| path.to_path_buf())
|
||||
}
|
||||
|
||||
fn __str__(&self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "DiagnosticLevel")]
|
||||
pub enum PyDiagnosticLevel {
|
||||
Error,
|
||||
Warning,
|
||||
}
|
||||
|
||||
#[pyclass(unsendable)]
|
||||
struct ComponentDefinition {
|
||||
definition: slint_interpreter::ComponentDefinition,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl ComponentDefinition {
|
||||
#[getter]
|
||||
fn name(&self) -> &str {
|
||||
self.definition.name()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn properties(&self) -> IndexMap<String, PyValueType> {
|
||||
self.definition.properties().map(|(name, ty)| (name, ty.into())).collect()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn callbacks(&self) -> Vec<String> {
|
||||
self.definition.callbacks().collect()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn globals(&self) -> Vec<String> {
|
||||
self.definition.globals().collect()
|
||||
}
|
||||
|
||||
fn global_properties(&self, name: &str) -> Option<IndexMap<String, PyValueType>> {
|
||||
self.definition
|
||||
.global_properties(name)
|
||||
.map(|propiter| propiter.map(|(name, ty)| (name, ty.into())).collect())
|
||||
}
|
||||
|
||||
fn global_callbacks(&self, name: &str) -> Option<Vec<String>> {
|
||||
self.definition.global_callbacks(name).map(|callbackiter| callbackiter.collect())
|
||||
}
|
||||
|
||||
fn create(&self) -> Result<ComponentInstance, PyPlatformError> {
|
||||
Ok(ComponentInstance { instance: self.definition.create()? })
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "ValueType")]
|
||||
pub enum PyValueType {
|
||||
Void,
|
||||
Number,
|
||||
String,
|
||||
Bool,
|
||||
Model,
|
||||
Struct,
|
||||
Brush,
|
||||
Image,
|
||||
}
|
||||
|
||||
impl From<slint_interpreter::ValueType> for PyValueType {
|
||||
fn from(value: slint_interpreter::ValueType) -> Self {
|
||||
match value {
|
||||
slint_interpreter::ValueType::Bool => PyValueType::Bool,
|
||||
slint_interpreter::ValueType::Void => PyValueType::Void,
|
||||
slint_interpreter::ValueType::Number => PyValueType::Number,
|
||||
slint_interpreter::ValueType::String => PyValueType::String,
|
||||
slint_interpreter::ValueType::Model => PyValueType::Model,
|
||||
slint_interpreter::ValueType::Struct => PyValueType::Struct,
|
||||
slint_interpreter::ValueType::Brush => PyValueType::Brush,
|
||||
slint_interpreter::ValueType::Image => PyValueType::Image,
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(unsendable)]
|
||||
struct ComponentInstance {
|
||||
instance: slint_interpreter::ComponentInstance,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl ComponentInstance {
|
||||
#[getter]
|
||||
fn definition(&self) -> ComponentDefinition {
|
||||
ComponentDefinition { definition: self.instance.definition() }
|
||||
}
|
||||
|
||||
fn get_property(&self, name: &str) -> Result<PyValue, PyGetPropertyError> {
|
||||
Ok(self.instance.get_property(name)?.into())
|
||||
}
|
||||
|
||||
fn set_property(&self, name: &str, value: &PyAny) -> PyResult<()> {
|
||||
let pv: PyValue = value.extract()?;
|
||||
Ok(self.instance.set_property(name, pv.0).map_err(|e| PySetPropertyError(e))?)
|
||||
}
|
||||
|
||||
fn get_global_property(
|
||||
&self,
|
||||
global_name: &str,
|
||||
prop_name: &str,
|
||||
) -> Result<PyValue, PyGetPropertyError> {
|
||||
Ok(self.instance.get_global_property(global_name, prop_name)?.into())
|
||||
}
|
||||
|
||||
fn set_global_property(
|
||||
&self,
|
||||
global_name: &str,
|
||||
prop_name: &str,
|
||||
value: &PyAny,
|
||||
) -> PyResult<()> {
|
||||
let pv: PyValue = value.extract()?;
|
||||
Ok(self
|
||||
.instance
|
||||
.set_global_property(global_name, prop_name, pv.0)
|
||||
.map_err(|e| PySetPropertyError(e))?)
|
||||
}
|
||||
|
||||
#[pyo3(signature = (callback_name, *args))]
|
||||
fn invoke(&self, callback_name: &str, args: &PyTuple) -> PyResult<PyValue> {
|
||||
let mut rust_args = vec![];
|
||||
for arg in args.iter() {
|
||||
let pv: PyValue = arg.extract()?;
|
||||
rust_args.push(pv.0)
|
||||
}
|
||||
Ok(self.instance.invoke(callback_name, &rust_args).map_err(|e| PyInvokeError(e))?.into())
|
||||
}
|
||||
|
||||
#[pyo3(signature = (global_name, callback_name, *args))]
|
||||
fn invoke_global(
|
||||
&self,
|
||||
global_name: &str,
|
||||
callback_name: &str,
|
||||
args: &PyTuple,
|
||||
) -> PyResult<PyValue> {
|
||||
let mut rust_args = vec![];
|
||||
for arg in args.iter() {
|
||||
let pv: PyValue = arg.extract()?;
|
||||
rust_args.push(pv.0)
|
||||
}
|
||||
Ok(self
|
||||
.instance
|
||||
.invoke_global(global_name, callback_name, &rust_args)
|
||||
.map_err(|e| PyInvokeError(e))?
|
||||
.into())
|
||||
}
|
||||
|
||||
fn set_callback(
|
||||
&self,
|
||||
callback_name: &str,
|
||||
callable: PyObject,
|
||||
) -> Result<(), PySetCallbackError> {
|
||||
Ok(self
|
||||
.instance
|
||||
.set_callback(callback_name, move |args| {
|
||||
Python::with_gil(|py| {
|
||||
let py_args = PyTuple::new(py, args.iter().map(|v| PyValue(v.clone())));
|
||||
let result =
|
||||
callable.call(py, py_args, None).expect("invoking python callback failed");
|
||||
let pv: PyValue = result.extract(py).expect(
|
||||
"unable to convert python callback result to slint interpreter value",
|
||||
);
|
||||
pv.0
|
||||
})
|
||||
})?
|
||||
.into())
|
||||
}
|
||||
|
||||
fn set_global_callback(
|
||||
&self,
|
||||
global_name: &str,
|
||||
callback_name: &str,
|
||||
callable: PyObject,
|
||||
) -> Result<(), PySetCallbackError> {
|
||||
Ok(self
|
||||
.instance
|
||||
.set_global_callback(global_name, callback_name, move |args| {
|
||||
Python::with_gil(|py| {
|
||||
let py_args = PyTuple::new(py, args.iter().map(|v| PyValue(v.clone())));
|
||||
let result =
|
||||
callable.call(py, py_args, None).expect("invoking python callback failed");
|
||||
let pv: PyValue = result.extract(py).expect(
|
||||
"unable to convert python callback result to slint interpreter value",
|
||||
);
|
||||
pv.0
|
||||
})
|
||||
})?
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
struct PyValue(slint_interpreter::Value);
|
||||
|
||||
impl IntoPy<PyObject> for PyValue {
|
||||
fn into_py(self, py: Python<'_>) -> PyObject {
|
||||
match self.0 {
|
||||
slint_interpreter::Value::Void => ().into_py(py),
|
||||
slint_interpreter::Value::Number(num) => num.into_py(py),
|
||||
slint_interpreter::Value::String(str) => str.into_py(py),
|
||||
slint_interpreter::Value::Bool(b) => b.into_py(py),
|
||||
slint_interpreter::Value::Image(_) => todo!(),
|
||||
slint_interpreter::Value::Model(_) => todo!(),
|
||||
slint_interpreter::Value::Struct(_) => todo!(),
|
||||
slint_interpreter::Value::Brush(_) => todo!(),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPyObject for PyValue {
|
||||
fn to_object(&self, py: Python<'_>) -> PyObject {
|
||||
match &self.0 {
|
||||
slint_interpreter::Value::Void => ().into_py(py),
|
||||
slint_interpreter::Value::Number(num) => num.into_py(py),
|
||||
slint_interpreter::Value::String(str) => str.into_py(py),
|
||||
slint_interpreter::Value::Bool(b) => b.into_py(py),
|
||||
slint_interpreter::Value::Image(_) => todo!(),
|
||||
slint_interpreter::Value::Model(_) => todo!(),
|
||||
slint_interpreter::Value::Struct(_) => todo!(),
|
||||
slint_interpreter::Value::Brush(_) => todo!(),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromPyObject<'_> for PyValue {
|
||||
fn extract(ob: &PyAny) -> PyResult<Self> {
|
||||
Ok(PyValue(
|
||||
ob.extract::<bool>()
|
||||
.map(|b| slint_interpreter::Value::Bool(b))
|
||||
.or_else(|_| {
|
||||
ob.extract::<&'_ str>().map(|s| slint_interpreter::Value::String(s.into()))
|
||||
})
|
||||
.or_else(|_| {
|
||||
ob.extract::<f64>().map(|num| slint_interpreter::Value::Number(num))
|
||||
})?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<slint_interpreter::Value> for PyValue {
|
||||
fn from(value: slint_interpreter::Value) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
struct PyGetPropertyError(slint_interpreter::GetPropertyError);
|
||||
|
||||
impl From<PyGetPropertyError> for PyErr {
|
||||
fn from(err: PyGetPropertyError) -> Self {
|
||||
pyo3::exceptions::PyValueError::new_err(err.0.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<slint_interpreter::GetPropertyError> for PyGetPropertyError {
|
||||
fn from(err: slint_interpreter::GetPropertyError) -> Self {
|
||||
Self(err)
|
||||
}
|
||||
}
|
||||
|
||||
struct PySetPropertyError(slint_interpreter::SetPropertyError);
|
||||
|
||||
impl From<PySetPropertyError> for PyErr {
|
||||
fn from(err: PySetPropertyError) -> Self {
|
||||
pyo3::exceptions::PyValueError::new_err(err.0.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<slint_interpreter::SetPropertyError> for PySetPropertyError {
|
||||
fn from(err: slint_interpreter::SetPropertyError) -> Self {
|
||||
Self(err)
|
||||
}
|
||||
}
|
||||
|
||||
struct PyPlatformError(slint_interpreter::PlatformError);
|
||||
|
||||
impl From<PyPlatformError> for PyErr {
|
||||
fn from(err: PyPlatformError) -> Self {
|
||||
pyo3::exceptions::PyRuntimeError::new_err(err.0.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<slint_interpreter::PlatformError> for PyPlatformError {
|
||||
fn from(err: slint_interpreter::PlatformError) -> Self {
|
||||
Self(err)
|
||||
}
|
||||
}
|
||||
|
||||
struct PyInvokeError(slint_interpreter::InvokeError);
|
||||
|
||||
impl From<PyInvokeError> for PyErr {
|
||||
fn from(err: PyInvokeError) -> Self {
|
||||
pyo3::exceptions::PyRuntimeError::new_err(err.0.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<slint_interpreter::InvokeError> for PyInvokeError {
|
||||
fn from(err: slint_interpreter::InvokeError) -> Self {
|
||||
Self(err)
|
||||
}
|
||||
}
|
||||
|
||||
struct PySetCallbackError(slint_interpreter::SetCallbackError);
|
||||
|
||||
impl From<PySetCallbackError> for PyErr {
|
||||
fn from(err: PySetCallbackError) -> Self {
|
||||
pyo3::exceptions::PyRuntimeError::new_err(err.0.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<slint_interpreter::SetCallbackError> for PySetCallbackError {
|
||||
fn from(err: slint_interpreter::SetCallbackError) -> Self {
|
||||
Self(err)
|
||||
}
|
||||
}
|
|
@ -1,9 +1,16 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
mod interpreter;
|
||||
use interpreter::{ComponentCompiler, PyDiagnostic, PyDiagnosticLevel, PyValueType};
|
||||
|
||||
use pyo3::prelude::*;
|
||||
|
||||
#[pymodule]
|
||||
fn slint(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_class::<ComponentCompiler>()?;
|
||||
m.add_class::<PyValueType>()?;
|
||||
m.add_class::<PyDiagnosticLevel>()?;
|
||||
m.add_class::<PyDiagnostic>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
10
api/python/noxfile.py
Normal file
10
api/python/noxfile.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
import nox
|
||||
|
||||
@nox.session
|
||||
def python(session: nox.Session):
|
||||
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
|
||||
session.install(".[dev]")
|
||||
session.run("pytest")
|
19
api/python/pyproject.toml
Normal file
19
api/python/pyproject.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
[build-system]
|
||||
requires = ["maturin>=1,<2"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "pyslint"
|
||||
version = "1.4.0"
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Developers",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Rust",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest"]
|
66
api/python/tests/test_compiler.py
Normal file
66
api/python/tests/test_compiler.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
# Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
import pytest
|
||||
import slint
|
||||
from slint import ValueType;
|
||||
|
||||
def test_basic_compiler():
|
||||
compiler = slint.ComponentCompiler()
|
||||
|
||||
assert compiler.include_paths == []
|
||||
compiler.include_paths = ["testing"]
|
||||
assert compiler.include_paths == ["testing"]
|
||||
|
||||
assert compiler.build_from_source("Garbage", "") == None
|
||||
|
||||
compdef = compiler.build_from_source("""
|
||||
export global TestGlobal {
|
||||
in property <string> theglobalprop;
|
||||
callback globallogic();
|
||||
}
|
||||
|
||||
export component Test {
|
||||
in property <string> strprop;
|
||||
in property <int> intprop;
|
||||
in property <float> floatprop;
|
||||
in property <bool> boolprop;
|
||||
in property <image> imgprop;
|
||||
in property <brush> brushprop;
|
||||
in property <color> colprop;
|
||||
in property <[string]> modelprop;
|
||||
|
||||
callback test-callback();
|
||||
}
|
||||
""", "")
|
||||
assert compdef != None
|
||||
|
||||
assert compdef.name == "Test"
|
||||
|
||||
props = [(name, type) for name, type in compdef.properties.items()]
|
||||
assert props == [('boolprop', ValueType.Bool), ('brushprop', ValueType.Brush), ('colprop', ValueType.Brush), ('floatprop', ValueType.Number), ('imgprop', ValueType.Image), ('intprop', ValueType.Number), ('modelprop', ValueType.Model), ('strprop', ValueType.String)]
|
||||
|
||||
assert compdef.callbacks == ["test-callback"]
|
||||
|
||||
assert compdef.globals == ["TestGlobal"]
|
||||
|
||||
assert compdef.global_properties("Garbage") == None
|
||||
assert [(name, type) for name, type in compdef.global_properties("TestGlobal").items()] == [('theglobalprop', ValueType.String)]
|
||||
|
||||
assert compdef.global_callbacks("Garbage") == None
|
||||
assert compdef.global_callbacks("TestGlobal") == ["globallogic"]
|
||||
|
||||
instance = compdef.create()
|
||||
assert instance != None
|
||||
|
||||
def test_compiler_build_from_path():
|
||||
compiler = slint.ComponentCompiler()
|
||||
|
||||
assert len(compiler.diagnostics) == 0
|
||||
|
||||
assert compiler.build_from_path("Nonexistent.slint") == None
|
||||
diags = compiler.diagnostics
|
||||
assert len(diags) == 1
|
||||
|
||||
assert diags[0].level == slint.DiagnosticLevel.Error
|
||||
assert diags[0].message.startswith("Could not load Nonexistent.slint:")
|
108
api/python/tests/test_instance.py
Normal file
108
api/python/tests/test_instance.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
# Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
import pytest
|
||||
import slint
|
||||
from slint import ValueType;
|
||||
|
||||
def test_property_access():
|
||||
compiler = slint.ComponentCompiler()
|
||||
|
||||
compdef = compiler.build_from_source("""
|
||||
export global TestGlobal {
|
||||
in property <string> theglobalprop: "Hey";
|
||||
callback globallogic();
|
||||
}
|
||||
|
||||
export component Test {
|
||||
in property <string> strprop: "Hello";
|
||||
in property <int> intprop: 42;
|
||||
in property <float> floatprop: 100;
|
||||
in property <bool> boolprop: true;
|
||||
in property <image> imgprop;
|
||||
in property <brush> brushprop;
|
||||
in property <color> colprop;
|
||||
in property <[string]> modelprop;
|
||||
|
||||
callback test-callback();
|
||||
}
|
||||
""", "")
|
||||
assert compdef != None
|
||||
|
||||
instance = compdef.create()
|
||||
assert instance != None
|
||||
|
||||
with pytest.raises(ValueError, match="no such property"):
|
||||
instance.set_property("nonexistent", 42)
|
||||
|
||||
assert instance.get_property("strprop") == "Hello"
|
||||
instance.set_property("strprop", "World")
|
||||
assert instance.get_property("strprop") == "World"
|
||||
with pytest.raises(ValueError, match="wrong type"):
|
||||
instance.set_property("strprop", 42)
|
||||
|
||||
assert instance.get_property("intprop") == 42
|
||||
instance.set_property("intprop", 100)
|
||||
assert instance.get_property("intprop") == 100
|
||||
with pytest.raises(ValueError, match="wrong type"):
|
||||
instance.set_property("intprop", False)
|
||||
|
||||
assert instance.get_property("floatprop") == 100
|
||||
instance.set_property("floatprop", 42)
|
||||
assert instance.get_property("floatprop") == 42
|
||||
with pytest.raises(ValueError, match="wrong type"):
|
||||
instance.set_property("floatprop", "Blah")
|
||||
|
||||
assert instance.get_property("boolprop") == True
|
||||
instance.set_property("boolprop", False)
|
||||
assert instance.get_property("boolprop") == False
|
||||
with pytest.raises(ValueError, match="wrong type"):
|
||||
instance.set_property("boolprop", 0)
|
||||
|
||||
with pytest.raises(ValueError, match="no such property"):
|
||||
instance.set_global_property("nonexistent", "theglobalprop", 42)
|
||||
with pytest.raises(ValueError, match="no such property"):
|
||||
instance.set_global_property("TestGlobal", "nonexistent", 42)
|
||||
|
||||
assert instance.get_global_property("TestGlobal", "theglobalprop") == "Hey"
|
||||
instance.set_global_property("TestGlobal", "theglobalprop", "Ok")
|
||||
assert instance.get_global_property("TestGlobal", "theglobalprop") == "Ok"
|
||||
|
||||
def test_callbacks():
|
||||
compiler = slint.ComponentCompiler()
|
||||
|
||||
compdef = compiler.build_from_source("""
|
||||
export global TestGlobal {
|
||||
callback globallogic(string) -> string;
|
||||
globallogic(value) => {
|
||||
return "global " + value;
|
||||
}
|
||||
}
|
||||
|
||||
export component Test {
|
||||
callback test-callback(string) -> string;
|
||||
test-callback(value) => {
|
||||
return "local " + value;
|
||||
}
|
||||
}
|
||||
""", "")
|
||||
assert compdef != None
|
||||
|
||||
instance = compdef.create()
|
||||
assert instance != None
|
||||
|
||||
assert instance.invoke("test-callback", "foo") == "local foo"
|
||||
|
||||
assert instance.invoke_global("TestGlobal", "globallogic", "foo") == "global foo"
|
||||
|
||||
with pytest.raises(RuntimeError, match="no such callback"):
|
||||
instance.set_callback("non-existent", lambda x: x)
|
||||
|
||||
instance.set_callback("test-callback", lambda x: "python " + x)
|
||||
assert instance.invoke("test-callback", "foo") == "python foo"
|
||||
|
||||
with pytest.raises(RuntimeError, match="no such callback"):
|
||||
instance.set_global_callback("TestGlobal", "non-existent", lambda x: x)
|
||||
|
||||
instance.set_global_callback("TestGlobal", "globallogic", lambda x: "python global " + x)
|
||||
assert instance.invoke_global("TestGlobal", "globallogic", "foo") == "python global foo"
|
Loading…
Add table
Add a link
Reference in a new issue