Skip to content
Open
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
e51b874
move MEDIA_CACHE to global
alanpoon Jul 10, 2025
f323303
Merge branch 'main' into image_viewer#327
alanpoon Jul 15, 2025
e1d2aaf
some testing
alanpoon Jul 16, 2025
e4fe2c8
Merge branch 'main' into image_viewer#327
alanpoon Aug 6, 2025
440bde3
changed default image
alanpoon Aug 8, 2025
d029bbc
Merge branch 'main' into image_viewer#327
alanpoon Aug 27, 2025
ddd04b9
Image Viewer with zoom and pann
alanpoon Sep 1, 2025
d641657
fix spelling
alanpoon Sep 1, 2025
9fe9ddc
simplify code for populate_image_modal
alanpoon Sep 2, 2025
27ebbd7
Deallocate image buffer
alanpoon Sep 5, 2025
92d78b3
Merge branch 'main' into image_viewer#327
alanpoon Sep 11, 2025
de263cc
remove default_image
alanpoon Sep 11, 2025
43137a1
Merge branch 'main' into image_viewer#327
alanpoon Sep 15, 2025
03e4120
Merge branch 'main' into image_viewer#327
alanpoon Oct 18, 2025
c9f2484
code improvement image_viewer
alanpoon Oct 19, 2025
81cf00a
Merge branch 'main' into image_viewer#327
alanpoon Oct 19, 2025
ec774a0
update makepad
alanpoon Oct 19, 2025
d85a312
fix
alanpoon Oct 20, 2025
09b9772
image_viewer_modal improvement
alanpoon Oct 21, 2025
8269f0a
remove global singleton for image_viewer_modal
alanpoon Oct 21, 2025
6ea6026
Merge branch 'main' into image_viewer#327
alanpoon Oct 21, 2025
4e4f34d
Friendly Error improvement
alanpoon Oct 21, 2025
ee6b8e7
change to more friendly error message
alanpoon Oct 21, 2025
0e10331
ImageViewer improvement
alanpoon Oct 22, 2025
4febe14
Minor code improvement
alanpoon Oct 22, 2025
4efdf4b
format improvement
alanpoon Oct 22, 2025
85cfb9b
update makepad version
alanpoon Oct 22, 2025
5188571
update mouse cursor
alanpoon Oct 22, 2025
c3cfc21
timeout error update
alanpoon Oct 22, 2025
932c318
ImageViewerModalAction
alanpoon Oct 23, 2025
9bd6dbb
Merge branch 'image_viewer#327' of https://github.com/alanpoon/robrix…
alanpoon Oct 23, 2025
048c3d3
Change to rotated_image
alanpoon Oct 30, 2025
816fae5
Merge branch 'main' into image_viewer#327
alanpoon Oct 30, 2025
f0cecc1
Added Image detail for Image Viewer
alanpoon Oct 31, 2025
2ca47a3
move image_buffer to background thread
alanpoon Oct 31, 2025
082de67
Fix image viewer size for thumbnail image
alanpoon Nov 3, 2025
bdf8cfc
Merge branch 'main' into image_viewer#327
alanpoon Nov 3, 2025
68179c5
added room_image_viewer_footer
alanpoon Nov 4, 2025
e55568c
fix clippy
alanpoon Nov 4, 2025
30ca041
Fix remove_cache_entry
alanpoon Nov 7, 2025
1a35f45
removed ImageViewer opening LinkPreview's image
alanpoon Nov 7, 2025
964b798
combine into room_image_viewer.rs
alanpoon Nov 7, 2025
1136030
revert LinkPreview
alanpoon Nov 7, 2025
897952c
minor code improvement
alanpoon Nov 7, 2025
571c888
Merge branch 'main' into image_viewer#327
alanpoon Nov 7, 2025
b43a96e
remove get_texture_and_size
alanpoon Nov 12, 2025
b4f81ab
Merge branch 'main' into image_viewer#327
alanpoon Nov 12, 2025
dd6555b
fix clippy
alanpoon Nov 12, 2025
9226992
fix grammar issues
alanpoon Nov 12, 2025
784a08b
Merge branch 'main' into image_viewer#327
alanpoon Nov 13, 2025
89d01b6
revert import statement
alanpoon Nov 13, 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
6 changes: 6 additions & 0 deletions resources/icons/rotate-anti-clockwise.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions resources/icons/rotate-clockwise.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
121 changes: 115 additions & 6 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,15 @@ use makepad_widgets::{makepad_micro_serde::*, *};
use matrix_sdk::ruma::{OwnedRoomId, RoomId};
use crate::{
avatar_cache::clear_avatar_cache, home::{
main_desktop_ui::MainDesktopUiAction, new_message_context_menu::NewMessageContextMenuWidgetRefExt, room_screen::{clear_timeline_states, MessageAction}, rooms_list::{clear_all_invited_rooms, enqueue_rooms_list_update, RoomsListAction, RoomsListRef, RoomsListUpdate}
main_desktop_ui::MainDesktopUiAction, new_message_context_menu::NewMessageContextMenuWidgetRefExt, room_image_viewer_detail::RoomImageViewerDetailWidgetRefExt, room_image_viewer_footer::RoomImageViewerFooterWidgetRefExt, room_screen::{MessageAction, clear_timeline_states}, rooms_list::{RoomsListAction, RoomsListRef, RoomsListUpdate, clear_all_invited_rooms, enqueue_rooms_list_update}
}, join_leave_room_modal::{
JoinLeaveModalKind, JoinLeaveRoomModalAction, JoinLeaveRoomModalWidgetRefExt
}, login::login_screen::LoginAction, logout::logout_confirm_modal::{LogoutAction, LogoutConfirmModalAction, LogoutConfirmModalWidgetRefExt}, persistence, profile::user_profile_cache::clear_user_profile_cache, room::BasicRoomDetails, shared::callout_tooltip::{
}, login::login_screen::LoginAction, logout::logout_confirm_modal::{LogoutAction, LogoutConfirmModalAction, LogoutConfirmModalWidgetRefExt}, persistence, profile::user_profile_cache::clear_user_profile_cache, room::BasicRoomDetails, shared::{callout_tooltip::{
CalloutTooltipOptions,
CalloutTooltipWidgetRefExt,
TooltipAction,
}, sliding_sync::current_user_id, utils::{
room_name_or_id,
OwnedRoomIdRon,
}, image_viewer::{ImageViewerAction, ImageViewerWidgetRefExt, LoadState}}, sliding_sync::current_user_id, utils::{
OwnedRoomIdRon, image_viewer_error_to_string, room_name_or_id
}, verification::VerificationAction, verification_modal::{
VerificationModalAction,
VerificationModalWidgetRefExt,
Expand All @@ -40,6 +39,9 @@ live_design! {
use crate::shared::popup_list::*;
use crate::home::new_message_context_menu::*;
use crate::shared::callout_tooltip::CalloutTooltip;
use crate::shared::image_viewer::ImageViewer;
use crate::home::room_image_viewer_detail::RoomImageViewerDetail;
use crate::home::room_image_viewer_footer::RoomImageViewerFooter;
use link::tsp_link::TspVerificationModal;


Expand Down Expand Up @@ -96,7 +98,32 @@ live_design! {
login_screen = <LoginScreen> {}
}

<PopupList> {}
image_viewer = <Modal> {
content: {
width: Fill, height: Fill,
flow: Down
show_bg: true
draw_bg: {
color: #000
}

<View> {
width: Fill, height: Fill,
flow: Overlay
image_viewer_inner = <ImageViewer> {
align: {x: 0.5, y: 0.5}
padding: {bottom: 0}
}
image_detail = <RoomImageViewerDetail> {
width: Fill, height: Fill,
}
}

footer = <RoomImageViewerFooter> {}
}
}

popup_list = <PopupList> {}

// Context menus should be shown in front of other UI elements,
// but behind verification modals.
Expand Down Expand Up @@ -388,6 +415,10 @@ impl MatchEvent for App {
_ => {}
}

if self.handle_image_viewer_action(cx, action) {
continue;
}

// `VerificationAction`s come from a background thread, so they are NOT widget actions.
// Therefore, we cannot use `as_widget_action().cast()` to match them.
//
Expand Down Expand Up @@ -481,7 +512,38 @@ impl AppMain for App {
}
}
}

// Ensure all draw events are handled on the main UI thread regardless of modal consuming the events.
if let Event::Draw(_) = event {
let scope = &mut Scope::with_data(&mut self.app_state);
self.ui.handle_event(cx, event, scope);
return;
}

// If the image viewer modal is really opened, handles non-Draw events using the modal.
let image_viewer_modal = self.ui.modal(ids!(image_viewer));
if image_viewer_modal.is_open() && image_viewer_modal.area().rect(cx).size.y > 0.0 {
let scope = &mut Scope::with_data(&mut self.app_state);
self.ui
.view(ids!(popup_list))
.handle_event(cx, event, scope);
self.ui
.modal(ids!(image_viewer))
.handle_event(cx, event, scope);
// Pass the Signal event to the underlying room screen, so as to populate the full image in the image viewer.
if let Event::Signal = event {
self.ui.handle_event(cx, event, scope);
}
if let Event::Actions(actions) = event {
for action in actions {
if self.handle_image_viewer_action(cx, action) {
continue;
}
}
}
return;
}
Copy link
Member

Choose a reason for hiding this comment

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

why is this one modal so uniquely different than all the other modals? I would only expect you to have to handle opening and closing the ImageViewer modal here, just like all other modals.

I don't understand why you need to explicitly pass events to the modal; this is almost certainly incorrect and will result in the event being delivered to the modal twice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I forgot to comment on the rationale. Modal widget is not behaving as expected with the events being propagated to underneath layer. This results in other image being clicked while the image viewer modal is opened.

Copy link
Member

Choose a reason for hiding this comment

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

I see, that's concerning. Can you open an issue on the Makepad repo with a minimal example that reproduces this, and then link to it in a code comment here? Thanks.

I'll try to dig into modal issues this week to see if we can fix it there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I created a branch at https://github.com/alanpoon/makepad/tree/modal_underneath_test to test modal. The event does not propagate to underneath layer. But it is happening to Robrix. Not sure why.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After testing, this line

self.ui.view(ids!(login_screen_view)).set_visible(cx, show_login);
is the culprit as to why event propagates to the underneath layer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Created a tracking issue here: #626. Hope to be settle this issue after this PR is merged.

Copy link
Member

Choose a reason for hiding this comment

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

ok ... strange. Perhaps we need to redesign the LoginScreen to be something other than just a simple overlay view.

However, I don't understand why the existence of the LoginScreen (or whether it's currently visible) would impact how events are delivered to other widgets in that same overlay view (within the app body). I don't think that's actually the problem, because all the other modals in that same overlay view work just fine.

