Initial attempt to implement Render Graph

This commit is contained in:
Tony Klink 2024-04-06 22:31:24 -06:00
parent 173cfe2acf
commit 3732fc5e3d
Signed by: klink
GPG key ID: 85175567C4D19231
33 changed files with 8851 additions and 938 deletions

29
vendor/egui-snarl/Cargo.toml vendored Normal file
View file

@ -0,0 +1,29 @@
[package]
name = "egui-snarl"
version = "0.2.1"
edition = "2021"
description = "Node-graphs for egui"
license = "MIT OR Apache-2.0"
documentation = "https://docs.rs/egui-snarl"
keywords = ["egui", "node", "graph", "ui", "node-graph"]
categories = ["gui", "visualization"]
[features]
serde = ["dep:serde", "egui/serde", "slab/serde"]
[dependencies]
egui = { version = "0.27" }
slab = { version = "0.4" }
serde = { version = "1.0", features = ["derive"], optional = true }
tiny-fn = { version = "0.1.6" }
egui-probe = { version = "0.2", features = ["derive"], optional = true }
[dev-dependencies]
eframe = { version = "0.26", features = ["serde", "persistence"] }
egui_extras = { version = "0.26", features = ["all_loaders"] }
syn = { version = "2.0", features = ["extra-traits"] }
serde_json = { version = "1.0" }
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-futures = "0.4"

696
vendor/egui-snarl/src/lib.rs vendored Normal file
View file

@ -0,0 +1,696 @@
//!
//! # egui-snarl
//!
//! Provides a node-graph container for egui.
//!
//!
#![deny(missing_docs)]
#![deny(clippy::correctness, clippy::complexity, clippy::perf, clippy::style)]
// #![warn(clippy::pedantic)]
#![allow(clippy::inline_always)]
pub mod ui;
use std::ops::{Index, IndexMut};
use egui::{ahash::HashSet, Pos2};
use slab::Slab;
impl<T> Default for Snarl<T> {
fn default() -> Self {
Snarl::new()
}
}
/// Node identifier.
///
/// This is newtype wrapper around [`usize`] that implements
/// necessary traits, but omits arithmetic operations.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(transparent)
)]
pub struct NodeId(pub usize);
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct Node<T> {
/// Node generic value.
value: T,
/// Position of the top-left corner of the node.
/// This does not include frame margin.
pos: egui::Pos2,
/// Flag indicating that the node is open - not collapsed.
open: bool,
}
/// Output pin identifier.
/// Cosists of node id and pin index.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OutPinId {
/// Node id.
pub node: NodeId,
/// Output pin index.
pub output: usize,
}
/// Input pin identifier. Cosists of node id and pin index.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct InPinId {
/// Node id.
pub node: NodeId,
/// Input pin index.
pub input: usize,
}
/// Connection between two nodes.
///
/// Nodes may support multiple connections to the same input or output.
/// But duplicate connections between same input and the same output are not allowed.
/// Attempt to insert existing connection will be ignored.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct Wire {
out_pin: OutPinId,
in_pin: InPinId,
}
#[derive(Clone, Debug)]
struct Wires {
wires: HashSet<Wire>,
}
#[cfg(feature = "serde")]
impl serde::Serialize for Wires {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeSeq;
let mut seq = serializer.serialize_seq(Some(self.wires.len()))?;
for wire in &self.wires {
seq.serialize_element(&wire)?;
}
seq.end()
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Wires {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = HashSet<Wire>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a sequence of wires")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut wires = HashSet::with_hasher(egui::ahash::RandomState::new());
while let Some(wire) = seq.next_element()? {
wires.insert(wire);
}
Ok(wires)
}
}
let wires = deserializer.deserialize_seq(Visitor)?;
Ok(Wires { wires })
}
}
impl Wires {
fn new() -> Self {
Wires {
wires: HashSet::with_hasher(egui::ahash::RandomState::new()),
}
}
fn insert(&mut self, wire: Wire) -> bool {
self.wires.insert(wire)
}
fn remove(&mut self, wire: &Wire) -> bool {
self.wires.remove(wire)
}
fn drop_node(&mut self, node: NodeId) -> usize {
let count = self.wires.len();
self.wires
.retain(|wire| wire.out_pin.node != node && wire.in_pin.node != node);
count - self.wires.len()
}
fn drop_inputs(&mut self, pin: InPinId) -> usize {
let count = self.wires.len();
self.wires.retain(|wire| wire.in_pin != pin);
count - self.wires.len()
}
fn drop_outputs(&mut self, pin: OutPinId) -> usize {
let count = self.wires.len();
self.wires.retain(|wire| wire.out_pin != pin);
count - self.wires.len()
}
fn wired_inputs(&self, out_pin: OutPinId) -> impl Iterator<Item = InPinId> + '_ {
self.wires
.iter()
.filter(move |wire| wire.out_pin == out_pin)
.map(|wire| (wire.in_pin))
}
fn wired_outputs(&self, in_pin: InPinId) -> impl Iterator<Item = OutPinId> + '_ {
self.wires
.iter()
.filter(move |wire| wire.in_pin == in_pin)
.map(|wire| (wire.out_pin))
}
fn iter(&self) -> impl Iterator<Item = Wire> + '_ {
self.wires.iter().copied()
}
}
/// Snarl is generic node-graph container.
///
/// It holds graph state - positioned nodes and wires between their pins.
/// It can be rendered using [`Snarl::show`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Snarl<T> {
// #[cfg_attr(feature = "serde", serde(with = "serde_nodes"))]
nodes: Slab<Node<T>>,
draw_order: Vec<NodeId>,
wires: Wires,
}
impl<T> Snarl<T> {
/// Create a new empty Snarl.
///
/// # Examples
///
/// ```
/// # use egui_snarl::Snarl;
/// let snarl = Snarl::<()>::new();
/// ```
#[must_use]
pub fn new() -> Self {
Snarl {
nodes: Slab::new(),
draw_order: Vec::new(),
wires: Wires::new(),
}
}
/// Adds a node to the Snarl.
/// Returns the index of the node.
///
/// # Examples
///
/// ```
/// # use egui_snarl::Snarl;
/// let mut snarl = Snarl::<()>::new();
/// snarl.insert_node(egui::pos2(0.0, 0.0), ());
/// ```
pub fn insert_node(&mut self, pos: egui::Pos2, node: T) -> NodeId {
let idx = self.nodes.insert(Node {
value: node,
pos,
open: true,
});
let id = NodeId(idx);
self.draw_order.push(id);
id
}
/// Adds a node to the Snarl in collapsed state.
/// Returns the index of the node.
///
/// # Examples
///
/// ```
/// # use egui_snarl::Snarl;
/// let mut snarl = Snarl::<()>::new();
/// snarl.insert_node_collapsed(egui::pos2(0.0, 0.0), ());
/// ```
pub fn insert_node_collapsed(&mut self, pos: egui::Pos2, node: T) -> NodeId {
let idx = self.nodes.insert(Node {
value: node,
pos,
open: false,
});
let id = NodeId(idx);
self.draw_order.push(id);
id
}
/// Opens or collapses a node.
///
/// # Panics
///
/// Panics if the node does not exist.
#[track_caller]
pub fn open_node(&mut self, node: NodeId, open: bool) {
self.nodes[node.0].open = open;
}
/// Removes a node from the Snarl.
/// Returns the node if it was removed.
///
/// # Panics
///
/// Panics if the node does not exist.
///
/// # Examples
///
/// ```
/// # use egui_snarl::Snarl;
/// let mut snarl = Snarl::<()>::new();
/// let node = snarl.insert_node(egui::pos2(0.0, 0.0), ());
/// snarl.remove_node(node);
/// ```
#[track_caller]
pub fn remove_node(&mut self, idx: NodeId) -> T {
let value = self.nodes.remove(idx.0).value;
self.wires.drop_node(idx);
let order = self.draw_order.iter().position(|&i| i == idx).unwrap();
self.draw_order.remove(order);
value
}
/// Connects two nodes.
/// Returns true if the connection was successful.
/// Returns false if the connection already exists.
///
/// # Panics
///
/// Panics if either node does not exist.
#[track_caller]
pub fn connect(&mut self, from: OutPinId, to: InPinId) -> bool {
assert!(self.nodes.contains(from.node.0));
assert!(self.nodes.contains(to.node.0));
let wire = Wire {
out_pin: from,
in_pin: to,
};
self.wires.insert(wire)
}
/// Disconnects two nodes.
/// Returns true if the connection was removed.
///
/// # Panics
///
/// Panics if either node does not exist.
#[track_caller]
pub fn disconnect(&mut self, from: OutPinId, to: InPinId) -> bool {
assert!(self.nodes.contains(from.node.0));
assert!(self.nodes.contains(to.node.0));
let wire = Wire {
out_pin: from,
in_pin: to,
};
self.wires.remove(&wire)
}
/// Removes all connections to the node's pin.
///
/// Returns number of removed connections.
///
/// # Panics
///
/// Panics if the node does not exist.
#[track_caller]
pub fn drop_inputs(&mut self, pin: InPinId) -> usize {
assert!(self.nodes.contains(pin.node.0));
self.wires.drop_inputs(pin)
}
/// Removes all connections from the node's pin.
/// Returns number of removed connections.
///
/// # Panics
///
/// Panics if the node does not exist.
#[track_caller]
pub fn drop_outputs(&mut self, pin: OutPinId) -> usize {
assert!(self.nodes.contains(pin.node.0));
self.wires.drop_outputs(pin)
}
/// Returns reference to the node.
#[must_use]
pub fn get_node(&self, idx: NodeId) -> Option<&T> {
match self.nodes.get(idx.0) {
Some(node) => Some(&node.value),
None => None,
}
}
/// Returns mutable reference to the node.
pub fn get_node_mut(&mut self, idx: NodeId) -> Option<&mut T> {
match self.nodes.get_mut(idx.0) {
Some(node) => Some(&mut node.value),
None => None,
}
}
/// Iterates over shared references to each node.
pub fn nodes(&self) -> NodesIter<'_, T> {
NodesIter {
nodes: self.nodes.iter(),
}
}
/// Iterates over mutable references to each node.
pub fn nodes_mut(&mut self) -> NodesIterMut<'_, T> {
NodesIterMut {
nodes: self.nodes.iter_mut(),
}
}
/// Iterates over shared references to each node and its position.
pub fn nodes_pos(&self) -> NodesPosIter<'_, T> {
NodesPosIter {
nodes: self.nodes.iter(),
}
}
/// Iterates over mutable references to each node and its position.
pub fn nodes_pos_mut(&mut self) -> NodesPosIterMut<'_, T> {
NodesPosIterMut {
nodes: self.nodes.iter_mut(),
}
}
/// Iterates over shared references to each node and its identifier.
pub fn node_ids(&self) -> NodesIdsIter<'_, T> {
NodesIdsIter {
nodes: self.nodes.iter(),
}
}
/// Iterates over mutable references to each node and its identifier.
pub fn nodes_ids_mut(&mut self) -> NodesIdsIterMut<'_, T> {
NodesIdsIterMut {
nodes: self.nodes.iter_mut(),
}
}
/// Iterates over shared references to each node, its position and its identifier.
pub fn nodes_pos_ids(&self) -> NodesPosIdsIter<'_, T> {
NodesPosIdsIter {
nodes: self.nodes.iter(),
}
}
/// Iterates over mutable references to each node, its position and its identifier.
pub fn nodes_pos_ids_mut(&mut self) -> NodesPosIdsIterMut<'_, T> {
NodesPosIdsIterMut {
nodes: self.nodes.iter_mut(),
}
}
/// Returns input pin of the node.
#[must_use]
pub fn in_pin(&self, pin: InPinId) -> InPin {
InPin::new(self, pin)
}
/// Returns output pin of the node.
#[must_use]
pub fn out_pin(&self, pin: OutPinId) -> OutPin {
OutPin::new(self, pin)
}
}
impl<T> Index<NodeId> for Snarl<T> {
type Output = T;
#[inline]
#[track_caller]
fn index(&self, idx: NodeId) -> &Self::Output {
&self.nodes[idx.0].value
}
}
impl<T> IndexMut<NodeId> for Snarl<T> {
#[inline]
#[track_caller]
fn index_mut(&mut self, idx: NodeId) -> &mut Self::Output {
&mut self.nodes[idx.0].value
}
}
/// Iterator over shared references to nodes.
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
pub struct NodesIter<'a, T> {
nodes: slab::Iter<'a, Node<T>>,
}
impl<'a, T> Iterator for NodesIter<'a, T> {
type Item = &'a T;
fn size_hint(&self) -> (usize, Option<usize>) {
self.nodes.size_hint()
}
fn next(&mut self) -> Option<&'a T> {
let (_, node) = self.nodes.next()?;
Some(&node.value)
}
fn nth(&mut self, n: usize) -> Option<&'a T> {
let (_, node) = self.nodes.nth(n)?;
Some(&node.value)
}
}
/// Iterator over mutable references to nodes.
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
pub struct NodesIterMut<'a, T> {
nodes: slab::IterMut<'a, Node<T>>,
}
impl<'a, T> Iterator for NodesIterMut<'a, T> {
type Item = &'a mut T;
fn size_hint(&self) -> (usize, Option<usize>) {
self.nodes.size_hint()
}
fn next(&mut self) -> Option<&'a mut T> {
let (_, node) = self.nodes.next()?;
Some(&mut node.value)
}
fn nth(&mut self, n: usize) -> Option<&'a mut T> {
let (_, node) = self.nodes.nth(n)?;
Some(&mut node.value)
}
}
/// Iterator over shared references to nodes and their positions.
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
pub struct NodesPosIter<'a, T> {
nodes: slab::Iter<'a, Node<T>>,
}
impl<'a, T> Iterator for NodesPosIter<'a, T> {
type Item = (Pos2, &'a T);
fn size_hint(&self) -> (usize, Option<usize>) {
self.nodes.size_hint()
}
fn next(&mut self) -> Option<(Pos2, &'a T)> {
let (_, node) = self.nodes.next()?;
Some((node.pos, &node.value))
}
fn nth(&mut self, n: usize) -> Option<(Pos2, &'a T)> {
let (_, node) = self.nodes.nth(n)?;
Some((node.pos, &node.value))
}
}
/// Iterator over mutable references to nodes and their positions.
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
pub struct NodesPosIterMut<'a, T> {
nodes: slab::IterMut<'a, Node<T>>,
}
impl<'a, T> Iterator for NodesPosIterMut<'a, T> {
type Item = (Pos2, &'a mut T);
fn size_hint(&self) -> (usize, Option<usize>) {
self.nodes.size_hint()
}
fn next(&mut self) -> Option<(Pos2, &'a mut T)> {
let (_, node) = self.nodes.next()?;
Some((node.pos, &mut node.value))
}
fn nth(&mut self, n: usize) -> Option<(Pos2, &'a mut T)> {
let (_, node) = self.nodes.nth(n)?;
Some((node.pos, &mut node.value))
}
}
/// Iterator over shared references to nodes and their identifiers.
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
pub struct NodesIdsIter<'a, T> {
nodes: slab::Iter<'a, Node<T>>,
}
impl<'a, T> Iterator for NodesIdsIter<'a, T> {
type Item = (NodeId, &'a T);
fn size_hint(&self) -> (usize, Option<usize>) {
self.nodes.size_hint()
}
fn next(&mut self) -> Option<(NodeId, &'a T)> {
let (idx, node) = self.nodes.next()?;
Some((NodeId(idx), &node.value))
}
fn nth(&mut self, n: usize) -> Option<(NodeId, &'a T)> {
let (idx, node) = self.nodes.nth(n)?;
Some((NodeId(idx), &node.value))
}
}
/// Iterator over mutable references to nodes and their identifiers.
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
pub struct NodesIdsIterMut<'a, T> {
nodes: slab::IterMut<'a, Node<T>>,
}
impl<'a, T> Iterator for NodesIdsIterMut<'a, T> {
type Item = (NodeId, &'a mut T);
fn size_hint(&self) -> (usize, Option<usize>) {
self.nodes.size_hint()
}
fn next(&mut self) -> Option<(NodeId, &'a mut T)> {
let (idx, node) = self.nodes.next()?;
Some((NodeId(idx), &mut node.value))
}
fn nth(&mut self, n: usize) -> Option<(NodeId, &'a mut T)> {
let (idx, node) = self.nodes.nth(n)?;
Some((NodeId(idx), &mut node.value))
}
}
/// Iterator over shared references to nodes, their positions and their identifiers.
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
pub struct NodesPosIdsIter<'a, T> {
nodes: slab::Iter<'a, Node<T>>,
}
impl<'a, T> Iterator for NodesPosIdsIter<'a, T> {
type Item = (NodeId, Pos2, &'a T);
fn size_hint(&self) -> (usize, Option<usize>) {
self.nodes.size_hint()
}
fn next(&mut self) -> Option<(NodeId, Pos2, &'a T)> {
let (idx, node) = self.nodes.next()?;
Some((NodeId(idx), node.pos, &node.value))
}
fn nth(&mut self, n: usize) -> Option<(NodeId, Pos2, &'a T)> {
let (idx, node) = self.nodes.nth(n)?;
Some((NodeId(idx), node.pos, &node.value))
}
}
/// Iterator over mutable references to nodes, their positions and their identifiers.
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
pub struct NodesPosIdsIterMut<'a, T> {
nodes: slab::IterMut<'a, Node<T>>,
}
impl<'a, T> Iterator for NodesPosIdsIterMut<'a, T> {
type Item = (NodeId, Pos2, &'a mut T);
fn size_hint(&self) -> (usize, Option<usize>) {
self.nodes.size_hint()
}
fn next(&mut self) -> Option<(NodeId, Pos2, &'a mut T)> {
let (idx, node) = self.nodes.next()?;
Some((NodeId(idx), node.pos, &mut node.value))
}
fn nth(&mut self, n: usize) -> Option<(NodeId, Pos2, &'a mut T)> {
let (idx, node) = self.nodes.nth(n)?;
Some((NodeId(idx), node.pos, &mut node.value))
}
}
/// Node and its output pin.
#[derive(Clone, Debug)]
pub struct OutPin {
/// Output pin identifier.
pub id: OutPinId,
/// List of input pins connected to this output pin.
pub remotes: Vec<InPinId>,
}
/// Node and its output pin.
#[derive(Clone, Debug)]
pub struct InPin {
/// Input pin identifier.
pub id: InPinId,
/// List of output pins connected to this input pin.
pub remotes: Vec<OutPinId>,
}
impl OutPin {
fn new<T>(snarl: &Snarl<T>, pin: OutPinId) -> Self {
OutPin {
id: pin,
remotes: snarl.wires.wired_inputs(pin).collect(),
}
}
}
impl InPin {
fn new<T>(snarl: &Snarl<T>, pin: InPinId) -> Self {
InPin {
id: pin,
remotes: snarl.wires.wired_outputs(pin).collect(),
}
}
}

1089
vendor/egui-snarl/src/ui.rs vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,259 @@
use std::fmt;
use egui::{emath::Rot2, vec2, Pos2, Rect, Stroke, Ui, Vec2};
use super::SnarlStyle;
/// Viewport is a rectangle in graph space that is visible on screen.
pub struct Viewport {
/// Screen-space rectangle.
pub rect: Rect,
/// Scale of the viewport.
pub scale: f32,
/// Offset of the viewport.
pub offset: Vec2,
}
impl Viewport {
/// Converts screen-space position to graph-space position.
#[inline(always)]
pub fn screen_pos_to_graph(&self, pos: Pos2) -> Pos2 {
(pos + self.offset - self.rect.center().to_vec2()) / self.scale
}
/// Converts graph-space position to screen-space position.
#[inline(always)]
pub fn graph_pos_to_screen(&self, pos: Pos2) -> Pos2 {
pos * self.scale - self.offset + self.rect.center().to_vec2()
}
/// Converts screen-space vector to graph-space vector.
#[inline(always)]
pub fn graph_vec_to_screen(&self, size: Vec2) -> Vec2 {
size * self.scale
}
/// Converts graph-space vector to screen-space vector.
#[inline(always)]
pub fn screen_vec_to_graph(&self, size: Vec2) -> Vec2 {
size / self.scale
}
/// Converts screen-space size to graph-space size.
#[inline(always)]
pub fn graph_size_to_screen(&self, size: f32) -> f32 {
size * self.scale
}
/// Converts graph-space size to screen-space size.
#[inline(always)]
pub fn screen_size_to_graph(&self, size: f32) -> f32 {
size / self.scale
}
}
///Grid background pattern.
///Use `SnarlStyle::background_pattern_stroke` for change stroke options
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
pub struct Grid {
/// Spacing between grid lines.
pub spacing: Vec2,
/// Angle of the grid.
#[cfg_attr(feature = "egui-probe", egui_probe(as egui_probe::angle))]
pub angle: f32,
}
const DEFAULT_GRID_SPACING: Vec2 = vec2(5.0, 5.0);
macro_rules! default_grid_spacing {
() => {
stringify!(vec2(5.0, 5.0))
};
}
const DEFAULT_GRID_ANGLE: f32 = 1.0;
macro_rules! default_grid_angle {
() => {
stringify!(1.0)
};
}
impl Default for Grid {
fn default() -> Self {
Self {
spacing: DEFAULT_GRID_SPACING,
angle: DEFAULT_GRID_ANGLE,
}
}
}
impl Grid {
/// Create new grid with given spacing and angle.
pub const fn new(spacing: Vec2, angle: f32) -> Self {
Self { spacing, angle }
}
fn draw(&self, style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui) {
let bg_stroke = style
.background_pattern_stroke
.unwrap_or_else(|| ui.visuals().widgets.noninteractive.bg_stroke);
let stroke = Stroke::new(
bg_stroke.width * viewport.scale.max(1.0),
bg_stroke.color.gamma_multiply(viewport.scale.min(1.0)),
);
let spacing =
ui.spacing().icon_width * vec2(self.spacing.x.max(1.0), self.spacing.y.max(1.0));
let rot = Rot2::from_angle(self.angle);
let rot_inv = rot.inverse();
let graph_viewport = Rect::from_min_max(
viewport.screen_pos_to_graph(viewport.rect.min),
viewport.screen_pos_to_graph(viewport.rect.max),
);
let pattern_bounds = graph_viewport.rotate_bb(rot_inv);
let min_x = (pattern_bounds.min.x / spacing.x).ceil();
let max_x = (pattern_bounds.max.x / spacing.x).floor();
for x in 0..=(max_x - min_x) as i64 {
#[allow(clippy::cast_possible_truncation)]
let x = (x as f32 + min_x) * spacing.x;
let top = (rot * vec2(x, pattern_bounds.min.y)).to_pos2();
let bottom = (rot * vec2(x, pattern_bounds.max.y)).to_pos2();
let top = viewport.graph_pos_to_screen(top);
let bottom = viewport.graph_pos_to_screen(bottom);
ui.painter().line_segment([top, bottom], stroke);
}
let min_y = (pattern_bounds.min.y / spacing.y).ceil();
let max_y = (pattern_bounds.max.y / spacing.y).floor();
for y in 0..=(max_y - min_y) as i64 {
#[allow(clippy::cast_possible_truncation)]
let y = (y as f32 + min_y) * spacing.y;
let top = (rot * vec2(pattern_bounds.min.x, y)).to_pos2();
let bottom = (rot * vec2(pattern_bounds.max.x, y)).to_pos2();
let top = viewport.graph_pos_to_screen(top);
let bottom = viewport.graph_pos_to_screen(bottom);
ui.painter().line_segment([top, bottom], stroke);
}
}
}
tiny_fn::tiny_fn! {
/// Custom background pattern function with signature
/// `Fn(style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui)`
pub struct CustomBackground = Fn(style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui);
}
impl<const INLINE_SIZE: usize> Default for CustomBackground<'_, INLINE_SIZE> {
fn default() -> Self {
Self::new(|_, _, _| {})
}
}
#[cfg(feature = "egui-probe")]
impl<const INLINE_SIZE: usize> egui_probe::EguiProbe for CustomBackground<'_, INLINE_SIZE> {
fn probe(
&mut self,
ui: &mut egui_probe::egui::Ui,
_style: &egui_probe::Style,
) -> egui_probe::egui::Response {
ui.weak("Custom")
}
}
/// Background pattern show beneath nodes and wires.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
pub enum BackgroundPattern {
/// No pattern.
NoPattern,
/// Linear grid.
#[cfg_attr(feature = "egui-probe", egui_probe(transparent))]
Grid(Grid),
/// Custom pattern.
/// Contains function with signature
/// `Fn(style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui)`
#[cfg_attr(feature = "egui-probe", egui_probe(transparent))]
Custom(#[cfg_attr(feature = "serde", serde(skip))] CustomBackground<'static>),
}
impl PartialEq for BackgroundPattern {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(BackgroundPattern::Grid(l), BackgroundPattern::Grid(r)) => *l == *r,
_ => false,
}
}
}
impl fmt::Debug for BackgroundPattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BackgroundPattern::Grid(grid) => f
.debug_tuple("BackgroundPattern::Grid")
.field(grid)
.finish(),
BackgroundPattern::Custom(_) => f.write_str("BackgroundPattern::Custom"),
BackgroundPattern::NoPattern => f.write_str("BackgroundPattern::NoPattern"),
}
}
}
impl Default for BackgroundPattern {
fn default() -> Self {
Self::Grid(Default::default())
}
}
impl BackgroundPattern {
/// Create new background pattern with default values.
///
/// Default patter is `Grid` with spacing - `
#[doc = default_grid_spacing!()]
/// ` and angle - `
#[doc = default_grid_angle!()]
/// ` radian.
pub const fn new() -> Self {
Self::Grid(Grid::new(DEFAULT_GRID_SPACING, DEFAULT_GRID_ANGLE))
}
/// Create new grid background pattern with given spacing and angle.
pub const fn grid(spacing: Vec2, angle: f32) -> Self {
Self::Grid(Grid::new(spacing, angle))
}
/// Create new custom background pattern.
pub fn custom<F>(f: F) -> Self
where
F: Fn(&SnarlStyle, &Viewport, &mut Ui) + 'static,
{
Self::Custom(CustomBackground::new(f))
}
/// Draws background pattern.
pub(super) fn draw(&self, style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui) {
match self {
BackgroundPattern::Grid(g) => g.draw(style, viewport, ui),
BackgroundPattern::Custom(c) => c.call(style, viewport, ui),
BackgroundPattern::NoPattern => {}
}
}
}

