use crate::canvas::box_scale::BoxScale;
use crate::canvas::box_select::Band;
use crate::canvas::caches::Caches;
use crate::canvas::rotate_handle::RotateDrag;
use crate::canvas::view::SceneView;
use crate::fixture_definition_editor::canvas_geometry::{origin, outgrows, scene_side};
use crate::fixture_definition_editor::generators::{GeneratorKind, GeneratorParams};
use crate::fixture_definition_editor::picked_file::{DefinitionFile, PickedFile};
use crate::fixture_definition_editor::shape::DrawState;
use crate::ui::file_pick::{FileFilter, FilePick};
use egui::Pos2;
use gled_model::patching::definition::FixtureDefinition;
use std::collections::BTreeSet;
#[derive(Debug, Clone)]
pub(crate) enum Drag {
Move {
pointer_start: Pos2,
start: Vec<(usize, [f32; 3])>,
},
Band(Band<usize>),
Scale(BoxScale<(usize, [f32; 3])>),
Rotate(RotateDrag<(usize, [f32; 3], f32)>),
}
pub(crate) struct EditorState {
pub(crate) editing: Option<uuid::Uuid>,
pub(crate) selection: BTreeSet<usize>,
pub(crate) view: SceneView,
pub(crate) scene_side: f32,
pub(crate) zoom: f32,
pub(crate) canvas_rect: egui::Rect,
pub(crate) drag: Option<Drag>,
pub(crate) mode: usize,
pub(crate) current_model: usize,
pub(crate) generator: Option<GeneratorKind>,
pub(crate) params: GeneratorParams,
pub(crate) draw: DrawState,
pub(crate) caches: Caches,
pub(crate) file_pick: FilePick<PickedFile>,
pub(crate) message: Option<String>,
pub(crate) typing: bool,
}
impl Default for EditorState {
fn default() -> Self {
Self {
editing: None,
selection: BTreeSet::new(),
view: SceneView::centred_on(origin(0.0)),
scene_side: 0.0,
zoom: 0.0,
canvas_rect: egui::Rect::NOTHING,
drag: None,
mode: 0,
current_model: 0,
generator: None,
params: GeneratorParams::default(),
draw: DrawState::default(),
caches: Caches::default(),
file_pick: FilePick::default(),
message: None,
typing: false,
}
}
}
impl std::fmt::Debug for EditorState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EditorState")
.field("editing", &self.editing)
.field("selection", &self.selection)
.field("mode", &self.mode)
.finish_non_exhaustive()
}
}
impl EditorState {
pub(crate) fn reset_for(&mut self, id: Option<uuid::Uuid>, definition: &FixtureDefinition) {
self.editing = id;
self.selection.clear();
self.drag = None;
self.mode = 0;
self.current_model = 0;
self.generator = None;
self.draw.shape = None;
self.message = None;
self.fit_view(definition);
}
pub(crate) fn fit_view(&mut self, definition: &FixtureDefinition) {
self.scene_side = scene_side(definition);
self.view = SceneView::centred_on(origin(self.scene_side));
}
pub(crate) fn clamp(&mut self, definition: &FixtureDefinition) {
let pixel_count = definition.pixels.len();
self.selection.retain(|pixel| *pixel < pixel_count);
if self.mode >= definition.modes.len() {
self.mode = 0;
}
if self.current_model >= definition.models.len() {
self.current_model = 0;
}
if self.drag.is_none() && outgrows(definition, self.scene_side) {
if self.view.zoom.is_none() && self.zoom > 0.0 {
self.view.zoom = Some(self.zoom);
}
let before = origin(self.scene_side);
self.scene_side = scene_side(definition);
self.view.center += origin(self.scene_side) - before;
}
}
pub(crate) fn ask_file(
&mut self,
ctx: &egui::Context,
filter: FileFilter,
load: fn(std::path::PathBuf) -> DefinitionFile,
) {
let definition = self.editing;
self.file_pick.ask(ctx, filter, move |path| PickedFile {
definition,
file: load(path),
});
}
pub(crate) fn origin(&self) -> Pos2 {
origin(self.scene_side)
}
}
#[cfg(test)]
mod tests {
use super::*;
use gled_model::patching::definition::{EmittedColor, Pixel};
fn reaching(x_m: f32) -> FixtureDefinition {
FixtureDefinition {
pixels: vec![Pixel {
name: "p".to_string(),
model: 0,
position_m: [x_m, 0.0, 0.0],
rotation: 0.0,
channels: vec![EmittedColor::Red, EmittedColor::Green, EmittedColor::Blue],
}],
..Default::default()
}
}
#[test]
fn the_scene_waits_for_a_drag_to_end_before_it_grows() {
let mut state = EditorState::default();
state.fit_view(&reaching(0.1));
let side = state.scene_side;
state.drag = Some(Drag::Move {
pointer_start: Pos2::ZERO,
start: vec![],
});
state.clamp(&reaching(2.0));
assert_eq!(state.scene_side, side);
state.drag = None;
state.zoom = 3.0;
state.clamp(&reaching(2.0));
assert!(state.scene_side > side);
assert_eq!(state.view.zoom, Some(3.0), "the zoom stays as it was");
}
}