1
0
Fork 0
mirror of synced 2024-06-18 18:34:54 +12:00

Improved ListView

This commit is contained in:
Rafał Mikrut 2023-11-12 16:00:55 +01:00
parent 186c0e1895
commit c919aa11fa
6 changed files with 171 additions and 112 deletions

View file

@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::thread::{sleep, JoinHandle}; use std::thread::{sleep, JoinHandle};
use std::time::{Duration, SystemTime}; use std::time::{Duration, Instant, SystemTime};
use std::{fs, thread}; use std::{fs, thread};
#[cfg(feature = "heif")] #[cfg(feature = "heif")]
@ -18,7 +18,7 @@ use image::{DynamicImage, ImageBuffer, Rgb};
use imagepipe::{ImageSource, Pipeline}; use imagepipe::{ImageSource, Pipeline};
#[cfg(feature = "heif")] #[cfg(feature = "heif")]
use libheif_rs::{ColorSpace, HeifContext, RgbChroma}; use libheif_rs::{ColorSpace, HeifContext, RgbChroma};
use log::{info, LevelFilter, Record}; use log::{debug, info, LevelFilter, Record};
// #[cfg(feature = "heif")] // #[cfg(feature = "heif")]
// use libheif_rs::LibHeif; // use libheif_rs::LibHeif;
@ -184,12 +184,17 @@ pub fn get_dynamic_image_from_heic(path: &str) -> Result<DynamicImage> {
} }
pub fn get_dynamic_image_from_raw_image(path: impl AsRef<Path> + std::fmt::Debug) -> Option<DynamicImage> { pub fn get_dynamic_image_from_raw_image(path: impl AsRef<Path> + std::fmt::Debug) -> Option<DynamicImage> {
let mut start_timer = Instant::now();
let mut times = Vec::new();
let file_handler = match OpenOptions::new().read(true).open(&path) { let file_handler = match OpenOptions::new().read(true).open(&path) {
Ok(t) => t, Ok(t) => t,
Err(_e) => { Err(_e) => {
return None; return None;
} }
}; };
times.push(("After opening", start_timer.elapsed()));
start_timer = Instant::now();
let mut reader = BufReader::new(file_handler); let mut reader = BufReader::new(file_handler);
let raw = match rawloader::decode(&mut reader) { let raw = match rawloader::decode(&mut reader) {
@ -199,8 +204,14 @@ pub fn get_dynamic_image_from_raw_image(path: impl AsRef<Path> + std::fmt::Debug
} }
}; };
times.push(("After decoding", start_timer.elapsed()));
start_timer = Instant::now();
let source = ImageSource::Raw(raw); 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) { let mut pipeline = match Pipeline::new_from_source(source) {
Ok(pipeline) => pipeline, Ok(pipeline) => pipeline,
Err(_e) => { Err(_e) => {
@ -208,6 +219,9 @@ pub fn get_dynamic_image_from_raw_image(path: impl AsRef<Path> + std::fmt::Debug
} }
}; };
times.push(("After creating pipeline", start_timer.elapsed()));
start_timer = Instant::now();
pipeline.run(None); pipeline.run(None);
let image = match pipeline.output_8bit(None) { let image = match pipeline.output_8bit(None) {
Ok(image) => image, Ok(image) => image,
@ -216,12 +230,22 @@ pub fn get_dynamic_image_from_raw_image(path: impl AsRef<Path> + std::fmt::Debug
} }
}; };
times.push(("After creating image", start_timer.elapsed()));
start_timer = Instant::now();
let Some(image) = ImageBuffer::<Rgb<u8>, Vec<u8>>::from_raw(image.width as u32, image.height as u32, image.data) else { let Some(image) = ImageBuffer::<Rgb<u8>, Vec<u8>>::from_raw(image.width as u32, image.height as u32, image.data) else {
return None; return None;
}; };
times.push(("After creating image buffer", start_timer.elapsed()));
start_timer = Instant::now();
// println!("Properly hashed {:?}", path); // 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::<Vec<_>>().join(", ");
debug!("Loading raw image --- {str_timer}");
res
} }
pub fn split_path(path: &Path) -> (String, String) { pub fn split_path(path: &Path) -> (String, String) {

View file

@ -1,5 +1,5 @@
use crate::{Callabler, GuiState, MainWindow}; 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 image::DynamicImage;
use log::{debug, error}; use log::{debug, error};
use slint::ComponentHandle; 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 image_extension = image_path.extension()?.to_string_lossy().to_lowercase();
let extension_with_dot = format!(".{}", image_extension); 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; return None;
} }
let load_img_start_timer = Instant::now(); let load_img_start_timer = Instant::now();
let img = image::open(image_name);
match img { // TODO this needs to be run inside closure
Ok(img) => Some((load_img_start_timer.elapsed(), img)), let img = if is_normal_image {
Err(e) => { match image::open(image_name) {
error!("Error while loading image: {}", e); 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; return None;
} }
} } else {
panic!("Used not supported image extension");
};
Some((load_img_start_timer.elapsed(), img))
} }

View file

