pub mod grid;
use crate::NodeSockets;
use std::collections::BTreeMap;
pub struct SocketLayout {
flow: egui::Direction,
inputs: SocketPositions,
outputs: SocketPositions,
}
enum SocketPositions {
EvenlySpaced(usize),
Explicit(BTreeMap<usize, f32>),
}
impl SocketLayout {
pub fn evenly_spaced(flow: egui::Direction, inputs: usize, outputs: usize) -> Self {
Self {
flow,
inputs: SocketPositions::EvenlySpaced(inputs),
outputs: SocketPositions::EvenlySpaced(outputs),
}
}
pub fn input_at(&mut self, ix: usize, cross: f32) {
self.inputs.set_explicit(ix, cross);
}
pub fn output_at(&mut self, ix: usize, cross: f32) {
self.outputs.set_explicit(ix, cross);
}
pub fn input(&mut self, ix: usize, rect: egui::Rect) {
self.input_at(ix, cross_axis_center(self.flow, rect));
}
pub fn output(&mut self, ix: usize, rect: egui::Rect) {
self.output_at(ix, cross_axis_center(self.flow, rect));
}
fn aligned<R>(
&mut self,
ui: &mut egui::Ui,
input: Option<usize>,
output: Option<usize>,
content: impl FnOnce(&mut egui::Ui) -> R,
) -> egui::InnerResponse<R> {
let ir = ui.scope(content);
if let Some(ix) = input {
self.input(ix, ir.response.rect);
}
if let Some(ix) = output {
self.output(ix, ir.response.rect);
}
ir
}
pub fn row<R>(
&mut self,
ui: &mut egui::Ui,
input: Option<usize>,
output: Option<usize>,
content: impl FnOnce(&mut egui::Ui) -> R,
) -> egui::InnerResponse<R> {
self.aligned(ui, input, output, content)
}
pub fn col<R>(
&mut self,
ui: &mut egui::Ui,
input: Option<usize>,
output: Option<usize>,
content: impl FnOnce(&mut egui::Ui) -> R,
) -> egui::InnerResponse<R> {
self.aligned(ui, input, output, content)
}
pub(crate) fn resolve(
self,
flow: egui::Direction,
rect: egui::Rect,
socket_padding: f32,
) -> NodeSockets {
let inputs = resolve_positions(&self.inputs, flow, rect, socket_padding, true);
let outputs = resolve_positions(&self.outputs, flow, rect, socket_padding, false);
NodeSockets {
flow,
inputs,
outputs,
}
}
}
impl SocketPositions {
fn set_explicit(&mut self, ix: usize, cross: f32) {
match self {
SocketPositions::EvenlySpaced(_) => {
let mut map = BTreeMap::new();
map.insert(ix, cross);
*self = SocketPositions::Explicit(map);
}
SocketPositions::Explicit(map) => {
map.insert(ix, cross);
}
}
}
}
fn cross_axis_center(flow: egui::Direction, rect: egui::Rect) -> f32 {
match flow {
egui::Direction::LeftToRight | egui::Direction::RightToLeft => rect.center().y,
egui::Direction::TopDown | egui::Direction::BottomUp => rect.center().x,
}
}
fn resolve_positions(
positions: &SocketPositions,
flow: egui::Direction,
rect: egui::Rect,
socket_padding: f32,
is_input: bool,
) -> BTreeMap<usize, egui::Pos2> {
match positions {
SocketPositions::EvenlySpaced(count) => {
resolve_evenly_spaced(*count, flow, rect, socket_padding, is_input)
}
SocketPositions::Explicit(map) => resolve_explicit(map, flow, rect, is_input),
}
}
fn resolve_evenly_spaced(
count: usize,
flow: egui::Direction,
rect: egui::Rect,
socket_padding: f32,
is_input: bool,
) -> BTreeMap<usize, egui::Pos2> {
let mut result = BTreeMap::new();
if count == 0 {
return result;
}
let gap = |len: f32| {
if count > 1 {
len / (count - 1) as f32
} else {
0.0
}
};
let (start, step) = match flow {
egui::Direction::LeftToRight => {
let len = rect.height() - socket_padding * 2.0;
let main_x = if is_input { rect.min.x } else { rect.max.x };
let start = egui::Pos2::new(main_x, rect.min.y + socket_padding);
let step = egui::Vec2::new(0.0, gap(len));
(start, step)
}
egui::Direction::RightToLeft => {
let len = rect.height() - socket_padding * 2.0;
let main_x = if is_input { rect.max.x } else { rect.min.x };
let start = egui::Pos2::new(main_x, rect.min.y + socket_padding);
let step = egui::Vec2::new(0.0, gap(len));
(start, step)
}
egui::Direction::TopDown => {
let len = rect.width() - socket_padding * 2.0;
let main_y = if is_input { rect.min.y } else { rect.max.y };
let start = egui::Pos2::new(rect.min.x + socket_padding, main_y);
let step = egui::Vec2::new(gap(len), 0.0);
(start, step)
}
egui::Direction::BottomUp => {
let len = rect.width() - socket_padding * 2.0;
let main_y = if is_input { rect.max.y } else { rect.min.y };
let start = egui::Pos2::new(rect.min.x + socket_padding, main_y);
let step = egui::Vec2::new(gap(len), 0.0);
(start, step)
}
};
for ix in 0..count {
result.insert(ix, start + step * ix as f32);
}
result
}
fn resolve_explicit(
map: &BTreeMap<usize, f32>,
flow: egui::Direction,
rect: egui::Rect,
is_input: bool,
) -> BTreeMap<usize, egui::Pos2> {
map.iter()
.map(|(&ix, &cross)| {
let pos = match flow {
egui::Direction::LeftToRight => {
let main_x = if is_input { rect.min.x } else { rect.max.x };
egui::Pos2::new(main_x, cross)
}
egui::Direction::RightToLeft => {
let main_x = if is_input { rect.max.x } else { rect.min.x };
egui::Pos2::new(main_x, cross)
}
egui::Direction::TopDown => {
let main_y = if is_input { rect.min.y } else { rect.max.y };
egui::Pos2::new(cross, main_y)
}
egui::Direction::BottomUp => {
let main_y = if is_input { rect.max.y } else { rect.min.y };
egui::Pos2::new(cross, main_y)
}
};
(ix, pos)
})
.collect()
}