diff --git a/src/app/control/actions.rs b/src/app/control/actions.rs index 8c45f84d6..b7b73d36b 100644 --- a/src/app/control/actions.rs +++ b/src/app/control/actions.rs @@ -267,7 +267,8 @@ impl OpenCADStudio { "mtext_cancel" => Message::MTextCancel, "text_input" => Message::TextInlineInput(string(req, "value")?.into()), "text_commit" => Message::TextInlineOk, - "pointer_move" | "pointer_press" | "pointer_release" => { + "pointer_move" | "pointer_press" | "pointer_release" | "pointer_right_press" + | "pointer_right_release" => { let x = req["x"] .as_f64() .filter(|v| v.is_finite()) @@ -289,6 +290,10 @@ impl OpenCADStudio { let event = match name { "pointer_press" => self.update(Message::ViewportLeftPress), "pointer_release" => self.update(Message::ViewportLeftRelease), + // Right button: the context menu / Enter behaviour chosen in + // Options (see `Message::ViewportRightRelease`). + "pointer_right_press" => self.update(Message::ViewportRightPress), + "pointer_right_release" => self.update(Message::ViewportRightRelease), _ => Task::none(), }; return Ok(Task::batch([move_task, event])); diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index b5712158f..1ef0dcc27 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -1603,7 +1603,7 @@ bg={bg_ms:.1}ms n={view_count}" crate::ui::command_line::history_max_height(self.win_size.1), ) + 72.0 } else { - 34.0 + 34.0 + self.command_line.overlay_lines_height() }; // Quick Properties: stay near the selection cursor, flipping around // it as needed to remain inside the visible drawing area. diff --git a/src/app/view/overlay.rs b/src/app/view/overlay.rs index 37b28d79a..7d99b6de4 100644 --- a/src/app/view/overlay.rs +++ b/src/app/view/overlay.rs @@ -994,6 +994,7 @@ impl Widget for ClampedPin<'_> { &layout::Limits::new(Size::ZERO, safe), ); let content = node.size(); + let bottom_inset = self.bottom_inset; let max_x = (max.width - MARGIN - content.width).max(MARGIN); let right = self.anchor.x + self.gap + self.offset.x; @@ -1006,8 +1007,7 @@ impl Widget for ClampedPin<'_> { max_x }; - let max_y = - (max.height - self.bottom_inset - MARGIN - content.height).max(MARGIN); + let max_y = (max.height - bottom_inset - MARGIN - content.height).max(MARGIN); let below = self.anchor.y + self.gap + self.offset.y; let above = self.anchor.y - self.gap - content.height; let y = if below <= max_y { @@ -1182,7 +1182,7 @@ pub(super) fn viewport_context_menu_overlay( .align_y(iced::Center); let enabled = !children.is_empty(); let mut btn = button(content) - .padding([4, 12]) + .padding([3, 12]) .width(Fill) .height(Length::Fixed(MENU_ROW_H)) .style(move |theme: &Theme, status| context_menu_row_style(theme, status, is_hl)); @@ -1263,9 +1263,9 @@ fn context_menu_row(item: &MenuItem, indent: f32, highlighted: bool) -> Element< } let mut btn = button(content) .padding(iced::Padding { - top: 4.0, + top: 3.0, right: 12.0, - bottom: 4.0, + bottom: 3.0, left: 12.0 + indent, }) .width(Fill) diff --git a/src/ui/command_line.rs b/src/ui/command_line.rs index fa5cf5865..a703c3800 100644 --- a/src/ui/command_line.rs +++ b/src/ui/command_line.rs @@ -500,6 +500,24 @@ impl CommandLine { e.pinned || (self.fade_ms > 0 && e.created_at.elapsed().as_secs_f32() < self.fade_secs()) } + /// Height of the overlaid prompt lines currently shown above the input + /// row (CLIPROMPTLINES, minus lines that have faded out), in pixels. Lets + /// cursor-anchored panels stay clear of the whole command area, not just + /// the input row. + pub fn overlay_lines_height(&self) -> f32 { + if self.cliprompt_lines == 0 || self.history_open { + return 0.0; + } + let visible = self + .history + .iter() + .filter(|e| self.entry_visible(e)) + .count() + .min(self.cliprompt_lines as usize); + // Text size 12 (line height ≈ 15.6) plus the 1 px padding each side. + visible as f32 * 18.0 + } + /// Visible overlay count respecting CLIPROMPTLINES (0–50). Used in tests. #[cfg(test)] pub fn visible_history_count(&self) -> usize { diff --git a/src/ui/popup/context_menu.rs b/src/ui/popup/context_menu.rs index 6d5905389..567aeb693 100644 --- a/src/ui/popup/context_menu.rs +++ b/src/ui/popup/context_menu.rs @@ -20,7 +20,7 @@ use crate::t; /// Fixed row height used by the renderer, so `ContextMenu::default_row_y` /// (which anchors the default row under the cursor) agrees with the layout. -pub const MENU_ROW_H: f32 = 24.0; +pub const MENU_ROW_H: f32 = 22.0; /// Height of a separator row (1 px line plus its vertical padding). pub const MENU_SEP_H: f32 = 7.0; /// Vertical padding inside the menu panel above the first row. @@ -576,7 +576,11 @@ fn idle_rows( // ── Navigation ───────────────────────────────────────────────────── rows.push(MenuRow::Item(cmd(t!("Pan").into_owned(), "PAN"))); rows.push(MenuRow::Item(cmd(t!("Zoom").into_owned(), "ZOOM"))); - rows.push(MenuRow::Item(cmd(t!("Zoom Extents").into_owned(), "ZOOM EXTENTS"))); + // The edit menu is long already; Zoom Extents stays on the idle menu + // (and on the middle button's double-click), as in AutoCAD. + if !has_selection { + rows.push(MenuRow::Item(cmd(t!("Zoom Extents").into_owned(), "ZOOM EXTENTS"))); + } rows.push(MenuRow::Separator); // ── Selection tools ──────────────────────────────────────────────── @@ -594,7 +598,9 @@ fn idle_rows( MenuAction::DeselectAll, ))); } - rows.push(MenuRow::Item(cmd(t!("Select All").into_owned(), "SELECTALL"))); + if !has_selection { + rows.push(MenuRow::Item(cmd(t!("Select All").into_owned(), "SELECTALL"))); + } rows.push(MenuRow::Item(MenuItem::new( t!("Quick Select...").into_owned(), MenuAction::QuickSelect,