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:
0HyperCube
2023-12-12 09:27:23 +00:00
committed by GitHub
co-authored by Keavon Chambers
parent 6bce72dccd
commit 29222700f4
20 changed files with 271 additions and 241 deletions
@@ -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],
@@ -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);
}
}
@@ -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);