Repository: slint-ui/slint (language: rust)

Issue:
slint-viewer fails to convert KeyEvent (and others) into string
<!-- Thanks for opening an issue! 🤗 -->
<!-- IMPORTANT: This issue tracker is for tracking bugs and features request. For questions or help on how to use Slint, please go to the "Discussions" tab -->
<!-- Please mention your platform and the programming language you are using Slint with -->
<!-- For bugs, please give steps on how to reproduce. What is the expected behavior and what do you see instead. -->
<!-- If possible, please include relevant code snippets, in code blocks (```  ```)-->

## Reproducer

`foo.slint`
```slint
export component Foo inherits Window {
    callback bar(KeyEvent);

    FocusScope {
        key-released(event) => { bar(event); accept }
    }
}
```

```sh
$ slint-viewer --on bar 'echo' foo.slint
```

## Current behaviour

It outputs an error like this for each callback invocation:
```
Error: Cannot convert argument to string: Value::Struct(Struct({"text": Value::String("l"), "repeat": Value::Bool(false), "modifiers": Value::Struct(Struct({"control": Value::Bool(false), "meta": Value::Bool(false), "shift": Value::Bool(false), "alt": Value::Bool(false)}))}))
```

## Expected behaviour

`KeyEvent`s (and other structs) are converted into strings and passed as arguments.


Discussion / hints:
We cannot serialize everything to String.
In particular, right now, we only support number, string, bool, and image that have a paths

It's unclear how we should convert struct or enums or models. We could do json.  But it's a bit strange that a string is not in json though.
I don't think JSON would be a bad idea, to be honest. Tools like jq have become quite widespread and are really useful when doing shell scripts.

This should not be hard to implement, by handling the Value::Struct and value::Model in https://github.com/slint-ui/slint/blob/5325f7234cb0662c23e0bdb6aa4731cca0ec7971/tools/viewer/main.rs#L409 to convert to json.
Note that there is already code that convert these to json in the same file (the `to_json` function that can be moved to the global scope)

Fix patch:
diff --git a/tools/viewer/main.rs b/tools/viewer/main.rs
index 84d9516e764..8ce6c1344f2 100644
--- a/tools/viewer/main.rs
+++ b/tools/viewer/main.rs
@@ -95,6 +95,33 @@ struct Cli {
     translation_dir: Option<std::path::PathBuf>,
 }
 
+fn to_json(val: slint_interpreter::Value) -> Option<serde_json::Value> {
+    match val {
+        slint_interpreter::Value::Number(x) => Some(x.into()),
+        slint_interpreter::Value::String(x) => Some(x.as_str().into()),
+        slint_interpreter::Value::Bool(x) => Some(x.into()),
+        slint_interpreter::Value::Model(model) => {
+            let mut res = Vec::with_capacity(model.row_count());
+            for i in 0..model.row_count() {
+                res.push(to_json(model.row_data(i).unwrap())?);
+            }
+            Some(serde_json::Value::Array(res))
+        }
+        slint_interpreter::Value::Struct(st) => {
+            let mut obj = serde_json::Map::new();
+            for (k, v) in st.iter() {
+                obj.insert(k.into(), to_json(v.clone())?);
+            }
+            Some(obj.into())
+        }
+        slint_interpreter::Value::EnumerationValue(_class, value) => Some(value.as_str().into()),
+        slint_interpreter::Value::Image(image) => {
+            image.path().and_then(|path| path.to_str()).map(|path| path.into())
+        }
+        _ => None,
+    }
+}
+
 thread_local! {static CURRENT_INSTANCE: std::cell::RefCell<Option<ComponentInstance>> = Default::default();}
 static EXIT_CODE: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
 
@@ -155,34 +182,6 @@ fn main() -> Result<()> {
     if let Some(data_path) = args.save_data {
         let mut obj = serde_json::Map::new();
         for (name, _) in c.properties() {
-            fn to_json(val: slint_interpreter::Value) -> Option<serde_json::Value> {
-                match val {
-                    slint_interpreter::Value::Number(x) => Some(x.into()),
-                    slint_interpreter::Value::String(x) => Some(x.as_str().into()),
-                    slint_interpreter::Value::Bool(x) => Some(x.into()),
-                    slint_interpreter::Value::Model(model) => {
-                        let mut res = Vec::with_capacity(model.row_count());
-                        for i in 0..model.row_count() {
-                            res.push(to_json(model.row_data(i).unwrap())?);
-                        }
-                        Some(serde_json::Value::Array(res))
-                    }
-                    slint_interpreter::Value::Struct(st) => {
-                        let mut obj = serde_json::Map::new();
-                        for (k, v) in st.iter() {
-                            obj.insert(k.into(), to_json(v.clone())?);
-                        }
-                        Some(obj.into())
-                    }
-                    slint_interpreter::Value::EnumerationValue(_class, value) => {
-                        Some(value.as_str().into())
-                    }
-                    slint_interpreter::Value::Image(image) => {
-                        image.path().and_then(|path| path.to_str()).map(|path| path.into())
-                    }
-                    _ => None,
-                }
-            }
             if let Some(v) = to_json(component.get_property(&name).unwrap()) {
                 obj.insert(name.into(), v);
             }
@@ -474,6 +473,13 @@ fn execute_cmd(cmd: &str, callback_args: &[Value]) -> Result<()> {
                 Value::Image(img) => {
                     img.path().map(|p| p.to_string_lossy()).unwrap_or_default().into()
                 }
+                Value::Struct(st) => {
+                    let mut obj = serde_json::Map::new();
+                    for (k, v) in st.iter() {
+                        obj.insert(k.into(), to_json(v.clone()).unwrap());
+                    }
+                    serde_json::to_string_pretty(&obj)?
+                }
                 _ => return Err(format!("Cannot convert argument to string: {:?}", v).into()),
             })
         })


Test patch: