Skip to content

Commit

Permalink
Always show welcome screen, but fade it in some situations (#5787)
Browse files Browse the repository at this point in the history
### What
* Closes #4317

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.
It also feel inconsistent with how the welcome screen is now just
another app in the recordings panel.

So the new design is much simpler. We always show the welcome screen,
but if we expect data to come streaming in at any moment, we delay the
welcome screen 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`

### Checklist
* [x] I have read and agree to [Contributor
Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and
the [Code of
Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md)
* [x] I've included a screenshot or gif (if applicable)
* [x] I have tested the web demo (if applicable):
* Using newly built examples:
[rerun.io/viewer](https://rerun.io/viewer/pr/5787)
* Using examples from latest `main` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/5787?manifest_url=https://app.rerun.io/version/main/examples_manifest.json)
* Using full set of examples from `nightly` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/5787?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json)
* [x] The PR title and labels are set such as to maximize their
usefulness for the next release's CHANGELOG
* [x] If applicable, add a new check to the [release
checklist](https://github.com/rerun-io/rerun/blob/main/tests/python/release_checklist)!

- [PR Build Summary](https://build.rerun.io/pr/5787)
- [Docs
preview](https://rerun.io/preview/e24d4fd0a23d862697fb1dce865adbd6d1c872b5/docs)
<!--DOCS-PREVIEW-->
- [Examples
preview](https://rerun.io/preview/e24d4fd0a23d862697fb1dce865adbd6d1c872b5/examples)
<!--EXAMPLES-PREVIEW-->
- [Recent benchmark results](https://build.rerun.io/graphs/crates.html)
- [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)

---------

Co-authored-by: Jeremy Leibs <[email protected]>
  • Loading branch information
emilk and jleibs authored Apr 5, 2024
1 parent 39af075 commit 95b373c
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 71 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("--expect-data-soon")
.args(opts.extra_args.clone())
.spawn()
.map_err(map_err)?;
Expand Down
1 change: 1 addition & 0 deletions crates/re_smart_channel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub enum SmartChannelSource {
/// js event.
///
/// Only applicable to web browser iframes.
/// Used for the inline web viewer in a notebook.
RrdWebEventListener,

/// The channel was created in the context of loading data using a Rerun SDK sharing the same
Expand Down
115 changes: 67 additions & 48 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 expect_data_soon: Option<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,
expect_data_soon: None,
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 @@ -906,6 +915,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 @@ -1166,60 +1176,60 @@ 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;
fn should_fade_in_welcome_screen(&self) -> bool {
if let Some(expect_data_soon) = self.startup_options.expect_data_soon {
return expect_data_soon;
}

let sources = self.rx.sources();

if sources.is_empty() {
return true;
}
// 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.

// 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 {
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;
}

// The workflows associated with these sources typically do not require showing the
// welcome screen until after some recording have been loaded and then closed.
SmartChannelSource::RrdWebEventListener
| SmartChannelSource::Stdin
| SmartChannelSource::RrdWebEventListener
| SmartChannelSource::Sdk
| SmartChannelSource::WsClient { .. } => {}
| SmartChannelSource::WsClient { .. } => {
return true; // We expect data soon, so fade-in
}

// 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;
// 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 that case we really want to fade in the welcome screen.
// Therefore `spawn()` uses the special `--expect-data-soon` 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 @@ -1235,7 +1245,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 @@ -1415,10 +1424,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,
expect_data_soon: None,
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 @@ -133,8 +130,6 @@ impl StoreHub {
active_blueprint_by_app_id,
store_bundle,

was_recording_active: false,

blueprint_last_save: Default::default(),
blueprint_last_gc: Default::default(),
}
Expand Down Expand Up @@ -287,7 +282,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 @@ -314,14 +308,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 @@ -354,7 +340,6 @@ impl StoreHub {
}

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

/// Activate a recording by its [`StoreId`].
Expand Down
16 changes: 13 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,
expect_data_soon: bool,

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

skip_welcome_screen: args.skip_welcome_screen,
expect_data_soon: if args.expect_data_soon {
Some(true)
} else {
None
},

// 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",
"--expect-data-soon",
],
env=new_env,
start_new_session=True,
Expand Down

0 comments on commit 95b373c

Please sign in to comment.