improve performance
This commit is contained in:
parent
6ebec39f5c
commit
a8f2917c20
4 changed files with 109 additions and 39 deletions
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
120
src/lib/gui.rs
120
src/lib/gui.rs
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue