Layers panel: right-click opens the fx and adjustment footer menus (#1680)

As in Photoshop, a secondary click on the Layers footer's Add a layer style and Create new fill or
adjustment layer buttons opens their menus too (a primary click still toggles them), and a small
arrow marks them as menu buttons. Fixes #1579.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nigel Ihza Firdausy 2026-10-09 20:17:02 +07:00 committed by GitHub
parent efa62efa0c
commit 681e7f3b89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 71 additions and 3 deletions

View File

@ -1606,8 +1606,8 @@ fn layers(app: &mut PhotocraftApp, ui: &mut egui::Ui) {
actions.push(("layer.new.group".into(), json!({})));
}
actions.extend(footer_drop(ui, &group, footer_drag, "layer.groupLayers"));
let adj = icons::button(ui, "contrast", 26.0, false, tl!("Create new fill or adjustment layer"));
egui::Popup::menu(&adj).show(|ui| {
let adj = footer_menu_button(ui, "contrast", 26.0, tl!("Create new fill or adjustment layer"));
egui::Popup::menu(&adj).open_memory(footer_menu_right_click(&adj)).show(|ui| {
ui.set_min_width(190.0);
for c in photocraft_engine::command_specs().iter().filter(|c| c.id.starts_with("layer.newAdjustmentLayer.")) {
if ui.button(tl!(c.label).trim_end_matches('…')).clicked() {
@ -1638,7 +1638,7 @@ fn layers(app: &mut PhotocraftApp, ui: &mut egui::Ui) {
actions.push((crate::layer_menu_ui::add_mask_command(doc.selection.is_some(), alt).into(), json!({})));
}
let fx = fx_button(ui, 26.0, tl!("Add a layer style"));
egui::Popup::menu(&fx).show(|ui| {
egui::Popup::menu(&fx).open_memory(footer_menu_right_click(&fx)).show(|ui| {
ui.set_min_width(180.0);
if ui.button(tl!("Blending Options…")).clicked() {
crate::layer_style::open(app, None);
@ -1711,6 +1711,31 @@ fn footer_label(command: &str) -> &'static str {
}
}
/// A secondary click opens a Layers footer popup, as in Photoshop; a primary click still toggles
/// it (the `Popup::menu` default this replaces).
fn footer_menu_right_click(response: &egui::Response) -> Option<egui::SetOpenCommand> {
if response.secondary_clicked() {
Some(egui::SetOpenCommand::Bool(true))
} else if response.clicked() {
Some(egui::SetOpenCommand::Toggle)
} else {
None
}
}
/// The small down-arrow makes it clear that the footer icon expands into a menu.
fn footer_menu_arrow(ui: &egui::Ui, rect: Rect) {
let t = Tokens::get(ui.ctx());
let arrow = Rect::from_min_size(rect.right_bottom() - vec2(11.0, 10.0), vec2(9.0, 9.0));
icons::paint(ui, arrow, "chevron-down", 8.0, t.text_dim);
}
fn footer_menu_button(ui: &mut egui::Ui, icon: &str, size: f32, tip: &str) -> egui::Response {
let response = icons::button(ui, icon, size, false, tip);
footer_menu_arrow(ui, response.rect);
response
}
/// Photoshop's italic "fx" footer button (no icon-font equivalent).
fn fx_button(ui: &mut egui::Ui, size: f32, tip: &str) -> egui::Response {
let t = Tokens::get(ui.ctx());
@ -1722,6 +1747,7 @@ fn fx_button(ui: &mut egui::Ui, size: f32, tip: &str) -> egui::Response {
job.append("fx", 0.0, egui::TextFormat { font_id: egui::FontId::proportional(15.0), color: t.icon, italics: true, ..Default::default() });
let g = ui.painter().layout_job(job);
ui.painter().galley(r.center() - g.size() / 2.0, g, t.icon);
footer_menu_arrow(ui, r);
resp.on_hover_text(tip)
}
@ -3433,6 +3459,48 @@ mod layer_drag_edge_scroll_tests {
}
}
#[cfg(test)]
mod footer_menu_tests {
use super::*;
// Exercise the actual egui pointer path, not just a mocked click flag.
#[test]
fn right_click_opens_footer_menu_and_primary_click_still_works() {
let ctx = egui::Context::default();
let rect = Rect::from_min_size(Pos2::ZERO, vec2(300.0, 100.0));
let pos = pos2(35.0, 20.0);
let frame = |events: Vec<egui::Event>| {
let mut opened = false;
let mut output = ctx.run_ui(egui::RawInput { screen_rect: Some(rect), events, ..Default::default() }, |ui| {
let response = ui.add_sized([100.0, 28.0], egui::Button::new("Footer menu"));
egui::Popup::menu(&response).open_memory(footer_menu_right_click(&response)).show(|ui| {
opened = true;
ui.label("Menu entry");
});
});
output.textures_delta.clear();
opened
};
frame(Vec::new());
frame(vec![
egui::Event::PointerMoved(pos),
egui::Event::PointerButton { pos, button: egui::PointerButton::Secondary, pressed: true, modifiers: egui::Modifiers::NONE },
]);
assert!(
frame(vec![egui::Event::PointerButton { pos, button: egui::PointerButton::Secondary, pressed: false, modifiers: egui::Modifiers::NONE }]),
"secondary release opens the popup"
);
// A primary click also remains supported by egui::Popup::menu.
frame(vec![egui::Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::NONE }]);
frame(vec![egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed: true, modifiers: egui::Modifiers::NONE }]);
assert!(
frame(vec![egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed: false, modifiers: egui::Modifiers::NONE }]),
"primary release still opens the popup"
);
}
}
#[cfg(test)]
mod group_drag_selection_tests {
use super::*;