Improved ListView
This commit is contained in:
parent
186c0e1895
commit
c919aa11fa
6 changed files with 171 additions and 112 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
match image::open(image_name) {
|
||||||
|
Ok(img) => img,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error while loading image: {}", e);
|
error!("Error while loading image: {}", e);
|
||||||
return None;
|
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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,6 +24,7 @@ export component SelectableTableView inherits Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
VerticalBox {
|
VerticalBox {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
forward-focus: focus-item;
|
forward-focus: focus-item;
|
||||||
|
@ -65,11 +65,6 @@ export component SelectableTableView inherits Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
|
||||||
overflow: elide;
|
|
||||||
text: last-column;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
list_view := ListView {
|
list_view := ListView {
|
||||||
|
@ -140,4 +135,12 @@ export component SelectableTableView inherits Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deselect_selected_item() {
|
||||||
|
if (root.selected_item != -1) {
|
||||||
|
root.values[root.selected-item].selected_row = false;
|
||||||
|
root.selected-item = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue