Skip to content

Commit 7f66924

Browse files
authored
Merge branch 'master' into 2615-fill-tool-on-strokes
2 parents e14e6e1 + 8edf8de commit 7f66924

File tree

29 files changed

+1037
-321
lines changed

29 files changed

+1037
-321
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,8 @@ impl PreferencesDialogMessageHandler {
187187
];
188188

189189
let mut checkbox_id = CheckboxId::default();
190-
let vector_mesh_tooltip = "Allow tools to produce vector meshes, where more than two segments can connect to an anchor point.\n\nCurrently this does not properly handle line joins and fills.";
190+
let vector_mesh_tooltip =
191+
"Allow tools to produce vector meshes, where more than two segments can connect to an anchor point.\n\nCurrently this does not properly handle stroke joins and fills.";
191192
let vector_meshes = vec![
192193
Separator::new(SeparatorType::Unrelated).widget_holder(),
193194
Separator::new(SeparatorType::Unrelated).widget_holder(),

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ pub enum DocumentMessage {
121121
SelectedLayersReorder {
122122
relative_index_offset: isize,
123123
},
124+
ClipLayer {
125+
id: NodeId,
126+
},
124127
SelectLayer {
125128
id: NodeId,
126129
ctrl: bool,
@@ -142,6 +145,9 @@ pub enum DocumentMessage {
142145
SetOpacityForSelectedLayers {
143146
opacity: f64,
144147
},
148+
SetFillForSelectedLayers {
149+
fill: f64,
150+
},
145151
SetOverlaysVisibility {
146152
visible: bool,
147153
overlays_type: Option<OverlaysType>,

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

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Flo
2020
use crate::messages::portfolio::document::utility_types::nodes::RawBuffer;
2121
use crate::messages::portfolio::utility_types::PersistentData;
2222
use crate::messages::prelude::*;
23-
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_blend_mode, get_opacity};
23+
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_blend_mode, get_fill, get_opacity};
2424
use crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys;
2525
use crate::messages::tool::tool_messages::tool_prelude::Key;
2626
use crate::messages::tool::utility_types::ToolType;
@@ -1082,6 +1082,12 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
10821082
DocumentMessage::SelectedLayersReorder { relative_index_offset } => {
10831083
self.selected_layers_reorder(relative_index_offset, responses);
10841084
}
1085+
DocumentMessage::ClipLayer { id } => {
1086+
let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]);
1087+
1088+
responses.add(DocumentMessage::AddTransaction);
1089+
responses.add(GraphOperationMessage::ClipModeToggle { layer });
1090+
}
10851091
DocumentMessage::SelectLayer { id, ctrl, shift } => {
10861092
let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]);
10871093

@@ -1176,6 +1182,12 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
11761182
responses.add(GraphOperationMessage::OpacitySet { layer, opacity });
11771183
}
11781184
}
1185+
DocumentMessage::SetFillForSelectedLayers { fill } => {
1186+
let fill = fill.clamp(0., 1.);
1187+
for layer in self.network_interface.selected_nodes().selected_layers_except_artboards(&self.network_interface) {
1188+
responses.add(GraphOperationMessage::BlendingFillSet { layer, fill });
1189+
}
1190+
}
11791191
DocumentMessage::SetOverlaysVisibility { visible, overlays_type } => {
11801192
let visibility_settings = &mut self.overlays_visibility_settings;
11811193
let overlays_type = match overlays_type {
@@ -2553,38 +2565,47 @@ impl DocumentMessageHandler {
25532565
let selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&self.network_interface);
25542566

25552567
// Look up the current opacity and blend mode of the selected layers (if any), and split the iterator into the first tuple and the rest.
2556-
let mut opacity_and_blend_mode = selected_layers_except_artboards.map(|layer| {
2568+
let mut blending_options = selected_layers_except_artboards.map(|layer| {
25572569
(
25582570
get_opacity(layer, &self.network_interface).unwrap_or(100.),
2571+
get_fill(layer, &self.network_interface).unwrap_or(100.),
25592572
get_blend_mode(layer, &self.network_interface).unwrap_or_default(),
25602573
)
25612574
});
2562-
let first_opacity_and_blend_mode = opacity_and_blend_mode.next();
2563-
let result_opacity_and_blend_mode = opacity_and_blend_mode;
2575+
let first_blending_options = blending_options.next();
2576+
let result_blending_options = blending_options;
25642577

25652578
// If there are no selected layers, disable the opacity and blend mode widgets.
2566-
let disabled = first_opacity_and_blend_mode.is_none();
2579+
let disabled = first_blending_options.is_none();
25672580

25682581
// Amongst the selected layers, check if the opacities and blend modes are identical across all layers.
25692582
// The result is setting `option` and `blend_mode` to Some value if all their values are identical, or None if they are not.
25702583
// If identical, we display the value in the widget. If not, we display a dash indicating dissimilarity.
2571-
let (opacity, blend_mode) = first_opacity_and_blend_mode
2572-
.map(|(first_opacity, first_blend_mode)| {
2584+
let (opacity, fill, blend_mode) = first_blending_options
2585+
.map(|(first_opacity, first_fill, first_blend_mode)| {
25732586
let mut opacity_identical = true;
2587+
let mut fill_identical = true;
25742588
let mut blend_mode_identical = true;
25752589

2576-
for (opacity, blend_mode) in result_opacity_and_blend_mode {
2590+
for (opacity, fill, blend_mode) in result_blending_options {
25772591
if (opacity - first_opacity).abs() > (f64::EPSILON * 100.) {
25782592
opacity_identical = false;
25792593
}
2594+
if (fill - first_fill).abs() > (f64::EPSILON * 100.) {
2595+
fill_identical = false;
2596+
}
25802597
if blend_mode != first_blend_mode {
25812598
blend_mode_identical = false;
25822599
}
25832600
}
25842601

2585-
(opacity_identical.then_some(first_opacity), blend_mode_identical.then_some(first_blend_mode))
2602+
(
2603+
opacity_identical.then_some(first_opacity),
2604+
fill_identical.then_some(first_fill),
2605+
blend_mode_identical.then_some(first_blend_mode),
2606+
)
25862607
})
2587-
.unwrap_or((None, None));
2608+
.unwrap_or((None, None, None));
25882609

25892610
let blend_mode_menu_entries = BlendMode::list_svg_subset()
25902611
.iter()
@@ -2643,6 +2664,28 @@ impl DocumentMessageHandler {
26432664
.max_width(100)
26442665
.tooltip("Opacity")
26452666
.widget_holder(),
2667+
Separator::new(SeparatorType::Related).widget_holder(),
2668+
NumberInput::new(fill)
2669+
.label("Fill")
2670+
.unit("%")
2671+
.display_decimal_places(0)
2672+
.disabled(disabled)
2673+
.min(0.)
2674+
.max(100.)
2675+
.range_min(Some(0.))
2676+
.range_max(Some(100.))
2677+
.mode_range()
2678+
.on_update(|number_input: &NumberInput| {
2679+
if let Some(value) = number_input.value {
2680+
DocumentMessage::SetFillForSelectedLayers { fill: value / 100. }.into()
2681+
} else {
2682+
Message::NoOp
2683+
}
2684+
})
2685+
.on_commit(|_| DocumentMessage::AddTransaction.into())
2686+
.max_width(100)
2687+
.tooltip("Fill")
2688+
.widget_holder(),
26462689
];
26472690
let layers_panel_control_bar_left = WidgetLayout::new(vec![LayoutGroup::Row { widgets }]);
26482691

editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ pub enum GraphOperationMessage {
2121
layer: LayerNodeIdentifier,
2222
fill: Fill,
2323
},
24+
BlendingFillSet {
25+
layer: LayerNodeIdentifier,
26+
fill: f64,
27+
},
2428
OpacitySet {
2529
layer: LayerNodeIdentifier,
2630
opacity: f64,
@@ -29,6 +33,9 @@ pub enum GraphOperationMessage {
2933
layer: LayerNodeIdentifier,
3034
blend_mode: BlendMode,
3135
},
36+
ClipModeToggle {
37+
layer: LayerNodeIdentifier,
38+
},
3239
StrokeSet {
3340
layer: LayerNodeIdentifier,
3441
stroke: Stroke,

editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
55
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface, OutputConnector};
66
use crate::messages::portfolio::document::utility_types::nodes::CollapsedLayers;
77
use crate::messages::prelude::*;
8+
use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode;
89
use glam::{DAffine2, DVec2, IVec2};
910
use graph_craft::document::{NodeId, NodeInput};
1011
use graphene_core::Color;
1112
use graphene_core::renderer::Quad;
1213
use graphene_core::text::{Font, TypesettingConfig};
13-
use graphene_core::vector::style::{Fill, Gradient, GradientStops, GradientType, LineCap, LineJoin, Stroke};
14+
use graphene_core::vector::style::{Fill, Gradient, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
1415
use graphene_std::vector::convert_usvg_path;
1516

1617
#[derive(Debug, Clone)]
@@ -41,6 +42,11 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
4142
modify_inputs.fill_set(fill);
4243
}
4344
}
45+
GraphOperationMessage::BlendingFillSet { layer, fill } => {
46+
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
47+
modify_inputs.blending_fill_set(fill);
48+
}
49+
}
4450
GraphOperationMessage::OpacitySet { layer, opacity } => {
4551
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
4652
modify_inputs.opacity_set(opacity);
@@ -51,6 +57,12 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
5157
modify_inputs.blend_mode_set(blend_mode);
5258
}
5359
}
60+
GraphOperationMessage::ClipModeToggle { layer } => {
61+
let clip_mode = get_clip_mode(layer, network_interface);
62+
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
63+
modify_inputs.clip_mode_toggle(clip_mode);
64+
}
65+
}
5466
GraphOperationMessage::StrokeSet { layer, stroke } => {
5567
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
5668
modify_inputs.stroke_set(stroke);
@@ -381,18 +393,20 @@ fn apply_usvg_stroke(stroke: &usvg::Stroke, modify_inputs: &mut ModifyInputsCont
381393
weight: stroke.width().get() as f64,
382394
dash_lengths: stroke.dasharray().as_ref().map(|lengths| lengths.iter().map(|&length| length as f64).collect()).unwrap_or_default(),
383395
dash_offset: stroke.dashoffset() as f64,
384-
line_cap: match stroke.linecap() {
385-
usvg::LineCap::Butt => LineCap::Butt,
386-
usvg::LineCap::Round => LineCap::Round,
387-
usvg::LineCap::Square => LineCap::Square,
396+
cap: match stroke.linecap() {
397+
usvg::LineCap::Butt => StrokeCap::Butt,
398+
usvg::LineCap::Round => StrokeCap::Round,
399+
usvg::LineCap::Square => StrokeCap::Square,
388400
},
389-
line_join: match stroke.linejoin() {
390-
usvg::LineJoin::Miter => LineJoin::Miter,
391-
usvg::LineJoin::MiterClip => LineJoin::Miter,
392-
usvg::LineJoin::Round => LineJoin::Round,
393-
usvg::LineJoin::Bevel => LineJoin::Bevel,
401+
join: match stroke.linejoin() {
402+
usvg::LineJoin::Miter => StrokeJoin::Miter,
403+
usvg::LineJoin::MiterClip => StrokeJoin::Miter,
404+
usvg::LineJoin::Round => StrokeJoin::Round,
405+
usvg::LineJoin::Bevel => StrokeJoin::Bevel,
394406
},
395-
line_join_miter_limit: stroke.miterlimit().get() as f64,
407+
join_miter_limit: stroke.miterlimit().get() as f64,
408+
align: StrokeAlign::Center,
409+
paint_order: PaintOrder::StrokeAbove,
396410
transform,
397411
non_scaling: false,
398412
})

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

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ use graphene_core::vector::brush_stroke::BrushStroke;
1515
use graphene_core::vector::style::{Fill, Stroke};
1616
use graphene_core::vector::{PointId, VectorModificationType};
1717
use graphene_core::{Artboard, Color};
18-
use graphene_std::GraphicGroupTable;
1918
use graphene_std::vector::{VectorData, VectorDataTable};
19+
use graphene_std::{GraphicGroupTable, NodeInputDecleration};
2020

2121
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
2222
pub enum TransformIn {
@@ -58,13 +58,13 @@ impl<'a> ModifyInputsContext<'a> {
5858
/// Non layer nodes directly upstream of a layer are treated as part of that layer. See insert_index == 2 in the diagram
5959
/// -----> Post node
6060
/// | if insert_index == 0, return (Post node, Some(Layer1))
61-
/// -> Layer1
61+
/// -> Layer1
6262
/// ↑ if insert_index == 1, return (Layer1, Some(Layer2))
63-
/// -> Layer2
63+
/// -> Layer2
6464
/// ↑
6565
/// -> NonLayerNode
6666
/// ↑ if insert_index == 2, return (NonLayerNode, Some(Layer3))
67-
/// -> Layer3
67+
/// -> Layer3
6868
/// if insert_index == 3, return (Layer3, None)
6969
pub fn get_post_node_with_index(network_interface: &NodeNetworkInterface, parent: LayerNodeIdentifier, insert_index: usize) -> InputConnector {
7070
let mut post_node_input_connector = if parent == LayerNodeIdentifier::ROOT_PARENT {
@@ -333,37 +333,52 @@ impl<'a> ModifyInputsContext<'a> {
333333
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Fill(fill), false), false);
334334
}
335335

336+
pub fn blend_mode_set(&mut self, blend_mode: BlendMode) {
337+
let Some(blend_node_id) = self.existing_node_id("Blending", true) else { return };
338+
let input_connector = InputConnector::node(blend_node_id, 1);
339+
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::BlendMode(blend_mode), false), false);
340+
}
341+
336342
pub fn opacity_set(&mut self, opacity: f64) {
337-
let Some(opacity_node_id) = self.existing_node_id("Opacity", true) else { return };
338-
let input_connector = InputConnector::node(opacity_node_id, 1);
343+
let Some(blend_node_id) = self.existing_node_id("Blending", true) else { return };
344+
let input_connector = InputConnector::node(blend_node_id, 2);
339345
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(opacity * 100.), false), false);
340346
}
341347

342-
pub fn blend_mode_set(&mut self, blend_mode: BlendMode) {
343-
let Some(blend_mode_node_id) = self.existing_node_id("Blend Mode", true) else {
344-
return;
345-
};
346-
let input_connector = InputConnector::node(blend_mode_node_id, 1);
347-
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::BlendMode(blend_mode), false), false);
348+
pub fn blending_fill_set(&mut self, fill: f64) {
349+
let Some(blend_node_id) = self.existing_node_id("Blending", true) else { return };
350+
let input_connector = InputConnector::node(blend_node_id, 3);
351+
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(fill * 100.), false), false);
352+
}
353+
354+
pub fn clip_mode_toggle(&mut self, clip_mode: Option<bool>) {
355+
let clip = !clip_mode.unwrap_or(false);
356+
let Some(clip_node_id) = self.existing_node_id("Blending", true) else { return };
357+
let input_connector = InputConnector::node(clip_node_id, 4);
358+
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Bool(clip), false), false);
348359
}
349360

