mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 19:08:05 +08:00
Migrate demo artwork and fix all failing CI tests (#1459)
* Initial work on fixing tests * Fix formatting * Remove dead code to satisfy rustc warnings * Insert into an artboard * Load updated artwork in editor * Remove popup when importing image * Fix up demo art * Change transform app[lication method * Reduce number of enums called BlendMode * Finalize the demo artwork upgrade * Code review pass --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
parent
719c96ecd8
commit
8a1cf3ad5d
@@ -1,6 +1,6 @@
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_core::renderer::ClickTarget;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeNetwork};
|
||||
@@ -12,6 +12,8 @@ pub struct DocumentMetadata {
|
||||
transforms: HashMap<LayerNodeIdentifier, DAffine2>,
|
||||
upstream_transforms: HashMap<NodeId, DAffine2>,
|
||||
structure: HashMap<LayerNodeIdentifier, NodeRelations>,
|
||||
artboards: HashSet<LayerNodeIdentifier>,
|
||||
folders: HashSet<LayerNodeIdentifier>,
|
||||
click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||
selected_nodes: Vec<NodeId>,
|
||||
/// Transform from document space to viewport space.
|
||||
@@ -25,6 +27,8 @@ impl Default for DocumentMetadata {
|
||||
upstream_transforms: HashMap::new(),
|
||||
click_targets: HashMap::new(),
|
||||
structure: HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]),
|
||||
artboards: HashSet::new(),
|
||||
folders: HashSet::new(),
|
||||
selected_nodes: Vec::new(),
|
||||
document_to_viewport: DAffine2::IDENTITY,
|
||||
}
|
||||
@@ -67,12 +71,12 @@ impl DocumentMetadata {
|
||||
!self.selected_nodes.is_empty()
|
||||
}
|
||||
|
||||
/// Access the [`NodeRelations`] of a layer
|
||||
/// Access the [`NodeRelations`] of a layer.
|
||||
fn get_relations(&self, node_identifier: LayerNodeIdentifier) -> Option<&NodeRelations> {
|
||||
self.structure.get(&node_identifier)
|
||||
}
|
||||
|
||||
/// Mutably access the [`NodeRelations`] of a layer
|
||||
/// Mutably access the [`NodeRelations`] of a layer.
|
||||
fn get_structure_mut(&mut self, node_identifier: LayerNodeIdentifier) -> &mut NodeRelations {
|
||||
self.structure.entry(node_identifier).or_default()
|
||||
}
|
||||
@@ -92,25 +96,41 @@ impl DocumentMetadata {
|
||||
sorted_layers
|
||||
}
|
||||
|
||||
/// Ancestor that is shared by all layers and that is deepest (more nested). May be the root layer.
|
||||
pub fn deepest_common_ancestor(&self, layers: impl Iterator<Item = LayerNodeIdentifier>) -> LayerNodeIdentifier {
|
||||
/// Ancestor that is shared by all layers and that is deepest (more nested). Default may be the root.
|
||||
pub fn deepest_common_ancestor(&self, layers: impl Iterator<Item = LayerNodeIdentifier>) -> Option<LayerNodeIdentifier> {
|
||||
layers
|
||||
.map(|layer| {
|
||||
let mut layer_path = layer.ancestors(self).skip(1).collect::<Vec<_>>();
|
||||
let mut layer_path = layer.ancestors(self).collect::<Vec<_>>();
|
||||
layer_path.reverse();
|
||||
|
||||
if !self.folders.contains(&layer) {
|
||||
layer_path.pop();
|
||||
}
|
||||
|
||||
layer_path
|
||||
})
|
||||
.reduce(|mut a, b| {
|
||||
a.truncate(a.iter().zip(b.iter()).position(|(&a, &b)| a != b).unwrap_or_else(|| a.len().min(b.len())));
|
||||
a
|
||||
})
|
||||
.and_then(|path| path.last().copied())
|
||||
.unwrap_or(LayerNodeIdentifier::ROOT)
|
||||
.and_then(|layer| layer.last().copied())
|
||||
}
|
||||
|
||||
pub fn active_artboard(&self) -> LayerNodeIdentifier {
|
||||
self.artboards.iter().next().copied().unwrap_or(LayerNodeIdentifier::ROOT)
|
||||
}
|
||||
|
||||
pub fn is_folder(&self, layer: LayerNodeIdentifier) -> bool {
|
||||
self.folders.contains(&layer)
|
||||
}
|
||||
|
||||
pub fn is_artboard(&self, layer: LayerNodeIdentifier) -> bool {
|
||||
self.artboards.contains(&layer)
|
||||
}
|
||||
|
||||
/// Filter out non folder layers
|
||||
pub fn folders<'a>(&'a self, layers: impl Iterator<Item = LayerNodeIdentifier> + 'a) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
|
||||
layers.filter(|layer| layer.has_children(self))
|
||||
layers.filter(|layer| self.folders.contains(layer))
|
||||
}
|
||||
|
||||
/// Folders sorted from most nested to least nested
|
||||
@@ -146,6 +166,8 @@ impl DocumentMetadata {
|
||||
/// Loads the structure of layer nodes from a node graph.
|
||||
pub fn load_structure(&mut self, graph: &NodeNetwork) {
|
||||
self.structure = HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]);
|
||||
self.folders = HashSet::new();
|
||||
self.artboards = HashSet::new();
|
||||
|
||||
let id = graph.outputs[0].node_id;
|
||||
let Some(output_node) = graph.nodes.get(&id) else {
|
||||
@@ -166,6 +188,13 @@ impl DocumentMetadata {
|
||||
if let Some((child_node, child_id)) = first_child_layer(graph, current_node) {
|
||||
stack.push((child_node, child_id, current_identifier));
|
||||
}
|
||||
|
||||
if is_artboard(current_identifier, graph) {
|
||||
self.artboards.insert(current_identifier);
|
||||
}
|
||||
if is_folder(current_identifier, graph) {
|
||||
self.folders.insert(current_identifier);
|
||||
}
|
||||
}
|
||||
|
||||
current = sibling_below(graph, current_node);
|
||||
@@ -210,6 +239,14 @@ fn is_artboard(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
|
||||
network.primary_flow_from_node(Some(layer.to_node())).any(|(node, _)| node.name == "Artboard")
|
||||
}
|
||||
|
||||
fn is_folder(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
|
||||
network.nodes.get(&layer.to_node()).and_then(|node| node.inputs.first()).is_some_and(|input| input.as_node().is_none())
|
||||
|| network
|
||||
.primary_flow_from_node(Some(layer.to_node()))
|
||||
.skip(1)
|
||||
.any(|(node, _)| node.name == "Artboard" || node.name == "Layer")
|
||||
}
|
||||
|
||||
// click targets
|
||||
impl DocumentMetadata {
|
||||
/// Update the cached click targets of the layers
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
/// Describes how overlapping SVG elements should be blended together.
|
||||
/// See the [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode#examples) for examples.
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug, Serialize, Deserialize, specta::Type)]
|
||||
pub enum BlendMode {
|
||||
// Normal group
|
||||
Normal,
|
||||
// Not supported by SVG, but we should someday support: Dissolve
|
||||
|
||||
// Darken group
|
||||
Darken,
|
||||
Multiply,
|
||||
ColorBurn,
|
||||
// Not supported by SVG, but we should someday support: Linear Burn, Darker Color
|
||||
|
||||
// Lighten group
|
||||
Lighten,
|
||||
Screen,
|
||||
ColorDodge,
|
||||
// Not supported by SVG, but we should someday support: Linear Dodge (Add), Lighter Color
|
||||
|
||||
// Contrast group
|
||||
Overlay,
|
||||
SoftLight,
|
||||
HardLight,
|
||||
// Not supported by SVG, but we should someday support: Vivid Light, Linear Light, Pin Light, Hard Mix
|
||||
|
||||
// Inversion group
|
||||
Difference,
|
||||
Exclusion,
|
||||
// Not supported by SVG, but we should someday support: Subtract, Divide
|
||||
|
||||
// Component group
|
||||
Hue,
|
||||
Saturation,
|
||||
Color,
|
||||
Luminosity,
|
||||
}
|
||||
|
||||
impl fmt::Display for BlendMode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
// Normal group
|
||||
BlendMode::Normal => write!(f, "Normal"),
|
||||
|
||||
// Darken group
|
||||
BlendMode::Darken => write!(f, "Darken"),
|
||||
BlendMode::Multiply => write!(f, "Multiply"),
|
||||
BlendMode::ColorBurn => write!(f, "Color Burn"),
|
||||
|
||||
// Lighten group
|
||||
BlendMode::Lighten => write!(f, "Lighten"),
|
||||
BlendMode::Screen => write!(f, "Screen"),
|
||||
BlendMode::ColorDodge => write!(f, "Color Dodge"),
|
||||
|
||||
// Contrast group
|
||||
BlendMode::Overlay => write!(f, "Overlay"),
|
||||
BlendMode::SoftLight => write!(f, "Soft Light"),
|
||||
BlendMode::HardLight => write!(f, "Hard Light"),
|
||||
|
||||
// Inversion group
|
||||
BlendMode::Difference => write!(f, "Difference"),
|
||||
BlendMode::Exclusion => write!(f, "Exclusion"),
|
||||
|
||||
// Component group
|
||||
BlendMode::Hue => write!(f, "Hue"),
|
||||
BlendMode::Saturation => write!(f, "Saturation"),
|
||||
BlendMode::Color => write!(f, "Color"),
|
||||
BlendMode::Luminosity => write!(f, "Luminosity"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) -> &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",
|
||||
}
|
||||
}
|
||||
|
||||
/// 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],
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
use super::blend_mode::BlendMode;
|
||||
use super::folder_layer::FolderLayer;
|
||||
use super::layer_layer::LayerLayer;
|
||||
use super::shape_layer::ShapeLayer;
|
||||
@@ -7,6 +6,7 @@ use crate::intersection::Quad;
|
||||
use crate::DocumentError;
|
||||
use crate::LayerId;
|
||||
|
||||
use graphene_core::raster::BlendMode;
|
||||
use graphene_core::vector::VectorData;
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
//! using the CSS [`mix-blend-mode`](https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode) property and the layer opacity.
|
||||
|
||||
pub mod base64_serde;
|
||||
/// Different ways of combining overlapping SVG elements.
|
||||
pub mod blend_mode;
|
||||
/// Contains the [FolderLayer](folder_layer::FolderLayer) type that encapsulates other layers, including more folders.
|
||||
pub mod folder_layer;
|
||||
/// Contains the base [Layer](layer_info::Layer) type, an abstraction over the different types of layers.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::layers::blend_mode::BlendMode;
|
||||
use crate::layers::layer_info::Layer;
|
||||
use crate::layers::style::{self, Stroke};
|
||||
use crate::LayerId;
|
||||
|
||||
use graphene_core::raster::BlendMode;
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
Reference in New Issue
Block a user