mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 02:39:28 +00:00
Python: Improve Struct mapping
When reading, create the local equivalent of a dataclass, so that access doesn't require ["foo"] key syntax. Also implement the copy protocol, so that we can safely make clones of the references returned by the ListModel.
This commit is contained in:
parent
0b6381d012
commit
e3aab79fdb
7 changed files with 103 additions and 25 deletions
|
@ -229,7 +229,7 @@ The types used for properties in the Slint Language each translate to specific t
|
|||
| `physical_length` | `float` | |
|
||||
| `duration` | `float` | The number of milliseconds |
|
||||
| `angle` | `float` | The angle in degrees |
|
||||
| structure | `dict` | Structures are mapped to Python dictionaries where each structure field is an item. |
|
||||
| structure | `dict`/`Struct` | When reading, structures are mapped to data classes, when writing dicts are also accepted. |
|
||||
| array | `slint.Model` | |
|
||||
|
||||
### Arrays and Models
|
||||
|
|
|
@ -41,6 +41,7 @@ fn slint(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|||
m.add_class::<brush::PyColor>()?;
|
||||
m.add_class::<brush::PyBrush>()?;
|
||||
m.add_class::<models::PyModelBase>()?;
|
||||
m.add_class::<value::PyStruct>()?;
|
||||
m.add_function(wrap_pyfunction!(run_event_loop, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(quit_event_loop, m)?)?;
|
||||
|
||||
|
|
|
@ -281,3 +281,4 @@ ListModel = models.ListModel
|
|||
Model = models.Model
|
||||
Timer = native.Timer
|
||||
TimerMode = native.TimerMode
|
||||
Struct = native.PyStruct
|
||||
|
|
|
@ -22,6 +22,7 @@ def test_property_access():
|
|||
export struct MyStruct {
|
||||
title: string,
|
||||
finished: bool,
|
||||
dash-prop: bool,
|
||||
}
|
||||
|
||||
export component Test {
|
||||
|
@ -36,6 +37,7 @@ def test_property_access():
|
|||
in property <MyStruct> structprop: {
|
||||
title: "builtin",
|
||||
finished: true,
|
||||
dash-prop: true,
|
||||
};
|
||||
in property <image> imageprop: @image-url("../../../examples/printerdemo/ui/images/cat.jpg");
|
||||
|
||||
|
@ -75,11 +77,16 @@ def test_property_access():
|
|||
instance.set_property("boolprop", 0)
|
||||
|
||||
structval = instance.get_property("structprop")
|
||||
assert isinstance(structval, dict)
|
||||
assert structval == {'title': 'builtin', 'finished': True}
|
||||
instance.set_property("structprop", {'title': 'new', 'finished': False})
|
||||
assert instance.get_property("structprop") == {
|
||||
'title': 'new', 'finished': False}
|
||||
assert isinstance(structval, native.PyStruct)
|
||||
assert structval.title == "builtin"
|
||||
assert structval.finished == True
|
||||
assert structval.dash_prop == True
|
||||
instance.set_property(
|
||||
"structprop", {'title': 'new', 'finished': False, 'dash_prop': False})
|
||||
structval = instance.get_property("structprop")
|
||||
assert structval.title == "new"
|
||||
assert structval.finished == False
|
||||
assert structval.dash_prop == False
|
||||
|
||||
imageval = instance.get_property("imageprop")
|
||||
assert imageval.width == 320
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{IntoPyDict, PyDict};
|
||||
use pyo3::types::PyDict;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct PyValue(pub slint_interpreter::Value);
|
||||
struct PyValueRef<'a>(&'a slint_interpreter::Value);
|
||||
|
@ -41,11 +43,9 @@ impl<'a> ToPyObject for PyValueRef<'a> {
|
|||
crate::models::PyModelShared::rust_into_js_model(model)
|
||||
.unwrap_or_else(|| crate::models::ReadOnlyRustModel::from(model).into_py(py))
|
||||
}
|
||||
slint_interpreter::Value::Struct(structval) => structval
|
||||
.iter()
|
||||
.map(|(name, val)| (name.to_string().into_py(py), PyValueRef(val).into_py(py)))
|
||||
.into_py_dict_bound(py)
|
||||
.into_py(py),
|
||||
slint_interpreter::Value::Struct(structval) => {
|
||||
PyStruct { data: structval.clone() }.into_py(py)
|
||||
}
|
||||
slint_interpreter::Value::Brush(brush) => {
|
||||
crate::brush::PyBrush::from(brush.clone()).into_py(py)
|
||||
}
|
||||
|
@ -90,13 +90,18 @@ impl FromPyObject<'_> for PyValue {
|
|||
ob.extract::<PyRef<'_, crate::models::ReadOnlyRustModel>>()
|
||||
.map(|rustmodel| slint_interpreter::Value::Model(rustmodel.0.clone()))
|
||||
})
|
||||
.or_else(|_| {
|
||||
ob.extract::<PyRef<'_, PyStruct>>().and_then(|pystruct| {
|
||||
Ok(slint_interpreter::Value::Struct(pystruct.data.clone()))
|
||||
})
|
||||
})
|
||||
.or_else(|_| {
|
||||
ob.extract::<&PyDict>().and_then(|dict| {
|
||||
let dict_items: Result<Vec<(String, slint_interpreter::Value)>, PyErr> = dict
|
||||
.iter()
|
||||
.map(|(name, pyval)| {
|
||||
let name = name.extract::<&str>()?.to_string();
|
||||
let slintval: PyValue = pyval.extract()?;
|
||||
let slintval = PyValue::extract(pyval)?;
|
||||
Ok((name, slintval.0))
|
||||
})
|
||||
.collect::<Result<Vec<(_, _)>, PyErr>>();
|
||||
|
@ -114,3 +119,64 @@ impl From<slint_interpreter::Value> for PyValue {
|
|||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(subclass, unsendable)]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PyStruct {
|
||||
data: slint_interpreter::Struct,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyStruct {
|
||||
#[new]
|
||||
fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn __getattr__(&self, key: &str) -> PyResult<PyValue> {
|
||||
self.data.get_field(key).map_or_else(
|
||||
|| {
|
||||
Err(pyo3::exceptions::PyAttributeError::new_err(format!(
|
||||
"Python: No such field {key} on PyStruct"
|
||||
)))
|
||||
},
|
||||
|value| Ok(value.clone().into()),
|
||||
)
|
||||
}
|
||||
fn __setattr__(&mut self, py: Python<'_>, key: String, value: PyObject) -> PyResult<()> {
|
||||
let pv: PyValue = value.extract(py)?;
|
||||
self.data.set_field(key, pv.0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn __iter__(slf: PyRef<'_, Self>) -> PyStructFieldIterator {
|
||||
PyStructFieldIterator {
|
||||
inner: slf
|
||||
.data
|
||||
.iter()
|
||||
.map(|(name, val)| (name.to_string(), val.clone()))
|
||||
.collect::<HashMap<_, _>>()
|
||||
.into_iter(),
|
||||
}
|
||||
}
|
||||
|
||||
fn __copy__(&self) -> Self {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(unsendable)]
|
||||
struct PyStructFieldIterator {
|
||||
inner: std::collections::hash_map::IntoIter<String, slint_interpreter::Value>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyStructFieldIterator {
|
||||
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
|
||||
slf
|
||||
}
|
||||
|
||||
fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<(String, PyValue)> {
|
||||
slf.inner.next().map(|(name, val)| (name, PyValue(val)))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue