Back to Blog

Text Editor in Rust egui — Open, Save & File Dialogs | Learn egui Ep32

Celest KimCelest Kim

Video: Text Editor in Rust egui — Open, Save & File Dialogs | Learn egui Ep32 by Taught by Celeste AI - AI Coding Coach

Watch full page →

Text Editor in Rust egui — Open, Save & File Dialogs

This example demonstrates how to build a simple text editor in Rust using the egui GUI framework and the rfd crate for native file dialogs. It covers opening and saving files with filters, displaying a multiline text area with a monospace font, and managing the current file state with Option and PathBuf.

Code

use eframe::{egui, epi};
use egui::{TextEdit, TopBottomPanel, Layout, ScrollArea, TextStyle};
use rfd::FileDialog;
use std::fs;
use std::path::PathBuf;

struct TextEditorApp {
  text: String,
  current_file: Option<PathBuf>,
}

impl Default for TextEditorApp {
  fn default() -> Self {
    Self {
      text: String::new(),
      current_file: None,
    }
  }
}

impl epi::App for TextEditorApp {
  fn name(&self) -> &str {
    "egui Text Editor"
  }

  fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
    // Top toolbar panel with buttons
    TopBottomPanel::top("toolbar").show(ctx, |ui| {
      ui.horizontal(|ui| {
        if ui.button("New").clicked() {
          self.text.clear();
          self.current_file = None;
        }
        if ui.button("Open").clicked() {
          if let Some(path) = FileDialog::new()
            .add_filter("Text", &["txt", "rs", "md", "toml"])
            .pick_file()
          {
            if let Ok(contents) = fs::read_to_string(&path) {
              self.text = contents;
              self.current_file = Some(path);
            }
          }
        }
        if ui.button("Save").clicked() {
          if let Some(path) = &self.current_file {
            let _ = fs::write(path, &self.text);
          } else {
            // Delegate to Save As if no file selected
            if let Some(path) = FileDialog::new()
              .add_filter("Text", &["txt", "rs", "md", "toml"])
              .save_file()
            {
              let _ = fs::write(&path, &self.text);
              self.current_file = Some(path);
            }
          }
        }
        if ui.button("Save As").clicked() {
          if let Some(path) = FileDialog::new()
            .add_filter("Text", &["txt", "rs", "md", "toml"])
            .save_file()
          {
            let _ = fs::write(&path, &self.text);
            self.current_file = Some(path);
          }
        }
      });
    });

    // Central scrollable multiline text editor
    egui::CentralPanel::default().show(ctx, |ui| {
      ScrollArea::vertical().show(ui, |ui| {
        ui.add(
          TextEdit::multiline(&mut self.text)
            .font(TextStyle::Monospace) // Use monospace font
            .desired_rows(20)
            .lock_focus(true)
        );
      });
    });

    // Bottom status bar with character count aligned right
    TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
      ui.with_layout(Layout::right_to_left(), |ui| {
        ui.label(format!("{} chars", self.text.chars().count()));
      });
    });
  }
}

fn main() {
  let app = TextEditorApp::default();
  let native_options = eframe::NativeOptions::default();
  eframe::run_native(Box::new(app), native_options);
}

Key Points

  • The rfd crate provides native file dialogs with FileDialog::new(), supporting filters and open/save modes.
  • Use std::fs::read_to_string and fs::write for reading and writing file contents as Strings.
  • Track the currently opened file with Option<PathBuf> to manage save behavior.
  • egui's TextEdit::multiline with monospace font creates a comfortable text editing area.
  • TopBottomPanel and Layout::right_to_left enable toolbar and status bar with aligned controls and info.