Fix 5 deferred performance/UX issues from audit

M8: Pre-compile regex once before rename preview loop instead of
recompiling per file. Adds apply_simple_compiled() to RenameConfig.

M9: Cache font data in watermark module using OnceLock (default font)
and Mutex<HashMap> (named fonts) to avoid repeated filesystem walks
during preview updates.

M12: Add 150ms debounce to watermark opacity, rotation, margin, and
scale sliders to avoid spawning preview threads on every pixel of
slider movement.

M13: Add 150ms debounce to compress per-format quality sliders (JPEG,
PNG, WebP, AVIF) for the same reason.

M14: Move thumbnail loading to background threads instead of blocking
the GTK main loop. Each thumbnail is decoded via image crate in a
spawned thread and delivered to the main thread via channel polling.
This commit is contained in:
2026-03-07 23:11:00 +02:00
parent 9fcbe237bd
commit 1a174d40a7
6 changed files with 249 additions and 65 deletions

View File

@@ -318,6 +318,11 @@ pub struct RenameConfig {
fn default_counter_position() -> u32 { 3 }
impl RenameConfig {
/// Pre-compile the regex for batch use. Call once before a loop of apply_simple_compiled.
pub fn compile_regex(&self) -> Option<regex::Regex> {
rename::compile_rename_regex(&self.regex_find)
}
pub fn apply_simple(&self, original_name: &str, extension: &str, index: u32) -> String {
// 1. Apply regex find-and-replace on the original name
let working_name = rename::apply_regex_replace(original_name, &self.regex_find, &self.regex_replace);
@@ -378,4 +383,68 @@ impl RenameConfig {
format!("{}.{}", result, extension)
}
/// Like apply_simple but uses a pre-compiled regex (avoids recompiling per file).
pub fn apply_simple_compiled(&self, original_name: &str, extension: &str, index: u32, compiled_re: Option<&regex::Regex>) -> String {
// 1. Apply regex find-and-replace on the original name
let working_name = match compiled_re {
Some(re) => rename::apply_regex_replace_compiled(original_name, re, &self.regex_replace),
None => original_name.to_string(),
};
// 2. Apply space replacement
let working_name = rename::apply_space_replacement(&working_name, self.replace_spaces);
// 3. Apply special character filtering
let working_name = rename::apply_special_chars(&working_name, self.special_chars);
// 4. Build counter string
let counter_str = if self.counter_enabled {
let counter = self.counter_start.saturating_add(index.saturating_sub(1));
let padding = (self.counter_padding as usize).min(10);
format!("{:0>width$}", counter, width = padding)
} else {
String::new()
};
let has_counter = self.counter_enabled && !counter_str.is_empty();
// 5. Assemble parts based on counter position
let mut result = String::new();
if has_counter && self.counter_position == 0 {
result.push_str(&counter_str);
result.push('_');
}
result.push_str(&self.prefix);
if has_counter && self.counter_position == 1 {
result.push_str(&counter_str);
result.push('_');
}
if has_counter && self.counter_position == 4 {
result.push_str(&counter_str);
} else {
result.push_str(&working_name);
}
if has_counter && self.counter_position == 2 {
result.push('_');
result.push_str(&counter_str);
}
result.push_str(&self.suffix);
if has_counter && self.counter_position == 3 {
result.push('_');
result.push_str(&counter_str);
}
// 6. Apply case conversion
let result = rename::apply_case_conversion(&result, self.case_mode);
format!("{}.{}", result, extension)
}
}