mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-29 05:08:13 +08:00
Add a gradient interpolation attribute with Stepped, Linear (existing), and Smooth modes (#4418)
* Delete dead code function * Add a Gradient Interpolation axis with Stepped, Linear, and Smooth paths through the stops * Keep the gradient interpolation attached when dragging stops in the picker * Give the gradient popover's dropdowns the same tooltips as their row labels * Order the gradient popover with Intrp. above Space * Hold gradient stops in place when the cyclic flag is toggled * Fix wrong Smooth gradient colors around stops sitting on the ramp boundaries * Consolidate gradient settings plumbing and fix bugs * Bundle whole-ramp gradient settings into GradientSettings across the editor plumbing * Name the Smooth gradient interpolation's spline type in its tooltip * Linearize the gamma hex stop colors recovered from imported Graphite SVGs * Code review * Aim the Smooth bake's seam subdivision at the copied boundary color * Add GradientEvaluator to build gradient sampling state once per loop instead of per sample * Correct the gradient evaluator's documented setup cost to O(n log n)
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
use crate::messages::layout::utility_types::widgets::input_widgets::{SpectrumInputUpdate, VisualColorPickersInputUpdate};
|
use crate::messages::layout::utility_types::widgets::input_widgets::{SpectrumInputUpdate, VisualColorPickersInputUpdate};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use graphene_std::vector::style::{FillChoice, GradientHueDirection, GradientSpace, GradientSpread};
|
use graphene_std::vector::style::{FillChoice, GradientHueDirection, GradientInterpolation, GradientSpace, GradientSpread};
|
||||||
|
|
||||||
/// Identifies which RGB channel a numeric input change targets.
|
/// Identifies which RGB channel a numeric input change targets.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
@@ -55,6 +55,8 @@ pub enum ColorPickerMessage {
|
|||||||
SetGradientSpace { gradient_space: GradientSpace },
|
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.
|
/// 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 },
|
SetGradientHueDirection { gradient_hue_direction: GradientHueDirection },
|
||||||
|
/// Gradient interpolation choice: the path the stops interpolate along, from the "Intrp." dropdown.
|
||||||
|
SetGradientInterpolation { gradient_interpolation: GradientInterpolation },
|
||||||
|
|
||||||
/// Tell the frontend to start an undo transaction (forwarded as a `FrontendMessage` it bridges out to the picker's parent).
|
/// Tell the frontend to start an undo transaction (forwarded as a `FrontendMessage` it bridges out to the picker's parent).
|
||||||
StartTransaction,
|
StartTransaction,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::messages::prelude::*;
|
|||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::color::SRGBA8;
|
use graphene_std::color::SRGBA8;
|
||||||
use graphene_std::core_types::misc::parse_css_color;
|
use graphene_std::core_types::misc::parse_css_color;
|
||||||
use graphene_std::vector::style::{FillChoice, Gradient, GradientHueDirection, GradientRamp, GradientSpace, GradientSpread, GradientStops};
|
use graphene_std::vector::style::{FillChoice, Gradient, GradientHueDirection, GradientInterpolation, GradientRamp, GradientSettings, GradientSpace, GradientSpread, GradientStops};
|
||||||
|
|
||||||
/// Bounds for a midpoint position (relative to the interval between two adjacent gradient stops).
|
/// Bounds for a midpoint position (relative to the interval between two adjacent gradient stops).
|
||||||
const MIN_MIDPOINT: f64 = 0.01;
|
const MIN_MIDPOINT: f64 = 0.01;
|
||||||
@@ -33,6 +33,7 @@ pub struct ColorPickerMessageHandler {
|
|||||||
gradient_space: GradientSpace,
|
gradient_space: GradientSpace,
|
||||||
gradient_cyclic: bool,
|
gradient_cyclic: bool,
|
||||||
gradient_hue_direction: GradientHueDirection,
|
gradient_hue_direction: GradientHueDirection,
|
||||||
|
gradient_interpolation: GradientInterpolation,
|
||||||
active_marker_index: Option<u32>,
|
active_marker_index: Option<u32>,
|
||||||
active_marker_is_midpoint: bool,
|
active_marker_is_midpoint: bool,
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ impl Default for ColorPickerMessageHandler {
|
|||||||
gradient_space: GradientSpace::default(),
|
gradient_space: GradientSpace::default(),
|
||||||
gradient_cyclic: false,
|
gradient_cyclic: false,
|
||||||
gradient_hue_direction: GradientHueDirection::default(),
|
gradient_hue_direction: GradientHueDirection::default(),
|
||||||
|
gradient_interpolation: GradientInterpolation::default(),
|
||||||
active_marker_index: None,
|
active_marker_index: None,
|
||||||
active_marker_is_midpoint: false,
|
active_marker_is_midpoint: false,
|
||||||
allow_none: true,
|
allow_none: true,
|
||||||
@@ -82,6 +84,7 @@ impl MessageHandler<ColorPickerMessage, ()> for ColorPickerMessageHandler {
|
|||||||
self.gradient_space = GradientSpace::default();
|
self.gradient_space = GradientSpace::default();
|
||||||
self.gradient_cyclic = false;
|
self.gradient_cyclic = false;
|
||||||
self.gradient_hue_direction = GradientHueDirection::default();
|
self.gradient_hue_direction = GradientHueDirection::default();
|
||||||
|
self.gradient_interpolation = GradientInterpolation::default();
|
||||||
self.active_marker_index = None;
|
self.active_marker_index = None;
|
||||||
self.active_marker_is_midpoint = false;
|
self.active_marker_is_midpoint = false;
|
||||||
}
|
}
|
||||||
@@ -91,6 +94,7 @@ impl MessageHandler<ColorPickerMessage, ()> for ColorPickerMessageHandler {
|
|||||||
self.gradient_space = GradientSpace::default();
|
self.gradient_space = GradientSpace::default();
|
||||||
self.gradient_cyclic = false;
|
self.gradient_cyclic = false;
|
||||||
self.gradient_hue_direction = GradientHueDirection::default();
|
self.gradient_hue_direction = GradientHueDirection::default();
|
||||||
|
self.gradient_interpolation = GradientInterpolation::default();
|
||||||
self.active_marker_index = None;
|
self.active_marker_index = None;
|
||||||
self.active_marker_is_midpoint = false;
|
self.active_marker_is_midpoint = false;
|
||||||
self.adopt_color(color);
|
self.adopt_color(color);
|
||||||
@@ -102,6 +106,7 @@ impl MessageHandler<ColorPickerMessage, ()> for ColorPickerMessageHandler {
|
|||||||
self.gradient_space = ramp.gradient_space;
|
self.gradient_space = ramp.gradient_space;
|
||||||
self.gradient_cyclic = ramp.gradient_cyclic;
|
self.gradient_cyclic = ramp.gradient_cyclic;
|
||||||
self.gradient_hue_direction = ramp.gradient_hue_direction;
|
self.gradient_hue_direction = ramp.gradient_hue_direction;
|
||||||
|
self.gradient_interpolation = ramp.gradient_interpolation;
|
||||||
let gradient = Gradient::from(ramp);
|
let gradient = Gradient::from(ramp);
|
||||||
let first_color = gradient.color(0).unwrap_or(Color::BLACK);
|
let first_color = gradient.color(0).unwrap_or(Color::BLACK);
|
||||||
self.gradient = Some(gradient);
|
self.gradient = Some(gradient);
|
||||||
@@ -212,28 +217,20 @@ impl MessageHandler<ColorPickerMessage, ()> for ColorPickerMessageHandler {
|
|||||||
responses.add(FrontendMessage::ColorPickerStartHistoryTransaction);
|
responses.add(FrontendMessage::ColorPickerStartHistoryTransaction);
|
||||||
self.gradient_spread = gradient_spread;
|
self.gradient_spread = gradient_spread;
|
||||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||||
value: FillChoice::Gradient(GradientRamp {
|
value: FillChoice::Gradient(GradientRamp::from(gradient).with_settings(self.gradient_settings())),
|
||||||
gradient_spread,
|
|
||||||
gradient_space: self.gradient_space,
|
|
||||||
gradient_cyclic: self.gradient_cyclic,
|
|
||||||
gradient_hue_direction: self.gradient_hue_direction,
|
|
||||||
..GradientRamp::from(gradient)
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
self.send_layouts(responses);
|
self.send_layouts(responses);
|
||||||
}
|
}
|
||||||
ColorPickerMessage::SetGradientCyclic { gradient_cyclic } => {
|
ColorPickerMessage::SetGradientCyclic { gradient_cyclic } => {
|
||||||
let Some(gradient) = &self.gradient else { return };
|
let Some(gradient) = &mut self.gradient else { return };
|
||||||
responses.add(FrontendMessage::ColorPickerStartHistoryTransaction);
|
responses.add(FrontendMessage::ColorPickerStartHistoryTransaction);
|
||||||
self.gradient_cyclic = gradient_cyclic;
|
|
||||||
|
let previous_cyclic = std::mem::replace(&mut self.gradient_cyclic, gradient_cyclic);
|
||||||
|
gradient.hold_positions_across_cyclic_change(previous_cyclic, gradient_cyclic);
|
||||||
|
|
||||||
|
let ramp = GradientRamp::from(&*gradient);
|
||||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||||
value: FillChoice::Gradient(GradientRamp {
|
value: FillChoice::Gradient(ramp.with_settings(self.gradient_settings())),
|
||||||
gradient_spread: self.gradient_spread,
|
|
||||||
gradient_space: self.gradient_space,
|
|
||||||
gradient_cyclic,
|
|
||||||
gradient_hue_direction: self.gradient_hue_direction,
|
|
||||||
..GradientRamp::from(gradient)
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
self.send_layouts(responses);
|
self.send_layouts(responses);
|
||||||
}
|
}
|
||||||
@@ -242,13 +239,7 @@ impl MessageHandler<ColorPickerMessage, ()> for ColorPickerMessageHandler {
|
|||||||
responses.add(FrontendMessage::ColorPickerStartHistoryTransaction);
|
responses.add(FrontendMessage::ColorPickerStartHistoryTransaction);
|
||||||
self.gradient_space = gradient_space;
|
self.gradient_space = gradient_space;
|
||||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||||
value: FillChoice::Gradient(GradientRamp {
|
value: FillChoice::Gradient(GradientRamp::from(gradient).with_settings(self.gradient_settings())),
|
||||||
gradient_spread: self.gradient_spread,
|
|
||||||
gradient_space,
|
|
||||||
gradient_cyclic: self.gradient_cyclic,
|
|
||||||
gradient_hue_direction: self.gradient_hue_direction,
|
|
||||||
..GradientRamp::from(gradient)
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
self.send_layouts(responses);
|
self.send_layouts(responses);
|
||||||
}
|
}
|
||||||
@@ -257,13 +248,16 @@ impl MessageHandler<ColorPickerMessage, ()> for ColorPickerMessageHandler {
|
|||||||
responses.add(FrontendMessage::ColorPickerStartHistoryTransaction);
|
responses.add(FrontendMessage::ColorPickerStartHistoryTransaction);
|
||||||
self.gradient_hue_direction = gradient_hue_direction;
|
self.gradient_hue_direction = gradient_hue_direction;
|
||||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||||
value: FillChoice::Gradient(GradientRamp {
|
value: FillChoice::Gradient(GradientRamp::from(gradient).with_settings(self.gradient_settings())),
|
||||||
gradient_spread: self.gradient_spread,
|
});
|
||||||
gradient_space: self.gradient_space,
|
self.send_layouts(responses);
|
||||||
gradient_cyclic: self.gradient_cyclic,
|
}
|
||||||
gradient_hue_direction,
|
ColorPickerMessage::SetGradientInterpolation { gradient_interpolation } => {
|
||||||
..GradientRamp::from(gradient)
|
let Some(gradient) = &self.gradient else { return };
|
||||||
}),
|
responses.add(FrontendMessage::ColorPickerStartHistoryTransaction);
|
||||||
|
self.gradient_interpolation = gradient_interpolation;
|
||||||
|
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||||
|
value: FillChoice::Gradient(GradientRamp::from(gradient).with_settings(self.gradient_settings())),
|
||||||
});
|
});
|
||||||
self.send_layouts(responses);
|
self.send_layouts(responses);
|
||||||
}
|
}
|
||||||
@@ -315,6 +309,17 @@ impl ColorPickerMessageHandler {
|
|||||||
self.old_is_none = is_none;
|
self.old_is_none = is_none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The whole-ramp settings the picker is currently editing with, bundled for sampling and for emitting ramps.
|
||||||
|
fn gradient_settings(&self) -> GradientSettings {
|
||||||
|
GradientSettings {
|
||||||
|
spread: self.gradient_spread,
|
||||||
|
cyclic: self.gradient_cyclic,
|
||||||
|
space: self.gradient_space,
|
||||||
|
hue_direction: self.gradient_hue_direction,
|
||||||
|
interpolation: self.gradient_interpolation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn snapshot_old(&mut self) {
|
fn snapshot_old(&mut self) {
|
||||||
self.old_hue = self.hue;
|
self.old_hue = self.hue;
|
||||||
self.old_saturation = self.saturation;
|
self.old_saturation = self.saturation;
|
||||||
@@ -350,14 +355,9 @@ impl ColorPickerMessageHandler {
|
|||||||
&& (active_index as usize) < gradient.len()
|
&& (active_index as usize) < gradient.len()
|
||||||
{
|
{
|
||||||
gradient.set_color(active_index as usize, color);
|
gradient.set_color(active_index as usize, color);
|
||||||
|
let ramp = GradientRamp::from(&*gradient);
|
||||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||||
value: FillChoice::Gradient(GradientRamp {
|
value: FillChoice::Gradient(ramp.with_settings(self.gradient_settings())),
|
||||||
gradient_spread: self.gradient_spread,
|
|
||||||
gradient_space: self.gradient_space,
|
|
||||||
gradient_cyclic: self.gradient_cyclic,
|
|
||||||
gradient_hue_direction: self.gradient_hue_direction,
|
|
||||||
..GradientRamp::from(&*gradient)
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||||
@@ -414,7 +414,7 @@ impl ColorPickerMessageHandler {
|
|||||||
gradient.set_midpoint(index as usize, position.clamp(MIN_MIDPOINT, MAX_MIDPOINT));
|
gradient.set_midpoint(index as usize, position.clamp(MIN_MIDPOINT, MAX_MIDPOINT));
|
||||||
}
|
}
|
||||||
SpectrumInputUpdate::InsertMarker { position } => {
|
SpectrumInputUpdate::InsertMarker { position } => {
|
||||||
let new_index = gradient.insert_stop(position, self.gradient_cyclic, self.gradient_space, self.gradient_hue_direction);
|
let new_index = gradient.insert_stop(position, self.gradient_settings());
|
||||||
self.active_marker_index = Some(new_index as u32);
|
self.active_marker_index = Some(new_index as u32);
|
||||||
self.active_marker_is_midpoint = false;
|
self.active_marker_is_midpoint = false;
|
||||||
if let Some(color) = gradient.color(new_index) {
|
if let Some(color) = gradient.color(new_index) {
|
||||||
@@ -487,13 +487,7 @@ impl ColorPickerMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||||
value: FillChoice::Gradient(GradientRamp {
|
value: FillChoice::Gradient(GradientRamp::from(&gradient).with_settings(self.gradient_settings())),
|
||||||
gradient_spread: self.gradient_spread,
|
|
||||||
gradient_space: self.gradient_space,
|
|
||||||
gradient_cyclic: self.gradient_cyclic,
|
|
||||||
gradient_hue_direction: self.gradient_hue_direction,
|
|
||||||
..GradientRamp::from(&gradient)
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
self.gradient = Some(gradient);
|
self.gradient = Some(gradient);
|
||||||
self.send_layouts(responses);
|
self.send_layouts(responses);
|
||||||
@@ -527,6 +521,7 @@ impl ColorPickerMessageHandler {
|
|||||||
.track_space(self.gradient_space)
|
.track_space(self.gradient_space)
|
||||||
.track_cyclic(self.gradient_cyclic)
|
.track_cyclic(self.gradient_cyclic)
|
||||||
.track_hue_direction(self.gradient_hue_direction)
|
.track_hue_direction(self.gradient_hue_direction)
|
||||||
|
.track_interpolation(self.gradient_interpolation)
|
||||||
.markers(markers)
|
.markers(markers)
|
||||||
.active_marker_index(self.active_marker_index)
|
.active_marker_index(self.active_marker_index)
|
||||||
.active_marker_is_midpoint(self.active_marker_is_midpoint)
|
.active_marker_is_midpoint(self.active_marker_is_midpoint)
|
||||||
@@ -726,6 +721,25 @@ impl ColorPickerMessageHandler {
|
|||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gradient interpolation (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());
|
||||||
|
|
||||||
|
groups.push(LayoutGroup::row(vec![
|
||||||
|
TextLabel::new("Intrp.")
|
||||||
|
.tooltip_label("Gradient Interpolation")
|
||||||
|
.tooltip_description(INTERPOLATION_DESCRIPTION)
|
||||||
|
.widget_instance(),
|
||||||
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
|
DropdownInput::new(entries)
|
||||||
|
.selected_index(Some(self.gradient_interpolation as u32))
|
||||||
|
.disabled(self.disabled)
|
||||||
|
.tooltip_label("Gradient Interpolation")
|
||||||
|
.tooltip_description(INTERPOLATION_DESCRIPTION)
|
||||||
|
.widget_instance(),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
// Gradient 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() {
|
if self.gradient.is_some() {
|
||||||
let entries = MenuListEntry::sections_from_choice_type(|gradient_space| ColorPickerMessage::SetGradientSpace { gradient_space }.into());
|
let entries = MenuListEntry::sections_from_choice_type(|gradient_space| ColorPickerMessage::SetGradientSpace { gradient_space }.into());
|
||||||
@@ -733,7 +747,12 @@ impl ColorPickerMessageHandler {
|
|||||||
groups.push(LayoutGroup::row(vec![
|
groups.push(LayoutGroup::row(vec![
|
||||||
TextLabel::new("Space").tooltip_label("Gradient Space").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(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
DropdownInput::new(entries).selected_index(Some(self.gradient_space as u32)).disabled(self.disabled).widget_instance(),
|
DropdownInput::new(entries)
|
||||||
|
.selected_index(Some(self.gradient_space as u32))
|
||||||
|
.disabled(self.disabled)
|
||||||
|
.tooltip_label("Gradient Space")
|
||||||
|
.tooltip_description(SPACE_DESCRIPTION)
|
||||||
|
.widget_instance(),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,6 +769,8 @@ impl ColorPickerMessageHandler {
|
|||||||
DropdownInput::new(entries)
|
DropdownInput::new(entries)
|
||||||
.selected_index(Some(self.gradient_hue_direction as u32))
|
.selected_index(Some(self.gradient_hue_direction as u32))
|
||||||
.disabled(self.disabled)
|
.disabled(self.disabled)
|
||||||
|
.tooltip_label("Gradient Hue Direction")
|
||||||
|
.tooltip_description(HUE_DIRECTION_DESCRIPTION)
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
@@ -811,6 +832,7 @@ const ENDS_DESCRIPTION: &str = "The method for how the gradient continues beyond
|
|||||||
const CYCLIC_DESCRIPTION: &str = "Treats the stops as a cycle, interpolating from the last stop back around to the first.";
|
const CYCLIC_DESCRIPTION: &str = "Treats the stops as a cycle, interpolating from the last stop back around to the first.";
|
||||||
const SPACE_DESCRIPTION: &str = "The color space where stops interpolate toward 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.";
|
const HUE_DIRECTION_DESCRIPTION: &str = "Which way around the hue wheel the stops interpolate.";
|
||||||
|
const INTERPOLATION_DESCRIPTION: &str = "The path the stops interpolate along, deciding whether the gradient jumps, turns corners, or flows smoothly through them.";
|
||||||
|
|
||||||
/// The popover's background color as sRGB gamma-encoded channels (the `--color-2-mildblack` design token, `#222`).
|
/// 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.
|
/// Used by the comparison swatch's outline computation to brighten the inset border for colors close to this background.
|
||||||
|
|||||||
@@ -531,13 +531,19 @@ fn populate_computed_display_fields(layout: &mut Layout) {
|
|||||||
color_input.chosen_gradient = color_input.value.to_css_background_image();
|
color_input.chosen_gradient = color_input.value.to_css_background_image();
|
||||||
}
|
}
|
||||||
Widget::SpectrumInput(spectrum_input) => {
|
Widget::SpectrumInput(spectrum_input) => {
|
||||||
spectrum_input.track_css = spectrum_input
|
// The track strip spans exactly 0 to 1, which no spread affects, so the widget carries no spread of its own
|
||||||
.track
|
let settings = graphene_std::vector::style::GradientSettings {
|
||||||
.to_css_linear_gradient(spectrum_input.track_cyclic, spectrum_input.track_space, spectrum_input.track_hue_direction);
|
spread: Default::default(),
|
||||||
|
cyclic: spectrum_input.track_cyclic,
|
||||||
|
space: spectrum_input.track_space,
|
||||||
|
hue_direction: spectrum_input.track_hue_direction,
|
||||||
|
interpolation: spectrum_input.track_interpolation,
|
||||||
|
};
|
||||||
|
spectrum_input.track_css = spectrum_input.track.to_css_linear_gradient(settings);
|
||||||
// The end caps sample the track's boundary colors, which a cyclic wrap makes the wrapped interval's boundary-crossing color rather than the outermost stops'
|
// The end caps sample the track's boundary colors, which a cyclic wrap makes the wrapped interval's boundary-crossing color rather than the outermost stops'
|
||||||
let track_gradient = graphene_std::vector::style::Gradient::from(&spectrum_input.track);
|
let track_evaluator = graphene_std::vector::style::Gradient::from(&spectrum_input.track).evaluator(settings);
|
||||||
let cap = |t: f64| {
|
let cap = |t: f64| {
|
||||||
let color = track_gradient.evaluate(t, Default::default(), spectrum_input.track_cyclic, spectrum_input.track_space, spectrum_input.track_hue_direction);
|
let color = track_evaluator.evaluate(t);
|
||||||
SRGBA8::from(color).to_css_hex()
|
SRGBA8::from(color).to_css_hex()
|
||||||
};
|
};
|
||||||
spectrum_input.track_start_css = cap(0.);
|
spectrum_input.track_start_css = cap(0.);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use derivative::*;
|
|||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::color::SRGBA8;
|
use graphene_std::color::SRGBA8;
|
||||||
use graphene_std::transform::ReferencePoint;
|
use graphene_std::transform::ReferencePoint;
|
||||||
use graphene_std::vector::style::{FillChoice, GradientHueDirection, GradientSpace, GradientStops};
|
use graphene_std::vector::style::{FillChoice, GradientHueDirection, GradientInterpolation, GradientSpace, GradientStops};
|
||||||
use graphite_proc_macros::WidgetBuilder;
|
use graphite_proc_macros::WidgetBuilder;
|
||||||
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
@@ -595,6 +595,9 @@ pub struct SpectrumInput {
|
|||||||
/// The hue direction the track's stops interpolate with in a polar space, used to compute `track_css`. Not sent to the frontend.
|
/// 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)]
|
#[serde(skip)]
|
||||||
pub track_hue_direction: GradientHueDirection,
|
pub track_hue_direction: GradientHueDirection,
|
||||||
|
/// The path the track's stops interpolate along, used to compute `track_css` and by the frontend to suppress the midpoint diamonds when stepped.
|
||||||
|
#[serde(rename = "trackInterpolation")]
|
||||||
|
pub track_interpolation: GradientInterpolation,
|
||||||
/// CSS `linear-gradient(...)` string for the track strip's `background-image`. Auto-populated from `track` at layout-send time.
|
/// CSS `linear-gradient(...)` string for the track strip's `background-image`. Auto-populated from `track` at layout-send time.
|
||||||
#[serde(rename = "trackCSS")]
|
#[serde(rename = "trackCSS")]
|
||||||
#[widget_builder(skip)]
|
#[widget_builder(skip)]
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ use graphene_std::transform::{ReferencePoint, ScaleType};
|
|||||||
use graphene_std::vector::misc::{
|
use graphene_std::vector::misc::{
|
||||||
ArcType, BooleanOperation, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType,
|
ArcType, BooleanOperation, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType,
|
||||||
};
|
};
|
||||||
use graphene_std::vector::style::{DashPattern, FillChoice, GradientForm, GradientHueDirection, GradientRamp, GradientSpace, GradientSpread, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphene_std::vector::style::{
|
||||||
|
DashPattern, FillChoice, GradientForm, GradientHueDirection, GradientInterpolation, GradientRamp, GradientSpace, GradientSpread, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin,
|
||||||
|
};
|
||||||
use graphene_std::vector::{QRCodeErrorCorrectionLevel, Vector};
|
use graphene_std::vector::{QRCodeErrorCorrectionLevel, Vector};
|
||||||
use graphene_std::{Artboard, Color, Graphic};
|
use graphene_std::{Artboard, Color, Graphic};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
@@ -220,6 +222,7 @@ fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'st
|
|||||||
List<GradientSpread>,
|
List<GradientSpread>,
|
||||||
List<GradientSpace>,
|
List<GradientSpace>,
|
||||||
List<GradientHueDirection>,
|
List<GradientHueDirection>,
|
||||||
|
List<GradientInterpolation>,
|
||||||
List<DashPattern>,
|
List<DashPattern>,
|
||||||
List<BoxCorners>,
|
List<BoxCorners>,
|
||||||
List<StrokeJoin>,
|
List<StrokeJoin>,
|
||||||
@@ -1014,6 +1017,7 @@ impl_table_item_layout_for_choice_enum!(
|
|||||||
GradientSpread,
|
GradientSpread,
|
||||||
GradientSpace,
|
GradientSpace,
|
||||||
GradientHueDirection,
|
GradientHueDirection,
|
||||||
|
GradientInterpolation,
|
||||||
StrokeJoin,
|
StrokeJoin,
|
||||||
StrokeAlign,
|
StrokeAlign,
|
||||||
StrokeCap,
|
StrokeCap,
|
||||||
@@ -1228,6 +1232,7 @@ macro_rules! known_item_types {
|
|||||||
GradientSpread,
|
GradientSpread,
|
||||||
GradientSpace,
|
GradientSpace,
|
||||||
GradientHueDirection,
|
GradientHueDirection,
|
||||||
|
GradientInterpolation,
|
||||||
StrokeJoin,
|
StrokeJoin,
|
||||||
StrokeAlign,
|
StrokeAlign,
|
||||||
StrokeCap,
|
StrokeCap,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use graphene_std::raster::BlendMode;
|
|||||||
use graphene_std::raster_types::Image;
|
use graphene_std::raster_types::Image;
|
||||||
use graphene_std::subpath::Subpath;
|
use graphene_std::subpath::Subpath;
|
||||||
use graphene_std::text::{Font, TypesettingConfig};
|
use graphene_std::text::{Font, TypesettingConfig};
|
||||||
use graphene_std::vector::style::{GradientForm, GradientHueDirection, GradientSpace, GradientSpread, Stroke};
|
use graphene_std::vector::style::{GradientForm, GradientHueDirection, GradientInterpolation, GradientSettings, GradientSpace, GradientSpread, Stroke};
|
||||||
use graphene_std::vector::{Gradient, PointId, VectorModificationType};
|
use graphene_std::vector::{Gradient, PointId, VectorModificationType};
|
||||||
|
|
||||||
#[impl_message(Message, DocumentMessage, GraphOperation)]
|
#[impl_message(Message, DocumentMessage, GraphOperation)]
|
||||||
@@ -29,10 +29,7 @@ pub enum GraphOperationMessage {
|
|||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
gradient: Gradient,
|
gradient: Gradient,
|
||||||
gradient_form: GradientForm,
|
gradient_form: GradientForm,
|
||||||
gradient_spread: GradientSpread,
|
gradient_settings: GradientSettings,
|
||||||
gradient_space: GradientSpace,
|
|
||||||
gradient_cyclic: bool,
|
|
||||||
gradient_hue_direction: GradientHueDirection,
|
|
||||||
transform: DAffine2,
|
transform: DAffine2,
|
||||||
},
|
},
|
||||||
BlendingFillSet {
|
BlendingFillSet {
|
||||||
@@ -76,6 +73,10 @@ pub enum GraphOperationMessage {
|
|||||||
layer: LayerNodeIdentifier,
|
layer: LayerNodeIdentifier,
|
||||||
gradient_hue_direction: GradientHueDirection,
|
gradient_hue_direction: GradientHueDirection,
|
||||||
},
|
},
|
||||||
|
GradientInterpolationSet {
|
||||||
|
layer: LayerNodeIdentifier,
|
||||||
|
gradient_interpolation: GradientInterpolation,
|
||||||
|
},
|
||||||
OpacitySet {
|
OpacitySet {
|
||||||
layer: LayerNodeIdentifier,
|
layer: LayerNodeIdentifier,
|
||||||
opacity: f64,
|
opacity: f64,
|
||||||
|
|||||||
+46
-14
@@ -14,7 +14,7 @@ use graph_craft::document::{NodeId, NodeInput};
|
|||||||
use graphene_std::list::List;
|
use graphene_std::list::List;
|
||||||
use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
|
use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
|
||||||
use graphene_std::text::{Font, TypesettingConfig};
|
use graphene_std::text::{Font, TypesettingConfig};
|
||||||
use graphene_std::vector::style::{Gradient, GradientForm, GradientSpace, GradientSpread, GradientStop, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphene_std::vector::style::{Gradient, GradientForm, GradientSettings, GradientSpace, GradientSpread, GradientStop, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
use graphene_std::{Artboard, Color};
|
use graphene_std::{Artboard, Color};
|
||||||
|
|
||||||
#[derive(ExtractField)]
|
#[derive(ExtractField)]
|
||||||
@@ -49,14 +49,11 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
|||||||
layer,
|
layer,
|
||||||
gradient,
|
gradient,
|
||||||
gradient_form,
|
gradient_form,
|
||||||
gradient_spread,
|
gradient_settings,
|
||||||
gradient_space,
|
|
||||||
gradient_cyclic,
|
|
||||||
gradient_hue_direction,
|
|
||||||
transform,
|
transform,
|
||||||
} => {
|
} => {
|
||||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
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_space, gradient_cyclic, gradient_hue_direction, transform);
|
modify_inputs.fill_gradient_set(gradient, gradient_form, gradient_settings, transform);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GraphOperationMessage::BlendingFillSet { layer, fill } => {
|
GraphOperationMessage::BlendingFillSet { layer, fill } => {
|
||||||
@@ -109,6 +106,11 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
|||||||
modify_inputs.gradient_hue_direction_set(gradient_hue_direction);
|
modify_inputs.gradient_hue_direction_set(gradient_hue_direction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
GraphOperationMessage::GradientInterpolationSet { layer, gradient_interpolation } => {
|
||||||
|
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||||
|
modify_inputs.gradient_interpolation_set(gradient_interpolation);
|
||||||
|
}
|
||||||
|
}
|
||||||
GraphOperationMessage::OpacitySet { layer, opacity } => {
|
GraphOperationMessage::OpacitySet { layer, opacity } => {
|
||||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||||
modify_inputs.opacity_set(opacity);
|
modify_inputs.opacity_set(opacity);
|
||||||
@@ -728,7 +730,7 @@ fn parse_hex_stop_color(hex: &str, opacity: f32) -> Option<Color> {
|
|||||||
let r = u8::from_str_radix(&hex[0..2], 16).ok()? as f32 / 255.;
|
let r = u8::from_str_radix(&hex[0..2], 16).ok()? as f32 / 255.;
|
||||||
let g = u8::from_str_radix(&hex[2..4], 16).ok()? as f32 / 255.;
|
let g = u8::from_str_radix(&hex[2..4], 16).ok()? as f32 / 255.;
|
||||||
let b = u8::from_str_radix(&hex[4..6], 16).ok()? as f32 / 255.;
|
let b = u8::from_str_radix(&hex[4..6], 16).ok()? as f32 / 255.;
|
||||||
Some(Color::from_rgbaf32_unchecked(r, g, b, opacity))
|
Some(Color::from_gamma_srgb_channels(r, g, b, opacity))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Import a usvg node as the root of an SVG import operation.
|
/// Import a usvg node as the root of an SVG import operation.
|
||||||
@@ -981,10 +983,13 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, g
|
|||||||
Gradient::new(stops)
|
Gradient::new(stops)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
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
|
// SVG interpolates between stops in gamma sRGB unless `color-interpolation` opts into linearRGB, carried explicitly rather than as the linear default
|
||||||
let gradient_space = gradient_info.spaces.get(linear.id()).copied().unwrap_or(GradientSpace::RgbGamma);
|
let settings = GradientSettings {
|
||||||
modify_inputs.fill_gradient_set(gradient, gradient_form, gradient_spread, gradient_space, false, Default::default(), transform);
|
spread: convert_gradient_spread(linear.spread_method()),
|
||||||
|
space: gradient_info.spaces.get(linear.id()).copied().unwrap_or(GradientSpace::RgbGamma),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
modify_inputs.fill_gradient_set(gradient, gradient_form, settings, transform);
|
||||||
}
|
}
|
||||||
usvg::Paint::RadialGradient(radial) => {
|
usvg::Paint::RadialGradient(radial) => {
|
||||||
let gradient_transform = usvg_transform(radial.transform());
|
let gradient_transform = usvg_transform(radial.transform());
|
||||||
@@ -1007,10 +1012,12 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, g
|
|||||||
Gradient::new(stops)
|
Gradient::new(stops)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let gradient_spread = convert_gradient_spread(radial.spread_method());
|
let settings = GradientSettings {
|
||||||
let gradient_space = gradient_info.spaces.get(radial.id()).copied().unwrap_or(GradientSpace::RgbGamma);
|
spread: convert_gradient_spread(radial.spread_method()),
|
||||||
|
space: gradient_info.spaces.get(radial.id()).copied().unwrap_or(GradientSpace::RgbGamma),
|
||||||
modify_inputs.fill_gradient_set(gradient, gradient_form, gradient_spread, gradient_space, false, Default::default(), transform);
|
..Default::default()
|
||||||
|
};
|
||||||
|
modify_inputs.fill_gradient_set(gradient, gradient_form, settings, transform);
|
||||||
}
|
}
|
||||||
usvg::Paint::Pattern(_) => warn!("SVG patterns are not currently supported"),
|
usvg::Paint::Pattern(_) => warn!("SVG patterns are not currently supported"),
|
||||||
};
|
};
|
||||||
@@ -1042,6 +1049,31 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn graphite_stop_extraction_keeps_real_stops_and_linearizes_their_colors() {
|
||||||
|
let svg = r##"<svg xmlns="http://www.w3.org/2000/svg" xmlns:graphite="https://graphite.art">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="ramp">
|
||||||
|
<stop stop-color="#000000" graphite:midpoint="0.3" />
|
||||||
|
<stop offset="0.25" stop-color="#404040" />
|
||||||
|
<stop offset="0.5" stop-color="#808080" stop-opacity="0.5" graphite:midpoint="0.5" />
|
||||||
|
<stop offset="1" stop-color="#ffffff" graphite:midpoint="0.5" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>"##;
|
||||||
|
|
||||||
|
let stops = extract_graphite_gradient_stops(svg);
|
||||||
|
let gradient = stops.get("ramp").expect("the tagged gradient should be recovered");
|
||||||
|
|
||||||
|
// The untagged stop is baked approximation residue, not authored data
|
||||||
|
assert_eq!(gradient.len(), 3, "only stops tagged with a midpoint should survive");
|
||||||
|
assert_eq!(gradient.positions(false), vec![0., 0.5, 1.]);
|
||||||
|
assert_eq!(gradient.midpoints(), vec![0.3, 0.5, 0.5]);
|
||||||
|
|
||||||
|
// Hex stop bytes are gamma-encoded, so the recovered color must lift them to linear light
|
||||||
|
assert_eq!(gradient.color(1), Some(Color::from_gamma_srgb_channels(128. / 255., 128. / 255., 128. / 255., 0.5)));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn color_interpolation_reads_style_blocks_with_selector_specificity() {
|
fn color_interpolation_reads_style_blocks_with_selector_specificity() {
|
||||||
let svg = r##"<svg xmlns="http://www.w3.org/2000/svg">
|
let svg = r##"<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use graphene_std::raster::BlendMode;
|
|||||||
use graphene_std::raster_types::Image;
|
use graphene_std::raster_types::Image;
|
||||||
use graphene_std::subpath::Subpath;
|
use graphene_std::subpath::Subpath;
|
||||||
use graphene_std::text::{Font, TypesettingConfig};
|
use graphene_std::text::{Font, TypesettingConfig};
|
||||||
use graphene_std::vector::style::{GradientForm, GradientHueDirection, GradientSpace, GradientSpread, Stroke};
|
use graphene_std::vector::style::{GradientForm, GradientHueDirection, GradientInterpolation, GradientSettings, GradientSpace, GradientSpread, Stroke};
|
||||||
use graphene_std::vector::{Gradient, GradientRamp, PointId, Vector, VectorModification, VectorModificationType};
|
use graphene_std::vector::{Gradient, GradientRamp, PointId, Vector, VectorModification, VectorModificationType};
|
||||||
use graphene_std::{Artboard, Color, Graphic};
|
use graphene_std::{Artboard, Color, Graphic};
|
||||||
|
|
||||||
@@ -433,30 +433,13 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
self.set_input_with_refresh(input_connector, NodeInput::value(fill_value, false), false);
|
self.set_input_with_refresh(input_connector, NodeInput::value(fill_value, false), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
pub fn fill_gradient_set(&mut self, gradient: Gradient, gradient_form: GradientForm, settings: GradientSettings, transform: DAffine2) {
|
||||||
pub fn fill_gradient_set(
|
|
||||||
&mut self,
|
|
||||||
gradient: Gradient,
|
|
||||||
gradient_form: GradientForm,
|
|
||||||
gradient_spread: GradientSpread,
|
|
||||||
gradient_space: GradientSpace,
|
|
||||||
gradient_cyclic: bool,
|
|
||||||
gradient_hue_direction: GradientHueDirection,
|
|
||||||
transform: DAffine2,
|
|
||||||
) {
|
|
||||||
let Some(fill_node_id) = self.existing_proto_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else {
|
let Some(fill_node_id) = self.existing_proto_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let backup_input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupGradientInput);
|
let backup_input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupGradientInput);
|
||||||
|
|
||||||
let ramp = GradientRamp::from(gradient);
|
let ramp = GradientRamp::from(gradient).with_settings(settings);
|
||||||
let ramp = GradientRamp {
|
|
||||||
gradient_spread,
|
|
||||||
gradient_space,
|
|
||||||
gradient_cyclic,
|
|
||||||
gradient_hue_direction,
|
|
||||||
..ramp
|
|
||||||
};
|
|
||||||
self.set_input_with_refresh(backup_input_connector, NodeInput::value(TaggedValue::GradientRamp(ramp.clone()), false), true);
|
self.set_input_with_refresh(backup_input_connector, NodeInput::value(TaggedValue::GradientRamp(ramp.clone()), false), true);
|
||||||
|
|
||||||
// Skip the rerender on all but the last input so the whole update triggers a single graph run
|
// Skip the rerender on all but the last input so the whole update triggers a single graph run
|
||||||
@@ -795,7 +778,9 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::GradientRamp(ramp), false), false);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::GradientRamp(ramp), false), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the cyclic wrap flag on the chain's gradient value, which is where the ramp carries it.
|
/// Set the cyclic wrap flag on the chain's gradient value, which is where the ramp carries it. This holds the existing
|
||||||
|
/// stops in place by reading their positions under the old flag, so batch it before any stops write rather than after one,
|
||||||
|
/// or it would reinterpret incoming stops already authored under the new flag.
|
||||||
pub fn gradient_cyclic_set(&mut self, gradient_cyclic: bool) {
|
pub fn gradient_cyclic_set(&mut self, gradient_cyclic: bool) {
|
||||||
let Some(output_layer) = self.get_output_layer() else { return };
|
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 {
|
let Some(gradient_value_id) = get_upstream_gradient_value_node_id(output_layer, self.network_interface) else {
|
||||||
@@ -803,7 +788,20 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
};
|
};
|
||||||
let Some(ramp) = self.gradient_value_ramp(gradient_value_id) else { return };
|
let Some(ramp) = self.gradient_value_ramp(gradient_value_id) else { return };
|
||||||
|
|
||||||
let ramp = GradientRamp { gradient_cyclic, ..ramp };
|
let ramp = ramp.with_cyclic(gradient_cyclic);
|
||||||
|
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 interpolation on the chain's gradient value, which is where the ramp carries it.
|
||||||
|
pub fn gradient_interpolation_set(&mut self, gradient_interpolation: GradientInterpolation) {
|
||||||
|
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 input_connector = InputConnector::node(gradient_value_id, graphene_std::math_nodes::gradient_value::GradientInput);
|
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);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::GradientRamp(ramp), false), false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ use graphene_std::vector::misc::{
|
|||||||
ArcType, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType,
|
ArcType, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType,
|
||||||
};
|
};
|
||||||
use graphene_std::vector::style::{
|
use graphene_std::vector::style::{
|
||||||
FillChoice, Gradient, GradientForm, GradientHueDirection, GradientRamp, GradientSpace, GradientSpread, GradientStops, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin,
|
FillChoice, Gradient, GradientForm, GradientHueDirection, GradientInterpolation, GradientRamp, GradientSettings, GradientSpace, GradientSpread, GradientStops, PaintOrder, StrokeAlign, StrokeCap,
|
||||||
build_transform_with_y_preservation,
|
StrokeJoin, build_transform_with_y_preservation,
|
||||||
};
|
};
|
||||||
use graphene_std::vector::{QRCodeErrorCorrectionLevel, VectorModification};
|
use graphene_std::vector::{QRCodeErrorCorrectionLevel, VectorModification};
|
||||||
use graphene_std::{NodeParameter, ParameterRef};
|
use graphene_std::{NodeParameter, ParameterRef};
|
||||||
@@ -297,6 +297,7 @@ pub(crate) fn property_from_type(
|
|||||||
Some(x) if id_is::<GradientSpread>(x) => enum_choice::<GradientSpread>().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::<GradientSpace>(x) => enum_choice::<GradientSpace>().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::<GradientHueDirection>(x) => enum_choice::<GradientHueDirection>().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::<RealTimeMode>(x) => enum_choice::<RealTimeMode>().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::<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(),
|
Some(x) if id_is::<RedGreenBlueAlpha>(x) => enum_choice::<RedGreenBlueAlpha>().for_socket(default_info).property_row(),
|
||||||
@@ -2408,10 +2409,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
Gradient {
|
Gradient {
|
||||||
gradient: Gradient,
|
gradient: Gradient,
|
||||||
gradient_form: GradientForm,
|
gradient_form: GradientForm,
|
||||||
gradient_spread: GradientSpread,
|
settings: GradientSettings,
|
||||||
gradient_space: GradientSpace,
|
|
||||||
gradient_cyclic: bool,
|
|
||||||
gradient_hue_direction: GradientHueDirection,
|
|
||||||
transform: DAffine2,
|
transform: DAffine2,
|
||||||
/// Whether the transform input holds a plain value (so the "Reverse Direction" button may write to it) rather than a wire.
|
/// 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,
|
transform_is_value: bool,
|
||||||
@@ -2441,10 +2439,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
Some(gradient) => ResolvedFill::Gradient {
|
Some(gradient) => ResolvedFill::Gradient {
|
||||||
gradient: gradient.stops,
|
gradient: gradient.stops,
|
||||||
gradient_form: gradient.gradient_form,
|
gradient_form: gradient.gradient_form,
|
||||||
gradient_spread: gradient.gradient_spread,
|
settings: gradient.settings,
|
||||||
gradient_space: gradient.gradient_space,
|
|
||||||
gradient_cyclic: gradient.gradient_cyclic,
|
|
||||||
gradient_hue_direction: gradient.gradient_hue_direction,
|
|
||||||
transform: gradient.transform,
|
transform: gradient.transform,
|
||||||
transform_is_value: gradient.transform_is_value,
|
transform_is_value: gradient.transform_is_value,
|
||||||
},
|
},
|
||||||
@@ -2472,33 +2467,15 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
};
|
};
|
||||||
|
|
||||||
match &fill {
|
match &fill {
|
||||||
ResolvedFill::Gradient {
|
ResolvedFill::Gradient { gradient: stops, settings, .. } => {
|
||||||
gradient: stops,
|
|
||||||
gradient_spread,
|
|
||||||
gradient_space,
|
|
||||||
gradient_cyclic,
|
|
||||||
gradient_hue_direction,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let stops = stops.clone();
|
let stops = stops.clone();
|
||||||
let gradient_spread = *gradient_spread;
|
let settings = *settings;
|
||||||
let gradient_space = *gradient_space;
|
|
||||||
let gradient_cyclic = *gradient_cyclic;
|
|
||||||
let gradient_hue_direction = *gradient_hue_direction;
|
|
||||||
|
|
||||||
let reverse_button = IconButton::new("Reverse", 24)
|
let reverse_button = IconButton::new("Reverse", 24)
|
||||||
.tooltip_label("Reverse Stops")
|
.tooltip_label("Reverse Stops")
|
||||||
.tooltip_description("Reverse the gradient color stops.")
|
.tooltip_description("Reverse the gradient color stops.")
|
||||||
.on_update(update_value(
|
.on_update(update_value(
|
||||||
move |_| {
|
move |_| TaggedValue::GradientRamp(GradientRamp::from(stops.reversed(settings.cyclic)).with_settings(settings)),
|
||||||
TaggedValue::GradientRamp(GradientRamp {
|
|
||||||
gradient_spread,
|
|
||||||
gradient_space,
|
|
||||||
gradient_cyclic,
|
|
||||||
gradient_hue_direction,
|
|
||||||
..GradientRamp::from(stops.reversed(gradient_cyclic))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
node_id,
|
node_id,
|
||||||
FillInput,
|
FillInput,
|
||||||
))
|
))
|
||||||
@@ -2517,20 +2494,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
FillChoice::<SRGBA8>::None
|
FillChoice::<SRGBA8>::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ResolvedFill::Gradient {
|
ResolvedFill::Gradient { gradient: stops, settings, .. } => FillChoice::<SRGBA8>::Gradient(GradientRamp::from(stops).with_settings(*settings)),
|
||||||
gradient: stops,
|
|
||||||
gradient_spread,
|
|
||||||
gradient_space,
|
|
||||||
gradient_cyclic,
|
|
||||||
gradient_hue_direction,
|
|
||||||
..
|
|
||||||
} => FillChoice::<SRGBA8>::Gradient(GradientRamp {
|
|
||||||
gradient_spread: *gradient_spread,
|
|
||||||
gradient_space: *gradient_space,
|
|
||||||
gradient_cyclic: *gradient_cyclic,
|
|
||||||
gradient_hue_direction: *gradient_hue_direction,
|
|
||||||
..GradientRamp::from(stops)
|
|
||||||
}),
|
|
||||||
ResolvedFill::Other => FillChoice::<SRGBA8>::None,
|
ResolvedFill::Other => FillChoice::<SRGBA8>::None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -280,10 +280,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
|||||||
node: graphene_std::math_nodes::gradient_form::IDENTIFIER,
|
node: graphene_std::math_nodes::gradient_form::IDENTIFIER,
|
||||||
aliases: &["math_nodes::GradientTypeNode"],
|
aliases: &["math_nodes::GradientTypeNode"],
|
||||||
},
|
},
|
||||||
NodeReplacement {
|
|
||||||
node: graphene_std::math_nodes::gradient_space::IDENTIFIER,
|
|
||||||
aliases: &["math_nodes::GradientInterpolationNode"],
|
|
||||||
},
|
|
||||||
NodeReplacement {
|
NodeReplacement {
|
||||||
node: graphene_std::math_nodes::gradient_spread::IDENTIFIER,
|
node: graphene_std::math_nodes::gradient_spread::IDENTIFIER,
|
||||||
aliases: &["math_nodes::SpreadMethodNode"],
|
aliases: &["math_nodes::SpreadMethodNode"],
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use graphene_std::subpath::Subpath;
|
|||||||
use graphene_std::text::{Font, TypesettingConfig};
|
use graphene_std::text::{Font, TypesettingConfig};
|
||||||
use graphene_std::vector::misc::ManipulatorPointId;
|
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::style::{FillChoice, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin, initial_gradient_transform_for_bounding_box};
|
||||||
use graphene_std::vector::{Gradient, GradientForm, GradientHueDirection, GradientRamp, GradientSpace, GradientSpread, PointId, SegmentId, VectorModificationType};
|
use graphene_std::vector::{Gradient, GradientForm, GradientRamp, GradientSettings, PointId, SegmentId, VectorModificationType};
|
||||||
use graphene_std::{NodeParameter, ParameterRef};
|
use graphene_std::{NodeParameter, ParameterRef};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
@@ -389,7 +389,7 @@ pub fn get_fill_input_node_id(layer: LayerNodeIdentifier, network_interface: &No
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The ramp held by the 'Gradient Value' node feeding a layer's chain, which carries the whole-ramp settings.
|
/// The ramp held by the 'Gradient Value' node feeding a layer's chain, which carries the whole-ramp settings.
|
||||||
fn get_chain_source_gradient_ramp<'a>(layer: LayerNodeIdentifier, network_interface: &'a NodeNetworkInterface) -> Option<&'a GradientRamp> {
|
fn get_chain_source_gradient_ramp(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<&GradientRamp> {
|
||||||
let gradient_value_node = network_interface.document_network().nodes.get(&get_upstream_gradient_value_node_id(layer, network_interface)?)?;
|
let gradient_value_node = network_interface.document_network().nodes.get(&get_upstream_gradient_value_node_id(layer, network_interface)?)?;
|
||||||
let TaggedValue::GradientRamp(ramp) = gradient_value_node.input(graphene_std::math_nodes::gradient_value::GradientInput)?.as_value()? else {
|
let TaggedValue::GradientRamp(ramp) = gradient_value_node.input(graphene_std::math_nodes::gradient_value::GradientInput)?.as_value()? else {
|
||||||
return None;
|
return None;
|
||||||
@@ -397,24 +397,9 @@ fn get_chain_source_gradient_ramp<'a>(layer: LayerNodeIdentifier, network_interf
|
|||||||
Some(ramp)
|
Some(ramp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The spread baked into the 'Gradient Value' node feeding a layer's chain.
|
/// The whole-ramp settings baked into the 'Gradient Value' node feeding a layer's chain.
|
||||||
pub fn get_chain_source_gradient_spread(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<GradientSpread> {
|
pub fn get_chain_source_gradient_settings(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<GradientSettings> {
|
||||||
Some(get_chain_source_gradient_ramp(layer, network_interface)?.gradient_spread)
|
Some(get_chain_source_gradient_ramp(layer, network_interface)?.into())
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 cyclic wrap flag baked into the 'Gradient Value' node feeding a layer's chain.
|
|
||||||
pub fn get_chain_source_gradient_cyclic(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<bool> {
|
|
||||||
Some(get_chain_source_gradient_ramp(layer, network_interface)?.gradient_cyclic)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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.
|
/// Get the gradient stops of a layer, if any.
|
||||||
@@ -771,10 +756,7 @@ pub fn set_stroke_weight_for_selected_layers(weight: f64, document: &DocumentMes
|
|||||||
pub struct FillNodeGradient {
|
pub struct FillNodeGradient {
|
||||||
pub stops: Gradient,
|
pub stops: Gradient,
|
||||||
pub gradient_form: GradientForm,
|
pub gradient_form: GradientForm,
|
||||||
pub gradient_spread: GradientSpread,
|
pub settings: GradientSettings,
|
||||||
pub gradient_space: GradientSpace,
|
|
||||||
pub gradient_cyclic: bool,
|
|
||||||
pub gradient_hue_direction: GradientHueDirection,
|
|
||||||
pub transform: DAffine2,
|
pub transform: DAffine2,
|
||||||
/// Whether the transform input holds a plain value (so it may be written to) rather than a wire.
|
/// Whether the transform input holds a plain value (so it may be written to) rather than a wire.
|
||||||
pub transform_is_value: bool,
|
pub transform_is_value: bool,
|
||||||
@@ -787,10 +769,7 @@ pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOn
|
|||||||
let TaggedValue::GradientRamp(ramp) = fill_node.input(fill::FillInput)?.as_value()? else {
|
let TaggedValue::GradientRamp(ramp) = fill_node.input(fill::FillInput)?.as_value()? else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
let gradient_spread = ramp.gradient_spread;
|
let settings = GradientSettings::from(ramp);
|
||||||
let gradient_space = ramp.gradient_space;
|
|
||||||
let gradient_cyclic = ramp.gradient_cyclic;
|
|
||||||
let gradient_hue_direction = ramp.gradient_hue_direction;
|
|
||||||
let stops = Gradient::from(ramp);
|
let stops = Gradient::from(ramp);
|
||||||
let gradient_form = match fill_node.input(fill::GradientFormInput).and_then(|input| input.as_value()) {
|
let gradient_form = match fill_node.input(fill::GradientFormInput).and_then(|input| input.as_value()) {
|
||||||
Some(&TaggedValue::GradientForm(value)) => value,
|
Some(&TaggedValue::GradientForm(value)) => value,
|
||||||
@@ -807,10 +786,7 @@ pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOn
|
|||||||
Some(FillNodeGradient {
|
Some(FillNodeGradient {
|
||||||
stops,
|
stops,
|
||||||
gradient_form,
|
gradient_form,
|
||||||
gradient_spread,
|
settings,
|
||||||
gradient_space,
|
|
||||||
gradient_cyclic,
|
|
||||||
gradient_hue_direction,
|
|
||||||
transform,
|
transform,
|
||||||
transform_is_value: transform_input.is_some(),
|
transform_is_value: transform_input.is_some(),
|
||||||
})
|
})
|
||||||
@@ -959,10 +935,7 @@ pub fn set_fill_for_selected_layers(fill_choice: FillChoice, document: &Document
|
|||||||
layer,
|
layer,
|
||||||
gradient: Gradient::from(ramp),
|
gradient: Gradient::from(ramp),
|
||||||
gradient_form,
|
gradient_form,
|
||||||
gradient_spread: ramp.gradient_spread,
|
gradient_settings: ramp.into(),
|
||||||
gradient_space: ramp.gradient_space,
|
|
||||||
gradient_cyclic: ramp.gradient_cyclic,
|
|
||||||
gradient_hue_direction: ramp.gradient_hue_direction,
|
|
||||||
transform,
|
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::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface};
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::{
|
use crate::messages::tool::common_functionality::graph_modification_utils::{
|
||||||
self, NodeGraphLayer, get_chain_source_gradient_cyclic, get_chain_source_gradient_hue_direction, get_chain_source_gradient_space, get_chain_source_gradient_spread,
|
self, NodeGraphLayer, get_chain_source_gradient_settings, get_fill_node_id_with_direct_fill_input, get_gradient_stops, get_upstream_gradient_value_node_id, gradient_chain_target_input,
|
||||||
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,
|
replaceable_paint_chain, reverse_direction_tooltip_description,
|
||||||
};
|
};
|
||||||
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
||||||
use glam::DMat2;
|
use glam::DMat2;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graphene_std::color::SRGBA8;
|
use graphene_std::color::SRGBA8;
|
||||||
use graphene_std::raster::color::Color;
|
use graphene_std::raster::color::Color;
|
||||||
use graphene_std::vector::style::{FillChoice, Gradient, GradientForm, GradientHueDirection, GradientRamp, GradientSpace, GradientSpread, GradientStop, build_transform_with_y_preservation};
|
use graphene_std::vector::style::{FillChoice, Gradient, GradientForm, GradientInterpolation, GradientRamp, GradientSettings, GradientStop, build_transform_with_y_preservation};
|
||||||
|
|
||||||
#[derive(Default, ExtractField)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct GradientTool {
|
pub struct GradientTool {
|
||||||
@@ -29,10 +29,7 @@ pub struct GradientTool {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct GradientOptions {
|
pub struct GradientOptions {
|
||||||
gradient_form: GradientForm,
|
gradient_form: GradientForm,
|
||||||
gradient_spread: GradientSpread,
|
settings: GradientSettings,
|
||||||
gradient_space: GradientSpace,
|
|
||||||
gradient_cyclic: bool,
|
|
||||||
gradient_hue_direction: GradientHueDirection,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Gradient)]
|
#[impl_message(Message, ToolMessage, Gradient)]
|
||||||
@@ -104,7 +101,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
|
|||||||
context,
|
context,
|
||||||
responses,
|
responses,
|
||||||
|_| true,
|
|_| true,
|
||||||
|(gradient, appearance)| *gradient = gradient.reversed(appearance.gradient_cyclic),
|
|(gradient, appearance)| *gradient = gradient.reversed(appearance.settings.cyclic),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
GradientOptionsUpdate::ReverseDirection => apply_gradient_update(
|
GradientOptionsUpdate::ReverseDirection => apply_gradient_update(
|
||||||
@@ -146,20 +143,8 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
|
|||||||
}
|
}
|
||||||
ToolMessage::Gradient(GradientToolMessage::UpdateRamp { ramp }) => {
|
ToolMessage::Gradient(GradientToolMessage::UpdateRamp { ramp }) => {
|
||||||
let ramp = GradientRamp::from(&ramp);
|
let ramp = GradientRamp::from(&ramp);
|
||||||
self.options.gradient_spread = ramp.gradient_spread;
|
self.options.settings = GradientSettings::from(&ramp);
|
||||||
self.options.gradient_space = ramp.gradient_space;
|
apply_stops_update(&mut self.data, context, responses, Gradient::from(&ramp), self.options.settings);
|
||||||
self.options.gradient_cyclic = ramp.gradient_cyclic;
|
|
||||||
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_cyclic,
|
|
||||||
ramp.gradient_hue_direction,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
ToolMessage::Gradient(GradientToolMessage::CloseStopColorPicker) => {
|
ToolMessage::Gradient(GradientToolMessage::CloseStopColorPicker) => {
|
||||||
if self.data.color_picker_transaction_open {
|
if self.data.color_picker_transaction_open {
|
||||||
@@ -193,20 +178,8 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
|
|||||||
self.options.gradient_form = appearance.gradient_form;
|
self.options.gradient_form = appearance.gradient_form;
|
||||||
needs_refresh = true;
|
needs_refresh = true;
|
||||||
}
|
}
|
||||||
if self.options.gradient_spread != appearance.gradient_spread {
|
if self.options.settings != appearance.settings {
|
||||||
self.options.gradient_spread = appearance.gradient_spread;
|
self.options.settings = appearance.settings;
|
||||||
needs_refresh = true;
|
|
||||||
}
|
|
||||||
if self.options.gradient_space != appearance.gradient_space {
|
|
||||||
self.options.gradient_space = appearance.gradient_space;
|
|
||||||
needs_refresh = true;
|
|
||||||
}
|
|
||||||
if self.options.gradient_cyclic != appearance.gradient_cyclic {
|
|
||||||
self.options.gradient_cyclic = appearance.gradient_cyclic;
|
|
||||||
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;
|
needs_refresh = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,23 +260,17 @@ impl LayoutHolder for GradientTool {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
let stops_widget = ColorInput::new(FillChoice::Gradient(GradientRamp {
|
let stops_widget = ColorInput::new(FillChoice::Gradient(GradientRamp::from(&stops_value).with_settings(self.options.settings)))
|
||||||
gradient_spread: self.options.gradient_spread,
|
.allow_none(false)
|
||||||
gradient_space: self.options.gradient_space,
|
.narrow(true)
|
||||||
gradient_cyclic: self.options.gradient_cyclic,
|
.tooltip_label("Gradient Stops")
|
||||||
gradient_hue_direction: self.options.gradient_hue_direction,
|
.tooltip_description("Edit the gradient's color stops.")
|
||||||
..GradientRamp::from(&stops_value)
|
.on_update(|input: &ColorInput| {
|
||||||
}))
|
let ramp = input.value.as_gradient().cloned().unwrap_or_default();
|
||||||
.allow_none(false)
|
GradientToolMessage::UpdateRamp { ramp }.into()
|
||||||
.narrow(true)
|
})
|
||||||
.tooltip_label("Gradient Stops")
|
.on_commit(|_| DocumentMessage::AddTransaction.into())
|
||||||
.tooltip_description("Edit the gradient's color stops.")
|
.widget_instance();
|
||||||
.on_update(|input: &ColorInput| {
|
|
||||||
let ramp = input.value.as_gradient().cloned().unwrap_or_default();
|
|
||||||
GradientToolMessage::UpdateRamp { ramp }.into()
|
|
||||||
})
|
|
||||||
.on_commit(|_| DocumentMessage::AddTransaction.into())
|
|
||||||
.widget_instance();
|
|
||||||
|
|
||||||
let reverse_stops = IconButton::new("Reverse", 24)
|
let reverse_stops = IconButton::new("Reverse", 24)
|
||||||
.tooltip_label("Reverse Stops")
|
.tooltip_label("Reverse Stops")
|
||||||
@@ -391,10 +358,7 @@ fn resolve_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkI
|
|||||||
gradient.stops,
|
gradient.stops,
|
||||||
GradientAppearance {
|
GradientAppearance {
|
||||||
gradient_form: gradient.gradient_form,
|
gradient_form: gradient.gradient_form,
|
||||||
gradient_spread: gradient.gradient_spread,
|
settings: gradient.settings,
|
||||||
gradient_space: gradient.gradient_space,
|
|
||||||
gradient_cyclic: gradient.gradient_cyclic,
|
|
||||||
gradient_hue_direction: gradient.gradient_hue_direction,
|
|
||||||
transform: gradient.transform,
|
transform: gradient.transform,
|
||||||
},
|
},
|
||||||
GradientSource::Direct,
|
GradientSource::Direct,
|
||||||
@@ -413,10 +377,7 @@ fn resolve_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkI
|
|||||||
struct GradientAppearance {
|
struct GradientAppearance {
|
||||||
transform: DAffine2,
|
transform: DAffine2,
|
||||||
gradient_form: GradientForm,
|
gradient_form: GradientForm,
|
||||||
gradient_spread: GradientSpread,
|
settings: GradientSettings,
|
||||||
gradient_space: GradientSpace,
|
|
||||||
gradient_cyclic: bool,
|
|
||||||
gradient_hue_direction: GradientHueDirection,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the gradient transform, form, and spread by walking the chain feeding the layer.
|
/// Resolve the gradient transform, form, and spread by walking the chain feeding the layer.
|
||||||
@@ -456,10 +417,7 @@ fn read_gradient_chain_state(layer: LayerNodeIdentifier, network_interface: &Nod
|
|||||||
GradientAppearance {
|
GradientAppearance {
|
||||||
transform: composed_transform,
|
transform: composed_transform,
|
||||||
gradient_form: gradient_form.unwrap_or_default(),
|
gradient_form: gradient_form.unwrap_or_default(),
|
||||||
gradient_spread: get_chain_source_gradient_spread(layer, network_interface).unwrap_or_default(),
|
settings: get_chain_source_gradient_settings(layer, network_interface).unwrap_or_default(),
|
||||||
gradient_space: get_chain_source_gradient_space(layer, network_interface).unwrap_or_default(),
|
|
||||||
gradient_cyclic: get_chain_source_gradient_cyclic(layer, network_interface).unwrap_or_default(),
|
|
||||||
gradient_hue_direction: get_chain_source_gradient_hue_direction(layer, network_interface).unwrap_or_default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,7 +462,13 @@ fn wrapped_interval_span(gradient: &Gradient) -> (f64, f64) {
|
|||||||
|
|
||||||
/// The gradient's visible midpoint diamonds as `(owning stop index, position along the gradient line)`, omitting intervals
|
/// The gradient's visible midpoint diamonds as `(owning stop index, position along the gradient line)`, omitting intervals
|
||||||
/// whose stops are too closely packed. A cyclic gradient's wrapped interval adds a final diamond owned by the last stop.
|
/// whose stops are too closely packed. A cyclic gradient's wrapped interval adds a final diamond owned by the last stop.
|
||||||
fn midpoint_diamonds(gradient: &Gradient, gradient_cyclic: bool, viewport_line_length: f64) -> Vec<(usize, f64)> {
|
fn midpoint_diamonds(gradient: &Gradient, settings: GradientSettings, viewport_line_length: f64) -> Vec<(usize, f64)> {
|
||||||
|
// A stepped ramp jumps at its stops, so no midpoint has anything to bias
|
||||||
|
if settings.interpolation == GradientInterpolation::Stepped {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let gradient_cyclic = settings.cyclic;
|
||||||
let mut diamonds = Vec::with_capacity(gradient.len());
|
let mut diamonds = Vec::with_capacity(gradient.len());
|
||||||
|
|
||||||
for index in 0..gradient.len().saturating_sub(1) {
|
for index in 0..gradient.len().saturating_sub(1) {
|
||||||
@@ -585,7 +549,8 @@ struct SelectedGradient {
|
|||||||
is_gradient_chain: bool,
|
is_gradient_chain: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_insertion(start: DVec2, end: DVec2, stops: &Gradient, gradient_cyclic: bool, mouse: DVec2) -> Option<f64> {
|
fn calculate_insertion(start: DVec2, end: DVec2, stops: &Gradient, settings: GradientSettings, mouse: DVec2) -> Option<f64> {
|
||||||
|
let gradient_cyclic = settings.cyclic;
|
||||||
let distance = (end - start).angle_to(mouse - start).sin() * (mouse - start).length();
|
let distance = (end - start).angle_to(mouse - start).sin() * (mouse - start).length();
|
||||||
let projection = ((end - start).angle_to(mouse - start)).cos() * start.distance(mouse) / start.distance(end);
|
let projection = ((end - start).angle_to(mouse - start)).cos() * start.distance(mouse) / start.distance(end);
|
||||||
|
|
||||||
@@ -602,7 +567,7 @@ fn calculate_insertion(start: DVec2, end: DVec2, stops: &Gradient, gradient_cycl
|
|||||||
|
|
||||||
// Don't insert when clicking near a (currently visible) midpoint diamond
|
// Don't insert when clicking near a (currently visible) midpoint diamond
|
||||||
let line_length = start.distance(end);
|
let line_length = start.distance(end);
|
||||||
for (_, midpoint_position) in midpoint_diamonds(stops, gradient_cyclic, line_length) {
|
for (_, midpoint_position) in midpoint_diamonds(stops, settings, line_length) {
|
||||||
let midpoint_viewport = start.lerp(end, midpoint_position);
|
let midpoint_viewport = start.lerp(end, midpoint_position);
|
||||||
if midpoint_viewport.distance_squared(mouse) < GRADIENT_MIDPOINT_DIAMOND_RADIUS.powi(2) {
|
if midpoint_viewport.distance_squared(mouse) < GRADIENT_MIDPOINT_DIAMOND_RADIUS.powi(2) {
|
||||||
return None;
|
return None;
|
||||||
@@ -770,7 +735,7 @@ impl SelectedGradient {
|
|||||||
let min_gap = GRADIENT_STOP_MIN_VIEWPORT_GAP / line_length;
|
let min_gap = GRADIENT_STOP_MIN_VIEWPORT_GAP / line_length;
|
||||||
let last_index = self.gradient.len() - 1;
|
let last_index = self.gradient.len() - 1;
|
||||||
|
|
||||||
let gradient_cyclic = self.appearance.gradient_cyclic;
|
let gradient_cyclic = self.appearance.settings.cyclic;
|
||||||
let has_other_stop_at_zero = stop != 0 && !self.gradient.is_empty() && self.gradient.position(0, gradient_cyclic).abs() < f64::EPSILON * 1000.;
|
let has_other_stop_at_zero = stop != 0 && !self.gradient.is_empty() && self.gradient.position(0, gradient_cyclic).abs() < f64::EPSILON * 1000.;
|
||||||
let has_other_stop_at_one = stop != last_index && !self.gradient.is_empty() && (1. - self.gradient.position(last_index, gradient_cyclic)).abs() < f64::EPSILON * 1000.;
|
let has_other_stop_at_one = stop != last_index && !self.gradient.is_empty() && (1. - self.gradient.position(last_index, gradient_cyclic)).abs() < f64::EPSILON * 1000.;
|
||||||
|
|
||||||
@@ -830,7 +795,7 @@ impl SelectedGradient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert to a midpoint ratio within the interval owned by the dragged diamond's stop
|
// Convert to a midpoint ratio within the interval owned by the dragged diamond's stop
|
||||||
if let Some(midpoint_ratio) = midpoint_ratio_at(&self.gradient, midpoint_index, self.appearance.gradient_cyclic, full_pos) {
|
if let Some(midpoint_ratio) = midpoint_ratio_at(&self.gradient, midpoint_index, self.appearance.settings.cyclic, full_pos) {
|
||||||
self.gradient.set_midpoint(midpoint_index, midpoint_ratio);
|
self.gradient.set_midpoint(midpoint_index, midpoint_ratio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -848,10 +813,7 @@ impl SelectedGradient {
|
|||||||
layer,
|
layer,
|
||||||
gradient: self.gradient.clone(),
|
gradient: self.gradient.clone(),
|
||||||
gradient_form: self.appearance.gradient_form,
|
gradient_form: self.appearance.gradient_form,
|
||||||
gradient_spread: self.appearance.gradient_spread,
|
gradient_settings: self.appearance.settings,
|
||||||
gradient_space: self.appearance.gradient_space,
|
|
||||||
gradient_cyclic: self.appearance.gradient_cyclic,
|
|
||||||
gradient_hue_direction: self.appearance.gradient_hue_direction,
|
|
||||||
transform: self.appearance.transform,
|
transform: self.appearance.transform,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -869,10 +831,14 @@ impl SelectedGradient {
|
|||||||
|
|
||||||
/// Send the per-attribute graph operations that mirror the in-memory `Gradient` onto the chain feeding the layer.
|
/// Send the per-attribute graph operations that mirror the in-memory `Gradient` onto the chain feeding the layer.
|
||||||
fn dispatch_gradient_chain_writes(layer: LayerNodeIdentifier, gradient: &Gradient, appearance: GradientAppearance, responses: &mut VecDeque<Message>) {
|
fn dispatch_gradient_chain_writes(layer: LayerNodeIdentifier, gradient: &Gradient, appearance: GradientAppearance, responses: &mut VecDeque<Message>) {
|
||||||
|
responses.add(GraphOperationMessage::GradientCyclicSet {
|
||||||
|
layer,
|
||||||
|
gradient_cyclic: appearance.settings.cyclic,
|
||||||
|
});
|
||||||
responses.add(GraphOperationMessage::GradientStopsSet { layer, stops: gradient.clone() });
|
responses.add(GraphOperationMessage::GradientStopsSet { layer, stops: gradient.clone() });
|
||||||
responses.add(GraphOperationMessage::GradientPositionsSet {
|
responses.add(GraphOperationMessage::GradientPositionsSet {
|
||||||
layer,
|
layer,
|
||||||
positions: gradient.nondefault_positions(appearance.gradient_cyclic).unwrap_or_default(),
|
positions: gradient.nondefault_positions(appearance.settings.cyclic).unwrap_or_default(),
|
||||||
});
|
});
|
||||||
responses.add(GraphOperationMessage::GradientMidpointsSet {
|
responses.add(GraphOperationMessage::GradientMidpointsSet {
|
||||||
layer,
|
layer,
|
||||||
@@ -888,19 +854,19 @@ fn dispatch_gradient_chain_writes(layer: LayerNodeIdentifier, gradient: &Gradien
|
|||||||
});
|
});
|
||||||
responses.add(GraphOperationMessage::GradientSpreadSet {
|
responses.add(GraphOperationMessage::GradientSpreadSet {
|
||||||
layer,
|
layer,
|
||||||
gradient_spread: appearance.gradient_spread,
|
gradient_spread: appearance.settings.spread,
|
||||||
});
|
});
|
||||||
responses.add(GraphOperationMessage::GradientSpaceSet {
|
responses.add(GraphOperationMessage::GradientSpaceSet {
|
||||||
layer,
|
layer,
|
||||||
gradient_space: appearance.gradient_space,
|
gradient_space: appearance.settings.space,
|
||||||
});
|
|
||||||
responses.add(GraphOperationMessage::GradientCyclicSet {
|
|
||||||
layer,
|
|
||||||
gradient_cyclic: appearance.gradient_cyclic,
|
|
||||||
});
|
});
|
||||||
responses.add(GraphOperationMessage::GradientHueDirectionSet {
|
responses.add(GraphOperationMessage::GradientHueDirectionSet {
|
||||||
layer,
|
layer,
|
||||||
gradient_hue_direction: appearance.gradient_hue_direction,
|
gradient_hue_direction: appearance.settings.hue_direction,
|
||||||
|
});
|
||||||
|
responses.add(GraphOperationMessage::GradientInterpolationSet {
|
||||||
|
layer,
|
||||||
|
gradient_interpolation: appearance.settings.interpolation,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1005,7 +971,8 @@ impl Fsm for GradientToolFsmState {
|
|||||||
let end_hex = gradient.color(gradient.len().saturating_sub(1)).map(color_to_hex).unwrap_or(String::from(COLOR_OVERLAY_BLUE));
|
let end_hex = gradient.color(gradient.len().saturating_sub(1)).map(color_to_hex).unwrap_or(String::from(COLOR_OVERLAY_BLUE));
|
||||||
|
|
||||||
// Check if the first/last stops are at position ~0/~1 (rendered as the endpoint dots rather than as separate stops)
|
// Check if the first/last stops are at position ~0/~1 (rendered as the endpoint dots rather than as separate stops)
|
||||||
let gradient_cyclic = appearance.gradient_cyclic;
|
let settings = appearance.settings;
|
||||||
|
let gradient_cyclic = settings.cyclic;
|
||||||
let first_at_start = !gradient.is_empty() && gradient.position(0, gradient_cyclic).abs() < f64::EPSILON * 1000.;
|
let first_at_start = !gradient.is_empty() && gradient.position(0, gradient_cyclic).abs() < f64::EPSILON * 1000.;
|
||||||
let last_at_end = !gradient.is_empty() && (1. - gradient.position(gradient.len() - 1, gradient_cyclic)).abs() < f64::EPSILON * 1000.;
|
let last_at_end = !gradient.is_empty() && (1. - gradient.position(gradient.len() - 1, gradient_cyclic)).abs() < f64::EPSILON * 1000.;
|
||||||
|
|
||||||
@@ -1100,7 +1067,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
let line_angle = (end - start).to_angle();
|
let line_angle = (end - start).to_angle();
|
||||||
let line_length = start.distance(end);
|
let line_length = start.distance(end);
|
||||||
let midpoint_tolerance = GRADIENT_MIDPOINT_DIAMOND_RADIUS.powi(2);
|
let midpoint_tolerance = GRADIENT_MIDPOINT_DIAMOND_RADIUS.powi(2);
|
||||||
for (index, midpoint_position) in midpoint_diamonds(gradient, gradient_cyclic, line_length) {
|
for (index, midpoint_position) in midpoint_diamonds(gradient, settings, line_length) {
|
||||||
let midpoint_viewport = start.lerp(end, midpoint_position);
|
let midpoint_viewport = start.lerp(end, midpoint_position);
|
||||||
|
|
||||||
let emphasis = if dragging == Some(GradientDragTarget::Midpoint(index)) {
|
let emphasis = if dragging == Some(GradientDragTarget::Midpoint(index)) {
|
||||||
@@ -1114,7 +1081,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !matches!(self, GradientToolFsmState::Drawing { .. })
|
if !matches!(self, GradientToolFsmState::Drawing { .. })
|
||||||
&& calculate_insertion(start, end, gradient, gradient_cyclic, mouse).is_some()
|
&& calculate_insertion(start, end, gradient, settings, mouse).is_some()
|
||||||
&& let Some(dir) = (end - start).try_normalize()
|
&& let Some(dir) = (end - start).try_normalize()
|
||||||
{
|
{
|
||||||
let perp = dir.perp();
|
let perp = dir.perp();
|
||||||
@@ -1163,7 +1130,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
let gradient = &selected_gradient.gradient;
|
let gradient = &selected_gradient.gradient;
|
||||||
if stop_index < gradient.len() {
|
if stop_index < gradient.len() {
|
||||||
let color = gradient.color(stop_index).unwrap_or(Color::BLACK);
|
let color = gradient.color(stop_index).unwrap_or(Color::BLACK);
|
||||||
let position = gradient.position(stop_index, selected_gradient.appearance.gradient_cyclic);
|
let position = gradient.position(stop_index, selected_gradient.appearance.settings.cyclic);
|
||||||
let start = transform.transform_point2(DVec2::ZERO);
|
let start = transform.transform_point2(DVec2::ZERO);
|
||||||
let end = transform.transform_point2(DVec2::X);
|
let end = transform.transform_point2(DVec2::X);
|
||||||
let position = start.lerp(end, position).into();
|
let position = start.lerp(end, position).into();
|
||||||
@@ -1203,7 +1170,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
GradientDragTarget::Start | GradientDragTarget::End | GradientDragTarget::Stop(_) => {
|
GradientDragTarget::Start | GradientDragTarget::End | GradientDragTarget::Stop(_) => {
|
||||||
// Find the stop index from the drag target
|
// Find the stop index from the drag target
|
||||||
let gradient = &selected_gradient.gradient;
|
let gradient = &selected_gradient.gradient;
|
||||||
let gradient_cyclic = selected_gradient.appearance.gradient_cyclic;
|
let gradient_cyclic = selected_gradient.appearance.settings.cyclic;
|
||||||
let stop_index = match selected_gradient.dragging {
|
let stop_index = match selected_gradient.dragging {
|
||||||
GradientDragTarget::Stop(i) => Some(i),
|
GradientDragTarget::Stop(i) => Some(i),
|
||||||
GradientDragTarget::Start => (0..gradient.len()).position(|i| gradient.position(i, gradient_cyclic).abs() < f64::EPSILON * 1000.),
|
GradientDragTarget::Start => (0..gradient.len()).position(|i| gradient.position(i, gradient_cyclic).abs() < f64::EPSILON * 1000.),
|
||||||
@@ -1219,7 +1186,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
tool_data.color_picker_transaction_open = false;
|
tool_data.color_picker_transaction_open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let stop_pos = selected_gradient.gradient.position(stop_index, selected_gradient.appearance.gradient_cyclic);
|
let stop_pos = selected_gradient.gradient.position(stop_index, selected_gradient.appearance.settings.cyclic);
|
||||||
let (start, end) = selected_gradient.viewport_handle_positions();
|
let (start, end) = selected_gradient.viewport_handle_positions();
|
||||||
let viewport_pos = start.lerp(end, stop_pos);
|
let viewport_pos = start.lerp(end, stop_pos);
|
||||||
let position = viewport_pos.into();
|
let position = viewport_pos.into();
|
||||||
@@ -1262,7 +1229,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
match selected_gradient.dragging {
|
match selected_gradient.dragging {
|
||||||
GradientDragTarget::Start => {
|
GradientDragTarget::Start => {
|
||||||
// Only delete if there's a real color stop at position ~0 (not the endpoint of the line which isn't itself a color stop)
|
// Only delete if there's a real color stop at position ~0 (not the endpoint of the line which isn't itself a color stop)
|
||||||
if !selected_gradient.gradient.is_empty() && selected_gradient.gradient.position(0, selected_gradient.appearance.gradient_cyclic).abs() < f64::EPSILON * 1000. {
|
if !selected_gradient.gradient.is_empty() && selected_gradient.gradient.position(0, selected_gradient.appearance.settings.cyclic).abs() < f64::EPSILON * 1000. {
|
||||||
selected_gradient.gradient.remove(0);
|
selected_gradient.gradient.remove(0);
|
||||||
} else {
|
} else {
|
||||||
responses.add(DocumentMessage::AbortTransaction);
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
@@ -1272,7 +1239,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
GradientDragTarget::End => {
|
GradientDragTarget::End => {
|
||||||
// Only delete if there's a real color stop at position ~1 (not the endpoint of the line which isn't itself a color stop)
|
// Only delete if there's a real color stop at position ~1 (not the endpoint of the line which isn't itself a color stop)
|
||||||
if !selected_gradient.gradient.is_empty()
|
if !selected_gradient.gradient.is_empty()
|
||||||
&& (1. - selected_gradient.gradient.position(selected_gradient.gradient.len() - 1, selected_gradient.appearance.gradient_cyclic)).abs() < f64::EPSILON * 1000.
|
&& (1. - selected_gradient.gradient.position(selected_gradient.gradient.len() - 1, selected_gradient.appearance.settings.cyclic)).abs() < f64::EPSILON * 1000.
|
||||||
{
|
{
|
||||||
let _ = selected_gradient.gradient.pop();
|
let _ = selected_gradient.gradient.pop();
|
||||||
} else {
|
} else {
|
||||||
@@ -1314,7 +1281,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find the minimum and maximum positions
|
// Find the minimum and maximum positions
|
||||||
let positions = selected_gradient.gradient.positions(selected_gradient.appearance.gradient_cyclic);
|
let positions = selected_gradient.gradient.positions(selected_gradient.appearance.settings.cyclic);
|
||||||
let min_position = positions.iter().copied().reduce(f64::min).expect("No min");
|
let min_position = positions.iter().copied().reduce(f64::min).expect("No min");
|
||||||
let max_position = positions.iter().copied().reduce(f64::max).expect("No max");
|
let max_position = positions.iter().copied().reduce(f64::max).expect("No max");
|
||||||
|
|
||||||
@@ -1350,14 +1317,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
// If click is on the line then insert point
|
// If click is on the line then insert point
|
||||||
if distance < (SELECTION_THRESHOLD * 2.) {
|
if distance < (SELECTION_THRESHOLD * 2.) {
|
||||||
// Try and insert the new stop
|
// Try and insert the new stop
|
||||||
if let Some(index) = insert_stop_at_point(
|
if let Some(index) = insert_stop_at_point(&mut gradient, mouse, unit_to_viewport, appearance.settings) {
|
||||||
&mut gradient,
|
|
||||||
mouse,
|
|
||||||
unit_to_viewport,
|
|
||||||
appearance.gradient_cyclic,
|
|
||||||
appearance.gradient_space,
|
|
||||||
appearance.gradient_hue_direction,
|
|
||||||
) {
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
let mut selected_gradient = SelectedGradient::new(gradient, appearance, source, layer, document);
|
let mut selected_gradient = SelectedGradient::new(gradient, appearance, source, layer, document);
|
||||||
@@ -1410,7 +1370,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
if drag_hint.is_none() {
|
if drag_hint.is_none() {
|
||||||
let line_length = start.distance(end);
|
let line_length = start.distance(end);
|
||||||
let midpoint_tolerance = GRADIENT_MIDPOINT_DIAMOND_RADIUS.powi(2);
|
let midpoint_tolerance = GRADIENT_MIDPOINT_DIAMOND_RADIUS.powi(2);
|
||||||
for (index, midpoint_position) in midpoint_diamonds(&gradient, appearance.gradient_cyclic, line_length) {
|
for (index, midpoint_position) in midpoint_diamonds(&gradient, appearance.settings, line_length) {
|
||||||
let midpoint_viewport = start.lerp(end, midpoint_position);
|
let midpoint_viewport = start.lerp(end, midpoint_position);
|
||||||
|
|
||||||
if midpoint_viewport.distance_squared(mouse) < midpoint_tolerance {
|
if midpoint_viewport.distance_squared(mouse) < midpoint_tolerance {
|
||||||
@@ -1437,14 +1397,14 @@ impl Fsm for GradientToolFsmState {
|
|||||||
if drag_hint.is_none() {
|
if drag_hint.is_none() {
|
||||||
let mut best: Option<(f64, usize)> = None;
|
let mut best: Option<(f64, usize)> = None;
|
||||||
for index in 0..gradient.len() {
|
for index in 0..gradient.len() {
|
||||||
let pos = start.lerp(end, gradient.position(index, appearance.gradient_cyclic));
|
let pos = start.lerp(end, gradient.position(index, appearance.settings.cyclic));
|
||||||
let dist_sq = pos.distance_squared(mouse);
|
let dist_sq = pos.distance_squared(mouse);
|
||||||
if dist_sq < tolerance && best.as_ref().is_none_or(|&(best_dist, _)| dist_sq < best_dist) {
|
if dist_sq < tolerance && best.as_ref().is_none_or(|&(best_dist, _)| dist_sq < best_dist) {
|
||||||
best = Some((dist_sq, index));
|
best = Some((dist_sq, index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some((_, index)) = best {
|
if let Some((_, index)) = best {
|
||||||
let stop_position = gradient.position(index, appearance.gradient_cyclic);
|
let stop_position = gradient.position(index, appearance.settings.cyclic);
|
||||||
// Stops at position 0 or 1 are locked endpoints: dragging moves the
|
// Stops at position 0 or 1 are locked endpoints: dragging moves the
|
||||||
// gradient line endpoint geometry (start/end) instead of stop position
|
// gradient line endpoint geometry (start/end) instead of stop position
|
||||||
let drag_target = if stop_position.abs() < f64::EPSILON * 1000. {
|
let drag_target = if stop_position.abs() < f64::EPSILON * 1000. {
|
||||||
@@ -1499,14 +1459,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
|
|
||||||
if distance.abs() < SEGMENT_INSERTION_DISTANCE && (0. ..=1.).contains(&projection) {
|
if distance.abs() < SEGMENT_INSERTION_DISTANCE && (0. ..=1.).contains(&projection) {
|
||||||
let mut new_gradient = gradient.clone();
|
let mut new_gradient = gradient.clone();
|
||||||
if let Some(index) = insert_stop_at_point(
|
if let Some(index) = insert_stop_at_point(&mut new_gradient, mouse, unit_to_viewport, appearance.settings) {
|
||||||
&mut new_gradient,
|
|
||||||
mouse,
|
|
||||||
unit_to_viewport,
|
|
||||||
appearance.gradient_cyclic,
|
|
||||||
appearance.gradient_space,
|
|
||||||
appearance.gradient_hue_direction,
|
|
||||||
) {
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
transaction_started = true;
|
transaction_started = true;
|
||||||
|
|
||||||
@@ -1584,10 +1537,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
GradientAppearance {
|
GradientAppearance {
|
||||||
transform: DAffine2::IDENTITY,
|
transform: DAffine2::IDENTITY,
|
||||||
gradient_form: tool_options.gradient_form,
|
gradient_form: tool_options.gradient_form,
|
||||||
gradient_spread: tool_options.gradient_spread,
|
settings: tool_options.settings,
|
||||||
gradient_space: tool_options.gradient_space,
|
|
||||||
gradient_cyclic: tool_options.gradient_cyclic,
|
|
||||||
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
|
// 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() {
|
if replaceable_paint_chain(layer, &document.network_interface).is_some() {
|
||||||
@@ -1685,9 +1635,9 @@ impl Fsm for GradientToolFsmState {
|
|||||||
|
|
||||||
// Clear the selection if we were dragging an endpoint of the gradient which isn't a stop
|
// Clear the selection if we were dragging an endpoint of the gradient which isn't a stop
|
||||||
if tool_data.selected_gradient.as_ref().is_some_and(|selected| match selected.dragging {
|
if tool_data.selected_gradient.as_ref().is_some_and(|selected| match selected.dragging {
|
||||||
GradientDragTarget::Start => selected.gradient.is_empty() || selected.gradient.position(0, selected.appearance.gradient_cyclic).abs() >= f64::EPSILON * 1000.,
|
GradientDragTarget::Start => selected.gradient.is_empty() || selected.gradient.position(0, selected.appearance.settings.cyclic).abs() >= f64::EPSILON * 1000.,
|
||||||
GradientDragTarget::End => {
|
GradientDragTarget::End => {
|
||||||
selected.gradient.is_empty() || (1. - selected.gradient.position(selected.gradient.len() - 1, selected.appearance.gradient_cyclic)).abs() >= f64::EPSILON * 1000.
|
selected.gradient.is_empty() || (1. - selected.gradient.position(selected.gradient.len() - 1, selected.appearance.settings.cyclic)).abs() >= f64::EPSILON * 1000.
|
||||||
}
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
}) {
|
}) {
|
||||||
@@ -1821,17 +1771,10 @@ impl Fsm for GradientToolFsmState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_stop_at_point(
|
fn insert_stop_at_point(gradient: &mut Gradient, point: DVec2, unit_to_viewport: DAffine2, settings: GradientSettings) -> Option<usize> {
|
||||||
gradient: &mut Gradient,
|
|
||||||
point: DVec2,
|
|
||||||
unit_to_viewport: DAffine2,
|
|
||||||
gradient_cyclic: bool,
|
|
||||||
gradient_space: GradientSpace,
|
|
||||||
gradient_hue_direction: GradientHueDirection,
|
|
||||||
) -> Option<usize> {
|
|
||||||
let (start, end) = gradient_handle_positions(unit_to_viewport);
|
let (start, end) = gradient_handle_positions(unit_to_viewport);
|
||||||
let t = ((end - start).angle_to(point - start)).cos() * start.distance(point) / start.distance(end);
|
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_cyclic, gradient_space, gradient_hue_direction))
|
(0. ..=1.).contains(&t).then(|| gradient.insert_stop(t, settings))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismiss_color_stop_color_picker(tool_data: &mut GradientToolData, responses: &mut VecDeque<Message>) {
|
fn dismiss_color_stop_color_picker(tool_data: &mut GradientToolData, responses: &mut VecDeque<Message>) {
|
||||||
@@ -1858,7 +1801,7 @@ fn detect_hover_target(mouse: DVec2, document: &DocumentMessageHandler) -> Gradi
|
|||||||
let line_length = start.distance(end);
|
let line_length = start.distance(end);
|
||||||
|
|
||||||
// Check midpoint diamonds first (smaller hit area, higher priority)
|
// Check midpoint diamonds first (smaller hit area, higher priority)
|
||||||
for (index, midpoint_position) in midpoint_diamonds(&gradient, appearance.gradient_cyclic, line_length) {
|
for (index, midpoint_position) in midpoint_diamonds(&gradient, appearance.settings, line_length) {
|
||||||
let midpoint_viewport = start.lerp(end, midpoint_position);
|
let midpoint_viewport = start.lerp(end, midpoint_position);
|
||||||
|
|
||||||
if midpoint_viewport.distance_squared(mouse) < midpoint_tolerance {
|
if midpoint_viewport.distance_squared(mouse) < midpoint_tolerance {
|
||||||
@@ -1887,7 +1830,7 @@ fn detect_hover_target(mouse: DVec2, document: &DocumentMessageHandler) -> Gradi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check insertion point on line
|
// Check insertion point on line
|
||||||
if calculate_insertion(start, end, &gradient, appearance.gradient_cyclic, mouse).is_some() {
|
if calculate_insertion(start, end, &gradient, appearance.settings, mouse).is_some() {
|
||||||
return GradientHoverTarget::InsertionPoint;
|
return GradientHoverTarget::InsertionPoint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1947,10 +1890,7 @@ fn apply_gradient_update(
|
|||||||
layer,
|
layer,
|
||||||
gradient,
|
gradient,
|
||||||
gradient_form: appearance.gradient_form,
|
gradient_form: appearance.gradient_form,
|
||||||
gradient_spread: appearance.gradient_spread,
|
gradient_settings: appearance.settings,
|
||||||
gradient_space: appearance.gradient_space,
|
|
||||||
gradient_cyclic: appearance.gradient_cyclic,
|
|
||||||
gradient_hue_direction: appearance.gradient_hue_direction,
|
|
||||||
transform: appearance.transform,
|
transform: appearance.transform,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1974,17 +1914,7 @@ fn apply_gradient_update(
|
|||||||
/// Set new gradient stops on every selected layer's gradient. Unlike `apply_gradient_update`, this doesn't open its own
|
/// Set new gradient stops on every selected layer's gradient. Unlike `apply_gradient_update`, this doesn't open its own
|
||||||
/// transaction so it can be called repeatedly during a color picker drag and have all the changes coalesced into a
|
/// transaction so it can be called repeatedly during a color picker drag and have all the changes coalesced into a
|
||||||
/// single undo entry by the surrounding 'on_commit' callback.
|
/// single undo entry by the surrounding 'on_commit' callback.
|
||||||
#[allow(clippy::too_many_arguments)]
|
fn apply_stops_update(data: &mut GradientToolData, context: &mut ToolActionMessageContext, responses: &mut VecDeque<Message>, new_gradient: Gradient, settings: GradientSettings) {
|
||||||
fn apply_stops_update(
|
|
||||||
data: &mut GradientToolData,
|
|
||||||
context: &mut ToolActionMessageContext,
|
|
||||||
responses: &mut VecDeque<Message>,
|
|
||||||
new_gradient: Gradient,
|
|
||||||
gradient_spread: GradientSpread,
|
|
||||||
gradient_space: GradientSpace,
|
|
||||||
gradient_cyclic: bool,
|
|
||||||
gradient_hue_direction: GradientHueDirection,
|
|
||||||
) {
|
|
||||||
let selected_layers: Vec<_> = context
|
let selected_layers: Vec<_> = context
|
||||||
.document
|
.document
|
||||||
.network_interface
|
.network_interface
|
||||||
@@ -1999,21 +1929,34 @@ fn apply_stops_update(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if get_upstream_gradient_value_node_id(layer, &context.document.network_interface).is_some() {
|
if get_upstream_gradient_value_node_id(layer, &context.document.network_interface).is_some() {
|
||||||
|
responses.add(GraphOperationMessage::GradientCyclicSet {
|
||||||
|
layer,
|
||||||
|
gradient_cyclic: settings.cyclic,
|
||||||
|
});
|
||||||
responses.add(GraphOperationMessage::GradientStopsSet { layer, stops: new_gradient.clone() });
|
responses.add(GraphOperationMessage::GradientStopsSet { layer, stops: new_gradient.clone() });
|
||||||
responses.add(GraphOperationMessage::GradientSpreadSet { layer, gradient_spread });
|
responses.add(GraphOperationMessage::GradientSpreadSet {
|
||||||
responses.add(GraphOperationMessage::GradientSpaceSet { layer, gradient_space });
|
layer,
|
||||||
responses.add(GraphOperationMessage::GradientCyclicSet { layer, gradient_cyclic });
|
gradient_spread: settings.spread,
|
||||||
responses.add(GraphOperationMessage::GradientHueDirectionSet { layer, gradient_hue_direction });
|
});
|
||||||
|
responses.add(GraphOperationMessage::GradientSpaceSet {
|
||||||
|
layer,
|
||||||
|
gradient_space: settings.space,
|
||||||
|
});
|
||||||
|
responses.add(GraphOperationMessage::GradientHueDirectionSet {
|
||||||
|
layer,
|
||||||
|
gradient_hue_direction: settings.hue_direction,
|
||||||
|
});
|
||||||
|
responses.add(GraphOperationMessage::GradientInterpolationSet {
|
||||||
|
layer,
|
||||||
|
gradient_interpolation: settings.interpolation,
|
||||||
|
});
|
||||||
updated_any_layer = true;
|
updated_any_layer = true;
|
||||||
} else if let Some((_gradient, appearance, _source)) = resolve_gradient(layer, &context.document.network_interface) {
|
} else if let Some((_gradient, appearance, _source)) = resolve_gradient(layer, &context.document.network_interface) {
|
||||||
responses.add(GraphOperationMessage::FillGradientSet {
|
responses.add(GraphOperationMessage::FillGradientSet {
|
||||||
layer,
|
layer,
|
||||||
gradient: new_gradient.clone(),
|
gradient: new_gradient.clone(),
|
||||||
gradient_form: appearance.gradient_form,
|
gradient_form: appearance.gradient_form,
|
||||||
gradient_spread,
|
gradient_settings: settings,
|
||||||
gradient_space,
|
|
||||||
gradient_cyclic,
|
|
||||||
gradient_hue_direction,
|
|
||||||
transform: appearance.transform,
|
transform: appearance.transform,
|
||||||
});
|
});
|
||||||
updated_any_layer = true;
|
updated_any_layer = true;
|
||||||
@@ -2022,10 +1965,7 @@ fn apply_stops_update(
|
|||||||
|
|
||||||
if let Some(selected_gradient) = &mut data.selected_gradient {
|
if let Some(selected_gradient) = &mut data.selected_gradient {
|
||||||
selected_gradient.gradient = new_gradient.clone();
|
selected_gradient.gradient = new_gradient.clone();
|
||||||
selected_gradient.appearance.gradient_spread = gradient_spread;
|
selected_gradient.appearance.settings = settings;
|
||||||
selected_gradient.appearance.gradient_space = gradient_space;
|
|
||||||
selected_gradient.appearance.gradient_cyclic = gradient_cyclic;
|
|
||||||
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.
|
// When no selected layer had a gradient to update, the user is editing the tool's default gradient instead.
|
||||||
@@ -2125,31 +2065,40 @@ mod test_gradient {
|
|||||||
use graphene_std::NodeParameter;
|
use graphene_std::NodeParameter;
|
||||||
use graphene_std::color::SRGBA8;
|
use graphene_std::color::SRGBA8;
|
||||||
use graphene_std::vector::style::{GradientForm, GradientSpread, build_transform_with_y_preservation};
|
use graphene_std::vector::style::{GradientForm, GradientSpread, build_transform_with_y_preservation};
|
||||||
|
use graphene_std::vector::style::{GradientHueDirection, GradientInterpolation, GradientSettings, GradientSpace};
|
||||||
use graphene_std::vector::{Gradient, GradientRamp, GradientStop, fill};
|
use graphene_std::vector::{Gradient, GradientRamp, GradientStop, fill};
|
||||||
|
|
||||||
/// A line long enough that no interval in these tests trips the closely-packed-stops hiding rule.
|
/// A line long enough that no interval in these tests trips the closely-packed-stops hiding rule.
|
||||||
const UNCROWDED_LINE_LENGTH: f64 = 10_000.;
|
const UNCROWDED_LINE_LENGTH: f64 = 10_000.;
|
||||||
|
|
||||||
|
const CYCLIC_SETTINGS: GradientSettings = GradientSettings {
|
||||||
|
spread: GradientSpread::Pad,
|
||||||
|
cyclic: true,
|
||||||
|
space: GradientSpace::OkLab,
|
||||||
|
hue_direction: GradientHueDirection::Shorter,
|
||||||
|
interpolation: GradientInterpolation::Linear,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cyclic_adds_a_wrap_diamond_owned_by_the_last_stop() {
|
fn cyclic_adds_a_wrap_diamond_owned_by_the_last_stop() {
|
||||||
let gradient = Gradient::from(vec![Color::BLACK, Color::WHITE]);
|
let gradient = Gradient::from(vec![Color::BLACK, Color::WHITE]);
|
||||||
|
|
||||||
// The elided cyclic stops sit at 0 and 0.5, so the wrapped interval spans the other half and centers its diamond at 0.75
|
// The elided cyclic stops sit at 0 and 0.5, so the wrapped interval spans the other half and centers its diamond at 0.75
|
||||||
assert_eq!(midpoint_diamonds(&gradient, false, UNCROWDED_LINE_LENGTH), vec![(0, 0.5)]);
|
assert_eq!(midpoint_diamonds(&gradient, GradientSettings::default(), UNCROWDED_LINE_LENGTH), vec![(0, 0.5)]);
|
||||||
assert_eq!(midpoint_diamonds(&gradient, true, UNCROWDED_LINE_LENGTH), vec![(0, 0.25), (1, 0.75)]);
|
assert_eq!(midpoint_diamonds(&gradient, CYCLIC_SETTINGS, UNCROWDED_LINE_LENGTH), vec![(0, 0.25), (1, 0.75)]);
|
||||||
|
|
||||||
// A wrapped interval crossing the boundary places its diamond on whichever side the midpoint lands
|
// A wrapped interval crossing the boundary places its diamond on whichever side the midpoint lands
|
||||||
let mut offset = Gradient::from(vec![Color::BLACK, Color::WHITE]);
|
let mut offset = Gradient::from(vec![Color::BLACK, Color::WHITE]);
|
||||||
offset.set_positions(&[0.25, 0.5]);
|
offset.set_positions(&[0.25, 0.5]);
|
||||||
offset.set_midpoints(&[0.5, 0.9]);
|
offset.set_midpoints(&[0.5, 0.9]);
|
||||||
let diamonds = midpoint_diamonds(&offset, true, UNCROWDED_LINE_LENGTH);
|
let diamonds = midpoint_diamonds(&offset, CYCLIC_SETTINGS, UNCROWDED_LINE_LENGTH);
|
||||||
assert_eq!(diamonds[1].0, 1);
|
assert_eq!(diamonds[1].0, 1);
|
||||||
assert!((diamonds[1].1 - 0.175).abs() < 1e-9, "the late wrap midpoint should land past the boundary, got {}", diamonds[1].1);
|
assert!((diamonds[1].1 - 0.175).abs() < 1e-9, "the late wrap midpoint should land past the boundary, got {}", diamonds[1].1);
|
||||||
|
|
||||||
// Stops pinned to both ends leave the wrapped interval no width, so it contributes no diamond
|
// Stops pinned to both ends leave the wrapped interval no width, so it contributes no diamond
|
||||||
let mut spanning = Gradient::from(vec![Color::BLACK, Color::WHITE]);
|
let mut spanning = Gradient::from(vec![Color::BLACK, Color::WHITE]);
|
||||||
spanning.set_positions(&[0., 1.]);
|
spanning.set_positions(&[0., 1.]);
|
||||||
assert_eq!(midpoint_diamonds(&spanning, true, UNCROWDED_LINE_LENGTH), vec![(0, 0.5)]);
|
assert_eq!(midpoint_diamonds(&spanning, CYCLIC_SETTINGS, UNCROWDED_LINE_LENGTH), vec![(0, 0.5)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2188,7 +2137,7 @@ mod test_gradient {
|
|||||||
fn new(stops: Gradient, appearance: super::GradientAppearance) -> Self {
|
fn new(stops: Gradient, appearance: super::GradientAppearance) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stops,
|
stops,
|
||||||
gradient_spread: appearance.gradient_spread,
|
gradient_spread: appearance.settings.spread,
|
||||||
transform: appearance.transform,
|
transform: appearance.transform,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2878,7 +2827,7 @@ mod test_gradient {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn spread_set_from_the_tool_lands_on_the_gradient_value_node() {
|
async fn spread_set_from_the_tool_lands_on_the_gradient_value_node() {
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_chain_source_gradient_spread;
|
use crate::messages::tool::common_functionality::graph_modification_utils::get_chain_source_gradient_settings;
|
||||||
|
|
||||||
let mut editor = EditorTestUtils::create();
|
let mut editor = EditorTestUtils::create();
|
||||||
editor.new_document().await;
|
editor.new_document().await;
|
||||||
@@ -2896,7 +2845,7 @@ mod test_gradient {
|
|||||||
// The Properties panel reads the value node's own ramp, so the spread has to be stored there
|
// The Properties panel reads the value node's own ramp, so the spread has to be stored there
|
||||||
let network_interface = &editor.active_document().network_interface;
|
let network_interface = &editor.active_document().network_interface;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_chain_source_gradient_spread(layer, network_interface),
|
get_chain_source_gradient_settings(layer, network_interface).map(|settings| settings.spread),
|
||||||
Some(GradientSpread::Reflect),
|
Some(GradientSpread::Reflect),
|
||||||
"the spread should be written into the gradient value's ramp"
|
"the spread should be written into the gradient value's ramp"
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { preventEscapeClosingParentFloatingMenu } from "/src/components/layout/FloatingMenu.svelte";
|
import { preventEscapeClosingParentFloatingMenu } from "/src/components/layout/FloatingMenu.svelte";
|
||||||
import LayoutCol from "/src/components/layout/LayoutCol.svelte";
|
import LayoutCol from "/src/components/layout/LayoutCol.svelte";
|
||||||
import LayoutRow from "/src/components/layout/LayoutRow.svelte";
|
import LayoutRow from "/src/components/layout/LayoutRow.svelte";
|
||||||
import type { SpectrumInputUpdate, SpectrumMarker } from "/wrapper/pkg/graphite_wasm_wrapper";
|
import type { GradientInterpolation, SpectrumInputUpdate, SpectrumMarker } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||||
|
|
||||||
const BUTTON_LEFT = 0;
|
const BUTTON_LEFT = 0;
|
||||||
const BUTTON_RIGHT = 2;
|
const BUTTON_RIGHT = 2;
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
export let trackStartCSS: string;
|
export let trackStartCSS: string;
|
||||||
export let trackEndCSS: string;
|
export let trackEndCSS: string;
|
||||||
export let trackCyclic = false;
|
export let trackCyclic = false;
|
||||||
|
export let trackInterpolation: GradientInterpolation = "Linear";
|
||||||
export let markers: SpectrumMarker[];
|
export let markers: SpectrumMarker[];
|
||||||
export let activeMarkerIndex: number | undefined = 0;
|
export let activeMarkerIndex: number | undefined = 0;
|
||||||
export let activeMarkerIsMidpoint = false;
|
export let activeMarkerIsMidpoint = false;
|
||||||
@@ -363,8 +364,9 @@
|
|||||||
|
|
||||||
// Map midpoint pairs to absolute track positions for rendering the diamond markers.
|
// Map midpoint pairs to absolute track positions for rendering the diamond markers.
|
||||||
// A rendered diamond's index is the index of the interval's left marker, which for the cyclic wrapped interval's diamond is the last marker.
|
// A rendered diamond's index is the index of the interval's left marker, which for the cyclic wrapped interval's diamond is the last marker.
|
||||||
function diamondPositions(markers: SpectrumMarker[], showMidpoints: boolean, trackCyclic: boolean): number[] {
|
function diamondPositions(markers: SpectrumMarker[], showMidpoints: boolean, trackCyclic: boolean, trackInterpolation: GradientInterpolation): number[] {
|
||||||
if (!showMidpoints || markers.length < 2) return [];
|
// A stepped ramp jumps at its stops, so no midpoint has anything to bias
|
||||||
|
if (!showMidpoints || trackInterpolation === "Stepped" || markers.length < 2) return [];
|
||||||
const positions = markers.slice(0, -1).map((marker, i) => marker.position + marker.midpoint * (markers[i + 1].position - marker.position));
|
const positions = markers.slice(0, -1).map((marker, i) => marker.position + marker.midpoint * (markers[i + 1].position - marker.position));
|
||||||
|
|
||||||
// The wrapped interval's diamond may land on either side of the 1|0 boundary
|
// The wrapped interval's diamond may land on either side of the 1|0 boundary
|
||||||
@@ -377,7 +379,7 @@
|
|||||||
|
|
||||||
return positions;
|
return positions;
|
||||||
}
|
}
|
||||||
$: midpointPositions = diamondPositions(markers, showMidpoints, trackCyclic);
|
$: midpointPositions = diamondPositions(markers, showMidpoints, trackCyclic, trackInterpolation);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
document.addEventListener("keydown", deleteShortcut);
|
document.addEventListener("keydown", deleteShortcut);
|
||||||
|
|||||||
@@ -614,9 +614,9 @@ tagged_value! {
|
|||||||
GradientForm(vector::style::GradientForm),
|
GradientForm(vector::style::GradientForm),
|
||||||
#[serde(alias = "GradientSpreadMethod")] // TODO: Eventually remove this document upgrade code
|
#[serde(alias = "GradientSpreadMethod")] // TODO: Eventually remove this document upgrade code
|
||||||
GradientSpread(vector::style::GradientSpread),
|
GradientSpread(vector::style::GradientSpread),
|
||||||
#[serde(alias = "GradientInterpolation")] // TODO: Eventually remove this document upgrade code
|
|
||||||
GradientSpace(vector::style::GradientSpace),
|
GradientSpace(vector::style::GradientSpace),
|
||||||
GradientHueDirection(vector::style::GradientHueDirection),
|
GradientHueDirection(vector::style::GradientHueDirection),
|
||||||
|
GradientInterpolation(vector::style::GradientInterpolation),
|
||||||
ReferencePoint(vector::ReferencePoint),
|
ReferencePoint(vector::ReferencePoint),
|
||||||
CentroidType(vector::misc::CentroidType),
|
CentroidType(vector::misc::CentroidType),
|
||||||
BooleanOperation(vector::misc::BooleanOperation),
|
BooleanOperation(vector::misc::BooleanOperation),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::renderer::{ClearGuardPlacement, RenderParams, format_transform_matrix, gradient_placement, spread_adjusted_samples, transform_is_invertible};
|
use crate::renderer::{ClearGuardPlacement, RenderParams, format_transform_matrix, gradient_placement, lane_gradient_settings, spread_adjusted_samples, transform_is_invertible};
|
||||||
use crate::{Render, RenderSvgSegmentList, SvgRender};
|
use crate::{Render, RenderSvgSegmentList, SvgRender};
|
||||||
use core_types::Color;
|
use core_types::Color;
|
||||||
use core_types::attribute::Transform;
|
use core_types::attribute::Transform;
|
||||||
@@ -14,7 +14,7 @@ use graphic_types::vector_types::markers::{
|
|||||||
use graphic_types::vector_types::vector::style::{PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphic_types::vector_types::vector::style::{PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use vector_types::Gradient;
|
use vector_types::Gradient;
|
||||||
use vector_types::gradient::{GradientHueDirection, GradientSpace, GradientSpread};
|
use vector_types::gradient::GradientSpread;
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
pub enum PaintTarget {
|
pub enum PaintTarget {
|
||||||
@@ -112,20 +112,9 @@ pub fn render_gradient_paint<S: core_types::lane::LaneSource<Element = Gradient>
|
|||||||
let Some(stops) = source.element(0) else { return 0 };
|
let Some(stops) = source.element(0) else { return 0 };
|
||||||
let gradient_form: GradientForm = source.attr::<GradientFormAttr>(0);
|
let gradient_form: GradientForm = source.attr::<GradientFormAttr>(0);
|
||||||
let local_gradient_transform: DAffine2 = source.attr::<Transform>(0);
|
let local_gradient_transform: DAffine2 = source.attr::<Transform>(0);
|
||||||
let gradient_spread: GradientSpread = source.attr::<GradientSpreadAttr>(0);
|
let settings = lane_gradient_settings(source, 0);
|
||||||
let gradient_space: GradientSpace = source.attr::<GradientSpaceAttr>(0);
|
|
||||||
let gradient_hue_direction: GradientHueDirection = source.attr::<GradientHueDirectionAttr>(0);
|
|
||||||
let gradient_cyclic: bool = source.attr::<GradientCyclicAttr>(0);
|
|
||||||
|
|
||||||
let (samples, _) = spread_adjusted_samples(
|
let (samples, _) = spread_adjusted_samples(stops, settings, gradient_form, ClearGuardPlacement::SvgStopOrder);
|
||||||
stops,
|
|
||||||
gradient_spread,
|
|
||||||
gradient_form,
|
|
||||||
gradient_cyclic,
|
|
||||||
gradient_space,
|
|
||||||
gradient_hue_direction,
|
|
||||||
ClearGuardPlacement::SvgStopOrder,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (position, color, original_midpoint) in samples {
|
for (position, color, original_midpoint) in samples {
|
||||||
stop.push_str("<stop");
|
stop.push_str("<stop");
|
||||||
@@ -164,10 +153,10 @@ pub fn render_gradient_paint<S: core_types::lane::LaneSource<Element = Gradient>
|
|||||||
format!(r#" gradientTransform="{gradient_transform}""#)
|
format!(r#" gradientTransform="{gradient_transform}""#)
|
||||||
};
|
};
|
||||||
|
|
||||||
let gradient_spread = if matches!(gradient_spread, GradientSpread::Pad | GradientSpread::Clear) {
|
let gradient_spread = if matches!(settings.spread, GradientSpread::Pad | GradientSpread::Clear) {
|
||||||
String::new()
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
format!(r#" spreadMethod="{}""#, gradient_spread.svg_name())
|
format!(r#" spreadMethod="{}""#, settings.spread.svg_name())
|
||||||
};
|
};
|
||||||
|
|
||||||
let gradient_id = generate_uuid();
|
let gradient_id = generate_uuid();
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ use graphene_resource::Resource;
|
|||||||
use graphic_types::graphic::{PaintColumns, PaintOverlay, PaintReach, has_paint, is_paint_present, paint_graphics, set_paint_attribute, vector_can_reduce_to_clip_path};
|
use graphic_types::graphic::{PaintColumns, PaintOverlay, PaintReach, has_paint, is_paint_present, paint_graphics, set_paint_attribute, vector_can_reduce_to_clip_path};
|
||||||
use graphic_types::markers::{EditorMergedLayers, Fill, Stroke};
|
use graphic_types::markers::{EditorMergedLayers, Fill, Stroke};
|
||||||
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster, Texture};
|
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster, Texture};
|
||||||
use graphic_types::vector_types::gradient::{Gradient, GradientForm, GradientHueDirection, GradientSpace};
|
use graphic_types::vector_types::gradient::{Gradient, GradientForm, GradientSettings};
|
||||||
|
use graphic_types::vector_types::markers::GradientInterpolation as GradientInterpolationAttr;
|
||||||
use graphic_types::vector_types::markers::{
|
use graphic_types::vector_types::markers::{
|
||||||
GradientCyclic as GradientCyclicAttr, GradientForm as GradientFormAttr, GradientHueDirection as GradientHueDirectionAttr, GradientSpace as GradientSpaceAttr, GradientSpread as GradientSpreadAttr,
|
GradientCyclic as GradientCyclicAttr, GradientForm as GradientFormAttr, GradientHueDirection as GradientHueDirectionAttr, GradientSpace as GradientSpaceAttr, GradientSpread as GradientSpreadAttr,
|
||||||
};
|
};
|
||||||
@@ -424,17 +425,9 @@ pub(crate) enum ClearGuardPlacement {
|
|||||||
/// The `Clear` spread brackets the samples with transparent guard stops placed per `guards`: the pad extension then
|
/// The `Clear` spread brackets the samples with transparent guard stops placed per `guards`: the pad extension then
|
||||||
/// paints transparency outward while hard stops cut the paint off exactly at the unit range's boundaries. A radial
|
/// paints transparency outward while hard stops cut the paint off exactly at the unit range's boundaries. A radial
|
||||||
/// gradient's span still starts at zero, since its sampling distance never goes below the center.
|
/// gradient's span still starts at zero, since its sampling distance never goes below the center.
|
||||||
pub(crate) fn spread_adjusted_samples(
|
pub(crate) fn spread_adjusted_samples(gradient: &Gradient, settings: GradientSettings, gradient_form: GradientForm, guards: ClearGuardPlacement) -> (GradientSamples, (f64, f64)) {
|
||||||
gradient: &Gradient,
|
let samples = gradient.interpolated_samples(settings);
|
||||||
gradient_spread: GradientSpread,
|
if settings.spread != GradientSpread::Clear {
|
||||||
gradient_form: GradientForm,
|
|
||||||
gradient_cyclic: bool,
|
|
||||||
gradient_space: GradientSpace,
|
|
||||||
gradient_hue_direction: GradientHueDirection,
|
|
||||||
guards: ClearGuardPlacement,
|
|
||||||
) -> (GradientSamples, (f64, f64)) {
|
|
||||||
let samples = gradient.interpolated_samples(gradient_cyclic, gradient_space, gradient_hue_direction);
|
|
||||||
if gradient_spread != GradientSpread::Clear {
|
|
||||||
return (samples, (0., 1.));
|
return (samples, (0., 1.));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,25 +505,25 @@ fn peniko_extend(gradient_spread: GradientSpread) -> peniko::Extend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The gradient's whole-ramp settings from its lane attributes.
|
||||||
|
pub(crate) fn lane_gradient_settings<S: LaneSource<Element = Gradient>>(source: &S, index: usize) -> GradientSettings {
|
||||||
|
GradientSettings {
|
||||||
|
spread: source.attr::<GradientSpreadAttr>(index),
|
||||||
|
cyclic: source.attr::<GradientCyclicAttr>(index),
|
||||||
|
space: source.attr::<GradientSpaceAttr>(index),
|
||||||
|
hue_direction: source.attr::<GradientHueDirectionAttr>(index),
|
||||||
|
interpolation: source.attr::<GradientInterpolationAttr>(index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn create_peniko_gradient_brush<S: LaneSource<Element = Gradient>>(gradient_list: &S, multiplied_transform: &DAffine2) -> Option<(peniko::Brush, DAffine2)> {
|
fn create_peniko_gradient_brush<S: LaneSource<Element = Gradient>>(gradient_list: &S, multiplied_transform: &DAffine2) -> Option<(peniko::Brush, DAffine2)> {
|
||||||
let stops = gradient_list.element(0)?;
|
let stops = gradient_list.element(0)?;
|
||||||
|
|
||||||
let gradient_form: GradientForm = gradient_list.attr::<GradientFormAttr>(0);
|
let gradient_form: GradientForm = gradient_list.attr::<GradientFormAttr>(0);
|
||||||
let gradient_transform: DAffine2 = gradient_list.attr::<Transform>(0);
|
let gradient_transform: DAffine2 = gradient_list.attr::<Transform>(0);
|
||||||
let gradient_spread: GradientSpread = gradient_list.attr::<GradientSpreadAttr>(0);
|
let settings = lane_gradient_settings(gradient_list, 0);
|
||||||
let gradient_space: GradientSpace = gradient_list.attr::<GradientSpaceAttr>(0);
|
|
||||||
let gradient_hue_direction: GradientHueDirection = gradient_list.attr::<GradientHueDirectionAttr>(0);
|
|
||||||
let gradient_cyclic: bool = gradient_list.attr::<GradientCyclicAttr>(0);
|
|
||||||
|
|
||||||
let (samples, span) = spread_adjusted_samples(
|
let (samples, span) = spread_adjusted_samples(stops, settings, gradient_form, ClearGuardPlacement::VelloRampTexels);
|
||||||
stops,
|
|
||||||
gradient_spread,
|
|
||||||
gradient_form,
|
|
||||||
gradient_cyclic,
|
|
||||||
gradient_space,
|
|
||||||
gradient_hue_direction,
|
|
||||||
ClearGuardPlacement::VelloRampTexels,
|
|
||||||
);
|
|
||||||
|
|
||||||
let peniko_stops = peniko_color_stops(&samples);
|
let peniko_stops = peniko_color_stops(&samples);
|
||||||
|
|
||||||
@@ -552,7 +545,7 @@ fn create_peniko_gradient_brush<S: LaneSource<Element = Gradient>>(gradient_list
|
|||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
},
|
},
|
||||||
extend: peniko_extend(gradient_spread),
|
extend: peniko_extend(settings.spread),
|
||||||
stops: peniko_stops,
|
stops: peniko_stops,
|
||||||
interpolation_alpha_space: peniko::InterpolationAlphaSpace::Premultiplied,
|
interpolation_alpha_space: peniko::InterpolationAlphaSpace::Premultiplied,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -2443,11 +2436,8 @@ fn render_gradient_svg<S: LaneSource<Element = Gradient>>(source: &S, render: &m
|
|||||||
let blend_mode: BlendMode = source.attr::<BlendModeAttr>(index);
|
let blend_mode: BlendMode = source.attr::<BlendModeAttr>(index);
|
||||||
let opacity_attr: f64 = source.attr::<Opacity>(index);
|
let opacity_attr: f64 = source.attr::<Opacity>(index);
|
||||||
let opacity_fill_attr: f64 = source.attr::<OpacityFill>(index);
|
let opacity_fill_attr: f64 = source.attr::<OpacityFill>(index);
|
||||||
let gradient_spread: GradientSpread = source.attr::<GradientSpreadAttr>(index);
|
let settings = lane_gradient_settings(source, index);
|
||||||
let gradient_form: GradientForm = source.attr::<GradientFormAttr>(index);
|
let gradient_form: GradientForm = source.attr::<GradientFormAttr>(index);
|
||||||
let gradient_space: GradientSpace = source.attr::<GradientSpaceAttr>(index);
|
|
||||||
let gradient_hue_direction: GradientHueDirection = source.attr::<GradientHueDirectionAttr>(index);
|
|
||||||
let gradient_cyclic: bool = source.attr::<GradientCyclicAttr>(index);
|
|
||||||
let tag = if thumbnail_rect.is_some() { "rect" } else { "polyline" };
|
let tag = if thumbnail_rect.is_some() { "rect" } else { "polyline" };
|
||||||
render.leaf_tag(tag, |attributes| {
|
render.leaf_tag(tag, |attributes| {
|
||||||
if let Some((min, size)) = thumbnail_rect {
|
if let Some((min, size)) = thumbnail_rect {
|
||||||
@@ -2463,15 +2453,7 @@ fn render_gradient_svg<S: LaneSource<Element = Gradient>>(source: &S, render: &m
|
|||||||
attributes.push("points", format!("{MAX},{MAX} -{MAX},{MAX} -{MAX},-{MAX} {MAX},-{MAX}"));
|
attributes.push("points", format!("{MAX},{MAX} -{MAX},{MAX} -{MAX},-{MAX} {MAX},-{MAX}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (samples, _) = spread_adjusted_samples(
|
let (samples, _) = spread_adjusted_samples(gradient, settings, gradient_form, ClearGuardPlacement::SvgStopOrder);
|
||||||
gradient,
|
|
||||||
gradient_spread,
|
|
||||||
gradient_form,
|
|
||||||
gradient_cyclic,
|
|
||||||
gradient_space,
|
|
||||||
gradient_hue_direction,
|
|
||||||
ClearGuardPlacement::SvgStopOrder,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut stop_string = String::new();
|
let mut stop_string = String::new();
|
||||||
for (position, color, original_midpoint) in samples {
|
for (position, color, original_midpoint) in samples {
|
||||||
@@ -2495,10 +2477,10 @@ fn render_gradient_svg<S: LaneSource<Element = Gradient>>(source: &S, render: &m
|
|||||||
};
|
};
|
||||||
|
|
||||||
let gradient_id = generate_uuid();
|
let gradient_id = generate_uuid();
|
||||||
let spread_method_attribute = if matches!(gradient_spread, GradientSpread::Pad | GradientSpread::Clear) {
|
let spread_method_attribute = if matches!(settings.spread, GradientSpread::Pad | GradientSpread::Clear) {
|
||||||
String::new()
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
format!(r#" spreadMethod="{}""#, gradient_spread.svg_name())
|
format!(r#" spreadMethod="{}""#, settings.spread.svg_name())
|
||||||
};
|
};
|
||||||
|
|
||||||
// The unit gradient line is the +X unit vector in local space, before the item's transform is applied
|
// The unit gradient line is the +X unit vector in local space, before the item's transform is applied
|
||||||
@@ -2540,11 +2522,8 @@ fn render_gradient_vello<S: LaneSource<Element = Gradient>>(source: &S, scene: &
|
|||||||
|
|
||||||
for index in 0..source.lane_count() {
|
for index in 0..source.lane_count() {
|
||||||
let Some(gradient) = source.element(index) else { continue };
|
let Some(gradient) = source.element(index) else { continue };
|
||||||
let gradient_spread: GradientSpread = source.attr::<GradientSpreadAttr>(index);
|
let settings = lane_gradient_settings(source, index);
|
||||||
let gradient_form: GradientForm = source.attr::<GradientFormAttr>(index);
|
let gradient_form: GradientForm = source.attr::<GradientFormAttr>(index);
|
||||||
let gradient_space: GradientSpace = source.attr::<GradientSpaceAttr>(index);
|
|
||||||
let gradient_hue_direction: GradientHueDirection = source.attr::<GradientHueDirectionAttr>(index);
|
|
||||||
let gradient_cyclic: bool = source.attr::<GradientCyclicAttr>(index);
|
|
||||||
let transform: DAffine2 = source.attr::<Transform>(index);
|
let transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
let blend_mode_attr: BlendMode = source.attr::<BlendModeAttr>(index);
|
let blend_mode_attr: BlendMode = source.attr::<BlendModeAttr>(index);
|
||||||
let opacity_attr: f64 = source.attr::<Opacity>(index);
|
let opacity_attr: f64 = source.attr::<Opacity>(index);
|
||||||
@@ -2554,18 +2533,10 @@ fn render_gradient_vello<S: LaneSource<Element = Gradient>>(source: &S, scene: &
|
|||||||
let blend_mode = blend_mode_attr.to_peniko();
|
let blend_mode = blend_mode_attr.to_peniko();
|
||||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
|
|
||||||
let (samples, span) = spread_adjusted_samples(
|
let (samples, span) = spread_adjusted_samples(gradient, settings, gradient_form, ClearGuardPlacement::VelloRampTexels);
|
||||||
gradient,
|
|
||||||
gradient_spread,
|
|
||||||
gradient_form,
|
|
||||||
gradient_cyclic,
|
|
||||||
gradient_space,
|
|
||||||
gradient_hue_direction,
|
|
||||||
ClearGuardPlacement::VelloRampTexels,
|
|
||||||
);
|
|
||||||
let stops = peniko_color_stops(&samples);
|
let stops = peniko_color_stops(&samples);
|
||||||
|
|
||||||
let extend = peniko_extend(gradient_spread);
|
let extend = peniko_extend(settings.spread);
|
||||||
|
|
||||||
// The unit gradient line is the +X unit vector in local space, before the item's transform is applied.
|
// The unit gradient line is the +X unit vector in local space, before the item's transform is applied.
|
||||||
// For radial, the unit-radius circle at the origin scales out to the line's length once the brush transform applies.
|
// For radial, the unit-radius circle at the origin scales out to the line's length once the brush transform applies.
|
||||||
@@ -3369,30 +3340,39 @@ mod group_walk_tests {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod spread_tests {
|
mod spread_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use graphic_types::vector_types::gradient::{GradientHueDirection, GradientInterpolation, GradientSpace};
|
||||||
#[test]
|
#[test]
|
||||||
fn spread_adjusted_samples_wraps_clear_in_transparent_guards() {
|
fn spread_adjusted_samples_wraps_clear_in_transparent_guards() {
|
||||||
let gradient = Gradient::from(vec![Color::BLACK, Color::WHITE]);
|
let gradient = Gradient::from(vec![Color::BLACK, Color::WHITE]);
|
||||||
|
|
||||||
let (samples, span) = spread_adjusted_samples(
|
let (samples, span) = spread_adjusted_samples(
|
||||||
&gradient,
|
&gradient,
|
||||||
GradientSpread::Repeat,
|
GradientSettings {
|
||||||
|
spread: GradientSpread::Repeat,
|
||||||
|
space: GradientSpace::RgbGamma,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
GradientForm::Linear,
|
GradientForm::Linear,
|
||||||
false,
|
|
||||||
GradientSpace::RgbGamma,
|
|
||||||
Default::default(),
|
|
||||||
ClearGuardPlacement::SvgStopOrder,
|
ClearGuardPlacement::SvgStopOrder,
|
||||||
);
|
);
|
||||||
assert_eq!(span, (0., 1.));
|
assert_eq!(span, (0., 1.));
|
||||||
assert_eq!(samples, gradient.interpolated_samples(false, GradientSpace::RgbGamma, Default::default()));
|
assert_eq!(
|
||||||
|
samples,
|
||||||
|
gradient.interpolated_samples(GradientSettings {
|
||||||
|
space: GradientSpace::RgbGamma,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// SVG guards share the range ends' exact offsets, ordered so the pad extension resolves to the transparent outer stops
|
// SVG guards share the range ends' exact offsets, ordered so the pad extension resolves to the transparent outer stops
|
||||||
let (samples, span) = spread_adjusted_samples(
|
let (samples, span) = spread_adjusted_samples(
|
||||||
&gradient,
|
&gradient,
|
||||||
GradientSpread::Clear,
|
GradientSettings {
|
||||||
|
spread: GradientSpread::Clear,
|
||||||
|
space: GradientSpace::RgbGamma,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
GradientForm::Linear,
|
GradientForm::Linear,
|
||||||
false,
|
|
||||||
GradientSpace::RgbGamma,
|
|
||||||
Default::default(),
|
|
||||||
ClearGuardPlacement::SvgStopOrder,
|
ClearGuardPlacement::SvgStopOrder,
|
||||||
);
|
);
|
||||||
assert_eq!(span, (0., 1.));
|
assert_eq!(span, (0., 1.));
|
||||||
@@ -3405,11 +3385,12 @@ mod spread_tests {
|
|||||||
let texel = 1. / (VELLO_GRADIENT_RAMP_TEXELS - 1.);
|
let texel = 1. / (VELLO_GRADIENT_RAMP_TEXELS - 1.);
|
||||||
let (samples, span) = spread_adjusted_samples(
|
let (samples, span) = spread_adjusted_samples(
|
||||||
&gradient,
|
&gradient,
|
||||||
GradientSpread::Clear,
|
GradientSettings {
|
||||||
|
spread: GradientSpread::Clear,
|
||||||
|
space: GradientSpace::RgbGamma,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
GradientForm::Linear,
|
GradientForm::Linear,
|
||||||
false,
|
|
||||||
GradientSpace::RgbGamma,
|
|
||||||
Default::default(),
|
|
||||||
ClearGuardPlacement::VelloRampTexels,
|
ClearGuardPlacement::VelloRampTexels,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -3426,11 +3407,12 @@ mod spread_tests {
|
|||||||
// A radial keeps its stops and span anchored at zero, with no guard below the center
|
// A radial keeps its stops and span anchored at zero, with no guard below the center
|
||||||
let (samples, span) = spread_adjusted_samples(
|
let (samples, span) = spread_adjusted_samples(
|
||||||
&gradient,
|
&gradient,
|
||||||
GradientSpread::Clear,
|
GradientSettings {
|
||||||
|
spread: GradientSpread::Clear,
|
||||||
|
space: GradientSpace::RgbGamma,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
GradientForm::Radial,
|
GradientForm::Radial,
|
||||||
false,
|
|
||||||
GradientSpace::RgbGamma,
|
|
||||||
Default::default(),
|
|
||||||
ClearGuardPlacement::VelloRampTexels,
|
ClearGuardPlacement::VelloRampTexels,
|
||||||
);
|
);
|
||||||
assert_eq!(span.0, 0.);
|
assert_eq!(span.0, 0.);
|
||||||
@@ -3442,11 +3424,12 @@ mod spread_tests {
|
|||||||
fn spread_adjusted_samples_keeps_a_stopless_clear_gradient_black_inside_the_range() {
|
fn spread_adjusted_samples_keeps_a_stopless_clear_gradient_black_inside_the_range() {
|
||||||
let (samples, _) = spread_adjusted_samples(
|
let (samples, _) = spread_adjusted_samples(
|
||||||
&Gradient::from(Vec::new()),
|
&Gradient::from(Vec::new()),
|
||||||
GradientSpread::Clear,
|
GradientSettings {
|
||||||
|
spread: GradientSpread::Clear,
|
||||||
|
space: GradientSpace::RgbGamma,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
GradientForm::Linear,
|
GradientForm::Linear,
|
||||||
false,
|
|
||||||
GradientSpace::RgbGamma,
|
|
||||||
Default::default(),
|
|
||||||
ClearGuardPlacement::SvgStopOrder,
|
ClearGuardPlacement::SvgStopOrder,
|
||||||
);
|
);
|
||||||
let colors: Vec<Color> = samples.iter().map(|&(_, color, _)| color).collect();
|
let colors: Vec<Color> = samples.iter().map(|&(_, color, _)| color).collect();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@ pub mod vector;
|
|||||||
|
|
||||||
// Re-export commonly used types at the crate root
|
// Re-export commonly used types at the crate root
|
||||||
pub use core_types as gcore;
|
pub use core_types as gcore;
|
||||||
pub use gradient::{Gradient, GradientForm, GradientHueDirection, GradientRamp, GradientSpace, GradientSpread, GradientStop};
|
pub use gradient::{Gradient, GradientForm, GradientHueDirection, GradientInterpolation, GradientRamp, GradientSettings, GradientSpace, GradientSpread, GradientStop};
|
||||||
pub use markers::{ATTR_EDITOR_CLICK_TARGET, ATTR_GRADIENT_FORM, ATTR_GRADIENT_SPREAD};
|
pub use markers::{ATTR_EDITOR_CLICK_TARGET, ATTR_GRADIENT_FORM, ATTR_GRADIENT_SPREAD};
|
||||||
pub use math::{QuadExt, RectExt};
|
pub use math::{QuadExt, RectExt};
|
||||||
pub use subpath::Subpath;
|
pub use subpath::Subpath;
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ core_types::attribute! {
|
|||||||
/// Gradient's `bool` (implicit default `false`) for treating the stop list as a cycle, where a wrapped interval
|
/// Gradient's `bool` (implicit default `false`) for treating the stop list as a cycle, where a wrapped interval
|
||||||
/// interpolates from the last stop through the 1|0 boundary back to the first.
|
/// interpolates from the last stop through the 1|0 boundary back to the first.
|
||||||
pub GradientCyclic("gradient_cyclic"): bool;
|
pub GradientCyclic("gradient_cyclic"): bool;
|
||||||
|
/// Gradient's `GradientInterpolation` (`Stepped`, `Linear`, or `Smooth`), how the color progresses across each interval.
|
||||||
|
pub GradientInterpolation("gradient_interpolation"): crate::gradient::GradientInterpolation;
|
||||||
/// Gradient's shape (`Linear` or `Radial`).
|
/// Gradient's shape (`Linear` or `Radial`).
|
||||||
pub GradientForm("gradient_form"): crate::gradient::GradientForm;
|
pub GradientForm("gradient_form"): crate::gradient::GradientForm;
|
||||||
/// Optional `Vector` that overrides the item's own geometry for click-target generation.
|
/// Optional `Vector` that overrides the item's own geometry for click-target generation.
|
||||||
@@ -29,6 +31,7 @@ core_types::named_value! {
|
|||||||
for crate::gradient::GradientForm;
|
for crate::gradient::GradientForm;
|
||||||
for crate::gradient::GradientSpace;
|
for crate::gradient::GradientSpace;
|
||||||
for crate::gradient::GradientHueDirection;
|
for crate::gradient::GradientHueDirection;
|
||||||
|
for crate::gradient::GradientInterpolation;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ATTR_GRADIENT_SPREAD: &str = GradientSpread::NAME;
|
pub const ATTR_GRADIENT_SPREAD: &str = GradientSpread::NAME;
|
||||||
@@ -36,6 +39,7 @@ pub const ATTR_GRADIENT_FORM: &str = GradientForm::NAME;
|
|||||||
pub const ATTR_GRADIENT_CYCLIC: &str = GradientCyclic::NAME;
|
pub const ATTR_GRADIENT_CYCLIC: &str = GradientCyclic::NAME;
|
||||||
pub const ATTR_GRADIENT_SPACE: &str = GradientSpace::NAME;
|
pub const ATTR_GRADIENT_SPACE: &str = GradientSpace::NAME;
|
||||||
pub const ATTR_GRADIENT_HUE_DIRECTION: &str = GradientHueDirection::NAME;
|
pub const ATTR_GRADIENT_HUE_DIRECTION: &str = GradientHueDirection::NAME;
|
||||||
|
pub const ATTR_GRADIENT_INTERPOLATION: &str = GradientInterpolation::NAME;
|
||||||
pub const ATTR_EDITOR_CLICK_TARGET: &str = EditorClickTarget::NAME;
|
pub const ATTR_EDITOR_CLICK_TARGET: &str = EditorClickTarget::NAME;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ impl FillChoice<SRGBA8> {
|
|||||||
let hex = srgba.to_rgba_hex();
|
let hex = srgba.to_rgba_hex();
|
||||||
Some(format!("linear-gradient(#{hex}, #{hex})"))
|
Some(format!("linear-gradient(#{hex}, #{hex})"))
|
||||||
}
|
}
|
||||||
Self::Gradient(ramp) => Some(ramp.stops.to_css_linear_gradient(ramp.gradient_cyclic, ramp.gradient_space, ramp.gradient_hue_direction)),
|
Self::Gradient(ramp) => Some(ramp.stops.to_css_linear_gradient(ramp.into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,19 +361,6 @@ impl Stroke {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_dash_lengths(mut self, dash_lengths: &str) -> Option<Self> {
|
|
||||||
dash_lengths
|
|
||||||
.split(&[',', ' '])
|
|
||||||
.filter(|x| !x.is_empty())
|
|
||||||
.map(str::parse::<f64>)
|
|
||||||
.collect::<Result<Vec<_>, _>>()
|
|
||||||
.ok()
|
|
||||||
.map(|lengths| {
|
|
||||||
self.dash_lengths = lengths;
|
|
||||||
self
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_dash_offset(mut self, dash_offset: f64) -> Self {
|
pub fn with_dash_offset(mut self, dash_offset: f64) -> Self {
|
||||||
self.dash_offset = dash_offset;
|
self.dash_offset = dash_offset;
|
||||||
self
|
self
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ use rand::{Rng, SeedableRng};
|
|||||||
use std::ops::{Add, Mul, Rem, Sub};
|
use std::ops::{Add, Mul, Rem, Sub};
|
||||||
use vector_types::Gradient;
|
use vector_types::Gradient;
|
||||||
use vector_types::markers::{
|
use vector_types::markers::{
|
||||||
GradientCyclic as GradientCyclicAttr, GradientForm as GradientFormAttr, GradientHueDirection as GradientHueDirectionAttr, GradientSpace as GradientSpaceAttr, GradientSpread as GradientSpreadAttr,
|
GradientCyclic as GradientCyclicAttr, GradientForm as GradientFormAttr, GradientHueDirection as GradientHueDirectionAttr, GradientInterpolation as GradientInterpolationAttr,
|
||||||
|
GradientSpace as GradientSpaceAttr, GradientSpread as GradientSpreadAttr,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The struct that stores the context for the maths parser.
|
/// The struct that stores the context for the maths parser.
|
||||||
@@ -1228,6 +1229,12 @@ fn gradient_hue_direction(_: impl Ctx, gradient: Gradient, gradient_hue_directio
|
|||||||
(gradient, Attr(gradient_hue_direction))
|
(gradient, Attr(gradient_hue_direction))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets how the color progresses across each interval between gradient stops: stepped, linear, or smoothstep.
|
||||||
|
#[node_macro::node(category("Gradient"))]
|
||||||
|
fn gradient_interpolation(_: impl Ctx, gradient: Gradient, gradient_interpolation: vector_types::GradientInterpolation) -> (Gradient, Attr<GradientInterpolationAttr>) {
|
||||||
|
(gradient, Attr(gradient_interpolation))
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the position of each of a gradient's stops, a factor from 0 to 1 along the gradient.
|
/// Sets the position of each of a gradient's stops, a factor from 0 to 1 along the gradient.
|
||||||
///
|
///
|
||||||
/// A list shorter than the stop count repeats its last value, a longer list is truncated, and an empty list sets each stop to its default evenly spaced position.
|
/// A list shorter than the stop count repeats its last value, a longer list is truncated, and an empty list sets each stop to its default evenly spaced position.
|
||||||
@@ -1263,11 +1270,14 @@ fn sample_gradient(
|
|||||||
return Err(GraphError::past_end().into());
|
return Err(GraphError::past_end().into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let gradient_spread = gradient.lane(0).attr::<GradientSpreadAttr>();
|
let settings = vector_types::GradientSettings {
|
||||||
let gradient_space = gradient.lane(0).attr::<GradientSpaceAttr>();
|
spread: gradient.lane(0).attr::<GradientSpreadAttr>(),
|
||||||
let gradient_hue_direction = gradient.lane(0).attr::<GradientHueDirectionAttr>();
|
cyclic: gradient.lane(0).attr::<GradientCyclicAttr>(),
|
||||||
let gradient_cyclic = gradient.lane(0).attr::<GradientCyclicAttr>();
|
space: gradient.lane(0).attr::<GradientSpaceAttr>(),
|
||||||
Ok(gradient.element_ref(0).evaluate(position, gradient_spread, gradient_cyclic, gradient_space, gradient_hue_direction))
|
hue_direction: gradient.lane(0).attr::<GradientHueDirectionAttr>(),
|
||||||
|
interpolation: gradient.lane(0).attr::<GradientInterpolationAttr>(),
|
||||||
|
};
|
||||||
|
Ok(gradient.element_ref(0).evaluate(position, settings))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a footprint value which may be set to any transformation of a unit square describing a render area, and a render resolution at least 1x1 integer pixels.
|
/// Constructs a footprint value which may be set to any transformation of a unit square describing a render area, and a render resolution at least 1x1 integer pixels.
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ mod blend_std {
|
|||||||
let mut combined_stops = self.positions(false).into_iter().chain(under.positions(false)).collect::<Vec<_>>();
|
let mut combined_stops = self.positions(false).into_iter().chain(under.positions(false)).collect::<Vec<_>>();
|
||||||
combined_stops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
|
combined_stops.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
|
||||||
combined_stops.dedup_by(|a, b| (*a - *b).abs() < 1e-6);
|
combined_stops.dedup_by(|a, b| (*a - *b).abs() < 1e-6);
|
||||||
|
let over_evaluator = self.evaluator(Default::default());
|
||||||
|
let under_evaluator = under.evaluator(Default::default());
|
||||||
let stops = combined_stops.into_iter().map(|position| {
|
let stops = combined_stops.into_iter().map(|position| {
|
||||||
let over_color = self.evaluate(position, Default::default(), false, Default::default(), Default::default());
|
let color = blend_fn(over_evaluator.evaluate(position), under_evaluator.evaluate(position));
|
||||||
let under_color = under.evaluate(position, Default::default(), false, Default::default(), Default::default());
|
|
||||||
let color = blend_fn(over_color, under_color);
|
|
||||||
GradientStop { position, midpoint: 0.5, color }
|
GradientStop { position, midpoint: 0.5, color }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -23,16 +23,19 @@ fn gradient_map<T: Adjust<Color> + Clone + Send + Sync + core_types::CacheHash +
|
|||||||
if gradient.is_empty() {
|
if gradient.is_empty() {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
let gradient_spread = gradient.lane(0).attr::<vector_types::markers::GradientSpread>();
|
let settings = vector_types::GradientSettings {
|
||||||
let gradient_space = gradient.lane(0).attr::<vector_types::markers::GradientSpace>();
|
spread: gradient.lane(0).attr::<vector_types::markers::GradientSpread>(),
|
||||||
let gradient_hue_direction = gradient.lane(0).attr::<vector_types::markers::GradientHueDirection>();
|
cyclic: gradient.lane(0).attr::<vector_types::markers::GradientCyclic>(),
|
||||||
let gradient_cyclic = gradient.lane(0).attr::<vector_types::markers::GradientCyclic>();
|
space: gradient.lane(0).attr::<vector_types::markers::GradientSpace>(),
|
||||||
let gradient = gradient.element_ref(0);
|
hue_direction: gradient.lane(0).attr::<vector_types::markers::GradientHueDirection>(),
|
||||||
|
interpolation: gradient.lane(0).attr::<vector_types::markers::GradientInterpolation>(),
|
||||||
|
};
|
||||||
|
let evaluator = gradient.element_ref(0).evaluator(settings);
|
||||||
|
|
||||||
image.adjust(|color| {
|
image.adjust(|color| {
|
||||||
let intensity = color.luminance_rec_709();
|
let intensity = color.luminance_rec_709();
|
||||||
let intensity = if reverse { 1. - intensity } else { intensity };
|
let intensity = if reverse { 1. - intensity } else { intensity };
|
||||||
gradient.evaluate(intensity as f64, gradient_spread, gradient_cyclic, gradient_space, gradient_hue_direction)
|
evaluator.evaluate(intensity as f64)
|
||||||
});
|
});
|
||||||
|
|
||||||
image
|
image
|
||||||
|
|||||||
@@ -45,17 +45,7 @@ use vector_types::vector::{PointDomain, RegionDomain};
|
|||||||
|
|
||||||
/// The gradient color for one assign-colors position, replaying the
|
/// The gradient color for one assign-colors position, replaying the
|
||||||
/// randomized draws up to it.
|
/// randomized draws up to it.
|
||||||
fn assign_color_at(
|
fn assign_color_at(gradient: &Gradient, settings: vector_types::GradientSettings, position: usize, length: usize, randomize: bool, seed: SeedValue, repeat_every: u32) -> Color {
|
||||||
gradient: &Gradient,
|
|
||||||
gradient_cyclic: bool,
|
|
||||||
gradient_space: vector_types::GradientSpace,
|
|
||||||
gradient_hue_direction: vector_types::GradientHueDirection,
|
|
||||||
position: usize,
|
|
||||||
length: usize,
|
|
||||||
randomize: bool,
|
|
||||||
seed: SeedValue,
|
|
||||||
repeat_every: u32,
|
|
||||||
) -> Color {
|
|
||||||
let factor = match randomize {
|
let factor = match randomize {
|
||||||
true => {
|
true => {
|
||||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
||||||
@@ -71,7 +61,7 @@ fn assign_color_at(
|
|||||||
_ => position as f64 % repeat_every as f64 / (repeat_every - 1) as f64,
|
_ => position as f64 % repeat_every as f64 / (repeat_every - 1) as f64,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
gradient.evaluate(factor, Default::default(), gradient_cyclic, gradient_space, gradient_hue_direction)
|
gradient.evaluate(factor, settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Uniquely sets the fill and/or stroke style of every vector element to individual colors sampled along a chosen gradient.
|
/// Uniquely sets the fill and/or stroke style of every vector element to individual colors sampled along a chosen gradient.
|
||||||
@@ -114,30 +104,24 @@ fn assign_colors<'e>(
|
|||||||
if gradient.is_empty() {
|
if gradient.is_empty() {
|
||||||
return Ok((content.lane(lane).map_element(element), Attr(existing_fill), Attr(existing_stroke)));
|
return Ok((content.lane(lane).map_element(element), Attr(existing_fill), Attr(existing_stroke)));
|
||||||
}
|
}
|
||||||
let gradient_cyclic = gradient.lane(0).attr::<vector_types::markers::GradientCyclic>();
|
let settings = vector_types::GradientSettings {
|
||||||
let gradient_space = gradient.lane(0).attr::<vector_types::markers::GradientSpace>();
|
spread: gradient.lane(0).attr::<vector_types::markers::GradientSpread>(),
|
||||||
let gradient_hue_direction = gradient.lane(0).attr::<vector_types::markers::GradientHueDirection>();
|
cyclic: gradient.lane(0).attr::<vector_types::markers::GradientCyclic>(),
|
||||||
|
space: gradient.lane(0).attr::<vector_types::markers::GradientSpace>(),
|
||||||
|
hue_direction: gradient.lane(0).attr::<vector_types::markers::GradientHueDirection>(),
|
||||||
|
interpolation: gradient.lane(0).attr::<vector_types::markers::GradientInterpolation>(),
|
||||||
|
};
|
||||||
let gradient_element = gradient.element_ref(0);
|
let gradient_element = gradient.element_ref(0);
|
||||||
let reversed;
|
let reversed;
|
||||||
let gradient_element = match reverse {
|
let gradient_element = match reverse {
|
||||||
true => {
|
true => {
|
||||||
reversed = gradient_element.reversed();
|
reversed = gradient_element.reversed(settings.cyclic);
|
||||||
&reversed
|
&reversed
|
||||||
}
|
}
|
||||||
false => gradient_element,
|
false => gradient_element,
|
||||||
};
|
};
|
||||||
|
|
||||||
let color = assign_color_at(
|
let color = assign_color_at(gradient_element, settings, lane, content.len(), randomize, seed, repeat_every);
|
||||||
gradient_element,
|
|
||||||
gradient_cyclic,
|
|
||||||
gradient_space,
|
|
||||||
gradient_hue_direction,
|
|
||||||
lane,
|
|
||||||
content.len(),
|
|
||||||
randomize,
|
|
||||||
seed,
|
|
||||||
repeat_every,
|
|
||||||
);
|
|
||||||
let paint = List::new_from_element(color).into_graphic_list();
|
let paint = List::new_from_element(color).into_graphic_list();
|
||||||
let parked = park_paint(ctx.arena(), paint)?;
|
let parked = park_paint(ctx.arena(), paint)?;
|
||||||
|
|
||||||
@@ -195,14 +179,18 @@ fn assign_colors_graphic<'e>(
|
|||||||
if gradient.is_empty() {
|
if gradient.is_empty() {
|
||||||
return Ok(content.lane(lane).map_element(original.clone()));
|
return Ok(content.lane(lane).map_element(original.clone()));
|
||||||
}
|
}
|
||||||
let gradient_cyclic = gradient.lane(0).attr::<vector_types::markers::GradientCyclic>();
|
let settings = vector_types::GradientSettings {
|
||||||
let gradient_space = gradient.lane(0).attr::<vector_types::markers::GradientSpace>();
|
spread: gradient.lane(0).attr::<vector_types::markers::GradientSpread>(),
|
||||||
let gradient_hue_direction = gradient.lane(0).attr::<vector_types::markers::GradientHueDirection>();
|
cyclic: gradient.lane(0).attr::<vector_types::markers::GradientCyclic>(),
|
||||||
|
space: gradient.lane(0).attr::<vector_types::markers::GradientSpace>(),
|
||||||
|
hue_direction: gradient.lane(0).attr::<vector_types::markers::GradientHueDirection>(),
|
||||||
|
interpolation: gradient.lane(0).attr::<vector_types::markers::GradientInterpolation>(),
|
||||||
|
};
|
||||||
let gradient_element = gradient.element_ref(0);
|
let gradient_element = gradient.element_ref(0);
|
||||||
let reversed;
|
let reversed;
|
||||||
let gradient_element = match reverse {
|
let gradient_element = match reverse {
|
||||||
true => {
|
true => {
|
||||||
reversed = gradient_element.reversed();
|
reversed = gradient_element.reversed(settings.cyclic);
|
||||||
&reversed
|
&reversed
|
||||||
}
|
}
|
||||||
false => gradient_element,
|
false => gradient_element,
|
||||||
@@ -243,17 +231,7 @@ fn assign_colors_graphic<'e>(
|
|||||||
Some(mut rows) => {
|
Some(mut rows) => {
|
||||||
for row in 0..rows.len() {
|
for row in 0..rows.len() {
|
||||||
let has_stroke = rows.element(row).is_some_and(|vector| vector.stroke.is_some());
|
let has_stroke = rows.element(row).is_some_and(|vector| vector.stroke.is_some());
|
||||||
let color = assign_color_at(
|
let color = assign_color_at(gradient_element, settings, position + row, length, randomize, seed, repeat_every);
|
||||||
gradient_element,
|
|
||||||
gradient_cyclic,
|
|
||||||
gradient_space,
|
|
||||||
gradient_hue_direction,
|
|
||||||
position + row,
|
|
||||||
length,
|
|
||||||
randomize,
|
|
||||||
seed,
|
|
||||||
repeat_every,
|
|
||||||
);
|
|
||||||
let paint = List::new_from_element(color).into_graphic_list();
|
let paint = List::new_from_element(color).into_graphic_list();
|
||||||
if fill {
|
if fill {
|
||||||
set_paint_attribute_at(&mut rows, row, ATTR_FILL, paint.clone());
|
set_paint_attribute_at(&mut rows, row, ATTR_FILL, paint.clone());
|
||||||
|
|||||||
Reference in New Issue
Block a user