use std::ops::Range; use chrono::{Datelike, NaiveDateTime, Timelike}; use egui::{collapsing_header::CollapsingState, Color32, DragValue, TextEdit, Ui}; use karlcommon::{Condition, Property, Schedule, ServerboundPacket, Task}; use crate::{ helper::{edit_value, format_value, from_timestamp}, Globals, }; #[derive(Default)] pub struct ShowAndEdit { edit: Option, } impl ShowAndEdit { pub fn ui(&mut self, ui: &mut Ui, g: &mut Globals) { for t in g.tasks.values_mut() { 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(); } if ui.button("βž• Add task").clicked() { let id = rand::random::(); g.tasks.insert(id, Task::default_with_id(id)); self.edit = Some(id); } } } 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) { CollapsingState::load_with_default_open(ui.ctx(), egui::Id::new(self.id), false) .show_header(ui, |ui| { if edit { ui.text_edit_singleline(&mut self.name); } else { ui.heading(&self.name); } }) .body(|ui| { if let Some(d) = &mut self.description { if edit { ui.text_edit_singleline(d); } else { ui.label(&*d); } } else if edit { if ui.button("Add description").clicked() { self.description = Some(String::new()) } } 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) { if edit { ui.menu_button("✏ Change schedule kind", |ui| { if ui.button("Never").clicked() { *self = Schedule::Never; ui.close_menu(); } if ui.button("Condition").clicked() { *self = Schedule::Condition(Condition::Never); ui.close_menu(); } if ui.button("Dynamic").clicked() { *self = Schedule::Dynamic { priority: 0.0, scheduled: None, duration: 60, condition: Condition::Never, }; ui.close_menu(); } if ui.button("Static").clicked() { *self = Schedule::Static(0..0); ui.close_menu(); } }); } 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) { let mut td = from_timestamp(self.start)..from_timestamp(self.end); td.ui(ui, edit); *self = td.start.timestamp()..td.end.timestamp(); } } impl EditableWidget for Range { fn ui(&mut self, ui: &mut Ui, edit: bool) { ui.horizontal(|ui| { ui.label("from"); self.start.ui(ui, edit); ui.label("to"); self.end.ui(ui, edit); }); } } impl EditableWidget for NaiveDateTime { fn ui(&mut self, ui: &mut Ui, edit: bool) { ui.label(&format!("{}", self)); if edit { let (mut y, mut m, mut d) = (self.year(), self.month(), self.day()); let (mut h, mut min, mut s) = (self.hour(), self.minute(), self.second()); ui.horizontal(|ui| { ui.add(DragValue::new(&mut y)); ui.label("-"); ui.add(DragValue::new(&mut m).clamp_range(1..=12)); ui.label("-"); ui.add(DragValue::new(&mut d).clamp_range(1..=31)); // TODO maybe 30, 28 or even 29 ui.label(" | "); ui.add(DragValue::new(&mut h).clamp_range(0..=23)); ui.label(":"); ui.add(DragValue::new(&mut min).clamp_range(0..=59)); ui.label(":"); ui.add(DragValue::new(&mut s).clamp_range(0..=59)); }); *self = self .with_year(y) .unwrap_or_default() .with_month(m) .unwrap_or_default() .with_day(d) .unwrap_or_default() .with_hour(h) .unwrap_or_default() .with_minute(min) .unwrap_or_default() .with_second(s) .unwrap_or_default(); } } } 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))) } }); }