Initial attempt to implement Render Graph
This commit is contained in:
parent
173cfe2acf
commit
3732fc5e3d
33 changed files with 8851 additions and 938 deletions
29
vendor/egui-snarl/Cargo.toml
vendored
Normal file
29
vendor/egui-snarl/Cargo.toml
vendored
Normal 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
696
vendor/egui-snarl/src/lib.rs
vendored
Normal 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
1089
vendor/egui-snarl/src/ui.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
259
vendor/egui-snarl/src/ui/background_pattern.rs
vendored
Normal file
259
vendor/egui-snarl/src/ui/background_pattern.rs
vendored
Normal 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
160
vendor/egui-snarl/src/ui/effect.rs
vendored
Normal 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
171
vendor/egui-snarl/src/ui/pin.rs
vendored
Normal 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
404
vendor/egui-snarl/src/ui/state.rs
vendored
Normal 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
186
vendor/egui-snarl/src/ui/viewer.rs
vendored
Normal 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
323
vendor/egui-snarl/src/ui/wire.rs
vendored
Normal 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
192
vendor/egui-snarl/src/ui/zoom.rs
vendored
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue