1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
3
4use std::cell::RefCell;
5use std::collections::HashMap;
6use std::path::PathBuf;
7use std::rc::Rc;
8
9use slint_interpreter::{ComponentHandle, Value};
10
11use indexmap::IndexMap;
12use pyo3::gc::PyVisit;
13use pyo3::prelude::*;
14use pyo3::types::PyTuple;
15use pyo3::PyTraverseError;
16
17use crate::errors::{
18 PyGetPropertyError, PyInvokeError, PyPlatformError, PySetCallbackError, PySetPropertyError,
19};
20use crate::value::PyValue;
21
22#[pyclass(unsendable)]
23pub struct ComponentCompiler {
24 compiler: slint_interpreter::ComponentCompiler,
25}
26
27#[pymethods]
28impl ComponentCompiler {
29 #[new]
30 fn py_new() -> PyResult<Self> {
31 Ok(Self { compiler: Default::default() })
32 }
33
34 #[getter]
35 fn get_include_paths(&self) -> PyResult<Vec<PathBuf>> {
36 Ok(self.compiler.include_paths().clone())
37 }
38
39 #[setter]
40 fn set_include_paths(&mut self, paths: Vec<PathBuf>) {
41 self.compiler.set_include_paths(paths)
42 }
43
44 #[getter]
45 fn get_style(&self) -> PyResult<Option<String>> {
46 Ok(self.compiler.style().cloned())
47 }
48
49 #[setter]
50 fn set_style(&mut self, style: String) {
51 self.compiler.set_style(style)
52 }
53
54 #[getter]
55 fn get_library_paths(&self) -> PyResult<HashMap<String, PathBuf>> {
56 Ok(self.compiler.library_paths().clone())
57 }
58
59 #[setter]
60 fn set_library_paths(&mut self, libraries: HashMap<String, PathBuf>) {
61 self.compiler.set_library_paths(libraries)
62 }
63
64 #[getter]
65 fn get_diagnostics(&self) -> Vec<PyDiagnostic> {
66 self.compiler.diagnostics().iter().map(|diag| PyDiagnostic(diag.clone())).collect()
67 }
68
69 #[setter]
70 fn set_translation_domain(&mut self, domain: String) {
71 self.compiler.set_translation_domain(domain)
72 }
73
74 fn build_from_path(&mut self, path: PathBuf) -> Option<ComponentDefinition> {
75 spin_on::spin_on(self.compiler.build_from_path(path))
76 .map(|definition| ComponentDefinition { definition })
77 }
78
79 fn build_from_source(
80 &mut self,
81 source_code: String,
82 path: PathBuf,
83 ) -> Option<ComponentDefinition> {
84 spin_on::spin_on(self.compiler.build_from_source(source_code, path))
85 .map(|definition| ComponentDefinition { definition })
86 }
87}
88
89#[derive(Debug, Clone)]
90#[pyclass(unsendable)]
91pub struct PyDiagnostic(slint_interpreter::Diagnostic);
92
93#[pymethods]
94impl PyDiagnostic {
95 #[getter]
96 fn level(&self) -> PyDiagnosticLevel {
97 match self.0.level() {
98 slint_interpreter::DiagnosticLevel::Error => PyDiagnosticLevel::Error,
99 slint_interpreter::DiagnosticLevel::Warning => PyDiagnosticLevel::Warning,
100 _ => unimplemented!(),
101 }
102 }
103
104 #[getter]
105 fn message(&self) -> &str {
106 self.0.message()
107 }
108
109 #[getter]
110 fn column_number(&self) -> usize {
111 self.0.line_column().0
112 }
113
114 #[getter]
115 fn line_number(&self) -> usize {
116 self.0.line_column().1
117 }
118
119 #[getter]
120 fn source_file(&self) -> Option<PathBuf> {
121 self.0.source_file().map(|path| path.to_path_buf())
122 }
123
124 fn __str__(&self) -> String {
125 self.0.to_string()
126 }
127}
128
129#[pyclass(name = "DiagnosticLevel")]
130pub enum PyDiagnosticLevel {
131 Error,
132 Warning,
133}
134
135#[pyclass(unsendable)]
136struct ComponentDefinition {
137 definition: slint_interpreter::ComponentDefinition,
138}
139
140#[pymethods]
141impl ComponentDefinition {
142 #[getter]
143 fn name(&self) -> &str {
144 self.definition.name()
145 }
146
147 #[getter]
148 fn properties(&self) -> IndexMap<String, PyValueType> {
149 self.definition.properties().map(|(name, ty)| (name, ty.into())).collect()
150 }
151
152 #[getter]
153 fn callbacks(&self) -> Vec<String> {
154 self.definition.callbacks().collect()
155 }
156
157 #[getter]
158 fn globals(&self) -> Vec<String> {
159 self.definition.globals().collect()
160 }
161
162 fn global_properties(&self, name: &str) -> Option<IndexMap<String, PyValueType>> {
163 self.definition
164 .global_properties(name)
165 .map(|propiter| propiter.map(|(name, ty)| (name, ty.into())).collect())
166 }
167
168 fn global_callbacks(&self, name: &str) -> Option<Vec<String>> {
169 self.definition.global_callbacks(name).map(|callbackiter| callbackiter.collect())
170 }
171
172 fn create(&self) -> Result<ComponentInstance, crate::errors::PyPlatformError> {
173 Ok(ComponentInstance {
174 instance: self.definition.create()?,
175 callbacks: Default::default(),
176 global_callbacks: Default::default(),
177 })
178 }
179}
180
181#[pyclass(name = "ValueType")]
182pub enum PyValueType {
183 Void,
184 Number,
185 String,
186 Bool,
187 Model,
188 Struct,
189 Brush,
190 Image,
191}
192
193impl From<slint_interpreter::ValueType> for PyValueType {
194 fn from(value: slint_interpreter::ValueType) -> Self {
195 match value {
196 slint_interpreter::ValueType::Bool => PyValueType::Bool,
197 slint_interpreter::ValueType::Void => PyValueType::Void,
198 slint_interpreter::ValueType::Number => PyValueType::Number,
199 slint_interpreter::ValueType::String => PyValueType::String,
200 slint_interpreter::ValueType::Model => PyValueType::Model,
201 slint_interpreter::ValueType::Struct => PyValueType::Struct,
202 slint_interpreter::ValueType::Brush => PyValueType::Brush,
203 slint_interpreter::ValueType::Image => PyValueType::Image,
204 _ => unimplemented!(),
205 }
206 }
207}
208
209#[pyclass(unsendable, weakref)]
210struct ComponentInstance {
211 instance: slint_interpreter::ComponentInstance,
212 callbacks: GcVisibleCallbacks,
213 global_callbacks: HashMap<String, GcVisibleCallbacks>,
214}
215
216#[pymethods]
217impl ComponentInstance {
218 #[getter]
219 fn definition(&self) -> ComponentDefinition {
220 ComponentDefinition { definition: self.instance.definition() }
221 }
222
223 fn get_property(&self, name: &str) -> Result<PyValue, PyGetPropertyError> {
224 Ok(self.instance.get_property(name)?.into())
225 }
226
227 fn set_property(&self, name: &str, value: &PyAny) -> PyResult<()> {
228 let pv: PyValue = value.extract()?;
229 Ok(self.instance.set_property(name, pv.0).map_err(|e| PySetPropertyError(e))?)
230 }
231
232 fn get_global_property(
233 &self,
234 global_name: &str,
235 prop_name: &str,
236 ) -> Result<PyValue, PyGetPropertyError> {
237 Ok(self.instance.get_global_property(global_name, prop_name)?.into())
238 }
239
240 fn set_global_property(
241 &self,
242 global_name: &str,
243 prop_name: &str,
244 value: &PyAny,
245 ) -> PyResult<()> {
246 let pv: PyValue = value.extract()?;
247 Ok(self
248 .instance
249 .set_global_property(global_name, prop_name, pv.0)
250 .map_err(|e| PySetPropertyError(e))?)
251 }
252
253 #[pyo3(signature = (callback_name, *args))]
254 fn invoke(&self, callback_name: &str, args: &PyTuple) -> PyResult<PyValue> {
255 let mut rust_args = vec![];
256 for arg in args.iter() {
257 let pv: PyValue = arg.extract()?;
258 rust_args.push(pv.0)
259 }
260 Ok(self.instance.invoke(callback_name, &rust_args).map_err(|e| PyInvokeError(e))?.into())
261 }
262
263 #[pyo3(signature = (global_name, callback_name, *args))]
264 fn invoke_global(
265 &self,
266 global_name: &str,
267 callback_name: &str,
268 args: &PyTuple,
269 ) -> PyResult<PyValue> {
270 let mut rust_args = vec![];
271 for arg in args.iter() {
272 let pv: PyValue = arg.extract()?;
273 rust_args.push(pv.0)
274 }
275 Ok(self
276 .instance
277 .invoke_global(global_name, callback_name, &rust_args)
278 .map_err(|e| PyInvokeError(e))?
279 .into())
280 }
281
282 fn set_callback(&self, name: &str, callable: PyObject) -> Result<(), PySetCallbackError> {
283 let rust_cb = self.callbacks.register(name.to_string(), callable);
284 Ok(self.instance.set_callback(name, rust_cb)?.into())
285 }
286
287 fn set_global_callback(
288 &mut self,
289 global_name: &str,
290 callback_name: &str,
291 callable: PyObject,
292 ) -> Result<(), PySetCallbackError> {
293 let rust_cb = self
294 .global_callbacks
295 .entry(global_name.to_string())
296 .or_default()
297 .register(callback_name.to_string(), callable);
298 Ok(self.instance.set_global_callback(global_name, callback_name, rust_cb)?.into())
299 }
300
301 fn show(&self) -> Result<(), PyPlatformError> {
302 Ok(self.instance.show()?)
303 }
304
305 fn hide(&self) -> Result<(), PyPlatformError> {
306 Ok(self.instance.hide()?)
307 }
308
309 fn run(&self) -> Result<(), PyPlatformError> {
310 Ok(self.instance.run()?)
311 }
312
313 fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
314 self.callbacks.__traverse__(&visit)?;
315 for global_callbacks in self.global_callbacks.values() {
316 global_callbacks.__traverse__(&visit)?;
317 }
318 Ok(())
319 }
320
321 fn __clear__(&mut self) {
322 self.callbacks.__clear__();
323 self.global_callbacks.clear();
324 }
325}
326
327#[derive(Default)]
328struct GcVisibleCallbacks {
329 callables: Rc<RefCell<HashMap<String, PyObject>>>,
330}
331
332impl GcVisibleCallbacks {
333 fn register(&self, name: String, callable: PyObject) -> impl Fn(&[Value]) -> Value + 'static {
334 self.callables.borrow_mut().insert(name.clone(), callable);
335
336 let callables = self.callables.clone();
337
338 move |args| {
339 let callables = callables.borrow();
340 let callable = callables.get(&name).unwrap();
341 Python::with_gil(|py| {
342 let py_args = PyTuple::new(py, args.iter().map(|v| PyValue(v.clone())));
343 let result = match callable.call(py, py_args, None) {
344 Ok(result) => result,
345 Err(err) => {
346 eprintln!(
347 "Python: Invoking python callback for {name} threw an exception: {err}"
348 );
349 return Value::Void;
350 }
351 };
352 let pv: PyValue = match result.extract(py) {
353 Ok(value) => value,
354 Err(err) => {
355 eprintln!("Python: Unable to convert return value of Python callback for {name} to Slint value: {err}");
356 return Value::Void;
357 }
358 };
359 pv.0
360 })
361 }
362 }
363
364 fn __traverse__(&self, visit: &PyVisit<'_>) -> Result<(), PyTraverseError> {
365 for callback in self.callables.borrow().values() {
366 visit.call(callback)?;
367 }
368 Ok(())
369 }
370
371 fn __clear__(&mut self) {
372 self.callables.borrow_mut().clear();
373 }
374}
375