mod paint;
use crate::canvas::box_scale::{BoxScale, HANDLE_HIT, box_handles, scaled_box};
use crate::canvas::box_select::Band;
use crate::canvas::frame::{CanvasSpec, show_canvas};
use crate::canvas::rotate_handle::{RotateDrag, rotation_handle};
use crate::canvas::scrollbars::panning;
use crate::canvas::selection::ClickIntent;
use crate::canvas::shapes::Shapes;
use crate::fixture_definition_editor::canvas_geometry::{
covered_pixels, origin, pixel_under, selection_bbox, to_metres, to_scene,
};
use crate::fixture_definition_editor::draw_tools::{self, Pointer, Tool};
use crate::fixture_definition_editor::edit::{DefinitionEdit, Edit, context_actions};
use crate::fixture_definition_editor::generators::{self, GeneratorKind};
use crate::fixture_definition_editor::state::{Drag, EditorState};
use crate::fixture_definition_editor::toolbar;
use egui::emath::{Rot2, TSTransform};
use egui::{Key, Modifiers, PointerButton, Pos2, Response, Sense, Ui, Vec2, vec2};
use gled_model::patching::fixture::UNITS_PER_METRE;
const MAX_ZOOM: f32 = 64.0;
const MIN_PIXEL_PX: f32 = 8.0;
const NUDGE_M: f32 = 0.001;
pub(crate) fn show(
ui: &mut Ui,
asset_id: uuid::Uuid,
edit: &mut Edit<'_>,
state: &mut EditorState,
) {
let side = state.scene_side;
let mut view = state.view;
let spec = CanvasSpec {
id_salt: "fixture definition canvas",
scene_size: Vec2::splat(side),
max_zoom: MAX_ZOOM,
sense: Sense::click_and_drag(),
};
let shown = show_canvas(ui, spec, &mut view, |canvas| {
toolbar::keys(canvas.ui.ctx(), edit, state);
if let Some(action) = context_actions(&canvas.response) {
action.apply(edit, state);
}
nudge(canvas.ui, edit, state);
let mut frame = Frame {
edit,
state,
scene_transform: canvas.scene_transform,
overlay: Shapes::default(),
};
frame.interact(canvas.ui, &canvas.response);
frame.paint(&canvas.painter, asset_id, &canvas.response);
});
if state.scene_side != side {
view.center += origin(state.scene_side) - origin(side);
}
state.view = view;
state.zoom = shown.zoom;
state.canvas_rect = shown.clip_rect;
}
fn nudge(ui: &Ui, edit: &mut Edit<'_>, state: &mut EditorState) {
if state.selection.is_empty() || ui.ctx().text_edit_focused() {
return;
}
let mut step = Vec2::ZERO;
ui.input_mut(|input| {
for (key, direction) in [
(Key::ArrowLeft, vec2(-1.0, 0.0)),
(Key::ArrowRight, vec2(1.0, 0.0)),
(Key::ArrowUp, vec2(0.0, -1.0)),
(Key::ArrowDown, vec2(0.0, 1.0)),
] {
if input.consume_key(Modifiers::SHIFT, key) {
step += direction * 10.0;
} else if input.consume_key(Modifiers::NONE, key) {
step += direction;
}
}
});
if step == Vec2::ZERO {
return;
}
let selection = state.selection.clone();
edit.change(state, |definition| {
for pixel in &selection {
if let Some(pixel) = definition.pixels.get_mut(*pixel) {
pixel.position_m[0] += step.x * NUDGE_M;
pixel.position_m[1] += step.y * NUDGE_M;
}
}
});
}
struct Frame<'a, 'b> {
edit: &'a mut Edit<'b>,
state: &'a mut EditorState,
scene_transform: TSTransform,
overlay: Shapes,
}
impl Frame<'_, '_> {
fn to_scene_point(&self, screen: Pos2) -> Pos2 {
self.scene_transform.inverse() * screen
}
fn min_half(&self) -> f32 {
MIN_PIXEL_PX / 2.0 / self.scene_transform.scaling
}
fn interact(&mut self, ui: &mut Ui, response: &Response) {
let (pointer, press, modifiers, primary_down) = ui.input(|input| {
(
input.pointer.interact_pos(),
input.pointer.press_origin(),
button_modifiers(input),
input.pointer.primary_down(),
)
});
let pointer = pointer.map(|p| self.to_scene_point(p));
let press = press.map(|p| self.to_scene_point(p));
if let Some(pointer) = pointer {
self.continue_drag(pointer, modifiers);
}
if !primary_down && self.state.drag.take().is_some() {
self.edit.end();
self.state.clamp(self.edit.value);
}
let clicked_at = response.interact_pointer_pos();
if self.state.draw.tool != Tool::Select {
let origin = self.state.origin();
let metres = |scene: Pos2| to_metres(origin, scene);
let drawing = Pointer {
at: pointer.map(metres),
press: press.map(metres),
clicked_at: clicked_at.map(metres),
pixels_per_metre: self.scene_transform.scaling * UNITS_PER_METRE,
modifiers,
};
if !panning(ui) {
draw_tools::interact(self.state, response, &drawing);
}
return;
}
self.selection_handles(ui, pointer);
if !panning(ui) {
self.start_drag(response, press, modifiers);
if let Some(pointer) = pointer {
self.continue_drag(pointer, modifiers);
}
self.click(response, clicked_at, modifiers);
}
}
fn continue_drag(&mut self, pointer: Pos2, modifiers: Modifiers) {
let origin = self.state.origin();
let Some(drag) = self.state.drag.as_mut() else {
return;
};
match drag {
Drag::Move {
pointer_start,
start,
} => {
let delta = (pointer - *pointer_start) / UNITS_PER_METRE;
let start = start.clone();
self.edit.continue_change(|definition| {
for (pixel, position) in &start {
if let Some(pixel) = definition.pixels.get_mut(*pixel) {
pixel.position_m =
[position[0] + delta.x, position[1] + delta.y, position[2]];
}
}
});
}
Drag::Band(band) => {
band.current = pointer;
let covered = covered_pixels(self.edit.value, origin, band.rect());
self.state.selection = band.selection(covered);
}
Drag::Scale(scale) => {
let (about, scale_x, scale_y) =
scale.factors(pointer, modifiers.alt, modifiers.shift);
let start = scale.start.clone();
self.edit.continue_change(|definition| {
for (pixel, position) in &start {
let offset = to_scene(origin, *position) - about;
let scaled = about + vec2(offset.x * scale_x, offset.y * scale_y);
let [x, y] = to_metres(origin, scaled);
if let Some(pixel) = definition.pixels.get_mut(*pixel) {
pixel.position_m = [x, y, position[2]];
}
}
});
}
Drag::Rotate(rotation) => {
let angle = rotation.angle(pointer, modifiers.shift);
let (centre, start) = (rotation.centre, rotation.start.clone());
self.edit.continue_change(|definition| {
let rotation = Rot2::from_angle(angle);
for (pixel, position, turned) in &start {
let turned_to = centre + rotation * (to_scene(origin, *position) - centre);
let [x, y] = to_metres(origin, turned_to);
if let Some(pixel) = definition.pixels.get_mut(*pixel) {
pixel.position_m = [x, y, position[2]];
pixel.rotation = turned + angle;
}
}
});
}
}
}
fn selection_handles(&mut self, ui: &mut Ui, pointer: Option<Pos2>) {
let origin = self.state.origin();
let margin = HANDLE_HIT / self.scene_transform.scaling;
let Some(live_box) = selection_bbox(
self.edit.value,
origin,
self.state.selection.iter().copied(),
)
.map(|bbox| bbox.expand(margin)) else {
return;
};
let modifiers = ui.input(|input| input.modifiers);
let shown_box = match (&self.state.drag, pointer) {
(Some(Drag::Scale(scale)), Some(pointer)) => {
let (about, scale_x, scale_y) =
scale.factors(pointer, modifiers.alt, modifiers.shift);
scaled_box(scale.start_box, about, scale_x, scale_y)
}
(Some(Drag::Rotate(rotation)), _) => rotation.start_box,
_ => live_box,
};
if self.state.selection.len() > 1
&& let Some(handle) =
box_handles(ui, &mut self.overlay, &self.scene_transform, shown_box)
&& let Some(pointer) = pointer
{
self.edit.begin();
self.state.drag = Some(Drag::Scale(BoxScale {
handle,
start_box: live_box,
start_pointer: pointer,
start: self
.selected_positions()
.map(|(pixel, position, _)| (pixel, position))
.collect(),
}));
}
let rotate = rotation_handle(ui, &mut self.overlay, &self.scene_transform, shown_box);
if rotate.drag_started_by(PointerButton::Primary)
&& let Some(pointer) = pointer
{
self.edit.begin();
self.state.drag = Some(Drag::Rotate(RotateDrag::new(
live_box,
pointer,
self.selected_positions().collect(),
)));
}
}
fn selected_positions(&self) -> impl Iterator<Item = (usize, [f32; 3], f32)> + '_ {
self.state.selection.iter().filter_map(|pixel| {
self.edit
.value
.pixels
.get(*pixel)
.map(|found| (*pixel, found.position_m, found.rotation))
})
}
fn start_drag(&mut self, response: &Response, press: Option<Pos2>, modifiers: Modifiers) {
if !response.drag_started_by(PointerButton::Primary) || self.state.drag.is_some() {
return;
}
let Some(press) = press else {
return;
};
let origin = self.state.origin();
match pixel_under(self.edit.value, origin, press, self.min_half()) {
Some(pixel) => {
if !self.state.selection.contains(&pixel) {
if !(modifiers.shift || modifiers.command) {
self.state.selection.clear();
}
self.state.selection.insert(pixel);
}
self.edit.begin();
self.state.drag = Some(Drag::Move {
pointer_start: press,
start: self
.selected_positions()
.map(|(pixel, position, _)| (pixel, position))
.collect(),
});
}
None => {
let band = Band::start(press, &mut self.state.selection, modifiers);
self.state.drag = Some(Drag::Band(band));
}
}
}
fn click(&mut self, response: &Response, press: Option<Pos2>, modifiers: Modifiers) {
let Some(press) = press else {
return;
};
let origin = self.state.origin();
let picked = pixel_under(self.edit.value, origin, press, self.min_half());
if response.double_clicked() && picked.is_none() {
generators::start(
GeneratorKind::Pixel,
self.edit,
self.state,
to_metres(origin, press),
);
return;
}
if !response.clicked_by(PointerButton::Primary) {
return;
}
self.state.message = None;
ClickIntent::from_modifiers(modifiers).apply(&mut self.state.selection, picked);
}
}
fn button_modifiers(input: &egui::InputState) -> Modifiers {
input
.events
.iter()
.rev()
.find_map(|event| match event {
egui::Event::PointerButton { modifiers, .. } => Some(*modifiers),
_ => None,
})
.unwrap_or(input.modifiers)
}