1
0
Fork 0
mirror of synced 2024-06-02 18:44:34 +12:00
This commit is contained in:
Rafał Mikrut 2023-11-12 13:54:59 +01:00
parent ff7cfff900
commit 186c0e1895
15 changed files with 148 additions and 28 deletions

1
Cargo.lock generated
View file

@ -3317,6 +3317,7 @@ dependencies = [
"handsome_logger", "handsome_logger",
"home", "home",
"humansize", "humansize",
"image",
"log", "log",
"open", "open",
"rand", "rand",

View file

@ -44,7 +44,7 @@ pub fn get_number_of_threads() -> usize {
fn filtering_messages(record: &Record) -> bool { fn filtering_messages(record: &Record) -> bool {
if let Some(module_path) = record.module_path() { if let Some(module_path) = record.module_path() {
module_path.starts_with("czkawka") module_path.starts_with("czkawka") || module_path.starts_with("krokiet")
} else { } else {
true true
} }

View file

@ -32,6 +32,7 @@ log = "0.4.20"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
humansize = "2.1.3" humansize = "2.1.3"
image = "0.24.7"
[build-dependencies] [build-dependencies]
slint-build = "1.3.0" slint-build = "1.3.0"

View file

@ -1,6 +1,6 @@
use crate::common::create_vec_model_from_vec_string; use crate::common::create_vec_model_from_vec_string;
use crate::settings::{collect_settings, SettingsCustom}; use crate::settings::{collect_settings, SettingsCustom};
use crate::Settings; use crate::GuiState;
use crate::{CurrentTab, MainListModel, MainWindow, ProgressToSend}; use crate::{CurrentTab, MainListModel, MainWindow, ProgressToSend};
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use crossbeam_channel::{Receiver, Sender}; 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.set_similar_images_model(items.into());
app.invoke_scan_ended(format!("Found {} similar images files", number_of_empty_files).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.set_empty_files_model(items.into());
app.invoke_scan_ended(format!("Found {} empty files", number_of_empty_files).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.set_empty_folder_model(items.into());
app.invoke_scan_ended(format!("Found {} empty folders", folder_map.len()).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());
}) })
}); });
} }

View 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;
}
}
}

View file

