Brushes: Pattern Brush Options' OK leaves corner tiles that were not set

OK used to save None for a corner tile the dialog showed as None because
the brush had no art or source for it, so that corner stopped drawing the
other kind of corner's art once the brush got some. It also copied the
art a corner borrows from the other kind of corner when that corner kept
its art. Now OK saves a corner tile's choice only when it was set from its
menu or with `ui.dialog.set`, or when the art it borrows goes away.
This commit is contained in:
dotCooCoo
2026-10-29 11:22:56 +07:00
committed by bflatastic
parent d4a30cc084
commit 1c4894b2f7
4 changed files with 75 additions and 3 deletions
+1 -1
View File
@@ -327,7 +327,7 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context, req: &ControlReques
return err(format!("the `{}` dialog has no field `{f}`; its fields: {names}", d.kind));
}
for (f, value) in fields {
d.fields.insert(f, value);
crate::dialogs::set_field(d, f, value);
}
ok(serde_json::to_value(&*d).unwrap_or_default())
}
+9
View File
@@ -410,6 +410,15 @@ pub fn accepts_field(d: &Dialog, field: &str) -> bool {
|| (spec.passes)(d, field)
}
/// Set `field` of `d` to `value` for `ui.dialog.set`, as the dialog's own control for it does: a
/// Pattern Brush Options corner tile set this way is saved by OK, as one chosen from its menu is.
pub fn set_field(d: &mut Dialog, field: String, value: Value) {
if d.kind == brush_options::KIND {
brush_art_options::note_set(d, &field);
}
d.fields.insert(field, value);
}
/// The fields `ui.dialog.set` can set on `d`, sorted, without the `__` ones: those it holds,
/// `discard`, `preview` and the spec's `inputs` for its kind.
pub fn field_names(d: &Dialog) -> Vec<String> {
@@ -63,6 +63,24 @@ fn corner_art_field<'a>(def: Option<&Value>, field: &'a str) -> Option<&'a str>
}
}
/// The dialog field that records that corner tile `key`'s menu was set.
fn corner_set_key(key: &str) -> String {
format!("__{key}Set")
}
/// Sets corner tile `key` to `choice` from its menu.
pub(super) fn choose_corner(d: &mut Dialog, key: &str, choice: &str) {
d.fields.insert(key.into(), json!(choice));
note_set(d, key);
}
/// Records that field `field` was set, from its menu or `ui.dialog.set`, when it is a corner tile.
pub(super) fn note_set(d: &mut Dialog, field: &str) {
if CORNER_TILES.iter().any(|(key, ..)| *key == field) {
d.fields.insert(corner_set_key(field), json!(true));
}
}
/// The most key colours offered.
const MAX_COLORS: usize = 16;
@@ -236,14 +254,21 @@ pub(super) fn params(d: &Dialog) -> Value {
});
// None or an automatic tile replaces the corner art. Original keeps the art the corner
// draws, the other kind of corner's art included, so changing the other kind leaves it.
// A None the menu was not set to, on a corner without art or a source, leaves the
// definition as it is: that corner still draws the other kind's art when it gets some.
let def = d.fields.get("__def");
for (key, art, source, _) in CORNER_TILES {
for (key, art, source, other) in CORNER_TILES {
let choice = d.str(key);
let unset = |f: &str| def.and_then(|d| d.get(f)).is_none_or(Value::is_null);
if choice == CornerSource::None.id() && !d.bool(&corner_set_key(key)) && unset(source) && corner_art_field(def, art).is_none() {
continue;
}
if let Some(s) = CornerSource::parse(&choice) {
p[art] = Value::Null;
p[source] = json!(s.id());
} else if choice == "original"
&& let Some(from) = corner_art_field(def, art).filter(|f| *f != art)
&& CORNER_TILES.iter().find(|(_, a, _, _)| *a == other).is_some_and(|(k, ..)| d.str(k) != "original")
{
p[art] = def.and_then(|d| d.get(from)).cloned().unwrap_or_default();
}
@@ -480,7 +505,7 @@ fn tiles(ui: &mut egui::Ui, d: &mut Dialog) {
if let Some((id, _)) =
widgets::dropdown_named(ui, ("brush-pattern-corner", key), Some(name), current, &labels, 76.0).and_then(|i| choices.get(i))
{
d.fields.insert(key.into(), json!(id));
choose_corner(d, key, id);
}
}
});
@@ -509,6 +509,44 @@ fn pattern_options_keep_the_corner_art_a_corner_borrows() {
assert_eq!(def["outer_source"], json!("none"));
}
/// OK leaves the corner fields of a corner tile the menus were not set to as they were: a corner
/// without art keeps borrowing the other kind's art when it gets some, and a corner that borrows
/// art still borrows it. A None picked from the menu is saved, after another choice too.
#[test]
fn pattern_options_ok_leaves_corner_tiles_the_menus_were_not_set_to() {
let mut app = app();
app.run("ui.brushOptions", json!({"name": "Diamonds"})).unwrap();
set(&mut app, json!({"scale": 150}));
super::super::confirm(&mut app).unwrap();
let def = app.run("brush.get", json!({"name": "Diamonds"})).unwrap();
assert!(def.get("outer_source").is_none() && def.get("inner_source").is_none(), "{def}");
let art = app.run("brush.get", json!({"name": "Chain"})).unwrap()["outer_corner"].clone();
app.run("brush.options", json!({"name": "Diamonds", "params": {"outer_corner": art.clone()}})).unwrap();
app.run("ui.brushOptions", json!({"name": "Diamonds"})).unwrap();
assert_eq!(app.ui.dialog.as_ref().unwrap().str("innerCornerTile"), "original", "the inner corners draw the outer art");
super::super::confirm(&mut app).unwrap();
let def = app.run("brush.get", json!({"name": "Diamonds"})).unwrap();
assert!(def["outer_corner"].is_object() && def.get("inner_corner").is_none(), "{def}");
app.run("brush.new", json!({"type": "pattern", "name": "Plain", "params": {"side": art}})).unwrap();
app.run("ui.brushOptions", json!({"name": "Plain"})).unwrap();
let d = app.ui.dialog.as_mut().unwrap();
crate::dialogs::brush_art_options::choose_corner(d, "outerCornerTile", "autoCentered");
crate::dialogs::brush_art_options::choose_corner(d, "outerCornerTile", "none");
super::super::confirm(&mut app).unwrap();
let def = app.run("brush.get", json!({"name": "Plain"})).unwrap();
assert_eq!(def["outer_source"], json!("none"));
assert!(def.get("inner_source").is_none(), "{def}");
// None set through the control channel is saved too.
app.run("ui.brushOptions", json!({"name": "Plain"})).unwrap();
let (set, _) = crate::control::ControlRequest::new("ui.dialog.set", json!({"field": "innerCornerTile", "value": "none"}));
let crate::control::Outcome::Done(r) = crate::control::handle(&mut app, &egui::Context::default(), &set) else { panic!("not done") };
assert!(r.get("error").is_none(), "{r}");
super::super::confirm(&mut app).unwrap();
let def = app.run("brush.get", json!({"name": "Plain"})).unwrap();
assert_eq!(def["inner_source"], json!("none"));
}
/// Art Brush Options' Width and Pattern Brush Options' Scale each have a slider beside their
/// field, which screen readers name as the row, with the field's number and text over the
/// value's whole range.