diff --git a/resources/icons/zoom_in.svg b/resources/icons/zoom_in.svg
new file mode 100644
index 00000000..12aa6d08
--- /dev/null
+++ b/resources/icons/zoom_in.svg
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/resources/icons/zoom_out.svg b/resources/icons/zoom_out.svg
new file mode 100644
index 00000000..39419cae
--- /dev/null
+++ b/resources/icons/zoom_out.svg
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/src/app.rs b/src/app.rs
index 411feca3..714ee12a 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -461,10 +461,6 @@ fn clear_all_app_state(cx: &mut Cx) {
impl AppMain for App {
fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
- // if let Event::WindowGeomChange(geom) = event {
- // log!("App::handle_event(): Window geometry changed: {:?}", geom);
- // }
-
if let Event::Shutdown = event {
let window_ref = self.ui.window(ids!(main_window));
if let Err(e) = persistence::save_window_state(window_ref, cx) {
diff --git a/src/home/room_read_receipt.rs b/src/home/room_read_receipt.rs
index 14c38660..01da2ded 100644
--- a/src/home/room_read_receipt.rs
+++ b/src/home/room_read_receipt.rs
@@ -181,8 +181,14 @@ impl AvatarRow {
self.buttons.iter_mut().zip(receipts_map.iter().rev())
{
if !*drawn {
- let (_, drawn_status) =
- avatar_ref.set_avatar_and_get_username(cx, room_id, user_id, None, event_id);
+ let (_, drawn_status) = avatar_ref.set_avatar_and_get_username(
+ cx,
+ room_id,
+ user_id,
+ None,
+ event_id,
+ true,
+ );
*drawn = drawn_status;
}
}
diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs
index 31aa813a..897288c5 100644
--- a/src/home/room_screen.rs
+++ b/src/home/room_screen.rs
@@ -2907,6 +2907,7 @@ fn populate_message_view(
event_tl_item.sender(),
Some(event_tl_item.sender_profile()),
event_tl_item.event_id(),
+ true,
);
// Prepend a "* " to the emote body, as suggested by the Matrix spec.
@@ -3195,6 +3196,7 @@ fn populate_message_view(
event_tl_item.sender(),
Some(event_tl_item.sender_profile()),
event_tl_item.event_id(),
+ true,
)
);
if is_notice {
@@ -3664,6 +3666,7 @@ fn draw_replied_to_message(
&replied_to_event.sender,
Some(&replied_to_event.sender_profile),
Some(in_reply_to_details.event_id.as_ref()),
+ true,
);
fully_drawn = is_avatar_fully_drawn;
@@ -3996,6 +3999,7 @@ fn populate_small_state_event(
event_tl_item.sender(),
Some(event_tl_item.sender_profile()),
event_tl_item.event_id(),
+ true,
);
// Draw the timestamp as part of the profile.
if let Some(dt) = unix_time_millis_to_datetime(event_tl_item.timestamp()) {
diff --git a/src/room/room_input_bar.rs b/src/room/room_input_bar.rs
index abf6ab4a..982f430e 100644
--- a/src/room/room_input_bar.rs
+++ b/src/room/room_input_bar.rs
@@ -371,6 +371,7 @@ impl RoomInputBar {
replying_to.0.sender(),
Some(replying_to.0.sender_profile()),
replying_to.0.event_id(),
+ true,
);
replying_preview
diff --git a/src/shared/avatar.rs b/src/shared/avatar.rs
index b6abafa4..f4cf2172 100644
--- a/src/shared/avatar.rs
+++ b/src/shared/avatar.rs
@@ -37,7 +37,6 @@ live_design! {
align: { x: 0.5, y: 0.5 }
// the text_view and img_view are overlaid on top of each other.
flow: Overlay,
- cursor: Hand,
text_view = {
visible: true,
@@ -94,6 +93,8 @@ live_design! {
pub struct Avatar {
#[deref] view: View,
+ /// Information about the user profile being shown in this Avatar.
+ /// If `Some`, this Avatar will respond to clicks/taps.
#[rust] info: Option,
}
@@ -101,9 +102,7 @@ impl Widget for Avatar {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
self.view.handle_event(cx, event, scope);
- let Some(info) = self.info.clone() else {
- return;
- };
+ let Some(info) = self.info.clone() else { return };
let area = self.view.area();
let widget_uid = self.widget_uid();
match event.hits(cx, area) {
@@ -152,16 +151,19 @@ impl Avatar {
info: Option,
username: T,
) {
- self.info = info.map(|AvatarTextInfo { user_id, username, room_id }|
- UserProfileAndRoomId {
+ if let Some(AvatarTextInfo { user_id, username, room_id }) = info {
+ self.info = Some(UserProfileAndRoomId {
user_profile: UserProfile {
user_id,
username,
avatar_state: AvatarState::Unknown,
},
room_id,
- }
- );
+ });
+ self.view.apply_over(cx, live!{ cursor: Hand });
+ } else {
+ self.view.apply_over(cx, live!{ cursor: Default });
+ }
self.set_text(cx, username.as_ref());
// Apply background color if provided
@@ -200,16 +202,19 @@ impl Avatar {
self.view(ids!(img_view)).set_visible(cx, true);
self.view(ids!(text_view)).set_visible(cx, false);
- self.info = info.map(|AvatarImageInfo { user_id, username, room_id, img_data }|
- UserProfileAndRoomId {
+ if let Some(AvatarImageInfo { user_id, username, room_id, img_data }) = info {
+ self.info = Some(UserProfileAndRoomId {
user_profile: UserProfile {
user_id,
username,
avatar_state: AvatarState::Loaded(img_data),
},
room_id,
- }
- );
+ });
+ self.view.apply_over(cx, live!{ cursor: Hand });
+ } else {
+ self.view.apply_over(cx, live!{ cursor: Default });
+ }
}
res
}
@@ -244,6 +249,8 @@ impl Avatar {
/// our user profile cache , then the `username` and `avatar` will be the user ID
/// and the first character of that user ID, respectively.
///
+ /// If `is_clickable` is `true`, this Avatar will respond to clicks.
+ ///
/// ## Return
/// Returns a tuple of:
/// 1. The displayable username that should be used to populate the username field.
@@ -256,6 +263,7 @@ impl Avatar {
avatar_user_id: &UserId,
avatar_profile_opt: Option<&TimelineDetails>,
event_id: Option<&EventId>,
+ is_clickable: bool,
) -> (String, bool) {
// Get the display name and avatar URL from the user's profile, if available,
// or if the profile isn't ready, fall back to querying our user profile cache.
@@ -330,12 +338,12 @@ impl Avatar {
.and_then(|data| {
self.show_image(
cx,
- Some((
+ is_clickable.then(|| AvatarImageInfo::from((
avatar_user_id.to_owned(),
username_opt.clone(),
room_id.to_owned(),
- data.clone()).into(),
- ),
+ data.clone()
+ ))),
|cx, img| utils::load_png_or_jpg(&img, cx, &data),
)
.ok()
@@ -344,7 +352,11 @@ impl Avatar {
self.show_text(
cx,
None,
- Some((avatar_user_id.to_owned(), username_opt, room_id.to_owned()).into()),
+ is_clickable.then(|| AvatarTextInfo::from((
+ avatar_user_id.to_owned(),
+ username_opt,
+ room_id.to_owned(),
+ ))),
&username,
)
});
@@ -399,9 +411,17 @@ impl AvatarRef {
avatar_user_id: &UserId,
avatar_profile_opt: Option<&TimelineDetails>,
event_id: Option<&EventId>,
+ is_clickable: bool,
) -> (String, bool) {
if let Some(mut inner) = self.borrow_mut() {
- inner.set_avatar_and_get_username(cx, room_id, avatar_user_id, avatar_profile_opt, event_id)
+ inner.set_avatar_and_get_username(
+ cx,
+ room_id,
+ avatar_user_id,
+ avatar_profile_opt,
+ event_id,
+ is_clickable,
+ )
} else {
(avatar_user_id.to_string(), false)
}
diff --git a/src/shared/image_viewer.rs b/src/shared/image_viewer.rs
index e9fc7f13..68f6747b 100644
--- a/src/shared/image_viewer.rs
+++ b/src/shared/image_viewer.rs
@@ -16,6 +16,9 @@ use matrix_sdk_ui::timeline::EventTimelineItem;
use thiserror::Error;
use crate::shared::{avatar::AvatarWidgetExt, timestamp::TimestampWidgetRefExt};
+/// The timeout for hiding the UI overlays after no user mouse/tap activity.
+const SHOW_UI_DURATION: f64 = 3.0;
+
/// Loads the given image `data` into an `ImageBuffer` as either a PNG or JPEG, using the `imghdr` library to determine which format it is.
///
/// Returns an error if either load fails or if the image format is unknown.
@@ -36,15 +39,13 @@ pub fn get_png_or_jpg_image_buffer(data: Vec) -> Result Self {
Self {
- min_zoom: 0.5,
- max_zoom: 4.0,
+ min_zoom: 0.1,
zoom_scale_factor: 1.2,
pan_sensitivity: 2.0,
}
@@ -87,7 +87,7 @@ struct DragState {
drag_start: DVec2,
/// The zoom level of the image.
/// The larger the value, the more zoomed in the image is.
- zoom_level: f32,
+ zoom_level: f64,
/// The pan offset of the image.
pan_offset: Option,
}
@@ -111,129 +111,43 @@ live_design! {
use crate::shared::avatar::Avatar;
use crate::shared::timestamp::Timestamp;
- pub MagnifyingGlass = {
- width: Fit, height: Fit
- flow: Overlay
- visible: true
-
- magnifying_glass_button = {
- width: Fit, height: Fit,
- spacing: 0,
- margin: 8,
- padding: 3
- draw_bg: {
- color: (COLOR_PRIMARY)
- }
- draw_icon: {
- svg_file: (ICON_ZOOM),
- fn get_color(self) -> vec4 {
- return #x0;
- }
- }
- icon_walk: {width: 30, height: 30}
- }
-
- sign_label = {
- width: Fill, height: Fill,
- align: { x: 0.4, y: 0.35 }
+ UI_ANIMATION_DURATION_SECS = 0.5
+ ROTATION_ANIMATION_DURATION_SECS = 0.2
- magnifying_glass_sign =