Skip to content

Commit

Permalink
Merge pull request #321 from HuangFuSL/Rust-gui-starter
Browse files Browse the repository at this point in the history
Rust gui starter project
  • Loading branch information
github-actions[bot] authored Feb 9, 2024
2 parents b87bcbc + 62bba4c commit 9d67de4
Show file tree
Hide file tree
Showing 27 changed files with 2,526 additions and 3 deletions.
4 changes: 3 additions & 1 deletion docs/coding/.pages
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ nav:
- Python:
- 语法: python
- matplotlib: matplotlib
- Rust: rust
- Rust:
- 语法: rust
- GUI: rust-gui
- 机器学习: machine-learning
- 数据结构: dsa
- 系统配置: configuration
1 change: 1 addition & 0 deletions docs/coding/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ hide:
* [Python](python/index.md)
* [matplotlib](matplotlib/index.md)
* [Rust](rust/index.md)
* [GUI](rust-gui/index.md)
* [机器学习](machine-learning/index.md)
* [数据结构](dsa/index.md)
* [系统配置](configuration/index.md)
Expand Down
13 changes: 13 additions & 0 deletions docs/coding/rust-gui/.pages
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
nav:
- index.md
- Text editor step by step:
- 1. Hello, World!: 1-hello-world.md
- 2. Multi-line input: 2-multi-line-input.md
- 3. Theme and cursor indicator: 3-theme-and-cursor-indicator.md
- 4. Async file loading: 4-async-file-loading.md
- 5. File picker: 5-file-picker.md
- 6. File path indicator: 6-file-path-indicator.md
- 7. New and Save: 7-new-and-save.md
- 8. Button Prettify: 8-button-prettify.md
- 9. Syntax Highlighting: 9-syntax-highlighting.md
- 10. Misc: 10-misc.md
95 changes: 95 additions & 0 deletions docs/coding/rust-gui/1-hello-world.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Hello World App

Keywords: `iced::Sandbox`, `iced::Settings`, `iced::widget::text`

## 创建项目

首先,创建一个新的 Rust 项目,并添加对`iced`库的依赖。

```bash
cargo new text-editor
cd text-editor
cargo add iced
```

此时的`main.rs`文件内容如下:

```rust
fn main() {
println!("Hello, world!");
}
```

## 创建GUI应用

`iced`的GUI类有`Application``Sandbox`两种。其中`Sandbox`是一个简化版的`Application`,提供了一些默认的行为。`Sandbox`特性包含如下方法:

```rust hl_lines="2 3 4 5 6"
pub trait Sandbox {
type Message: std::fmt::Debug + Send;
fn new() -> Self;
fn title(&self) -> String;
fn update(&mut self, message: Self::Message);
fn view(&self) -> Element<'_, Self::Message>;
fn theme(&self) -> Theme {
Theme::default()
}
fn style(&self) -> theme::Application {
theme::Application::default()
}
fn scale_factor(&self) -> f64 {
1.0
}
fn run(settings: Settings<()>) -> Result<(), Error>
where
Self: 'static + Sized,
{
<Self as Application>::run(settings)
}
}
```

为了使用`Sandbox`,我们需要实现其中未实现的方法,即`Message``new``title``update``view`

* `Message`是一个枚举类型,用于定义应用会产生的消息类型,需要实现`std::fmt::Debug``Send`特性。

```rust
#[derive(Debug)] // Inherit the Debug trait
enum Message {} // No message required
```

* `new`方法用于创建一个新的`Sandbox`实例,初始化实例状态,一般情况下直接返回`Self`即可。

```rust
fn new() -> Self {
Self
}
```

* `title`方法用于返回GUI窗口的标题。

```rust
fn title(&self) -> String {
String::from("A text editor")
}
```

* `update`方法和`view`方法共同组成应用的消息循环:`update`方法用于处理消息,更新应用状态,`view`方法用于在状态更新后更新应用界面。此处我们在`view`方法中放置一个文本控件。

```rust
fn update(&mut self, message: Self::Message) {
match message {
// No message to handle
}
}

fn view(&self) -> Element<'_, Self::Message> {
text("Hello, world!").into()
}
```

通过`Sandbox::run`方法启动应用,该方法返回`Result<(), Error>`类型,可以直接作为`main`函数的返回值。

以下为完整的`main.rs`文件内容:

{{ read_code_from_file('docs/coding/rust-gui/source/1.rs') }}
97 changes: 97 additions & 0 deletions docs/coding/rust-gui/10-misc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Miscellaneous

本节添加一些额外功能。

首先,当文件没有被修改时,可以设置禁用保存按钮。这样可以避免用户误操作。在`on_press_maybe`中传入`None`即可。同时,可以根据文件的修改情况设置按钮的样式。

