diff options
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)          }      }  } | 
