mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-01 14:26:49 -06:00
M4: the colour picker closes like every other detached panel
The picker draws on the shared popover, so it closes the way the popover does: the X, Escape, and a press outside it. - A press on the canvas closes it; presses in the panel beside it keep it, since a swatch, a style chip or a field there switches the colour it edits. - The control that opened it is held out of that closing and its rect is refreshed every frame, so pressing the same swatch again reaches its own toggle — after the panel scrolls too (the bug #730 had, in the picker's hands). - Escape closes it before the binding deselects the layer, and not while one of its own dropdowns is open. Tests: a press on the canvas closes it, the same swatch closes it again (panel_remove_tests).
This commit is contained in:
@@ -465,8 +465,14 @@ pub fn show(app: &mut FigApp, ctx: &egui::Context) {
|
||||
let pos = beside.unwrap_or(pos2(screen.max.x - app.ui.right_width - 260.0, 140.0));
|
||||
// The colour picker's own header — title 16 in, the X 8 in from the right — is the shared
|
||||
// popover chrome now; the picker keeps its body.
|
||||
let mut pop = app.ui.picker_popover.take().unwrap_or_else(|| Popover::new("color_picker", tr("Custom")).pad(0.0).close_on_outside(false));
|
||||
let closed = pop.show(ctx, Open::At(pos), Hold::default(), |ui| {
|
||||
let mut pop = app.ui.picker_popover.take().unwrap_or_else(|| Popover::new("color_picker", tr("Custom")).pad(0.0));
|
||||
// Closing is the shared widget's: the X, Escape and a press outside it. Presses in the panel
|
||||
// beside it keep it (swatches and fields there switch the colour it edits); the control that
|
||||
// opened it is held too, so pressing it again reaches its own toggle.
|
||||
let panel = Rect::from_min_max(pos2(screen.max.x - app.ui.right_width, screen.min.y), screen.max);
|
||||
let mut held: Vec<Rect> = vec![panel];
|
||||
held.extend(app.ui.picker_button);
|
||||
let closed = pop.show(ctx, Open::At(pos), Hold { rects: &held, escape: false }, |ui| {
|
||||
ui.spacing_mut().item_spacing = vec2(8.0, 8.0);
|
||||
// Paint type.
|
||||
if !matches!(
|
||||
@@ -855,10 +861,20 @@ pub fn show(app: &mut FigApp, ctx: &egui::Context) {
|
||||
|
||||
/// The colour picker closes (its X, Escape, or another popover took over): pending edits commit
|
||||
/// and the picker with its popover go.
|
||||
/// Escape with the picker open closes it (the selection stays).
|
||||
pub fn close_if_open(app: &mut FigApp) -> bool {
|
||||
if app.ui.picker.is_none() {
|
||||
return false;
|
||||
}
|
||||
close(app);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn close(app: &mut FigApp) {
|
||||
finish(app);
|
||||
app.ui.picker = None;
|
||||
app.ui.picker_popover = None;
|
||||
app.ui.picker_button = None;
|
||||
}
|
||||
|
||||
/// Swatches wrapped in rows inset 16 points from both sides of the picker.
|
||||
|
||||
@@ -490,6 +490,9 @@ pub struct UiState {
|
||||
pub picker: Option<colorpicker::PickerTarget>,
|
||||
/// The colour picker's popover while it is open (its place survives a drag).
|
||||
pub picker_popover: Option<popover::Popover>,
|
||||
/// The control that opened the picker, refreshed every frame it is drawn (the panel scrolls):
|
||||
/// a press in it is held out of the popover's closing, so its own click toggles the picker.
|
||||
pub picker_button: Option<egui::Rect>,
|
||||
pub context_menu: Option<(egui::Pos2, Vec<NodeId>)>,
|
||||
/// The context menu was asked for from the keyboard (`view.contextMenu`): it opens at the
|
||||
/// focused layer row or the canvas selection and takes the keyboard.
|
||||
@@ -635,6 +638,7 @@ impl Default for UiState {
|
||||
preview: None,
|
||||
picker: None,
|
||||
picker_popover: None,
|
||||
picker_button: None,
|
||||
context_menu: None,
|
||||
key_menu: false,
|
||||
key_box: None,
|
||||
|
||||
@@ -167,3 +167,36 @@ fn adding_a_fill_to_mixed_fills_opens_no_picker() {
|
||||
assert_eq!((doc.node(a).unwrap().fills.len(), doc.node(b).unwrap().fills.len()), (2, 3));
|
||||
assert_eq!(u.app.ui.picker, None);
|
||||
}
|
||||
|
||||
/// The shared popover's closing reaches the colour picker: a press on the canvas closes it.
|
||||
#[test]
|
||||
fn a_press_on_the_canvas_closes_the_colour_picker() {
|
||||
let mut s = Session::new();
|
||||
let r = id(s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 50, "height": 50})).unwrap());
|
||||
s.execute("fill.add", &json!({"ids": [r], "color": "#FF0000"})).unwrap();
|
||||
s.execute("select.set", &json!({"ids": [r]})).unwrap();
|
||||
let mut u = Ui::new(s);
|
||||
u.ctx.enable_accesskit();
|
||||
let sw = u.ctx.read_response(Id::new(("swatch", true, 0usize))).unwrap().rect.center();
|
||||
click(&mut u, sw);
|
||||
assert!(u.app.ui.picker.is_some(), "the swatch opens the picker");
|
||||
click(&mut u, Pos2::new(320.0, 300.0));
|
||||
assert_eq!(u.app.ui.picker, None, "a press on the canvas closes the picker");
|
||||
}
|
||||
|
||||
/// Pressing the swatch that opened the picker again closes it: its press is held out of the
|
||||
/// popover's outside-press closing, so the swatch's own toggle gets the click.
|
||||
#[test]
|
||||
fn the_swatch_that_opened_the_colour_picker_closes_it_again() {
|
||||
let mut s = Session::new();
|
||||
let r = id(s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 50, "height": 50})).unwrap());
|
||||
s.execute("fill.add", &json!({"ids": [r], "color": "#FF0000"})).unwrap();
|
||||
s.execute("select.set", &json!({"ids": [r]})).unwrap();
|
||||
let mut u = Ui::new(s);
|
||||
u.ctx.enable_accesskit();
|
||||
let sw = u.ctx.read_response(Id::new(("swatch", true, 0usize))).unwrap().rect.center();
|
||||
click(&mut u, sw);
|
||||
assert!(u.app.ui.picker.is_some(), "the swatch opens the picker");
|
||||
click(&mut u, sw);
|
||||
assert_eq!(u.app.ui.picker, None, "the same swatch closes it again");
|
||||
}
|
||||
|
||||
@@ -1547,6 +1547,9 @@ fn paint_row(app: &mut FigApp, ui: &mut Ui, ids: &[NodeId], i: usize, p: &Paint,
|
||||
let sw = Rect::from_min_size(pos2(r.min.x + 4.0, r.center().y - 8.0), vec2(16.0, 16.0));
|
||||
widgets::paint_swatch(ui.painter(), sw, p);
|
||||
let sresp = ui.interact(sw.expand(2.0), Id::new(("swatch", fills, i)), Sense::click());
|
||||
if app.ui.picker.as_ref() == Some(&crate::colorpicker::PickerTarget::Paint { fills, index: i, ids: ids.clone() }) {
|
||||
app.ui.picker_button = Some(sresp.rect);
|
||||
}
|
||||
if sresp.clicked() {
|
||||
app.toggle_picker(crate::colorpicker::PickerTarget::Paint { fills, index: i, ids: ids.clone() }, Some(sresp.rect.min.y));
|
||||
}
|
||||
|
||||
@@ -241,6 +241,11 @@ fn key_event(app: &mut FigApp, key: Key, physical: Option<Key>, m: Modifiers, re
|
||||
if key == Key::Escape && crate::selection_spacing::cancel(app, ctx) {
|
||||
return true;
|
||||
}
|
||||
// The colour picker open: Escape closes it here, before the binding deselects — unless one of
|
||||
// its own dropdowns is open, which takes this Escape.
|
||||
if key == Key::Escape && !egui::Popup::is_any_open(ctx) && crate::colorpicker::close_if_open(app) {
|
||||
return true;
|
||||
}
|
||||
if key_box(app, key, m) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user