Rust Slint库达成桌面萌宠源码分享(包含拖动、右键菜单效果)

一、效果展示

1、效果展示

在这里插入图片描述

2、源码分享

2.1、工程结构

在这里插入图片描述

2.2、main.slint

import { AboutSlint, VerticalBox, LineEdit, HorizontalBox, Button, GroupBox, GridBox,
ComboBox, Spinner, Slider, ListView, Palette, ProgressIndicator, CheckBox, Switch } from "std-widgets.slint";
import { DataAdapter,Theme } from "models.slint";
export { DataAdapter ,Theme}
export component MainWindow inherits Window {
width: 100px;
height: 150px;
always-on-top: true;
no-frame: true;
background: transparent;
private property <Point> pressed_point;
  private property <bool> is_pressed: false;
    private property <int> image_cnt:0;
      private property <[image]> image : [
        @image-url("./image/0.png"),
        @image-url("./image/1.png"),
        @image-url("./image/2.png"),
        @image-url("./image/3.png"),
        @image-url("./image/4.png"),
        @image-url("./image/5.png"),
        @image-url("./image/6.png"),
        @image-url("./image/7.png"),
        @image-url("./image/8.png"),
        @image-url("./image/9.png"),
        @image-url("./image/10.png"),
        @image-url("./image/11.png"),
        @image-url("./image/12.png"),
        @image-url("./image/13.png"),
        @image-url("./image/14.png"),
        @image-url("./image/15.png"),
        @image-url("./image/16.png"),
        @image-url("./image/17.png"),
        @image-url("./image/18.png"),
        @image-url("./image/19.png"),
        @image-url("./image/20.png"),
        @image-url("./image/21.png"),
        @image-url("./image/22.png"),
        @image-url("./image/23.png"),
        @image-url("./image/24.png"),
        @image-url("./image/25.png"),
        @image-url("./image/26.png"),
        @image-url("./image/27.png"),
        @image-url("./image/28.png"),
        @image-url("./image/29.png"),
        @image-url("./image/30.png"),
        @image-url("./image/31.png"),
        @image-url("./image/32.png"),
        @image-url("./image/33.png"),
        @image-url("./image/34.png"),
        @image-url("./image/35.png"),
        @image-url("./image/36.png"),
        @image-url("./image/37.png"),
        @image-url("./image/38.png"),
        @image-url("./image/39.png"),
        @image-url("./image/40.png"),
        @image-url("./image/41.png"),
        @image-url("./image/42.png"),
        @image-url("./image/43.png"),
        @image-url("./image/44.png"),
        @image-url("./image/45.png"),
        @image-url("./image/46.png"),
        @image-url("./image/47.png"),
        @image-url("./image/48.png"),
        @image-url("./image/49.png"),
        ];
        image := Image {
        width: parent.width;
        height: parent.height;
        source: @image-url("./image/0.png");
        image-fit: fill;
        image-rendering: smooth;
        }
        timer := Timer {
        interval: 30ms;
        running: false;
        triggered => {
        image.source = root.image[image_cnt];
        image_cnt +=1;
        if image_cnt >= 49 {
        image_cnt = 0;
        timer.stop();
        }
        }
        }
        TouchArea {
        enabled: true;
        pointer-event(event) => {
        if event.kind == PointerEventKind.move{
        if !timer.running {
        timer.start();
        }
        }
        if event.kind == PointerEventKind.down && event.button == PointerEventButton.left {
        root.is_pressed = true;
        root.pressed_point.x = self.mouse-x;
        root.pressed_point.y = self.mouse-y
        } else if event.kind == PointerEventKind.up && event.button == PointerEventButton.left {
        root.is_pressed = false;
        }
        }
        moved => {
        if(root.is_pressed){
        DataAdapter.position_changed((self.mouse-x - root.pressed_point.x)/1px,(self.mouse-y - root.pressed_point.y)/1px);
        }
        }
        }
        ContextMenuArea {
        Menu {
        MenuItem {
        title: @tr("剪切");
        activated => { debug("Cut"); }
        }
        MenuItem {
        title: @tr("复制");
        activated => { debug("Copy"); }
        }
        MenuItem {
        title: @tr("粘贴");
        activated => { debug("Paste"); }
        }
        MenuSeparator {}
        Menu {
        title: @tr("查找");
        MenuItem {
        title: @tr("查找下一个");
        }
        MenuItem {
        title: @tr("查找上一个");
        }
        }
        MenuSeparator {}
        MenuItem {
        title: @tr("退出");
        activated => { debug("quit");
        DataAdapter.quit_clicked();
        }
        }
        }
        }
        }

2.3、models.slint

export enum Theme {
Light,
Dark,
System
}
export global DataAdapter {
in-out property <string> textResValue:0;
  callback btn_clicked(string);
  callback position_changed(int, int);
  callback quit_clicked();
  }

