feat(render): handle mesh-gradient + shader fills across editor consumers

vendor/jian ff39a41 added PenFill::MeshGradient / PenFill::Shader plus
SceneGradient::Mesh and SceneNode.shader; every exhaustive match over
those types stopped compiling. Cover them with faithful conservative
semantics: opacity getters/setters treat the new bodies like their
gradient siblings, variable binding walks mesh vertex stops (shader
uniforms are untyped — left alone), canvas paint degrades mesh to its
first vertex colour until the RenderBackend grows a mesh method, the
property panel reports the neutral Solid type (no lossy conversion
offered), and lint contrast checks skip both like image fills.
This commit is contained in:
Kayshen-X 2026-07-03 00:08:19 +08:00
parent 0ba60361d4
commit 061296c779
9 changed files with 72 additions and 2 deletions

View file

@ -186,6 +186,9 @@ fn first_solid_color(fills: Option<&Vec<PenFill>>) -> Option<String> {
}
}
PenFill::Image(_) => continue,
// Mesh gradients / SkSL shaders have no single representative
// color for contrast checks — treated like image fills.
PenFill::MeshGradient(_) | PenFill::Shader(_) => continue,
}
}
None

View file

@ -348,6 +348,8 @@ pub fn first_solid_fill_opacity(node: &PenNode) -> f32 {
PenFill::Solid(b) => b.opacity.unwrap_or(1.0),
PenFill::LinearGradient(b) => b.opacity.unwrap_or(1.0),
PenFill::RadialGradient(b) => b.opacity.unwrap_or(1.0),
PenFill::MeshGradient(b) => b.opacity.unwrap_or(1.0),
PenFill::Shader(b) => b.opacity.unwrap_or(1.0),
PenFill::Image(b) => b.opacity.unwrap_or(1.0),
})
.unwrap_or(1.0)
@ -367,6 +369,8 @@ pub fn first_solid_stroke_opacity(node: &PenNode) -> f32 {
PenFill::Solid(b) => b.opacity.unwrap_or(1.0),
PenFill::LinearGradient(b) => b.opacity.unwrap_or(1.0),
PenFill::RadialGradient(b) => b.opacity.unwrap_or(1.0),
PenFill::MeshGradient(b) => b.opacity.unwrap_or(1.0),
PenFill::Shader(b) => b.opacity.unwrap_or(1.0),
PenFill::Image(b) => b.opacity.unwrap_or(1.0),
})
.unwrap_or(1.0)
@ -475,6 +479,8 @@ pub fn set_primary_fill_opacity(node: &mut PenNode, opacity: f32) -> bool {
PenFill::Solid(b) => b.opacity = Some(opacity),
PenFill::LinearGradient(b) => b.opacity = Some(opacity),
PenFill::RadialGradient(b) => b.opacity = Some(opacity),
PenFill::MeshGradient(b) => b.opacity = Some(opacity),
PenFill::Shader(b) => b.opacity = Some(opacity),
PenFill::Image(b) => b.opacity = Some(opacity),
}
true
@ -618,6 +624,10 @@ pub fn fill_type_of(fill: &PenFill) -> FillType {
PenFill::Solid(_) => FillType::Solid,
PenFill::LinearGradient(_) => FillType::LinearGradient,
PenFill::RadialGradient(_) => FillType::RadialGradient,
// The property panel has no mesh-gradient / shader picker rows
// yet — report the neutral default so the panel stays usable
// without offering a lossy conversion.
PenFill::MeshGradient(_) | PenFill::Shader(_) => FillType::Solid,
PenFill::Image(_) => FillType::Image,
}
}
@ -692,6 +702,9 @@ fn fill_hex(fill: &PenFill) -> Option<&str> {
PenFill::Solid(body) => Some(body.color.as_str()),
PenFill::LinearGradient(body) => body.stops.first().map(|s| s.color.as_str()),
PenFill::RadialGradient(body) => body.stops.first().map(|s| s.color.as_str()),
PenFill::MeshGradient(body) => body.stops.first().map(|s| s.color.as_str()),
// A shader has no representative colour.
PenFill::Shader(_) => None,
PenFill::Image(_) => None,
}
}

