Rust Slint虚拟键盘源码分享

一、效果展示

在这里插入图片描述

在这里插入图片描述

二、源码分享

1、main.rs

use std::any::Any;
use slint::{PlatformError};
slint::include_modules!();

fn main() ->Result<(), PlatformError>{

    let app: MainWindow  = MainWindow::new()?;
    let weak: slint::Weak<MainWindow> = app.as_weak();

    app.global::<DataAdapter>().on_btn_clicked({
        let weak = weak.clone();
        move |text|{
            if let Some(strong) = weak.upgrade(){
                let adapter = strong.global::<DataAdapter>();
                println!("{}",text);
            }
        }
    });
    app.global::<VirtualKeyboardHandler>().on_key_pressed({
        move |key| {
            weak.unwrap()
                .window()
                .dispatch_event(slint::platform::WindowEvent::KeyPressed { text: key.clone() });
            weak.unwrap()
                .window()
                .dispatch_event(slint::platform::WindowEvent::KeyReleased { text: key });
        }
    });


    let _ = app.run();


    Ok(())
}                             


2、icons.slint

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT

export global Icons {
    out property <image> arrow-up: @image-url("image/arrow-up.svg");
    out property <image> arrow-left: @image-url("image/arrow-left.svg");
    out property <image> arrow-right: @image-url("image/arrow-right.svg");
    out property <image> chevron-left: @image-url("image/chevron-left.svg");
    out property <image> arrow-circle-o-left: @image-url("image/arrow-circle-o-left.svg");
    out property <image> globe: @image-url("image/globe.svg");
    out property <image> expand-more: @image-url("image/expand-more.svg");
}

3、main.slint

import { AboutSlint, VerticalBox, LineEdit, HorizontalBox, Button, GroupBox, GridBox, 
    ComboBox, Spinner, Slider, ListView, Palette, ProgressIndicator, CheckBox, Switch } from "std-widgets.slint";
import { DataAdapter} from "models.slint";
export { DataAdapter}

import { VirtualKeyboardHandler, VirtualKeyboard, KeyModel } from "virtual_keyboard.slint";

export { VirtualKeyboardHandler, KeyModel }





export component MainWindow inherits Window {
    width: 800px;
    height: 600px;
    background: #020414;
    Rectangle {
        VerticalLayout {
            alignment: start;
            padding: 16px;
            spacing: 8px;

            Text {
                text: "Focus to open keyboard";
                horizontal-alignment: left;
            }
            LineEdit {}

            Text {
                text: "Focus to open keyboard";
                horizontal-alignment: left;
            }

            LineEdit {}

            HorizontalLayout {
                alignment: start;

                Button {
                    text: self.checked ? "Click to close keyboard" : "Click to open keyboard";
                    checked: TextInputInterface.text-input-focused;

                    clicked => {
                        TextInputInterface.text-input-focused = !TextInputInterface.text-input-focused;

                    }
                }
            }
        }

        keyboard := VirtualKeyboard {
            y: TextInputInterface.text-input-focused ? parent.height - self.height : parent.height;
        }
       }

}

4、virtual_keyboard.slint

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT

import { Button, Palette } from "std-widgets.slint";

import { Icons } from "icons.slint";

component VirtualKeyboardButton {
    in property <string> key;
    in property <image> icon;

    callback key-pressed(/* key */ string);

    min-width: 32px;
    min-height: 32px;
    horizontal-stretch: 0;

    states [
        pressed when i-touch-area.pressed : {
            i-state-area.opacity: 0.5;
        }
    ]

    i-container := Rectangle {
        border-radius: 4px;
        background: Palette.color-scheme == ColorScheme.dark ? #373737 : #ffffff;

        HorizontalLayout {
            padding: 8px;

            if (root.key != "") : Text {
                text: root.key;
                color: Palette.color-scheme == ColorScheme.dark ? #ffffff : #000000;
                font-size: 12px;
                vertical-alignment: center;
                horizontal-alignment: center;
            }

            if (root.key == "") : Image {
                y: (parent.height - self.height) / 2;
                source: root.icon;
                height: 18px;
                colorize: Palette.color-scheme == ColorScheme.dark ? #ffffff : #000000;
            }
        }
    }

    i-state-area := Rectangle {
        border-radius: i-container.border-radius;
        opacity: 0;
        background: #000000;

        animate opacity { duration: 150ms; }
    }

    i-touch-area := TouchArea {
        pointer-event(event) => {
            if(event.kind == PointerEventKind.down) {
                root.key-pressed(key);
            }
        }
    }
}

