【Rust】Iced GUI库初使用及踩坑——写一个计数器

(该文写于去年8月份,目前iced更新到0.7已有些许API改动,本文内容已有部分错误,但仍有借鉴价值)

写在前面

从5月份起,我一直在寻找一个可用的,稳定的,开发者友好的Rust GUI框架,试图做到All in Rust,即一切的一切都可以靠Rust实现,其中GUI是相当重要的一部分,但找了许久,大部分给我答案是使用tauri,可我不是一个前端程序员,也没有深入学习前端的想法,故寻找计划只好搁置,直到9月份我发现PopOS团队使用Iced框架制作了发行版的桌面,证明该框架已经得到了生产的认证,于是我开始尝试使用Iced进行GUI的制作。

劝退警告

Iced 作为一款正在高速发展的框架,其API有极大的不确定性,用来自开发团队的话就是

Iced is currently experimental software.Iced moves fast and the master branch can contain breaking changes!

指不定啥时候你平时用着的API就没了,或者一个lib突然出现了极大的变化,导致之前的代码完全推翻,如果你能承受此风险,可以继续往下学习。我在接触这个框架是碰到了如下问题

  • 文档和crate实际使用时出现严重的不符合,当我试图在文档中搜索时,文档的结果和实际有较大出入
  • 文档和crate和官方示例有极大的出入,体现在一些库成员已经deprecated或者move到了别的子库中,以及rust本身的不断更新导致代码也有一定的差异。

Iced框架介绍

Iced是一个简单的,易于理解的,高度模块化的GUI框架,其整个框架可以被简单的描述为4点

  • 状态(State)
  • 消息(Messages)
  • 界面逻辑(View Logic)
  • 更新逻辑(Update Logic)

根据本人自己的使用体验,这4者之间大概的关系如下

在这里插入图片描述

其中 view()中的控件布局逻辑会绑定应用结构体里的控件状态,这样在交互时也会改变控件的状态。
可以发现其实是很好理解的,使用逻辑十分分明,接下来将用本人结合官方的例子来具体说明。

Iced-rs相关链接:
github
API文档
crate.io主页

成果展示

在这里插入图片描述

代码讲解

新建一个工程

进入命令行,输入

> cargo new counter
> cd counter

打开Cargo.toml,向dependencies中添加

iced = "0.4"

然后执行

cargo run

完成对crate的获取以及看到输出Hello,World

建立一个Iced应用

首先引入Iced库

use iced::widget::{button, Column, Text};
use iced::{ Element, Length, Sandbox, Settings};

当我们需要实现一个计数器时,我们先想想,他大概由哪几部分组成,首先计数,那么他需要一个文本控件来显示数字,既然要实现数字的变化,那么就需要有两个按钮来使他增加或减少。于是我们建立结构体

struct Counter {
    Value: i32,
    increment_button: button::State,
    decrement_button: button::State,
}

其中Value为计数器的计数值,剩余两个为两个按钮的状态,这样就完成了我们的应用的“建模”。

给大伙发两条信息

根据上面的框图我们知道,你得让程序知道你点击了按钮,那么我们就需要定义Message

#[derive(Debug, Clone, Copy)]
pub enum Message {
    IncementPressed,
    DecrementPressed,
}

将结构体转变为真正的iced应用

根据官方文档知道,我们要为结构体实现(implement)一些 trait 让其成为应用

impl Sandbox for counter {
    ...
}

fn new

这个比较好理解,实例化一个counter结构体

fn new() -> Self {
        Self {
            Value: 0,
            increment_button: button::State::new(),
            decrement_button: button::State::new(),
        }
    }

fn update

首先你需要告诉iced你的消息枚举叫啥名字,所以

type Message = Message;

注意!第一个Message是Sandbox trait中定义的,而后一个是你自己在上面定义的,注意区分!

然后书写更新逻辑

fn update(&mut self, message: Self::Message) {
        match message {
            Message::IncementPressed => {
                self.Value += 1;
            }
            Message::DecrementPressed => {
                self.Value -= 1;
            }
        }
    }

可以看到,当收到IncrementPressed消息时,结构体自身的Value就会 +1

fn view

这个便是书写界面布局和实例化控件的步骤了,直接上代码!

