mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 09:38:11 +08:00
Add support for setting the spread method for gradient fills (#3953)
* Add spread method support for gradients * Add GradientSpreadMethod enum (Pad, Repeat, Reflect) to vector-types * Add radio buttons to gradient tool and fill properties panel * Convert spread method when importing SVGs via usvg * Sync backup gradient input when changing spread method * Table<GradientStops> rendering is not yet updated for spread method * Sync gradient tool options with layer's gradient * Sync gradient_type and spread_method from the selected layer's existing gradient to the tool options bar when switching to the gradient tool * Refactor has_gradient_on_selected_layers to reuse a new get_gradient_on_selected_layer helper * Swap Reflect and Repeat order in UI radio buttons * Fix alignment of the radio buttons in right panel * Fix the position of the radio buttons in the tool * Rename SpreadMethod to SetSpreadMethod * Move default spread method omission logic
This commit is contained in:
+25
-3
@@ -15,7 +15,7 @@ use graphene_std::renderer::Quad;
|
|||||||
use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
|
use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
|
||||||
use graphene_std::table::Table;
|
use graphene_std::table::Table;
|
||||||
use graphene_std::text::{Font, TypesettingConfig};
|
use graphene_std::text::{Font, TypesettingConfig};
|
||||||
use graphene_std::vector::style::{Fill, Gradient, GradientStop, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphene_std::vector::style::{Fill, Gradient, GradientSpreadMethod, GradientStop, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
|
|
||||||
#[derive(ExtractField)]
|
#[derive(ExtractField)]
|
||||||
pub struct GraphOperationMessageContext<'a> {
|
pub struct GraphOperationMessageContext<'a> {
|
||||||
@@ -765,6 +765,14 @@ fn apply_usvg_stroke(stroke: &usvg::Stroke, modify_inputs: &mut ModifyInputsCont
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn convert_spread_method(spread_method: usvg::SpreadMethod) -> GradientSpreadMethod {
|
||||||
|
match spread_method {
|
||||||
|
usvg::SpreadMethod::Pad => GradientSpreadMethod::Pad,
|
||||||
|
usvg::SpreadMethod::Reflect => GradientSpreadMethod::Reflect,
|
||||||
|
usvg::SpreadMethod::Repeat => GradientSpreadMethod::Repeat,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, bounds_transform: DAffine2, graphite_gradient_stops: &HashMap<String, GradientStops>) {
|
fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, bounds_transform: DAffine2, graphite_gradient_stops: &HashMap<String, GradientStops>) {
|
||||||
modify_inputs.fill_set(match &fill.paint() {
|
modify_inputs.fill_set(match &fill.paint() {
|
||||||
usvg::Paint::Color(color) => Fill::solid(usvg_color(*color, fill.opacity().get())),
|
usvg::Paint::Color(color) => Fill::solid(usvg_color(*color, fill.opacity().get())),
|
||||||
@@ -787,8 +795,15 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, b
|
|||||||
GradientStops::new(stops)
|
GradientStops::new(stops)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let spread_method = convert_spread_method(linear.spread_method());
|
||||||
|
|
||||||
Fill::Gradient(Gradient { start, end, gradient_type, stops })
|
Fill::Gradient(Gradient {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
gradient_type,
|
||||||
|
stops,
|
||||||
|
spread_method,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
usvg::Paint::RadialGradient(radial) => {
|
usvg::Paint::RadialGradient(radial) => {
|
||||||
let gradient_transform = usvg_transform(radial.transform());
|
let gradient_transform = usvg_transform(radial.transform());
|
||||||
@@ -810,8 +825,15 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, b
|
|||||||
GradientStops::new(stops)
|
GradientStops::new(stops)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let spread_method = convert_spread_method(radial.spread_method());
|
||||||
|
|
||||||
Fill::Gradient(Gradient { start, end, gradient_type, stops })
|
Fill::Gradient(Gradient {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
gradient_type,
|
||||||
|
stops,
|
||||||
|
spread_method,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
usvg::Paint::Pattern(_) => {
|
usvg::Paint::Pattern(_) => {
|
||||||
warn!("SVG patterns are not currently supported");
|
warn!("SVG patterns are not currently supported");
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ use graphene_std::transform::{Footprint, ReferencePoint, ScaleType, Transform};
|
|||||||
use graphene_std::vector::QRCodeErrorCorrectionLevel;
|
use graphene_std::vector::QRCodeErrorCorrectionLevel;
|
||||||
use graphene_std::vector::misc::BooleanOperation;
|
use graphene_std::vector::misc::BooleanOperation;
|
||||||
use graphene_std::vector::misc::{ArcType, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType};
|
use graphene_std::vector::misc::{ArcType, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType};
|
||||||
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientSpreadMethod, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
|
|
||||||
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
|
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
|
||||||
let widget = TextLabel::new(text).widget_instance();
|
let widget = TextLabel::new(text).widget_instance();
|
||||||
@@ -2006,6 +2006,55 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
widgets.push(LayoutGroup::row(row));
|
widgets.push(LayoutGroup::row(row));
|
||||||
|
|
||||||
|
let mut spread_methods_row: Vec<WidgetInstance> = vec![TextLabel::new("").widget_instance(), Separator::new(SeparatorStyle::Unrelated).widget_instance()];
|
||||||
|
|
||||||
|
let spread_method_entries = [GradientSpreadMethod::Pad, GradientSpreadMethod::Reflect, GradientSpreadMethod::Repeat]
|
||||||
|
.iter()
|
||||||
|
.map(|&spread_method| {
|
||||||
|
let gradient_for_input = gradient_for_closure.clone();
|
||||||
|
let gradient_for_backup = gradient_for_closure.clone();
|
||||||
|
|
||||||
|
let set_input_value = update_value(
|
||||||
|
move |_: &()| {
|
||||||
|
let mut new_gradient = gradient_for_input.clone();
|
||||||
|
new_gradient.spread_method = spread_method;
|
||||||
|
TaggedValue::Fill(Fill::Gradient(new_gradient))
|
||||||
|
},
|
||||||
|
node_id,
|
||||||
|
FillInput::<Color>::INDEX,
|
||||||
|
);
|
||||||
|
|
||||||
|
let set_backup_value = update_value(
|
||||||
|
move |_: &()| {
|
||||||
|
let mut new_gradient = gradient_for_backup.clone();
|
||||||
|
new_gradient.spread_method = spread_method;
|
||||||
|
TaggedValue::Gradient(new_gradient)
|
||||||
|
},
|
||||||
|
node_id,
|
||||||
|
BackupGradientInput::INDEX,
|
||||||
|
);
|
||||||
|
|
||||||
|
RadioEntryData::new(format!("{:?}", spread_method))
|
||||||
|
.label(format!("{:?}", spread_method))
|
||||||
|
.on_update(move |_| Message::Batched {
|
||||||
|
messages: Box::new([
|
||||||
|
set_input_value(&()),
|
||||||
|
set_backup_value(&()),
|
||||||
|
GradientToolMessage::UpdateOptions {
|
||||||
|
options: GradientOptionsUpdate::SetSpreadMethod(spread_method),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
.on_commit(commit_value)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
add_blank_assist(&mut spread_methods_row);
|
||||||
|
spread_methods_row.extend_from_slice(&[RadioInput::new(spread_method_entries).selected_index(Some(gradient.spread_method as u32)).widget_instance()]);
|
||||||
|
|
||||||
|
widgets.push(LayoutGroup::row(spread_methods_row));
|
||||||
}
|
}
|
||||||
|
|
||||||
widgets
|
widgets
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
|||||||
use crate::messages::tool::common_functionality::graph_modification_utils::{NodeGraphLayer, get_gradient};
|
use crate::messages::tool::common_functionality::graph_modification_utils::{NodeGraphLayer, get_gradient};
|
||||||
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
||||||
use graphene_std::raster::color::Color;
|
use graphene_std::raster::color::Color;
|
||||||
use graphene_std::vector::style::{Fill, Gradient, GradientStops, GradientType};
|
use graphene_std::vector::style::{Fill, Gradient, GradientSpreadMethod, GradientStops, GradientType};
|
||||||
|
|
||||||
#[derive(Default, ExtractField)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct GradientTool {
|
pub struct GradientTool {
|
||||||
@@ -21,6 +21,7 @@ pub struct GradientTool {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct GradientOptions {
|
pub struct GradientOptions {
|
||||||
gradient_type: GradientType,
|
gradient_type: GradientType,
|
||||||
|
spread_method: GradientSpreadMethod,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Gradient)]
|
#[impl_message(Message, ToolMessage, Gradient)]
|
||||||
@@ -53,6 +54,7 @@ pub enum GradientOptionsUpdate {
|
|||||||
Type(GradientType),
|
Type(GradientType),
|
||||||
ReverseStops,
|
ReverseStops,
|
||||||
ReverseDirection,
|
ReverseDirection,
|
||||||
|
SetSpreadMethod(GradientSpreadMethod),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToolMetadata for GradientTool {
|
impl ToolMetadata for GradientTool {
|
||||||
@@ -84,6 +86,10 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
|
|||||||
GradientOptionsUpdate::ReverseDirection => {
|
GradientOptionsUpdate::ReverseDirection => {
|
||||||
apply_gradient_update(&mut self.data, context, responses, |_| true, |g| std::mem::swap(&mut g.start, &mut g.end));
|
apply_gradient_update(&mut self.data, context, responses, |_| true, |g| std::mem::swap(&mut g.start, &mut g.end));
|
||||||
}
|
}
|
||||||
|
GradientOptionsUpdate::SetSpreadMethod(spread_method) => {
|
||||||
|
self.options.spread_method = spread_method;
|
||||||
|
apply_gradient_update(&mut self.data, context, responses, |g| g.spread_method != spread_method, |g| g.spread_method = spread_method);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
ToolMessage::Gradient(GradientToolMessage::StartTransactionForColorStop) => {
|
ToolMessage::Gradient(GradientToolMessage::StartTransactionForColorStop) => {
|
||||||
if self.data.color_picker_transaction_open {
|
if self.data.color_picker_transaction_open {
|
||||||
@@ -123,6 +129,22 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
|
|||||||
self.data.has_selected_gradient = has_gradient;
|
self.data.has_selected_gradient = has_gradient;
|
||||||
responses.add(ToolMessage::RefreshToolOptions);
|
responses.add(ToolMessage::RefreshToolOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync tool options with the selected layer's gradient
|
||||||
|
if has_gradient && let Some(gradient) = get_gradient_on_selected_layer(&context.document) {
|
||||||
|
let type_differs = self.options.gradient_type != gradient.gradient_type;
|
||||||
|
let spread_method_differs = self.options.spread_method != gradient.spread_method;
|
||||||
|
|
||||||
|
if type_differs {
|
||||||
|
self.options.gradient_type = gradient.gradient_type;
|
||||||
|
}
|
||||||
|
if spread_method_differs {
|
||||||
|
self.options.spread_method = gradient.spread_method;
|
||||||
|
}
|
||||||
|
if type_differs || spread_method_differs {
|
||||||
|
responses.add(ToolMessage::RefreshToolOptions);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,7 +190,36 @@ impl LayoutHolder for GradientTool {
|
|||||||
})
|
})
|
||||||
.widget_instance();
|
.widget_instance();
|
||||||
|
|
||||||
let mut widgets = vec![gradient_type, Separator::new(SeparatorStyle::Unrelated).widget_instance(), reverse_stops];
|
let spread_method = RadioInput::new(vec![
|
||||||
|
RadioEntryData::new("Pad").label("Pad").tooltip_label("Pad").on_update(move |_| {
|
||||||
|
GradientToolMessage::UpdateOptions {
|
||||||
|
options: GradientOptionsUpdate::SetSpreadMethod(GradientSpreadMethod::Pad),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
RadioEntryData::new("Reflect").label("Reflect").tooltip_label("Reflect").on_update(move |_| {
|
||||||
|
GradientToolMessage::UpdateOptions {
|
||||||
|
options: GradientOptionsUpdate::SetSpreadMethod(GradientSpreadMethod::Reflect),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
RadioEntryData::new("Repeat").label("Repeat").tooltip_label("Repeat").on_update(move |_| {
|
||||||
|
GradientToolMessage::UpdateOptions {
|
||||||
|
options: GradientOptionsUpdate::SetSpreadMethod(GradientSpreadMethod::Repeat),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
.selected_index(Some(self.options.spread_method as u32))
|
||||||
|
.widget_instance();
|
||||||
|
|
||||||
|
let mut widgets = vec![
|
||||||
|
gradient_type,
|
||||||
|
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||||
|
spread_method,
|
||||||
|
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||||
|
reverse_stops,
|
||||||
|
];
|
||||||
|
|
||||||
if self.options.gradient_type == GradientType::Radial {
|
if self.options.gradient_type == GradientType::Radial {
|
||||||
let orientation = self
|
let orientation = self
|
||||||
@@ -1149,7 +1200,14 @@ impl Fsm for GradientToolFsmState {
|
|||||||
gradient.clone()
|
gradient.clone()
|
||||||
} else {
|
} else {
|
||||||
// Generate a new gradient
|
// Generate a new gradient
|
||||||
Gradient::new(DVec2::ZERO, global_tool_data.secondary_color, DVec2::ONE, global_tool_data.primary_color, tool_options.gradient_type)
|
Gradient::new(
|
||||||
|
DVec2::ZERO,
|
||||||
|
global_tool_data.secondary_color,
|
||||||
|
DVec2::ONE,
|
||||||
|
global_tool_data.primary_color,
|
||||||
|
tool_options.gradient_type,
|
||||||
|
tool_options.spread_method,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
let mut selected_gradient = SelectedGradient::new(gradient, layer, document);
|
let mut selected_gradient = SelectedGradient::new(gradient, layer, document);
|
||||||
selected_gradient.dragging = GradientDragTarget::New;
|
selected_gradient.dragging = GradientDragTarget::New;
|
||||||
@@ -1501,12 +1559,16 @@ fn apply_gradient_update(
|
|||||||
responses.add(ToolMessage::RefreshToolOptions);
|
responses.add(ToolMessage::RefreshToolOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_gradient_on_selected_layers(document: &DocumentMessageHandler) -> bool {
|
fn get_gradient_on_selected_layer(document: &DocumentMessageHandler) -> Option<Gradient> {
|
||||||
document
|
document
|
||||||
.network_interface
|
.network_interface
|
||||||
.selected_nodes()
|
.selected_nodes()
|
||||||
.selected_visible_layers(&document.network_interface)
|
.selected_visible_layers(&document.network_interface)
|
||||||
.any(|layer| get_gradient(layer, &document.network_interface).is_some())
|
.find_map(|layer| get_gradient(layer, &document.network_interface))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_gradient_on_selected_layers(document: &DocumentMessageHandler) -> bool {
|
||||||
|
get_gradient_on_selected_layer(document).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@@ -1941,4 +2003,38 @@ mod test_gradient {
|
|||||||
// Additional verification that 0.75 stop is gone
|
// Additional verification that 0.75 stop is gone
|
||||||
assert!(!final_positions.iter().any(|pos| (pos - 0.75).abs() < 0.05), "Stop at position 0.75 should have been deleted");
|
assert!(!final_positions.iter().any(|pos| (pos - 0.75).abs() < 0.05), "Stop at position 0.75 should have been deleted");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn change_spread_method() {
|
||||||
|
use graphene_std::vector::style::GradientSpreadMethod;
|
||||||
|
|
||||||
|
let mut editor = EditorTestUtils::create();
|
||||||
|
editor.new_document().await;
|
||||||
|
editor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;
|
||||||
|
editor.drag_tool(ToolType::Gradient, 10., 10., 90., 90., ModifierKeys::empty()).await;
|
||||||
|
|
||||||
|
// Verify default spread method is Pad
|
||||||
|
let (gradient, _) = get_gradient(&mut editor).await;
|
||||||
|
assert_eq!(gradient.spread_method, GradientSpreadMethod::Pad);
|
||||||
|
|
||||||
|
// Update spread method to Repeat
|
||||||
|
editor
|
||||||
|
.handle_message(GradientToolMessage::UpdateOptions {
|
||||||
|
options: GradientOptionsUpdate::SetSpreadMethod(GradientSpreadMethod::Repeat),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let (gradient, _) = get_gradient(&mut editor).await;
|
||||||
|
assert_eq!(gradient.spread_method, GradientSpreadMethod::Repeat);
|
||||||
|
|
||||||
|
// Update spread method to Reflect
|
||||||
|
editor
|
||||||
|
.handle_message(GradientToolMessage::UpdateOptions {
|
||||||
|
options: GradientOptionsUpdate::SetSpreadMethod(GradientSpreadMethod::Reflect),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let (gradient, _) = get_gradient(&mut editor).await;
|
||||||
|
assert_eq!(gradient.spread_method, GradientSpreadMethod::Reflect);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use glam::DAffine2;
|
|||||||
use graphic_types::vector_types::gradient::{Gradient, GradientType};
|
use graphic_types::vector_types::gradient::{Gradient, GradientType};
|
||||||
use graphic_types::vector_types::vector::style::{Fill, PaintOrder, PathStyle, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphic_types::vector_types::vector::style::{Fill, PaintOrder, PathStyle, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
use vector_types::gradient::GradientSpreadMethod;
|
||||||
|
|
||||||
pub trait RenderExt {
|
pub trait RenderExt {
|
||||||
type Output;
|
type Output;
|
||||||
@@ -47,13 +48,19 @@ impl RenderExt for Gradient {
|
|||||||
format!(r#" gradientTransform="{gradient_transform}""#)
|
format!(r#" gradientTransform="{gradient_transform}""#)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let spread_method = if self.spread_method == GradientSpreadMethod::Pad {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
format!(r#" spreadMethod="{}""#, self.spread_method.svg_name())
|
||||||
|
};
|
||||||
|
|
||||||
let gradient_id = generate_uuid();
|
let gradient_id = generate_uuid();
|
||||||
|
|
||||||
match self.gradient_type {
|
match self.gradient_type {
|
||||||
GradientType::Linear => {
|
GradientType::Linear => {
|
||||||
let _ = write!(
|
let _ = write!(
|
||||||
svg_defs,
|
svg_defs,
|
||||||
r#"<linearGradient id="{}" x1="{}" y1="{}" x2="{}" y2="{}"{gradient_transform}>{}</linearGradient>"#,
|
r#"<linearGradient id="{}" x1="{}" y1="{}" x2="{}" y2="{}"{spread_method}{gradient_transform}>{}</linearGradient>"#,
|
||||||
gradient_id, start.x, start.y, end.x, end.y, stop
|
gradient_id, start.x, start.y, end.x, end.y, stop
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -61,7 +68,7 @@ impl RenderExt for Gradient {
|
|||||||
let radius = (f64::powi(start.x - end.x, 2) + f64::powi(start.y - end.y, 2)).sqrt();
|
let radius = (f64::powi(start.x - end.x, 2) + f64::powi(start.y - end.y, 2)).sqrt();
|
||||||
let _ = write!(
|
let _ = write!(
|
||||||
svg_defs,
|
svg_defs,
|
||||||
r#"<radialGradient id="{}" cx="{}" cy="{}" r="{}"{gradient_transform}>{}</radialGradient>"#,
|
r#"<radialGradient id="{}" cx="{}" cy="{}" r="{}"{spread_method}{gradient_transform}>{}</radialGradient>"#,
|
||||||
gradient_id, start.x, start.y, radius, stop
|
gradient_id, start.x, start.y, radius, stop
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ use std::fmt::Write;
|
|||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::{Arc, LazyLock};
|
use std::sync::{Arc, LazyLock};
|
||||||
|
use vector_types::gradient::GradientSpreadMethod;
|
||||||
use vello::*;
|
use vello::*;
|
||||||
|
|
||||||
/// Cached 16x16 transparency checkerboard image data (two 8x8 cells of #ffffff and #cccccc).
|
/// Cached 16x16 transparency checkerboard image data (two 8x8 cells of #ffffff and #cccccc).
|
||||||
@@ -1159,6 +1160,11 @@ impl Render for Table<Vector> {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
extend: match gradient.spread_method {
|
||||||
|
GradientSpreadMethod::Pad => peniko::Extend::Pad,
|
||||||
|
GradientSpreadMethod::Reflect => peniko::Extend::Reflect,
|
||||||
|
GradientSpreadMethod::Repeat => peniko::Extend::Repeat,
|
||||||
|
},
|
||||||
stops,
|
stops,
|
||||||
interpolation_alpha_space: peniko::InterpolationAlphaSpace::Premultiplied,
|
interpolation_alpha_space: peniko::InterpolationAlphaSpace::Premultiplied,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
@@ -334,6 +334,27 @@ impl GradientStops {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, DynAny, node_macro::ChoiceType)]
|
||||||
|
#[widget(Radio)]
|
||||||
|
pub enum GradientSpreadMethod {
|
||||||
|
#[default]
|
||||||
|
Pad,
|
||||||
|
Reflect,
|
||||||
|
Repeat,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GradientSpreadMethod {
|
||||||
|
pub fn svg_name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
GradientSpreadMethod::Pad => "pad",
|
||||||
|
GradientSpreadMethod::Reflect => "reflect",
|
||||||
|
GradientSpreadMethod::Repeat => "repeat",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A gradient fill.
|
/// A gradient fill.
|
||||||
///
|
///
|
||||||
/// Contains the start and end points, along with the colors at varying points along the length.
|
/// Contains the start and end points, along with the colors at varying points along the length.
|
||||||
@@ -345,6 +366,8 @@ pub struct Gradient {
|
|||||||
pub gradient_type: GradientType,
|
pub gradient_type: GradientType,
|
||||||
pub start: DVec2,
|
pub start: DVec2,
|
||||||
pub end: DVec2,
|
pub end: DVec2,
|
||||||
|
#[serde(default)]
|
||||||
|
pub spread_method: GradientSpreadMethod,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Gradient {
|
impl Default for Gradient {
|
||||||
@@ -354,6 +377,7 @@ impl Default for Gradient {
|
|||||||
gradient_type: GradientType::Linear,
|
gradient_type: GradientType::Linear,
|
||||||
start: DVec2::new(0., 0.5),
|
start: DVec2::new(0., 0.5),
|
||||||
end: DVec2::new(1., 0.5),
|
end: DVec2::new(1., 0.5),
|
||||||
|
spread_method: GradientSpreadMethod::Pad,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,6 +393,7 @@ impl std::hash::Hash for Gradient {
|
|||||||
.for_each(|x| x.to_bits().hash(state));
|
.for_each(|x| x.to_bits().hash(state));
|
||||||
self.stops.color.iter().for_each(|color| color.hash(state));
|
self.stops.color.iter().for_each(|color| color.hash(state));
|
||||||
self.gradient_type.hash(state);
|
self.gradient_type.hash(state);
|
||||||
|
self.spread_method.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,7 +412,7 @@ impl std::fmt::Display for Gradient {
|
|||||||
|
|
||||||
impl Gradient {
|
impl Gradient {
|
||||||
/// Constructs a new gradient with the colors at 0 and 1 specified.
|
/// Constructs a new gradient with the colors at 0 and 1 specified.
|
||||||
pub fn new(start: DVec2, start_color: Color, end: DVec2, end_color: Color, gradient_type: GradientType) -> Self {
|
pub fn new(start: DVec2, start_color: Color, end: DVec2, end_color: Color, gradient_type: GradientType, spread_method: GradientSpreadMethod) -> Self {
|
||||||
let stops = GradientStops::new([
|
let stops = GradientStops::new([
|
||||||
GradientStop {
|
GradientStop {
|
||||||
position: 0.,
|
position: 0.,
|
||||||
@@ -401,7 +426,13 @@ impl Gradient {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Self { start, end, stops, gradient_type }
|
Self {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
stops,
|
||||||
|
gradient_type,
|
||||||
|
spread_method,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lerp(&self, other: &Self, time: f64) -> Self {
|
pub fn lerp(&self, other: &Self, time: f64) -> Self {
|
||||||
@@ -414,8 +445,15 @@ impl Gradient {
|
|||||||
});
|
});
|
||||||
let stops = GradientStops::new(stops);
|
let stops = GradientStops::new(stops);
|
||||||
let gradient_type = if time < 0.5 { self.gradient_type } else { other.gradient_type };
|
let gradient_type = if time < 0.5 { self.gradient_type } else { other.gradient_type };
|
||||||
|
let spread_method = if time < 0.5 { self.spread_method } else { other.spread_method };
|
||||||
|
|
||||||
Self { start, end, stops, gradient_type }
|
Self {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
stops,
|
||||||
|
gradient_type,
|
||||||
|
spread_method,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a stop into the gradient, the index if successful
|
/// Insert a stop into the gradient, the index if successful
|
||||||
|
|||||||
Reference in New Issue
Block a user