export struct KeyModel {
    key: string,
    shift-key: string,
}

export global VirtualKeyboardHandler {
    in property <[[[KeyModel]]]> default-key-sets: [
       [
            [
                { key: "q", shift-key: "Q" },
                { key: "w", shift-key: "W"  },
                { key: "e", shift-key: "E"  },
                { key: "r", shift-key: "R"  },
                { key: "t", shift-key: "T"  },
                { key: "y", shift-key: "Y"  },
                { key: "u", shift-key: "U"  },
                { key: "i", shift-key: "I"  },
                { key: "o", shift-key: "O"  },
                { key: "p", shift-key: "P"  }
            ],
            [
                { key: "a", shift-key: "A" },
                { key: "s", shift-key: "S" },
                { key: "d", shift-key: "D" },
                { key: "f", shift-key: "F" },
                { key: "g", shift-key: "G" },
                { key: "h", shift-key: "H" },
                { key: "j", shift-key: "J" },
                { key: "k", shift-key: "K" },
                { key: "l", shift-key: "L" }
            ],
            [
                { key: "z", shift-key: "Z" },
                { key: "x", shift-key: "X" },
                { key: "c", shift-key: "C" },
                { key: "v", shift-key: "V" },
                { key: "b", shift-key: "B" },
                { key: "n", shift-key: "N" },
                { key: "m", shift-key: "M" },
                { key: ",", shift-key: ";" },
                { key: ".", shift-key: ":" },
                { key: "?", shift-key: "?" }
            ],
       ],
       [
            [
                { key: "1", shift-key: "[" },
                { key: "2", shift-key: "]" },
                { key: "3", shift-key: "{" },
                { key: "4", shift-key: "}" },
                { key: "5", shift-key: "#" },
                { key: "6", shift-key: "%" },
                { key: "7", shift-key: "^" },
                { key: "8", shift-key: "*" },
                { key: "9", shift-key: "+" },
                { key: "0", shift-key: "=" }
            ],
            [
                { key: "-", shift-key: "_" },
                { key: "/", shift-key: "\\" },
                { key: ":", shift-key: "|" },
                { key: ";", shift-key: "~" },
                { key: "(", shift-key: "<" },
                { key: ")", shift-key: ">" },
                { key: "€", shift-key: "$" },
                { key: "&", shift-key: "€" },
                { key: "@", shift-key: "°" },
                { key: "'", shift-key: "#" },
            ],
            [
                { key: ".", shift-key: "." },
                { key: ",", shift-key: "," },
                { key: "?", shift-key: "?" },
                { key: "!", shift-key: "!" },
                { key: "'", shift-key: "'" },
            ],
       ]
    ];

    out property <int> current-key-set;
    out property <[[KeyModel]]> keys: default-key-sets[self.current-key-set];
    in-out property <bool> open;

    callback key_pressed(/* key */ string);

    public function switch-keyboard() {
        if (self.current-key-set < self.default-key-sets.length - 1) {
            self.current-key-set += 1;
        } else {
            self.current-key-set -= 1;
        }

        self.current-key-set = min(self.default-key-sets.length - 1, max(0, self.current-key-set))
    }
}

