mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
Add the "Perceptual" and "Classic" families of gradient spaces with OkLab as the new default (#4415)
* Add an OkLab gradient interpolation space and make it the new default * Fix the Properties panel fill row resetting the gradient interpolation space to the default * Add OkLch, Lab, LCh, and HSL gradient interpolation spaces * Add a hue direction attribute and picker choice for polar gradient interpolation spaces * Add an HSV gradient interpolation space * Rename the sRGB Linear and sRGB Gamma gradient spaces to RGB Linear and RGB Gamma * Divide the gradient interpolation dropdown between absolute and relative color spaces * Rename the OkLch gradient interpolation space to OkLCh for channel-notation capitalization * Shorten the color picker popover's hue direction row label to Arc * Call the Gradient Interpolation node's parameter Space and extend the picker tooltip title to match * Rename the gradient interpolation attribute and type family to gradient space * Rename the gradient tool's gradient space transform to gradient to viewport transform * Add serde aliases and a node replacement covering the gradient space renames * Give the gradient space choices artist-facing labels and reorder the dropdown sections * Add a Gradient Hue Direction node and its attribute read node * Say interpolate instead of blend for gradient stop color traversal in docs and tooltips * Label the polar perceptual gradient spaces as Perceptual Hue and group the dropdown sections by geometry * Add a migration alias covering the gradient space attribute reader rename * Carry the gradient hue direction attribute through boolean operations * Register GradientHueDirection wire types in the node registry
This commit is contained in:
committed by
Dennis Kobert
parent
11dfc27645
commit
a35143586a
@@ -1,6 +1,6 @@
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::{SpectrumInputUpdate, VisualColorPickersInputUpdate};
|
||||
use crate::messages::prelude::*;
|
||||
use graphene_std::vector::style::{FillChoice, GradientInterpolation, GradientSpread};
|
||||
use graphene_std::vector::style::{FillChoice, GradientHueDirection, GradientSpace, GradientSpread};
|
||||
|
||||
/// Identifies which RGB channel a numeric input change targets.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
@@ -49,8 +49,10 @@ pub enum ColorPickerMessage {
|
||||
GradientUpdate { update: SpectrumInputUpdate },
|
||||
/// Gradient spread choice from the gradient "Ends" selection.
|
||||
SetGradientSpread { gradient_spread: GradientSpread },
|
||||
/// Gradient interpolation choice: the color space the stops blend in, from the "Space" dropdown.
|
||||
SetGradientInterpolation { gradient_interpolation: GradientInterpolation },
|
||||
/// Gradient space choice: the color space the stops interpolate in, from the "Space" dropdown.
|
||||
SetGradientSpace { gradient_space: GradientSpace },
|
||||
/// Gradient hue direction choice: which way around the hue wheel the stops interpolate in a polar space, from the "Arc" dropdown.
|
||||
SetGradientHueDirection { gradient_hue_direction: GradientHueDirection },
|
||||
|
||||
/// Tell the frontend to start an undo transaction (forwarded as a `FrontendMessage` it bridges out to the picker's parent).
|
||||
StartTransaction,
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::messages::prelude::*;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::core_types::misc::parse_css_color;
|
||||
use graphene_std::vector::style::{FillChoice, Gradient, GradientInterpolation, GradientRamp, GradientSpread, GradientStops};
|
||||
use graphene_std::vector::style::{FillChoice, Gradient, GradientHueDirection, GradientRamp, GradientSpace, GradientSpread, GradientStops};
|
||||
|
||||
/// Bounds for a midpoint position (relative to the interval between two adjacent gradient stops).
|
||||
const MIN_MIDPOINT: f64 = 0.01;
|
||||
@@ -30,7 +30,8 @@ pub struct ColorPickerMessageHandler {
|
||||
// When set, the picker is editing a gradient: the visual pickers and inputs target the active stop's color.
|
||||
gradient: Option<Gradient>,
|
||||
gradient_spread: GradientSpread,
|
||||
gradient_interpolation: GradientInterpolation,
|
||||
gradient_space: GradientSpace,
|
||||
gradient_hue_direction: GradientHueDirection,
|
||||
active_marker_index: Option<u32>,
|
||||
active_marker_is_midpoint: bool,
|
||||
|
||||
@@ -53,7 +54,8 @@ impl Default for ColorPickerMessageHandler {
|
||||
old_is_none: true,
|
||||
gradient: None,
|
||||
gradient_spread: GradientSpread::default(),
|
||||
gradient_interpolation: GradientInterpolation::default(),
|
||||
gradient_space: GradientSpace::default(),
|
||||
gradient_hue_direction: GradientHueDirection::default(),
|
||||
active_marker_index: None,
|
||||
active_marker_is_midpoint: false,
|
||||
allow_none: true,
|
||||
@@ -75,14 +77,16 @@ impl MessageHandler<ColorPickerMessage, ()> for ColorPickerMessageHandler {
|
||||
self.set_new_hsva(0., 0., 0., 1., true);
|
||||
self.gradient = None;
|
||||
self.gradient_spread = GradientSpread::default();
|
||||
self.gradient_interpolation = GradientInterpolation::default();
|
||||
self.gradient_space = GradientSpace::default();
|
||||
self.gradient_hue_direction = GradientHueDirection::default();
|
||||
self.active_marker_index = None;
|
||||
self.active_marker_is_midpoint = false;
|
||||
}
|
||||
FillChoice::Solid(color) => {
|
||||
self.gradient = None;
|
||||
self.gradient_spread = GradientSpread::default();
|
||||
self.gradient_interpolation = GradientInterpolation::default();
|
||||
self.gradient_space = GradientSpace::default();
|
||||
self.gradient_hue_direction = GradientHueDirection::default();
|
||||
self.active_marker_index = None;
|
||||
self.active_marker_is_midpoint = false;
|
||||
self.adopt_color(color);
|
||||
@@ -91,7 +95,8 @@ impl MessageHandler<ColorPickerMessage, ()> for ColorPickerMessageHandler {
|
||||
self.active_marker_index = Some(0);
|
||||
self.active_marker_is_midpoint = false;
|
||||
self.gradient_spread = ramp.gradient_spread;
|
||||
self.gradient_interpolation = ramp.gradient_interpolation;
|
||||
self.gradient_space = ramp.gradient_space;
|
||||
self.gradient_hue_direction = ramp.gradient_hue_direction;
|
||||
let gradient = Gradient::from(ramp);
|
||||
let first_color = gradient.color(0).unwrap_or(Color::BLACK);
|
||||
self.gradient = Some(gradient);
|
||||
@@ -204,20 +209,36 @@ impl MessageHandler<ColorPickerMessage, ()> for ColorPickerMessageHandler {
|
||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||
value: FillChoice::Gradient(GradientRamp {
|
||||
gradient_spread,
|
||||
gradient_interpolation: self.gradient_interpolation,
|
||||
gradient_space: self.gradient_space,
|
||||
gradient_hue_direction: self.gradient_hue_direction,
|
||||
..GradientRamp::from(gradient)
|
||||
}),
|
||||
});
|
||||
self.send_layouts(responses);
|
||||
}
|
||||
ColorPickerMessage::SetGradientInterpolation { gradient_interpolation } => {
|
||||
ColorPickerMessage::SetGradientSpace { gradient_space } => {
|
||||
let Some(gradient) = &self.gradient else { return };
|
||||
responses.add(FrontendMessage::ColorPickerStartHistoryTransaction);
|
||||
self.gradient_interpolation = gradient_interpolation;
|
||||
self.gradient_space = gradient_space;
|
||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||
value: FillChoice::Gradient(GradientRamp {
|
||||
gradient_spread: self.gradient_spread,
|
||||
gradient_interpolation,
|
||||
gradient_space,
|
||||
gradient_hue_direction: self.gradient_hue_direction,
|
||||
..GradientRamp::from(gradient)
|
||||
}),
|
||||
});
|
||||
self.send_layouts(responses);
|
||||
}
|
||||
ColorPickerMessage::SetGradientHueDirection { gradient_hue_direction } => {
|
||||
let Some(gradient) = &self.gradient else { return };
|
||||
responses.add(FrontendMessage::ColorPickerStartHistoryTransaction);
|
||||
self.gradient_hue_direction = gradient_hue_direction;
|
||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||
value: FillChoice::Gradient(GradientRamp {
|
||||
gradient_spread: self.gradient_spread,
|
||||
gradient_space: self.gradient_space,
|
||||
gradient_hue_direction,
|
||||
..GradientRamp::from(gradient)
|
||||
}),
|
||||
});
|
||||
@@ -309,7 +330,8 @@ impl ColorPickerMessageHandler {
|
||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||
value: FillChoice::Gradient(GradientRamp {
|
||||
gradient_spread: self.gradient_spread,
|
||||
gradient_interpolation: self.gradient_interpolation,
|
||||
gradient_space: self.gradient_space,
|
||||
gradient_hue_direction: self.gradient_hue_direction,
|
||||
..GradientRamp::from(&*gradient)
|
||||
}),
|
||||
});
|
||||
@@ -368,7 +390,7 @@ impl ColorPickerMessageHandler {
|
||||
gradient.set_midpoint(index as usize, position.clamp(MIN_MIDPOINT, MAX_MIDPOINT));
|
||||
}
|
||||
SpectrumInputUpdate::InsertMarker { position } => {
|
||||
let new_index = gradient.insert_stop(position, self.gradient_interpolation);
|
||||
let new_index = gradient.insert_stop(position, self.gradient_space, self.gradient_hue_direction);
|
||||
self.active_marker_index = Some(new_index as u32);
|
||||
self.active_marker_is_midpoint = false;
|
||||
if let Some(color) = gradient.color(new_index) {
|
||||
@@ -440,7 +462,8 @@ impl ColorPickerMessageHandler {
|
||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||
value: FillChoice::Gradient(GradientRamp {
|
||||
gradient_spread: self.gradient_spread,
|
||||
gradient_interpolation: self.gradient_interpolation,
|
||||
gradient_space: self.gradient_space,
|
||||
gradient_hue_direction: self.gradient_hue_direction,
|
||||
..GradientRamp::from(&gradient)
|
||||
}),
|
||||
});
|
||||
@@ -471,7 +494,8 @@ impl ColorPickerMessageHandler {
|
||||
let markers = gradient.iter().map(|stop| SpectrumMarker::new(stop.position, stop.midpoint, stop.color)).collect();
|
||||
let mut row_widgets = vec![
|
||||
SpectrumInput::new(GradientStops::from(gradient))
|
||||
.track_interpolation(self.gradient_interpolation)
|
||||
.track_space(self.gradient_space)
|
||||
.track_hue_direction(self.gradient_hue_direction)
|
||||
.markers(markers)
|
||||
.active_marker_index(self.active_marker_index)
|
||||
.active_marker_is_midpoint(self.active_marker_is_midpoint)
|
||||
@@ -654,15 +678,29 @@ impl ColorPickerMessageHandler {
|
||||
]));
|
||||
}
|
||||
|
||||
// Gradient interpolation color space (only present when the picker is in gradient mode)
|
||||
// Gradient color space (only present when the picker is in gradient mode)
|
||||
if self.gradient.is_some() {
|
||||
let entries = MenuListEntry::sections_from_choice_type(|gradient_interpolation| ColorPickerMessage::SetGradientInterpolation { gradient_interpolation }.into());
|
||||
let entries = MenuListEntry::sections_from_choice_type(|gradient_space| ColorPickerMessage::SetGradientSpace { gradient_space }.into());
|
||||
|
||||
groups.push(LayoutGroup::row(vec![
|
||||
TextLabel::new("Space").tooltip_label("Gradient Interpolation").tooltip_description(SPACE_DESCRIPTION).widget_instance(),
|
||||
TextLabel::new("Space").tooltip_label("Gradient Space").tooltip_description(SPACE_DESCRIPTION).widget_instance(),
|
||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||
DropdownInput::new(entries).selected_index(Some(self.gradient_space as u32)).disabled(self.disabled).widget_instance(),
|
||||
]));
|
||||
}
|
||||
|
||||
// Gradient hue direction (only present when the chosen space is polar)
|
||||
if self.gradient.is_some() && self.gradient_space.is_polar() {
|
||||
let entries = MenuListEntry::sections_from_choice_type(|gradient_hue_direction| ColorPickerMessage::SetGradientHueDirection { gradient_hue_direction }.into());
|
||||
|
||||
groups.push(LayoutGroup::row(vec![
|
||||
TextLabel::new("Arc")
|
||||
.tooltip_label("Gradient Hue Direction")
|
||||
.tooltip_description(HUE_DIRECTION_DESCRIPTION)
|
||||
.widget_instance(),
|
||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||
DropdownInput::new(entries)
|
||||
.selected_index(Some(self.gradient_interpolation as u32))
|
||||
.selected_index(Some(self.gradient_hue_direction as u32))
|
||||
.disabled(self.disabled)
|
||||
.widget_instance(),
|
||||
]));
|
||||
@@ -722,7 +760,8 @@ const SATURATION_DESCRIPTION: &str = "The vividness from grayscale to full color
|
||||
const VALUE_DESCRIPTION: &str = "The brightness from black to full color.";
|
||||
const ALPHA_DESCRIPTION: &str = "The level of translucency, from transparent (0%) to opaque (100%).";
|
||||
const ENDS_DESCRIPTION: &str = "The method for how the gradient continues beyond its ends.";
|
||||
const SPACE_DESCRIPTION: &str = "The color space where stops blend into their neighbors.";
|
||||
const SPACE_DESCRIPTION: &str = "The color space where stops interpolate toward their neighbors.";
|
||||
const HUE_DIRECTION_DESCRIPTION: &str = "Which way around the hue wheel the stops interpolate.";
|
||||
|
||||
/// The popover's background color as sRGB gamma-encoded channels (the `--color-2-mildblack` design token, `#222`).
|
||||
/// Used by the comparison swatch's outline computation to brighten the inset border for colors close to this background.
|
||||
|
||||
@@ -531,7 +531,7 @@ fn populate_computed_display_fields(layout: &mut Layout) {
|
||||
color_input.chosen_gradient = color_input.value.to_css_background_image();
|
||||
}
|
||||
Widget::SpectrumInput(spectrum_input) => {
|
||||
spectrum_input.track_css = spectrum_input.track.to_css_linear_gradient(spectrum_input.track_interpolation);
|
||||
spectrum_input.track_css = spectrum_input.track.to_css_linear_gradient(spectrum_input.track_space, spectrum_input.track_hue_direction);
|
||||
spectrum_input.track_start_css = spectrum_input.track.color.first().map(|color| color.to_css_hex()).unwrap_or_else(|| "black".to_string());
|
||||
spectrum_input.track_end_css = spectrum_input.track.color.last().map(|color| color.to_css_hex()).unwrap_or_else(|| "black".to_string());
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use derivative::*;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphene_std::vector::style::{FillChoice, GradientInterpolation, GradientStops};
|
||||
use graphene_std::vector::style::{FillChoice, GradientHueDirection, GradientSpace, GradientStops};
|
||||
use graphite_proc_macros::WidgetBuilder;
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
@@ -586,9 +586,12 @@ pub struct SpectrumInput {
|
||||
/// The colored gradient drawn behind the markers (display-only, caller-owned).
|
||||
#[widget_builder(constructor)]
|
||||
pub track: GradientStops<SRGBA8>,
|
||||
/// The interpolation color space the track's stops blend in, used to compute `track_css`. Not sent to the frontend.
|
||||
/// The color space the track's stops interpolate in, used to compute `track_css`. Not sent to the frontend.
|
||||
#[serde(skip)]
|
||||
pub track_interpolation: GradientInterpolation,
|
||||
pub track_space: GradientSpace,
|
||||
/// The hue direction the track's stops interpolate with in a polar space, used to compute `track_css`. Not sent to the frontend.
|
||||
#[serde(skip)]
|
||||
pub track_hue_direction: GradientHueDirection,
|
||||
/// CSS `linear-gradient(...)` string for the track strip's `background-image`. Auto-populated from `track` at layout-send time.
|
||||
#[serde(rename = "trackCSS")]
|
||||
#[widget_builder(skip)]
|
||||
|
||||
@@ -23,7 +23,7 @@ use graphene_std::transform::{ReferencePoint, ScaleType};
|
||||
use graphene_std::vector::misc::{
|
||||
ArcType, BooleanOperation, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType,
|
||||
};
|
||||
use graphene_std::vector::style::{DashPattern, FillChoice, GradientForm, GradientInterpolation, GradientRamp, GradientSpread, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use graphene_std::vector::style::{DashPattern, FillChoice, GradientForm, GradientRamp, GradientSpace, GradientSpread, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use graphene_std::vector::{QRCodeErrorCorrectionLevel, Vector};
|
||||
use graphene_std::{Artboard, Color, Graphic};
|
||||
use std::any::Any;
|
||||
@@ -218,7 +218,7 @@ fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'st
|
||||
List<BlendMode>,
|
||||
List<GradientForm>,
|
||||
List<GradientSpread>,
|
||||
List<GradientInterpolation>,
|
||||
List<GradientSpace>,
|
||||
List<DashPattern>,
|
||||
List<BoxCorners>,
|
||||
List<StrokeJoin>,
|
||||
@@ -271,7 +271,7 @@ fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'st
|
||||
DAffine2,
|
||||
BlendMode,
|
||||
GradientForm,
|
||||
GradientInterpolation,
|
||||
GradientSpace,
|
||||
GradientSpread,
|
||||
DashPattern,
|
||||
BoxCorners,
|
||||
@@ -1011,7 +1011,7 @@ impl_table_item_layout_for_choice_enum!(
|
||||
BlendMode,
|
||||
GradientForm,
|
||||
GradientSpread,
|
||||
GradientInterpolation,
|
||||
GradientSpace,
|
||||
StrokeJoin,
|
||||
StrokeAlign,
|
||||
StrokeCap,
|
||||
@@ -1224,7 +1224,7 @@ macro_rules! known_item_types {
|
||||
BlendMode,
|
||||
GradientForm,
|
||||
GradientSpread,
|
||||
GradientInterpolation,
|
||||
GradientSpace,
|
||||
StrokeJoin,
|
||||
StrokeAlign,
|
||||
StrokeCap,
|
||||
|
||||
@@ -10,7 +10,7 @@ use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::Image;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::style::{GradientForm, GradientInterpolation, GradientSpread, Stroke};
|
||||
use graphene_std::vector::style::{GradientForm, GradientHueDirection, GradientSpace, GradientSpread, Stroke};
|
||||
use graphene_std::vector::{Gradient, PointId, VectorModificationType};
|
||||
|
||||
#[impl_message(Message, DocumentMessage, GraphOperation)]
|
||||
@@ -30,7 +30,8 @@ pub enum GraphOperationMessage {
|
||||
gradient: Gradient,
|
||||
gradient_form: GradientForm,
|
||||
gradient_spread: GradientSpread,
|
||||
gradient_interpolation: GradientInterpolation,
|
||||
gradient_space: GradientSpace,
|
||||
gradient_hue_direction: GradientHueDirection,
|
||||
transform: DAffine2,
|
||||
},
|
||||
BlendingFillSet {
|
||||
@@ -62,9 +63,13 @@ pub enum GraphOperationMessage {
|
||||
layer: LayerNodeIdentifier,
|
||||
gradient_spread: GradientSpread,
|
||||
},
|
||||
GradientInterpolationSet {
|
||||
GradientSpaceSet {
|
||||
layer: LayerNodeIdentifier,
|
||||
gradient_interpolation: GradientInterpolation,
|
||||
gradient_space: GradientSpace,
|
||||
},
|
||||
GradientHueDirectionSet {
|
||||
layer: LayerNodeIdentifier,
|
||||
gradient_hue_direction: GradientHueDirection,
|
||||
},
|
||||
OpacitySet {
|
||||
layer: LayerNodeIdentifier,
|
||||
|
||||
@@ -14,7 +14,7 @@ use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::list::List;
|
||||
use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::style::{Gradient, GradientForm, GradientInterpolation, GradientSpread, GradientStop, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use graphene_std::vector::style::{Gradient, GradientForm, GradientSpace, GradientSpread, GradientStop, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use graphene_std::{Artboard, Color};
|
||||
|
||||
#[derive(ExtractField)]
|
||||
@@ -50,11 +50,12 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
||||
gradient,
|
||||
gradient_form,
|
||||
gradient_spread,
|
||||
gradient_interpolation,
|
||||
gradient_space,
|
||||
gradient_hue_direction,
|
||||
transform,
|
||||
} => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.fill_gradient_set(gradient, gradient_form, gradient_spread, gradient_interpolation, transform);
|
||||
modify_inputs.fill_gradient_set(gradient, gradient_form, gradient_spread, gradient_space, gradient_hue_direction, transform);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::BlendingFillSet { layer, fill } => {
|
||||
@@ -92,9 +93,14 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
||||
modify_inputs.gradient_spread_set(gradient_spread);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::GradientInterpolationSet { layer, gradient_interpolation } => {
|
||||
GraphOperationMessage::GradientSpaceSet { layer, gradient_space } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.gradient_interpolation_set(gradient_interpolation);
|
||||
modify_inputs.gradient_space_set(gradient_space);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::GradientHueDirectionSet { layer, gradient_hue_direction } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.gradient_hue_direction_set(gradient_hue_direction);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::OpacitySet { layer, opacity } => {
|
||||
@@ -489,7 +495,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
||||
|
||||
let gradient_info = SvgGradientInfo {
|
||||
graphite_stops: extract_graphite_gradient_stops(&svg),
|
||||
interpolations: extract_gradient_interpolations(&svg),
|
||||
spaces: extract_gradient_spaces(&svg),
|
||||
};
|
||||
|
||||
// Pass identity so each leaf layer receives only its SVG-native transform from `abs_transform`.
|
||||
@@ -530,14 +536,14 @@ const GRAPHITE_NAMESPACE: &str = "https://graphite.art";
|
||||
struct SvgGradientInfo {
|
||||
/// Real stops, keyed by gradient element `id`, for gradients Graphite exported with midpoint curve data.
|
||||
graphite_stops: HashMap<String, Gradient>,
|
||||
/// Interpolation spaces, keyed by gradient element `id`, resolved from the `color-interpolation` property.
|
||||
interpolations: HashMap<String, GradientInterpolation>,
|
||||
/// Gradient spaces, keyed by gradient element `id`, resolved from the `color-interpolation` property.
|
||||
spaces: HashMap<String, GradientSpace>,
|
||||
}
|
||||
|
||||
/// Pre-parses the raw SVG XML to resolve each gradient's inherited `color-interpolation` property, which usvg's
|
||||
/// tree does not carry. Only `linearRGB` selects linear interpolation; `auto` and `sRGB` (browsers treat the
|
||||
/// tree does not carry. Only `linearRGB` selects the linear space; `auto` and `sRGB` (browsers treat the
|
||||
/// user-agent-defined `auto` as `sRGB`) mean gamma, as does any unrecognized value.
|
||||
fn extract_gradient_interpolations(svg: &str) -> HashMap<String, GradientInterpolation> {
|
||||
fn extract_gradient_spaces(svg: &str) -> HashMap<String, GradientSpace> {
|
||||
let mut result = HashMap::new();
|
||||
|
||||
// Quick check: gradients in an SVG that never mentions `color-interpolation` all take the sRGB default
|
||||
@@ -568,9 +574,9 @@ fn extract_gradient_interpolations(svg: &str) -> HashMap<String, GradientInterpo
|
||||
}
|
||||
|
||||
if let Some(gradient_id) = node.attribute("id")
|
||||
&& let Some(gradient_interpolation) = resolve_color_interpolation(node, &stylesheet)
|
||||
&& let Some(gradient_space) = resolve_color_interpolation(node, &stylesheet)
|
||||
{
|
||||
result.insert(gradient_id.to_string(), gradient_interpolation);
|
||||
result.insert(gradient_id.to_string(), gradient_space);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,15 +585,15 @@ fn extract_gradient_interpolations(svg: &str) -> HashMap<String, GradientInterpo
|
||||
|
||||
/// The `color-interpolation` in effect for an element: the nearest self-or-ancestor declaration, taking each
|
||||
/// element's own winning declaration per [`declared_color_interpolation`]'s cascade order.
|
||||
fn resolve_color_interpolation(element: usvg::roxmltree::Node, stylesheet: &simplecss::StyleSheet) -> Option<GradientInterpolation> {
|
||||
fn resolve_color_interpolation(element: usvg::roxmltree::Node, stylesheet: &simplecss::StyleSheet) -> Option<GradientSpace> {
|
||||
let mut next = Some(element);
|
||||
|
||||
while let Some(element) = next {
|
||||
match declared_color_interpolation(element, stylesheet) {
|
||||
Some("linearRGB") => return Some(GradientInterpolation::SrgbLinear),
|
||||
Some("linearRGB") => return Some(GradientSpace::RgbLinear),
|
||||
// `inherit` defers to the ancestors like an undeclared element
|
||||
Some("inherit") | None => {}
|
||||
Some(_) => return Some(GradientInterpolation::SrgbGamma),
|
||||
Some(_) => return Some(GradientSpace::RgbGamma),
|
||||
}
|
||||
|
||||
next = element.parent_element();
|
||||
@@ -971,8 +977,8 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, g
|
||||
};
|
||||
let gradient_spread = convert_gradient_spread(linear.spread_method());
|
||||
// SVG interpolates between stops in gamma sRGB unless `color-interpolation` opts into linearRGB, carried explicitly rather than as the linear default
|
||||
let gradient_interpolation = gradient_info.interpolations.get(linear.id()).copied().unwrap_or(GradientInterpolation::SrgbGamma);
|
||||
modify_inputs.fill_gradient_set(gradient, gradient_form, gradient_spread, gradient_interpolation, transform);
|
||||
let gradient_space = gradient_info.spaces.get(linear.id()).copied().unwrap_or(GradientSpace::RgbGamma);
|
||||
modify_inputs.fill_gradient_set(gradient, gradient_form, gradient_spread, gradient_space, Default::default(), transform);
|
||||
}
|
||||
usvg::Paint::RadialGradient(radial) => {
|
||||
let gradient_transform = usvg_transform(radial.transform());
|
||||
@@ -996,9 +1002,9 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, g
|
||||
}
|
||||
};
|
||||
let gradient_spread = convert_gradient_spread(radial.spread_method());
|
||||
let gradient_interpolation = gradient_info.interpolations.get(radial.id()).copied().unwrap_or(GradientInterpolation::SrgbGamma);
|
||||
let gradient_space = gradient_info.spaces.get(radial.id()).copied().unwrap_or(GradientSpace::RgbGamma);
|
||||
|
||||
modify_inputs.fill_gradient_set(gradient, gradient_form, gradient_spread, gradient_interpolation, transform);
|
||||
modify_inputs.fill_gradient_set(gradient, gradient_form, gradient_spread, gradient_space, Default::default(), transform);
|
||||
}
|
||||
usvg::Paint::Pattern(_) => warn!("SVG patterns are not currently supported"),
|
||||
};
|
||||
@@ -1019,25 +1025,13 @@ mod tests {
|
||||
</defs>
|
||||
</svg>"##;
|
||||
|
||||
let interpolations = extract_gradient_interpolations(svg);
|
||||
let spaces = extract_gradient_spaces(svg);
|
||||
assert_eq!(spaces.get("inherited"), Some(&GradientSpace::RgbLinear), "an undeclared gradient should inherit from its ancestors");
|
||||
assert_eq!(spaces.get("attribute"), Some(&GradientSpace::RgbGamma), "an sRGB declaration should beat the inherited linearRGB");
|
||||
assert_eq!(spaces.get("styled"), Some(&GradientSpace::RgbLinear), "the inline style should beat the presentation attribute");
|
||||
assert_eq!(
|
||||
interpolations.get("inherited"),
|
||||
Some(&GradientInterpolation::SrgbLinear),
|
||||
"an undeclared gradient should inherit from its ancestors"
|
||||
);
|
||||
assert_eq!(
|
||||
interpolations.get("attribute"),
|
||||
Some(&GradientInterpolation::SrgbGamma),
|
||||
"an sRGB declaration should beat the inherited linearRGB"
|
||||
);
|
||||
assert_eq!(
|
||||
interpolations.get("styled"),
|
||||
Some(&GradientInterpolation::SrgbLinear),
|
||||
"the inline style should beat the presentation attribute"
|
||||
);
|
||||
assert_eq!(
|
||||
interpolations.get("auto"),
|
||||
Some(&GradientInterpolation::SrgbGamma),
|
||||
spaces.get("auto"),
|
||||
Some(&GradientSpace::RgbGamma),
|
||||
"auto should mean gamma like browsers treat it, not defer to ancestors"
|
||||
);
|
||||
}
|
||||
@@ -1059,26 +1053,18 @@ mod tests {
|
||||
</defs>
|
||||
</svg>"##;
|
||||
|
||||
let interpolations = extract_gradient_interpolations(svg);
|
||||
let spaces = extract_gradient_spaces(svg);
|
||||
assert_eq!(spaces.get("from-type-rule"), Some(&GradientSpace::RgbLinear), "a type rule in a style block should reach the gradient");
|
||||
assert_eq!(
|
||||
interpolations.get("from-type-rule"),
|
||||
Some(&GradientInterpolation::SrgbLinear),
|
||||
"a type rule in a style block should reach the gradient"
|
||||
);
|
||||
assert_eq!(
|
||||
interpolations.get("from-class-rule"),
|
||||
Some(&GradientInterpolation::SrgbGamma),
|
||||
spaces.get("from-class-rule"),
|
||||
Some(&GradientSpace::RgbGamma),
|
||||
"the class rule should outrank the type rule by specificity"
|
||||
);
|
||||
assert_eq!(interpolations.get("exact"), Some(&GradientInterpolation::SrgbLinear), "the ID rule should outrank the class rule");
|
||||
assert_eq!(spaces.get("exact"), Some(&GradientSpace::RgbLinear), "the ID rule should outrank the class rule");
|
||||
assert_eq!(spaces.get("inline-beats-rules"), Some(&GradientSpace::RgbLinear), "the inline style should beat every style block rule");
|
||||
assert_eq!(
|
||||
interpolations.get("inline-beats-rules"),
|
||||
Some(&GradientInterpolation::SrgbLinear),
|
||||
"the inline style should beat every style block rule"
|
||||
);
|
||||
assert_eq!(
|
||||
interpolations.get("rule-beats-attribute"),
|
||||
Some(&GradientInterpolation::SrgbGamma),
|
||||
spaces.get("rule-beats-attribute"),
|
||||
Some(&GradientSpace::RgbGamma),
|
||||
"a style block rule should beat the presentation attribute"
|
||||
);
|
||||
}
|
||||
@@ -1092,20 +1078,20 @@ mod tests {
|
||||
<linearGradient id="important-rule-beats-inline" class="forced" style="color-interpolation: sRGB"/>
|
||||
</svg>"##;
|
||||
|
||||
let interpolations = extract_gradient_interpolations(svg);
|
||||
let spaces = extract_gradient_spaces(svg);
|
||||
assert_eq!(
|
||||
interpolations.get("last-declaration-wins"),
|
||||
Some(&GradientInterpolation::SrgbLinear),
|
||||
spaces.get("last-declaration-wins"),
|
||||
Some(&GradientSpace::RgbLinear),
|
||||
"the last of repeated inline declarations should win"
|
||||
);
|
||||
assert_eq!(
|
||||
interpolations.get("important-beats-later"),
|
||||
Some(&GradientInterpolation::SrgbLinear),
|
||||
spaces.get("important-beats-later"),
|
||||
Some(&GradientSpace::RgbLinear),
|
||||
"an `!important` declaration should beat a later normal one"
|
||||
);
|
||||
assert_eq!(
|
||||
interpolations.get("important-rule-beats-inline"),
|
||||
Some(&GradientInterpolation::SrgbLinear),
|
||||
spaces.get("important-rule-beats-inline"),
|
||||
Some(&GradientSpace::RgbLinear),
|
||||
"an `!important` style block rule should beat the inline style"
|
||||
);
|
||||
}
|
||||
@@ -1115,7 +1101,7 @@ mod tests {
|
||||
let svg = r##"<svg xmlns="http://www.w3.org/2000/svg"><linearGradient id="plain"/></svg>"##;
|
||||
|
||||
assert!(
|
||||
extract_gradient_interpolations(svg).is_empty(),
|
||||
extract_gradient_spaces(svg).is_empty(),
|
||||
"gradients without any declaration should fall back to the caller's gamma default"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::Image;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::style::{GradientForm, GradientInterpolation, GradientSpread, Stroke};
|
||||
use graphene_std::vector::style::{GradientForm, GradientHueDirection, GradientSpace, GradientSpread, Stroke};
|
||||
use graphene_std::vector::{Gradient, GradientRamp, PointId, Vector, VectorModification, VectorModificationType};
|
||||
use graphene_std::{Artboard, Color, Graphic};
|
||||
|
||||
@@ -433,7 +433,15 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(fill_value, false), false);
|
||||
}
|
||||
|
||||
pub fn fill_gradient_set(&mut self, gradient: Gradient, gradient_form: GradientForm, gradient_spread: GradientSpread, gradient_interpolation: GradientInterpolation, transform: DAffine2) {
|
||||
pub fn fill_gradient_set(
|
||||
&mut self,
|
||||
gradient: Gradient,
|
||||
gradient_form: GradientForm,
|
||||
gradient_spread: GradientSpread,
|
||||
gradient_space: GradientSpace,
|
||||
gradient_hue_direction: GradientHueDirection,
|
||||
transform: DAffine2,
|
||||
) {
|
||||
let Some(fill_node_id) = self.existing_proto_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else {
|
||||
return;
|
||||
};
|
||||
@@ -442,7 +450,8 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
let ramp = GradientRamp::from(gradient);
|
||||
let ramp = GradientRamp {
|
||||
gradient_spread,
|
||||
gradient_interpolation,
|
||||
gradient_space,
|
||||
gradient_hue_direction,
|
||||
..ramp
|
||||
};
|
||||
self.set_input_with_refresh(backup_input_connector, NodeInput::value(TaggedValue::GradientRamp(ramp.clone()), false), true);
|
||||
@@ -769,16 +778,29 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
Some(ramp.clone())
|
||||
}
|
||||
|
||||
/// Set the interpolation on the chain's gradient value, which is where the ramp carries it. Never touches a
|
||||
/// 'Gradient Interpolation' node: that one is a user-authored procedural override, not something the tools manage.
|
||||
pub fn gradient_interpolation_set(&mut self, gradient_interpolation: GradientInterpolation) {
|
||||
/// Set the space on the chain's gradient value, which is where the ramp carries it. Never touches a
|
||||
/// 'Gradient Space' node: that one is a user-authored procedural override, not something the tools manage.
|
||||
pub fn gradient_space_set(&mut self, gradient_space: GradientSpace) {
|
||||
let Some(output_layer) = self.get_output_layer() else { return };
|
||||
let Some(gradient_value_id) = get_upstream_gradient_value_node_id(output_layer, self.network_interface) else {
|
||||
return;
|
||||
};
|
||||
let Some(ramp) = self.gradient_value_ramp(gradient_value_id) else { return };
|
||||
|
||||
let ramp = GradientRamp { gradient_interpolation, ..ramp };
|
||||
let ramp = GradientRamp { gradient_space, ..ramp };
|
||||
let input_connector = InputConnector::node(gradient_value_id, graphene_std::math_nodes::gradient_value::GradientInput);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::GradientRamp(ramp), false), false);
|
||||
}
|
||||
|
||||
/// Set the hue direction on the chain's gradient value, which is where the ramp carries it.
|
||||
pub fn gradient_hue_direction_set(&mut self, gradient_hue_direction: GradientHueDirection) {
|
||||
let Some(output_layer) = self.get_output_layer() else { return };
|
||||
let Some(gradient_value_id) = get_upstream_gradient_value_node_id(output_layer, self.network_interface) else {
|
||||
return;
|
||||
};
|
||||
let Some(ramp) = self.gradient_value_ramp(gradient_value_id) else { return };
|
||||
|
||||
let ramp = GradientRamp { gradient_hue_direction, ..ramp };
|
||||
let input_connector = InputConnector::node(gradient_value_id, graphene_std::math_nodes::gradient_value::GradientInput);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::GradientRamp(ramp), false), false);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@ use graphene_std::vector::misc::{
|
||||
ArcType, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType,
|
||||
};
|
||||
use graphene_std::vector::style::{
|
||||
FillChoice, Gradient, GradientForm, GradientInterpolation, GradientRamp, GradientSpread, GradientStops, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin, build_transform_with_y_preservation,
|
||||
FillChoice, Gradient, GradientForm, GradientHueDirection, GradientRamp, GradientSpace, GradientSpread, GradientStops, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin,
|
||||
build_transform_with_y_preservation,
|
||||
};
|
||||
use graphene_std::vector::{QRCodeErrorCorrectionLevel, VectorModification};
|
||||
use graphene_std::{NodeParameter, ParameterRef};
|
||||
@@ -294,7 +295,8 @@ pub(crate) fn property_from_type(
|
||||
// =========================
|
||||
Some(x) if id_is::<GradientForm>(x) => enum_choice::<GradientForm>().for_socket(default_info).property_row(),
|
||||
Some(x) if id_is::<GradientSpread>(x) => enum_choice::<GradientSpread>().for_socket(default_info).property_row(),
|
||||
Some(x) if id_is::<GradientInterpolation>(x) => enum_choice::<GradientInterpolation>().for_socket(default_info).property_row(),
|
||||
Some(x) if id_is::<GradientSpace>(x) => enum_choice::<GradientSpace>().for_socket(default_info).property_row(),
|
||||
Some(x) if id_is::<GradientHueDirection>(x) => enum_choice::<GradientHueDirection>().for_socket(default_info).property_row(),
|
||||
Some(x) if id_is::<RealTimeMode>(x) => enum_choice::<RealTimeMode>().for_socket(default_info).property_row(),
|
||||
Some(x) if id_is::<RedGreenBlue>(x) => enum_choice::<RedGreenBlue>().for_socket(default_info).property_row(),
|
||||
Some(x) if id_is::<RedGreenBlueAlpha>(x) => enum_choice::<RedGreenBlueAlpha>().for_socket(default_info).property_row(),
|
||||
@@ -1390,7 +1392,7 @@ fn build_shared_spectrum_section(node_id: NodeId, context: &mut NodePropertiesCo
|
||||
// Build the shared spectrum widget (placed on the first non-exposed row)
|
||||
let spectrum_widget = (!spectrum_markers.is_empty()).then(|| {
|
||||
SpectrumInput::new(GradientStops::from(&bw_track()))
|
||||
.track_interpolation(GradientInterpolation::SrgbGamma)
|
||||
.track_space(GradientSpace::RgbGamma)
|
||||
.markers(spectrum_markers)
|
||||
.show_midpoints(false)
|
||||
.allow_insert(false)
|
||||
@@ -1563,7 +1565,7 @@ fn spectrum_slider_row(
|
||||
let position_to_value = move |position: f64| value_min + position * value_range;
|
||||
row.push(
|
||||
SpectrumInput::new(GradientStops::from(&track))
|
||||
.track_interpolation(GradientInterpolation::SrgbGamma)
|
||||
.track_space(GradientSpace::RgbGamma)
|
||||
.markers(vec![SpectrumMarker::new(position, 0.5, handle_color)])
|
||||
.show_midpoints(false)
|
||||
.allow_insert(false)
|
||||
@@ -2392,7 +2394,7 @@ fn root_layer_for_chain_node(node_id: NodeId, context: &mut NodePropertiesContex
|
||||
/// and reusing the same helper the Gradient tool uses, so canvas tilt and layer transforms behave identically.
|
||||
fn gradient_orientation_in_fill_node(node_id: NodeId, gradient_transform: DAffine2, context: &mut NodePropertiesContext) -> Option<bool> {
|
||||
let layer = root_layer_for_chain_node(node_id, context)?;
|
||||
let transform = graph_modification_utils::gradient_space_transform(layer, context.network_interface);
|
||||
let transform = graph_modification_utils::gradient_to_viewport_transform(layer, context.network_interface);
|
||||
Some(graph_modification_utils::gradient_orientation_rightward(transform * gradient_transform))
|
||||
}
|
||||
|
||||
@@ -2407,6 +2409,8 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
gradient: Gradient,
|
||||
gradient_form: GradientForm,
|
||||
gradient_spread: GradientSpread,
|
||||
gradient_space: GradientSpace,
|
||||
gradient_hue_direction: GradientHueDirection,
|
||||
transform: DAffine2,
|
||||
/// Whether the transform input holds a plain value (so the "Reverse Direction" button may write to it) rather than a wire.
|
||||
transform_is_value: bool,
|
||||
@@ -2437,6 +2441,8 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
gradient: gradient.stops,
|
||||
gradient_form: gradient.gradient_form,
|
||||
gradient_spread: gradient.gradient_spread,
|
||||
gradient_space: gradient.gradient_space,
|
||||
gradient_hue_direction: gradient.gradient_hue_direction,
|
||||
transform: gradient.transform,
|
||||
transform_is_value: gradient.transform_is_value,
|
||||
},
|
||||
@@ -2464,9 +2470,17 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
};
|
||||
|
||||
match &fill {
|
||||
ResolvedFill::Gradient { gradient: stops, gradient_spread, .. } => {
|
||||
ResolvedFill::Gradient {
|
||||
gradient: stops,
|
||||
gradient_spread,
|
||||
gradient_space,
|
||||
gradient_hue_direction,
|
||||
..
|
||||
} => {
|
||||
let stops = stops.clone();
|
||||
let gradient_spread = *gradient_spread;
|
||||
let gradient_space = *gradient_space;
|
||||
let gradient_hue_direction = *gradient_hue_direction;
|
||||
|
||||
let reverse_button = IconButton::new("Reverse", 24)
|
||||
.tooltip_label("Reverse Stops")
|
||||
@@ -2475,6 +2489,8 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
move |_| {
|
||||
TaggedValue::GradientRamp(GradientRamp {
|
||||
gradient_spread,
|
||||
gradient_space,
|
||||
gradient_hue_direction,
|
||||
..GradientRamp::from(stops.reversed())
|
||||
})
|
||||
},
|
||||
@@ -2496,8 +2512,16 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
FillChoice::<SRGBA8>::None
|
||||
}
|
||||
}
|
||||
ResolvedFill::Gradient { gradient: stops, gradient_spread, .. } => FillChoice::<SRGBA8>::Gradient(GradientRamp {
|
||||
ResolvedFill::Gradient {
|
||||
gradient: stops,
|
||||
gradient_spread,
|
||||
gradient_space,
|
||||
gradient_hue_direction,
|
||||
..
|
||||
} => FillChoice::<SRGBA8>::Gradient(GradientRamp {
|
||||
gradient_spread: *gradient_spread,
|
||||
gradient_space: *gradient_space,
|
||||
gradient_hue_direction: *gradient_hue_direction,
|
||||
..GradientRamp::from(stops)
|
||||
}),
|
||||
ResolvedFill::Other => FillChoice::<SRGBA8>::None,
|
||||
|
||||
@@ -808,8 +808,8 @@ async fn legacy_four_input_fill_migrates_to_the_split_transform_shape() {
|
||||
panic!("the legacy stops parameter should become a gradient ramp value, but became {stops:?}");
|
||||
};
|
||||
assert_eq!(
|
||||
ramp.gradient_interpolation,
|
||||
graphene_std::vector::style::GradientInterpolation::SrgbGamma,
|
||||
ramp.gradient_space,
|
||||
graphene_std::vector::style::GradientSpace::RgbGamma,
|
||||
"a legacy document's ramps should deserialize with the explicit gamma interpolation"
|
||||
);
|
||||
let stops = graphene_std::vector::Gradient::from(ramp);
|
||||
@@ -850,8 +850,8 @@ async fn eight_input_fill_migrates_the_spread_input_into_the_ramp() {
|
||||
};
|
||||
assert_eq!(ramp.gradient_spread, GradientSpread::Repeat, "the spread input's value should fold into the fill ramp");
|
||||
assert_eq!(
|
||||
ramp.gradient_interpolation,
|
||||
graphene_std::vector::style::GradientInterpolation::SrgbGamma,
|
||||
ramp.gradient_space,
|
||||
graphene_std::vector::style::GradientSpace::RgbGamma,
|
||||
"a legacy document's ramps should deserialize with the explicit gamma interpolation"
|
||||
);
|
||||
|
||||
@@ -861,8 +861,8 @@ async fn eight_input_fill_migrates_the_spread_input_into_the_ramp() {
|
||||
};
|
||||
assert_eq!(backup_ramp.gradient_spread, GradientSpread::Repeat, "the spread input's value should fold into the backup ramp");
|
||||
assert_eq!(
|
||||
backup_ramp.gradient_interpolation,
|
||||
graphene_std::vector::style::GradientInterpolation::SrgbGamma,
|
||||
backup_ramp.gradient_space,
|
||||
graphene_std::vector::style::GradientSpace::RgbGamma,
|
||||
"the backup ramp should carry the explicit gamma interpolation too"
|
||||
);
|
||||
|
||||
|
||||
@@ -172,6 +172,13 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
node: graphene_std::graphic::read_gradient_spread_attribute::IDENTIFIER,
|
||||
aliases: &["graphic_nodes::graphic::ReadAttributeSpreadMethodNode", "graphic_nodes::graphic::ReadSpreadMethodAttributeNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic::read_gradient_space_attribute::IDENTIFIER,
|
||||
aliases: &[
|
||||
"graphic_nodes::graphic::ReadAttributeGradientInterpolationNode",
|
||||
"graphic_nodes::graphic::ReadGradientInterpolationAttributeNode",
|
||||
],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::list::remove_at_index::IDENTIFIER,
|
||||
aliases: &["graphic_nodes::graphic::OmitElementNode", "graphic_nodes::graphic::RemoveAtIndexNode"],
|
||||
@@ -273,6 +280,10 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
node: graphene_std::math_nodes::gradient_form::IDENTIFIER,
|
||||
aliases: &["math_nodes::GradientTypeNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::math_nodes::gradient_space::IDENTIFIER,
|
||||
aliases: &["math_nodes::GradientInterpolationNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::math_nodes::gradient_spread::IDENTIFIER,
|
||||
aliases: &["math_nodes::SpreadMethodNode"],
|
||||
|
||||
@@ -14,7 +14,7 @@ use graphene_std::subpath::Subpath;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::misc::ManipulatorPointId;
|
||||
use graphene_std::vector::style::{FillChoice, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin, initial_gradient_transform_for_bounding_box};
|
||||
use graphene_std::vector::{Gradient, GradientForm, GradientInterpolation, GradientRamp, GradientSpread, PointId, SegmentId, VectorModificationType};
|
||||
use graphene_std::vector::{Gradient, GradientForm, GradientHueDirection, GradientRamp, GradientSpace, GradientSpread, PointId, SegmentId, VectorModificationType};
|
||||
use graphene_std::{NodeParameter, ParameterRef};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
@@ -402,9 +402,14 @@ pub fn get_chain_source_gradient_spread(layer: LayerNodeIdentifier, network_inte
|
||||
Some(get_chain_source_gradient_ramp(layer, network_interface)?.gradient_spread)
|
||||
}
|
||||
|
||||
/// The interpolation baked into the 'Gradient Value' node feeding a layer's chain.
|
||||
pub fn get_chain_source_gradient_interpolation(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<GradientInterpolation> {
|
||||
Some(get_chain_source_gradient_ramp(layer, network_interface)?.gradient_interpolation)
|
||||
/// The space baked into the 'Gradient Value' node feeding a layer's chain.
|
||||
pub fn get_chain_source_gradient_space(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<GradientSpace> {
|
||||
Some(get_chain_source_gradient_ramp(layer, network_interface)?.gradient_space)
|
||||
}
|
||||
|
||||
/// The hue direction baked into the 'Gradient Value' node feeding a layer's chain.
|
||||
pub fn get_chain_source_gradient_hue_direction(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<GradientHueDirection> {
|
||||
Some(get_chain_source_gradient_ramp(layer, network_interface)?.gradient_hue_direction)
|
||||
}
|
||||
|
||||
/// Get the gradient stops of a layer, if any.
|
||||
@@ -466,7 +471,7 @@ pub fn get_gradient_stops(layer: LayerNodeIdentifier, network_interface: &NodeNe
|
||||
/// Compute the transform from a gradient's local space to viewport space for the given layer. For a `List<Gradient>`
|
||||
/// layer this is the layer's incoming footprint transform; for a Fill-owned gradient value it composes the layer's viewport
|
||||
/// transform with the [0,1]² → bounding-box mapping.
|
||||
pub fn gradient_space_transform(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> glam::DAffine2 {
|
||||
pub fn gradient_to_viewport_transform(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> glam::DAffine2 {
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
|
||||
|
||||
let metadata = network_interface.document_metadata();
|
||||
@@ -762,7 +767,8 @@ pub struct FillNodeGradient {
|
||||
pub stops: Gradient,
|
||||
pub gradient_form: GradientForm,
|
||||
pub gradient_spread: GradientSpread,
|
||||
pub gradient_interpolation: GradientInterpolation,
|
||||
pub gradient_space: GradientSpace,
|
||||
pub gradient_hue_direction: GradientHueDirection,
|
||||
pub transform: DAffine2,
|
||||
/// Whether the transform input holds a plain value (so it may be written to) rather than a wire.
|
||||
pub transform_is_value: bool,
|
||||
@@ -776,7 +782,8 @@ pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOn
|
||||
return None;
|
||||
};
|
||||
let gradient_spread = ramp.gradient_spread;
|
||||
let gradient_interpolation = ramp.gradient_interpolation;
|
||||
let gradient_space = ramp.gradient_space;
|
||||
let gradient_hue_direction = ramp.gradient_hue_direction;
|
||||
let stops = Gradient::from(ramp);
|
||||
let gradient_form = match fill_node.input(fill::GradientFormInput).and_then(|input| input.as_value()) {
|
||||
Some(&TaggedValue::GradientForm(value)) => value,
|
||||
@@ -794,7 +801,8 @@ pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOn
|
||||
stops,
|
||||
gradient_form,
|
||||
gradient_spread,
|
||||
gradient_interpolation,
|
||||
gradient_space,
|
||||
gradient_hue_direction,
|
||||
transform,
|
||||
transform_is_value: transform_input.is_some(),
|
||||
})
|
||||
@@ -944,7 +952,8 @@ pub fn set_fill_for_selected_layers(fill_choice: FillChoice, document: &Document
|
||||
gradient: Gradient::from(ramp),
|
||||
gradient_form,
|
||||
gradient_spread: ramp.gradient_spread,
|
||||
gradient_interpolation: ramp.gradient_interpolation,
|
||||
gradient_space: ramp.gradient_space,
|
||||
gradient_hue_direction: ramp.gradient_hue_direction,
|
||||
transform,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,15 +9,15 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface};
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{
|
||||
self, NodeGraphLayer, get_chain_source_gradient_interpolation, get_chain_source_gradient_spread, get_fill_node_id_with_direct_fill_input, get_gradient_stops, get_upstream_gradient_value_node_id,
|
||||
gradient_chain_target_input, replaceable_paint_chain, reverse_direction_tooltip_description,
|
||||
self, NodeGraphLayer, get_chain_source_gradient_hue_direction, get_chain_source_gradient_space, get_chain_source_gradient_spread, get_fill_node_id_with_direct_fill_input, get_gradient_stops,
|
||||
get_upstream_gradient_value_node_id, gradient_chain_target_input, replaceable_paint_chain, reverse_direction_tooltip_description,
|
||||
};
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
||||
use glam::DMat2;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::raster::color::Color;
|
||||
use graphene_std::vector::style::{FillChoice, Gradient, GradientForm, GradientInterpolation, GradientRamp, GradientSpread, GradientStop, build_transform_with_y_preservation};
|
||||
use graphene_std::vector::style::{FillChoice, Gradient, GradientForm, GradientHueDirection, GradientRamp, GradientSpace, GradientSpread, GradientStop, build_transform_with_y_preservation};
|
||||
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct GradientTool {
|
||||
@@ -30,7 +30,8 @@ pub struct GradientTool {
|
||||
pub struct GradientOptions {
|
||||
gradient_form: GradientForm,
|
||||
gradient_spread: GradientSpread,
|
||||
gradient_interpolation: GradientInterpolation,
|
||||
gradient_space: GradientSpace,
|
||||
gradient_hue_direction: GradientHueDirection,
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Gradient)]
|
||||
@@ -139,8 +140,17 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
|
||||
ToolMessage::Gradient(GradientToolMessage::UpdateRamp { ramp }) => {
|
||||
let ramp = GradientRamp::from(&ramp);
|
||||
self.options.gradient_spread = ramp.gradient_spread;
|
||||
self.options.gradient_interpolation = ramp.gradient_interpolation;
|
||||
apply_stops_update(&mut self.data, context, responses, Gradient::from(&ramp), ramp.gradient_spread, ramp.gradient_interpolation);
|
||||
self.options.gradient_space = ramp.gradient_space;
|
||||
self.options.gradient_hue_direction = ramp.gradient_hue_direction;
|
||||
apply_stops_update(
|
||||
&mut self.data,
|
||||
context,
|
||||
responses,
|
||||
Gradient::from(&ramp),
|
||||
ramp.gradient_spread,
|
||||
ramp.gradient_space,
|
||||
ramp.gradient_hue_direction,
|
||||
);
|
||||
}
|
||||
ToolMessage::Gradient(GradientToolMessage::CloseStopColorPicker) => {
|
||||
if self.data.color_picker_transaction_open {
|
||||
@@ -178,8 +188,12 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
|
||||
self.options.gradient_spread = appearance.gradient_spread;
|
||||
needs_refresh = true;
|
||||
}
|
||||
if self.options.gradient_interpolation != appearance.gradient_interpolation {
|
||||
self.options.gradient_interpolation = appearance.gradient_interpolation;
|
||||
if self.options.gradient_space != appearance.gradient_space {
|
||||
self.options.gradient_space = appearance.gradient_space;
|
||||
needs_refresh = true;
|
||||
}
|
||||
if self.options.gradient_hue_direction != appearance.gradient_hue_direction {
|
||||
self.options.gradient_hue_direction = appearance.gradient_hue_direction;
|
||||
needs_refresh = true;
|
||||
}
|
||||
}
|
||||
@@ -198,7 +212,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
|
||||
|
||||
let new_orientation = match (current_layer, ¤t_gradient) {
|
||||
(Some(layer), Some((_gradient, appearance))) => {
|
||||
let transform = gradient_space_transform(layer, context.document) * appearance.transform;
|
||||
let transform = gradient_to_viewport_transform(layer, context.document) * appearance.transform;
|
||||
graph_modification_utils::gradient_orientation_rightward(transform)
|
||||
}
|
||||
_ => true,
|
||||
@@ -262,7 +276,8 @@ impl LayoutHolder for GradientTool {
|
||||
});
|
||||
let stops_widget = ColorInput::new(FillChoice::Gradient(GradientRamp {
|
||||
gradient_spread: self.options.gradient_spread,
|
||||
gradient_interpolation: self.options.gradient_interpolation,
|
||||
gradient_space: self.options.gradient_space,
|
||||
gradient_hue_direction: self.options.gradient_hue_direction,
|
||||
..GradientRamp::from(&stops_value)
|
||||
}))
|
||||
.allow_none(false)
|
||||
@@ -335,8 +350,8 @@ impl Default for GradientToolFsmState {
|
||||
}
|
||||
|
||||
/// Computes the transform from gradient space to viewport space.
|
||||
fn gradient_space_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
|
||||
graph_modification_utils::gradient_space_transform(layer, &document.network_interface)
|
||||
fn gradient_to_viewport_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
|
||||
graph_modification_utils::gradient_to_viewport_transform(layer, &document.network_interface)
|
||||
}
|
||||
|
||||
/// Viewport positions of the gradient's start (unit param 0) and end (unit param 1) handles.
|
||||
@@ -363,7 +378,8 @@ fn resolve_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkI
|
||||
GradientAppearance {
|
||||
gradient_form: gradient.gradient_form,
|
||||
gradient_spread: gradient.gradient_spread,
|
||||
gradient_interpolation: gradient.gradient_interpolation,
|
||||
gradient_space: gradient.gradient_space,
|
||||
gradient_hue_direction: gradient.gradient_hue_direction,
|
||||
transform: gradient.transform,
|
||||
},
|
||||
GradientSource::Direct,
|
||||
@@ -383,7 +399,8 @@ struct GradientAppearance {
|
||||
transform: DAffine2,
|
||||
gradient_form: GradientForm,
|
||||
gradient_spread: GradientSpread,
|
||||
gradient_interpolation: GradientInterpolation,
|
||||
gradient_space: GradientSpace,
|
||||
gradient_hue_direction: GradientHueDirection,
|
||||
}
|
||||
|
||||
/// Resolve the gradient transform, form, and spread by walking the chain feeding the layer.
|
||||
@@ -424,7 +441,8 @@ fn read_gradient_chain_state(layer: LayerNodeIdentifier, network_interface: &Nod
|
||||
transform: composed_transform,
|
||||
gradient_form: gradient_form.unwrap_or_default(),
|
||||
gradient_spread: get_chain_source_gradient_spread(layer, network_interface).unwrap_or_default(),
|
||||
gradient_interpolation: get_chain_source_gradient_interpolation(layer, network_interface).unwrap_or_default(),
|
||||
gradient_space: get_chain_source_gradient_space(layer, network_interface).unwrap_or_default(),
|
||||
gradient_hue_direction: get_chain_source_gradient_hue_direction(layer, network_interface).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,7 +495,7 @@ struct SelectedGradient {
|
||||
layer: Option<LayerNodeIdentifier>,
|
||||
dragging: GradientDragTarget,
|
||||
/// Transform from the geometry's local gradient space to viewport space.
|
||||
gradient_space_transform: DAffine2,
|
||||
gradient_to_viewport_transform: DAffine2,
|
||||
gradient: Gradient,
|
||||
appearance: GradientAppearance,
|
||||
initial_gradient: Gradient,
|
||||
@@ -526,10 +544,10 @@ fn calculate_insertion(start: DVec2, end: DVec2, stops: &Gradient, mouse: DVec2)
|
||||
|
||||
impl SelectedGradient {
|
||||
pub fn new(gradient: Gradient, appearance: GradientAppearance, source: GradientSource, layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Self {
|
||||
let gradient_space_transform = gradient_space_transform(layer, document);
|
||||
let gradient_to_viewport_transform = gradient_to_viewport_transform(layer, document);
|
||||
Self {
|
||||
layer: Some(layer),
|
||||
gradient_space_transform,
|
||||
gradient_to_viewport_transform,
|
||||
gradient: gradient.clone(),
|
||||
appearance,
|
||||
dragging: GradientDragTarget::End,
|
||||
@@ -615,7 +633,7 @@ impl SelectedGradient {
|
||||
snap_manager.update_indicator(snapped);
|
||||
}
|
||||
|
||||
let local_mouse = self.gradient_space_transform.inverse().transform_point2(mouse);
|
||||
let local_mouse = self.gradient_to_viewport_transform.inverse().transform_point2(mouse);
|
||||
let local_start = self.appearance.transform.transform_point2(DVec2::ZERO);
|
||||
let local_end = self.appearance.transform.transform_point2(DVec2::X);
|
||||
|
||||
@@ -630,7 +648,7 @@ impl SelectedGradient {
|
||||
self.appearance.transform = create_new_gradient_transform(local_start, local_mouse);
|
||||
}
|
||||
GradientDragTarget::New => {
|
||||
self.appearance.transform = create_new_gradient_transform(self.gradient_space_transform.inverse().transform_point2(drag_start), local_mouse);
|
||||
self.appearance.transform = create_new_gradient_transform(self.gradient_to_viewport_transform.inverse().transform_point2(drag_start), local_mouse);
|
||||
}
|
||||
GradientDragTarget::Stop(stop) => {
|
||||
let document_to_viewport = snap_data.document.metadata().document_to_viewport;
|
||||
@@ -761,7 +779,8 @@ impl SelectedGradient {
|
||||
gradient: self.gradient.clone(),
|
||||
gradient_form: self.appearance.gradient_form,
|
||||
gradient_spread: self.appearance.gradient_spread,
|
||||
gradient_interpolation: self.appearance.gradient_interpolation,
|
||||
gradient_space: self.appearance.gradient_space,
|
||||
gradient_hue_direction: self.appearance.gradient_hue_direction,
|
||||
transform: self.appearance.transform,
|
||||
});
|
||||
}
|
||||
@@ -769,7 +788,7 @@ impl SelectedGradient {
|
||||
}
|
||||
|
||||
fn unit_to_viewport_transform(&self) -> DAffine2 {
|
||||
self.gradient_space_transform * self.appearance.transform
|
||||
self.gradient_to_viewport_transform * self.appearance.transform
|
||||
}
|
||||
|
||||
fn viewport_handle_positions(&self) -> (DVec2, DVec2) {
|
||||
@@ -800,9 +819,13 @@ fn dispatch_gradient_chain_writes(layer: LayerNodeIdentifier, gradient: &Gradien
|
||||
layer,
|
||||
gradient_spread: appearance.gradient_spread,
|
||||
});
|
||||
responses.add(GraphOperationMessage::GradientInterpolationSet {
|
||||
responses.add(GraphOperationMessage::GradientSpaceSet {
|
||||
layer,
|
||||
gradient_interpolation: appearance.gradient_interpolation,
|
||||
gradient_space: appearance.gradient_space,
|
||||
});
|
||||
responses.add(GraphOperationMessage::GradientHueDirectionSet {
|
||||
layer,
|
||||
gradient_hue_direction: appearance.gradient_hue_direction,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -883,7 +906,7 @@ impl Fsm for GradientToolFsmState {
|
||||
let Some((gradient, appearance, _source)) = resolve_gradient(layer, &document.network_interface) else {
|
||||
continue;
|
||||
};
|
||||
let unit_to_viewport = gradient_space_transform(layer, document) * appearance.transform;
|
||||
let unit_to_viewport = gradient_to_viewport_transform(layer, document) * appearance.transform;
|
||||
let dragging = selected
|
||||
.filter(|selected| selected.layer.is_some_and(|selected_layer| selected_layer == layer))
|
||||
.map(|selected| selected.dragging);
|
||||
@@ -1065,8 +1088,8 @@ impl Fsm for GradientToolFsmState {
|
||||
&& let Some(selected_gradient) = tool_data.selected_gradient.as_ref()
|
||||
&& let Some(layer) = selected_gradient.layer
|
||||
{
|
||||
// The gradient space transform has be recalculated as the saved transform in SelectedGradient may become stale by panning/zooming during the rendering of the overlay.
|
||||
let transform = gradient_space_transform(layer, document) * selected_gradient.appearance.transform;
|
||||
// The gradient-to-viewport transform has to be recalculated as the saved transform in SelectedGradient may become stale by panning/zooming during the rendering of the overlay.
|
||||
let transform = gradient_to_viewport_transform(layer, document) * selected_gradient.appearance.transform;
|
||||
let gradient = &selected_gradient.gradient;
|
||||
if stop_index < gradient.len() {
|
||||
let color = gradient.color(stop_index).unwrap_or(Color::BLACK);
|
||||
@@ -1244,7 +1267,7 @@ impl Fsm for GradientToolFsmState {
|
||||
continue;
|
||||
};
|
||||
// TODO: This transform is incorrect. I think this is since it is based on the Footprint which has not been updated yet
|
||||
let unit_to_viewport = gradient_space_transform(layer, document) * appearance.transform;
|
||||
let unit_to_viewport = gradient_to_viewport_transform(layer, document) * appearance.transform;
|
||||
let mouse = input.mouse.position;
|
||||
let (start, end) = gradient_handle_positions(unit_to_viewport);
|
||||
|
||||
@@ -1254,7 +1277,7 @@ impl Fsm for GradientToolFsmState {
|
||||
// If click is on the line then insert point
|
||||
if distance < (SELECTION_THRESHOLD * 2.) {
|
||||
// Try and insert the new stop
|
||||
if let Some(index) = insert_stop_at_point(&mut gradient, mouse, unit_to_viewport, appearance.gradient_interpolation) {
|
||||
if let Some(index) = insert_stop_at_point(&mut gradient, mouse, unit_to_viewport, appearance.gradient_space, appearance.gradient_hue_direction) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let mut selected_gradient = SelectedGradient::new(gradient, appearance, source, layer, document);
|
||||
@@ -1298,8 +1321,8 @@ impl Fsm for GradientToolFsmState {
|
||||
let Some((gradient, appearance, source)) = resolve_gradient(layer, &document.network_interface) else {
|
||||
continue;
|
||||
};
|
||||
let gradient_space_transform = gradient_space_transform(layer, document);
|
||||
let unit_to_viewport = gradient_space_transform * appearance.transform;
|
||||
let gradient_to_viewport_transform = gradient_to_viewport_transform(layer, document);
|
||||
let unit_to_viewport = gradient_to_viewport_transform * appearance.transform;
|
||||
let is_gradient_chain = source == GradientSource::Chain;
|
||||
let (start, end) = gradient_handle_positions(unit_to_viewport);
|
||||
|
||||
@@ -1324,7 +1347,7 @@ impl Fsm for GradientToolFsmState {
|
||||
|
||||
tool_data.selected_gradient = Some(SelectedGradient {
|
||||
layer: Some(layer),
|
||||
gradient_space_transform,
|
||||
gradient_to_viewport_transform,
|
||||
gradient: gradient.clone(),
|
||||
appearance,
|
||||
initial_gradient_transform: appearance.transform,
|
||||
@@ -1368,7 +1391,7 @@ impl Fsm for GradientToolFsmState {
|
||||
tool_data.selected_gradient = Some(SelectedGradient {
|
||||
layer: Some(layer),
|
||||
dragging: drag_target,
|
||||
gradient_space_transform,
|
||||
gradient_to_viewport_transform,
|
||||
gradient: gradient.clone(),
|
||||
appearance,
|
||||
initial_gradient: gradient.clone(),
|
||||
@@ -1386,7 +1409,7 @@ impl Fsm for GradientToolFsmState {
|
||||
tool_data.selected_gradient = Some(SelectedGradient {
|
||||
layer: Some(layer),
|
||||
dragging: dragging_target,
|
||||
gradient_space_transform,
|
||||
gradient_to_viewport_transform,
|
||||
gradient: gradient.clone(),
|
||||
appearance,
|
||||
initial_gradient: gradient.clone(),
|
||||
@@ -1404,7 +1427,7 @@ impl Fsm for GradientToolFsmState {
|
||||
|
||||
if distance.abs() < SEGMENT_INSERTION_DISTANCE && (0. ..=1.).contains(&projection) {
|
||||
let mut new_gradient = gradient.clone();
|
||||
if let Some(index) = insert_stop_at_point(&mut new_gradient, mouse, unit_to_viewport, appearance.gradient_interpolation) {
|
||||
if let Some(index) = insert_stop_at_point(&mut new_gradient, mouse, unit_to_viewport, appearance.gradient_space, appearance.gradient_hue_direction) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
transaction_started = true;
|
||||
|
||||
@@ -1483,7 +1506,8 @@ impl Fsm for GradientToolFsmState {
|
||||
transform: DAffine2::IDENTITY,
|
||||
gradient_form: tool_options.gradient_form,
|
||||
gradient_spread: tool_options.gradient_spread,
|
||||
gradient_interpolation: tool_options.gradient_interpolation,
|
||||
gradient_space: tool_options.gradient_space,
|
||||
gradient_hue_direction: tool_options.gradient_hue_direction,
|
||||
},
|
||||
// A blank layer, or one holding only the other tool's paint, starts a whole-expanse gradient chain; a layer with content gets its Fill painted
|
||||
if replaceable_paint_chain(layer, &document.network_interface).is_some() {
|
||||
@@ -1524,8 +1548,8 @@ impl Fsm for GradientToolFsmState {
|
||||
|
||||
// Recompute the gradient-to-viewport transform fresh each frame so zoom/pan mid-drag works correctly
|
||||
if let Some(layer) = selected_gradient.layer {
|
||||
selected_gradient.gradient_space_transform = gradient_space_transform(layer, document);
|
||||
selected_gradient.gradient_space_transform.translation += tool_data.auto_pan_shift;
|
||||
selected_gradient.gradient_to_viewport_transform = gradient_to_viewport_transform(layer, document);
|
||||
selected_gradient.gradient_to_viewport_transform.translation += tool_data.auto_pan_shift;
|
||||
}
|
||||
|
||||
// Convert drag_start from document space to effective viewport space
|
||||
@@ -1715,10 +1739,10 @@ impl Fsm for GradientToolFsmState {
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_stop_at_point(gradient: &mut Gradient, point: DVec2, unit_to_viewport: DAffine2, gradient_interpolation: GradientInterpolation) -> Option<usize> {
|
||||
fn insert_stop_at_point(gradient: &mut Gradient, point: DVec2, unit_to_viewport: DAffine2, gradient_space: GradientSpace, gradient_hue_direction: GradientHueDirection) -> Option<usize> {
|
||||
let (start, end) = gradient_handle_positions(unit_to_viewport);
|
||||
let t = ((end - start).angle_to(point - start)).cos() * start.distance(point) / start.distance(end);
|
||||
(0. ..=1.).contains(&t).then(|| gradient.insert_stop(t, gradient_interpolation))
|
||||
(0. ..=1.).contains(&t).then(|| gradient.insert_stop(t, gradient_space, gradient_hue_direction))
|
||||
}
|
||||
|
||||
fn dismiss_color_stop_color_picker(tool_data: &mut GradientToolData, responses: &mut VecDeque<Message>) {
|
||||
@@ -1739,8 +1763,8 @@ fn detect_hover_target(mouse: DVec2, document: &DocumentMessageHandler) -> Gradi
|
||||
let Some((gradient, appearance, _source)) = resolve_gradient(layer, &document.network_interface) else {
|
||||
continue;
|
||||
};
|
||||
let gradient_space_transform = gradient_space_transform(layer, document);
|
||||
let unit_to_viewport = gradient_space_transform * appearance.transform;
|
||||
let gradient_to_viewport_transform = gradient_to_viewport_transform(layer, document);
|
||||
let unit_to_viewport = gradient_to_viewport_transform * appearance.transform;
|
||||
let (start, end) = gradient_handle_positions(unit_to_viewport);
|
||||
let line_length = start.distance(end);
|
||||
|
||||
@@ -1842,7 +1866,8 @@ fn apply_gradient_update(
|
||||
gradient,
|
||||
gradient_form: appearance.gradient_form,
|
||||
gradient_spread: appearance.gradient_spread,
|
||||
gradient_interpolation: appearance.gradient_interpolation,
|
||||
gradient_space: appearance.gradient_space,
|
||||
gradient_hue_direction: appearance.gradient_hue_direction,
|
||||
transform: appearance.transform,
|
||||
});
|
||||
}
|
||||
@@ -1872,7 +1897,8 @@ fn apply_stops_update(
|
||||
responses: &mut VecDeque<Message>,
|
||||
new_gradient: Gradient,
|
||||
gradient_spread: GradientSpread,
|
||||
gradient_interpolation: GradientInterpolation,
|
||||
gradient_space: GradientSpace,
|
||||
gradient_hue_direction: GradientHueDirection,
|
||||
) {
|
||||
let selected_layers: Vec<_> = context
|
||||
.document
|
||||
@@ -1890,7 +1916,8 @@ fn apply_stops_update(
|
||||
if get_upstream_gradient_value_node_id(layer, &context.document.network_interface).is_some() {
|
||||
responses.add(GraphOperationMessage::GradientStopsSet { layer, stops: new_gradient.clone() });
|
||||
responses.add(GraphOperationMessage::GradientSpreadSet { layer, gradient_spread });
|
||||
responses.add(GraphOperationMessage::GradientInterpolationSet { layer, gradient_interpolation });
|
||||
responses.add(GraphOperationMessage::GradientSpaceSet { layer, gradient_space });
|
||||
responses.add(GraphOperationMessage::GradientHueDirectionSet { layer, gradient_hue_direction });
|
||||
updated_any_layer = true;
|
||||
} else if let Some((_gradient, appearance, _source)) = resolve_gradient(layer, &context.document.network_interface) {
|
||||
responses.add(GraphOperationMessage::FillGradientSet {
|
||||
@@ -1898,7 +1925,8 @@ fn apply_stops_update(
|
||||
gradient: new_gradient.clone(),
|
||||
gradient_form: appearance.gradient_form,
|
||||
gradient_spread,
|
||||
gradient_interpolation,
|
||||
gradient_space,
|
||||
gradient_hue_direction,
|
||||
transform: appearance.transform,
|
||||
});
|
||||
updated_any_layer = true;
|
||||
@@ -1908,7 +1936,8 @@ fn apply_stops_update(
|
||||
if let Some(selected_gradient) = &mut data.selected_gradient {
|
||||
selected_gradient.gradient = new_gradient.clone();
|
||||
selected_gradient.appearance.gradient_spread = gradient_spread;
|
||||
selected_gradient.appearance.gradient_interpolation = gradient_interpolation;
|
||||
selected_gradient.appearance.gradient_space = gradient_space;
|
||||
selected_gradient.appearance.gradient_hue_direction = gradient_hue_direction;
|
||||
}
|
||||
|
||||
// When no selected layer had a gradient to update, the user is editing the tool's default gradient instead.
|
||||
@@ -2008,7 +2037,7 @@ mod test_gradient {
|
||||
use graphene_std::vector::style::{GradientForm, GradientSpread, build_transform_with_y_preservation};
|
||||
use graphene_std::vector::{Gradient, GradientRamp, GradientStop, fill};
|
||||
|
||||
use super::gradient_space_transform;
|
||||
use super::gradient_to_viewport_transform;
|
||||
|
||||
struct ResolvedGradient {
|
||||
stops: Gradient,
|
||||
@@ -2065,7 +2094,7 @@ mod test_gradient {
|
||||
transform: local_transform,
|
||||
};
|
||||
|
||||
let transform = gradient_space_transform(layer, document);
|
||||
let transform = gradient_to_viewport_transform(layer, document);
|
||||
Some((gradient, transform))
|
||||
})
|
||||
.collect()
|
||||
@@ -2082,7 +2111,7 @@ mod test_gradient {
|
||||
|
||||
let (gradient, appearance, _) = super::resolve_gradient(layer, &document.network_interface)?;
|
||||
let gradient = ResolvedGradient::new(gradient, appearance);
|
||||
let transform = gradient_space_transform(layer, document);
|
||||
let transform = gradient_to_viewport_transform(layer, document);
|
||||
Some((gradient, transform))
|
||||
})
|
||||
.collect()
|
||||
@@ -2770,11 +2799,11 @@ mod test_gradient {
|
||||
editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }).await;
|
||||
|
||||
let document = editor.active_document();
|
||||
let space_transform = gradient_space_transform(layer, document);
|
||||
let to_viewport_transform = gradient_to_viewport_transform(layer, document);
|
||||
let (gradient, appearance, _) = super::resolve_gradient(layer, &document.network_interface).unwrap();
|
||||
let gradient = ResolvedGradient::new(gradient, appearance);
|
||||
let viewport_start = space_transform.transform_point2(gradient.start());
|
||||
let viewport_end = space_transform.transform_point2(gradient.end());
|
||||
let viewport_start = to_viewport_transform.transform_point2(gradient.start());
|
||||
let viewport_end = to_viewport_transform.transform_point2(gradient.end());
|
||||
|
||||
// Drag target of the end point, move 80px down
|
||||
let new_viewport_end = viewport_end + DVec2::new(0., 80.);
|
||||
@@ -2797,9 +2826,9 @@ mod test_gradient {
|
||||
let document = editor.active_document();
|
||||
let (updated, appearance, _) = super::resolve_gradient(layer, &document.network_interface).expect("Gradient should exist after drag");
|
||||
let updated = ResolvedGradient::new(updated, appearance);
|
||||
let updated_space_transform = gradient_space_transform(layer, document);
|
||||
let updated_viewport_start = updated_space_transform.transform_point2(updated.start());
|
||||
let updated_viewport_end = updated_space_transform.transform_point2(updated.end());
|
||||
let updated_to_viewport_transform = gradient_to_viewport_transform(layer, document);
|
||||
let updated_viewport_start = updated_to_viewport_transform.transform_point2(updated.start());
|
||||
let updated_viewport_end = updated_to_viewport_transform.transform_point2(updated.end());
|
||||
|
||||
assert!(
|
||||
updated_viewport_start.abs_diff_eq(viewport_start, 1.),
|
||||
@@ -2848,10 +2877,10 @@ mod test_gradient {
|
||||
editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }).await;
|
||||
|
||||
let document = editor.active_document();
|
||||
let space_transform = gradient_space_transform(layer, document);
|
||||
let to_viewport_transform = gradient_to_viewport_transform(layer, document);
|
||||
let (gradient, appearance, _) = super::resolve_gradient(layer, &document.network_interface).unwrap();
|
||||
let gradient = ResolvedGradient::new(gradient, appearance);
|
||||
let viewport_end = space_transform.transform_point2(gradient.end());
|
||||
let viewport_end = to_viewport_transform.transform_point2(gradient.end());
|
||||
|
||||
// Drag the end point 80px down
|
||||
let new_viewport_end = viewport_end + DVec2::new(0., 80.);
|
||||
|
||||
Reference in New Issue
Block a user