fn view(&mut self) -> Element<'_, Self::Message> {
        Column::new()
            .push(
                button::Button::new(&mut self.increment_button, Text::new("+"))
                    .on_press(Message::IncementPressed),
            )
            .push(Text::new(self.Value.to_string()).size(50))
            .push(
                button::Button::new(&mut self.decrement_button, Text::new("-"))
                    .on_press(Message::DecrementPressed),
            )
            .into()
    }

代码中,首先我实例化了一个Column控件,用来容纳按钮和文本控件,然后通过push将控件添加到Column中,注意控件的push顺序,会影响布局的顺序。最后的into()相当重要,他负责把Column转换为Element类型,否则编译不通过。

大功告成

最后在main中对结构体执行run函数即可

fn main() -> {
    Counter::run(Settings::default());
}

运行cargo run则会出现一个简单的计数器界面。

写在最后

这篇文章主要是为了加深我对Iced GUI框架的认识,将技术与思考细节记录下来,如果能够帮助到你,真是不胜荣幸
(不过在跑通这个例子前,我真的碰到了一堆莫名其妙的问题,才有了这份结晶,大概率能帮助减少碰壁的概率)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是用 Rust 语言Iced 库实现的简单计算器: ```rust use iced::{button, Button, Column, Element, Sandbox, Settings, Text}; pub struct Calculator { first_operand: String, second_operand: String, operator: Operator, result: String, calculate_button_state: button::State, } #[derive(Debug, Clone, Copy)] pub enum Operator { Add, Subtract, Multiply, Divide, } impl Default for Calculator { fn default() -> Self { Self { first_operand: String::new(), second_operand: String::new(), operator: Operator::Add, result: String::new(), calculate_button_state: button::State::new(), } } } #[derive(Debug, Clone)] pub enum Message { FirstOperandChanged(String), SecondOperandChanged(String), OperatorChanged(Operator), CalculateButtonPressed, } impl Sandbox for Calculator { type Message = Message; fn new() -> Self { Self::default() } fn title(&self) -> String { String::from("Calculator") } fn update(&mut self, message: Message) { match message { Message::FirstOperandChanged(value) => { self.first_operand = value; } Message::SecondOperandChanged(value) => { self.second_operand = value; } Message::OperatorChanged(operator) => { self.operator = operator; } Message::CalculateButtonPressed => { let first_operand = self.first_operand.parse::<f32>().unwrap_or_default(); let second_operand = self.second_operand.parse::<f32>().unwrap_or_default(); let result = match self.operator { Operator::Add => first_operand + second_operand, Operator::Subtract => first_operand - second_operand, Operator::Multiply => first_operand * second_operand, Operator::Divide => first_operand / second_operand, }; self.result = result.to_string(); } } } fn view(&mut self) -> Element<Message> { let first_operand_input = Text::new("First operand:").size(20); let second_operand_input = Text::new("Second operand:").size(20); let calculate_button = Button::new( &mut self.calculate_button_state, Text::new("Calculate").size(20), ) .on_press(Message::CalculateButtonPressed); let result_text = Text::new(&self.result).size(20); Column::new() .push(first_operand_input) .push( Text::new(&self.first_operand) .size(20) .padding(10) .width(iced::Length::Units(200)) .border_width(1) .border_radius(5), ) .push(second_operand_input) .push( Text::new(&self.second_operand) .size(20) .padding(10) .width(iced::Length::Units(200)) .border_width(1) .border_radius(5), ) .push( Column::new() .push( Button::new( &mut button::State::new(), Text::new("Add").size(20), ) .on_press(Message::OperatorChanged(Operator::Add)), ) .push( Button::new( &mut button::State::new(), Text::new("Subtract").size(20), ) .on_press(Message::OperatorChanged(Operator::Subtract)), ) .push( Button::new( &mut button::State::new(), Text::new("Multiply").size(20), ) .on_press(Message::OperatorChanged(Operator::Multiply)), ) .push( Button::new( &mut button::State::new(), Text::new("Divide").size(20), ) .on_press(Message::OperatorChanged(Operator::Divide)), ), ) .push(calculate_button) .push(result_text) .spacing(20) .padding(20) .into() } } fn main() -> iced::Result { Calculator::run(Settings::default()) } ``` 这个计算器允许用户输入两个操作数和选择一个操作符,然后计算结果。请在 Rust 环境中运行这个代码,然后使用键盘或鼠标输入操作数和选择操作符,最后按下“Calculate”按钮计算结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值