Add support for mapping image properties

This exposes a slint.Image class, which has a load_from_path class
method as well as size/width/height properties.

cc #4202
This commit is contained in:
Simon Hausmann 2024-02-06 11:52:28 +01:00 committed by Simon Hausmann
parent 9aa931f1f8
commit 93efd74e24
6 changed files with 97 additions and 3 deletions

View file

@ -86,3 +86,17 @@ impl From<slint_interpreter::SetCallbackError> for PySetCallbackError {
Self(err)
}
}
pub struct PyLoadImageError(pub slint_interpreter::LoadImageError);
impl From<PyLoadImageError> for PyErr {
fn from(err: PyLoadImageError) -> Self {
pyo3::exceptions::PyRuntimeError::new_err(err.0.to_string())
}
}
impl From<slint_interpreter::LoadImageError> for PyLoadImageError {
fn from(err: slint_interpreter::LoadImageError) -> Self {
Self(err)
}
}

55
api/python/image.rs Normal file
View file

@ -0,0 +1,55 @@
// 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 pyo3::prelude::*;
#[pyclass(unsendable)]
pub struct PyImage {
pub image: slint_interpreter::Image,
}
#[pymethods]
impl PyImage {
#[new]
fn py_new() -> PyResult<Self> {
Ok(Self { image: Default::default() })
}
#[getter]
fn size(&self) -> PyResult<(u32, u32)> {
Ok(self.image.size().into())
}
#[getter]
fn width(&self) -> PyResult<u32> {
Ok(self.image.size().width)
}
#[getter]
fn height(&self) -> PyResult<u32> {
Ok(self.image.size().height)
}
#[getter]
fn path(&self) -> PyResult<Option<&std::path::Path>> {
Ok(self.image.path())
}
#[staticmethod]
fn load_from_path(path: std::path::PathBuf) -> Result<Self, crate::errors::PyLoadImageError> {
let image = slint_interpreter::Image::load_from_path(&path)?;
Ok(Self { image })
}
#[staticmethod]
fn load_from_svg_data(data: &[u8]) -> Result<Self, crate::errors::PyLoadImageError> {
let image = slint_interpreter::Image::load_from_svg_data(data)?;
Ok(Self { image })
}
}
impl From<&slint_interpreter::Image> for PyImage {
fn from(image: &slint_interpreter::Image) -> Self {
Self { image: image.clone() }
}
}

View file

@ -1,6 +1,7 @@
// 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 image;
mod interpreter;
use interpreter::{ComponentCompiler, PyDiagnostic, PyDiagnosticLevel, PyValueType};
mod errors;
@ -28,6 +29,7 @@ fn slint(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
.map_err(|e| errors::PyPlatformError(e))?;
m.add_class::<ComponentCompiler>()?;
m.add_class::<image::PyImage>()?;
m.add_class::<PyValueType>()?;
m.add_class::<PyDiagnosticLevel>()?;
m.add_class::<PyDiagnostic>()?;

View file

@ -8,3 +8,5 @@ def load_file(path):
compdef = compiler.build_from_path(path)
instance = compdef.create()
return instance
Image = native.PyImage

View file

@ -3,7 +3,8 @@
import pytest
from slint import slint as native
from slint.slint import ValueType;
from slint.slint import ValueType, PyImage;
import os
def test_property_access():
compiler = native.ComponentCompiler()
@ -32,10 +33,11 @@ def test_property_access():
title: "builtin",
finished: true,
};
in property <image> imageprop: @image-url("../../../examples/printerdemo/ui/images/cat.jpg");
callback test-callback();
}
""", "")
""", os.path.join(os.path.dirname(__file__), "main.slint"))
assert compdef != None
instance = compdef.create()
@ -74,6 +76,19 @@ def test_property_access():
instance.set_property("structprop", {'title': 'new', 'finished': False})
assert instance.get_property("structprop") == {'title': 'new', 'finished': False}
imageval = instance.get_property("imageprop")
assert imageval.width == 320
assert imageval.height == 480
assert "cat.jpg" in imageval.path
with pytest.raises(RuntimeError, match="The image cannot be loaded"):
PyImage.load_from_path("non-existent.png")
instance.set_property("imageprop", PyImage.load_from_path(os.path.join(os.path.dirname(__file__), "../../../examples/iot-dashboard/images/humidity.png")))
imageval = instance.get_property("imageprop")
assert imageval.size == (36, 36)
assert "humidity.png" in imageval.path
with pytest.raises(TypeError, match="'int' object cannot be converted to 'PyString'"):
instance.set_property("structprop", {42: 'wrong'})

View file

@ -34,7 +34,9 @@ impl<'a> ToPyObject for PyValueRef<'a> {
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::Image(image) => {
crate::image::PyImage::from(image).into_py(py)
}
slint_interpreter::Value::Model(_) => todo!(),
slint_interpreter::Value::Struct(structval) => structval
.iter()
@ -60,6 +62,10 @@ impl FromPyObject<'_> for PyValue {
ob.extract::<&'_ str>().map(|s| slint_interpreter::Value::String(s.into()))
})
.or_else(|_| ob.extract::<f64>().map(|num| slint_interpreter::Value::Number(num)))
.or_else(|_| {
ob.extract::<PyRef<'_, crate::image::PyImage>>()
.map(|pyimg| slint_interpreter::Value::Image(pyimg.image.clone()))
})
.or_else(|_| {
ob.extract::<&PyDict>().and_then(|dict| {
let dict_items: Result<Vec<(String, slint_interpreter::Value)>, PyErr> = dict