mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Fix style controls corrupting stack nodes like Transform that can't host blending or paint nodes (#4428)
* Stop chain node insertion from stranding a node on layers without a chain * Gate the Layers panel blending controls on whether the layer can host them * Gate the fill and stroke setters on whether the layer can host paint nodes * Make the tool options fill and stroke controls treat layers that cannot take paint as unselected * Remove the unused first_selected_stroke_weight helper
This commit is contained in:
committed by
Dennis Kobert
parent
0e0687c8cb
commit
ae1ff71f45
@@ -3409,6 +3409,11 @@ impl DocumentMessageHandler {
|
||||
let selected_nodes = self.network_interface.selected_nodes();
|
||||
let selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&self.network_interface);
|
||||
|
||||
// A layer whose chain cannot carry blending nodes has nowhere to put the value, so it disqualifies the whole selection
|
||||
let all_layers_support_blending = selected_nodes
|
||||
.selected_layers_except_artboards(&self.network_interface)
|
||||
.all(|layer| self.network_interface.layer_hosts_blending_nodes(&layer.to_node(), &[]));
|
||||
|
||||
// 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.
|
||||
let mut blending_options = selected_layers_except_artboards.map(|layer| {
|
||||
(
|
||||
@@ -3420,8 +3425,8 @@ impl DocumentMessageHandler {
|
||||
let first_blending_options = blending_options.next();
|
||||
let result_blending_options = blending_options;
|
||||
|
||||
// If there are no selected layers, disable the opacity and blend mode widgets.
|
||||
let disabled = first_blending_options.is_none();
|
||||
// If there are no selected layers, or any of them cannot host the nodes, disable the opacity and blend mode widgets.
|
||||
let disabled = first_blending_options.is_none() || !all_layers_support_blending;
|
||||
|
||||
// Amongst the selected layers, check if the opacities and blend modes are identical across all layers.
|
||||
// The result is setting `option` and `blend_mode` to Some value if all their values are identical, or None if they are not.
|
||||
|
||||
@@ -348,6 +348,16 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
self.existing_node_id(&DefinitionIdentifier::ProtoNode(reference), create_if_nonexistent)
|
||||
}
|
||||
|
||||
/// The same as [`Self::existing_proto_node_id`], but yielding `None` on layers whose chain cannot host the node.
|
||||
fn existing_chain_hosted_node_id(&mut self, reference: ProtoNodeIdentifier, create_if_nonexistent: bool) -> Option<NodeId> {
|
||||
let output_layer = self.get_output_layer()?;
|
||||
if !self.network_interface.layer_chain_hosts_node(&output_layer.to_node(), &[], &reference) {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.existing_proto_node_id(reference, create_if_nonexistent)
|
||||
}
|
||||
|
||||
/// Gets the node id of a document node with a specific reference that is upstream from the layer node, and optionally creates it if it does not exist.
|
||||
fn existing_node_id(&mut self, reference: &DefinitionIdentifier, create_if_nonexistent: bool) -> Option<NodeId> {
|
||||
// Start from the layer node or export
|
||||
@@ -398,6 +408,9 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
return None;
|
||||
};
|
||||
|
||||
// Without a secondary input there is no chain to hold the node, so inserting it would strand it at the graph origin
|
||||
self.network_interface.input_from_connector(&InputConnector::layer_secondary_input(output_layer.to_node()), &[])?;
|
||||
|
||||
// If inserting a 'Path' node, insert a 'Combine Paths' node if the type is `Graphic`.
|
||||
// TODO: Allow the 'Path' node to operate on `List` data by utilizing the reference (index or ID?) for each item.
|
||||
if node_definition.identifier == "Path" {
|
||||
@@ -419,7 +432,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
}
|
||||
|
||||
pub fn fill_color_set(&mut self, color: Option<Color>) {
|
||||
let Some(fill_node_id) = self.existing_proto_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else {
|
||||
let Some(fill_node_id) = self.existing_chain_hosted_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else {
|
||||
return;
|
||||
};
|
||||
let input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput);
|
||||
@@ -434,7 +447,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
}
|
||||
|
||||
pub fn fill_gradient_set(&mut self, gradient: Gradient, gradient_form: GradientForm, settings: GradientSettings, transform: DAffine2) {
|
||||
let Some(fill_node_id) = self.existing_proto_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else {
|
||||
let Some(fill_node_id) = self.existing_chain_hosted_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else {
|
||||
return;
|
||||
};
|
||||
let backup_input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupGradientInput);
|
||||
@@ -478,7 +491,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
}
|
||||
|
||||
pub fn blend_mode_set(&mut self, blend_mode: BlendMode) {
|
||||
let Some(blend_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::blend_mode::IDENTIFIER, true) else {
|
||||
let Some(blend_node_id) = self.existing_chain_hosted_node_id(graphene_std::blending_nodes::blend_mode::IDENTIFIER, true) else {
|
||||
return;
|
||||
};
|
||||
let input_connector = InputConnector::node(blend_node_id, graphene_std::blending_nodes::blend_mode::BlendModeInput);
|
||||
@@ -486,7 +499,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
}
|
||||
|
||||
pub fn opacity_set(&mut self, opacity: f64) {
|
||||
let Some(opacity_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::opacity::IDENTIFIER, true) else {
|
||||
let Some(opacity_node_id) = self.existing_chain_hosted_node_id(graphene_std::blending_nodes::opacity::IDENTIFIER, true) else {
|
||||
return;
|
||||
};
|
||||
// Enable the `has_opacity` checkbox so the value is applied
|
||||
@@ -505,9 +518,9 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
pub fn opacity_fill_set(&mut self, fill: f64) {
|
||||
// Reuse an existing Opacity node to avoid a redundant chain walk on slider drags
|
||||
let identifier = graphene_std::blending_nodes::opacity::IDENTIFIER;
|
||||
let existing = self.existing_proto_node_id(identifier.clone(), false);
|
||||
let existing = self.existing_chain_hosted_node_id(identifier.clone(), false);
|
||||
let existed = existing.is_some();
|
||||
let Some(opacity_node_id) = existing.or_else(|| self.existing_proto_node_id(identifier, true)) else {
|
||||
let Some(opacity_node_id) = existing.or_else(|| self.existing_chain_hosted_node_id(identifier, true)) else {
|
||||
return;
|
||||
};
|
||||
// Freshly-created node defaults to opacity enabled; disable it so the fill slider works independently
|
||||
@@ -821,7 +834,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
|
||||
pub fn clip_mode_toggle(&mut self, clip_mode: Option<bool>) {
|
||||
let clip = !clip_mode.unwrap_or(false);
|
||||
let Some(clip_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::clipping_mask::IDENTIFIER, true) else {
|
||||
let Some(clip_node_id) = self.existing_chain_hosted_node_id(graphene_std::blending_nodes::clipping_mask::IDENTIFIER, true) else {
|
||||
return;
|
||||
};
|
||||
let input_connector = InputConnector::node(clip_node_id, graphene_std::blending_nodes::clipping_mask::ClipInput);
|
||||
@@ -829,7 +842,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
}
|
||||
|
||||
pub fn stroke_set(&mut self, color: Option<Color>, stroke: Stroke) {
|
||||
let Some(stroke_node_id) = self.existing_proto_node_id(graphene_std::vector::stroke::IDENTIFIER, true) else {
|
||||
let Some(stroke_node_id) = self.existing_chain_hosted_node_id(graphene_std::vector::stroke::IDENTIFIER, true) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -2853,7 +2853,7 @@ impl NodeGraphMessageHandler {
|
||||
}))
|
||||
);
|
||||
|
||||
let clippable = layer.can_be_clipped(network_interface.document_metadata());
|
||||
let clippable = layer.can_be_clipped(network_interface.document_metadata()) && network_interface.layer_hosts_blending_nodes(&node_id, &[]);
|
||||
|
||||
let data = LayerPanelEntry {
|
||||
id: node_id,
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNodeImplementation, InlineRust, NodeInput};
|
||||
use graph_craft::proto::{GraphErrorType, GraphErrors};
|
||||
use graph_craft::{Type, concrete};
|
||||
use graph_craft::{ProtoNodeIdentifier, Type, concrete};
|
||||
use graphene_std::uuid::NodeId;
|
||||
use interpreted_executor::dynamic_executor::{NodeTypes, ResolvedDocumentNodeTypesDelta};
|
||||
use interpreted_executor::node_registry::NODE_REGISTRY;
|
||||
@@ -174,6 +174,39 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the given node has a registered implementation accepting the layer chain's element type as its content input.
|
||||
/// A chain awaiting compilation has no resolved type yet, so only a known-wrong type or a type error locks the layer out.
|
||||
pub fn layer_chain_hosts_node(&self, node_id: &NodeId, network_path: &[NodeId], node: &ProtoNodeIdentifier) -> bool {
|
||||
let secondary_input = InputConnector::layer_secondary_input(*node_id);
|
||||
if !self.input_from_connector(&secondary_input, network_path).is_some_and(|input| input.is_exposed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let chain_type = self.input_type(&secondary_input, network_path);
|
||||
match chain_type.compiled_nested_type() {
|
||||
Some(element) => {
|
||||
let Some(implementations) = NODE_REGISTRY.get(node) else {
|
||||
log::error!("Proto node {node:?} not found in the node registry, in layer_chain_hosts_node");
|
||||
return false;
|
||||
};
|
||||
implementations.iter().any(|entry| entry.io.inputs.first().is_some_and(|content| content.nested_type() == element))
|
||||
}
|
||||
None => !matches!(chain_type, TypeSource::Invalid),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the blending nodes (blend mode, opacity, clipping mask) can be spliced into this layer's chain.
|
||||
pub fn layer_hosts_blending_nodes(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
// Blend Mode stands in for the trio since they share one implementations list
|
||||
self.layer_chain_hosts_node(node_id, network_path, &graphene_std::blending_nodes::blend_mode::IDENTIFIER)
|
||||
}
|
||||
|
||||
/// Whether the Fill and Stroke nodes can be spliced into this layer's chain.
|
||||
pub fn layer_hosts_paint_nodes(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
// Fill stands in for both since they share one implementations list
|
||||
self.layer_chain_hosts_node(node_id, network_path, &graphene_std::vector_nodes::fill::IDENTIFIER)
|
||||
}
|
||||
|
||||
/// Get the [`TypeSource`] for any InputConnector.
|
||||
/// If the input is not compiled, then an Unknown or default from the definition is returned.
|
||||
pub fn input_type(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> TypeSource {
|
||||
|
||||
@@ -304,10 +304,8 @@ pub fn sync_drawing_state(drawing: &mut DrawingToolState, natural_fill_enabled:
|
||||
/// Reads the stroke proto-node inputs (align, cap, join, miter limit, paint order, dash lengths, dash offset) across the selection and updates
|
||||
/// the matching fields on `drawing`. Each field becomes `None` (mixed) when selected strokes disagree. With no selection, fields are left as-is.
|
||||
fn sync_stroke_options(drawing: &mut DrawingToolState, document: &DocumentMessageHandler) -> bool {
|
||||
let strokes: Vec<_> = document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_layers_except_artboards(&document.network_interface)
|
||||
let strokes: Vec<_> = graph_modification_utils::paintable_selected_layers(document)
|
||||
.into_iter()
|
||||
.filter_map(|layer| graph_modification_utils::get_stroke_options(layer, &document.network_interface))
|
||||
.collect();
|
||||
if strokes.is_empty() {
|
||||
@@ -391,14 +389,9 @@ pub fn sync_fill_only(fill: &mut ToolColorOptions, natural_fill_enabled: bool, f
|
||||
}
|
||||
}
|
||||
|
||||
/// True if at least one (non-artboard) layer is currently selected.
|
||||
pub fn has_selection(document: &DocumentMessageHandler) -> bool {
|
||||
document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_layers_except_artboards(&document.network_interface)
|
||||
.next()
|
||||
.is_some()
|
||||
/// True if at least one selected layer can take paint, making the swatches edit the selection instead of the tool's own colors.
|
||||
pub fn has_paintable_selection(document: &DocumentMessageHandler) -> bool {
|
||||
!graph_modification_utils::paintable_selected_layers(document).is_empty()
|
||||
}
|
||||
|
||||
/// Applies a user-picked fill (gradient or solid). With a selection, writes to the layers; with none, pushes a solid to the swap-routed working color slot.
|
||||
@@ -411,7 +404,7 @@ pub fn apply_fill_only_color_pick(fill: &mut ToolColorOptions, fill_choice: Fill
|
||||
fill.fill_choice = Some(fill_choice.clone());
|
||||
fill.enabled = Some(true);
|
||||
fill.tracks_working_color = false;
|
||||
if has_selection(document) {
|
||||
if has_paintable_selection(document) {
|
||||
if document.network_interface.transaction_status() == TransactionStatus::Finished {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
}
|
||||
@@ -426,7 +419,7 @@ pub fn apply_stroke_color_pick(drawing: &mut DrawingToolState, color: Option<Col
|
||||
drawing.stroke.fill_choice = Some(color.map_or(FillChoice::None, FillChoice::Solid));
|
||||
drawing.stroke.enabled = Some(true);
|
||||
drawing.stroke.tracks_working_color = false;
|
||||
if has_selection(document) {
|
||||
if has_paintable_selection(document) {
|
||||
if document.network_interface.transaction_status() == TransactionStatus::Finished {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
}
|
||||
@@ -447,7 +440,7 @@ pub fn apply_fill_enabled(drawing: &mut DrawingToolState, enabled: bool, global:
|
||||
/// Single-slot variant of [`apply_fill_enabled`]. `working_color` is the fallback used when re-ticking or unticking from a mixed state.
|
||||
pub fn apply_fill_only_enabled(fill: &mut ToolColorOptions, enabled: bool, working_color: Color, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
fill.enabled = Some(enabled);
|
||||
if has_selection(document) {
|
||||
if has_paintable_selection(document) {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
}
|
||||
if enabled {
|
||||
@@ -471,7 +464,7 @@ pub fn apply_fill_only_enabled(fill: &mut ToolColorOptions, enabled: bool, worki
|
||||
/// Toggles the stroke checkbox: mirrors [`apply_fill_enabled`].
|
||||
pub fn apply_stroke_enabled(drawing: &mut DrawingToolState, enabled: bool, global: &DocumentToolData, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
drawing.stroke.enabled = Some(enabled);
|
||||
if has_selection(document) {
|
||||
if has_paintable_selection(document) {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
}
|
||||
if enabled {
|
||||
@@ -493,7 +486,7 @@ pub fn apply_stroke_enabled(drawing: &mut DrawingToolState, enabled: bool, globa
|
||||
/// Applies a user-edited stroke weight to the selection, also persisting it as the no-selection default.
|
||||
pub fn apply_line_weight(drawing: &mut DrawingToolState, line_weight: f64, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
drawing.line_weight = Some(line_weight);
|
||||
if !has_selection(document) {
|
||||
if !has_paintable_selection(document) {
|
||||
drawing.default_line_weight = line_weight;
|
||||
}
|
||||
graph_modification_utils::set_stroke_weight_for_selected_layers(line_weight, document, responses);
|
||||
@@ -507,7 +500,7 @@ pub fn apply_working_colors(drawing: &mut DrawingToolState, global: &DocumentToo
|
||||
|
||||
/// Refreshes a single swatch from the given working color, subject to the rules in [`apply_working_colors`].
|
||||
pub fn refresh_slot_working_color(slot: &mut ToolColorOptions, working_color: Color, document: &DocumentMessageHandler) {
|
||||
if slot.fill_choice.is_some() && (!has_selection(document) || slot.tracks_working_color) {
|
||||
if slot.fill_choice.is_some() && (!has_paintable_selection(document) || slot.tracks_working_color) {
|
||||
slot.fill_choice = Some(solid(working_color));
|
||||
}
|
||||
}
|
||||
@@ -524,7 +517,7 @@ pub fn reset_colors_on_deactivation(drawing: &mut DrawingToolState, global: &Doc
|
||||
pub fn swap_fill_and_stroke(drawing: &mut DrawingToolState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
drawing.colors_swapped = !drawing.colors_swapped;
|
||||
|
||||
if has_selection(document) {
|
||||
if has_paintable_selection(document) {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
}
|
||||
|
||||
@@ -539,7 +532,7 @@ pub fn swap_fill_and_stroke(drawing: &mut DrawingToolState, document: &DocumentM
|
||||
drawing.fill.tracks_working_color = new_fill_tracks;
|
||||
drawing.stroke.tracks_working_color = new_stroke_tracks;
|
||||
|
||||
if has_selection(document) {
|
||||
if has_paintable_selection(document) {
|
||||
// Apply to layers only when we have a concrete value (`None` means mixed, no single value to broadcast).
|
||||
if drawing.fill.is_active()
|
||||
&& let Some(choice) = new_fill
|
||||
@@ -579,7 +572,7 @@ pub enum WeightSyncOutcome {
|
||||
|
||||
/// Inspects the selection and returns how the weight widget should update.
|
||||
pub fn compute_weight_sync(document: &DocumentMessageHandler) -> WeightSyncOutcome {
|
||||
let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect();
|
||||
let layers = graph_modification_utils::paintable_selected_layers(document);
|
||||
|
||||
if layers.is_empty() {
|
||||
return WeightSyncOutcome::NoSelection;
|
||||
|
||||
@@ -721,23 +721,11 @@ pub fn get_stroke_id(layer: LayerNodeIdentifier, network_interface: &NodeNetwork
|
||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER))
|
||||
}
|
||||
|
||||
/// Stroke weight of the first selected non-artboard layer, used by tool control bars to mirror the selection's weight.
|
||||
/// Returns `Some(0.)` if the layer has no Stroke node so the widget reads "0 px", and `None` only when no layer is selected.
|
||||
pub fn first_selected_stroke_weight(document: &DocumentMessageHandler) -> Option<f64> {
|
||||
document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_layers_except_artboards(&document.network_interface)
|
||||
.next()
|
||||
.map(|layer| get_stroke_width(layer, &document.network_interface).unwrap_or(0.))
|
||||
}
|
||||
|
||||
/// Writes the weight back to every selected non-artboard layer's stroke. Layers with an existing stroke just have their
|
||||
/// `WeightInput` updated; layers without one get a fresh stroke node added (defaulting to a black stroke with the new
|
||||
/// weight) only when the new weight is nonzero, so changing back to 0 doesn't keep adding empty strokes.
|
||||
pub fn set_stroke_weight_for_selected_layers(weight: f64, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect();
|
||||
for layer in layers {
|
||||
for layer in paintable_selected_layers(document) {
|
||||
if let Some(node_id) = get_stroke_id(layer, &document.network_interface) {
|
||||
responses.add(NodeGraphMessage::SetInputValue {
|
||||
node_id,
|
||||
@@ -817,12 +805,21 @@ pub struct SelectedStrokeState {
|
||||
pub optional_color: Option<Option<Color>>,
|
||||
}
|
||||
|
||||
/// The selected layers (artboards excluded) whose chains can host Fill and Stroke nodes.
|
||||
/// The tool options bar's paint widgets treat these as the whole selection, so other layers act as if unselected.
|
||||
pub fn paintable_selected_layers(document: &DocumentMessageHandler) -> Vec<LayerNodeIdentifier> {
|
||||
let selected_nodes = document.network_interface.selected_nodes();
|
||||
selected_nodes
|
||||
.selected_layers_except_artboards(&document.network_interface)
|
||||
.filter(|layer| document.network_interface.layer_hosts_paint_nodes(&layer.to_node(), &[]))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Reads the fill state across all selected non-artboard layers, including whether their enabled states or colors differ.
|
||||
/// "Enabled" tracks node attachment: a layer counts as enabled whenever a Fill node is attached, even when that fill's value is the no-paint choice.
|
||||
/// Unticked means there is no Fill node. Returns `None` only when no layer is selected.
|
||||
/// Unticked means there is no Fill node. Returns `None` only when no paintable layer is selected.
|
||||
pub fn selected_fill_state(document: &DocumentMessageHandler) -> Option<SelectedFillState> {
|
||||
let selected_nodes = document.network_interface.selected_nodes();
|
||||
let mut per_layer = selected_nodes.selected_layers_except_artboards(&document.network_interface).map(|layer| {
|
||||
let mut per_layer = paintable_selected_layers(document).into_iter().map(|layer| {
|
||||
let Some(fill_node_id) = get_fill_id(layer, &document.network_interface) else {
|
||||
return (false, FillChoice::None);
|
||||
};
|
||||
@@ -873,8 +870,7 @@ pub fn selected_fill_state(document: &DocumentMessageHandler) -> Option<Selected
|
||||
/// "Enabled" tracks node attachment: a layer counts as enabled whenever a Stroke node is attached, even when that stroke's color is `None`.
|
||||
/// Unticked means there is no Stroke node. Returns `None` only when no layer is selected.
|
||||
pub fn selected_stroke_state(document: &DocumentMessageHandler) -> Option<SelectedStrokeState> {
|
||||
let selected_nodes = document.network_interface.selected_nodes();
|
||||
let mut per_layer = selected_nodes.selected_layers_except_artboards(&document.network_interface).map(|layer| {
|
||||
let mut per_layer = paintable_selected_layers(document).into_iter().map(|layer| {
|
||||
if get_stroke_id(layer, &document.network_interface).is_none() {
|
||||
return (false, None);
|
||||
}
|
||||
@@ -911,8 +907,7 @@ pub fn selected_stroke_state(document: &DocumentMessageHandler) -> Option<Select
|
||||
|
||||
/// Sets the fill on all selected non-artboard layers, preserving gradient transform data when the layer already has a gradient fill.
|
||||
pub fn set_fill_for_selected_layers(fill_choice: FillChoice, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect();
|
||||
for layer in layers {
|
||||
for layer in paintable_selected_layers(document) {
|
||||
match &fill_choice {
|
||||
FillChoice::None => responses.add(GraphOperationMessage::FillColorSet { layer, color: None }),
|
||||
FillChoice::Solid(color) => responses.add(GraphOperationMessage::FillColorSet { layer, color: Some(*color) }),
|
||||
@@ -947,8 +942,7 @@ pub fn set_fill_for_selected_layers(fill_choice: FillChoice, document: &Document
|
||||
/// the provided `weight`, so picking any color (including `None`) from an unticked stroke control bar entry both attaches
|
||||
/// the Stroke node and applies the chosen color.
|
||||
pub fn set_stroke_color_for_selected_layers(color: Option<Color>, weight: f64, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect();
|
||||
for layer in layers {
|
||||
for layer in paintable_selected_layers(document) {
|
||||
if let Some(node_id) = get_stroke_id(layer, &document.network_interface) {
|
||||
responses.add(NodeGraphMessage::SetInputValue {
|
||||
node_id,
|
||||
@@ -964,8 +958,7 @@ pub fn set_stroke_color_for_selected_layers(color: Option<Color>, weight: f64, d
|
||||
|
||||
/// Removes the Fill node from all selected non-artboard layers.
|
||||
pub fn remove_fill_for_selected_layers(document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect();
|
||||
for layer in layers {
|
||||
for layer in paintable_selected_layers(document) {
|
||||
if let Some(node_id) = get_fill_id(layer, &document.network_interface) {
|
||||
responses.add(NodeGraphMessage::DeleteNodes {
|
||||
node_ids: vec![node_id],
|
||||
@@ -979,8 +972,7 @@ pub fn remove_fill_for_selected_layers(document: &DocumentMessageHandler, respon
|
||||
|
||||
/// Removes the Stroke node from all selected non-artboard layers.
|
||||
pub fn remove_stroke_for_selected_layers(document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect();
|
||||
for layer in layers {
|
||||
for layer in paintable_selected_layers(document) {
|
||||
if let Some(node_id) = get_stroke_id(layer, &document.network_interface) {
|
||||
responses.add(NodeGraphMessage::DeleteNodes {
|
||||
node_ids: vec![node_id],
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::color_selector::{
|
||||
DrawingToolState, apply_fill_color_pick, apply_fill_enabled, apply_stroke_color_pick, apply_stroke_enabled, apply_working_colors, swap_fill_and_stroke, sync_drawing_state,
|
||||
DrawingToolState, apply_fill_color_pick, apply_fill_enabled, apply_stroke_color_pick, apply_stroke_enabled, apply_working_colors, has_paintable_selection, swap_fill_and_stroke, sync_drawing_state,
|
||||
};
|
||||
use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
@@ -236,8 +236,8 @@ impl LayoutHolder for SelectTool {
|
||||
fn layout(&self) -> Layout {
|
||||
let mut widgets = Vec::new();
|
||||
|
||||
// Fill/Stroke widget set (only shown when there's a selection to apply edits to)
|
||||
if self.tool_data.selected_layers_count > 0 {
|
||||
// Fill/Stroke widget set (only shown when there's a paintable selection to apply edits to)
|
||||
if self.tool_data.paintable_layers_selected {
|
||||
widgets.append(&mut self.drawing.fill.create_widgets(
|
||||
"Fill:",
|
||||
|checkbox: &CheckboxInput| {
|
||||
@@ -509,6 +509,8 @@ struct SelectToolData {
|
||||
skew_edge: EdgeBool,
|
||||
nested_selection_behavior: NestedSelectionBehavior,
|
||||
selected_layers_count: usize,
|
||||
/// Whether any selected layer can take paint, controlling the visibility of the fill/stroke widget row.
|
||||
paintable_layers_selected: bool,
|
||||
selected_layers_changed: bool,
|
||||
snap_candidates: Vec<SnapCandidatePoint>,
|
||||
auto_panning: AutoPanning,
|
||||
@@ -738,8 +740,10 @@ impl Fsm for SelectToolFsmState {
|
||||
crate::messages::tool::common_functionality::layer_origin_cross::draw_for_selected_layers(&mut overlay_context, document);
|
||||
|
||||
let selected_layers_count = document.network_interface.selected_nodes().selected_unlocked_layers(&document.network_interface).count();
|
||||
tool_data.selected_layers_changed = selected_layers_count != tool_data.selected_layers_count;
|
||||
let paintable_layers_selected = has_paintable_selection(document);
|
||||
tool_data.selected_layers_changed = selected_layers_count != tool_data.selected_layers_count || paintable_layers_selected != tool_data.paintable_layers_selected;
|
||||
tool_data.selected_layers_count = selected_layers_count;
|
||||
tool_data.paintable_layers_selected = paintable_layers_selected;
|
||||
|
||||
// Outline selected layers, but not artboards
|
||||
if overlay_context.visibility_settings.selection_outline() {
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayContex
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::color_selector::{
|
||||
DrawingToolState, apply_fill_color_pick, apply_fill_enabled, apply_stroke_color_pick, apply_stroke_enabled, apply_working_colors, has_selection, reset_colors_on_deactivation,
|
||||
DrawingToolState, apply_fill_color_pick, apply_fill_enabled, apply_stroke_color_pick, apply_stroke_enabled, apply_working_colors, has_paintable_selection, reset_colors_on_deactivation,
|
||||
swap_fill_and_stroke, sync_color_options, sync_drawing_state,
|
||||
};
|
||||
use crate::messages::tool::common_functionality::gizmos::gizmo_manager::GizmoManager;
|
||||
@@ -582,8 +582,8 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Shap
|
||||
apply_fill_color_pick(&mut self.options.drawing, fill_choice, context.document, responses);
|
||||
}
|
||||
ShapeOptionsUpdate::FillEnabled(enabled) => {
|
||||
// When toggled with no selection, persist the new state as the current shape mode's default
|
||||
if !has_selection(context.document) {
|
||||
// When toggled with no paintable selection, persist the new state as the current shape mode's default
|
||||
if !has_paintable_selection(context.document) {
|
||||
self.options.shape_fill_defaults.insert(self.tool_data.current_shape, enabled);
|
||||
}
|
||||
apply_fill_enabled(&mut self.options.drawing, enabled, context.global_tool_data, context.document, responses);
|
||||
@@ -595,8 +595,8 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Shap
|
||||
apply_stroke_color_pick(&mut self.options.drawing, color, context.document, responses);
|
||||
}
|
||||
ShapeOptionsUpdate::StrokeEnabled(enabled) => {
|
||||
// When toggled with no selection, persist the new state as the current shape mode's default
|
||||
if !has_selection(context.document) {
|
||||
// When toggled with no paintable selection, persist the new state as the current shape mode's default
|
||||
if !has_paintable_selection(context.document) {
|
||||
self.options.shape_stroke_defaults.insert(self.tool_data.current_shape, enabled);
|
||||
}
|
||||
apply_stroke_enabled(&mut self.options.drawing, enabled, context.global_tool_data, context.document, responses);
|
||||
|
||||
Reference in New Issue
Block a user