terminal emulator
git clone https://git.lucas.co/cce-terminal.git
Open the terminal menu on right-click
A right press on the grid opens the corner control's menu at the pointer,
as a context menu. The rows are shared with the corner menu (menu_rows), so
Copy is offered for a live selection and the dispatch path is unchanged.
While a TUI has mouse reporting on, the press is reported instead; Shift
bypasses reporting, so Shift+right-click still reaches the menu. No edge
clamping: the menu is its own popup, and the positioner keeps it on screen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/main.rs | 15 ++++++++++++++-
src/plate_menu.rs | 42 +++++++++++++++++++++++++++++-------------
2 files changed, 43 insertions(+), 14 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index 2d9acda..4493d93 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -23,7 +23,8 @@
//! circular trigger on the top-right that cce-designer's panes wear, opening
//! a menu of the actions a menubar-less terminal has nowhere else to put —
//! copy/paste, text zoom, scrollback and terminal resets, a new window, and
-//! the tabs. A tab ([`Tab`]) is one shell on one pty with its own `Term`,
+//! the tabs. A right-click on the grid opens the same menu at the pointer
+//! (Shift+right-click while a TUI has mouse reporting on). A tab ([`Tab`]) is one shell on one pty with its own `Term`,
//! title, view offset and bell; the window shows the active one and the
//! menu lists them as a radio group (the designer's dock-tab language) with
//! New Tab / Close Tab. There is no tab bar: the title carries `[i/n]` while
@@ -1362,6 +1363,18 @@ impl Application for TerminalApp {
*needs_rebuild = true;
return None;
}
+ // A right press opens it as a context menu at the pointer — unless
+ // the app owns the pointer, where the press is reported instead
+ // (Shift bypasses reporting, so the menu stays reachable there too).
+ // The selection is left alone, so Copy is offered for it.
+ if button == MouseButton::Right
+ && state == ElementState::Pressed
+ && !(self.mouse_reporting() && !self.selecting)
+ {
+ self.open_context_menu(px, py);
+ *needs_rebuild = true;
+ return None;
+ }
// Application-owned pointer: report and stop — no selection, no
// middle-paste (Shift bypasses via mouse_reporting).
diff --git a/src/plate_menu.rs b/src/plate_menu.rs
index 959c18b..0215d4e 100644
--- a/src/plate_menu.rs
+++ b/src/plate_menu.rs
@@ -1,7 +1,8 @@
//! The root plate's corner control: the DE's circular menu trigger (the same
//! affordance cce-designer's panes and cce-files' preview pane carry, on
//! `cce_ui::widget::plate_dock`) riding the top-right of the terminal
-//! window, and the menu it opens.
+//! window, and the menu it opens. A right-click on the grid opens the same
+//! menu at the pointer, as a context menu.
//!
//! The terminal has one plate — the window itself — so the toolkit's dock
//! vocabulary (collapse, detach) does not apply; the rows are the actions a
@@ -71,12 +72,34 @@ impl TerminalApp {
context_menu::is_visible() && !self.plate_menu_actions.is_empty()
}
- /// Open the corner menu under its control. Rows are contextual: Copy
- /// only with a selection to copy, Reset Text Size only while zoomed.
- /// The menu hangs off the control's RIGHT edge, leftwards — anchored on
- /// the left as the designer's panes do, it would run off the window.
+ /// Open the corner menu under its control. The menu hangs off the
+ /// control's RIGHT edge, leftwards — anchored on the left as the
+ /// designer's panes do, it would run off the window.
pub(crate) fn open_plate_menu(&mut self) {
let Some((cx, cy)) = self.plate_corner_center() else { return };
+ let (options, actions) = self.menu_rows();
+ // The menu sizes itself from its labels on `show`, so place it once
+ // to learn the width, then again with its right edge on the control.
+ let top = cy + CORNER_R;
+ context_menu::show(0.0, top, options.clone(), 0, NO_TARGET);
+ let left = (cx + CORNER_R - context_menu::w()).max(0.0);
+ context_menu::show(left, top, options, 0, NO_TARGET);
+ self.plate_menu_actions = actions;
+ }
+
+ /// Open the same menu as a context menu, its top-left at the pointer
+ /// (a right-click on the grid). No clamping here: the menu is its own
+ /// popup surface, and the positioner's constraint adjustment keeps it on
+ /// the output when the click is near the window's right or bottom edge.
+ pub(crate) fn open_context_menu(&mut self, px: f32, py: f32) {
+ let (options, actions) = self.menu_rows();
+ context_menu::show(px, py, options, 0, NO_TARGET);
+ self.plate_menu_actions = actions;
+ }
+
+ /// The rows, contextual: Copy only with a selection to copy, Reset Text
+ /// Size only while zoomed, Close Tab only with another tab to fall to.
+ fn menu_rows(&self) -> (Vec<String>, Vec<PlateMenuAction>) {
let mut options: Vec<String> = Vec::new();
let mut actions: Vec<PlateMenuAction> = Vec::new();
let mut row = |label: &str, action: PlateMenuAction| {
@@ -111,14 +134,7 @@ impl TerminalApp {
}
row("-", PlateMenuAction::Separator);
row("New Window", PlateMenuAction::NewWindow);
-
- // The menu sizes itself from its labels on `show`, so place it once
- // to learn the width, then again with its right edge on the control.
- let top = cy + CORNER_R;
- context_menu::show(0.0, top, options.clone(), 0, NO_TARGET);
- let left = (cx + CORNER_R - context_menu::w()).max(0.0);
- context_menu::show(left, top, options, 0, NO_TARGET);
- self.plate_menu_actions = actions;
+ (options, actions)
}
pub(crate) fn close_plate_menu(&mut self) {