Skip to content

Add full-screen ImageViewer widget. Support storing original files and thumbnails in the MediaCache #443

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 102 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
fc81c57
Add imageviewer base
aaravlu Jan 18, 2025
da49f8f
Add image viewer base
aaravlu Jan 18, 2025
bf3106f
Finished image viewer
aaravlu Jan 18, 2025
2345679
Wrong path to get room screen
aaravlu Jan 20, 2025
ade49c8
Finish image viewer base
aaravlu Jan 21, 2025
e141b6b
Remove '.log' File
aaravlu Jan 21, 2025
a3fcb9d
Add doc
aaravlu Jan 22, 2025
c0957ee
Merge branch 'main' into fix271-2-2
aaravlu Jan 22, 2025
e8dfb49
Fix align for image viewer
aaravlu Jan 23, 2025
9cd54b5
Remove '.log' file
aaravlu Jan 23, 2025
e074794
Move related logic from 'app.rs' to 'image_viewer_modal.rs'
aaravlu Jan 23, 2025
2e293a4
Remove '.log' file
aaravlu Jan 23, 2025
4a091f9
Resolve conflicts in 'room_screen.rs'
aaravlu Jan 23, 2025
15eb0a3
Merge branch 'main' into fix327
aaravlu Jan 23, 2025
a519e68
Merge branch 'main' into fix327
aaravlu Jan 24, 2025
094f76f
Resolve conflicts
aaravlu Jan 24, 2025
84ae954
Collate related logic about fetching image
aaravlu Jan 24, 2025
cd9c6a4
Block by makepad because of <Modal>
aaravlu Jan 24, 2025
b39788f
Merge branch 'main' into fix327
aaravlu Jan 26, 2025
9058bfd
Merge branch 'main' into fix327
aaravlu Jan 28, 2025
93d1d90
Resolve conflicts
aaravlu Jan 28, 2025
04d23bc
Rename 'ImageViewer'
aaravlu Jan 30, 2025
ebca9a7
Resolve errors about 'ImageViewer'
aaravlu Jan 30, 2025
adce4cf
Remove 'need_to_draw' field
aaravlu Jan 30, 2025
3a7311e
A bad implmentation
aaravlu Jan 30, 2025
d8ef44a
No change to 'home_screen.rs'
aaravlu Jan 30, 2025
b97ab00
No change to 'shared/mod.rs'
aaravlu Jan 30, 2025
94ad1b7
Merge branch 'main' into fix327
aaravlu Jan 30, 2025
0b926b0
Fix fetching thumbnail for some images
aaravlu Feb 1, 2025
101eb7e
Final fix
aaravlu Feb 1, 2025
ef3b29b
Merge branch 'main' into fix327
aaravlu Feb 1, 2025
585b25e
Format 'default' macro code
aaravlu Feb 4, 2025
f0ffb6c
Peel <Modal> off for image_viewer
aaravlu Feb 4, 2025
4393e6e
Remove methods implemented for Ref
aaravlu Feb 4, 2025
eba8cd2
Add doc
aaravlu Feb 4, 2025
7ec8681
Add doc
aaravlu Feb 4, 2025
2bd35f9
Remove redundant live code in <ImageViewer>
aaravlu Feb 4, 2025
1f29098
Correct doc
aaravlu Feb 4, 2025
7ad4a3a
Private mod 'image_viewer.rs'
aaravlu Feb 4, 2025
901597a
Add doc
aaravlu Feb 4, 2025
5bf5526
Remove bool
aaravlu Feb 5, 2025
226c692
Fix area click and background
aaravlu Feb 5, 2025
87c83e8
Image in timeline 'Size' to 'Smallest'
aaravlu Feb 5, 2025
452e407
Add spin loader for image viewer
aaravlu Feb 11, 2025
b4b6c07
Reduce icon_walk width
aaravlu Feb 11, 2025
3f0ad24
Change spin loader to shader code
aaravlu Feb 11, 2025
15cfc26
Adjust bg color for image viewer
aaravlu Feb 11, 2025
336a97f
Add doc for spin loader
aaravlu Feb 12, 2025
bea98a8
Remove redundant svg files
aaravlu Feb 12, 2025
5781a62
Remove redundant traits for 'MediaCache'
aaravlu Feb 12, 2025
1dbfe3c
Merge branch 'main' into fix327
aaravlu Feb 14, 2025
3bdce7a
Improved dependency for image viewer
aaravlu Feb 14, 2025
fec50ca
Fix first time click one image which is not cached in local disk show…
aaravlu Feb 14, 2025
af012a2
Clean redundant logs
aaravlu Feb 14, 2025
15e660d
Merge branch 'main' into fix327
aaravlu Feb 23, 2025
6db0c3a
Try to remove in
aaravlu Feb 23, 2025
e94358a
Remove 'MediaCache' for imageviewer and redundant 'MatrixRequest' for…
aaravlu Feb 24, 2025
83ac0c1
Fix cannot load small image
aaravlu Feb 24, 2025
1a0d1dc
Fix media format
aaravlu Feb 24, 2025
7d91225
Revert because one mxc_uri to multi image
aaravlu Feb 24, 2025
8207a50
Remove .log file
aaravlu Feb 24, 2025
d95d4b8
Remove redundant 'MatrixRequest'
aaravlu Feb 24, 2025
0908831
Merge branch 'main' into fix327
aaravlu Mar 13, 2025
9714b24
Resolve conflicts and finish the functionality
aaravlu Mar 13, 2025
cc41e6f
Feel free to rename the closure
aaravlu Mar 13, 2025
0051f17
TextOrImage fit revert to 'Smallest'
aaravlu Mar 13, 2025
91b9a9f
Remove Default trait of 'MediaFormatConst'
aaravlu Mar 13, 2025
18fca6d
Not fixed
aaravlu Mar 13, 2025
a5b3f05
Not Fixed
aaravlu Mar 13, 2025
0f3135c
Refactor again and finish image viewer
aaravlu Mar 14, 2025
0c8fe17
Remove 'test' file
aaravlu Mar 14, 2025
3b07fee
Add case of matching mediacache entry
aaravlu Mar 14, 2025
68842c1
Add comments
aaravlu Mar 16, 2025
b072a73
Fix typos
aaravlu Mar 16, 2025
90e1910
Dont close viewer when clicking blank area
aaravlu Mar 17, 2025
f824f99
Fix Closing quickly caused a bug
aaravlu Mar 19, 2025
04ee5f9
Fix a panic on cliking same image in timeline quickly
aaravlu Mar 20, 2025
8cd8aa8
Clarify match in 'fn insert_keys'
aaravlu Mar 20, 2025
d92c318
Fix327-2
aaravlu Mar 25, 2025
d3169e1
Fix dead lock
aaravlu Mar 25, 2025
c7d8f18
Fix hit events
aaravlu Mar 26, 2025
5dc8bf5
Merge branch 'main' into fix327-2
aaravlu Mar 27, 2025
6785e0f
Add docs & resolve conflicts
aaravlu Mar 27, 2025
3e3f5aa
fix(image-viewer): prevent unexpected viewer appearance and refactor …
aaravlu Apr 1, 2025
35968b0
refactor(action-handling): streamline action dispatch and event matching
aaravlu Apr 1, 2025
7c403ba
Reduce clone on fetching
aaravlu Apr 1, 2025
484ad35
refactor(naming): improve variable and field names for media handling
aaravlu Apr 1, 2025
b4caac9
Merge branch 'main' into fix327-2
aaravlu Apr 6, 2025
abc3ec4
Resolve conflicts
aaravlu Apr 6, 2025
c443aff
Delete image data testing log
aaravlu Apr 6, 2025
f383824
Add docs for 'try_get_media_or_fetch'
aaravlu Apr 6, 2025
f1c8b3f
Fix typo
aaravlu Apr 6, 2025
60ef3ff
Update src/shared/text_or_image.rs
aaravlu Apr 8, 2025
4dc2fae
Update src/image_viewer.rs
aaravlu Apr 8, 2025
e29cae4
Resolve conflicts
aaravlu Apr 9, 2025
3a6bd09
Merge branch 'main' into fix327-2
aaravlu Apr 9, 2025
f78eac4
Replace 'Cx::post_action' with 'cx.action'
aaravlu Apr 9, 2025
d98facf
Rusty redesign 'TimelineImageInfo'
aaravlu Apr 9, 2025
837bf11
Merge branch 'main' into fix327-2
aaravlu Apr 10, 2025
095df00
Resolve conflicts
aaravlu Apr 9, 2025
103ea80
Update src/shared/text_or_image.rs
aaravlu Apr 10, 2025
3225300
Change 'TextOrImage' methods' visibility to public
aaravlu Apr 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ live_design! {
use crate::home::home_screen::HomeScreen;
use crate::profile::my_profile_screen::MyProfileScreen;
use crate::verification_modal::VerificationModal;
use crate::image_viewer::ImageViewer;
use crate::login::login_screen::LoginScreen;
use crate::shared::popup_list::PopupList;
use crate::home::new_message_context_menu::*;
use crate::shared::callout_tooltip::CalloutTooltip;


APP_TAB_COLOR = #344054
APP_TAB_COLOR_HOVER = #636e82
APP_TAB_COLOR_ACTIVE = #091
Expand Down Expand Up @@ -131,6 +131,8 @@ live_design! {
// but beneath the verification modal.
new_message_context_menu = <NewMessageContextMenu> { }

image_viewer = <ImageViewer> {}

// message_source_modal = <Modal> {
// content: {
// message_source_modal_inner = <MessageSourceModal> {}
Expand Down Expand Up @@ -174,6 +176,7 @@ impl LiveRegister for App {
crate::home::live_design(cx);
crate::profile::live_design(cx);
crate::login::live_design(cx);
crate::image_viewer::live_design(cx);
}
}

Expand Down
81 changes: 47 additions & 34 deletions src/home/room_screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@ use matrix_sdk::{room::RoomMember, ruma::{
AudioMessageEventContent, CustomEventContent, EmoteMessageEventContent, FileMessageEventContent, FormattedBody, ImageMessageEventContent, KeyVerificationRequestEventContent, LocationMessageEventContent, MessageFormat, MessageType, NoticeMessageEventContent, RoomMessageEventContent, ServerNoticeMessageEventContent, TextMessageEventContent, VideoMessageEventContent
}, ImageInfo, MediaSource
},
sticker::StickerEventContent, Mentions}, matrix_uri::MatrixId, uint, EventId, MatrixToUri, MatrixUri, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedMxcUri, OwnedRoomId
sticker::StickerEventContent, Mentions}, matrix_uri::MatrixId, uint, EventId, MatrixToUri, MatrixUri, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId
}, OwnedServerName};
use matrix_sdk_ui::timeline::{
self, EventTimelineItem, InReplyToDetails, MemberProfileChange, RepliedToInfo, RoomMembershipChange, TimelineDetails, TimelineEventItemId, TimelineItem, TimelineItemContent, TimelineItemKind, VirtualTimelineItem
};
use robius_location::Coordinates;

