From 5e6509163df53788799bb7aa97d07a90bf416eb7 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Wed, 17 Aug 2022 08:38:33 +0200 Subject: refactor --- Cargo.lock | 10 ++ karlgui/Cargo.toml | 1 + karlgui/src/edit.rs | 305 ---------------------------------------- karlgui/src/helper.rs | 63 ++++----- karlgui/src/main.rs | 56 +++++--- karlgui/src/views/calendar.rs | 34 +++++ karlgui/src/views/edit.rs | 313 ++++++++++++++++++++++++++++++++++++++++++ karlgui/src/views/mod.rs | 2 + 8 files changed, 430 insertions(+), 354 deletions(-) delete mode 100644 karlgui/src/edit.rs create mode 100644 karlgui/src/views/calendar.rs create mode 100644 karlgui/src/views/edit.rs create mode 100644 karlgui/src/views/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 279e57c..d5cf770 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -493,6 +493,15 @@ dependencies = [ "winit", ] +[[package]] +name = "egui_extras" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877bfcce06463cdbcfd7f4efd57608b1384d6d9ae03b33e503fbba1d1a899a52" +dependencies = [ + "egui", +] + [[package]] name = "egui_glow" version = "0.18.1" @@ -847,6 +856,7 @@ dependencies = [ "crossbeam-channel", "eframe", "egui", + "egui_extras", "env_logger", "karlcommon", "log", diff --git a/karlgui/Cargo.toml b/karlgui/Cargo.toml index c41c874..fd1aec0 100644 --- a/karlgui/Cargo.toml +++ b/karlgui/Cargo.toml @@ -14,3 +14,4 @@ log = "0.4.17" eframe = "0.18.0" egui = "0.18.1" +egui_extras = "0.18.0" diff --git a/karlgui/src/edit.rs b/karlgui/src/edit.rs deleted file mode 100644 index b65feb2..0000000 --- a/karlgui/src/edit.rs +++ /dev/null @@ -1,305 +0,0 @@ -use std::ops::Range; - -use egui::{Color32, DragValue, TextEdit, Ui}; -use karlcommon::{Condition, Property, Schedule, Task}; - -use crate::helper::{edit_value, format_value}; - -pub struct ShowOrEdit { - pub inner: T, - pub edit: bool, -} - -impl ShowOrEdit { - pub fn new(inner: T, edit: bool) -> ShowOrEdit { - Self { inner, edit } - } - pub fn show_only(&mut self, ui: &mut Ui) { - self.inner.ui(ui, false); - } - pub fn changed(&mut self, ui: &mut Ui) -> bool { - let changed = match self.edit { - true => { - if ui.button("πŸ’Ύ Save").clicked() { - self.edit = false; - true - } else { - false - } - } - false => { - if ui.button("✏ Edit").clicked() { - self.edit = true; - } - false - } - }; - self.inner.ui(ui, self.edit); - changed - } -} - -pub 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) = &mut self.description { - if edit { - ui.text_edit_singleline(d); - } else { - ui.label(&*d); - } - } - if self.tags.len() != 0 || edit { - ui.horizontal(|ui| { - ui.label("Tags:"); - let mut remove = None; - for (i, t) in self.tags.iter_mut().enumerate() { - if edit { - TextEdit::singleline(t).desired_width(50.0).show(ui); - if ui.button("πŸ—‘").clicked() { - remove = Some(i); - } - } else { - ui.colored_label(Color32::LIGHT_GREEN, t); - } - } - if edit { - if ui.button("βž•").clicked() { - self.tags.push(String::from("blub")) - } - } - if let Some(remove) = remove { - self.tags.remove(remove); - } - }); - } - self.schedule.ui(ui, edit); - }); - } -} - -impl EditableWidget for Schedule { - fn ui(&mut self, ui: &mut Ui, edit: bool) { - match self { - Schedule::Never => { - ui.label("No schedule"); - } - Schedule::Dynamic { - priority, - scheduled, - duration, - condition, - } => { - ui.horizontal(|ui| { - ui.label("Dynamic with priority"); - match edit { - true => ui.add(DragValue::new(priority)), - false => ui.label(&format!("{}", priority)), - }; - ui.label("and duration"); - match edit { - true => ui.add(DragValue::new(duration)), - false => ui.label(&format!("{}", duration)), - }; - ui.label("seconds"); - if let Some(scheduled) = scheduled { - ui.label("at "); - scheduled.ui(ui, false) - } - ui.label("during"); - condition.ui(ui, edit); - }); - } - 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) { - let res = ui - .group(|ui| match self { - Condition::Never => { - ui.label("never"); - } - Condition::From(c) => { - ui.horizontal(|ui| { - ui.label("Starting from"); - c.ui(ui, edit); - if edit { - ui.menu_button("✏ Change type", |ui| { - add_condition(ui, |d| *c = Box::new(d)) - }); - } - }); - } - Condition::Invert(c) => { - ui.horizontal(|ui| { - ui.label("not when"); - c.ui(ui, edit); - if edit { - ui.menu_button("✏ Change type", |ui| { - add_condition(ui, |d| *c = Box::new(d)) - }); - } - }); - } - Condition::Or(cs) => combine_condition(ui, edit, "or", cs), - Condition::And(cs) => combine_condition(ui, edit, "and", cs), - 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(prop.to_str()); - } - ui.label("="); - if edit { - ui.push_id(0, |ui| edit_value(ui, *prop, value)); - } else { - ui.label(&format!("{}", format_value(*prop, *value))); - } - }); - } - Condition::Range { - prop, - min, - max, - modulus: _, - } => { - ui.horizontal(|ui| { - ui.label("when"); - match edit { - true => drop(ui.push_id(0, |ui| edit_value(ui, *prop, min))), - false => drop(ui.label(&format!("{}", format_value(*prop, *min)))), - } - ui.label("≀"); - 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(prop.to_str()); - } - ui.label("<"); - match edit { - true => drop(ui.push_id(1, |ui| edit_value(ui, *prop, max))), - false => drop(ui.label(&format!("{}", format_value(*prop, *max)))), - } - }); - } - }) - .response; - if edit { - res.context_menu(|ui| { - ui.menu_button("Replace with…", |ui| { - add_condition(ui, |c| { - *self = c; - }) - }); - }); - } - } -} - -impl EditableWidget for Range { - fn ui(&mut self, ui: &mut Ui, _edit: bool) { - ui.label("todo"); - } -} - -fn combine_condition(ui: &mut Ui, edit: bool, combinator: &str, cs: &mut Vec) { - ui.vertical(|ui| { - let mut remove = None; - for (i, c) in cs.iter_mut().enumerate() { - ui.push_id(i, |ui| { - ui.horizontal(|ui| { - ui.label(if i != 0 { combinator } else { "" }); - c.ui(ui, edit); - if edit { - if ui.button("πŸ—‘").clicked() { - remove = Some(i); - } - } - }); - }); - } - if edit { - ui.menu_button("βž• Add condition", |ui| { - add_condition(ui, |e| { - cs.push(e); - }) - }); - } - if let Some(remove) = remove { - cs.remove(remove); - } - }); -} - -fn add_condition(ui: &mut Ui, mut add: impl FnMut(Condition) -> ()) { - ui.menu_button("Combinators", |ui| { - if ui.button("And").clicked() { - ui.close_menu(); - add(Condition::And(vec![])) - } - if ui.button("Or").clicked() { - ui.close_menu(); - add(Condition::Or(vec![])) - } - }); - ui.menu_button("Constraints", |ui| { - if ui.button("Equal").clicked() { - ui.close_menu(); - add(Condition::Equal { - modulus: None, - prop: Property::Unix, - value: 0, - }) - } - if ui.button("Range").clicked() { - ui.close_menu(); - add(Condition::Range { - prop: Property::Unix, - min: 0, - max: 10, - modulus: None, - }) - } - if ui.button("Never").clicked() { - ui.close_menu(); - add(Condition::Never) - } - }); - ui.menu_button("Modifier", |ui| { - if ui.button("Invert").clicked() { - ui.close_menu(); - add(Condition::Invert(Box::new(Condition::Never))) - } - if ui.button("Starting from").clicked() { - ui.close_menu(); - add(Condition::From (Box::new(Condition::Never))) - } - }); -} diff --git a/karlgui/src/helper.rs b/karlgui/src/helper.rs index 1ae29c8..eb4c981 100644 --- a/karlgui/src/helper.rs +++ b/karlgui/src/helper.rs @@ -4,40 +4,11 @@ use karlcommon::Property; pub fn format_value(prop: Property, value: i64) -> String { match prop { Property::Year => format!("{value}"), - Property::Monthofyear => format!( - "{}", - match value { - 0 => "January", - 1 => "February", - 2 => "March", - 3 => "April", - 4 => "May", - 5 => "June", - 6 => "July", - 7 => "August", - 8 => "September", - 9 => "October", - 10 => "November", - 11 => "December", - _ => "(invalid)", - } - ), + Property::Monthofyear => format!("{}", month_to_str(value)), Property::Weekofmonth => format!("{value}"), Property::Dayofyear => format!("{value}"), Property::Dayofmonth => format!("{value}"), - Property::Dayofweek => format!( - "{}", - match value { - 0 => "Monday", - 1 => "Thuesday", - 2 => "Wednesday", - 3 => "Thursday", - 4 => "Friday", - 5 => "Saturday", - 6 => "Sunday", - _ => "(invalid)", - } - ), + Property::Dayofweek => format!("{}", weekday_to_str(value)), Property::Hour => format!("{value}h"), Property::Minute => format!("{value}min"), Property::Second => format!("{value}s"), @@ -91,3 +62,33 @@ pub fn edit_value(ui: &mut Ui, prop: Property, value: &mut i64) { } } } + +pub fn weekday_to_str(value: i64) -> &'static str { + match value { + 0 => "Monday", + 1 => "Thuesday", + 2 => "Wednesday", + 3 => "Thursday", + 4 => "Friday", + 5 => "Saturday", + 6 => "Sunday", + _ => "(invalid)", + } +} +pub fn month_to_str(value: i64) -> &'static str { + match value { + 0 => "January", + 1 => "February", + 2 => "March", + 3 => "April", + 4 => "May", + 5 => "June", + 6 => "July", + 7 => "August", + 8 => "September", + 9 => "October", + 10 => "November", + 11 => "December", + _ => "(invalid)", + } +} diff --git a/karlgui/src/main.rs b/karlgui/src/main.rs index a85459e..d683a78 100644 --- a/karlgui/src/main.rs +++ b/karlgui/src/main.rs @@ -1,14 +1,14 @@ pub mod client; -pub mod edit; pub mod helper; +pub mod views; use crate::client::Client; -use edit::ShowOrEdit; use eframe::CreationContext; use egui::CentralPanel; use karlcommon::{socket_path, ClientboundPacket, ServerboundPacket, Task}; use log::{error, info}; use std::{os::unix::net::UnixStream, process::exit}; +use views::{calendar::Calendar, edit::ShowAndEdit}; fn main() { env_logger::init(); @@ -19,9 +19,24 @@ fn main() { ) } -struct App { +pub struct Globals { client: Client, - tasks: Vec>, + tasks: Vec, +} + +struct App { + g: Globals, + + current_tab: Tab, + + show_and_edit: ShowAndEdit, + calendar: Calendar, +} + +#[derive(PartialEq)] +enum Tab { + ShowAndEdit, + CalendarWeek, } impl App { @@ -41,19 +56,22 @@ impl App { client.send(ServerboundPacket::ListTasks); App { - client, - tasks: vec![], + current_tab: Tab::ShowAndEdit, + g: Globals { + client, + tasks: vec![], + }, + show_and_edit: Default::default(), + calendar: Default::default(), } } pub fn update_network(&mut self) { - for p in self.client.receiver.try_iter() { + for p in self.g.client.receiver.try_iter() { match p { - ClientboundPacket::TaskList(t) => { - self.tasks = t.into_iter().map(|t| ShowOrEdit::new(t, false)).collect() - } + ClientboundPacket::TaskList(t) => self.g.tasks = t, ClientboundPacket::Sync => { - self.client.busy = false; + self.g.client.busy = false; } _ => {} } @@ -65,13 +83,15 @@ impl eframe::App for App { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { self.update_network(); CentralPanel::default().show(ctx, |ui| { - ui.add_enabled_ui(!self.client.busy, |ui| { - for t in &mut self.tasks { - if t.changed(ui) { - self.client - .send_sync(ServerboundPacket::UpdateTask(t.inner.clone())) - } - ui.separator(); + ui.add_enabled_ui(!self.g.client.busy, |ui| { + ui.horizontal(|ui| { + ui.selectable_value(&mut self.current_tab, Tab::ShowAndEdit, "Tasks"); + ui.selectable_value(&mut self.current_tab, Tab::CalendarWeek, "Calendar: Week"); + }); + ui.separator(); + match self.current_tab { + Tab::ShowAndEdit => self.show_and_edit.ui(ui, &mut self.g), + Tab::CalendarWeek => self.calendar.ui(ui, &mut self.g), } }); }); diff --git a/karlgui/src/views/calendar.rs b/karlgui/src/views/calendar.rs new file mode 100644 index 0000000..b216504 --- /dev/null +++ b/karlgui/src/views/calendar.rs @@ -0,0 +1,34 @@ +use crate::{helper::weekday_to_str, Globals}; +use egui::Ui; +use egui_extras::TableBuilder; + +#[derive(Default)] +pub struct Calendar; + +impl Calendar { + pub fn ui(&mut self, ui: &mut Ui, _g: &mut Globals) { + TableBuilder::new(ui) + .column(egui_extras::Size::exact(50.0)) + .columns(egui_extras::Size::remainder(), 7) + .header(25.0, |mut ui| { + ui.col(|_| {}); + for d in 0..7 { + ui.col(|ui| { + ui.heading(weekday_to_str(d)); + }); + } + }) + .body(|ui| { + ui.rows(50.0, 24, |h, mut ui| { + ui.col(|ui| { + ui.heading(&format!("{h:02}:00")); + }); + for d in 0..7 { + ui.col(|ui| { + ui.label(&format!("day {d} at {h}:00")); + }); + } + }) + }) + } +} diff --git a/karlgui/src/views/edit.rs b/karlgui/src/views/edit.rs new file mode 100644 index 0000000..04146dd --- /dev/null +++ b/karlgui/src/views/edit.rs @@ -0,0 +1,313 @@ +use std::ops::Range; + +use egui::{Color32, DragValue, TextEdit, Ui}; +use karlcommon::{Condition, Property, Schedule, ServerboundPacket, Task}; + +use crate::{ + helper::{edit_value, format_value}, + Globals, +}; + +#[derive(Default)] +pub struct ShowAndEdit { + edit: Option, +} + +impl ShowAndEdit { + pub fn ui(&mut self, ui: &mut Ui, g: &mut Globals) { + for t in &mut g.tasks { + let edit = self.edit == Some(t.id); + let changed = if edit { + if ui.button("πŸ’Ύ Save").clicked() { + self.edit = None; + true + } else { + false + } + } else { + ui.add_enabled_ui(self.edit.is_none(), |ui| { + if ui + .button("✏ Edit") + .on_disabled_hover_text( + "Some other task is still being edited, save that one first.", + ) + .clicked() + { + self.edit = Some(t.id); + } + }); + false + }; + if changed { + g.client.send_sync(ServerboundPacket::UpdateTask(t.clone())); + } + t.ui(ui, edit); + ui.separator(); + } + } +} + +pub 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) = &mut self.description { + if edit { + ui.text_edit_singleline(d); + } else { + ui.label(&*d); + } + } + if self.tags.len() != 0 || edit { + ui.horizontal(|ui| { + ui.label("Tags:"); + let mut remove = None; + for (i, t) in self.tags.iter_mut().enumerate() { + if edit { + TextEdit::singleline(t).desired_width(50.0).show(ui); + if ui.button("πŸ—‘").clicked() { + remove = Some(i); + } + } else { + ui.colored_label(Color32::LIGHT_GREEN, t); + } + } + if edit { + if ui.button("βž•").clicked() { + self.tags.push(String::from("blub")) + } + } + if let Some(remove) = remove { + self.tags.remove(remove); + } + }); + } + self.schedule.ui(ui, edit); + }); + } +} + +impl EditableWidget for Schedule { + fn ui(&mut self, ui: &mut Ui, edit: bool) { + match self { + Schedule::Never => { + ui.label("No schedule"); + } + Schedule::Dynamic { + priority, + scheduled, + duration, + condition, + } => { + ui.horizontal(|ui| { + ui.label("Dynamic with priority"); + match edit { + true => ui.add(DragValue::new(priority)), + false => ui.label(&format!("{}", priority)), + }; + ui.label("and duration"); + match edit { + true => ui.add(DragValue::new(duration)), + false => ui.label(&format!("{}", duration)), + }; + ui.label("seconds"); + if let Some(scheduled) = scheduled { + ui.label("at "); + scheduled.ui(ui, false) + } + ui.label("during"); + condition.ui(ui, edit); + }); + } + 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) { + let res = ui + .group(|ui| match self { + Condition::Never => { + ui.label("never"); + } + Condition::From(c) => { + ui.horizontal(|ui| { + ui.label("Starting from"); + c.ui(ui, edit); + if edit { + ui.menu_button("✏ Change type", |ui| { + add_condition(ui, |d| *c = Box::new(d)) + }); + } + }); + } + Condition::Invert(c) => { + ui.horizontal(|ui| { + ui.label("not when"); + c.ui(ui, edit); + if edit { + ui.menu_button("✏ Change type", |ui| { + add_condition(ui, |d| *c = Box::new(d)) + }); + } + }); + } + Condition::Or(cs) => combine_condition(ui, edit, "or", cs), + Condition::And(cs) => combine_condition(ui, edit, "and", cs), + 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(prop.to_str()); + } + ui.label("="); + if edit { + ui.push_id(0, |ui| edit_value(ui, *prop, value)); + } else { + ui.label(&format!("{}", format_value(*prop, *value))); + } + }); + } + Condition::Range { + prop, + min, + max, + modulus: _, + } => { + ui.horizontal(|ui| { + ui.label("when"); + match edit { + true => drop(ui.push_id(0, |ui| edit_value(ui, *prop, min))), + false => drop(ui.label(&format!("{}", format_value(*prop, *min)))), + } + ui.label("≀"); + 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(prop.to_str()); + } + ui.label("<"); + match edit { + true => drop(ui.push_id(1, |ui| edit_value(ui, *prop, max))), + false => drop(ui.label(&format!("{}", format_value(*prop, *max)))), + } + }); + } + }) + .response; + if edit { + res.context_menu(|ui| { + ui.menu_button("Replace with…", |ui| { + add_condition(ui, |c| { + *self = c; + }) + }); + }); + } + } +} + +impl EditableWidget for Range { + fn ui(&mut self, ui: &mut Ui, _edit: bool) { + ui.label("todo"); + } +} + +fn combine_condition(ui: &mut Ui, edit: bool, combinator: &str, cs: &mut Vec) { + ui.vertical(|ui| { + let mut remove = None; + for (i, c) in cs.iter_mut().enumerate() { + ui.push_id(i, |ui| { + ui.horizontal(|ui| { + ui.label(if i != 0 { combinator } else { "" }); + c.ui(ui, edit); + if edit { + if ui.button("πŸ—‘").clicked() { + remove = Some(i); + } + } + }); + }); + } + if edit { + ui.menu_button("βž• Add condition", |ui| { + add_condition(ui, |e| { + cs.push(e); + }) + }); + } + if let Some(remove) = remove { + cs.remove(remove); + } + }); +} + +fn add_condition(ui: &mut Ui, mut add: impl FnMut(Condition) -> ()) { + ui.menu_button("Combinators", |ui| { + if ui.button("And").clicked() { + ui.close_menu(); + add(Condition::And(vec![])) + } + if ui.button("Or").clicked() { + ui.close_menu(); + add(Condition::Or(vec![])) + } + }); + ui.menu_button("Constraints", |ui| { + if ui.button("Equal").clicked() { + ui.close_menu(); + add(Condition::Equal { + modulus: None, + prop: Property::Unix, + value: 0, + }) + } + if ui.button("Range").clicked() { + ui.close_menu(); + add(Condition::Range { + prop: Property::Unix, + min: 0, + max: 10, + modulus: None, + }) + } + if ui.button("Never").clicked() { + ui.close_menu(); + add(Condition::Never) + } + }); + ui.menu_button("Modifier", |ui| { + if ui.button("Invert").clicked() { + ui.close_menu(); + add(Condition::Invert(Box::new(Condition::Never))) + } + if ui.button("Starting from").clicked() { + ui.close_menu(); + add(Condition::From(Box::new(Condition::Never))) + } + }); +} diff --git a/karlgui/src/views/mod.rs b/karlgui/src/views/mod.rs new file mode 100644 index 0000000..9c6ea7c --- /dev/null +++ b/karlgui/src/views/mod.rs @@ -0,0 +1,2 @@ +pub mod edit; +pub mod calendar; -- cgit v1.2.3-70-g09d2