diff --git a/crates/ui-egui/src/field_tests.rs b/crates/ui-egui/src/field_tests.rs index 566f0828b..63995fc80 100644 --- a/crates/ui-egui/src/field_tests.rs +++ b/crates/ui-egui/src/field_tests.rs @@ -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"); +} diff --git a/crates/ui-egui/src/panels/right.rs b/crates/ui-egui/src/panels/right.rs index 6dde13136..4f31f4089 100644 --- a/crates/ui-egui/src/panels/right.rs +++ b/crates/ui-egui/src/panels/right.rs @@ -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)> = 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);