Merge pull request #933 from storytold/fix/921-properties-gradient

Fix #921: the Properties panel's Gradient section with Type and Draw
This commit is contained in:
BFlat 2026-10-10 15:51:05 +07:00 committed by GitHub
commit fe2d2ca173
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 72 additions and 14 deletions

File diff suppressed because one or more lines are too long

View File

@ -95,6 +95,43 @@ fn dragged_stops(origin: &[GradientStop], i: usize, offset: f32, copy: bool, ove
// ---------- shared edits ----------
/// The Type row: Linear, Radial and Freeform for the paint behind the active proxy (one of them
/// lit when it is a gradient). The Gradient panel's and the Properties panel's.
pub(crate) fn type_row(app: &mut VectorcraftApp, ui: &mut Ui) {
let kind = current(app).map(|g| g.gradient.kind);
ui.horizontal(|ui| {
widgets::dim_label(ui, tl!("Type:"));
for (k, icon, tip) in [
(GradientKind::Linear, "dc-grad-linear", tl!("Linear Gradient")),
(GradientKind::Radial, "dc-grad-radial", tl!("Radial Gradient")),
(GradientKind::Freeform, "dc-grad-freeform", tl!("Freeform Gradient")),
] {
if widgets::icon_button(ui, icon, tip, kind == Some(k), 24.0).clicked() {
edit(app, json!({"kind": k.label().to_lowercase()}), Live::Released);
}
}
});
}
/// Set how the Gradient tool adds the points of the freeform gradient behind the active proxy.
fn set_draw_mode(app: &mut VectorcraftApp, mode: FreeformMode) {
edit(app, json!({ "mode": mode.label().to_lowercase() }), Live::Released);
}
/// The Properties panel's Draw row for a freeform gradient: Points and Lines as radio buttons.
pub(crate) fn draw_radios(app: &mut VectorcraftApp, ui: &mut Ui) {
let Some(g) = current(app).filter(|g| g.gradient.kind == GradientKind::Freeform) else { return };
let mode = shown_points(&g).mode;
ui.horizontal(|ui| {
widgets::dim_label(ui, tl!("Draw:"));
for (m, label) in [(FreeformMode::Points, tl!("Points")), (FreeformMode::Lines, tl!("Lines"))] {
if widgets::radio(ui, label, mode == m, true) && mode != m {
set_draw_mode(app, m);
}
}
});
}
/// The gradient behind the active proxy.
fn current(app: &VectorcraftApp) -> Option<GradientPaint> {
match active_paint(app) {
@ -184,18 +221,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
}
ui.add_space(4.0);
ui.vertical(|ui| {
ui.horizontal(|ui| {
widgets::dim_label(ui, tl!("Type:"));
for (k, icon, tip) in [
(GradientKind::Linear, "dc-grad-linear", tl!("Linear Gradient")),
(GradientKind::Radial, "dc-grad-radial", tl!("Radial Gradient")),
(GradientKind::Freeform, "dc-grad-freeform", tl!("Freeform Gradient")),
] {
if widgets::icon_button(ui, icon, tip, is_grad && kind == k, 24.0).clicked() {
edit(app, json!({"kind": k.label().to_lowercase()}), Live::Released);
}
}
});
type_row(app, ui);
if widgets::flat_button(ui, tl!("Edit Gradient"), 96.0).clicked() {
app.select_tool("gradient");
}
@ -297,7 +323,7 @@ fn freeform_section(app: &mut VectorcraftApp, ui: &mut Ui, g: &GradientPaint) {
(FreeformMode::Lines, "spline", tl!("Lines: clicks add points joined by a line")),
] {
if widgets::icon_button(ui, icon, tip, mode == m, 24.0).clicked() && mode != m {
edit(app, json!({ "mode": m.label().to_lowercase() }), Live::Released);
set_draw_mode(app, m);
}
}
});

View File

@ -40,6 +40,13 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
}
transform_section(app, ui);
divider(ui);
// The Gradient tool: the gradient's type and, for a freeform one, how clicks add points (#921).
if app.session.tool_id() == "gradient" {
section_header(ui, tl!("Gradient"));
super::gradient::type_row(app, ui);
super::gradient::draw_radios(app, ui);
divider(ui);
}
let is_image = n_sel == 1 && matches!(first.as_ref().map(|n| &n.kind), Some(NodeKind::Image(_)));
if is_image {
image_section(app, ui);
@ -563,6 +570,31 @@ mod tests {
frame(app, ctx, vec![b(false)]);
}
/// #921: with the Gradient tool, a Gradient section: the type, and for a freeform gradient
/// Draw as Points or Lines radio buttons.
#[test]
fn the_gradient_tool_shows_the_gradient_type_and_draw() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 200, "height": 100})).unwrap();
let id = app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 50, "height": 40})).unwrap()["id"].as_u64().unwrap();
app.run("paint.setFill", json!({"gradient": {}})).unwrap();
let ctx = egui::Context::default();
let has = |texts: &[(String, Rect)], s: &str| texts.iter().any(|(t, _)| t == s);
let texts = frame(&mut app, &ctx, vec![]);
assert!(!has(&texts, "Gradient"), "only with the Gradient tool");
app.select_tool("gradient");
let texts = frame(&mut app, &ctx, vec![]);
assert!(has(&texts, "Gradient") && has(&texts, "Type:") && !has(&texts, "Draw:"), "{texts:?}");
app.run("paint.editGradient", json!({"kind": "freeform"})).unwrap();
frame(&mut app, &ctx, vec![]);
let texts = frame(&mut app, &ctx, vec![]);
assert!(has(&texts, "Draw:") && has(&texts, "Points") && has(&texts, "Lines"), "{texts:?}");
click(&mut app, &ctx, &texts, "Lines");
let fill = app.session.doc().unwrap().doc.node(vectorcraft_doc::NodeId(id)).unwrap().appearance.fill_paint();
let vectorcraft_color::Paint::Gradient(g) = fill else { panic!("{fill:?}") };
assert_eq!(super::super::gradient::shown_points(&g).mode, vectorcraft_color::FreeformMode::Lines);
}
/// #696: the number fields are all as wide: the Transform fields, the rotation, the corner
/// radius, Opacity and the stroke weight.
#[test]