Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
703ab9a
Introduce the mesh gradient model and evaluator
YohYamasaki Aug 9, 2026
6fbe14f
Render mesh gradient paints in SVG and Vello
YohYamasaki Aug 16, 2026
d2c6adf
Support mesh gradients in Fill nodes
YohYamasaki Aug 16, 2026
7af29c9
Integrate mesh gradients into Fill properties
YohYamasaki Aug 16, 2026
6b83664
Add mesh gradient editing operations
YohYamasaki Aug 16, 2026
b2fb247
Add the mesh gradient editing tool
YohYamasaki Aug 16, 2026
6a0bd70
Refactor & Add corner alpha support
YohYamasaki Aug 16, 2026
9a1833d
Add support for more mesh gradient interpolation color spaces
YohYamasaki Aug 16, 2026
c8b340f
Add support for interpolation method change
YohYamasaki Aug 16, 2026
161a7c4
Improve displacement map quality and performance
YohYamasaki Aug 16, 2026
2fbed5a
Improve mesh gradient tool/property
YohYamasaki Aug 16, 2026
0ec523c
Fix subpatch inflation to consider transform
YohYamasaki Aug 16, 2026
40ea0d6
Make subdivision rendering viewport independent
YohYamasaki Aug 16, 2026
45c0ad6
Fix after AI review
YohYamasaki Aug 16, 2026
4ef8f6f
Merge branch 'master' into mesh-gradient-poc
YohYamasaki Aug 16, 2026
7958d0f
Fix after AI review
YohYamasaki Aug 17, 2026
7bf5041
Merge branch 'master' into mesh-gradient-poc
YohYamasaki Aug 17, 2026
8ca9fae
Fix after AI review
YohYamasaki Aug 17, 2026
8387968
Merge remote-tracking branch 'origin/master' into mesh-gradient-poc
YohYamasaki Aug 17, 2026
dd447d8
Fix after AI review
YohYamasaki Aug 17, 2026
8791bfb
Fix after AI review
YohYamasaki Aug 17, 2026
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions editor/src/messages/input_mapper/input_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,16 @@ pub fn input_mappings(zoom_with_scroll: bool) -> Mapping {
entry!(KeyDown(MouseRight); action_dispatch=GradientToolMessage::Abort),
entry!(KeyDown(Escape); action_dispatch=GradientToolMessage::Abort),
//
// MeshGradientToolMessage
entry!(DoubleClick(MouseButton::Left); action_dispatch=MeshGradientToolMessage::DoubleClick),
entry!(KeyDown(MouseLeft); action_dispatch=MeshGradientToolMessage::PointerDown),
entry!(PointerMove; refresh_keys=[Shift], action_dispatch=MeshGradientToolMessage::PointerMove { constrain_axis: Shift }),
entry!(KeyUp(MouseLeft); action_dispatch=MeshGradientToolMessage::PointerUp),
entry!(KeyDown(Delete); action_dispatch=MeshGradientToolMessage::DeleteEdge),
entry!(KeyDown(Backspace); action_dispatch=MeshGradientToolMessage::DeleteEdge),
entry!(KeyDown(MouseRight); action_dispatch=MeshGradientToolMessage::Abort),
entry!(KeyDown(Escape); action_dispatch=MeshGradientToolMessage::Abort),
//
// ShapeToolMessage
entry!(KeyDown(MouseLeft); action_dispatch=ShapeToolMessage::DragStart),
entry!(KeyUp(MouseLeft); action_dispatch=ShapeToolMessage::DragStop),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use graphene_std::vector::misc::{
ArcType, BooleanOperation, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType,
};
use graphene_std::vector::style::{
DashPattern, FillChoice, GradientForm, GradientHueDirection, GradientInterpolation, GradientRamp, GradientSettings, GradientSpace, GradientSpread, StrokeAlign, StrokeCap, StrokeJoin,
DashPattern, FillChoice, GradientForm, GradientHueDirection, GradientInterpolation, GradientRamp, GradientSettings, GradientSpace, GradientSpread, MeshGradient, StrokeAlign, StrokeCap, StrokeJoin,
};
use graphene_std::vector::{QRCodeErrorCorrectionLevel, Vector};
use graphene_std::{Appearance, Artboard, Color, Context, Cover, Coverage, Graphic};
Expand Down Expand Up @@ -208,6 +208,7 @@ fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'st
List<Raster<GPU>>,
List<Color>,
List<Gradient>,
List<MeshGradient>,
List<String>,
List<f64>,
List<f32>,
Expand Down Expand Up @@ -263,6 +264,7 @@ fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'st
Item<Raster<GPU>>,
Item<Color>,
Item<Gradient>,
Item<MeshGradient>,
Item<String>,
Item<f64>,
Item<f32>,
Expand Down Expand Up @@ -596,6 +598,7 @@ impl TableItemLayout for Graphic {
Self::RasterGPU(item) => item.identifier(),
Self::Color(item) => item.identifier(),
Self::Gradient(item) => item.identifier(),
Self::MeshGradient(item) => item.identifier(),
Self::Text(item) => item.identifier(),
Self::NoneList(list) => list.identifier(),
Self::GraphicList(list) => list.identifier(),
Expand All @@ -604,6 +607,7 @@ impl TableItemLayout for Graphic {
Self::RasterGPUList(list) => list.identifier(),
Self::ColorList(list) => list.identifier(),
Self::GradientList(list) => list.identifier(),
Self::MeshGradientList(list) => list.identifier(),
Self::TextList(list) => list.identifier(),
}
}
Expand All @@ -620,6 +624,7 @@ impl TableItemLayout for Graphic {
Self::RasterGPU(item) => item.layout_with_breadcrumb(data),
Self::Color(item) => item.layout_with_breadcrumb(data),
Self::Gradient(item) => item.layout_with_breadcrumb(data),
Self::MeshGradient(item) => item.layout_with_breadcrumb(data),
Self::Text(item) => item.layout_with_breadcrumb(data),
Self::NoneList(list) => list.layout_with_breadcrumb(data),
Self::GraphicList(list) => list.layout_with_breadcrumb(data),
Expand All @@ -628,6 +633,7 @@ impl TableItemLayout for Graphic {
Self::RasterGPUList(list) => list.layout_with_breadcrumb(data),
Self::ColorList(list) => list.layout_with_breadcrumb(data),
Self::GradientList(list) => list.layout_with_breadcrumb(data),
Self::MeshGradientList(list) => list.layout_with_breadcrumb(data),
Self::TextList(list) => list.layout_with_breadcrumb(data),
}
}
Expand Down Expand Up @@ -807,6 +813,33 @@ impl TableItemLayout for Gradient {
}
}

