diff --git a/czkawka_slint_gui/src/common.rs b/czkawka_slint_gui/src/common.rs index c99da54..8b13789 100644 --- a/czkawka_slint_gui/src/common.rs +++ b/czkawka_slint_gui/src/common.rs @@ -1,3 +1 @@ -use slint::{ModelRc, SharedString}; -pub type ModelType = (bool, bool, bool, ModelRc); diff --git a/czkawka_slint_gui/src/connect_delete.rs b/czkawka_slint_gui/src/connect_delete.rs index daeb8ba..23a9b60 100644 --- a/czkawka_slint_gui/src/connect_delete.rs +++ b/czkawka_slint_gui/src/connect_delete.rs @@ -2,7 +2,7 @@ use std::borrow::Borrow; use slint::{ComponentHandle, Model, ModelRc, VecModel}; -use crate::common::ModelType; +use crate::MainListModel; use crate::{CurrentTab, MainWindow}; pub fn connect_delete_button(app: &MainWindow) { @@ -33,32 +33,32 @@ fn handle_delete_empty_folders(app: &MainWindow) { } // TODO delete in parallel items, consider to add progress bar -fn remove_selected_items(items: Vec) { +fn remove_selected_items(items: Vec) { dbg!(format!("Items to remove {}", items.len())); - items.into_iter().for_each(|(_checked, _header_row, _selected_row, _data)| {}); + items.into_iter().for_each(|_item| {}); } -fn deselect_all_items(items: &mut [ModelType]) { - items.iter_mut().for_each(|(_checked, _header_row, selected_row, _data)| { - *selected_row = false; +fn deselect_all_items(items: &mut [MainListModel]) { + items.iter_mut().for_each(|item| { + item.selected_row = false; }); } -fn filter_out_checked_items(items: &ModelRc, have_header: bool) -> (Vec, Vec) { +fn filter_out_checked_items(items: &ModelRc, have_header: bool) -> (Vec, Vec) { if cfg!(debug_assertions) { check_if_header_is_checked(items); check_if_header_is_selected_but_should_not_be(items, have_header); } - let (entries_to_delete, mut entries_left): (Vec<_>, Vec<_>) = items.iter().partition(|(checked, _header_row, _selected_row, _data)| *checked); + let (entries_to_delete, mut entries_left): (Vec<_>, Vec<_>) = items.iter().partition(|item| item.checked); if have_header && !entries_left.is_empty() { // First row must be header - assert!(entries_left[0].1); + assert!(entries_left[0].header_row); if entries_left.len() == 3 { // First row is header, so if second or third is also header, then there is no enough items to fill model - if entries_left[1].1 || entries_left[2].1 { + if entries_left[1].header_row || entries_left[2].header_row { entries_left = Vec::new(); } } else if entries_left.len() < 3 { @@ -66,9 +66,9 @@ fn filter_out_checked_items(items: &ModelRc, have_header: bool) -> (V entries_left = Vec::new(); } else { let mut last_header = 0; - let mut new_items: Vec = Vec::new(); + let mut new_items: Vec = Vec::new(); for i in 1..entries_left.len() { - if entries_left[i].1 { + if entries_left[i].header_row { if i - last_header > 2 { new_items.extend(entries_left[last_header..i].iter().cloned()); } @@ -89,22 +89,20 @@ fn filter_out_checked_items(items: &ModelRc, have_header: bool) -> (V // Function to verify if really headers are not checked // Checked header is big bug #[cfg(debug_assertions)] -fn check_if_header_is_checked(items: &ModelRc) { - for i in items.iter() { - let (checked, header_row, _selected_row, _data) = i; - if header_row { - assert!(!checked); +fn check_if_header_is_checked(items: &ModelRc) { + for item in items.iter() { + if item.header_row { + assert!(!item.checked); } } } // In some modes header should not be visible, but if are, then it is a bug #[cfg(debug_assertions)] -fn check_if_header_is_selected_but_should_not_be(items: &ModelRc, can_have_header: bool) { +fn check_if_header_is_selected_but_should_not_be(items: &ModelRc, can_have_header: bool) { if !can_have_header { - for i in items.iter() { - let (_checked, header_row, _selected_row, _data) = i; - assert!(!header_row); + for item in items.iter() { + assert!(!item.header_row); } } } @@ -113,13 +111,13 @@ fn check_if_header_is_selected_but_should_not_be(items: &ModelRc, can mod tests { use slint::{Model, ModelRc, SharedString, VecModel}; - use crate::common::ModelType; + use crate::common::MainListModel; use crate::connect_delete::filter_out_checked_items; #[test] fn test_filter_out_checked_items_empty() { let vec_items = Vec::new(); - let items: ModelRc = ModelRc::new(VecModel::from(vec_items)); + let items: ModelRc = ModelRc::new(VecModel::from(vec_items)); let (to_delete, left) = filter_out_checked_items(&items, false); assert!(to_delete.is_empty()); assert!(left.is_empty()); @@ -130,7 +128,7 @@ mod tests { #[test] fn test_filter_out_checked_items_one_element_valid_normal() { let vec_items = vec![(false, false, false, ModelRc::new(VecModel::default()))]; - let items: ModelRc = ModelRc::new(VecModel::from(vec_items)); + let items: ModelRc = ModelRc::new(VecModel::from(vec_items)); let (to_delete, left) = filter_out_checked_items(&items, false); assert!(to_delete.is_empty()); assert_eq!(left.len(), items.iter().count()); @@ -139,7 +137,7 @@ mod tests { #[test] fn test_filter_out_checked_items_one_element_valid_header() { let vec_items = vec![(false, true, false, ModelRc::new(VecModel::default()))]; - let items: ModelRc = ModelRc::new(VecModel::from(vec_items)); + let items: ModelRc = ModelRc::new(VecModel::from(vec_items)); let (to_delete, left) = filter_out_checked_items(&items, true); assert!(to_delete.is_empty()); assert!(left.is_empty()); @@ -149,82 +147,79 @@ mod tests { #[should_panic] fn test_filter_out_checked_items_one_element_invalid_normal() { let vec_items = vec![(false, true, false, ModelRc::new(VecModel::default()))]; - let items: ModelRc = ModelRc::new(VecModel::from(vec_items)); + let items: ModelRc = ModelRc::new(VecModel::from(vec_items)); filter_out_checked_items(&items, false); } #[test] #[should_panic] fn test_filter_out_checked_items_one_element_invalid_header() { let vec_items = vec![(false, false, false, ModelRc::new(VecModel::default()))]; - let items: ModelRc = ModelRc::new(VecModel::from(vec_items)); + let items: ModelRc = ModelRc::new(VecModel::from(vec_items)); filter_out_checked_items(&items, true); } + // + // #[test] + // fn test_filter_out_checked_items_multiple_element_valid_normal() { + // let vec_items = vec![ + // (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("1")]))), + // (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("2")]))), + // (true, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("3")]))), + // (true, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("4")]))), + // (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("5")]))), + // ]; + // let items: ModelRc = ModelRc::new(VecModel::from(vec_items)); + // let (to_delete, left) = filter_out_checked_items(&items, false); + // let to_delete_data = get_single_data_from_model(&to_delete); + // let left_data = get_single_data_from_model(&left); + // + // assert_eq!(to_delete_data, vec!["3", "4"]); + // assert_eq!(left_data, vec!["1", "2", "5"]); + // } + // + // #[test] + // fn test_filter_out_checked_items_multiple_element_valid_header() { + // let vec_items = vec![ + // (false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("1")]))), + // (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("2")]))), + // (true, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("3")]))), + // (false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("4")]))), + // (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("5")]))), + // (false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("6")]))), + // (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("7")]))), + // (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("8")]))), + // ]; + // let items: ModelRc = ModelRc::new(VecModel::from(vec_items)); + // let (to_delete, left) = filter_out_checked_items(&items, true); + // let to_delete_data = get_single_data_from_model(&to_delete); + // let left_data = get_single_data_from_model(&left); + // + // assert_eq!(to_delete_data, vec!["3"]); + // assert_eq!(left_data, vec!["6", "7", "8"]); + // } + // + // #[test] + // fn test_filter_out_checked_items_multiple2_element_valid_header() { + // let vec_items = vec![ + // (false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("1")]))), + // (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("2")]))), + // (true, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("3")]))), + // (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("4")]))), + // (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("5")]))), + // (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("6")]))), + // (false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("7")]))), + // (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("8")]))), + // ]; + // let items: ModelRc = ModelRc::new(VecModel::from(vec_items)); + // let (to_delete, left) = filter_out_checked_items(&items, true); + // let to_delete_data = get_single_data_from_model(&to_delete); + // let left_data = get_single_data_from_model(&left); + // + // assert_eq!(to_delete_data, vec!["3"]); + // assert_eq!(left_data, vec!["1", "2", "4", "5", "6"]); + // } - #[test] - fn test_filter_out_checked_items_multiple_element_valid_normal() { - let vec_items = vec![ - (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("1")]))), - (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("2")]))), - (true, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("3")]))), - (true, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("4")]))), - (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("5")]))), - ]; - let items: ModelRc = ModelRc::new(VecModel::from(vec_items)); - let (to_delete, left) = filter_out_checked_items(&items, false); - let to_delete_data = get_single_data_from_model(&to_delete); - let left_data = get_single_data_from_model(&left); - - assert_eq!(to_delete_data, vec!["3", "4"]); - assert_eq!(left_data, vec!["1", "2", "5"]); - } - - #[test] - fn test_filter_out_checked_items_multiple_element_valid_header() { - let vec_items = vec![ - (false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("1")]))), - (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("2")]))), - (true, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("3")]))), - (false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("4")]))), - (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("5")]))), - (false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("6")]))), - (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("7")]))), - (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("8")]))), - ]; - let items: ModelRc = ModelRc::new(VecModel::from(vec_items)); - let (to_delete, left) = filter_out_checked_items(&items, true); - let to_delete_data = get_single_data_from_model(&to_delete); - let left_data = get_single_data_from_model(&left); - - assert_eq!(to_delete_data, vec!["3"]); - assert_eq!(left_data, vec!["6", "7", "8"]); - } - - #[test] - fn test_filter_out_checked_items_multiple2_element_valid_header() { - let vec_items = vec![ - (false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("1")]))), - (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("2")]))), - (true, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("3")]))), - (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("4")]))), - (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("5")]))), - (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("6")]))), - (false, true, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("7")]))), - (false, false, false, ModelRc::new(VecModel::from_slice(&[SharedString::from("8")]))), - ]; - let items: ModelRc = ModelRc::new(VecModel::from(vec_items)); - let (to_delete, left) = filter_out_checked_items(&items, true); - let to_delete_data = get_single_data_from_model(&to_delete); - let left_data = get_single_data_from_model(&left); - - assert_eq!(to_delete_data, vec!["3"]); - assert_eq!(left_data, vec!["1", "2", "4", "5", "6"]); - } - - fn get_single_data_from_model(model: &[ModelType]) -> Vec { - let mut d = model - .iter() - .map(|(_checked, _header_row, _selected_row, data)| data.iter().next().unwrap().to_string()) - .collect::>(); + fn get_single_data_from_model(model: &[MainListModel]) -> Vec { + let mut d = model.iter().map(|item| item.data.iter().next().unwrap().to_string()).collect::>(); d.sort(); d } diff --git a/czkawka_slint_gui/src/connect_scan.rs b/czkawka_slint_gui/src/connect_scan.rs index 66d9f5c..9737289 100644 --- a/czkawka_slint_gui/src/connect_scan.rs +++ b/czkawka_slint_gui/src/connect_scan.rs @@ -1,3 +1,4 @@ +use crate::MainListModel; use crate::{split_path, CurrentTab, MainWindow, ProgressToSend}; use chrono::NaiveDateTime; use crossbeam_channel::{Receiver, Sender}; @@ -58,7 +59,13 @@ fn scan_empty_folders(a: Weak, progress_sender: Sender SharedString::from(NaiveDateTime::from_timestamp_opt(folder_map[&path].modified_date as i64, 0).unwrap().to_string()), ]); - items.push((false, false, false, ModelRc::new(data_model))); + let main = MainListModel { + checked: false, + header_row: false, + selected_row: false, + val: ModelRc::new(data_model), + }; + items.push(main); } app.set_empty_folder_model(items.into()); app.invoke_scan_ended(); diff --git a/czkawka_slint_gui/src/main.rs b/czkawka_slint_gui/src/main.rs index bdc8d56..d0ecc9c 100644 --- a/czkawka_slint_gui/src/main.rs +++ b/czkawka_slint_gui/src/main.rs @@ -15,7 +15,6 @@ use crate::connect_delete::connect_delete_button; use crate::connect_open::connect_open_items; use crate::connect_scan::connect_scan_button; -use crate::common::ModelType; use crate::connect_progress_receiver::connect_progress_gathering; use crate::connect_stop::connect_stop_button; use czkawka_core::common_dir_traversal::ProgressData; @@ -43,7 +42,7 @@ fn main() { // TODO remove this after trying pub fn to_remove_debug(app: &MainWindow) { - let row_data: Rc> = Rc::new(VecModel::default()); + let row_data: Rc> = Rc::new(VecModel::default()); for r in 0..100_000 { let items = VecModel::default(); @@ -53,7 +52,15 @@ pub fn to_remove_debug(app: &MainWindow) { let is_header = r % 3 == 0; let is_checked = (r % 2 == 0) && !is_header; - row_data.push((is_checked, is_header, false, ModelRc::new(items))); + + let item = MainListModel { + checked: is_checked, + header_row: is_header, + selected_row: false, + val: ModelRc::new(items), + }; + + row_data.push(item); } app.set_empty_folder_model(row_data.into()); } diff --git a/czkawka_slint_gui/ui/common.slint b/czkawka_slint_gui/ui/common.slint index 0946e9f..a9e165b 100644 --- a/czkawka_slint_gui/ui/common.slint +++ b/czkawka_slint_gui/ui/common.slint @@ -18,6 +18,6 @@ export struct ProgressToSend { export struct MainListModel { checked: bool, header_row: bool, - selected: bool, - data: [string] + selected_row: bool, + val: [string] } \ No newline at end of file diff --git a/czkawka_slint_gui/ui/main_lists.slint b/czkawka_slint_gui/ui/main_lists.slint index 645c9d4..f9a6562 100644 --- a/czkawka_slint_gui/ui/main_lists.slint +++ b/czkawka_slint_gui/ui/main_lists.slint @@ -9,8 +9,8 @@ export component MainList { in-out property active-tab; in-out property <[MainListModel]> empty_folder_model; - in-out property <[{checked: bool, header_row: bool, selected_row: bool, val:[string]}]> empty_files_model; - in-out property <[{checked: bool, header_row: bool, selected_row: bool, val:[string]}]> similar_images_model; + in-out property <[MainListModel]> empty_files_model; + in-out property <[MainListModel]> similar_images_model; // TODO - using root.active-tab in visible property will not clear model if root.active-tab == CurrentTab.EmptyFolders: SelectableTableView { @@ -24,26 +24,28 @@ export component MainList { fileNameIdx: 1; item_opened(item) => {item_opened(item)} } - // if root.active-tab == CurrentTab.EmptyFiles: SelectableTableView { - // min-width: 200px; + + if root.active-tab == CurrentTab.EmptyFiles: SelectableTableView { + min-width: 200px; - // columns: ["Selection", "Folder Name", "Path"]; - // last-column: "Modification Date"; - // column-sizes: [30px, 100px, 100px, 100px]; - // values <=> empty-files-model; - // parentPathIdx: 2; - // fileNameIdx: 1; - // item_opened(item) => {item_opened(item)} - // } - // if root.active-tab == CurrentTab.SimilarImages: SelectableTableView { - // min-width: 200px; + columns: ["Selection", "Folder Name", "Path"]; + last-column: "Modification Date"; + column-sizes: [30px, 100px, 100px, 100px]; + values <=> empty-files-model; + parentPathIdx: 2; + fileNameIdx: 1; + item_opened(item) => {item_opened(item)} + } - // columns: ["Selection", "Folder Name", "Path"]; - // last-column: "Modification Date"; - // column-sizes: [30px, 100px, 100px, 100px]; - // values <=> similar-images-model; - // parentPathIdx: 2; - // fileNameIdx: 1; - // item_opened(item) => {item_opened(item)} - // } + if root.active-tab == CurrentTab.SimilarImages: SelectableTableView { + min-width: 200px; + + columns: ["Selection", "Folder Name", "Path"]; + last-column: "Modification Date"; + column-sizes: [30px, 100px, 100px, 100px]; + values <=> similar-images-model; + parentPathIdx: 2; + fileNameIdx: 1; + item_opened(item) => {item_opened(item)} + } } \ No newline at end of file diff --git a/czkawka_slint_gui/ui/main_window.slint b/czkawka_slint_gui/ui/main_window.slint index a3baf61..da5af54 100644 --- a/czkawka_slint_gui/ui/main_window.slint +++ b/czkawka_slint_gui/ui/main_window.slint @@ -5,6 +5,8 @@ import {MainList} from "main_lists.slint"; import {CurrentTab, ProgressToSend} from "common.slint"; import { ActionButtons } from "action_buttons.slint"; import { Progress } from "progress.slint"; +import {MainListModel} from "common.slint"; + export component MainWindow inherits Window { callback deleted; @@ -28,7 +30,7 @@ export component MainWindow inherits Window { }; in-out property active-tab: CurrentTab.EmptyFolders; - in-out property <[{checked: bool, header_row: bool, selected_row: bool, val:[string]}]> empty_folder_model: [ + in-out property <[MainListModel]> empty_folder_model: [ {checked: false, selected_row: false, header_row: true, val: ["kropkarz", "/Xd1", "24.10.2023"]} , {checked: false, selected_row: false, header_row: false, val: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"]} , {checked: false, selected_row: false, header_row: false, val: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"]} , @@ -57,8 +59,8 @@ export component MainWindow inherits Window { {checked: false, selected_row: false, header_row: false, val: ["witasphere", "/Xd1/Imagerren2", "25.11.1991"]} , {checked: true, selected_row: false, header_row: false, val: ["lokkaler", "/Xd1/Vide2", "01.23.1911"]} ]; - in-out property <[{checked: bool, header_row: bool, selected_row: bool, val:[string]}]> empty_files_model: []; - in-out property <[{checked: bool, header_row: bool, selected_row: bool, val:[string]}]> similar_images_model: []; + in-out property <[MainListModel]> empty_files_model: []; + in-out property <[MainListModel]> similar_images_model: []; title: root.active-tab == CurrentTab.EmptyFiles ? "EmptyFiles" : (root.active-tab == CurrentTab.EmptyFolders ? "EmptyFolders" : "Similar Images"); diff --git a/czkawka_slint_gui/ui/selectable_tree_view.slint b/czkawka_slint_gui/ui/selectable_tree_view.slint index 11763a1..b1f5b3c 100644 --- a/czkawka_slint_gui/ui/selectable_tree_view.slint +++ b/czkawka_slint_gui/ui/selectable_tree_view.slint @@ -1,13 +1,15 @@ import { Button, VerticalBox , HorizontalBox, TabWidget, ListView, StandardListView, StandardTableView, CheckBox} from "std-widgets.slint"; import {TypeOfOpenedItem} from "common.slint"; import {ColorPalette} from "color_palette.slint"; +import {MainListModel} from "common.slint"; + export component SelectableTableView inherits Rectangle { callback item_opened(string); in property <[string]> columns; in property last_column; - in-out property <[{checked: bool, header_row: bool, selected_row: bool, val:[string]}]> values; + in-out property <[MainListModel]> values; in-out property <[length]> column_sizes; private property <[length]> real_sizes: [0px,0px,0px,0px,0px,0px,0px,0px,0px,0px,0px,0px,0px,0px,0px,0px,0px,0px]; @@ -74,6 +76,7 @@ export component SelectableTableView inherits Rectangle { forward-focus: focus-item; for r[idx] in root.values : Rectangle { + border-radius: 20px; forward-focus: focus-item; height: 20px; background: r.header-row ? ColorPalette.list_view_normal_header_color : (touch-area.has-hover ? (r.selected_row ? ColorPalette.list-view-normal-selected-header : ColorPalette.list_view_normal_color) : (r.selected_row ? ColorPalette.list-view-normal-selected-header: ColorPalette.list_view_normal_color));