git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commit8edca46662ce225b18b199117bd97eb70e3ca898
parent8fc965f8d7
authorLucas Galante <[email protected]>
date2026-09-22 12:56
fix: the dialog owns right presses while it is open

A right press inside the plate fell through to the pane beneath, whose
context menu then opened over the modal with its labels clipped by the
dialog's occluder — a menu with no legible entries. Inside the plate a
right press is now swallowed; outside it dismisses, as a left press does.
The middle button still passes through.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

 src/dialog.rs | 21 ++++++++++++++++-----
 src/main.rs   | 33 +++++++++++++++++++++++++++++++++
 2 files changed, 49 insertions(+), 5 deletions(-)

diff --git a/src/dialog.rs b/src/dialog.rs
index c029a15..c7a997c 100644
--- a/src/dialog.rs
+++ b/src/dialog.rs
@@ -1830,19 +1830,30 @@ impl State {
 
     /// A mouse button, while the dialog is open.
     ///
-    /// `None` hands the press back to the ordinary cascade — which happens
-    /// only for the buttons the dialog has no use for, so a right-click still
-    /// reaches whatever is under it outside the plate. `Some(handled)` means
-    /// the dialog dealt with it and nothing else should.
+    /// `None` hands the press back to the ordinary cascade — only the middle
+    /// button, which the dialog has no use for. `Some(handled)` means the
+    /// dialog dealt with it and nothing else should. A RIGHT press is the
+    /// dialog's too: inside the plate it is swallowed (nothing in the dialog
+    /// has a context menu, and until 2026-09-22 it fell through to the pane
+    /// beneath, whose menu then opened over a modal with its labels clipped
+    /// by the dialog's occluder — a menu with no legible entries), outside
+    /// it dismisses, exactly as a left press does.
     pub(crate) fn dialog_mouse_input(
         &mut self,
         button: MouseButton,
         state: ElementState,
     ) -> Option<bool> {
+        let (x, y) = (self.cursor_x, self.cursor_y);
+        if button == MouseButton::Right {
+            let inside = self.in_dialog_slot(DIALOG_IDX, x, y) || self.in_dialog_slot(DIALOG_PARAMS_IDX, x, y);
+            if !inside && state == ElementState::Pressed {
+                self.close_dialog();
+            }
+            return Some(true);
+        }
         if button != MouseButton::Left {
             return None;
         }
-        let (x, y) = (self.cursor_x, self.cursor_y);
 
         // A slider drag started in the settings body ends wherever the pointer
         // happens to be — including outside the plate. Ending it has to come
diff --git a/src/main.rs b/src/main.rs
index 8bfca24..903dcc6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -8481,6 +8481,39 @@ mod tests {
         assert_eq!(d.take_slider_change(), None, "over the label the wheel is the list's");
     }
 
+    /// A right press is the dialog's while it is open: inside the plate it is
+    /// swallowed — no context menu opens for the pane beneath, which used to
+    /// come up over the modal with its labels clipped — and outside it
+    /// dismisses, as a left press does.
+    #[test]
+    fn dialog_owns_right_presses_while_open() {
+        use cce_ui::widget::{ElementState, MouseButton};
+        let mut state = State::new(false);
+        state.run_command("toggle_dialog");
+        assert!(state.dialog_visible());
+        let (dx, dy, dw, dh) = state.positions[crate::slots::DIALOG_IDX];
+        assert!(dw > 0.0 && dh > 0.0, "the dialog is laid out");
+
+        // Inside: swallowed, nothing opens, the dialog stays.
+        state.cursor_x = dx + dw * 0.5;
+        state.cursor_y = dy + dh * 0.5;
+        assert_eq!(state.dialog_mouse_input(MouseButton::Right, ElementState::Pressed), Some(true));
+        assert!(state.dialog_visible());
+        assert!(!cce_ui::widget::context_menu::is_visible(), "no menu opened over the modal");
+        assert_eq!(state.dialog_mouse_input(MouseButton::Right, ElementState::Released), Some(true));
+
+        // Outside: dismisses, and is swallowed rather than reaching the pane.
+        state.cursor_x = (dx - 20.0).max(0.0);
+        state.cursor_y = (dy - 20.0).max(0.0);
+        assert_eq!(state.dialog_mouse_input(MouseButton::Right, ElementState::Pressed), Some(true));
+        assert!(!state.dialog_visible());
+        assert!(!cce_ui::widget::context_menu::is_visible());
+
+        // The middle button is still nobody's.
+        state.run_command("toggle_dialog");
+        assert_eq!(state.dialog_mouse_input(MouseButton::Middle, ElementState::Pressed), None);
+    }
+
     /// Backspace walks the query back, and the ranking follows it.
     #[test]
     fn dialog_backspace_widens_the_filter() {