diff --git a/src/home/navigation_tab_bar.rs b/src/home/navigation_tab_bar.rs index bc23d32d..da284e9c 100644 --- a/src/home/navigation_tab_bar.rs +++ b/src/home/navigation_tab_bar.rs @@ -31,10 +31,10 @@ use makepad_widgets::*; use crate::{ avatar_cache::{self, AvatarCacheEntry}, login::login_screen::LoginAction, logout::logout_confirm_modal::LogoutAction, profile::{ - user_profile::{AvatarState, UserProfile}, + user_profile::UserProfile, user_profile_cache::{self, UserProfileUpdate}, }, shared::{ - avatar::AvatarWidgetExt, + avatar::{AvatarState, AvatarWidgetExt}, callout_tooltip::{CalloutTooltipOptions, TooltipAction, TooltipPosition}, styles::*, verification_badge::VerificationBadgeWidgetExt, diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index 7f9ccec3..5a0799c2 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -26,12 +26,12 @@ use ruma::{OwnedUserId, events::{AnySyncMessageLikeEvent, AnySyncTimelineEvent, use crate::{ app::AppStateAction, avatar_cache, event_preview::{plaintext_body_of_timeline_item, text_preview_of_encrypted_message, text_preview_of_member_profile_change, text_preview_of_other_message_like, text_preview_of_other_state, text_preview_of_room_membership_change, text_preview_of_timeline_item}, home::{edited_indicator::EditedIndicatorWidgetRefExt, link_preview::{LinkPreviewCache, LinkPreviewRef, LinkPreviewWidgetRefExt}, loading_pane::{LoadingPaneState, LoadingPaneWidgetExt}, room_image_viewer::{get_image_name_and_filesize, populate_matrix_image_modal}, rooms_list::RoomsListRef, tombstone_footer::SuccessorRoomDetails}, media_cache::{MediaCache, MediaCacheEntry}, profile::{ - user_profile::{AvatarState, ShowUserProfileAction, UserProfile, UserProfileAndRoomId, UserProfilePaneInfo, UserProfileSlidingPaneRef, UserProfileSlidingPaneWidgetExt}, + user_profile::{ShowUserProfileAction, UserProfile, UserProfileAndRoomId, UserProfilePaneInfo, UserProfileSlidingPaneRef, UserProfileSlidingPaneWidgetExt}, user_profile_cache, }, room::{BasicRoomDetails, room_input_bar::RoomInputBarState, typing_notice::TypingNoticeWidgetExt}, shared::{ - avatar::AvatarWidgetRefExt, callout_tooltip::{CalloutTooltipOptions, TooltipAction, TooltipPosition}, confirmation_modal::ConfirmationModalContent, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt, RobrixHtmlLinkAction}, image_viewer::{ImageViewerAction, ImageViewerMetaData, LoadState}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, popup_list::{PopupItem, PopupKind, enqueue_popup_notification}, restore_status_view::RestoreStatusViewWidgetExt, styles::*, text_or_image::{TextOrImageAction, TextOrImageRef, TextOrImageWidgetRefExt}, timestamp::TimestampWidgetRefExt + avatar::{AvatarState, AvatarWidgetRefExt}, callout_tooltip::{CalloutTooltipOptions, TooltipAction, TooltipPosition}, confirmation_modal::ConfirmationModalContent, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt, RobrixHtmlLinkAction}, image_viewer::{ImageViewerAction, ImageViewerMetaData, LoadState}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, popup_list::{PopupItem, PopupKind, enqueue_popup_notification}, restore_status_view::RestoreStatusViewWidgetExt, styles::*, text_or_image::{TextOrImageAction, TextOrImageRef, TextOrImageWidgetRefExt}, timestamp::TimestampWidgetRefExt }, sliding_sync::{BackwardsPaginateUntilEventRequest, MatrixRequest, PaginationDirection, TimelineEndpoints, TimelineRequestSender, UserPowerLevels, get_client, submit_async_request, take_timeline_endpoints}, utils::{self, ImageFormat, MEDIA_THUMBNAIL_FORMAT, RoomNameId, unix_time_millis_to_datetime} }; diff --git a/src/home/rooms_list.rs b/src/home/rooms_list.rs index 165a037d..e49a9d1b 100644 --- a/src/home/rooms_list.rs +++ b/src/home/rooms_list.rs @@ -358,7 +358,7 @@ pub enum InviteState { /// The value in the RoomsList's `space_map` that contains info about a space. #[derive(Default)] -struct SpaceMapValue { +struct SpaceMapValue { /// Whether this space is fully paginated, meaning that our client has obtained /// the full list of direct children within this space. /// @@ -1034,6 +1034,8 @@ impl RoomsList { kind: PopupKind::Error, }); } + // DetailedChildren is handled by SpaceLobbyScreen, not RoomsList. + SpaceRoomListAction::DetailedChildren { .. } => { } } } @@ -1395,6 +1397,22 @@ impl RoomsListRef { pub fn get_selected_space_id(&self) -> Option { self.borrow()?.selected_space.as_ref().map(|ss| ss.room_id().clone()) } + + /// Returns a clone of the space request sender channel, if available. + /// + /// This allows other widgets to submit space-related requests directly + /// to the background space service. + pub fn get_space_request_sender(&self) -> Option> { + self.borrow()?.space_request_sender.clone() + } + + /// Returns the parent chain of the given space, if known. + pub fn get_space_parent_chain(&self, space_id: &OwnedRoomId) -> Option { + self.borrow()? + .space_map + .get(space_id) + .map(|smv| smv.parent_chain.clone()) + } } pub struct RoomsListScopeProps { diff --git a/src/home/space_lobby.rs b/src/home/space_lobby.rs index 9a7c60d4..2a6f27b6 100644 --- a/src/home/space_lobby.rs +++ b/src/home/space_lobby.rs @@ -6,8 +6,16 @@ //! that allows the user to click on it to show the `SpaceLobby`. //! +use std::collections::{HashMap, HashSet}; +use imbl::Vector; use makepad_widgets::*; -use crate::utils::RoomNameId; +use matrix_sdk::{RoomState, ruma::OwnedRoomId}; +use matrix_sdk_ui::spaces::SpaceRoom; +use ruma::room::JoinRuleSummary; +use tokio::sync::mpsc::UnboundedSender; +use crate::{ + home::rooms_list::RoomsListRef, shared::avatar::{AvatarState, AvatarWidgetExt, AvatarWidgetRefExt}, space_service_sync::{SpaceRequest, SpaceRoomExt, SpaceRoomListAction}, utils::{self, RoomNameId} +}; live_design! { @@ -19,6 +27,8 @@ live_design! { use crate::shared::helpers::*; use crate::shared::avatar::*; + ICON_COLLAPSE = dep("crate://self/resources/icons/triangle_fill.svg") + // An entry in the RoomsList that will show the SpaceLobby when clicked. pub SpaceLobbyEntry = {{SpaceLobbyEntry}} { visible: false, // only visible when a space is selected @@ -171,26 +181,270 @@ live_design! { } } + // A view that draws the hierarchical tree structure lines. + DrawTreeLine = {{DrawTreeLine}} { } + + TreeLines = {{TreeLines}} { + width: 0, height: Fill + draw_bg: { + indent_width: 44.0 + level: 0.0 + is_last: 0.0 + parent_mask: 0.0 + + fn pixel(self) -> vec4 { + let pos = self.pos * self.rect_size; + let indent = self.indent_width; + // Yes, this should be 0.5, but 0.6 makes it line up nicely + // with the middle of the parent-level avatar, which is better. + let half_indent = indent * 0.6; + let line_width = 1.0; + let half_line = 0.5; + + let c = vec4(0.0); + + // Dumb approach, but it works. + for i in 0..20 { + if float(i) > self.level { break; } + + if float(i) < self.level { + // Check mask for parent levels + let mask_bit = mod(floor(self.parent_mask / pow(2.0, float(i))), 2.0); + if mask_bit > 0.5 { + // Draw full vertical line + if abs(pos.x - (float(i) * indent + half_indent)) < half_line && pos.y < self.rect_size.y { + return vec4(0.8, 0.8, 0.8, 1.0); + } + } + } else { + // Current level: connection to self + + // Horizontal line to content + let hy = self.rect_size.y * 0.5; + if abs(pos.y - hy) < half_line && pos.x > (float(i) * indent + half_indent) { + return vec4(0.8, 0.8, 0.8, 1.0); + } + + // Vertical line (L shape) + if abs(pos.x - (float(i) * indent + half_indent)) < half_line && pos.y < (self.rect_size.y * (1.0 - 0.5 * self.is_last)) { + return vec4(0.8, 0.8, 0.8, 1.0); + } + } + } + return c; + } + } + } + + // Entry for a child subspace (can be expanded) + pub SubspaceEntry = {{SubspaceEntry}} { + width: Fill, + height: 44, + flow: Right, + align: {y: 0.5} + padding: {left: 8, right: 12} + cursor: Hand + + show_bg: true + draw_bg: { + instance hover: 0.0 + color: #fff + uniform color_hover: #f5f5f5 + fn pixel(self) -> vec4 { + return mix(self.color, self.color_hover, self.hover); + } + } + + // The connecting hierarchical lines on the left. + tree_lines = {} + + // Expand/collapse icon + expand_icon = { + width: 16, + height: 16, + margin: { top: 7, left: -8, right: 2 } + draw_icon: { + svg_file: (ICON_COLLAPSE) + rotation_angle: 90.0 + color: #888 + } + icon_walk: { width: 10, height: 10 } + } + + avatar = { width: 32, height: 32, margin: {right: 8} } + + content = { + width: Fill, height: Fit, flow: Down, spacing: 5, + name_label =