From a8c67ab8c04ce833c38bc52df0e2bdc19e5d1f15 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 28 Jun 2023 20:45:32 +0200 Subject: [PATCH 01/15] Simplify the space-view auto-layout --- crates/re_viewport/src/auto_layout.rs | 332 ++++++-------------------- crates/re_viewport/src/viewport.rs | 7 +- 2 files changed, 80 insertions(+), 259 deletions(-) diff --git a/crates/re_viewport/src/auto_layout.rs b/crates/re_viewport/src/auto_layout.rs index 352a26c8ce6f..e1b1a61d4825 100644 --- a/crates/re_viewport/src/auto_layout.rs +++ b/crates/re_viewport/src/auto_layout.rs @@ -1,56 +1,29 @@ //! Code for automatic layout of space views. //! -//! This uses rough heuristics and have a lot of room for improvement. -//! -//! Some of the heuristics include: -//! * We want similar space views together. Similar can mean: -//! * Same category (2D vs text vs …) -//! * Similar entity path (common prefix) -//! * We also want to pick aspect ratios that fit the data pretty well -// TODO(emilk): fix O(N^2) execution time (where N = number of spaces) +//! This uses some very rough heuristics and have a lot of room for improvement. use std::collections::BTreeMap; -use ahash::HashMap; -use egui::Vec2; use itertools::Itertools as _; -use re_data_store::{EntityPath, EntityPathPart}; -use re_viewer_context::{SpaceViewId, ViewerContext}; +use re_viewer_context::SpaceViewId; use super::{space_view::SpaceViewBlueprint, view_category::ViewCategory}; #[derive(Clone, Debug)] -pub struct SpaceMakeInfo { - pub id: SpaceViewId, - - /// Some path we use to group the views by - pub path: EntityPath, - - pub category: ViewCategory, - - /// Desired aspect ratio, if any. - pub aspect_ratio: Option, -} - -enum LayoutSplit { - LeftRight(Box, f32, Box), - TopBottom(Box, f32, Box), - Leaf(SpaceMakeInfo), -} - -enum SplitDirection { - LeftRight { left: Vec2, t: f32, right: Vec2 }, - TopBottom { top: Vec2, t: f32, bottom: Vec2 }, +struct SpaceMakeInfo { + id: SpaceViewId, + category: ViewCategory, } pub(crate) fn tree_from_space_views( - ctx: &mut ViewerContext<'_>, - viewport_size: egui::Vec2, space_views: &BTreeMap, - space_view_states: &HashMap>, ) -> egui_tiles::Tree { - let mut space_make_infos = space_views + if space_views.is_empty() { + return egui_tiles::Tree::empty(); + } + + let space_make_infos = space_views .iter() // Sort for determinism: .sorted_by_key(|(space_view_id, space_view)| { @@ -60,234 +33,87 @@ pub(crate) fn tree_from_space_views( *space_view_id, ) }) - .map(|(space_view_id, space_view)| { - let aspect_ratio = space_view_states.get(space_view_id).and_then(|state| { - space_view - .class(ctx) - .preferred_tile_aspect_ratio(state.as_ref()) - }); - - SpaceMakeInfo { - id: *space_view_id, - path: space_view.space_origin.clone(), - category: space_view.category, - aspect_ratio, - } + .map(|(space_view_id, space_view)| SpaceMakeInfo { + id: *space_view_id, + category: space_view.category, }) .collect_vec(); - if space_make_infos.is_empty() { - egui_tiles::Tree::empty() + let mut tiles = egui_tiles::Tiles::default(); + + let root = if space_make_infos.len() == 1 { + tiles.insert_pane(space_make_infos[0].id) + } else if space_make_infos.len() == 3 { + // Special-case this common case (that doesn't fit nicely in a grid!): + // tile_by_category(&mut tiles, &space_make_infos) + arrange_three( + [ + space_make_infos[0].clone(), + space_make_infos[1].clone(), + space_make_infos[2].clone(), + ], + &mut tiles, + ) } else { - let mut tiles = egui_tiles::Tiles::default(); - // Users often organize by path prefix, so we start by splitting along that - let layout = layout_by_path_prefix(viewport_size, &mut space_make_infos); - let root = tree_from_split(&mut tiles, &layout); - egui_tiles::Tree::new(root, tiles) - } + // Arrange it all in a grid that is responsive to changes in viewport size: + let child_tile_ids = space_make_infos + .into_iter() + .map(|smi| tiles.insert_pane(smi.id)) + .collect_vec(); + tiles.insert_grid_tile(child_tile_ids) + }; + + egui_tiles::Tree::new(root, tiles) } -fn tree_from_split( +fn arrange_three( + mut spaces: [SpaceMakeInfo; 3], tiles: &mut egui_tiles::Tiles, - split: &LayoutSplit, ) -> egui_tiles::TileId { - match split { - LayoutSplit::LeftRight(left, fraction, right) => { - let container = egui_tiles::Linear::new_binary( - egui_tiles::LinearDir::Horizontal, - [tree_from_split(tiles, left), tree_from_split(tiles, right)], - *fraction, - ); - tiles.insert_container(container) + // We will arrange it like so: + // + // +-------------+ + // | | + // | | + // | | + // +-------+-----+ + // | | | + // | | | + // +-------+-----+ + // + // or like so: + // + // +-----------------------+ + // | | | + // | | | + // | +------------+ + // | | | + // | | | + // | | | + // +----------+------------+ + // + // But which space gets a full side, and which doesn't? + // Answer: we prioritize them by category: + + /// lower is better + fn category_priority(category: ViewCategory) -> usize { + match category { + ViewCategory::Spatial => 0, + ViewCategory::Tensor => 1, + ViewCategory::TimeSeries => 2, + ViewCategory::BarChart => 3, + ViewCategory::TextBox => 4, + ViewCategory::Text => 5, } - LayoutSplit::TopBottom(top, fraction, bottom) => { - let container = egui_tiles::Linear::new_binary( - egui_tiles::LinearDir::Vertical, - [tree_from_split(tiles, top), tree_from_split(tiles, bottom)], - *fraction, - ); - tiles.insert_container(container) - } - LayoutSplit::Leaf(space_info) => tiles.insert_pane(space_info.id), } -} -/// Group categories together, i.e. so that 2D stuff is next to 2D stuff, and text logs are next to text logs. -fn layout_by_category(viewport_size: egui::Vec2, spaces: &mut [SpaceMakeInfo]) -> LayoutSplit { - assert!(!spaces.is_empty()); + spaces.sort_by_key(|smi| category_priority(smi.category)); - if spaces.len() == 1 { - LayoutSplit::Leaf(spaces[0].clone()) - } else { - let groups = group_by_category(spaces); - - if groups.len() == 1 { - // All same category - layout_by_path_prefix(viewport_size, spaces) - } else { - // Mixed categories. - split_groups(viewport_size, groups) - } - } -} - -/// Put spaces with common path prefix close together. -fn layout_by_path_prefix(viewport_size: egui::Vec2, spaces: &mut [SpaceMakeInfo]) -> LayoutSplit { - assert!(!spaces.is_empty()); - - if spaces.len() == 1 { - LayoutSplit::Leaf(spaces[0].clone()) - } else { - let groups = group_by_path_prefix(spaces); - - if groups.len() == 1 { - // failed to separate by group - try category instead: - layout_by_category(viewport_size, spaces) - } else { - split_groups(viewport_size, groups) - } - } -} - -fn split_groups(viewport_size: egui::Vec2, groups: Vec>) -> LayoutSplit { - let (mut spaces, split_point) = find_group_split_point(groups); - split_spaces_at(viewport_size, &mut spaces, split_point) -} - -fn find_group_split_point(groups: Vec>) -> (Vec, usize) { - assert!(groups.len() > 1); - - let num_spaces: usize = groups.iter().map(|g| g.len()).sum(); - - let mut best_split = 0; - let mut rearranged_spaces = vec![]; - for mut group in groups { - rearranged_spaces.append(&mut group); - - let split_candidate = rearranged_spaces.len(); - - // Prefer the split that is closest to the middle: - if (split_candidate as f32 / num_spaces as f32 - 0.5).abs() - < (best_split as f32 / num_spaces as f32 - 0.5).abs() - { - best_split = split_candidate; - } - } - assert_eq!(rearranged_spaces.len(), num_spaces); - assert!(0 < best_split && best_split < num_spaces,); - - (rearranged_spaces, best_split) -} - -fn suggest_split_direction( - viewport_size: egui::Vec2, - spaces: &[SpaceMakeInfo], - split_index: usize, -) -> SplitDirection { - use egui::vec2; - - assert!(0 < split_index && split_index < spaces.len()); - - let t = split_index as f32 / spaces.len() as f32; - - let desired_aspect_ratio = desired_aspect_ratio(spaces).unwrap_or(16.0 / 9.0); - - if viewport_size.x > desired_aspect_ratio * viewport_size.y { - let left = vec2(viewport_size.x * t, viewport_size.y); - let right = vec2(viewport_size.x * (1.0 - t), viewport_size.y); - SplitDirection::LeftRight { left, t, right } - } else { - let top = vec2(viewport_size.x, viewport_size.y * t); - let bottom = vec2(viewport_size.x, viewport_size.y * (1.0 - t)); - SplitDirection::TopBottom { top, t, bottom } - } -} - -fn split_spaces_at( - viewport_size: egui::Vec2, - spaces: &mut [SpaceMakeInfo], - split_index: usize, -) -> LayoutSplit { - assert!(0 < split_index && split_index < spaces.len()); - - match suggest_split_direction(viewport_size, spaces, split_index) { - SplitDirection::LeftRight { left, t, right } => { - let left = layout_by_path_prefix(left, &mut spaces[..split_index]); - let right = layout_by_path_prefix(right, &mut spaces[split_index..]); - LayoutSplit::LeftRight(left.into(), t, right.into()) - } - SplitDirection::TopBottom { top, t, bottom } => { - let top = layout_by_path_prefix(top, &mut spaces[..split_index]); - let bottom = layout_by_path_prefix(bottom, &mut spaces[split_index..]); - LayoutSplit::TopBottom(top.into(), t, bottom.into()) - } - } -} - -/// If we need to pick only one aspect ratio for all these spaces, what is a good aspect ratio? -/// -/// This is a very, VERY, rough heuristic. It really only work in a few cases: -/// -/// * All spaces have similar aspect ration (e.g. all portrait or all landscape) -/// * Only one space care about aspect ratio, and the other are flexible -/// * A mix of the above -/// -/// Still, it is better than nothing. -fn desired_aspect_ratio(spaces: &[SpaceMakeInfo]) -> Option { - // Taking the arithmetic mean of all given aspect ratios. - // It makes very little sense, unless the aspect ratios are all close already. - // Perhaps a mode or median would make more sense? - - let mut sum = 0.0; - let mut num = 0.0; - for space in spaces { - if let Some(aspect_ratio) = space.aspect_ratio { - if aspect_ratio.is_finite() { - sum += aspect_ratio; - num += 1.0; - } - } - } - - (num != 0.0).then_some(sum / num) -} - -fn group_by_category(space_infos: &[SpaceMakeInfo]) -> Vec> { - let mut groups: BTreeMap> = Default::default(); - for info in space_infos { - groups.entry(info.category).or_default().push(info.clone()); - } - groups.into_values().collect() -} - -fn group_by_path_prefix(space_infos: &[SpaceMakeInfo]) -> Vec> { - if space_infos.len() < 2 { - return vec![space_infos.to_vec()]; - } - re_tracing::profile_function!(); - - let paths = space_infos - .iter() - .map(|space_info| space_info.path.as_slice().to_vec()) + let pane_ids = spaces + .into_iter() + .map(|smi| tiles.insert_pane(smi.id)) .collect_vec(); - for i in 0.. { - let mut groups: BTreeMap, Vec<&SpaceMakeInfo>> = Default::default(); - for (path, space) in paths.iter().zip(space_infos) { - groups.entry(path.get(i)).or_default().push(space); - } - if groups.len() == 1 && groups.contains_key(&None) { - break; - } - if groups.len() > 1 { - return groups - .values() - .map(|spaces| spaces.iter().cloned().cloned().collect()) - .collect(); - } - } - space_infos - .iter() - .map(|space| vec![space.clone()]) - .collect() + let inner_grid = tiles.insert_grid_tile(vec![pane_ids[1], pane_ids[2]]); + tiles.insert_grid_tile(vec![pane_ids[0], inner_grid]) } diff --git a/crates/re_viewport/src/viewport.rs b/crates/re_viewport/src/viewport.rs index 4b017aa5a604..e97267267c77 100644 --- a/crates/re_viewport/src/viewport.rs +++ b/crates/re_viewport/src/viewport.rs @@ -536,12 +536,7 @@ impl Viewport { &mut maximized_tree } else { if self.tree.root().is_none() { - self.tree = super::auto_layout::tree_from_space_views( - ctx, - ui.available_size(), - &self.space_views, - &state.space_view_states, - ); + self.tree = super::auto_layout::tree_from_space_views(&self.space_views); } &mut self.tree }; From f076f0fa58ef34a64fbe7da9666d45220199221d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 28 Jun 2023 20:57:21 +0200 Subject: [PATCH 02/15] run_all.py: speed up by just compiling what we need --- scripts/run_all.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/scripts/run_all.py b/scripts/run_all.py index fad09785be7f..a0ea4febc1da 100755 --- a/scripts/run_all.py +++ b/scripts/run_all.py @@ -169,15 +169,16 @@ def run_sdk_build() -> None: assert returncode == 0, f"process exited with error code {returncode}" -def run_viewer_build() -> None: - print("Building rerun…") +def run_viewer_build(web: bool) -> None: + print("Building Rerun Viewer…") returncode = subprocess.Popen( [ "cargo", "build", "-p", "rerun-cli", - "--all-features", + "--no-default-features", + "--features=web_viewer" if web else "--features=native_viewer", "--quiet", ] ).wait() @@ -289,8 +290,10 @@ def main() -> None: examples = collect_examples(args.fast) if not args.skip_build: - run_sdk_build() - run_viewer_build() + if not args.load: + run_sdk_build() + if not args.save: + run_viewer_build(args.web) if args.web: run_web(examples, separate=args.separate) From c7797eeda3e1212e70fd833316a0264c0f23b310 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 28 Jun 2023 20:57:42 +0200 Subject: [PATCH 03/15] run_all.py: skip broken objectron example --- scripts/run_all.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/run_all.py b/scripts/run_all.py index a0ea4febc1da..b88401647bc6 100755 --- a/scripts/run_all.py +++ b/scripts/run_all.py @@ -93,9 +93,14 @@ def collect_examples(fast: bool) -> list[str]: "examples/python/text_logging", ] else: - # ros requires complex system dependencies to be installed - # depth_sensor requires a specific piece of hardware to be attached - skip_list = ["examples/python/ros_node/main.py", "examples/python/live_depth_sensor/main.py"] + skip_list = [ + # depth_sensor requires a specific piece of hardware to be attached + "examples/python/live_depth_sensor/main.py", + # ros requires complex system dependencies to be installed + "examples/python/ros_node/main.py", + # objectron currently broken; see https://github.com/rerun-io/rerun/issues/2557 + "examples/python/objectron/main.py", + ] return [ os.path.dirname(main_path) for main_path in glob("examples/python/**/main.py") if main_path not in skip_list From fde813c9352b3a327d20ca2c4c56b376e0115f57 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 28 Jun 2023 20:58:48 +0200 Subject: [PATCH 04/15] run_all.py: code clean up --- scripts/run_all.py | 106 ++++++++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 54 deletions(-) diff --git a/scripts/run_all.py b/scripts/run_all.py index b88401647bc6..a742fad27343 100755 --- a/scripts/run_all.py +++ b/scripts/run_all.py @@ -191,34 +191,34 @@ def run_viewer_build(web: bool) -> None: def run_web(examples: list[str], separate: bool) -> None: - if not separate: + if separate: + entries: list[tuple[str, Any, Any]] = [] + # start all examples in parallel + for path in examples: + # each example gets its own viewer + viewer = Viewer(web=True).start() + example = run_py_example(path, viewer_port=viewer.sdk_port, wait=False) + entries.append((path, viewer, example)) + + # wait for examples to finish logging + for entry in entries: + _, _, example = entry + example.wait() + + # give servers/viewers a moment to finish loading data + time.sleep(5) + + # shut down servers/viewers + for entry in entries: + path, viewer, example = entry + print_example_output(path, example) + viewer.close() + + else: with Viewer(close=True, web=True) as viewer: for path in examples: example = run_py_example(path, viewer_port=viewer.sdk_port) print_example_output(path, example) - return - - entries: list[tuple[str, Any, Any]] = [] - # start all examples in parallel - for path in examples: - # each example gets its own viewer - viewer = Viewer(web=True).start() - example = run_py_example(path, viewer_port=viewer.sdk_port, wait=False) - entries.append((path, viewer, example)) - - # wait for examples to finish logging - for entry in entries: - _, _, example = entry - example.wait() - - # give servers/viewers a moment to finish loading data - time.sleep(5) - - # shut down servers/viewers - for entry in entries: - path, viewer, example = entry - print_example_output(path, example) - viewer.close() def run_save(examples: list[str]) -> None: @@ -229,49 +229,47 @@ def run_save(examples: list[str]) -> None: def run_load(examples: list[str], separate: bool, close: bool) -> None: - if not separate: + if separate: + entries: list[tuple[str, Any]] = [] + for path in examples: + example = run_saved_example(path, wait=False) + entries.append((path, example)) + + for entry in entries: + path, example = entry + print_example_output(path, example) + if close: + example.kill() + else: # run all examples sequentially for path in examples: # each one must be closed for the next one to start running example = run_saved_example(path) print_example_output(path, example) - return - - entries: list[tuple[str, Any]] = [] - for path in examples: - example = run_saved_example(path, wait=False) - entries.append((path, example)) - - for entry in entries: - path, example = entry - print_example_output(path, example) - if close: - example.kill() def run_native(examples: list[str], separate: bool, close: bool) -> None: - if not separate: + if separate: + # start all examples in parallel: + cleanup: list[tuple[Any, Any]] = [] + for path in examples: + # each example gets its own viewer + viewer = Viewer().start() + example = run_py_example(path, viewer.sdk_port, False) + cleanup.append((viewer, example)) + + # wait for all processes to finish, and close the viewers if requested + for pair in cleanup: + viewer, example = pair + print_example_output(path, example) + if close: + viewer.close() + else: # run all examples sequentially in a single viewer with Viewer(close) as viewer: for path in examples: example = run_py_example(path, viewer_port=viewer.sdk_port, wait=True) print_example_output(path, example) - return - - cleanup: list[tuple[Any, Any]] = [] - # start all examples in parallel - for path in examples: - # each example gets its own viewer - viewer = Viewer().start() - example = run_py_example(path, viewer.sdk_port, False) - cleanup.append((viewer, example)) - - # wait for all processes to finish, and close the viewers if requested - for pair in cleanup: - viewer, example = pair - print_example_output(path, example) - if close: - viewer.close() def main() -> None: From 06f21f64f53d919378930062fc57da300c811426 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 28 Jun 2023 21:10:31 +0200 Subject: [PATCH 05/15] run_all.py: Better error message when failing to find an `out.rrd` file --- scripts/run_all.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/scripts/run_all.py b/scripts/run_all.py index a742fad27343..81097b83cd3d 100755 --- a/scripts/run_all.py +++ b/scripts/run_all.py @@ -26,7 +26,7 @@ } -def start_process(args: list[str], cwd: str, wait: bool) -> Any: +def start_process(args: list[str], *, wait: bool, cwd: str | None = None) -> Any: process = subprocess.Popen( args, cwd=cwd, @@ -63,14 +63,6 @@ def run_py_example(path: str, viewer_port: int | None = None, wait: bool = True, ) -def run_saved_example(path: str, wait: bool = True) -> Any: - return start_process( - ["cargo", "run", "-p", "rerun-cli", "--all-features", "--", "out.rrd"], - cwd=path, - wait=wait, - ) - - def get_free_port() -> int: with socket.socket() as s: s.bind(("", 0)) @@ -228,6 +220,13 @@ def run_save(examples: list[str]) -> None: print_example_output(path, example) +def run_saved_example(path: str, wait: bool) -> Any: + return start_process( + ["cargo", "run", "-p", "rerun-cli", "--all-features", "--", os.path.join(path, "out.rrd")], + wait=wait, + ) + + def run_load(examples: list[str], separate: bool, close: bool) -> None: if separate: entries: list[tuple[str, Any]] = [] @@ -244,7 +243,7 @@ def run_load(examples: list[str], separate: bool, close: bool) -> None: # run all examples sequentially for path in examples: # each one must be closed for the next one to start running - example = run_saved_example(path) + example = run_saved_example(path, wait=True) print_example_output(path, example) From 7bc4010a697943175184d980f047934f76443e4e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 28 Jun 2023 21:13:27 +0200 Subject: [PATCH 06/15] sort --- scripts/run_all.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/run_all.py b/scripts/run_all.py index 81097b83cd3d..32eb854fd725 100755 --- a/scripts/run_all.py +++ b/scripts/run_all.py @@ -19,10 +19,10 @@ } HAS_NO_SAVE_ARG = { - "examples/python/multiprocessing", - "examples/python/minimal", - "examples/python/dna", "examples/python/blueprint", + "examples/python/dna", + "examples/python/minimal", + "examples/python/multiprocessing", } @@ -88,10 +88,10 @@ def collect_examples(fast: bool) -> list[str]: skip_list = [ # depth_sensor requires a specific piece of hardware to be attached "examples/python/live_depth_sensor/main.py", - # ros requires complex system dependencies to be installed - "examples/python/ros_node/main.py", # objectron currently broken; see https://github.com/rerun-io/rerun/issues/2557 "examples/python/objectron/main.py", + # ros requires complex system dependencies to be installed + "examples/python/ros_node/main.py", ] return [ From 22e44d1a6af853b3075948834ba040d5eb0e522b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 28 Jun 2023 21:16:06 +0200 Subject: [PATCH 07/15] Don't load non-existing files --- scripts/run_all.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/run_all.py b/scripts/run_all.py index 32eb854fd725..0ea0dcc8f02b 100755 --- a/scripts/run_all.py +++ b/scripts/run_all.py @@ -228,6 +228,8 @@ def run_saved_example(path: str, wait: bool) -> Any: def run_load(examples: list[str], separate: bool, close: bool) -> None: + examples = [path for path in examples if path not in HAS_NO_SAVE_ARG] + if separate: entries: list[tuple[str, Any]] = [] for path in examples: From 06cfae8e5f372e46abeb9d3f0849a6175611c416 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 28 Jun 2023 21:30:49 +0200 Subject: [PATCH 08/15] Better output from run_all.py --- scripts/run_all.py | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/scripts/run_all.py b/scripts/run_all.py index 0ea0dcc8f02b..4cbe1864585e 100755 --- a/scripts/run_all.py +++ b/scripts/run_all.py @@ -26,24 +26,27 @@ } -def start_process(args: list[str], *, wait: bool, cwd: str | None = None) -> Any: +def start_process(args: list[str], *, wait: bool) -> Any: + readable_cmd = " ".join(f'"{a}"' if " " in a else a for a in args) + print(f"> {readable_cmd}") + process = subprocess.Popen( args, - cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) if wait: returncode = process.wait() if returncode != 0: - print(process.communicate()[0].decode("utf-8").rstrip()) + print(output_from_process(process)) + print() print(f"process exited with error code {returncode}") exit(returncode) return process def run_py_example(path: str, viewer_port: int | None = None, wait: bool = True, save: str | None = None) -> Any: - args = ["python3", "main.py"] + args = [os.path.join(path, "main.py")] if path in EXTRA_ARGS: args += EXTRA_ARGS[path] @@ -53,15 +56,14 @@ def run_py_example(path: str, viewer_port: int | None = None, wait: bool = True, if viewer_port is not None: args += ["--connect", f"--addr=127.0.0.1:{viewer_port}"] - cmd = " ".join(f'"{a}"' if " " in a else a for a in args) - print(f"Running example '{path}' via '{cmd}'") - return start_process( args, - cwd=path, wait=wait, ) +# stdout and stderr +def output_from_process(process: Any) -> str: + return process.communicate()[0].decode("utf-8").rstrip() def get_free_port() -> int: with socket.socket() as s: @@ -100,7 +102,8 @@ def collect_examples(fast: bool) -> list[str]: def print_example_output(path: str, example: Any) -> None: - print(f"\nExample {path}:\n{example.communicate()[0].decode('utf-8').rstrip()}") + output = example.communicate()[0].decode('utf-8').rstrip() + print(f"\nExample {path}:\n{output}\n") class Viewer: @@ -209,20 +212,24 @@ def run_web(examples: list[str], separate: bool) -> None: else: with Viewer(close=True, web=True) as viewer: for path in examples: - example = run_py_example(path, viewer_port=viewer.sdk_port) - print_example_output(path, example) + process = run_py_example(path, viewer_port=viewer.sdk_port) + print(f"{output_from_process(process)}\n") def run_save(examples: list[str]) -> None: for path in examples: if path not in HAS_NO_SAVE_ARG: - example = run_py_example(path, save="out.rrd") - print_example_output(path, example) + process = run_py_example(path, save="out.rrd") + print(f"{output_from_process(process)}\n") def run_saved_example(path: str, wait: bool) -> Any: return start_process( - ["cargo", "run", "-p", "rerun-cli", "--all-features", "--", os.path.join(path, "out.rrd")], + [ + "cargo", + "rerun", + os.path.join(path, "out.rrd"), + ], wait=wait, ) @@ -245,8 +252,8 @@ def run_load(examples: list[str], separate: bool, close: bool) -> None: # run all examples sequentially for path in examples: # each one must be closed for the next one to start running - example = run_saved_example(path, wait=True) - print_example_output(path, example) + process = run_saved_example(path, wait=True) + print(f"{output_from_process(process)}\n") def run_native(examples: list[str], separate: bool, close: bool) -> None: @@ -269,8 +276,8 @@ def run_native(examples: list[str], separate: bool, close: bool) -> None: # run all examples sequentially in a single viewer with Viewer(close) as viewer: for path in examples: - example = run_py_example(path, viewer_port=viewer.sdk_port, wait=True) - print_example_output(path, example) + process = run_py_example(path, viewer_port=viewer.sdk_port, wait=True) + print(f"{output_from_process(process)}\n") def main() -> None: From 8ae3beb699f439925e919d5c4470b053e0d70792 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 28 Jun 2023 21:33:05 +0200 Subject: [PATCH 09/15] py-format --- scripts/run_all.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/run_all.py b/scripts/run_all.py index 4cbe1864585e..31831529f0c3 100755 --- a/scripts/run_all.py +++ b/scripts/run_all.py @@ -61,10 +61,12 @@ def run_py_example(path: str, viewer_port: int | None = None, wait: bool = True, wait=wait, ) + # stdout and stderr def output_from_process(process: Any) -> str: return process.communicate()[0].decode("utf-8").rstrip() + def get_free_port() -> int: with socket.socket() as s: s.bind(("", 0)) @@ -102,7 +104,7 @@ def collect_examples(fast: bool) -> list[str]: def print_example_output(path: str, example: Any) -> None: - output = example.communicate()[0].decode('utf-8').rstrip() + output = example.communicate()[0].decode("utf-8").rstrip() print(f"\nExample {path}:\n{output}\n") From b33091a08c6108da0dc383a92dfece6c1f185e0c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 28 Jun 2023 22:01:16 +0200 Subject: [PATCH 10/15] remove objectron exemption --- scripts/run_all.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/run_all.py b/scripts/run_all.py index 31831529f0c3..fd9ae8681d22 100755 --- a/scripts/run_all.py +++ b/scripts/run_all.py @@ -92,8 +92,6 @@ def collect_examples(fast: bool) -> list[str]: skip_list = [ # depth_sensor requires a specific piece of hardware to be attached "examples/python/live_depth_sensor/main.py", - # objectron currently broken; see https://github.com/rerun-io/rerun/issues/2557 - "examples/python/objectron/main.py", # ros requires complex system dependencies to be installed "examples/python/ros_node/main.py", ] From 377633850fc43d02b1712bd6e443278e9be31660 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 28 Jun 2023 22:04:19 +0200 Subject: [PATCH 11/15] Code cleanup --- crates/re_viewport/src/auto_layout.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/re_viewport/src/auto_layout.rs b/crates/re_viewport/src/auto_layout.rs index e1b1a61d4825..3e84e807f497 100644 --- a/crates/re_viewport/src/auto_layout.rs +++ b/crates/re_viewport/src/auto_layout.rs @@ -44,8 +44,7 @@ pub(crate) fn tree_from_space_views( let root = if space_make_infos.len() == 1 { tiles.insert_pane(space_make_infos[0].id) } else if space_make_infos.len() == 3 { - // Special-case this common case (that doesn't fit nicely in a grid!): - // tile_by_category(&mut tiles, &space_make_infos) + // Special-case for common case that doesn't fit nicely in a grid arrange_three( [ space_make_infos[0].clone(), From c4ac2ae1968bd687ee10185b0e8a714a458630b3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 29 Jun 2023 11:00:32 +0200 Subject: [PATCH 12/15] Update egui_tiles with drag-and-drop fix --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/re_viewport/src/viewport.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 24ccac865f04..b50ed20b3230 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1491,7 +1491,7 @@ dependencies = [ [[package]] name = "egui_tiles" version = "0.1.0" -source = "git+https://github.com/rerun-io/egui_tiles.git?rev=f958d8af7ed27d925bc2ff7862fb1c1c45961b9e#f958d8af7ed27d925bc2ff7862fb1c1c45961b9e" +source = "git+https://github.com/rerun-io/egui_tiles.git?rev=3fff2b01310aa3569e29d2dd3ee97b2e62c45870#3fff2b01310aa3569e29d2dd3ee97b2e62c45870" dependencies = [ "ahash 0.8.3", "egui", diff --git a/Cargo.toml b/Cargo.toml index 916d7328113e..6d6f77a36d45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,4 +144,4 @@ debug = true # As a last resport, patch with a commit to our own repository. # ALWAYS document what PR the commit hash is part of, or when it was merged into the upstream trunk. -egui_tiles = { git = "https://github.com/rerun-io/egui_tiles.git", rev = "f958d8af7ed27d925bc2ff7862fb1c1c45961b9e" } +egui_tiles = { git = "https://github.com/rerun-io/egui_tiles.git", rev = "3fff2b01310aa3569e29d2dd3ee97b2e62c45870" } # 2023-06-29: drag-and-drop fixes diff --git a/crates/re_viewport/src/viewport.rs b/crates/re_viewport/src/viewport.rs index e97267267c77..e45f948e3d35 100644 --- a/crates/re_viewport/src/viewport.rs +++ b/crates/re_viewport/src/viewport.rs @@ -535,7 +535,7 @@ impl Viewport { maximized_tree = egui_tiles::Tree::new(root, tiles); &mut maximized_tree } else { - if self.tree.root().is_none() { + if self.tree.is_empty() { self.tree = super::auto_layout::tree_from_space_views(&self.space_views); } &mut self.tree From f5abeb5586d023b60a10d186df8f16d1ec0657f0 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 29 Jun 2023 11:26:22 +0200 Subject: [PATCH 13/15] Less use of ViewerContext --- crates/re_viewer/src/ui/selection_panel.rs | 16 +++++++++------- crates/re_viewport/src/lib.rs | 2 +- crates/re_viewport/src/space_view.rs | 10 ++++++---- crates/re_viewport/src/viewport.rs | 4 ++-- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/crates/re_viewer/src/ui/selection_panel.rs b/crates/re_viewer/src/ui/selection_panel.rs index cbf5e559de39..1bdba66580c2 100644 --- a/crates/re_viewer/src/ui/selection_panel.rs +++ b/crates/re_viewer/src/ui/selection_panel.rs @@ -250,13 +250,15 @@ fn blueprint_ui( space_view.class_name(), ); - space_view.class(ctx).selection_ui( - ctx, - ui, - space_view_state, - &space_view.space_origin, - space_view.id, - ); + space_view + .class(ctx.space_view_class_registry) + .selection_ui( + ctx, + ui, + space_view_state, + &space_view.space_origin, + space_view.id, + ); } } diff --git a/crates/re_viewport/src/lib.rs b/crates/re_viewport/src/lib.rs index 90ec2b3ea70e..862a0af299cb 100644 --- a/crates/re_viewport/src/lib.rs +++ b/crates/re_viewport/src/lib.rs @@ -45,7 +45,7 @@ pub mod item_ui { .re_ui .selectable_label_with_icon( ui, - space_view.class(ctx).icon(), + space_view.class(ctx.space_view_class_registry).icon(), space_view.display_name.clone(), is_selected, ) diff --git a/crates/re_viewport/src/space_view.rs b/crates/re_viewport/src/space_view.rs index 0e2ba3221d72..29fd60e8ad9e 100644 --- a/crates/re_viewport/src/space_view.rs +++ b/crates/re_viewport/src/space_view.rs @@ -77,9 +77,11 @@ impl SpaceViewBlueprint { &self.class_name } - pub fn class<'a>(&self, ctx: &ViewerContext<'a>) -> &'a dyn DynSpaceViewClass { - ctx.space_view_class_registry - .get_or_log_error(&self.class_name) + pub fn class<'a>( + &self, + space_view_class_registry: &'a re_viewer_context::SpaceViewClassRegistry, + ) -> &'a dyn DynSpaceViewClass { + space_view_class_registry.get_or_log_error(&self.class_name) } pub fn on_frame_start( @@ -164,7 +166,7 @@ impl SpaceViewBlueprint { return; } - let class = self.class(ctx); + let class = self.class(ctx.space_view_class_registry); class.prepare_populate( ctx, diff --git a/crates/re_viewport/src/viewport.rs b/crates/re_viewport/src/viewport.rs index e45f948e3d35..b9b31b686001 100644 --- a/crates/re_viewport/src/viewport.rs +++ b/crates/re_viewport/src/viewport.rs @@ -576,7 +576,7 @@ impl Viewport { .re_ui .selectable_label_with_icon( ui, - space_view.class(ctx).icon(), + space_view.class(ctx.space_view_class_registry).icon(), if space_view.space_origin.is_root() { space_view.display_name.clone() } else { @@ -871,7 +871,7 @@ impl<'a, 'b> egui_tiles::Behavior for TabViewer<'a, 'b> { } let help_text = space_view - .class(self.ctx) + .class(self.ctx.space_view_class_registry) .help_text(self.ctx.re_ui, space_view_state); re_ui::help_hover_button(ui).on_hover_text(help_text); } From 6430e40bf915ef31ae5fc10c5a43e8aa300c4de2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 29 Jun 2023 11:27:23 +0200 Subject: [PATCH 14/15] Add SpaceViewClassLayoutPriority --- .../src/space_view_class.rs | 4 ++++ .../src/space_view_class.rs | 4 ++++ .../src/space_view_class.rs | 4 ++++ .../re_space_view_text/src/space_view_class.rs | 4 ++++ .../src/space_view_class.rs | 4 ++++ .../src/space_view_class.rs | 4 ++++ crates/re_viewer_context/src/lib.rs | 7 ++++--- .../src/space_view/dyn_space_view_class.rs | 18 ++++++++++++++++++ crates/re_viewer_context/src/space_view/mod.rs | 3 ++- .../src/space_view/space_view_class.rs | 8 ++++++++ .../space_view/space_view_class_placeholder.rs | 4 ++++ .../src/color_coordinates_space_view.rs | 8 ++++++-- 12 files changed, 66 insertions(+), 6 deletions(-) diff --git a/crates/re_space_view_bar_chart/src/space_view_class.rs b/crates/re_space_view_bar_chart/src/space_view_class.rs index a4f6db8c40b8..cc49f5971a9e 100644 --- a/crates/re_space_view_bar_chart/src/space_view_class.rs +++ b/crates/re_space_view_bar_chart/src/space_view_class.rs @@ -50,6 +50,10 @@ impl SpaceViewClass for BarChartSpaceView { None } + fn layout_priority(&self) -> re_viewer_context::SpaceViewClassLayoutPriority { + re_viewer_context::SpaceViewClassLayoutPriority::Low + } + fn selection_ui( &self, _ctx: &mut ViewerContext<'_>, diff --git a/crates/re_space_view_spatial/src/space_view_class.rs b/crates/re_space_view_spatial/src/space_view_class.rs index f25f59daf412..636f1173637b 100644 --- a/crates/re_space_view_spatial/src/space_view_class.rs +++ b/crates/re_space_view_spatial/src/space_view_class.rs @@ -38,6 +38,10 @@ impl SpaceViewClass for SpatialSpaceView { } } + fn layout_priority(&self) -> re_viewer_context::SpaceViewClassLayoutPriority { + re_viewer_context::SpaceViewClassLayoutPriority::High + } + fn prepare_populate( &self, ctx: &mut re_viewer_context::ViewerContext<'_>, diff --git a/crates/re_space_view_tensor/src/space_view_class.rs b/crates/re_space_view_tensor/src/space_view_class.rs index 80cb922fdd7f..162eca9bc38b 100644 --- a/crates/re_space_view_tensor/src/space_view_class.rs +++ b/crates/re_space_view_tensor/src/space_view_class.rs @@ -139,6 +139,10 @@ impl SpaceViewClass for TensorSpaceView { None } + fn layout_priority(&self) -> re_viewer_context::SpaceViewClassLayoutPriority { + re_viewer_context::SpaceViewClassLayoutPriority::Medium + } + fn selection_ui( &self, ctx: &mut ViewerContext<'_>, diff --git a/crates/re_space_view_text/src/space_view_class.rs b/crates/re_space_view_text/src/space_view_class.rs index f9c97753202c..f1abf0e7a5d9 100644 --- a/crates/re_space_view_text/src/space_view_class.rs +++ b/crates/re_space_view_text/src/space_view_class.rs @@ -58,6 +58,10 @@ impl SpaceViewClass for TextSpaceView { Some(2.0) // Make text logs wide } + fn layout_priority(&self) -> re_viewer_context::SpaceViewClassLayoutPriority { + re_viewer_context::SpaceViewClassLayoutPriority::Low + } + fn selection_ui( &self, ctx: &mut ViewerContext<'_>, diff --git a/crates/re_space_view_text_box/src/space_view_class.rs b/crates/re_space_view_text_box/src/space_view_class.rs index 57bc008033cd..56612b4d11f1 100644 --- a/crates/re_space_view_text_box/src/space_view_class.rs +++ b/crates/re_space_view_text_box/src/space_view_class.rs @@ -53,6 +53,10 @@ impl SpaceViewClass for TextBoxSpaceView { "Displays text from a text entry components.".into() } + fn layout_priority(&self) -> re_viewer_context::SpaceViewClassLayoutPriority { + re_viewer_context::SpaceViewClassLayoutPriority::Low + } + fn selection_ui( &self, ctx: &mut ViewerContext<'_>, diff --git a/crates/re_space_view_time_series/src/space_view_class.rs b/crates/re_space_view_time_series/src/space_view_class.rs index e96369136367..7e1f79509abc 100644 --- a/crates/re_space_view_time_series/src/space_view_class.rs +++ b/crates/re_space_view_time_series/src/space_view_class.rs @@ -59,6 +59,10 @@ impl SpaceViewClass for TimeSeriesSpaceView { None } + fn layout_priority(&self) -> re_viewer_context::SpaceViewClassLayoutPriority { + re_viewer_context::SpaceViewClassLayoutPriority::Low + } + fn selection_ui( &self, _ctx: &mut ViewerContext<'_>, diff --git a/crates/re_viewer_context/src/lib.rs b/crates/re_viewer_context/src/lib.rs index a442af02f691..ba55e8d180f7 100644 --- a/crates/re_viewer_context/src/lib.rs +++ b/crates/re_viewer_context/src/lib.rs @@ -36,9 +36,10 @@ pub use selection_state::{ }; pub use space_view::{ ArchetypeDefinition, DynSpaceViewClass, Scene, SceneContext, SceneContextPart, ScenePart, - ScenePartCollection, SceneQuery, SpaceViewClass, SpaceViewClassName, SpaceViewClassRegistry, - SpaceViewClassRegistryError, SpaceViewEntityHighlight, SpaceViewHighlights, - SpaceViewOutlineMasks, SpaceViewState, TypedScene, + ScenePartCollection, SceneQuery, SpaceViewClass, SpaceViewClassLayoutPriority, + SpaceViewClassName, SpaceViewClassRegistry, SpaceViewClassRegistryError, + SpaceViewEntityHighlight, SpaceViewHighlights, SpaceViewOutlineMasks, SpaceViewState, + TypedScene, }; pub use store_context::StoreContext; pub use tensor::{TensorDecodeCache, TensorStats, TensorStatsCache}; diff --git a/crates/re_viewer_context/src/space_view/dyn_space_view_class.rs b/crates/re_viewer_context/src/space_view/dyn_space_view_class.rs index 1fc86963b0e8..0304573adf7c 100644 --- a/crates/re_viewer_context/src/space_view/dyn_space_view_class.rs +++ b/crates/re_viewer_context/src/space_view/dyn_space_view_class.rs @@ -15,6 +15,21 @@ re_string_interner::declare_new_type!( pub struct SpaceViewClassName; ); +#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq)] +pub enum SpaceViewClassLayoutPriority { + /// This space view can share space with others + /// + /// Used for boring things like text and plots. + Low, + + #[default] + Medium, + + /// Give this space view lots of space. + /// Used for spatial views (2D/3D). + High, +} + /// Defines a class of space view without any concrete types making it suitable for storage and interfacing. /// /// Implemented by [`crate::SpaceViewClass`]. @@ -56,6 +71,9 @@ pub trait DynSpaceViewClass { /// Preferred aspect ratio for the ui tiles of this space view. fn preferred_tile_aspect_ratio(&self, state: &dyn SpaceViewState) -> Option; + /// Controls how likely this space view will get a large tile in the ui. + fn layout_priority(&self) -> SpaceViewClassLayoutPriority; + /// Executed before the scene is populated, can be use for heuristic & state updates before populating the scene. /// /// Is only allowed to access archetypes defined by [`Self::blueprint_archetype`] diff --git a/crates/re_viewer_context/src/space_view/mod.rs b/crates/re_viewer_context/src/space_view/mod.rs index 1994d49bfe88..aa1656554d5b 100644 --- a/crates/re_viewer_context/src/space_view/mod.rs +++ b/crates/re_viewer_context/src/space_view/mod.rs @@ -15,7 +15,8 @@ mod space_view_class_placeholder; mod space_view_class_registry; pub use dyn_space_view_class::{ - ArchetypeDefinition, DynSpaceViewClass, SpaceViewClassName, SpaceViewState, + ArchetypeDefinition, DynSpaceViewClass, SpaceViewClassLayoutPriority, SpaceViewClassName, + SpaceViewState, }; pub use highlights::{SpaceViewEntityHighlight, SpaceViewHighlights, SpaceViewOutlineMasks}; pub use scene::{Scene, TypedScene}; diff --git a/crates/re_viewer_context/src/space_view/space_view_class.rs b/crates/re_viewer_context/src/space_view/space_view_class.rs index c4d0971f2679..8dd1941395c8 100644 --- a/crates/re_viewer_context/src/space_view/space_view_class.rs +++ b/crates/re_viewer_context/src/space_view/space_view_class.rs @@ -48,6 +48,9 @@ pub trait SpaceViewClass: std::marker::Sized { None } + /// Controls how likely this space view will get a large tile in the ui. + fn layout_priority(&self) -> crate::SpaceViewClassLayoutPriority; + /// Optional archetype of the Space View's blueprint properties. /// /// Blueprint components that only apply to the space view itself, not to the entities it displays. @@ -125,6 +128,11 @@ impl DynSpaceViewClass for T { typed_state_wrapper(state, |state| self.preferred_tile_aspect_ratio(state)) } + #[inline] + fn layout_priority(&self) -> crate::SpaceViewClassLayoutPriority { + self.layout_priority() + } + fn blueprint_archetype(&self) -> Option { self.blueprint_archetype() } diff --git a/crates/re_viewer_context/src/space_view/space_view_class_placeholder.rs b/crates/re_viewer_context/src/space_view/space_view_class_placeholder.rs index 3fb6634aca7e..2551a61b2101 100644 --- a/crates/re_viewer_context/src/space_view/space_view_class_placeholder.rs +++ b/crates/re_viewer_context/src/space_view/space_view_class_placeholder.rs @@ -22,6 +22,10 @@ impl SpaceViewClass for SpaceViewClassPlaceholder { "The Space View Class was not recognized.\nThis happens if either the Blueprint specifies an invalid Space View Class or this version of the Viewer does not know about this type.".into() } + fn layout_priority(&self) -> crate::SpaceViewClassLayoutPriority { + crate::SpaceViewClassLayoutPriority::Low + } + fn selection_ui( &self, _ctx: &mut crate::ViewerContext<'_>, diff --git a/examples/rust/custom_space_view/src/color_coordinates_space_view.rs b/examples/rust/custom_space_view/src/color_coordinates_space_view.rs index d1d28a3d1ed5..f51441737bb4 100644 --- a/examples/rust/custom_space_view/src/color_coordinates_space_view.rs +++ b/examples/rust/custom_space_view/src/color_coordinates_space_view.rs @@ -5,8 +5,8 @@ use re_viewer::external::{ re_log_types::EntityPath, re_ui, re_viewer_context::{ - HoverHighlight, Item, SelectionHighlight, SpaceViewClass, SpaceViewClassName, SpaceViewId, - SpaceViewState, TypedScene, UiVerbosity, ViewerContext, + HoverHighlight, Item, SelectionHighlight, SpaceViewClass, SpaceViewClassLayoutPriority, + SpaceViewClassName, SpaceViewId, SpaceViewState, TypedScene, UiVerbosity, ViewerContext, }, }; @@ -95,6 +95,10 @@ impl SpaceViewClass for ColorCoordinatesSpaceView { Some(1.0) } + fn layout_priority(&self) -> SpaceViewClassLayoutPriority { + Default::default() + } + /// Additional UI displayed when the space view is selected. /// /// In this sample we show a combo box to select the color coordinates mode. From e2ed87c0b256ef57bf48ffeddebaf7786c380cb8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 29 Jun 2023 11:27:43 +0200 Subject: [PATCH 15/15] Use SpaceViewClassLayoutPriority in auto-layout --- crates/re_viewport/src/auto_layout.rs | 32 +++++++++++---------------- crates/re_viewport/src/viewport.rs | 5 ++++- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/crates/re_viewport/src/auto_layout.rs b/crates/re_viewport/src/auto_layout.rs index 3e84e807f497..8c37e7fc5d21 100644 --- a/crates/re_viewport/src/auto_layout.rs +++ b/crates/re_viewport/src/auto_layout.rs @@ -8,15 +8,16 @@ use itertools::Itertools as _; use re_viewer_context::SpaceViewId; -use super::{space_view::SpaceViewBlueprint, view_category::ViewCategory}; +use super::space_view::SpaceViewBlueprint; #[derive(Clone, Debug)] struct SpaceMakeInfo { id: SpaceViewId, - category: ViewCategory, + layout_priority: re_viewer_context::SpaceViewClassLayoutPriority, } pub(crate) fn tree_from_space_views( + space_view_class_registry: &re_viewer_context::SpaceViewClassRegistry, space_views: &BTreeMap, ) -> egui_tiles::Tree { if space_views.is_empty() { @@ -33,9 +34,14 @@ pub(crate) fn tree_from_space_views( *space_view_id, ) }) - .map(|(space_view_id, space_view)| SpaceMakeInfo { - id: *space_view_id, - category: space_view.category, + .map(|(space_view_id, space_view)| { + let layout_priority = space_view + .class(space_view_class_registry) + .layout_priority(); + SpaceMakeInfo { + id: *space_view_id, + layout_priority, + } }) .collect_vec(); @@ -92,21 +98,9 @@ fn arrange_three( // +----------+------------+ // // But which space gets a full side, and which doesn't? - // Answer: we prioritize them by category: + // Answer: we prioritize them based on a class-specific layout priority: - /// lower is better - fn category_priority(category: ViewCategory) -> usize { - match category { - ViewCategory::Spatial => 0, - ViewCategory::Tensor => 1, - ViewCategory::TimeSeries => 2, - ViewCategory::BarChart => 3, - ViewCategory::TextBox => 4, - ViewCategory::Text => 5, - } - } - - spaces.sort_by_key(|smi| category_priority(smi.category)); + spaces.sort_by_key(|smi| -(smi.layout_priority as isize)); let pane_ids = spaces .into_iter() diff --git a/crates/re_viewport/src/viewport.rs b/crates/re_viewport/src/viewport.rs index b9b31b686001..d6ac0c9d5fed 100644 --- a/crates/re_viewport/src/viewport.rs +++ b/crates/re_viewport/src/viewport.rs @@ -536,7 +536,10 @@ impl Viewport { &mut maximized_tree } else { if self.tree.is_empty() { - self.tree = super::auto_layout::tree_from_space_views(&self.space_views); + self.tree = super::auto_layout::tree_from_space_views( + ctx.space_view_class_registry, + &self.space_views, + ); } &mut self.tree };