media/viewer: provide image-click provenance
We will be using this for transitions Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
@@ -1,12 +1,36 @@
|
|||||||
use crate::{Images, MediaCacheType, TexturedImage};
|
use crate::{Images, MediaCacheType, TexturedImage};
|
||||||
use poll_promise::Promise;
|
use poll_promise::Promise;
|
||||||
|
|
||||||
|
/// Tracks where media was on the screen so that
|
||||||
|
/// we can do fun animations when opening the
|
||||||
|
/// Media Viewer
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct MediaInfo {
|
||||||
|
/// The original screen position where it
|
||||||
|
/// was rendered from. This is not where
|
||||||
|
/// it should be rendered in the scene.
|
||||||
|
pub original_position: egui::Rect,
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contains various information for when a user
|
||||||
|
/// clicks a piece of media. It contains the current
|
||||||
|
/// location on screen for each piece of media.
|
||||||
|
///
|
||||||
|
/// Viewers can use this to smoothly transition from
|
||||||
|
/// the timeline to the viewer
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct ViewMediaInfo {
|
||||||
|
pub clicked_index: usize,
|
||||||
|
pub medias: Vec<MediaInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Actions generated by media ui interactions
|
/// Actions generated by media ui interactions
|
||||||
pub enum MediaAction {
|
pub enum MediaAction {
|
||||||
/// An image was clicked on in a carousel, we have
|
/// An image was clicked on in a carousel, we have
|
||||||
/// the opportunity to open into a fullscreen media viewer
|
/// the opportunity to open into a fullscreen media viewer
|
||||||
/// with a list of url values
|
/// with a list of url values
|
||||||
ViewMedias(Vec<String>),
|
ViewMedias(ViewMediaInfo),
|
||||||
|
|
||||||
FetchImage {
|
FetchImage {
|
||||||
url: String,
|
url: String,
|
||||||
@@ -22,7 +46,14 @@ pub enum MediaAction {
|
|||||||
impl std::fmt::Debug for MediaAction {
|
impl std::fmt::Debug for MediaAction {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::ViewMedias(urls) => f.debug_struct("ViewMedias").field("urls", urls).finish(),
|
Self::ViewMedias(ViewMediaInfo {
|
||||||
|
clicked_index,
|
||||||
|
medias,
|
||||||
|
}) => f
|
||||||
|
.debug_struct("ViewMedias")
|
||||||
|
.field("clicked_index", clicked_index)
|
||||||
|
.field("media", medias)
|
||||||
|
.finish(),
|
||||||
Self::FetchImage {
|
Self::FetchImage {
|
||||||
url,
|
url,
|
||||||
cache_type,
|
cache_type,
|
||||||
@@ -44,9 +75,9 @@ impl std::fmt::Debug for MediaAction {
|
|||||||
|
|
||||||
impl MediaAction {
|
impl MediaAction {
|
||||||
/// Handle view media actions
|
/// Handle view media actions
|
||||||
pub fn on_view_media(&self, handler: impl FnOnce(Vec<String>)) {
|
pub fn on_view_media(&self, handler: impl FnOnce(&ViewMediaInfo)) {
|
||||||
if let MediaAction::ViewMedias(urls) = self {
|
if let MediaAction::ViewMedias(view_medias) = self {
|
||||||
handler(urls.clone())
|
handler(view_medias)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ pub mod images;
|
|||||||
pub mod imeta;
|
pub mod imeta;
|
||||||
pub mod renderable;
|
pub mod renderable;
|
||||||
|
|
||||||
pub use action::MediaAction;
|
pub use action::{MediaAction, MediaInfo, ViewMediaInfo};
|
||||||
pub use blur::{
|
pub use blur::{
|
||||||
compute_blurhash, update_imeta_blurhashes, ImageMetadata, ObfuscationType, PixelDimensions,
|
compute_blurhash, update_imeta_blurhashes, ImageMetadata, ObfuscationType, PixelDimensions,
|
||||||
PointDimensions,
|
PointDimensions,
|
||||||
|
|||||||
@@ -158,7 +158,8 @@ fn execute_note_action(
|
|||||||
},
|
},
|
||||||
NoteAction::Media(media_action) => {
|
NoteAction::Media(media_action) => {
|
||||||
media_action.on_view_media(|medias| {
|
media_action.on_view_media(|medias| {
|
||||||
view_state.media_viewer.urls = medias;
|
view_state.media_viewer.media_info = medias.clone();
|
||||||
|
tracing::debug!("on_view_media {:?}", &medias);
|
||||||
app_options.set(AppOptions::FullscreenMedia, true);
|
app_options.set(AppOptions::FullscreenMedia, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ fn fullscreen_media_viewer_ui(
|
|||||||
viewer_state: &mut MediaViewerState,
|
viewer_state: &mut MediaViewerState,
|
||||||
img_cache: &mut Images,
|
img_cache: &mut Images,
|
||||||
) {
|
) {
|
||||||
if !options.contains(AppOptions::FullscreenMedia) || viewer_state.urls.is_empty() {
|
if !options.contains(AppOptions::FullscreenMedia) || viewer_state.media_info.medias.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use egui::{pos2, Color32, Rect};
|
use egui::{pos2, Color32, Rect};
|
||||||
|
use notedeck::media::{MediaInfo, ViewMediaInfo};
|
||||||
use notedeck::{ImageType, Images};
|
use notedeck::{ImageType, Images};
|
||||||
|
|
||||||
/// State used in the MediaViewer ui widget.
|
/// State used in the MediaViewer ui widget.
|
||||||
///
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct MediaViewerState {
|
pub struct MediaViewerState {
|
||||||
pub urls: Vec<String>,
|
/// When
|
||||||
|
pub media_info: ViewMediaInfo,
|
||||||
pub scene_rect: Option<Rect>,
|
pub scene_rect: Option<Rect>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ impl<'a> MediaViewer<'a> {
|
|||||||
let resp = egui::Scene::new()
|
let resp = egui::Scene::new()
|
||||||
.zoom_range(0.0..=10.0) // enhance 🔬
|
.zoom_range(0.0..=10.0) // enhance 🔬
|
||||||
.show(ui, &mut scene_rect, |ui| {
|
.show(ui, &mut scene_rect, |ui| {
|
||||||
self.render_image_tiles(images, ui);
|
Self::render_image_tiles(&self.state.media_info.medias, images, ui);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.state.scene_rect = Some(scene_rect);
|
self.state.scene_rect = Some(scene_rect);
|
||||||
@@ -74,8 +75,10 @@ impl<'a> MediaViewer<'a> {
|
|||||||
/// TODO(jb55): Let's improve image tiling over time, spiraling outward. We
|
/// TODO(jb55): Let's improve image tiling over time, spiraling outward. We
|
||||||
/// should have a way to click "next" and have the scene smoothly transition and
|
/// should have a way to click "next" and have the scene smoothly transition and
|
||||||
/// focus on the next image
|
/// focus on the next image
|
||||||
fn render_image_tiles(&self, images: &mut Images, ui: &mut egui::Ui) {
|
fn render_image_tiles(infos: &[MediaInfo], images: &mut Images, ui: &mut egui::Ui) {
|
||||||
for url in &self.state.urls {
|
for info in infos {
|
||||||
|
let url = &info.url;
|
||||||
|
|
||||||
// fetch image texture
|
// fetch image texture
|
||||||
let Some(texture) = images.latest_texture(ui, url, ImageType::Content(None)) else {
|
let Some(texture) = images.latest_texture(ui, url, ImageType::Content(None)) else {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use notedeck::{
|
|||||||
|
|
||||||
use notedeck::media::gif::ensure_latest_texture;
|
use notedeck::media::gif::ensure_latest_texture;
|
||||||
use notedeck::media::images::{fetch_no_pfp_promise, ImageType};
|
use notedeck::media::images::{fetch_no_pfp_promise, ImageType};
|
||||||
|
use notedeck::media::{MediaInfo, ViewMediaInfo};
|
||||||
|
|
||||||
use crate::{app_images, AnimationHelper, PulseAlpha};
|
use crate::{app_images, AnimationHelper, PulseAlpha};
|
||||||
|
|
||||||
@@ -43,7 +44,9 @@ pub(crate) fn image_carousel(
|
|||||||
.id_salt(carousel_id)
|
.id_salt(carousel_id)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
let mut media_infos: Vec<MediaInfo> = Vec::with_capacity(medias.len());
|
||||||
let mut media_action: Option<(usize, MediaUIAction)> = None;
|
let mut media_action: Option<(usize, MediaUIAction)> = None;
|
||||||
|
|
||||||
for (i, media) in medias.iter().enumerate() {
|
for (i, media) in medias.iter().enumerate() {
|
||||||
let RenderableMedia {
|
let RenderableMedia {
|
||||||
url,
|
url,
|
||||||
@@ -68,23 +71,32 @@ pub(crate) fn image_carousel(
|
|||||||
blur_type,
|
blur_type,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(cur_action) = render_media(
|
let media_response = render_media(
|
||||||
ui,
|
ui,
|
||||||
&mut img_cache.gif_states,
|
&mut img_cache.gif_states,
|
||||||
media_state,
|
media_state,
|
||||||
url,
|
url,
|
||||||
height,
|
height,
|
||||||
i18n,
|
i18n,
|
||||||
) {
|
);
|
||||||
media_action = Some((i, cur_action));
|
|
||||||
|
if let Some(action) = media_response.inner {
|
||||||
|
media_action = Some((i, action))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let rect = media_response.response.rect;
|
||||||
|
media_infos.push(MediaInfo {
|
||||||
|
url: url.clone(),
|
||||||
|
original_position: rect,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((i, media_action)) = &media_action {
|
if let Some((i, media_action)) = media_action {
|
||||||
action = media_action.to_media_action(
|
action = media_action.into_media_action(
|
||||||
ui.ctx(),
|
ui.ctx(),
|
||||||
medias,
|
medias,
|
||||||
*i,
|
media_infos,
|
||||||
|
i,
|
||||||
img_cache,
|
img_cache,
|
||||||
ImageType::Content(Some((width as u32, height as u32))),
|
ImageType::Content(Some((width as u32, height as u32))),
|
||||||
);
|
);
|
||||||
@@ -106,18 +118,24 @@ enum MediaUIAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MediaUIAction {
|
impl MediaUIAction {
|
||||||
pub fn to_media_action(
|
pub fn into_media_action(
|
||||||
&self,
|
self,
|
||||||
ctx: &egui::Context,
|
ctx: &egui::Context,
|
||||||
medias: &[RenderableMedia],
|
medias: &[RenderableMedia],
|
||||||
|
responses: Vec<MediaInfo>,
|
||||||
selected: usize,
|
selected: usize,
|
||||||
img_cache: &Images,
|
img_cache: &Images,
|
||||||
img_type: ImageType,
|
img_type: ImageType,
|
||||||
) -> Option<MediaAction> {
|
) -> Option<MediaAction> {
|
||||||
match self {
|
match self {
|
||||||
MediaUIAction::Clicked => Some(MediaAction::ViewMedias(
|
// We've clicked on some media, let's package up
|
||||||
medias.iter().map(|m| m.url.to_owned()).collect(),
|
// all of the rendered media responses, and send
|
||||||
)),
|
// them to the ViewMedias action so that our fullscreen
|
||||||
|
// media viewer can smoothly transition from them
|
||||||
|
MediaUIAction::Clicked => Some(MediaAction::ViewMedias(ViewMediaInfo {
|
||||||
|
clicked_index: selected,
|
||||||
|
medias: responses,
|
||||||
|
})),
|
||||||
|
|
||||||
MediaUIAction::Unblur => {
|
MediaUIAction::Unblur => {
|
||||||
let url = &medias[selected].url;
|
let url = &medias[selected].url;
|
||||||
@@ -291,44 +309,44 @@ fn render_media(
|
|||||||
url: &str,
|
url: &str,
|
||||||
height: f32,
|
height: f32,
|
||||||
i18n: &mut Localization,
|
i18n: &mut Localization,
|
||||||
) -> Option<MediaUIAction> {
|
) -> egui::InnerResponse<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() {
|
let resp = render_success_media(ui, url, image, gifs, height, i18n);
|
||||||
Some(MediaUIAction::Clicked)
|
if resp.clicked() {
|
||||||
|
egui::InnerResponse::new(Some(MediaUIAction::Clicked), resp)
|
||||||
} else {
|
} else {
|
||||||
None
|
egui::InnerResponse::new(None, resp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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()) {
|
let resp =
|
||||||
Some(MediaUIAction::DoneLoading)
|
render_blur_transition(ui, url, height, texture, image.get_first_texture());
|
||||||
|
if resp.inner {
|
||||||
|
egui::InnerResponse::new(Some(MediaUIAction::DoneLoading), resp.response)
|
||||||
} else {
|
} else {
|
||||||
None
|
egui::InnerResponse::new(None, resp.response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ObfuscatedTexture::Default => {
|
ObfuscatedTexture::Default => egui::InnerResponse::new(
|
||||||
ui.add(texture_to_image(image.get_first_texture(), height));
|
Some(MediaUIAction::DoneLoading),
|
||||||
Some(MediaUIAction::DoneLoading)
|
ui.add(texture_to_image(image.get_first_texture(), height)),
|
||||||
}
|
),
|
||||||
},
|
},
|
||||||
MediaRenderState::Error(e) => {
|
MediaRenderState::Error(e) => {
|
||||||
ui.allocate_space(egui::vec2(height, height));
|
let resp = ui.allocate_response(egui::vec2(height, height), egui::Sense::click());
|
||||||
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)
|
egui::InnerResponse::new(Some(MediaUIAction::Error), resp)
|
||||||
}
|
}
|
||||||
MediaRenderState::Shimmering(obfuscated_texture) => {
|
MediaRenderState::Shimmering(obfuscated_texture) => match obfuscated_texture {
|
||||||
match obfuscated_texture {
|
ObfuscatedTexture::Blur(texture_handle) => {
|
||||||
ObfuscatedTexture::Blur(texture_handle) => {
|
egui::InnerResponse::new(None, shimmer_blurhash(texture_handle, ui, url, height))
|
||||||
shimmer_blurhash(texture_handle, ui, url, height);
|
|
||||||
}
|
|
||||||
ObfuscatedTexture::Default => {
|
|
||||||
render_default_blur_bg(ui, height, url, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None
|
ObfuscatedTexture::Default => {
|
||||||
}
|
egui::InnerResponse::new(None, render_default_blur_bg(ui, height, url, true))
|
||||||
|
}
|
||||||
|
},
|
||||||
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) => {
|
||||||
@@ -338,13 +356,11 @@ fn render_media(
|
|||||||
ObfuscatedTexture::Default => render_default_blur(ui, i18n, height, url),
|
ObfuscatedTexture::Default => render_default_blur(ui, i18n, height, url),
|
||||||
};
|
};
|
||||||
|
|
||||||
if resp
|
let resp = resp.on_hover_cursor(egui::CursorIcon::PointingHand);
|
||||||
.on_hover_cursor(egui::CursorIcon::PointingHand)
|
if resp.clicked() {
|
||||||
.clicked()
|
egui::InnerResponse::new(Some(MediaUIAction::Unblur), resp)
|
||||||
{
|
|
||||||
Some(MediaUIAction::Unblur)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
egui::InnerResponse::new(None, resp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -442,12 +458,17 @@ fn render_default_blur(
|
|||||||
height: f32,
|
height: f32,
|
||||||
url: &str,
|
url: &str,
|
||||||
) -> egui::Response {
|
) -> egui::Response {
|
||||||
let rect = render_default_blur_bg(ui, height, url, false);
|
let response = render_default_blur_bg(ui, height, url, false);
|
||||||
render_blur_text(ui, i18n, url, rect)
|
render_blur_text(ui, i18n, url, response.rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_default_blur_bg(ui: &mut egui::Ui, height: f32, url: &str, shimmer: bool) -> egui::Rect {
|
fn render_default_blur_bg(
|
||||||
let (rect, _) = ui.allocate_exact_size(egui::vec2(height, height), egui::Sense::click());
|
ui: &mut egui::Ui,
|
||||||
|
height: f32,
|
||||||
|
url: &str,
|
||||||
|
shimmer: bool,
|
||||||
|
) -> egui::Response {
|
||||||
|
let (rect, response) = ui.allocate_exact_size(egui::vec2(height, height), egui::Sense::click());
|
||||||
|
|
||||||
let painter = ui.painter_at(rect);
|
let painter = ui.painter_at(rect);
|
||||||
|
|
||||||
@@ -460,7 +481,7 @@ fn render_default_blur_bg(ui: &mut egui::Ui, height: f32, url: &str, shimmer: bo
|
|||||||
|
|
||||||
painter.rect_filled(rect, CornerRadius::same(8), color);
|
painter.rect_filled(rect, CornerRadius::same(8), color);
|
||||||
|
|
||||||
rect
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum MediaRenderState<'a> {
|
pub enum MediaRenderState<'a> {
|
||||||
@@ -540,24 +561,28 @@ 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,
|
||||||
|
max_height: f32,
|
||||||
|
) -> egui::Response {
|
||||||
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, max_height);
|
||||||
let img = scaled.get_image();
|
let img = scaled.get_image();
|
||||||
show_blurhash_with_alpha(ui, img, cur_alpha);
|
show_blurhash_with_alpha(ui, img, cur_alpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fade_color(alpha: u8) -> egui::Color32 {
|
fn fade_color(alpha: u8) -> egui::Color32 {
|
||||||
Color32::from_rgba_unmultiplied(255, 255, 255, alpha)
|
Color32::from_rgba_unmultiplied(255, 255, 255, alpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_blurhash_with_alpha(ui: &mut egui::Ui, img: Image, alpha: u8) {
|
fn show_blurhash_with_alpha(ui: &mut egui::Ui, img: Image, alpha: u8) -> egui::Response {
|
||||||
let cur_color = fade_color(alpha);
|
let cur_color = fade_color(alpha);
|
||||||
|
|
||||||
let img = img.tint(cur_color);
|
let img = img.tint(cur_color);
|
||||||
|
|
||||||
ui.add(img);
|
ui.add(img)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FinishedTransition = bool;
|
type FinishedTransition = bool;
|
||||||
@@ -569,14 +594,13 @@ fn render_blur_transition(
|
|||||||
max_height: f32,
|
max_height: f32,
|
||||||
blur_texture: &TextureHandle,
|
blur_texture: &TextureHandle,
|
||||||
image_texture: &TextureHandle,
|
image_texture: &TextureHandle,
|
||||||
) -> FinishedTransition {
|
) -> egui::InnerResponse<FinishedTransition> {
|
||||||
let scaled_texture = ScaledTexture::new(image_texture, max_height);
|
let scaled_texture = ScaledTexture::new(image_texture, max_height);
|
||||||
|
|
||||||
let blur_img = texture_to_image(blur_texture, max_height);
|
let blur_img = texture_to_image(blur_texture, max_height);
|
||||||
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);
|
egui::InnerResponse::new(false, show_blurhash_with_alpha(ui, blur_img, cur_alpha))
|
||||||
false
|
|
||||||
}
|
}
|
||||||
BlurTransitionState::FadingBlur => render_blur_fade(ui, url, blur_img, &scaled_texture),
|
BlurTransitionState::FadingBlur => render_blur_fade(ui, url, blur_img, &scaled_texture),
|
||||||
}
|
}
|
||||||
@@ -621,7 +645,7 @@ fn render_blur_fade(
|
|||||||
url: &str,
|
url: &str,
|
||||||
blur_img: Image,
|
blur_img: Image,
|
||||||
image_texture: &ScaledTexture,
|
image_texture: &ScaledTexture,
|
||||||
) -> FinishedTransition {
|
) -> egui::InnerResponse<FinishedTransition> {
|
||||||
let blur_fade_id = ui.id().with(("blur_fade", url));
|
let blur_fade_id = ui.id().with(("blur_fade", url));
|
||||||
|
|
||||||
let cur_alpha = {
|
let cur_alpha = {
|
||||||
@@ -637,12 +661,12 @@ fn render_blur_fade(
|
|||||||
|
|
||||||
let alloc_size = image_texture.scaled_size;
|
let alloc_size = image_texture.scaled_size;
|
||||||
|
|
||||||
let (rect, _) = ui.allocate_exact_size(alloc_size, egui::Sense::hover());
|
let (rect, resp) = ui.allocate_exact_size(alloc_size, egui::Sense::hover());
|
||||||
|
|
||||||
img.paint_at(ui, rect);
|
img.paint_at(ui, rect);
|
||||||
blur_img.paint_at(ui, rect);
|
blur_img.paint_at(ui, rect);
|
||||||
|
|
||||||
cur_alpha == 0
|
egui::InnerResponse::new(cur_alpha == 0, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_blur_transition_state(ctx: &Context, url: &str) -> BlurTransitionState {
|
fn get_blur_transition_state(ctx: &Context, url: &str) -> BlurTransitionState {
|
||||||
|
|||||||
Reference in New Issue
Block a user