#![warn(clippy::all, clippy::pedantic, clippy::nursery)] use std::{ collections::BTreeMap, error::Error, fmt::{self, Debug, Display}, fs::{File, OpenOptions}, io::{Read, Seek, SeekFrom, Write}, path::Path, }; use eframe::{ egui::{self, FontData, FontDefinitions, Key, TextEdit, ViewportBuilder}, epaint::{FontFamily, FontId}, CreationContext, }; enum RunError { HomeDir(homedir::GetHomeError), NoHome, OpenFile(std::io::Error), EFrame(eframe::Error), } impl Debug for RunError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Display::fmt(self, f) } } impl Display for RunError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::HomeDir(e) => f.write_fmt(format_args!("homedir error: {e}")), Self::NoHome => f.write_fmt(format_args!("failed to find home directory")), Self::OpenFile(e) => f.write_fmt(format_args!("error while opening todo.txt: {e}")), Self::EFrame(e) => f.write_fmt(format_args!("eframe error: {e}")), } } } impl Error for RunError {} fn main() -> Result<(), RunError> { for argument in std::env::args() { if argument == "-v" || argument == "--version" { println!(env!("CARGO_PKG_VERSION")); return Ok(()); } } std::env::set_var("WINIT_UNIX_BACKEND", "wayland"); let home_dir = homedir::get_my_home() .map_err(RunError::HomeDir)? .ok_or(RunError::NoHome)?; let todo_file = OpenOptions::new() .create(true) .read(true) .write(true) .append(false) .open(Path::join(&home_dir, "todo.txt")) .map_err(RunError::OpenFile)?; let native_options = eframe::NativeOptions { viewport: ViewportBuilder::default().with_app_id("todoodoo"), ..Default::default() }; eframe::run_native( "To-DooDoo", native_options, Box::new(|creation_context| Box::new(ToDooDooApp::new(creation_context, todo_file))), ) .map_err(RunError::EFrame)?; Ok(()) } struct ToDooDooApp { notes: String, file: File, } impl ToDooDooApp { fn new(creation_context: &CreationContext, mut file: File) -> Self { const FONT_NAME: &str = "Monocraft"; let mut families = BTreeMap::new(); families.insert(FontFamily::Proportional, vec![FONT_NAME.to_string()]); families.insert(FontFamily::Monospace, vec![FONT_NAME.to_string()]); let mut font_data = BTreeMap::new(); font_data.insert( "Monocraft".into(), FontData::from_static(include_bytes!(concat!( env!("CARGO_MANIFEST_DIR"), "/fonts/Monocraft-NF.ttf" ))), ); creation_context.egui_ctx.set_fonts(FontDefinitions { font_data, families, }); creation_context.egui_ctx.style_mut(|style| { style.override_font_id = Some(FontId::new(12.0, FontFamily::Monospace)); }); let mut notes = String::with_capacity( file.metadata() .expect("file metadata") .len() .try_into() .expect("file too big"), ); file.read_to_string(&mut notes) .expect("failed to read file"); Self { notes, file } } fn save(&mut self) { self.file.set_len(0).expect("failed to truncate file"); self.file.seek(SeekFrom::Start(0)).expect("failed to seek"); self.file .write_all(self.notes.as_bytes()) .expect("failed to write file"); self.file.flush().expect("failed to flush"); } } impl eframe::App for ToDooDooApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::TopBottomPanel::top("button_bar") .frame(egui::Frame::none()) .show(ctx, |ui| { ui.horizontal(|ui| { ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { if ui.button("save").on_hover_text("or use ").clicked() { self.save(); } }); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.label("hover for licenses") .on_hover_text("Monocraft font - SIL OPEN FONT LICENSE Version 1.1"); }); }); }); egui::CentralPanel::default() .frame(egui::Frame::none()) .show(ctx, |ui| { if ctx.input(|i| i.key_pressed(Key::S) && i.modifiers.command) { self.save(); } ui.add_sized(ui.available_size(), TextEdit::multiline(&mut self.notes)); }); } fn save(&mut self, _storage: &mut dyn eframe::Storage) { self.save(); } }