use super::*;
const EDGE_CUT_LINE_WIDTH: f32 = 3.0;
fn clipped_shape_bounds(b: [f32; 4], clip: Rectangle) -> Option<Rectangle> {
let x0 = b[0].max(clip.x);
let y0 = b[1].max(clip.y);
let x1 = (b[0] + b[2]).min(clip.x + clip.width);
let y1 = (b[1] + b[3]).min(clip.y + clip.height);
if x1 <= x0 || y1 <= y0 {
return None; }
Some(Rectangle::new(
Point::new(x0, y0),
Size::new(x1 - x0, y1 - y0),
))
}
fn layer_camera(
camera_position: WorldPoint,
zoom: f32,
widget_origin: Point,
clip: Rectangle,
) -> (f32, f32) {
let cx = camera_position.x + (widget_origin.x * (1.0 - zoom) - clip.x) / zoom;
let cy = camera_position.y + (widget_origin.y * (1.0 - zoom) - clip.y) / zoom;
(cx, cy)
}
fn draw_sdf<Renderer>(
renderer: &mut Renderer,
animated: &std::cell::Cell<bool>,
clip: Rectangle,
primitive: SdfPrimitive,
) where
Renderer: iced_wgpu::primitive::Renderer,
{
if primitive.has_animations() {
animated.set(true);
}
renderer.draw_primitive(clip, primitive);
}
fn world_bbox_to_screen_bounds(
x0: f32,
y0: f32,
x1: f32,
y1: f32,
padding: f32,
ctx: &RenderContext,
) -> [f32; 4] {
let min_x = x0.min(x1) - padding;
let min_y = y0.min(y1) - padding;
let max_x = x0.max(x1) + padding;
let max_y = y0.max(y1) + padding;
let ox = ctx.viewport_origin.x * (1.0 - ctx.camera_zoom);
let oy = ctx.viewport_origin.y * (1.0 - ctx.camera_zoom);
let screen_min_x = (min_x + ctx.camera_position.x) * ctx.camera_zoom + ox;
let screen_min_y = (min_y + ctx.camera_position.y) * ctx.camera_zoom + oy;
let screen_max_x = (max_x + ctx.camera_position.x) * ctx.camera_zoom + ox;
let screen_max_y = (max_y + ctx.camera_position.y) * ctx.camera_zoom + oy;
[
screen_min_x,
screen_min_y,
screen_max_x - screen_min_x,
screen_max_y - screen_min_y,
]
}
fn edge_shape(
start: &WorldPoint,
end: &WorldPoint,
start_side: u32,
end_side: u32,
curve: &crate::style::EdgeCurve,
) -> Shape {
let p0 = [start.x, start.y];
let p1 = [end.x, end.y];
match curve {
crate::style::EdgeCurve::Line => Shape::line(p0, p1),
_ => {
let dir_from = pin_side_direction(start_side);
let dir_to = pin_side_direction(end_side);
let l = adaptive_bezier_length(p0, p1);
let cp0 = [p0[0] + dir_from[0] * l, p0[1] + dir_from[1] * l];
let cp1 = [p1[0] + dir_to[0] * l, p1[1] + dir_to[1] * l];
Shape::bezier(p0, cp0, cp1, p1)
}
}
}
fn edge_shapes(
start: &WorldPoint,
end: &WorldPoint,
start_side: u32,
end_side: u32,
style: &EdgeStyle,
) -> (Shape, Shape) {
let shape = edge_shape(start, end, start_side, end_side, &style.curve);
let has_shadow = style.shadow_blur > 0.0
&& (style.shadow_color.near_start.a > 0.0 || style.shadow_color.near_end.a > 0.0);
let shadow_shape = if has_shadow && style.shadow_offset != (0.0, 0.0) {
let (ox, oy) = style.shadow_offset;
let s_start = WorldPoint::new(start.x + ox, start.y + oy);
let s_end = WorldPoint::new(end.x + ox, end.y + oy);
edge_shape(&s_start, &s_end, start_side, end_side, &style.curve)
} else {
shape.clone()
};
(shape, shadow_shape)
}
fn push_edge_layers(
batch: &mut SdfPrimitive,
shape: &Shape,
shadow_shape: &Shape,
style: &EdgeStyle,
) {
for layer in style.sdf_layers() {
let shape = match layer.geometry {
EdgeGeometry::Stroke => shape,
EdgeGeometry::Shadow => shadow_shape,
};
batch.push(shape, &layer.style, [0.0, 0.0]);
}
}
fn resolve_node_style(
style_fn: Option<&NodeStyleFn<'_, Theme>>,
theme: &Theme,
status: NodeStatus,
) -> NodeStyle {
match style_fn {
Some(f) => f(theme, status),
None => crate::style::default_node_style(theme, status),
}
}
fn resolve_edge_style<P: PinId + 'static, UI>(
style_fn: Option<&EdgeStyleFn<'_, P, UI, Theme>>,
theme: &Theme,
status: EdgeStatus,
start: Option<PinInfo<'_, P, UI>>,
end: Option<PinInfo<'_, P, UI>>,
) -> EdgeStyle {
match (style_fn, start, end) {
(Some(f), Some(s), Some(e)) => f(theme, status, s, e),
_ => crate::style::default_edge_style(theme, status),
}
}
fn pin_info<'s, P, UI>(state: &'s NodePinState<P, UI>) -> Option<PinInfo<'s, P, UI>> {
Some(PinInfo::new(
state.direction,
&state.pin_id,
&state.user_info,
))
}
fn resolve_pin_style<P: PinId + 'static, UI>(
pin_style_fn: Option<&PinStyleFn<'_, P, UI, Theme>>,
state: &NodePinState<P, UI>,
other: Option<&NodePinState<P, UI>>,
theme: &Theme,
status: PinStatus,
) -> PinStyle {
if let (Some(f), Some(this)) = (pin_style_fn, pin_info::<P, UI>(state)) {
let other_info = other.and_then(pin_info::<P, UI>);
f(theme, &this, other_info.as_ref(), status)
} else {
crate::style::default_pin_style(theme, status)
}
}
fn pin_cutout_params<P: PinId + 'static, UI>(
pins: &[(usize, &NodePinState<P, UI>, (Point, Point))],
pin_style_fn: Option<&PinStyleFn<'_, P, UI, Theme>>,
other: Option<&NodePinState<P, UI>>,
theme: &Theme,
offset: WorldVector,
mut is_valid_target: impl FnMut(usize) -> bool,
) -> Vec<([f32; 2], f32)> {
let mut cuts = Vec::new();
for (pin_idx, (_pin_index, pin_state, (pos_a, pos_b))) in pins.iter().enumerate() {
let valid = is_valid_target(pin_idx);
let pin_status = if valid {
PinStatus::ValidTarget
} else {
PinStatus::Idle
};
let pin_style =
resolve_pin_style::<P, UI>(pin_style_fn, pin_state, other, theme, pin_status);
let indicator_r = pin_style.radius * 0.4;
let cutout_r = (indicator_r + pin_style.border_width) * 2.0;
if cutout_r <= 0.01 {
continue;
}
let positions: &[Point] = if pin_state.side == crate::PinSide::Row {
&[*pos_a, *pos_b]
} else {
std::slice::from_ref(pos_a)
};
for pos in positions {
cuts.push(([pos.x + offset.x, pos.y + offset.y], cutout_r));
}
}
cuts
}
impl<N, P, E, UI, Message, Renderer> NodeGraph<'_, N, P, UI, Message, iced::Theme, Renderer, E>
where
N: NodeId + 'static,
P: PinId + 'static,
E: EdgeId + 'static,
UI: Clone + 'static,
Renderer: iced_wgpu::core::renderer::Renderer + iced_wgpu::primitive::Renderer,
{
#[allow(clippy::too_many_arguments)]
pub(super) fn draw_impl(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &iced::Theme,
style: &renderer::Style,
layout: layout::Layout<'_>,
cursor: iced::mouse::Cursor,
viewport: &Rectangle,
) {
let state = tree.state.downcast_ref::<NodeGraphState>();
state.sdf_animated.set(false);
let mut camera = state
.camera
.with_viewport_origin(layout.bounds().position().into_euclid().to_vector());
let z_indices = z_render_indices(state, self.nodes.len());
let time = {
let now = Instant::now();
if let Some(last_update) = state.last_update {
let delta = now.duration_since(last_update).as_secs_f32();
let capped_delta = delta.min(0.1);
state.time + capped_delta
} else {
state.time
}
};
let mut render_context = RenderContext {
camera_zoom: state.camera.zoom(),
camera_position: state.camera.position(),
viewport_origin: camera.viewport_origin(),
time,
};
if let Dragging::Graph(origin) = state.dragging
&& let Some(cursor_position) = cursor.position()
{
let cursor_position: ScreenPoint = cursor_position.into_euclid();
let cursor_position: WorldPoint =
camera.screen_to_world().transform_point(cursor_position);
camera = camera.move_by(cursor_position - origin);
}
render_context.camera_zoom = camera.zoom();
render_context.camera_position = camera.position();
let resolved_graph = if let Some(ref style_fn) = self.graph_style {
style_fn(theme)
} else {
GraphStyle::from_theme(theme)
};
let is_edge_dragging = matches!(
state.dragging,
Dragging::Edge(_, _, _) | Dragging::EdgeOver(_, _, _, _)
);
let mut node_pins: Vec<Vec<(usize, &NodePinState<P, UI>, (Point, Point))>> = layout
.children()
.zip(&tree.children)
.map(|(node_layout, node_tree)| find_pins::<P, UI>(node_tree, node_layout))
.collect();
node_pins.resize_with(self.nodes.len(), Vec::new);
let drag_source: Option<NodePinState<P, UI>> = match state.dragging {
Dragging::Edge(from_node, from_pin, _)
| Dragging::EdgeOver(from_node, from_pin, _, _) => node_pins
.get(from_node)
.and_then(|pins| pins.get(from_pin))
.map(|(_, s, _)| (*s).clone()),
_ => None,
};
renderer.with_layer(layout.bounds(), |renderer| {
renderer.fill_quad(
iced_wgpu::core::renderer::Quad {
bounds: layout.bounds(),
border: iced::Border::default(),
shadow: iced::Shadow::default(),
snap: true,
},
iced_wgpu::core::Background::Color(resolved_graph.background_color),
);
});
let vo = camera.viewport_origin();
let cursor_layout = |cursor_pos: iced::Point| -> WorldPoint {
let w = camera
.screen_to_world()
.transform_point(cursor_pos.into_euclid());
WorldPoint::new(w.x + vo.x, w.y + vo.y)
};
let compute_node_offset = |node_idx: usize| -> WorldVector {
let mut offset = WorldVector::zero();
let is_selected = state.selected_nodes.contains(&node_idx);
if let (Dragging::Node(drag_idx, origin), Some(cursor_pos)) =
(&state.dragging, cursor.position())
&& *drag_idx == node_idx
{
offset = cursor_layout(cursor_pos) - *origin;
}
if let (Dragging::GroupMove(origin), Some(cursor_pos)) =
(&state.dragging, cursor.position())
&& is_selected
{
offset = cursor_layout(cursor_pos) - *origin;
}
offset
};
struct NodeGeom {
shape: Shape,
center: [f32; 2],
resolved: NodeStyle,
offset: WorldVector,
position: WorldPoint,
size: Size,
}
impl NodeGeom {
fn push_body(
&self,
batch: &mut SdfPrimitive,
style: &iced_nodegraph_sdf::Style,
extra: (f32, f32),
) {
batch.push(
&self.shape,
style,
[self.center[0] + extra.0, self.center[1] + extra.1],
);
}
}
let t_geom_start = Instant::now();
let node_geoms: Vec<Option<NodeGeom>> = (0..self.nodes.len())
.map(|node_index| {
let (_id, _position, _element, node_style, node_pin_style) =
&self.nodes[node_index];
let node_layout = layout.children().nth(node_index)?;
let _node_tree = tree.children.get(node_index)?;
let status = if state.selected_nodes.contains(&node_index) {
NodeStatus::Selected
} else {
NodeStatus::Idle
};
let resolved = resolve_node_style(node_style.as_ref(), theme, status);
let offset = compute_node_offset(node_index);
let position: WorldPoint =
(node_layout.bounds().position().into_euclid().to_vector() + offset).to_point();
let size = node_layout.bounds().size();
let pins = &node_pins[node_index];
let center = [
position.x + size.width * 0.5,
position.y + size.height * 0.5,
];
let cut_params = pin_cutout_params(
pins,
node_pin_style.as_ref(),
drag_source.as_ref(),
theme,
offset,
|pin_idx| {
is_edge_dragging
&& state.valid_drop_targets.contains(&(node_index, pin_idx))
},
);
let mut shape =
Shape::rounded_box([size.width, size.height], [resolved.corner_radius; 4]);
for &(c, r) in &cut_params {
shape =
shape - Shape::circle(r).translate([c[0] - center[0], c[1] - center[1]]);
}
Some(NodeGeom {
shape,
center,
resolved,
offset,
position,
size,
})
})
.collect();
let t_after_geom = Instant::now();
let bg_layer = {
let mut bg = SdfPrimitive::with_capacity(self.nodes.len() + self.edges.len() * 4 + 1);
let pending_cuts = match &state.dragging {
Dragging::EdgeCutting { pending_cuts, .. } => Some(pending_cuts),
_ => None,
};
let mut edge_strokes: Vec<(Shape, Style)> = Vec::with_capacity(self.edges.len() * 2);
let mut edge_shadows: Vec<(Shape, Style)> = Vec::with_capacity(self.edges.len());
for (edge_idx, (_edge_id, from, to, edge_style_fn)) in self.edges.iter().enumerate() {
let Some(from_node_idx) = self.node_index(&from.node_id) else {
continue;
};
let Some(to_node_idx) = self.node_index(&to.node_id) else {
continue;
};
let from_offset = compute_node_offset(from_node_idx);
let to_offset = compute_node_offset(to_node_idx);
let from_pins = &node_pins[from_node_idx];
let Some((_, from_pin_state, (from_pin_pos, _))) = from_pins
.iter()
.find(|(_, state, _)| state.pin_id == from.pin_id)
else {
continue;
};
let to_pins = &node_pins[to_node_idx];
let Some((_, to_pin_state, (to_pin_pos, _))) = to_pins
.iter()
.find(|(_, state, _)| state.pin_id == to.pin_id)
else {
continue;
};
let from_pos = (from_pin_pos.into_euclid().to_vector() + from_offset).to_point();
let to_pos = (to_pin_pos.into_euclid().to_vector() + to_offset).to_point();
let from_side: u32 = from_pin_state.side.into();
let to_side: u32 = to_pin_state.side.into();
let from_info = pin_info::<P, UI>(from_pin_state);
let to_info = pin_info::<P, UI>(to_pin_state);
let swap = !matches!(from_pin_state.direction, PinDirection::Output)
&& matches!(to_pin_state.direction, PinDirection::Output);
let (start_pos, end_pos, start_side, end_side, start_info, end_info) = if swap {
(to_pos, from_pos, to_side, from_side, to_info, from_info)
} else {
(from_pos, to_pos, from_side, to_side, from_info, to_info)
};
let edge_status = if pending_cuts.is_some_and(|cuts| cuts.contains(&edge_idx)) {
EdgeStatus::PendingCut
} else {
EdgeStatus::Idle
};
let edge_style = resolve_edge_style(
edge_style_fn.as_ref(),
theme,
edge_status,
start_info,
end_info,
);
let (shape, shadow_shape) =
edge_shapes(&start_pos, &end_pos, start_side, end_side, &edge_style);
for layer in edge_style.sdf_layers() {
match layer.geometry {
EdgeGeometry::Stroke => {
edge_strokes.push((shape.clone(), layer.style));
}
EdgeGeometry::Shadow => {
edge_shadows.push((shadow_shape.clone(), layer.style));
}
}
}
}
for (shape, style) in &edge_strokes {
bg.push(shape, style, [0.0, 0.0]);
}
for (shape, style) in &edge_shadows {
bg.push(shape, style, [0.0, 0.0]);
}
for geom in node_geoms.iter().flatten() {
if !geom.resolved.has_shadow() {
continue;
}
let (ox, oy) = geom.resolved.shadow_offset;
for band in geom.resolved.shadow_sdf_layers(geom.resolved.opacity) {
geom.push_body(&mut bg, &band, (ox, oy));
}
}
if let Some(tiling) = resolved_graph.tiling {
let tiling_shape = Shape::tiling(match tiling.kind {
TilingKind::Grid => {
Tiling::grid(tiling.spacing, tiling.spacing, tiling.thickness)
}
TilingKind::Dots => {
Tiling::dots(tiling.spacing, tiling.spacing, tiling.thickness)
}
TilingKind::Triangles => Tiling::triangles(tiling.spacing, tiling.thickness),
TilingKind::Hex => Tiling::hex(tiling.spacing, tiling.thickness),
});
let style = Style::solid(tiling.color);
bg.push(&tiling_shape, &style, [0.0, 0.0]);
}
bg
};
if !bg_layer.is_empty() {
let wo = layout.bounds().position();
let (cx, cy) = layer_camera(
render_context.camera_position,
render_context.camera_zoom,
wo,
layout.bounds(),
);
renderer.with_layer(layout.bounds(), |renderer| {
draw_sdf(
renderer,
&state.sdf_animated,
layout.bounds(),
bg_layer
.camera(cx, cy, render_context.camera_zoom)
.time(render_context.time),
);
});
}
if let Dragging::Edge(from_node_idx, from_pin_idx, _) = &state.dragging
&& let Some(cursor_pos) = cursor.position()
{
let from_pins = &node_pins[*from_node_idx];
if let Some((_, from_pin_state, (from_pin_pos, _))) = from_pins.get(*from_pin_idx) {
let from_offset = compute_node_offset(*from_node_idx);
let start_pos = (from_pin_pos.into_euclid().to_vector() + from_offset).to_point();
let end_pos: WorldPoint = cursor_layout(cursor_pos);
let drag_edge_style = match (
self.dragging_edge_style_fn.as_ref(),
pin_info::<P, UI>(from_pin_state),
) {
(Some(f), Some(info)) => f(theme, info),
_ => crate::style::default_edge_style(theme, EdgeStatus::Idle),
};
let from_side: u32 = from_pin_state.side.into();
let cursor_side: u32 = match from_pin_state.side {
PinSide::Left => 1,
PinSide::Right => 0,
PinSide::Top => 3,
PinSide::Bottom => 2,
PinSide::Row => 1,
};
let (start_pos, end_pos, start_side, end_side) =
if matches!(from_pin_state.direction, PinDirection::Input) {
(end_pos, start_pos, cursor_side, from_side)
} else {
(start_pos, end_pos, from_side, cursor_side)
};
let (shape, shadow_shape) =
edge_shapes(&start_pos, &end_pos, start_side, end_side, &drag_edge_style);
let mut drag_batch = SdfPrimitive::new();
push_edge_layers(&mut drag_batch, &shape, &shadow_shape, &drag_edge_style);
let wo = layout.bounds().position();
let (cx, cy) = layer_camera(
render_context.camera_position,
render_context.camera_zoom,
wo,
layout.bounds(),
);
renderer.with_layer(layout.bounds(), |renderer| {
draw_sdf(
renderer,
&state.sdf_animated,
layout.bounds(),
drag_batch
.camera(cx, cy, render_context.camera_zoom)
.time(render_context.time),
);
});
}
}
let t_after_background = Instant::now();
for &node_index in &z_indices {
let (_id, _position, element, _node_style, node_pin_style) = &self.nodes[node_index];
let Some(node_tree) = tree.children.get(node_index) else {
continue;
};
let Some(node_layout) = layout.children().nth(node_index) else {
continue;
};
let Some(geom) = node_geoms[node_index].as_ref() else {
continue;
};
let resolved = &geom.resolved;
let offset = geom.offset;
let node_position = geom.position;
let node_size = geom.size;
let opacity = resolved.opacity;
let cam_zoom = render_context.camera_zoom;
let pins = &node_pins[node_index];
let fill_pad = 2.0 / cam_zoom;
let fb = world_bbox_to_screen_bounds(
node_position.x,
node_position.y,
node_position.x + node_size.width,
node_position.y + node_size.height,
fill_pad,
&render_context,
);
if let Some(fill_clip) = clipped_shape_bounds(fb, layout.bounds()) {
let (cx, cy) = layer_camera(
render_context.camera_position,
cam_zoom,
layout.bounds().position(),
fill_clip,
);
renderer.with_layer(layout.bounds(), |renderer| {
let mut fill_batch = SdfPrimitive::new();
geom.push_body(
&mut fill_batch,
&resolved.fill_sdf_style(opacity),
(0.0, 0.0),
);
draw_sdf(
renderer,
&state.sdf_animated,
fill_clip,
fill_batch
.camera(cx, cy, cam_zoom)
.time(render_context.time),
);
});
}
let clipped_viewport = layout
.bounds()
.intersection(viewport)
.unwrap_or(Rectangle::new(layout.bounds().position(), Size::ZERO));
renderer.with_layer(layout.bounds(), |renderer| {
camera.draw_with::<_, Renderer>(
renderer,
&clipped_viewport,
cursor,
|renderer, viewport, cursor| {
let bounds = node_layout.bounds();
let screen_offset: Vector = offset.into_iced();
let node_clip = Rectangle {
x: bounds.x + screen_offset.x,
y: bounds.y + screen_offset.y,
width: bounds.width,
height: bounds.height,
};
let clip_bounds = node_clip
.intersection(viewport)
.unwrap_or(Rectangle::new(node_clip.position(), Size::ZERO));
let child_viewport = Rectangle {
x: viewport.x - screen_offset.x,
y: viewport.y - screen_offset.y,
width: viewport.width,
height: viewport.height,
};
renderer.with_layer(clip_bounds, |renderer| {
renderer.with_translation(screen_offset, |renderer| {
element.as_widget().draw(
node_tree,
renderer,
theme,
style,
node_layout,
cursor,
&child_viewport,
);
});
});
},
);
});
let has_border = resolved.border_pattern.thickness > 0.0;
let has_pins = !pins.is_empty();
if has_border || has_pins {
let mut fg_batch = SdfPrimitive::with_capacity(pins.len() * 2 + 2);
let mut fg_min_x = f32::MAX;
let mut fg_min_y = f32::MAX;
let mut fg_max_x = f32::MIN;
let mut fg_max_y = f32::MIN;
let border_layers = resolved.border_sdf_layers(opacity);
if !border_layers.is_empty() {
let border_pad = border_layers
.iter()
.map(|s| s.extent(true))
.fold(0.0_f32, f32::max)
+ 2.0 / cam_zoom;
let bb = world_bbox_to_screen_bounds(
node_position.x,
node_position.y,
node_position.x + node_size.width,
node_position.y + node_size.height,
border_pad,
&render_context,
);
for style in &border_layers {
geom.push_body(&mut fg_batch, style, (0.0, 0.0));
}
fg_min_x = fg_min_x.min(bb[0]);
fg_min_y = fg_min_y.min(bb[1]);
fg_max_x = fg_max_x.max(bb[0] + bb[2]);
fg_max_y = fg_max_y.max(bb[1] + bb[3]);
}
for (pin_idx, (_pin_index, pin_state, (pin_pos, _))) in pins.iter().enumerate() {
let is_valid_target = is_edge_dragging
&& state.valid_drop_targets.contains(&(node_index, pin_idx));
let pin_status = if is_valid_target {
PinStatus::ValidTarget
} else {
PinStatus::Idle
};
let pin_style = resolve_pin_style(
node_pin_style.as_ref(),
pin_state,
drag_source.as_ref(),
theme,
pin_status,
);
let indicator_r = pin_style.radius * 0.4;
let pin_world: WorldPoint =
(pin_pos.into_euclid().to_vector() + offset).to_point();
let pw = [pin_world.x, pin_world.y];
let (pin_shape, pin_place) = match pin_style.shape {
crate::style::PinShape::Square => {
let h = indicator_r * 0.7;
(Shape::rounded_box([2.0 * h, 2.0 * h], [0.0; 4]), pw)
}
_ => (Shape::circle(indicator_r), pw),
};
let pin_layers = pin_style.sdf_layers(pin_state.direction, indicator_r);
let pin_pad = indicator_r
+ pin_layers
.iter()
.map(|s| s.extent(true))
.fold(0.0_f32, f32::max)
+ 2.0 / cam_zoom;
let pin_bounds = world_bbox_to_screen_bounds(
pin_world.x - pin_pad,
pin_world.y - pin_pad,
pin_world.x + pin_pad,
pin_world.y + pin_pad,
0.0,
&render_context,
);
for style in &pin_layers {
fg_batch.push(&pin_shape, style, pin_place);
}
fg_min_x = fg_min_x.min(pin_bounds[0]);
fg_min_y = fg_min_y.min(pin_bounds[1]);
fg_max_x = fg_max_x.max(pin_bounds[0] + pin_bounds[2]);
fg_max_y = fg_max_y.max(pin_bounds[1] + pin_bounds[3]);
}
if let Some(fg_clip) = clipped_shape_bounds(
[fg_min_x, fg_min_y, fg_max_x - fg_min_x, fg_max_y - fg_min_y],
layout.bounds(),
) {
let (cx, cy) = layer_camera(
render_context.camera_position,
cam_zoom,
layout.bounds().position(),
fg_clip,
);
renderer.with_layer(layout.bounds(), |renderer| {
draw_sdf(
renderer,
&state.sdf_animated,
fg_clip,
fg_batch.camera(cx, cy, cam_zoom).time(render_context.time),
);
});
}
}
}
let t_after_fg = Instant::now();
if let Dragging::BoxSelect(start, _end) = &state.dragging {
let cursor_world = cursor.position().map(cursor_layout).unwrap_or(*start);
let (fill_color, border_color) = if let Some(ref style_fn) = self.box_select_style_fn {
style_fn(theme)
} else {
(
resolved_graph.selection_style.box_select_fill,
resolved_graph.selection_style.box_select_border,
)
};
let center = [
(start.x + cursor_world.x) * 0.5,
(start.y + cursor_world.y) * 0.5,
];
let half_size = [
((cursor_world.x - start.x) * 0.5).abs(),
((cursor_world.y - start.y) * 0.5).abs(),
];
let border_width = 1.5 / camera.zoom();
let select_bounds = world_bbox_to_screen_bounds(
start.x,
start.y,
cursor_world.x,
cursor_world.y,
border_width + 2.0 / camera.zoom(),
&render_context,
);
if let Some(select_clip) = clipped_shape_bounds(select_bounds, layout.bounds()) {
let select_shape =
Shape::rounded_box([half_size[0] * 2.0, half_size[1] * 2.0], [0.0; 4]);
let select_place = center;
let mut select_batch = SdfPrimitive::with_capacity(2);
select_batch.push(
&select_shape,
&Style::stroke(border_color, Pattern::solid(border_width)),
select_place,
);
select_batch.push(&select_shape, &Style::solid(fill_color), select_place);
let (cx, cy) = layer_camera(
render_context.camera_position,
render_context.camera_zoom,
layout.bounds().position(),
select_clip,
);
let select_primitive = select_batch
.camera(cx, cy, render_context.camera_zoom)
.time(render_context.time);
renderer.with_layer(layout.bounds(), |renderer| {
draw_sdf(renderer, &state.sdf_animated, select_clip, select_primitive);
});
}
}
if let Dragging::EdgeCutting { trail, .. } = &state.dragging
&& let Some(start) = trail.first()
{
let cursor_world = cursor.position().map(cursor_layout).unwrap_or(*start);
let cutting_color = if let Some(ref style_fn) = self.cutting_tool_style_fn {
style_fn(theme)
} else {
resolved_graph.selection_style.edge_cutting_color
};
let cutting_bounds = world_bbox_to_screen_bounds(
start.x,
start.y,
cursor_world.x,
cursor_world.y,
EDGE_CUT_LINE_WIDTH + 2.0 / render_context.camera_zoom,
&render_context,
);
if let Some(cutting_clip) = clipped_shape_bounds(cutting_bounds, layout.bounds()) {
let mut cutting_batch = SdfPrimitive::new();
cutting_batch.push(
&Shape::line([start.x, start.y], [cursor_world.x, cursor_world.y]),
&Style::stroke(cutting_color, Pattern::solid(EDGE_CUT_LINE_WIDTH)),
[0.0, 0.0],
);
let (cx, cy) = layer_camera(
render_context.camera_position,
render_context.camera_zoom,
layout.bounds().position(),
cutting_clip,
);
let cutting_primitive = cutting_batch
.camera(cx, cy, render_context.camera_zoom)
.time(render_context.time);
renderer.with_layer(layout.bounds(), |renderer| {
draw_sdf(
renderer,
&state.sdf_animated,
cutting_clip,
cutting_primitive,
);
});
}
}
if self.on_info.is_some() {
let viewport = layout.bounds();
let mut node_in_view = vec![false; node_geoms.len()];
let mut nodes_in = 0usize;
let mut pins_total = 0usize;
let mut pins_in = 0usize;
for (i, geom) in node_geoms.iter().enumerate() {
let Some(geom) = geom else { continue };
let bb = world_bbox_to_screen_bounds(
geom.position.x,
geom.position.y,
geom.position.x + geom.size.width,
geom.position.y + geom.size.height,
0.0,
&render_context,
);
let rect = Rectangle {
x: bb[0],
y: bb[1],
width: bb[2],
height: bb[3],
};
let in_view = rect.intersects(&viewport);
node_in_view[i] = in_view;
if in_view {
nodes_in += 1;
}
let pin_count = node_pins[i].len();
pins_total += pin_count;
if in_view {
pins_in += pin_count;
}
}
let edges_in = self
.edges
.iter()
.filter(|(_, from, to, _)| {
let visible = |id| self.node_index(id).is_some_and(|idx| node_in_view[idx]);
visible(&from.node_id) || visible(&to.node_id)
})
.count();
let counts = |total: usize, in_view: usize| Counts {
total,
in_view,
culled: total - in_view,
};
let sdf = iced_nodegraph_sdf::sdf_stats();
let info = GraphInfo {
nodes: counts(node_geoms.len(), nodes_in),
pins: counts(pins_total, pins_in),
edges: counts(self.edges.len(), edges_in),
timings: vec![
OpTiming {
label: "geometry",
duration: t_after_geom - t_geom_start,
},
OpTiming {
label: "background",
duration: t_after_background - t_after_geom,
},
OpTiming {
label: "foreground",
duration: t_after_fg - t_after_background,
},
OpTiming {
label: "sdf_prepare",
duration: std::time::Duration::from_micros(sdf.prepare_cpu_us),
},
],
sdf_entries: sdf.entry_count,
sdf_tiles: sdf.tile_count,
};
state.last_info.replace(Some(info));
}
}
}