mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Swatches panel follows the selection's swatch
Selecting art scrolls the tiles to its swatch once (centred) and clears the panel's own selection, so the art's swatch is the one highlighted; so does a change of the selection's paint (after Recolor Artwork). An unlinked process colour highlights the swatch of that very colour. A selected image, or text in a colour no swatch has, highlights nothing (not None). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
committed by
bflatastic
co-authored by
Claude Opus 5.5
parent
5a43337a42
commit
91e1434cbf
@@ -654,6 +654,7 @@ fn body(app: &mut VectorcraftApp, ui: &mut Ui, salt: &'static str) {
|
||||
let query = if pstate(ui.ctx(), "swatch-show-find") { widgets::search_field(ui, find_id(), tl!("Find")) } else { String::new() };
|
||||
let items = entries(app, kind, &query);
|
||||
let active_swatch = active_swatch(app);
|
||||
let focus = focus_swatch(app, ui, active_swatch.as_deref());
|
||||
let selected = selection(app, ui);
|
||||
// Without a selection the swatch of the active paint is highlighted.
|
||||
let fallback: Vec<String> = if selected.is_empty() { active_swatch.into_iter().collect() } else { vec![] };
|
||||
@@ -669,6 +670,9 @@ fn body(app: &mut VectorcraftApp, ui: &mut Ui, salt: &'static str) {
|
||||
for e in &items {
|
||||
let name = e.name();
|
||||
let (r, resp, chip) = list_row(ui, tile_id(salt, e), view, is_sel(name));
|
||||
if focus.as_deref() == Some(name) {
|
||||
ui.scroll_to_rect(r, Some(egui::Align::Center));
|
||||
}
|
||||
match e {
|
||||
Entry::Registration => draw_registration(ui, chip),
|
||||
Entry::Swatch { paint, global, spot, .. } => {
|
||||
@@ -685,6 +689,9 @@ fn body(app: &mut VectorcraftApp, ui: &mut Ui, salt: &'static str) {
|
||||
for (e, cell) in tile_grid(ui, &items, view, Entry::is_folder) {
|
||||
let resp = ui.interact(cell, tile_id(salt, e), Sense::click_and_drag());
|
||||
let name = e.name();
|
||||
if focus.as_deref() == Some(name) {
|
||||
ui.scroll_to_rect(cell, Some(egui::Align::Center));
|
||||
}
|
||||
match e {
|
||||
Entry::Registration => draw_registration(ui, cell),
|
||||
Entry::Swatch { paint, global, spot, .. } => {
|
||||
@@ -729,15 +736,49 @@ fn body(app: &mut VectorcraftApp, ui: &mut Ui, salt: &'static str) {
|
||||
bottom(app, ui, &selected);
|
||||
}
|
||||
|
||||
/// The document and the objects selected in it.
|
||||
type SelectedArt = (u64, Vec<u64>);
|
||||
|
||||
/// The swatch the tiles scroll to this frame, once: the active paint's swatch when it changed, or
|
||||
/// when other art was selected. Selecting other art also clears the panel's own selection, so its
|
||||
/// swatch is the one highlighted.
|
||||
fn focus_swatch(app: &VectorcraftApp, ui: &Ui, active: Option<&str>) -> Option<String> {
|
||||
let art: Option<SelectedArt> = app.session.active().map(|st| (st.uid, st.selection.objects.iter().map(|id| id.0).collect()));
|
||||
// (None the first time the panel shows.)
|
||||
let (last_art, last_swatch): (Option<Option<SelectedArt>>, Option<String>) = pstate(ui.ctx(), "swatch-focus");
|
||||
let reselected = last_art.as_ref().is_some_and(|l| *l != art);
|
||||
if reselected {
|
||||
set_pstate(ui.ctx(), "swatch-selected", Vec::<String>::new());
|
||||
}
|
||||
let changed = reselected || last_art.is_none() || last_swatch.as_deref() != active;
|
||||
set_pstate(ui.ctx(), "swatch-focus", (Some(art), active.map(str::to_string)));
|
||||
active.filter(|_| changed).map(str::to_string)
|
||||
}
|
||||
|
||||
/// The swatch the active paint comes from: a linked colour's global swatch (a tint's tint swatch
|
||||
/// when there is one), a gradient's gradient swatch, or None.
|
||||
/// when there is one), a process colour's swatch of that colour (a colour no swatch has: none, so
|
||||
/// text in a colour of its own highlights nothing), a gradient's gradient swatch, or None; nothing
|
||||
/// for selected images.
|
||||
fn active_swatch(app: &VectorcraftApp) -> Option<String> {
|
||||
// Images and placed files have no fill or stroke: no swatch (not None).
|
||||
let st = app.session.active()?;
|
||||
let unpainted = |id: &vectorcraft_doc::NodeId| {
|
||||
st.doc.node(*id).is_some_and(|n| matches!(n.kind, vectorcraft_doc::NodeKind::Image(_) | vectorcraft_doc::NodeKind::PlacedDocument(_)))
|
||||
};
|
||||
if !st.selection.objects.is_empty() && st.selection.objects.iter().all(unpainted) {
|
||||
return None;
|
||||
}
|
||||
match active_paint(app) {
|
||||
Paint::Solid { swatch: Some(n), tint, .. } => {
|
||||
let d = &app.session.active()?.doc;
|
||||
let tint_swatch = d.swatches_iter().find(|w| w.tint_of() == Some((n.as_str(), tint)));
|
||||
Some(tint_swatch.map_or(n.clone(), |w| w.name.clone()))
|
||||
}
|
||||
// A process colour isn't linked: the swatch of that very colour.
|
||||
Paint::Solid { color, swatch: None, .. } => {
|
||||
let d = &app.session.active()?.doc;
|
||||
d.swatches_iter().find(|w| matches!(w.paint, Paint::Solid { color: c, swatch: None, .. } if c == color)).map(|w| w.name.clone())
|
||||
}
|
||||
Paint::Gradient(g) => g.swatch,
|
||||
Paint::None => Some("[None]".to_string()),
|
||||
_ => None,
|
||||
@@ -1626,6 +1667,54 @@ mod tests {
|
||||
assert_eq!((d.kind.as_str(), d.str("__swatch").as_str()), (KIND, "Ink"), "a tint swatch opens its base's options");
|
||||
}
|
||||
|
||||
/// Selecting art scrolls the tiles to its swatch, once, and clears the panel's own selection so
|
||||
/// that swatch is the one highlighted.
|
||||
#[test]
|
||||
fn selecting_art_scrolls_to_its_swatch() {
|
||||
let mut app = app();
|
||||
let colors: Vec<String> = (0..400).map(|i| format!("#{:02x}{:02x}{:02x}", i % 256, (i / 2) % 256, 128)).collect();
|
||||
app.run("swatch.new", json!({ "colors": colors })).unwrap();
|
||||
app.run("swatch.new", json!({"name": "Last Ink", "color": "#123456", "spot": true})).unwrap();
|
||||
let id = app.run("shape.rectangle", json!({"x": 0, "y": 0, "width": 50, "height": 50})).unwrap()["id"].clone();
|
||||
app.run("paint.setFill", json!({"ids": [id], "swatch": "Last Ink"})).unwrap();
|
||||
app.run("select.none", json!({})).unwrap();
|
||||
let ctx = context();
|
||||
let tile = |ctx: &egui::Context| ctx.read_response(egui::Id::new(("swatch-tile", "Last Ink"))).unwrap().rect;
|
||||
frame(&mut app, &ctx, vec![], 0.0, show);
|
||||
frame(&mut app, &ctx, vec![], 0.1, show);
|
||||
let far = tile(&ctx).top();
|
||||
set_pstate(&ctx, "swatch-selected", vec!["Red".to_string()]);
|
||||
app.run("select.set", json!({"ids": [id]})).unwrap();
|
||||
for t in [0.2, 0.3, 0.4] {
|
||||
frame(&mut app, &ctx, vec![], t, show);
|
||||
}
|
||||
let near = tile(&ctx).top();
|
||||
assert!(near < far - 100.0, "scrolled to the swatch: {far} → {near}");
|
||||
assert!(selected(&ctx).is_empty(), "the art's swatch is highlighted");
|
||||
// Once: later frames leave the scroll where the user puts it.
|
||||
assert_eq!(focus_swatch(&app, &egui::Ui::new(ctx.clone(), egui::Id::new("t"), egui::UiBuilder::new()), Some("Last Ink")), None);
|
||||
}
|
||||
|
||||
/// A selected image, or text in a colour no swatch has, highlights no swatch (not None); text in
|
||||
/// a swatch's colour highlights that swatch.
|
||||
#[test]
|
||||
fn images_and_unswatched_text_highlight_no_swatch() {
|
||||
let mut app = app();
|
||||
let mut png = vec![];
|
||||
image::RgbaImage::from_pixel(4, 4, image::Rgba([255, 0, 0, 255]))
|
||||
.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png)
|
||||
.unwrap();
|
||||
let data = vectorcraft_format::base64_encode(&png);
|
||||
app.run("file.place", json!({"name": "a.png", "dataBase64": data, "link": false})).unwrap();
|
||||
assert_eq!(active_swatch(&app), None, "an image: no swatch");
|
||||
let text = app.run("text.create", json!({"x": 10, "y": 50, "text": "Hi"})).unwrap()["id"].clone();
|
||||
app.run("select.set", json!({"ids": [text]})).unwrap();
|
||||
app.run("paint.setFill", json!({"color": "#3a7b5c"})).unwrap();
|
||||
assert_eq!(active_swatch(&app), None, "a colour of its own: no swatch");
|
||||
app.run("swatch.new", json!({"name": "Moss", "color": "#3a7b5c"})).unwrap();
|
||||
assert_eq!(active_swatch(&app).as_deref(), Some("Moss"), "a swatch's colour");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn medium_tiles_match_measured_metrics() {
|
||||
assert_eq!(View::MediumThumb.tile(), (15.5, 17.0));
|
||||
|
||||
Reference in New Issue
Block a user