impl TableItemLayout for MeshGradient {
fn type_name() -> &'static str {
"MeshGradient"
}
fn identifier(&self) -> String {
format!("MeshGradient ({} corners)", self.size())
}
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
let mut rows = vec![column_headings(&["corner", "point ID", "position", "color"])];
rows.extend(self.corners().map(|corner| {
vec![
TextLabel::new(format!("{}", corner.index)).narrow(true).widget_instance(),
TextLabel::new(format!("{}", corner.point_id.inner())).narrow(true).widget_instance(),
TextLabel::new(format!("{}", corner.position)).narrow(true).widget_instance(),
corner
.color
.value_widgets(PathStep::Element(corner.index), data)
.into_iter()
.next()
.expect("Color always provides one value widget"),
]
}));

vec![LayoutGroup::table(rows, false)]
}
}

impl TableItemLayout for f64 {
fn type_name() -> &'static str {
"Number (f64)"
Expand Down Expand Up @@ -1233,6 +1266,7 @@ macro_rules! known_item_types {
List<Raster<GPU>>,
List<Color>,
List<Gradient>,
List<MeshGradient>,
List<String>,
List<f64>,
Gradient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use graphene_std::raster::BlendMode;
use graphene_std::raster_types::Image;
use graphene_std::subpath::Subpath;
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::MeshGradientSurface;
use graphene_std::vector::style::{GradientForm, GradientHueDirection, GradientInterpolation, GradientSettings, GradientSpace, GradientSpread, PaintOrder, Stroke};
use graphene_std::vector::{Gradient, PointId, VectorModificationType};

Expand All @@ -32,6 +33,10 @@ pub enum GraphOperationMessage {
gradient_settings: GradientSettings,
transform: DAffine2,
},
FillMeshGradientSet {
layer: LayerNodeIdentifier,
mesh_gradient: MeshGradientSurface,
},
BlendingFillSet {
layer: LayerNodeIdentifier,
fill: f64,
Expand Down Expand Up @@ -77,6 +82,10 @@ pub enum GraphOperationMessage {
layer: LayerNodeIdentifier,
gradient_interpolation: GradientInterpolation,
},
MeshGradientSet {
layer: LayerNodeIdentifier,
mesh_gradient: MeshGradientSurface,
},
OpacitySet {
layer: LayerNodeIdentifier,
opacity: f64,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
modify_inputs.fill_gradient_set(gradient, gradient_form, gradient_settings, transform);
}
}
GraphOperationMessage::FillMeshGradientSet { layer, mesh_gradient } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.fill_mesh_gradient_set(mesh_gradient);
}
}
GraphOperationMessage::BlendingFillSet { layer, fill } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.opacity_fill_set(fill);
Expand Down Expand Up @@ -110,6 +115,11 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
modify_inputs.gradient_interpolation_set(gradient_interpolation);
}
}
GraphOperationMessage::MeshGradientSet { layer, mesh_gradient } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.mesh_gradient_set(mesh_gradient);
}
}
GraphOperationMessage::OpacitySet { layer, opacity } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.opacity_set(opacity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
use crate::messages::portfolio::document::utility_types::network_interface::{self, FlowType, InputConnector, NodeNetworkInterface, OutputConnector};
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils::{
ReplaceablePaintChain, get_fill_input_node_id, get_upstream_gradient_value_node_id, gradient_chain_target_input, replaceable_paint_chain,
ReplaceablePaintChain, get_fill_input_node_id, get_upstream_gradient_value_node_id, get_upstream_mesh_gradient_value_node_id, gradient_chain_target_input, replaceable_paint_chain,
};
use glam::{DAffine2, DVec2, IVec2};
use graph_craft::application_io::resource::ResourceId;
Expand All @@ -18,6 +18,7 @@ use graphene_std::raster::BlendMode;
use graphene_std::raster_types::Image;
use graphene_std::subpath::Subpath;
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::MeshGradientSurface;
use graphene_std::vector::style::{GradientForm, GradientHueDirection, GradientInterpolation, GradientSettings, GradientSpace, GradientSpread, PaintOrder, Stroke};
use graphene_std::vector::{Gradient, GradientRamp, PointId, Vector, VectorModification, VectorModificationType};
use graphene_std::{Artboard, Color, Graphic};
Expand Down Expand Up @@ -587,6 +588,39 @@ impl<'a> ModifyInputsContext<'a> {
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(color), false), false);
}