```rust hl_lines="10 15 16 17 18 19 20 21"
fn toolbar_button<'a>(description: &str, callback: Option<Message>) -> Element<'a, Message> {
let font = Font::with_name("editor-icon");
let lower = description.to_lowercase();
let icon = text(match lower.as_str() {
"new" => '\u{E800}',
"open" => '\u{F115}',
"save" => '\u{E801}',
_ => ' '
}).font(font);
let is_disabled = callback.is_none();
tooltip(
button(container(icon)
.width(30) // Set the width of the button
.center_x() // Center the icon
).on_press_maybe(callback).style(
if is_disabled {
theme::Button::Secondary
} else {
theme::Button::Primary
}
),
description, tooltip::Position::FollowCursor
).style(theme::Container::Box).into()
}
```

同时修改`toolbar_button`的调用。

```rust hl_lines="5"
// ... In `view` function
let controls = row![
toolbar_button("New", Some(Message::NewButtonPressed)),
toolbar_button("Open", Some(Message::OpenButtonPressed)),
toolbar_button("Save", if self.modified { Some(Message::SaveButtonPressed) } else { None }),
horizontal_space(Length::Fill),
pick_list(highlighter::Theme::ALL, Some(self.theme), Message::ThemeChanged)
].spacing(10);
```

我们可以添加不同的快捷键,以方便用户操作。

```rust
// In `impl Application for Editor`
fn subscription(&self) -> Subscription<Message> {
keyboard::on_key_press(|keycode, modifier| {
match (keycode, modifier) {
(keyboard::KeyCode::S, keyboard::Modifiers::COMMAND) => {
Some(Message::SaveButtonPressed)
},
(keyboard::KeyCode::O, keyboard::Modifiers::COMMAND) => {
Some(Message::OpenButtonPressed)
},
(keyboard::KeyCode::N, keyboard::Modifiers::COMMAND) => {
Some(Message::NewButtonPressed)
},
_ => None
}
})
}
```

这样,用户可以使用`Command + S`来保存文件,`Command + O`来打开文件,`Command + N`来新建文件。

文件的标题栏通常显示文件路径,可以和左下角的状态栏保持同步。

```rust
// ... In `impl Application for Editor`
fn title(&self) -> String {
let path_text = match &self.path {
None => String::from("New file"),
Some(path) => path.to_string_lossy().to_string()
};
let suffix = if self.modified { "*" } else { "" };
format!("{}{}", path_text, suffix)
}

// ... In `view` function
let path_indicator = if let Some(error) = &self.error {
match error {
Error::DialogClosed => text("Dialog closed"),
Error::IO(kind) => text(format!("I/O error: {:?}", kind))
}
} else {
text(self.title())
};
```

以下为完整的`main.rs`文件内容:

{{ read_code_from_file('docs/coding/rust-gui/source/10.rs') }}
102 changes: 102 additions & 0 deletions docs/coding/rust-gui/2-multi-line-input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Add Multi-line Input

Keywords: `iced::widget::text_editor`, `iced::widget::container`.

本节我们将`text`控件替换为`text_editor`控件,以实现多行文本的输入。

!!! warning "iced库版本"

`text_editor`控件尚未在正式版本中发布,需要使用`git`仓库中的代码。

```toml
[dependencies]
iced = { git = "https://github.com/iced-rs/iced.git", rev = "refs/tags/text-editor" }
```

首先,将引入的`iced::widget::text`替换为`iced::widget::text_editor``text_editor`是有状态的,需要在`new`中初始化空间状态,并且在`view`中根据状态更新控件内容。

```rust
struct Editor {
content: text_editor::Content
}

// ...

impl Sandbox for Editor {
type Message = Message;

fn new() -> Self {
Editor {
content: text_editor::Content::new() // Initialize the content
}
}

// ...

fn view(&self) -> Element<'_, Message> {
text_editor(&self.content).into() // Link the content state
}
}
```

默认情况下,`into`方法会使控件充满整个窗口,我们可以使用`container`包围`text_editor`控件辅助布局。

```rust
use iced::widget::container;

// ...

impl Sandbox for Editor {
// ...
fn view(&self) -> Element<'_, Message> {
let editor = text_editor(&self.content);
container(editor).padding(10).into() // Add padding and wrap the editor
}
}
```

此时,多行文本输入框的布局已经完成。但由于输入框没有绑定事件处理,因此目前无法输入文本。`text_editor`控件支持的事件有

```rust hl_lines="6"
pub enum Action {
Move(Motion),
Select(Motion),
SelectWord,
SelectLine,
Edit(Edit),
Click(Point),
Drag(Point),
Scroll { lines: i32 },
}
```

我们需要将`Edit`事件在`update``view`中进行处理,同时更新`Message`枚举类型。

```rust
#[derive(Debug, Clone)]
enum Message {
EditorEdit(text_editor::Action)
}

// ...

impl Sandbox for Editor {
// ...
fn update(&mut self, message: Message) {
match message {
Message::EditorEdit(action) => {
self.content = self.content.edit(action);
}
}
}

fn view(&self) -> Element<'_, Message> {
let editor = text_editor(&self.content).on_edit(Message::EditorEdit); // Bind the edit event
container(editor).padding(10).into() // Add padding and wrap the editor
}
}
```

以下为完整的`main.rs`文件内容:

{{ read_code_from_file('docs/coding/rust-gui/source/2.rs') }}
Loading

0 comments on commit 9d67de4

Please sign in to comment.