notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 953496fc74f035ed2f4b31670b09e62f4a5f950f
parent 01636786be26be6524cb685e9eecfa9479572f50
Author: kernelkind <kernelkind@gmail.com>
Date:   Tue, 29 Apr 2025 12:39:54 -0400

note media: unnest full screen media

Signed-off-by: kernelkind <kernelkind@gmail.com>

Diffstat:
Mcrates/notedeck_ui/src/note/media.rs | 350+++++++++++++++++++++++++++++++++++++++++--------------------------------------
1 file changed, 181 insertions(+), 169 deletions(-)

diff --git a/crates/notedeck_ui/src/note/media.rs b/crates/notedeck_ui/src/note/media.rs @@ -1,5 +1,7 @@ +use std::collections::HashMap; + use egui::{Button, Color32, Image, Response, Sense, Window}; -use notedeck::{Images, MediaCacheType}; +use notedeck::{GifState, Images, MediaCacheType, TexturedImage}; use crate::{ gif::{handle_repaint, retrieve_latest_texture}, @@ -86,179 +88,189 @@ pub(crate) fn image_carousel( }); if show_popup { - let current_image = current_image - .as_ref() - .expect("the image was actually clicked"); - let image = current_image.clone().0; - let cache_type = current_image.clone().1; - - Window::new("image_popup") - .title_bar(false) - .fixed_size(ui.ctx().screen_rect().size()) - .fixed_pos(ui.ctx().screen_rect().min) - .frame(egui::Frame::NONE) - .show(ui.ctx(), |ui| { - ui.centered_and_justified(|ui| { - render_images( - ui, - img_cache, - &image, - ImageType::Content, - cache_type, - |_| {}, - |_, _| {}, - |ui, url, renderable_media, gifs| { - let screen_rect = ui.ctx().screen_rect(); - - // escape - if ui.input(|i| i.key_pressed(egui::Key::Escape)) { - ui.ctx().memory_mut(|mem| { - mem.data.insert_temp(carousel_id.with("show_popup"), false); - }); - } - - // background - ui.painter().rect_filled( - screen_rect, - 0.0, - Color32::from_black_alpha(230), - ); - - // zoom init - let zoom_id = carousel_id.with("zoom_level"); - let mut zoom = ui - .ctx() - .memory(|mem| mem.data.get_temp(zoom_id).unwrap_or(1.0_f32)); - - // pan init - let pan_id = carousel_id.with("pan_offset"); - let mut pan_offset = ui.ctx().memory(|mem| { - mem.data.get_temp(pan_id).unwrap_or(egui::Vec2::ZERO) - }); - - // zoom & scroll - if ui.input(|i| i.pointer.hover_pos()).is_some() { - let scroll_delta = ui.input(|i| i.smooth_scroll_delta); - if scroll_delta.y != 0.0 { - let zoom_factor = - if scroll_delta.y > 0.0 { 1.05 } else { 0.95 }; - zoom *= zoom_factor; - zoom = zoom.clamp(0.1, 5.0); - - if zoom <= 1.0 { - pan_offset = egui::Vec2::ZERO; - } + if let Some((image_url, cache_type)) = current_image { + show_full_screen_media(ui, &image_url, cache_type, img_cache, carousel_id); + } + } +} - ui.ctx().memory_mut(|mem| { - mem.data.insert_temp(zoom_id, zoom); - mem.data.insert_temp(pan_id, pan_offset); - }); - } - } - - let texture = handle_repaint( - ui, - retrieve_latest_texture(&image, gifs, renderable_media), - ); - - let texture_size = texture.size_vec2(); - let screen_size = screen_rect.size(); - let scale = (screen_size.x / texture_size.x) - .min(screen_size.y / texture_size.y) - .min(1.0); - let scaled_size = texture_size * scale * zoom; - - let visible_width = scaled_size.x.min(screen_size.x); - let visible_height = scaled_size.y.min(screen_size.y); - - let max_pan_x = ((scaled_size.x - visible_width) / 2.0).max(0.0); - let max_pan_y = ((scaled_size.y - visible_height) / 2.0).max(0.0); - - if max_pan_x > 0.0 { - pan_offset.x = pan_offset.x.clamp(-max_pan_x, max_pan_x); - } else { - pan_offset.x = 0.0; - } - - if max_pan_y > 0.0 { - pan_offset.y = pan_offset.y.clamp(-max_pan_y, max_pan_y); - } else { - pan_offset.y = 0.0; - } - - let (rect, response) = ui.allocate_exact_size( - egui::vec2(visible_width, visible_height), - egui::Sense::click_and_drag(), - ); - - let uv_min = egui::pos2( - 0.5 - (visible_width / scaled_size.x) / 2.0 - + pan_offset.x / scaled_size.x, - 0.5 - (visible_height / scaled_size.y) / 2.0 - + pan_offset.y / scaled_size.y, - ); - - let uv_max = egui::pos2( - uv_min.x + visible_width / scaled_size.x, - uv_min.y + visible_height / scaled_size.y, - ); - - let uv = egui::Rect::from_min_max(uv_min, uv_max); - - ui.painter() - .image(texture.id(), rect, uv, egui::Color32::WHITE); - let img_rect = ui.allocate_rect(rect, Sense::click()); - - if img_rect.clicked() { - ui.ctx().memory_mut(|mem| { - mem.data.insert_temp(carousel_id.with("show_popup"), true); - }); - } else if img_rect.clicked_elsewhere() { - ui.ctx().memory_mut(|mem| { - mem.data.insert_temp(carousel_id.with("show_popup"), false); - }); - } - - // Handle dragging for pan - if response.dragged() { - let delta = response.drag_delta(); - - pan_offset.x -= delta.x; - pan_offset.y -= delta.y; - - if max_pan_x > 0.0 { - pan_offset.x = pan_offset.x.clamp(-max_pan_x, max_pan_x); - } else { - pan_offset.x = 0.0; - } +fn show_full_screen_media( + ui: &mut egui::Ui, + image_url: &str, + cache_type: MediaCacheType, + img_cache: &mut Images, + carousel_id: egui::Id, +) { + Window::new("image_popup") + .title_bar(false) + .fixed_size(ui.ctx().screen_rect().size()) + .fixed_pos(ui.ctx().screen_rect().min) + .frame(egui::Frame::NONE) + .show(ui.ctx(), |ui| { + ui.centered_and_justified(|ui| { + render_images( + ui, + img_cache, + image_url, + ImageType::Content, + cache_type, + |_| {}, + |_, _| {}, + |ui, url, renderable_media, gifs| { + render_full_screen_media(ui, renderable_media, gifs, url, carousel_id); + }, + ); + }); + }); +} - if max_pan_y > 0.0 { - pan_offset.y = pan_offset.y.clamp(-max_pan_y, max_pan_y); - } else { - pan_offset.y = 0.0; - } +fn render_full_screen_media( + ui: &mut egui::Ui, + renderable_media: &mut TexturedImage, + gifs: &mut HashMap<String, GifState>, + image_url: &str, + carousel_id: egui::Id, +) { + let screen_rect = ui.ctx().screen_rect(); - ui.ctx().memory_mut(|mem| { - mem.data.insert_temp(pan_id, pan_offset); - }); - } - - // reset zoom on double-click - if response.double_clicked() { - pan_offset = egui::Vec2::ZERO; - zoom = 1.0; - ui.ctx().memory_mut(|mem| { - mem.data.insert_temp(pan_id, pan_offset); - mem.data.insert_temp(zoom_id, zoom); - }); - } - - copy_link(url, response); - }, - ); - }); + // escape + if ui.input(|i| i.key_pressed(egui::Key::Escape)) { + ui.ctx().memory_mut(|mem| { + mem.data.insert_temp(carousel_id.with("show_popup"), false); + }); + } + + // background + ui.painter() + .rect_filled(screen_rect, 0.0, Color32::from_black_alpha(230)); + + // zoom init + let zoom_id = carousel_id.with("zoom_level"); + let mut zoom = ui + .ctx() + .memory(|mem| mem.data.get_temp(zoom_id).unwrap_or(1.0_f32)); + + // pan init + let pan_id = carousel_id.with("pan_offset"); + let mut pan_offset = ui + .ctx() + .memory(|mem| mem.data.get_temp(pan_id).unwrap_or(egui::Vec2::ZERO)); + + // zoom & scroll + if ui.input(|i| i.pointer.hover_pos()).is_some() { + let scroll_delta = ui.input(|i| i.smooth_scroll_delta); + if scroll_delta.y != 0.0 { + let zoom_factor = if scroll_delta.y > 0.0 { 1.05 } else { 0.95 }; + zoom *= zoom_factor; + zoom = zoom.clamp(0.1, 5.0); + + if zoom <= 1.0 { + pan_offset = egui::Vec2::ZERO; + } + + ui.ctx().memory_mut(|mem| { + mem.data.insert_temp(zoom_id, zoom); + mem.data.insert_temp(pan_id, pan_offset); }); + } } + + let texture = handle_repaint( + ui, + retrieve_latest_texture(image_url, gifs, renderable_media), + ); + + let texture_size = texture.size_vec2(); + let screen_size = screen_rect.size(); + let scale = (screen_size.x / texture_size.x) + .min(screen_size.y / texture_size.y) + .min(1.0); + let scaled_size = texture_size * scale * zoom; + + let visible_width = scaled_size.x.min(screen_size.x); + let visible_height = scaled_size.y.min(screen_size.y); + + let max_pan_x = ((scaled_size.x - visible_width) / 2.0).max(0.0); + let max_pan_y = ((scaled_size.y - visible_height) / 2.0).max(0.0); + + if max_pan_x > 0.0 { + pan_offset.x = pan_offset.x.clamp(-max_pan_x, max_pan_x); + } else { + pan_offset.x = 0.0; + } + + if max_pan_y > 0.0 { + pan_offset.y = pan_offset.y.clamp(-max_pan_y, max_pan_y); + } else { + pan_offset.y = 0.0; + } + + let (rect, response) = ui.allocate_exact_size( + egui::vec2(visible_width, visible_height), + egui::Sense::click_and_drag(), + ); + + let uv_min = egui::pos2( + 0.5 - (visible_width / scaled_size.x) / 2.0 + pan_offset.x / scaled_size.x, + 0.5 - (visible_height / scaled_size.y) / 2.0 + pan_offset.y / scaled_size.y, + ); + + let uv_max = egui::pos2( + uv_min.x + visible_width / scaled_size.x, + uv_min.y + visible_height / scaled_size.y, + ); + + let uv = egui::Rect::from_min_max(uv_min, uv_max); + + ui.painter() + .image(texture.id(), rect, uv, egui::Color32::WHITE); + let img_rect = ui.allocate_rect(rect, Sense::click()); + + if img_rect.clicked() { + ui.ctx().memory_mut(|mem| { + mem.data.insert_temp(carousel_id.with("show_popup"), true); + }); + } else if img_rect.clicked_elsewhere() { + ui.ctx().memory_mut(|mem| { + mem.data.insert_temp(carousel_id.with("show_popup"), false); + }); + } + + // Handle dragging for pan + if response.dragged() { + let delta = response.drag_delta(); + + pan_offset.x -= delta.x; + pan_offset.y -= delta.y; + + if max_pan_x > 0.0 { + pan_offset.x = pan_offset.x.clamp(-max_pan_x, max_pan_x); + } else { + pan_offset.x = 0.0; + } + + if max_pan_y > 0.0 { + pan_offset.y = pan_offset.y.clamp(-max_pan_y, max_pan_y); + } else { + pan_offset.y = 0.0; + } + + ui.ctx().memory_mut(|mem| { + mem.data.insert_temp(pan_id, pan_offset); + }); + } + + // reset zoom on double-click + if response.double_clicked() { + pan_offset = egui::Vec2::ZERO; + zoom = 1.0; + ui.ctx().memory_mut(|mem| { + mem.data.insert_temp(pan_id, pan_offset); + mem.data.insert_temp(zoom_id, zoom); + }); + } + + copy_link(image_url, response); } fn copy_link(url: &str, img_resp: Response) {