slint + Rust auf allen[tm] Plattformen.

OP #8091725
Lesenswert?

Ich wollte mal was probieren ...

Voraussetzungen:
1.) Ich hab keine Lust in C++ zu programmieren noch will ich Kotlin lernen.
2.) Ich hab mir vor langer Zeit mal Qt angeschaut, hat mir nicht gefallen (könnte jetzt aber besser sein).
3.) Ich will/muss den GUI-Teil (slint) unbedingt dynamisch laden. Angeblich geht das (bis jetzt ohne Nachweis).
4.) Der Code muss auf Android, (Linux, Win und (MacOS und iOS)) laufen. Fokus ist Android, entwickeln und testen auf MacOS.

Also hab ich mein inzwischen leider angestaubtes Rust-Wissen rausgekramt und mich mit slint beschäftigt. Wer mal mit Tk (z.B. Tkl/Tk) gearbeitet hat, kommt mit slint schnell zurecht. Allerdings find ich die Kombination Tkl/Tk unschlagbar, da kommt wohl slint nicht ran. Nicht so schlimm.

Das mit den enums und i32 in Rust ist schon arg .... äh ... anstrengend. Man verzeihe mir hier die Brechstange 'unsafe{}'.
Unschön ist, dass ich noch keine Möglichkeit gefunden hab, die enums in slint reinzubekommen. Wird wohl auch nie gehen, erst recht nicht dynamisch.

Ich werd mir jetzt ein Android Tablett kaufen (SW soll nicht auf einem Smartphone laufen). Denn angeblich soll man inzwischen auch auf Android selbst Rust-Entwicklung machen können. "Rustroid" nennt sich das Dingens für USD 7. Wenn das nicht wirklich geht, ist es auch nicht schlimm. Wäre aber witzig.

Der code ist aus einem YT-Video entstanden mit massiven Änderungen. Bitte nicht glauben, dass das ein irgendwie sinnvoller Taschenrechner ist, darum ging es mir absolut nicht!

Macht sonst noch jemand was mit slint? Und wenn ja, warum nicht mehr?

Angehängte Dateien:
OP #8092359
Lesenswert?

Das Selbstgespräch geht weiter ... :-)
Aber nur aus dokumentarischen Gründen.

Es klappt! .slint zur Laufzeit laden, compilieren und dann auch tatsächlich benutzen.
Die Nuss war, wie man die callback-Funktion setzt. 'set_global_callback' und nicht 'set_callback' wie es in den einfacheren Beispielen beschrieben ist.

Hier der code. Die Logik für die Verarbeitung hab ich ausgelassen. Die ist jetzt das schnöde "println!("clicked {}", arg);"

1
// cargo add slint_interpreter
2
// cargo add spin_on
3

4
// https://docs.slint.dev/latest/docs/rust/slint_interpreter/struct.ComponentInstance#method.set_callback
5
// https://github.com/slint-ui/slint/discussions/4061
6

7
use slint_interpreter::{Compiler, ComponentHandle, SharedString, Value};
8
use spin_on;
9

10
#[derive(PartialEq)]
11
#[repr(i32)]
12
enum ButtonID {
13
    ID0,
14
    ID1,
15
    ID2,
16
    ID3,
17
    ID4,
18
    ID5,
19
    ID6,
20
    ID7,
21
    ID8,
22
    ID9,
23
    IDPlus = 20,
24
    IDMinus,
25
    IDOver,
26
    IDTimes,
27
    IDEquals,
28
    IDSentinel = 99,
29
}
30

31
struct CalcState {
32
    prev_value: i32,
33
    current_value: i32,
34
    operator: ButtonID,
35
}
36

37
fn main() {
38
    let compiler = Compiler::default();
39
    let result = spin_on::spin_on(compiler.build_from_path("./src/dyn_slint.slint"));
40

41
    let diagnostics: Vec<_> = result.diagnostics().collect();
42
    for diag in &diagnostics {
43
        println!("{:?}", diag);
44
    }
45

46
    if let Some(definition) = result.component("App") {
47
        println!("Component 'App' loaded and compiled");
48
    } else {
49
        println!("Component not found");
50
        return;
51
    }
52

53
    let mut state: CalcState = CalcState {
54
        prev_value: 0,
55
        current_value: 0,
56
        operator: ButtonID::IDSentinel,
57
    };
58

59
    let definition = result.component("App");
60
    let instance = definition.unwrap().create().unwrap();
61

62
    instance
63
        .set_global_callback(
64
            "CalcLogic",
65
            "button_pressed",
66
            move |args: &[Value]| -> Value {
67
                // ---- Das ist die eigentliche callback-Funktion
68
                let arg: u32 = args[0].clone().try_into().unwrap();
69
                println!("clicked {}", arg);
70

71
                Value::from(0)
72
            },
73
        )
74
        .unwrap();
75

76
    instance.run().unwrap();
77
}

Das schöne an den dynamisch geladenen .slint ist, dass der Benutzer das an seine Bedürfnisse anpassen kann/könnte, ohne dass ich neu kompilieren muss oder er/ich verschiedene compilate brauch. Für den .slint-File gibt es von slint auch einen Viewer, er/ich muss also nicht im Blindflug arbeiten.
Das Ganze geht auch in C++ und anderen Sprachen, die ich mir aber nicht antue.

OP #8093305
Lesenswert?

Nick schrieb:

Das schöne an den dynamisch geladenen .slint ist, dass der Benutzer das an seine Bedürfnisse anpassen kann/könnte,

Man kann den slint-code auch leserlicher schreiben. ;-)
Wenn man verstanden hat, wo es lang geht.

1
/*
2

3
General layout
4

5
 +-------------------------------------+---------------------------+
6
 |                                     |                           |
7
 |                                     |                           |
8
 |         Value-Grid                  |     Button-Grid           |
9
 |                                     |                           |
10
 |                                     |                           |
11
 |                                     |                           |
12
 |                                     |                           |
13
 |                                     |                           |
14
 +-------------------------------------+---------------------------+
15
 |                          Extra-Area                             |
16
 +-----------------------------------------------------------------+
17

18
 */
19

20
import { VerticalBox, GroupBox } from "std-widgets.slint";
21
    export global ButtonAreaLogic {
22
        callback clicked(int);
23
    }
24
    export global ValueAreaLogic {
25
        callback clicked(int);
26
    }
27

28
// ---- This is the component that shows a SINGLE axis name and its value
29
component ValueView inherits GridLayout {
30
    in property <string> name;
31
    in property <string> value; // string, not float, because of formatting
32
    in property <int> id;
33
    // height: 80px;
34
    // min_width: 200px;
35

36
    Rectangle {
37
        border-width: 2px;
38
        border-radius: 5px;
39
        border-color: darkgray;
40
        background: lightgray;
41
        width: 100px; // Adjust here for longer axis/value names
42
        height: 50px;
43
        Text {horizontal-alignment: left; text: name; font-size: 40px; font-weight: FontWeight.extra-bold; }
44
    }
45
    Rectangle {
46
        ta := TouchArea { clicked => { ValueAreaLogic.clicked(root.id); }}
47
        horizontal-stretch: 1;
48
        background: #05050510;
49
        Text { text: value; font-size: 40px; horizontal-alignment: right;}
50
    }
51
}
52

53
// ---- This represents the Value-Grid. Expand, by adding further "Row"-blocks, to the number of values
54
// you want to display.component Mapping to the input channels happens by the id that must match the XML-section above
55
component ValueGrid inherits GridLayout {
56
    GridLayout {
57
        spacing: 10px;
58
        padding: 10px;
59

60
        // First value ...
61
        Row {
62
            ValueView {
63
                id: 1000;           // Nust match XML's <Slint id = "x" />
64
                name:"U";           // Just a placeholder, will be overwritten by the definition in the XML-section
65
                value: "4711.0015"; // Just a placeholder
66
            }
67
        }
68

69
        // Second value. WATCH the id! ...
70
        Row {
71
            ValueView { id: 1001; name:"I"; value: "-3.1415"; }
72
        }
73
        // Further values ...
74
        Row {
75
            ValueView { id: 1002; name:"P"; value: "88.1"; }
76
        }
77
    }
78
}

Das war ein Versuch, das Ganze erweiterbar zu gestalten. Das Problem dabei ist aber, dass man hinterher sein muss, dass Konstanten (die IDs) zwischen Rust-code und slint-code manuell synchon gehalten werden müssen. Das sind mir zu viele Fallen.
Ich verfolg jetzt die Idee weiter, den slint-code zur Laufzeit zu generieren und dann den generierten code zu compilieren (dynamic slint). Dazu werde ich das alles in XML packen. In den Templates werden dann an nur wenigen Stellen automatisch Ersetzungen gemacht (/@ValueView.name@/) Das soll dann in etwa so aussehen: Nur ein Entwurf, Denkansatz!

1
<Config>
2
  <Mapping>
3
    <Node name = "X1" template = "ValueView">
4
      <Math>channel(0) * 3.5</Math>
5
      <Format trailing = "4" />
6
      <Slint ID = "1000" />
7
      <Referencing/>
8
    </Node>
9
  </Mapping>
10
  <Template name = "ValueView">
11
  component ValueView inherits GridLayout {
12
      in property <string> value; // string, not float, because of formatting
13
      in property <int> id;
14
      // height: 80px;
15
      // min_width: 200px;
16

17
      Rectangle {
18
          border-width: 2px;
19
          border-radius: 5px;
20
          border-color: darkgray;
21
          background: lightgray;
22
          width: 100px; // Adjust here for longer axis/value names
23
          height: 50px;
24
          Text {horizontal-alignment: left; text: name; font-size: 40px; font-weight: FontWeight.extra-bold; }
25
      }
26
      Rectangle {
27
          ta := TouchArea { clicked => { ValueAreaLogic.clicked(root.id); }}
28
          horizontal-stretch: 1;
29
          background: #05050510;
30
          Text { text: /*@ValueView.name@*/; font-size: 40px; horizontal-alignment: right;}
31
      }
32
  }
33
  <Template>
34
</Config>

"Zufälligerweise" hab ich mir einen XML-Parser in Rust geschrieben. Indem man den slint (in Fragmenten) in XML packt, kann man einen exakt passenden slint code generieren ohne an x Plätzen auf Konsistenz achten zu müssen.

Nach wie vor gefällt mir das slint. Bis auf die Tatsache, dass ich Text (noch) nicht rechtsbündig hinbekomme.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren