Merge branch 'master' of github.com:slint-ui/slint

This commit is contained in:
Florian Blasius 2024-07-12 06:28:57 +02:00
commit 2b129b87c4
9 changed files with 118 additions and 30 deletions

View file

@ -229,7 +229,7 @@ The types used for properties in the Slint Language each translate to specific t
| `physical_length` | `float` | | | `physical_length` | `float` | |
| `duration` | `float` | The number of milliseconds | | `duration` | `float` | The number of milliseconds |
| `angle` | `float` | The angle in degrees | | `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` | | | array | `slint.Model` | |
### Arrays and Models ### Arrays and Models

View file

@ -41,6 +41,7 @@ fn slint(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<brush::PyColor>()?; m.add_class::<brush::PyColor>()?;
m.add_class::<brush::PyBrush>()?; m.add_class::<brush::PyBrush>()?;
m.add_class::<models::PyModelBase>()?; m.add_class::<models::PyModelBase>()?;
m.add_class::<value::PyStruct>()?;
m.add_function(wrap_pyfunction!(run_event_loop, m)?)?; m.add_function(wrap_pyfunction!(run_event_loop, m)?)?;
m.add_function(wrap_pyfunction!(quit_event_loop, m)?)?; m.add_function(wrap_pyfunction!(quit_event_loop, m)?)?;

View file

@ -178,11 +178,15 @@ def _build_class(compdef):
for global_name in compdef.globals: for global_name in compdef.globals:
global_class = _build_global_class(compdef, global_name) global_class = _build_global_class(compdef, global_name)
def mk_global(global_class):
def global_getter(self): def global_getter(self):
wrapper = global_class() wrapper = global_class()
setattr(wrapper, "__instance__", self.__instance__) setattr(wrapper, "__instance__", self.__instance__)
return wrapper return wrapper
properties_and_callbacks[global_name] = property(global_getter)
return property(global_getter)
properties_and_callbacks[global_name] = mk_global(global_class)
return type("SlintClassWrapper", (Component,), properties_and_callbacks) return type("SlintClassWrapper", (Component,), properties_and_callbacks)
@ -277,3 +281,4 @@ ListModel = models.ListModel
Model = models.Model Model = models.Model
Timer = native.Timer Timer = native.Timer
TimerMode = native.TimerMode TimerMode = native.TimerMode
Struct = native.PyStruct

View file

@ -22,6 +22,7 @@ def test_property_access():
export struct MyStruct { export struct MyStruct {
title: string, title: string,
finished: bool, finished: bool,
dash-prop: bool,
} }
export component Test { export component Test {
@ -36,6 +37,7 @@ def test_property_access():
in property <MyStruct> structprop: { in property <MyStruct> structprop: {
title: "builtin", title: "builtin",
finished: true, finished: true,
dash-prop: true,
}; };
in property <image> imageprop: @image-url("../../../examples/printerdemo/ui/images/cat.jpg"); 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) instance.set_property("boolprop", 0)
structval = instance.get_property("structprop") structval = instance.get_property("structprop")
assert isinstance(structval, dict) assert isinstance(structval, native.PyStruct)
assert structval == {'title': 'builtin', 'finished': True} assert structval.title == "builtin"
instance.set_property("structprop", {'title': 'new', 'finished': False}) assert structval.finished == True
assert instance.get_property("structprop") == { assert structval.dash_prop == True
'title': 'new', 'finished': False} 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") imageval = instance.get_property("imageprop")
assert imageval.width == 320 assert imageval.width == 320

View file

@ -45,6 +45,8 @@ def test_load_file_wrapper():
assert instance.MyGlobal.minus_one(100) == 99 assert instance.MyGlobal.minus_one(100) == 99
assert instance.SecondGlobal.second == "second"
del instance del instance

View file

@ -9,6 +9,10 @@ export global MyGlobal {
} }
} }
export global SecondGlobal {
out property <string> second: "second";
}
export component App inherits Window { 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;

View file

@ -2,7 +2,9 @@
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 // 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::prelude::*;
use pyo3::types::{IntoPyDict, PyDict}; use pyo3::types::PyDict;
use std::collections::HashMap;
pub struct PyValue(pub slint_interpreter::Value); pub struct PyValue(pub slint_interpreter::Value);
struct PyValueRef<'a>(&'a 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) crate::models::PyModelShared::rust_into_js_model(model)
.unwrap_or_else(|| crate::models::ReadOnlyRustModel::from(model).into_py(py)) .unwrap_or_else(|| crate::models::ReadOnlyRustModel::from(model).into_py(py))
} }
slint_interpreter::Value::Struct(structval) => structval slint_interpreter::Value::Struct(structval) => {
.iter() PyStruct { data: structval.clone() }.into_py(py)
.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::Brush(brush) => { slint_interpreter::Value::Brush(brush) => {
crate::brush::PyBrush::from(brush.clone()).into_py(py) crate::brush::PyBrush::from(brush.clone()).into_py(py)
} }
@ -90,13 +90,18 @@ impl FromPyObject<'_> for PyValue {
ob.extract::<PyRef<'_, crate::models::ReadOnlyRustModel>>() ob.extract::<PyRef<'_, crate::models::ReadOnlyRustModel>>()
.map(|rustmodel| slint_interpreter::Value::Model(rustmodel.0.clone())) .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(|_| { .or_else(|_| {
ob.extract::<&PyDict>().and_then(|dict| { ob.extract::<&PyDict>().and_then(|dict| {
let dict_items: Result<Vec<(String, slint_interpreter::Value)>, PyErr> = dict let dict_items: Result<Vec<(String, slint_interpreter::Value)>, PyErr> = dict
.iter() .iter()
.map(|(name, pyval)| { .map(|(name, pyval)| {
let name = name.extract::<&str>()?.to_string(); let name = name.extract::<&str>()?.to_string();
let slintval: PyValue = pyval.extract()?; let slintval = PyValue::extract(pyval)?;
Ok((name, slintval.0)) Ok((name, slintval.0))
}) })
.collect::<Result<Vec<(_, _)>, PyErr>>(); .collect::<Result<Vec<(_, _)>, PyErr>>();
@ -114,3 +119,64 @@ impl From<slint_interpreter::Value> for PyValue {
Self(value) 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)))
}
}

View file

@ -6,6 +6,7 @@ from datetime import timedelta, datetime
import os import os
import random import random
import itertools import itertools
import copy
import slint import slint
from slint import Color, ListModel, Timer, TimerMode from slint import Color, ListModel, Timer, TimerMode
@ -14,31 +15,32 @@ class MainWindow(slint.loader.memory.MainWindow):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
initial_tiles = self.memory_tiles initial_tiles = self.memory_tiles
tiles = ListModel(itertools.chain(initial_tiles, initial_tiles)) tiles = ListModel(itertools.chain(
map(copy.copy, initial_tiles), map(copy.copy, initial_tiles)))
random.shuffle(tiles) random.shuffle(tiles)
self.memory_tiles = tiles self.memory_tiles = tiles
@slint.callback @slint.callback
def check_if_pair_solved(self): def check_if_pair_solved(self):
flipped_tiles = [(index, tile) for index, tile in enumerate( flipped_tiles = [(index, copy.copy(tile)) for index, tile in enumerate(
self.memory_tiles) if tile["image-visible"] and not tile["solved"]] self.memory_tiles) if tile.image_visible and not tile.solved]
if len(flipped_tiles) == 2: if len(flipped_tiles) == 2:
tile1_index, tile1 = flipped_tiles[0] tile1_index, tile1 = flipped_tiles[0]
tile2_index, tile2 = flipped_tiles[1] tile2_index, tile2 = flipped_tiles[1]
is_pair_solved = tile1["image"].path == tile2["image"].path is_pair_solved = tile1.image.path == tile2.image.path
if is_pair_solved: if is_pair_solved:
tile1["solved"] = True tile1.solved = True
self.memory_tiles[tile1_index] = tile1 self.memory_tiles[tile1_index] = tile1
tile2["solved"] = True tile2.solved = True
self.memory_tiles[tile2_index] = tile2 self.memory_tiles[tile2_index] = tile2
else: else:
self.disable_tiles = True self.disable_tiles = True
def reenable_tiles(): def reenable_tiles():
self.disable_tiles = False self.disable_tiles = False
tile1["image-visible"] = False tile1.image_visible = False
self.memory_tiles[tile1_index] = tile1 self.memory_tiles[tile1_index] = tile1
tile2["image-visible"] = False tile2.image_visible = False
self.memory_tiles[tile2_index] = tile2 self.memory_tiles[tile2_index] = tile2
Timer.single_shot(timedelta(seconds=1), reenable_tiles) Timer.single_shot(timedelta(seconds=1), reenable_tiles)

View file

@ -5,6 +5,7 @@ from slint import Color, ListModel, Timer, TimerMode
import slint import slint
from datetime import timedelta, datetime from datetime import timedelta, datetime
import os import os
import copy
import sys import sys
sys.path.append(os.path.join(os.path.dirname(__file__), "..")) sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
@ -48,13 +49,13 @@ class MainWindow(slint.loader.ui.printerdemo.MainWindow):
def update_jobs(self): def update_jobs(self):
if len(self.printer_queue) <= 0: if len(self.printer_queue) <= 0:
return return
top_item = self.printer_queue[0] top_item = copy.copy(self.printer_queue[0])
top_item["progress"] += 1 top_item.progress += 1
if top_item["progress"] >= 100: if top_item.progress >= 100:
del self.printer_queue[0] del self.printer_queue[0]
if len(self.printer_queue) == 0: if len(self.printer_queue) == 0:
return return
top_item = self.printer_queue[0] top_item = copy.copy(self.printer_queue[0])
self.printer_queue[0] = top_item self.printer_queue[0] = top_item