跳转至

File Path Indicator

本节在状态栏中添加一个显示文件路径的文本控件。首先需要存储文件路径状态,并在初始化阶段设置为None

struct Editor {
    path: Option<PathBuf>,
    // ...
}

impl Application for Editor {
    // ...
    fn new() -> (Editor, Command<Message>) {
        (
            Editor {
                path: None,
                // ...
            },
            // ...
        )
    }
    // ...
}

Path与PathBuf

std::path::Pathstd::path::PathBuf的关系类似于&strString的关系。Path是一个不可变引用,PathBuf是一个可变的对象,因此存储路径状态需要使用PathBuf,引用路径可以使用Path

之前的load_file函数只返回了文件内容,此处需要将文件的路径一同返回。注意函数不能返回引用,因此需要将路径转换为PathBuf类型。and_then方法处理ResultOk值,在成功读取文件后将Path转换为PathBuf并返回。调用load_filepick_file函数也需要一并修改,同时返回路径和文件内容。

async fn pick_file() -> Result<(PathBuf, Arc<String>), Error> {
    // ...
}

async fn load_file(path: impl AsRef<Path>) -> Result<(PathBuf, Arc<String>), Error> {
    let content = tokio::fs::read_to_string(path.as_ref())
        .await
        .map(Arc::new)
        .map_err(|err| err.kind())
        .map_err(Error::IO);
    content.and_then(|content| Ok((path.as_ref().to_path_buf(), content)))
}

在修改读取文件的函数后,需要修改函数回调事件的类型和对应的处理函数

enum Message {
    // ...
    FileOpened(Result<(PathBuf, Arc<String>), Error>),
}

impl Application for Editor {
    // ...
    fn update(&mut self, message: Message) -> Command<Message> {
        match message {
            // ...
            Message::FileOpened(Ok((path, result))) => {
                self.path = Some(path);
                self.content = text_editor::Content::with(&result);
                Command::none()
            },
        }
    }
}
enum Message {
    // ...
    FileOpened(Result<Arc<String>, Error>),
}

impl Application for Editor {
    // ...
    fn update(&mut self, message: Message) -> Command<Message> {
        match message {
            // ...
            Message::FileOpened(Ok(result)) => {
                self.content = text_editor::Content::with(&result);
                Command::none()
            },
        }
    }
}

最后在状态栏中添加一个文本控件显示文件路径即可。

impl Application for Editor {
    // ...
    fn view(&mut self) -> Element<Message> {
        // ...
        let path_indicator = match &self.path {
            None => text(""),
            Some(path) => text(path.to_string_lossy())
        };
        let status_bar = row![
            path_indicator, // Add path indicator here
            horizontal_space(Length::Fill),
            cursor_indicator
        ];
        // ...
    }
}

以下为完整的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
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use std::io;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use iced::{executor, Application, Command, Element, Length, Settings, Theme};
use iced::widget::{button, column, container, horizontal_space, row, text, text_editor};

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

struct Editor {
    path: Option<PathBuf>,
    content: text_editor::Content,
    error: Option<Error>
}

#[derive(Debug, Clone)]
enum Message {
    EditorEdit(text_editor::Action),
    FileOpened(Result<(PathBuf, Arc<String>), Error>),
    OpenButtonPressed
}

impl Application for Editor {
    type Message = Message; // Define the type of messages
    type Theme = Theme;
    type Executor = executor::Default; // Engine for running async tasks
    type Flags = ();

    fn new(_flags: Self::Flags) -> (Self, Command<Message>) {
        (
            Self {
                content: text_editor::Content::new(),
                error: None,
                path: None
            },
            Command::perform(
            load_file(format!("{}/src/main.rs", env!("CARGO_MANIFEST_DIR"))),
            Message::FileOpened
            )
        )
    }

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

    fn update(&mut self, message: Message) -> Command<Message> {
        // Handle messages here
        match message {
            Message::EditorEdit(action) => {
                self.content.edit(action);
                Command::none()
            },
            Message::FileOpened(Ok((path, result))) => {
                self.path = Some(path);
                self.content = text_editor::Content::with(&result);
                Command::none()
            },
            Message::FileOpened(Err(error)) => {
                self.error = Some(error);
                Command::none()
            },
            Message::OpenButtonPressed => {
                Command::perform(pick_file(), Message::FileOpened)
            }
        }
    }

    fn view(&self) -> Element<'_, Message> {
        // Create the user interface here
        let editor = text_editor(&self.content).on_edit(Message::EditorEdit);
        let controls = row![button("Open").on_press(Message::OpenButtonPressed)];

        // Query cursor position
        let path_indicator = match &self.path {
            None => text(""),
            Some(path) => text(path.to_string_lossy())
        };
        let cursor_indicator = {
            let (line, column) = self.content.cursor_position();

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

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

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

async fn pick_file() -> Result<(PathBuf, Arc<String>), Error> {
    let file_handle = rfd::AsyncFileDialog::new()
        .set_title("Choose a text file...")
        .pick_file()
        .await
        .ok_or(Error::DialogClosed)?;
    load_file(file_handle.path()).await
}

async fn load_file(path: impl AsRef<Path>) -> Result<(PathBuf, Arc<String>), Error> {
    let content = tokio::fs::read_to_string(path.as_ref())
        .await
        .map(Arc::new)
        .map_err(|err| err.kind())
        .map_err(Error::IO);
    content.and_then(|content| Ok((path.as_ref().to_path_buf(), content)))
}

#[derive(Debug, Clone)]
enum Error {
    DialogClosed,
    IO(io::ErrorKind)
}

评论