use crate::canvas::images::{Decoded, decode_image};
use crate::fixture_definition_editor::draw_tools::Tool;
use crate::fixture_definition_editor::edit::{DefinitionEdit, Edit};
use crate::fixture_definition_editor::properties::new_photo;
use crate::fixture_definition_editor::shape::Shape;
use crate::fixture_definition_editor::state::EditorState;
use crate::fixture_definition_editor::svg_path::path_from_svg;
use egui::Context;
use gled_model::patching::definition::Model;
use std::path::{Path, PathBuf};
pub(crate) struct PickedFile {
pub(crate) definition: Option<uuid::Uuid>,
pub(crate) file: DefinitionFile,
}
pub(crate) enum DefinitionFile {
Photo { path: PathBuf, image: Decoded },
Model(Result<Model, String>),
Path {
path: PathBuf,
shape: Result<(Shape, usize), String>,
},
}
impl DefinitionFile {
pub(crate) fn photo(path: PathBuf) -> Self {
let image = decode_image(&path);
Self::Photo { path, image }
}
pub(crate) fn model(path: PathBuf) -> Self {
Self::Model(load_model(&path))
}
pub(crate) fn path(path: PathBuf) -> Self {
let shape = std::fs::read_to_string(&path)
.map_err(|error| error.to_string())
.and_then(|svg| path_from_svg(&svg));
Self::Path { path, shape }
}
}
fn load_model(path: &Path) -> Result<Model, String> {
let svg = std::fs::read_to_string(path)
.map_err(|error| format!("Could not read {}: {error}", path.display()))?;
let size_m = gled_svg::ParsedSvg::parse(&svg)
.map(|parsed| parsed.size_mm().map(|mm| mm / 1000.0))
.map_err(|error| format!("{} is no SVG gled can draw: {error}", path.display()))?;
let name = path.file_stem().map_or("model".to_string(), |stem| {
stem.to_string_lossy().into_owned()
});
Ok(Model { name, size_m, svg })
}
pub(crate) fn apply_picked_file(
ctx: &Context,
edit: &mut Edit<'_>,
state: &mut EditorState,
edited: uuid::Uuid,
picked: PickedFile,
) {
if picked.definition != Some(edited) {
state.message = Some("Left out a file chosen for another definition".to_string());
return;
}
match picked.file {
DefinitionFile::Photo {
path,
image: Ok(image),
} => {
let [width, height] = image.size;
let aspect = width as f32 / height as f32;
state.caches.images.insert(ctx, path.clone(), Ok(image));
let chosen = new_photo(path, edit.value.size_m, Some(aspect));
edit.change(state, |definition| definition.photo = Some(chosen));
}
DefinitionFile::Photo {
path,
image: Err(error),
} => state.message = Some(format!("Could not load {}: {error}", path.display())),
DefinitionFile::Model(Ok(model)) => {
edit.change(state, |definition| definition.models.push(model));
}
DefinitionFile::Model(Err(message)) => state.message = Some(message),
DefinitionFile::Path {
shape: Ok((shape, paths)),
..
} => {
state.draw.tool = Tool::Path;
state.draw.shape = Some(shape);
state.message =
(paths > 1).then(|| format!("{paths} paths in the file; the longest is used"));
}
DefinitionFile::Path {
path,
shape: Err(error),
} => state.message = Some(format!("{}: {error}", path.display())),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn model_for(definition: uuid::Uuid) -> PickedFile {
PickedFile {
definition: Some(definition),
file: DefinitionFile::Model(Ok(Model::circle("led", 0.01))),
}
}
#[test]
fn a_file_chosen_for_another_definition_is_left_out() {
let ctx = Context::default();
let (asked, shown) = (uuid::Uuid::new_v4(), uuid::Uuid::new_v4());
let mut definition = gled_model::patching::definition::FixtureDefinition::default();
let mut undo = crate::canvas::undo::UndoStack::default();
let mut edit = Edit::new(&mut definition, &mut undo);
let mut state = EditorState::default();
apply_picked_file(&ctx, &mut edit, &mut state, shown, model_for(asked));
assert!(edit.value.models.is_empty());
assert!(!edit.changed(), "the definition shown is not touched");
assert!(state.message.is_some(), "and the user is told");
apply_picked_file(&ctx, &mut edit, &mut state, shown, model_for(shown));
assert_eq!(edit.value.models.len(), 1);
}
#[test]
fn a_model_that_cannot_be_read_says_which_file() {
let Err(message) = load_model(Path::new("/nonexistent/gled/model.svg")) else {
panic!("there is no such file");
};
assert!(message.contains("/nonexistent/gled/model.svg"), "{message}");
}
}