Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

iced 标志

介绍

iced 是一个用于 Rust 的跨平台 GUI 库。它受到 Elm 的启发,Elm 是一种用于构建 Web 应用程序的令人愉悦的函数式语言。

作为一个 GUI 库,iced 帮助您为 Rust 应用程序构建图形用户界面

iced 强烈专注于简洁性类型安全。因此,iced 试图提供简单的构建块,这些构建块可以通过强类型组合在一起,以减少运行时错误的可能性。

本书将:

  • 向您介绍 iced 的基本思想。
  • 教您如何使用 iced 构建交互式应用程序。
  • 强调扩展和发展 iced 应用程序的原则。

在继续之前,您应该对 Rust 有一些基本的了解。如果您是 Rust 新手或在某些时候感到困惑,我建议您阅读官方 Rust 书籍

本文档是iced文档的中文翻译,由Claude辅助翻译,部分章节正在校对中,有任何问题欢迎指正。

架构

让我们从基础开始!您可能已经非常熟悉图形用户界面了。 您可以在手机、计算机和大多数交互式电子设备上找到它们。事实上,您很可能正在使用一个来阅读这本书!

从本质上讲,图形用户界面是向用户图形化显示一些信息的应用程序。然后用户可以选择与应用程序交互——通常使用某种设备;比如键盘、鼠标或触摸屏。

界面显示,用户交互

用户交互可能导致应用程序更新并因此显示新信息,这反过来可能导致进一步的用户交互,进而导致进一步的更新…等等。这种快速的反馈循环是产生交互性感觉的原因。

注意:在本书中,我们将图形用户界面称为 GUIUI用户界面或简称界面。从技术上讲,并非所有界面都是图形化的或面向用户的;但是,考虑到本书的上下文,我们将交替使用所有这些术语。

剖析界面

由于我们有兴趣创建用户界面,让我们仔细看看它们。我们将从一个非常简单的开始:经典的计数器界面。它由什么组成?

经典计数器界面

正如我们可以清楚地看到的,这个界面有三个视觉上不同的元素:两个按钮中间有一个数字。我们将用户界面的这些视觉上不同的元素称为组件元素

一些组件可能是交互式的,比如按钮。在计数器界面中,按钮可以用来触发某些交互。具体来说,顶部的按钮可以用来增加计数器值,而底部的按钮可以用来减少它。

我们也可以说用户界面是有状态的——有一些状态在交互之间持续存在。计数器界面显示一个表示计数器值的数字。显示的数字将根据我们按按钮的次数而改变。按一次增加按钮将导致与按两次不同的显示值。

剖析的计数器界面

GUI 三要素

我们的快速剖析成功识别了用户界面中的三个基本思想:

  • 组件 — 界面的不同视觉元素。
  • 交互 — 可能由某些组件触发的动作。
  • 状态 — 界面的底层条件或信息。

这些思想相互连接,形成另一个反馈循环!

组件在用户与它们交互时产生交互。这些交互然后改变界面的状态。改变的状态传播并决定必须显示的新组件。这些新组件然后可能产生新的交互,这可以再次改变状态…等等。

GUI 三要素

这些思想及其连接构成了用户界面的基本架构。因此,创建用户界面必然包括定义这些组件交互状态;以及它们之间的连接。

不同的思想,不同的性质

界面的三个基本思想在可重用性方面差异很大。

界面的状态和交互对应用程序及其目的非常特定。如果我告诉您我有一个具有数值和增加和减少交互的界面,您很容易猜到我在谈论计数器界面。

然而,如果我告诉您我有一个具有两个按钮和一个数字的界面…您猜测我在谈论什么样的界面就相当困难了。它可能是任何东西!

这是因为组件通常非常通用,因此更可重用。大多数界面显示熟悉组件的组合——比如按钮和数字。事实上,用户期望熟悉的组件总是以某种方式行为。如果它们行为不当,界面将不直观并具有糟糕的用户体验

