mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-28 04:27:06 -06:00
Merge #1338: Format: Fill shows Color and Transparency only for fills they change
This commit is contained in:
@@ -156,8 +156,8 @@ fn run(app: &mut SlideApp, id: &str, p: Value) {
|
||||
}
|
||||
|
||||
/// Percent transparency (0 = opaque) of a solid fill, the same number `shape.fill`'s
|
||||
/// `transparency` parameter writes as an alpha transform. Anything else — no fill, a
|
||||
/// gradient, a picture, a pattern — is 0, because that command only changes a solid.
|
||||
/// `transparency` parameter writes as an alpha transform. Anything else (no fill, a
|
||||
/// gradient, a picture, a pattern) is 0.
|
||||
fn solid_fill_transparency_percent(fill: Option<&Fill>) -> f32 {
|
||||
let Some(Fill::Solid { color }) = fill else {
|
||||
return 0.0;
|
||||
@@ -478,22 +478,29 @@ fn format_shape(app: &mut SlideApp, ui: &mut Ui) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// A pattern takes its colors from its own Foreground and Background menus below.
|
||||
let pattern = matches!(sh.fill, Some(Fill::Pattern(_)));
|
||||
if !pattern {
|
||||
// The Color button sets a solid fill, so it shows only for a solid or automatic fill. A
|
||||
// gradient takes its colors from its stops, and a pattern takes them from its Foreground
|
||||
// and Background menus below.
|
||||
if matches!(kind, "solid" | "auto") {
|
||||
color_button(app, ui, "Color", Some("No Fill"), |c| match c {
|
||||
Some(c) => ("shape.fill".into(), json!({"color": cref_param(&c)})),
|
||||
None => ("shape.fill".into(), json!({"none": true})),
|
||||
});
|
||||
}
|
||||
// `shape.fill {transparency}` changes a solid fill (an automatic one through its
|
||||
// effective color) and a picture fill; a gradient has a transparency per stop.
|
||||
let transparency = matches!(sh.fill, None | Some(Fill::Solid { .. } | Fill::Picture(_)));
|
||||
ui.horizontal(|ui| {
|
||||
if pattern {
|
||||
if !transparency {
|
||||
return;
|
||||
}
|
||||
ui.label("Transparency");
|
||||
// Seed from this shape. A shared temp defaulted to 0, so a shape that was
|
||||
// already 40% transparent showed as opaque, and leaving the slider wrote 0.
|
||||
let seed = solid_fill_transparency_percent(sh.fill.as_ref());
|
||||
let seed = match &sh.fill {
|
||||
Some(Fill::Picture(pf)) => ((1.0 - pf.alpha.unwrap_or(1.0)) * 100.0) as f32,
|
||||
fill => solid_fill_transparency_percent(fill.as_ref()),
|
||||
};
|
||||
let id = ui.id().with(("ftr", sh.id.0));
|
||||
let mut v: f32 = ui.data_mut(|d| d.get_temp(id).unwrap_or(seed));
|
||||
let r = ui.add(egui::Slider::new(&mut v, 0.0..=100.0).suffix("%"));
|
||||
@@ -1569,6 +1576,75 @@ mod tests {
|
||||
h.run_steps(2);
|
||||
assert_eq!(solid_alpha(h.state(), b), Some(60_000));
|
||||
}
|
||||
|
||||
/// A blank slide with one selected rectangle, its fill set by `fill` (none for an automatic fill),
|
||||
/// in Format Shape ▸ Fill.
|
||||
fn fill_pane(fill: Option<serde_json::Value>) -> Harness<'static, SlideApp> {
|
||||
let mut s = Session::with_new();
|
||||
s.execute("slide.new", &json!({"layout": "blank"})).unwrap();
|
||||
let id = s.execute("shape.insert", &json!({"preset": "rect", "rect": [40.0, 40.0, 120.0, 80.0]})).unwrap()["id"].as_u64().unwrap();
|
||||
s.execute("edit.select", &json!({"ids": [id]})).unwrap();
|
||||
if let Some(p) = fill {
|
||||
s.execute("shape.fill", &p).unwrap();
|
||||
}
|
||||
let mut app = SlideApp::new(s, Services::default());
|
||||
app.ui.pane = Some("format".into());
|
||||
app.ui.format_tab = "fill".into();
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_ui_state(
|
||||
|ui, app: &mut SlideApp| {
|
||||
app.logic(ui.ctx());
|
||||
app.ui(ui);
|
||||
},
|
||||
app,
|
||||
);
|
||||
h.run_steps(3);
|
||||
h
|
||||
}
|
||||
|
||||
fn picture_fill(transparency: f64) -> serde_json::Value {
|
||||
let png = deckcraft_render::Image { width: 2, height: 2, pixels: [255, 0, 0, 255].repeat(4) }.to_png();
|
||||
json!({"picture": deckcraft_engine::cmd::base64_encode(&png), "transparency": transparency})
|
||||
}
|
||||
|
||||
/// The "Color" drop buttons in the pane: the fill's, when it shows, and the line's.
|
||||
fn color_buttons(h: &Harness<'_, SlideApp>) -> usize {
|
||||
h.query_all_by_role(egui::accesskit::Role::Button).filter(|n| n.accesskit_node().label().is_some_and(|l| l.trim() == "Color")).count()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_color_and_transparency_show_only_where_they_change_the_fill() {
|
||||
let gradient = json!({"gradient": {"stops": [[0, "accent1"], [1, "accent2"]], "angle": 90}});
|
||||
for (name, fill, color, transparency) in [
|
||||
("automatic", None, true, 1),
|
||||
("solid", Some(json!({"color": "accent2"})), true, 1),
|
||||
("no fill", Some(json!({"none": true})), false, 0),
|
||||
// The one Transparency row left is the selected gradient stop's.
|
||||
("gradient", Some(gradient), false, 1),
|
||||
("picture", Some(picture_fill(0.0)), false, 1),
|
||||
] {
|
||||
let h = fill_pane(fill);
|
||||
let fill_color = color_buttons(&h) == 2;
|
||||
assert_eq!(fill_color, color, "{name}: fill Color button");
|
||||
assert_eq!(h.query_all_by_label("Transparency").count(), transparency, "{name}: Transparency rows");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn picture_fill_transparency_slider_shows_the_picture_transparency() {
|
||||
let mut h = fill_pane(Some(picture_fill(0.4)));
|
||||
let alpha = |h: &Harness<'_, SlideApp>| {
|
||||
let doc = h.state().session.active().unwrap();
|
||||
match &doc.selected_shapes()[0].fill {
|
||||
Some(Fill::Picture(pf)) => pf.alpha,
|
||||
other => panic!("expected a picture fill, got {other:?}"),
|
||||
}
|
||||
};
|
||||
let shown = slider_values(&h);
|
||||
assert!(shown.iter().any(|v| (*v - 40.0).abs() < 0.5), "a 40% transparent picture should show 40%, got {shown:?}");
|
||||
h.get_by_label("Transparency").click();
|
||||
h.run_steps(2);
|
||||
assert!(alpha(&h).is_some_and(|a| (a - 0.6).abs() < 1e-9), "{:?}", alpha(&h));
|
||||
}
|
||||
}
|
||||
|
||||
/// Review › Thesaurus: look a word up; clicking a synonym puts it in place of the selected word
|
||||
|
||||
Reference in New Issue
Block a user