Skip to content

Commit 8b94dec

Browse files
committed
Add preference for wire style
1 parent dce08de commit 8b94dec

11 files changed

Lines changed: 173 additions & 23 deletions

File tree

editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::messages::layout::utility_types::widget_prelude::*;
2+
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
23
use crate::messages::preferences::SelectionMode;
34
use crate::messages::prelude::*;
45

@@ -32,6 +33,29 @@ impl PreferencesDialogMessageHandler {
3233
const TITLE: &'static str = "Editor Preferences";
3334

3435
fn layout(&self, preferences: &PreferencesMessageHandler) -> Layout {
36+
// =====
37+
// INPUT
38+
// =====
39+
40+
let zoom_with_scroll_tooltip = "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads)";
41+
let input_section = vec![TextLabel::new("Input").italic(true).widget_holder()];
42+
let zoom_with_scroll = vec![
43+
CheckboxInput::new(preferences.zoom_with_scroll)
44+
.tooltip(zoom_with_scroll_tooltip)
45+
.on_update(|checkbox_input: &CheckboxInput| {
46+
PreferencesMessage::ModifyLayout {
47+
zoom_with_scroll: checkbox_input.checked,
48+
}
49+
.into()
50+
})
51+
.widget_holder(),
52+
TextLabel::new("Zoom with Scroll").table_align(true).tooltip(zoom_with_scroll_tooltip).widget_holder(),
53+
];
54+
55+
// =========
56+
// SELECTION
57+
// =========
58+
3559
let selection_section = vec![TextLabel::new("Selection").italic(true).widget_holder()];
3660
let selection_mode = RadioInput::new(vec![
3761
RadioEntryData::new(SelectionMode::Touched.to_string())
@@ -65,20 +89,28 @@ impl PreferencesDialogMessageHandler {
6589
.selected_index(Some(preferences.selection_mode as u32))
6690
.widget_holder();
6791

68-
let zoom_with_scroll_tooltip = "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads)";
69-
let input_section = vec![TextLabel::new("Input").italic(true).widget_holder()];
70-
let zoom_with_scroll = vec![
71-
CheckboxInput::new(preferences.zoom_with_scroll)
72-
.tooltip(zoom_with_scroll_tooltip)
73-
.on_update(|checkbox_input: &CheckboxInput| {
74-
PreferencesMessage::ModifyLayout {
75-
zoom_with_scroll: checkbox_input.checked,
76-
}
77-
.into()
78-
})
79-
.widget_holder(),
80-
TextLabel::new("Zoom with Scroll").table_align(true).tooltip(zoom_with_scroll_tooltip).widget_holder(),
81-
];
92+
// ================
93+
// NODE GRAPH WIRES
94+
// ================
95+
96+
let node_graph_section_tooltip = "Display wires in the node graph as grid-aligned straight lines or as smooth curves";
97+
let node_graph_section = vec![TextLabel::new("Node Graph Wires").tooltip(node_graph_section_tooltip).italic(true).widget_holder()];
98+
let graph_wire_style = RadioInput::new(vec![
99+
RadioEntryData::new(GraphWireStyle::Straight.to_string())
100+
.label(GraphWireStyle::Straight.to_string())
101+
.tooltip(GraphWireStyle::Straight.tooltip_description())
102+
.on_update(move |_| PreferencesMessage::GraphWireStyle { style: GraphWireStyle::Straight }.into()),
103+
RadioEntryData::new(GraphWireStyle::Curved.to_string())
104+
.label(GraphWireStyle::Curved.to_string())
105+
.tooltip(GraphWireStyle::Curved.tooltip_description())
106+
.on_update(move |_| PreferencesMessage::GraphWireStyle { style: GraphWireStyle::Curved }.into()),
107+
])
108+
.selected_index(Some(preferences.graph_wire_style as u32))
109+
.widget_holder();
110+
111+
// ============
112+
// EXPERIMENTAL
113+
// ============
82114

83115
let vello_tooltip = "Use the experimental Vello renderer (your browser must support WebGPU)";
84116
let renderer_section = vec![TextLabel::new("Experimental").italic(true).widget_holder()];
@@ -126,10 +158,12 @@ impl PreferencesDialogMessageHandler {
126158
// ];
127159

128160
Layout::WidgetLayout(WidgetLayout::new(vec![
129-
LayoutGroup::Row { widgets: selection_section },
130-
LayoutGroup::Row { widgets: vec![selection_mode] },
131161
LayoutGroup::Row { widgets: input_section },
132162
LayoutGroup::Row { widgets: zoom_with_scroll },
163+
LayoutGroup::Row { widgets: selection_section },
164+
LayoutGroup::Row { widgets: vec![selection_mode] },
165+
LayoutGroup::Row { widgets: node_graph_section },
166+
LayoutGroup::Row { widgets: vec![graph_wire_style] },
133167
LayoutGroup::Row { widgets: renderer_section },
134168
LayoutGroup::Row { widgets: use_vello },
135169
LayoutGroup::Row { widgets: vector_meshes },

editor/src/messages/frontend/frontend_message.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ pub enum FrontendMessage {
250250
UpdateNodeGraph {
251251
nodes: Vec<FrontendNode>,
252252
wires: Vec<FrontendNodeWire>,
253+
#[serde(rename = "wiresCurvedNotStraight")]
254+
wires_curved_not_straight: bool,
253255
},
254256
UpdateNodeGraphControlBarLayout {
255257
#[serde(rename = "layoutTarget")]

editor/src/messages/portfolio/document/document_message_handler.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub struct DocumentMessageData<'a> {
4242
pub persistent_data: &'a PersistentData,
4343
pub executor: &'a mut NodeGraphExecutor,
4444
pub current_tool: &'a ToolType,
45+
pub preferences: &'a PreferencesMessageHandler,
4546
}
4647

4748
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
@@ -172,6 +173,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
172173
persistent_data,
173174
executor,
174175
current_tool,
176+
preferences,
175177
} = data;
176178

177179
let selected_nodes_bounding_box_viewport = self.network_interface.selected_nodes_bounding_box_viewport(&self.breadcrumb_network_path);
@@ -222,6 +224,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
222224
graph_view_overlay_open: self.graph_view_overlay_open,
223225
graph_fade_artwork_percentage: self.graph_fade_artwork_percentage,
224226
navigation_handler: &self.navigation_handler,
227+
preferences,
225228
},
226229
);
227230
}

editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub struct NodeGraphHandlerData<'a> {
3535
pub graph_view_overlay_open: bool,
3636
pub graph_fade_artwork_percentage: f64,
3737
pub navigation_handler: &'a NavigationMessageHandler,
38+
pub preferences: &'a PreferencesMessageHandler,
3839
}
3940

4041
#[derive(Debug, Clone)]
@@ -93,6 +94,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
9394
graph_view_overlay_open,
9495
graph_fade_artwork_percentage,
9596
navigation_handler,
97+
preferences,
9698
} = data;
9799

98100
match message {
@@ -1293,8 +1295,14 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
12931295
let wires = Self::collect_wires(network_interface, breadcrumb_network_path);
12941296
let nodes = self.collect_nodes(network_interface, breadcrumb_network_path);
12951297
let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path);
1298+
let wires_curved_not_straight = preferences.graph_wire_style.is_curved();
1299+
12961300
responses.add(NodeGraphMessage::UpdateImportsExports);
1297-
responses.add(FrontendMessage::UpdateNodeGraph { nodes, wires });
1301+
responses.add(FrontendMessage::UpdateNodeGraph {
1302+
nodes,
1303+
wires,
1304+
wires_curved_not_straight,
1305+
});
12981306
responses.add(FrontendMessage::UpdateLayerWidths {
12991307
layer_widths,
13001308
chain_widths,

editor/src/messages/portfolio/document/node_graph/utility_types.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,32 @@ pub enum Direction {
200200
Left,
201201
Right,
202202
}
203+
204+
#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
205+
pub enum GraphWireStyle {
206+
#[default]
207+
Straight = 0,
208+
Curved = 1,
209+
}
210+
211+
impl std::fmt::Display for GraphWireStyle {
212+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213+
match self {
214+
GraphWireStyle::Straight => write!(f, "Straight"),
215+
GraphWireStyle::Curved => write!(f, "Curved"),
216+
}
217+
}
218+
}
219+
220+
impl GraphWireStyle {
221+
pub fn tooltip_description(&self) -> &'static str {
222+
match self {
223+
GraphWireStyle::Straight => "Wires follow the grid in straight line runs",
224+
GraphWireStyle::Curved => "Wires bend smoothly between nodes",
225+
}
226+
}
227+
228+
pub fn is_curved(&self) -> bool {
229+
*self == GraphWireStyle::Curved
230+
}
231+
}

editor/src/messages/portfolio/portfolio_message_handler.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
106106
persistent_data: &self.persistent_data,
107107
executor: &mut self.executor,
108108
current_tool,
109+
preferences,
109110
};
110111
document.process_message(message, responses, document_inputs)
111112
}
@@ -121,6 +122,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
121122
persistent_data: &self.persistent_data,
122123
executor: &mut self.executor,
123124
current_tool,
125+
preferences,
124126
};
125127
document.process_message(message, responses, document_inputs)
126128
}

