M1: the page background opacity can be edited in the Page section (#1748)

The Design panel drew the page opacity as a "100 %" label, so it could not be changed from the panel although the model, page.setBackground and the colour picker already handled a translucent page. The label is now a percent number field like the other opacity fields (named Opacity for screen readers), committing page.setBackground; a scrub stays one undo step. The hex field keeps the current opacity unless the typed colour carries one, as the colour picker does. Regression test in field_tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Trevor BrownandClaude Fable 5.1 committed 2026-10-31 11:20:21 -04:00
1 parent e04c5d2274
commit 40534ddf14
2 files changed
+71 -9

No files matched your search

+41
View File
@@ -1194,3 +1194,44 @@ fn sidebar_field_text_stays_in_place_on_focus() {
assert!((before.center().y - after.center().y).abs() <= 0.5, "numeric={numeric}: before={before:?}, after={after:?}");
}
}
/// The Page section's opacity field edits the page background's alpha (#1748; it was a label),
/// one undo step per commit, and the hex field keeps that opacity unless the typed colour
/// carries its own.
#[test]
fn the_page_background_opacity_field_sets_the_page_colour_alpha() {
let mut u = Ui::new(Session::new());
u.ctx.enable_accesskit();
u.frame(vec![]);
let page_bg = |u: &Ui| {
let page = u.app.session.page().unwrap();
match &u.app.session.doc().unwrap().node(page).unwrap().kind {
figcraft_doc::NodeKind::Page { background, .. } => *background,
_ => panic!("the page"),
}
};
// Within an 8-bit step: the colour travels as RRGGBBAA.
let near = |a: f32, b: f32| (a - b).abs() < 1.0 / 255.0;
assert!(near(page_bg(&u).a, 1.0));
let steps = undo_steps(&u);
type_into(&mut u, "Opacity", "50", Key::Enter);
assert!(near(page_bg(&u).a, 0.5), "{:?}", page_bg(&u));
assert_eq!(undo_steps(&u), steps + 1, "one undo step for the commit");
// The hex field keeps the opacity when the typed colour has none, and takes one it carries.
let hex = |u: &mut Ui| u.ctx.read_response(Id::unique("pagebg").with("click")).expect("the hex field").rect.center();
let at = hex(&mut u);
click(&mut u, at);
u.frame(vec![Event::Text("FF0000".into())]);
key(&mut u, Key::Enter, Modifiers::NONE);
let c = page_bg(&u);
assert!(near(c.r, 1.0) && near(c.g, 0.0) && near(c.a, 0.5), "{c:?}");
let at = hex(&mut u);
click(&mut u, at);
u.frame(vec![Event::Text("00FF0080".into())]);
key(&mut u, Key::Enter, Modifiers::NONE);
let c = page_bg(&u);
assert!(near(c.g, 1.0) && near(c.a, 128.0 / 255.0), "{c:?}");
assert_eq!(undo_steps(&u), steps + 3);
u.app.run("edit.undo", json!({}));
assert!(near(page_bg(&u).r, 1.0) && near(page_bg(&u).a, 0.5), "undo takes back the last colour alone");
}
+30 -9
View File
@@ -311,27 +311,48 @@ fn design(app: &mut FigApp, ui: &mut Ui) {
/// Draw the page section: background and colour profile, local variables/styles, and the
/// page export section.
fn page_section(app: &mut FigApp, ui: &mut Ui, doc: &Document) {
let t = Tokens::get(ui.ctx());
let Ok(page) = app.session.page() else { return };
let bg = match doc.node(page).map(|n| &n.kind) {
Some(NodeKind::Page { background, .. }) => *background,
_ => figcraft_color::Color::DEFAULT_PAGE,
};
widgets::section_header(ui, tr("Page"), &[], false);
let mut new_hex = None;
// The new colour and, from a scrub, whether the scrub is over (live edits stay one undo step).
let mut new_color: Option<(figcraft_color::Color, Option<bool>)> = None;
row(ui, 32.0, |ui| {
let p = Paint::solid(bg);
let sw = widgets::swatch(ui, &p, 16.0);
app.picker_control(crate::colorpicker::PickerTarget::PageBackground, &sw);
if let Some(h) = widgets::text_field(ui, Id::unique("pagebg"), &bg.to_hex(), 96.0, "") {
new_hex = Some(h);
// A typed colour keeps the page's opacity unless it carries one (`FF000080`), as the
// colour picker's hex field does.
if let Some(h) = widgets::text_field(ui, Id::unique("pagebg"), &bg.to_hex(), 96.0, "")
&& let Some(c) = figcraft_color::Color::from_hex_or_name(&h)
{
new_color = Some((c.with_alpha(if c.a < 1.0 { c.a } else { bg.a }), None));
}
// The opacity: a field like every other opacity field, not a label (#1748).
let alpha = |v: f64| bg.with_alpha((v / 100.0).clamp(0.0, 1.0) as f32);
let shown = Some(f64::from((bg.a * 100.0).round()));
match widgets::number_field(ui, Id::unique("pagebg_a"), Lead::Named(tr("Opacity")), shown, "%", 56.0, 1.0) {
Some(FieldEdit::Set(v)) => new_color = Some((alpha(v), None)),
Some(FieldEdit::Scrub(v, done)) => new_color = Some((alpha(v), Some(done))),
None => {}
}
widgets::label(ui, "100 %", t.text);
});
if let Some(h) = new_hex
&& let Some(c) = figcraft_color::Color::from_hex_or_name(&h)
{
app.run("page.setBackground", json!({"color": c.to_css_hex()}));
if let Some((c, scrub)) = new_color {
let p = json!({"color": c.to_css_hex()});
match scrub {
None => {
app.run("page.setBackground", p);
}
Some(done) => {
let _ = app.session.begin_interaction("Page background");
let _ = app.session.execute("page.setBackground", &p);
if done {
let _ = app.session.commit_interaction();
}
}
}
}
widgets::separator(ui);
widgets::section_header(ui, tr("Color profile"), &[], false);