-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #321 from HuangFuSL/Rust-gui-starter
Rust gui starter project
- Loading branch information
Showing
27 changed files
with
2,526 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') }} |
Oops, something went wrong.