commit 45bbed29397ba53e595a025c8568faa2ec5bf00f
parent 9c2cf3af751af6901f357256d30440c14b78f16a
Author: kernelkind <kernelkind@gmail.com>
Date: Sat, 31 Jan 2026 18:51:24 -0500
perf-fix(banner): use our custom img loader via jobs for banner
instead of egui_extras. Also, give a size hint so we don't load
massive banners in egui
Closes: https://github.com/damus-io/notedeck/issues/918
Signed-off-by: kernelkind <kernelkind@gmail.com>
Diffstat:
4 files changed, 55 insertions(+), 21 deletions(-)
diff --git a/crates/notedeck_columns/src/ui/profile/edit.rs b/crates/notedeck_columns/src/ui/profile/edit.rs
@@ -45,7 +45,7 @@ impl<'a> EditProfileView<'a> {
.id_salt(EditProfileView::scroll_id())
.stick_to_bottom(true)
.show(ui, |ui| {
- banner(ui, self.state.banner(), 188.0);
+ banner(ui, self.img_cache, self.jobs, self.state.banner(), 188.0);
let padding = 24.0;
notedeck_ui::padding(padding, ui, |ui| {
diff --git a/crates/notedeck_columns/src/ui/profile/mod.rs b/crates/notedeck_columns/src/ui/profile/mod.rs
@@ -149,6 +149,8 @@ fn profile_body(
ui.vertical(|ui| {
let banner_resp = banner(
ui,
+ note_context.img_cache,
+ note_context.jobs,
profile
.map(|p| p.record().profile())
.and_then(|p| p.and_then(|p| p.banner())),
diff --git a/crates/notedeck_ui/src/profile/mod.rs b/crates/notedeck_ui/src/profile/mod.rs
@@ -8,8 +8,12 @@ pub mod preview;
pub use picture::ProfilePic;
pub use preview::ProfilePreview;
-use egui::{load::TexturePoll, Label, RichText};
-use notedeck::{IsFollowing, NostrName, NotedeckTextStyle};
+use egui::{Label, RichText, TextureHandle};
+use notedeck::media::images::ImageType;
+use notedeck::media::AnimationMode;
+use notedeck::{
+ Images, IsFollowing, MediaJobSender, NostrName, NotedeckTextStyle, PointDimensions,
+};
use crate::{app_images, colors, widgets::styled_button_toggleable};
@@ -93,38 +97,64 @@ pub fn about_section_widget<'a>(profile: Option<&'a ProfileRecord<'a>>) -> impl
}
}
-pub fn banner_texture(ui: &mut egui::Ui, banner_url: &str) -> Option<egui::load::SizedTexture> {
- // TODO: cache banner
- if !banner_url.is_empty() {
- let texture_load_res =
- egui::Image::new(banner_url).load_for_size(ui.ctx(), ui.available_size());
- if let Ok(texture_poll) = texture_load_res {
- match texture_poll {
- TexturePoll::Pending { .. } => {}
- TexturePoll::Ready { texture, .. } => return Some(texture),
- }
- }
+/// Loads a banner texture using the shared media cache to prevent blocking.
+#[profiling::function]
+pub fn banner_texture<'a>(
+ ui: &mut egui::Ui,
+ cache: &'a mut Images,
+ jobs: &MediaJobSender,
+ banner_url: &str,
+ size: PointDimensions,
+) -> Option<&'a TextureHandle> {
+ if banner_url.is_empty() {
+ return None;
}
- None
+ cache.latest_texture(
+ jobs,
+ ui,
+ banner_url,
+ ImageType::Content(Some(size.to_pixels(ui))),
+ AnimationMode::NoAnimation,
+ )
}
-pub fn banner(ui: &mut egui::Ui, banner_url: Option<&str>, height: f32) -> egui::Response {
- ui.add_sized([ui.available_size().x, height], |ui: &mut egui::Ui| {
+/// Renders a profile banner via the cached loader so we avoid egui_extras overhead.
+#[profiling::function]
+pub fn banner(
+ ui: &mut egui::Ui,
+ cache: &mut Images,
+ jobs: &MediaJobSender,
+ banner_url: Option<&str>,
+ height: f32,
+) -> egui::Response {
+ let x = ui.available_size().x;
+ ui.add_sized([x, height], |ui: &mut egui::Ui| {
banner_url
- .and_then(|url| banner_texture(ui, url))
+ .and_then(|url| banner_texture(ui, cache, jobs, url, PointDimensions { x, y: height }))
.map(|texture| {
+ let size = texture.size_vec2();
+ let aspect_ratio = if size.y == 0.0 { 1.0 } else { size.x / size.y };
+
notedeck::media::images::aspect_fill(
ui,
egui::Sense::hover(),
- texture.id,
- texture.size.x / texture.size.y,
+ texture.id(),
+ aspect_ratio,
)
})
- .unwrap_or_else(|| ui.label(""))
+ .unwrap_or_else(|| empty_banner(ui))
})
}
+/// Draws an empty banner placeholder while the image loads or is missing.
+fn empty_banner(ui: &mut egui::Ui) -> egui::Response {
+ let (rect, response) = ui.allocate_exact_size(ui.available_size(), egui::Sense::hover());
+ ui.painter()
+ .rect_filled(rect, 0.0, ui.visuals().faint_bg_color);
+ response
+}
+
pub fn follow_button(following: IsFollowing) -> impl egui::Widget + 'static {
move |ui: &mut egui::Ui| -> egui::Response {
let (bg_color, text) = match following {
diff --git a/crates/notedeck_ui/src/profile/preview.rs b/crates/notedeck_ui/src/profile/preview.rs
@@ -65,6 +65,8 @@ impl egui::Widget for ProfilePreview<'_, '_> {
ui.vertical(|ui| {
banner(
ui,
+ self.cache,
+ self.jobs,
self.profile.record().profile().and_then(|p| p.banner()),
80.0,
);