pub mod client; use crate::client::Client; use eframe::CreationContext; use egui::{text::LayoutJob, CentralPanel, Color32, DragValue, Response, TextFormat, Ui, Widget}; use karlcommon::{ socket_path, ClientboundPacket, Condition, Property, Schedule, ServerboundPacket, Task, }; use log::{error, info}; use std::{ ops::{Deref, DerefMut, Range}, os::unix::net::UnixStream, process::exit, }; fn main() { env_logger::init(); eframe::run_native( "karlender", eframe::NativeOptions::default(), Box::new(move |cc| Box::new(App::new(cc))), ) } struct App { client: Client, tasks: Vec>, } impl App { pub fn new(cc: &CreationContext) -> Self { cc.egui_ctx.set_visuals(egui::Visuals::dark()); let socket = match UnixStream::connect(socket_path()) { Ok(s) => s, Err(e) => { error!("failed to connect to socket: {}", e); exit(1) } }; let mut client = Client::new(socket); info!("connected"); client.send(ServerboundPacket::ListTasks); App { client, tasks: vec![], } } pub fn update_network(&mut self) { for p in self.client.receiver.try_iter() { match p { ClientboundPacket::TaskList(t) => { self.tasks = t.into_iter().map(|t| ShowOrEdit::new(t, false)).collect() } _ => {} } } } } impl eframe::App for App { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { self.update_network(); CentralPanel::default().show(ctx, |ui| { for t in &mut self.tasks { t.ui(ui); ui.separator(); } }); } } struct ShowOrEdit { pub inner: T, pub edit: bool, } impl ShowOrEdit { pub fn new(inner: T, edit: bool) -> ShowOrEdit { Self { inner, edit } } pub fn ui(&mut self, ui: &mut Ui) { match self.edit { true => { if ui.button("💾 Save").clicked() { self.edit = false; } } false => { if ui.button("✏ Edit").clicked() { self.edit = true; } } } self.inner.ui(ui, self.edit); } } trait EditableWidget { fn ui(&mut self, ui: &mut Ui, edit: bool); } impl EditableWidget for Task { fn ui(&mut self, ui: &mut Ui, edit: bool) { ui.heading(&self.name); ui.indent((), |ui| { if let Some(d) = &self.description { ui.label(d); } ui.horizontal(|ui| { ui.label("Tags:"); for t in &self.tags { ui.colored_label(Color32::LIGHT_GREEN, t); } }); self.schedule.ui(ui, edit); }); } } impl EditableWidget for Schedule { fn ui(&mut self, ui: &mut Ui, edit: bool) { match self { Schedule::Never => { let mut j = LayoutJob::default(); j.append( "No schedule", 0.0, TextFormat { italics: true, ..Default::default() }, ); ui.label(j); } Schedule::Dynamic { priority, scheduled, duration, condition, } => { ui.horizontal(|ui| { ui.label("Dynamic with priority"); ui.label(&format!(" {} ", priority)); }); } Schedule::Condition(c) => c.ui(ui, edit), Schedule::Static(t) => t.ui(ui, edit), } } } impl EditableWidget for Condition { fn ui(&mut self, ui: &mut Ui, edit: bool) { ui.group(|ui| match self { Condition::Never => { ui.label("never"); } Condition::From(c) => { ui.horizontal(|ui| { ui.label("Starting from"); c.ui(ui, edit); }); } Condition::Or(cs) => { ui.vertical(|ui| { for (i, c) in cs.iter_mut().enumerate() { ui.push_id(i, |ui| { ui.horizontal(|ui| { ui.label(if i != 0 { "or " } else { " " }); c.ui(ui, edit); }); }); } }); } Condition::And(cs) => { ui.vertical(|ui| { for (i, c) in cs.iter_mut().enumerate() { ui.push_id(i, |ui| { ui.horizontal(|ui| { ui.label(if i != 0 { "and" } else { "" }); c.ui(ui, edit); }); }); } }); } Condition::Invert(c) => { ui.horizontal(|ui| { ui.label("not when"); c.ui(ui, edit); }); } Condition::Equal { prop, value, modulus, } => { ui.horizontal(|ui| { ui.label("when"); if edit { egui::ComboBox::from_id_source(ui.id()) .selected_text(prop.to_str()) .show_ui(ui, |ui| { for v in Property::VALUES { ui.selectable_value(prop, *v, v.to_str()); } }); } else { ui.label(&format!("{:?}", prop)); } ui.label("="); if edit { ui.add(DragValue::new(value)); } else { ui.label(&format!("{}", value)); } }); } Condition::Range { prop, min, max, modulus, } => { ui.horizontal(|ui| { ui.label("when "); ui.label(&format!("{}", min)); ui.label("≤"); ui.label(&format!("{:?}", prop)); ui.label("<"); ui.label(&format!("{}", max)) }); } }); } } impl EditableWidget for Range { fn ui(&mut self, ui: &mut Ui, edit: bool) { ui.label("todo"); } }