use eframe::egui;
use log::error;
use crate::app::{AppState, ViewMode};
use crate::app_impl::constants::SPACE;
use crate::app_impl::ui_helpers::display_path;
use crate::map_state::MapState;
use maps_rendering::TextureRequest;
fn map_tooltip(ui: &mut egui::Ui, name: &str, map: &mut MapState) {
ui.horizontal_top(|ui| {
map.get_or_create_texture_state("tooltip_thumbnail").put(
ui,
&TextureRequest::new(
"tooltip".to_owned(),
egui::Rect::from_min_size(egui::Pos2::ZERO, egui::Vec2::splat(200.)),
),
);
egui::Grid::new("map_info")
.striped(true)
.num_columns(2)
.show(ui, |ui| {
ui.label("YAML file path");
ui.label(name);
ui.end_row();
ui.label("Image file path");
ui.label(map.meta.image_path.to_str().expect("invalid path?"));
ui.end_row();
ui.label("Image file size");
ui.label(format!(
"{} x {} pixels",
map.image_pyramid.original_size.x, map.image_pyramid.original_size.y
));
ui.end_row();
ui.label("Resolution");
ui.label(format!("{} m/pixel", map.meta.resolution));
});
});
}
impl AppState {
pub(crate) fn menu_panel(&mut self, ui: &mut egui::Ui) {
if !self.options.menu_visible {
return;
}
egui::Panel::left("menu").show_inside(ui, |ui| {
egui::ScrollArea::vertical().show(ui, |ui| {
self.menu_content(ui);
});
});
}
fn deselect_toggle(&mut self, ui: &mut egui::Ui) {
if self.data.maps.is_empty() {
return;
}
let mut all_off = self.data.maps.iter().all(|(_, map)| !map.visible);
let icon = if all_off { "☑" } else { "⛶" };
let action = if all_off { "Select" } else { "Deselect" };
if ui
.toggle_value(&mut all_off, icon)
.on_hover_text(format!("{action} all."))
.clicked()
{
for (name, map) in &mut self.data.maps {
map.visible = !all_off;
self.tile_manager.set_visible(name, !all_off);
}
}
}
fn map_entry(&mut self, ui: &mut egui::Ui, name: &str, to_delete: &mut Vec<String>) {
let Some(map) = self.data.maps.get_mut(name) else {
error!("Attempted to show map entry for unknown map {name}.");
return;
};
let warn_origin_rotation = map.meta.origin_theta.angle() != 0.;
egui::Sides::new().show(
ui,
|ui_left| {
if ui_left
.checkbox(
&mut map.visible,
display_path(name, self.options.display.show_full_paths),
)
.on_hover_ui(|ui_left: &mut egui::Ui| {
map_tooltip(ui_left, name, map);
})
.changed()
{
self.tile_manager.set_visible(name, map.visible);
}
},
|ui_right| {
if ui_right.button("🗑").on_hover_text("Delete Map").clicked() {
to_delete.push(name.to_string());
}
if warn_origin_rotation {
ui_right
.label(
egui::RichText::new("⚠")
.strong()
.color(egui::Color32::ORANGE),
)
.on_hover_text(
"This map has a non-zero origin rotation component in its metadata.\n\
maps uses it, but it's not supported by most ROS tools.\n\n\
It's recommended to save alignment transformations separately,\n\
e.g. using the Pose editor here.",
);
}
},
);
}
fn maps_list(&mut self, ui: &mut egui::Ui) {
let mut to_delete: Vec<String> = Vec::new();
let draw_order_keys = self.data.draw_order.keys().clone();
egui::Grid::new("maps_list")
.num_columns(1)
.striped(true)
.show(ui, |ui| {
for name in &draw_order_keys {
self.map_entry(ui, name, &mut to_delete);
ui.end_row();
}
});
self.delete(&to_delete);
}
fn menu_content(&mut self, ui: &mut egui::Ui) {
egui::Sides::new().show(
ui,
|ui_left| {
ui_left.heading("Maps");
},
|ui_right| {
ui_right.visuals_mut().widgets.inactive.weak_bg_fill =
ui_right.visuals().window_fill();
if ui_right
.button(egui::RichText::new("🏠").heading())
.on_hover_text("Switch to load screen")
.clicked()
{
self.options.view_mode = ViewMode::LoadScreen;
}
},
);
ui.add_space(SPACE);
ui.horizontal(|ui| {
self.load_meta_button(ui);
ui.separator();
ui.add_enabled_ui(cfg!(not(target_arch = "wasm32")), |ui| {
self.load_session_button(ui);
});
ui.add_enabled_ui(
cfg!(not(target_arch = "wasm32")) && !self.data.maps.is_empty(),
|ui| {
self.save_session_button(ui, false);
},
);
});
ui.separator();
if self.data.maps.is_empty() {
ui.label(egui::RichText::new("No maps loaded.").weak().italics());
return;
}
egui::CollapsingHeader::new("List")
.default_open(true)
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.toggle_value(&mut self.status.draw_order_edit_active, "⬆⬇")
.on_hover_text("Click to edit the draw order via drag and drop.");
if !self.status.draw_order_edit_active && !self.data.maps.is_empty() {
ui.separator();
self.deselect_toggle(ui);
}
ui.separator();
ui.toggle_value(&mut self.options.display.show_full_paths, "/../..")
.on_hover_text("Show full paths instead of just the file name.");
});
if self.status.draw_order_edit_active {
self.data
.draw_order
.ui(ui, self.options.display.show_full_paths);
} else {
self.maps_list(ui);
}
});
ui.separator();
ui.add_space(SPACE);
ui.heading("Pose");
ui.add_space(SPACE);
self.pose_edit(ui);
if !self.options.pose_edit.selected_map.is_empty() && self.data.maps.len() > 1 {
ui.separator();
ui.add_space(SPACE);
self.apply_pose_to_other_maps(ui);
}
}
}