Add support for timers and run/quit_event_loop

This commit is contained in:
Simon Hausmann 2023-12-14 17:31:09 +01:00 committed by Simon Hausmann
parent ebaecbcb7e
commit 33a1d07226
5 changed files with 148 additions and 1 deletions

View file

@ -24,8 +24,9 @@ i-slint-backend-testing = { version = "=1.4.0", path="../../internal/backends/te
i-slint-renderer-skia = { version = "=1.4.0", path="../../internal/renderers/skia", optional = true, features = ["x11", "wayland"] } i-slint-renderer-skia = { version = "=1.4.0", path="../../internal/renderers/skia", optional = true, features = ["x11", "wayland"] }
i-slint-core = { version = "=1.4.0", path="../../internal/core", features = ["ffi"] } i-slint-core = { version = "=1.4.0", path="../../internal/core", features = ["ffi"] }
slint-interpreter = { workspace = true, features = ["default", "display-diagnostics", "internal"] } slint-interpreter = { workspace = true, features = ["default", "display-diagnostics", "internal"] }
pyo3 = { version = "0.20.0", features = ["extension-module", "indexmap"] } pyo3 = { version = "0.20.0", features = ["extension-module", "indexmap", "chrono"] }
indexmap = { version = "2.1.0" } indexmap = { version = "2.1.0" }
chrono = "0.4"
spin_on = "0.1" spin_on = "0.1"
[build-dependencies] [build-dependencies]

View file

@ -45,6 +45,20 @@ impl From<slint_interpreter::PlatformError> for PyPlatformError {
} }
} }
pub struct PyEventLoopError(pub slint_interpreter::EventLoopError);
impl From<PyEventLoopError> for PyErr {
fn from(err: PyEventLoopError) -> Self {
pyo3::exceptions::PyRuntimeError::new_err(err.0.to_string())
}
}
impl From<slint_interpreter::EventLoopError> for PyEventLoopError {
fn from(err: slint_interpreter::EventLoopError) -> Self {
Self(err)
}
}
pub struct PyInvokeError(pub slint_interpreter::InvokeError); pub struct PyInvokeError(pub slint_interpreter::InvokeError);
impl From<PyInvokeError> for PyErr { impl From<PyInvokeError> for PyErr {

View file

@ -4,16 +4,37 @@
mod interpreter; mod interpreter;
use interpreter::{ComponentCompiler, PyDiagnostic, PyDiagnosticLevel, PyValueType}; use interpreter::{ComponentCompiler, PyDiagnostic, PyDiagnosticLevel, PyValueType};
mod errors; mod errors;
mod timer;
mod value; mod value;
#[pyfunction]
fn run_event_loop() -> Result<(), errors::PyPlatformError> {
slint_interpreter::run_event_loop().map_err(|e| e.into())
}
#[pyfunction]
fn quit_event_loop() -> Result<(), errors::PyEventLoopError> {
slint_interpreter::quit_event_loop().map_err(|e| e.into())
}
use pyo3::prelude::*; use pyo3::prelude::*;
#[pymodule] #[pymodule]
fn slint(_py: Python<'_>, m: &PyModule) -> PyResult<()> { fn slint(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
i_slint_backend_selector::with_platform(|_b| {
// Nothing to do, just make sure a backend was created
Ok(())
})
.map_err(|e| errors::PyPlatformError(e))?;
m.add_class::<ComponentCompiler>()?; m.add_class::<ComponentCompiler>()?;
m.add_class::<PyValueType>()?; m.add_class::<PyValueType>()?;
m.add_class::<PyDiagnosticLevel>()?; m.add_class::<PyDiagnosticLevel>()?;
m.add_class::<PyDiagnostic>()?; m.add_class::<PyDiagnostic>()?;
m.add_class::<timer::PyTimerMode>()?;
m.add_class::<timer::PyTimer>()?;
m.add_function(wrap_pyfunction!(run_event_loop, m)?)?;
m.add_function(wrap_pyfunction!(quit_event_loop, m)?)?;
Ok(()) Ok(())
} }

View file

@ -0,0 +1,26 @@
# 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
from datetime import timedelta
def test_timer():
global counter
counter = 0
def quit_after_two_invocations():
global counter
counter = counter + 1
if counter >= 2:
slint.quit_event_loop()
test_timer = slint.Timer()
test_timer.start(slint.TimerMode.Repeated, timedelta(milliseconds=100), quit_after_two_invocations)
slint.run_event_loop()
test_timer.stop()
assert(counter == 2)
def test_single_shot():
slint.Timer.single_shot(timedelta(milliseconds=100), slint.quit_event_loop)
slint.run_event_loop()

85
api/python/timer.rs Normal file
View file

@ -0,0 +1,85 @@
// 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::*;
#[derive(Copy, Clone)]
#[pyclass(name = "TimerMode")]
pub enum PyTimerMode {
/// A SingleShot timer is fired only once.
SingleShot,
/// A Repeated timer is fired repeatedly until it is stopped or dropped.
Repeated,
}
impl From<PyTimerMode> for i_slint_core::timers::TimerMode {
fn from(value: PyTimerMode) -> Self {
match value {
PyTimerMode::SingleShot => i_slint_core::timers::TimerMode::SingleShot,
PyTimerMode::Repeated => i_slint_core::timers::TimerMode::Repeated,
}
}
}
#[pyclass(name = "Timer")]
pub struct PyTimer {
timer: i_slint_core::timers::Timer,
}
#[pymethods]
impl PyTimer {
#[new]
fn py_new() -> Self {
PyTimer { timer: Default::default() }
}
fn start(
&self,
mode: PyTimerMode,
interval: chrono::Duration,
callback: PyObject,
) -> PyResult<()> {
let interval = interval
.to_std()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?;
self.timer.start(mode.into(), interval, move || {
Python::with_gil(|py| {
callback.call0(py).expect("unexpected failure running python timer callback");
});
});
Ok(())
}
#[staticmethod]
fn single_shot(duration: chrono::Duration, callback: PyObject) -> PyResult<()> {
let duration = duration
.to_std()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?;
i_slint_core::timers::Timer::single_shot(duration, move || {
Python::with_gil(|py| {
callback.call0(py).expect("unexpected failure running python timer callback");
});
});
Ok(())
}
fn stop(&self) {
self.timer.stop();
}
fn restart(&self) {
self.timer.restart();
}
fn running(&self) -> bool {
self.timer.running()
}
fn set_interval(&self, interval: chrono::Duration) -> PyResult<()> {
let interval = interval
.to_std()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?;
self.timer.set_interval(interval);
Ok(())
}
}