/// Write the mesh gradient to the Fill node's direct value, adding a 'Fill' node to the layer when it has none.
pub fn fill_mesh_gradient_set(&mut self, mesh_gradient: MeshGradientSurface) {
let existing_fill_node_id = self.existing_chain_hosted_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, false);
let Some(fill_node_id) = existing_fill_node_id.or_else(|| self.existing_chain_hosted_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true)) else {
return;
};

self.set_input_with_refresh(
InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupMeshGradientInput),
NodeInput::value(TaggedValue::MeshGradient(mesh_gradient.clone()), false),
true,
);
self.set_input_with_refresh(
InputConnector::node(fill_node_id, graphene_std::vector::fill::PaintInput),
NodeInput::value(TaggedValue::MeshGradient(mesh_gradient), false),
false,
);

if existing_fill_node_id.is_none() {
self.restore_default_stroke_order();
}
}

/// Write the mesh gradient to the Mesh Gradient Value node feeding the layer.
pub fn mesh_gradient_set(&mut self, mesh_gradient: MeshGradientSurface) {
let Some(output_layer) = self.get_output_layer() else { return };
let Some(mesh_gradient_value_id) = get_upstream_mesh_gradient_value_node_id(output_layer, self.network_interface) else {
return;
};
let input_connector = InputConnector::node(mesh_gradient_value_id, graphene_std::math_nodes::mesh_gradient_value::MeshGradientInput);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::MeshGradient(mesh_gradient), false), false);
}

/// Write the gradient stops to the 'Gradient Value' node feeding the layer.
pub fn gradient_stops_set(&mut self, stops: Gradient) {
let Some(output_layer) = self.get_output_layer() else { return };
Expand Down
Loading
Loading