Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
79 changes: 78 additions & 1 deletion src/home/main_desktop_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use matrix_sdk::ruma::OwnedRoomId;
use tokio::sync::Notify;
use std::{collections::HashMap, sync::Arc};

use crate::{app::{AppState, AppStateAction, SelectedRoom}, utils::room_name_or_id};
use crate::{app::{AppState, AppStateAction, SelectedRoom}, room::loading_screen::{RoomLoadingScreenAction, RoomLoadingScreenWidgetRefExt, drain_room_loading_screen_actions, loading_tab_live_id}, utils::room_name_or_id};
use super::{invite_screen::InviteScreenWidgetRefExt, room_screen::RoomScreenWidgetRefExt, rooms_list::RoomsListAction};

live_design! {
Expand All @@ -17,6 +17,7 @@ live_design! {
use crate::home::welcome_screen::WelcomeScreen;
use crate::home::room_screen::RoomScreen;
use crate::home::invite_screen::InviteScreen;
use crate::room::loading_screen::RoomLoadingScreen;

pub MainDesktopUI = {{MainDesktopUI}} {
dock = <Dock> {
Expand Down Expand Up @@ -54,6 +55,7 @@ live_design! {
welcome_screen = <WelcomeScreen> {}
room_screen = <RoomScreen> {}
invite_screen = <InviteScreen> {}
loading_screen = <RoomLoadingScreen> { visible: true }
}
}
}
Expand All @@ -67,6 +69,10 @@ pub struct MainDesktopUI {
#[rust]
open_rooms: HashMap<LiveId, SelectedRoom>,

/// Tabs that are currently showing a loading screen (tab_id -> last message).
#[rust]
loading_tabs: HashMap<LiveId, (Option<String>, Option<String>)>,

/// The tab that should be closed in the next draw event
#[rust]
tab_to_close: Option<LiveId>,
Expand All @@ -92,6 +98,20 @@ pub struct MainDesktopUI {

impl Widget for MainDesktopUI {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
// Apply queued loading tab actions when the UI thread is signalled.
if let Event::Signal = event {
for action in drain_room_loading_screen_actions() {
match action {
RoomLoadingScreenAction::ShowTab { tab_id, tab_name, message, details } => {
self.show_loading_tab(cx, tab_id, &tab_name, message.as_deref(), details.as_deref());
}
RoomLoadingScreenAction::HideTab { tab_id } => {
self.close_loading_tab(cx, tab_id);
}
}
}
}

self.widget_match_event(cx, event, scope); // invokes `WidgetMatchEvent` impl
self.view.handle_event(cx, event, scope);
}
Expand Down Expand Up @@ -121,6 +141,10 @@ impl MainDesktopUI {

// If the room is already open, select (jump to) its existing tab
let room_id_as_live_id = LiveId::from_str(room.room_id().as_str());
let loading_id = loading_tab_live_id(room.room_id().as_str());
if self.loading_tabs.remove(&loading_id).is_some() {
dock.close_tab(cx, loading_id);
}
if self.open_rooms.contains_key(&room_id_as_live_id) {
dock.select_tab(cx, room_id_as_live_id);
self.most_recently_selected_room = Some(room);
Expand Down Expand Up @@ -204,6 +228,8 @@ impl MainDesktopUI {
dock.select_tab(cx, id!(home_tab));
self.most_recently_selected_room = None;
}
} else if self.loading_tabs.remove(&tab_id).is_some() {
// Nothing else to do; just close the loading tab.
}

dock.close_tab(cx, tab_id);
Expand All @@ -226,6 +252,57 @@ impl MainDesktopUI {
self.tab_to_close = None;
self.room_order.clear();
self.most_recently_selected_room = None;

for tab_id in self.loading_tabs.keys().copied().collect::<Vec<_>>() {
dock.close_tab(cx, tab_id);
}
self.loading_tabs.clear();
}

/// Show or create a loading-only tab.
fn show_loading_tab(&mut self, cx: &mut Cx, tab_id: LiveId, tab_name: &str, message: Option<&str>, details: Option<&str>) {
let dock_ref = self.view.dock(ids!(dock));

// If the tab already exists and is a loading tab, just update it and select it.
let mut should_select_existing = false;
if let Some(mut dock) = dock_ref.borrow_mut() {
if let Some((_, widget)) = dock.items().get(&tab_id) {
widget.as_room_loading_screen().show(cx, message, details);
self.loading_tabs.insert(tab_id, (message.map(str::to_owned), details.map(str::to_owned)));
should_select_existing = true;
}
}
if should_select_existing {
dock_ref.select_tab(cx, tab_id);
return;
}

// Otherwise, create a new loading tab at the end.
let (tab_bar, _pos) = dock_ref.find_tab_bar_of_tab(id!(home_tab)).unwrap();
let new_tab_widget = dock_ref.create_and_select_tab(
cx,
tab_bar,
tab_id,
id!(loading_screen),
tab_name.to_string(),
id!(CloseableTab),
None,
);

if let Some(widget) = new_tab_widget {
widget.as_room_loading_screen().show(cx, message, details);
self.loading_tabs.insert(tab_id, (message.map(str::to_owned), details.map(str::to_owned)));
dock_ref.select_tab(cx, tab_id);
} else {
error!("BUG: failed to create loading tab for {tab_name}");
}
}

/// Close a loading-only tab if it exists.
fn close_loading_tab(&mut self, cx: &mut Cx, tab_id: LiveId) {
if self.loading_tabs.remove(&tab_id).is_some() {
self.view.dock(ids!(dock)).close_tab(cx, tab_id);
}
}

/// Replaces an invite with a joined room in the dock.
Expand Down
20 changes: 16 additions & 4 deletions src/home/room_screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use crate::{
user_profile::{AvatarState, ShowUserProfileAction, UserProfile, UserProfileAndRoomId, UserProfilePaneInfo, UserProfileSlidingPaneRef, UserProfileSlidingPaneWidgetExt},
user_profile_cache,
},
room::{room_input_bar::RoomInputBarState, typing_notice::TypingNoticeWidgetExt},
room::{loading_screen::{loading_tab_live_id, show_room_loading_tab}, room_input_bar::RoomInputBarState, typing_notice::TypingNoticeWidgetExt},
shared::{
avatar::AvatarWidgetRefExt, callout_tooltip::{CalloutTooltipOptions, TooltipAction, TooltipPosition}, html_or_plaintext::{HtmlOrPlaintextRef, HtmlOrPlaintextWidgetRefExt, RobrixHtmlLinkAction}, jump_to_bottom_button::{JumpToBottomButtonWidgetExt, UnreadMessageCount}, popup_list::{PopupItem, PopupKind, enqueue_popup_notification}, restore_status_view::RestoreStatusViewWidgetExt, styles::*, text_or_image::{TextOrImageRef, TextOrImageWidgetRefExt}, timestamp::TimestampWidgetRefExt
},
Expand Down Expand Up @@ -1483,16 +1483,28 @@ impl RoomScreen {
log!("TODO: jump to known room {}", room_id);
} else {
log!("TODO: fetch and display room preview for room {}", room_id);
show_room_loading_tab(
loading_tab_live_id(room_id.as_str()),
"Loading...",
Some("Loading...".to_string()),
Some("Loading Room ID...".to_string()),
);
}
false
true
}
MatrixId::RoomAlias(room_alias) => {
log!("TODO: open room alias {}", room_alias);
// TODO: open a room loading screen that shows a spinner
// while our background async task calls Client::resolve_room_alias()
// and then either jumps to the room if known, or fetches and displays
// a room preview for that room.
false
show_room_loading_tab(
loading_tab_live_id(room_alias.alias()),
"Loading...",
Some("Loading...".to_string()),
Some("Loading Room Alias...".to_string()),
);
true
}
MatrixId::Event(room_id, event_id) => {
log!("TODO: open event {} in room {}", event_id, room_id);
Expand Down Expand Up @@ -4174,4 +4186,4 @@ pub fn clear_timeline_states(_cx: &mut Cx) {
TIMELINE_STATES.with_borrow_mut(|states| {
states.clear();
});
}
}
180 changes: 180 additions & 0 deletions src/room/loading_screen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
//! A dock tab loading view for room operations.
//! Use `show_room_loading_tab()` / `hide_room_loading_tab()` to toggle.

use crossbeam_queue::SegQueue;
use makepad_widgets::*;

/// Actions for loading tabs inside the dock.
#[derive(Debug, Clone)]
pub enum RoomLoadingScreenAction {
/// Show or create a loading tab with the given id/name/message.
ShowTab {
tab_id: LiveId,
tab_name: String,
message: Option<String>,
details: Option<String>,
},
/// Close the loading tab with the given id.
HideTab {
tab_id: LiveId,
},
}

/// Pending actions that should be applied on the UI thread.
static PENDING_LOADING_ACTIONS: SegQueue<RoomLoadingScreenAction> = SegQueue::new();

/// Show (or create) a dock tab that only contains a room loading screen.
///
/// `tab_id` should be unique within the dock; a common pattern is
/// `LiveId::from_str(room_id.as_str())` or `LiveId::from_str(&format!(\"loading_{room_id}\"))`.
pub fn show_room_loading_tab(
tab_id: LiveId,
tab_name: impl Into<String>,
message: impl Into<Option<String>>,
details: impl Into<Option<String>>,
) {
PENDING_LOADING_ACTIONS.push(RoomLoadingScreenAction::ShowTab {
tab_id,
tab_name: tab_name.into(),
message: message.into(),
details: details.into(),
});
SignalToUI::set_ui_signal();
}

/// Hide and close the loading tab with the given id, if it exists.
pub fn hide_room_loading_tab(tab_id: LiveId) {
PENDING_LOADING_ACTIONS.push(RoomLoadingScreenAction::HideTab { tab_id });
SignalToUI::set_ui_signal();
}

/// Drain all pending actions for loading tabs.
pub fn drain_room_loading_screen_actions(
) -> impl Iterator<Item = RoomLoadingScreenAction> {
std::iter::from_fn(|| PENDING_LOADING_ACTIONS.pop())
}

/// Deterministic helper to derive a unique LiveId for a loading tab
/// from any stable string (e.g., room id or alias). This keeps the same tab
/// reusable across multiple jumps/clicks.
pub fn loading_tab_live_id(key: &str) -> LiveId {
LiveId::from_str(&format!("loading_{key}"))
}

live_design! {
use link::theme::*;
use link::shaders::*;
use link::widgets::*;

use crate::shared::helpers::*;
use crate::shared::styles::*;


pub RoomLoadingScreen = {{RoomLoadingScreen}}<ScrollXYView> {
width: Fill, height: Fill,
flow: Down,
align: {x: 0.5, y: 0.5},
spacing: 10.0,

show_bg: true,
draw_bg: {
color: (COLOR_PRIMARY_DARKER),
}

loading_spinner = <LoadingSpinner> {
width: 60,
height: 60,
visible: true,
draw_bg: {
color: (COLOR_ACTIVE_PRIMARY)
border_size: 4.0,
}
}

title = <Label> {
width: Fill, height: Fit,
align: {x: 0.5, y: 0.0},
padding: {left: 5.0, right: 0.0}
margin: {top: 10.0},
flow: RightWrap,
draw_text: {
color: (TYPING_NOTICE_TEXT_COLOR),
}
text: "Loading..."
}

details = <Label> {
width: Fill, height: Fit,
align: {x: 0.5, y: 0.0},
padding: {left: 5.0, right: 0.0}
margin: {top: 5.0},
flow: RightWrap,
draw_text: {
color: (TYPING_NOTICE_TEXT_COLOR),
}
text: ""
}
}
}

/// A centered overlay with a spinner and status text.
#[derive(Live, LiveHook, Widget)]
pub struct RoomLoadingScreen {
#[deref]
view: View,

#[live(false)]
visible: bool,
}

impl Widget for RoomLoadingScreen {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
if self.visible {
self.view.handle_event(cx, event, scope);
}
}

fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
if self.visible {
self.view.draw_walk(cx, scope, walk)
} else {
DrawStep::done()
}
}
}

impl RoomLoadingScreenRef {
/// Show the overlay and update the displayed message and optional details.
pub fn show(&self, cx: &mut Cx, message: Option<&str>, details: Option<&str>) {
if let Some(mut inner) = self.borrow_mut() {
inner.visible = true;
inner.view.set_visible(cx, true);
let text = message.unwrap_or("Loading...");
inner.view.label(ids!(title)).set_text(cx, text);
let details_label = inner.view.label(ids!(details));
if let Some(detail_text) = details {
details_label.set_visible(cx, true);
details_label.set_text(cx, detail_text);
} else {
details_label.set_visible(cx, false);
details_label.set_text(cx, "");
}
}
}

/// Update the message without toggling visibility.
pub fn set_message(&self, cx: &mut Cx, message: Option<&str>) {
if let Some(inner) = self.borrow() {
let text = message.unwrap_or("Loading...");
inner.view.label(ids!(title)).set_text(cx, text);
}
}

/// Hide the overlay.
pub fn hide(&self, cx: &mut Cx) {
if let Some(mut inner) = self.borrow_mut() {
inner.visible = false;
inner.view.set_visible(cx, false);
}
}
}
2 changes: 2 additions & 0 deletions src/room/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ use crate::utils::avatar_from_room_name;
pub mod reply_preview;
pub mod room_input_bar;
pub mod room_display_filter;
pub mod loading_screen;
pub mod typing_notice;

pub fn live_design(cx: &mut Cx) {
reply_preview::live_design(cx);
room_input_bar::live_design(cx);
loading_screen::live_design(cx);
typing_notice::live_design(cx);
}

Expand Down