pub mod layout;
use crate::node::NodeId;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum SocketKind {
Input,
Output,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Socket {
pub node: NodeId,
pub kind: SocketKind,
pub index: usize,
}
#[derive(Clone, Copy, Debug)]
pub struct PositionedSocket {
pub socket: Socket,
pub pos: egui::Pos2,
pub normal: egui::Vec2,
}
pub struct SocketResponses {
inputs: std::collections::BTreeMap<usize, egui::Response>,
outputs: std::collections::BTreeMap<usize, egui::Response>,
}
impl SocketResponses {
pub fn input(&self, ix: usize) -> Option<&egui::Response> {
self.inputs.get(&ix)
}
pub fn output(&self, ix: usize) -> Option<&egui::Response> {
self.outputs.get(&ix)
}
pub fn inputs(&self) -> impl Iterator<Item = (usize, &egui::Response)> {
self.inputs.iter().map(|(&ix, r)| (ix, r))
}
pub fn outputs(&self) -> impl Iterator<Item = (usize, &egui::Response)> {
self.outputs.iter().map(|(&ix, r)| (ix, r))
}
}
fn semicircle_segments(radius: f32) -> usize {
if radius <= 2.0 {
4
} else if radius <= 5.0 {
8
} else if radius < 18.0 {
16
} else if radius < 50.0 {
32
} else {
64
}
}
fn paint_semicircle(
painter: &egui::Painter,
center: egui::Pos2,
radius: f32,
normal: egui::Vec2,
color: egui::Color32,
) {
let segments = semicircle_segments(radius);
let perp = egui::Vec2::new(-normal.y, normal.x);
let mut pts = Vec::with_capacity(segments + 1);
for i in 0..=segments {
let angle = std::f32::consts::PI * (i as f32) / (segments as f32);
let (sin, cos) = angle.sin_cos();
pts.push(center + perp * (radius * cos) + normal * (radius * sin));
}
painter.add(egui::Shape::convex_polygon(pts, color, egui::Stroke::NONE));
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn show(
ui: &mut egui::Ui,
graph_id: egui::Id,
node_id: NodeId,
egui_id: egui::Id,
socket_layer: egui::LayerId,
frame_rect: egui::Rect,
node_sockets: &crate::NodeSockets,
socket_color: egui::Color32,
socket_radius: f32,
) -> SocketResponses {
let (pressed_socket, closest_socket) = if !node_sockets.inputs.is_empty()
|| !node_sockets.outputs.is_empty()
{
let gmem_arc = crate::memory(ui, graph_id);
let mut gmem = gmem_arc.lock().expect("failed to lock graph temp memory");
gmem.sockets.insert(node_id, node_sockets.clone());
let pressed_socket = gmem
.pressed
.as_ref()
.and_then(|pressed| match pressed.action {
crate::PressAction::Socket(socket) if socket.node == node_id => {
Some((socket.kind, socket.index))
}
_ => None,
});
let closest_socket = match gmem.closest_socket {
Some(closest) if closest.node == node_id => {
match gmem.pressed.as_ref().map(|p| &p.action) {
Some(crate::PressAction::Socket(socket)) if closest.kind == socket.kind => None,
_ => Some((closest.kind, closest.index)),
}
}
_ => None,
};
(pressed_socket, closest_socket)
} else {
(None, None)
};
let hl_size = (socket_radius + 4.0).max(4.0);
let interact_diameter = ui
.spacing()
.interact_size
.x
.min(ui.spacing().interact_size.y);
let paint_highlight = |kind, ix| {
if let Some((k, i)) = pressed_socket {
if k == kind && i == ix {
return true;
}
}
if let Some((k, i)) = closest_socket {
if k == kind && i == ix {
return true;
}
}
false
};
let builder = egui::UiBuilder::new()
.max_rect(frame_rect.expand(hl_size))
.layer_id(socket_layer);
let mut input_responses = std::collections::BTreeMap::new();
let mut output_responses = std::collections::BTreeMap::new();
ui.scope_builder(builder, |ui| {
let painter = ui.painter();
for (ix, pos, normal) in node_sockets.inputs() {
if paint_highlight(SocketKind::Input, ix) {
paint_semicircle(
painter,
pos,
hl_size,
normal,
socket_color.linear_multiply(0.25),
);
}
paint_semicircle(painter, pos, socket_radius, normal, socket_color);
let id = egui_id.with("in").with(ix);
let rect = egui::Rect::from_center_size(pos, egui::Vec2::splat(interact_diameter));
input_responses.insert(ix, ui.interact(rect, id, egui::Sense::hover()));
}
for (ix, pos, normal) in node_sockets.outputs() {
if paint_highlight(SocketKind::Output, ix) {
paint_semicircle(
painter,
pos,
hl_size,
normal,
socket_color.linear_multiply(0.25),
);
}
paint_semicircle(painter, pos, socket_radius, normal, socket_color);
let id = egui_id.with("out").with(ix);
let rect = egui::Rect::from_center_size(pos, egui::Vec2::splat(interact_diameter));
output_responses.insert(ix, ui.interact(rect, id, egui::Sense::hover()));
}
});
SocketResponses {
inputs: input_responses,
outputs: output_responses,
}
}