diff --git a/Cargo.lock b/Cargo.lock index 9197671..0812e0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -554,6 +554,9 @@ name = "bitflags" version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +dependencies = [ + "serde", +] [[package]] name = "block" @@ -936,9 +939,12 @@ version = "0.1.0" dependencies = [ "chrono", "eframe", + "egui", "egui_extras", "env_logger", "reqwest", + "serde", + "serde_json", ] [[package]] @@ -1047,6 +1053,7 @@ dependencies = [ "epaint", "log", "nohash-hasher", + "ron", "serde", ] @@ -2740,6 +2747,18 @@ dependencies = [ "winreg", ] +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64", + "bitflags 2.4.2", + "serde", + "serde_derive", +] + [[package]] name = "rustc-demangle" version = "0.1.23" diff --git a/Cargo.toml b/Cargo.toml index a6bf832..366f7ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ publish = false [dependencies] chrono = "0.4.34" +egui = { version = "0.26.2", features = ["persistence"] } eframe = { version = "0.26.2", features = [ "default", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO @@ -21,3 +22,5 @@ env_logger = { version = "0.11.2", default-features = false, features = [ "humantime", ] } reqwest = { version = "0.11.24", features = ["blocking"] } +serde = { version = "1.0.197", features = ["derive"] } +serde_json = "1.0.114" diff --git a/src/apps/clock.rs b/src/apps/clock.rs index c216be3..b1efa23 100644 --- a/src/apps/clock.rs +++ b/src/apps/clock.rs @@ -6,13 +6,24 @@ use eframe::{ App, }; +use crate::prelude::CanonicalName; + +#[derive(Default)] pub struct ClockWidget; +impl CanonicalName for ClockWidget { + fn canonical_name() -> String { + "Clock".into() + } +} + impl App for ClockWidget { fn update(&mut self, ctx: &eframe::egui::Context, frame: &mut eframe::Frame) { Window::new("Clock") + .title_bar(false) .resizable(false) .movable(true) + .open(&mut true) .show(ctx, |ui| { ui.label( RichText::new(Local::now().format("%H:%M:%S").to_string()) diff --git a/src/apps/mod.rs b/src/apps/mod.rs index c13e65f..4497914 100644 --- a/src/apps/mod.rs +++ b/src/apps/mod.rs @@ -1,3 +1,4 @@ pub mod uptime; pub mod greeting; -pub mod clock; \ No newline at end of file +pub mod clock; +pub mod todo; \ No newline at end of file diff --git a/src/apps/todo.rs b/src/apps/todo.rs new file mode 100644 index 0000000..0ce07fc --- /dev/null +++ b/src/apps/todo.rs @@ -0,0 +1,39 @@ +use eframe::WindowBuilder; +use egui::{Key, Window}; + +use crate::prelude::CanonicalName; + +#[derive(Default)] +pub struct TodoWidget { + todos: Vec<(String, bool)>, + _goal_input_cache: String, +} + +impl CanonicalName for TodoWidget { + fn canonical_name() -> String { + "Todos".into() + } +} + +impl eframe::App for TodoWidget { + fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { + Window::new("Todos").show(ctx, |ui| { + self.todos.sort_by_key(|a| !a.1); + self.todos.retain_mut(|t| { + ui.horizontal(|ui| { + ui.checkbox(&mut t.1, t.0.clone()); + !ui.button("remove").clicked() + }) + .inner + }); + + ui.horizontal(|ui| { + ui.text_edit_singleline(&mut self._goal_input_cache); + if ctx.input(|i| i.key_pressed(Key::Enter)) || ui.button("add").clicked() { + self.todos.push((self._goal_input_cache.clone(), false)); + self._goal_input_cache = String::default(); + } + }); + }); + } +} diff --git a/src/apps/uptime.rs b/src/apps/uptime.rs index bec84b2..8ad9790 100644 --- a/src/apps/uptime.rs +++ b/src/apps/uptime.rs @@ -1,13 +1,17 @@ -use eframe::egui::{Color32, Context, Label, Response, Ui, Widget}; +use chrono::Local; +use eframe::egui::{Color32, Context, Label, Response, RichText, Ui, Widget}; use eframe::{egui, Frame}; -use reqwest::Url; +use serde::{Deserialize, Serialize}; +use std::default; use std::ops::Sub; -use std::str::FromStr; -use std::time::{Duration, Instant}; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use crate::prelude::CanonicalName; + +#[derive(Serialize, Deserialize)] pub struct MonitorTarget { url: String, - last_update: Instant, + last_update: u128, last_status: bool, } @@ -16,14 +20,14 @@ impl MonitorTarget { MonitorTarget { url: url.into(), last_status: false, - last_update: Instant::now().sub(Duration::new(30, 0)), + last_update: Local::now().timestamp_millis() as u128, } } pub fn check(&mut self) -> bool { - if self.last_update.elapsed().as_secs() > 30 { + if (Local::now().timestamp_millis() as u128 - self.last_update) > 30 { self.last_status = reqwest::blocking::get(self.url.as_str()).is_ok(); - self.last_update = Instant::now(); + self.last_update = Local::now().timestamp_millis() as u128; } self.last_status } @@ -35,36 +39,63 @@ pub struct UptimeWidget { _url_input_cache: String, } -impl eframe::App for UptimeWidget { - fn update(&mut self, ctx: &Context, frame: &mut Frame) { - egui::Window::new("Uptime Widget").show(ctx, |ui| { - self.targets.retain_mut(|target| { - ui.horizontal(|ui| { - ui.label(target.url.clone()); - ui.label({ - if target.check() { - format!("OK ({}s ago)", target.last_update.elapsed().as_secs()) - } else { - format!("FAIL ({}s ago)", target.last_update.elapsed().as_secs()) - } - }); - !ui.button("x").clicked() - }) - .inner - }); - ui.horizontal(|ui| { - ui.text_edit_singleline(&mut self._url_input_cache) - .on_hover_text("URL to add"); - if ui.button("Add URL").clicked() { - self.targets - .push(MonitorTarget::new(self._url_input_cache.clone())); - self._url_input_cache = String::default(); - } - }); - - if ui.is_visible() { - ctx.request_repaint(); - } - }); +impl CanonicalName for UptimeWidget { + fn canonical_name() -> String { + "Uptimes".into() + } +} + +impl eframe::App for UptimeWidget { + fn update(&mut self, ctx: &Context, frame: &mut Frame) { + egui::Window::new("Uptime Settings") + .default_open(false) + .show(ctx, |ui| { + self.targets.retain(|t| { + ui.add_space(1.); + ui.horizontal(|ui| { + ui.style_mut().visuals.extreme_bg_color = Color32::DARK_GRAY; + ui.label(t.url.clone()); + !ui.button("remove").clicked() + }) + .inner + }); + ui.add_space(2.); + ui.horizontal(|ui| { + ui.text_edit_singleline(&mut self._url_input_cache) + .on_hover_text("URL to add"); + if ui.button("Add URL").clicked() { + self.targets + .push(MonitorTarget::new(self._url_input_cache.clone())); + self._url_input_cache = String::default(); + } + }); + }); + + self.targets.iter_mut().for_each(|t| { + egui::Window::new(t.url.clone()) + .title_bar(false) + .movable(true) + .resizable(true) + .show(ctx, |ui| { + ui.vertical_centered(|ui| { + ui.label(t.url.clone()); + ui.label({ + if ctx.is_using_pointer() { + RichText::new("...") + } else if t.check() { + RichText::new("Up").color(Color32::LIGHT_GREEN) + } else { + RichText::new("Down") + .color(Color32::LIGHT_RED) + .strong() + }.size(32.) + }); + }); + }); + }); + + if self.targets.len() > 0 { + ctx.request_repaint(); + } } } diff --git a/src/dashboard.rs b/src/dashboard.rs index 118f2cd..22c0fc6 100644 --- a/src/dashboard.rs +++ b/src/dashboard.rs @@ -2,11 +2,14 @@ use std::collections::HashMap; use crate::apps::clock::ClockWidget; use crate::apps::greeting::Greeting; +use crate::apps::todo::TodoWidget; use crate::apps::uptime::UptimeWidget; +use crate::prelude::CanonicalName; use eframe::egui::accesskit::Role::Window; use eframe::egui::panel::Side; use eframe::egui::{CentralPanel, Id, SidePanel}; use eframe::{egui, App}; +use serde::{Deserialize, Serialize}; #[derive(Default)] pub struct Dashboard { @@ -29,16 +32,16 @@ impl Dashboard { } macro_rules! register_app { - ($dash:ident, $ui:ident, $name:literal, $app:expr) => { - let button = $ui.button($name); + ($dash:ident, $ui:ident, $app:tt) => { + let button = $ui.button($app::canonical_name()); if button.clicked() { - if $dash.has_app($name) { - $dash.remove_app($name); + if $dash.has_app($app::canonical_name()) { + $dash.remove_app($app::canonical_name()); } else { - $dash.add_app($name, Box::new($app)); + $dash.add_app($app::canonical_name(), Box::new($app::default())); } } - if $dash.has_app($name) { + if $dash.has_app($app::canonical_name()) { button.highlight(); } }; @@ -48,12 +51,13 @@ impl eframe::App for Dashboard { fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { SidePanel::new(Side::Left, Id::new("side_panel")).show(ctx, |ui| { ui.heading("Apps"); - register_app!(self, ui, "Clock", ClockWidget); - register_app!(self, ui, "Uptime Widget", UptimeWidget::default()); + register_app!(self, ui, ClockWidget); + register_app!(self, ui, UptimeWidget); + register_app!(self, ui, TodoWidget); }); - CentralPanel::default().show(ctx, |ui| {}); + CentralPanel::default().show(ctx, |_| {}); self.apps.iter_mut().for_each(|(_, app)| { app.update(ctx, frame); }) - } + } } diff --git a/src/main.rs b/src/main.rs index da5be73..bf933de 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,9 +7,11 @@ use dashboard::Dashboard; use eframe::egui; use std::ops::Sub; use std::time::{Duration, Instant}; +use crate::prelude::CanonicalName; mod apps; mod dashboard; +pub mod prelude; fn main() -> Result<(), eframe::Error> { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..bad1675 --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,3 @@ +pub trait CanonicalName { + fn canonical_name() -> String; +} \ No newline at end of file diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..3e2b744 --- /dev/null +++ b/todo.txt @@ -0,0 +1 @@ +persistance