Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use graphene_std::renderer::Quad;
use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
use graphene_std::table::Table;
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)]
pub struct GraphOperationMessageContext<'a> {
Expand Down Expand Up @@ -687,6 +687,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>) {
modify_inputs.fill_set(match &fill.paint() {
usvg::Paint::Color(color) => Fill::solid(usvg_color(*color, fill.opacity().get())),
Expand All @@ -709,8 +717,15 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, b
GradientStops::new(stops)
}
};

Fill::Gradient(Gradient { start, end, gradient_type, stops })
let spread_method = convert_spread_method(linear.spread_method());

Fill::Gradient(Gradient {
start,
end,
gradient_type,
stops,
spread_method,
})
}
usvg::Paint::RadialGradient(radial) => {
let gradient_transform = usvg_transform(radial.transform());
Expand All @@ -732,8 +747,15 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, b
GradientStops::new(stops)
}
};

Fill::Gradient(Gradient { start, end, gradient_type, stops })
let spread_method = convert_spread_method(radial.spread_method());

Fill::Gradient(Gradient {
start,
end,
gradient_type,
stops,
spread_method,
})
}
usvg::Paint::Pattern(_) => {
warn!("SVG patterns are not currently supported");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use graphene_std::transform::{Footprint, ReferencePoint, Transform};
use graphene_std::vector::QRCodeErrorCorrectionLevel;
use graphene_std::vector::misc::BooleanOperation;
use graphene_std::vector::misc::{ArcType, CentroidType, ExtrudeJoiningAlgorithm, GridType, 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> {
let widget = TextLabel::new(text).widget_instance();
Expand Down Expand Up @@ -2004,6 +2004,55 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
]);

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::SpreadMethod(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
Expand Down
106 changes: 101 additions & 5 deletions editor/src/messages/tool/tool_messages/gradient_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
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)]
pub struct GradientTool {
Expand All @@ -21,6 +21,7 @@ pub struct GradientTool {
#[derive(Default)]
pub struct GradientOptions {
gradient_type: GradientType,
spread_method: GradientSpreadMethod,
}

#[impl_message(Message, ToolMessage, Gradient)]
Expand Down Expand Up @@ -53,6 +54,7 @@ pub enum GradientOptionsUpdate {
Type(GradientType),
ReverseStops,
ReverseDirection,
SpreadMethod(GradientSpreadMethod),
}

impl ToolMetadata for GradientTool {
Expand Down Expand Up @@ -84,6 +86,10 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
GradientOptionsUpdate::ReverseDirection => {
apply_gradient_update(&mut self.data, context, responses, |_| true, |g| std::mem::swap(&mut g.start, &mut g.end));
}
GradientOptionsUpdate::SpreadMethod(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) => {
if self.data.color_picker_transaction_open {
Expand Down Expand Up @@ -123,6 +129,22 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
self.data.has_selected_gradient = has_gradient;
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);
}
};
}
}
}
Expand Down Expand Up @@ -168,7 +190,36 @@ impl LayoutHolder for GradientTool {
})
.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::SpreadMethod(GradientSpreadMethod::Pad),
}
.into()
}),
RadioEntryData::new("Reflect").label("Reflect").tooltip_label("Reflect").on_update(move |_| {
GradientToolMessage::UpdateOptions {
options: GradientOptionsUpdate::SpreadMethod(GradientSpreadMethod::Reflect),
}
.into()
}),
RadioEntryData::new("Repeat").label("Repeat").tooltip_label("Repeat").on_update(move |_| {
GradientToolMessage::UpdateOptions {
options: GradientOptionsUpdate::SpreadMethod(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 {
let orientation = self
Expand Down Expand Up @@ -1149,7 +1200,14 @@ impl Fsm for GradientToolFsmState {
gradient.clone()
} else {
// 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);
selected_gradient.dragging = GradientDragTarget::New;
Expand Down Expand Up @@ -1501,12 +1559,16 @@ fn apply_gradient_update(
responses.add(ToolMessage::RefreshToolOptions);
}

fn has_gradient_on_selected_layers(document: &DocumentMessageHandler) -> bool {
fn get_gradient_on_selected_layer(document: &DocumentMessageHandler) -> Option<Gradient> {
document
.network_interface
.selected_nodes()
.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)]
Expand Down Expand Up @@ -1941,4 +2003,38 @@ mod test_gradient {
// 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");
}

#[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::SpreadMethod(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::SpreadMethod(GradientSpreadMethod::Reflect),
})
.await;

let (gradient, _) = get_gradient(&mut editor).await;
assert_eq!(gradient.spread_method, GradientSpreadMethod::Reflect);
}
}
7 changes: 5 additions & 2 deletions node-graph/libraries/rendering/src/render_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,24 @@ impl RenderExt for Gradient {
format!(r#" gradientTransform="{gradient_transform}""#)
};

let spread_method = self.spread_method.svg_name();
let spread_method = if spread_method.is_empty() { String::new() } else { format!(r#" spreadMethod="{spread_method}""#) };

let gradient_id = generate_uuid();

match self.gradient_type {
GradientType::Linear => {
let _ = write!(
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
);
}
GradientType::Radial => {
let radius = (f64::powi(start.x - end.x, 2) + f64::powi(start.y - end.y, 2)).sqrt();
let _ = write!(
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
);
}
Expand Down
6 changes: 6 additions & 0 deletions node-graph/libraries/rendering/src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use std::fmt::Write;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::sync::{Arc, LazyLock};
use vector_types::gradient::GradientSpreadMethod;
use vello::*;

#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
Expand Down Expand Up @@ -1077,6 +1078,11 @@ impl Render for Table<Vector> {
.into()
}
},
extend: match gradient.spread_method {
GradientSpreadMethod::Pad => peniko::Extend::Pad,
GradientSpreadMethod::Reflect => peniko::Extend::Reflect,
GradientSpreadMethod::Repeat => peniko::Extend::Repeat,
},
stops,
interpolation_alpha_space: peniko::InterpolationAlphaSpace::Premultiplied,
..Default::default()
Expand Down
Loading
Loading