From a631d14ae04b6e98745b071742c4ff9956c778f7 Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Mon, 1 Jun 2026 16:50:10 -0700 Subject: [PATCH 1/2] Move room upload media/location buttons into a popup menu Cleans up the room input bar and looks a bit more standardized --- resources/icons/add.svg | 5 +- src/home/room_screen.rs | 98 +++++++++++- src/room/room_input_bar.rs | 117 ++++++-------- src/shared/mod.rs | 2 + src/shared/room_input_popup_menu.rs | 235 ++++++++++++++++++++++++++++ src/shared/styles.rs | 1 + 6 files changed, 388 insertions(+), 70 deletions(-) create mode 100644 src/shared/room_input_popup_menu.rs diff --git a/resources/icons/add.svg b/resources/icons/add.svg index b72f3b8f0..d9b0c3644 100644 --- a/resources/icons/add.svg +++ b/resources/icons/add.svg @@ -1 +1,4 @@ - \ No newline at end of file + + + + diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index 803487f4b..a16571a78 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -32,7 +32,7 @@ use crate::{ }, room::{BasicRoomDetails, room_input_bar::{RoomInputBarState, RoomInputBarWidgetRefExt}, typing_notice::TypingNoticeWidgetExt}, shared::{ - attachment_download::{enqueue_already_downloading_notification, DownloadDisplayState, DownloadKind, DownloadableAttachment, PendingDownload, PendingDownloadState, media_source_mxc, start_attachment_download}, avatar::{AvatarState, AvatarWidgetRefExt}, confirmation_modal::ConfirmationModalContent, file_upload_modal::FileUploadAttemptId, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt, RobrixHtmlLinkAction}, image_viewer::{ImageViewerAction, ImageViewerMetaData, LoadState}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, popup_list::{PopupKind, enqueue_popup_notification}, restore_status_view::RestoreStatusViewWidgetExt, styles::*, text_or_image::{TextOrImageAction, TextOrImageRef, TextOrImageStatus, TextOrImageWidgetRefExt}, timestamp::TimestampWidgetRefExt + attachment_download::{enqueue_already_downloading_notification, DownloadDisplayState, DownloadKind, DownloadableAttachment, PendingDownload, PendingDownloadState, media_source_mxc, start_attachment_download}, avatar::{AvatarState, AvatarWidgetRefExt}, confirmation_modal::ConfirmationModalContent, file_upload_modal::FileUploadAttemptId, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt, RobrixHtmlLinkAction}, image_viewer::{ImageViewerAction, ImageViewerMetaData, LoadState}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, popup_list::{PopupKind, enqueue_popup_notification}, restore_status_view::RestoreStatusViewWidgetExt, room_input_popup_menu::{RoomInputPopupMenuAction, RoomInputPopupMenuWidgetExt}, styles::*, text_or_image::{TextOrImageAction, TextOrImageRef, TextOrImageStatus, TextOrImageWidgetRefExt}, timestamp::TimestampWidgetRefExt }, sliding_sync::{BackwardsPaginateUntilEventRequest, MatrixRequest, PaginationDirection, TimelineEndpoints, TimelineKind, TimelineRequestSender, UserPowerLevels, get_client, submit_async_request, take_timeline_endpoints}, utils::{self, ImageFormat, MEDIA_THUMBNAIL_FORMAT, RoomNameId, unix_time_millis_to_datetime} }; @@ -693,6 +693,10 @@ script_mod! { // to finish loading, e.g., when loading an older replied-to message. loading_pane := LoadingPane { } + // The popup menu for uploading/sending other content to this room, + // which is controlled by actions from the RoomInputBar. + room_input_popup_menu := RoomInputPopupMenu { } + /* * TODO: add the action bar back in as a series of floating buttons. @@ -766,6 +770,7 @@ impl Widget for RoomScreen { let portal_list = self.portal_list(cx, ids!(timeline.list)); let user_profile_sliding_pane = self.user_profile_sliding_pane(cx, ids!(user_profile_sliding_pane)); let loading_pane = self.loading_pane(cx, ids!(loading_pane)); + let room_input_popup_menu = self.room_input_popup_menu(cx, ids!(room_input_popup_menu)); // Handle actions here before processing timeline updates. // Normally (in most other widgets), the order of event handling doesn't matter much. @@ -773,6 +778,10 @@ impl Widget for RoomScreen { // we want to handle those before processing any updates that might change // the set of timeline indices (which would invalidate the index values in any actions). if let Event::Actions(actions) = event { + if let Some(action) = room_input_popup_menu.selected(actions) { + self.handle_room_input_popup_menu_action(cx, action); + } + for (index, wr) in portal_list.items_with_actions(actions) { // Handle a hover-in action on the reaction list: show a reaction summary. let reaction_list = wr.reaction_list(cx, ids!(reaction_list)); @@ -1016,7 +1025,27 @@ impl Widget for RoomScreen { // let is_interactive_hit = utils::is_interactive_hit_event(event); let is_pane_shown: bool; - if loading_pane.is_currently_shown(cx) { + let mut close_room_input_popup_menu_after_forwarding = false; + if room_input_popup_menu.is_open() { + if event.back_pressed() || matches!(event, Event::KeyUp(KeyEvent { key_code: KeyCode::Escape, .. })) { + room_input_popup_menu.close(cx); + is_pane_shown = true; + } + else if is_interactive_hit { + if room_input_popup_menu.is_event_within_popup_menu(cx, event) { + is_pane_shown = true; + room_input_popup_menu.handle_event(cx, event, scope); + } else { + // Let outside clicks, hovers, and mouse moves fall through to the underlying UI. + close_room_input_popup_menu_after_forwarding = + room_input_popup_menu.should_dismiss_for_outside_event(cx, event); + is_pane_shown = false; + } + } else { + is_pane_shown = false; + } + } + else if loading_pane.is_currently_shown(cx) { is_pane_shown = true; if is_interactive_hit { loading_pane.handle_event(cx, event, scope); @@ -1102,6 +1131,17 @@ impl Widget for RoomScreen { return false; } + match action + .as_widget_action() + .cast_ref::() + { + RoomInputPopupMenuAction::None => {} + menu_action => { + self.handle_room_input_popup_menu_action(cx, *menu_action); + return false; + } + } + // Handle the action that requests to show the user profile sliding pane. if let ShowUserProfileAction::ShowUserProfile(profile_and_room_id) = action.as_widget_action().cast() { self.show_user_profile( @@ -1160,10 +1200,17 @@ impl Widget for RoomScreen { }); // Add back any unhandled actions to the global action list. cx.extend_actions(actions_generated_within_this_room_screen); + + if close_room_input_popup_menu_after_forwarding { + let room_input_popup_menu = + self.room_input_popup_menu(cx, ids!(room_input_popup_menu)); + if room_input_popup_menu.is_open() { + room_input_popup_menu.close(cx); + } + } } } - fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { // If the room isn't loaded yet, we show the restore status label only. if !self.is_loaded { @@ -1375,6 +1422,51 @@ impl RoomScreen { self.room_name_id.as_ref().map(|r| r.room_id()) } + fn show_room_input_popup_menu(&mut self, cx: &mut Cx, button_rect: Rect) { + let popup_menu = self.room_input_popup_menu(cx, ids!(room_input_popup_menu)); + let room_screen_rect = self.view(cx, ids!(room_screen_wrapper)).area().rect(cx); + let margin = Inset { + left: button_rect.pos.x - room_screen_rect.pos.x, + top: 0.0, + right: 0.0, + bottom: room_screen_rect.pos.y + room_screen_rect.size.y + - button_rect.pos.y + + 9.0 + }; + + let mut main_content = popup_menu.view(cx, ids!(main_content)); + script_apply_eval!(cx, main_content, { + margin: #(margin) + }); + popup_menu.show(cx); + self.view.redraw(cx); + } + + fn handle_room_input_popup_menu_action( + &mut self, + cx: &mut Cx, + action: RoomInputPopupMenuAction, + ) { + let room_input_bar = self.view.room_input_bar(cx, ids!(room_input_bar)); + match action { + RoomInputPopupMenuAction::Show { button_rect } => { + self.show_room_input_popup_menu(cx, button_rect); + } + RoomInputPopupMenuAction::UploadPhotoOrVideo => { + let Some(timeline_kind) = self.timeline_kind.clone() else { return }; + room_input_bar.open_photo_video_picker(cx, timeline_kind); + } + RoomInputPopupMenuAction::UploadFile => { + let Some(timeline_kind) = self.timeline_kind.clone() else { return }; + room_input_bar.open_file_picker(cx, timeline_kind); + } + RoomInputPopupMenuAction::SendCurrentLocation => { + room_input_bar.show_current_location_preview(cx); + } + RoomInputPopupMenuAction::None => {} + } + } + /// Processes all pending background updates to the currently-shown timeline. /// /// Redraws this RoomScreen view if any updates were applied. diff --git a/src/room/room_input_bar.rs b/src/room/room_input_bar.rs index 3e8e39ce9..c0a61c1a5 100644 --- a/src/room/room_input_bar.rs +++ b/src/room/room_input_bar.rs @@ -5,7 +5,8 @@ //! The widgets included in the RoomInputBar are: //! * a preview of the message the user is replying to. //! * the location preview (which allows you to send your current location to the room), -//! and a button to show the location preview. +//! plus a menu item to show the location preview. +//! * a button that opens the RoomScreen-level popup menu for uploads/location. //! * If TSP is enabled, a checkbox to enable TSP signing for the outgoing message. //! * A MentionableTextInput, which allows the user to type a message //! and mention other users via the `@` key. @@ -21,16 +22,13 @@ use matrix_sdk::room::reply::{EnforceThread, Reply}; use ruma::events::room::message::AddMentions; use matrix_sdk_ui::timeline::{EmbeddedEvent, EventTimelineItem, TimelineEventItemId}; use ruma::{events::room::message::{LocationMessageEventContent, MessageType, ReplyWithinThread, RoomMessageEventContent}, OwnedEventId, OwnedRoomId}; -use crate::{home::{editing_pane::{EditingPaneState, EditingPaneWidgetExt, EditingPaneWidgetRefExt}, location_preview::{LocationPreviewWidgetExt, LocationPreviewWidgetRefExt}, room_screen::{MessageAction, RoomScreenProps, populate_preview_of_timeline_item}, tombstone_footer::{SuccessorRoomDetails, TombstoneFooterWidgetExt}, upload_progress::UploadProgressViewWidgetRefExt}, location::init_location_subscriber, settings::app_preferences::{AppPreferencesAction, AppPreferencesGlobal}, shared::{avatar::AvatarWidgetRefExt, file_upload_modal::{AttachmentUpload, FilePreviewerAction, FileUploadAttemptId, load_selected_file}, html_or_plaintext::HtmlOrPlaintextWidgetRefExt, mentionable_text_input::MentionableTextInputWidgetExt, popup_list::{PopupKind, enqueue_popup_notification}, styles::*}, sliding_sync::{MatrixRequest, TimelineKind, UserPowerLevels, submit_async_request}, utils}; +use crate::{home::{editing_pane::{EditingPaneState, EditingPaneWidgetExt, EditingPaneWidgetRefExt}, location_preview::{LocationPreviewWidgetExt, LocationPreviewWidgetRefExt}, room_screen::{MessageAction, RoomScreenProps, populate_preview_of_timeline_item}, tombstone_footer::{SuccessorRoomDetails, TombstoneFooterWidgetExt}, upload_progress::UploadProgressViewWidgetRefExt}, location::init_location_subscriber, settings::app_preferences::{AppPreferencesAction, AppPreferencesGlobal}, shared::{avatar::AvatarWidgetRefExt, file_upload_modal::{AttachmentUpload, FilePreviewerAction, FileUploadAttemptId, load_selected_file}, html_or_plaintext::HtmlOrPlaintextWidgetRefExt, mentionable_text_input::MentionableTextInputWidgetExt, popup_list::{PopupKind, enqueue_popup_notification}, room_input_popup_menu::RoomInputPopupMenuAction, styles::*}, sliding_sync::{MatrixRequest, TimelineKind, UserPowerLevels, submit_async_request}, utils}; script_mod! { use mod.prelude.widgets.* use mod.widgets.* - mod.widgets.ICON_LOCATION_PIN = crate_resource("self://resources/icons/location-pin.svg") - - mod.widgets.RoomInputBar = set_type_default() do #(RoomInputBar::register_widget(vm)) { ..mod.widgets.RoundedView @@ -83,29 +81,11 @@ script_mod! { align: Align{y: 1.0}, padding: 6, - // Attachment button for uploading files. - send_attachment_button := RobrixIconButton { + open_popup_menu_button := RobrixIconButton { margin: 4 spacing: 0, draw_icon +: { - svg: (ICON_ADD_ATTACHMENT) - color: (COLOR_ACTIVE_PRIMARY_DARKER) - }, - draw_bg +: { - color: (COLOR_BG_PREVIEW) - color_hover: #E0E8F0 - color_down: #D0D8E8 - } - icon_walk: Walk{width: 21, height: 21} - text: "", - } - - // Photo/video button for uploading media from the native media picker. - send_photo_video_button := RobrixIconButton { - margin: 4 - spacing: 0, - draw_icon +: { - svg: (ICON_ADD_PHOTO) + svg: (ICON_ADD) color: (COLOR_ACTIVE_PRIMARY_DARKER) }, draw_bg +: { @@ -117,22 +97,6 @@ script_mod! { text: "", } - location_button := RobrixIconButton { - margin: 4 - spacing: 0, - draw_icon +: { - svg: (mod.widgets.ICON_LOCATION_PIN) - color: (COLOR_ACTIVE_PRIMARY_DARKER) - }, - draw_bg +: { - color: (COLOR_BG_PREVIEW) - color_hover: #E0E8F0 - color_down: #D0D8E8 - } - icon_walk: Walk{width: 21, height: 21} - text: "", - } - // A checkbox that enables TSP signing for the outgoing message. // If TSP is not enabled, this will be an empty invisible view. tsp_sign_checkbox := TspSignAnycastCheckbox { @@ -319,31 +283,13 @@ impl RoomInputBar { self.redraw(cx); } - // Handle the add attachment button being clicked. - if self.button(cx, ids!(send_attachment_button)).clicked(actions) { - log!("Add attachment button clicked; opening file picker..."); - self.open_file_picker(cx, room_screen_props.timeline_kind.clone()); - } - - // Handle the add photo/video button being clicked. - if self.button(cx, ids!(send_photo_video_button)).clicked(actions) { - log!("Add photo/video button clicked; opening media picker..."); - self.open_photo_video_picker(cx, room_screen_props.timeline_kind.clone()); - } - - // Handle the add location button being clicked. - if self.button(cx, ids!(location_button)).clicked(actions) { - log!("Add location button clicked; requesting current location..."); - if let Err(_e) = init_location_subscriber(cx) { - error!("Failed to initialize location subscriber"); - enqueue_popup_notification( - "Failed to initialize location services.", - PopupKind::Error, - None, - ); - } - self.view.location_preview(cx, ids!(location_preview)).show(); - self.redraw(cx); + let open_popup_menu_button = self.button(cx, ids!(open_popup_menu_button)); + if open_popup_menu_button.clicked(actions) { + let button_rect = open_popup_menu_button.area().rect(cx); + cx.widget_action( + room_screen_props.room_screen_widget_uid, + RoomInputPopupMenuAction::Show { button_rect }, + ); } // Handle the send location button being clicked. @@ -471,6 +417,19 @@ impl RoomInputBar { } } + fn show_current_location_preview(&mut self, cx: &mut Cx) { + if let Err(_e) = init_location_subscriber(cx) { + error!("Failed to initialize location subscriber"); + enqueue_popup_notification( + "Failed to initialize location services.", + PopupKind::Error, + None, + ); + } + self.view.location_preview(cx, ids!(location_preview)).show(); + self.redraw(cx); + } + /// Shows a preview of the given event that the user is currently replying to /// above the message input bar. /// @@ -796,6 +755,32 @@ impl RoomInputBarRef { inner.update_tombstone_footer(cx, tombstoned_room_id, successor_room_details); } + /// Opens the native picker to upload a photo or video into this room. + pub fn open_photo_video_picker( + &self, + cx: &mut Cx, + timeline_kind: TimelineKind, + ) { + let Some(mut inner) = self.borrow_mut() else { return }; + inner.open_photo_video_picker(cx, timeline_kind); + } + + /// Opens the native picker to upload a file into this room. + pub fn open_file_picker( + &self, + cx: &mut Cx, + timeline_kind: TimelineKind, + ) { + let Some(mut inner) = self.borrow_mut() else { return }; + inner.open_file_picker(cx, timeline_kind); + } + + /// Shows the preview flow for sending the current location into this room. + pub fn show_current_location_preview(&self, cx: &mut Cx) { + let Some(mut inner) = self.borrow_mut() else { return }; + inner.show_current_location_preview(cx); + } + /// Forwards the result of an edit request to the `EditingPane` widget /// within this `RoomInputBar`. pub fn handle_edit_result( diff --git a/src/shared/mod.rs b/src/shared/mod.rs index 20be2eb97..6bf576f19 100644 --- a/src/shared/mod.rs +++ b/src/shared/mod.rs @@ -15,6 +15,7 @@ pub mod navigation_bar_button; pub mod popup_list; pub mod progress_bar; pub mod room_filter_input_bar; +pub mod room_input_popup_menu; pub mod styles; pub mod text_or_image; pub mod timestamp; @@ -37,6 +38,7 @@ pub fn script_mod(vm: &mut ScriptVm) { collapsible_header::script_mod(vm); timestamp::script_mod(vm); room_filter_input_bar::script_mod(vm); + room_input_popup_menu::script_mod(vm); avatar::script_mod(vm); text_or_image::script_mod(vm); html_or_plaintext::script_mod(vm); diff --git a/src/shared/room_input_popup_menu.rs b/src/shared/room_input_popup_menu.rs new file mode 100644 index 000000000..9ef83e66a --- /dev/null +++ b/src/shared/room_input_popup_menu.rs @@ -0,0 +1,235 @@ +//! A popup menu that contains buttons for sending attachments or location to a room. +//! +//! This is shown when clicking the add/plus-sign button in the RoomInputBar. + +use makepad_widgets::*; +use makepad_widgets::makepad_platform::event::finger::TouchState; + +script_mod! { + use mod.prelude.widgets.* + use mod.widgets.* + + mod.widgets.RoomInputPopupMenuButton = RobrixIconButton { + height: 38 + width: Fill + margin: 0 + padding: Inset{left: 10, right: 12, top: 9, bottom: 9} + spacing: 10 + align: Align{x: 0, y: 0.5} + + draw_bg +: { + color: (COLOR_PRIMARY) + color_hover: #xE0E8F0 + color_down: #xD0D8E8 + border_radius: 4.0 + } + draw_text +: { + color: (COLOR_TEXT) + color_hover: (COLOR_TEXT) + color_down: (COLOR_TEXT) + text_style: REGULAR_TEXT {font_size: 11} + } + draw_icon.color: (COLOR_ACTIVE_PRIMARY_DARKER) + icon_walk: Walk{width: 18, height: 18} + } + + mod.widgets.RoomInputPopupMenu = set_type_default() do #(RoomInputPopupMenu::register_widget(vm)) { + ..mod.widgets.SolidView + + visible: false + width: Fill + height: Fill + flow: Overlay + align: Align{x: 0, y: 1} + cursor: MouseCursor.Default + + show_bg: false + draw_bg +: { + color: #00000000 + } + + // This works kinda like the other context menus; we position the main content + // within the entire room screen using margins. + main_content := RoundedShadowView { + width: 235 + height: Fit + flow: Down + padding: 6 + spacing: 2 + align: Align{x: 0, y: 0} + + show_bg: true + draw_bg +: { + color: (COLOR_PRIMARY) + border_radius: 5.0 + border_size: 0.0 + shadow_color: #0005 + shadow_radius: 14.0 + shadow_offset: vec2(0.0, 4.0) + } + + upload_photo_video_button := mod.widgets.RoomInputPopupMenuButton { + draw_icon.svg: (ICON_ADD_PHOTO) + text: "Upload photo or video" + } + + upload_file_button := mod.widgets.RoomInputPopupMenuButton { + draw_icon.svg: (ICON_ADD_ATTACHMENT) + text: "Upload file" + } + + send_location_button := mod.widgets.RoomInputPopupMenuButton { + draw_icon.svg: (ICON_LOCATION_PIN) + text: "Send current location" + } + } + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub enum RoomInputPopupMenuAction { + Show { button_rect: Rect }, + UploadPhotoOrVideo, + UploadFile, + SendCurrentLocation, + #[default] + None, +} + +impl ActionDefaultRef for RoomInputPopupMenuAction { + fn default_ref() -> &'static Self { + static DEFAULT: RoomInputPopupMenuAction = RoomInputPopupMenuAction::None; + &DEFAULT + } +} + +#[derive(Script, ScriptHook, Widget)] +pub struct RoomInputPopupMenu { + #[source] source: ScriptObjectRef, + #[deref] view: View, +} + +impl Widget for RoomInputPopupMenu { + fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { + if !self.visible { return; } + + if matches!(event, Event::KeyUp(KeyEvent {key_code: KeyCode::Escape, .. })) + || event.back_pressed() + { + self.close(cx); + return; + } + + self.view.handle_event(cx, event, scope); + self.widget_match_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 WidgetMatchEvent for RoomInputPopupMenu { + fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions, _scope: &mut Scope) { + let action = if self.button(cx, ids!(upload_photo_video_button)).clicked(actions) { + RoomInputPopupMenuAction::UploadPhotoOrVideo + } else if self.button(cx, ids!(upload_file_button)).clicked(actions) { + RoomInputPopupMenuAction::UploadFile + } else if self.button(cx, ids!(send_location_button)).clicked(actions) { + RoomInputPopupMenuAction::SendCurrentLocation + } else { + RoomInputPopupMenuAction::None + }; + + if action != RoomInputPopupMenuAction::None { + self.close(cx); + cx.widget_action(self.widget_uid(), action); + } + } +} + +impl RoomInputPopupMenu { + pub fn is_open(&self) -> bool { + self.visible + } + + pub fn show(&mut self, cx: &mut Cx) { + self.reset_button_hover(cx); + self.visible = true; + cx.set_key_focus(self.view.area()); + self.redraw(cx); + } + + pub fn close(&mut self, cx: &mut Cx) { + if !self.visible { return; } + self.visible = false; + cx.revert_key_focus(); + self.redraw(cx); + } + + fn reset_button_hover(&mut self, cx: &mut Cx) { + self.button(cx, ids!(upload_photo_video_button)).reset_hover(cx); + self.button(cx, ids!(upload_file_button)).reset_hover(cx); + self.button(cx, ids!(send_location_button)).reset_hover(cx); + } + + pub fn is_event_within_popup_menu(&self, cx: &mut Cx, event: &Event) -> bool { + let main_rect = self.view(cx, ids!(main_content)).area().rect(cx); + match event { + Event::MouseDown(e) => main_rect.contains(e.abs), + Event::MouseUp(e) => main_rect.contains(e.abs), + Event::MouseMove(e) => main_rect.contains(e.abs), + Event::Scroll(e) => main_rect.contains(e.abs), + Event::LongPress(e) => main_rect.contains(e.abs), + Event::TouchUpdate(e) => e.touches.iter().any(|touch| main_rect.contains(touch.abs)), + _ => false, + } + } + + pub fn should_dismiss_for_outside_event(&self, cx: &mut Cx, event: &Event) -> bool { + let main_rect = self.view(cx, ids!(main_content)).area().rect(cx); + match event { + Event::MouseDown(e) => !main_rect.contains(e.abs), + Event::LongPress(e) => !main_rect.contains(e.abs), + Event::TouchUpdate(e) => e.touches.iter().any(|touch| { + touch.state == TouchState::Start && !main_rect.contains(touch.abs) + }), + _ => false, + } + } +} + +impl RoomInputPopupMenuRef { + pub fn is_open(&self) -> bool { + let Some(inner) = self.borrow() else { return false }; + inner.is_open() + } + + pub fn close(&self, cx: &mut Cx) { + let Some(mut inner) = self.borrow_mut() else { return }; + inner.close(cx); + } + + pub fn show(&self, cx: &mut Cx) { + let Some(mut inner) = self.borrow_mut() else { return }; + inner.show(cx); + } + + pub fn is_event_within_popup_menu(&self, cx: &mut Cx, event: &Event) -> bool { + let Some(inner) = self.borrow() else { return false }; + inner.is_event_within_popup_menu(cx, event) + } + + pub fn should_dismiss_for_outside_event(&self, cx: &mut Cx, event: &Event) -> bool { + let Some(inner) = self.borrow() else { return false }; + inner.should_dismiss_for_outside_event(cx, event) + } + + pub fn selected(&self, actions: &Actions) -> Option { + match actions.find_widget_action(self.widget_uid()).cast_ref() { + RoomInputPopupMenuAction::None => None, + action => Some(*action), + } + } + +} diff --git a/src/shared/styles.rs b/src/shared/styles.rs index c83b80572..1a06a9a94 100644 --- a/src/shared/styles.rs +++ b/src/shared/styles.rs @@ -29,6 +29,7 @@ script_mod! { mod.widgets.ICON_INVITE = crate_resource("self://resources/icons/invite.svg") mod.widgets.ICON_JOIN_ROOM = crate_resource("self://resources/icons/join_room.svg") mod.widgets.ICON_JUMP = crate_resource("self://resources/icons/go_back.svg") + mod.widgets.ICON_LOCATION_PIN = crate_resource("self://resources/icons/location-pin.svg") mod.widgets.ICON_LOGOUT = crate_resource("self://resources/icons/logout.svg") mod.widgets.ICON_LINK = crate_resource("self://resources/icons/link.svg") mod.widgets.ICON_PIN = crate_resource("self://resources/icons/pin.svg") From 3b4851709204ee5b12023ba852af7c5cd0105773 Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Mon, 1 Jun 2026 17:17:33 -0700 Subject: [PATCH 2/2] cleanup unnecessary cast_ref usage for an action enum that impls Copy --- src/home/room_screen.rs | 10 ++++------ src/shared/room_input_popup_menu.rs | 17 ++++++++--------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index a16571a78..4205fd097 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -1131,13 +1131,11 @@ impl Widget for RoomScreen { return false; } - match action - .as_widget_action() - .cast_ref::() - { + // Handle actions related to the room input popup menu. + match action.as_widget_action().cast() { RoomInputPopupMenuAction::None => {} - menu_action => { - self.handle_room_input_popup_menu_action(cx, *menu_action); + room_popup_menu_action => { + self.handle_room_input_popup_menu_action(cx, room_popup_menu_action); return false; } } diff --git a/src/shared/room_input_popup_menu.rs b/src/shared/room_input_popup_menu.rs index 9ef83e66a..70f8bf93b 100644 --- a/src/shared/room_input_popup_menu.rs +++ b/src/shared/room_input_popup_menu.rs @@ -86,23 +86,22 @@ script_mod! { } } +/// Widget actions related to the room input popup menu. #[derive(Clone, Copy, Debug, Default, PartialEq)] pub enum RoomInputPopupMenuAction { + /// Emitted by the RoomInputBar when the add button is clicked, + /// which is a request to show the room input popup menu. Show { button_rect: Rect }, + /// Emitted by the RoomInputPopupMenu when the upload photo/video button is clicked. UploadPhotoOrVideo, + /// Emitted by the RoomInputPopupMenu when the upload file button is clicked. UploadFile, + /// Emitted by the RoomInputPopupMenu when the send location button is clicked. SendCurrentLocation, #[default] None, } -impl ActionDefaultRef for RoomInputPopupMenuAction { - fn default_ref() -> &'static Self { - static DEFAULT: RoomInputPopupMenuAction = RoomInputPopupMenuAction::None; - &DEFAULT - } -} - #[derive(Script, ScriptHook, Widget)] pub struct RoomInputPopupMenu { #[source] source: ScriptObjectRef, @@ -226,9 +225,9 @@ impl RoomInputPopupMenuRef { } pub fn selected(&self, actions: &Actions) -> Option { - match actions.find_widget_action(self.widget_uid()).cast_ref() { + match actions.find_widget_action(self.widget_uid()).cast() { RoomInputPopupMenuAction::None => None, - action => Some(*action), + action => Some(action), } }