160
vendor/egui-snarl/src/ui/effect.rs vendored Normal file
View file

@ -0,0 +1,160 @@
use std::cell::RefCell;
use egui::Pos2;
use crate::{wire_pins, InPinId, Node, OutPinId, Snarl};
pub enum Effect<T> {
/// Adds a new node to the Snarl.
InsertNode { pos: Pos2, node: T },
/// Removes a node from snarl.
RemoveNode { node: NodeId },
/// Opens/closes a node.
OpenNode { node: NodeId, open: bool },
/// Adds connection between two nodes.
Connect { from: OutPinId, to: InPinId },
/// Removes connection between two nodes.
Disconnect { from: OutPinId, to: InPinId },
/// Removes all connections from the output pin.
DropOutputs { pin: OutPinId },
/// Removes all connections to the input pin.
DropInputs { pin: InPinId },
/// Executes a closure with mutable reference to the Snarl.
Closure(Box<dyn FnOnce(&mut Snarl<T>)>),
}
/// Contained for deferred execution of effects.
/// It is populated by [`SnarlViewer`] methods and then applied to the Snarl.
pub struct Effects<T> {
effects: Vec<Effect<T>>,
}
impl<T> Default for Effects<T> {
#[inline]
fn default() -> Self {
Effects {
effects: Default::default(),
}
}
}
impl<T> Effects<T> {
#[inline(always)]
#[doc(hidden)]
pub fn new() -> Self {
Effects {
effects: Vec::new(),
}
}
/// Returns `true` if there are no effects.
/// Returns `false` otherwise.
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.effects.is_empty()
}
/// Inserts a new node to the Snarl.
#[inline(always)]
pub fn insert_node(&mut self, pos: Pos2, node: T) {
self.effects.push(Effect::InsertNode { node, pos });
}
/// Removes a node from the Snarl.
#[inline(always)]
pub fn remove_node(&mut self, node: NodeId) {
self.effects.push(Effect::RemoveNode { node });
}
/// Opens/closes a node.
#[inline(always)]
pub fn open_node(&mut self, node: NodeId, open: bool) {
self.effects.push(Effect::OpenNode { node, open });
}
/// Connects two nodes.
#[inline(always)]
pub fn connect(&mut self, from: OutPinId, to: InPinId) {
self.effects.push(Effect::Connect { from, to });
}
/// Disconnects two nodes.
#[inline(always)]
pub fn disconnect(&mut self, from: OutPinId, to: InPinId) {
self.effects.push(Effect::Disconnect { from, to });
}
/// Removes all connections from the output pin.
#[inline(always)]
pub fn drop_inputs(&mut self, pin: InPinId) {
self.effects.push(Effect::DropInputs { pin });
}
/// Removes all connections to the input pin.
#[inline(always)]
pub fn drop_outputs(&mut self, pin: OutPinId) {
self.effects.push(Effect::DropOutputs { pin });
}
}
impl<T> Snarl<T> {
pub fn apply_effects(&mut self, effects: Effects<T>) {
if effects.effects.is_empty() {
return;
}
for effect in effects.effects {
self.apply_effect(effect);
}
}
pub fn apply_effect(&mut self, effect: Effect<T>) {
match effect {
Effect::InsertNode { node, pos } => {
let idx = self.nodes.insert(Node {
value: RefCell::new(node),
pos,
open: true,
});
self.draw_order.push(idx);
}
Effect::RemoveNode { node } => {
if self.nodes.contains(node) {
self.remove_node(node);
}
}
Effect::OpenNode { node, open } => {
if self.nodes.contains(node) {
self.nodes[node].open = open;
}
}
Effect::Connect { from, to } => {
if self.nodes.contains(from.node) && self.nodes.contains(to.node) {
self.wires.insert(wire_pins(from, to));
}
}
Effect::Disconnect { from, to } => {
if self.nodes.contains(from.node) && self.nodes.contains(to.node) {
self.wires.remove(&wire_pins(from, to));
}
}
Effect::DropOutputs { pin } => {
if self.nodes.contains(pin.node) {
self.wires.drop_outputs(pin);
}
}
Effect::DropInputs { pin } => {
if self.nodes.contains(pin.node) {
self.wires.drop_inputs(pin);
}
}
Effect::Closure(f) => f(self),
}
}
}