View file

@ -242,6 +242,18 @@ pub(crate) fn paint_gradient_rect(
*opacity,
);
}
SceneGradient::Mesh {
colors, opacity, ..
} => {
// No mesh-gradient method on the RenderBackend yet —
// degrade to the first vertex colour (same fallback the
// scene builder bakes into `SceneNode.fill`).
if let Some(first) = colors.first() {
let mut c = *first;
c.a = (c.a * opacity).clamp(0.0, 1.0);
cx.backend.fill_round_rect(rect, corner_radius, c);
}
}
}
}

View file

@ -729,7 +729,9 @@ pub(crate) fn paint_svg_path_node(
d, world_rect, &flat, *gx, *cy, *radius, *opacity,
);
}
None => {
// Mesh gradients have no svg-path shader yet — fall back to
// the node's resolved `fill` colour like the no-gradient path.
Some(SceneGradient::Mesh { .. }) | None => {
if let Some(fill) = node.fill {
cx.backend.fill_svg_path_in_rect(d, world_rect, fill);
}

View file

@ -1160,6 +1160,12 @@ fn fills_of(node: &PenNode) -> Vec<FillSummary> {
b.stops.first().map(|s| s.color.as_str()),
b.opacity.unwrap_or(1.0),
),
PenFill::MeshGradient(b) => (
b.stops.first().map(|s| s.color.as_str()),
b.opacity.unwrap_or(1.0),
),
// Shader fills have no representative colour.
PenFill::Shader(b) => (None, b.opacity.unwrap_or(1.0)),
PenFill::Image(b) => (None, b.opacity.unwrap_or(1.0)),
};
FillSummary {

View file

@ -105,6 +105,14 @@ fn bind_fills(fills: &mut [PenFill], refs: &ColorRefs) {
bind_color_string(&mut stop.color, refs);
}
}
PenFill::MeshGradient(body) => {
for stop in &mut body.stops {
bind_color_string(&mut stop.color, refs);
}
}
// Shader colours live in untyped uniform values — leave
// them alone rather than guess which uniforms are colours.
PenFill::Shader(_) => {}
PenFill::Image(_) => {}
}
}

View file

@ -216,6 +216,9 @@ fn node_payload_to_scene(
.gradient
.as_ref()
.map(|g| scale_gradient_opacity(payload_gradient_to_scene(g), cum_opacity)),
// The payload path doesn't carry SkSL shader bodies yet — the
// scene painter degrades to the resolved `fill` colour.
shader: None,
stroke: node.stroke.as_ref().map(|s| {
let mut st = scene_stroke(s, &node_id, var_table);
st.color = mul_alpha(st.color, cum_opacity);
@ -377,6 +380,17 @@ fn scale_gradient_opacity(g: SceneGradient, k: f32) -> SceneGradient {
opacity: (opacity * k).clamp(0.0, 1.0),
stops,
},
SceneGradient::Mesh {
rows,
cols,
colors,
opacity,
} => SceneGradient::Mesh {
rows,
cols,
colors,
opacity: (opacity * k).clamp(0.0, 1.0),
},
}
}

View file

@ -255,6 +255,18 @@ fn first_solid_color(fills: Option<&[PenFill]>) -> Option<[f32; 4]> {
}
}
}
PenFill::MeshGradient(body) => {
if let Some(stop) = body.stops.first() {
if let Some(rgba) = parse_hex(&stop.color) {
return Some(apply_alpha(rgba, body.opacity));
}
}
}
PenFill::Shader(body) => {
// Payload painters can't run SkSL — mid-gray fallback
// (mirrors SceneShader::fallback's default).
return Some(apply_alpha([0.5, 0.5, 0.5, 1.0], body.opacity));
}
PenFill::Image(_) => {
return Some([0.85, 0.86, 0.88, 1.0]);
}

2
vendor/jian vendored

@ -1 +1 @@
Subproject commit 8ed9a89fa46b13bb26524e3176b708b2ed6b2970
Subproject commit ff39a4163edee286fa7bd8c6afc969bcca2cbaec