Skip to content

Commit

Permalink
Always show welcome screen, but fade it in some situations
Browse files Browse the repository at this point in the history
It used to be that `rerun --web-viewer` didn't show the welcome screen,
which is super annoying when testing.
It also feels very brittle to sometimes show it, and sometimes not,
and to keep track if we've ever seen a recording, etc.

SO: the new design is much simpler. We always show the welcome screen,
but if we expect data to come streaming in at any second we delay
it for a a fraction of a second and then fade it in.
This prevents the problem of a flashing welcome screen before showing
actual user data.
This fade-in was proposed during the initial design of the welcome
screen, but we never got to it.

This replaces `--skip-welcome-screen` with `--fade-in-welcome-screen`.

You can test it with `pixi run rerun ----fade-in-welcome-screen`
  • Loading branch information
emilk committed Apr 4, 2024
1 parent 31cc314 commit 912f24f
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 64 deletions.
2 changes: 1 addition & 1 deletion crates/re_sdk/src/spawn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ pub fn spawn(opts: &SpawnOptions) -> Result<(), SpawnError> {
.stdin(std::process::Stdio::null())
.arg(format!("--port={port}"))
.arg(format!("--memory-limit={memory_limit}"))
.arg("--skip-welcome-screen")
.arg("--fade-in-welcome-screen")
.args(opts.extra_args.clone())
.spawn()
.map_err(map_err)?;
Expand Down
105 changes: 64 additions & 41 deletions crates/re_viewer/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use re_data_source::{DataSource, FileContents};
use re_entity_db::entity_db::EntityDb;
use re_log_types::{FileSource, LogMsg, StoreKind};
use re_log_types::{ApplicationId, FileSource, LogMsg, StoreKind};
use re_renderer::WgpuResourcePoolStatistics;
use re_smart_channel::{ReceiveSet, SmartChannelSource};
use re_ui::{toasts, UICommand, UICommandSender};
Expand Down Expand Up @@ -51,7 +51,14 @@ pub struct StartupOptions {
#[cfg(not(target_arch = "wasm32"))]
pub resolution_in_points: Option<[f32; 2]>,

pub skip_welcome_screen: bool,
/// This is a hint that we expect a recording to stream in very soon.
///
/// This is set by the `spawn()` method in our logging SDK.
///
/// The viewer will respond by fading in the welcome screen,
/// instead of showing it directly.
/// This ensures that it won't blink for a few frames before switching to the recording.
pub fade_in_welcome_screen: bool,

/// Forces wgpu backend to use the specified graphics API.
pub force_wgpu_backend: Option<String>,
Expand All @@ -73,7 +80,7 @@ impl Default for StartupOptions {
#[cfg(not(target_arch = "wasm32"))]
resolution_in_points: None,

skip_welcome_screen: false,
fade_in_welcome_screen: false,
force_wgpu_backend: None,
}
}
Expand All @@ -90,6 +97,7 @@ const MAX_ZOOM_FACTOR: f32 = 5.0;
pub struct App {
build_info: re_build_info::BuildInfo,
startup_options: StartupOptions,
start_time: web_time::Instant,
ram_limit_warner: re_memory::RamLimitWarner,
pub(crate) re_ui: re_ui::ReUi,
screenshotter: crate::screenshotter::Screenshotter,
Expand Down Expand Up @@ -217,6 +225,7 @@ impl App {
Self {
build_info,
startup_options,
start_time: web_time::Instant::now(),
ram_limit_warner: re_memory::RamLimitWarner::warn_at_fraction_of_max(0.75),
re_ui,
screenshotter,
Expand Down Expand Up @@ -901,6 +910,7 @@ impl App {
&self.space_view_class_registry,
&self.rx,
&self.command_sender,
self.welcome_screen_opacity(egui_ctx),
);
}
render_ctx.before_submit();
Expand Down Expand Up @@ -1161,41 +1171,22 @@ impl App {
}
}

/// 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.active_recording().is_some() || store_hub.active_app().is_some() {
return false;
}

// Don't show the welcome screen if the `--skip-welcome-screen` flag was used (e.g. by the
// Python SDK), until some data has been loaded and shown. This way, we *still* show the
// welcome screen when the user closes all recordings after, e.g., running a Python example.
if self.startup_options.skip_welcome_screen && !store_hub.was_recording_active() {
return false;
}

let sources = self.rx.sources();

if sources.is_empty() {
fn should_fade_in_welcome_screen(&self) -> bool {
if self.startup_options.fade_in_welcome_screen {
return true;
}

// Here, we use the type of Receiver as a proxy for which kind of workflow the viewer is
// being used in.
for source in sources {
// The reason for the fade-in is to avoid the welcome screen
// flickering quickly before receiving some data.
// So: if we expect data very soon, we do a fade-in.

for source in self.rx.sources() {
#[allow(clippy::match_same_arms)]
match &*source {
// No need for a welcome screen - data is coming soon!
SmartChannelSource::File(_)
| SmartChannelSource::RrdHttpStream { .. }
| SmartChannelSource::Stdin => {
return false;
return true; // Data is coming soon, so fade-in
}

// The workflows associated with these sources typically do not require showing the
Expand All @@ -1204,17 +1195,40 @@ impl App {
| SmartChannelSource::Sdk
| SmartChannelSource::WsClient { .. } => {}

// This might be the trickiest case. When running the bare executable, we want to show
// the welcome screen (default, "new user" workflow). There are other case using Tcp
// where it's not the case, including Python/C++ SDKs and possibly other, advanced used,
// scenarios. In this cases, `--skip-welcome-screen` should be used.
SmartChannelSource::TcpServer { .. } => {
return true;
// This might be the trickiest case.
// We start a TCP server by default in native rerun, i.e. when just running `rerun`,
// and in that case fading in the welcome screen would be slightly annoying.
// However, we also use the TCP server for sending data from the logging SDKs
// when they call `spawn()`, and in thaty case we really want to fade in the welcome screen.
// Therefore `spawn()` uses the special `--fade-in-welcome-screen` flag
// (handled earlier in this function), so here we know we are in the other case:
// a user calling `rerun` in their terminal (don't fade in).
}
}
}

false
false // No special sources (or no sources at all), so don't fade in
}

/// Handle fading in the welcome screen, if we should.
fn welcome_screen_opacity(&self, egui_ctx: &egui::Context) -> f32 {
if self.should_fade_in_welcome_screen() {
// The reason for this delay is to avoid the welcome screen
// flickering quickly before receiving some data.
// The only time it has for that is between the call to `spawn` and sending the recording info,
// which should happen _right away_, so we only need a small delay.
// Why not skip the wlecome screen completely when we expect the data?
// Because maybe the data never comes.
let sec_since_first_shown = self.start_time.elapsed().as_secs_f32();
let opacity = egui::remap_clamp(sec_since_first_shown, 0.4..=0.6, 0.0..=1.0);
if opacity < 1.0 {
egui_ctx.request_repaint();
}
opacity
} else {
1.0
}
}
}

Expand All @@ -1230,7 +1244,6 @@ fn blueprint_loader() -> BlueprintPersistence {
#[cfg(not(target_arch = "wasm32"))]
fn blueprint_loader() -> BlueprintPersistence {
use re_entity_db::StoreBundle;
use re_log_types::ApplicationId;

fn load_blueprint_from_disk(app_id: &ApplicationId) -> anyhow::Result<Option<StoreBundle>> {
let blueprint_path = crate::saving::default_blueprint_path(app_id)?;
Expand Down Expand Up @@ -1394,10 +1407,20 @@ impl eframe::App for App {

file_saver_progress_ui(egui_ctx, &mut self.background_tasks); // toasts for background file saver

// Heuristic to set the app_id to the welcome screen blueprint.
// Make sure some app is active
// Must be called before `read_context` below.
if self.should_show_welcome_screen(&store_hub) {
store_hub.set_active_app(StoreHub::welcome_screen_app_id());
if store_hub.active_app().is_none() {
let apps: std::collections::BTreeSet<&ApplicationId> = store_hub
.store_bundle()
.entity_dbs()
.filter_map(|db| db.app_id())
.filter(|&app_id| app_id != &StoreHub::welcome_screen_app_id())
.collect();
if let Some(app_id) = apps.first().cloned() {
store_hub.set_active_app(app_id.clone());
} else {
store_hub.set_active_app(StoreHub::welcome_screen_app_id());
}
}

let store_context = store_hub.read_context();
Expand Down
3 changes: 2 additions & 1 deletion crates/re_viewer/src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ impl AppState {
space_view_class_registry: &SpaceViewClassRegistry,
rx: &ReceiveSet<LogMsg>,
command_sender: &CommandSender,
welcome_screen_opacity: f32,
) {
re_tracing::profile_function!();

Expand Down Expand Up @@ -379,7 +380,7 @@ impl AppState {
.frame(viewport_frame)
.show_inside(ui, |ui| {
if show_welcome {
welcome_screen.ui(ui, re_ui, command_sender);
welcome_screen.ui(ui, re_ui, command_sender, welcome_screen_opacity);
} else {
viewport.viewport_ui(ui, &ctx);
}
Expand Down
14 changes: 13 additions & 1 deletion crates/re_viewer/src/ui/welcome_screen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,19 @@ impl WelcomeScreen {
ui: &mut egui::Ui,
re_ui: &re_ui::ReUi,
command_sender: &re_viewer_context::CommandSender,
welcome_screen_opacity: f32,
) {
if welcome_screen_opacity <= 0.0 {
return;
}

// This is needed otherwise `example_page_ui` bleeds by a few pixels over the timeline panel
// TODO(ab): figure out why that happens
ui.set_clip_rect(ui.available_rect_before_wrap());

let horizontal_scroll = ui.available_width() < 40.0 * 2.0 + MIN_COLUMN_WIDTH;

egui::ScrollArea::new([horizontal_scroll, true])
let response = egui::ScrollArea::new([horizontal_scroll, true])
.id_source("welcome_screen_page")
.auto_shrink([false, false])
.show(ui, |ui| {
Expand All @@ -45,5 +50,12 @@ impl WelcomeScreen {
.ui(ui, re_ui, command_sender, &welcome_section_ui);
});
});

if welcome_screen_opacity < 1.0 {
let cover_opacity = 1.0 - welcome_screen_opacity;
let fill_color = ui.visuals().panel_fill.gamma_multiply(cover_opacity);
ui.painter()
.rect_filled(response.inner_rect, 0.0, fill_color);
}
}
}
2 changes: 1 addition & 1 deletion crates/re_viewer/src/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ fn create_app(
location: Some(cc.integration_info.web_info.location.clone()),
persist_state: get_persist_state(&cc.integration_info),
is_in_notebook: is_in_notebook(&cc.integration_info),
skip_welcome_screen: false,
fade_in_welcome_screen: false,
force_wgpu_backend: None,
};
let re_ui = crate::customize_eframe(cc);
Expand Down
15 changes: 0 additions & 15 deletions crates/re_viewer_context/src/store_hub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ pub struct StoreHub {
active_blueprint_by_app_id: HashMap<ApplicationId, StoreId>,
store_bundle: StoreBundle,

/// Was a recording ever activated? Used by the heuristic controlling the welcome screen.
was_recording_active: bool,

// The [`StoreGeneration`] from when the [`EntityDb`] was last saved
blueprint_last_save: HashMap<StoreId, StoreGeneration>,
}
Expand Down Expand Up @@ -125,8 +122,6 @@ impl StoreHub {
active_blueprint_by_app_id,
store_bundle,

was_recording_active: false,

blueprint_last_save: Default::default(),
}
}
Expand Down Expand Up @@ -270,7 +265,6 @@ impl StoreHub {
{
if rec.app_id() == Some(&app_id) {
self.active_rec_id = Some(rec.store_id().clone());
self.was_recording_active = true;
return;
}
}
Expand All @@ -297,14 +291,6 @@ impl StoreHub {
// ---------------------
// Active recording

/// Keeps track if a recording was ever activated.
///
/// This is useful for the heuristic controlling the welcome screen.
#[inline]
pub fn was_recording_active(&self) -> bool {
self.was_recording_active
}

/// Directly access the [`EntityDb`] for the active recording.
#[inline]
pub fn active_recording_id(&self) -> Option<&StoreId> {
Expand Down Expand Up @@ -337,7 +323,6 @@ impl StoreHub {
}

self.active_rec_id = Some(recording_id);
self.was_recording_active = true;
}

/// Activate a recording by its [`StoreId`].
Expand Down
12 changes: 9 additions & 3 deletions crates/rerun/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,15 @@ When persisted, the state will be stored at the following locations:
#[clap(long)]
serve: bool,

/// Do not display the welcome screen.
/// This is a hint that we expect a recording to stream in very soon.
///
/// This is set by the `spawn()` method in our logging SDK.
///
/// The viewer will respond by fading in the welcome screen,
/// instead of showing it directly.
/// This ensures that it won't blink for a few frames before switching to the recording.
#[clap(long)]
skip_welcome_screen: bool,
fade_in_welcome_screen: bool,

/// The number of compute threads to use.
///
Expand Down Expand Up @@ -614,7 +620,7 @@ async fn run_impl(
is_in_notebook: false,
screenshot_to_path_then_quit: args.screenshot_to.clone(),

skip_welcome_screen: args.skip_welcome_screen,
fade_in_welcome_screen: args.fade_in_welcome_screen,

// TODO(emilk): make it easy to set this on eframe instead
resolution_in_points: if let Some(size) = &args.window_size {
Expand Down
2 changes: 1 addition & 1 deletion rerun_py/rerun_sdk/rerun/sinks.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ def spawn(
"import rerun_bindings; rerun_bindings.main()",
f"--port={port}",
f"--memory-limit={memory_limit}",
"--skip-welcome-screen",
"--fade-in-welcome-screen",
],
env=new_env,
start_new_session=True,
Expand Down

0 comments on commit 912f24f

Please sign in to comment.