use crate::{
avatar_cache, event_preview::{body_of_timeline_item, text_preview_of_member_profile_change, text_preview_of_other_state, text_preview_of_redacted_message, text_preview_of_room_membership_change, text_preview_of_timeline_item}, home::loading_pane::{LoadingPaneState, LoadingPaneWidgetExt}, location::{get_latest_location, init_location_subscriber, request_location_update, LocationAction, LocationRequest, LocationUpdate}, media_cache::{MediaCache, MediaCacheEntry}, profile::{
avatar_cache, event_preview::{body_of_timeline_item, text_preview_of_member_profile_change, text_preview_of_other_state, text_preview_of_redacted_message, text_preview_of_room_membership_change, text_preview_of_timeline_item}, home::loading_pane::{LoadingPaneState, LoadingPaneWidgetExt}, image_viewer::ImageViewerAction, location::{get_latest_location, init_location_subscriber, request_location_update, LocationAction, LocationRequest, LocationUpdate}, media_cache::{image_viewer_insert_into_cache, insert_into_cache, MediaCache, MediaCacheEntry}, profile::{
user_profile::{AvatarState, ShowUserProfileAction, UserProfile, UserProfileAndRoomId, UserProfilePaneInfo, UserProfileSlidingPaneRef, UserProfileSlidingPaneWidgetExt},
user_profile_cache,
}, shared::{
avatar::AvatarWidgetRefExt, callout_tooltip::TooltipAction, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt, RobrixHtmlLinkAction}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, popup_list::enqueue_popup_notification, text_or_image::{TextOrImageRef, TextOrImageWidgetRefExt}, typing_animation::TypingAnimationWidgetExt
}, sliding_sync::{get_client, submit_async_request, take_timeline_endpoints, BackwardsPaginateUntilEventRequest, MatrixRequest, PaginationDirection, TimelineRequestSender, UserPowerLevels}, utils::{self, unix_time_millis_to_datetime, ImageFormat, MEDIA_THUMBNAIL_FORMAT}
avatar::AvatarWidgetRefExt, callout_tooltip::TooltipAction, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt, RobrixHtmlLinkAction}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, popup_list::enqueue_popup_notification, text_or_image::{TextOrImageAction, TextOrImageRef, TextOrImageWidgetRefExt, TimelineImageInfo}, typing_animation::TypingAnimationWidgetExt
}, sliding_sync::{get_client, submit_async_request, take_timeline_endpoints, BackwardsPaginateUntilEventRequest, MatrixRequest, PaginationDirection, TimelineRequestSender, UserPowerLevels}, utils::{self, unix_time_millis_to_datetime, ImageFormat}
};
use crate::home::event_reaction_list::ReactionListWidgetRefExt;
use crate::home::room_read_receipt::AvatarRowWidgetRefExt;
Expand Down Expand Up @@ -1021,6 +1021,14 @@ impl Widget for RoomScreen {
for action in actions {
// Handle the highlight animation.
let Some(tl) = self.tl_state.as_mut() else { return };

if let Some(TextOrImageAction::Click(mxc_uri)) = action.downcast_ref() {
if let MediaCacheEntry::Loaded(image_viewer_image_data) = tl.media_cache.try_get_media_or_fetch(mxc_uri, image_viewer_insert_into_cache) {
cx.action(ImageViewerAction::Show);
cx.action(ImageViewerAction::SetImage(image_viewer_image_data));
}
}

if let MessageHighlightAnimationState::Pending { item_id } = tl.message_highlight_animation_state {
if portal_list.smooth_scroll_reached(actions) {
cx.widget_action(
Expand Down Expand Up @@ -3501,25 +3509,48 @@ fn populate_image_message_content(

let mut fully_drawn = false;

// A closure that fetches and shows the image from the given `mxc_uri`,
// marking it as fully drawn if the image was available.
let mut fetch_and_show_image_uri = |cx: &mut Cx2d, mxc_uri: OwnedMxcUri, image_info: Option<&ImageInfo>| {
match media_cache.try_get_media_or_fetch(mxc_uri.clone(), MEDIA_THUMBNAIL_FORMAT.into()) {
(MediaCacheEntry::Loaded(data), _media_format) => {
let mut fetch_and_handle_image = |cx: &mut Cx2d, original_source: &MediaSource, image_info: Option<&ImageInfo>| {
let thumbnail_mxc_uri = image_info.and_then(|info|{info.thumbnail_source.as_ref()}).and_then(|source|{
match source {
MediaSource::Encrypted(encrypted) => {
text_or_image_ref.show_text(
cx,
format!("{body}\n\n[TODO] fetch encrypted image at {:?}", encrypted.url)
);
None
}
MediaSource::Plain(mxc_uri) => {
Some(mxc_uri)
}
}
});

let MediaSource::Plain(original_mxc_uri) = original_source else { return };

let timeline_mxc_uri = thumbnail_mxc_uri.unwrap_or(original_mxc_uri);

media_cache.image_set_keys(original_mxc_uri, thumbnail_mxc_uri);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, should we still call image_set_keys() if the thumbnail_mxc_uri is None?

Currently, if thumbnail_mxc_uri is None, the mxc_uri_for_timeline is the same as the original_mxc_uri. Why would we need to set the keys if we don't have two different URIs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, should we still call image_set_keys() if the thumbnail_mxc_uri is None?

Of course.

Why would we need to set the keys if we don't have two different URIs?

Since we need to keep the correct of data structure (BTreeMap)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, should we still call image_set_keys() if the thumbnail_mxc_uri is None?

Of course.

okay, let me rephrase. Why is that necessary? what benefit does it provide?

Why would we need to set the keys if we don't have two different URIs?

Since we need to keep the correct of data structure (BTreeMap)

Please elaborate, because I don't understand why this is necessary. In my mind, if the URIs are the same, we don't need to do this separate "set_keys" step, since the single URI will be inserted into the cache later once the media content is ready.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kindly answer this

Copy link
Contributor Author

@aaravlu aaravlu Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the URIs are the same, we don't need to do this separate "set_keys" step.

Yeah, it is what i did actually.

Note set_keys fuction, only if the entry is vacant, we insert.

If occupied, it can confirm they are same URIs, we dont insert.


match media_cache.try_get_media_or_fetch(timeline_mxc_uri, insert_into_cache) {
MediaCacheEntry::Loaded(timeline_image_data) => {
let timeline_image_info = TimelineImageInfo {
timeline_image_data: timeline_image_data.clone(),
original_mxc_uri: original_mxc_uri.clone()
};
text_or_image_ref.set_status_to_image(timeline_image_info);
let show_image_result = text_or_image_ref.show_image(cx, |cx, img| {
utils::load_png_or_jpg(&img, cx, &data)
utils::load_png_or_jpg(&img, cx, &timeline_image_data)
.map(|()| img.size_in_pixels(cx).unwrap_or_default())
});
if let Err(e) = show_image_result {
let err_str = format!("{body}\n\nFailed to display image: {e:?}");
error!("{err_str}");
text_or_image_ref.show_text(cx, &err_str);
}

// We're done drawing the image, so mark it as fully drawn.
fully_drawn = true;
}
(MediaCacheEntry::Requested, _media_format) => {
MediaCacheEntry::Requested => {
if let Some(image_info) = image_info {
if let (Some(ref blurhash), Some(width), Some(height)) = (image_info.blurhash.clone(), image_info.width, image_info.height) {
let show_image_result = text_or_image_ref.show_image(cx, |cx, img| {
Expand All @@ -3543,38 +3574,20 @@ fn populate_image_message_content(
}
fully_drawn = false;
}
(MediaCacheEntry::Failed, _media_format) => {
MediaCacheEntry::Failed => {
text_or_image_ref
.show_text(cx, format!("{body}\n\nFailed to fetch image from {:?}", mxc_uri));
.show_text(cx, format!("{body}\n\nFailed to fetch image from {:?}", timeline_mxc_uri));
// For now, we consider this as being "complete". In the future, we could support
// retrying to fetch thumbnail of the image on a user click/tap.
fully_drawn = true;
}
}
};

let mut fetch_and_show_media_source = |cx: &mut Cx2d, media_source: MediaSource, image_info: Option<&ImageInfo>| {
match media_source {
MediaSource::Encrypted(encrypted) => {
// We consider this as "fully drawn" since we don't yet support encryption.
text_or_image_ref.show_text(
cx,
format!("{body}\n\n[TODO] fetch encrypted image at {:?}", encrypted.url)
);
},
MediaSource::Plain(mxc_uri) => {
fetch_and_show_image_uri(cx, mxc_uri, image_info)
}
_ => { }
}
};

match image_info_source {
Some((image_info, original_source)) => {
// Use the provided thumbnail URI if it exists; otherwise use the original URI.
let media_source = image_info.clone()
.and_then(|image_info| image_info.thumbnail_source)
.unwrap_or(original_source);
fetch_and_show_media_source(cx, media_source, image_info.as_ref());
fetch_and_handle_image(cx, &original_source, image_info.as_ref());
}
None => {
text_or_image_ref.show_text(cx, "{body}\n\nImage message had no source URL.");
Expand Down
121 changes: 121 additions & 0 deletions src/image_viewer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use std::sync::Arc;
use crate::utils;

use makepad_widgets::*;

live_design! {
use link::theme::*;
use link::shaders::*;
use link::widgets::*;

use crate::shared::styles::*;
use crate::shared::icon_button::RobrixIconButton;

pub ImageViewer = {{ImageViewer}} {
visible: false
width: Fill, height: Fill
align: {x: 0.5, y: 0.5}
spacing: 12
flow: Overlay
show_bg: true
draw_bg: {
color: (COLOR_IMAGE_VIEWER_BG)
}

<View> {
align: {x: 1.0, y: 0.0}
width: Fill, height: Fill
close_button = <RobrixIconButton> {
padding: {left: 15, right: 15}
draw_icon: {
svg_file: (ICON_CLOSE)
color: (COLOR_CLOSE),
}
icon_walk: {width: 25, height: 25, margin: {left: -1, right: -1} }

draw_bg: {
border_color: (COLOR_CLOSE_BG),
color: (COLOR_CLOSE_BG) // light red
}
}
}

image_view = <View> {
padding: {top: 40, bottom: 30, left: 20, right: 20}
flow: Overlay
align: {x: 0.5, y: 0.5}
width: Fill, height: Fill,
image = <Image> {
width: Fill, height: Fill,
fit: Smallest,
}
}
}
}

#[derive(Live, LiveHook, Widget)]
pub struct ImageViewer {
#[deref]
view: View,
}

/// Actions handled by the `ImageViewer` widget.
#[derive(Clone, Debug, DefaultNone)]
pub enum ImageViewerAction {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, on second thought, it might be better to completely redesign this action like so:

/// Actions handled by the `ImageViewer` widget.
#[derive(Clone, Debug, DefaultNone)]
pub enum ImageViewerAction {
    /// Make the ImageViewer widget visible.
    Show,
    /// Set the image being displayed by the ImageViewer.
    SetImage(Arc<[u8]>),
    None,
}

Then, when you want to show the image viewer for the first time, you emit two actions:

  1. SetImage
  2. Show

You'd also need to remove the if visible check when handling the SetImage action.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's OK but if we design it as this, we must post two actions in RoomScreen's handle_event:

if let Some(TextOrImageAction::Click(mxc_uri)) = action.downcast_ref() {
                    if let MediaCacheEntry::Loaded(image_viewer_image_data) = tl.media_cache.try_get_media_or_fetch(mxc_uri, image_viewer_insert_into_cache) {
                        cx.action(ImageViewerAction::Show);
                        cx.action(ImageViewerAction::SetImage(image_viewer_image_data));
                    }
                }

/// Make the ImageViewer widget visible.
Show,
/// Set the image being displayed by the ImageViewer.
SetImage(Arc<[u8]>),
None,
}

impl Widget for ImageViewer {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
self.match_event(cx, event);
self.view.handle_event(cx, event, scope);
}

fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
self.view.draw_walk(cx, scope, walk)
}
}
impl MatchEvent for ImageViewer {
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
if self.view.button(id!(close_button)).clicked(actions) {
self.close(cx);
}

for action in actions {
match action.downcast_ref::<ImageViewerAction>() {
Some(ImageViewerAction::Show) => {
self.open(cx);
}
Some(ImageViewerAction::SetImage(data)) => {
self.load_with_data(cx, data);
}
_ => {}
}
}
}
}

impl ImageViewer {
fn open(&mut self, cx: &mut Cx) {
self.visible = true;
self.redraw(cx);
}
fn close(&mut self, cx: &mut Cx) {
self.visible = false;
self.view.image(id!(image_view.image)).set_texture(cx, None);
self.redraw(cx);
}
fn load_with_data(&mut self, cx: &mut Cx, data: &[u8]) {
let image = self.view.image(id!(image_view.image));

if let Err(e) = utils::load_png_or_jpg(&image, cx, data) {
log!("Error to load image: {e}");
} else {
self.view.redraw(cx);
}
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub mod home;
mod profile;
/// A modal/dialog popup for interactive verification of users/devices.
mod verification_modal;
// A image viewer for viewing images.
mod image_viewer;
/// Shared UI components.
pub mod shared;
/// Generating text previews of timeline events/messages.
Expand Down
Loading