171
vendor/egui-snarl/src/ui/pin.rs vendored Normal file
View file

@ -0,0 +1,171 @@
use egui::{epaint::PathShape, vec2, Color32, Painter, Pos2, Rect, Shape, Stroke, Vec2};
use crate::{InPinId, OutPinId};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AnyPin {
Out(OutPinId),
In(InPinId),
}
/// In the current context, these are the I/O pins of the 'source' node that the newly
/// created node's I/O pins will connect to.
#[derive(Debug)]
pub enum AnyPins<'a> {
/// Output pins.
Out(&'a [OutPinId]),
/// Input pins
In(&'a [InPinId]),
}
tiny_fn::tiny_fn! {
/// Custom pin shape drawing function with signature
/// `Fn(painter: &Painter, rect: Rect, fill: Color32, stroke: Stroke)`
pub struct CustomPinShape = Fn(painter: &Painter, rect: Rect, fill: Color32, stroke: Stroke);
}
/// Shape of a pin.
pub enum PinShape {
/// Circle shape.
Circle,
/// Triangle shape.
Triangle,
/// Square shape.
Square,
/// Custom shape.
Custom(CustomPinShape<'static>),
}
/// Information about a pin returned by `SnarlViewer::show_input` and `SnarlViewer::show_output`.
pub struct PinInfo {
/// Shape of the pin.
pub shape: PinShape,
/// Size of the pin.
pub size: f32,
/// Fill color of the pin.
pub fill: Color32,
/// Outline stroke of the pin.
pub stroke: Stroke,
}
impl Default for PinInfo {
fn default() -> Self {
PinInfo {
shape: PinShape::Circle,
size: 1.0,
fill: Color32::GRAY,
stroke: Stroke::new(1.0, Color32::BLACK),
}
}
}
impl PinInfo {
/// Sets the shape of the pin.
pub fn with_shape(mut self, shape: PinShape) -> Self {
self.shape = shape;
self
}
/// Sets the size of the pin.
pub fn with_size(mut self, size: f32) -> Self {
self.size = size;
self
}
/// Sets the fill color of the pin.
pub fn with_fill(mut self, fill: Color32) -> Self {
self.fill = fill;
self
}
/// Sets the outline stroke of the pin.
pub fn with_stroke(mut self, stroke: Stroke) -> Self {
self.stroke = stroke;
self
}
/// Creates a circle pin.
pub fn circle() -> Self {
PinInfo {
shape: PinShape::Circle,
..Default::default()
}
}
/// Creates a triangle pin.
pub fn triangle() -> Self {
PinInfo {
shape: PinShape::Triangle,
..Default::default()
}
}
/// Creates a square pin.
pub fn square() -> Self {
PinInfo {
shape: PinShape::Square,
..Default::default()
}
}
/// Creates a square pin.
pub fn custom<F>(f: F) -> Self
where
F: Fn(&Painter, Rect, Color32, Stroke) + 'static,
{
PinInfo {
shape: PinShape::Custom(CustomPinShape::new(f)),
..Default::default()
}
}
}
pub fn draw_pin(painter: &Painter, pin: PinInfo, pos: Pos2, base_size: f32) {
let size = base_size * pin.size;
match pin.shape {
PinShape::Circle => {
painter.circle(pos, size * 2.0 / std::f32::consts::PI, pin.fill, pin.stroke);
}
PinShape::Triangle => {
const A: Vec2 = vec2(-0.649_519, 0.4875);
const B: Vec2 = vec2(0.649_519, 0.4875);
const C: Vec2 = vec2(0.0, -0.6375);
let points = vec![pos + A * size, pos + B * size, pos + C * size];
painter.add(Shape::Path(PathShape {
points,
closed: true,
fill: pin.fill,
stroke: pin.stroke,
}));
}
PinShape::Square => {
let points = vec![
pos + vec2(-0.5, -0.5) * size,
pos + vec2(0.5, -0.5) * size,
pos + vec2(0.5, 0.5) * size,
pos + vec2(-0.5, 0.5) * size,
];
painter.add(Shape::Path(PathShape {
points,
closed: true,
fill: pin.fill,
stroke: pin.stroke,
}));
}
PinShape::Custom(f) => f.call(
painter,
Rect::from_center_size(pos, vec2(size, size)),
pin.fill,
pin.stroke,
),
}
}

404
vendor/egui-snarl/src/ui/state.rs vendored Normal file
View file

@ -0,0 +1,404 @@
use egui::{style::Spacing, Align, Context, Id, Pos2, Rect, Vec2};
use crate::{InPinId, OutPinId, Snarl};
use super::SnarlStyle;
/// Node UI state.
pub struct NodeState {
/// Node size for this frame.
/// It is updated to fit content.
size: Vec2,
header_height: f32,
body_width: f32,
footer_width: f32,
id: Id,
scale: f32,
dirty: bool,
}
#[derive(Clone, Copy, PartialEq)]
struct NodeData {
unscaled_size: Vec2,
unscaled_header_height: f32,
unscaled_body_width: f32,
unsacled_footer_width: f32,
}
impl NodeState {
pub fn load(cx: &Context, id: Id, spacing: &Spacing, scale: f32) -> Self {
match cx.data_mut(|d| d.get_temp::<NodeData>(id)) {
Some(data) => NodeState {
size: data.unscaled_size * scale,
header_height: data.unscaled_header_height * scale,
body_width: data.unscaled_body_width * scale,
footer_width: data.unsacled_footer_width * scale,
id,
scale,
dirty: false,
},
None => Self::initial(id, spacing, scale),
}
}
pub fn clear(self, cx: &Context) {
cx.data_mut(|d| d.remove::<Self>(self.id));
}
pub fn store(&self, cx: &Context) {
if self.dirty {
cx.data_mut(|d| {
d.insert_temp(
self.id,
NodeData {
unscaled_size: self.size / self.scale,
unscaled_header_height: self.header_height / self.scale,
unscaled_body_width: self.body_width / self.scale,
unsacled_footer_width: self.footer_width / self.scale,
},
)
});
}
}
/// Finds node rect at specific position (excluding node frame margin).
pub fn node_rect(&self, pos: Pos2, openness: f32) -> Rect {
Rect::from_min_size(
pos,
egui::vec2(
self.size.x,
f32::max(self.header_height, self.size.y * openness),
),
)
}
pub fn payload_offset(&self, openness: f32) -> f32 {
(self.size.y) * (1.0 - openness)
}
pub fn align_body(&mut self, rect: Rect) -> Rect {
let x_range = Align::Center.align_size_within_range(self.body_width, rect.x_range());
Rect::from_x_y_ranges(x_range, rect.y_range())
}
pub fn align_footer(&mut self, rect: Rect) -> Rect {
let x_range = Align::Center.align_size_within_range(self.footer_width, rect.x_range());
Rect::from_x_y_ranges(x_range, rect.y_range())
}
pub fn set_size(&mut self, size: Vec2) {
if self.size != size {
self.size = size;
self.dirty = true;
}
}
pub fn set_header_height(&mut self, height: f32) {
if self.header_height != height {
self.header_height = height;
self.dirty = true;
}
}
pub fn set_body_width(&mut self, width: f32) {
if self.body_width != width {
self.body_width = width;
self.dirty = true;
}
}
pub fn set_footer_width(&mut self, width: f32) {
if self.footer_width != width {
self.footer_width = width;
self.dirty = true;
}
}
fn initial(id: Id, spacing: &Spacing, scale: f32) -> Self {
NodeState {
size: spacing.interact_size,
header_height: spacing.interact_size.y,
body_width: 0.0,
footer_width: 0.0,
id,
dirty: true,
scale,
}
}
}
#[derive(Clone)]
pub enum NewWires {
In(Vec<InPinId>),
Out(Vec<OutPinId>),
}
pub struct SnarlState {
/// Where viewport's center in graph's space.
offset: Vec2,
/// Scale of the viewport.
scale: f32,
target_scale: f32,
new_wires: Option<NewWires>,
id: Id,
/// Flag indicating that the graph state is dirty must be saved.
dirty: bool,
/// Flag indicating that the link menu is open.
is_link_menu_open: bool,
}
#[derive(Clone)]
struct SnarlStateData {
offset: Vec2,
scale: f32,
target_scale: f32,
new_wires: Option<NewWires>,
is_link_menu_open: bool,
}
impl SnarlState {
pub fn load<T>(
cx: &Context,
id: Id,
pivot: Pos2,
viewport: Rect,
snarl: &Snarl<T>,
style: &SnarlStyle,
) -> Self {
let Some(mut data) = cx.data_mut(|d| d.get_temp::<SnarlStateData>(id)) else {
return Self::initial(id, viewport, snarl, style);
};
let new_scale = cx.animate_value_with_time(id.with("zoom-scale"), data.target_scale, 0.1);
let mut dirty = false;
if new_scale != data.scale {
let a = pivot + data.offset - viewport.center().to_vec2();
data.offset += a * new_scale / data.scale - a;
data.scale = new_scale;
dirty = true;
}
SnarlState {
offset: data.offset,
scale: data.scale,
target_scale: data.target_scale,
new_wires: data.new_wires,
is_link_menu_open: data.is_link_menu_open,
id,
dirty,
}
}
fn initial<T>(id: Id, viewport: Rect, snarl: &Snarl<T>, style: &SnarlStyle) -> Self {
let mut bb = Rect::NOTHING;
for (_, node) in snarl.nodes.iter() {
bb.extend_with(node.pos);
}
if !bb.is_positive() {
let scale = 1.0f32.clamp(style.min_scale, style.max_scale);
return SnarlState {
offset: Vec2::ZERO,
scale,
target_scale: scale,
new_wires: None,
is_link_menu_open: false,
id,
dirty: true,
};
}
bb = bb.expand(100.0);
let bb_size = bb.size();
let viewport_size = viewport.size();
let scale = (viewport_size.x / bb_size.x)
.min(1.0)
.min(viewport_size.y / bb_size.y)
.min(style.max_scale)
.max(style.min_scale);
let offset = bb.center().to_vec2() * scale;
SnarlState {
offset,
scale,
target_scale: scale,
new_wires: None,
is_link_menu_open: false,
id,
dirty: true,
}
}
#[inline(always)]
pub fn store(self, cx: &Context) {
if self.dirty {
cx.data_mut(|d| {
d.insert_temp(
self.id,
SnarlStateData {
offset: self.offset,
scale: self.scale,
target_scale: self.target_scale,
new_wires: self.new_wires,
is_link_menu_open: self.is_link_menu_open,
},
)
});
}
}
#[inline(always)]
pub fn pan(&mut self, delta: Vec2) {
self.offset += delta;
self.dirty = true;
}
#[inline(always)]
pub fn scale(&self) -> f32 {
self.scale
}
#[inline(always)]
pub fn offset(&self) -> Vec2 {
self.offset
}
#[inline(always)]
pub fn set_scale(&mut self, scale: f32) {
self.target_scale = scale;
self.dirty = true;
}
#[inline(always)]
pub fn screen_pos_to_graph(&self, pos: Pos2, viewport: Rect) -> Pos2 {
(pos + self.offset - viewport.center().to_vec2()) / self.scale
}
#[inline(always)]
pub fn graph_pos_to_screen(&self, pos: Pos2, viewport: Rect) -> Pos2 {
pos * self.scale - self.offset + viewport.center().to_vec2()
}
// #[inline(always)]
// pub fn graph_vec_to_screen(&self, size: Vec2) -> Vec2 {
// size * self.scale
// }
#[inline(always)]
pub fn screen_vec_to_graph(&self, size: Vec2) -> Vec2 {
size / self.scale
}
// #[inline(always)]
// pub fn graph_value_to_screen(&self, value: f32) -> f32 {
// value * self.scale
// }
// #[inline(always)]
// pub fn screen_value_to_graph(&self, value: f32) -> f32 {
// value / self.scale
// }
pub fn start_new_wire_in(&mut self, pin: InPinId) {
self.new_wires = Some(NewWires::In(vec![pin]));
self.dirty = true;
}
pub fn start_new_wire_out(&mut self, pin: OutPinId) {
self.new_wires = Some(NewWires::Out(vec![pin]));
self.dirty = true;
}
pub fn start_new_wires_in(&mut self, pins: &[InPinId]) {
self.new_wires = Some(NewWires::In(pins.to_vec()));
self.dirty = true;
}
pub fn start_new_wires_out(&mut self, pins: &[OutPinId]) {
self.new_wires = Some(NewWires::Out(pins.to_vec()));
self.dirty = true;
}
pub fn add_new_wire_in(&mut self, pin: InPinId) {
if let Some(NewWires::In(pins)) = &mut self.new_wires {
if !pins.contains(&pin) {
pins.push(pin);
self.dirty = true;
}
}
}
pub fn add_new_wire_out(&mut self, pin: OutPinId) {
if let Some(NewWires::Out(pins)) = &mut self.new_wires {
if !pins.contains(&pin) {
pins.push(pin);
self.dirty = true;
}
}
}
pub fn remove_new_wire_in(&mut self, pin: InPinId) {
if let Some(NewWires::In(pins)) = &mut self.new_wires {
if let Some(idx) = pins.iter().position(|p| *p == pin) {
pins.swap_remove(idx);
self.dirty = true;
}
}
}
pub fn remove_new_wire_out(&mut self, pin: OutPinId) {
if let Some(NewWires::Out(pins)) = &mut self.new_wires {
if let Some(idx) = pins.iter().position(|p| *p == pin) {
pins.swap_remove(idx);
self.dirty = true;
}
}
}
pub fn has_new_wires(&self) -> bool {
self.new_wires.is_some()
}
pub fn new_wires(&self) -> Option<&NewWires> {
self.new_wires.as_ref()
}
pub fn take_wires(&mut self) -> Option<NewWires> {
self.dirty |= self.new_wires.is_some();
self.new_wires.take()
}
pub(crate) fn revert_take_wires(&mut self, wires: NewWires) {
self.new_wires = Some(wires);
}
pub(crate) fn open_link_menu(&mut self) {
self.is_link_menu_open = true;
self.dirty = true;
}
pub(crate) fn close_link_menu(&mut self) {
self.new_wires = None;
self.is_link_menu_open = false;
self.dirty = true;
}
pub(crate) fn is_link_menu_open(&self) -> bool {
self.is_link_menu_open
}
}

186
vendor/egui-snarl/src/ui/viewer.rs vendored Normal file
View file

@ -0,0 +1,186 @@
use egui::{Color32, Pos2, Style, Ui};
use crate::{InPin, NodeId, OutPin, Snarl};
use super::pin::{AnyPins, PinInfo};
/// SnarlViewer is a trait for viewing a Snarl.
///
/// It can extract necessary data from the nodes and controls their
/// response to certain events.
pub trait SnarlViewer<T> {
/// Returns title of the node.
fn title(&mut self, node: &T) -> String;
/// Checks if node has something to show in body - between input and output pins.
fn has_body(&mut self, node: &T) -> bool {
let _ = node;
false
}
/// Checks if node has something to show in footer - below pins and body.
fn has_footer(&mut self, node: &T) -> bool {
let _ = node;
false
}
/// Checks if node has something to show in on-hover popup.
fn has_on_hover_popup(&mut self, node: &T) -> bool {
let _ = node;
false
}
/// Renders the node's header.
fn show_header(
&mut self,
node: NodeId,
inputs: &[InPin],
outputs: &[OutPin],
ui: &mut Ui,
scale: f32,
snarl: &mut Snarl<T>,
) {
let _ = (inputs, outputs, scale);
ui.label(self.title(&snarl[node]));
}
/// Returns number of output pins of the node.
fn outputs(&mut self, node: &T) -> usize;
/// Returns number of input pins of the node.
fn inputs(&mut self, node: &T) -> usize;
/// Renders the node's input pin.
fn show_input(&mut self, pin: &InPin, ui: &mut Ui, scale: f32, snarl: &mut Snarl<T>)
-> PinInfo;
/// Renders the node's output pin.
fn show_output(
&mut self,
pin: &OutPin,
ui: &mut Ui,
scale: f32,
snarl: &mut Snarl<T>,
) -> PinInfo;
/// Renders the node's body.
fn show_body(
&mut self,
node: NodeId,
inputs: &[InPin],
outputs: &[OutPin],
ui: &mut Ui,
scale: f32,
snarl: &mut Snarl<T>,
) {
let _ = (node, inputs, outputs, ui, scale, snarl);
}
/// Renders the node's footer.
fn show_footer(
&mut self,
node: NodeId,
inputs: &[InPin],
outputs: &[OutPin],
ui: &mut Ui,
scale: f32,
snarl: &mut Snarl<T>,
) {
let _ = (node, inputs, outputs, ui, scale, snarl);
}
/// Renders the node's on-hover popup.
fn show_on_hover_popup(
&mut self,
node: NodeId,
inputs: &[InPin],
outputs: &[OutPin],
ui: &mut Ui,
scale: f32,
snarl: &mut Snarl<T>,
) {
let _ = (node, inputs, outputs, ui, scale, snarl);
}
/// Returns color of the node's input pin.
/// Called when pin in not visible.
fn input_color(&mut self, pin: &InPin, style: &Style, snarl: &mut Snarl<T>) -> Color32;
/// Returns color of the node's output pin.
/// Called when pin in not visible.
fn output_color(&mut self, pin: &OutPin, style: &Style, snarl: &mut Snarl<T>) -> Color32;
/// Show context menu for the snarl.
///
/// This can be used to implement menu for adding new nodes.
fn graph_menu(&mut self, pos: Pos2, ui: &mut Ui, scale: f32, snarl: &mut Snarl<T>) {
let _ = (pos, ui, scale, snarl);
}
/// Show context menu for the snarl. This menu is opened when releasing a pin to empty
/// space. It can be used to implement menu for adding new node, and directly
/// connecting it to the released wire.
///
///
fn graph_menu_for_dropped_wire(
&mut self,
pos: Pos2,
ui: &mut Ui,
scale: f32,
src_pins: AnyPins,
snarl: &mut Snarl<T>,
) {
let _ = (pos, scale, src_pins, snarl);
// Default implementation simply doesn't utilize wire drop.
ui.close_menu();
}
/// Show context menu for the snarl.
///
/// This can be used to implement menu for adding new nodes.
fn node_menu(
&mut self,
node: NodeId,
inputs: &[InPin],
outputs: &[OutPin],
ui: &mut Ui,
scale: f32,
snarl: &mut Snarl<T>,
) {
let _ = (node, inputs, outputs, ui, scale, snarl);
}
/// Asks the viewer to connect two pins.
///
/// This is usually happens when user drags a wire from one node's output pin to another node's input pin or vice versa.
/// By default this method connects the pins and returns `Ok(())`.
#[inline]
fn connect(&mut self, from: &OutPin, to: &InPin, snarl: &mut Snarl<T>) {
snarl.connect(from.id, to.id);
}
/// Asks the viewer to disconnect two pins.
#[inline]
fn disconnect(&mut self, from: &OutPin, to: &InPin, snarl: &mut Snarl<T>) {
snarl.disconnect(from.id, to.id);
}
/// Asks the viewer to disconnect all wires from the output pin.
///
/// This is usually happens when right-clicking on an output pin.
/// By default this method disconnects the pins and returns `Ok(())`.
#[inline]
fn drop_outputs(&mut self, pin: &OutPin, snarl: &mut Snarl<T>) {
snarl.drop_outputs(pin.id);
}
/// Asks the viewer to disconnect all wires from the input pin.
///
/// This is usually happens when right-clicking on an input pin.
/// By default this method disconnects the pins and returns `Ok(())`.
#[inline]
fn drop_inputs(&mut self, pin: &InPin, snarl: &mut Snarl<T>) {
snarl.drop_inputs(pin.id);
}
}

323
vendor/egui-snarl/src/ui/wire.rs vendored Normal file
View file

@ -0,0 +1,323 @@
use egui::{epaint::PathShape, pos2, Color32, Pos2, Rect, Shape, Stroke, Ui};
/// Layer where wires are rendered.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
pub enum WireLayer {
/// Wires are rendered behind nodes.
/// This is default.
BehindNodes,
/// Wires are rendered above nodes.
AboveNodes,
}
/// Returns 6th degree bezier curve for the wire
fn wire_bezier(
mut frame_size: f32,
upscale: bool,
downscale: bool,
from: Pos2,
to: Pos2,
) -> [Pos2; 6] {
if upscale {
frame_size = frame_size.max((from - to).length() / 4.0);
}
if downscale {
frame_size = frame_size.min((from - to).length() / 4.0);
}
let from_norm_x = frame_size;
let from_2 = pos2(from.x + from_norm_x, from.y);
let to_norm_x = -from_norm_x;
let to_2 = pos2(to.x + to_norm_x, to.y);
let between = (from_2 - to_2).length();
if from_2.x <= to_2.x && between >= frame_size * 2.0 {
let middle_1 = from_2 + (to_2 - from_2).normalized() * frame_size;
let middle_2 = to_2 + (from_2 - to_2).normalized() * frame_size;
[from, from_2, middle_1, middle_2, to_2, to]
} else if from_2.x <= to_2.x {
let t =
(between - (to_2.y - from_2.y).abs()) / (frame_size * 2.0 - (to_2.y - from_2.y).abs());
let mut middle_1 = from_2 + (to_2 - from_2).normalized() * frame_size;
let mut middle_2 = to_2 + (from_2 - to_2).normalized() * frame_size;
if from_2.y >= to_2.y + frame_size {
let u = (from_2.y - to_2.y - frame_size) / frame_size;
let t0_middle_1 = pos2(from_2.x + (1.0 - u) * frame_size, from_2.y - frame_size * u);
let t0_middle_2 = pos2(to_2.x, to_2.y + frame_size);
middle_1 = t0_middle_1.lerp(middle_1, t);
middle_2 = t0_middle_2.lerp(middle_2, t);
} else if from_2.y >= to_2.y {
let u = (from_2.y - to_2.y) / frame_size;
let t0_middle_1 = pos2(from_2.x + u * frame_size, from_2.y + frame_size * (1.0 - u));
let t0_middle_2 = pos2(to_2.x, to_2.y + frame_size);
middle_1 = t0_middle_1.lerp(middle_1, t);
middle_2 = t0_middle_2.lerp(middle_2, t);
} else if to_2.y >= from_2.y + frame_size {
let u = (to_2.y - from_2.y - frame_size) / frame_size;
let t0_middle_1 = pos2(from_2.x, from_2.y + frame_size);
let t0_middle_2 = pos2(to_2.x - (1.0 - u) * frame_size, to_2.y - frame_size * u);
middle_1 = t0_middle_1.lerp(middle_1, t);
middle_2 = t0_middle_2.lerp(middle_2, t);
} else if to_2.y >= from_2.y {
let u = (to_2.y - from_2.y) / frame_size;
let t0_middle_1 = pos2(from_2.x, from_2.y + frame_size);
let t0_middle_2 = pos2(to_2.x - u * frame_size, to_2.y + frame_size * (1.0 - u));
middle_1 = t0_middle_1.lerp(middle_1, t);
middle_2 = t0_middle_2.lerp(middle_2, t);
} else {
unreachable!();
}
[from, from_2, middle_1, middle_2, to_2, to]
} else if from_2.y >= to_2.y + frame_size * 2.0 {
let middle_1 = pos2(from_2.x, from_2.y - frame_size);
let middle_2 = pos2(to_2.x, to_2.y + frame_size);
[from, from_2, middle_1, middle_2, to_2, to]
} else if from_2.y >= to_2.y + frame_size {
let t = (from_2.y - to_2.y - frame_size) / frame_size;
let middle_1 = pos2(from_2.x + (1.0 - t) * frame_size, from_2.y - frame_size * t);
let middle_2 = pos2(to_2.x, to_2.y + frame_size);
[from, from_2, middle_1, middle_2, to_2, to]
} else if from_2.y >= to_2.y {
let t = (from_2.y - to_2.y) / frame_size;
let middle_1 = pos2(from_2.x + t * frame_size, from_2.y + frame_size * (1.0 - t));
let middle_2 = pos2(to_2.x, to_2.y + frame_size);
[from, from_2, middle_1, middle_2, to_2, to]
} else if to_2.y >= from_2.y + frame_size * 2.0 {
let middle_1 = pos2(from_2.x, from_2.y + frame_size);
let middle_2 = pos2(to_2.x, to_2.y - frame_size);
[from, from_2, middle_1, middle_2, to_2, to]
} else if to_2.y >= from_2.y + frame_size {
let t = (to_2.y - from_2.y - frame_size) / frame_size;
let middle_1 = pos2(from_2.x, from_2.y + frame_size);
let middle_2 = pos2(to_2.x - (1.0 - t) * frame_size, to_2.y - frame_size * t);
[from, from_2, middle_1, middle_2, to_2, to]
} else if to_2.y >= from_2.y {
let t = (to_2.y - from_2.y) / frame_size;
let middle_1 = pos2(from_2.x, from_2.y + frame_size);
let middle_2 = pos2(to_2.x - t * frame_size, to_2.y + frame_size * (1.0 - t));
[from, from_2, middle_1, middle_2, to_2, to]
} else {
unreachable!();
}
}
#[allow(clippy::too_many_arguments)]
pub fn draw_wire(
ui: &mut Ui,
shapes: &mut Vec<Shape>,
frame_size: f32,
upscale: bool,
downscale: bool,
from: Pos2,
to: Pos2,
stroke: Stroke,
) {
let points = wire_bezier(frame_size, upscale, downscale, from, to);
let bb = Rect::from_points(&points);
if ui.is_rect_visible(bb) {
draw_bezier(shapes, &points, stroke);
}
}
pub fn hit_wire(
pos: Pos2,
frame_size: f32,
upscale: bool,
downscale: bool,
from: Pos2,
to: Pos2,
threshold: f32,
) -> bool {
let points = wire_bezier(frame_size, upscale, downscale, from, to);
hit_bezier(pos, &points, threshold)
}
fn bezier_reference_size(points: &[Pos2; 6]) -> f32 {
let [p0, p1, p2, p3, p4, p5] = *points;
(p1 - p0).length()
+ (p2 - p1).length()
+ (p3 - p2).length()
+ (p4 - p3).length()
+ (p5 - p4).length()
}
const MAX_BEZIER_SAMPLES: usize = 100;
fn bezier_samples_number(points: &[Pos2; 6], threshold: f32) -> usize {
let reference_size = bezier_reference_size(points);
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_possible_truncation)]
((reference_size / threshold).ceil().max(0.0) as usize).min(MAX_BEZIER_SAMPLES)
}
fn draw_bezier(shapes: &mut Vec<Shape>, points: &[Pos2; 6], mut stroke: Stroke) {
if stroke.width < 1.0 {
stroke.color = stroke.color.gamma_multiply(stroke.width);
stroke.width = 1.0;
}
let samples = bezier_samples_number(points, stroke.width);
let mut path = Vec::new();
for i in 0..samples {
#[allow(clippy::cast_precision_loss)]
let t = i as f32 / (samples - 1) as f32;
path.push(sample_bezier(points, t));
}
let shape = Shape::Path(PathShape {
points: path,
closed: false,
fill: Color32::TRANSPARENT,
stroke,
});
shapes.push(shape);
}
#[allow(clippy::let_and_return)]
fn sample_bezier(points: &[Pos2; 6], t: f32) -> Pos2 {
let [p0, p1, p2, p3, p4, p5] = *points;
let p0_0 = p0;
let p1_0 = p1;
let p2_0 = p2;
let p3_0 = p3;
let p4_0 = p4;
let p5_0 = p5;
let p0_1 = p0_0.lerp(p1_0, t);
let p1_1 = p1_0.lerp(p2_0, t);
let p2_1 = p2_0.lerp(p3_0, t);
let p3_1 = p3_0.lerp(p4_0, t);
let p4_1 = p4_0.lerp(p5_0, t);
let p0_2 = p0_1.lerp(p1_1, t);
let p1_2 = p1_1.lerp(p2_1, t);
let p2_2 = p2_1.lerp(p3_1, t);
let p3_2 = p3_1.lerp(p4_1, t);
let p0_3 = p0_2.lerp(p1_2, t);
let p1_3 = p1_2.lerp(p2_2, t);
let p2_3 = p2_2.lerp(p3_2, t);
let p0_4 = p0_3.lerp(p1_3, t);
let p1_4 = p1_3.lerp(p2_3, t);
let p0_5 = p0_4.lerp(p1_4, t);
p0_5
}
fn split_bezier(points: &[Pos2; 6], t: f32) -> [[Pos2; 6]; 2] {
let [p0, p1, p2, p3, p4, p5] = *points;
let p0_0 = p0;
let p1_0 = p1;
let p2_0 = p2;
let p3_0 = p3;
let p4_0 = p4;
let p5_0 = p5;
let p0_1 = p0_0.lerp(p1_0, t);
let p1_1 = p1_0.lerp(p2_0, t);
let p2_1 = p2_0.lerp(p3_0, t);
let p3_1 = p3_0.lerp(p4_0, t);
let p4_1 = p4_0.lerp(p5_0, t);
let p0_2 = p0_1.lerp(p1_1, t);
let p1_2 = p1_1.lerp(p2_1, t);
let p2_2 = p2_1.lerp(p3_1, t);
let p3_2 = p3_1.lerp(p4_1, t);
let p0_3 = p0_2.lerp(p1_2, t);
let p1_3 = p1_2.lerp(p2_2, t);
let p2_3 = p2_2.lerp(p3_2, t);
let p0_4 = p0_3.lerp(p1_3, t);
let p1_4 = p1_3.lerp(p2_3, t);
let p0_5 = p0_4.lerp(p1_4, t);
[
[p0_0, p0_1, p0_2, p0_3, p0_4, p0_5],
[p0_5, p1_4, p2_3, p3_2, p4_1, p5_0],
]
}
fn hit_bezier(pos: Pos2, points: &[Pos2; 6], threshold: f32) -> bool {
let aabb = Rect::from_points(points);
if pos.x + threshold < aabb.left() {
return false;
}
if pos.x - threshold > aabb.right() {
return false;
}
if pos.y + threshold < aabb.top() {
return false;
}
if pos.y - threshold > aabb.bottom() {
return false;
}
let samples = bezier_samples_number(points, threshold);
if samples > 16 {
let [points1, points2] = split_bezier(points, 0.5);
return hit_bezier(pos, &points1, threshold) || hit_bezier(pos, &points2, threshold);
}
for i in 0..samples {
#[allow(clippy::cast_precision_loss)]
let t = i as f32 / (samples - 1) as f32;
let p = sample_bezier(points, t);
if (p - pos).length() < threshold {
return true;
}
}
false
}
pub fn mix_colors(a: Color32, b: Color32) -> Color32 {
let [or, og, ob, oa] = a.to_array();
let [ir, ig, ib, ia] = b.to_array();
Color32::from_rgba_premultiplied(
or / 2 + ir / 2,
og / 2 + ig / 2,
ob / 2 + ib / 2,
oa / 2 + ia / 2,
)
}

