mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-24 03:38:11 +08:00
Remove the 'Blending' node, moving clip into a new 'Clipping Mask' node and fill into the 'Opacity' node (#4085)
* Separate 'Clip' into its own node out from the removed 'Blending' node * Code review * Rename to Clipping Mask * Update Opacity node in demo art that use it * Use DIsplay not Debug for printing blend modes
This commit is contained in:
@@ -6,15 +6,16 @@ use crate::messages::prelude::*;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::{Affine2, DAffine2, Vec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::Context;
|
||||
use graphene_std::Graphic;
|
||||
use graphene_std::blending::BlendMode;
|
||||
use graphene_std::gradient::GradientStops;
|
||||
use graphene_std::memo::IORecord;
|
||||
use graphene_std::raster_types::{CPU, GPU, Raster};
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::vector::style::{Fill, FillChoice};
|
||||
use graphene_std::{AlphaBlending, Color};
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -283,7 +284,7 @@ impl<T: TableRowLayout> TableRowLayout for Table<T> {
|
||||
for key in &attribute_keys {
|
||||
let target = PathStep::Attribute { row: index, key: key.clone() };
|
||||
let widget = self.attribute_any(key, index).and_then(|any| dispatch_value_widget(any, target, data)).unwrap_or_else(|| {
|
||||
let text = self.attribute_display_value(key, index, |_| None).unwrap_or_else(|| "-".to_string());
|
||||
let text = self.attribute_display_value(key, index, display_value_override).unwrap_or_else(|| "-".to_string());
|
||||
TextLabel::new(text).narrow(true).widget_instance()
|
||||
});
|
||||
values.push(widget);
|
||||
@@ -742,21 +743,6 @@ impl TableRowLayout for Affine2 {
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for AlphaBlending {
|
||||
fn type_name() -> &'static str {
|
||||
"AlphaBlending"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format_alpha_blending(*self)
|
||||
}
|
||||
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||
TextLabel::new(format_alpha_blending(*self)).narrow(true).widget_instance()
|
||||
}
|
||||
fn value_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])]
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the value/breadcrumb label for a `NodeId` against `network_interface` at the given `network_path`,
|
||||
/// falling back to "Node {id}" if the node isn't present (e.g. an ID that no longer maps to a real node).
|
||||
fn node_id_display_label(node_id: NodeId, network_interface: &NodeNetworkInterface, network_path: &[NodeId]) -> String {
|
||||
@@ -899,7 +885,6 @@ macro_rules! known_table_row_types {
|
||||
GradientStops,
|
||||
Color,
|
||||
NodeId,
|
||||
AlphaBlending,
|
||||
DAffine2,
|
||||
DVec2,
|
||||
Affine2,
|
||||
@@ -919,6 +904,16 @@ macro_rules! known_table_row_types {
|
||||
};
|
||||
}
|
||||
|
||||
/// Override hook for [`Table::attribute_display_value`] that prefers `Display` over `Debug` for select
|
||||
/// attribute types. The underlying storage is generic and can only see a `Debug` bound, so types whose
|
||||
/// nicer `Display` rendering matters in the data panel are listed here explicitly.
|
||||
fn display_value_override(any: &dyn Any) -> Option<String> {
|
||||
if let Some(value) = any.downcast_ref::<BlendMode>() {
|
||||
return Some(value.to_string());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Type-dispatched widget for displaying an attribute value in a `Table<T>` item.
|
||||
/// Delegates to [`TableRowLayout::value_widget`] so the same widget code is shared between
|
||||
/// element-column rendering and attribute-column rendering. Returns `None` for unrecognized types so the
|
||||
@@ -1032,14 +1027,3 @@ fn format_dvec2(value: DVec2) -> String {
|
||||
let round = |x: f64| (x * 1e3).round() / 1e3;
|
||||
format!("({} px, {} px)", round(value.x), round(value.y))
|
||||
}
|
||||
|
||||
fn format_alpha_blending(value: AlphaBlending) -> String {
|
||||
let round = |x: f32| (x * 1e3).round() / 1e3;
|
||||
format!(
|
||||
"Blend Mode: {} — Opacity: {}% — Fill: {}% — Clip: {}",
|
||||
value.blend_mode,
|
||||
round(value.opacity * 100.),
|
||||
round(value.fill * 100.),
|
||||
if value.clip { "Yes" } else { "No" }
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
||||
}
|
||||
GraphOperationMessage::BlendingFillSet { layer, fill } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.blending_fill_set(fill);
|
||||
modify_inputs.opacity_fill_set(fill);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::GradientStopsSet { layer, stops } => {
|
||||
|
||||
@@ -437,7 +437,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::blending::IDENTIFIER, true) else {
|
||||
let Some(blend_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::blend_mode::IDENTIFIER, true) else {
|
||||
return;
|
||||
};
|
||||
let input_connector = InputConnector::node(blend_node_id, 1);
|
||||
@@ -445,19 +445,30 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
}
|
||||
|
||||
pub fn opacity_set(&mut self, opacity: f64) {
|
||||
let Some(blend_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::blending::IDENTIFIER, true) else {
|
||||
let Some(opacity_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::opacity::IDENTIFIER, true) else {
|
||||
return;
|
||||
};
|
||||
let input_connector = InputConnector::node(blend_node_id, 2);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(opacity * 100.), false), false);
|
||||
// Enable the `has_opacity` checkbox so the value is applied
|
||||
self.set_input_with_refresh(InputConnector::node(opacity_node_id, 1), NodeInput::value(TaggedValue::Bool(true), false), false);
|
||||
self.set_input_with_refresh(InputConnector::node(opacity_node_id, 2), NodeInput::value(TaggedValue::F64(opacity * 100.), false), false);
|
||||
}
|
||||
|
||||
pub fn blending_fill_set(&mut self, fill: f64) {
|
||||
let Some(blend_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::blending::IDENTIFIER, true) else {
|
||||
pub fn opacity_fill_set(&mut self, fill: f64) {
|
||||
// Reuse the Opacity node if already present (saving a chain walk on slider drags), otherwise let the next call create it
|
||||
let identifier = graphene_std::blending_nodes::opacity::IDENTIFIER;
|
||||
let existing = self.existing_proto_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 {
|
||||
return;
|
||||
};
|
||||
let input_connector = InputConnector::node(blend_node_id, 3);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(fill * 100.), false), false);
|
||||
// Disable the opacity component on a freshly-created node so the slider only affects fill, mirroring the opacity-slider case
|
||||
// (where the node's default `has_fill = false` already keeps fill out of the picture)
|
||||
if !existed {
|
||||
self.set_input_with_refresh(InputConnector::node(opacity_node_id, 1), NodeInput::value(TaggedValue::Bool(false), false), false);
|
||||
}
|
||||
// Enable the `has_fill` checkbox so the value is applied
|
||||
self.set_input_with_refresh(InputConnector::node(opacity_node_id, 3), NodeInput::value(TaggedValue::Bool(true), false), false);
|
||||
self.set_input_with_refresh(InputConnector::node(opacity_node_id, 4), NodeInput::value(TaggedValue::F64(fill * 100.), false), false);
|
||||
}
|
||||
|
||||
/// Set the stops table on the 'Gradient Value' node, creating it if necessary.
|
||||
@@ -556,10 +567,10 @@ 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::blending::IDENTIFIER, true) else {
|
||||
let Some(clip_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::clipping_mask::IDENTIFIER, true) else {
|
||||
return;
|
||||
};
|
||||
let input_connector = InputConnector::node(clip_node_id, 4);
|
||||
let input_connector = InputConnector::node(clip_node_id, 1);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Bool(clip), false), false);
|
||||
}
|
||||
|
||||
|
||||
@@ -2036,6 +2036,19 @@ fn static_input_properties() -> InputProperties {
|
||||
))])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
// Like `optional_f64`, but the number input is configured as a percentage with a 0-100 range.
|
||||
// As with `optional_f64`, the bool input must be at the input index directly before the f64 input.
|
||||
"optional_percentage".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let number_input = NumberInput::default().percentage().min(0.).max(100.);
|
||||
Ok(vec![LayoutGroup::row(node_properties::optional_f64_widget(
|
||||
ParameterWidgetsInfo::new(node_id, index, false, context),
|
||||
index - 1,
|
||||
number_input,
|
||||
))])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"vec2".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
|
||||
@@ -38,13 +38,19 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
// ================================
|
||||
// blending
|
||||
// ================================
|
||||
NodeReplacement {
|
||||
node: graphene_std::blending_nodes::blending::IDENTIFIER,
|
||||
aliases: &["graphene_core::raster::BlendingNode", "graphene_core::blending_nodes::BlendingNode"],
|
||||
},
|
||||
// The legacy combined "Blending" node was split into separate Blend Mode, Opacity (now also covers fill), and Clip nodes.
|
||||
// Old Blending references are remapped here to `blend_mode::IDENTIFIER` so the per-node migration in `migrate_node` can
|
||||
// detect a 5-input Blend Mode node and rewrite it as a chain (skipping any sub-node whose value is at the default and
|
||||
// not exposed/wired up).
|
||||
NodeReplacement {
|
||||
node: graphene_std::blending_nodes::blend_mode::IDENTIFIER,
|
||||
aliases: &["graphene_core::raster::BlendModeNode", "graphene_core::blending_nodes::BlendModeNode"],
|
||||
aliases: &[
|
||||
"graphene_core::raster::BlendModeNode",
|
||||
"graphene_core::blending_nodes::BlendModeNode",
|
||||
"graphene_core::raster::BlendingNode",
|
||||
"graphene_core::blending_nodes::BlendingNode",
|
||||
"blending_nodes::BlendingNode",
|
||||
],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::blending_nodes::opacity::IDENTIFIER,
|
||||
@@ -1090,6 +1096,175 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
|
||||
let mut inputs_count = node.inputs.len();
|
||||
|
||||
// Split the legacy combined "Blending" node into a chain of separate Blend Mode, Opacity (now also covers fill), and Clip nodes.
|
||||
// `NODE_REPLACEMENTS` rewrites the old `Blending` proto identifier (and its older aliases) to `blend_mode::IDENTIFIER`, so a leftover
|
||||
// 5-input shape on a Blend Mode node identifies an old Blending node that still needs structural splitting. Sub-nodes whose values
|
||||
// were at the default and weren't exposed/wired up are skipped, and the existing node ID is reused for the first kept sub-node so
|
||||
// any references elsewhere in the document survive. If everything would be skipped, a no-op default Blend Mode node is left behind
|
||||
// to preserve the chain structure (the user can delete it manually).
|
||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::blending_nodes::blend_mode::IDENTIFIER) && inputs_count == 5 {
|
||||
use graphene_std::blending::BlendMode;
|
||||
|
||||
// Snapshot the old inputs before any rewriting
|
||||
let old_inputs = node.inputs.clone();
|
||||
let content_input = old_inputs[0].clone();
|
||||
let blend_mode_input = old_inputs[1].clone();
|
||||
let opacity_input = old_inputs[2].clone();
|
||||
let fill_input = old_inputs[3].clone();
|
||||
let clip_input = old_inputs[4].clone();
|
||||
|
||||
// A sub-node is kept if its input is exposed/wired (so the user can drive it from the graph) OR if its value differs from the default
|
||||
let keep_blend_mode = blend_mode_input.is_exposed() || !matches!(blend_mode_input.as_value(), Some(TaggedValue::BlendMode(BlendMode::Normal)));
|
||||
let keep_opacity = opacity_input.is_exposed() || !matches!(opacity_input.as_value(), Some(TaggedValue::F64(v)) if (*v - 100.).abs() < f64::EPSILON);
|
||||
let keep_fill = fill_input.is_exposed() || !matches!(fill_input.as_value(), Some(TaggedValue::F64(v)) if (*v - 100.).abs() < f64::EPSILON);
|
||||
let keep_clip = clip_input.is_exposed() || !matches!(clip_input.as_value(), Some(TaggedValue::Bool(false)));
|
||||
|
||||
// Find the downstream connection so we can chain new nodes between this node and downstream
|
||||
let downstream = document
|
||||
.network_interface
|
||||
.outward_wires(network_path)
|
||||
.and_then(|wires| wires.get(&OutputConnector::node(*node_id, 0)))
|
||||
.and_then(|connections| connections.first().cloned());
|
||||
|
||||
// The position of the existing node; subsequent chain nodes are placed to the right
|
||||
let base_position = document.network_interface.position(node_id, network_path).unwrap_or_default();
|
||||
|
||||
// Decide which sub-node the existing ID becomes (the first one in the chain order: Blend Mode, Opacity, Clip)
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum SubNode {
|
||||
BlendMode,
|
||||
Opacity,
|
||||
Clip,
|
||||
}
|
||||
let first_kind = if keep_blend_mode {
|
||||
SubNode::BlendMode
|
||||
} else if keep_opacity || keep_fill {
|
||||
SubNode::Opacity
|
||||
} else if keep_clip {
|
||||
SubNode::Clip
|
||||
} else {
|
||||
// Everything was at default and not exposed: leave a no-op Blend Mode node so the chain structure is preserved
|
||||
SubNode::BlendMode
|
||||
};
|
||||
|
||||
let identifier_for = |kind: SubNode| match kind {
|
||||
SubNode::BlendMode => graphene_std::blending_nodes::blend_mode::IDENTIFIER,
|
||||
SubNode::Opacity => graphene_std::blending_nodes::opacity::IDENTIFIER,
|
||||
SubNode::Clip => graphene_std::blending_nodes::clipping_mask::IDENTIFIER,
|
||||
};
|
||||
|
||||
// Replace the existing node's implementation with the first kept sub-node's template
|
||||
let first_reference = DefinitionIdentifier::ProtoNode(identifier_for(first_kind));
|
||||
let mut first_template = resolve_document_node_type(&first_reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut first_template);
|
||||
let _ = document.network_interface.replace_inputs(node_id, network_path, &mut first_template);
|
||||
|
||||
// Wire the existing node's inputs based on its new role (input 0 is always `content`)
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), content_input, network_path);
|
||||
match first_kind {
|
||||
SubNode::BlendMode => {
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), blend_mode_input.clone(), network_path);
|
||||
}
|
||||
SubNode::Opacity => {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Bool(keep_opacity), false), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), opacity_input.clone(), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(keep_fill), false), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), fill_input.clone(), network_path);
|
||||
}
|
||||
SubNode::Clip => {
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), clip_input.clone(), network_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Build the list of remaining sub-nodes to insert (after the first one), in chain order. Blend Mode is never
|
||||
// reinserted because if it was kept it would already be the `first_kind`.
|
||||
let mut remaining: Vec<SubNode> = Vec::new();
|
||||
if first_kind != SubNode::Opacity && (keep_opacity || keep_fill) {
|
||||
remaining.push(SubNode::Opacity);
|
||||
}
|
||||
if first_kind != SubNode::Clip && keep_clip {
|
||||
remaining.push(SubNode::Clip);
|
||||
}
|
||||
|
||||
// Insert each remaining sub-node into the chain between the previous node and the original downstream
|
||||
let mut chain_x_offset = 7;
|
||||
for sub in remaining {
|
||||
let identifier = identifier_for(sub);
|
||||
let new_id = NodeId::new();
|
||||
let definition = match resolve_document_node_type(&DefinitionIdentifier::ProtoNode(identifier.clone())) {
|
||||
Some(definition) => definition,
|
||||
None => {
|
||||
log::error!("Could not resolve `{identifier:?}` while migrating Blending node");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let template = definition.default_node_template();
|
||||
document.network_interface.insert_node(new_id, template, network_path);
|
||||
document
|
||||
.network_interface
|
||||
.shift_absolute_node_position(&new_id, base_position + IVec2::new(chain_x_offset, 0), network_path);
|
||||
chain_x_offset += 7;
|
||||
|
||||
// Splice this node in just before the original downstream input (so each iteration appends to the chain end)
|
||||
if let Some(downstream_input) = &downstream {
|
||||
document.network_interface.insert_node_between(&new_id, downstream_input, 0, network_path);
|
||||
}
|
||||
|
||||
// Wire the parameter inputs for the inserted node
|
||||
match sub {
|
||||
SubNode::BlendMode => {
|
||||
document.network_interface.set_input(&InputConnector::node(new_id, 1), blend_mode_input.clone(), network_path);
|
||||
}
|
||||
SubNode::Opacity => {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(new_id, 1), NodeInput::value(TaggedValue::Bool(keep_opacity), false), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(new_id, 2), opacity_input.clone(), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(new_id, 3), NodeInput::value(TaggedValue::Bool(keep_fill), false), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(new_id, 4), fill_input.clone(), network_path);
|
||||
}
|
||||
SubNode::Clip => {
|
||||
document.network_interface.set_input(&InputConnector::node(new_id, 1), clip_input.clone(), network_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputs_count = match first_kind {
|
||||
SubNode::BlendMode => 2,
|
||||
SubNode::Opacity => 5,
|
||||
SubNode::Clip => 2,
|
||||
};
|
||||
}
|
||||
|
||||
// Upgrade old single-purpose Opacity node (content, opacity) to the new shape that also covers fill:
|
||||
// (content, has_opacity, opacity, has_fill, fill). The original opacity input is preserved with `has_opacity` enabled,
|
||||
// while `has_fill` is left disabled and the fill value defaults to 100% (no change to fill behavior).
|
||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::blending_nodes::opacity::IDENTIFIER) && inputs_count == 2 {
|
||||
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Bool(true), false), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(false), false), network_path);
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 4), NodeInput::value(TaggedValue::F64(100.), false), network_path);
|
||||
|
||||
inputs_count = 5;
|
||||
}
|
||||
|
||||
// Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644)
|
||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER) && inputs_count == 8 {
|
||||
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
||||
|
||||
@@ -333,25 +333,28 @@ pub fn get_fill_color(layer: LayerNodeIdentifier, network_interface: &NodeNetwor
|
||||
Some(color.to_linear_srgb())
|
||||
}
|
||||
|
||||
/// Get the current blend mode of a layer from the closest "Blending" node.
|
||||
/// Get the current blend mode of a layer from the closest upstream "Blend Mode" node.
|
||||
pub fn get_blend_mode(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<BlendMode> {
|
||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::blending_nodes::blending::IDENTIFIER))?;
|
||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::blending_nodes::blend_mode::IDENTIFIER))?;
|
||||
let TaggedValue::BlendMode(blend_mode) = inputs.get(1)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
Some(*blend_mode)
|
||||
}
|
||||
|
||||
/// Get the current opacity of a layer from the closest "Blending" node.
|
||||
/// Get the current opacity of a layer from the closest upstream "Opacity" node, only when the node's `has_opacity` checkbox is enabled.
|
||||
/// This may differ from the actual opacity contained within the data type reaching this layer, because that actual opacity may be:
|
||||
/// - Multiplied with additional opacity nodes earlier in the chain
|
||||
/// - Multiplied with additional Opacity nodes earlier in the chain
|
||||
/// - Set by an Opacity node with an exposed input value driven by another node
|
||||
/// - Already factored into the pixel alpha channel of an image
|
||||
/// - The default value of 100% if no Opacity node is present, but this function returns None in that case
|
||||
/// - The default value of 100% if no Opacity node has its checkbox enabled (this function returns `None` in that case)
|
||||
///
|
||||
/// With those limitations in mind, the intention of this function is to show just the value already present in an upstream Opacity node so that value can be directly edited.
|
||||
pub fn get_opacity(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {
|
||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::blending_nodes::blending::IDENTIFIER))?;
|
||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::blending_nodes::opacity::IDENTIFIER))?;
|
||||
let TaggedValue::Bool(true) = inputs.get(1)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
let TaggedValue::F64(opacity) = inputs.get(2)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
@@ -359,16 +362,20 @@ pub fn get_opacity(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn
|
||||
}
|
||||
|
||||
pub fn get_clip_mode(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<bool> {
|
||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::blending_nodes::blending::IDENTIFIER))?;
|
||||
let TaggedValue::Bool(clip) = inputs.get(4)?.as_value()? else {
|
||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::blending_nodes::clipping_mask::IDENTIFIER))?;
|
||||
let TaggedValue::Bool(clip) = inputs.get(1)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
Some(*clip)
|
||||
}
|
||||
|
||||
/// Get the current fill of a layer from the closest upstream "Opacity" node, only when the node's `has_fill` checkbox is enabled.
|
||||
pub fn get_fill(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {
|
||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::blending_nodes::blending::IDENTIFIER))?;
|
||||
let TaggedValue::F64(fill) = inputs.get(3)?.as_value()? else {
|
||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::blending_nodes::opacity::IDENTIFIER))?;
|
||||
let TaggedValue::Bool(true) = inputs.get(3)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
let TaggedValue::F64(fill) = inputs.get(4)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
Some(*fill)
|
||||
|
||||
Reference in New Issue
Block a user