improve performance

This commit is contained in:
Alexander Mohr 2025-04-22 22:21:19 +02:00
parent 6ebec39f5c
commit a8f2917c20
4 changed files with 109 additions and 39 deletions

View file

@ -428,7 +428,7 @@ impl Default for Config {
#[allow(clippy::unnecessary_wraps)] #[allow(clippy::unnecessary_wraps)]
#[must_use] #[must_use]
pub fn default_show_animation_time() -> Option<u64> { pub fn default_show_animation_time() -> Option<u64> {
Some(70) Some(30)
} }
// allowed because option is needed for serde macro // allowed because option is needed for serde macro

View file

@ -61,6 +61,14 @@ fn fetch_icon_from_theme(icon_name: &str) -> Result<String, DesktopError> {
} }
} }
pub fn known_image_extension_regex_pattern() -> Regex {
Regex::new(&format!(
r"(?i).*{}",
format!("\\.({})$", ["png", "jpg", "gif", "svg", "jpeg"].join("|"))
))
.expect("Internal image regex is not valid anymore.")
}
/// # Errors /// # Errors
/// ///
/// Will return `Err` /// Will return `Err`
@ -76,8 +84,7 @@ pub fn fetch_icon_from_common_dirs(icon_name: &str) -> Result<String, DesktopErr
paths.push(home.join(".local/share/icons")); paths.push(home.join(".local/share/icons"));
} }
let extensions = ["png", "jpg", "gif", "svg"].join("|"); // Create regex group for extensions let formatted_name = Regex::new(icon_name);
let formatted_name = Regex::new(&format!(r"(?i){icon_name}\.({extensions})$"));
if let Ok(formatted_name) = formatted_name { if let Ok(formatted_name) = formatted_name {
paths paths
.into_iter() .into_iter()

View file

@ -1,7 +1,10 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::{Duration, Instant};
use crate::config::{Anchor, Animation, Config, MatchMethod, WrapMode};
use crate::desktop::known_image_extension_regex_pattern;
use crate::{config, desktop};
use anyhow::anyhow; use anyhow::anyhow;
use crossbeam::channel; use crossbeam::channel;
use crossbeam::channel::Sender; use crossbeam::channel::Sender;
@ -11,8 +14,8 @@ use gdk4::prelude::{Cast, DisplayExt, MonitorExt};
use gdk4::{Display, Key}; use gdk4::{Display, Key};
use gtk4::glib::ControlFlow; use gtk4::glib::ControlFlow;
use gtk4::prelude::{ use gtk4::prelude::{
ApplicationExt, ApplicationExtManual, BoxExt, EditableExt, FlowBoxChildExt, GestureSingleExt, AppChooserExt, ApplicationExt, ApplicationExtManual, BoxExt, EditableExt, FlowBoxChildExt,
GtkWindowExt, ListBoxRowExt, NativeExt, WidgetExt, GestureSingleExt, GtkWindowExt, ListBoxRowExt, NativeExt, WidgetExt,
}; };
use gtk4::{ use gtk4::{
Align, EventControllerKey, Expander, FlowBox, FlowBoxChild, GestureClick, Image, Label, Align, EventControllerKey, Expander, FlowBox, FlowBoxChild, GestureClick, Image, Label,
@ -22,9 +25,7 @@ use gtk4::{
use gtk4::{Application, ApplicationWindow, CssProvider, Orientation}; use gtk4::{Application, ApplicationWindow, CssProvider, Orientation};
use gtk4_layer_shell::{Edge, KeyboardMode, LayerShell}; use gtk4_layer_shell::{Edge, KeyboardMode, LayerShell};
use log; use log;
use regex::Regex;
use crate::config;
use crate::config::{Anchor, Animation, Config, MatchMethod, WrapMode};
type ArcMenuMap<T> = Arc<Mutex<HashMap<FlowBoxChild, MenuItem<T>>>>; type ArcMenuMap<T> = Arc<Mutex<HashMap<FlowBoxChild, MenuItem<T>>>>;
type ArcProvider<T> = Arc<Mutex<dyn ItemProvider<T>>>; type ArcProvider<T> = Arc<Mutex<dyn ItemProvider<T>>>;
@ -95,6 +96,8 @@ impl<T: Clone> AsRef<MenuItem<T>> for MenuItem<T> {
} }
} }
type IconCache = Arc<HashMap<String, Image>>;
/// # Errors /// # Errors
/// ///
/// Will return Err when the channel between the UI and this is broken /// Will return Err when the channel between the UI and this is broken
@ -137,6 +140,8 @@ fn build_ui<T, P>(
T: Clone + 'static, T: Clone + 'static,
P: ItemProvider<T> + 'static, P: ItemProvider<T> + 'static,
{ {
let start = Instant::now();
let window = ApplicationWindow::builder() let window = ApplicationWindow::builder()
.application(app) .application(app)
.decorated(false) .decorated(false)
@ -206,8 +211,16 @@ fn build_ui<T, P>(
let item_provider = Arc::new(Mutex::new(item_provider)); let item_provider = Arc::new(Mutex::new(item_provider));
let list_items: ArcMenuMap<T> = Arc::new(Mutex::new(HashMap::new())); let list_items: ArcMenuMap<T> = Arc::new(Mutex::new(HashMap::new()));
// let icon_cache: IconCache = Default::default();
let elements = item_provider.lock().unwrap().get_elements(None);
// for e in elements {
// tokio::spawn(async move {
// lookup_icon(e, config);
// });
// }
build_ui_from_menu_items( build_ui_from_menu_items(
&item_provider.lock().unwrap().get_elements(None), &elements,
&list_items, &list_items,
&inner_box, &inner_box,
config, config,
@ -243,9 +256,19 @@ fn build_ui<T, P>(
item_provider, item_provider,
); );
window.set_child(Widget::NONE); window.set_child(Some(&outer_box));
let window_done = Instant::now();
window.show(); window.show();
animate_window_show(config, window.clone(), outer_box); animate_window_show(config, window.clone(), outer_box);
log::debug!(
"Building UI took {:?}, window creation {:?}, animation {:?}",
start.elapsed(),
window_done - start,
window_done.elapsed()
);
} }
fn build_ui_from_menu_items<T: Clone + 'static>( fn build_ui_from_menu_items<T: Clone + 'static>(
@ -257,14 +280,20 @@ fn build_ui_from_menu_items<T: Clone + 'static>(
app: &Application, app: &Application,
window: &ApplicationWindow, window: &ApplicationWindow,
) { ) {
let start = Instant::now();
{ {
let mut arc_lock = list_items.lock().unwrap(); let mut arc_lock = list_items.lock().unwrap();
let got_lock = Instant::now();
inner_box.unset_sort_func(); inner_box.unset_sort_func();
while let Some(b) = inner_box.child_at_index(0) { while let Some(b) = inner_box.child_at_index(0) {
inner_box.remove(&b); inner_box.remove(&b);
drop(b); drop(b);
} }
arc_lock.clear();
let cleared_box = Instant::now();
for entry in items { for entry in items {
arc_lock.insert( arc_lock.insert(
@ -272,6 +301,15 @@ fn build_ui_from_menu_items<T: Clone + 'static>(
(*entry).clone(), (*entry).clone(),
); );
} }
let created_ui = Instant::now();
log::debug!(
"Creating UI took {:?}, got locker after {:?}, cleared box after {:?}, created UI after {:?}",
start.elapsed(),
got_lock - start,
cleared_box - start,
created_ui - start
);
} }
let lic = ArcMenuMap::clone(list_items); let lic = ArcMenuMap::clone(list_items);
inner_box.set_sort_func(move |child2, child1| sort_menu_items_by_score(child1, child2, &lic)); inner_box.set_sort_func(move |child2, child1| sort_menu_items_by_score(child1, child2, &lic));
@ -466,7 +504,6 @@ fn animate_window_show(config: &Config, window: ApplicationWindow, outer_box: gt
target_height, target_height,
target_width, target_width,
move || { move || {
window.set_child(Some(&outer_box));
}, },
); );
} }
@ -518,7 +555,7 @@ fn animate_window<Func>(
{ {
let allocation = window.allocation(); let allocation = window.allocation();
let animation_step_length = Duration::from_millis(10); // ~60 FPS let animation_step_length = Duration::from_millis(10);
let animation_speed = Duration::from_millis(animation_time); let animation_speed = Duration::from_millis(animation_time);
let animation_steps = let animation_steps =
@ -780,6 +817,7 @@ fn create_menu_row<T: Clone + 'static>(
window: ApplicationWindow, window: ApplicationWindow,
inner_box: FlowBox, inner_box: FlowBox,
) -> Widget { ) -> Widget {
let start = Instant::now();
let row = ListBoxRow::new(); let row = ListBoxRow::new();
row.set_hexpand(true); row.set_hexpand(true);
row.set_halign(Align::Fill); row.set_halign(Align::Fill);
@ -817,23 +855,17 @@ fn create_menu_row<T: Clone + 'static>(
row_box.set_halign(Align::Fill); row_box.set_halign(Align::Fill);
row.set_child(Some(&row_box)); row.set_child(Some(&row_box));
let ui_created = Instant::now();
if let Some(image_path) = &menu_item.icon_path { if config.allow_images.is_some_and(|allow_images| allow_images) {
let image = if image_path.starts_with("/") { if let Some(image) = lookup_icon(&menu_item, config) {
Image::from_file(image_path) image.set_widget_name("img");
} else { row_box.append(&image);
Image::from_icon_name(image_path) }
};
image.set_pixel_size(
config
.image_size
.unwrap_or(config::default_image_size().unwrap()),
);
image.set_widget_name("img");
row_box.append(&image);
} }
let icon_found = Instant::now();
let label = Label::new(Some(menu_item.label.as_str())); let label = Label::new(Some(menu_item.label.as_str()));
let wrap_mode: NaturalWrapMode = if let Some(config_wrap) = &config.line_wrap { let wrap_mode: NaturalWrapMode = if let Some(config_wrap) = &config.line_wrap {
config_wrap.into() config_wrap.into()
@ -856,9 +888,43 @@ fn create_menu_row<T: Clone + 'static>(
{ {
label.set_xalign(0.0); label.set_xalign(0.0);
} }
log::debug!(
"Creating menu took {:?}, ui created after {:?}, icon found after {:?}",
start.elapsed(),
ui_created - start,
icon_found - start
);
row.upcast() row.upcast()
} }
fn lookup_icon<T: Clone>(menu_item: &MenuItem<T>, config: &Config) -> Option<Image> {
if let Some(image_path) = &menu_item.icon_path {
let img_regex = Regex::new(&format!(r"(?i).*{}", known_image_extension_regex_pattern()));
let image = if image_path.starts_with("/") {
Image::from_file(image_path)
} else if img_regex.unwrap().is_match(image_path) {
if let Ok(img) = desktop::fetch_icon_from_common_dirs(&image_path) {
Image::from_file(img)
} else {
Image::from_icon_name(image_path)
}
} else {
Image::from_icon_name(image_path)
};
image.set_pixel_size(
config
.image_size
.unwrap_or(config::default_image_size().unwrap()),
);
Some(image)
} else {
None
}
}
fn filter_widgets<T: Clone>( fn filter_widgets<T: Clone>(
query: &str, query: &str,
item_arc: &ArcMenuMap<T>, item_arc: &ArcMenuMap<T>,
@ -924,12 +990,6 @@ fn filter_widgets<T: Clone>(
} }
}; };
log::debug!(
"menu item {}, search score {}",
menu_item_search,
search_sort_score
);
menu_item.search_sort_score = search_sort_score; menu_item.search_sort_score = search_sort_score;
flowbox_child.set_visible(visible); flowbox_child.set_visible(visible);
} }

View file

@ -1,14 +1,14 @@
use anyhow::Context;
use freedesktop_file_parser::EntryType;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Read; use std::io::Read;
use std::os::unix::prelude::CommandExt; use std::os::unix::prelude::CommandExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::{env, fmt, fs, io};
use std::time::Instant; use std::time::Instant;
use anyhow::Context; use std::{env, fmt, fs, io};
use freedesktop_file_parser::EntryType;
use regex::Regex;
use serde::{Deserialize, Serialize};
use crate::config::{Config, expand_path}; use crate::config::{Config, expand_path};
use crate::desktop::{ use crate::desktop::{
@ -148,7 +148,10 @@ impl<T: Clone> DRunProvider<T> {
entries.push(entry); entries.push(entry);
} }
log::info!("parsing desktop files took {}ms", start.elapsed().as_millis()); log::info!(
"parsing desktop files took {}ms",
start.elapsed().as_millis()
);
gui::sort_menu_items_alphabetically_honor_initial_score(&mut entries); gui::sort_menu_items_alphabetically_honor_initial_score(&mut entries);