diff options
author | Joris | 2022-04-24 16:31:49 +0200 |
---|---|---|
committer | Joris | 2022-04-24 16:31:49 +0200 |
commit | 47fe90ee23d8ab04645ef3c7a17459ed40c5b765 (patch) | |
tree | d1aa7d3840a3bd825dcae0a0398fec7ca310f45c /src/gui | |
parent | 6d271b09303d924381cc65e7c0b5eb56833780ed (diff) |
Allow to attach categories to events
Diffstat (limited to 'src/gui')
-rw-r--r-- | src/gui/app.rs | 40 | ||||
-rw-r--r-- | src/gui/calendar.rs | 56 | ||||
-rw-r--r-- | src/gui/form/mod.rs | 54 | ||||
-rw-r--r-- | src/gui/style.css | 12 | ||||
-rw-r--r-- | src/gui/update.rs | 22 |
5 files changed, 135 insertions, 49 deletions
diff --git a/src/gui/app.rs b/src/gui/app.rs index 8cd7096..4ed864b 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -10,18 +10,23 @@ use std::rc::Rc; use crate::gui::calendar; use crate::gui::update::Msg; -use crate::{db, model::event::Event}; +use crate::{db, model::category::Category, model::event::Event}; pub struct App { pub conn: Rc<Connection>, pub window: Rc<gtk::ApplicationWindow>, - pub grid: gtk::Grid, + pub tx: Sender<Msg>, + // Calendar + pub calendar: gtk::Grid, pub events: Vec<Event>, // TODO: use Hashmap to have fast access to events by id ? pub recurring_events: Vec<Event>, // TODO: use Hashmap to have fast access to events by id ? pub today: NaiveDate, pub start_date: NaiveDate, pub end_date: NaiveDate, - pub tx: Sender<Msg>, + // Categories + // pub categories: gtk::Box, + pub categories: Vec<Category>, + pub default_color: String, } impl App { @@ -29,7 +34,7 @@ impl App { let window = Rc::new( gtk::ApplicationWindow::builder() .application(app) - .title("Calendar") + .title("Calendrier") .default_width(800) .default_height(600) .visible(true) @@ -41,19 +46,32 @@ impl App { NaiveDate::from_isoywd(today.year(), today.iso_week().week(), Weekday::Mon); let end_date = start_date + Duration::days(7 * 4 - 1); - let events = db::list_non_recurring_between(&conn, start_date, end_date)?; - let recurring_events = db::list_recurring(&conn)?; + let events = db::events::list_non_recurring_between(&conn, start_date, end_date)?; + let recurring_events = db::events::list_recurring(&conn)?; + let categories = db::categories::list(&conn)?; + let default_color = db::event_color::get_default_color(&conn)?; - let grid = calendar::create( + let calendar = calendar::create( tx.clone(), today, start_date, end_date, &events, &recurring_events, + &categories, + &default_color, ); - window.set_child(Some(&grid)); + // let categories = gtk::Box::builder() + // .orientation(gtk::Orientation::Vertical) + // .build(); + + // let notebook = gtk::Notebook::builder().build(); + // notebook.append_page(&calendar, Some(>k::Label::new(Some("Calendrier")))); + // notebook.append_page(&categories, Some(>k::Label::new(Some("Catégories")))); + // window.set_child(Some(¬ebook)); + + window.set_child(Some(&calendar)); window.connect_close_request(move |window| { if let Some(application) = window.application() { @@ -65,13 +83,15 @@ impl App { Ok(Self { conn, window, - grid, + tx, + calendar, events, recurring_events, today, start_date, end_date, - tx, + categories, + default_color, }) } } diff --git a/src/gui/calendar.rs b/src/gui/calendar.rs index 547c087..f5f9c10 100644 --- a/src/gui/calendar.rs +++ b/src/gui/calendar.rs @@ -6,7 +6,7 @@ use gtk::glib; use gtk::prelude::*; use std::collections::HashMap; -use crate::{gui::update, gui::update::Msg, gui::App, model::event, model::event::Event}; +use crate::{gui::update, gui::update::Msg, gui::App, model::event, model::event::Event, model::category::Category}; static DAYS: [&str; 7] = ["LUN", "MAR", "MER", "JEU", "VEN", "SAM", "DIM"]; static MONTHES: [&str; 12] = [ @@ -20,6 +20,8 @@ pub fn create( end_date: NaiveDate, events: &[Event], recurring_events: &[Event], + categories: &[Category], + default_color: &str, ) -> gtk::Grid { let grid = gtk::Grid::builder().build(); @@ -28,7 +30,7 @@ pub fn create( } let repetitions = event::repetitions_between(recurring_events, start_date, end_date); - attach_days(tx.clone(), &grid, start_date, today, events, &repetitions); + attach_days(tx.clone(), &grid, start_date, today, events, &repetitions, categories, default_color); let event_controller_key = gtk::EventControllerKey::new(); event_controller_key.connect_key_released(glib::clone!(@strong tx => move |_, _, keycode, _| { @@ -50,12 +52,14 @@ fn attach_days( today: NaiveDate, events: &[Event], repetitions: &HashMap<NaiveDate, Vec<Event>>, + categories: &[Category], + default_color: &str, ) { let mut d = start_date; for row in 1..5 { for col in 0..7 { grid.attach( - &day_entry(tx.clone(), d, today, events, repetitions), + &day_entry(tx.clone(), d, today, events, repetitions, categories, default_color), col, row, 1, @@ -66,14 +70,21 @@ fn attach_days( } } -pub fn refresh_date(app: &App, date: NaiveDate, repetitions: &HashMap<NaiveDate, Vec<Event>>) { +pub fn refresh_date( + app: &App, + date: NaiveDate, + repetitions: &HashMap<NaiveDate, + Vec<Event>>, + categories: &[Category], + default_color: &str +) { let d = date.signed_duration_since(app.start_date).num_days(); let col = (d % 7) as i32; let row = 1 + (d / 7) as i32; - app.grid.attach( - &day_entry(app.tx.clone(), date, app.today, &app.events, repetitions), + app.calendar.attach( + &day_entry(app.tx.clone(), date, app.today, &app.events, repetitions, categories, default_color), col, row, 1, @@ -101,6 +112,8 @@ pub fn day_entry( today: NaiveDate, events: &[Event], repetitions: &HashMap<NaiveDate, Vec<Event>>, + categories: &[Category], + default_color: &str, ) -> gtk::ScrolledWindow { let vbox = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) @@ -131,7 +144,7 @@ pub fn day_entry( events.sort_by_key(|e| e.start); if !events.is_empty() { - vbox.append(&day_events(date, tx, events)); + vbox.append(&day_events(date, tx, events, categories, default_color)); } gtk::ScrolledWindow::builder() @@ -164,7 +177,13 @@ fn day_label(today: NaiveDate, date: NaiveDate) -> gtk::Label { label } -fn day_events(date: NaiveDate, tx: Sender<Msg>, events: Vec<&Event>) -> gtk::Box { +fn day_events( + date: NaiveDate, + tx: Sender<Msg>, + events: Vec<&Event>, + categories: &[Category], + default_color: &str, +) -> gtk::Box { let vbox = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .build(); @@ -175,6 +194,25 @@ fn day_events(date: NaiveDate, tx: Sender<Msg>, events: Vec<&Event>) -> gtk::Box .hexpand(true) .build(); + let background_color = categories.iter().find(|c| event.category == Some(c.id)).map(|c| c.color.clone()).unwrap_or_else(|| default_color.to_string()); + + let provider = gtk::CssProvider::new(); + provider.load_from_data(format!(" + .event {{ + background-color: {}; + color: white; + border-radius: 4px; + padding: 4px; + margin: 4px; + }} + + .event:hover {{ + filter: brightness(120%); + }} + ", background_color).as_bytes()); + hbox.style_context().add_provider(&provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); + hbox.style_context().add_class("event"); + let gesture = gtk::GestureClick::new(); gesture.connect_pressed( glib::clone!(@strong event, @strong tx => move |gesture, n, _, _| { @@ -190,8 +228,6 @@ fn day_events(date: NaiveDate, tx: Sender<Msg>, events: Vec<&Event>) -> gtk::Box ); hbox.add_controller(&gesture); - hbox.add_css_class("g-Calendar__DayEvent"); - let event_txt = &event.pprint(); let label = gtk::Label::builder() .label(event_txt) diff --git a/src/gui/form/mod.rs b/src/gui/form/mod.rs index bb43ef5..cc77a19 100644 --- a/src/gui/form/mod.rs +++ b/src/gui/form/mod.rs @@ -15,7 +15,7 @@ use uuid::Uuid; use crate::{ db, gui::{update, update::Msg, App}, - model::{event, event::Event, repetition::Repetition}, + model::{category::Category, event, event::Event, repetition::Repetition}, }; pub async fn repetition_dialog(app: &App, date: NaiveDate, event: &Event) { @@ -148,6 +148,19 @@ pub async fn show(app: &App, target: Target) { column1.append(&utils::label("Fin")); column1.append(&end); + let dropdown_categories = get_dropdown_categories(&app.categories); + let category_dropdown = gtk::DropDown::from_strings( + &dropdown_categories + .iter() + .map(|s| s.as_str()) + .collect::<Vec<&str>>(), + ); + category_dropdown.set_margin_bottom(10); + let selected = get_selected_category(&event, &app.categories).unwrap_or_else(|| "".to_string()); + category_dropdown.set_selected(dropdown_categories.iter().position(|d| d == &selected).unwrap_or(0) as u32); + column1.append(&utils::label("Catégorie")); + column1.append(&category_dropdown); + // Second column let repetition = match target { @@ -174,7 +187,8 @@ pub async fn show(app: &App, target: Target) { lines.append(&button); let conn = app.conn.clone(); let tx = app.tx.clone(); - button.connect_clicked(glib::clone!(@weak dialog, @strong target, @strong event => move |_| { + let categories = app.categories.clone(); + button.connect_clicked(glib::clone!(@weak dialog, @strong target, @strong event, @strong categories => move |_| { let removed_occurences = match &target { Target::Update { event, .. } => { event.repetition.as_ref().map(|r| r.removed_occurences.clone()).unwrap_or_default() @@ -187,11 +201,15 @@ pub async fn show(app: &App, target: Target) { Target::Update {event} => event.id, _ => Uuid::new_v4(), }; - match event::validate(id, date.buffer().text(), name.buffer().text(), start.buffer().text(), end.buffer().text(), repetition) { + + // Find category id from selected id + let category = categories.iter().find(|c| c.name == dropdown_categories[category_dropdown.selected() as usize]).map(|c| c.id); + + match event::validate(id, date.buffer().text(), name.buffer().text(), start.buffer().text(), end.buffer().text(), repetition, category) { Some(new) => { match &target { Target::New {..} => { - match db::insert(&conn, &new) { + match db::events::insert(&conn, &new) { Ok(_) => { update::send(tx.clone(), Msg::AddEvent { new }); dialog.close() @@ -200,7 +218,7 @@ pub async fn show(app: &App, target: Target) { } } Target::Update {event} => { - match db::update(&conn, &new) { + match db::events::update(&conn, &new) { Ok(_) => { update::send(tx.clone(), Msg::UpdateEvent { old: event.clone(), new }); dialog.close() @@ -212,7 +230,7 @@ pub async fn show(app: &App, target: Target) { // TODO: improve intermediate error state match delete_repetition_occurence(&conn, event, *date) { Ok(occurence) => { - match db::insert(&conn, &new) { + match db::events::insert(&conn, &new) { Ok(_) => { update::send(tx.clone(), Msg::UpdateEventOccurence { event: event.clone(), @@ -231,7 +249,7 @@ pub async fn show(app: &App, target: Target) { Target::UpdateFromOccurence { date, event } => { match update_repetition_until(&conn, *date - Duration::days(1), event) { Ok(updated) => { - match db::insert(&conn, &new) { + match db::events::insert(&conn, &new) { Ok(_) => { update::send(tx.clone(), Msg::UpdateRepeatedFrom { old: event.clone(), @@ -288,7 +306,7 @@ pub async fn show(app: &App, target: Target) { } } _ => { - let operation = db::delete(&conn, &event.id); + let operation = db::events::delete(&conn, &event.id); if operation.is_ok() { update::send(tx.clone(), Msg::DeleteEvent { event: event.clone() }); dialog.close() @@ -301,6 +319,22 @@ pub async fn show(app: &App, target: Target) { dialog.run_future().await; } +fn get_dropdown_categories(categories: &[Category]) -> Vec<String> { + let mut xs = categories + .iter() + .map(|c| c.name.clone()) + .collect::<Vec<String>>(); + xs.push("".to_string()); + xs.sort(); + xs +} + +fn get_selected_category(event: &Option<Event>, categories: &[Category]) -> Option<String> { + let id = event.as_ref()?.category?; + let category = categories.iter().find(|c| c.id == id)?; + Some(category.name.clone()) +} + #[derive(Error, Debug)] enum DeleteError { #[error("Repetition not found")] @@ -320,7 +354,7 @@ fn delete_repetition_occurence( let mut repetition = repetition.clone(); repetition.removed_occurences.insert(occurence); event.repetition = Some(repetition); - db::update(conn, &event).map(|_| occurence) + db::events::update(conn, &event).map(|_| occurence) } else { Err(anyhow::Error::new(DeleteError::OccurenceNotFound)) } @@ -336,6 +370,6 @@ fn update_repetition_until(conn: &Connection, date: NaiveDate, event: &Event) -> removed_occurences: r.removed_occurences.clone(), until: Some(date), }); - db::update(conn, &with_repetition_until)?; + db::events::update(conn, &with_repetition_until)?; Ok(with_repetition_until) } diff --git a/src/gui/style.css b/src/gui/style.css index 59ae30d..1b592d3 100644 --- a/src/gui/style.css +++ b/src/gui/style.css @@ -26,18 +26,6 @@ font-weight: bold; } -.g-Calendar__DayEvent { - background-color: #A8C2E0; - color: white; - border-radius: 4px; - padding: 4px; - margin: 4px; -} - -.g-Calendar__DayEvent:hover { - background-color: pink; -} - .g-Dialog { background-color: white; color: black; diff --git a/src/gui/update.rs b/src/gui/update.rs index bd4e7a9..7b3625c 100644 --- a/src/gui/update.rs +++ b/src/gui/update.rs @@ -166,24 +166,32 @@ pub async fn event_handler(rx: Receiver<Msg>, mut app: App) { refresh(&app, &HashSet::from([date])) } Msg::SelectPreviousWeek => { - app.grid.remove_row(4); - app.grid.insert_row(1); + app.calendar.remove_row(4); + app.calendar.insert_row(1); app.start_date -= Duration::days(7); app.end_date -= Duration::days(7); - match db::list_non_recurring_between(&app.conn, app.start_date, app.end_date) { + match db::events::list_non_recurring_between( + &app.conn, + app.start_date, + app.end_date, + ) { Ok(events) => app.events = events, Err(err) => eprintln!("{}", err), }; refresh(&app, &HashSet::from_iter(week_from(app.start_date))); } Msg::SelectNextWeek => { - app.grid.remove_row(1); - app.grid.insert_row(4); + app.calendar.remove_row(1); + app.calendar.insert_row(4); app.start_date += Duration::days(7); app.end_date += Duration::days(7); - match db::list_non_recurring_between(&app.conn, app.start_date, app.end_date) { + match db::events::list_non_recurring_between( + &app.conn, + app.start_date, + app.end_date, + ) { Ok(events) => app.events = events, Err(err) => eprintln!("{}", err), }; @@ -275,7 +283,7 @@ fn refresh(app: &App, dates: &HashSet<NaiveDate>) { for date in dates { if date >= &app.start_date && date <= &app.end_date { - calendar::refresh_date(app, *date, &repetitions) + calendar::refresh_date(app, *date, &repetitions, &app.categories, &app.default_color) } } } |