perf: optimize startup by caching icons, texture, and async avatar
- Replace manual icon theme lookup + Pixbuf scaling with native GTK4 Image::from_icon_name() (uses internal cache + GPU rendering) - Decode wallpaper texture once and share across all windows instead of N+1 separate JPEG decodes - Load file-based avatars asynchronously via gio::spawn_blocking to avoid blocking the UI thread
This commit is contained in:
parent
d6979c1792
commit
b22172c3a0
@ -51,12 +51,13 @@ fn activate(app: >k::Application) {
|
|||||||
|
|
||||||
load_css(&display);
|
load_css(&display);
|
||||||
|
|
||||||
// Resolve wallpaper once, share across all windows
|
// Resolve wallpaper once, decode texture once, share across all windows
|
||||||
let config = config::load_config(None);
|
let config = config::load_config(None);
|
||||||
let bg_path = config::resolve_background_path(&config);
|
let bg_path = config::resolve_background_path(&config);
|
||||||
|
let texture = panel::load_background_texture(&bg_path);
|
||||||
|
|
||||||
// Panel on focused output (no set_monitor → compositor picks focused)
|
// Panel on focused output (no set_monitor → compositor picks focused)
|
||||||
let panel = panel::create_panel_window(&bg_path, app);
|
let panel = panel::create_panel_window(&texture, app);
|
||||||
setup_layer_shell(&panel, true, gtk4_layer_shell::Layer::Overlay);
|
setup_layer_shell(&panel, true, gtk4_layer_shell::Layer::Overlay);
|
||||||
panel.present();
|
panel.present();
|
||||||
|
|
||||||
@ -64,7 +65,7 @@ fn activate(app: >k::Application) {
|
|||||||
let monitors = display.monitors();
|
let monitors = display.monitors();
|
||||||
for i in 0..monitors.n_items() {
|
for i in 0..monitors.n_items() {
|
||||||
if let Some(monitor) = monitors.item(i).and_then(|obj| obj.downcast::<gdk::Monitor>().ok()) {
|
if let Some(monitor) = monitors.item(i).and_then(|obj| obj.downcast::<gdk::Monitor>().ok()) {
|
||||||
let wallpaper = panel::create_wallpaper_window(&bg_path, app);
|
let wallpaper = panel::create_wallpaper_window(&texture, app);
|
||||||
setup_layer_shell(&wallpaper, false, gtk4_layer_shell::Layer::Top);
|
setup_layer_shell(&wallpaper, false, gtk4_layer_shell::Layer::Top);
|
||||||
wallpaper.set_monitor(Some(&monitor));
|
wallpaper.set_monitor(Some(&monitor));
|
||||||
wallpaper.present();
|
wallpaper.present();
|
||||||
|
|||||||
108
src/panel.rs
108
src/panel.rs
@ -79,14 +79,26 @@ pub fn action_definitions() -> Vec<ActionDef> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load the wallpaper as a texture once, for sharing across all windows.
|
||||||
|
pub fn load_background_texture(bg_path: &Path) -> gdk::Texture {
|
||||||
|
if bg_path.starts_with("/dev/moonarch/moonset") {
|
||||||
|
gdk::Texture::from_resource(bg_path.to_str().unwrap_or(""))
|
||||||
|
} else {
|
||||||
|
let file = gio::File::for_path(bg_path);
|
||||||
|
gdk::Texture::from_file(&file).unwrap_or_else(|_| {
|
||||||
|
gdk::Texture::from_resource("/dev/moonarch/moonset/wallpaper.jpg")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a wallpaper-only window for secondary monitors.
|
/// Create a wallpaper-only window for secondary monitors.
|
||||||
pub fn create_wallpaper_window(bg_path: &Path, app: >k::Application) -> gtk::ApplicationWindow {
|
pub fn create_wallpaper_window(texture: &gdk::Texture, app: >k::Application) -> gtk::ApplicationWindow {
|
||||||
let window = gtk::ApplicationWindow::builder()
|
let window = gtk::ApplicationWindow::builder()
|
||||||
.application(app)
|
.application(app)
|
||||||
.build();
|
.build();
|
||||||
window.add_css_class("wallpaper");
|
window.add_css_class("wallpaper");
|
||||||
|
|
||||||
let background = create_background_picture(bg_path);
|
let background = create_background_picture(texture);
|
||||||
window.set_child(Some(&background));
|
window.set_child(Some(&background));
|
||||||
|
|
||||||
// Fade-in on map
|
// Fade-in on map
|
||||||
@ -104,7 +116,7 @@ pub fn create_wallpaper_window(bg_path: &Path, app: >k::Application) -> gtk::A
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create the main panel window with action buttons and confirm flow.
|
/// Create the main panel window with action buttons and confirm flow.
|
||||||
pub fn create_panel_window(bg_path: &Path, app: >k::Application) -> gtk::ApplicationWindow {
|
pub fn create_panel_window(texture: &gdk::Texture, app: >k::Application) -> gtk::ApplicationWindow {
|
||||||
let window = gtk::ApplicationWindow::builder()
|
let window = gtk::ApplicationWindow::builder()
|
||||||
.application(app)
|
.application(app)
|
||||||
.build();
|
.build();
|
||||||
@ -126,7 +138,7 @@ pub fn create_panel_window(bg_path: &Path, app: >k::Application) -> gtk::Appli
|
|||||||
window.set_child(Some(&overlay));
|
window.set_child(Some(&overlay));
|
||||||
|
|
||||||
// Background wallpaper
|
// Background wallpaper
|
||||||
let background = create_background_picture(bg_path);
|
let background = create_background_picture(texture);
|
||||||
overlay.set_child(Some(&background));
|
overlay.set_child(Some(&background));
|
||||||
|
|
||||||
// Click on background dismisses the menu
|
// Click on background dismisses the menu
|
||||||
@ -157,13 +169,8 @@ pub fn create_panel_window(bg_path: &Path, app: >k::Application) -> gtk::Appli
|
|||||||
avatar_frame.append(&avatar_image);
|
avatar_frame.append(&avatar_image);
|
||||||
content_box.append(&avatar_frame);
|
content_box.append(&avatar_frame);
|
||||||
|
|
||||||
// Load avatar
|
// Load avatar (file-based avatars load asynchronously)
|
||||||
let avatar_path = users::get_avatar_path(&user.home, Some(&user.username));
|
load_avatar_async(&avatar_image, &window, &user);
|
||||||
if let Some(path) = avatar_path {
|
|
||||||
set_avatar_from_file(&avatar_image, &path);
|
|
||||||
} else {
|
|
||||||
set_default_avatar(&avatar_image, &window);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Username label
|
// Username label
|
||||||
let username_label = gtk::Label::new(Some(&user.display_name));
|
let username_label = gtk::Label::new(Some(&user.display_name));
|
||||||
@ -237,13 +244,9 @@ pub fn create_panel_window(bg_path: &Path, app: >k::Application) -> gtk::Appli
|
|||||||
window
|
window
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a Picture widget for the wallpaper background.
|
/// Create a Picture widget for the wallpaper background from a shared texture.
|
||||||
fn create_background_picture(bg_path: &Path) -> gtk::Picture {
|
fn create_background_picture(texture: &gdk::Texture) -> gtk::Picture {
|
||||||
let background = if bg_path.starts_with("/dev/moonarch/moonset") {
|
let background = gtk::Picture::for_paintable(texture);
|
||||||
gtk::Picture::for_resource(bg_path.to_str().unwrap_or(""))
|
|
||||||
} else {
|
|
||||||
gtk::Picture::for_filename(bg_path.to_str().unwrap_or(""))
|
|
||||||
};
|
|
||||||
background.set_content_fit(gtk::ContentFit::Cover);
|
background.set_content_fit(gtk::ContentFit::Cover);
|
||||||
background.set_hexpand(true);
|
background.set_hexpand(true);
|
||||||
background.set_vexpand(true);
|
background.set_vexpand(true);
|
||||||
@ -302,34 +305,9 @@ fn create_action_button(
|
|||||||
button
|
button
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load a symbolic icon at 22px and scale to 64px via GdkPixbuf.
|
/// Load a symbolic icon using native GTK4 rendering at the target size.
|
||||||
fn load_scaled_icon(icon_name: &str) -> gtk::Image {
|
fn load_scaled_icon(icon_name: &str) -> gtk::Image {
|
||||||
let display = gdk::Display::default().unwrap();
|
let icon = gtk::Image::from_icon_name(icon_name);
|
||||||
let theme = gtk::IconTheme::for_display(&display);
|
|
||||||
let icon_paintable = theme.lookup_icon(
|
|
||||||
icon_name,
|
|
||||||
&[],
|
|
||||||
22,
|
|
||||||
1,
|
|
||||||
gtk::TextDirection::None,
|
|
||||||
gtk::IconLookupFlags::FORCE_SYMBOLIC,
|
|
||||||
);
|
|
||||||
|
|
||||||
let icon = gtk::Image::new();
|
|
||||||
if let Some(file) = icon_paintable.file() {
|
|
||||||
if let Some(path) = file.path() {
|
|
||||||
if let Ok(pixbuf) =
|
|
||||||
Pixbuf::from_file_at_scale(path.to_str().unwrap_or(""), 64, 64, true)
|
|
||||||
{
|
|
||||||
let texture = gdk::Texture::for_pixbuf(&pixbuf);
|
|
||||||
icon.set_paintable(Some(&texture));
|
|
||||||
return icon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: use icon name directly
|
|
||||||
icon.set_icon_name(Some(icon_name));
|
|
||||||
icon.set_pixel_size(64);
|
icon.set_pixel_size(64);
|
||||||
icon
|
icon
|
||||||
}
|
}
|
||||||
@ -479,15 +457,39 @@ fn execute_action(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load an image file and set it as the avatar.
|
/// Load the avatar asynchronously. File-based avatars are decoded off the UI thread.
|
||||||
fn set_avatar_from_file(image: >k::Image, path: &Path) {
|
fn load_avatar_async(image: >k::Image, window: >k::ApplicationWindow, user: &users::User) {
|
||||||
match Pixbuf::from_file_at_scale(path.to_str().unwrap_or(""), AVATAR_SIZE, AVATAR_SIZE, true) {
|
let avatar_path = users::get_avatar_path(&user.home, Some(&user.username));
|
||||||
Ok(pixbuf) => {
|
|
||||||
let texture = gdk::Texture::for_pixbuf(&pixbuf);
|
match avatar_path {
|
||||||
image.set_paintable(Some(&texture));
|
Some(path) => {
|
||||||
|
// File-based avatar: load and scale in background thread
|
||||||
|
glib::spawn_future_local(clone!(
|
||||||
|
#[weak]
|
||||||
|
image,
|
||||||
|
async move {
|
||||||
|
let result = gio::spawn_blocking(move || {
|
||||||
|
Pixbuf::from_file_at_scale(
|
||||||
|
path.to_str().unwrap_or(""),
|
||||||
|
AVATAR_SIZE,
|
||||||
|
AVATAR_SIZE,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
.map(|pb| gdk::Texture::for_pixbuf(&pb))
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(Some(texture)) => image.set_paintable(Some(&texture)),
|
||||||
|
_ => image.set_icon_name(Some("avatar-default-symbolic")),
|
||||||
}
|
}
|
||||||
Err(_) => {
|
}
|
||||||
image.set_icon_name(Some("avatar-default-symbolic"));
|
));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Default SVG avatar: needs widget color, keep synchronous
|
||||||
|
set_default_avatar(image, window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user