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> {
|
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
|
|
|
}
|