1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! Code for automatic layout of space views.
//!
//! This uses some very rough heuristics and have a lot of room for improvement.

use std::collections::BTreeMap;

use itertools::Itertools as _;

use re_types::SpaceViewClassIdentifier;
use re_viewer_context::SpaceViewId;

use re_viewport_blueprint::SpaceViewBlueprint;

#[derive(Clone, Debug)]
struct SpaceMakeInfo {
    id: SpaceViewId,
    class_identifier: SpaceViewClassIdentifier,
    layout_priority: re_viewer_context::SpaceViewClassLayoutPriority,
}

pub(crate) fn tree_from_space_views(
    space_view_class_registry: &re_viewer_context::SpaceViewClassRegistry,
    space_views: &BTreeMap<SpaceViewId, SpaceViewBlueprint>,
) -> egui_tiles::Tree<SpaceViewId> {
    re_log::trace!("Auto-layout of {} space views", space_views.len());

    let space_make_infos = space_views
        .iter()
        // Sort for determinism:
        .sorted_by_key(|(space_view_id, space_view)| {
            (
                &space_view.space_origin,
                &space_view.display_name,
                *space_view_id,
            )
        })
        .map(|(space_view_id, space_view)| {
            let class_identifier = space_view.class_identifier();
            let layout_priority = space_view
                .class(space_view_class_registry)
                .layout_priority();
            SpaceMakeInfo {
                id: *space_view_id,
                class_identifier,
                layout_priority,
            }
        })
        .collect_vec();

    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 for common case that doesn't fit nicely in a grid
        arrange_three(
            [
                space_make_infos[0].clone(),
                space_make_infos[1].clone(),
                space_make_infos[2].clone(),
            ],
            &mut tiles,
        )
    } else if space_make_infos.len() <= 12 {
        // 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)
    } else {
        // So many space views - lets group by class and put the members of each group into tabs:
        let mut grouped_by_class: BTreeMap<SpaceViewClassIdentifier, Vec<SpaceMakeInfo>> =
            Default::default();
        for smi in space_make_infos {
            grouped_by_class
                .entry(smi.class_identifier)
                .or_default()
                .push(smi);
        }

        let groups = grouped_by_class
            .values()
            .cloned()
            .sorted_by_key(|group| -(group[0].layout_priority as isize));

        let tabs = groups
            .into_iter()
            .map(|group| {
                let children = group
                    .into_iter()
                    .map(|smi| tiles.insert_pane(smi.id))
                    .collect_vec();
                tiles.insert_tab_tile(children)
            })
            .collect_vec();

        if tabs.len() == 1 {
            tabs[0]
        } else {
            tiles.insert_grid_tile(tabs)
        }
    };

    egui_tiles::Tree::new("viewport_tree", root, tiles)
}

fn arrange_three(
    mut spaces: [SpaceMakeInfo; 3],
    tiles: &mut egui_tiles::Tiles<SpaceViewId>,
) -> egui_tiles::TileId {
    // We will arrange it like so:
    //
    // +-------------+
    // |             |
    // |             |
    // |             |
    // +-------+-----+
    // |       |     |
    // |       |     |
    // +-------+-----+
    //
    // or like so:
    //
    // +-----------------------+
    // |          |            |
    // |          |            |
    // |          +------------+
    // |          |            |
    // |          |            |
    // |          |            |
    // +----------+------------+
    //
    // But which space gets a full side, and which doesn't?
    // Answer: we prioritize them based on a class-specific layout priority:

    spaces.sort_by_key(|smi| -(smi.layout_priority as isize));

    let pane_ids = spaces
        .into_iter()
        .map(|smi| tiles.insert_pane(smi.id))
        .collect_vec();

    let inner_grid = tiles.insert_grid_tile(vec![pane_ids[1], pane_ids[2]]);
    tiles.insert_grid_tile(vec![pane_ids[0], inner_grid])
}