fix(media): use ScaledTexture

This commit is contained in:
Fernando López Guevara
2025-07-25 14:18:21 -03:00
parent 8306003f6f
commit 661acb3a12

View File

@@ -1,8 +1,8 @@
use std::{collections::HashMap, path::Path}; use std::{collections::HashMap, path::Path};
use egui::{ use egui::{
Button, Color32, Context, CornerRadius, FontId, Image, Response, RichText, Sense, vec2, Button, Color32, Context, CornerRadius, FontId, Image, Response, RichText, Sense,
TextureHandle, UiBuilder, Window, TextureHandle, UiBuilder, Vec2, Window,
}; };
use notedeck::{ use notedeck::{
fonts::get_font_size, note::MediaAction, show_one_error_message, supported_mime_hosted_at_url, fonts::get_font_size, note::MediaAction, show_one_error_message, supported_mime_hosted_at_url,
@@ -33,14 +33,17 @@ pub(crate) fn image_carousel(
) -> Option<MediaAction> { ) -> Option<MediaAction> {
// let's make sure everything is within our area // let's make sure everything is within our area
let height = 360.0; let size = {
let width = ui.available_width(); let height = 360.0;
let width = ui.available_width();
egui::vec2(width, height)
};
let show_popup = get_show_popup(ui, popup_id(carousel_id)); let show_popup = get_show_popup(ui, popup_id(carousel_id));
let mut action = None; let mut action = None;
//let has_touch_screen = ui.ctx().input(|i| i.has_touch_screen()); //let has_touch_screen = ui.ctx().input(|i| i.has_touch_screen());
ui.add_sized([width, height], |ui: &mut egui::Ui| { ui.add_sized(size, |ui: &mut egui::Ui| {
egui::ScrollArea::horizontal() egui::ScrollArea::horizontal()
.drag_to_scroll(false) .drag_to_scroll(false)
.id_salt(carousel_id) .id_salt(carousel_id)
@@ -63,7 +66,7 @@ pub(crate) fn image_carousel(
job_pool, job_pool,
jobs, jobs,
trusted_media, trusted_media,
height, size,
&mut cache.textures_cache, &mut cache.textures_cache,
url, url,
*media_type, *media_type,
@@ -76,7 +79,7 @@ pub(crate) fn image_carousel(
&mut img_cache.gif_states, &mut img_cache.gif_states,
media_state, media_state,
url, url,
height, size,
i18n, i18n,
) { ) {
// clicked the media, lets set the active index // clicked the media, lets set the active index
@@ -90,7 +93,7 @@ pub(crate) fn image_carousel(
url, url,
*media_type, *media_type,
cache, cache,
ImageType::Content(Some((width as u32, height as u32))), ImageType::Content(Some((size.x as u32, size.y as u32))),
); );
} }
} }
@@ -276,7 +279,7 @@ pub fn get_content_media_render_state<'a>(
job_pool: &'a mut JobPool, job_pool: &'a mut JobPool,
jobs: &'a mut JobsCache, jobs: &'a mut JobsCache,
media_trusted: bool, media_trusted: bool,
height: f32, size: Vec2,
cache: &'a mut TexturesCache, cache: &'a mut TexturesCache,
url: &'a str, url: &'a str,
cache_type: MediaCacheType, cache_type: MediaCacheType,
@@ -302,7 +305,7 @@ pub fn get_content_media_render_state<'a>(
obfuscation_type, obfuscation_type,
job_pool, job_pool,
jobs, jobs,
height, size,
)); ));
}; };
@@ -313,11 +316,11 @@ pub fn get_content_media_render_state<'a>(
obfuscation_type, obfuscation_type,
job_pool, job_pool,
jobs, jobs,
height, size,
)), )),
notedeck::LoadableTextureState::Error(e) => MediaRenderState::Error(e), notedeck::LoadableTextureState::Error(e) => MediaRenderState::Error(e),
notedeck::LoadableTextureState::Loading { actual_image_tex } => { notedeck::LoadableTextureState::Loading { actual_image_tex } => {
let obfuscation = get_obfuscated(ui, url, obfuscation_type, job_pool, jobs, height); let obfuscation = get_obfuscated(ui, url, obfuscation_type, job_pool, jobs, size);
MediaRenderState::Transitioning { MediaRenderState::Transitioning {
image: actual_image_tex, image: actual_image_tex,
obfuscation, obfuscation,
@@ -335,7 +338,7 @@ fn get_obfuscated<'a>(
obfuscation_type: ObfuscationType<'a>, obfuscation_type: ObfuscationType<'a>,
job_pool: &'a mut JobPool, job_pool: &'a mut JobPool,
jobs: &'a mut JobsCache, jobs: &'a mut JobsCache,
height: f32, size: Vec2,
) -> ObfuscatedTexture<'a> { ) -> ObfuscatedTexture<'a> {
let ObfuscationType::Blurhash(renderable_blur) = obfuscation_type else { let ObfuscationType::Blurhash(renderable_blur) = obfuscation_type else {
return ObfuscatedTexture::Default; return ObfuscatedTexture::Default;
@@ -348,8 +351,8 @@ fn get_obfuscated<'a>(
}; };
let available_points = PointDimensions { let available_points = PointDimensions {
x: ui.available_width(), x: size.x,
y: height, y: size.y,
}; };
let pixel_sizes = renderable_blur.scaled_pixel_dimensions(ui, available_points); let pixel_sizes = renderable_blur.scaled_pixel_dimensions(ui, available_points);
@@ -731,12 +734,12 @@ fn render_media(
gifs: &mut GifStateMap, gifs: &mut GifStateMap,
render_state: MediaRenderState, render_state: MediaRenderState,
url: &str, url: &str,
height: f32, size: egui::Vec2,
i18n: &mut Localization, i18n: &mut Localization,
) -> Option<MediaUIAction> { ) -> Option<MediaUIAction> {
match render_state { match render_state {
MediaRenderState::ActualImage(image) => { MediaRenderState::ActualImage(image) => {
if render_success_media(ui, url, image, gifs, height, i18n).clicked() { if render_success_media(ui, url, image, gifs, size, i18n).clicked() {
Some(MediaUIAction::Clicked) Some(MediaUIAction::Clicked)
} else { } else {
None None
@@ -744,29 +747,31 @@ fn render_media(
} }
MediaRenderState::Transitioning { image, obfuscation } => match obfuscation { MediaRenderState::Transitioning { image, obfuscation } => match obfuscation {
ObfuscatedTexture::Blur(texture) => { ObfuscatedTexture::Blur(texture) => {
if render_blur_transition(ui, url, height, texture, image.get_first_texture()) { if render_blur_transition(ui, url, size, texture, image.get_first_texture()) {
Some(MediaUIAction::DoneLoading) Some(MediaUIAction::DoneLoading)
} else { } else {
None None
} }
} }
ObfuscatedTexture::Default => { ObfuscatedTexture::Default => {
ui.add(texture_to_image(ui, image.get_first_texture(), height)); let scaled =
ScaledTexture::new(image.get_first_texture(), size, is_narrow(ui.ctx()));
ui.add(scaled.get_image());
Some(MediaUIAction::DoneLoading) Some(MediaUIAction::DoneLoading)
} }
}, },
MediaRenderState::Error(e) => { MediaRenderState::Error(e) => {
ui.allocate_space(egui::vec2(height, height)); ui.allocate_space(size);
show_one_error_message(ui, &format!("Could not render media {url}: {e}")); show_one_error_message(ui, &format!("Could not render media {url}: {e}"));
Some(MediaUIAction::Error) Some(MediaUIAction::Error)
} }
MediaRenderState::Shimmering(obfuscated_texture) => { MediaRenderState::Shimmering(obfuscated_texture) => {
match obfuscated_texture { match obfuscated_texture {
ObfuscatedTexture::Blur(texture_handle) => { ObfuscatedTexture::Blur(texture_handle) => {
shimmer_blurhash(texture_handle, ui, url, height); shimmer_blurhash(texture_handle, ui, url, size);
} }
ObfuscatedTexture::Default => { ObfuscatedTexture::Default => {
render_default_blur_bg(ui, height, url, true); render_default_blur_bg(ui, size, url, true);
} }
} }
None None
@@ -774,12 +779,12 @@ fn render_media(
MediaRenderState::Obfuscated(obfuscated_texture) => { MediaRenderState::Obfuscated(obfuscated_texture) => {
let resp = match obfuscated_texture { let resp = match obfuscated_texture {
ObfuscatedTexture::Blur(texture_handle) => { ObfuscatedTexture::Blur(texture_handle) => {
let img = texture_to_image(ui, texture_handle, height); let scaled = ScaledTexture::new(texture_handle, size, is_narrow(ui.ctx()));
let resp = ui.add(img); let resp = ui.add(scaled.get_image());
render_blur_text(ui, i18n, url, resp.rect) render_blur_text(ui, i18n, url, resp.rect)
} }
ObfuscatedTexture::Default => render_default_blur(ui, i18n, height, url), ObfuscatedTexture::Default => render_default_blur(ui, i18n, size, url),
}; };
if resp if resp
@@ -883,20 +888,26 @@ fn render_blur_text(
fn render_default_blur( fn render_default_blur(
ui: &mut egui::Ui, ui: &mut egui::Ui,
i18n: &mut Localization, i18n: &mut Localization,
height: f32, size: egui::Vec2,
url: &str, url: &str,
) -> egui::Response { ) -> egui::Response {
let rect = render_default_blur_bg(ui, height, url, false); let rect = render_default_blur_bg(ui, size, url, false);
render_blur_text(ui, i18n, url, rect) render_blur_text(ui, i18n, url, rect)
} }
fn render_default_blur_bg(ui: &mut egui::Ui, height: f32, url: &str, shimmer: bool) -> egui::Rect { fn render_default_blur_bg(
let width = if is_narrow(ui.ctx()) { ui: &mut egui::Ui,
ui.available_width() size: egui::Vec2,
url: &str,
shimmer: bool,
) -> egui::Rect {
let size = if is_narrow(ui.ctx()) {
size
} else { } else {
height vec2(size.y, size.y)
}; };
let (rect, _) = ui.allocate_exact_size(egui::vec2(width, height), egui::Sense::click());
let (rect, _) = ui.allocate_exact_size(size, egui::Sense::click());
let painter = ui.painter_at(rect); let painter = ui.painter_at(rect);
@@ -980,30 +991,25 @@ fn render_success_media(
url: &str, url: &str,
tex: &mut TexturedImage, tex: &mut TexturedImage,
gifs: &mut GifStateMap, gifs: &mut GifStateMap,
height: f32, size: Vec2,
i18n: &mut Localization, i18n: &mut Localization,
) -> Response { ) -> Response {
let texture = handle_repaint(ui, retrieve_latest_texture(url, gifs, tex)); let texture = handle_repaint(ui, retrieve_latest_texture(url, gifs, tex));
let img = texture_to_image(ui, texture, height);
let img_resp = ui.add(Button::image(img).frame(false)); let scaled = ScaledTexture::new(texture, size, is_narrow(ui.ctx()));
let img_resp = ui.add(Button::image(scaled.get_image()).frame(false));
copy_link(i18n, url, &img_resp); copy_link(i18n, url, &img_resp);
img_resp img_resp
} }
fn texture_to_image<'a>(ui: &egui::Ui, tex: &TextureHandle, max_height: f32) -> egui::Image<'a> { fn texture_to_image<'a>(tex: &TextureHandle, size: Vec2) -> egui::Image<'a> {
let mut img = Image::new(tex) Image::new(tex)
.max_height(max_height)
.corner_radius(5.0) .corner_radius(5.0)
.maintain_aspect_ratio(true); .fit_to_exact_size(size)
.maintain_aspect_ratio(true)
if is_narrow(ui.ctx()) {
img = img.max_width(ui.available_width());
}
img
} }
static BLUR_SHIMMER_ID: fn(&str) -> egui::Id = |url| egui::Id::new(("blur_shimmer", url)); static BLUR_SHIMMER_ID: fn(&str) -> egui::Id = |url| egui::Id::new(("blur_shimmer", url));
@@ -1022,11 +1028,11 @@ fn get_blur_current_alpha(ui: &mut egui::Ui, url: &str) -> u8 {
.animate() .animate()
} }
fn shimmer_blurhash(tex: &TextureHandle, ui: &mut egui::Ui, url: &str, max_height: f32) { fn shimmer_blurhash(tex: &TextureHandle, ui: &mut egui::Ui, url: &str, size: Vec2) {
let cur_alpha = get_blur_current_alpha(ui, url); let cur_alpha = get_blur_current_alpha(ui, url);
let scaled = ScaledTexture::new(tex, max_height); let scaled = ScaledTexture::new(tex, size, is_narrow(ui.ctx()));
let img = scaled.get_image(ui); let img = scaled.get_image();
show_blurhash_with_alpha(ui, img, cur_alpha); show_blurhash_with_alpha(ui, img, cur_alpha);
} }
@@ -1048,54 +1054,60 @@ type FinishedTransition = bool;
fn render_blur_transition( fn render_blur_transition(
ui: &mut egui::Ui, ui: &mut egui::Ui,
url: &str, url: &str,
max_height: f32, size: Vec2,
blur_texture: &TextureHandle, blur_texture: &TextureHandle,
image_texture: &TextureHandle, image_texture: &TextureHandle,
) -> FinishedTransition { ) -> FinishedTransition {
let scaled_texture = ScaledTexture::new(image_texture, max_height); let scaled_texture = ScaledTexture::new(image_texture, size, is_narrow(ui.ctx()));
let blur_img = texture_to_image(ui, blur_texture, max_height); let scaled_blur_img = ScaledTexture::new(blur_texture, size, is_narrow(ui.ctx()));
match get_blur_transition_state(ui.ctx(), url) { match get_blur_transition_state(ui.ctx(), url) {
BlurTransitionState::StoppingShimmer { cur_alpha } => { BlurTransitionState::StoppingShimmer { cur_alpha } => {
show_blurhash_with_alpha(ui, blur_img, cur_alpha); show_blurhash_with_alpha(ui, scaled_blur_img.get_image(), cur_alpha);
false false
} }
BlurTransitionState::FadingBlur => render_blur_fade(ui, url, blur_img, &scaled_texture), BlurTransitionState::FadingBlur => {
render_blur_fade(ui, url, scaled_blur_img.get_image(), &scaled_texture)
}
} }
} }
struct ScaledTexture<'a> { struct ScaledTexture<'a> {
tex: &'a TextureHandle, tex: &'a TextureHandle,
max_height: f32, size: Vec2,
pub scaled_size: egui::Vec2, pub scaled_size: Vec2,
} }
impl<'a> ScaledTexture<'a> { impl<'a> ScaledTexture<'a> {
pub fn new(tex: &'a TextureHandle, max_height: f32) -> Self { pub fn new(tex: &'a TextureHandle, max_size: Vec2, is_narrow: bool) -> Self {
let scaled_size = { let tex_size = tex.size_vec2();
let mut size = tex.size_vec2();
if size.y > max_height { let scaled_size = if !is_narrow {
let old_y = size.y; if tex_size.y > max_size.y {
size.y = max_height; let scale = max_size.y / tex_size.y;
size.x *= max_height / old_y; tex_size * scale
} else {
tex_size
}
} else {
if tex_size.x < max_size.x || tex_size.x > max_size.x {
let scale = max_size.x / tex_size.x;
tex_size * scale
} else {
tex_size
} }
size
}; };
Self { Self {
tex, tex,
max_height, size: max_size,
scaled_size, scaled_size,
} }
} }
pub fn get_image(&self, ui: &egui::Ui) -> Image { pub fn get_image(&self) -> Image {
texture_to_image(ui, self.tex, self.max_height) texture_to_image(self.tex, self.size).fit_to_exact_size(self.scaled_size)
.max_size(self.scaled_size)
.shrink_to_fit()
} }
} }
@@ -1114,7 +1126,7 @@ fn render_blur_fade(
.animate() .animate()
}; };
let img = image_texture.get_image(ui); let img = image_texture.get_image();
let blur_img = blur_img.tint(fade_color(cur_alpha)); let blur_img = blur_img.tint(fade_color(cur_alpha));