跳转至

Asynchronous File Loading

Keywords: iced::Application, iced::Command, iced::executor, std::io, std::path::Path, std::sync::Arc, tokio::fs::read_to_string

为了实现异步文件加载,我们需要使用tokio

[dependencies]
tokio = { version = "1.32", features = ["fs"] }

Sandbox不能直接执行异步函数,需要用Application。相比于SandboxApplication需要额外实现如下类型

impl Application for Editor {
    // ...
    type Theme; // Color theme
    type Executor; // Engine for running async tasks
    type Flags; // Initial state
}

此处,ThemeFlags类型不需要额外实现,可以分别用iced::Theme()代替。默认情况下,Executor类型需要用iced::executor::Default代替。

在修改为Application后,newupdate方法的返回值标签也需要修改,new方法返回(Self, Command<Message>)update方法返回Command<Message>。其中Command<Message>是一个异步任务,在执行完毕后会发送一个对应类型的Message

impl Application for Editor {
    // ...

    fn new(_flags: ()) -> (Self, Command<Message>) {
        (
            Self {
                // ...
            },
            Command::none(),
        )
    }

    fn update(&mut self, message: Message) -> Command<Message> {
        match message {
            // ...
        }

        Command::none()
    }
}

此后,我们需要编写用于读取文件的函数。

use std::io;
use std::path::Path;
use std::sync::Arc;

async fn load_file<T>(path: T) -> Result<Arc<String>, io::ErrorKind>
    where T: AsRef<Path>
{
    tokio::fs::read_to_string(path)
        .await
        .map(Arc::new)
        .map_err(|e| e.kind())
}

new方法中,我们可以使用Command来调用load_file函数。

enum Message {
    EditorEdit(text_editor::Action),
    FileOpened(Result<Arc<String>, io::ErrorKind>)
}

impl Application for Editor {
    // ...

    fn new(_flags: ()) -> (Self, Command<Message>) {
        let path = "path/to/file.txt";
        let file = load_file(path);

        (
            Self {
                // ...
            },
            Command::perform(
                load_file(format!("{}/main.rs", env!("CARGO_MANIFEST_DIR"))),
                Message::FileLoaded,
            ),
        )
    }
}

读取文件时可能会发生错误,需要对异常消息进行处理。在load_file函数中已经通过Result返回了对应的错误类型,只需要在update方法中处理Message::FileOpened即可。

struct Editor {
    // ...
    error: Option<io::ErrorKind>, // Use Option to store error
}

// ...

impl Application for Editor {
    // ...

    fn new(_flags: ()) -> (Self, Command<Message>) {
        (
            Self {
                // ...
                error: None, // Initialize error as None
            },
            // ...
        )
    }

    fn update(&mut self, message: Message) -> Command<Message> {
        // ...
        match message {
            // ...
            Message::FileOpened(Error(e)) => {
                self.error = Some(e); // Store error
            }
        }
        // ...
    }
}

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

Download source code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
use iced::{Theme, Element, Sandbox, Settings, Length};
use iced::widget::{column, container, horizontal_space, row, text, text_editor};

fn main() -> iced::Result {
    Editor::run(Settings::default())
}

struct Editor {
    content: text_editor::Content
}

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

impl Sandbox for Editor {
    type Message = Message; // Define the type of messages

    fn new() -> Self {
        Self {
            content: text_editor::Content::with(include_str!("main.rs"))
        }
    }

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

    fn update(&mut self, message: Message) {
        // Handle messages here
        match message {
            Message::EditorEdit(action) => {
                self.content.edit(action);
            }
        }
    }

    fn view(&self) -> Element<'_, Message> {
        // Create the user interface here
        let editor = text_editor(&self.content).on_edit(Message::EditorEdit);

        // Query cursor position
        let cursor_indicator = {
            let (line, column) = self.content.cursor_position();

            text(format!("Line: {}, Column: {}", line + 1, column + 1))
        };
        let status_bar = row![horizontal_space(Length::Fill), cursor_indicator];

        container(column![editor, status_bar].spacing(10)).padding(10).into()
    }

    fn theme(&self) -> Theme {
        Theme::Dark
    }
}

评论