use crate::canvas::box_scale::{BoxScale, along_axis_factor};
use crate::canvas::box_select::Band;
use crate::canvas::definition_drawing::fixture_footprint;
use crate::canvas::rotate_handle::RotateDrag;
use egui::emath::Rot2;
use egui::{Modifiers, Pos2, Vec2};
use gled_model::patching::definition::{FixtureDefinition, Scaling};
use gled_model::patching::fixture::{FixtureId, LayoutPosition};
use gled_model::patching::layout::Layout;
#[derive(Debug, Clone)]
pub(crate) enum Drag {
Move {
start_pointer: Pos2,
start: Vec<(FixtureId, LayoutPosition)>,
},
Band(Band<FixtureId>),
Scale(BoxScale<(FixtureId, LayoutPosition)>),
Rotate(RotateDrag<(FixtureId, LayoutPosition)>),
End(EndDrag),
}
impl Drag {
pub(crate) fn edits_patch(&self) -> bool {
!matches!(self, Self::Band(_))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum FixtureEnd {
Near,
Far,
}
impl FixtureEnd {
pub(crate) fn along(self) -> f32 {
match self {
Self::Near => 0.0,
Self::Far => 1.0,
}
}
fn opposite(self) -> Self {
match self {
Self::Near => Self::Far,
Self::Far => Self::Near,
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct EndDrag {
pub(crate) fixture: FixtureId,
start: LayoutPosition,
pivot: Pos2,
grab: Pos2,
start_pointer: Pos2,
}
impl EndDrag {
pub(crate) fn new(
fixture: FixtureId,
definition: &FixtureDefinition,
start: LayoutPosition,
end: FixtureEnd,
start_pointer: Pos2,
) -> Self {
let footprint = fixture_footprint(definition, &start, 1.0, 0.0);
Self {
fixture,
start,
pivot: footprint.along_axis(end.opposite().along()),
grab: footprint.along_axis(end.along()),
start_pointer,
}
}
pub(crate) fn position(&self, pointer: Pos2) -> LayoutPosition {
end_scaled(
self.start,
self.pivot,
self.grab,
self.grab + (pointer - self.start_pointer),
)
}
}
pub(crate) fn end_scaled(
start: LayoutPosition,
pivot: Pos2,
grab: Pos2,
target: Pos2,
) -> LayoutPosition {
let from = grab - pivot;
let to = target - pivot;
if from.length() < f32::EPSILON || to.length() < f32::EPSILON {
return start;
}
let ratio = to.length() / from.length();
let turn = to.angle() - from.angle();
let rotation = Rot2::from_angle(turn);
LayoutPosition {
pos: pivot + rotation * ((start.pos - pivot) * ratio),
angle: start.angle + turn,
scale: [start.scale[0] * ratio, start.scale[1] * ratio],
}
}
pub(crate) fn drag_positions(
drag: &Drag,
layout: &mut Layout,
pointer: Pos2,
modifiers: Modifiers,
definition_of: impl Fn(FixtureId) -> Scaling,
) {
let mut place = |id: &FixtureId, position: LayoutPosition| {
if let Some(member) = layout.members.get_mut(id) {
*member = position;
}
};
match drag {
Drag::Move {
start_pointer,
start,
} => {
let delta = pointer - *start_pointer;
for (id, position) in start {
place(
id,
LayoutPosition {
pos: position.pos + delta,
..*position
},
);
}
}
Drag::End(end) => place(&end.fixture, end.position(pointer)),
Drag::Scale(scale) => {
let (origin, scale_x, scale_y) = scale.factors(pointer, modifiers.alt, modifiers.shift);
for (id, start) in &scale.start {
let offset = start.pos - origin;
let scale = if definition_of(*id) == Scaling::Free {
[start.scale[0] * scale_x, start.scale[1] * scale_y]
} else {
let along = along_axis_factor(start.angle, scale_x, scale_y);
[start.scale[0] * along, start.scale[1] * along]
};
place(
id,
LayoutPosition {
pos: origin + Vec2::new(offset.x * scale_x, offset.y * scale_y),
scale,
..*start
},
);
}
}
Drag::Rotate(rotation) => {
let angle = rotation.angle(pointer, modifiers.shift);
let turn = Rot2::from_angle(angle);
for (id, start) in &rotation.start {
place(
id,
LayoutPosition {
pos: rotation.centre + turn * (start.pos - rotation.centre),
angle: start.angle + angle,
..*start
},
);
}
}
Drag::Band(_) => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
use egui::{pos2, vec2};
fn tiny_bar() -> FixtureDefinition {
FixtureDefinition {
size_m: [0.2, 0.01, 0.01],
..Default::default()
}
}
#[test]
fn a_dragged_end_stays_under_the_pointer() {
let definition = tiny_bar();
let start = LayoutPosition::at(pos2(100.0, 100.0));
let far_end = pos2(110.0, 100.0);
let drag = EndDrag::new(
FixtureId::new(),
&definition,
start,
FixtureEnd::Far,
far_end,
);
let mut pointer = far_end;
let mut position = start;
for _ in 0..20 {
pointer += vec2(1.0, 0.5);
position = drag.position(pointer);
}
let end = fixture_footprint(&definition, &position, 1.0, 0.0).along_axis(1.0);
assert!((end - pointer).length() < 1e-3, "{end:?} vs {pointer:?}");
let near = fixture_footprint(&definition, &position, 1.0, 0.0).along_axis(0.0);
assert!(
(near - pos2(90.0, 100.0)).length() < 1e-3,
"the other end stays put: {near:?}"
);
}
#[test]
fn a_drag_onto_the_pivot_leaves_the_fixture_alone() {
let start = LayoutPosition::at(pos2(0.0, 0.0));
let moved = end_scaled(start, pos2(-10.0, 0.0), pos2(10.0, 0.0), pos2(-10.0, 0.0));
assert_eq!(moved, start);
}
}