虽然组件通常非常可重用;但由应用程序状态及其交互决定的特定组件配置是非常特定于应用程序的。按钮是通用的;但具有“+“标签并在按下时导致值增加的按钮是非常特定的。

所有这些意味着,当我们创建特定的用户界面时,我们不想专注于实现每个熟悉的组件及其行为。相反,我们想要利用组件作为可重用的构建块——独立于我们的应用程序并由某个库提供——同时将我们的重点放在基本架构的应用程序特定部分:状态、交互、交互如何改变状态,以及状态如何决定组件。

GUI 的应用程序特定部分

Elm 架构

事实证明,界面架构的四个应用程序特定部分也是Elm 架构的四个基本思想。

Elm 架构是一种用于构建交互式程序的模式,它在 Elm 中自然出现,Elm 是一种用于可靠 Web 应用程序的令人愉悦的纯函数式编程语言。

在纯函数式编程语言中出现的模式和思想在 Rust 中往往工作得很好,因为它们利用了不可变性和引用透明性——这两个非常理想的属性不仅使代码易于推理,而且与借用检查器配合得很好。

此外,Elm 架构不仅在 Elm 中自然出现,而且在简单地剖析用户界面并形式化其内部工作时也会出现;就像我们刚才在本章中所做的那样。

Elm 架构为其基本部分使用不同的——如果不是更精确的——命名法:

  • 模型 — 应用程序的状态。
  • 消息 — 应用程序的交互。
  • 更新逻辑 — 消息如何改变状态。
  • 视图逻辑 — 状态如何决定组件。

这些是不同的名称,但它们指向我们已经发现的完全相同的基本思想,因此可以互换使用。

Elm 架构

注意:在 iced 中,状态消息这些名称比模型交互使用得更频繁。

第一步

但是理论已经够了。是时候开始写一些代码了!

iced 采用 Elm 架构作为构建交互式应用程序最自然的方法。 因此,在使用 iced 时,我们将处理上一章中介绍的四个主要思想: 状态消息更新逻辑视图逻辑

在上一章中,我们剖析并研究了经典的计数器界面。让我们尝试 在 Rust 中构建它,同时利用 Elm 架构。

经典计数器界面

状态

让我们从状态开始——应用程序的底层数据。

在 Rust 中,考虑到所有权和借用规则,仔细思考应用程序的数据模型 是极其重要的。

我鼓励您总是从思考应用程序的数据及其 不同状态开始——不仅是那些可能的,还有那些必须不可能的。然后尽可能多地利用 类型系统来_使不可能的状态变得不可能_。

对于我们的计数器界面,我们只需要一个计数器值。由于我们有增加和减少交互, 数字可能是负数。这意味着我们需要一个有符号整数。

另外,我们知道一些用户很疯狂,他们可能想要计算很多东西。让我们给他们 64 位来玩:

struct Counter {
    value: i64,
}

如果一个疯狂的用户每秒计算 1000 个东西,他们需要大约 3 亿年才能用完数字。 希望这就足够了。

消息

接下来,我们需要定义我们的消息——应用程序的交互。

我们的计数器界面有两个交互:增加减少。从技术上讲,我们可以使用简单的布尔值来 编码这些交互:例如,true 表示增加,false 表示减少。

但是…我们在 Rust 中可以做得更好!交互是互斥的——当我们有一个交互时,我们真正拥有的是一个 可能值集合中的一个值。事实证明,Rust 有完美的数据类型来建模这种思想:枚举

因此,我们可以这样定义我们的消息:

enum Message {
    Increment,
    Decrement,
}

足够简单!这也为我们的长期发展做好了准备。如果我们想要向应用程序添加额外的交互——比如 Reset 交互——我们只需向这个类型引入额外的变体。枚举非常强大和方便。

更新逻辑

现在,是时候编写我们的更新逻辑了——消息如何改变应用程序的状态。

基本上,我们需要编写一些逻辑,给定任何消息都可以相应地更新应用程序的任何状态。在 Rust 中表达这种逻辑的最简单 和最惯用的方法是在我们的应用程序状态中定义一个名为 update 的方法。

