use crate::canvas::frame::CanvasFrame;
use crate::canvas::scrollbars::panning;
use crate::canvas::selection::ClickIntent;
use crate::canvas::shapes::{Shapes, draw_edge};
use crate::fixture_editor::canvas::{Definitions, continue_drag, definition_of, draw_layered_grid};
use crate::fixture_editor::chains::{draw_order, fixture_colors};
use crate::fixture_editor::commands::EditorTransaction::{MakeLeaf, MakeSuccessor};
use crate::fixture_editor::commands::{EditorTransaction, apply_transaction};
use crate::fixture_editor::drag::{Drag, EndDrag};
use crate::fixture_editor::fixture_ui::FixtureUI;
use crate::fixture_editor::selection_handles::{
box_selection, draw_bounding_box, draw_rotation_handle, selected_positions,
};
use crate::fixture_editor::state::{CanvasMode, EditorState, PatchEdit};
use egui::epaint::PathShape;
use egui::{Color32, DragAndDrop, PointerButton, Pos2, Shape, Stroke};
use gled_model::collection::Collections;
use gled_model::patching::Patch;
use gled_model::patching::definition::Scaling;
use gled_model::patching::fixture::FixtureId;
use gled_model::patching::layout::LayoutRef;
use std::collections::{BTreeMap, HashSet};
#[derive(Clone)]
pub(crate) struct FloatingConnection {
fixture: FixtureId,
end: Pos2,
}
pub(super) fn draw_fixtures(
canvas: &mut CanvasFrame<'_>,
edit: &mut PatchEdit<'_>,
state: &mut EditorState,
collections: &Collections,
mode: CanvasMode,
drawn: LayoutRef,
active: bool,
) {
let scene_transform = canvas.scene_transform;
let node_painter = &canvas.painter;
let canvas_response = &canvas.response;
let child_ui = &mut *canvas.ui;
let under_everything = node_painter.add(Shape::Noop);
let mut edge_painter = Shapes::default();
let mut overlay_painter = Shapes::default();
draw_layered_grid(&mut edge_painter, &scene_transform);
let modifiers = child_ui.input(|i| i.modifiers);
let cmd_down = modifiers.command;
let single_selected = state.selected_fixtures.len() == 1;
let chains = draw_order(edit.value, mode, drawn);
let definitions: Definitions = chains
.iter()
.flatten()
.filter_map(|id| {
edit.value
.definition(*id, collections)
.map(|asset| (*id, asset))
})
.collect();
let to_scene = scene_transform.inverse();
let (pointer, press, primary_down) = child_ui.input(|i| {
(
i.pointer.interact_pos().map(|at| to_scene * at),
i.pointer.press_origin().map(|at| to_scene * at),
i.pointer.primary_down(),
)
});
if active
&& !panning(child_ui)
&& let Some(pointer) = pointer
{
continue_drag(
edit,
state,
drawn,
&definitions,
pointer,
modifiers,
scene_transform.scaling,
);
}
if !primary_down
&& let Some(drag) = state.drag.take()
&& drag.edits_patch()
{
edit.end();
}
let dragging = state.drag.is_some();
let patch: &Patch = edit.value;
let colors = fixture_colors(patch, state, &chains, mode, collections);
let successors: BTreeMap<FixtureId, bool> = chains
.iter()
.flatten()
.map(|id| (*id, patch.topology.successor(*id).is_some()))
.collect();
let heads: HashSet<FixtureId> = patch.topology.chain_heads().into_iter().collect();
let ports = &patch.topology.ports;
let mut deferred_operations: Vec<EditorTransaction> = vec![];
let mut deferred_selection: Option<FixtureId> = None;
let mut deferred_port: Option<FixtureId> = None;
let mut fixture_took_focus = false;
let mut dnd_released_onto_fixture = false;
for chain in &chains {
let mut prev_terminal: Option<Pos2> = None;
for id in chain {
let id = *id;
let Some(position) = patch
.layout(drawn)
.and_then(|layout| layout.members.get(&id).copied())
else {
continue;
};
let is_selected = state.selected_fixtures.contains(&id);
let attach_network = heads.contains(&id);
let color = colors.get(&id).copied().unwrap_or(Color32::LIGHT_RED);
let end_handles = definition_of(&definitions, id).scaling == Scaling::AlongAxis;
let mut pointer_was_down = state.pointer_down_on.contains(&id);
let mut fixture_ui = FixtureUI::new(
id,
&position,
definition_of(&definitions, id),
definitions.get(&id).map(|asset| asset.id.id),
color,
is_selected,
attach_network,
scene_transform,
)
.with_port(ports.get(&id).copied(), state.selected_port == Some(id));
{
if let Some(prev_terminal) = &prev_terminal {
draw_edge(
&mut edge_painter,
prev_terminal,
&fixture_ui.input_terminal(),
);
}
prev_terminal = Some(fixture_ui.output_terminal());
}
if mode.edits_network() && active {
let next_connected = successors.get(&id).copied().unwrap_or(false);
let output_port_response =
fixture_ui.node_port(child_ui, node_painter, next_connected);
if output_port_response.dragged() && !panning(child_ui) {
DragAndDrop::set_payload(
child_ui.ctx(),
FloatingConnection {
fixture: id,
end: fixture_ui.output_terminal(),
},
);
}
}
let node_response = fixture_ui.show(
child_ui,
node_painter,
&mut overlay_painter,
&mut edge_painter,
canvas_response.clone(),
single_selected,
&mut pointer_was_down,
end_handles && active,
&mut state.caches,
);
if pointer_was_down {
state.pointer_down_on.insert(id);
} else {
state.pointer_down_on.remove(&id);
}
if let Some(end) = node_response.end_drag
&& let Some(press) = press
{
state.drag = Some(Drag::End(EndDrag::new(
id,
definition_of(&definitions, id),
position,
end,
press,
)));
}
if let Some(dnd_response) = &node_response.released_payload {
if !patch.topology.is_reachable(id, dnd_response.fixture) {
deferred_operations.push(MakeLeaf(dnd_response.fixture));
deferred_operations.push(MakeSuccessor(dnd_response.fixture, id));
dnd_released_onto_fixture = true;
}
}
if node_response.clicked && !(cmd_down && node_response.dragged) {
fixture_took_focus = true;
deferred_selection = Some(id);
}
if node_response.network_clicked {
fixture_took_focus = true;
deferred_port = Some(id);
}
if node_response.has_unhandled_pointer_down {
fixture_took_focus = true;
}
if !node_response.dragged
&& let Some(hover_rect) = node_response.hovered
&& !is_selected
{
overlay_painter.add(PathShape::convex_polygon(
hover_rect.corners().to_vec(),
Color32::TRANSPARENT,
Stroke::new(2.0, Color32::from_gray(180)),
));
}
if let Some((hover_rect, other)) = node_response.hover_payload.clone()
&& other.fixture != id
{
let color = if patch.topology.is_reachable(id, other.fixture) {
Color32::RED
} else {
Color32::CYAN
};
overlay_painter.add(PathShape::convex_polygon(
hover_rect.corners().to_vec(),
Color32::TRANSPARENT,
Stroke::new(5.0, color),
));
}
if !panning(child_ui)
&& node_response.dragged
&& canvas_response.drag_started_by(PointerButton::Primary)
{
if !is_selected {
state.select_single(id);
}
if let Some(press) = press {
state.drag = Some(Drag::Move {
start_pointer: press,
start: selected_positions(patch, state, drawn),
});
}
fixture_took_focus = true;
}
}
}
if active && !panning(child_ui) {
box_selection(
state,
canvas_response,
&mut overlay_painter,
&scene_transform,
press.or(pointer),
modifiers,
fixture_took_focus,
);
}
let boxed_selection = state.selected_fixtures.len() > 1
|| state
.selected_fixtures
.iter()
.any(|id| definition_of(&definitions, *id).scaling == Scaling::Free);
if active && !state.selected_fixtures.is_empty() && boxed_selection {
draw_bounding_box(
child_ui,
patch,
state,
drawn,
&mut overlay_painter,
&scene_transform,
&definitions,
pointer,
modifiers,
);
}
if active && !state.selected_fixtures.is_empty() {
draw_rotation_handle(
child_ui,
patch,
state,
drawn,
&mut overlay_painter,
&scene_transform,
&definitions,
pointer,
);
}
if let Some(released) = canvas_response.dnd_release_payload::<FloatingConnection>()
&& !dnd_released_onto_fixture
{
deferred_operations.push(MakeLeaf(released.fixture));
}
if !dragging && state.drag.as_ref().is_some_and(Drag::edits_patch) {
edit.begin();
if let Some(pointer) = pointer {
continue_drag(
edit,
state,
drawn,
&definitions,
pointer,
modifiers,
scene_transform.scaling,
);
}
}
if !deferred_operations.is_empty() {
edit.apply(|patch| {
for operation in deferred_operations {
apply_transaction(patch, state, collections, operation);
}
});
}
let intent = ClickIntent::from_modifiers(modifiers);
if let Some(id) = deferred_selection {
state.click(intent, Some(id));
}
if let Some(id) = deferred_port {
state.select_port(id);
}
if !fixture_took_focus && canvas_response.clicked() {
state.click(intent, None);
}
let floating = DragAndDrop::payload::<FloatingConnection>(child_ui.ctx());
if let (Some(fc), Some(hoverpos)) = (floating, child_ui.ctx().pointer_hover_pos()) {
draw_edge(&mut edge_painter, &fc.end, &hoverpos);
}
node_painter.set(under_everything, edge_painter.into_shape());
node_painter.add(overlay_painter.into_shape());
}