192
vendor/egui-snarl/src/ui/zoom.rs vendored Normal file
View file

@ -0,0 +1,192 @@
use egui::{
epaint::Shadow,
style::{Interaction, ScrollStyle, Spacing, WidgetVisuals, Widgets},
FontId, Frame, Margin, Rounding, Stroke, Style, Vec2, Visuals,
};
pub trait Zoom {
fn zoomed(mut self, zoom: f32) -> Self
where
Self: Copy,
{
self.zoom(zoom);
self
}
fn zoom(&mut self, zoom: f32);
}
impl Zoom for f32 {
#[inline(always)]
fn zoom(&mut self, zoom: f32) {
*self *= zoom;
}
}
impl Zoom for Vec2 {
#[inline(always)]
fn zoom(&mut self, zoom: f32) {
*self *= zoom;
}
}
impl Zoom for Rounding {
#[inline(always)]
fn zoom(&mut self, zoom: f32) {
self.nw.zoom(zoom);
self.ne.zoom(zoom);
self.se.zoom(zoom);
self.sw.zoom(zoom);
}
}
impl Zoom for Margin {
#[inline(always)]
fn zoom(&mut self, zoom: f32) {
self.left.zoom(zoom);
self.right.zoom(zoom);
self.top.zoom(zoom);
self.bottom.zoom(zoom);
}
}
impl Zoom for Shadow {
#[inline(always)]
fn zoom(&mut self, zoom: f32) {
self.spread = zoom;
// self.extrusion.zoom(zoom);
}
}
impl Zoom for Stroke {
#[inline(always)]
fn zoom(&mut self, zoom: f32) {
self.width *= zoom;
if self.width < 1.0 {
self.color.gamma_multiply(self.width);
self.width = 1.0;
}
}
}
impl Zoom for WidgetVisuals {
#[inline(always)]
fn zoom(&mut self, zoom: f32) {
self.bg_stroke.zoom(zoom);
self.rounding.zoom(zoom);
self.fg_stroke.zoom(zoom);
self.expansion.zoom(zoom);
}
}
impl Zoom for Interaction {
#[inline(always)]
fn zoom(&mut self, zoom: f32) {
self.resize_grab_radius_corner.zoom(zoom);
self.resize_grab_radius_side.zoom(zoom);
}
}
impl Zoom for Widgets {
#[inline(always)]
fn zoom(&mut self, zoom: f32) {
self.noninteractive.zoom(zoom);
self.inactive.zoom(zoom);
self.hovered.zoom(zoom);
self.active.zoom(zoom);
self.open.zoom(zoom);
}
}
impl Zoom for Visuals {
#[inline(always)]
fn zoom(&mut self, zoom: f32) {
self.clip_rect_margin.zoom(zoom);
self.menu_rounding.zoom(zoom);
self.popup_shadow.zoom(zoom);
self.resize_corner_size.zoom(zoom);
self.selection.stroke.zoom(zoom);
self.text_cursor.zoom(zoom);
self.widgets.zoom(zoom);
self.window_rounding.zoom(zoom);
self.window_shadow.zoom(zoom);
self.window_stroke.zoom(zoom);
}
}
impl Zoom for ScrollStyle {
#[inline(always)]
fn zoom(&mut self, zoom: f32) {
self.bar_inner_margin.zoom(zoom);
self.bar_outer_margin.zoom(zoom);
self.bar_width.zoom(zoom);
self.floating_allocated_width.zoom(zoom);
self.floating_width.zoom(zoom);
self.handle_min_length.zoom(zoom);
}
}
impl Zoom for Spacing {
#[inline(always)]
fn zoom(&mut self, zoom: f32) {
self.button_padding.zoom(zoom);
self.combo_height.zoom(zoom);
self.combo_width.zoom(zoom);
self.icon_spacing.zoom(zoom);
self.icon_width.zoom(zoom);
self.icon_width_inner.zoom(zoom);
self.indent.zoom(zoom);
self.interact_size.zoom(zoom);
self.item_spacing.zoom(zoom);
self.menu_margin.zoom(zoom);
self.scroll.zoom(zoom);
self.slider_width.zoom(zoom);
self.text_edit_width.zoom(zoom);
self.tooltip_width.zoom(zoom);
self.window_margin.zoom(zoom);
}
}
impl Zoom for FontId {
fn zoom(&mut self, zoom: f32) {
self.size.zoom(zoom);
}
}
impl Zoom for Style {
#[inline(always)]
fn zoom(&mut self, zoom: f32) {
if let Some(font_id) = &mut self.override_font_id {
font_id.zoom(zoom);
}
for font_id in self.text_styles.values_mut() {
font_id.zoom(zoom);
}
self.interaction.zoom(zoom);
self.spacing.zoom(zoom);
self.visuals.zoom(zoom);
}
}
impl<T> Zoom for Option<T>
where
T: Zoom,
{
#[inline(always)]
fn zoom(&mut self, zoom: f32) {
if let Some(value) = self {
value.zoom(zoom)
}
}
}
impl Zoom for Frame {
#[inline(always)]
fn zoom(&mut self, zoom: f32) {
self.inner_margin.zoom(zoom);
self.outer_margin.zoom(zoom);
self.rounding.zoom(zoom);
self.shadow.zoom(zoom);
self.stroke.zoom(zoom);
}
}