对于我们的计数器界面,我们只需要根据我们刚刚定义的 Message 正确地增加或减少我们 Counter 结构的 value

impl Counter {
    fn update(&mut self, message: Message) {
        match message {
            Message::Increment => {
                self.value += 1;
            }
            Message::Decrement => {
                self.value -= 1;
            }
        }
    }
}

太好了!现在我们准备好处理用户交互了。例如,想象我们这样初始化我们的计数器:

let mut counter = Counter { value: 0 };

假设我们想要模拟用户玩我们的界面一会儿——按两次增加按钮 然后按一次减少按钮。我们可以使用我们的更新逻辑轻松计算计数器的最终状态:

counter.update(Message::Increment);
counter.update(Message::Increment);
counter.update(Message::Decrement);

这将导致我们的 Counter 最终的 value1

assert_eq!(counter.value, 1);

事实上,我们刚刚为我们的应用程序逻辑编写了一个简单的测试:

#[test]
fn it_counts_properly() {
    let mut counter = Counter { value: 0 };

    counter.update(Message::Increment);
    counter.update(Message::Increment);
    counter.update(Message::Decrement);

    assert_eq!(counter.value, 1);
}

注意这写起来多么容易!到目前为止,我们只是利用了非常简单的 Rust 概念。没有依赖项! 您甚至可能想知道…“GUI 代码在哪里?!”

这是 Elm 架构的主要优势之一。正如我们在上一章中发现的,组件是 界面中唯一本质上可重用的基本思想。到目前为止我们定义的所有部分都是特定于应用程序的, 因此根本不需要了解 UI 库!

Elm 架构正确地拥抱了用户界面每个部分的不同性质——将状态消息更新逻辑组件视图逻辑解耦。

视图逻辑

最后,我们需要定义的唯一部分是我们的视图逻辑——状态如何决定应用程序的组件。

这就是魔法发生的地方!在视图逻辑中,我们将应用程序的状态及其可能的交互结合起来 产生必须向用户显示的用户界面的视觉表示。

经典计数器界面

正如我们已经学到的,这种视觉表示由组件组成——界面的视觉上不同的单元。大多数 组件不是特定于应用程序的,它们可以被抽象并打包到可重用的库中。这些库通常 被称为_组件工具包_、GUI 框架_或简称_GUI 库

这就是 iced 的用武之地——终于!iced 是一个用于 Rust 的跨平台 GUI 库。它打包了相当多的 现成可用的组件;包括按钮和数字。正是我们计数器所需要的。

按钮

我们的计数器界面有两个按钮。让我们看看如何使用 iced 定义它们。

在 iced 中,组件是独立的值。就像您可以在变量中有一个整数一样,您也可以有一个组件。 这些值通常使用 widget 模块中的_辅助函数_创建。

对于我们的按钮,我们可以使用 button 辅助函数:

use iced::widget::button;

let increment = button("+");
let decrement = button("-");

这很简单,不是吗?现在,我们只是为我们的按钮定义了几个变量。

正如我们所看到的,组件辅助函数可能需要参数来配置组件的部分以符合我们的喜好。 在这种情况下,button 函数接受一个用于描述按钮内容的参数。

数字

我们的按钮很好地放在我们的 incrementdecrement 变量中。我们如何为 计数器值做同样的事情?

虽然 iced 实际上没有 number 组件,但它确实有一个更通用的 text 组件,可以用来 显示任何类型的文本——包括数字:

use iced::widget::text;

let counter = text(15);

太好了!像 button 一样,text 也接受一个用于描述其内容的参数。由于我们刚刚开始,让我们 暂时简单地硬编码 15

布局

好的!我们在 incrementdecrement 中有我们的两个按钮,在 counter 中有我们的计数器值。这应该就是一切了,对吧?

