diff --git a/crates/re_viewer/src/app.rs b/crates/re_viewer/src/app.rs index 3d03edb6d6dc..23800a15bc2e 100644 --- a/crates/re_viewer/src/app.rs +++ b/crates/re_viewer/src/app.rs @@ -678,7 +678,7 @@ impl App { crate::ui::mobile_warning_ui(&self.re_ui, ui); - crate::ui::top_panel(app_blueprint, store_context, ui, self, gpu_resource_stats); + crate::ui::top_panel(self, app_blueprint, store_context, gpu_resource_stats, ui); self.memory_panel_ui(ui, gpu_resource_stats, store_stats); @@ -731,10 +731,11 @@ impl App { render_ctx.before_submit(); } } else { - // This is part of the loading vs. welcome screen UI logic. The loading screen - // is displayed when no app ID is set. This is e.g. the initial state for the - // web demos. - crate::ui::loading_ui(ui, &self.rx); + // There's nothing to show. + // We get here when + // A) there is nothing loaded + // B) we decided not to show the welcome screen, presumably because data is expected at any time now. + // The user can see the connection status in the top bar. } }); } @@ -941,12 +942,13 @@ impl App { } } - /// This function will create an empty blueprint whenever the welcome screen should be - /// displayed. - /// - /// The welcome screen can be displayed only when a blueprint is available (and no recording is - /// loaded). This function implements the heuristic which determines when the welcome screen + /// This function implements a heuristic which determines when the welcome screen /// should show up. + /// + /// Why not always show it when no data is loaded? + /// Because sometimes we expect data to arrive at any moment, + /// and showing the wlecome screen for a few frames will just be an annoying flash + /// in the users face. fn should_show_welcome_screen(&mut self, store_hub: &StoreHub) -> bool { // Don't show the welcome screen if we have actual data to display. if store_hub.current_recording().is_some() || store_hub.selected_application_id().is_some() diff --git a/crates/re_viewer/src/ui/mod.rs b/crates/re_viewer/src/ui/mod.rs index 075eb1d4aafc..53eeb8b238fd 100644 --- a/crates/re_viewer/src/ui/mod.rs +++ b/crates/re_viewer/src/ui/mod.rs @@ -17,5 +17,5 @@ pub use recordings_panel::recordings_panel_ui; pub(crate) use { self::mobile_warning_ui::mobile_warning_ui, self::top_panel::top_panel, - self::welcome_screen::loading_ui, self::welcome_screen::WelcomeScreen, + self::welcome_screen::WelcomeScreen, }; diff --git a/crates/re_viewer/src/ui/recordings_panel.rs b/crates/re_viewer/src/ui/recordings_panel.rs index 42dc8abbe761..876b7751165e 100644 --- a/crates/re_viewer/src/ui/recordings_panel.rs +++ b/crates/re_viewer/src/ui/recordings_panel.rs @@ -69,7 +69,7 @@ fn loading_receivers_ui( | SmartChannelSource::Sdk | SmartChannelSource::WsClient { .. } | SmartChannelSource::TcpServer { .. } => { - // TODO(#3046): show these in status bar + // These show up in the top panel - see `top_panel.rs`. continue; } }; diff --git a/crates/re_viewer/src/ui/top_panel.rs b/crates/re_viewer/src/ui/top_panel.rs index 660895ecec39..dfc2eae5e1d1 100644 --- a/crates/re_viewer/src/ui/top_panel.rs +++ b/crates/re_viewer/src/ui/top_panel.rs @@ -1,17 +1,19 @@ use egui::NumExt as _; +use itertools::Itertools; use re_format::format_number; use re_renderer::WgpuResourcePoolStatistics; +use re_smart_channel::{ReceiveSet, SmartChannelSource}; use re_ui::UICommand; use re_viewer_context::StoreContext; use crate::{app_blueprint::AppBlueprint, App}; pub fn top_panel( + app: &mut App, app_blueprint: &AppBlueprint<'_>, store_context: Option<&StoreContext<'_>>, - ui: &mut egui::Ui, - app: &mut App, gpu_resource_stats: &WgpuResourcePoolStatistics, + ui: &mut egui::Ui, ) { re_tracing::profile_function!(); @@ -26,10 +28,11 @@ pub fn top_panel( ui.set_height(top_bar_style.height); ui.add_space(top_bar_style.indent); - top_bar_ui(app_blueprint, store_context, ui, app, gpu_resource_stats); + top_bar_ui(app, app_blueprint, store_context, ui, gpu_resource_stats); }) .response; + // React to dragging and double-clicking the top bar: #[cfg(not(target_arch = "wasm32"))] if !re_ui::NATIVE_WINDOW_BAR { let title_bar_response = _response.interact(egui::Sense::click()); @@ -45,10 +48,10 @@ pub fn top_panel( } fn top_bar_ui( + app: &mut App, app_blueprint: &AppBlueprint<'_>, store_context: Option<&StoreContext<'_>>, ui: &mut egui::Ui, - app: &mut App, gpu_resource_stats: &WgpuResourcePoolStatistics, ) { app.rerun_menu_button_ui(store_context, ui); @@ -76,58 +79,11 @@ fn top_bar_ui( ui.add_space(extra_margin); } - let mut selection_panel_expanded = app_blueprint.selection_panel_expanded; - if app - .re_ui() - .medium_icon_toggle_button( - ui, - &re_ui::icons::RIGHT_PANEL_TOGGLE, - &mut selection_panel_expanded, - ) - .on_hover_text(format!( - "Toggle Selection View{}", - UICommand::ToggleSelectionPanel.format_shortcut_tooltip_suffix(ui.ctx()) - )) - .clicked() - { - app_blueprint.toggle_selection_panel(&app.command_sender); - } - - let mut time_panel_expanded = app_blueprint.time_panel_expanded; - if app - .re_ui() - .medium_icon_toggle_button( - ui, - &re_ui::icons::BOTTOM_PANEL_TOGGLE, - &mut time_panel_expanded, - ) - .on_hover_text(format!( - "Toggle Timeline View{}", - UICommand::ToggleTimePanel.format_shortcut_tooltip_suffix(ui.ctx()) - )) - .clicked() - { - app_blueprint.toggle_time_panel(&app.command_sender); - } + panel_buttons_r2l(app, app_blueprint, ui); - let mut blueprint_panel_expanded = app_blueprint.blueprint_panel_expanded; - if app - .re_ui() - .medium_icon_toggle_button( - ui, - &re_ui::icons::LEFT_PANEL_TOGGLE, - &mut blueprint_panel_expanded, - ) - .on_hover_text(format!( - "Toggle Blueprint View{}", - UICommand::ToggleBlueprintPanel.format_shortcut_tooltip_suffix(ui.ctx()) - )) - .clicked() - { - app_blueprint.toggle_blueprint_panel(&app.command_sender); - } + connection_status_ui(ui, app.msg_receive_set()); - if cfg!(debug_assertions) && app.app_options().show_metrics { + if cfg!(debug_assertions) { ui.vertical_centered(|ui| { ui.style_mut().wrap = Some(false); ui.add_space(6.0); // TODO(emilk): in egui, add a proper way of centering a single widget in a UI. @@ -137,6 +93,143 @@ fn top_bar_ui( }); } +fn connection_status_ui(ui: &mut egui::Ui, rx: &ReceiveSet) { + let sources = rx + .sources() + .into_iter() + .filter(|source| { + match source.as_ref() { + SmartChannelSource::File(_) | SmartChannelSource::RrdHttpStream { .. } => { + false // These show up in the recordings panel as a "Loading…" in `recordings_panel.rs` + } + + re_smart_channel::SmartChannelSource::RrdWebEventListener + | re_smart_channel::SmartChannelSource::Sdk + | re_smart_channel::SmartChannelSource::WsClient { .. } + | re_smart_channel::SmartChannelSource::TcpServer { .. } => true, + } + }) + .collect_vec(); + + match sources.len() { + 0 => return, + 1 => { + source_label(ui, sources[0].as_ref()); + } + n => { + // In practice we never get here + ui.label(format!("{n} sources connected")) + .on_hover_ui(|ui| { + ui.vertical(|ui| { + for source in &sources { + source_label(ui, source.as_ref()); + } + }); + }); + } + } + + fn source_label(ui: &mut egui::Ui, source: &SmartChannelSource) -> egui::Response { + let response = ui.label(status_string(source)); + + let tooltip = match source { + SmartChannelSource::File(_) + | SmartChannelSource::RrdHttpStream { .. } + | SmartChannelSource::RrdWebEventListener + | SmartChannelSource::Sdk + | SmartChannelSource::WsClient { .. } => None, + + SmartChannelSource::TcpServer { .. } => { + Some("Waiting for an SDK to connect".to_owned()) + } + }; + + if let Some(tooltip) = tooltip { + response.on_hover_text(tooltip) + } else { + response + } + } + + fn status_string(source: &SmartChannelSource) -> String { + match source { + re_smart_channel::SmartChannelSource::File(path) => { + format!("Loading {}…", path.display()) + } + re_smart_channel::SmartChannelSource::RrdHttpStream { url } => { + format!("Loading {url}…") + } + re_smart_channel::SmartChannelSource::RrdWebEventListener => { + "Waiting for logging data…".to_owned() + } + re_smart_channel::SmartChannelSource::Sdk => { + "Waiting for logging data from SDK".to_owned() + } + re_smart_channel::SmartChannelSource::WsClient { ws_server_url } => { + // TODO(emilk): it would be even better to know whether or not we are connected, or are attempting to connect + format!("Waiting for data from {ws_server_url}") + } + re_smart_channel::SmartChannelSource::TcpServer { port } => { + format!("Listening on TCP port {port}") + } + } + } +} + +/// Lay out the panel button right-to-left +fn panel_buttons_r2l(app: &App, app_blueprint: &AppBlueprint<'_>, ui: &mut egui::Ui) { + let mut selection_panel_expanded = app_blueprint.selection_panel_expanded; + if app + .re_ui() + .medium_icon_toggle_button( + ui, + &re_ui::icons::RIGHT_PANEL_TOGGLE, + &mut selection_panel_expanded, + ) + .on_hover_text(format!( + "Toggle Selection View{}", + UICommand::ToggleSelectionPanel.format_shortcut_tooltip_suffix(ui.ctx()) + )) + .clicked() + { + app_blueprint.toggle_selection_panel(&app.command_sender); + } + + let mut time_panel_expanded = app_blueprint.time_panel_expanded; + if app + .re_ui() + .medium_icon_toggle_button( + ui, + &re_ui::icons::BOTTOM_PANEL_TOGGLE, + &mut time_panel_expanded, + ) + .on_hover_text(format!( + "Toggle Timeline View{}", + UICommand::ToggleTimePanel.format_shortcut_tooltip_suffix(ui.ctx()) + )) + .clicked() + { + app_blueprint.toggle_time_panel(&app.command_sender); + } + + let mut blueprint_panel_expanded = app_blueprint.blueprint_panel_expanded; + if app + .re_ui() + .medium_icon_toggle_button( + ui, + &re_ui::icons::LEFT_PANEL_TOGGLE, + &mut blueprint_panel_expanded, + ) + .on_hover_text(format!( + "Toggle Blueprint View{}", + UICommand::ToggleBlueprintPanel.format_shortcut_tooltip_suffix(ui.ctx()) + )) + .clicked() + { + app_blueprint.toggle_blueprint_panel(&app.command_sender); + } +} + /// Shows clickable website link as an image (text doesn't look as nice) fn website_link_ui(ui: &mut egui::Ui) { let desired_height = ui.max_rect().height(); diff --git a/crates/re_viewer/src/ui/welcome_screen/mod.rs b/crates/re_viewer/src/ui/welcome_screen/mod.rs index f2391cd16a77..319b0fd8dfa7 100644 --- a/crates/re_viewer/src/ui/welcome_screen/mod.rs +++ b/crates/re_viewer/src/ui/welcome_screen/mod.rs @@ -1,12 +1,14 @@ mod example_page; mod welcome_page; +use std::hash::Hash; + use egui::Widget; +use welcome_page::welcome_page_ui; + use re_log_types::LogMsg; -use re_smart_channel::{ReceiveSet, SmartChannelSource}; +use re_smart_channel::ReceiveSet; use re_ui::ReUi; -use std::hash::Hash; -use welcome_page::welcome_page_ui; #[derive(Debug, Default, PartialEq, Hash)] enum WelcomeScreenPage { @@ -104,39 +106,6 @@ impl WelcomeScreen { } } -/// Full-screen UI shown while in loading state. -pub fn loading_ui(ui: &mut egui::Ui, rx: &ReceiveSet) { - let status_strings = status_strings(rx); - if status_strings.is_empty() { - return; - } - - ui.centered_and_justified(|ui| { - for status_string in status_strings { - let style = ui.style(); - let mut layout_job = egui::text::LayoutJob::default(); - layout_job.append( - status_string.status, - 0.0, - egui::TextFormat::simple( - egui::TextStyle::Heading.resolve(style), - style.visuals.strong_text_color(), - ), - ); - layout_job.append( - &format!("\n\n{}", status_string.source), - 0.0, - egui::TextFormat::simple( - egui::TextStyle::Body.resolve(style), - style.visuals.text_color(), - ), - ); - layout_job.halign = egui::Align::Center; - ui.label(layout_job); - } - }); -} - fn set_large_button_style(ui: &mut egui::Ui) { ui.style_mut().spacing.button_padding = egui::vec2(10.0, 7.0); let visuals = ui.visuals_mut(); @@ -182,64 +151,3 @@ fn large_text_button(ui: &mut egui::Ui, text: impl Into) -> eg }) .inner } - -/// Describes the current state of the Rerun viewer. -struct StatusString { - /// General status string (e.g. "Ready", "Loading…", etc.). - status: &'static str, - - /// Source string (e.g. listening IP, file path, etc.). - source: String, - - /// Whether or not the status is valid once data loading is completed, i.e. if data may still - /// be received later. - long_term: bool, -} - -impl StatusString { - fn new(status: &'static str, source: String, long_term: bool) -> Self { - Self { - status, - source, - long_term, - } - } -} - -/// Returns the status strings to be displayed by the loading and welcome screen. -fn status_strings(rx: &ReceiveSet) -> Vec { - rx.sources() - .into_iter() - .map(|s| status_string(&s)) - .collect() -} - -fn status_string(source: &SmartChannelSource) -> StatusString { - match source { - re_smart_channel::SmartChannelSource::File(path) => { - StatusString::new("Loading…", path.display().to_string(), false) - } - re_smart_channel::SmartChannelSource::RrdHttpStream { url } => { - StatusString::new("Loading…", url.clone(), false) - } - re_smart_channel::SmartChannelSource::RrdWebEventListener => { - StatusString::new("Ready", "Waiting for logging data…".to_owned(), true) - } - re_smart_channel::SmartChannelSource::Sdk => StatusString::new( - "Ready", - "Waiting for logging data from SDK".to_owned(), - true, - ), - re_smart_channel::SmartChannelSource::WsClient { ws_server_url } => { - // TODO(emilk): it would be even better to know whether or not we are connected, or are attempting to connect - StatusString::new( - "Ready", - format!("Waiting for data from {ws_server_url}"), - true, - ) - } - re_smart_channel::SmartChannelSource::TcpServer { port } => { - StatusString::new("Ready", format!("Listening on port {port}"), true) - } - } -} diff --git a/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs b/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs index 5d2f912e3cc1..843f3ff80433 100644 --- a/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs +++ b/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs @@ -1,4 +1,4 @@ -use super::{large_text_button, status_strings, url_large_text_button, WelcomeScreenResponse}; +use super::{large_text_button, url_large_text_button, WelcomeScreenResponse}; use egui::{NumExt, Ui}; use re_data_store::StoreDb; use re_log_types::{ @@ -46,23 +46,7 @@ pub(super) fn welcome_page_ui( ) -> WelcomeScreenResponse { ui.vertical(|ui| { let accepts_connections = rx.accepts_tcp_connections(); - - let show_example = onboarding_content_ui(ui, command_sender, accepts_connections); - - for status_strings in status_strings(rx) { - if status_strings.long_term { - ui.add_space(55.0); - ui.vertical_centered(|ui| { - ui.label(status_strings.status); - ui.label( - egui::RichText::new(status_strings.source) - .color(ui.visuals().weak_text_color()), - ); - }); - } - } - - show_example + onboarding_content_ui(ui, command_sender, accepts_connections) }) .inner }