diff --git a/Cargo.lock b/Cargo.lock index 5def2cc..cb38179 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1868,6 +1868,7 @@ dependencies = [ "env_logger", "log", "opener", + "rand", "relm4", "relm4-components", "relm4-icons", diff --git a/Cargo.toml b/Cargo.toml index 95c5d9e..6367c3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ chrono = "0.4.33" strum = "0.26" strum_macros = "0.26" +rand = "0.8.5" # strum = { version = "0.26", features = ["derive"] } [profile.dev.package.sqlx-macros] diff --git a/flake.lock b/flake.lock index 288fafe..a8b42ce 100644 --- a/flake.lock +++ b/flake.lock @@ -21,11 +21,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1704842529, - "narHash": "sha256-OTeQA+F8d/Evad33JMfuXC89VMetQbsU4qcaePchGr4=", + "lastModified": 1707907779, + "narHash": "sha256-dtktfFJn+36yBkZ1mnQGdiDsqnzC9pXt/Ecpsui0hiY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "eabe8d3eface69f5bb16c18f8662a702f50c20d5", + "rev": "c5e9528855e4e6feda2b16dec28de880ce774b93", "type": "github" }, "original": { @@ -35,11 +35,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1704842529, - "narHash": "sha256-OTeQA+F8d/Evad33JMfuXC89VMetQbsU4qcaePchGr4=", + "lastModified": 1707907779, + "narHash": "sha256-dtktfFJn+36yBkZ1mnQGdiDsqnzC9pXt/Ecpsui0hiY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "eabe8d3eface69f5bb16c18f8662a702f50c20d5", + "rev": "c5e9528855e4e6feda2b16dec28de880ce774b93", "type": "github" }, "original": { @@ -76,11 +76,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1701680307, - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", "owner": "numtide", "repo": "flake-utils", - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", "type": "github" }, "original": { diff --git a/src/ui/app.rs b/src/ui/app.rs index 9d07e9c..78c66bc 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -1,5 +1,6 @@ use chrono::Utc; use gtk::prelude::*; +use log::debug; use relm4::{ component::{AsyncComponent, AsyncComponentParts}, prelude::*, @@ -22,12 +23,17 @@ pub struct AppModel { database: Database, mcdu: Controller, sheets_listing: Controller, + edit_mode: bool, } #[derive(Debug)] pub enum AppInput { SearchStarted(String), SheetPressed(Sheet), + Refresh, + Sort, + Shuffle, + SetEditMode(bool), } pub struct AppInitData { @@ -54,6 +60,32 @@ impl AsyncComponent for AppModel { gtk::Box { set_orientation: gtk::Orientation::Vertical, set_hexpand: true, + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_margin_all: 10, + set_spacing: 3, + gtk::Button { + set_icon_name: "view-refresh-symbolic", + set_margin_end: 10, + connect_clicked[sender] => move |_| sender.input(AppInput::Refresh), + }, + gtk::ToggleButton { + set_icon_name: "document-edit-symbolic", + set_margin_end: 10, + connect_clicked[sender] => move |button| sender.input(AppInput::SetEditMode(button.is_active())), + }, + #[name = "button_sort"] + gtk::ToggleButton { + set_icon_name: "view-sort-descending-symbolic", + set_active: true, + connect_clicked[sender] => move |_| sender.input(AppInput::Sort), + }, + gtk::ToggleButton { + set_icon_name: "media-playlist-shuffle-symbolic", + set_group: Some(&button_sort), + connect_clicked[sender] => move |_| sender.input(AppInput::Shuffle), + }, + }, gtk::ScrolledWindow { model.sheets_listing.widget(), set_vexpand: true, @@ -93,6 +125,7 @@ impl AsyncComponent for AppModel { database: init_data.database, mcdu, sheets_listing, + edit_mode: false, }; let widgets = view_output!(); @@ -112,14 +145,21 @@ impl AsyncComponent for AppModel { .emit(SheetListingInput::Query(query.clone())); } AppInput::SheetPressed(sheet) => { - sheet.open_file(); - // TODO: updating directly does not work - let mut sheet = sheet; - sheet.last_opened = I64DateTime(Utc::now()); - sheet_dao::update_sheet_last_opened(&self.database, &sheet) - .await - .unwrap(); + if self.edit_mode { + debug!("Sheet pressed, but we are in edit mode!"); + } else { + sheet.open_file(); + let mut sheet = sheet; + sheet.last_opened = I64DateTime(Utc::now()); + sheet_dao::update_sheet_last_opened(&self.database, &sheet) + .await + .unwrap(); + } } + AppInput::Refresh => todo!(), + AppInput::Sort => self.sheets_listing.emit(SheetListingInput::Sort), + AppInput::Shuffle => self.sheets_listing.emit(SheetListingInput::Shuffle), + AppInput::SetEditMode(edit_mode) => self.edit_mode = edit_mode, } } } diff --git a/src/ui/sheet_listing.rs b/src/ui/sheet_listing.rs index cf0fc8a..22d1c9e 100644 --- a/src/ui/sheet_listing.rs +++ b/src/ui/sheet_listing.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use gtk::prelude::*; use relm4::factory::FactoryVecDeque; @@ -8,6 +10,8 @@ use crate::sheet::Sheet; use super::sheet_model::{OnQueryUpdate, SheetModel}; +use rand::{seq::SliceRandom, thread_rng}; + pub struct SheetListingModel { sheets: FactoryVecDeque, } @@ -16,6 +20,8 @@ pub struct SheetListingModel { pub enum SheetListingInput { Query(String), ListBoxRowClicked(i32), + Sort, + Shuffle, } #[derive(Debug)] @@ -74,6 +80,74 @@ impl SimpleComponent for SheetListingModel { }) .unwrap(); } + SheetListingInput::Sort => sort_sheets(&mut self.sheets), + SheetListingInput::Shuffle => shuffle_sheets(&mut self.sheets), } } } + +fn shuffle_sheets(sheets: &mut FactoryVecDeque) { + let mut new_order: Vec = (0..sheets.len()).collect(); + new_order.shuffle(&mut rand::thread_rng()); + order_sheets(sheets, &mut new_order); +} + +fn sort_sheets(sheets: &mut FactoryVecDeque) { + let mut order = Vec::new(); + { + let guard = sheets.guard(); + let mut numerated_sheets: Vec<_> = guard.iter().enumerate().collect(); + numerated_sheets.sort_by(|a, b| a.1.sheet.cmp(&b.1.sheet).reverse()); + for (i, _) in numerated_sheets { + order.push(i); + } + } + order_sheets(sheets, &mut order); +} + +fn order_sheets(sheets: &mut FactoryVecDeque, order: &mut Vec) { + assert!(sheets.len() == order.len()); + + for i in 0..sheets.len() { + let new_i = order[i]; + let old_i = i; + + if old_i != new_i { + sheets.guard().swap(old_i, new_i); + order.swap(old_i, new_i); + } + } +} + +#[cfg(test)] +mod tests { + // Note this useful idiom: importing names from outer (for mod tests) scope. + use super::*; + + #[test] + fn test_sort() { + let original: Vec = (0..10).collect(); + let mut to_sort = original.clone(); + to_sort.shuffle(&mut rand::thread_rng()); + + println!("To sort: {:?}", to_sort); + + let mut order_builder: Vec<_> = to_sort.clone().into_iter().enumerate().collect(); + order_builder.sort_by(|a, b| a.1.cmp(&b.1)); + let mut order: Vec<_> = order_builder.into_iter().map(|(i, _)| i).collect(); + + for i in 0..to_sort.len() { + let new_i = order[i]; + let old_i = i; + + println!("Swap {} and {}", old_i, new_i); + if old_i != new_i { + to_sort.swap(old_i, new_i); + order.swap(old_i, new_i); + } + println!("order: {:?} - to_sort: {:?}", order, to_sort); + } + + assert_eq!(original, to_sort); + } +}