2.4、main.rs

use slint::{PlatformError, WindowPosition};
slint::include_modules!();
use slint::{Color,Brush};
use slint::Timer;
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>();
        }
        }
        });
        app.global::<DataAdapter>().on_position_changed({
          let app = app.clone_strong();
          move |x:i32,y:i32|{
          let win = app.window();
          let mut pos = win.position();
          pos.x += x;
          pos.y += y;
          win.set_position(pos);
          }
          });
          app.global::<DataAdapter>().on_quit_clicked({
            let app = app.clone_strong();
            move ||{
            let _ = app.window().hide();
            }
            });
            let _ = app.run();
            Ok(())
            }

2.5、Cargo.toml

[dependencies]
slint = "1.13.1"
[build-dependencies]
slint-build = "1.13.1"

二、工程搭建及资源文件

1、工程搭建

参考我这篇文章:工程搭建详细教程

2、资源文件

在文章顶部下载

三、实现原理

主要通过ImageTimerContextMenuArea三个控件实现。

1、Image控件介绍

Image控件用于在界面中显示图片,支持多种图片格式和加载方式。

1.1、Image控件的基本用法

在Slint中,Image控件可以通过声明式语法或编程方式创建。以下是一个简单的Rust示例:

slint::slint! {
import { VerticalBox, Image } from "std-widgets.slint";
export component MainWindow inherits Window {
VerticalBox {
Image {
source: @image-url("path/to/image.png");
width: 200px;
height: 200px;
}
}
}
}

1.2、支持的图片格式

Slint的Image控件支持常见的图片格式,包括PNG、JPEG、GIF和BMP。图片可以通过文件路径、内存数据或URL加载。

1.3、动态更新图片

Image控件支持动态更新图片源。可以通过绑定到变量或回调函数实现:

slint::slint! {
export component MainWindow inherits Window {
in-out property <string> image-path: "default.png";
  Image {
  source: @image-url(image-path);
  }
  }
  }

1.4、图片缩放和裁剪

Image控件提供多种缩放和裁剪选项:

  • fit: 保持宽高比适应控件大小
  • fill: 拉伸填满整个控件
  • stretch: 不保持宽高比拉伸
  • tile: 平铺图片
slint::slint! {
Image {
source: @image-url("image.jpg");
image-fit: fill;
}
}

1.5、性能优化

对于频繁更新的图片,建议:

  • 使用适当大小的图片资源
  • 考虑缓存机制
  • 避免在热路径中频繁加载图片

2、Timer介绍

Timer控件是Slint中用于处理定时任务的核心组件,通常用于执行周期性任务或延迟操作。

2.1、基本用法

在Slint中,Timer控件通常通过Timer结构体或相关接口实现。以下是一个简单的示例代码:

import slint;
export component MainWindow {
    in property  counter: 0;
    callback tick;
    Timer {
        interval: 1000ms;
        running: true;
        triggered => {
            tick();
            counter += 1;
        }
    }
}

2.2、主要属性

interval
定义定时器触发的时间间隔,支持毫秒(ms)和秒(s)单位。例如1000ms1s

running
布尔值属性,控制定时器是否处于活动状态。设置为true时定时器开始运行,false时停止。

2.3、信号处理

定时器触发时会发送triggered信号,可以通过回调函数处理:

triggered()  => {
    // 处理定时器触发时的逻辑
}

2.4、注意事项

  1. 定时器的精度取决于底层系统实现,不可用于需要高精度计时的场景
  2. 在界面不可见时,某些平台可能会限制或暂停定时器的执行
  3. 过度使用定时器可能影响应用性能

Timer控件是Slint中处理时间相关任务的简单有效方式,适用于UI动画、定期更新等场景。

3、ContextMenuArea介绍

ContextMenuAreaslint 库中用于处理上下文菜单(右键菜单)的组件。它允许开发者为 UI 元素绑定自定义的右键菜单逻辑,提供更丰富的交互体验。

3.1、核心功能

  • 右键菜单触发:监听鼠标右键点击事件,触发自定义菜单。
  • 动态菜单内容:支持根据上下文动态生成菜单项。
  • 跨平台兼容:在支持 slint 的平台上(如 Windows、macOS、Linux)均可使用。

3.2、基本用法

export component MyComponent {
    in-out property  text: "Right-click me!";
    ContextMenuArea {
        Menu {
            MenuItem {
                title: "Copy";
                activated => { }
            }
            MenuItem {
                title: "Paste";
                activated => { }
            }
        }
    }
}

3.3、注意事项

  1. 移动端平台可能需要特殊处理,因为通常没有右键操作
  2. 菜单层级过深时需考虑用户体验
  3. 快捷键绑定需与系统快捷键避免冲突

在这里插入图片描述

posted @ 2025-10-03 11:02  wzzkaifa  阅读(26)  评论(0)    收藏  举报