diff --git a/crates/re_ui/data/images/onboarding-configure.png b/crates/re_ui/data/images/onboarding-configure.png new file mode 100644 index 000000000000..717c4fcdd12c Binary files /dev/null and b/crates/re_ui/data/images/onboarding-configure.png differ diff --git a/crates/re_ui/data/images/onboarding-examples.jpg b/crates/re_ui/data/images/onboarding-examples.jpg new file mode 100644 index 000000000000..2b7387d5ab21 Binary files /dev/null and b/crates/re_ui/data/images/onboarding-examples.jpg differ diff --git a/crates/re_ui/data/images/onboarding-live-data.png b/crates/re_ui/data/images/onboarding-live-data.png new file mode 100644 index 000000000000..fe722309c2f7 Binary files /dev/null and b/crates/re_ui/data/images/onboarding-live-data.png differ diff --git a/crates/re_ui/data/images/onboarding-recorded-data.png b/crates/re_ui/data/images/onboarding-recorded-data.png new file mode 100644 index 000000000000..bd096327d331 Binary files /dev/null and b/crates/re_ui/data/images/onboarding-recorded-data.png differ diff --git a/crates/re_ui/data/images/welcome_screen_configure.png b/crates/re_ui/data/images/welcome_screen_configure.png deleted file mode 100644 index da6ae84b2295..000000000000 Binary files a/crates/re_ui/data/images/welcome_screen_configure.png and /dev/null differ diff --git a/crates/re_ui/data/images/welcome_screen_live_data.png b/crates/re_ui/data/images/welcome_screen_live_data.png deleted file mode 100644 index 39faa8551fcb..000000000000 Binary files a/crates/re_ui/data/images/welcome_screen_live_data.png and /dev/null differ diff --git a/crates/re_ui/data/images/welcome_screen_recorded_data.png b/crates/re_ui/data/images/welcome_screen_recorded_data.png deleted file mode 100644 index 34ae4c4c4c27..000000000000 Binary files a/crates/re_ui/data/images/welcome_screen_recorded_data.png and /dev/null differ diff --git a/crates/re_ui/src/icons.rs b/crates/re_ui/src/icons.rs index 5824007a72a4..1072661738b0 100644 --- a/crates/re_ui/src/icons.rs +++ b/crates/re_ui/src/icons.rs @@ -109,15 +109,20 @@ pub const CONTAINER: Icon = Icon::new("container", include_bytes!("../data/icons pub const WELCOME_SCREEN_CONFIGURE: Icon = Icon::new( "welcome_screen_configure", - include_bytes!("../data/images/welcome_screen_configure.png"), + include_bytes!("../data/images/onboarding-configure.png"), ); pub const WELCOME_SCREEN_LIVE_DATA: Icon = Icon::new( "welcome_screen_live_data", - include_bytes!("../data/images/welcome_screen_live_data.png"), + include_bytes!("../data/images/onboarding-live-data.png"), ); pub const WELCOME_SCREEN_RECORDED_DATA: Icon = Icon::new( "welcome_screen_recorded_data", - include_bytes!("../data/images/welcome_screen_recorded_data.png"), + include_bytes!("../data/images/onboarding-recorded-data.png"), +); + +pub const WELCOME_SCREEN_EXAMPLES: Icon = Icon::new( + "welcome_screen_examples", + include_bytes!("../data/images/onboarding-examples.jpg"), ); diff --git a/crates/re_viewer/data/examples_manifest.json b/crates/re_viewer/data/examples_manifest.json index 79e4d619a927..cf97a410b3e0 100644 --- a/crates/re_viewer/data/examples_manifest.json +++ b/crates/re_viewer/data/examples_manifest.json @@ -11,8 +11,8 @@ "object-detection", "pinhole-camera" ], - "demo_url": "https://demo.rerun.io/commit/5be001c/examples/arkit_scenes/", - "rrd_url": "https://demo.rerun.io/commit/5be001c/examples/arkit_scenes/data.rrd", + "demo_url": "https://demo.rerun.io/commit/6f0c1f2/examples/arkit_scenes/", + "rrd_url": "https://demo.rerun.io/commit/6f0c1f2/examples/arkit_scenes/data.rrd", "thumbnail": { "url": "https://static.rerun.io/8b90a80c72b27fad289806b7e5dff0c9ac97e87c_arkit_scenes_480w.png", "width": 480, @@ -30,8 +30,8 @@ "pinhole-camera", "time-series" ], - "demo_url": "https://demo.rerun.io/commit/5be001c/examples/structure_from_motion/", - "rrd_url": "https://demo.rerun.io/commit/5be001c/examples/structure_from_motion/data.rrd", + "demo_url": "https://demo.rerun.io/commit/6f0c1f2/examples/structure_from_motion/", + "rrd_url": "https://demo.rerun.io/commit/6f0c1f2/examples/structure_from_motion/data.rrd", "thumbnail": { "url": "https://static.rerun.io/033edff752f86bcdc9a81f7877e0b4411ff4e6c5_structure_from_motion_480w.png", "width": 480, @@ -47,8 +47,8 @@ "mri", "dicom" ], - "demo_url": "https://demo.rerun.io/commit/5be001c/examples/dicom_mri/", - "rrd_url": "https://demo.rerun.io/commit/5be001c/examples/dicom_mri/data.rrd", + "demo_url": "https://demo.rerun.io/commit/6f0c1f2/examples/dicom_mri/", + "rrd_url": "https://demo.rerun.io/commit/6f0c1f2/examples/dicom_mri/data.rrd", "thumbnail": { "url": "https://static.rerun.io/b8b25dd01e892e6daf5177e6fc05ff5feb19ee8d_dicom_mri_480w.png", "width": 480, @@ -65,8 +65,8 @@ "2D", "3D" ], - "demo_url": "https://demo.rerun.io/commit/5be001c/examples/human_pose_tracking/", - "rrd_url": "https://demo.rerun.io/commit/5be001c/examples/human_pose_tracking/data.rrd", + "demo_url": "https://demo.rerun.io/commit/6f0c1f2/examples/human_pose_tracking/", + "rrd_url": "https://demo.rerun.io/commit/6f0c1f2/examples/human_pose_tracking/data.rrd", "thumbnail": { "url": "https://static.rerun.io/277b9c72da1d0d0ae9d221f7552dede9c4d5b2fa_human_pose_tracking_480w.png", "width": 480, @@ -82,8 +82,8 @@ "plots", "api-example" ], - "demo_url": "https://demo.rerun.io/commit/5be001c/examples/plots/", - "rrd_url": "https://demo.rerun.io/commit/5be001c/examples/plots/data.rrd", + "demo_url": "https://demo.rerun.io/commit/6f0c1f2/examples/plots/", + "rrd_url": "https://demo.rerun.io/commit/6f0c1f2/examples/plots/data.rrd", "thumbnail": { "url": "https://static.rerun.io/ca0c72df93d70c79b0e640fb4b7c33cdc0bfe5f4_plots_480w.png", "width": 480, @@ -101,8 +101,8 @@ "object-tracking", "opencv" ], - "demo_url": "https://demo.rerun.io/commit/5be001c/examples/detect_and_track_objects/", - "rrd_url": "https://demo.rerun.io/commit/5be001c/examples/detect_and_track_objects/data.rrd", + "demo_url": "https://demo.rerun.io/commit/6f0c1f2/examples/detect_and_track_objects/", + "rrd_url": "https://demo.rerun.io/commit/6f0c1f2/examples/detect_and_track_objects/data.rrd", "thumbnail": { "url": "https://static.rerun.io/efb301d64eef6f25e8f6ae29294bd003c0cda3a7_detect_and_track_objects_480w.png", "width": 480, @@ -117,8 +117,8 @@ "3d", "api-example" ], - "demo_url": "https://demo.rerun.io/commit/5be001c/examples/dna/", - "rrd_url": "https://demo.rerun.io/commit/5be001c/examples/dna/data.rrd", + "demo_url": "https://demo.rerun.io/commit/6f0c1f2/examples/dna/", + "rrd_url": "https://demo.rerun.io/commit/6f0c1f2/examples/dna/data.rrd", "thumbnail": { "url": "https://static.rerun.io/ea7a9ab2f716bd37d1bbc1eabf3f55e4f526660e_helix_480w.png", "width": 480, diff --git a/crates/re_viewer/src/ui/welcome_screen/example_page.rs b/crates/re_viewer/src/ui/welcome_screen/example_page.rs index 8bcd4ed2f599..e8dde8ad9922 100644 --- a/crates/re_viewer/src/ui/welcome_screen/example_page.rs +++ b/crates/re_viewer/src/ui/welcome_screen/example_page.rs @@ -1,3 +1,4 @@ +use super::WelcomeScreenResponse; use egui::load::TexturePoll; use egui::{NumExt, TextureOptions, Ui}; use re_log_types::LogMsg; @@ -105,7 +106,7 @@ impl ExamplePage { ui: &mut egui::Ui, rx: &re_smart_channel::ReceiveSet, command_sender: &re_viewer_context::CommandSender, - ) { + ) -> WelcomeScreenResponse { let mut margin = egui::Margin::same(MARGINS); margin.bottom = MARGINS - ROW_VSPACE; egui::Frame { @@ -144,7 +145,7 @@ impl ExamplePage { )); ui.add(egui::Label::new( - egui::RichText::new("Learn from the community.") + egui::RichText::new("Explore what you can build.") .line_height(Some(32.0)) .text_style(re_ui::ReUi::welcome_screen_h1()), )); @@ -232,6 +233,8 @@ impl ExamplePage { }); }); }); + + WelcomeScreenResponse::default() } } diff --git a/crates/re_viewer/src/ui/welcome_screen/mod.rs b/crates/re_viewer/src/ui/welcome_screen/mod.rs index f6a361cba205..6b44c9149788 100644 --- a/crates/re_viewer/src/ui/welcome_screen/mod.rs +++ b/crates/re_viewer/src/ui/welcome_screen/mod.rs @@ -22,6 +22,12 @@ pub struct WelcomeScreen { example_page: example_page::ExamplePage, } +#[derive(Default)] +#[must_use] +pub(super) struct WelcomeScreenResponse { + pub go_to_example_page: bool, +} + impl Default for WelcomeScreen { fn default() -> Self { Self { @@ -66,16 +72,18 @@ impl WelcomeScreen { // TODO(ab): figure out why that happens ui.set_clip_rect(ui.available_rect_before_wrap()); - egui::ScrollArea::new([ - matches!(self.current_page, WelcomeScreenPage::Welcome), - true, - ]) - .id_source(("welcome_screen_page", &self.current_page)) - .auto_shrink([false, false]) - .show(ui, |ui| match self.current_page { - WelcomeScreenPage::Welcome => welcome_page_ui(re_ui, ui, rx, command_sender), - WelcomeScreenPage::Examples => self.example_page.ui(ui, rx, command_sender), - }); + let response: WelcomeScreenResponse = egui::ScrollArea::vertical() + .id_source(("welcome_screen_page", &self.current_page)) + .auto_shrink([false, false]) + .show(ui, |ui| match self.current_page { + WelcomeScreenPage::Welcome => welcome_page_ui(re_ui, ui, rx, command_sender), + WelcomeScreenPage::Examples => self.example_page.ui(ui, rx, command_sender), + }) + .inner; + + if response.go_to_example_page { + self.current_page = WelcomeScreenPage::Examples; + } } } @@ -112,15 +120,8 @@ pub fn loading_ui(ui: &mut egui::Ui, rx: &ReceiveSet) { }); } -fn button_centered_label(ui: &mut egui::Ui, label: impl Into) { - ui.vertical(|ui| { - ui.add_space(9.0); - ui.label(label); - }); -} - fn set_large_button_style(ui: &mut egui::Ui) { - ui.style_mut().spacing.button_padding = egui::vec2(12.0, 9.0); + ui.style_mut().spacing.button_padding = egui::vec2(10.0, 7.0); let visuals = ui.visuals_mut(); visuals.widgets.hovered.expansion = 0.0; visuals.widgets.active.expansion = 0.0; @@ -148,7 +149,6 @@ fn url_large_text_button( if egui::Button::image_and_text(texture_id, ReUi::small_icon_size(), text) .ui(ui) .on_hover_cursor(egui::CursorIcon::PointingHand) - .on_hover_text(url) .clicked() { ui.ctx().output_mut(|o| { 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 35cae6a1aafe..e68dae2b8d67 100644 --- a/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs +++ b/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs @@ -1,23 +1,23 @@ -use super::{button_centered_label, large_text_button, status_strings, url_large_text_button}; -use egui::Ui; +use super::{large_text_button, status_strings, url_large_text_button, WelcomeScreenResponse}; +use egui::{NumExt, Ui}; use re_log_types::LogMsg; use re_smart_channel::ReceiveSet; use re_ui::{ReUi, UICommandSender}; -const MIN_COLUMN_WIDTH: f32 = 250.0; -const MAX_COLUMN_WIDTH: f32 = 400.0; - //const CPP_QUICKSTART: &str = "https://www.rerun.io/docs/getting-started/cpp"; const PYTHON_QUICKSTART: &str = "https://www.rerun.io/docs/getting-started/python"; const RUST_QUICKSTART: &str = "https://www.rerun.io/docs/getting-started/rust"; const SPACE_VIEWS_HELP: &str = "https://www.rerun.io/docs/getting-started/viewer-walkthrough"; +/// Show the welcome page. +/// +/// Return `true` if the user wants to switch to the example page. pub(super) fn welcome_page_ui( re_ui: &re_ui::ReUi, ui: &mut egui::Ui, rx: &ReceiveSet, command_sender: &re_viewer_context::CommandSender, -) { +) -> WelcomeScreenResponse { let mut margin = egui::Margin::same(40.0); margin.bottom = 0.0; egui::Frame { @@ -26,26 +26,7 @@ pub(super) fn welcome_page_ui( } .show(ui, |ui| { ui.vertical(|ui| { - ui.add( - egui::Label::new( - egui::RichText::new("Welcome") - .strong() - .text_style(re_ui::ReUi::welcome_screen_h1()), - ) - .wrap(false), - ); - - ui.add( - egui::Label::new( - egui::RichText::new("Visualize multimodal data") - .text_style(re_ui::ReUi::welcome_screen_h2()), - ) - .wrap(false), - ); - - ui.add_space(20.0); - - onboarding_content_ui(re_ui, ui, command_sender); + let show_example = onboarding_content_ui(re_ui, ui, command_sender); for status_strings in status_strings(rx) { if status_strings.long_term { @@ -59,115 +40,189 @@ pub(super) fn welcome_page_ui( }); } } - }); - }); + + show_example + }) + .inner + }) + .inner +} + +struct WelcomePagePanel<'a> { + title: &'static str, + body: &'static str, + image: &'static re_ui::Icon, + add_buttons: Box bool + 'a>, // returns true if example must be shown } fn onboarding_content_ui( re_ui: &ReUi, ui: &mut Ui, command_sender: &re_viewer_context::CommandSender, -) { - let column_spacing = 15.0; - let stability_adjustment = 1.0; // minimize jitter with sizing and scroll bars - let column_width = ((ui.available_width() - 2. * column_spacing) / 3.0 - stability_adjustment) - .clamp(MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH); - - let grid = egui::Grid::new("welcome_screen_grid") - .spacing(egui::Vec2::splat(column_spacing)) - .min_col_width(column_width) - .max_col_width(column_width); - - grid.show(ui, |ui| { - image_banner( - re_ui, - ui, - &re_ui::icons::WELCOME_SCREEN_LIVE_DATA, - column_width, - ); - image_banner( - re_ui, - ui, - &re_ui::icons::WELCOME_SCREEN_RECORDED_DATA, - column_width, - ); - image_banner( - re_ui, - ui, - &re_ui::icons::WELCOME_SCREEN_CONFIGURE, - column_width, - ); - - ui.end_row(); +) -> WelcomeScreenResponse { + // The panel data is stored in this ad hoc structure such that it can easily be iterated over + // in chunks, to make the layout grid code simpler. + let panels = [ + WelcomePagePanel { + title: "Connect to live data", + body: "Use the Rerun SDK to stream data from your code to the Rerun Viewer. \ + Visualize synchronized data from multiple processes, locally or over a network.", + image: &re_ui::icons::WELCOME_SCREEN_LIVE_DATA, + add_buttons: Box::new(|ui: &mut egui::Ui| { + // TODO(ab): activate when C++ is ready! + // url_large_text_button(re_ui, ui, "C++", CPP_QUICKSTART); + url_large_text_button(re_ui, ui, "Python", PYTHON_QUICKSTART); + url_large_text_button(re_ui, ui, "Rust", RUST_QUICKSTART); + + false + }), + }, + WelcomePagePanel { + title: "Load recorded data", + body: + "Open and visualize recorded data from previous Rerun sessions (.rrd) as well as \ + data in other formats like .gltf and .jpg. Files can be local or remote.", + image: &re_ui::icons::WELCOME_SCREEN_RECORDED_DATA, + add_buttons: Box::new(|ui: &mut egui::Ui| { + if large_text_button(ui, "Open file…").clicked() { + command_sender.send_ui(re_ui::UICommand::Open); + } - ui.vertical(|ui| { - ui.label( - egui::RichText::new("Connect to live data") - .strong() - .text_style(re_ui::ReUi::welcome_screen_h3()), - ); - ui.label( - egui::RichText::new( - "Use the Rerun SDK to stream data from your code to the Rerun Viewer. \ - synchronized data from multiple processes, locally or over a network.", - ) - .text_style(re_ui::ReUi::welcome_screen_body()), - ); - }); + false + }), + }, + WelcomePagePanel { + title: "Build your views", + body: "Add and rearrange views. Configure what data is shown and how. Design \ + interactively in the viewer or (coming soon) directly from code in the SDK.", + image: &re_ui::icons::WELCOME_SCREEN_CONFIGURE, + add_buttons: Box::new(|ui: &mut egui::Ui| { + url_large_text_button(re_ui, ui, "Learn about Views", SPACE_VIEWS_HELP); + + false + }), + }, + WelcomePagePanel { + title: "Start with an example", + body: "Load pre-built examples to explore what you can build with Rerun. Each example \ + comes with easy to run code so you can see how it's done.", + image: &re_ui::icons::WELCOME_SCREEN_EXAMPLES, + add_buttons: Box::new(|ui: &mut egui::Ui| { + large_text_button(ui, "View Examples").clicked() + }), + }, + ]; + + let panel_count = panels.len(); + + const MAX_COLUMN_WIDTH: f32 = 255.0; + const MIN_COLUMN_WIDTH: f32 = 164.0; + + let grid_spacing = egui::vec2(12.0, 16.0); + + let mut column_count = (((ui.available_width() + grid_spacing.x) + / (MIN_COLUMN_WIDTH + grid_spacing.x)) + .floor() as usize) + .clamp(1, panels.len()); + + // we either display 4, 2, or a single column, because 3 columns is ugly with 4 panels. + if column_count == 3 { + column_count = 2; + } - ui.vertical(|ui| { - ui.label( - egui::RichText::new("Load recorded data") - .strong() - .text_style(re_ui::ReUi::welcome_screen_h3()), - ); - ui.label( - egui::RichText::new( - "Open and visualize recorded data from previous Rerun sessions (.rrd) as well \ - as data in formats like .gltf and .jpg.", - ) - .text_style(re_ui::ReUi::welcome_screen_body()), - ); - }); + let column_width = ((ui.available_width() + grid_spacing.x) / column_count as f32 + - grid_spacing.x) + .floor() + .at_most(MAX_COLUMN_WIDTH); + + // this space is added on the left so that the grid is centered + let centering_space = (ui.available_width() + - column_count as f32 * column_width + - (column_count - 1) as f32 * grid_spacing.x) + .max(0.0) + / 2.0; + + ui.horizontal(|ui| { + ui.add_space(centering_space); ui.vertical(|ui| { - ui.label( - egui::RichText::new("Configure your views") - .strong() - .text_style(re_ui::ReUi::welcome_screen_h3()), - ); - ui.label( - egui::RichText::new( - "Add and rearrange views, and configure what data is shown and how. Configure \ - interactively in the viewer or (coming soon) directly from code in the SDK.", - ) - .text_style(re_ui::ReUi::welcome_screen_body()), - ); - }); - - ui.end_row(); - - ui.horizontal(|ui| { - button_centered_label(ui, "Quick start…"); - // TODO(ab): activate when C++ is ready! - // url_large_text_button(re_ui, ui, "C++", CPP_QUICKSTART); - url_large_text_button(re_ui, ui, "Python", PYTHON_QUICKSTART); - url_large_text_button(re_ui, ui, "Rust", RUST_QUICKSTART); - }); - - ui.horizontal(|ui| { - if large_text_button(ui, "Open file…").clicked() { - command_sender.send_ui(re_ui::UICommand::Open); - } - button_centered_label(ui, "Or drop a file anywhere!"); - }); + ui.horizontal_wrapped(|ui| { + ui.add(egui::Label::new( + egui::RichText::new("Welcome.") + .strong() + .line_height(Some(32.0)) + .text_style(re_ui::ReUi::welcome_screen_h1()), + )); - ui.horizontal(|ui| { - url_large_text_button(re_ui, ui, "Learn about Views", SPACE_VIEWS_HELP); - }); + ui.add(egui::Label::new( + egui::RichText::new("Visualize multimodal data.") + .line_height(Some(32.0)) + .text_style(re_ui::ReUi::welcome_screen_h1()), + )); + }); + + ui.add_space(32.0); + + let grid = egui::Grid::new("welcome_screen_grid") + .spacing(grid_spacing) + .min_col_width(column_width) + .max_col_width(column_width); + + grid.show(ui, |ui| { + let mut show_example = false; + + for panels in panels.chunks(column_count) { + if column_count == panel_count { + for panel in panels { + image_banner(re_ui, ui, panel.image, column_width); + } + } else { + for _ in panels { + ui.vertical(|ui| { + ui.add_space(20.0); + }); + } + } + + ui.end_row(); + + for panel in panels { + ui.vertical(|ui| { + // don't let the text get too close to the right-hand content + ui.set_max_width(column_width - 8.0); + + ui.label( + egui::RichText::new(panel.title) + .strong() + .text_style(re_ui::ReUi::welcome_screen_h3()), + ); + ui.label(egui::RichText::new(panel.body).line_height(Some(19.0))); + }); + } + + ui.end_row(); + + for panel in panels { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 4.0; + if (panel.add_buttons)(ui) { + show_example = true; + } + }); + } + + ui.end_row(); + } - ui.end_row(); - }); + WelcomeScreenResponse { + go_to_example_page: show_example, + } + }) + .inner + }) + .inner + }) + .inner } fn image_banner(re_ui: &re_ui::ReUi, ui: &mut egui::Ui, image: &re_ui::Icon, column_width: f32) {