350361
pub fn stroke_set(&mut self, stroke: Stroke) {
351362
let Some(stroke_node_id) = self.existing_node_id("Stroke", true) else { return };
352363

353-
let input_connector = InputConnector::node(stroke_node_id, 1);
364+
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::ColorInput::<Option<Color>>::INDEX);
354365
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::OptionalColor(stroke.color), false), true);
355-
let input_connector = InputConnector::node(stroke_node_id, 2);
366+
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::WeightInput::INDEX);
356367
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.weight), false), true);
357-
let input_connector = InputConnector::node(stroke_node_id, 3);
368+
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::AlignInput::INDEX);
369+
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::StrokeAlign(stroke.align), false), false);
370+
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::CapInput::INDEX);
371+
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::StrokeCap(stroke.cap), false), true);
372+
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::JoinInput::INDEX);
373+
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::StrokeJoin(stroke.join), false), true);
374+
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::MiterLimitInput::INDEX);
375+
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.join_miter_limit), false), false);
376+
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintOrderInput::INDEX);
377+
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::PaintOrder(stroke.paint_order), false), false);
378+
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashLengthsInput::INDEX);
358379
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::VecF64(stroke.dash_lengths), false), true);
359-
let input_connector = InputConnector::node(stroke_node_id, 4);
380+
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashOffsetInput::INDEX);
360381
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.dash_offset), false), true);
361-
let input_connector = InputConnector::node(stroke_node_id, 5);
362-
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::LineCap(stroke.line_cap), false), true);
363-
let input_connector = InputConnector::node(stroke_node_id, 6);
364-
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::LineJoin(stroke.line_join), false), true);
365-
let input_connector = InputConnector::node(stroke_node_id, 7);
366-
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.line_join_miter_limit), false), false);
367382
}
368383

369384
pub fn stroke_color_set(&mut self, color: Option<Color>) {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{
1515
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry};
1616
use crate::messages::prelude::*;
1717
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
18+
use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode;
1819
use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion};
1920
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
2021
use glam::{DAffine2, DVec2, IVec2};
@@ -2442,6 +2443,7 @@ impl NodeGraphMessageHandler {
24422443
}
24432444
});
24442445

2446+
let clippable = layer.can_be_clipped(network_interface.document_metadata());
24452447
let data = LayerPanelEntry {
24462448
id: node_id,
24472449
alias: network_interface.display_name(&node_id, &[]),
@@ -2461,6 +2463,8 @@ impl NodeGraphMessageHandler {
24612463
selected: selected_layers.contains(&node_id),
24622464
ancestor_of_selected: ancestors_of_selected.contains(&node_id),
24632465
descendant_of_selected: descendants_of_selected.contains(&node_id),
2466+
clipped: get_clip_mode(layer, network_interface).unwrap_or(false) && clippable,
2467+
clippable,
24642468
};
24652469
responses.add(FrontendMessage::UpdateDocumentLayerDetails { data });
24662470
}

0 commit comments

Comments
 (0)