不要这么快!我们计数器界面中的组件以特定的顺序显示。给定我们的三个组件,总共有 种不同的排序方式。然而,我们想要的顺序是:incrementcounterdecrement

描述这种顺序的一个非常简单的方法是创建一个包含我们组件的列表:

let interface = vec![increment, counter, decrement];

但我们仍然缺少一些东西!不仅顺序是特定的,我们的界面还有特定的视觉布局

组件彼此堆叠,但它们也可以从左到右定位。在我们迄今为止的描述中,没有任何内容 谈论我们组件的布局

在 iced 中,布局使用…嗯,更多组件来描述!没错。并非所有组件都直接产生视觉结果;有些可能只是 管理现有组件的位置。由于组件只是值,它们可以很好地嵌套和组合。

我们计数器需要的那种垂直布局可以通过 column 组件实现:

use iced::widget::column;

let interface = column![increment, counter, decrement];

这与我们之前的代码片段非常相似。iced 提供了一个 column! 宏,用于从特定 顺序的一些组件创建 column——类似于 vec!

交互

此时,我们在 interface 变量中有一个表示我们计数器界面的 column。但如果我们实际尝试运行它, 我们会很快发现有些不对劲。

我们的按钮将完全被禁用。当然!我们还没有为它们定义任何交互。注意我们还没有 在视图逻辑中使用我们的 Message 枚举。如果我们不指定,我们的用户界面如何产生消息 呢?让我们现在就做。

在 iced 中,每个组件都有一个特定的类型,可以使用简单的构建器方法进行进一步配置。button 辅助函数返回Button 类型的实例,它有一个 on_press 方法,我们可以用它来定义当用户按下按钮时它必须 产生的消息:

use iced::widget::button;

let increment = button("+").on_press(Message::Increment);
let decrement = button("-").on_press(Message::Decrement);

太棒了!我们的交互已经连接好了。但还有一个小细节。按钮可以被多次按下。因此, 同一个按钮可能需要产生同一个 Message 的多个实例。结果,我们需要我们的 Message 类型是可克隆的。

我们可以轻松地_派生_ Clone 特征——以及 DebugCopy 以防万一:

#[derive(Debug, Clone, Copy)]
enum Message {
    Increment,
    Decrement,
}

在 Elm 架构中,消息表示已经发生的事件——由纯数据组成。因此,为我们的 Message 类型派生 DebugClone 应该总是很容易的。

视图

我们快到了!只剩下一件事要做:将我们的应用程序状态连接到视图逻辑。

让我们汇总到目前为止编写的所有视图逻辑:

use iced::widget::{button, column, text};

// 按钮
let increment = button("+").on_press(Message::Increment);
let decrement = button("-").on_press(Message::Decrement);

// 数字
let counter = text(15);

// 布局
let interface = column![increment, counter, decrement];

如果我们运行这个视图逻辑,我们现在能够按下按钮。然而,结果什么也不会发生。 计数器会卡住——总是显示数字 15。我们的界面完全是无状态的!

显然,这里的问题是我们的 counter 变量包含一个硬编码 15 的文本组件。相反,我们 想要的是实际显示我们 Counter 状态的 value 字段。这样,当按下按钮并 触发我们的更新逻辑时,文本组件将显示新的 value

我们可以通过在我们的 Counter 的方法中运行我们的视图逻辑来轻松做到这一点——就像我们对更新逻辑所做的那样:

use iced::widget::{button, column, text};

impl Counter {
    fn view(&self) {
        // 按钮
        let increment = button("+").on_press(Message::Increment);
        let decrement = button("-").on_press(Message::Decrement);

        // 数字
        let counter = text(self.value);

        // 布局
        let interface = column![increment, counter, decrement];
    }
}

我们的 counter 变量现在将始终有一个带有我们 Counter 当前 valuetext 组件。太好了!

然而,正如您可能注意到的,这个 view 方法完全没用——它构造了一个 interface,但然后…它什么也不做,把它扔掉了!

在 iced 中,构造和配置组件没有副作用。在您的视图代码中没有需要 担心的“全局上下文“。

我们需要返回 interface,而不是把它扔掉。记住,我们视图逻辑的目的是 决定我们用户界面的组件;interface 变量的内容正是我们想要的 界面的描述:

use iced::widget::{button, column, text, Column};

impl Counter {
    fn view(&self) -> Column<Message> {
        // 按钮
        let increment = button("+").on_press(Message::Increment);
        let decrement = button("-").on_press(Message::Decrement);

        // 数字
        let counter = text(self.value);

        // 布局
        let interface = column![increment, counter, decrement];

        interface
    }
}

太棒了!注意 view 方法现在需要一个返回类型。返回的类型是 Column,因为 column! 宏产生 这种类型的组件——就像 button 产生 Button 类型的组件一样。

您可能还注意到这个 Column 类型有一个泛型类型参数。这个类型参数简单地指定 组件可能产生的消息类型。在这种情况下,它接受我们的 Message,因为列内的 incrementdecrement 按钮 产生这种类型的消息。

iced 强烈关注类型安全——利用类型系统和编译时保证来尽可能减少运行时错误。

嗯…就是这样!我们的视图逻辑完成了!但等等…现在有点冗长。由于这是一个如此简单的界面, 让我们内联所有内容:

经典计数器界面
use iced::widget::{button, column, text, Column};

impl Counter {
    fn view(&self) -> Column<Message> {
        column![
            button("+").on_press(Message::Increment),
            text(self.value),
            button("-").on_press(Message::Decrement),
        ]
    }
}

这更简洁了。它甚至类似于实际的界面!由于创建组件只是产生没有 副作用的值;我们可以在视图逻辑中移动东西而不用担心破坏其他东西。没有诡异的 远距离作用!

这就是我们计数器界面的全部内容。我相信您迫不及待地想要运行它。我们开始吧?

运行时

在上一章中,我们使用 iced 和 Elm 架构构建了经典的计数器界面。我们专注于每个 基本部分——一次一个:状态消息更新逻辑视图逻辑

但现在怎么办?是的,我们有用户界面的所有基本部分——正如我们在 剖析过程中学到的——但不清楚我们应该如何让它活起来。

看起来我们缺少_某些东西_,可以将所有部分组合在一起并让它们协调_运行_。_某些东西_可以 创建并运行用户界面的基本循环——向用户显示组件并对任何交互做出反应。

这个_某些东西_被称为运行时。您可以将其视为用户界面反馈循环发生的环境。运行时负责循环的每个部分:初始化状态、 产生消息、执行更新逻辑和运行我们的视图逻辑

运行时

想象运行时的另一种方式是想象一个缺少四个基本部分的巨大引擎。我们的工作是 填补这些部分——然后引擎就可以运行了!

神奇的运行时

让我们通过探索基本(虽然非常神奇!)运行时的内部来更好地理解界面的生命周期。

事实上,我们实际上已经开始编写运行时了!当我们实现计数器的更新逻辑时, 我们编写了一个模拟用户的非常小的测试:

#[test]
fn it_counts_properly() {
    let mut counter = Counter { value: 0 };

    counter.update(Message::Increment);
    counter.update(Message::Increment);
    counter.update(Message::Decrement);

    assert_eq!(counter.value, 1);
}

从技术上讲,这是一个非常基础的运行时。它初始化状态,产生一些交互, 并执行更新逻辑

当然,交互是编造的,它非常短暂,并且没有涉及视图逻辑 ——远非我们实际想要的。尽管如此,这是一个很好的开始!让我们尝试逐步扩展它。

初始化状态

我们的小运行时已经正确初始化了应用程序状态:

// 初始化状态
let mut counter = Counter { value: 0 };

但是,我们可以通过利用 Default 特征来避免硬编码初始状态。让我们派生它:

#[derive(Default)]
struct Counter {
    value: i64
}

然后,我们在运行时中简单地使用 Counter::default

// 初始化状态
let mut counter = Counter::default();

