use crate::{bezier, EdgesCtx, NodeId};
use std::ops;
pub struct Edge<'a> {
edge: ((NodeId, OutputIx), (NodeId, InputIx)),
distance_per_point: f32,
curvature: f32,
selected: &'a mut bool,
}
pub struct EdgeResponse {
response: egui::Response,
changed: bool,
deleted: bool,
closest_point: egui::Pos2,
}
pub type SocketIx = usize;
pub type InputIx = SocketIx;
pub type OutputIx = SocketIx;
impl<'a> Edge<'a> {
pub const DEFAULT_DISTANCE_PER_POINT: f32 = 5.0;
pub fn new(a: (NodeId, OutputIx), b: (NodeId, InputIx), selected: &'a mut bool) -> Self {
Self {
edge: (a, b),
distance_per_point: Self::DEFAULT_DISTANCE_PER_POINT,
curvature: bezier::Cubic::DEFAULT_CURVATURE,
selected,
}
}
pub fn distance_per_point(mut self, dist: f32) -> Self {
self.distance_per_point = dist;
self
}
pub fn curvature_factor(mut self, curvature: f32) -> Self {
self.curvature = curvature;
self
}
pub fn show(self, ectx: &mut EdgesCtx, ui: &mut egui::Ui) -> EdgeResponse {
let Self {
edge: ((a, output), (b, input)),
distance_per_point,
curvature,
selected,
} = self;
let (a_out, b_in) = match (ectx.output(ui, a, output), ectx.input(ui, b, input)) {
(Some(a_out), Some(b_in)) => (a_out, b_in),
_ => {
let edge_id = ui.id().with(("edge", a, output, b, input));
let response = ui.interact(egui::Rect::NOTHING, edge_id, egui::Sense::click());
return EdgeResponse {
response,
changed: false,
deleted: false,
closest_point: egui::Pos2::ZERO,
};
}
};
let bezier = bezier::Cubic::from_edge_points(a_out, b_in, curvature);
let ui_response = ui.response();
let mouse_pos = ui_response
.interact_pointer_pos()
.or(ui_response.hover_pos())
.unwrap_or_default();
let closest_point = bezier.closest_point(distance_per_point, mouse_pos);
let select_dist = ui.style().interaction.interact_radius;
let edge_id = ui.id().with(("edge", a, output, b, input));
let interact_rect = egui::Rect::from_center_size(
closest_point,
egui::vec2(select_dist * 2.0, select_dist * 2.0),
);
let response = ui.interact(interact_rect, edge_id, egui::Sense::click());
let edge_in_progress = ectx.in_progress(ui).is_some();
let can_interact = !edge_in_progress && ectx.closest_socket.is_none();
let clicked = can_interact && response.clicked();
let under_selection_rect = ectx
.selection_rect
.map(|rect| bezier.intersects_rect(distance_per_point, rect))
.unwrap_or(false);
let old_selected = *selected;
if *selected {
if edge_in_progress
|| (clicked && ui.input(|i| i.modifiers.ctrl))
|| ui.input(|i| i.pointer.primary_pressed() && !i.modifiers.ctrl)
{
*selected = false;
}
} else if clicked
|| (under_selection_rect
&& ui.input(|i| i.modifiers.shift && i.pointer.primary_released()))
{
*selected = true;
}
let mut deleted = false;
if !ectx.immutable && *selected && !ui.ctx().egui_wants_keyboard_input() {
let del_keys = [egui::Key::Delete, egui::Key::Backspace];
if ui.input(|i| del_keys.iter().any(|&k| i.key_pressed(k))) {
deleted = true;
}
}
let show_hover = can_interact
&& response.hovered()
&& ui.input(|i| !i.pointer.primary_down() || i.pointer.could_any_button_be_click());
let pts: Vec<_> = bezier.flatten(distance_per_point).collect();
let stroke = if *selected {
ui.style().visuals.selection.stroke
} else if show_hover || (under_selection_rect && ui.input(|i| i.modifiers.shift)) {
ui.style().visuals.widgets.hovered.fg_stroke
} else {
ui.style().visuals.widgets.noninteractive.fg_stroke
};
ui.painter().add(egui::Shape::line(pts, stroke));
let changed = old_selected != *selected;
EdgeResponse {
response,
changed,
deleted,
closest_point,
}
}
}
impl EdgeResponse {
pub fn changed(&self) -> bool {
self.changed
}
pub fn deleted(&self) -> bool {
self.deleted
}
pub fn closest_point(&self) -> egui::Pos2 {
self.closest_point
}
}
impl ops::Deref for EdgeResponse {
type Target = egui::Response;
fn deref(&self) -> &Self::Target {
&self.response
}
}
impl From<EdgeResponse> for egui::Response {
fn from(response: EdgeResponse) -> Self {
response.response
}
}