editor/src/messages/preferences/preferences_message.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
12
use crate::messages::preferences::SelectionMode;
23
use crate::messages::prelude::*;
34

@@ -13,6 +14,7 @@ pub enum PreferencesMessage {
1314
SelectionMode { selection_mode: SelectionMode },
1415
VectorMeshes { enabled: bool },
1516
ModifyLayout { zoom_with_scroll: bool },
17+
GraphWireStyle { style: GraphWireStyle },
1618
// ImaginateRefreshFrequency { seconds: f64 },
1719
// ImaginateServerHostname { hostname: String },
1820
}

editor/src/messages/preferences/preferences_message_handler.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::messages::input_mapper::key_mapping::MappingVariant;
2+
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
23
use crate::messages::preferences::SelectionMode;
34
use crate::messages::prelude::*;
45

@@ -12,6 +13,7 @@ pub struct PreferencesMessageHandler {
1213
pub zoom_with_scroll: bool,
1314
pub use_vello: bool,
1415
pub vector_meshes: bool,
16+
pub graph_wire_style: GraphWireStyle,
1517
}
1618

1719
impl PreferencesMessageHandler {
@@ -37,13 +39,15 @@ impl Default for PreferencesMessageHandler {
3739
imaginate_hostname: host_name,
3840
use_vello,
3941
} = Default::default();
42+
4043
Self {
4144
imaginate_server_hostname: host_name,
4245
imaginate_refresh_frequency: 1.,
4346
selection_mode: SelectionMode::Touched,
4447
zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll),
4548
use_vello,
4649
vector_meshes: false,
50+
graph_wire_style: GraphWireStyle::default(),
4751
}
4852
}
4953
}
@@ -95,6 +99,10 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
9599
PreferencesMessage::SelectionMode { selection_mode } => {
96100
self.selection_mode = selection_mode;
97101
}
102+
PreferencesMessage::GraphWireStyle { style } => {
103+
self.graph_wire_style = style;
104+
responses.add(NodeGraphMessage::SendGraph);
105+
}
98106
}
99107
// TODO: Reenable when Imaginate is restored (and move back up one line since the auto-formatter doesn't like it in that block)
100108
// PreferencesMessage::ImaginateRefreshFrequency { seconds } => {

