mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 01:48:11 +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
@@ -54,6 +54,10 @@ impl DocumentMetadata {
|
|||||||
self.all_layers().filter(|layer| self.selected_nodes.contains(&layer.to_node()))
|
self.all_layers().filter(|layer| self.selected_nodes.contains(&layer.to_node()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn selected_layers_except_artboards(&self) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
|
||||||
|
self.selected_layers().filter(move |layer| !self.artboards.contains(layer))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn selected_layers_contains(&self, layer: LayerNodeIdentifier) -> bool {
|
pub fn selected_layers_contains(&self, layer: LayerNodeIdentifier) -> bool {
|
||||||
self.selected_layers().any(|selected| selected == layer)
|
self.selected_layers().any(|selected| selected == layer)
|
||||||
}
|
}
|
||||||
@@ -259,7 +263,7 @@ impl DocumentMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_artboard(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
|
pub fn is_artboard(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
|
||||||
network.upstream_flow_back_from_nodes(vec![layer.to_node()], true).any(|(node, _)| node.name == "Artboard")
|
network.upstream_flow_back_from_nodes(vec![layer.to_node()], true).any(|(node, _)| node.is_artboard())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_folder(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
|
pub fn is_folder(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
|
||||||
@@ -267,7 +271,7 @@ pub fn is_folder(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
|
|||||||
|| network
|
|| network
|
||||||
.upstream_flow_back_from_nodes(vec![layer.to_node()], true)
|
.upstream_flow_back_from_nodes(vec![layer.to_node()], true)
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.any(|(node, _)| node.name == "Artboard" || node.is_layer())
|
.any(|(node, _)| node.is_artboard() || node.is_layer())
|
||||||
}
|
}
|
||||||
|
|
||||||
// click targets
|
// click targets
|
||||||
|
|||||||
@@ -342,13 +342,7 @@ impl Layer {
|
|||||||
self.transform.to_cols_array().iter().enumerate().for_each(|(i, f)| {
|
self.transform.to_cols_array().iter().enumerate().for_each(|(i, f)| {
|
||||||
let _ = self.cache.write_str(&(f.to_string() + if i == 5 { "" } else { "," }));
|
let _ = self.cache.write_str(&(f.to_string() + if i == 5 { "" } else { "," }));
|
||||||
});
|
});
|
||||||
let _ = write!(
|
let _ = write!(self.cache, r#")" style="opacity: {};{}">{}</g>"#, self.opacity, self.blend_mode.render(), self.thumbnail_cache.as_str());
|
||||||
self.cache,
|
|
||||||
r#")" style="mix-blend-mode: {}; opacity: {}">{}</g>"#,
|
|
||||||
self.blend_mode.to_svg_style_name(),
|
|
||||||
self.opacity,
|
|
||||||
self.thumbnail_cache.as_str()
|
|
||||||
);
|
|
||||||
|
|
||||||
self.cache_dirty = false;
|
self.cache_dirty = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArtworkDialog, LicensesDialog};
|
use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArtworkDialog, LicensesDialog};
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
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;
|
||||||
|
|
||||||
/// Stores the dialogs which require state. These are the ones that have their own message handlers, and are not the ones defined in `simple_dialogs`.
|
/// Stores the dialogs which require state. These are the ones that have their own message handlers, and are not the ones defined in `simple_dialogs`.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
@@ -78,7 +78,7 @@ impl MessageHandler<DialogMessage, DialogData<'_>> for DialogMessageHandler {
|
|||||||
.document_legacy
|
.document_legacy
|
||||||
.metadata
|
.metadata
|
||||||
.all_layers()
|
.all_layers()
|
||||||
.filter(|&layer| is_artboard(layer, &document.document_legacy))
|
.filter(|&layer| is_layer_fed_by_node_of_name(layer, &document.document_legacy, "Artboard"))
|
||||||
.map(|layer| {
|
.map(|layer| {
|
||||||
(
|
(
|
||||||
layer,
|
layer,
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ pub enum FrontendMessage {
|
|||||||
#[serde(rename = "hintData")]
|
#[serde(rename = "hintData")]
|
||||||
hint_data: HintData,
|
hint_data: HintData,
|
||||||
},
|
},
|
||||||
UpdateLayerTreeOptionsLayout {
|
UpdateLayersPanelOptionsLayout {
|
||||||
#[serde(rename = "layoutTarget")]
|
#[serde(rename = "layoutTarget")]
|
||||||
layout_target: LayoutTarget,
|
layout_target: LayoutTarget,
|
||||||
diff: Vec<WidgetDiff>,
|
diff: Vec<WidgetDiff>,
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ impl LayoutMessageHandler {
|
|||||||
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { layout_target, diff },
|
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { layout_target, diff },
|
||||||
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { layout_target, diff },
|
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { layout_target, diff },
|
||||||
LayoutTarget::GraphViewOverlayButton => FrontendMessage::UpdateGraphViewOverlayButtonLayout { layout_target, diff },
|
LayoutTarget::GraphViewOverlayButton => FrontendMessage::UpdateGraphViewOverlayButtonLayout { layout_target, diff },
|
||||||
LayoutTarget::LayerTreeOptions => FrontendMessage::UpdateLayerTreeOptionsLayout { layout_target, diff },
|
LayoutTarget::LayersPanelOptions => FrontendMessage::UpdateLayersPanelOptionsLayout { layout_target, diff },
|
||||||
LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"),
|
LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"),
|
||||||
LayoutTarget::NodeGraphBar => FrontendMessage::UpdateNodeGraphBarLayout { layout_target, diff },
|
LayoutTarget::NodeGraphBar => FrontendMessage::UpdateNodeGraphBarLayout { layout_target, diff },
|
||||||
LayoutTarget::PropertiesOptions => FrontendMessage::UpdatePropertyPanelOptionsLayout { layout_target, diff },
|
LayoutTarget::PropertiesOptions => FrontendMessage::UpdatePropertyPanelOptionsLayout { layout_target, diff },
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ pub enum LayoutTarget {
|
|||||||
/// The button below the tool shelf and directly above the working colors which lets the user toggle the node graph overlaid on the canvas.
|
/// The button below the tool shelf and directly above the working colors which lets the user toggle the node graph overlaid on the canvas.
|
||||||
GraphViewOverlayButton,
|
GraphViewOverlayButton,
|
||||||
/// Options for opacity seen at the top of the Layers panel.
|
/// Options for opacity seen at the top of the Layers panel.
|
||||||
LayerTreeOptions,
|
LayersPanelOptions,
|
||||||
/// The dropdown menu at the very top of the application: File, Edit, etc.
|
/// The dropdown menu at the very top of the application: File, Edit, etc.
|
||||||
MenuBar,
|
MenuBar,
|
||||||
/// Bar at the top of the node graph containing the location and the "Preview" and "Hide" buttons.
|
/// Bar at the top of the node graph containing the location and the "Preview" and "Hide" buttons.
|
||||||
|
|||||||
@@ -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::document::utility_types::vectorize_layer_metadata;
|
||||||
use crate::messages::portfolio::utility_types::PersistentData;
|
use crate::messages::portfolio::utility_types::PersistentData;
|
||||||
use crate::messages::prelude::*;
|
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::messages::tool::utility_types::ToolType;
|
||||||
use crate::node_graph_executor::NodeGraphExecutor;
|
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(FolderChanged { affected_folder_path: vec![] });
|
||||||
responses.add(BroadcastEvent::SelectionChanged);
|
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 } => {
|
AlignSelectedLayers { axis, aggregate } => {
|
||||||
self.backup(responses);
|
self.backup(responses);
|
||||||
@@ -268,7 +269,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
|||||||
// Clear the options bar
|
// Clear the options bar
|
||||||
responses.add(LayoutMessage::SendLayout {
|
responses.add(LayoutMessage::SendLayout {
|
||||||
layout: Layout::WidgetLayout(Default::default()),
|
layout: Layout::WidgetLayout(Default::default()),
|
||||||
layout_target: LayoutTarget::LayerTreeOptions,
|
layout_target: LayoutTarget::LayersPanelOptions,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
CommitTransaction => (),
|
CommitTransaction => (),
|
||||||
@@ -449,7 +450,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
|||||||
responses.add(FrontendMessage::UpdateDocumentLayerDetails { data: layer_entry });
|
responses.add(FrontendMessage::UpdateDocumentLayerDetails { data: layer_entry });
|
||||||
}
|
}
|
||||||
responses.add(PropertiesPanelMessage::CheckSelectedWasUpdated { path: affected_layer_path });
|
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 } => {
|
MoveSelectedLayersTo { parent, insert_index } => {
|
||||||
let selected_layers = self.metadata().selected_layers().collect::<Vec<_>>();
|
let selected_layers = self.metadata().selected_layers().collect::<Vec<_>>();
|
||||||
@@ -697,8 +698,8 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
|||||||
}
|
}
|
||||||
SetBlendModeForSelectedLayers { blend_mode } => {
|
SetBlendModeForSelectedLayers { blend_mode } => {
|
||||||
self.backup(responses);
|
self.backup(responses);
|
||||||
for path in self.selected_layers() {
|
for layer in self.metadata().selected_layers_except_artboards() {
|
||||||
responses.add(DocumentOperation::SetLayerBlendMode { path: path.to_vec(), blend_mode });
|
responses.add(GraphOperationMessage::BlendModeSet { layer: layer.to_path(), blend_mode });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SetImageBlobUrl {
|
SetImageBlobUrl {
|
||||||
@@ -737,10 +738,10 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
|||||||
}
|
}
|
||||||
SetOpacityForSelectedLayers { opacity } => {
|
SetOpacityForSelectedLayers { opacity } => {
|
||||||
self.backup(responses);
|
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()) {
|
for layer in self.metadata().selected_layers_except_artboards() {
|
||||||
responses.add(DocumentOperation::SetLayerOpacity { path, opacity });
|
responses.add(GraphOperationMessage::OpacitySet { layer: layer.to_path(), opacity });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SetOverlaysVisibility { visible } => {
|
SetOverlaysVisibility { visible } => {
|
||||||
@@ -1519,72 +1520,71 @@ impl DocumentMessageHandler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_layer_tree_options_bar_widgets(&self, responses: &mut VecDeque<Message>, render_data: &RenderData) {
|
pub fn update_layers_panel_options_bar_widgets(&self, responses: &mut VecDeque<Message>) {
|
||||||
let mut opacity = None;
|
// Get an iterator over the selected layers (excluding artboards which don't have an opacity or blend mode).
|
||||||
let mut opacity_is_mixed = false;
|
let selected_layers_except_artboards = self.metadata().selected_layers_except_artboards();
|
||||||
|
|
||||||
let mut blend_mode = None;
|
// 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 blend_mode_is_mixed = false;
|
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
|
// If there are no selected layers, disable the opacity and blend mode widgets.
|
||||||
.keys()
|
let disabled = first_opacity_and_blend_mode.is_none();
|
||||||
.filter_map(|path| self.layer_panel_entry_from_path(path, render_data))
|
|
||||||
.filter(|layer_panel_entry| layer_panel_entry.layer_metadata.selected)
|
// Amongst the selected layers, check if the opacities and blend modes are identical across all layers.
|
||||||
.flat_map(|layer_panel_entry| self.document_legacy.layer(layer_panel_entry.path.as_slice()))
|
// The result is setting `option` and `blend_mode` to Some value if all their values are identical, or None if they are not.
|
||||||
.for_each(|layer| {
|
// If identical, we display the value in the widget. If not, we display a dash indicating dissimilarity.
|
||||||
match opacity {
|
let (opacity, blend_mode) = first_opacity_and_blend_mode
|
||||||
None => opacity = Some(layer.opacity),
|
.map(|(first_opacity, first_blend_mode)| {
|
||||||
Some(opacity) => {
|
let mut opacity_identical = true;
|
||||||
if (opacity - layer.opacity).abs() > (1. / 1_000_000.) {
|
let mut blend_mode_identical = true;
|
||||||
opacity_is_mixed = 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 {
|
(opacity_identical.then(|| first_opacity), blend_mode_identical.then(|| first_blend_mode))
|
||||||
None => blend_mode = Some(layer.blend_mode),
|
})
|
||||||
Some(blend_mode) => {
|
.unwrap_or((None, None));
|
||||||
if blend_mode != layer.blend_mode {
|
|
||||||
blend_mode_is_mixed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if opacity_is_mixed {
|
let blend_mode_menu_entries = BlendMode::list_svg_subset()
|
||||||
opacity = None;
|
|
||||||
}
|
|
||||||
if blend_mode_is_mixed {
|
|
||||||
blend_mode = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let blend_mode_menu_entries = BlendMode::list_modes_in_groups()
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|modes| {
|
.map(|modes| {
|
||||||
modes
|
modes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|mode| {
|
.map(|&blend_mode| {
|
||||||
MenuListEntry::new(mode.to_string())
|
MenuListEntry::new(blend_mode.to_string())
|
||||||
.value(mode.to_string())
|
.value(blend_mode.to_string())
|
||||||
.on_update(|_| DocumentMessage::SetBlendModeForSelectedLayers { blend_mode: *mode }.into())
|
.on_update(move |_| DocumentMessage::SetBlendModeForSelectedLayers { blend_mode }.into())
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let layer_tree_options = WidgetLayout::new(vec![LayoutGroup::Row {
|
let layers_panel_options_bar = WidgetLayout::new(vec![LayoutGroup::Row {
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
DropdownInput::new(blend_mode_menu_entries)
|
DropdownInput::new(blend_mode_menu_entries)
|
||||||
.selected_index(blend_mode.map(|blend_mode| blend_mode as u32))
|
.selected_index(blend_mode.map(|blend_mode| blend_mode.index_in_list_svg_subset()).flatten().map(|index| index as u32))
|
||||||
.disabled(blend_mode.is_none() && !blend_mode_is_mixed)
|
.disabled(disabled)
|
||||||
.draw_icon(false)
|
.draw_icon(false)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
Separator::new(SeparatorType::Related).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")
|
.label("Opacity")
|
||||||
.unit("%")
|
.unit("%")
|
||||||
.display_decimal_places(2)
|
.display_decimal_places(2)
|
||||||
.disabled(opacity.is_none() && !opacity_is_mixed)
|
.disabled(disabled)
|
||||||
.min(0.)
|
.min(0.)
|
||||||
.max(100.)
|
.max(100.)
|
||||||
.range_min(Some(0.))
|
.range_min(Some(0.))
|
||||||
@@ -1613,8 +1613,8 @@ impl DocumentMessageHandler {
|
|||||||
}]);
|
}]);
|
||||||
|
|
||||||
responses.add(LayoutMessage::SendLayout {
|
responses.add(LayoutMessage::SendLayout {
|
||||||
layout: Layout::WidgetLayout(layer_tree_options),
|
layout: Layout::WidgetLayout(layers_panel_options_bar),
|
||||||
layout_target: LayoutTarget::LayerTreeOptions,
|
layout_target: LayoutTarget::LayersPanelOptions,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use bezier_rs::Subpath;
|
|||||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||||
use graph_craft::document::DocumentNode;
|
use graph_craft::document::DocumentNode;
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
|
use graphene_core::raster::BlendMode;
|
||||||
use graphene_core::raster::ImageFrame;
|
use graphene_core::raster::ImageFrame;
|
||||||
use graphene_core::text::Font;
|
use graphene_core::text::Font;
|
||||||
use graphene_core::uuid::ManipulatorGroupId;
|
use graphene_core::uuid::ManipulatorGroupId;
|
||||||
@@ -23,6 +24,14 @@ pub enum GraphOperationMessage {
|
|||||||
layer: LayerIdentifier,
|
layer: LayerIdentifier,
|
||||||
fill: Fill,
|
fill: Fill,
|
||||||
},
|
},
|
||||||
|
OpacitySet {
|
||||||
|
layer: LayerIdentifier,
|
||||||
|
opacity: f32,
|
||||||
|
},
|
||||||
|
BlendModeSet {
|
||||||
|
layer: LayerIdentifier,
|
||||||
|
blend_mode: BlendMode,
|
||||||
|
},
|
||||||
UpdateBounds {
|
UpdateBounds {
|
||||||
layer: LayerIdentifier,
|
layer: LayerIdentifier,
|
||||||
old_bounds: [DVec2; 2],
|
old_bounds: [DVec2; 2],
|
||||||
|
|||||||
+30
-3
@@ -7,7 +7,7 @@ use document_legacy::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
|||||||
use document_legacy::{LayerId, Operation};
|
use document_legacy::{LayerId, Operation};
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{generate_uuid, DocumentNode, NodeId, NodeInput, NodeNetwork, NodeOutput};
|
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::text::Font;
|
||||||
use graphene_core::uuid::ManipulatorGroupId;
|
use graphene_core::uuid::ManipulatorGroupId;
|
||||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
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 new_input = output_node.inputs.first().cloned().filter(|input| input.as_node().is_some());
|
||||||
let node_id = generate_uuid();
|
let node_id = generate_uuid();
|
||||||
|
|
||||||
output_node.metadata.position.x += 8;
|
|
||||||
output_node.inputs[0] = NodeInput::node(node_id, 0);
|
output_node.inputs[0] = NodeInput::node(node_id, 0);
|
||||||
|
|
||||||
let Some(node_type) = resolve_document_node_type(name) else {
|
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);
|
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);
|
update_input(&mut new_document_node.inputs, node_id, self.document_metadata);
|
||||||
self.network.nodes.insert(node_id, new_document_node);
|
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
|
/// 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) {
|
fn stroke_set(&mut self, stroke: Stroke) {
|
||||||
self.modify_inputs("Stroke", false, |inputs, _node_id, _metadata| {
|
self.modify_inputs("Stroke", false, |inputs, _node_id, _metadata| {
|
||||||
inputs[1] = NodeInput::value(TaggedValue::OptionalColor(stroke.color), false);
|
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 });
|
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 } => {
|
GraphOperationMessage::UpdateBounds { layer, old_bounds, new_bounds } => {
|
||||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(&layer, document, node_graph, responses) {
|
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(&layer, document, node_graph, responses) {
|
||||||
modify_inputs.update_bounds(old_bounds, new_bounds);
|
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 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<_>>();
|
let layer_nodes = modify_inputs.network.nodes.iter().filter(|(_, node)| node.is_layer()).map(|(id, _)| *id).collect::<Vec<_>>();
|
||||||
for layer in layer_nodes {
|
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);
|
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")
|
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 {
|
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);
|
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||||
if let &NodeInput::Value {
|
if let &NodeInput::Value {
|
||||||
tagged_value: TaggedValue::BlendMode(mode),
|
tagged_value: TaggedValue::BlendMode(blend_mode),
|
||||||
exposed: false,
|
exposed: false,
|
||||||
} = &document_node.inputs[index]
|
} = &document_node.inputs[index]
|
||||||
{
|
{
|
||||||
let entries = BlendMode::list()
|
let entries = BlendMode::list_svg_subset()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|category| {
|
.map(|category| {
|
||||||
category
|
category
|
||||||
.iter()
|
.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()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
widgets.extend_from_slice(&[
|
widgets.extend_from_slice(&[
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
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")
|
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 {
|
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);
|
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||||
if let &NodeInput::Value {
|
if let &NodeInput::Value {
|
||||||
|
|||||||
@@ -680,7 +680,7 @@ impl PortfolioMessageHandler {
|
|||||||
.map(|entry| FrontendMessage::UpdateDocumentLayerDetails { data: entry }.into())
|
.map(|entry| FrontendMessage::UpdateDocumentLayerDetails { data: entry }.into())
|
||||||
.collect::<Vec<_>>(),
|
.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);
|
self.documents.insert(document_id, new_document);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::messages::prelude::*;
|
|||||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||||
use document_legacy::{document::Document, document_metadata::LayerNodeIdentifier, LayerId, Operation};
|
use document_legacy::{document::Document, document_metadata::LayerNodeIdentifier, LayerId, Operation};
|
||||||
use graph_craft::document::{value::TaggedValue, DocumentNode, NodeId, NodeInput, NodeNetwork};
|
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::text::Font;
|
||||||
use graphene_core::uuid::ManipulatorGroupId;
|
use graphene_core::uuid::ManipulatorGroupId;
|
||||||
use graphene_core::vector::style::{FillType, Gradient};
|
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> {
|
pub fn get_gradient(layer: LayerNodeIdentifier, document: &Document) -> Option<Gradient> {
|
||||||
let inputs = NodeGraphLayer::new(layer, document)?.find_node_inputs("Fill")?;
|
let inputs = NodeGraphLayer::new(layer, document)?.find_node_inputs("Fill")?;
|
||||||
let TaggedValue::FillType(FillType::Gradient) = inputs.get(1)?.as_value()? else {
|
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> {
|
pub fn get_fill_color(layer: LayerNodeIdentifier, document: &Document) -> Option<Color> {
|
||||||
let inputs = NodeGraphLayer::new(layer, document)?.find_node_inputs("Fill")?;
|
let inputs = NodeGraphLayer::new(layer, document)?.find_node_inputs("Fill")?;
|
||||||
let TaggedValue::Color(color) = inputs.get(2)?.as_value()? else {
|
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)
|
Some(*color)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_text_id(layer: LayerNodeIdentifier, document: &Document) -> Option<NodeId> {
|
/// Get the current blend mode of a layer from the closest Blend Mode node
|
||||||
NodeGraphLayer::new(layer, document)?.node_id("Text")
|
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> {
|
pub fn get_fill_id(layer: LayerNodeIdentifier, document: &Document) -> Option<NodeId> {
|
||||||
NodeGraphLayer::new(layer, document)?.node_id("Fill")
|
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)> {
|
pub fn get_text(layer: LayerNodeIdentifier, document: &Document) -> Option<(&String, &Font, f64)> {
|
||||||
let inputs = NodeGraphLayer::new(layer, document)?.find_node_inputs("Text")?;
|
let inputs = NodeGraphLayer::new(layer, document)?.find_node_inputs("Text")?;
|
||||||
let NodeInput::Value {
|
let NodeInput::Value {
|
||||||
@@ -174,19 +199,9 @@ pub fn get_text(layer: LayerNodeIdentifier, document: &Document) -> Option<(&Str
|
|||||||
Some((text, font, font_size))
|
Some((text, font, font_size))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is a specified layer an artboard?
|
/// Checks if a specified layer uses an upstream node matching the given name.
|
||||||
pub fn is_artboard(layer: LayerNodeIdentifier, document: &Document) -> bool {
|
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.uses_node("Artboard"))
|
NodeGraphLayer::new(layer, document).is_some_and(|layer| layer.find_node_inputs(node_name).is_some())
|
||||||
}
|
|
||||||
|
|
||||||
/// 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"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert subpaths to an iterator of manipulator groups
|
/// 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)
|
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
|
/// Node id of a node if it exists in the layer's primary flow
|
||||||
pub fn node_id(&self, node_name: &str) -> Option<NodeId> {
|
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)
|
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>> {
|
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
|
/// Find a specific input of a node within the layer's primary flow
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::application::generate_uuid;
|
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::snapping::SnapManager;
|
||||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
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 {
|
fn select_artboard(&mut self, document: &DocumentMessageHandler, render_data: &RenderData, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> bool {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
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);
|
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||||
if let Some(intersection) = intersections.next() {
|
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::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
|
||||||
use graphene_core::Color;
|
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.;
|
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)]
|
#[derive(PartialEq, Copy, Clone, Debug, Serialize, Deserialize, specta::Type)]
|
||||||
pub enum DrawMode {
|
pub enum DrawMode {
|
||||||
Draw = 0,
|
Draw = 0,
|
||||||
@@ -192,7 +161,7 @@ impl LayoutHolder for BrushTool {
|
|||||||
|
|
||||||
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
|
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()
|
.iter()
|
||||||
.map(|group| {
|
.map(|group| {
|
||||||
group
|
group
|
||||||
@@ -207,7 +176,7 @@ impl LayoutHolder for BrushTool {
|
|||||||
.collect();
|
.collect();
|
||||||
widgets.push(
|
widgets.push(
|
||||||
DropdownInput::new(blend_mode_entries)
|
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.")
|
.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)
|
.disabled(self.options.draw_mode != DrawMode::Draw)
|
||||||
.widget_holder(),
|
.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::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::misc::{AlignAggregate, AlignAxis, FlipAxis};
|
||||||
use crate::messages::portfolio::document::utility_types::transformation::Selected;
|
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_layer_fed_by_node_of_name;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::is_text_layer;
|
|
||||||
use crate::messages::tool::common_functionality::path_outline::*;
|
use crate::messages::tool::common_functionality::path_outline::*;
|
||||||
use crate::messages::tool::common_functionality::pivot::Pivot;
|
use crate::messages::tool::common_functionality::pivot::Pivot;
|
||||||
use crate::messages::tool::common_functionality::snapping::{self, SnapManager};
|
use crate::messages::tool::common_functionality::snapping::{self, SnapManager};
|
||||||
@@ -804,7 +803,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
|
|
||||||
if let Some(layer) = selected_layers.next() {
|
if let Some(layer) = selected_layers.next() {
|
||||||
// Check that only one layer is selected
|
// 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_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });
|
||||||
responses.add(TextToolMessage::EditSelected);
|
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>) {
|
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_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });
|
||||||
responses.add(TextToolMessage::EditSelected);
|
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 });
|
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Path });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use super::tool_prelude::*;
|
|||||||
use crate::application::generate_uuid;
|
use crate::application::generate_uuid;
|
||||||
use crate::consts::COLOR_ACCENT;
|
use crate::consts::COLOR_ACCENT;
|
||||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
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::document_metadata::LayerNodeIdentifier;
|
||||||
use document_legacy::intersection::Quad;
|
use document_legacy::intersection::Quad;
|
||||||
@@ -277,7 +277,7 @@ impl TextToolData {
|
|||||||
if let Some(clicked_text_layer_path) = document
|
if let Some(clicked_text_layer_path) = document
|
||||||
.document_legacy
|
.document_legacy
|
||||||
.click(mouse, document.network())
|
.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);
|
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;
|
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;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import { beginDraggingElement } from "@graphite/io-managers/drag";
|
import { beginDraggingElement } from "@graphite/io-managers/drag";
|
||||||
import { platformIsMac } from "@graphite/utility-functions/platform";
|
import { platformIsMac } from "@graphite/utility-functions/platform";
|
||||||
import type { Editor } from "@graphite/wasm-communication/editor";
|
import type { Editor } from "@graphite/wasm-communication/editor";
|
||||||
import { defaultWidgetLayout, patchWidgetLayout, UpdateDocumentLayerDetails, UpdateDocumentLayerTreeStructureJs, UpdateLayerTreeOptionsLayout } from "@graphite/wasm-communication/messages";
|
import { defaultWidgetLayout, patchWidgetLayout, UpdateDocumentLayerDetails, UpdateDocumentLayerTreeStructureJs, UpdateLayersPanelOptionsLayout } from "@graphite/wasm-communication/messages";
|
||||||
import type { LayerType, LayerPanelEntry } from "@graphite/wasm-communication/messages";
|
import type { LayerType, LayerPanelEntry } from "@graphite/wasm-communication/messages";
|
||||||
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
@@ -46,12 +46,12 @@
|
|||||||
let dragInPanel = false;
|
let dragInPanel = false;
|
||||||
|
|
||||||
// Layouts
|
// Layouts
|
||||||
let layerTreeOptionsLayout = defaultWidgetLayout();
|
let layersPanelOptionsLayout = defaultWidgetLayout();
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateLayerTreeOptionsLayout, (updateLayerTreeOptionsLayout) => {
|
editor.subscriptions.subscribeJsMessage(UpdateLayersPanelOptionsLayout, (updateLayersPanelOptionsLayout) => {
|
||||||
patchWidgetLayout(layerTreeOptionsLayout, updateLayerTreeOptionsLayout);
|
patchWidgetLayout(layersPanelOptionsLayout, updateLayersPanelOptionsLayout);
|
||||||
layerTreeOptionsLayout = layerTreeOptionsLayout;
|
layersPanelOptionsLayout = layersPanelOptionsLayout;
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerTreeStructureJs, (updateDocumentLayerTreeStructure) => {
|
editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerTreeStructureJs, (updateDocumentLayerTreeStructure) => {
|
||||||
@@ -301,7 +301,7 @@
|
|||||||
|
|
||||||
<LayoutCol class="layers" on:dragleave={() => (dragInPanel = false)}>
|
<LayoutCol class="layers" on:dragleave={() => (dragInPanel = false)}>
|
||||||
<LayoutRow class="options-bar" scrollableX={true}>
|
<LayoutRow class="options-bar" scrollableX={true}>
|
||||||
<WidgetLayout layout={layerTreeOptionsLayout} />
|
<WidgetLayout layout={layersPanelOptionsLayout} />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<LayoutRow class="list-area" scrollableY={true}>
|
<LayoutRow class="list-area" scrollableY={true}>
|
||||||
<LayoutCol class="list" bind:this={list} on:click={() => deselectAllLayers()} on:dragover={(e) => draggable && updateInsertLine(e)} on:dragend={() => draggable && drop()}>
|
<LayoutCol class="list" bind:this={list} on:click={() => deselectAllLayers()} on:dragover={(e) => draggable && updateInsertLine(e)} on:dragend={() => draggable && drop()}>
|
||||||
|
|||||||
@@ -1345,7 +1345,7 @@ export class UpdateDocumentModeLayout extends WidgetDiffUpdate {}
|
|||||||
|
|
||||||
export class UpdateGraphViewOverlayButtonLayout extends WidgetDiffUpdate {}
|
export class UpdateGraphViewOverlayButtonLayout extends WidgetDiffUpdate {}
|
||||||
|
|
||||||
export class UpdateLayerTreeOptionsLayout extends WidgetDiffUpdate {}
|
export class UpdateLayersPanelOptionsLayout extends WidgetDiffUpdate {}
|
||||||
|
|
||||||
// Extends JsMessage instead of WidgetDiffUpdate because the menu bar isn't diffed
|
// Extends JsMessage instead of WidgetDiffUpdate because the menu bar isn't diffed
|
||||||
export class UpdateMenuBarLayout extends JsMessage {
|
export class UpdateMenuBarLayout extends JsMessage {
|
||||||
@@ -1441,7 +1441,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||||||
UpdateGraphViewOverlayButtonLayout,
|
UpdateGraphViewOverlayButtonLayout,
|
||||||
UpdateImageData,
|
UpdateImageData,
|
||||||
UpdateInputHints,
|
UpdateInputHints,
|
||||||
UpdateLayerTreeOptionsLayout,
|
UpdateLayersPanelOptionsLayout,
|
||||||
UpdateMenuBarLayout,
|
UpdateMenuBarLayout,
|
||||||
UpdateMouseCursor,
|
UpdateMouseCursor,
|
||||||
UpdateNodeGraph,
|
UpdateNodeGraph,
|
||||||
|
|||||||
@@ -52,42 +52,14 @@ impl core::fmt::Display for LuminanceCalculation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlendMode {
|
|
||||||
pub fn list() -> [&'static [BlendMode]; 6] {
|
|
||||||
[
|
|
||||||
// Normal group
|
|
||||||
&[BlendMode::Normal],
|
|
||||||
// Darken group
|
|
||||||
&[BlendMode::Darken, BlendMode::Multiply, BlendMode::ColorBurn, BlendMode::LinearBurn, BlendMode::DarkerColor],
|
|
||||||
// Lighten group
|
|
||||||
&[BlendMode::Lighten, BlendMode::Screen, BlendMode::ColorDodge, BlendMode::LinearDodge, BlendMode::LighterColor],
|
|
||||||
// Contrast group
|
|
||||||
&[
|
|
||||||
BlendMode::Overlay,
|
|
||||||
BlendMode::SoftLight,
|
|
||||||
BlendMode::HardLight,
|
|
||||||
BlendMode::VividLight,
|
|
||||||
BlendMode::LinearLight,
|
|
||||||
BlendMode::PinLight,
|
|
||||||
BlendMode::HardMix,
|
|
||||||
],
|
|
||||||
// Inversion group
|
|
||||||
&[BlendMode::Difference, BlendMode::Exclusion, BlendMode::Subtract, BlendMode::Divide],
|
|
||||||
// Component group
|
|
||||||
&[BlendMode::Hue, BlendMode::Saturation, BlendMode::Color, BlendMode::Luminosity],
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "std", derive(specta::Type))]
|
#[cfg_attr(feature = "std", derive(specta::Type))]
|
||||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, DynAny, Hash)]
|
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, DynAny, Hash)]
|
||||||
#[repr(i32)] // TODO: Enable Int8 capability for SPIR-V so that we don't need this?
|
#[repr(i32)] // TODO: Enable Int8 capability for SPIR-V so that we don't need this?
|
||||||
pub enum BlendMode {
|
pub enum BlendMode {
|
||||||
#[default]
|
|
||||||
// Basic group
|
// Basic group
|
||||||
|
#[default]
|
||||||
Normal,
|
Normal,
|
||||||
// Not supported by SVG, but we should someday support: Dissolve
|
|
||||||
|
|
||||||
// Darken group
|
// Darken group
|
||||||
Darken,
|
Darken,
|
||||||
@@ -130,6 +102,95 @@ pub enum BlendMode {
|
|||||||
MultiplyAlpha,
|
MultiplyAlpha,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BlendMode {
|
||||||
|
/// All standard blend modes ordered by group.
|
||||||
|
pub fn list() -> [&'static [BlendMode]; 6] {
|
||||||
|
use BlendMode::*;
|
||||||
|
[
|
||||||
|
// Normal 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],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The subset of [`BlendMode::list()`] that is supported by SVG.
|
||||||
|
pub fn list_svg_subset() -> [&'static [BlendMode]; 6] {
|
||||||
|
use BlendMode::*;
|
||||||
|
[
|
||||||
|
// Normal group
|
||||||
|
&[Normal],
|
||||||
|
// Darken group
|
||||||
|
&[Darken, Multiply, ColorBurn],
|
||||||
|
// Lighten group
|
||||||
|
&[Lighten, Screen, ColorDodge],
|
||||||
|
// Contrast group
|
||||||
|
&[Overlay, SoftLight, HardLight],
|
||||||
|
// Inversion group
|
||||||
|
&[Difference, Exclusion],
|
||||||
|
// Component group
|
||||||
|
&[Hue, Saturation, Color, Luminosity],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn index_in_list(&self) -> Option<usize> {
|
||||||
|
Self::list().iter().flat_map(|x| x.iter()).position(|&blend_mode| blend_mode == *self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn index_in_list_svg_subset(&self) -> Option<usize> {
|
||||||
|
Self::list_svg_subset().iter().flat_map(|x| x.iter()).position(|&blend_mode| blend_mode == *self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert the enum to the CSS string for the blend mode.
|
||||||
|
/// [Read more](https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode#values)
|
||||||
|
pub fn to_svg_style_name(&self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
// Normal group
|
||||||
|
BlendMode::Normal => Some("normal"),
|
||||||
|
// Darken group
|
||||||
|
BlendMode::Darken => Some("darken"),
|
||||||
|
BlendMode::Multiply => Some("multiply"),
|
||||||
|
BlendMode::ColorBurn => Some("color-burn"),
|
||||||
|
// Lighten group
|
||||||
|
BlendMode::Lighten => Some("lighten"),
|
||||||
|
BlendMode::Screen => Some("screen"),
|
||||||
|
BlendMode::ColorDodge => Some("color-dodge"),
|
||||||
|
// Contrast group
|
||||||
|
BlendMode::Overlay => Some("overlay"),
|
||||||
|
BlendMode::SoftLight => Some("soft-light"),
|
||||||
|
BlendMode::HardLight => Some("hard-light"),
|
||||||
|
// Inversion group
|
||||||
|
BlendMode::Difference => Some("difference"),
|
||||||
|
BlendMode::Exclusion => Some("exclusion"),
|
||||||
|
// Component group
|
||||||
|
BlendMode::Hue => Some("hue"),
|
||||||
|
BlendMode::Saturation => Some("saturation"),
|
||||||
|
BlendMode::Color => Some("color"),
|
||||||
|
BlendMode::Luminosity => Some("luminosity"),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders the blend mode CSS style declaration.
|
||||||
|
pub fn render(&self) -> String {
|
||||||
|
format!(
|
||||||
|
r#" mix-blend-mode: {};"#,
|
||||||
|
self.to_svg_style_name().unwrap_or_else(|| {
|
||||||
|
warn!("Unsupported blend mode {self:?}");
|
||||||
|
"normal"
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl core::fmt::Display for BlendMode {
|
impl core::fmt::Display for BlendMode {
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
@@ -173,64 +234,6 @@ impl core::fmt::Display for BlendMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlendMode {
|
|
||||||
/// Convert the enum to the CSS string for the blend mode.
|
|
||||||
/// [Read more](https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode#values)
|
|
||||||
pub fn to_svg_style_name(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
// Normal group
|
|
||||||
BlendMode::Normal => "normal",
|
|
||||||
// Darken group
|
|
||||||
BlendMode::Darken => "darken",
|
|
||||||
BlendMode::Multiply => "multiply",
|
|
||||||
BlendMode::ColorBurn => "color-burn",
|
|
||||||
// Lighten group
|
|
||||||
BlendMode::Lighten => "lighten",
|
|
||||||
BlendMode::Screen => "screen",
|
|
||||||
BlendMode::ColorDodge => "color-dodge",
|
|
||||||
// Contrast group
|
|
||||||
BlendMode::Overlay => "overlay",
|
|
||||||
BlendMode::SoftLight => "soft-light",
|
|
||||||
BlendMode::HardLight => "hard-light",
|
|
||||||
// Inversion group
|
|
||||||
BlendMode::Difference => "difference",
|
|
||||||
BlendMode::Exclusion => "exclusion",
|
|
||||||
// Component group
|
|
||||||
BlendMode::Hue => "hue",
|
|
||||||
BlendMode::Saturation => "saturation",
|
|
||||||
BlendMode::Color => "color",
|
|
||||||
BlendMode::Luminosity => "luminosity",
|
|
||||||
_ => {
|
|
||||||
warn!("Unsupported blend mode {self:?}");
|
|
||||||
"normal"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Renders the blend mode CSS style declaration.
|
|
||||||
pub fn render(&self) -> String {
|
|
||||||
format!(r#" mix-blend-mode: {};"#, self.to_svg_style_name())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List of all the blend modes in their conventional ordering and grouping.
|
|
||||||
pub fn list_modes_in_groups() -> [&'static [BlendMode]; 6] {
|
|
||||||
[
|
|
||||||
// Normal group
|
|
||||||
&[BlendMode::Normal],
|
|
||||||
// Darken group
|
|
||||||
&[BlendMode::Darken, BlendMode::Multiply, BlendMode::ColorBurn],
|
|
||||||
// Lighten group
|
|
||||||
&[BlendMode::Lighten, BlendMode::Screen, BlendMode::ColorDodge],
|
|
||||||
// Contrast group
|
|
||||||
&[BlendMode::Overlay, BlendMode::SoftLight, BlendMode::HardLight],
|
|
||||||
// Inversion group
|
|
||||||
&[BlendMode::Difference, BlendMode::Exclusion],
|
|
||||||
// Component group
|
|
||||||
&[BlendMode::Hue, BlendMode::Saturation, BlendMode::Color, BlendMode::Luminosity],
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct LuminanceNode<LuminanceCalculation> {
|
pub struct LuminanceNode<LuminanceCalculation> {
|
||||||
luminance_calc: LuminanceCalculation,
|
luminance_calc: LuminanceCalculation,
|
||||||
|
|||||||
@@ -276,6 +276,12 @@ impl DocumentNode {
|
|||||||
// TODO: Or, more fundamentally separate the concept of a layer from a node.
|
// TODO: Or, more fundamentally separate the concept of a layer from a node.
|
||||||
self.name == "Layer"
|
self.name == "Layer"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_artboard(&self) -> bool {
|
||||||
|
// TODO: Use something more robust than checking against a string.
|
||||||
|
// TODO: Or, more fundamentally separate the concept of a layer from a node.
|
||||||
|
self.name == "Artboard"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the possible inputs to a node.
|
/// Represents the possible inputs to a node.
|
||||||
|
|||||||
Reference in New Issue
Block a user