use crate::canvas::images::{Decoded, decode_picture};
use crate::canvas::pictures;
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 crate::ui::file_pick::ChosenFile;
use egui::Context;
use gled_model::{asset::picture::new_picture_path, patching::definition::Model};
use std::path::PathBuf;
pub(crate) struct PickedFile {
pub(crate) definition: Option<uuid::Uuid>,
pub(crate) file: DefinitionFile,
}
pub(crate) enum DefinitionFile {
Photo {
name: String,
path: PathBuf,
bytes: Vec<u8>,
image: Decoded,
},
Model(Result<Model, String>),
Path {
name: String,
shape: Result<(Shape, usize), String>,
},
}
impl DefinitionFile {
pub(crate) fn photo(file: ChosenFile) -> Self {
let path = new_picture_path(&file.extension());
let image = decode_picture(&path, &file.bytes);
Self::Photo {
name: file.name,
path,
bytes: file.bytes,
image,
}
}
pub(crate) fn model(file: ChosenFile) -> Self {
Self::Model(load_model(&file))
}
pub(crate) fn path(file: ChosenFile) -> Self {
let shape = file.text().and_then(path_from_svg);
Self::Path {
name: file.name,
shape,
}
}
}
fn load_model(file: &ChosenFile) -> Result<Model, String> {
let svg = file.text()?.to_owned();
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}", file.name))?;
Ok(Model {
name: file.stem(),
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,
bytes,
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));
pictures::store(path.clone(), bytes);
let chosen = new_photo(path, edit.value.size_m, Some(aspect));
edit.change(state, |definition| definition.photo = Some(chosen));
}
DefinitionFile::Photo {
name,
image: Err(error),
..
} => state.message = Some(format!("Could not load {name}: {error}")),
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 {
name,
shape: Err(error),
} => state.message = Some(format!("{name}: {error}")),
}
}
#[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 broken = ChosenFile {
name: "model.svg".to_owned(),
bytes: b"<not svg".to_vec(),
};
let Err(message) = load_model(&broken) else {
panic!("it is no SVG");
};
assert!(message.contains("model.svg"), "{message}");
}
#[test]
fn a_model_is_named_after_its_file() {
let file = ChosenFile {
name: "Bar.svg".to_owned(),
bytes: br#"<svg xmlns="http://www.w3.org/2000/svg" width="10mm" height="5mm"/>"#
.to_vec(),
};
let model = load_model(&file).expect("an SVG");
assert_eq!(model.name, "Bar");
}
}