diff --git a/czkawka_core/src/common.rs b/czkawka_core/src/common.rs index 6613f86..9404994 100644 --- a/czkawka_core/src/common.rs +++ b/czkawka_core/src/common.rs @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; use std::thread::{sleep, JoinHandle}; -use std::time::{Duration, SystemTime}; +use std::time::{Duration, Instant, SystemTime}; use std::{fs, thread}; #[cfg(feature = "heif")] @@ -18,7 +18,7 @@ use image::{DynamicImage, ImageBuffer, Rgb}; use imagepipe::{ImageSource, Pipeline}; #[cfg(feature = "heif")] use libheif_rs::{ColorSpace, HeifContext, RgbChroma}; -use log::{info, LevelFilter, Record}; +use log::{debug, info, LevelFilter, Record}; // #[cfg(feature = "heif")] // use libheif_rs::LibHeif; @@ -184,12 +184,17 @@ pub fn get_dynamic_image_from_heic(path: &str) -> Result { } pub fn get_dynamic_image_from_raw_image(path: impl AsRef + std::fmt::Debug) -> Option { + let mut start_timer = Instant::now(); + let mut times = Vec::new(); + let file_handler = match OpenOptions::new().read(true).open(&path) { Ok(t) => t, Err(_e) => { return None; } }; + times.push(("After opening", start_timer.elapsed())); + start_timer = Instant::now(); let mut reader = BufReader::new(file_handler); let raw = match rawloader::decode(&mut reader) { @@ -199,8 +204,14 @@ pub fn get_dynamic_image_from_raw_image(path: impl AsRef + std::fmt::Debug } }; + times.push(("After decoding", start_timer.elapsed())); + start_timer = Instant::now(); + let source = ImageSource::Raw(raw); + times.push(("After creating source", start_timer.elapsed())); + start_timer = Instant::now(); + let mut pipeline = match Pipeline::new_from_source(source) { Ok(pipeline) => pipeline, Err(_e) => { @@ -208,6 +219,9 @@ pub fn get_dynamic_image_from_raw_image(path: impl AsRef + std::fmt::Debug } }; + times.push(("After creating pipeline", start_timer.elapsed())); + start_timer = Instant::now(); + pipeline.run(None); let image = match pipeline.output_8bit(None) { Ok(image) => image, @@ -216,12 +230,22 @@ pub fn get_dynamic_image_from_raw_image(path: impl AsRef + std::fmt::Debug } }; + times.push(("After creating image", start_timer.elapsed())); + start_timer = Instant::now(); + let Some(image) = ImageBuffer::, Vec>::from_raw(image.width as u32, image.height as u32, image.data) else { return None; }; + times.push(("After creating image buffer", start_timer.elapsed())); + start_timer = Instant::now(); // println!("Properly hashed {:?}", path); - Some(DynamicImage::ImageRgb8(image)) + let res = Some(DynamicImage::ImageRgb8(image)); + times.push(("After creating dynamic image", start_timer.elapsed())); + + let str_timer = times.into_iter().map(|(name, time)| format!("{name}: {time:?}")).collect::>().join(", "); + debug!("Loading raw image --- {str_timer}"); + res } pub fn split_path(path: &Path) -> (String, String) { diff --git a/krokiet/src/connect_show_preview.rs b/krokiet/src/connect_show_preview.rs index bcd6e6a..30aeec3 100644 --- a/krokiet/src/connect_show_preview.rs +++ b/krokiet/src/connect_show_preview.rs @@ -1,5 +1,5 @@ use crate::{Callabler, GuiState, MainWindow}; -use czkawka_core::common::IMAGE_RS_EXTENSIONS; +use czkawka_core::common::{get_dynamic_image_from_raw_image, IMAGE_RS_EXTENSIONS, RAW_IMAGE_EXTENSIONS}; use image::DynamicImage; use log::{debug, error}; use slint::ComponentHandle; @@ -50,17 +50,33 @@ fn load_image(image_path: &Path) -> Option<(Duration, image::DynamicImage)> { let image_extension = image_path.extension()?.to_string_lossy().to_lowercase(); let extension_with_dot = format!(".{}", image_extension); - if !IMAGE_RS_EXTENSIONS.contains(&extension_with_dot.as_str()) { + let is_raw_image = RAW_IMAGE_EXTENSIONS.contains(&extension_with_dot.as_str()); + let is_normal_image = IMAGE_RS_EXTENSIONS.contains(&extension_with_dot.as_str()); + + if !is_raw_image && !is_normal_image { return None; } let load_img_start_timer = Instant::now(); - let img = image::open(image_name); - match img { - Ok(img) => Some((load_img_start_timer.elapsed(), img)), - Err(e) => { - error!("Error while loading image: {}", e); + // TODO this needs to be run inside closure + let img = if is_normal_image { + match image::open(image_name) { + Ok(img) => img, + Err(e) => { + error!("Error while loading image: {}", e); + return None; + } + } + } else if is_raw_image { + if let Some(img) = get_dynamic_image_from_raw_image(image_name) { + img + } else { + error!("Error while loading raw image - not sure why - try to guess"); return None; } - } + } else { + panic!("Used not supported image extension"); + }; + + Some((load_img_start_timer.elapsed(), img)) } diff --git a/krokiet/ui/left_side_panel.slint b/krokiet/ui/left_side_panel.slint index 8b9dac2..85196aa 100644 --- a/krokiet/ui/left_side_panel.slint +++ b/krokiet/ui/left_side_panel.slint @@ -7,6 +7,8 @@ component TabItem { in-out property active-tab; in property text; in property curr_tab; + callback changed_current_tab(); + Rectangle { width: parent.width; horizontal-stretch: 1.0; @@ -17,6 +19,7 @@ component TabItem { return; } root.active-tab = root.curr-tab; + changed_current_tab(); } } } @@ -61,6 +64,7 @@ component TabItem { export component LeftSidePanel { in-out property active-tab; in-out property scanning; + callback changed_current_tab(); width: 120px; VerticalLayout { spacing: 20px; @@ -82,6 +86,7 @@ export component LeftSidePanel { text: "Empty Folders"; active-tab <=> root.active-tab; curr_tab: CurrentTab.EmptyFolders; + changed_current_tab() => {root.changed_current_tab();} } TabItem { @@ -90,6 +95,7 @@ export component LeftSidePanel { text: "Empty Files"; active-tab <=> root.active-tab; curr_tab: CurrentTab.EmptyFiles; + changed_current_tab() => {root.changed_current_tab();} } TabItem { @@ -98,6 +104,7 @@ export component LeftSidePanel { text: "Similar Images"; active-tab <=> root.active-tab; curr_tab: CurrentTab.SimilarImages; + changed_current_tab() => {root.changed_current_tab();} } } diff --git a/krokiet/ui/main_lists.slint b/krokiet/ui/main_lists.slint index 15fd1aa..d8e1029 100644 --- a/krokiet/ui/main_lists.slint +++ b/krokiet/ui/main_lists.slint @@ -9,40 +9,43 @@ export component MainList { in-out property <[MainListModel]> empty_folder_model; in-out property <[MainListModel]> empty_files_model; in-out property <[MainListModel]> similar_images_model; + callback changed_current_tab(); - SelectableTableView { + empty_folders := SelectableTableView { visible: root.active-tab == CurrentTab.EmptyFolders; min-width: 200px; height: parent.height; - columns: ["Selection", "Folder Name", "Path"]; - last-column: "Modification Date"; - column-sizes: [35px, 100px, 350px, 100px]; + columns: ["Selection", "Folder Name", "Path", "Modification Date"]; + column-sizes: [35px, 100px, 350px, 150px]; values <=> empty-folder-model; parentPathIdx: 2; fileNameIdx: 1; } - SelectableTableView { + empty_files := SelectableTableView { visible: root.active-tab == CurrentTab.EmptyFiles; min-width: 200px; height: parent.height; - columns: ["Selection", "File Name", "Path"]; - last-column: "Modification Date"; - column-sizes: [35px, 100px, 350px, 100px]; + columns: ["Selection", "File Name", "Path", "Modification Date"]; + column-sizes: [35px, 100px, 350px, 150px]; values <=> empty-files-model; parentPathIdx: 2; fileNameIdx: 1; } - SelectableTableView { + similar_images := SelectableTableView { visible: root.active-tab == CurrentTab.SimilarImages; min-width: 200px; height: parent.height; - columns: ["Selection", "Similarity", "Size", "Dimensions", "File Name", "Path"]; - last-column: "Modification Date"; - column-sizes: [35px, 80px, 80px, 80px, 350px, 100px, 100px]; + columns: ["Selection", "Similarity", "Size", "Dimensions", "File Name", "Path", "Modification Date"]; + column-sizes: [35px, 80px, 80px, 80px, 100px, 350px, 150px]; values <=> similar-images-model; parentPathIdx: 5; fileNameIdx: 4; } + changed_current_tab() => { + empty_folders.deselect_selected_item(); + empty_files.deselect_selected_item(); + similar_images.deselect_selected_item(); + } } diff --git a/krokiet/ui/main_window.slint b/krokiet/ui/main_window.slint index 5668fbe..69d3aa2 100644 --- a/krokiet/ui/main_window.slint +++ b/krokiet/ui/main_window.slint @@ -53,13 +53,18 @@ export component MainWindow inherits Window { horizontal-stretch: 0.0; scanning <=> root.scanning; active-tab <=> root.active-tab; + changed_current_tab() => { + GuiState.preview-visible = false; + main_list.changed_current_tab(); + } } VerticalLayout { horizontal-stretch: 1.0; Rectangle { vertical-stretch: 1.0; - MainList { + main_list := MainList { + x: 0; width: GuiState.preview_visible ? parent.width / 2 : parent.width; height: parent.height; horizontal-stretch: 0.5; @@ -74,6 +79,7 @@ export component MainWindow inherits Window { width: GuiState.preview_visible ? parent.width / 2 : 0; visible: GuiState.preview_visible; source: GuiState.preview_image; + image-fit: ImageFit.contain; } } diff --git a/krokiet/ui/selectable_tree_view.slint b/krokiet/ui/selectable_tree_view.slint index b4a4efe..642d393 100644 --- a/krokiet/ui/selectable_tree_view.slint +++ b/krokiet/ui/selectable_tree_view.slint @@ -8,7 +8,6 @@ import {GuiState} from "gui_state.slint"; export component SelectableTableView inherits Rectangle { callback item_opened(string); in property <[string]> columns; - in property last_column; in-out property <[MainListModel]> values; in-out property <[length]> column_sizes; private property column_number: column-sizes.length + 1; @@ -25,119 +24,123 @@ export component SelectableTableView inherits Rectangle { } } - VerticalBox { - padding: 5px; - forward-focus: focus-item; - HorizontalLayout { + ScrollView { + VerticalBox { padding: 5px; - spacing: 5px; - vertical-stretch: 0; - for title [idx] in root.columns: HorizontalLayout { - width: root.column-sizes[idx]; - Text { - overflow: elide; - text: title; - } + forward-focus: focus-item; + HorizontalLayout { + padding: 5px; + spacing: 5px; + vertical-stretch: 0; + for title [idx] in root.columns: HorizontalLayout { + width: root.column-sizes[idx]; + Text { + overflow: elide; + text: title; + } - Rectangle { - width: 1px; - background: gray; - forward-focus: focus-item; - TouchArea { - width: 5px; - x: (parent.width - self.width) / 2; - property cached; + Rectangle { + width: 1px; + background: gray; forward-focus: focus-item; - pointer-event(event) => { - if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) { - self.cached = root.column_sizes[idx]; - } - } - moved => { - if (self.pressed) { - root.column_sizes[idx] += (self.mouse-x - self.pressed-x); - if (root.column_sizes[idx] < 20px) { - root.column_sizes[idx] = 20px; + TouchArea { + width: 5px; + x: (parent.width - self.width) / 2; + property cached; + forward-focus: focus-item; + pointer-event(event) => { + if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) { + self.cached = root.column_sizes[idx]; } } + moved => { + if (self.pressed) { + root.column_sizes[idx] += (self.mouse-x - self.pressed-x); + if (root.column_sizes[idx] < 20px) { + root.column_sizes[idx] = 20px; + } + } + } + mouse-cursor: ew-resize; } - mouse-cursor: ew-resize; } } } - Text { - overflow: elide; - text: last-column; - } - } - - list_view := ListView { - min-width: 100px; - forward-focus: focus-item; - for r [idx] in root.values: Rectangle { - border-radius: 5px; + list_view := ListView { + min-width: 100px; 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)); - touch_area := TouchArea { + for r [idx] in root.values: Rectangle { + border-radius: 5px; forward-focus: focus-item; - clicked => { - if (!r.header_row) { - r.selected_row = !r.selected_row; - if (root.selected-item == -1) { - root.selected-item = idx; - } else { - if (r.selected_row == true) { - root.values[root.selected-item].selected_row = false; + 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)); + touch_area := TouchArea { + forward-focus: focus-item; + clicked => { + if (!r.header_row) { + r.selected_row = !r.selected_row; + if (root.selected-item == -1) { root.selected-item = idx; } else { - root.selected-item = -1; + if (r.selected_row == true) { + root.values[root.selected-item].selected_row = false; + root.selected-item = idx; + } else { + root.selected-item = -1; + } + } + + if (root.selected_item != -1) { + Callabler.load_image_preview(r.val[root.parentPathIdx - 1] + "/" + r.val[root.fileNameIdx - 1]); + } else { + GuiState.preview-visible = false; } } - - if (root.selected_item != -1) { - Callabler.load_image_preview(r.val[root.parentPathIdx - 1] + "/" + r.val[root.fileNameIdx - 1]); - } else { - GuiState.preview-visible = false; + } + pointer-event(event) => { + // TODO this should be clicked by double-click + if (event.button == PointerEventButton.right && event.kind == PointerEventKind.up) { + Callabler.item_opened(r.val[root.parentPathIdx - 1]) + } else if (event.button == PointerEventButton.middle && event.kind == PointerEventKind.up) { + Callabler.item_opened(r.val[root.parentPathIdx - 1] + "/" + r.val[root.fileNameIdx - 1]) } } } - pointer-event(event) => { - // TODO this should be clicked by double-click - if (event.button == PointerEventButton.right && event.kind == PointerEventKind.up) { - Callabler.item_opened(r.val[root.parentPathIdx - 1]) - } else if (event.button == PointerEventButton.middle && event.kind == PointerEventKind.up) { - Callabler.item_opened(r.val[root.parentPathIdx - 1] + "/" + r.val[root.fileNameIdx - 1]) - } - } - } - - HorizontalLayout { - forward-focus: focus-item; - CheckBox { - visible: !r.header-row; - checked: r.checked && !r.header-row; - width: root.column-sizes[0]; - forward-focus: focus-item; - toggled => { - r.checked = self.checked; - } - } HorizontalLayout { - spacing: 5px; - for f [idx] in r.val: Text { - width: root.column-sizes[idx + 1]; - text: f; - font-size: 12px; + forward-focus: focus-item; + CheckBox { + visible: !r.header-row; + checked: r.checked && !r.header-row; + width: root.column-sizes[0]; forward-focus: focus-item; - vertical-alignment: center; - overflow: elide; + toggled => { + r.checked = self.checked; + } + } + + HorizontalLayout { + spacing: 5px; + for f [idx] in r.val: Text { + width: root.column-sizes[idx + 1]; + text: f; + font-size: 12px; + forward-focus: focus-item; + vertical-alignment: center; + overflow: elide; + } } } } } } } + + public function deselect_selected_item() { + if (root.selected_item != -1) { + root.values[root.selected-item].selected_row = false; + root.selected-item = -1; + } + } }