diff --git a/src/main.rs b/src/main.rs index 4923c2d..e12217a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,12 +51,13 @@ fn activate(app: >k::Application) { 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 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) - 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); panel.present(); @@ -64,7 +65,7 @@ fn activate(app: >k::Application) { let monitors = display.monitors(); for i in 0..monitors.n_items() { if let Some(monitor) = monitors.item(i).and_then(|obj| obj.downcast::().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); wallpaper.set_monitor(Some(&monitor)); wallpaper.present(); diff --git a/src/panel.rs b/src/panel.rs index f32aef3..3d262fa 100644 --- a/src/panel.rs +++ b/src/panel.rs @@ -79,14 +79,26 @@ pub fn action_definitions() -> Vec { ] } +/// 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. -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() .application(app) .build(); window.add_css_class("wallpaper"); - let background = create_background_picture(bg_path); + let background = create_background_picture(texture); window.set_child(Some(&background)); // 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. -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() .application(app) .build(); @@ -126,7 +138,7 @@ pub fn create_panel_window(bg_path: &Path, app: >k::Application) -> gtk::Appli window.set_child(Some(&overlay)); // Background wallpaper - let background = create_background_picture(bg_path); + let background = create_background_picture(texture); overlay.set_child(Some(&background)); // 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); content_box.append(&avatar_frame); - // Load avatar - let avatar_path = users::get_avatar_path(&user.home, Some(&user.username)); - if let Some(path) = avatar_path { - set_avatar_from_file(&avatar_image, &path); - } else { - set_default_avatar(&avatar_image, &window); - } + // Load avatar (file-based avatars load asynchronously) + load_avatar_async(&avatar_image, &window, &user); // Username label 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 } -/// Create a Picture widget for the wallpaper background. -fn create_background_picture(bg_path: &Path) -> gtk::Picture { - let background = if bg_path.starts_with("/dev/moonarch/moonset") { - gtk::Picture::for_resource(bg_path.to_str().unwrap_or("")) - } else { - gtk::Picture::for_filename(bg_path.to_str().unwrap_or("")) - }; +/// Create a Picture widget for the wallpaper background from a shared texture. +fn create_background_picture(texture: &gdk::Texture) -> gtk::Picture { + let background = gtk::Picture::for_paintable(texture); background.set_content_fit(gtk::ContentFit::Cover); background.set_hexpand(true); background.set_vexpand(true); @@ -302,34 +305,9 @@ fn create_action_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 { - let display = gdk::Display::default().unwrap(); - 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)); + let icon = gtk::Image::from_icon_name(icon_name); icon.set_pixel_size(64); icon } @@ -479,15 +457,39 @@ fn execute_action( )); } -/// Load an image file and set it as the avatar. -fn set_avatar_from_file(image: >k::Image, path: &Path) { - match Pixbuf::from_file_at_scale(path.to_str().unwrap_or(""), AVATAR_SIZE, AVATAR_SIZE, true) { - Ok(pixbuf) => { - let texture = gdk::Texture::for_pixbuf(&pixbuf); - image.set_paintable(Some(&texture)); +/// Load the avatar asynchronously. File-based avatars are decoded off the UI thread. +fn load_avatar_async(image: >k::Image, window: >k::ApplicationWindow, user: &users::User) { + let avatar_path = users::get_avatar_path(&user.home, Some(&user.username)); + + match avatar_path { + 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); } } }