Slint 的介绍
Slint 是一个声明式 GUI 开源框架,该框架完全用 Rust 开发。用户的开发方式类似 Qt 的 QML,同时支持 Rust/C++/Js语言的绑定。Slint 正在积极开发中,目前已支持多平台运行,如嵌入式、Windows、Mac、Web 、Android 等。
Slint 的优点
- 开发简单,Slint 申明式开发界面,轻松绑定不同语言的业务逻辑.
- 跨平台, 完全支持嵌入式、桌面、移动端运行
- 多语言,完美支持 C++/Rust/Js语言,用户可选择自己擅长的语言
- 快速设计,桌面端轻松仿真,便捷的代码提示
- 固件体积小,相比 Qt 臃肿的框架,Slint 能在小容量的单片机中也能轻松运行
移植到Swm341单片机
开发平台
目前所有代码都在 Mac 下编译和下载。
开发板使用 SWM341RET6 单片机,64KRAM+512KFlash,LCD 分辨率大小为 800*480.
编写 Swm341 的 Rust 外设驱动库
Swm341 单片机目前官方只提供 C 的库接口,并不能直接运行 Slint,因为 Slint 由 Rust 开发,因此笔者需要自己实现一套 Rust 的外设驱动库。Swm341 Rust版本的外设驱动框架如下:
~/mywork/synwit/synwit_hal_common ‹18e6b51*› $ tree -d -L 2
.
├── examples
└── src
├── can
├── cmp
├── cordic
├── crc
├── dac
├── div
├── dma
├── dma2d
├── dwt
├── flash
├── gpio
├── i2c
├── jpeg
├── lcd
├── opa
├── rtc
├── saradc
├── sdio
├── sdram
├── spi
├── syscon
├── timer
├── usb
└── util
移植 Lcd 接口到 Slint
Slint 提供便利的 trait 引导接口的适配,接口适配如下:
#[derive(Debug)]
enum Error {}
#[derive(Clone, Copy)]
struct MyLcd<
Bl: GpioFun,
const PIN: u8,
const WIDTH: usize = DISPLAY_WIDTH,
const HEIGHT: usize = DISPLAY_HEIGTH,
> {
back_light_io: AnyPin,
lcd: Lcd,
}
impl
MyLcd
{
pub fn new() -> Result {
let mut back_light_io = AnyPin::::new();
let _ = back_light_io.set_high();
let config: LcdConfig = LcdConfig::default().size(WIDTH as u16, HEIGHT as u16);
let lcd = Lcd::new(config);
Ok(Self { back_light_io, lcd })
}
pub fn start(&self) {
self.lcd.start();
}
pub fn set_display_addr(&self, addr: u32) {
let layer_config = LayerConfig::default().set_source(addr);
Lcd::layer_init(Layer::Layer0, layer_config);
}
pub fn width(&self) -> u16 {
WIDTH as u16
}
pub fn height(&self) -> u16 {
HEIGHT as u16
}
fn set_pixel(&mut self, x: u16, y: u16, color: Rgb565) {
let fb = unsafe { &mut *core::ptr::addr_of_mut!(FB1) };
fb[y as usize][x as usize] = color;
}
}
impl OriginDimensions
for MyLcd
{
fn size(&self) -> embedded_graphics_core::prelude::Size {
Size::new(WIDTH as u32, HEIGHT as u32)
}
}
impl DrawTarget
for MyLcd
{
type Color = Rgb565;
type Error = Error;
fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator>,
{
let bb = self.bounding_box();
pixels
.into_iter()
.filter(|Pixel(pos, _color)| bb.contains(*pos))
.for_each(|Pixel(pos, color)| self.set_pixel(pos.x as u16, pos.y as u16, color));
Ok(())
}
}
界面开发
界面开发非常简单,直接使用 Slint 标记语法开发 GUI界面即可,由于例程简单,因此无需 Rust 后台逻辑处理交互。
import { AboutSlint, VerticalBox, CheckBox,Switch, HorizontalBox } from "std-widgets.slint";
export component MyButton inherits Rectangle {
in-out property text <=> txt.text;
callback clicked <=> touch.clicked;
border-radius: root.height / 2;
border-width: 1px;
border-color: root.background.darker(25%);
background: touch.pressed ? #6b8282 : touch.has-hover ? #6c616c : #456;
height: txt.preferred-height * 1.33;
min-width: txt.preferred-width + 20px;
txt := Text {
x: (parent.width - self.width)/2 + (touch.pressed ? 2px : 0);
y: (parent.height - self.height)/2 + (touch.pressed ? 1px : 0);
color: touch.pressed ? #fff : #eee;
}
touch := TouchArea { }
}
export component MySlider inherits Rectangle {
in-out property maximum: 100;
in-out property minimum: 0;
in-out property value;
min-height: 24px;
min-width: 100px;
horizontal-stretch: 1;
vertical-stretch: 0;
border-radius: root.height/2;
background: touch.pressed ? #eee: #ddd;
border-width: 1px;
border-color: root.background.darker(25%);
handle := Rectangle {
width: self.height;
height: parent.height;
border-width: 3px;
border-radius: self.height / 2;
background: touch.pressed ? #f8f: touch.has-hover ? #66f : #0000ff;
border-color: self.background.darker(15%);
x: (root.width - handle.width) * (root.value - root.minimum)/(root.maximum - root.minimum);
touch := TouchArea {
moved => {
if (self.enabled && self.pressed) {
root.value = max(root.minimum, min(root.maximum,
root.value + (self.mouse-x - self.pressed-x) * (root.maximum - root.minimum) / root.width));
}
}
}
}
}
export component MainWindow inherits Window {
width: 800px;
height: 480px;
VerticalBox {
alignment: LayoutAlignment.center;
Text {
text: "Hello SWM341 Slint!";
font-size: 30px;
horizontal-alignment: center;
}
Text {
text: "Author: EmbeddedTeckTalk";
font-size: 12px;
horizontal-alignment: center;
}
AboutSlint {
preferred-height: 200px;
}
HorizontalLayout {
alignment: center;
VerticalLayout {
alignment: center;
MyButton {
height: 45px;
width: 80px;
text: "My Button";
}
}
CheckBox {
text: "CheckBox";
checked: true;
}
Switch {
}
Rectangle {
height: 40px;
width: 40px;
border-radius: 20px;
background: red;
Text {
text: "Red";
}
}
Rectangle {
height: 40px;
width: 40px;
border-radius: 20px;
background: green;
Text {
text: "Green";
}
}
Rectangle {
height: 40px;
width: 40px;
border-radius: 20px;
background: blue;
Text {
text: "Blue";
}
}
}
VerticalBox {
alignment: LayoutAlignment.center;
slider := MySlider {
maximum: 100;
value: 49;
}
Text {
text: "Slider value: \{round(slider.value)}";
horizontal-alignment: center;
}
}
}
}
运行效果
MCU显示与仿真效果几乎一致,目前触摸接口暂未移植,后续继续更新。