mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 03:38:13 +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
@@ -4,7 +4,7 @@ use crate::messages::prelude::*;
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use document_legacy::{document::Document, document_metadata::LayerNodeIdentifier, LayerId, Operation};
|
||||
use graph_craft::document::{value::TaggedValue, DocumentNode, NodeId, NodeInput, NodeNetwork};
|
||||
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::style::{FillType, Gradient};
|
||||
@@ -98,7 +98,7 @@ pub fn get_mirror_handles(layer: LayerNodeIdentifier, document: &Document) -> Op
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current gradient of a layer from the closest fill node
|
||||
/// Get the current gradient of a layer from the closest Fill node
|
||||
pub fn get_gradient(layer: LayerNodeIdentifier, document: &Document) -> Option<Gradient> {
|
||||
let inputs = NodeGraphLayer::new(layer, document)?.find_node_inputs("Fill")?;
|
||||
let TaggedValue::FillType(FillType::Gradient) = inputs.get(1)?.as_value()? else {
|
||||
@@ -128,7 +128,7 @@ pub fn get_gradient(layer: LayerNodeIdentifier, document: &Document) -> Option<G
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the current fill of a layer from the closest fill node
|
||||
/// Get the current fill of a layer from the closest Fill node
|
||||
pub fn get_fill_color(layer: LayerNodeIdentifier, document: &Document) -> Option<Color> {
|
||||
let inputs = NodeGraphLayer::new(layer, document)?.find_node_inputs("Fill")?;
|
||||
let TaggedValue::Color(color) = inputs.get(2)?.as_value()? else {
|
||||
@@ -137,14 +137,39 @@ pub fn get_fill_color(layer: LayerNodeIdentifier, document: &Document) -> Option
|
||||
Some(*color)
|
||||
}
|
||||
|
||||
pub fn get_text_id(layer: LayerNodeIdentifier, document: &Document) -> Option<NodeId> {
|
||||
NodeGraphLayer::new(layer, document)?.node_id("Text")
|
||||
/// Get the current blend mode of a layer from the closest Blend Mode node
|
||||
pub fn get_blend_mode(layer: LayerNodeIdentifier, document: &Document) -> Option<BlendMode> {
|
||||
let inputs = NodeGraphLayer::new(layer, document)?.find_node_inputs("Blend Mode")?;
|
||||
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 Opacity node.
|
||||
/// 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
|
||||
/// - Set by an Opacity node with an exposed parameter 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
|
||||
/// 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, document: &Document) -> Option<f32> {
|
||||
let inputs = NodeGraphLayer::new(layer, document)?.find_node_inputs("Opacity")?;
|
||||
let TaggedValue::F32(opacity) = inputs.get(1)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
Some(*opacity)
|
||||
}
|
||||
|
||||
pub fn get_fill_id(layer: LayerNodeIdentifier, document: &Document) -> Option<NodeId> {
|
||||
NodeGraphLayer::new(layer, document)?.node_id("Fill")
|
||||
}
|
||||
|
||||
/// Gets properties from the text node
|
||||
pub fn get_text_id(layer: LayerNodeIdentifier, document: &Document) -> Option<NodeId> {
|
||||
NodeGraphLayer::new(layer, document)?.node_id("Text")
|
||||
}
|
||||
|
||||
/// Gets properties from the Text node
|
||||
pub fn get_text(layer: LayerNodeIdentifier, document: &Document) -> Option<(&String, &Font, f64)> {
|
||||
let inputs = NodeGraphLayer::new(layer, document)?.find_node_inputs("Text")?;
|
||||
let NodeInput::Value {
|
||||
@@ -174,19 +199,9 @@ pub fn get_text(layer: LayerNodeIdentifier, document: &Document) -> Option<(&Str
|
||||
Some((text, font, font_size))
|
||||
}
|
||||
|
||||
/// Is a specified layer an artboard?
|
||||
pub fn is_artboard(layer: LayerNodeIdentifier, document: &Document) -> bool {
|
||||
NodeGraphLayer::new(layer, document).is_some_and(|layer| layer.uses_node("Artboard"))
|
||||
}
|
||||
|
||||
/// Is a specified layer a shape?
|
||||
pub fn is_shape_layer(layer: LayerNodeIdentifier, document: &Document) -> bool {
|
||||
NodeGraphLayer::new(layer, document).is_some_and(|layer| layer.uses_node("Shape"))
|
||||
}
|
||||
|
||||
/// Is a specified layer text?
|
||||
pub fn is_text_layer(layer: LayerNodeIdentifier, document: &Document) -> bool {
|
||||
NodeGraphLayer::new(layer, document).is_some_and(|layer| layer.uses_node("Text"))
|
||||
/// Checks if a specified layer uses an upstream node matching the given name.
|
||||
pub fn is_layer_fed_by_node_of_name(layer: LayerNodeIdentifier, document: &Document, node_name: &str) -> bool {
|
||||
NodeGraphLayer::new(layer, document).is_some_and(|layer| layer.find_node_inputs(node_name).is_some())
|
||||
}
|
||||
|
||||
/// Convert subpaths to an iterator of manipulator groups
|
||||
@@ -243,19 +258,18 @@ impl<'a> NodeGraphLayer<'a> {
|
||||
self.node_graph.upstream_flow_back_from_nodes(vec![self.layer_node], true)
|
||||
}
|
||||
|
||||
/// Does a node exist in the layer's primary flow
|
||||
pub fn uses_node(&self, node_name: &str) -> bool {
|
||||
self.primary_layer_flow().any(|(node, _id)| node.name == node_name)
|
||||
}
|
||||
|
||||
/// Node id of a node if it exists in the layer's primary flow
|
||||
pub fn node_id(&self, node_name: &str) -> Option<NodeId> {
|
||||
self.primary_layer_flow().find(|(node, _id)| node.name == node_name).map(|(_node, id)| id)
|
||||
}
|
||||
|
||||
/// Find all of the inputs of a specific node within the layer's primary flow
|
||||
/// Find all of the inputs of a specific node within the layer's primary flow, up until the next layer is reached.
|
||||
pub fn find_node_inputs(&self, node_name: &str) -> Option<&'a Vec<NodeInput>> {
|
||||
self.primary_layer_flow().find(|(node, _id)| node.name == node_name).map(|(node, _id)| &node.inputs)
|
||||
self.primary_layer_flow()
|
||||
.skip(1)
|
||||
.take_while(|(node, _)| !node.is_layer())
|
||||
.find(|(node, _)| node.name == node_name)
|
||||
.map(|(node, _id)| &node.inputs)
|
||||
}
|
||||
|
||||
/// Find a specific input of a node within the layer's primary flow
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::application::generate_uuid;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::is_artboard;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
|
||||
@@ -150,7 +150,10 @@ impl ArtboardToolData {
|
||||
fn select_artboard(&mut self, document: &DocumentMessageHandler, render_data: &RenderData, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> bool {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let mut intersections = document.document_legacy.click_xray(input.mouse.position).filter(|&layer| is_artboard(layer, &document.document_legacy));
|
||||
let mut intersections = document
|
||||
.document_legacy
|
||||
.click_xray(input.mouse.position)
|
||||
.filter(|&layer| is_layer_fed_by_node_of_name(layer, &document.document_legacy, "Artboard"));
|
||||
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
if let Some(intersection) = intersections.next() {
|
||||
|
||||
@@ -11,39 +11,8 @@ use graphene_core::uuid::generate_uuid;
|
||||
use graphene_core::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
|
||||
use graphene_core::Color;
|
||||
|
||||
const EXPOSED_BLEND_MODES: &[&[BlendMode]] = {
|
||||
use BlendMode::*;
|
||||
&[
|
||||
// Basic group
|
||||
&[Normal],
|
||||
// Darken group
|
||||
&[Darken, Multiply, ColorBurn, LinearBurn, DarkerColor],
|
||||
// Lighten group
|
||||
&[Lighten, Screen, ColorDodge, LinearDodge, LighterColor],
|
||||
// Contrast group
|
||||
&[Overlay, SoftLight, HardLight, VividLight, LinearLight, PinLight, HardMix],
|
||||
// Inversion group
|
||||
&[Difference, Exclusion, Subtract, Divide],
|
||||
// Component group
|
||||
&[Hue, Saturation, Color, Luminosity],
|
||||
]
|
||||
};
|
||||
|
||||
const BRUSH_MAX_SIZE: f64 = 5000.;
|
||||
|
||||
fn blend_mode_dropdown_idx(target_blend_mode: BlendMode) -> Option<u32> {
|
||||
let mut i = 0;
|
||||
for group in EXPOSED_BLEND_MODES {
|
||||
for &blend_mode in group.iter() {
|
||||
if blend_mode == target_blend_mode {
|
||||
return Some(i);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, Debug, Serialize, Deserialize, specta::Type)]
|
||||
pub enum DrawMode {
|
||||
Draw = 0,
|
||||
@@ -192,7 +161,7 @@ impl LayoutHolder for BrushTool {
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
|
||||
|
||||
let blend_mode_entries: Vec<Vec<_>> = EXPOSED_BLEND_MODES
|
||||
let blend_mode_entries: Vec<Vec<_>> = BlendMode::list()
|
||||
.iter()
|
||||
.map(|group| {
|
||||
group
|
||||
@@ -207,7 +176,7 @@ impl LayoutHolder for BrushTool {
|
||||
.collect();
|
||||
widgets.push(
|
||||
DropdownInput::new(blend_mode_entries)
|
||||
.selected_index(blend_mode_dropdown_idx(self.options.blend_mode))
|
||||
.selected_index(self.options.blend_mode.index_in_list().map(|index| index as u32))
|
||||
.tooltip("The blend mode used with the background when performing a brush stroke. Only used in draw mode.")
|
||||
.disabled(self.options.draw_mode != DrawMode::Draw)
|
||||
.widget_holder(),
|
||||
|
||||
@@ -4,8 +4,7 @@ use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE};
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis};
|
||||
use crate::messages::portfolio::document::utility_types::transformation::Selected;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::is_shape_layer;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::is_text_layer;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
|
||||
use crate::messages::tool::common_functionality::path_outline::*;
|
||||
use crate::messages::tool::common_functionality::pivot::Pivot;
|
||||
use crate::messages::tool::common_functionality::snapping::{self, SnapManager};
|
||||
@@ -804,7 +803,7 @@ impl Fsm for SelectToolFsmState {
|
||||
|
||||
if let Some(layer) = selected_layers.next() {
|
||||
// Check that only one layer is selected
|
||||
if selected_layers.next().is_none() && is_text_layer(layer, &document.document_legacy) {
|
||||
if selected_layers.next().is_none() && is_layer_fed_by_node_of_name(layer, &document.document_legacy, "Text") {
|
||||
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });
|
||||
responses.add(TextToolMessage::EditSelected);
|
||||
}
|
||||
@@ -952,10 +951,10 @@ fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, layer:
|
||||
}
|
||||
|
||||
fn edit_layer_deepest_manipulation(layer: LayerNodeIdentifier, document: &Document, responses: &mut VecDeque<Message>) {
|
||||
if is_text_layer(layer, document) {
|
||||
if is_layer_fed_by_node_of_name(layer, document, "Text") {
|
||||
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });
|
||||
responses.add(TextToolMessage::EditSelected);
|
||||
} else if is_shape_layer(layer, document) {
|
||||
} else if is_layer_fed_by_node_of_name(layer, document, "Shape") {
|
||||
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Path });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use super::tool_prelude::*;
|
||||
use crate::application::generate_uuid;
|
||||
use crate::consts::COLOR_ACCENT;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_text_layer};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name};
|
||||
|
||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||
use document_legacy::intersection::Quad;
|
||||
@@ -277,7 +277,7 @@ impl TextToolData {
|
||||
if let Some(clicked_text_layer_path) = document
|
||||
.document_legacy
|
||||
.click(mouse, document.network())
|
||||
.filter(|&layer| is_text_layer(layer, &document.document_legacy))
|
||||
.filter(|&layer| is_layer_fed_by_node_of_name(layer, &document.document_legacy, "Text"))
|
||||
{
|
||||
self.start_editing_layer(clicked_text_layer_path, state, document, render_data, responses);
|
||||
|
||||
@@ -417,7 +417,7 @@ fn can_edit_selected(document: &DocumentMessageHandler) -> Option<LayerNodeIdent
|
||||
return None;
|
||||
}
|
||||
|
||||
if !is_text_layer(layer, &document.document_legacy) {
|
||||
if !is_layer_fed_by_node_of_name(layer, &document.document_legacy, "Text") {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user