@ -7,6 +7,8 @@ component TabItem {
in-out property <CurrentTab> active-tab; in-out property <CurrentTab> active-tab;
in property <string> text; in property <string> text;
in property <CurrentTab> curr_tab; in property <CurrentTab> curr_tab;
callback changed_current_tab();
Rectangle { Rectangle {
width: parent.width; width: parent.width;
horizontal-stretch: 1.0; horizontal-stretch: 1.0;
@ -17,6 +19,7 @@ component TabItem {
return; return;
} }
root.active-tab = root.curr-tab; root.active-tab = root.curr-tab;
changed_current_tab();
} }
} }
} }
@ -61,6 +64,7 @@ component TabItem {
export component LeftSidePanel { export component LeftSidePanel {
in-out property <CurrentTab> active-tab; in-out property <CurrentTab> active-tab;
in-out property <bool> scanning; in-out property <bool> scanning;
callback changed_current_tab();
width: 120px; width: 120px;
VerticalLayout { VerticalLayout {
spacing: 20px; spacing: 20px;
@ -82,6 +86,7 @@ export component LeftSidePanel {
text: "Empty Folders"; text: "Empty Folders";
active-tab <=> root.active-tab; active-tab <=> root.active-tab;
curr_tab: CurrentTab.EmptyFolders; curr_tab: CurrentTab.EmptyFolders;
changed_current_tab() => {root.changed_current_tab();}
} }
TabItem { TabItem {
@ -90,6 +95,7 @@ export component LeftSidePanel {
text: "Empty Files"; text: "Empty Files";
active-tab <=> root.active-tab; active-tab <=> root.active-tab;
curr_tab: CurrentTab.EmptyFiles; curr_tab: CurrentTab.EmptyFiles;
changed_current_tab() => {root.changed_current_tab();}
} }
TabItem { TabItem {
@ -98,6 +104,7 @@ export component LeftSidePanel {
text: "Similar Images"; text: "Similar Images";
active-tab <=> root.active-tab; active-tab <=> root.active-tab;
curr_tab: CurrentTab.SimilarImages; curr_tab: CurrentTab.SimilarImages;
changed_current_tab() => {root.changed_current_tab();}
} }
} }

View file

@ -9,40 +9,43 @@ export component MainList {
in-out property <[MainListModel]> empty_folder_model; in-out property <[MainListModel]> empty_folder_model;
in-out property <[MainListModel]> empty_files_model; in-out property <[MainListModel]> empty_files_model;
in-out property <[MainListModel]> similar_images_model; in-out property <[MainListModel]> similar_images_model;
callback changed_current_tab();
SelectableTableView { empty_folders := SelectableTableView {
visible: root.active-tab == CurrentTab.EmptyFolders; visible: root.active-tab == CurrentTab.EmptyFolders;
min-width: 200px; min-width: 200px;
height: parent.height; height: parent.height;
columns: ["Selection", "Folder Name", "Path"]; columns: ["Selection", "Folder Name", "Path", "Modification Date"];
last-column: "Modification Date"; column-sizes: [35px, 100px, 350px, 150px];
column-sizes: [35px, 100px, 350px, 100px];
values <=> empty-folder-model; values <=> empty-folder-model;
parentPathIdx: 2; parentPathIdx: 2;
fileNameIdx: 1; fileNameIdx: 1;
} }
SelectableTableView { empty_files := SelectableTableView {
visible: root.active-tab == CurrentTab.EmptyFiles; visible: root.active-tab == CurrentTab.EmptyFiles;
min-width: 200px; min-width: 200px;
height: parent.height; height: parent.height;
columns: ["Selection", "File Name", "Path"]; columns: ["Selection", "File Name", "Path", "Modification Date"];
last-column: "Modification Date"; column-sizes: [35px, 100px, 350px, 150px];
column-sizes: [35px, 100px, 350px, 100px];
values <=> empty-files-model; values <=> empty-files-model;
parentPathIdx: 2; parentPathIdx: 2;
fileNameIdx: 1; fileNameIdx: 1;
} }
SelectableTableView { similar_images := SelectableTableView {
visible: root.active-tab == CurrentTab.SimilarImages; visible: root.active-tab == CurrentTab.SimilarImages;
min-width: 200px; min-width: 200px;
height: parent.height; height: parent.height;
columns: ["Selection", "Similarity", "Size", "Dimensions", "File Name", "Path"]; columns: ["Selection", "Similarity", "Size", "Dimensions", "File Name", "Path", "Modification Date"];
last-column: "Modification Date"; column-sizes: [35px, 80px, 80px, 80px, 100px, 350px, 150px];
column-sizes: [35px, 80px, 80px, 80px, 350px, 100px, 100px];
values <=> similar-images-model; values <=> similar-images-model;
parentPathIdx: 5; parentPathIdx: 5;
fileNameIdx: 4; fileNameIdx: 4;
} }
changed_current_tab() => {
empty_folders.deselect_selected_item();
empty_files.deselect_selected_item();
similar_images.deselect_selected_item();
}
} }

View file

@ -53,13 +53,18 @@ export component MainWindow inherits Window {
horizontal-stretch: 0.0; horizontal-stretch: 0.0;
scanning <=> root.scanning; scanning <=> root.scanning;
active-tab <=> root.active-tab; active-tab <=> root.active-tab;
changed_current_tab() => {
GuiState.preview-visible = false;
main_list.changed_current_tab();
}
} }
VerticalLayout { VerticalLayout {
horizontal-stretch: 1.0; horizontal-stretch: 1.0;
Rectangle { Rectangle {
vertical-stretch: 1.0; vertical-stretch: 1.0;
MainList { main_list := MainList {
x: 0;
width: GuiState.preview_visible ? parent.width / 2 : parent.width; width: GuiState.preview_visible ? parent.width / 2 : parent.width;
height: parent.height; height: parent.height;
horizontal-stretch: 0.5; horizontal-stretch: 0.5;
@ -74,6 +79,7 @@ export component MainWindow inherits Window {
width: GuiState.preview_visible ? parent.width / 2 : 0; width: GuiState.preview_visible ? parent.width / 2 : 0;
visible: GuiState.preview_visible; visible: GuiState.preview_visible;
source: GuiState.preview_image; source: GuiState.preview_image;
image-fit: ImageFit.contain;
} }
} }

View file

@ -8,7 +8,6 @@ import {GuiState} from "gui_state.slint";
export component SelectableTableView inherits Rectangle { export component SelectableTableView inherits Rectangle {
callback item_opened(string); callback item_opened(string);
in property <[string]> columns; in property <[string]> columns;
in property <string> last_column;
in-out property <[MainListModel]> values; in-out property <[MainListModel]> values;
in-out property <[length]> column_sizes; in-out property <[length]> column_sizes;
private property <int> column_number: column-sizes.length + 1; private property <int> column_number: column-sizes.length + 1;
@ -25,119 +24,123 @@ export component SelectableTableView inherits Rectangle {
} }
} }
VerticalBox { ScrollView {
padding: 5px; VerticalBox {
forward-focus: focus-item;
HorizontalLayout {
padding: 5px; padding: 5px;
spacing: 5px; forward-focus: focus-item;
vertical-stretch: 0; HorizontalLayout {
for title [idx] in root.columns: HorizontalLayout { padding: 5px;
width: root.column-sizes[idx]; spacing: 5px;
Text { vertical-stretch: 0;
overflow: elide; for title [idx] in root.columns: HorizontalLayout {
text: title; width: root.column-sizes[idx];
} Text {
overflow: elide;
text: title;
}
Rectangle { Rectangle {
width: 1px; width: 1px;
background: gray; background: gray;
forward-focus: focus-item;
TouchArea {
width: 5px;
x: (parent.width - self.width) / 2;
property <length> cached;
forward-focus: focus-item; forward-focus: focus-item;
pointer-event(event) => { TouchArea {
if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) { width: 5px;
self.cached = root.column_sizes[idx]; x: (parent.width - self.width) / 2;
} property <length> cached;
} forward-focus: focus-item;
moved => { pointer-event(event) => {
if (self.pressed) { if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) {
root.column_sizes[idx] += (self.mouse-x - self.pressed-x); self.cached = root.column_sizes[idx];
if (root.column_sizes[idx] < 20px) {
root.column_sizes[idx] = 20px;
} }
} }
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 { list_view := ListView {
overflow: elide; min-width: 100px;
text: last-column;
}
}
list_view := ListView {
min-width: 100px;
forward-focus: focus-item;
for r [idx] in root.values: Rectangle {
border-radius: 5px;
forward-focus: focus-item; forward-focus: focus-item;
height: 20px; for r [idx] in root.values: Rectangle {
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)); border-radius: 5px;
touch_area := TouchArea {
forward-focus: focus-item; forward-focus: focus-item;
clicked => { height: 20px;
if (!r.header_row) { 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));
r.selected_row = !r.selected_row; touch_area := TouchArea {
if (root.selected-item == -1) { forward-focus: focus-item;
root.selected-item = idx; clicked => {
} else { if (!r.header_row) {
if (r.selected_row == true) { r.selected_row = !r.selected_row;
root.values[root.selected-item].selected_row = false; if (root.selected-item == -1) {
root.selected-item = idx; root.selected-item = idx;
} else { } 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) { pointer-event(event) => {
Callabler.load_image_preview(r.val[root.parentPathIdx - 1] + "/" + r.val[root.fileNameIdx - 1]); // TODO this should be clicked by double-click
} else { if (event.button == PointerEventButton.right && event.kind == PointerEventKind.up) {
GuiState.preview-visible = false; 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 { HorizontalLayout {
spacing: 5px; forward-focus: focus-item;
for f [idx] in r.val: Text { CheckBox {
width: root.column-sizes[idx + 1]; visible: !r.header-row;
text: f; checked: r.checked && !r.header-row;
font-size: 12px; width: root.column-sizes[0];
forward-focus: focus-item; forward-focus: focus-item;
vertical-alignment: center; toggled => {
overflow: elide; 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;
}
}
} }