use std::rc::Rc;
use itertools::Itertools;
use re_viewer_context::{ContainerId, Item, Selection, SpaceViewId, ViewerContext};
use crate::{Contents, ViewportBlueprint};
mod container_and_space_view_actions;
mod utils;
use container_and_space_view_actions::{
AddContainer, AddSpaceView, CloneSpaceViewItem, ContentRemove, ContentVisibilityToggle,
MoveContentsToNewContainer,
};
use utils::{Separator, SubMenu};
trait ContextMenuItem {
fn label(&self, _ctx: &ViewerContext<'_>, _viewport_blueprint: &ViewportBlueprint) -> String {
String::new()
}
fn run(&self, _ctx: &ViewerContext<'_>, _viewport_blueprint: &ViewportBlueprint) {}
fn ui(
&self,
ctx: &ViewerContext<'_>,
viewport_blueprint: &ViewportBlueprint,
ui: &mut egui::Ui,
) -> egui::Response {
let label = self.label(ctx, viewport_blueprint);
let response = ui.button(label);
if response.clicked() {
self.run(ctx, viewport_blueprint);
}
response
}
}
fn context_menu_items_for_selection_summary(
ctx: &ViewerContext<'_>,
viewport_blueprint: &ViewportBlueprint,
item: &Item,
selection_summary: SelectionSummary,
) -> Vec<Box<dyn ContextMenuItem>> {
match selection_summary {
SelectionSummary::SingleContainerItem(container_id) => {
let mut items = context_menu_items_for_selection_summary(
ctx,
viewport_blueprint,
item,
SelectionSummary::ContentsItems(vec![Contents::Container(container_id)]),
);
if !items.is_empty() {
items.push(Separator::item());
}
items.extend([
SubMenu::item(
"Add Container",
possible_child_container_kind(viewport_blueprint, container_id)
.map(|kind| AddContainer::item(container_id, kind)),
),
SubMenu::item(
"Add Space View",
ctx.space_view_class_registry
.iter_registry()
.sorted_by_key(|entry| entry.class.display_name())
.map(|entry| AddSpaceView::item(container_id, entry.class.identifier())),
),
]);
items
}
SelectionSummary::SingleSpaceView(space_view_id) => {
let mut items = context_menu_items_for_selection_summary(
ctx,
viewport_blueprint,
item,
SelectionSummary::ContentsItems(vec![Contents::SpaceView(space_view_id)]),
);
items.push(CloneSpaceViewItem::item(space_view_id));
items
}
SelectionSummary::ContentsItems(contents) => {
let contents: Rc<Vec<_>> = Rc::new(
contents
.into_iter()
.filter(|c| {
Some(*c) != viewport_blueprint.root_container.map(Contents::Container)
})
.collect(),
);
if contents.is_empty() {
vec![]
} else if let Some(root_container_id) = viewport_blueprint.root_container {
let clicked_content = match item {
Item::Container(container_id) => Some(Contents::Container(*container_id)),
Item::SpaceView(space_view_id) => Some(Contents::SpaceView(*space_view_id)),
_ => None,
};
let (target_container_id, target_position) = clicked_content
.and_then(|c| viewport_blueprint.find_parent_and_position_index(&c))
.unwrap_or((root_container_id, 0));
vec![
ContentVisibilityToggle::item(viewport_blueprint, contents.clone()),
ContentRemove::item(contents.clone()),
Separator::item(),
SubMenu::item(
"Move to new container",
possible_child_container_kind(viewport_blueprint, target_container_id).map(
|kind| {
MoveContentsToNewContainer::item(
target_container_id,
target_position,
kind,
contents.clone(),
)
},
),
),
]
} else {
vec![]
}
}
SelectionSummary::Heterogeneous | SelectionSummary::Empty => vec![],
}
}
pub fn context_menu_ui_for_item(
ctx: &ViewerContext<'_>,
viewport_blueprint: &ViewportBlueprint,
item: &Item,
item_response: &egui::Response,
) {
item_response.context_menu(|ui| {
if ui.input(|i| i.key_pressed(egui::Key::Escape)) {
ui.close_menu();
return;
}
let selection_summary = if !ctx.selection().contains_item(item) {
if item_response.hovered() && item_response.secondary_clicked() {
ctx.selection_state()
.set_selection(std::iter::once(item.clone()));
summarize_selection(&Selection::from(item.clone()))
} else {
summarize_selection(ctx.selection())
}
} else {
summarize_selection(ctx.selection())
};
let actions = context_menu_items_for_selection_summary(
ctx,
viewport_blueprint,
item,
selection_summary,
);
if actions.is_empty() {
ui.label(
egui::RichText::from("No action available for the current selection").italics(),
);
}
for action in actions {
let response = action.ui(ctx, viewport_blueprint, ui);
if response.clicked() {
ui.close_menu();
}
}
});
}
fn possible_child_container_kind(
viewport_blueprint: &ViewportBlueprint,
container_id: ContainerId,
) -> impl Iterator<Item = egui_tiles::ContainerKind> + 'static {
let container_kind = viewport_blueprint
.container(&container_id)
.map(|c| c.container_kind);
static ALL_CONTAINERS: &[egui_tiles::ContainerKind] = &[
egui_tiles::ContainerKind::Tabs,
egui_tiles::ContainerKind::Horizontal,
egui_tiles::ContainerKind::Vertical,
egui_tiles::ContainerKind::Grid,
];
ALL_CONTAINERS
.iter()
.copied()
.filter(move |kind| match kind {
egui_tiles::ContainerKind::Horizontal | egui_tiles::ContainerKind::Vertical => {
container_kind != Some(*kind)
}
_ => true,
})
}
#[derive(Debug, Clone)]
enum SelectionSummary {
SingleContainerItem(ContainerId),
SingleSpaceView(SpaceViewId),
ContentsItems(Vec<Contents>),
Heterogeneous,
Empty,
}
fn summarize_selection(selection: &Selection) -> SelectionSummary {
if selection.is_empty() {
return SelectionSummary::Empty;
}
if selection.len() == 1 {
if let Some(Item::Container(container_id)) = selection.first_item() {
return SelectionSummary::SingleContainerItem(*container_id);
} else if let Some(Item::SpaceView(space_view_id)) = selection.first_item() {
return SelectionSummary::SingleSpaceView(*space_view_id);
}
}
let only_space_view_or_container: Option<Vec<_>> = selection
.iter()
.map(|(item, _)| match item {
Item::Container(container_id) => Some(Contents::Container(*container_id)),
Item::SpaceView(space_view_id) => Some(Contents::SpaceView(*space_view_id)),
_ => None,
})
.collect();
if let Some(contents) = only_space_view_or_container {
return SelectionSummary::ContentsItems(contents);
}
SelectionSummary::Heterogeneous
}