frontend/src/components/views/Graph.svelte

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,13 @@
159159
return { nodeOutput, nodeInput };
160160
}
161161
162-
function createWirePath(outputPort: SVGSVGElement, inputPort: SVGSVGElement, verticalOut: boolean, verticalIn: boolean, dashed: boolean): WirePath {
162+
function createWirePath(outputPort: SVGSVGElement, inputPort: SVGSVGElement, verticalOut: boolean, verticalIn: boolean, dashed: boolean, curvedNotStraight: boolean): WirePath {
163163
const inputPortRect = inputPort.getBoundingClientRect();
164164
const outputPortRect = outputPort.getBoundingClientRect();
165165
166-
const pathString = buildWirePathString(outputPortRect, inputPortRect, verticalOut, verticalIn);
166+
const pathString = curvedNotStraight
167+
? buildCurvedWirePathString(outputPortRect, inputPortRect, verticalOut, verticalIn)
168+
: buildStraightWirePathString(outputPortRect, inputPortRect, verticalOut, verticalIn);
167169
const dataType = (outputPort.getAttribute("data-datatype") as FrontendGraphDataType) || "General";
168170
const thick = verticalIn && verticalOut;
169171
@@ -184,7 +186,7 @@
184186
const wireEndNode = wire.wireEnd.nodeId !== undefined ? $nodeGraph.nodes.get(wire.wireEnd.nodeId) : undefined;
185187
const wireEnd = (wireEndNode?.isLayer && Number(wire.wireEnd.index) === 0) || false;
186188
187-
return [createWirePath(nodeOutput, nodeInput, wireStart, wireEnd, wire.dashed)];
189+
return [createWirePath(nodeOutput, nodeInput, wireStart, wireEnd, wire.dashed, $nodeGraph.wiresCurvedNotStraight)];
188190
});
189191
}
190192
@@ -198,7 +200,7 @@
198200
return iconMap[icon] || "NodeNodes";
199201
}
200202
201-
function buildWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
203+
function buildStraightWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
202204
if (!nodesContainer) return [];
203205
204206
const VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP = 1;
@@ -426,8 +428,8 @@
426428
return construct([x1, y1], [x20, y1], [x20, y3], [x4, y3]);
427429
}
428430
429-
function buildWirePathString(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): string {
430-
const locations = buildWirePathLocations(outputBounds, inputBounds, verticalOut, verticalIn);
431+
function buildStraightWirePathString(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): string {
432+
const locations = buildStraightWirePathLocations(outputBounds, inputBounds, verticalOut, verticalIn);
431433
if (locations.length === 0) return "[error]";
432434
if (locations.length === 2) return `M${locations[0].x},${locations[0].y} L${locations[1].x},${locations[1].y}`;
433435
@@ -461,6 +463,62 @@
461463
return path;
462464
}
463465
466+
function buildCurvedWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
467+
if (!nodesContainer) return [];
468+
469+
const VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP = 1;
470+
471+
const containerBounds = nodesContainer.getBoundingClientRect();
472+
473+
const outX = verticalOut ? outputBounds.x + outputBounds.width / 2 : outputBounds.x + outputBounds.width - 1;
474+
const outY = verticalOut ? outputBounds.y + VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : outputBounds.y + outputBounds.height / 2;
475+
const outConnectorX = (outX - containerBounds.x) / $nodeGraph.transform.scale;
476+
const outConnectorY = (outY - containerBounds.y) / $nodeGraph.transform.scale;
477+
478+
const inX = verticalIn ? inputBounds.x + inputBounds.width / 2 : inputBounds.x + 1;
479+
const inY = verticalIn ? inputBounds.y + inputBounds.height - VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : inputBounds.y + inputBounds.height / 2;
480+
const inConnectorX = (inX - containerBounds.x) / $nodeGraph.transform.scale;
481+
const inConnectorY = (inY - containerBounds.y) / $nodeGraph.transform.scale;
482+
const horizontalGap = Math.abs(outConnectorX - inConnectorX);
483+
const verticalGap = Math.abs(outConnectorY - inConnectorY);
484+
485+
const curveLength = 24;
486+
const curveFalloffRate = curveLength * Math.PI * 2;
487+
488+
const horizontalCurveAmount = -(2 ** ((-10 * horizontalGap) / curveFalloffRate)) + 1;
489+
const verticalCurveAmount = -(2 ** ((-10 * verticalGap) / curveFalloffRate)) + 1;
490+
const horizontalCurve = horizontalCurveAmount * curveLength;
491+
const verticalCurve = verticalCurveAmount * curveLength;
492+
493+
return [
494+
{ x: outConnectorX, y: outConnectorY },
495+
{ x: verticalOut ? outConnectorX : outConnectorX + horizontalCurve, y: verticalOut ? outConnectorY - verticalCurve : outConnectorY },
496+
{ x: verticalIn ? inConnectorX : inConnectorX - horizontalCurve, y: verticalIn ? inConnectorY + verticalCurve : inConnectorY },
497+
{ x: inConnectorX, y: inConnectorY },
498+
];
499+
}
500+
501+
function buildCurvedWirePathString(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): string {
502+
const locations = buildCurvedWirePathLocations(outputBounds, inputBounds, verticalOut, verticalIn);
503+
if (locations.length === 0) return "[error]";
504+
505+
const SMOOTHING = 0.5;
506+
const delta01 = { x: (locations[1].x - locations[0].x) * SMOOTHING, y: (locations[1].y - locations[0].y) * SMOOTHING };
507+
const delta23 = { x: (locations[3].x - locations[2].x) * SMOOTHING, y: (locations[3].y - locations[2].y) * SMOOTHING };
508+
509+
return `
510+
M${locations[0].x},${locations[0].y}
511+
L${locations[1].x},${locations[1].y}
512+
C${locations[1].x + delta01.x},${locations[1].y + delta01.y}
513+
${locations[2].x - delta23.x},${locations[2].y - delta23.y}
514+
${locations[2].x},${locations[2].y}
515+
L${locations[3].x},${locations[3].y}
516+
`
517+
.split("\n")
518+
.map((line) => line.trim())
519+
.join(" ");
520+
}
521+
464522
function toggleLayerDisplay(displayAsLayer: boolean, toggleId: bigint) {
465523
let node = $nodeGraph.nodes.get(toggleId);
466524
if (node) editor.handle.setToNodeOrLayer(node.id, displayAsLayer);

frontend/src/messages.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ export class UpdateNodeGraph extends JsMessage {
100100

101101
@Type(() => FrontendNodeWire)
102102
readonly wires!: FrontendNodeWire[];
103+
104+
readonly wiresCurvedNotStraight!: boolean;
103105
}
104106

105107
export class UpdateNodeGraphTransform extends JsMessage {

0 commit comments

Comments
 (0)