use crate::canvas::context_menu::ContextMenu;
use crate::fixture_definition_editor::state::EditorState;
use egui::{Key, KeyboardShortcut, Modifiers, Response};
use gled_model::patching::definition::{DefinitionError, FixtureDefinition};
const DUPLICATE_OFFSET_M: f32 = 0.01;
pub(crate) type Edit<'a> = crate::canvas::edit::Edit<'a, FixtureDefinition>;
pub(crate) trait DefinitionEdit {
fn change<R>(
&mut self,
state: &mut EditorState,
change: impl FnOnce(&mut FixtureDefinition) -> R,
) -> R;
fn try_change(
&mut self,
state: &mut EditorState,
change: impl FnOnce(&mut FixtureDefinition) -> Result<(), DefinitionError>,
) -> bool;
}
impl DefinitionEdit for Edit<'_> {
fn change<R>(
&mut self,
state: &mut EditorState,
change: impl FnOnce(&mut FixtureDefinition) -> R,
) -> R {
state.message = None;
state.drag = None;
let result = self.apply(change);
state.clamp(self.value);
result
}
fn try_change(
&mut self,
state: &mut EditorState,
change: impl FnOnce(&mut FixtureDefinition) -> Result<(), DefinitionError>,
) -> bool {
match self.change(state, change) {
Ok(()) => true,
Err(error) => {
state.message = Some(error.to_string());
false
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum EditorAction {
SelectAll,
Delete,
Duplicate,
ReverseOrder,
MoveToFront,
Group,
Ungroup,
EachOwnGroup,
Undo,
Redo,
}
impl EditorAction {
pub(crate) fn apply(self, edit: &mut Edit<'_>, state: &mut EditorState) {
state.message = None;
let selection = state.selection.clone();
let mode = state.mode;
match self {
Self::SelectAll => state.selection = (0..edit.value.pixels.len()).collect(),
Self::Delete => {
edit.change(state, |definition| definition.remove_pixels(&selection));
state.selection.clear();
}
Self::Duplicate => {
let offset = [DUPLICATE_OFFSET_M, DUPLICATE_OFFSET_M, 0.0];
let copies = edit.change(state, |definition| {
definition.duplicate_pixels(&selection, offset)
});
state.selection = copies.collect();
}
Self::ReverseOrder => {
edit.change(state, |definition| definition.reverse_pixels(&selection));
}
Self::MoveToFront => {
edit.change(state, |definition| definition.move_to_front(&selection));
state.selection = (0..selection.len()).collect();
}
Self::Group => {
edit.try_change(state, |definition| {
definition.group_selection(mode, &selection)
});
}
Self::Ungroup => {
edit.try_change(state, |definition| {
definition.ungroup_selection(mode, &selection)
});
}
Self::EachOwnGroup => {
edit.try_change(state, |definition| definition.each_own_group(mode));
}
Self::Undo | Self::Redo => {
let done = if self == Self::Undo {
edit.undo()
} else {
edit.redo()
};
if done {
state.drag = None;
state.clamp(edit.value);
}
}
}
}
}
pub(crate) fn context_actions(response: &Response) -> Option<EditorAction> {
let command = |key| Some(KeyboardShortcut::new(Modifiers::COMMAND, key));
let command_shift = |key| {
Some(KeyboardShortcut::new(
Modifiers::COMMAND | Modifiers::SHIFT,
key,
))
};
ContextMenu::new(&response.ctx)
.entry("Select All", EditorAction::SelectAll, command(Key::A))
.entry("Duplicate", EditorAction::Duplicate, command(Key::D))
.entry(
"Delete",
EditorAction::Delete,
Some(KeyboardShortcut::new(Modifiers::NONE, Key::Delete)),
)
.entry("Reverse DMX Order", EditorAction::ReverseOrder, None)
.entry("Make First in DMX Order", EditorAction::MoveToFront, None)
.entry(
"Ungroup in Mode",
EditorAction::Ungroup,
command_shift(Key::G),
)
.entry("Group in Mode", EditorAction::Group, command(Key::G))
.entry("Each Own Group in Mode", EditorAction::EachOwnGroup, None)
.entry("Redo", EditorAction::Redo, command_shift(Key::Z))
.entry("Undo", EditorAction::Undo, command(Key::Z))
.key(
KeyboardShortcut::new(Modifiers::NONE, Key::Backspace),
EditorAction::Delete,
)
.key(
KeyboardShortcut::new(Modifiers::COMMAND, Key::Y),
EditorAction::Redo,
)
.show(response)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::canvas::undo::UndoStack;
use crate::fixture_definition_editor::state::Drag;
use egui::Pos2;
use gled_model::patching::definition::{EmittedColor, Mode, Pixel};
#[test]
fn an_edit_ends_the_drag_in_flight() {
let pixel = |x_m: f32| 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],
};
let mut definition = FixtureDefinition {
pixels: (0..4).map(|index| pixel(index as f32 * 0.1)).collect(),
modes: vec![Mode::individual(4)],
..Default::default()
};
let mut undo = UndoStack::default();
let mut edit = Edit::new(&mut definition, &mut undo);
let mut state = EditorState {
selection: [1].into_iter().collect(),
drag: Some(Drag::Move {
pointer_start: Pos2::ZERO,
start: vec![(1, [0.1, 0.0, 0.0])],
}),
..Default::default()
};
EditorAction::Delete.apply(&mut edit, &mut state);
assert!(state.drag.is_none());
assert_eq!(edit.value.pixels.len(), 3);
}
}