Files
deckcraft/crates/ui-egui/src/pattern_fill_tests.rs
T
dotCooCoo 2ddd2abe67 Format: Pattern fill shows a gallery of every preset with Foreground and Background colors
Format Shape's Pattern fill set 25 % in accent 1 on background 1 and
offered no way to change it, and Format Background listed 10 of the 54
presets by their XML names. Both panes now show the same editor: a
gallery of the 54 DrawingML presets drawn in the fill's own colors,
each named in words, and Foreground and Background color menus.

With a pattern fill, Format Shape no longer shows the solid-fill Color
button, which replaced the pattern with a solid fill, or the
Transparency slider, which did not change a pattern. The renderer
exports the preset list and tiles for the gallery to draw.
2026-10-24 14:13:22 -07:00

132 lines
5.6 KiB
Rust

//! Format Shape and Format Background ▸ Pattern fill: the preset gallery and the Foreground and
//! Background color menus.
use deckcraft_color::Rgba;
use deckcraft_engine::Session;
use deckcraft_model::style::PatternFill;
use deckcraft_model::{Background, ColorRef, Fill, ShapeId};
use egui::accesskit::Role;
use egui::{Event, Key, Modifiers, PointerButton};
use egui_kittest::Harness;
use egui_kittest::kittest::Queryable;
use serde_json::json;
use crate::{Services, SlideApp};
fn harness(mut app: SlideApp, pane: &str) -> Harness<'static, SlideApp> {
app.ui.pane = Some(pane.into());
let mut h = Harness::builder().with_size(egui::vec2(1800.0, 1100.0)).build_ui_state(
|ui, app: &mut SlideApp| {
app.logic(ui.ctx());
app.ui(ui);
},
app,
);
h.run_steps(3);
h
}
/// A new deck with one rectangle selected and filled with `pct25` in accent 1 on background 1.
fn patterned_shape() -> (Session, ShapeId) {
let mut s = Session::with_new();
let r = s.execute("shape.insert", &json!({"preset": "rect", "rect": [80.0, 80.0, 200.0, 120.0]})).unwrap();
let id = r.get("id").and_then(|v| v.as_u64()).unwrap();
s.execute("edit.select", &json!({"ids": [id]})).unwrap();
s.execute("shape.fill", &json!({"pattern": {"preset": "pct25", "fg": "accent1", "bg": "bg1"}})).unwrap();
(s, ShapeId(u32::try_from(id).unwrap()))
}
fn shape_pattern(h: &Harness<'_, SlideApp>, id: ShapeId) -> PatternFill {
match &h.state().session.active().unwrap().shape(id).unwrap().fill {
Some(Fill::Pattern(pt)) => pt.clone(),
other => panic!("expected a pattern fill, got {other:?}"),
}
}
fn background_pattern(h: &Harness<'_, SlideApp>) -> PatternFill {
match &h.state().session.active().unwrap().doc.slides[0].background {
Some(Background::Fill { fill: Fill::Pattern(pt) }) => pt.clone(),
other => panic!("expected a pattern background, got {other:?}"),
}
}
/// Open the color menu named `button` and pick `hex` from its More Colors field.
fn pick_hex(h: &mut Harness<'_, SlideApp>, button: &str, hex: &str) {
h.get_by_label(button).click();
h.run_steps(2);
// The hex field is the text input on the "More Colors… #" label's row, to its right.
let label = h.get_by_label("More Colors… #").rect();
let field = h
.get_all_by_role(Role::TextInput)
.find(|n| n.rect().min.x >= label.max.x - 1.0 && (n.rect().center().y - label.center().y).abs() < 6.0)
.map(|n| n.rect().center())
.expect("the More Colors hex field");
for pressed in [true, false] {
h.input_mut().events.push(Event::PointerButton { pos: field, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE });
h.run_steps(2);
}
h.input_mut().events.push(Event::Text(hex.into()));
h.run_steps(2);
for pressed in [true, false] {
h.input_mut().events.push(Event::Key { key: Key::Enter, physical_key: None, pressed, repeat: false, modifiers: Modifiers::NONE });
h.run_steps(2);
}
h.run_steps(2);
}
#[test]
fn shape_pattern_fill_picks_a_preset_from_the_gallery() {
let (s, id) = patterned_shape();
let mut h = harness(SlideApp::new(s, Services::default()), "format");
let _ = h.get_by_label("Pattern fill");
assert!(h.query_by_label("Transparency").is_none(), "a pattern has no solid-fill transparency");
assert_eq!(deckcraft_render::pattern_presets().count(), 54);
for name in deckcraft_render::pattern_presets() {
let _ = h.get_by_label(crate::fillui::pattern_label(name));
}
let undo = h.state().session.active().unwrap().history.undo.len();
h.get_by_label("Dark downward diagonal").click();
h.run_steps(2);
let pt = shape_pattern(&h, id);
assert_eq!(pt.preset, "dkDnDiag");
assert_eq!((pt.fg, pt.bg), (ColorRef::scheme(deckcraft_color::SchemeSlot::Accent1), ColorRef::scheme(deckcraft_color::SchemeSlot::Bg1)));
assert_eq!(h.state().session.active().unwrap().history.undo.len(), undo + 1, "one undo step");
}
#[test]
fn shape_solid_fill_keeps_its_transparency_without_a_pattern_gallery() {
let (mut s, _) = patterned_shape();
s.execute("shape.fill", &json!({"color": "accent2"})).unwrap();
let h = harness(SlideApp::new(s, Services::default()), "format");
let _ = h.get_by_label("Transparency");
assert!(h.query_by_label("Foreground color").is_none());
assert!(h.query_by_label("Zig zag").is_none());
}
#[test]
fn shape_pattern_fill_sets_its_foreground_and_background_colors() {
let (s, id) = patterned_shape();
let mut h = harness(SlideApp::new(s, Services::default()), "format");
pick_hex(&mut h, "Foreground color", "00FF00");
let pt = shape_pattern(&h, id);
assert_eq!(pt.fg, ColorRef::rgb(Rgba::from_hex("00FF00").unwrap()));
assert_eq!(pt.preset, "pct25", "the pattern stays");
pick_hex(&mut h, "Background color", "112233");
let pt = shape_pattern(&h, id);
assert_eq!(pt.bg, ColorRef::rgb(Rgba::from_hex("112233").unwrap()));
assert_eq!(pt.fg, ColorRef::rgb(Rgba::from_hex("00FF00").unwrap()), "the foreground stays");
}
#[test]
fn background_pattern_fill_offers_every_preset_and_both_colors() {
let mut s = Session::with_new();
s.execute("design.background", &json!({"pattern": {"preset": "pct20", "fg": "accent1", "bg": "bg1"}})).unwrap();
let mut h = harness(SlideApp::new(s, Services::default()), "background");
h.get_by_label("Zig zag").click();
h.run_steps(2);
assert_eq!(background_pattern(&h).preset, "zigZag");
pick_hex(&mut h, "Background color", "FFEE00");
let pt = background_pattern(&h);
assert_eq!((pt.preset.as_str(), pt.bg), ("zigZag", ColorRef::rgb(Rgba::from_hex("FFEE00").unwrap())));
}