Copy link
Member

Choose a reason for hiding this comment

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

see one of my latest comments — i think if you just put the ImageViewer in a Modal (like with the verification modal or other modals), it will work as expected. I've never had any problems with modals in the top-level app not working as intended.


// Forward events to the MatchEvent trait implementation.
self.match_event(cx, event);
let scope = &mut Scope::with_data(&mut self.app_state);
Expand Down Expand Up @@ -590,6 +652,53 @@ impl App {
closure(cx);
}
}

/// Handles actions for the image viewer.
/// Returns a boolean, is true continues the actions for loop.
fn handle_image_viewer_action(&mut self, cx: &mut Cx, action: &Action) -> bool {

match action.downcast_ref() {
Some(ImageViewerAction::Show(load_state)) => {
match load_state {
LoadState::Loading(texture, image_size) => {
self.ui.modal(ids!(image_viewer)).open(cx);
self.ui.image_viewer(ids!(image_viewer_inner)).reset(cx);
self.ui.room_image_viewer_footer(ids!(footer)).show_loading(cx);
self.ui.view(ids!(footer)).apply_over(cx, live!{
height: 50
});
self.ui.image_viewer(ids!(image_viewer_inner)).display_using_texture(cx, texture.as_ref().clone(), image_size);
}
LoadState::Loaded(image_bytes) => {
self.ui.modal(ids!(image_viewer)).open(cx);
self.ui.image_viewer(ids!(image_viewer_inner)).display_using_background_thread(cx, image_bytes);
}
LoadState::FinishedBackgroundDecoding => {
self.ui.room_image_viewer_footer(ids!(footer)).hide(cx);
// Collapse the footer
self.ui.view(ids!(footer)).apply_over(cx, live!{
height: 0
});
}
LoadState::Error(error) => {
if self.ui.modal(ids!(image_viewer)).is_open() {
self.ui.room_image_viewer_footer(ids!(footer)).show_error(cx, image_viewer_error_to_string(error));
self.ui.view(ids!(footer)).apply_over(cx, live!{
height: 50
});
}
}
}
true
}
Some(ImageViewerAction::Hide) => {
self.ui.modal(ids!(image_viewer)).close(cx);
self.ui.room_image_viewer_detail(ids!(image_detail)).reset_state(cx);
true
}
_ => false
}
}
}

/// App-wide state that is stored persistently across multiple app runs
Expand Down
10 changes: 9 additions & 1 deletion src/home/link_preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,14 @@ impl LinkPreviewRef {
}
}

/// Gets the children of the LinkPreview widget.
pub fn get_children(&self) -> Vec<ViewRef> {
if let Some(inner) = self.borrow() {
return inner.children.clone();
}
Vec::new()
}

/// Shows the collapsible button for the link preview.
///
/// This function is usually called when the link preview is updated.
Expand Down Expand Up @@ -624,7 +632,7 @@ fn insert_into_cache(

if let Some(sender) = update_sender {
// Reuse TimelineUpdate MediaFetched to trigger redraw in the timeline.
let _ = sender.send(TimelineUpdate::MediaFetched);
let _ = sender.send(TimelineUpdate::MediaFetched(None));
}
SignalToUI::set_ui_signal();
}
Expand Down
4 changes: 4 additions & 0 deletions src/home/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub mod welcome_screen;
pub mod event_reaction_list;
pub mod new_message_context_menu;
pub mod link_preview;
pub mod room_image_viewer_detail;
pub mod room_image_viewer_footer;

pub fn live_design(cx: &mut Cx) {
search_messages::live_design(cx);
Expand All @@ -46,4 +48,6 @@ pub fn live_design(cx: &mut Cx) {
light_themed_dock::live_design(cx);
event_reaction_list::live_design(cx);
link_preview::live_design(cx);
room_image_viewer_detail::live_design(cx);
room_image_viewer_footer::live_design(cx);
}
Loading
Loading