mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-28 12:36:56 -06:00
Format: pattern edits keep preset and system colors, and the gallery shows focus
A pattern fill imported with a preset (prstClr) or system (sysClr) color turned that color black on any change in the gallery or either color menu, because the color parameter fell back to #000000 for those bases. Color parameters now carry them as {"preset": name} and {"system": name, "last": "#RRGGBB"}, with their transforms, and the engine reads both forms. A pattern swatch with keyboard focus now gets the same outline as a hovered or selected one.
This commit is contained in:
@@ -371,6 +371,8 @@ pub(crate) fn ok() -> Result<Value> {
|
||||
}
|
||||
|
||||
/// Parse a colour param: `"#RRGGBB"`, `"accent1"`…, or `{"scheme":"accent2","lumMod":75000}`.
|
||||
/// A preset or system colour keeps its base: `{"preset":"red"}`,
|
||||
/// `{"system":"windowText","last":"#000000"}`.
|
||||
pub(crate) fn color_param(p: &Value, key: &str) -> Option<deckcraft_model::ColorRef> {
|
||||
let v = p.get(key)?;
|
||||
color_value(v)
|
||||
@@ -391,6 +393,11 @@ pub(crate) fn color_value(v: &Value) -> Option<deckcraft_model::ColorRef> {
|
||||
Value::Object(o) => {
|
||||
let mut c = if let Some(s) = o.get("scheme").and_then(Value::as_str) {
|
||||
ColorRef::scheme(deckcraft_color::SchemeSlot::from_xml(s)?)
|
||||
} else if let Some(name) = o.get("preset").and_then(Value::as_str) {
|
||||
ColorRef { base: deckcraft_model::ColorBase::Preset { name: name.to_string() }, mods: vec![] }
|
||||
} else if let Some(name) = o.get("system").and_then(Value::as_str) {
|
||||
let last = deckcraft_color::Rgba::from_hex(o.get("last").and_then(Value::as_str)?)?;
|
||||
ColorRef { base: deckcraft_model::ColorBase::System { name: name.to_string(), last }, mods: vec![] }
|
||||
} else {
|
||||
ColorRef::rgb(deckcraft_color::Rgba::from_hex(o.get("rgb").and_then(Value::as_str)?)?)
|
||||
};
|
||||
@@ -639,4 +646,16 @@ mod tests {
|
||||
assert!(!needs_arguments(save.params));
|
||||
assert!(needs_arguments(audio.params));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preset_and_system_colors_keep_their_base() {
|
||||
use deckcraft_color::{ColorTransform, Rgba};
|
||||
use deckcraft_model::{ColorBase, ColorRef};
|
||||
use serde_json::json;
|
||||
let preset = color_value(&json!({"preset": "red", "lumMod": 50_000}));
|
||||
assert_eq!(preset, Some(ColorRef { base: ColorBase::Preset { name: "red".into() }, mods: vec![ColorTransform::LumMod(50_000)] }));
|
||||
let system = color_value(&json!({"system": "windowText", "last": "#123456"}));
|
||||
assert_eq!(system, Some(ColorRef { base: ColorBase::System { name: "windowText".into(), last: Rgba::rgb(0x12, 0x34, 0x56) }, mods: vec![] }));
|
||||
assert_eq!(color_value(&json!({"system": "windowText"})), None, "a system color needs its last value");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,7 +420,7 @@ pub fn pattern_editor(ui: &mut Ui, pt: &PatternFill, sc: &ColorScheme) -> Option
|
||||
let on = pt.preset == name;
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(22.0, 22.0), Sense::click());
|
||||
paint_pattern(ui.painter(), r.shrink(2.0), name, fg, bg);
|
||||
let stroke = if on || resp.hovered() { Stroke::new(2.0, t.accent) } else { Stroke::new(1.0, t.border) };
|
||||
let stroke = if on || resp.hovered() || resp.has_focus() { Stroke::new(2.0, t.accent) } else { Stroke::new(1.0, t.border) };
|
||||
ui.painter().rect_stroke(r.shrink(1.0), CornerRadius::same(2), stroke, egui::StrokeKind::Inside);
|
||||
let label = pattern_label(name);
|
||||
resp.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::SelectableLabel, true, on, label));
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use deckcraft_color::Rgba;
|
||||
use deckcraft_engine::Session;
|
||||
use deckcraft_model::style::PatternFill;
|
||||
use deckcraft_model::{Background, ColorRef, Fill, ShapeId};
|
||||
use deckcraft_model::{Background, ColorBase, ColorRef, Fill, ShapeId};
|
||||
use egui::accesskit::Role;
|
||||
use egui::{Event, Key, Modifiers, PointerButton};
|
||||
use egui_kittest::Harness;
|
||||
@@ -93,6 +93,45 @@ fn shape_pattern_fill_picks_a_preset_from_the_gallery() {
|
||||
assert_eq!(h.state().session.active().unwrap().history.undo.len(), undo + 1, "one undo step");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shape_pattern_fill_keeps_preset_and_system_colors() {
|
||||
let (mut s, id) = patterned_shape();
|
||||
let fg = json!({"preset": "red", "lumMod": 50000});
|
||||
let bg = json!({"system": "window", "last": "#F0F0F0"});
|
||||
s.execute("shape.fill", &json!({"pattern": {"preset": "pct25", "fg": fg, "bg": bg}})).unwrap();
|
||||
let mut h = harness(SlideApp::new(s, Services::default()), "format");
|
||||
let before = shape_pattern(&h, id);
|
||||
assert!(matches!(before.fg.base, ColorBase::Preset { .. }) && matches!(before.bg.base, ColorBase::System { .. }));
|
||||
h.get_by_label("Zig zag").click();
|
||||
h.run_steps(2);
|
||||
let pt = shape_pattern(&h, id);
|
||||
assert_eq!(pt.preset, "zigZag");
|
||||
assert_eq!((&pt.fg, &pt.bg), (&before.fg, &before.bg), "a preset change keeps both colors");
|
||||
pick_hex(&mut h, "Foreground color", "00FF00");
|
||||
assert_eq!(shape_pattern(&h, id).bg, before.bg, "a foreground change keeps the background");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_gallery_shows_which_swatch_has_keyboard_focus() {
|
||||
let (s, _) = patterned_shape();
|
||||
let mut h = harness(SlideApp::new(s, Services::default()), "format");
|
||||
let r = h.get_by_label("Zig zag").rect();
|
||||
let accent = crate::theme::Tokens::get(&h.ctx).accent;
|
||||
let outlined = |h: &Harness<'_, SlideApp>| {
|
||||
h.output().shapes.iter().any(|c| match &c.shape {
|
||||
egui::Shape::Rect(rs) => {
|
||||
rs.stroke.width == 2.0 && rs.stroke.color == accent && (rs.rect.center() - r.center()).length() < 0.5 && rs.rect.width() < r.width()
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
};
|
||||
assert!(!outlined(&h), "an unselected swatch has a plain border");
|
||||
h.get_by_label("Zig zag").focus();
|
||||
h.run_steps(2);
|
||||
assert!(h.get_by_label("Zig zag").is_focused());
|
||||
assert!(outlined(&h), "the focused swatch is outlined");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shape_solid_fill_keeps_its_transparency_without_a_pattern_gallery() {
|
||||
let (mut s, _) = patterned_shape();
|
||||
@@ -129,3 +168,17 @@ fn background_pattern_fill_offers_every_preset_and_both_colors() {
|
||||
let pt = background_pattern(&h);
|
||||
assert_eq!((pt.preset.as_str(), pt.bg), ("zigZag", ColorRef::rgb(Rgba::from_hex("FFEE00").unwrap())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn background_pattern_fill_keeps_a_system_color() {
|
||||
let mut s = Session::with_new();
|
||||
let bg = json!({"system": "window", "last": "#F0F0F0"});
|
||||
s.execute("design.background", &json!({"pattern": {"preset": "pct20", "fg": "accent1", "bg": bg}})).unwrap();
|
||||
let mut h = harness(SlideApp::new(s, Services::default()), "background");
|
||||
let before = background_pattern(&h).bg;
|
||||
assert!(matches!(before.base, ColorBase::System { .. }));
|
||||
h.get_by_label("Zig zag").click();
|
||||
h.run_steps(2);
|
||||
let pt = background_pattern(&h);
|
||||
assert_eq!((pt.preset.as_str(), pt.bg), ("zigZag", before));
|
||||
}
|
||||
|
||||
@@ -604,7 +604,27 @@ pub fn cref_param(c: &deckcraft_model::ColorRef) -> Value {
|
||||
}
|
||||
Value::Object(o)
|
||||
}
|
||||
_ => json!("#000000"),
|
||||
deckcraft_model::ColorBase::Preset { name } => {
|
||||
let mut o = serde_json::Map::new();
|
||||
o.insert("preset".into(), json!(name));
|
||||
for m in &c.mods {
|
||||
if let Some(v) = m.value() {
|
||||
o.insert(m.xml_name().into(), json!(v));
|
||||
}
|
||||
}
|
||||
Value::Object(o)
|
||||
}
|
||||
deckcraft_model::ColorBase::System { name, last } => {
|
||||
let mut o = serde_json::Map::new();
|
||||
o.insert("system".into(), json!(name));
|
||||
o.insert("last".into(), json!(format!("#{}", last.hex())));
|
||||
for m in &c.mods {
|
||||
if let Some(v) = m.value() {
|
||||
o.insert(m.xml_name().into(), json!(v));
|
||||
}
|
||||
}
|
||||
Value::Object(o)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user