1
0
Fork 0
mirror of synced 2024-05-19 20:02:24 +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",
"home",
"humansize",
"image",
"log",
"open",
"rand",

View file

@ -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
}

View file

@ -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"

View file

@ -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());
})
});
}

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_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
}

View file

@ -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 {

View file

@ -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 {

View file

@ -7,4 +7,7 @@ export global Callabler {
callback item_opened(string);
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_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];

View file

@ -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
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 {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) => {

View file

@ -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";
}