差异可能很微妙,但我们正在分离关注点——我们将应用程序的初始状态保持在 状态定义附近,并与运行时分离。这样,我们最终可能能够让我们的运行时与 _任何_应用程序一起工作!

显示界面

好的!我们已经初始化了状态。接下来是什么?嗯,在用户可以与我们的界面交互之前,我们 需要向他们显示它。

这很容易!我们只需要在用户运行的任何操作系统中打开一个窗口,初始化适当的图形后端, 然后渲染我们视图逻辑返回的组件——当然要正确布局!

什么?您不知道如何做到这一点?别担心,我有这个神奇的函数:display。它接受任何界面的引用并将其显示给用户。它完全有效!

use magic::display;

// 初始化状态
let mut counter = Counter::default();

// 运行我们的视图逻辑以获得我们的界面
let interface = counter.view();

// 向用户显示界面
display(&interface);

看?简单!玩笑归玩笑,本章的目的不是让我们学习图形编程;而是让我们 更好地理解运行时是如何工作的。一点魔法不会有害!

收集交互

用户正在看我们的界面并与之交互。我们需要非常注意所有 交互并产生我们的组件指定的所有相关消息

如何?当然是用更多的魔法!我刚刚在我的礼帽里找到了这个 interact 函数——它接受一个 界面并产生对应于用户最新交互的消息

use magic::{display, interact};

// 初始化状态
let mut counter = Counter::default();

// 运行我们的视图逻辑以获得我们的界面
let interface = counter.view();

// 向用户显示界面
display(&interface);

// 处理用户交互并获得我们的消息
let messages = interact(&interface);

太好了!interact 为我们返回一个消息列表——准备好被迭代。

对交互做出反应

此时,我们已经收集了用户交互并将它们转换为一堆消息。为了 正确地对用户做出反应,我们需要为每个消息相应地更新我们的状态

幸运的是,这一步不涉及更多的魔法技巧——我们可以只使用我们的更新逻辑

use magic::{display, interact};

// 初始化状态
let mut counter = Counter::default();

// 运行我们的视图逻辑以获得我们的界面
let interface = counter.view();

// 向用户显示界面
display(&interface);

// 处理用户交互并获得我们的消息
let messages = interact(&interface);

// 通过处理每个消息来更新我们的状态
for message in messages {
    counter.update(message);
}

这应该让我们的状态与最新的用户交互完全保持同步。

循环

好的!我们的状态已经更新以反映用户交互。现在,我们需要再次向用户显示结果界面。之后,我们必须处理任何进一步的交互…然后,再次更新我们的状态。 然后…再次重复这一切!

这是一个循环!不,循环并不是很神奇——至少当我们编写 Rust 时不是:

use magic::{display, interact};

// 初始化状态
let mut counter = Counter::default();

// 保持交互。一直如此!
loop {
    // 运行我们的视图逻辑以获得我们的界面
    let interface = counter.view();

    // 向用户显示界面
    display(&interface);

    // 处理用户交互并获得我们的消息
    let messages = interact(&interface);

    // 通过处理每个消息来更新我们的状态
    for message in messages {
        counter.update(message);
    }
}

恭喜!我们刚刚编写了一个完全功能的运行时——除了神奇的属性。我们可以清楚地理解这里 Elm 架构的每个基本部分如何适应应用程序的生命周期。

具体来说,

  • 状态初始化一次,
  • 视图逻辑在启动时运行一次,然后在每批交互后运行,
  • 更新逻辑为每个创建消息的交互运行。

冰雪法师

“这很酷”,您说,“但我不是法师,我仍然不知道如何运行我编写的计数器界面。 我有东西要计算!”

很公平!iced 实现了一个与我们刚刚构建的非常相似的运行时。它自带 自己的魔法1——所以您不需要担心自己学习黑暗艺术。

如果我们想要运行我们的 Counter,我们所要做的就是调用 run

use iced::widget::{button, column, text, Column};

pub fn main() -> iced::Result {
    iced::run("一个酷炫的计数器", Counter::update, Counter::view)
}

#[derive(Default)]
struct Counter {
    value: i64,
}

#[derive(Debug, Clone, Copy)]
enum Message {
    Increment,
    Decrement,
}

impl Counter {
    fn update(&mut self, message: Message) {
        match message {
            Message::Increment => {
                self.value += 1;
            }
            Message::Decrement => {
                self.value -= 1;
            }
        }
    }

    fn view(&self) -> Column<Message> {
        column![
            button("+").on_press(Message::Increment),
            text(self.value),
            button("-").on_press(Message::Decrement),
        ]
    }
}

我们只是给我们的应用程序一个_酷炫_的标题,然后向 运行时提供更新逻辑视图逻辑——然后它会处理其余的事情!

运行时能够从我们更新逻辑视图逻辑的类型签名中推断出 状态消息的类型。状态使用 Default 初始化,正如我们之前描述的。

还要注意 run 可能失败,因此它返回一个 iced::Result。如果我们所做的只是运行 应用程序,我们可以在 main 中直接返回这个结果。

就是这样!享受计算东西的乐趣吧——至少 3 亿年!

作者的话

您已经到达了本书的结尾,目前为止!

我认为它应该已经作为库基础知识的快速介绍。 还有很多东西需要揭示——但希望您现在已经到了可以开始 玩耍、享受乐趣和进一步实验的地步。

这本书远未完成——我想在这里涵盖更多主题,即:

  • 布局
  • 样式
  • 并发
  • 扩展应用程序
  • 扩展运行时
  • 以及更多!

在我写完它们之前,如果您想要探索和进一步学习,请查看其他资源 章节。

我希望您到目前为止享受阅读。敬请期待!

— Héctor


  1. 主要是 winitsoftbufferwgputiny-skiacosmic-text

其他资源

在我仍在编写这本书的时候,这里有一些您可以用来了解更多关于 iced 的进一步资源:

请记住,这些资源中的一些可能使用的是较旧版本的 iced。然而,虽然使用的 API 的具体细节可能会改变,但 iced 的基本思想往往是相当稳定的。

我们还有一个非常热情和活跃的社区!请随时在我们的 Discord 服务器我们的 Discourse 论坛中提出任何问题。

Discord Server Discourse

常见问题

这本书什么时候完成?

Soon™。开源是一份礼物;所以随时我想写的时候。

如何扩展大型应用程序?

您将应用程序拆分为多个屏幕,然后使用简单的组合。

袖珍指南一个展示这种方法的特定部分

我的应用程序如何从通道接收更新?

您可以使用 Task::run 从异步 Stream 生成消息。

或者,如果您控制通道的创建;您可以使用 Subscription::run

Iced 支持从右到左的文本和/或 CJK 脚本吗?

还不是很好!

您可能能够使用带有 Shaping::AdvancedText::shaping 渲染一些脚本, 但这些脚本的文本编辑尚不受支持;输入法编辑器 也不受支持。

不过,这些功能在 ROADMAP 中!

viewsubscription 函数何时被调用?

在每批消息和 update 调用之后。但这是一个实现细节; 永远不应该依赖这一点。

尝试将这些函数视为声明性的、无状态的函数。

Iced 一直在重绘吗?!

是的!iced 目前在每个运行时事件后重绘;包括微小的鼠标移动。

有计划通过检测组件状态变化来减少重绘频率,但性能到目前为止还不是优先考虑的。

渲染器确实执行了相当多的缓存;所以重绘相当便宜。因此, 对于大多数用例来说,这很少是问题!

我收到一个恐慌,说没有运行反应器。这是怎么回事?

您可能正在使用 Task 来执行需要 tokio 执行器的 Future

there is no reactor running, must be called from the context of a Tokio 1.x runtime

您应该能够通过在 iced crate 中启用 the tokio feature flag 来解决这个问题:

iced = { version = "0.13", features = ["tokio"] }