@ -18,6 +18,7 @@ mod connect_directories_changes;
mod connect_open; mod connect_open;
mod connect_progress_receiver; mod connect_progress_receiver;
mod connect_scan; mod connect_scan;
mod connect_show_preview;
mod connect_stop; mod connect_stop;
mod settings; 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_directories_changes::connect_add_remove_directories;
use crate::connect_progress_receiver::connect_progress_gathering; 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::connect_stop::connect_stop_button;
use crate::settings::reset_settings; use crate::settings::reset_settings;
use czkawka_core::common::setup_logger; use czkawka_core::common::setup_logger;
@ -53,14 +55,21 @@ fn main() {
connect_open_items(&app); connect_open_items(&app);
connect_progress_gathering(&app, progress_receiver); connect_progress_gathering(&app, progress_receiver);
connect_add_remove_directories(&app); connect_add_remove_directories(&app);
connect_show_preview(&app);
reset_settings(&app); reset_settings(&app);
app.run().unwrap(); app.run().unwrap();
} }
// TODO remove this after trying // TODO remove this after debugging - or leave commented
pub fn to_remove_debug(app: &MainWindow) { 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()); let header_row_data: Rc<VecModel<MainListModel>> = Rc::new(VecModel::default());
for r in 0..100_000 { for r in 0..100_000 {
let items = VecModel::default(); let items = VecModel::default();
@ -81,12 +90,15 @@ pub fn to_remove_debug(app: &MainWindow) {
header_row_data.push(item); 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()); let non_header_row_data: Rc<VecModel<MainListModel>> = Rc::new(VecModel::default());
for r in 0..100_000 { for r in 0..100_000 {
let items = VecModel::default(); let items = VecModel::default();
for c in 0..3 { 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; let is_checked = r % 2 == 0;
@ -100,8 +112,5 @@ pub fn to_remove_debug(app: &MainWindow) {
non_header_row_data.push(item); non_header_row_data.push(item);
} }
non_header_row_data
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());
} }

View file

@ -3,6 +3,7 @@ use std::env;
use std::path::PathBuf; use std::path::PathBuf;
use crate::common::create_string_standard_list_view_from_pathbuf; use crate::common::create_string_standard_list_view_from_pathbuf;
use crate::GuiState;
use crate::Settings; use crate::Settings;
use home::home_dir; use home::home_dir;
use slint::{ComponentHandle, Model}; 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); settings.set_excluded_directories(excluded_items);
// Clear text // Clear text
app.global::<Settings>().set_info_text("".into()); app.global::<GuiState>().set_info_text("".into());
} }
impl Default for SettingsCustom { impl Default for SettingsCustom {

View file

@ -3,6 +3,7 @@ import {Button, StandardListView, VerticalBox, ScrollView, TextEdit} from "std-w
import {Settings} from "settings.slint"; import {Settings} from "settings.slint";
import {BottomPanelVisibility} from "common.slint"; import {BottomPanelVisibility} from "common.slint";
import {Callabler} from "callabler.slint"; import {Callabler} from "callabler.slint";
import {GuiState} from "gui_state.slint";
component DirectoriesPanel inherits HorizontalLayout { component DirectoriesPanel inherits HorizontalLayout {
callback folder_choose_requested(bool); callback folder_choose_requested(bool);
@ -98,7 +99,7 @@ component TextErrorsPanel inherits TextEdit {
height: 20px; height: 20px;
read-only: true; read-only: true;
wrap: TextWrap.no-wrap; wrap: TextWrap.no-wrap;
text <=> Settings.info_text; text <=> GuiState.info_text;
} }
export component BottomPanel { export component BottomPanel {

View file

@ -7,4 +7,7 @@ export global Callabler {
callback item_opened(string); callback item_opened(string);
callback delete_selected_items(); callback delete_selected_items();
// Preview
callback load_image_preview(string);
} }

View 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;
}

View file

@ -9,19 +9,23 @@ 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;
// 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; min-width: 200px;
height: parent.height;
columns: ["Selection", "Folder Name", "Path"]; columns: ["Selection", "Folder Name", "Path"];
last-column: "Modification Date"; last-column: "Modification Date";
column-sizes: [35px, 100px, 350px, 300px]; column-sizes: [35px, 100px, 350px, 100px];
values <=> empty-folder-model; values <=> empty-folder-model;
parentPathIdx: 2; parentPathIdx: 2;
fileNameIdx: 1; fileNameIdx: 1;
} }
if root.active-tab == CurrentTab.EmptyFiles: SelectableTableView { SelectableTableView {
visible: root.active-tab == CurrentTab.EmptyFiles;
min-width: 200px; min-width: 200px;
height: parent.height;
columns: ["Selection", "File Name", "Path"]; columns: ["Selection", "File Name", "Path"];
last-column: "Modification Date"; last-column: "Modification Date";
column-sizes: [35px, 100px, 350px, 100px]; column-sizes: [35px, 100px, 350px, 100px];
@ -30,8 +34,10 @@ export component MainList {
fileNameIdx: 1; fileNameIdx: 1;
} }
if root.active-tab == CurrentTab.SimilarImages: SelectableTableView { SelectableTableView {
visible: root.active-tab == CurrentTab.SimilarImages;
min-width: 200px; min-width: 200px;
height: parent.height;
columns: ["Selection", "Similarity", "Size", "Dimensions", "File Name", "Path"]; columns: ["Selection", "Similarity", "Size", "Dimensions", "File Name", "Path"];
last-column: "Modification Date"; last-column: "Modification Date";
column-sizes: [35px, 80px, 80px, 80px, 350px, 100px, 100px]; column-sizes: [35px, 80px, 80px, 80px, 350px, 100px, 100px];

View file

@ -10,8 +10,10 @@ import {Settings} from "settings.slint";
import {Callabler} from "callabler.slint"; import {Callabler} from "callabler.slint";
import { BottomPanel } from "bottom_panel.slint"; import { BottomPanel } from "bottom_panel.slint";
import {ColorPalette} from "color_palette.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 { export component MainWindow inherits Window {
callback scan_stopping; 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: 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"]} {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: []; in-out property <[MainListModel]> similar_images_model: [];
VerticalBox { VerticalBox {
HorizontalBox { HorizontalBox {
@ -50,12 +57,24 @@ export component MainWindow inherits Window {
VerticalLayout { VerticalLayout {
horizontal-stretch: 1.0; horizontal-stretch: 1.0;
MainList { Rectangle {
vertical-stretch: 1.0; vertical-stretch: 1.0;
active-tab <=> root.active-tab; MainList {
empty_folder_model <=> root.empty_folder_model; width: GuiState.preview_visible ? parent.width / 2 : parent.width;
empty_files_model <=> root.empty_files_model; height: parent.height;
similar_images_model <=> root.similar_images_model; 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 { if root.scanning: Progress {

3
krokiet/ui/preview.slint Normal file
View file

@ -0,0 +1,3 @@
export component Preview inherits Image {
}

View file

@ -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 {TypeOfOpenedItem} from "common.slint";
import {ColorPalette} from "color_palette.slint"; import {ColorPalette} from "color_palette.slint";
import {MainListModel} from "common.slint"; import {MainListModel} from "common.slint";
import {Callabler} from "callabler.slint"; import {Callabler} from "callabler.slint";
import {GuiState} from "gui_state.slint";
export component SelectableTableView inherits Rectangle { export component SelectableTableView inherits Rectangle {
callback item_opened(string); callback item_opened(string);
@ -10,7 +11,6 @@ export component SelectableTableView inherits Rectangle {
in property <string> last_column; 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 <[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; 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 // 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; in-out property <int> parentPathIdx;
@ -95,6 +95,12 @@ export component SelectableTableView inherits Rectangle {
root.selected-item = -1; 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) => { pointer-event(event) => {

View file

@ -1,5 +1,4 @@
export global Settings { export global Settings {
in-out property <[StandardListViewItem]> included_directories: [{text: "ABCD"}, {text: "BCDA"}]; 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 <[StandardListViewItem]> excluded_directories: [{text: "ABCD"}, {text: "BCDA"}, {text: "CDFFF"}];
in-out property <string> info_text: "Nothing to report";
} }