export component VirtualKeyboard  {
    private property <bool> shift;

    callback close();

    preferred-width: 100%;

    TouchArea {}

    Rectangle {
        background: Palette.color-scheme == ColorScheme.dark ? #1c1c1c : #d4d4d4;
        height: 100%;
    }

    i-layout := VerticalLayout {
        padding: 8px;
        spacing: 4px;

        for row[index] in VirtualKeyboardHandler.keys : HorizontalLayout {
            spacing: 4px;

            if (index == 0) : VirtualKeyboardButton {
                key: "ESC";

                key-pressed => {
                    VirtualKeyboardHandler.key-pressed(Key.Escape);
                }
            }

            if (index == 1) : VirtualKeyboardButton {
                key: "Tab";

                key-pressed => {
                    VirtualKeyboardHandler.key-pressed(Key.Tab);
                }
            }

            // shift
            if (index == 2) : VirtualKeyboardButton {
                icon: Icons.arrow-up;

                key-pressed => {
                    root.shift = !root.shift;
                }
            }

            for km in row : VirtualKeyboardButton {
                key: root.shift ? km.shift-key : km.key;

                key-pressed(key) => {
                    VirtualKeyboardHandler.key-pressed(key);
                    root.shift = false;
                }
            }

            if (index == 0) : VirtualKeyboardButton {
                icon: Icons.chevron-left;

                key-pressed => {
                    VirtualKeyboardHandler.key-pressed(Key.Backspace);
                }
            }

            if (index == 1) : VirtualKeyboardButton {
                icon: Icons.arrow-circle-o-left;

                key-pressed => {
                    VirtualKeyboardHandler.key-pressed(Key.Return);
                }
            }

            // shift
            if (index == 2) : VirtualKeyboardButton {
                icon: Icons.arrow-up;

                key-pressed => {
                    root.shift = !root.shift;
                }
            }
        }

        HorizontalLayout {
            spacing: 4px;

             VirtualKeyboardButton {
                icon: Icons.expand-more;

                key-pressed(key) => {
                    root.close();
                }
            }

            VirtualKeyboardButton {
                icon: Icons.globe;

                key-pressed(key) => {
                    VirtualKeyboardHandler.switch-keyboard();
                }
            }
            VirtualKeyboardButton {
                horizontal-stretch: 1;
                key: " ";

                key-pressed(key) => {
                    root.shift = false;
                    VirtualKeyboardHandler.key-pressed(key);
                }
            }
            VirtualKeyboardButton {
                icon: Icons.arrow-left;

                key-pressed(key) => {
                    VirtualKeyboardHandler.key-pressed(Key.LeftArrow);
                }
            }
            VirtualKeyboardButton {
                icon: Icons.arrow-right;

                key-pressed(key) => {
                    VirtualKeyboardHandler.key-pressed(Key.RightArrow);
                }
            }
        }


    }

    animate y { duration: 500ms; easing: cubic-bezier(0.05, 0.7, 0.1, 1.0); }
}

5、资源文件

文章顶部下载

6、工程结构

在这里插入图片描述

三、Slint介绍

1、 Slint 是什么?

Slint 是一个用于构建原生用户界面工具包,特别适用于嵌入式设备桌面应用程序。它最初是用 Rust 编写的,并且为 Rust 开发者提供了优秀的支持。其核心目标是提供一种高效、安全且现代化的方式来创建流畅、响应式的 UI。

2、核心特点

  1. 声明式 UI:Slint 使用一种名为 .slint 的声明式语言(受 QML 和 HTML 启发)来描述用户界面。开发者专注于定义 UI 的结构状态,而不是编写大量的命令式代码来控制每个像素。
  2. 高效渲染:它使用轻量级的渲染引擎,针对性能进行了优化,特别适合资源受限的环境(如微控制器)。
  3. 数据绑定:UI 元素可以轻松绑定到 Rust 代码中的数据模型或属性。当底层数据发生变化时,UI 会自动更新(响应式)。
  4. 内存安全:得益于 Rust 的所有权和借用规则,Slint 能够帮助开发者避免常见的内存错误和安全漏洞。
  5. 跨平台:支持 Linux、Windows、macOS 以及各种嵌入式平台(如通过 LVGL 集成)。

3、 Slint 的核心组成部分

  • .slint 文件:这是定义 UI 布局、组件、属性、状态和交互逻辑的主要文件。它使用类似 QML 的语法。
  • Slint 编译器 (slint-compiler):将 .slint 文件编译成高效的 Rust 代码(或 C++ 代码)。
  • Slint 运行时库 (slint crate):提供在运行时渲染 UI、处理事件、管理状态和执行数据绑定所需的库功能。

4、 基本用法示例 (Rust)

假设我们有一个简单的 UI,包含一个按钮和一个标签。点击按钮时,标签显示点击次数。

Step 1: 定义 UI (example.slint)

import { Button, VerticalBox } from "std-widgets.slint";

export component ExampleWindow {
    callback button-clicked;
    in-out property <int> count: 0;

    VerticalBox {
        Text {
            text: "Count: " + count;
        }
        Button {
            text: "Click Me!";
            clicked => {
                button-clicked();
            }
        }
    }
}

Step 2: Rust 代码集成

// 引入 slint 宏和类型
slint::slint! {
    // 编译器会自动处理 example.slint 并生成 Rust 代码
    include!("example.slint");
}
use slint::ComponentHandle; // 用于访问窗口实例的方法

fn main() {
    // 创建 UI 窗口实例
    let ui = ExampleWindow::new().unwrap();

    // 获取对 count 属性的弱引用 (Weak<> 避免循环引用)
    let ui_weak = ui.as_weak();

    // 连接按钮点击信号到 Rust 闭包
    ui.on_button_clicked(move || {
        let ui = ui_weak.unwrap();
        // 更新 count 属性 (会自动触发 UI 更新)
        let current_count = ui.get_count();
        ui.set_count(current_count + 1);
    });

    // 运行 UI 主循环
    ui.run().unwrap();
}

关键点解释

  1. slint::slint!:它将 .slint 文件编译并集成到 Rust 代码中,生成 ExampleWindow 结构体和相关方法。
  2. 属性 (property <int> count):在 .slint 文件中定义了一个可读写的整数属性 count。在 Rust 中,可以通过 get_count()set_count() 方法访问和修改它。
  3. 信号 (callback button-clicked):定义了一个信号 button-clicked。在 Rust 中,使用 on_button_clicked 方法注册一个闭包来处理这个信号。
  4. 数据绑定:在 .slint 文件中,Texttext 属性绑定到表达式 "Count: " + count。当 Rust 代码调用 set_count 改变 count 的值时,这个文本会自动更新。
  5. ComponentHandle:提供访问窗口实例的方法,如 as_weak 用于获取弱引用。
  6. run():启动 UI 的事件循环。

5、 优势

  • 开发效率:声明式语法和数据绑定简化了 UI 开发。
  • 性能:轻量级渲染引擎和优化的 Rust 代码带来流畅体验。
  • 安全:Rust 的内存安全特性贯穿整个 UI 开发过程。
  • 跨平台:一套代码可部署到多种设备。
  • 现代性:响应式设计理念。

6、适用场景

  • 嵌入式设备仪表盘
  • 桌面应用 GUI (替代部分 Electron 场景)
  • 工业控制界面
  • 需要高性能和低资源占用的 UI

7、与其他 Rust GUI 库的比较

特性SlintDruidIcedegui
范式声明式 (.slint)命令式/响应式响应式/Elm-like即时模式 (Immediate)
学习曲线中等 (需学新语言)中等中等较低
性能非常高中等
跨平台广泛 (含嵌入式)桌面桌面/Web桌面/Web/WASM
成熟度快速发展较成熟较成熟较成熟
强项嵌入式、性能、安全性数据驱动、灵活性纯 Rust、Elm 架构简单、快速原型

8、总结

Slint 为 Rust 开发者提供了一个强大且现代的解决方案,用于构建高性能、安全且美观的用户界面,尤其是在嵌入式系统和资源受限的桌面环境中表现出色。其声明式的 .slint 语言结合 Rust 的强大功能,使得开发响应式 UI 变得更加高效和安全。如果你正在寻找一个高效、跨平台且安全的 Rust GUI 框架,Slint 绝对值得一试。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小灰灰搞电子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值