diff --git a/crates/ui-egui/src/panels.rs b/crates/ui-egui/src/panels.rs index 7feed8f95..6015dbaa6 100644 --- a/crates/ui-egui/src/panels.rs +++ b/crates/ui-egui/src/panels.rs @@ -236,13 +236,18 @@ pub fn panel_menu_items(app: &mut VectorcraftApp, ui: &mut Ui, id: &str) -> bool true } -/// The ≡ panel-menu button drawn into `rect` (the right end of a panel's title/tab strip). -pub fn panel_menu(app: &mut VectorcraftApp, ui: &mut Ui, id: &str, rect: Rect) { +/// The ≡ button drawn into `rect` that opens a panel menu. +fn menu_button(ui: &mut Ui, id: &str, rect: Rect) -> egui::Response { let t = Tokens::get(ui.ctx()); let resp = ui.interact(rect, ui.scope_id().with(("panel-menu", id)), Sense::click()); icons::paint(ui, "menu", rect.shrink(1.0), if resp.hovered() { t.text_strong } else { t.text_dim }); resp.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), tl!("Panel menu"))); - let resp = resp.on_hover_text(tl!("Panel menu")); + resp.on_hover_text(tl!("Panel menu")) +} + +/// The ≡ panel-menu button drawn into `rect` (the right end of a panel's title/tab strip). +pub fn panel_menu(app: &mut VectorcraftApp, ui: &mut Ui, id: &str, rect: Rect) { + let resp = menu_button(ui, id, rect); egui::Popup::menu(&resp).show(|ui| { crate::widgets::menu_scroll(ui, |ui| { ui.set_min_width(220.0); @@ -431,6 +436,18 @@ fn paint_popover(app: &mut VectorcraftApp, ui: &mut Ui) { set_pstate(ui.ctx(), MIXER, m); } } + // The mixer is the Color panel, so its menu comes along: the color modes (Grayscale, + // RGB, HSB, CMYK, Web Safe RGB), Invert, Complement and the rest (#4249). + if mixer { + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + let (rect, _) = ui.allocate_exact_size(vec2(18.0, 18.0), Sense::hover()); + let resp = menu_button(ui, "color-mixer", rect); + egui::Popup::menu(&resp).show(|ui| { + ui.set_min_width(200.0); + color::menu(app, ui); + }); + }); + } }); crate::widgets::divider(ui); if mixer { diff --git a/crates/ui-egui/src/tests/paintchips.rs b/crates/ui-egui/src/tests/paintchips.rs index ba5730238..5ede05407 100644 --- a/crates/ui-egui/src/tests/paintchips.rs +++ b/crates/ui-egui/src/tests/paintchips.rs @@ -322,3 +322,31 @@ fn the_popover_swatch_libraries_button_opens_the_libraries_menu() { let open = crate::panels::library_panel::last_open(&app.ui).map(|o| (o.kind.clone(), o.id.clone())); assert_eq!(open, Some(("swatches".into(), "earth-tones".into()))); } + +/// #4249: the Color Mixer a Shift-click on a Fill chip opens has the Color panel's menu, whose +/// color modes (Grayscale, RGB, HSB, CMYK, Web Safe RGB) switch the color it shows and edits. +#[test] +fn the_mixer_popover_has_the_color_panels_menu() { + let mut app = app(); + app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 50, "height": 50})).unwrap(); + app.run("paint.setFill", json!({"color": "#ff0000"})).unwrap(); + let ctx = context(); + frame(&mut app, &ctx, vec![], Modifiers::NONE, control_bar); + let (fill, _) = chips(&ctx); + click(&mut app, &ctx, fill.center(), Modifiers::SHIFT, control_bar); + let menu = ctx.viewport(|vp| { + vp.prev_pass.widgets.layers().filter(|(l, _)| l.order != egui::Order::Background).flat_map(|(_, w)| w.iter()).find_map(|w| { + (w.rect.size() == vec2(18.0, 18.0) && w.info.as_ref().and_then(|i| i.label.as_deref()) == Some("Panel menu")).then_some(w.rect) + }) + }); + let menu = menu.expect("the mixer has a panel menu button"); + click(&mut app, &ctx, menu.center(), Modifiers::NONE, control_bar); + let shapes = frame(&mut app, &ctx, vec![], Modifiers::NONE, control_bar); + for mode in ["Grayscale", "RGB", "HSB", "Web Safe RGB", "Invert", "Complement"] { + assert!(menu_item_rect(&shapes, mode).is_some(), "{mode} in the mixer's menu"); + } + let cmyk = menu_item_rect(&shapes, "CMYK").expect("CMYK in the mixer's menu"); + click(&mut app, &ctx, cmyk.center(), Modifiers::NONE, control_bar); + let fill = panels::current_paints(&app).0.color(); + assert!(matches!(fill, Some(vectorcraft_color::Color::Cmyk { .. })), "the fill is CMYK now: {fill:?}"); +}