aboutsummaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
authorJoris2022-04-24 16:31:49 +0200
committerJoris2022-04-24 16:31:49 +0200
commit47fe90ee23d8ab04645ef3c7a17459ed40c5b765 (patch)
treed1aa7d3840a3bd825dcae0a0398fec7ca310f45c /src/gui
parent6d271b09303d924381cc65e7c0b5eb56833780ed (diff)
Allow to attach categories to events
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/app.rs40
-rw-r--r--src/gui/calendar.rs56
-rw-r--r--src/gui/form/mod.rs54
-rw-r--r--src/gui/style.css12
-rw-r--r--src/gui/update.rs22
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(&gtk::Label::new(Some("Calendrier"))));
+ // notebook.append_page(&categories, Some(&gtk::Label::new(Some("Catégories"))));
+ // window.set_child(Some(&notebook));
+
+ 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)
}
}
}