Preview
This commit is contained in:
parent
ff7cfff900
commit
186c0e1895
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3317,6 +3317,7 @@ dependencies = [
|
|||
"handsome_logger",
|
||||
"home",
|
||||
"humansize",
|
||||
"image",
|
||||
"log",
|
||||
"open",
|
||||
"rand",
|
||||
|
|
|
@ -44,7 +44,7 @@ pub fn get_number_of_threads() -> usize {
|
|||
|
||||
fn filtering_messages(record: &Record) -> bool {
|
||||
if let Some(module_path) = record.module_path() {
|
||||
module_path.starts_with("czkawka")
|
||||
module_path.starts_with("czkawka") || module_path.starts_with("krokiet")
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ log = "0.4.20"
|
|||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
humansize = "2.1.3"
|
||||
image = "0.24.7"
|
||||
|
||||
[build-dependencies]
|
||||
slint-build = "1.3.0"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::common::create_vec_model_from_vec_string;
|
||||
use crate::settings::{collect_settings, SettingsCustom};
|
||||
use crate::Settings;
|
||||
use crate::GuiState;
|
||||
use crate::{CurrentTab, MainListModel, MainWindow, ProgressToSend};
|
||||
use chrono::NaiveDateTime;
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
|
@ -98,7 +98,7 @@ fn scan_similar_images(a: Weak<MainWindow>, progress_sender: Sender<ProgressData
|
|||
}
|
||||
app.set_similar_images_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {} similar images files", number_of_empty_files).into());
|
||||
app.global::<Settings>().set_info_text(messages.into());
|
||||
app.global::<GuiState>().set_info_text(messages.into());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ fn scan_empty_files(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>,
|
|||
}
|
||||
app.set_empty_files_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {} empty files", number_of_empty_files).into());
|
||||
app.global::<Settings>().set_info_text(messages.into());
|
||||
app.global::<GuiState>().set_info_text(messages.into());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
@ -180,7 +180,7 @@ fn scan_empty_folders(a: Weak<MainWindow>, progress_sender: Sender<ProgressData>
|
|||
}
|
||||
app.set_empty_folder_model(items.into());
|
||||
app.invoke_scan_ended(format!("Found {} empty folders", folder_map.len()).into());
|
||||
app.global::<Settings>().set_info_text(messages.into());
|
||||
app.global::<GuiState>().set_info_text(messages.into());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
|
66
krokiet/src/connect_show_preview.rs
Normal file
66
krokiet/src/connect_show_preview.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
use crate::{Callabler, GuiState, MainWindow};
|
||||
use czkawka_core::common::IMAGE_RS_EXTENSIONS;
|
||||
use image::DynamicImage;
|
||||
use log::{debug, error};
|
||||
use slint::ComponentHandle;
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub type ImageBufferRgba = image::ImageBuffer<image::Rgba<u8>, Vec<u8>>;
|
||||
|
||||
pub fn connect_show_preview(app: &MainWindow) {
|
||||
let a = app.as_weak();
|
||||
app.global::<Callabler>().on_load_image_preview(move |image_path| {
|
||||
let app = a.upgrade().unwrap();
|
||||
|
||||
let path = Path::new(image_path.as_str());
|
||||
|
||||
let res = load_image(path);
|
||||
if let Some((load_time, img)) = res {
|
||||
let start_timer_convert_time = Instant::now();
|
||||
let slint_image = convert_into_slint_image(img);
|
||||
let convert_time = start_timer_convert_time.elapsed();
|
||||
|
||||
let start_set_time = Instant::now();
|
||||
app.global::<GuiState>().set_preview_image(slint_image);
|
||||
let set_time = start_set_time.elapsed();
|
||||
|
||||
debug!(
|
||||
"Loading image took: {:?}, converting image took: {:?}, setting image took: {:?}",
|
||||
load_time, convert_time, set_time
|
||||
);
|
||||
app.global::<GuiState>().set_preview_visible(true);
|
||||
} else {
|
||||
app.global::<GuiState>().set_preview_visible(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn convert_into_slint_image(img: DynamicImage) -> slint::Image {
|
||||
let image_buffer: ImageBufferRgba = img.to_rgba8();
|
||||
let buffer = slint::SharedPixelBuffer::<slint::Rgba8Pixel>::clone_from_slice(image_buffer.as_raw(), image_buffer.width(), image_buffer.height());
|
||||
slint::Image::from_rgba8(buffer)
|
||||
}
|
||||
|
||||
fn load_image(image_path: &Path) -> Option<(Duration, image::DynamicImage)> {
|
||||
if !image_path.is_file() {
|
||||
return None;
|
||||
}
|
||||
let image_name = image_path.to_string_lossy().to_string();
|
||||
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()) {
|
||||
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);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ mod connect_directories_changes;
|
|||
mod connect_open;
|
||||
mod connect_progress_receiver;
|
||||
mod connect_scan;
|
||||
mod connect_show_preview;
|
||||
mod connect_stop;
|
||||
mod settings;
|
||||
|
||||
|
@ -30,6 +31,7 @@ use crate::connect_scan::connect_scan_button;
|
|||
|
||||
use crate::connect_directories_changes::connect_add_remove_directories;
|
||||
use crate::connect_progress_receiver::connect_progress_gathering;
|
||||
use crate::connect_show_preview::connect_show_preview;
|
||||
use crate::connect_stop::connect_stop_button;
|
||||
use crate::settings::reset_settings;
|
||||
use czkawka_core::common::setup_logger;
|
||||
|
@ -53,14 +55,21 @@ fn main() {
|
|||
connect_open_items(&app);
|
||||
connect_progress_gathering(&app, progress_receiver);
|
||||
connect_add_remove_directories(&app);
|
||||
connect_show_preview(&app);
|
||||
|
||||
reset_settings(&app);
|
||||
|
||||
app.run().unwrap();
|
||||
}
|
||||
|
||||
// TODO remove this after trying
|
||||
// TODO remove this after debugging - or leave commented
|
||||
pub fn to_remove_debug(app: &MainWindow) {
|
||||
app.set_empty_folder_model(to_remove_create_without_header("@@").into());
|
||||
app.set_empty_files_model(to_remove_create_without_header("%%").into());
|
||||
app.set_similar_images_model(to_remove_create_with_header().into());
|
||||
}
|
||||
|
||||
fn to_remove_create_with_header() -> Rc<VecModel<MainListModel>> {
|
||||
let header_row_data: Rc<VecModel<MainListModel>> = Rc::new(VecModel::default());
|
||||
for r in 0..100_000 {
|
||||
let items = VecModel::default();
|
||||
|
@ -81,12 +90,15 @@ pub fn to_remove_debug(app: &MainWindow) {
|
|||
|
||||
header_row_data.push(item);
|
||||
}
|
||||
header_row_data
|
||||
}
|
||||
fn to_remove_create_without_header(s: &str) -> Rc<VecModel<MainListModel>> {
|
||||
let non_header_row_data: Rc<VecModel<MainListModel>> = Rc::new(VecModel::default());
|
||||
for r in 0..100_000 {
|
||||
let items = VecModel::default();
|
||||
|
||||
for c in 0..3 {
|
||||
items.push(slint::format!("Item {r}.{c}"));
|
||||
items.push(slint::format!("Item {r}.{c}.{s}"));
|
||||
}
|
||||
|
||||
let is_checked = r % 2 == 0;
|
||||
|
@ -100,8 +112,5 @@ pub fn to_remove_debug(app: &MainWindow) {
|
|||
|
||||
non_header_row_data.push(item);
|
||||
}
|
||||
|
||||
app.set_empty_folder_model(non_header_row_data.clone().into());
|
||||
app.set_empty_files_model(non_header_row_data.into());
|
||||
app.set_similar_images_model(header_row_data.into());
|
||||
non_header_row_data
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::env;
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::common::create_string_standard_list_view_from_pathbuf;
|
||||
use crate::GuiState;
|
||||
use crate::Settings;
|
||||
use home::home_dir;
|
||||
use slint::{ComponentHandle, Model};
|
||||
|
@ -33,7 +34,7 @@ pub fn set_settings_to_gui(app: &MainWindow, custom_settings: &SettingsCustom) {
|
|||
settings.set_excluded_directories(excluded_items);
|
||||
|
||||
// Clear text
|
||||
app.global::<Settings>().set_info_text("".into());
|
||||
app.global::<GuiState>().set_info_text("".into());
|
||||
}
|
||||
|
||||
impl Default for SettingsCustom {
|
||||
|
|
|
@ -3,6 +3,7 @@ import {Button, StandardListView, VerticalBox, ScrollView, TextEdit} from "std-w
|
|||
import {Settings} from "settings.slint";
|
||||
import {BottomPanelVisibility} from "common.slint";
|
||||
import {Callabler} from "callabler.slint";
|
||||
import {GuiState} from "gui_state.slint";
|
||||
|
||||
component DirectoriesPanel inherits HorizontalLayout {
|
||||
callback folder_choose_requested(bool);
|
||||
|
@ -98,7 +99,7 @@ component TextErrorsPanel inherits TextEdit {
|
|||
height: 20px;
|
||||
read-only: true;
|
||||
wrap: TextWrap.no-wrap;
|
||||
text <=> Settings.info_text;
|
||||
text <=> GuiState.info_text;
|
||||
}
|
||||
|
||||
export component BottomPanel {
|
||||
|
|
|
@ -7,4 +7,7 @@ export global Callabler {
|
|||
callback item_opened(string);
|
||||
|
||||
callback delete_selected_items();
|
||||
|
||||
// Preview
|
||||
callback load_image_preview(string);
|
||||
}
|
||||
|
|
5
krokiet/ui/gui_state.slint
Normal file
5
krokiet/ui/gui_state.slint
Normal file
|
@ -0,0 +1,5 @@
|
|||
export global GuiState {
|
||||
in-out property <string> info_text: "Nothing to report";
|
||||
in-out property <bool> preview_visible;
|
||||
in-out property <image> preview_image;
|
||||
}
|
|
@ -9,19 +9,23 @@ export component MainList {
|
|||
in-out property <[MainListModel]> empty_folder_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 {
|
||||
|
||||
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, 300px];
|
||||
column-sizes: [35px, 100px, 350px, 100px];
|
||||
values <=> empty-folder-model;
|
||||
parentPathIdx: 2;
|
||||
fileNameIdx: 1;
|
||||
}
|
||||
|
||||
if root.active-tab == CurrentTab.EmptyFiles: SelectableTableView {
|
||||
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];
|
||||
|
@ -30,8 +34,10 @@ export component MainList {
|
|||
fileNameIdx: 1;
|
||||
}
|
||||
|
||||
if root.active-tab == CurrentTab.SimilarImages: SelectableTableView {
|
||||
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];
|
||||
|
|
|
@ -10,8 +10,10 @@ import {Settings} from "settings.slint";
|
|||
import {Callabler} from "callabler.slint";
|
||||
import { BottomPanel } from "bottom_panel.slint";
|
||||
import {ColorPalette} from "color_palette.slint";
|
||||
import {GuiState} from "gui_state.slint";
|
||||
import { Preview } from "preview.slint";
|
||||
|
||||
export {Settings, Callabler}
|
||||
export {Settings, Callabler, GuiState}
|
||||
|
||||
export component MainWindow inherits Window {
|
||||
callback scan_stopping;
|
||||
|
@ -36,7 +38,12 @@ 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 <[MainListModel]> empty_files_model: [];
|
||||
in-out property <[MainListModel]> empty_files_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"]} ,
|
||||
{checked: true, selected_row: false, header_row: false, val: ["lokkaler", "/Xd1/Vide2", "01.23.1911"]}
|
||||
];
|
||||
in-out property <[MainListModel]> similar_images_model: [];
|
||||
VerticalBox {
|
||||
HorizontalBox {
|
||||
|
@ -50,12 +57,24 @@ export component MainWindow inherits Window {
|
|||
|
||||
VerticalLayout {
|
||||
horizontal-stretch: 1.0;
|
||||
MainList {
|
||||
Rectangle {
|
||||
vertical-stretch: 1.0;
|
||||
active-tab <=> root.active-tab;
|
||||
empty_folder_model <=> root.empty_folder_model;
|
||||
empty_files_model <=> root.empty_files_model;
|
||||
similar_images_model <=> root.similar_images_model;
|
||||
MainList {
|
||||
width: GuiState.preview_visible ? parent.width / 2 : parent.width;
|
||||
height: parent.height;
|
||||
horizontal-stretch: 0.5;
|
||||
active-tab <=> root.active-tab;
|
||||
empty_folder_model <=> root.empty_folder_model;
|
||||
empty_files_model <=> root.empty_files_model;
|
||||
similar_images_model <=> root.similar_images_model;
|
||||
}
|
||||
Preview {
|
||||
height: parent.height;
|
||||
x: parent.width / 2;
|
||||
width: GuiState.preview_visible ? parent.width / 2 : 0;
|
||||
visible: GuiState.preview_visible;
|
||||
source: GuiState.preview_image;
|
||||
}
|
||||
}
|
||||
|
||||
if root.scanning: Progress {
|
||||
|
|
3
krokiet/ui/preview.slint
Normal file
3
krokiet/ui/preview.slint
Normal file
|
@ -0,0 +1,3 @@
|
|||
export component Preview inherits Image {
|
||||
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
import { Button, VerticalBox , HorizontalBox, TabWidget, ListView, StandardListView, StandardTableView, CheckBox} from "std-widgets.slint";
|
||||
import { Button, VerticalBox , HorizontalBox, TabWidget, ListView, StandardListView, StandardTableView, CheckBox, ScrollView} from "std-widgets.slint";
|
||||
import {TypeOfOpenedItem} from "common.slint";
|
||||
import {ColorPalette} from "color_palette.slint";
|
||||
import {MainListModel} from "common.slint";
|
||||
import {Callabler} from "callabler.slint";
|
||||
import {GuiState} from "gui_state.slint";
|
||||
|
||||
export component SelectableTableView inherits Rectangle {
|
||||
callback item_opened(string);
|
||||
|
@ -10,7 +11,6 @@ export component SelectableTableView inherits Rectangle {
|
|||
in property <string> last_column;
|
||||
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];
|
||||
private property <int> column_number: column-sizes.length + 1;
|
||||
// This idx, starts from zero, but since first is always a checkbox, and is not in model.val values, remove 1 from idx
|
||||
in-out property <int> parentPathIdx;
|
||||
|
@ -95,6 +95,12 @@ export component SelectableTableView inherits Rectangle {
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
pointer-event(event) => {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
export global Settings {
|
||||
in-out property <[StandardListViewItem]> included_directories: [{text: "ABCD"}, {text: "BCDA"}];
|
||||
in-out property <[StandardListViewItem]> excluded_directories: [{text: "ABCD"}, {text: "BCDA"}, {text: "CDFFF"}];
|
||||
in-out property <string> info_text: "Nothing to report";
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue