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..4205fd097 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,15 @@ impl Widget for RoomScreen {
return false;
}
+ // Handle actions related to the room input popup menu.
+ match action.as_widget_action().cast() {
+ RoomInputPopupMenuAction::None => {}
+ room_popup_menu_action => {
+ self.handle_room_input_popup_menu_action(cx, room_popup_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 +1198,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 +1420,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..70f8bf93b
--- /dev/null
+++ b/src/shared/room_input_popup_menu.rs
@@ -0,0 +1,234 @@
+//! 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"
+ }
+ }
+ }
+}
+
+/// 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,
+}
+
+#[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() {
+ 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")