use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use eframe::egui;
use image::GenericImageView;
use log::{debug, error, info};
use crate::map_state::MapState;
use crate::persistence;
use crate::tiles::Pane;
use maps_io_ros::{Meta, load_image};
use maps_rendering::{ImagePyramid, TextureFilter};
use crate::app::{AppState, Error, ViewMode};
use crate::app_impl::compat::migrate_old_egui_color;
use maps_io_ros::MapPose;
use maps_io_ros::value_interpretation;
impl AppState {
#[cfg(not(target_arch = "wasm32"))]
fn load_meta(&mut self, yaml_path: &std::path::Path) -> Result<bool, Error> {
let meta = Meta::load_from_file(yaml_path)?;
self.load_map(meta)?;
Ok(true)
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn load_meta_button(&mut self, ui: &mut egui::Ui) {
if ui.button("📂 Load Maps").clicked() {
let mut dialog = rfd::FileDialog::new().add_filter("YAML", &["yaml", "yml"]);
if let Some(dir) = &self.last_file_dir {
dialog = dialog.set_directory(dir);
}
if let Some(paths) = dialog.pick_files() {
for path in paths {
ui.ctx().request_repaint();
match self.load_meta(&path) {
Ok(_) => {
self.last_file_dir = path.parent().map(std::path::Path::to_path_buf);
}
Err(e) => {
self.status.error = e.to_string();
error!("{e}");
}
}
}
}
}
}
pub(crate) fn add_map(&mut self, name: &String, meta: Meta, image_pyramid: &Arc<ImagePyramid>) {
let use_interpretation = meta.value_interpretation.explicit_mode;
if use_interpretation {
self.options.tint_settings.active_tint_selection = Some(name.clone());
}
self.tile_manager.add_pane(Pane { id: name.clone() });
self.data.maps.insert(
name.clone(),
MapState {
meta,
pose: MapPose::default(),
visible: true,
image_pyramid: image_pyramid.clone(),
texture_states: HashMap::new(),
tint: None,
color_to_alpha: None,
texture_filter: TextureFilter::default(),
use_value_interpretation: use_interpretation,
},
);
self.data.draw_order.add(name.clone());
info!("Loaded map: {name}");
self.status.unsaved_changes = true;
if self.options.view_mode == ViewMode::LoadScreen {
self.options.view_mode = ViewMode::default();
}
}
pub(crate) fn load_map(&mut self, meta: Meta) -> Result<String, Error> {
if !meta.image_path.exists() {
return Err(Error::app(format!(
"Image file doesn't exist: {:?}",
meta.image_path
)));
}
let image = if self.options.advanced.dry_run {
info!("Dry-run mode, not loading image {:?}.", meta.image_path);
Ok(image::DynamicImage::new_rgba8(0, 0))
} else {
debug!("Loading image: {:?}", meta.image_path);
load_image(&meta.image_path)
}?;
debug!(
"Loaded image: {:?} {:?}",
meta.image_path,
image.dimensions()
);
let image_pyramid = Arc::new(ImagePyramid::new(image));
let name = meta
.yaml_path
.to_str()
.expect("invalid unicode path, can't use as map name")
.to_owned();
self.add_map(&name, meta, &image_pyramid);
Ok(name)
}
pub(crate) fn delete(&mut self, to_delete: &Vec<String>) {
for name in to_delete {
info!("Removing {name}");
self.data.maps.remove(name);
self.data.draw_order.remove(name);
self.tile_manager.remove_pane(name);
if let Some(active_tool) = &self.status.active_tool
&& active_tool == name
{
self.status.active_tool = None;
}
if let Some(active_tint_selection) = &self.options.tint_settings.active_tint_selection
&& active_tint_selection == name
{
self.options.tint_settings.active_tint_selection =
self.data.maps.keys().last().map(|s| s.to_string());
}
if self.options.pose_edit.selected_map == *name {
self.options.pose_edit.selected_map = "".to_string();
}
self.status.unsaved_changes = true;
}
}
pub(crate) fn add_map_pose(&mut self, map_name: &str, map_pose: MapPose) {
if let Some(map) = self.data.maps.get_mut(map_name) {
map.pose = map_pose;
info!("Loaded pose for: {map_name}");
self.status.unsaved_changes = true;
} else {
error!("Tried to add pose to non-existing map: {map_name}");
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn load_map_pose_button(&mut self, ui: &mut egui::Ui, map_name: &str) {
if ui
.button("📂 Load Pose")
.on_hover_text("Load a map pose from a YAML file.")
.clicked()
{
let mut dialog = rfd::FileDialog::new().add_filter("YAML", &["yaml", "yml"]);
if let Some(dir) = &self.last_file_dir {
dialog = dialog.set_directory(dir);
}
if let Some(path) = dialog.pick_file() {
debug!("Loading pose file: {path:?}");
match MapPose::from_yaml_file(&path) {
Ok(map_pose) => {
self.add_map_pose(map_name, map_pose);
self.last_file_dir = path.parent().map(std::path::Path::to_path_buf);
self.status.unsaved_changes = true;
}
Err(e) => {
self.status.error = e.to_string();
error!("{e}");
}
}
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn save_map_pose_button(&mut self, ui: &mut egui::Ui, map_name: &str) {
if ui
.button("💾 Save Pose")
.on_hover_text("Save the map pose to a YAML file.")
.clicked()
{
let mut dialog = rfd::FileDialog::new()
.add_filter("YAML", &["yaml", "yml"])
.set_file_name("map_pose.yaml");
if let Some(dir) = &self.last_file_dir {
dialog = dialog.set_directory(dir);
}
if let Some(path) = dialog.save_file() {
ui.ctx().request_repaint();
debug!("Saving pose file: {path:?}");
let Some(map) = self.data.maps.get(map_name) else {
self.status.error = format!("Can't save pose, map doesn't exist: {map_name}");
error!("{}", self.status.error);
return;
};
match map.pose.to_yaml_file(&path) {
Ok(_) => {
info!("Saved pose file: {path:?}");
self.last_file_dir = path.parent().map(std::path::Path::to_path_buf);
}
Err(e) => {
self.status.error = e.to_string();
error!("{e}");
}
}
}
}
}
pub fn load_session(&mut self, path: &PathBuf) -> Result<(), Error> {
let deserialized_session = persistence::load_session(path)?;
self.last_file_dir = path.parent().map(std::path::Path::to_path_buf);
self.data
.draw_order
.extend(&deserialized_session.draw_order);
let migrate_colors = deserialized_session.version.is_none();
if migrate_colors {
debug!("Session was saved with maps < 1.7.0, migrating serialized colors.");
}
for (name, map) in deserialized_session.maps {
debug!("Restoring map state: {name}");
let map_name = self.load_map(map.meta).inspect_err(|_| {
self.data
.draw_order
.retain(|key| self.data.maps.contains_key(key));
})?;
let map_state = self.data.maps.get_mut(&map_name).expect("missing map");
map_state.pose = map.pose;
map_state.visible = map.visible;
map_state.tint = map.tint;
if migrate_colors {
map_state.tint = migrate_old_egui_color(map_state.tint);
}
map_state.texture_filter = map.texture_filter;
if map_state.tint.is_some()
|| map_state.meta.value_interpretation.mode != value_interpretation::Mode::Raw
{
self.options.tint_settings.active_tint_selection = Some(name.clone());
}
self.tile_manager
.set_visible(map_name.as_str(), map.visible);
map_state.color_to_alpha = map.color_to_alpha;
if migrate_colors {
map_state.color_to_alpha = migrate_old_egui_color(map_state.color_to_alpha);
}
self.status.unsaved_changes = false;
}
for (id, lens_pos) in deserialized_session.grid_lenses {
debug!("Restoring lens {id}");
self.data.grid_lenses.insert(id, lens_pos);
}
Ok(())
}
pub(crate) fn load_session_button(&mut self, ui: &mut egui::Ui) {
if ui
.button("📂 Load Session")
.on_hover_text("Load a session from a file.")
.on_disabled_hover_text("Only supported in native builds.")
.clicked()
{
#[cfg(not(target_arch = "wasm32"))]
{
let mut dialog = rfd::FileDialog::new().add_filter("TOML", &["toml"]);
if let Some(dir) = &self.last_file_dir {
dialog = dialog.set_directory(dir);
}
if let Some(path) = dialog.pick_file() {
self.load_session(&path).unwrap_or_else(|e| {
self.status.error = e.to_string();
error!("{e}");
});
}
}
}
}
pub(crate) fn save_session_button(&mut self, ui: &mut egui::Ui, quit_after_save: bool) {
let text = if quit_after_save {
"💾 Save Session and Quit"
} else {
"💾 Save Session"
};
let disabled_hover_text = if cfg!(target_arch = "wasm32") {
"Only supported in native builds."
} else {
"No maps to save."
};
if ui
.button(text.to_owned())
.on_hover_text("Save the current session to a file.")
.on_disabled_hover_text(disabled_hover_text)
.clicked()
{
#[cfg(not(target_arch = "wasm32"))]
{
let mut dialog = rfd::FileDialog::new()
.add_filter("TOML", &["toml"])
.set_file_name("maps_session.toml");
if let Some(dir) = &self.last_file_dir {
dialog = dialog.set_directory(dir);
}
if let Some(path) = dialog.save_file() {
match persistence::save_session(&path, &self.data) {
Ok(_) => {
self.last_file_dir = path.parent().map(std::path::Path::to_path_buf);
self.status.unsaved_changes = false;
if quit_after_save {
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
}
}
Err(e) => {
self.status.error = e.to_string();
error!("{e}");
}
}
}
self.status.quit_modal_active = false;
}
}
}
}