mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-23 06:48:11 +08:00
Wrap serialized gradient stops in a new GradientRamp struct and unify FillChoice (#4400)
* Introduce the GradientRamp exchange struct as the serialized TaggedValue::Gradient payload * Unify FillChoice and FillChoiceUI into one enum generic over color format, carrying GradientRamp stops * Rename the TaggedValue::Gradient variant to GradientRamp to match its payload * Move the Color variant into the tagged_value macro list since its stored and wire forms match
This commit is contained in:
@@ -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, FillChoiceUI, Gradient, GradientStops};
|
use graphene_std::vector::style::{FillChoice, Gradient, GradientRamp, 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;
|
||||||
@@ -79,11 +79,12 @@ impl MessageHandler<ColorPickerMessage, ()> for ColorPickerMessageHandler {
|
|||||||
self.active_marker_is_midpoint = false;
|
self.active_marker_is_midpoint = false;
|
||||||
self.adopt_color(color);
|
self.adopt_color(color);
|
||||||
}
|
}
|
||||||
FillChoice::Gradient(stops) => {
|
FillChoice::Gradient(ramp) => {
|
||||||
self.active_marker_index = Some(0);
|
self.active_marker_index = Some(0);
|
||||||
self.active_marker_is_midpoint = false;
|
self.active_marker_is_midpoint = false;
|
||||||
let first_color = stops.color(0).unwrap_or(Color::BLACK);
|
let gradient = Gradient::from(ramp);
|
||||||
self.gradient = Some(stops);
|
let first_color = gradient.color(0).unwrap_or(Color::BLACK);
|
||||||
|
self.gradient = Some(gradient);
|
||||||
self.adopt_color(first_color);
|
self.adopt_color(first_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,7 +155,7 @@ impl MessageHandler<ColorPickerMessage, ()> for ColorPickerMessageHandler {
|
|||||||
match preset {
|
match preset {
|
||||||
FillChoice::None => {
|
FillChoice::None => {
|
||||||
self.set_new_hsva(0., 0., 0., 1., true);
|
self.set_new_hsva(0., 0., 0., 1., true);
|
||||||
responses.add(FrontendMessage::ColorPickerColorChanged { value: FillChoiceUI::None });
|
responses.add(FrontendMessage::ColorPickerColorChanged { value: FillChoice::<SRGBA8>::None });
|
||||||
}
|
}
|
||||||
FillChoice::Solid(color) => {
|
FillChoice::Solid(color) => {
|
||||||
self.adopt_color(color);
|
self.adopt_color(color);
|
||||||
@@ -179,7 +180,7 @@ impl MessageHandler<ColorPickerMessage, ()> for ColorPickerMessageHandler {
|
|||||||
self.set_old_hsva(temp.0, temp.1, temp.2, temp.3, temp.4);
|
self.set_old_hsva(temp.0, temp.1, temp.2, temp.3, temp.4);
|
||||||
|
|
||||||
if self.is_none {
|
if self.is_none {
|
||||||
responses.add(FrontendMessage::ColorPickerColorChanged { value: FillChoiceUI::None });
|
responses.add(FrontendMessage::ColorPickerColorChanged { value: FillChoice::<SRGBA8>::None });
|
||||||
} else {
|
} else {
|
||||||
self.emit_color(responses);
|
self.emit_color(responses);
|
||||||
}
|
}
|
||||||
@@ -269,15 +270,12 @@ 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 stops = gradient.clone();
|
|
||||||
let fill_choice = FillChoice::Gradient(stops);
|
|
||||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||||
value: FillChoiceUI::from(&fill_choice),
|
value: FillChoice::Gradient(GradientRamp::from(&*gradient)),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let fill_choice = FillChoice::Solid(color);
|
|
||||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||||
value: FillChoiceUI::from(&fill_choice),
|
value: FillChoice::Solid(SRGBA8::from(color)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -399,11 +397,10 @@ impl ColorPickerMessageHandler {
|
|||||||
SpectrumInputUpdate::ActiveMarker { .. } => unreachable!("handled above"),
|
SpectrumInputUpdate::ActiveMarker { .. } => unreachable!("handled above"),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.gradient = Some(gradient.clone());
|
|
||||||
let fill_choice = FillChoice::Gradient(gradient);
|
|
||||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||||
value: FillChoiceUI::from(&fill_choice),
|
value: FillChoice::Gradient(GradientRamp::from(&gradient)),
|
||||||
});
|
});
|
||||||
|
self.gradient = Some(gradient);
|
||||||
self.send_layouts(responses);
|
self.send_layouts(responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use crate::messages::prelude::*;
|
|||||||
use crate::messages::tool::tool_messages::eyedropper_tool::PrimarySecondary;
|
use crate::messages::tool::tool_messages::eyedropper_tool::PrimarySecondary;
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::color::SRGBA8;
|
use graphene_std::color::SRGBA8;
|
||||||
use graphene_std::vector::style::FillChoiceUI;
|
use graphene_std::vector::style::FillChoice;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
@@ -165,7 +165,7 @@ pub enum FrontendMessage {
|
|||||||
},
|
},
|
||||||
/// The Rust color picker handler picked a new color/gradient. The frontend `<ColorPicker />` forwards this as its `colorOrGradient` event.
|
/// The Rust color picker handler picked a new color/gradient. The frontend `<ColorPicker />` forwards this as its `colorOrGradient` event.
|
||||||
ColorPickerColorChanged {
|
ColorPickerColorChanged {
|
||||||
value: FillChoiceUI,
|
value: FillChoice<SRGBA8>,
|
||||||
},
|
},
|
||||||
/// The Rust color picker handler is starting an undo transaction. The frontend `<ColorPicker />` forwards this as its `startHistoryTransaction` event.
|
/// The Rust color picker handler is starting an undo transaction. The frontend `<ColorPicker />` forwards this as its `startHistoryTransaction` event.
|
||||||
ColorPickerStartHistoryTransaction,
|
ColorPickerStartHistoryTransaction,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
|||||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
|
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use graphene_std::color::SRGBA8;
|
use graphene_std::color::SRGBA8;
|
||||||
use graphene_std::vector::style::FillChoiceUI;
|
use graphene_std::vector::style::FillChoice;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -197,11 +197,11 @@ impl LayoutMessageHandler {
|
|||||||
let callback_message = match action {
|
let callback_message = match action {
|
||||||
WidgetValueAction::Commit => (color_button.on_commit.callback)(&()),
|
WidgetValueAction::Commit => (color_button.on_commit.callback)(&()),
|
||||||
WidgetValueAction::Update => {
|
WidgetValueAction::Update => {
|
||||||
let Ok(fill_choice_ui) = serde_json::from_value::<FillChoiceUI>(value) else {
|
let Ok(fill_choice) = serde_json::from_value::<FillChoice<SRGBA8>>(value) else {
|
||||||
warn!("ColorInput update was not able to be parsed as FillChoiceUI: {color_button:?}");
|
warn!("ColorInput update was not able to be parsed as FillChoice<SRGBA8>: {color_button:?}");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
color_button.value = fill_choice_ui;
|
color_button.value = fill_choice;
|
||||||
(color_button.on_update.callback)(color_button)
|
(color_button.on_update.callback)(color_button)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
|||||||
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
|
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::WidgetCallback;
|
use crate::messages::tool::tool_messages::tool_prelude::WidgetCallback;
|
||||||
use derivative::*;
|
use derivative::*;
|
||||||
use graphene_std::vector::style::FillChoiceUI;
|
use graphene_std::color::SRGBA8;
|
||||||
|
use graphene_std::vector::style::FillChoice;
|
||||||
use graphite_proc_macros::WidgetBuilder;
|
use graphite_proc_macros::WidgetBuilder;
|
||||||
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
@@ -191,9 +192,9 @@ pub struct ImageButton {
|
|||||||
pub struct ColorInput {
|
pub struct ColorInput {
|
||||||
// Content
|
// Content
|
||||||
#[widget_builder(constructor)]
|
#[widget_builder(constructor)]
|
||||||
pub value: FillChoiceUI,
|
pub value: FillChoice<SRGBA8>,
|
||||||
/// CSS `linear-gradient(...)` (or solid-color stand-in) for the swatch's `background-image`. Auto-populated from `value` at layout-send time.
|
/// CSS `linear-gradient(...)` (or solid-color stand-in) for the swatch's `background-image`. Auto-populated from `value` at layout-send time.
|
||||||
/// `None` when `value` is `FillChoiceUI::None`, in which case the frontend uses its "none" fallback styling.
|
/// `None` when `value` is `FillChoice::<SRGBA8>::None`, in which case the frontend uses its "none" fallback styling.
|
||||||
#[serde(rename = "chosenGradient")]
|
#[serde(rename = "chosenGradient")]
|
||||||
#[widget_builder(skip)]
|
#[widget_builder(skip)]
|
||||||
pub chosen_gradient: Option<String>,
|
pub chosen_gradient: Option<String>,
|
||||||
|
|||||||
@@ -6,7 +6,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::{FillChoiceUI, GradientStops};
|
use graphene_std::vector::style::{FillChoice, 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))]
|
||||||
@@ -520,7 +520,7 @@ pub struct ColorPresetsInput {
|
|||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum ColorPresetsInputUpdate {
|
pub enum ColorPresetsInputUpdate {
|
||||||
Preset(FillChoiceUI),
|
Preset(FillChoice<SRGBA8>),
|
||||||
EyedropperColorCode(String),
|
EyedropperColorCode(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ 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, FillChoiceUI, GradientSpreadMethod, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphene_std::vector::style::{DashPattern, FillChoice, GradientRamp, GradientSpreadMethod, GradientType, 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;
|
||||||
@@ -726,7 +726,7 @@ impl TableItemLayout for Color {
|
|||||||
}
|
}
|
||||||
fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec<WidgetInstance> {
|
fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec<WidgetInstance> {
|
||||||
vec![
|
vec![
|
||||||
ColorInput::new(FillChoiceUI::from(&FillChoice::Solid(*self)))
|
ColorInput::new(FillChoice::<SRGBA8>::from(&FillChoice::Solid(*self)))
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
.menu_direction(Some(MenuDirection::Top))
|
.menu_direction(Some(MenuDirection::Top))
|
||||||
.narrow(true)
|
.narrow(true)
|
||||||
@@ -757,7 +757,7 @@ impl TableItemLayout for Gradient {
|
|||||||
.narrow(true)
|
.narrow(true)
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
ColorInput::new(FillChoiceUI::from(&FillChoice::Gradient(self.clone())))
|
ColorInput::new(FillChoice::<SRGBA8>::Gradient(GradientRamp::from(self)))
|
||||||
.menu_direction(Some(MenuDirection::Top))
|
.menu_direction(Some(MenuDirection::Top))
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
.narrow(true)
|
.narrow(true)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ pub enum GraphOperationMessage {
|
|||||||
},
|
},
|
||||||
FillGradientSet {
|
FillGradientSet {
|
||||||
layer: LayerNodeIdentifier,
|
layer: LayerNodeIdentifier,
|
||||||
|
#[serde(skip)]
|
||||||
gradient: Gradient,
|
gradient: Gradient,
|
||||||
gradient_type: GradientType,
|
gradient_type: GradientType,
|
||||||
spread_method: GradientSpreadMethod,
|
spread_method: GradientSpreadMethod,
|
||||||
@@ -33,6 +34,7 @@ pub enum GraphOperationMessage {
|
|||||||
},
|
},
|
||||||
GradientStopsSet {
|
GradientStopsSet {
|
||||||
layer: LayerNodeIdentifier,
|
layer: LayerNodeIdentifier,
|
||||||
|
#[serde(skip)]
|
||||||
stops: Gradient,
|
stops: Gradient,
|
||||||
},
|
},
|
||||||
GradientPositionsSet {
|
GradientPositionsSet {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ 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::{GradientSpreadMethod, GradientType, Stroke};
|
use graphene_std::vector::style::{GradientSpreadMethod, GradientType, Stroke};
|
||||||
use graphene_std::vector::{Gradient, 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};
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
@@ -410,12 +410,13 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
};
|
};
|
||||||
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);
|
||||||
|
|
||||||
self.set_input_with_refresh(backup_input_connector, NodeInput::value(TaggedValue::Gradient(gradient.clone()), false), true);
|
let ramp = GradientRamp::from(gradient);
|
||||||
|
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
|
||||||
self.set_input_with_refresh(
|
self.set_input_with_refresh(
|
||||||
InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput),
|
InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput),
|
||||||
NodeInput::value(TaggedValue::Gradient(gradient), false),
|
NodeInput::value(TaggedValue::GradientRamp(ramp), false),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -549,7 +550,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
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::Gradient(stops), false), false);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::GradientRamp(GradientRamp::from(stops)), false), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the last 'Gradient Positions' node in the chain when one exists, so on-canvas stop drags stay live even
|
/// Update the last 'Gradient Positions' node in the chain when one exists, so on-canvas stop drags stay live even
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ use graphene_std::vector::misc::BooleanOperation;
|
|||||||
use graphene_std::vector::misc::{
|
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::{FillChoiceUI, Gradient, GradientSpreadMethod, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin, build_transform_with_y_preservation};
|
use graphene_std::vector::style::{
|
||||||
|
FillChoice, Gradient, GradientRamp, GradientSpreadMethod, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, 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};
|
||||||
|
|
||||||
@@ -1157,9 +1159,9 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
|
|||||||
|
|
||||||
// Add the color input
|
// Add the color input
|
||||||
let widget_value = match &**tagged_value {
|
let widget_value = match &**tagged_value {
|
||||||
TaggedValue::Color(color) => FillChoiceUI::Solid(SRGBA8::from(*color)),
|
TaggedValue::Color(color) => FillChoice::<SRGBA8>::Solid(SRGBA8::from(*color)),
|
||||||
TaggedValue::Gradient(stops) => FillChoiceUI::Gradient(GradientStops::from(stops)),
|
TaggedValue::GradientRamp(ramp) => FillChoice::<SRGBA8>::Gradient(GradientRamp::from(ramp)),
|
||||||
value if value.is_no_paint() => FillChoiceUI::None,
|
value if value.is_no_paint() => FillChoice::<SRGBA8>::None,
|
||||||
x => {
|
x => {
|
||||||
warn!("Color {x:?}");
|
warn!("Color {x:?}");
|
||||||
return LayoutGroup::row(widgets);
|
return LayoutGroup::row(widgets);
|
||||||
@@ -1170,12 +1172,12 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
|
|||||||
// while a plain color or gradient input always keeps its own value type
|
// while a plain color or gradient input always keeps its own value type
|
||||||
let on_update: fn(&ColorInput) -> TaggedValue = if color_button.allow_none {
|
let on_update: fn(&ColorInput) -> TaggedValue = if color_button.allow_none {
|
||||||
|input| match &input.value {
|
|input| match &input.value {
|
||||||
FillChoiceUI::None => TaggedValue::no_paint(),
|
FillChoice::<SRGBA8>::None => TaggedValue::no_paint(),
|
||||||
FillChoiceUI::Solid(srgba) => TaggedValue::Color(Color::from(*srgba)),
|
FillChoice::<SRGBA8>::Solid(srgba) => TaggedValue::Color(Color::from(*srgba)),
|
||||||
FillChoiceUI::Gradient(gradient_ui) => TaggedValue::Gradient(Gradient::from(gradient_ui)),
|
FillChoice::<SRGBA8>::Gradient(ramp) => TaggedValue::GradientRamp(GradientRamp::from(ramp)),
|
||||||
}
|
}
|
||||||
} else if matches!(&**tagged_value, TaggedValue::Gradient(_)) {
|
} else if matches!(&**tagged_value, TaggedValue::GradientRamp(_)) {
|
||||||
|input| TaggedValue::Gradient(input.value.as_gradient().map(Gradient::from).unwrap_or_else(Gradient::black_to_white))
|
|input| TaggedValue::GradientRamp(input.value.as_gradient().map(GradientRamp::from).unwrap_or_else(GradientRamp::black_to_white))
|
||||||
} else {
|
} else {
|
||||||
|input| TaggedValue::Color(input.value.as_solid().map(Color::from).unwrap_or(Color::TRANSPARENT))
|
|input| TaggedValue::Color(input.value.as_solid().map(Color::from).unwrap_or(Color::TRANSPARENT))
|
||||||
};
|
};
|
||||||
@@ -2424,7 +2426,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
Ok(document_node) => match document_node.input_value(FillInput) {
|
Ok(document_node) => match document_node.input_value(FillInput) {
|
||||||
Some(TaggedValue::Color(color)) => ResolvedFill::Solid(Some(*color)),
|
Some(TaggedValue::Color(color)) => ResolvedFill::Solid(Some(*color)),
|
||||||
Some(value) if value.is_no_paint() => ResolvedFill::Solid(None),
|
Some(value) if value.is_no_paint() => ResolvedFill::Solid(None),
|
||||||
Some(TaggedValue::Gradient(_)) => {
|
Some(TaggedValue::GradientRamp(_)) => {
|
||||||
match graph_modification_utils::read_fill_node_gradient(document_node, || {
|
match graph_modification_utils::read_fill_node_gradient(document_node, || {
|
||||||
layer.map_or([DVec2::ZERO, DVec2::ONE], |layer| context.network_interface.document_metadata().nonzero_bounding_box(layer))
|
layer.map_or([DVec2::ZERO, DVec2::ONE], |layer| context.network_interface.document_metadata().nonzero_bounding_box(layer))
|
||||||
}) {
|
}) {
|
||||||
@@ -2450,12 +2452,12 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
let backup_stops = match document_node.input_value(BackupGradientInput) {
|
let backup_stops = match document_node.input_value(BackupGradientInput) {
|
||||||
Some(TaggedValue::Gradient(stops)) => stops.clone(),
|
Some(TaggedValue::GradientRamp(ramp)) => ramp.clone(),
|
||||||
_ => Gradient::black_to_white(),
|
_ => GradientRamp::black_to_white(),
|
||||||
};
|
};
|
||||||
(backup_color, backup_stops)
|
(backup_color, backup_stops)
|
||||||
}
|
}
|
||||||
Err(_) => (None, Gradient::black_to_white()),
|
Err(_) => (None, GradientRamp::black_to_white()),
|
||||||
};
|
};
|
||||||
|
|
||||||
match &fill {
|
match &fill {
|
||||||
@@ -2465,7 +2467,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
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(move |_| TaggedValue::Gradient(stops.reversed()), node_id, FillInput))
|
.on_update(update_value(move |_| TaggedValue::GradientRamp(GradientRamp::from(stops.reversed())), node_id, FillInput))
|
||||||
.widget_instance();
|
.widget_instance();
|
||||||
widgets_first_row.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
widgets_first_row.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
||||||
widgets_first_row.push(reverse_button);
|
widgets_first_row.push(reverse_button);
|
||||||
@@ -2473,16 +2475,16 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
_ => add_blank_assist(&mut widgets_first_row),
|
_ => add_blank_assist(&mut widgets_first_row),
|
||||||
}
|
}
|
||||||
|
|
||||||
let fill_choice_ui = match &fill {
|
let widget_value = match &fill {
|
||||||
ResolvedFill::Solid(color) => {
|
ResolvedFill::Solid(color) => {
|
||||||
if let Some(color) = color {
|
if let Some(color) = color {
|
||||||
FillChoiceUI::Solid(SRGBA8::from(*color))
|
FillChoice::<SRGBA8>::Solid(SRGBA8::from(*color))
|
||||||
} else {
|
} else {
|
||||||
FillChoiceUI::None
|
FillChoice::<SRGBA8>::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ResolvedFill::Gradient { gradient: stops, .. } => FillChoiceUI::Gradient(GradientStops::from(stops)),
|
ResolvedFill::Gradient { gradient: stops, .. } => FillChoice::<SRGBA8>::Gradient(GradientRamp::from(stops)),
|
||||||
ResolvedFill::Other => FillChoiceUI::None,
|
ResolvedFill::Other => FillChoice::<SRGBA8>::None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let solid_set_messages = move |color: Option<Color>| {
|
let solid_set_messages = move |color: Option<Color>| {
|
||||||
@@ -2507,18 +2509,18 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
Message::Batched { messages: messages.into() }
|
Message::Batched { messages: messages.into() }
|
||||||
};
|
};
|
||||||
|
|
||||||
let gradient_set_messages = move |gradient: Gradient| Message::Batched {
|
let gradient_set_messages = move |ramp: GradientRamp| Message::Batched {
|
||||||
messages: Box::new([
|
messages: Box::new([
|
||||||
NodeGraphMessage::SetInputValue {
|
NodeGraphMessage::SetInputValue {
|
||||||
node_id,
|
node_id,
|
||||||
input_index: FillInput::INDEX,
|
input_index: FillInput::INDEX,
|
||||||
value: TaggedValue::Gradient(gradient.clone()).into(),
|
value: Box::new(TaggedValue::GradientRamp(ramp.clone())),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
NodeGraphMessage::SetInputValue {
|
NodeGraphMessage::SetInputValue {
|
||||||
node_id,
|
node_id,
|
||||||
input_index: BackupGradientInput::INDEX,
|
input_index: BackupGradientInput::INDEX,
|
||||||
value: Box::new(TaggedValue::Gradient(gradient)),
|
value: Box::new(TaggedValue::GradientRamp(ramp)),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
]),
|
]),
|
||||||
@@ -2527,17 +2529,14 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
widgets_first_row.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
widgets_first_row.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
||||||
widgets_first_row.push(
|
widgets_first_row.push(
|
||||||
ColorInput::default()
|
ColorInput::default()
|
||||||
.value(fill_choice_ui)
|
.value(widget_value)
|
||||||
.on_update(move |x: &ColorInput| match &x.value {
|
.on_update(move |x: &ColorInput| match &x.value {
|
||||||
FillChoiceUI::None => solid_set_messages(None),
|
FillChoice::<SRGBA8>::None => solid_set_messages(None),
|
||||||
FillChoiceUI::Solid(srgba8) => {
|
FillChoice::<SRGBA8>::Solid(srgba8) => {
|
||||||
let color = Some(Color::from(*srgba8));
|
let color = Some(Color::from(*srgba8));
|
||||||
solid_set_messages(color)
|
solid_set_messages(color)
|
||||||
}
|
}
|
||||||
FillChoiceUI::Gradient(gradient_stops_ui) => {
|
FillChoice::<SRGBA8>::Gradient(ramp) => gradient_set_messages(GradientRamp::from(ramp)),
|
||||||
let gradient = Gradient::from(gradient_stops_ui);
|
|
||||||
gradient_set_messages(gradient)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.on_commit(commit_value)
|
.on_commit(commit_value)
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
@@ -2556,7 +2555,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
.on_commit(commit_value),
|
.on_commit(commit_value),
|
||||||
RadioEntryData::new("gradient")
|
RadioEntryData::new("gradient")
|
||||||
.label("Gradient")
|
.label("Gradient")
|
||||||
.on_update(update_value(move |_| TaggedValue::Gradient(backup_gradient.clone()), node_id, FillInput))
|
.on_update(update_value(move |_| TaggedValue::GradientRamp(backup_gradient.clone()), node_id, FillInput))
|
||||||
.on_commit(commit_value),
|
.on_commit(commit_value),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ impl FrontendGraphDataType {
|
|||||||
match TaggedValue::from_type_or_none(input) {
|
match TaggedValue::from_type_or_none(input) {
|
||||||
TaggedValue::U32(_) | TaggedValue::U64(_) | TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::DVec2(_) | TaggedValue::F64Array(_) | TaggedValue::DAffine2(_) => Self::Number,
|
TaggedValue::U32(_) | TaggedValue::U64(_) | TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::DVec2(_) | TaggedValue::F64Array(_) | TaggedValue::DAffine2(_) => Self::Number,
|
||||||
TaggedValue::Color(_) => Self::Color,
|
TaggedValue::Color(_) => Self::Color,
|
||||||
TaggedValue::LegacyGradient(_) | TaggedValue::Gradient(_) => Self::Gradient,
|
TaggedValue::LegacyGradient(_) | TaggedValue::GradientRamp(_) => Self::Gradient,
|
||||||
TaggedValue::String(_) => Self::Typography,
|
TaggedValue::String(_) => Self::Typography,
|
||||||
// Types whose `TaggedValue` variant has been removed are routed through `TypeDefault` and identified by the descriptor's type name.
|
// Types whose `TaggedValue` variant has been removed are routed through `TypeDefault` and identified by the descriptor's type name.
|
||||||
TaggedValue::TypeDefault(td) => match td.name.as_ref() {
|
TaggedValue::TypeDefault(td) => match td.name.as_ref() {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::messages::prelude::*;
|
|||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use graphene_std::color::SRGBA8;
|
use graphene_std::color::SRGBA8;
|
||||||
use graphene_std::renderer::Quad;
|
use graphene_std::renderer::Quad;
|
||||||
use graphene_std::vector::style::FillChoiceUI;
|
use graphene_std::vector::style::FillChoice;
|
||||||
|
|
||||||
fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, spacing: DVec2) {
|
fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, spacing: DVec2) {
|
||||||
let origin = document.snapping_state.grid.origin;
|
let origin = document.snapping_state.grid.origin;
|
||||||
@@ -274,7 +274,7 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
|
|||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
]);
|
]);
|
||||||
color_widgets.push(
|
color_widgets.push(
|
||||||
ColorInput::new(FillChoiceUI::Solid(SRGBA8::from_hex_str(&grid.color).unwrap_or(SRGBA8::BLACK)))
|
ColorInput::new(FillChoice::<SRGBA8>::Solid(SRGBA8::from_hex_str(&grid.color).unwrap_or(SRGBA8::BLACK)))
|
||||||
.tooltip_label("Grid Display Color")
|
.tooltip_label("Grid Display Color")
|
||||||
.allow_none(false)
|
.allow_none(false)
|
||||||
.on_update(update_val::<ColorInput, _>(grid, |grid, color| {
|
.on_update(update_val::<ColorInput, _>(grid, |grid, color| {
|
||||||
|
|||||||
@@ -801,12 +801,13 @@ async fn legacy_four_input_fill_migrates_to_the_split_transform_shape() {
|
|||||||
"the transform input should hold a matrix, but became {transform:?}"
|
"the transform input should hold a matrix, but became {transform:?}"
|
||||||
);
|
);
|
||||||
|
|
||||||
// The Sample Gradient parameter held the tuple-form stops, which parse as the stops value with even positions elided
|
// The Sample Gradient parameter held the tuple-form stops, which parse as the ramp value with even positions elided
|
||||||
let sample_gradient_node = &network.nodes[&graph_craft::document::NodeId(2)];
|
let sample_gradient_node = &network.nodes[&graph_craft::document::NodeId(2)];
|
||||||
let stops = sample_gradient_node.input_value(graphene_std::math_nodes::sample_gradient::GradientInput);
|
let stops = sample_gradient_node.input_value(graphene_std::math_nodes::sample_gradient::GradientInput);
|
||||||
let Some(TaggedValue::Gradient(stops)) = stops else {
|
let Some(TaggedValue::GradientRamp(ramp)) = stops else {
|
||||||
panic!("the legacy stops parameter should become a gradient stops value, but became {stops:?}");
|
panic!("the legacy stops parameter should become a gradient ramp value, but became {stops:?}");
|
||||||
};
|
};
|
||||||
|
let stops = graphene_std::vector::Gradient::from(ramp);
|
||||||
assert_eq!(stops.len(), 2);
|
assert_eq!(stops.len(), 2);
|
||||||
assert!(!stops.has_position_attribute(), "even legacy tuple positions should elide rather than materialize");
|
assert!(!stops.has_position_attribute(), "even legacy tuple positions should elide rather than materialize");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1673,7 +1673,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||||||
let fill_value = match old_fill {
|
let fill_value = match old_fill {
|
||||||
graphic_types::migrations::legacy::LegacyFill::None => TaggedValue::no_paint(),
|
graphic_types::migrations::legacy::LegacyFill::None => TaggedValue::no_paint(),
|
||||||
graphic_types::migrations::legacy::LegacyFill::Solid(color) => TaggedValue::Color(*color),
|
graphic_types::migrations::legacy::LegacyFill::Solid(color) => TaggedValue::Color(*color),
|
||||||
graphic_types::migrations::legacy::LegacyFill::Gradient(gradient) => TaggedValue::Gradient(gradient.stops.clone()),
|
graphic_types::migrations::legacy::LegacyFill::Gradient(gradient) => TaggedValue::GradientRamp(gradient.stops.clone()),
|
||||||
};
|
};
|
||||||
document
|
document
|
||||||
.network_interface
|
.network_interface
|
||||||
@@ -1721,7 +1721,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||||||
if let Some(TaggedValue::LegacyGradient(g)) = old_inputs[3].as_value() {
|
if let Some(TaggedValue::LegacyGradient(g)) = old_inputs[3].as_value() {
|
||||||
document.network_interface.set_input(
|
document.network_interface.set_input(
|
||||||
&InputConnector::node_at_index(*node_id, 3),
|
&InputConnector::node_at_index(*node_id, 3),
|
||||||
NodeInput::value(TaggedValue::Gradient(g.stops.clone()), false),
|
NodeInput::value(TaggedValue::GradientRamp(g.stops.clone()), false),
|
||||||
network_path,
|
network_path,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ use crate::messages::prelude::*;
|
|||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use crate::messages::tool::utility_types::DocumentToolData;
|
use crate::messages::tool::utility_types::DocumentToolData;
|
||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::vector::style::{FillChoice, FillChoiceUI, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphene_std::color::SRGBA8;
|
||||||
|
use graphene_std::vector::style::{FillChoice, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
|
|
||||||
/// Color selector widgets seen in [`LayoutTarget::ToolOptions`] bar.
|
/// Color selector widgets seen in [`LayoutTarget::ToolOptions`] bar.
|
||||||
pub struct ToolColorOptions {
|
pub struct ToolColorOptions {
|
||||||
@@ -85,8 +86,8 @@ impl ToolColorOptions {
|
|||||||
// In the mixed state (`fill_choice` is `None`) the dash overlay covers the swatch, so the underlying widget value just drives the picker's initial position.
|
// In the mixed state (`fill_choice` is `None`) the dash overlay covers the swatch, so the underlying widget value just drives the picker's initial position.
|
||||||
// `FillChoice::None` gives it a neutral starting point.
|
// `FillChoice::None` gives it a neutral starting point.
|
||||||
let mixed_color = self.fill_choice.is_none();
|
let mixed_color = self.fill_choice.is_none();
|
||||||
// Convert the internal linear-light `FillChoice` to the JS-boundary `FillChoiceUI` (with `SRGBA8` colors) for the widget value.
|
// Convert the internal linear-light `FillChoice` to the JS-boundary `FillChoice<SRGBA8>` (with `SRGBA8` colors) for the widget value.
|
||||||
let widget_value = FillChoiceUI::from(self.fill_choice.as_ref().unwrap_or(&FillChoice::None));
|
let widget_value = FillChoice::<SRGBA8>::from(self.fill_choice.as_ref().unwrap_or(&FillChoice::None));
|
||||||
let mixed_enabled = self.enabled.is_none();
|
let mixed_enabled = self.enabled.is_none();
|
||||||
// In the mixed-enabled state the underlying `checked` value is hidden behind the indeterminate dash.
|
// In the mixed-enabled state the underlying `checked` value is hidden behind the indeterminate dash.
|
||||||
// The frontend's click handler sends `true` when the user resolves the mixed state by clicking.
|
// The frontend's click handler sends `true` when the user resolves the mixed state by clicking.
|
||||||
|
|||||||
@@ -316,14 +316,14 @@ pub fn get_gradient_stops(layer: LayerNodeIdentifier, network_interface: &NodeNe
|
|||||||
.get(&fill_node_id)
|
.get(&fill_node_id)
|
||||||
.and_then(|node| node.input(graphene_std::vector::fill::FillInput))
|
.and_then(|node| node.input(graphene_std::vector::fill::FillInput))
|
||||||
.and_then(|input| input.as_value())
|
.and_then(|input| input.as_value())
|
||||||
.and_then(|value| if let TaggedValue::Gradient(gradient) = value { Some(gradient.clone()) } else { None });
|
.and_then(|value| if let TaggedValue::GradientRamp(ramp) = value { Some(Gradient::from(ramp)) } else { None });
|
||||||
}
|
}
|
||||||
|
|
||||||
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::Gradient(stops) = 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;
|
||||||
};
|
};
|
||||||
let mut stops = stops.clone();
|
let mut stops = Gradient::from(ramp);
|
||||||
|
|
||||||
// The chain's stop placement comes from the closest-to-layer 'Gradient Positions'/'Gradient Midpoints' nodes,
|
// The chain's stop placement comes from the closest-to-layer 'Gradient Positions'/'Gradient Midpoints' nodes,
|
||||||
// matching the runtime where each later node overwrites the whole attribute
|
// matching the runtime where each later node overwrites the whole attribute
|
||||||
@@ -662,10 +662,10 @@ pub struct FillNodeGradient {
|
|||||||
pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOnce() -> [DVec2; 2]) -> Option<FillNodeGradient> {
|
pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOnce() -> [DVec2; 2]) -> Option<FillNodeGradient> {
|
||||||
use graphene_std::vector::fill;
|
use graphene_std::vector::fill;
|
||||||
|
|
||||||
let TaggedValue::Gradient(stops) = 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 stops = stops.clone();
|
let stops = Gradient::from(ramp);
|
||||||
let gradient_type = match fill_node.input(fill::GradientTypeInput).and_then(|input| input.as_value()) {
|
let gradient_type = match fill_node.input(fill::GradientTypeInput).and_then(|input| input.as_value()) {
|
||||||
Some(&TaggedValue::GradientType(value)) => value,
|
Some(&TaggedValue::GradientType(value)) => value,
|
||||||
_ => GradientType::default(),
|
_ => GradientType::default(),
|
||||||
@@ -731,7 +731,7 @@ pub fn selected_fill_state(document: &DocumentMessageHandler) -> Option<Selected
|
|||||||
|
|
||||||
match fill_node.input(graphene_std::vector::fill::FillInput)?.as_value()? {
|
match fill_node.input(graphene_std::vector::fill::FillInput)?.as_value()? {
|
||||||
TaggedValue::Color(color) => Some(FillChoice::Solid(*color)),
|
TaggedValue::Color(color) => Some(FillChoice::Solid(*color)),
|
||||||
TaggedValue::Gradient(stops) => Some(FillChoice::Gradient(stops.clone())),
|
TaggedValue::GradientRamp(ramp) => Some(FillChoice::Gradient(ramp.clone())),
|
||||||
value if value.is_no_paint() => Some(FillChoice::None),
|
value if value.is_no_paint() => Some(FillChoice::None),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
@@ -815,7 +815,7 @@ pub fn set_fill_for_selected_layers(fill_choice: FillChoice, document: &Document
|
|||||||
match &fill_choice {
|
match &fill_choice {
|
||||||
FillChoice::None => responses.add(GraphOperationMessage::FillColorSet { layer, color: None }),
|
FillChoice::None => responses.add(GraphOperationMessage::FillColorSet { layer, color: None }),
|
||||||
FillChoice::Solid(color) => responses.add(GraphOperationMessage::FillColorSet { layer, color: Some(*color) }),
|
FillChoice::Solid(color) => responses.add(GraphOperationMessage::FillColorSet { layer, color: Some(*color) }),
|
||||||
FillChoice::Gradient(stops) => {
|
FillChoice::Gradient(ramp) => {
|
||||||
use graphene_std::vector::fill;
|
use graphene_std::vector::fill;
|
||||||
let fill_parameters = NodeGraphLayer::new(layer, &document.network_interface).find_node_parameters(fill::IDENTIFIER);
|
let fill_parameters = NodeGraphLayer::new(layer, &document.network_interface).find_node_parameters(fill::IDENTIFIER);
|
||||||
|
|
||||||
@@ -836,7 +836,7 @@ pub fn set_fill_for_selected_layers(fill_choice: FillChoice, document: &Document
|
|||||||
|
|
||||||
responses.add(GraphOperationMessage::FillGradientSet {
|
responses.add(GraphOperationMessage::FillGradientSet {
|
||||||
layer,
|
layer,
|
||||||
gradient: stops.clone(),
|
gradient: Gradient::from(ramp),
|
||||||
gradient_type,
|
gradient_type,
|
||||||
spread_method,
|
spread_method,
|
||||||
transform,
|
transform,
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ use graph_craft::document::NodeId;
|
|||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::brush::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
|
use graphene_std::brush::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
|
||||||
|
use graphene_std::color::SRGBA8;
|
||||||
use graphene_std::raster::BlendMode;
|
use graphene_std::raster::BlendMode;
|
||||||
use graphene_std::vector::style::{FillChoice, FillChoiceUI};
|
use graphene_std::vector::style::FillChoice;
|
||||||
|
|
||||||
const BRUSH_MAX_SIZE: f64 = 5000.;
|
const BRUSH_MAX_SIZE: f64 = 5000.;
|
||||||
|
|
||||||
@@ -104,7 +105,7 @@ impl ToolMetadata for BrushTool {
|
|||||||
impl LayoutHolder for BrushTool {
|
impl LayoutHolder for BrushTool {
|
||||||
fn layout(&self) -> Layout {
|
fn layout(&self) -> Layout {
|
||||||
let mut widgets = vec![
|
let mut widgets = vec![
|
||||||
ColorInput::new(FillChoiceUI::from(self.options.color.fill_choice.as_ref().unwrap_or(&FillChoice::None)))
|
ColorInput::new(FillChoice::<SRGBA8>::from(self.options.color.fill_choice.as_ref().unwrap_or(&FillChoice::None)))
|
||||||
.mixed(self.options.color.fill_choice.is_none())
|
.mixed(self.options.color.fill_choice.is_none())
|
||||||
.narrow(true)
|
.narrow(true)
|
||||||
.on_update(|color: &ColorInput| {
|
.on_update(|color: &ColorInput| {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::messages::tool::common_functionality::color_selector::solid;
|
|||||||
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
|
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
|
||||||
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::FillChoiceUI;
|
use graphene_std::vector::style::FillChoice;
|
||||||
|
|
||||||
#[derive(Default, ExtractField)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct FillTool {
|
pub struct FillTool {
|
||||||
@@ -44,7 +44,7 @@ impl ToolMetadata for FillTool {
|
|||||||
impl LayoutHolder for FillTool {
|
impl LayoutHolder for FillTool {
|
||||||
fn layout(&self) -> Layout {
|
fn layout(&self) -> Layout {
|
||||||
let widgets = vec![
|
let widgets = vec![
|
||||||
ColorInput::new(FillChoiceUI::from(&solid(self.primary_color)))
|
ColorInput::new(FillChoice::<SRGBA8>::from(&solid(self.primary_color)))
|
||||||
.narrow(true)
|
.narrow(true)
|
||||||
.on_update(|color: &ColorInput| {
|
.on_update(|color: &ColorInput| {
|
||||||
FillToolMessage::SetColor {
|
FillToolMessage::SetColor {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ 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, FillChoiceUI, Gradient, GradientSpreadMethod, GradientStop, GradientStops, GradientType, build_transform_with_y_preservation};
|
use graphene_std::vector::style::{FillChoice, Gradient, GradientRamp, GradientSpreadMethod, GradientStop, GradientStops, GradientType, build_transform_with_y_preservation};
|
||||||
|
|
||||||
#[derive(Default, ExtractField)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct GradientTool {
|
pub struct GradientTool {
|
||||||
@@ -257,33 +257,27 @@ impl LayoutHolder for GradientTool {
|
|||||||
.widget_instance();
|
.widget_instance();
|
||||||
|
|
||||||
// Display priority: the selected layer's stops, then any user-customized tool default, then the working colors
|
// Display priority: the selected layer's stops, then any user-customized tool default, then the working colors
|
||||||
let stops_value = self
|
let stops_value = self.data.current_gradient_stops.clone().or_else(|| self.data.default_gradient_stops.clone()).unwrap_or_else(|| {
|
||||||
.data
|
Gradient::new([
|
||||||
.current_gradient_stops
|
GradientStop {
|
||||||
.clone()
|
position: 0.,
|
||||||
.or_else(|| self.data.default_gradient_stops.clone())
|
midpoint: 0.5,
|
||||||
.map(FillChoice::Gradient)
|
color: self.data.primary_color,
|
||||||
.unwrap_or_else(|| {
|
},
|
||||||
FillChoice::Gradient(Gradient::new([
|
GradientStop {
|
||||||
GradientStop {
|
position: 1.,
|
||||||
position: 0.,
|
midpoint: 0.5,
|
||||||
midpoint: 0.5,
|
color: self.data.secondary_color,
|
||||||
color: self.data.primary_color,
|
},
|
||||||
},
|
])
|
||||||
GradientStop {
|
});
|
||||||
position: 1.,
|
let stops_widget = ColorInput::new(FillChoice::Gradient(GradientRamp::from(&stops_value)))
|
||||||
midpoint: 0.5,
|
|
||||||
color: self.data.secondary_color,
|
|
||||||
},
|
|
||||||
]))
|
|
||||||
});
|
|
||||||
let stops_widget = ColorInput::new(FillChoiceUI::from(&stops_value))
|
|
||||||
.allow_none(false)
|
.allow_none(false)
|
||||||
.narrow(true)
|
.narrow(true)
|
||||||
.tooltip_label("Gradient Stops")
|
.tooltip_label("Gradient Stops")
|
||||||
.tooltip_description("Edit the gradient's color stops.")
|
.tooltip_description("Edit the gradient's color stops.")
|
||||||
.on_update(|input: &ColorInput| {
|
.on_update(|input: &ColorInput| {
|
||||||
let stops = input.value.as_gradient().cloned().unwrap_or_default();
|
let stops = input.value.as_gradient().map(|ramp| ramp.stops.clone()).unwrap_or_default();
|
||||||
GradientToolMessage::UpdateStops { stops }.into()
|
GradientToolMessage::UpdateStops { stops }.into()
|
||||||
})
|
})
|
||||||
.on_commit(|_| DocumentMessage::AddTransaction.into())
|
.on_commit(|_| DocumentMessage::AddTransaction.into())
|
||||||
@@ -2024,7 +2018,7 @@ mod test_gradient {
|
|||||||
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::vector::style::{GradientSpreadMethod, build_transform_with_y_preservation};
|
use graphene_std::vector::style::{GradientSpreadMethod, build_transform_with_y_preservation};
|
||||||
use graphene_std::vector::{Gradient, GradientStop, fill};
|
use graphene_std::vector::{Gradient, GradientRamp, GradientStop, fill};
|
||||||
|
|
||||||
use super::gradient_space_transform;
|
use super::gradient_space_transform;
|
||||||
|
|
||||||
@@ -2067,7 +2061,7 @@ mod test_gradient {
|
|||||||
let fill_node = document.network_interface.document_network().nodes.get(&fill_node_id)?;
|
let fill_node = document.network_interface.document_network().nodes.get(&fill_node_id)?;
|
||||||
|
|
||||||
let stops = match fill_node.input(fill::FillInput)?.as_value()? {
|
let stops = match fill_node.input(fill::FillInput)?.as_value()? {
|
||||||
TaggedValue::Gradient(stops) => stops.clone(),
|
TaggedValue::GradientRamp(ramp) => Gradient::from(ramp),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2156,7 +2150,7 @@ mod test_gradient {
|
|||||||
.handle_message(NodeGraphMessage::SetInputValue {
|
.handle_message(NodeGraphMessage::SetInputValue {
|
||||||
node_id: gradient_node_id,
|
node_id: gradient_node_id,
|
||||||
input_index: 1,
|
input_index: 1,
|
||||||
value: Box::new(TaggedValue::Gradient(Gradient::new([
|
value: Box::new(TaggedValue::GradientRamp(GradientRamp::from(Gradient::new([
|
||||||
GradientStop {
|
GradientStop {
|
||||||
position: 0.,
|
position: 0.,
|
||||||
midpoint: 0.5,
|
midpoint: 0.5,
|
||||||
@@ -2167,7 +2161,7 @@ mod test_gradient {
|
|||||||
midpoint: 0.5,
|
midpoint: 0.5,
|
||||||
color: Color::BLUE,
|
color: Color::BLUE,
|
||||||
},
|
},
|
||||||
]))),
|
])))),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -2190,10 +2184,10 @@ mod test_gradient {
|
|||||||
.and_then(|node| node.input(graphene_std::math_nodes::gradient_value::GradientInput))
|
.and_then(|node| node.input(graphene_std::math_nodes::gradient_value::GradientInput))
|
||||||
.and_then(|input| input.as_value())
|
.and_then(|input| input.as_value())
|
||||||
.cloned();
|
.cloned();
|
||||||
let Some(TaggedValue::Gradient(stops)) = stops else {
|
let Some(TaggedValue::GradientRamp(ramp)) = stops else {
|
||||||
panic!("expected a gradient default, got {stops:?}")
|
panic!("expected a gradient default, got {stops:?}")
|
||||||
};
|
};
|
||||||
assert_eq!(stops.positions(), vec![0., 1.], "the parameter default should be the black-to-white starting gradient");
|
assert_eq!(Gradient::from(ramp).positions(), vec![0., 1.], "the parameter default should be the black-to-white starting gradient");
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_fill_gradient_chain_layer(editor: &mut EditorTestUtils) -> LayerNodeIdentifier {
|
async fn create_fill_gradient_chain_layer(editor: &mut EditorTestUtils) -> LayerNodeIdentifier {
|
||||||
@@ -2215,7 +2209,7 @@ mod test_gradient {
|
|||||||
.handle_message(NodeGraphMessage::SetInputValue {
|
.handle_message(NodeGraphMessage::SetInputValue {
|
||||||
node_id: gradient_node_id,
|
node_id: gradient_node_id,
|
||||||
input_index: 1,
|
input_index: 1,
|
||||||
value: Box::new(TaggedValue::Gradient(Gradient::new([
|
value: Box::new(TaggedValue::GradientRamp(GradientRamp::from(Gradient::new([
|
||||||
GradientStop {
|
GradientStop {
|
||||||
position: 0.,
|
position: 0.,
|
||||||
midpoint: 0.5,
|
midpoint: 0.5,
|
||||||
@@ -2226,7 +2220,7 @@ mod test_gradient {
|
|||||||
midpoint: 0.5,
|
midpoint: 0.5,
|
||||||
color: Color::BLUE,
|
color: Color::BLUE,
|
||||||
},
|
},
|
||||||
]))),
|
])))),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -2858,7 +2852,7 @@ mod test_gradient {
|
|||||||
.handle_message(NodeGraphMessage::SetInputValue {
|
.handle_message(NodeGraphMessage::SetInputValue {
|
||||||
node_id: gradient_value_id,
|
node_id: gradient_value_id,
|
||||||
input_index: 1,
|
input_index: 1,
|
||||||
value: Box::new(TaggedValue::Gradient(Gradient::new([
|
value: Box::new(TaggedValue::GradientRamp(GradientRamp::from(Gradient::new([
|
||||||
GradientStop {
|
GradientStop {
|
||||||
position: 0.,
|
position: 0.,
|
||||||
midpoint: 0.5,
|
midpoint: 0.5,
|
||||||
@@ -2869,7 +2863,7 @@ mod test_gradient {
|
|||||||
midpoint: 0.5,
|
midpoint: 0.5,
|
||||||
color: Color::BLUE,
|
color: Color::BLUE,
|
||||||
},
|
},
|
||||||
]))),
|
])))),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use graphene_std::choice_type::ChoiceTypeStatic;
|
|||||||
use graphene_std::color::SRGBA8;
|
use graphene_std::color::SRGBA8;
|
||||||
use graphene_std::renderer::Quad;
|
use graphene_std::renderer::Quad;
|
||||||
use graphene_std::text::{Font, TextAlign, TypesettingConfig, lines_clipping};
|
use graphene_std::text::{Font, TextAlign, TypesettingConfig, lines_clipping};
|
||||||
use graphene_std::vector::style::{FillChoice, FillChoiceUI};
|
use graphene_std::vector::style::FillChoice;
|
||||||
use graphene_std::{Color, NodeParameter};
|
use graphene_std::{Color, NodeParameter};
|
||||||
|
|
||||||
#[derive(Default, ExtractField)]
|
#[derive(Default, ExtractField)]
|
||||||
@@ -261,7 +261,7 @@ impl TextTool {
|
|||||||
|
|
||||||
fn layout(&self, font_catalog: &FontCatalog, document: &DocumentMessageHandler) -> Layout {
|
fn layout(&self, font_catalog: &FontCatalog, document: &DocumentMessageHandler) -> Layout {
|
||||||
let mut widgets = vec![
|
let mut widgets = vec![
|
||||||
ColorInput::new(FillChoiceUI::from(self.options.fill.fill_choice.as_ref().unwrap_or(&FillChoice::None)))
|
ColorInput::new(FillChoice::<SRGBA8>::from(self.options.fill.fill_choice.as_ref().unwrap_or(&FillChoice::None)))
|
||||||
.mixed(self.options.fill.fill_choice.is_none())
|
.mixed(self.options.fill.fill_choice.is_none())
|
||||||
.narrow(true)
|
.narrow(true)
|
||||||
.on_update(|color: &ColorInput| {
|
.on_update(|color: &ColorInput| {
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
import LayoutRow from "/src/components/layout/LayoutRow.svelte";
|
import LayoutRow from "/src/components/layout/LayoutRow.svelte";
|
||||||
import WidgetLayout from "/src/components/widgets/WidgetLayout.svelte";
|
import WidgetLayout from "/src/components/widgets/WidgetLayout.svelte";
|
||||||
import type { ColorPickerCallbacks, ColorPickerStore } from "/src/stores/color-picker";
|
import type { ColorPickerCallbacks, ColorPickerStore } from "/src/stores/color-picker";
|
||||||
import type { EditorWrapper, FillChoiceUI, MenuDirection } from "/wrapper/pkg/graphite_wasm_wrapper";
|
import type { EditorWrapper, FillChoice, MenuDirection, SRGBA8 } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ colorOrGradient: FillChoiceUI; startHistoryTransaction: undefined; commitHistoryTransaction: undefined }>();
|
const dispatch = createEventDispatcher<{ colorOrGradient: FillChoice<SRGBA8>; startHistoryTransaction: undefined; commitHistoryTransaction: undefined }>();
|
||||||
|
|
||||||
const editor = getContext<EditorWrapper>("editor");
|
const editor = getContext<EditorWrapper>("editor");
|
||||||
const colorPickerStore = getContext<ColorPickerStore>("colorPicker");
|
const colorPickerStore = getContext<ColorPickerStore>("colorPicker");
|
||||||
|
|
||||||
export let colorOrGradient: FillChoiceUI;
|
export let colorOrGradient: FillChoice<SRGBA8>;
|
||||||
export let allowNone = false;
|
export let allowNone = false;
|
||||||
// export let allowTransparency = false; // TODO: Implement
|
// export let allowTransparency = false; // TODO: Implement
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
import type { DocumentStore } from "/src/stores/document";
|
import type { DocumentStore } from "/src/stores/document";
|
||||||
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
import type { MessageBody } from "/src/subscriptions-router";
|
import type { MessageBody } from "/src/subscriptions-router";
|
||||||
import { fillChoiceUIColor, createSRgba8 } from "/src/utility-functions/colors";
|
import { fillChoiceColor, createSRgba8 } from "/src/utility-functions/colors";
|
||||||
import { pasteFile } from "/src/utility-functions/files";
|
import { pasteFile } from "/src/utility-functions/files";
|
||||||
import { textInputCleanup } from "/src/utility-functions/keyboard-entry";
|
import { textInputCleanup } from "/src/utility-functions/keyboard-entry";
|
||||||
import { rasterizeSVGCanvas } from "/src/utility-functions/rasterization";
|
import { rasterizeSVGCanvas } from "/src/utility-functions/rasterization";
|
||||||
@@ -680,7 +680,7 @@
|
|||||||
}}
|
}}
|
||||||
colorOrGradient={{ Solid: gradientStopPickerColor || createSRgba8(0, 0, 0, 255) }}
|
colorOrGradient={{ Solid: gradientStopPickerColor || createSRgba8(0, 0, 0, 255) }}
|
||||||
on:colorOrGradient={({ detail }) => {
|
on:colorOrGradient={({ detail }) => {
|
||||||
const color = fillChoiceUIColor(detail);
|
const color = fillChoiceColor(detail);
|
||||||
if (color) editor.updateGradientStopColor(color);
|
if (color) editor.updateGradientStopColor(color);
|
||||||
}}
|
}}
|
||||||
on:startHistoryTransaction={() => editor.startGradientStopColorTransaction()}
|
on:startHistoryTransaction={() => editor.startGradientStopColorTransaction()}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
import ShortcutLabel from "/src/components/widgets/labels/ShortcutLabel.svelte";
|
import ShortcutLabel from "/src/components/widgets/labels/ShortcutLabel.svelte";
|
||||||
import TextLabel from "/src/components/widgets/labels/TextLabel.svelte";
|
import TextLabel from "/src/components/widgets/labels/TextLabel.svelte";
|
||||||
import type { ColorPickerStore } from "/src/stores/color-picker";
|
import type { ColorPickerStore } from "/src/stores/color-picker";
|
||||||
import { parseFillChoiceUI } from "/src/utility-functions/colors";
|
import { parseFillChoice } from "/src/utility-functions/colors";
|
||||||
import type { EditorWrapper, LayoutTarget, Widget, WidgetInstance } from "/wrapper/pkg/graphite_wasm_wrapper";
|
import type { EditorWrapper, LayoutTarget, Widget, WidgetInstance } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||||
|
|
||||||
// Extract the discriminant key names from the Widget tagged enum union (e.g. "TextButton" | "CheckboxInput" | ...)
|
// Extract the discriminant key names from the Widget tagged enum union (e.g. "TextButton" | "CheckboxInput" | ...)
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
component: ColorInput,
|
component: ColorInput,
|
||||||
getProps: (props, index) => ({
|
getProps: (props, index) => ({
|
||||||
...props,
|
...props,
|
||||||
value: parseFillChoiceUI(props.value),
|
value: parseFillChoice(props.value),
|
||||||
$$events: {
|
$$events: {
|
||||||
value: (e: CustomEvent) => widgetValueUpdate(index, e.detail, false),
|
value: (e: CustomEvent) => widgetValueUpdate(index, e.detail, false),
|
||||||
startHistoryTransaction: () => widgetValueCommit(index, props.value),
|
startHistoryTransaction: () => widgetValueCommit(index, props.value),
|
||||||
@@ -150,7 +150,7 @@
|
|||||||
getProps: (props, index) => ({
|
getProps: (props, index) => ({
|
||||||
...props,
|
...props,
|
||||||
$$events: {
|
$$events: {
|
||||||
// The widget dispatches `"None"` or a bare `SRGBA8`, wrap the color in `{ Solid: ... }` so the payload matches Rust's `FillChoiceUI` shape (which the `Preset` variant expects).
|
// The widget dispatches `"None"` or a bare `SRGBA8`, wrap the color in `{ Solid: ... }` so the payload matches the Rust `FillChoice` shape the `Preset` variant expects
|
||||||
preset: (e: CustomEvent) => widgetValueCommitAndUpdate(index, { Preset: e.detail === "None" ? "None" : { Solid: e.detail } }, true),
|
preset: (e: CustomEvent) => widgetValueCommitAndUpdate(index, { Preset: e.detail === "None" ? "None" : { Solid: e.detail } }, true),
|
||||||
eyedropperColorCode: (e: CustomEvent) => widgetValueCommitAndUpdate(index, { EyedropperColorCode: e.detail }, true),
|
eyedropperColorCode: (e: CustomEvent) => widgetValueCommitAndUpdate(index, { EyedropperColorCode: e.detail }, true),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
import { createEventDispatcher } from "svelte";
|
import { createEventDispatcher } from "svelte";
|
||||||
import ColorPicker from "/src/components/floating-menus/ColorPicker.svelte";
|
import ColorPicker from "/src/components/floating-menus/ColorPicker.svelte";
|
||||||
import LayoutCol from "/src/components/layout/LayoutCol.svelte";
|
import LayoutCol from "/src/components/layout/LayoutCol.svelte";
|
||||||
import { contrastingOutlineFactor, fillChoiceUIColor, fillChoiceUIGradient } from "/src/utility-functions/colors";
|
import { contrastingOutlineFactor, fillChoiceColor, fillChoiceGradient } from "/src/utility-functions/colors";
|
||||||
import type { FillChoiceUI, MenuDirection, ActionShortcut } from "/wrapper/pkg/graphite_wasm_wrapper";
|
import type { FillChoice, MenuDirection, ActionShortcut, SRGBA8 } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ value: FillChoiceUI; startHistoryTransaction: undefined }>();
|
const dispatch = createEventDispatcher<{ value: FillChoice<SRGBA8>; startHistoryTransaction: undefined }>();
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
export let value: FillChoiceUI;
|
export let value: FillChoice<SRGBA8>;
|
||||||
export let chosenGradient: string | undefined = undefined;
|
export let chosenGradient: string | undefined = undefined;
|
||||||
export let allowNone = false;
|
export let allowNone = false;
|
||||||
// export let allowTransparency = false; // TODO: Implement
|
// export let allowTransparency = false; // TODO: Implement
|
||||||
@@ -29,8 +29,8 @@
|
|||||||
|
|
||||||
$: outlineFactor = contrastingOutlineFactor(value, "--color-3-darkgray", 0.01);
|
$: outlineFactor = contrastingOutlineFactor(value, "--color-3-darkgray", 0.01);
|
||||||
$: outlined = outlineFactor > 0.0001;
|
$: outlined = outlineFactor > 0.0001;
|
||||||
$: gradient = fillChoiceUIGradient(value);
|
$: gradient = fillChoiceGradient(value);
|
||||||
$: solidColor = fillChoiceUIColor(value);
|
$: solidColor = fillChoiceColor(value);
|
||||||
$: none = value === "None";
|
$: none = value === "None";
|
||||||
$: transparency = gradient ? gradient.color.some((color) => color.alpha < 255) : solidColor ? solidColor.alpha < 255 : false;
|
$: transparency = gradient ? gradient.color.some((color) => color.alpha < 255) : solidColor ? solidColor.alpha < 255 : false;
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import ColorPicker from "/src/components/floating-menus/ColorPicker.svelte";
|
import ColorPicker from "/src/components/floating-menus/ColorPicker.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 { fillChoiceUIColor, sRgba8ToRgbaCSS } from "/src/utility-functions/colors";
|
import { fillChoiceColor, sRgba8ToRgbaCSS } from "/src/utility-functions/colors";
|
||||||
import type { SRGBA8, EditorWrapper } from "/wrapper/pkg/graphite_wasm_wrapper";
|
import type { SRGBA8, EditorWrapper } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||||
|
|
||||||
const editor = getContext<EditorWrapper>("editor");
|
const editor = getContext<EditorWrapper>("editor");
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
on:open={({ detail }) => (primaryOpen = detail)}
|
on:open={({ detail }) => (primaryOpen = detail)}
|
||||||
colorOrGradient={{ Solid: primary }}
|
colorOrGradient={{ Solid: primary }}
|
||||||
on:colorOrGradient={({ detail }) => {
|
on:colorOrGradient={({ detail }) => {
|
||||||
const color = fillChoiceUIColor(detail);
|
const color = fillChoiceColor(detail);
|
||||||
if (color) primaryColorChanged(color);
|
if (color) primaryColorChanged(color);
|
||||||
}}
|
}}
|
||||||
direction="Right"
|
direction="Right"
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
on:open={({ detail }) => (secondaryOpen = detail)}
|
on:open={({ detail }) => (secondaryOpen = detail)}
|
||||||
colorOrGradient={{ Solid: secondary }}
|
colorOrGradient={{ Solid: secondary }}
|
||||||
on:colorOrGradient={({ detail }) => {
|
on:colorOrGradient={({ detail }) => {
|
||||||
const color = fillChoiceUIColor(detail);
|
const color = fillChoiceColor(detail);
|
||||||
if (color) secondaryColorChanged(color);
|
if (color) secondaryColorChanged(color);
|
||||||
}}
|
}}
|
||||||
direction="Right"
|
direction="Right"
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { writable } from "svelte/store";
|
|||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
import { patchLayout } from "/src/utility-functions/widgets";
|
import { patchLayout } from "/src/utility-functions/widgets";
|
||||||
import type { FillChoiceUI, Layout } from "/wrapper/pkg/graphite_wasm_wrapper";
|
import type { FillChoice, Layout, SRGBA8 } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||||
|
|
||||||
export type ColorPickerCallbacks = {
|
export type ColorPickerCallbacks = {
|
||||||
onColorChanged?: (value: FillChoiceUI) => void;
|
onColorChanged?: (value: FillChoice<SRGBA8>) => void;
|
||||||
onStartTransaction?: () => void;
|
onStartTransaction?: () => void;
|
||||||
onCommitTransaction?: () => void;
|
onCommitTransaction?: () => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { FillChoiceUI, GradientStops, SRGBA8 } from "/wrapper/pkg/graphite_wasm_wrapper";
|
import type { FillChoice, GradientRamp, GradientStops, SRGBA8 } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||||
|
|
||||||
// Channels can have any range (0-1, 0-255, 0-100, 0-360) in the context they are being used in, these are just containers for the numbers
|
// Channels can have any range (0-1, 0-255, 0-100, 0-360) in the context they are being used in, these are just containers for the numbers
|
||||||
export type HSV = { h: number; s: number; v: number };
|
export type HSV = { h: number; s: number; v: number };
|
||||||
@@ -150,7 +150,7 @@ export function sRgba8ContrastingColor(color: SRGBA8 | undefined): "black" | "wh
|
|||||||
return luminance > Math.sqrt(1.05 * 0.05) - 0.05 ? "black" : "white";
|
return luminance > Math.sqrt(1.05 * 0.05) - 0.05 ? "black" : "white";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function contrastingOutlineFactor(value: FillChoiceUI, proximityColor: string | [string, string], proximityRange: number): number {
|
export function contrastingOutlineFactor(value: FillChoice<SRGBA8>, proximityColor: string | [string, string], proximityRange: number): number {
|
||||||
const pair = Array.isArray(proximityColor) ? [proximityColor[0], proximityColor[1]] : [proximityColor, proximityColor];
|
const pair = Array.isArray(proximityColor) ? [proximityColor[0], proximityColor[1]] : [proximityColor, proximityColor];
|
||||||
const [range1, range2] = pair.map((color) => sRgba8FromCSS(window.getComputedStyle(document.body).getPropertyValue(color)));
|
const [range1, range2] = pair.map((color) => sRgba8FromCSS(window.getComputedStyle(document.body).getPropertyValue(color)));
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ export function contrastingOutlineFactor(value: FillChoiceUI, proximityColor: st
|
|||||||
return (1 - Math.min(distance / proximityRange, 1)) * (1 - sRgba8ToHSV(color).s);
|
return (1 - Math.min(distance / proximityRange, 1)) * (1 - sRgba8ToHSV(color).s);
|
||||||
};
|
};
|
||||||
|
|
||||||
const gradient = fillChoiceUIGradient(value);
|
const gradient = fillChoiceGradient(value);
|
||||||
if (gradient) {
|
if (gradient) {
|
||||||
if (gradient.color.length === 0) return 0;
|
if (gradient.color.length === 0) return 0;
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ export function contrastingOutlineFactor(value: FillChoiceUI, proximityColor: st
|
|||||||
return Math.min(first, last);
|
return Math.min(first, last);
|
||||||
}
|
}
|
||||||
|
|
||||||
return contrast(fillChoiceUIColor(value));
|
return contrast(fillChoiceColor(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
// GRADIENT UTILITY FUNCTIONS
|
// GRADIENT UTILITY FUNCTIONS
|
||||||
@@ -186,21 +186,25 @@ export function isGradientStops(value: unknown): value is GradientStops<SRGBA8>
|
|||||||
return typeof value === "object" && value !== null && "color" in value && Array.isArray(value.color);
|
return typeof value === "object" && value !== null && "color" in value && Array.isArray(value.color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isGradientRamp(value: unknown): value is GradientRamp<SRGBA8> {
|
||||||
|
return typeof value === "object" && value !== null && "stops" in value && isGradientStops(value.stops);
|
||||||
|
}
|
||||||
|
|
||||||
// FILL CHOICE UTILITY FUNCTIONS
|
// FILL CHOICE UTILITY FUNCTIONS
|
||||||
|
|
||||||
export function fillChoiceUIColor(value: FillChoiceUI): SRGBA8 | undefined {
|
export function fillChoiceColor(value: FillChoice<SRGBA8>): SRGBA8 | undefined {
|
||||||
if (typeof value === "object" && "Solid" in value) return value.Solid;
|
if (typeof value === "object" && "Solid" in value) return value.Solid;
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fillChoiceUIGradient(value: FillChoiceUI): GradientStops<SRGBA8> | undefined {
|
export function fillChoiceGradient(value: FillChoice<SRGBA8>): GradientStops<SRGBA8> | undefined {
|
||||||
if (typeof value === "object" && "Gradient" in value) return value.Gradient;
|
if (typeof value === "object" && "Gradient" in value) return value.Gradient.stops;
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseFillChoiceUI(value: unknown): FillChoiceUI {
|
export function parseFillChoice(value: unknown): FillChoice<SRGBA8> {
|
||||||
if (value === "None" || value === undefined || value === null) return "None";
|
if (value === "None" || value === undefined || value === null) return "None";
|
||||||
if (typeof value === "object" && value !== null && "Solid" in value && isSRgba8(value.Solid)) return { Solid: value.Solid };
|
if (typeof value === "object" && value !== null && "Solid" in value && isSRgba8(value.Solid)) return { Solid: value.Solid };
|
||||||
if (typeof value === "object" && value !== null && "Gradient" in value && isGradientStops(value.Gradient)) return { Gradient: value.Gradient };
|
if (typeof value === "object" && value !== null && "Gradient" in value && isGradientRamp(value.Gradient)) return { Gradient: value.Gradient };
|
||||||
return "None";
|
return "None";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -390,9 +390,9 @@ mod editor_commands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the Rust color picker handler with a starting value (used when the frontend `<ColorPicker />` opens).
|
/// Initialize the Rust color picker handler with a starting value (used when the frontend `<ColorPicker />` opens).
|
||||||
fn open_color_picker(initial_value: FillChoiceUI, allow_none: bool, disabled: bool) -> Message {
|
fn open_color_picker(initial_value: FillChoiceSRGBA8, allow_none: bool, disabled: bool) -> Message {
|
||||||
ColorPickerMessage::Open {
|
ColorPickerMessage::Open {
|
||||||
initial_value: FillChoice::from(&initial_value),
|
initial_value: FillChoice::from(&initial_value.0),
|
||||||
allow_none,
|
allow_none,
|
||||||
disabled,
|
disabled,
|
||||||
}
|
}
|
||||||
@@ -659,6 +659,17 @@ mod editor_commands {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "editor")]
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Tsify)]
|
||||||
|
#[tsify(from_wasm_abi)]
|
||||||
|
pub struct FillChoiceSRGBA8(
|
||||||
|
/// Concrete wasm boundary form of the generic [`FillChoice`], since a `#[wasm_bindgen]` argument's TS declaration names its type without the generic's argument.
|
||||||
|
#[tsify(type = "FillChoice<SRGBA8>")]
|
||||||
|
pub graphene_std::vector::style::FillChoice<graphene_std::color::SRGBA8>,
|
||||||
|
);
|
||||||
|
#[cfg(not(feature = "editor"))]
|
||||||
|
pub type FillChoiceSRGBA8 = Any;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Tsify)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Tsify)]
|
||||||
#[tsify(from_wasm_abi)]
|
#[tsify(from_wasm_abi)]
|
||||||
pub struct Any(#[tsify(type = "any")] serde_json::Value);
|
pub struct Any(#[tsify(type = "any")] serde_json::Value);
|
||||||
@@ -685,5 +696,4 @@ editor_proxy_types! {
|
|||||||
DockingSplitDirection = editor::messages::portfolio::utility_types::DockingSplitDirection;
|
DockingSplitDirection = editor::messages::portfolio::utility_types::DockingSplitDirection;
|
||||||
PanelTypes = Vec<editor::messages::portfolio::utility_types::PanelType>;
|
PanelTypes = Vec<editor::messages::portfolio::utility_types::PanelType>;
|
||||||
SRGBA8 = graphene_std::color::SRGBA8;
|
SRGBA8 = graphene_std::color::SRGBA8;
|
||||||
FillChoiceUI = graphene_std::vector::style::FillChoiceUI;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use graphene_application_io::resource::ResourceId;
|
|||||||
use graphic_types::raster_types::{CPU, Image, Raster};
|
use graphic_types::raster_types::{CPU, Image, Raster};
|
||||||
use graphic_types::vector_types::vector::misc::BoxCorners;
|
use graphic_types::vector_types::vector::misc::BoxCorners;
|
||||||
use graphic_types::vector_types::vector::style::DashPattern;
|
use graphic_types::vector_types::vector::style::DashPattern;
|
||||||
use graphic_types::vector_types::vector::style::Gradient;
|
use graphic_types::vector_types::vector::style::{Gradient, GradientRamp};
|
||||||
use graphic_types::vector_types::vector::{self, ReferencePoint};
|
use graphic_types::vector_types::vector::{self, ReferencePoint};
|
||||||
use graphic_types::{Artboard, Graphic, Vector};
|
use graphic_types::{Artboard, Graphic, Vector};
|
||||||
use rendering::RenderMetadata;
|
use rendering::RenderMetadata;
|
||||||
@@ -74,10 +74,10 @@ macro_rules! tagged_value {
|
|||||||
#[serde(deserialize_with = "core_types::misc::migrate_to_color")] // TODO: Eventually remove this document upgrade code
|
#[serde(deserialize_with = "core_types::misc::migrate_to_color")] // TODO: Eventually remove this document upgrade code
|
||||||
#[serde(alias = "ColorTable", alias = "OptionalColor", alias = "ColorNotInTable")]
|
#[serde(alias = "ColorTable", alias = "OptionalColor", alias = "ColorNotInTable")]
|
||||||
Color(Color),
|
Color(Color),
|
||||||
/// Stored as the `{ color, position?, midpoint? }` stops struct, materializes as a single-row `List<Gradient>` at runtime via `to_dynany`/`to_any`. Aliases recover legacy on-disk shapes.
|
/// Stored as the `GradientRamp` exchange struct (nested `{ stops: { color, position?, midpoint? } }`), materializes as a single-row `List<Gradient>` at runtime via `to_dynany`/`to_any`. Aliases recover legacy on-disk shapes.
|
||||||
/// (Old documents that stored a full `Gradient` struct under this same `"Gradient"` tag are routed to `LegacyGradient` by `deserialize_tagged_value_with_legacy_migration`.)
|
/// (Old documents stored flat stops, a tuple list, or the ancient full `Gradient` struct under the legacy `"Gradient"` tag, all routed by `deserialize_tagged_value_with_legacy_migration`.)
|
||||||
#[serde(alias = "GradientTable", alias = "GradientPositions", alias = "GradientStops")]
|
#[serde(alias = "Gradient", alias = "GradientTable", alias = "GradientPositions", alias = "GradientStops")]
|
||||||
Gradient(Gradient),
|
GradientRamp(GradientRamp),
|
||||||
/// Stored compactly as a `Vec<BrushStroke>`, materializes as `List<BrushStroke>` at runtime via `to_dynany`/`to_any`. Aliases recover legacy on-disk shapes.
|
/// Stored compactly as a `Vec<BrushStroke>`, materializes as `List<BrushStroke>` at runtime via `to_dynany`/`to_any`. Aliases recover legacy on-disk shapes.
|
||||||
#[serde(deserialize_with = "brush_nodes::migrations::migrate_to_brush_strokes")] // TODO: Eventually remove this document upgrade code
|
#[serde(deserialize_with = "brush_nodes::migrations::migrate_to_brush_strokes")] // TODO: Eventually remove this document upgrade code
|
||||||
#[serde(alias = "BrushStrokeTable")]
|
#[serde(alias = "BrushStrokeTable")]
|
||||||
@@ -124,7 +124,7 @@ macro_rules! tagged_value {
|
|||||||
Self::DashPattern(lengths) => lengths.cache_hash(state),
|
Self::DashPattern(lengths) => lengths.cache_hash(state),
|
||||||
Self::BoxCorners(values) => values.cache_hash(state),
|
Self::BoxCorners(values) => values.cache_hash(state),
|
||||||
Self::Color(color) => color.cache_hash(state),
|
Self::Color(color) => color.cache_hash(state),
|
||||||
Self::Gradient(stops) => stops.cache_hash(state),
|
Self::GradientRamp(ramp) => ramp.cache_hash(state),
|
||||||
Self::BrushStrokes(strokes) => strokes.cache_hash(state),
|
Self::BrushStrokes(strokes) => strokes.cache_hash(state),
|
||||||
// =======================
|
// =======================
|
||||||
// NON-SERIALIZED VARIANTS
|
// NON-SERIALIZED VARIANTS
|
||||||
@@ -167,7 +167,7 @@ macro_rules! tagged_value {
|
|||||||
Self::DashPattern(lengths) => Box::new(DashPattern::from(lengths)),
|
Self::DashPattern(lengths) => Box::new(DashPattern::from(lengths)),
|
||||||
Self::BoxCorners(values) => Box::new(BoxCorners::from(values)),
|
Self::BoxCorners(values) => Box::new(BoxCorners::from(values)),
|
||||||
Self::Color(color) => Box::new(List::<Color>::new_from_element(color)),
|
Self::Color(color) => Box::new(List::<Color>::new_from_element(color)),
|
||||||
Self::Gradient(stops) => Box::new(List::<Gradient>::new_from_element(stops)),
|
Self::GradientRamp(ramp) => Box::new(List::<Gradient>::new_from_element(Gradient::from(ramp))),
|
||||||
Self::BrushStrokes(strokes) => {
|
Self::BrushStrokes(strokes) => {
|
||||||
let list: List<BrushStroke> = strokes.into_iter().map(core_types::list::Item::new_from_element).collect();
|
let list: List<BrushStroke> = strokes.into_iter().map(core_types::list::Item::new_from_element).collect();
|
||||||
Box::new(list)
|
Box::new(list)
|
||||||
@@ -213,7 +213,7 @@ macro_rules! tagged_value {
|
|||||||
Self::DashPattern(lengths) => Arc::new(DashPattern::from(lengths)),
|
Self::DashPattern(lengths) => Arc::new(DashPattern::from(lengths)),
|
||||||
Self::BoxCorners(values) => Arc::new(BoxCorners::from(values)),
|
Self::BoxCorners(values) => Arc::new(BoxCorners::from(values)),
|
||||||
Self::Color(color) => Arc::new(List::<Color>::new_from_element(color)),
|
Self::Color(color) => Arc::new(List::<Color>::new_from_element(color)),
|
||||||
Self::Gradient(stops) => Arc::new(List::<Gradient>::new_from_element(stops)),
|
Self::GradientRamp(ramp) => Arc::new(List::<Gradient>::new_from_element(Gradient::from(ramp))),
|
||||||
Self::BrushStrokes(strokes) => {
|
Self::BrushStrokes(strokes) => {
|
||||||
let list: List<BrushStroke> = strokes.into_iter().map(core_types::list::Item::new_from_element).collect();
|
let list: List<BrushStroke> = strokes.into_iter().map(core_types::list::Item::new_from_element).collect();
|
||||||
Arc::new(list)
|
Arc::new(list)
|
||||||
@@ -256,7 +256,7 @@ macro_rules! tagged_value {
|
|||||||
Self::DashPattern(_) => concrete!(DashPattern),
|
Self::DashPattern(_) => concrete!(DashPattern),
|
||||||
Self::BoxCorners(_) => concrete!(BoxCorners),
|
Self::BoxCorners(_) => concrete!(BoxCorners),
|
||||||
Self::Color(_) => concrete!(Color),
|
Self::Color(_) => concrete!(Color),
|
||||||
Self::Gradient(_) => concrete!(Gradient),
|
Self::GradientRamp(_) => concrete!(Gradient),
|
||||||
Self::BrushStrokes(_) => concrete!(BrushStroke),
|
Self::BrushStrokes(_) => concrete!(BrushStroke),
|
||||||
// =======================
|
// =======================
|
||||||
// AUTO-GENERATED VARIANTS
|
// AUTO-GENERATED VARIANTS
|
||||||
@@ -307,7 +307,7 @@ macro_rules! tagged_value {
|
|||||||
Self::DashPattern(_) => scalar::<DashPattern>(),
|
Self::DashPattern(_) => scalar::<DashPattern>(),
|
||||||
Self::BoxCorners(_) => scalar::<BoxCorners>(),
|
Self::BoxCorners(_) => scalar::<BoxCorners>(),
|
||||||
Self::Color(_) => leveled::<Color>(),
|
Self::Color(_) => leveled::<Color>(),
|
||||||
Self::Gradient(_) => leveled::<Gradient>(),
|
Self::GradientRamp(_) => leveled::<Gradient>(),
|
||||||
Self::BrushStrokes(_) => leveled::<BrushStroke>(),
|
Self::BrushStrokes(_) => leveled::<BrushStroke>(),
|
||||||
$( Self::$identifier(_) => scalar::<$ty>(), )*
|
$( Self::$identifier(_) => scalar::<$ty>(), )*
|
||||||
Self::RenderOutput(_) => scalar::<RenderOutput>(),
|
Self::RenderOutput(_) => scalar::<RenderOutput>(),
|
||||||
@@ -352,7 +352,7 @@ macro_rules! tagged_value {
|
|||||||
Self::DashPattern(lengths) => Ok(record_value_source(DashPattern::from(lengths))),
|
Self::DashPattern(lengths) => Ok(record_value_source(DashPattern::from(lengths))),
|
||||||
Self::BoxCorners(values) => Ok(record_value_source(BoxCorners::from(values))),
|
Self::BoxCorners(values) => Ok(record_value_source(BoxCorners::from(values))),
|
||||||
Self::Color(color) => Ok(leveled_record_value_source(vec![color])),
|
Self::Color(color) => Ok(leveled_record_value_source(vec![color])),
|
||||||
Self::Gradient(stops) => Ok(leveled_record_value_source(vec![stops])),
|
Self::GradientRamp(ramp) => Ok(leveled_record_value_source(vec![Gradient::from(ramp)])),
|
||||||
Self::BrushStrokes(strokes) => Ok(leveled_record_value_source(strokes)),
|
Self::BrushStrokes(strokes) => Ok(leveled_record_value_source(strokes)),
|
||||||
// =======================
|
// =======================
|
||||||
// AUTO-GENERATED VARIANTS
|
// AUTO-GENERATED VARIANTS
|
||||||
@@ -412,8 +412,7 @@ macro_rules! tagged_value {
|
|||||||
x if x == TypeId::of::<List<f64>>() => Ok(TaggedValue::F64Array(downcast::<List<f64>>(input).unwrap().iter_element_values().copied().collect())),
|
x if x == TypeId::of::<List<f64>>() => Ok(TaggedValue::F64Array(downcast::<List<f64>>(input).unwrap().iter_element_values().copied().collect())),
|
||||||
x if x == TypeId::of::<DashPattern>() => Ok(TaggedValue::DashPattern(downcast::<DashPattern>(input).unwrap().0.iter_element_values().copied().collect())),
|
x if x == TypeId::of::<DashPattern>() => Ok(TaggedValue::DashPattern(downcast::<DashPattern>(input).unwrap().0.iter_element_values().copied().collect())),
|
||||||
x if x == TypeId::of::<BoxCorners>() => Ok(TaggedValue::BoxCorners(downcast::<BoxCorners>(input).unwrap().0.iter_element_values().copied().collect())),
|
x if x == TypeId::of::<BoxCorners>() => Ok(TaggedValue::BoxCorners(downcast::<BoxCorners>(input).unwrap().0.iter_element_values().copied().collect())),
|
||||||
x if x == TypeId::of::<Color>() => Ok(TaggedValue::Color(*downcast(input).unwrap())),
|
x if x == TypeId::of::<Gradient>() => Ok(TaggedValue::GradientRamp(GradientRamp::from(*downcast::<Gradient>(input).unwrap()))),
|
||||||
x if x == TypeId::of::<Gradient>() => Ok(TaggedValue::Gradient(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<Vec<BrushStroke>>() => Ok(TaggedValue::BrushStrokes(*downcast(input).unwrap())),
|
x if x == TypeId::of::<Vec<BrushStroke>>() => Ok(TaggedValue::BrushStrokes(*downcast(input).unwrap())),
|
||||||
// =======================
|
// =======================
|
||||||
// AUTO-GENERATED VARIANTS
|
// AUTO-GENERATED VARIANTS
|
||||||
@@ -442,8 +441,7 @@ macro_rules! tagged_value {
|
|||||||
x if x == TypeId::of::<List<f64>>() => Ok(TaggedValue::F64Array(input.downcast_ref::<List<f64>>().unwrap().iter_element_values().copied().collect())),
|
x if x == TypeId::of::<List<f64>>() => Ok(TaggedValue::F64Array(input.downcast_ref::<List<f64>>().unwrap().iter_element_values().copied().collect())),
|
||||||
x if x == TypeId::of::<DashPattern>() => Ok(TaggedValue::DashPattern(input.downcast_ref::<DashPattern>().unwrap().0.iter_element_values().copied().collect())),
|
x if x == TypeId::of::<DashPattern>() => Ok(TaggedValue::DashPattern(input.downcast_ref::<DashPattern>().unwrap().0.iter_element_values().copied().collect())),
|
||||||
x if x == TypeId::of::<BoxCorners>() => Ok(TaggedValue::BoxCorners(input.downcast_ref::<BoxCorners>().unwrap().0.iter_element_values().copied().collect())),
|
x if x == TypeId::of::<BoxCorners>() => Ok(TaggedValue::BoxCorners(input.downcast_ref::<BoxCorners>().unwrap().0.iter_element_values().copied().collect())),
|
||||||
x if x == TypeId::of::<Color>() => Ok(TaggedValue::Color(*input.downcast_ref::<Color>().unwrap())),
|
x if x == TypeId::of::<Gradient>() => Ok(TaggedValue::GradientRamp(GradientRamp::from(input.downcast_ref::<Gradient>().unwrap()))),
|
||||||
x if x == TypeId::of::<Gradient>() => Ok(TaggedValue::Gradient(input.downcast_ref::<Gradient>().unwrap().clone())),
|
|
||||||
x if x == TypeId::of::<Vec<BrushStroke>>() => Ok(TaggedValue::BrushStrokes(input.downcast_ref::<Vec<BrushStroke>>().unwrap().clone())),
|
x if x == TypeId::of::<Vec<BrushStroke>>() => Ok(TaggedValue::BrushStrokes(input.downcast_ref::<Vec<BrushStroke>>().unwrap().clone())),
|
||||||
// =======================
|
// =======================
|
||||||
// AUTO-GENERATED VARIANTS
|
// AUTO-GENERATED VARIANTS
|
||||||
@@ -471,7 +469,7 @@ macro_rules! tagged_value {
|
|||||||
if name == core_types::normalize_type_name(std::any::type_name::<()>()) { return Some(TaggedValue::None) }
|
if name == core_types::normalize_type_name(std::any::type_name::<()>()) { return Some(TaggedValue::None) }
|
||||||
// List-wrapped types need a single-item default with the element's default, not an empty list
|
// List-wrapped types need a single-item default with the element's default, not an empty list
|
||||||
if name == core_types::normalize_type_name(std::any::type_name::<List<Color>>()) { return Some(TaggedValue::Color(Color::default())) }
|
if name == core_types::normalize_type_name(std::any::type_name::<List<Color>>()) { return Some(TaggedValue::Color(Color::default())) }
|
||||||
if name == core_types::normalize_type_name(std::any::type_name::<List<Gradient>>()) { return Some(TaggedValue::Gradient(Gradient::default())) }
|
if name == core_types::normalize_type_name(std::any::type_name::<List<Gradient>>()) { return Some(TaggedValue::GradientRamp(GradientRamp::default())) }
|
||||||
$( if name == core_types::normalize_type_name(std::any::type_name::<$ty>()) { return Some(TaggedValue::$identifier(Default::default())) } )*
|
$( if name == core_types::normalize_type_name(std::any::type_name::<$ty>()) { return Some(TaggedValue::$identifier(Default::default())) } )*
|
||||||
if name == core_types::normalize_type_name(std::any::type_name::<List<f64>>()) { return Some(TaggedValue::F64Array(Vec::new())) }
|
if name == core_types::normalize_type_name(std::any::type_name::<List<f64>>()) { return Some(TaggedValue::F64Array(Vec::new())) }
|
||||||
if name == core_types::normalize_type_name(std::any::type_name::<DashPattern>()) { return Some(TaggedValue::DashPattern(Vec::new())) }
|
if name == core_types::normalize_type_name(std::any::type_name::<DashPattern>()) { return Some(TaggedValue::DashPattern(Vec::new())) }
|
||||||
@@ -480,7 +478,7 @@ macro_rules! tagged_value {
|
|||||||
// Leveled inputs type by their element; each element name maps to the
|
// Leveled inputs type by their element; each element name maps to the
|
||||||
// same tagged default as its legacy list form.
|
// same tagged default as its legacy list form.
|
||||||
if name == core_types::normalize_type_name(std::any::type_name::<Color>()) { return Some(TaggedValue::Color(Color::default())) }
|
if name == core_types::normalize_type_name(std::any::type_name::<Color>()) { return Some(TaggedValue::Color(Color::default())) }
|
||||||
if name == core_types::normalize_type_name(std::any::type_name::<Gradient>()) { return Some(TaggedValue::Gradient(Gradient::default())) }
|
if name == core_types::normalize_type_name(std::any::type_name::<Gradient>()) { return Some(TaggedValue::GradientRamp(GradientRamp::default())) }
|
||||||
if name == core_types::normalize_type_name(std::any::type_name::<BrushStroke>()) { return Some(TaggedValue::BrushStrokes(Vec::new())) }
|
if name == core_types::normalize_type_name(std::any::type_name::<BrushStroke>()) { return Some(TaggedValue::BrushStrokes(Vec::new())) }
|
||||||
if name == core_types::normalize_type_name(std::any::type_name::<Graphic>()) { return Some(TaggedValue::TypeDefault(core_types::descriptor!(List<Graphic>))) }
|
if name == core_types::normalize_type_name(std::any::type_name::<Graphic>()) { return Some(TaggedValue::TypeDefault(core_types::descriptor!(List<Graphic>))) }
|
||||||
if name == core_types::normalize_type_name(std::any::type_name::<Artboard>()) { return Some(TaggedValue::TypeDefault(core_types::descriptor!(List<Artboard>))) }
|
if name == core_types::normalize_type_name(std::any::type_name::<Artboard>()) { return Some(TaggedValue::TypeDefault(core_types::descriptor!(List<Artboard>))) }
|
||||||
@@ -515,7 +513,7 @@ macro_rules! tagged_value {
|
|||||||
Self::DashPattern(lengths) => format!("DashPattern({lengths:?})"),
|
Self::DashPattern(lengths) => format!("DashPattern({lengths:?})"),
|
||||||
Self::BoxCorners(values) => format!("BoxCorners({values:?})"),
|
Self::BoxCorners(values) => format!("BoxCorners({values:?})"),
|
||||||
Self::Color(color) => format!("Color({color:?})"),
|
Self::Color(color) => format!("Color({color:?})"),
|
||||||
Self::Gradient(stops) => format!("Gradient({stops:?})"),
|
Self::GradientRamp(ramp) => format!("GradientRamp({ramp:?})"),
|
||||||
Self::BrushStrokes(strokes) => format!("BrushStrokes({strokes:?})"),
|
Self::BrushStrokes(strokes) => format!("BrushStrokes({strokes:?})"),
|
||||||
// =======================
|
// =======================
|
||||||
// AUTO-GENERATED VARIANTS
|
// AUTO-GENERATED VARIANTS
|
||||||
@@ -745,10 +743,10 @@ impl TaggedValue {
|
|||||||
() if ty == TypeId::of::<List<Color>>() => to_color(string).map(TaggedValue::Color)?,
|
() if ty == TypeId::of::<List<Color>>() => to_color(string).map(TaggedValue::Color)?,
|
||||||
// The Fill and Stroke nodes' paint connectors default to `List<Graphic>`, their first registered implementation row
|
// The Fill and Stroke nodes' paint connectors default to `List<Graphic>`, their first registered implementation row
|
||||||
() if ty == TypeId::of::<List<Graphic>>() => to_color(string).map(TaggedValue::Color)?,
|
() if ty == TypeId::of::<List<Graphic>>() => to_color(string).map(TaggedValue::Color)?,
|
||||||
() if ty == TypeId::of::<List<Gradient>>() => to_gradient(string).map(TaggedValue::Gradient)?,
|
() if ty == TypeId::of::<List<Gradient>>() => to_gradient(string).map(|gradient| TaggedValue::GradientRamp(gradient.into()))?,
|
||||||
// A paint default also parses against the bare element forms, as a color or gradient literal
|
// A paint default also parses against the bare element forms, as a color or gradient literal
|
||||||
() if ty == TypeId::of::<Graphic>() => to_color(string).map(TaggedValue::Color)?,
|
() if ty == TypeId::of::<Graphic>() => to_color(string).map(TaggedValue::Color)?,
|
||||||
() if ty == TypeId::of::<Gradient>() => to_gradient(string).map(TaggedValue::Gradient)?,
|
() if ty == TypeId::of::<Gradient>() => to_gradient(string).map(|gradient| TaggedValue::GradientRamp(gradient.into()))?,
|
||||||
() if ty == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,
|
() if ty == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,
|
||||||
() if ty == TypeId::of::<DashPattern>() => TaggedValue::DashPattern(core_types::misc::parse_f64_list(string)),
|
() if ty == TypeId::of::<DashPattern>() => TaggedValue::DashPattern(core_types::misc::parse_f64_list(string)),
|
||||||
() if ty == TypeId::of::<BoxCorners>() => TaggedValue::BoxCorners(core_types::misc::parse_f64_list(string)),
|
() if ty == TypeId::of::<BoxCorners>() => TaggedValue::BoxCorners(core_types::misc::parse_f64_list(string)),
|
||||||
@@ -792,7 +790,7 @@ impl TaggedValue {
|
|||||||
/// - `Vector` (or alias `VectorData`):
|
/// - `Vector` (or alias `VectorData`):
|
||||||
/// - non-empty → `TaggedValue::VectorModification(<built from first element>)` (the document_migration's Path pass disambiguates this between SVG-import legacy and a discardable modern baked value via the input's `exposed` flag)
|
/// - non-empty → `TaggedValue::VectorModification(<built from first element>)` (the document_migration's Path pass disambiguates this between SVG-import legacy and a discardable modern baked value via the input's `exposed` flag)
|
||||||
/// - empty → `TaggedValue::TypeDefault(descriptor!(List<Vector>))`
|
/// - empty → `TaggedValue::TypeDefault(descriptor!(List<Vector>))`
|
||||||
/// - `FillChoice` → `TaggedValue::Color` (solid), `TaggedValue::Gradient` (gradient), or `TaggedValue::no_paint()` (none)
|
/// - `FillChoice` → `TaggedValue::Color` (solid), `TaggedValue::GradientRamp` (gradient), or `TaggedValue::no_paint()` (none)
|
||||||
///
|
///
|
||||||
/// All other tags (including ones with the modern shape) fall through to the standard derived `Deserialize` for `TaggedValue`.
|
/// All other tags (including ones with the modern shape) fall through to the standard derived `Deserialize` for `TaggedValue`.
|
||||||
// TODO: Eventually remove this document upgrade code
|
// TODO: Eventually remove this document upgrade code
|
||||||
@@ -848,14 +846,14 @@ pub fn deserialize_tagged_value_with_legacy_migration<'de, D: serde::Deserialize
|
|||||||
return Ok(MemoHash::new(TaggedValue::Color(color)));
|
return Ok(MemoHash::new(TaggedValue::Color(color)));
|
||||||
}
|
}
|
||||||
if let Some(gradient) = payload.get("Gradient") {
|
if let Some(gradient) = payload.get("Gradient") {
|
||||||
let gradient: Gradient = serde_json::from_value(gradient.clone()).map_err(serde::de::Error::custom)?;
|
let ramp = graphic_types::migrations::migrate_to_gradient_ramp(gradient.clone()).map_err(serde::de::Error::custom)?;
|
||||||
return Ok(MemoHash::new(TaggedValue::Gradient(gradient)));
|
return Ok(MemoHash::new(TaggedValue::GradientRamp(ramp)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ok(MemoHash::new(TaggedValue::no_paint()));
|
return Ok(MemoHash::new(TaggedValue::no_paint()));
|
||||||
}
|
}
|
||||||
// The gradient tags carried several shapes over time, disambiguated here: the ancient full struct (`start`/`end` keys) becomes `LegacyGradient`,
|
// The gradient tags carried several shapes over time, disambiguated here: the ancient full struct (`start`/`end` keys) becomes `LegacyGradient`,
|
||||||
// while the current stops struct, the old tuple list, and the legacy one-element table wrapper all parse as the stops value directly
|
// while the current ramp, the flat stops struct, the old tuple list, and the legacy one-element table wrapper all parse as the ramp value directly
|
||||||
"Gradient" | "GradientTable" | "GradientPositions" | "GradientStops" => {
|
"Gradient" | "GradientTable" | "GradientPositions" | "GradientStops" => {
|
||||||
let table_element = content
|
let table_element = content
|
||||||
.as_object()
|
.as_object()
|
||||||
@@ -866,7 +864,7 @@ pub fn deserialize_tagged_value_with_legacy_migration<'de, D: serde::Deserialize
|
|||||||
if let Some(array) = table_element
|
if let Some(array) = table_element
|
||||||
&& array.is_empty()
|
&& array.is_empty()
|
||||||
{
|
{
|
||||||
return Ok(MemoHash::new(TaggedValue::Gradient(Gradient::default())));
|
return Ok(MemoHash::new(TaggedValue::GradientRamp(GradientRamp::default())));
|
||||||
}
|
}
|
||||||
|
|
||||||
let payload = table_element.and_then(|array| array.first()).unwrap_or(content);
|
let payload = table_element.and_then(|array| array.first()).unwrap_or(content);
|
||||||
@@ -876,8 +874,8 @@ pub fn deserialize_tagged_value_with_legacy_migration<'de, D: serde::Deserialize
|
|||||||
return Ok(MemoHash::new(TaggedValue::LegacyGradient(gradient)));
|
return Ok(MemoHash::new(TaggedValue::LegacyGradient(gradient)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let gradient: Gradient = serde_json::from_value(payload.clone()).map_err(serde::de::Error::custom)?;
|
let ramp = graphic_types::migrations::migrate_to_gradient_ramp(payload.clone()).map_err(serde::de::Error::custom)?;
|
||||||
return Ok(MemoHash::new(TaggedValue::Gradient(gradient)));
|
return Ok(MemoHash::new(TaggedValue::GradientRamp(ramp)));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -1028,7 +1026,7 @@ mod leveled_edges {
|
|||||||
TaggedValue::F64Array(vec![1.]),
|
TaggedValue::F64Array(vec![1.]),
|
||||||
TaggedValue::Bool(true),
|
TaggedValue::Bool(true),
|
||||||
TaggedValue::TypeDefault(descriptor!(List<Vector>)),
|
TaggedValue::TypeDefault(descriptor!(List<Vector>)),
|
||||||
TaggedValue::Gradient(Default::default()),
|
TaggedValue::GradientRamp(Default::default()),
|
||||||
] {
|
] {
|
||||||
let layout = value.value_layout().unwrap();
|
let layout = value.value_layout().unwrap();
|
||||||
let edge = value.to_edge().unwrap();
|
let edge = value.to_edge().unwrap();
|
||||||
@@ -1085,3 +1083,74 @@ mod paint_default_parsing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod gradient_shape_migration {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn load(payload: serde_json::Value) -> TaggedValue {
|
||||||
|
deserialize_tagged_value_with_legacy_migration(payload)
|
||||||
|
.expect("The gradient payload should deserialize")
|
||||||
|
.into_inner()
|
||||||
|
.as_ref()
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn white() -> serde_json::Value {
|
||||||
|
serde_json::to_value(Color::WHITE).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn modern_ramp_payload_round_trips() {
|
||||||
|
let mut gradient = Gradient::from(vec![Color::BLACK, Color::WHITE]);
|
||||||
|
gradient.set_positions(&[0.2, 0.9]);
|
||||||
|
let value = TaggedValue::GradientRamp(GradientRamp::from(gradient));
|
||||||
|
|
||||||
|
let json = serde_json::to_value(&value).unwrap();
|
||||||
|
assert!(json.get("GradientRamp").and_then(|payload| payload.get("stops")).is_some(), "the payload should nest its stops: {json}");
|
||||||
|
assert_eq!(load(json), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Eventually remove this document upgrade code
|
||||||
|
#[test]
|
||||||
|
fn legacy_flat_stops_parse_faithfully() {
|
||||||
|
let json = serde_json::json!({ "Gradient": { "color": [white(), white()], "position": [0., 0.25], "midpoint": [0.5, 0.5] } });
|
||||||
|
let TaggedValue::GradientRamp(ramp) = load(json) else {
|
||||||
|
panic!("the flat stops should become a gradient ramp value")
|
||||||
|
};
|
||||||
|
|
||||||
|
let gradient = Gradient::from(ramp);
|
||||||
|
assert_eq!(gradient.positions(), vec![0., 0.25]);
|
||||||
|
assert!(gradient.has_midpoint_attribute(), "the flat form must parse faithfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Eventually remove this document upgrade code
|
||||||
|
#[test]
|
||||||
|
fn legacy_tuple_stops_parse_with_defaults_elided() {
|
||||||
|
let json = serde_json::json!({ "Gradient": [[0., white()], [1., white()]] });
|
||||||
|
let TaggedValue::GradientRamp(ramp) = load(json) else {
|
||||||
|
panic!("the tuple stops should become a gradient ramp value")
|
||||||
|
};
|
||||||
|
|
||||||
|
let gradient = Gradient::from(ramp);
|
||||||
|
assert_eq!(gradient.positions(), vec![0., 1.]);
|
||||||
|
assert!(!gradient.has_position_attribute(), "even legacy tuple positions should elide");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Eventually remove this document upgrade code
|
||||||
|
#[test]
|
||||||
|
fn empty_legacy_gradient_table_degrades_to_the_default() {
|
||||||
|
let json = serde_json::json!({ "GradientTable": { "element": [] } });
|
||||||
|
assert_eq!(load(json), TaggedValue::GradientRamp(GradientRamp::default()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Eventually remove this document upgrade code
|
||||||
|
#[test]
|
||||||
|
fn ancient_full_struct_routes_to_legacy_gradient() {
|
||||||
|
let json = serde_json::json!({ "Gradient": { "stops": [[0., white()], [1., white()]], "gradient_type": "Linear", "start": [0., 0.], "end": [1., 0.] } });
|
||||||
|
let TaggedValue::LegacyGradient(legacy) = load(json) else {
|
||||||
|
panic!("the ancient full struct should become a legacy gradient value")
|
||||||
|
};
|
||||||
|
assert_eq!(Gradient::from(legacy.stops).positions(), vec![0., 1.], "the nested tuple stops should parse through the field adapter");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ pub use markers::{ATTR_EDITOR_MERGED_LAYERS, ATTR_FILL, ATTR_STROKE};
|
|||||||
|
|
||||||
pub mod migrations {
|
pub mod migrations {
|
||||||
use crate::Vector;
|
use crate::Vector;
|
||||||
|
use core_types::Color;
|
||||||
|
use vector_types::gradient::GradientStops;
|
||||||
|
use vector_types::{Gradient, GradientRamp};
|
||||||
|
|
||||||
// Storing legacy structs that are only used in document migration.
|
// Storing legacy structs that are only used in document migration.
|
||||||
// TODO: Eventually remove this document upgrade code
|
// TODO: Eventually remove this document upgrade code
|
||||||
@@ -23,11 +26,12 @@ pub mod migrations {
|
|||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use vector_types::vector::{PointDomain, RegionDomain, SegmentDomain, misc::HandleId, style::Stroke};
|
use vector_types::vector::{PointDomain, RegionDomain, SegmentDomain, misc::HandleId, style::Stroke};
|
||||||
use vector_types::{Gradient, Vector, vector};
|
use vector_types::{GradientRamp, Vector, vector};
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct LegacyGradient {
|
pub struct LegacyGradient {
|
||||||
pub stops: Gradient,
|
#[serde(deserialize_with = "crate::migrations::migrate_to_gradient_ramp")]
|
||||||
|
pub stops: GradientRamp,
|
||||||
pub gradient_type: vector::style::GradientType,
|
pub gradient_type: vector::style::GradientType,
|
||||||
pub start: DVec2,
|
pub start: DVec2,
|
||||||
pub end: DVec2,
|
pub end: DVec2,
|
||||||
@@ -145,6 +149,33 @@ pub mod migrations {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Eventually remove this document upgrade code
|
||||||
|
/// Recovers a [`GradientRamp`] from any of its on-disk shapes: the current nested form, the flat stops struct
|
||||||
|
/// that preceded it, or the ancient position-color tuple list (whose even positions elide back to absence).
|
||||||
|
pub fn migrate_to_gradient_ramp<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<GradientRamp, D::Error> {
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum GradientRampFormat {
|
||||||
|
Ramp(GradientRamp),
|
||||||
|
FlatStops(GradientStops<Color>),
|
||||||
|
Tuples(Vec<(f64, Color)>),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(match GradientRampFormat::deserialize(deserializer)? {
|
||||||
|
GradientRampFormat::Ramp(ramp) => ramp,
|
||||||
|
GradientRampFormat::FlatStops(stops) => GradientRamp::from(stops),
|
||||||
|
GradientRampFormat::Tuples(stops) => {
|
||||||
|
let position: Vec<f64> = stops.iter().map(|(position, _)| *position).collect();
|
||||||
|
let mut gradient = Gradient::from(stops.into_iter().map(|(_, color)| color).collect::<Vec<_>>());
|
||||||
|
gradient.set_positions(&position);
|
||||||
|
gradient.elide_default_attributes();
|
||||||
|
GradientRamp::from(gradient)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod migration_tests {
|
mod migration_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ pub enum GradientType {
|
|||||||
#[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash, DynAny)]
|
#[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash, DynAny)]
|
||||||
pub struct Gradient(List<Color>);
|
pub struct Gradient(List<Color>);
|
||||||
|
|
||||||
/// A gradient's per-stop parallel arrays, generic over color format: `GradientStops<Color>` is the document serialization
|
/// A gradient's per-stop parallel arrays, generic over color format: `GradientStops<Color>` nests inside the
|
||||||
/// of `TaggedValue::Gradient`, while `GradientStops<SRGBA8>` is the JS-boundary shape used by the color picker UI.
|
/// [`GradientRamp`] exchange struct, while `GradientStops<SRGBA8>` is the JS-boundary shape used by the color picker UI.
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
#[derive(Debug, Clone, PartialEq, Default)]
|
#[derive(Debug, Clone, PartialEq, Default, graphene_hash::CacheHash)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct GradientStops<C> {
|
pub struct GradientStops<C> {
|
||||||
pub color: Vec<C>,
|
pub color: Vec<C>,
|
||||||
@@ -96,34 +96,87 @@ impl GradientStops<SRGBA8> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
/// The serialized exchange form of a gradient: its stops, nested so that whole-ramp settings
|
||||||
impl serde::Serialize for Gradient {
|
/// like spread method can join as sibling fields opted in from their defaults.
|
||||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
GradientStops::<Color>::from(self).serialize(serializer)
|
#[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub struct GradientRamp<C = Color> {
|
||||||
|
pub stops: GradientStops<C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<C: dyn_any::StaticTypeSized> dyn_any::StaticType for GradientRamp<C> {
|
||||||
|
type Static = GradientRamp<C::Static>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> From<GradientStops<C>> for GradientRamp<C> {
|
||||||
|
fn from(stops: GradientStops<C>) -> Self {
|
||||||
|
Self { stops }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Eventually remove this document upgrade code
|
impl From<&Gradient> for GradientRamp {
|
||||||
#[cfg(feature = "serde")]
|
fn from(gradient: &Gradient) -> Self {
|
||||||
impl<'de> serde::Deserialize<'de> for Gradient {
|
Self { stops: gradient.into() }
|
||||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
}
|
||||||
#[derive(serde::Deserialize)]
|
}
|
||||||
#[serde(untagged)]
|
|
||||||
enum GradientStopsFormat {
|
|
||||||
Struct(GradientStops<Color>),
|
|
||||||
Tuples(Vec<(f64, Color)>),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(match GradientStopsFormat::deserialize(deserializer)? {
|
impl From<Gradient> for GradientRamp {
|
||||||
GradientStopsFormat::Struct(stops) => Gradient::from(stops),
|
fn from(gradient: Gradient) -> Self {
|
||||||
GradientStopsFormat::Tuples(stops) => {
|
Self::from(&gradient)
|
||||||
let position: Vec<f64> = stops.iter().map(|(p, _)| *p).collect();
|
}
|
||||||
let mut gradient = Gradient::from(stops.into_iter().map(|(_, c)| c).collect::<Vec<_>>());
|
}
|
||||||
gradient.set_positions(&position);
|
|
||||||
gradient.elide_default_attributes();
|
impl From<GradientRamp> for Gradient {
|
||||||
gradient
|
fn from(ramp: GradientRamp) -> Self {
|
||||||
}
|
Gradient::from(ramp.stops)
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&GradientRamp> for Gradient {
|
||||||
|
fn from(ramp: &GradientRamp) -> Self {
|
||||||
|
Gradient::from(ramp.stops.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&GradientRamp> for GradientStops<SRGBA8> {
|
||||||
|
fn from(ramp: &GradientRamp) -> Self {
|
||||||
|
Self {
|
||||||
|
position: ramp.stops.position.clone(),
|
||||||
|
midpoint: ramp.stops.midpoint.clone(),
|
||||||
|
color: ramp.stops.color.iter().map(|&color| SRGBA8::from(color)).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color picker round-trip: routes through the runtime type so default-restating attributes elide
|
||||||
|
impl From<&GradientStops<SRGBA8>> for GradientRamp {
|
||||||
|
fn from(stops: &GradientStops<SRGBA8>) -> Self {
|
||||||
|
Self::from(Gradient::from(stops))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&GradientRamp> for GradientRamp<SRGBA8> {
|
||||||
|
fn from(ramp: &GradientRamp) -> Self {
|
||||||
|
Self { stops: ramp.into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Gradient> for GradientRamp<SRGBA8> {
|
||||||
|
fn from(gradient: &Gradient) -> Self {
|
||||||
|
Self { stops: gradient.into() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&GradientRamp<SRGBA8>> for GradientRamp {
|
||||||
|
fn from(ramp: &GradientRamp<SRGBA8>) -> Self {
|
||||||
|
Self::from(&ramp.stops)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GradientRamp {
|
||||||
|
pub fn black_to_white() -> Self {
|
||||||
|
Self::from(Gradient::black_to_white())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -793,31 +846,17 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn serde_round_trip_preserves_attribute_absence() {
|
fn serde_round_trip_preserves_attribute_absence() {
|
||||||
let implicit = Gradient::from(vec![Color::BLACK, Color::WHITE]);
|
let implicit = GradientRamp::from(Gradient::from(vec![Color::BLACK, Color::WHITE]));
|
||||||
let json = serde_json::to_string(&implicit).unwrap();
|
let json = serde_json::to_string(&implicit).unwrap();
|
||||||
assert!(!json.contains("position") && !json.contains("midpoint"), "absent attributes must not serialize: {json}");
|
assert!(!json.contains("position") && !json.contains("midpoint"), "absent attributes must not serialize: {json}");
|
||||||
assert_eq!(serde_json::from_str::<Gradient>(&json).unwrap(), implicit);
|
assert_eq!(serde_json::from_str::<GradientRamp>(&json).unwrap(), implicit);
|
||||||
|
|
||||||
let mut explicit = implicit.clone();
|
let mut explicit = Gradient::from(vec![Color::BLACK, Color::WHITE]);
|
||||||
explicit.set_positions(&[0.2, 0.9]);
|
explicit.set_positions(&[0.2, 0.9]);
|
||||||
explicit.set_midpoints(&[0.3, 0.5]);
|
explicit.set_midpoints(&[0.3, 0.5]);
|
||||||
|
let explicit = GradientRamp::from(explicit);
|
||||||
let json = serde_json::to_string(&explicit).unwrap();
|
let json = serde_json::to_string(&explicit).unwrap();
|
||||||
assert_eq!(serde_json::from_str::<Gradient>(&json).unwrap(), explicit);
|
assert_eq!(serde_json::from_str::<GradientRamp>(&json).unwrap(), explicit);
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn legacy_tuple_format_deserializes_with_defaults_elided() {
|
|
||||||
let color = serde_json::to_value(Color::WHITE).unwrap();
|
|
||||||
|
|
||||||
let struct_format = serde_json::json!({ "position": [0., 0.25], "midpoint": [0.5, 0.5], "color": [color, color] });
|
|
||||||
let gradient: Gradient = serde_json::from_value(struct_format).unwrap();
|
|
||||||
assert_eq!(gradient.positions(), vec![0., 0.25]);
|
|
||||||
assert!(gradient.has_midpoint_attribute(), "the struct form must parse faithfully");
|
|
||||||
|
|
||||||
let tuple_format = serde_json::json!([[0., color], [1., color]]);
|
|
||||||
let gradient: Gradient = serde_json::from_value(tuple_format).unwrap();
|
|
||||||
assert_eq!(gradient.positions(), vec![0., 1.]);
|
|
||||||
assert!(!gradient.has_position_attribute(), "even legacy tuple positions should elide");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -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, GradientSpreadMethod, GradientStop, GradientType};
|
pub use gradient::{Gradient, GradientRamp, GradientSpreadMethod, GradientStop, GradientType};
|
||||||
pub use markers::{ATTR_EDITOR_CLICK_TARGET, ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD};
|
pub use markers::{ATTR_EDITOR_CLICK_TARGET, ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD};
|
||||||
pub use math::{QuadExt, RectExt};
|
pub use math::{QuadExt, RectExt};
|
||||||
pub use subpath::Subpath;
|
pub use subpath::Subpath;
|
||||||
|
|||||||
@@ -9,68 +9,65 @@ use dyn_any::DynAny;
|
|||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
use std::f64::consts::{PI, TAU};
|
use std::f64::consts::{PI, TAU};
|
||||||
|
|
||||||
/// The editor's in-memory paint picker state, storing color or gradient stops without gradient placement metadata.
|
/// The paint picker's choice of fill, generic over color format: `FillChoice<Color>` is the editor's in-memory
|
||||||
/// Not stored in documents: paint inputs hold the picked value as a plain color, gradient, or no-paint type default.
|
/// form, while `FillChoice<SRGBA8>` is the JS-boundary shape used by the color picker UI. Stores a color or
|
||||||
|
/// gradient ramp without gradient placement metadata, and is not stored in documents: paint inputs hold the
|
||||||
|
/// picked value as a plain color, gradient, or no-paint type default.
|
||||||
///
|
///
|
||||||
/// Can be None, a solid [Color], or a linear/radial [Gradient].
|
/// Can be None, a solid color, or the [`GradientRamp`] of a linear/radial gradient.
|
||||||
///
|
///
|
||||||
/// In the future we'll probably also add a pattern fill.
|
/// In the future we'll probably also add a pattern fill.
|
||||||
///
|
|
||||||
/// Use [`FillChoiceUI`] at the JS boundary.
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash, DynAny)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub enum FillChoice {
|
|
||||||
#[default]
|
|
||||||
None,
|
|
||||||
Solid(Color),
|
|
||||||
Gradient(Gradient),
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Deprecate [`FillChoice`] and keep this, renamed, as the main widget-controlling type
|
|
||||||
/// JS-boundary version of [`FillChoice`] where the solid color is [`SRGBA8`] and the gradient is its [`GradientStops`] exchange form.
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(from_wasm_abi))]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(from_wasm_abi))]
|
||||||
#[derive(Default, Debug, Clone, PartialEq, DynAny)]
|
#[derive(Default, Debug, Clone, PartialEq, graphene_hash::CacheHash)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum FillChoiceUI {
|
pub enum FillChoice<C = Color> {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
Solid(SRGBA8),
|
Solid(C),
|
||||||
Gradient(GradientStops<SRGBA8>),
|
Gradient(GradientRamp<C>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&FillChoice> for FillChoiceUI {
|
unsafe impl<C: dyn_any::StaticTypeSized> dyn_any::StaticType for FillChoice<C> {
|
||||||
|
type Static = FillChoice<C::Static>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&FillChoice> for FillChoice<SRGBA8> {
|
||||||
fn from(value: &FillChoice) -> Self {
|
fn from(value: &FillChoice) -> Self {
|
||||||
match value {
|
match value {
|
||||||
FillChoice::None => Self::None,
|
FillChoice::None => Self::None,
|
||||||
FillChoice::Solid(color) => Self::Solid(SRGBA8::from(*color)),
|
FillChoice::Solid(color) => Self::Solid(SRGBA8::from(*color)),
|
||||||
FillChoice::Gradient(stops) => Self::Gradient(stops.into()),
|
FillChoice::Gradient(ramp) => Self::Gradient(ramp.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&FillChoiceUI> for FillChoice {
|
impl From<&FillChoice<SRGBA8>> for FillChoice {
|
||||||
fn from(value: &FillChoiceUI) -> Self {
|
fn from(value: &FillChoice<SRGBA8>) -> Self {
|
||||||
match value {
|
match value {
|
||||||
FillChoiceUI::None => Self::None,
|
FillChoice::None => Self::None,
|
||||||
FillChoiceUI::Solid(srgba) => Self::Solid(Color::from(*srgba)),
|
FillChoice::Solid(srgba) => Self::Solid(Color::from(*srgba)),
|
||||||
FillChoiceUI::Gradient(stops) => Self::Gradient(Gradient::from(stops)),
|
FillChoice::Gradient(ramp) => Self::Gradient(ramp.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FillChoiceUI {
|
impl<C: Copy> FillChoice<C> {
|
||||||
pub fn as_solid(&self) -> Option<SRGBA8> {
|
pub fn as_solid(&self) -> Option<C> {
|
||||||
let Self::Solid(c) = self else { return None };
|
let Self::Solid(color) = self else { return None };
|
||||||
Some(*c)
|
Some(*color)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_gradient(&self) -> Option<&GradientStops<SRGBA8>> {
|
impl<C> FillChoice<C> {
|
||||||
let Self::Gradient(g) = self else { return None };
|
pub fn as_gradient(&self) -> Option<&GradientRamp<C>> {
|
||||||
Some(g)
|
let Self::Gradient(ramp) = self else { return None };
|
||||||
|
Some(ramp)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Build a CSS `background-image` string (always a `linear-gradient(...)`) representing this fill, or `None` if the fill is [`FillChoiceUI::None`].
|
impl FillChoice<SRGBA8> {
|
||||||
|
/// Build a CSS `background-image` string (always a `linear-gradient(...)`) representing this fill, or `None` if the fill is [`FillChoice::None`].
|
||||||
/// Solid colors become a degenerate gradient between the same color so the CSS variable can always be assigned to a `background-image`.
|
/// Solid colors become a degenerate gradient between the same color so the CSS variable can always be assigned to a `background-image`.
|
||||||
pub fn to_css_background_image(&self) -> Option<String> {
|
pub fn to_css_background_image(&self) -> Option<String> {
|
||||||
match self {
|
match self {
|
||||||
@@ -79,31 +76,7 @@ impl FillChoiceUI {
|
|||||||
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(stops) => Some(stops.to_css_linear_gradient()),
|
Self::Gradient(ramp) => Some(ramp.stops.to_css_linear_gradient()),
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FillChoice {
|
|
||||||
pub fn as_solid(&self) -> Option<Color> {
|
|
||||||
let Self::Solid(color) = self else { return None };
|
|
||||||
Some(*color)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_gradient(&self) -> Option<&Gradient> {
|
|
||||||
let Self::Gradient(gradient) = self else { return None };
|
|
||||||
Some(gradient)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build a CSS `background-image` string (always a `linear-gradient(...)`) representing this fill, or `None` if the fill is [`FillChoice::None`]. Solid colors become a degenerate gradient between the same color so the CSS variable can always be assigned to a `background-image`.
|
|
||||||
pub fn to_css_background_image(&self) -> Option<String> {
|
|
||||||
match self {
|
|
||||||
Self::None => None,
|
|
||||||
Self::Solid(color) => {
|
|
||||||
let hex = SRGBA8::from(*color).to_rgba_hex();
|
|
||||||
Some(format!("linear-gradient(#{hex}, #{hex})"))
|
|
||||||
}
|
|
||||||
Self::Gradient(stops) => Some(stops.to_css_linear_gradient()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user