mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 12:28:12 +08:00
Fix the blend mode and opacity widgets of the Layers panel (#1506)
* Fix blend mode and opacity * Cleanup and bug fixes --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
6bce72dccd
commit
29222700f4
@@ -12,6 +12,7 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate,
|
||||
use crate::messages::portfolio::document::utility_types::vectorize_layer_metadata;
|
||||
use crate::messages::portfolio::utility_types::PersistentData;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{get_blend_mode, get_opacity};
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
|
||||
@@ -222,7 +223,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
||||
responses.add(FolderChanged { affected_folder_path: vec![] });
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
|
||||
self.update_layer_tree_options_bar_widgets(responses, &render_data);
|
||||
self.update_layers_panel_options_bar_widgets(responses);
|
||||
}
|
||||
AlignSelectedLayers { axis, aggregate } => {
|
||||
self.backup(responses);
|
||||
@@ -268,7 +269,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
||||
// Clear the options bar
|
||||
responses.add(LayoutMessage::SendLayout {
|
||||
layout: Layout::WidgetLayout(Default::default()),
|
||||
layout_target: LayoutTarget::LayerTreeOptions,
|
||||
layout_target: LayoutTarget::LayersPanelOptions,
|
||||
});
|
||||
}
|
||||
CommitTransaction => (),
|
||||
@@ -449,7 +450,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
||||
responses.add(FrontendMessage::UpdateDocumentLayerDetails { data: layer_entry });
|
||||
}
|
||||
responses.add(PropertiesPanelMessage::CheckSelectedWasUpdated { path: affected_layer_path });
|
||||
self.update_layer_tree_options_bar_widgets(responses, &render_data);
|
||||
self.update_layers_panel_options_bar_widgets(responses);
|
||||
}
|
||||
MoveSelectedLayersTo { parent, insert_index } => {
|
||||
let selected_layers = self.metadata().selected_layers().collect::<Vec<_>>();
|
||||
@@ -697,8 +698,8 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
||||
}
|
||||
SetBlendModeForSelectedLayers { blend_mode } => {
|
||||
self.backup(responses);
|
||||
for path in self.selected_layers() {
|
||||
responses.add(DocumentOperation::SetLayerBlendMode { path: path.to_vec(), blend_mode });
|
||||
for layer in self.metadata().selected_layers_except_artboards() {
|
||||
responses.add(GraphOperationMessage::BlendModeSet { layer: layer.to_path(), blend_mode });
|
||||
}
|
||||
}
|
||||
SetImageBlobUrl {
|
||||
@@ -737,10 +738,10 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
||||
}
|
||||
SetOpacityForSelectedLayers { opacity } => {
|
||||
self.backup(responses);
|
||||
let opacity = opacity.clamp(0., 1.);
|
||||
let opacity = opacity.clamp(0., 1.) as f32;
|
||||
|
||||
for path in self.selected_layers().map(|path| path.to_vec()) {
|
||||
responses.add(DocumentOperation::SetLayerOpacity { path, opacity });
|
||||
for layer in self.metadata().selected_layers_except_artboards() {
|
||||
responses.add(GraphOperationMessage::OpacitySet { layer: layer.to_path(), opacity });
|
||||
}
|
||||
}
|
||||
SetOverlaysVisibility { visible } => {
|
||||
@@ -1519,72 +1520,71 @@ impl DocumentMessageHandler {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update_layer_tree_options_bar_widgets(&self, responses: &mut VecDeque<Message>, render_data: &RenderData) {
|
||||
let mut opacity = None;
|
||||
let mut opacity_is_mixed = false;
|
||||
pub fn update_layers_panel_options_bar_widgets(&self, responses: &mut VecDeque<Message>) {
|
||||
// Get an iterator over the selected layers (excluding artboards which don't have an opacity or blend mode).
|
||||
let selected_layers_except_artboards = self.metadata().selected_layers_except_artboards();
|
||||
|
||||
let mut blend_mode = None;
|
||||
let mut blend_mode_is_mixed = false;
|
||||
// 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 opacity_and_blend_mode = selected_layers_except_artboards.map(|layer| {
|
||||
(
|
||||
get_opacity(layer, &self.document_legacy).unwrap_or(100.),
|
||||
get_blend_mode(layer, &self.document_legacy).unwrap_or_default(),
|
||||
)
|
||||
});
|
||||
let first_opacity_and_blend_mode = opacity_and_blend_mode.next();
|
||||
let result_opacity_and_blend_mode = opacity_and_blend_mode;
|
||||
|
||||
self.layer_metadata
|
||||
.keys()
|
||||
.filter_map(|path| self.layer_panel_entry_from_path(path, render_data))
|
||||
.filter(|layer_panel_entry| layer_panel_entry.layer_metadata.selected)
|
||||
.flat_map(|layer_panel_entry| self.document_legacy.layer(layer_panel_entry.path.as_slice()))
|
||||
.for_each(|layer| {
|
||||
match opacity {
|
||||
None => opacity = Some(layer.opacity),
|
||||
Some(opacity) => {
|
||||
if (opacity - layer.opacity).abs() > (1. / 1_000_000.) {
|
||||
opacity_is_mixed = true;
|
||||
}
|
||||
// If there are no selected layers, disable the opacity and blend mode widgets.
|
||||
let disabled = first_opacity_and_blend_mode.is_none();
|
||||
|
||||
// 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.
|
||||
// If identical, we display the value in the widget. If not, we display a dash indicating dissimilarity.
|
||||
let (opacity, blend_mode) = first_opacity_and_blend_mode
|
||||
.map(|(first_opacity, first_blend_mode)| {
|
||||
let mut opacity_identical = true;
|
||||
let mut blend_mode_identical = true;
|
||||
|
||||
for (opacity, blend_mode) in result_opacity_and_blend_mode {
|
||||
if (opacity - first_opacity).abs() > (f32::EPSILON * 100.) {
|
||||
opacity_identical = false;
|
||||
}
|
||||
if blend_mode != first_blend_mode {
|
||||
blend_mode_identical = false;
|
||||
}
|
||||
}
|
||||
|
||||
match blend_mode {
|
||||
None => blend_mode = Some(layer.blend_mode),
|
||||
Some(blend_mode) => {
|
||||
if blend_mode != layer.blend_mode {
|
||||
blend_mode_is_mixed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
(opacity_identical.then(|| first_opacity), blend_mode_identical.then(|| first_blend_mode))
|
||||
})
|
||||
.unwrap_or((None, None));
|
||||
|
||||
if opacity_is_mixed {
|
||||
opacity = None;
|
||||
}
|
||||
if blend_mode_is_mixed {
|
||||
blend_mode = None;
|
||||
}
|
||||
|
||||
let blend_mode_menu_entries = BlendMode::list_modes_in_groups()
|
||||
let blend_mode_menu_entries = BlendMode::list_svg_subset()
|
||||
.iter()
|
||||
.map(|modes| {
|
||||
modes
|
||||
.iter()
|
||||
.map(|mode| {
|
||||
MenuListEntry::new(mode.to_string())
|
||||
.value(mode.to_string())
|
||||
.on_update(|_| DocumentMessage::SetBlendModeForSelectedLayers { blend_mode: *mode }.into())
|
||||
.map(|&blend_mode| {
|
||||
MenuListEntry::new(blend_mode.to_string())
|
||||
.value(blend_mode.to_string())
|
||||
.on_update(move |_| DocumentMessage::SetBlendModeForSelectedLayers { blend_mode }.into())
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let layer_tree_options = WidgetLayout::new(vec![LayoutGroup::Row {
|
||||
let layers_panel_options_bar = WidgetLayout::new(vec![LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
DropdownInput::new(blend_mode_menu_entries)
|
||||
.selected_index(blend_mode.map(|blend_mode| blend_mode as u32))
|
||||
.disabled(blend_mode.is_none() && !blend_mode_is_mixed)
|
||||
.selected_index(blend_mode.map(|blend_mode| blend_mode.index_in_list_svg_subset()).flatten().map(|index| index as u32))
|
||||
.disabled(disabled)
|
||||
.draw_icon(false)
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
NumberInput::new(opacity.map(|opacity| opacity * 100.))
|
||||
NumberInput::new(opacity.map(|opacity| opacity as f64))
|
||||
.label("Opacity")
|
||||
.unit("%")
|
||||
.display_decimal_places(2)
|
||||
.disabled(opacity.is_none() && !opacity_is_mixed)
|
||||
.disabled(disabled)
|
||||
.min(0.)
|
||||
.max(100.)
|
||||
.range_min(Some(0.))
|
||||
@@ -1613,8 +1613,8 @@ impl DocumentMessageHandler {
|
||||
}]);
|
||||
|
||||
responses.add(LayoutMessage::SendLayout {
|
||||
layout: Layout::WidgetLayout(layer_tree_options),
|
||||
layout_target: LayoutTarget::LayerTreeOptions,
|
||||
layout: Layout::WidgetLayout(layers_panel_options_bar),
|
||||
layout_target: LayoutTarget::LayersPanelOptions,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use bezier_rs::Subpath;
|
||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||
use graph_craft::document::DocumentNode;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_core::raster::BlendMode;
|
||||
use graphene_core::raster::ImageFrame;
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
@@ -23,6 +24,14 @@ pub enum GraphOperationMessage {
|
||||
layer: LayerIdentifier,
|
||||
fill: Fill,
|
||||
},
|
||||
OpacitySet {
|
||||
layer: LayerIdentifier,
|
||||
opacity: f32,
|
||||
},
|
||||
BlendModeSet {
|
||||
layer: LayerIdentifier,
|
||||
blend_mode: BlendMode,
|
||||
},
|
||||
UpdateBounds {
|
||||
layer: LayerIdentifier,
|
||||
old_bounds: [DVec2; 2],
|
||||
|
||||
+30
-3
@@ -7,7 +7,7 @@ use document_legacy::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{generate_uuid, DocumentNode, NodeId, NodeInput, NodeNetwork, NodeOutput};
|
||||
use graphene_core::raster::ImageFrame;
|
||||
use graphene_core::raster::{BlendMode, ImageFrame};
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
||||
@@ -284,7 +284,6 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
let new_input = output_node.inputs.first().cloned().filter(|input| input.as_node().is_some());
|
||||
let node_id = generate_uuid();
|
||||
|
||||
output_node.metadata.position.x += 8;
|
||||
output_node.inputs[0] = NodeInput::node(node_id, 0);
|
||||
|
||||
let Some(node_type) = resolve_document_node_type(name) else {
|
||||
@@ -294,6 +293,12 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
let mut new_document_node = node_type.to_document_node_default_inputs([new_input], metadata);
|
||||
update_input(&mut new_document_node.inputs, node_id, self.document_metadata);
|
||||
self.network.nodes.insert(node_id, new_document_node);
|
||||
|
||||
let upstream_nodes = self.network.upstream_flow_back_from_nodes(vec![node_id], true).map(|(_, id)| id).collect::<Vec<_>>();
|
||||
for node_id in upstream_nodes {
|
||||
let Some(node) = self.network.nodes.get_mut(&node_id) else { continue };
|
||||
node.metadata.position.x -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
/// Changes the inputs of a specific node
|
||||
@@ -367,6 +372,18 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
});
|
||||
}
|
||||
|
||||
fn opacity_set(&mut self, opacity: f32) {
|
||||
self.modify_inputs("Opacity", false, |inputs, _node_id, _metadata| {
|
||||
inputs[1] = NodeInput::value(TaggedValue::F32(opacity * 100.), false);
|
||||
});
|
||||
}
|
||||
|
||||
fn blend_mode_set(&mut self, blend_mode: BlendMode) {
|
||||
self.modify_inputs("Blend Mode", false, |inputs, _node_id, _metadata| {
|
||||
inputs[1] = NodeInput::value(TaggedValue::BlendMode(blend_mode), false);
|
||||
});
|
||||
}
|
||||
|
||||
fn stroke_set(&mut self, stroke: Stroke) {
|
||||
self.modify_inputs("Stroke", false, |inputs, _node_id, _metadata| {
|
||||
inputs[1] = NodeInput::value(TaggedValue::OptionalColor(stroke.color), false);
|
||||
@@ -564,6 +581,16 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
|
||||
responses.add(Operation::SetLayerFill { path: layer, fill });
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::OpacitySet { layer, opacity } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(&layer, document, node_graph, responses) {
|
||||
modify_inputs.opacity_set(opacity);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::BlendModeSet { layer, blend_mode } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(&layer, document, node_graph, responses) {
|
||||
modify_inputs.blend_mode_set(blend_mode);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::UpdateBounds { layer, old_bounds, new_bounds } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(&layer, document, node_graph, responses) {
|
||||
modify_inputs.update_bounds(old_bounds, new_bounds);
|
||||
@@ -715,7 +742,7 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
|
||||
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
|
||||
let layer_nodes = modify_inputs.network.nodes.iter().filter(|(_, node)| node.is_layer()).map(|(id, _)| *id).collect::<Vec<_>>();
|
||||
for layer in layer_nodes {
|
||||
if modify_inputs.network.upstream_flow_back_from_nodes(vec![layer], true).any(|(node, _id)| node.name == "Artboard") {
|
||||
if modify_inputs.network.upstream_flow_back_from_nodes(vec![layer], true).any(|(node, _id)| node.is_artboard()) {
|
||||
modify_inputs.delete_layer(layer);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-6
@@ -368,33 +368,35 @@ fn noise_type(document_node: &DocumentNode, node_id: u64, index: usize, name: &s
|
||||
LayoutGroup::Row { widgets }.with_tooltip("Type of Noise")
|
||||
}
|
||||
|
||||
//TODO Use generalized Version of this as soon as it's available
|
||||
// TODO: Use generalized version of this as soon as it's available
|
||||
fn blend_mode(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
if let &NodeInput::Value {
|
||||
tagged_value: TaggedValue::BlendMode(mode),
|
||||
tagged_value: TaggedValue::BlendMode(blend_mode),
|
||||
exposed: false,
|
||||
} = &document_node.inputs[index]
|
||||
{
|
||||
let entries = BlendMode::list()
|
||||
let entries = BlendMode::list_svg_subset()
|
||||
.iter()
|
||||
.map(|category| {
|
||||
category
|
||||
.iter()
|
||||
.map(|mode| MenuListEntry::new(mode.to_string()).on_update(update_value(move |_| TaggedValue::BlendMode(*mode), node_id, index)))
|
||||
.map(|blend_mode| MenuListEntry::new(blend_mode.to_string()).on_update(update_value(move |_| TaggedValue::BlendMode(*blend_mode), node_id, index)))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
DropdownInput::new(entries).selected_index(Some(mode as u32)).widget_holder(),
|
||||
DropdownInput::new(entries)
|
||||
.selected_index(blend_mode.index_in_list_svg_subset().map(|index| index as u32))
|
||||
.widget_holder(),
|
||||
]);
|
||||
}
|
||||
LayoutGroup::Row { widgets }.with_tooltip("Formula used for blending")
|
||||
}
|
||||
|
||||
// TODO: Generalize this for all dropdowns ( also see blend_mode and channel_extration )
|
||||
// TODO: Generalize this for all dropdowns (also see blend_mode and channel_extration)
|
||||
fn luminance_calculation(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||
if let &NodeInput::Value {
|
||||
|
||||
@@ -680,7 +680,7 @@ impl PortfolioMessageHandler {
|
||||
.map(|entry| FrontendMessage::UpdateDocumentLayerDetails { data: entry }.into())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
new_document.update_layer_tree_options_bar_widgets(responses, &render_data);
|
||||
new_document.update_layers_panel_options_bar_widgets(responses);
|
||||
|
||||
self.documents.insert(document_id, new_document);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user