todoodoo/src/main.rs

153 lines
4.9 KiB
Rust
Raw Normal View History

2024-02-05 21:17:27 +01:00
#![warn(clippy::all, clippy::pedantic, clippy::nursery)]
2024-01-04 23:28:47 +01:00
use std::{
2024-01-05 16:07:01 +01:00
collections::BTreeMap,
2024-01-04 23:28:47 +01:00
error::Error,
fmt::{self, Debug, Display},
fs::{File, OpenOptions},
io::{Read, Seek, SeekFrom, Write},
path::Path,
};
2024-01-04 16:36:41 +01:00
2024-01-05 16:07:01 +01:00
use eframe::{
egui::{self, FontData, FontDefinitions, Key, TextEdit, ViewportBuilder},
epaint::{FontFamily, FontId},
CreationContext,
};
2024-01-04 23:28:47 +01:00
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(());
}
}
2024-01-04 16:36:41 +01:00
std::env::set_var("WINIT_UNIX_BACKEND", "wayland");
2024-01-04 23:28:47 +01:00
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)?;
2024-01-05 13:16:08 +01:00
let native_options = eframe::NativeOptions {
viewport: ViewportBuilder::default().with_app_id("todoodoo"),
..Default::default()
};
2024-01-04 23:28:47 +01:00
eframe::run_native(
"To-DooDoo",
2024-01-04 16:36:41 +01:00
native_options,
2024-01-05 16:07:01 +01:00
Box::new(|creation_context| Box::new(ToDooDooApp::new(creation_context, todo_file))),
2024-01-04 23:28:47 +01:00
)
.map_err(RunError::EFrame)?;
Ok(())
2024-01-04 16:36:41 +01:00
}
2024-01-04 23:28:47 +01:00
struct ToDooDooApp {
notes: String,
file: File,
}
impl ToDooDooApp {
2024-01-05 16:07:01 +01:00
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));
});
2024-01-04 23:28:47 +01:00
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");
2024-02-05 21:17:27 +01:00
Self { notes, file }
2024-01-04 23:28:47 +01:00
}
2024-01-04 16:36:41 +01:00
2024-01-04 23:28:47 +01:00
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");
2024-01-04 16:36:41 +01:00
}
}
2024-01-04 23:28:47 +01:00
impl eframe::App for ToDooDooApp {
2024-01-04 16:36:41 +01:00
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
2024-01-05 16:07:01 +01:00
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 <C-s>").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));
});
2024-01-04 16:36:41 +01:00
}
2024-01-05 13:16:35 +01:00
fn save(&mut self, _storage: &mut dyn eframe::Storage) {
self.save();
}
2024-01-04 16:36:41 +01:00
}