forked from 1b-agent/opencadstudio-upstream
feat(snap): expose MTP in snap override and context menus
The MTP/M2P midpoint-between-two-points modifier existed as a typed transparent command only. Add it to the Shift+RMB snap override menu as an icon-only cell after Parallel (hover tooltip, no text) with a new osnap/mtp.svg marker, plus a context-menu entry while a point prompt is active.
This commit is contained in:
1 parent
7d0c059b30
commit
03075612fa
6 files changed
+77
-12
No files matched your search
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#B4B6B9" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8.26 16.05 15.74H7.95Z"/><circle cx="4.5" cy="19.5" r="1.5" fill="#B4B6B9" stroke="none"/><circle cx="19.5" cy="4.5" r="1.5" fill="#B4B6B9" stroke="none"/></svg>
|
||||
|
After Width: | Height: | Size: 330 B |
+5
-2
@@ -1892,9 +1892,12 @@ pub enum Message {
|
||||
WebFieldCopy,
|
||||
/// Web only: the focused field's text — write it to the clipboard.
|
||||
WebFieldCopyText(Option<String>),
|
||||
/// Pick from the one-shot snap override menu (Shift+RMB): only this snap
|
||||
/// applies to the next point pick, then the configuration restores (#337).
|
||||
/// Pick from the one-shot snap override menu (Shift+RMB): only this
|
||||
/// snap applies to the next point pick, then the configuration restores (#337).
|
||||
SnapOverridePick(crate::snap::SnapType),
|
||||
/// Mid Between 2 Points from the snap menu: modal 2-pick modifier over
|
||||
/// the active point prompt (AutoCAD `_M2P`).
|
||||
SnapOverrideMtp,
|
||||
/// Close the one-shot snap override menu without picking.
|
||||
SnapOverrideClose,
|
||||
/// Open a path from the Start tab's recent-documents list (skips the
|
||||
|
||||
@@ -680,6 +680,29 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::SnapOverrideMtp => {
|
||||
self.snap_override_popup = None;
|
||||
self.tabs[self.active_tab]
|
||||
.scene
|
||||
.selection
|
||||
.borrow_mut()
|
||||
.context_menu = None;
|
||||
// Same guard as typed MTP/M2P: point step, not entity pick.
|
||||
let i = self.active_tab;
|
||||
let allowed = self.tabs[i].active_cmd.as_ref().is_some_and(|c| {
|
||||
(!c.input_kind().wants_text() || c.point_step_accepts_keywords())
|
||||
&& !c.needs_entity_pick()
|
||||
});
|
||||
if allowed {
|
||||
self.start_mtp_modifier(i);
|
||||
} else {
|
||||
self.command_line.push_info(
|
||||
crate::t!("MTP needs an active point prompt.").as_ref(),
|
||||
);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::SnapOverrideClose => {
|
||||
self.snap_override_popup = None;
|
||||
Task::none()
|
||||
|
||||
@@ -1629,6 +1629,11 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
};
|
||||
if let Some(p) = ctx_pos {
|
||||
let has_cmd = tab.active_cmd.is_some();
|
||||
// Same guard as typed MTP/M2P and SnapOverrideMtp.
|
||||
let has_point_step = tab.active_cmd.as_ref().is_some_and(|c| {
|
||||
(!c.input_kind().wants_text() || c.point_step_accepts_keywords())
|
||||
&& !c.needs_entity_pick()
|
||||
});
|
||||
let has_selection = !tab.scene.selected.is_empty();
|
||||
let isolation_active = tab.scene.is_isolation_active();
|
||||
let last_cmds: Vec<String> = self
|
||||
@@ -1647,6 +1652,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
isolation_active,
|
||||
last_cmds,
|
||||
draworder_open,
|
||||
has_point_step,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
+36
-10
@@ -1102,6 +1102,7 @@ pub(super) fn viewport_context_menu_overlay(
|
||||
isolation_active: bool,
|
||||
last_cmds: Vec<String>,
|
||||
draworder_open: bool,
|
||||
has_point_step: bool,
|
||||
) -> Element<'static, Message> {
|
||||
let item = |label: String, msg: Message| -> Element<'static, Message> {
|
||||
button(text(label).size(12))
|
||||
@@ -1146,6 +1147,15 @@ pub(super) fn viewport_context_menu_overlay(
|
||||
if has_cmd {
|
||||
items.push(item(t!("Cancel").into_owned(), Message::CommandEscape));
|
||||
items.push(item(t!("Enter").into_owned(), Message::CommandFinalize));
|
||||
// MTP (`_M2P`) goes last, after Parallel: two picks, so not a
|
||||
// `SnapType`, but same icon-only cell with hover tooltip.
|
||||
if has_point_step {
|
||||
items.push(sep());
|
||||
items.push(item(
|
||||
t!("Mid Between 2 Points (M2P)").into_owned(),
|
||||
Message::SnapOverrideMtp,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if !last_cmds.is_empty() {
|
||||
let last = last_cmds[0].clone();
|
||||
@@ -1262,21 +1272,19 @@ pub(super) fn viewport_context_menu_overlay(
|
||||
|
||||
/// One-shot snap override menu (Shift+RMB, #337): a cursor-anchored grid of
|
||||
/// snap ICONS only — the names show as hover tooltips. Picking one applies
|
||||
/// that snap to just the next point pick.
|
||||
/// that snap to just the next point pick; the trailing MTP cell instead
|
||||
/// suspends the prompt for two picks and returns their midpoint.
|
||||
pub(super) fn snap_override_overlay(pos: iced::Point) -> Element<'static, Message> {
|
||||
const COLS: usize = 4;
|
||||
|
||||
let cell = |snap_type: crate::snap::SnapType, label: &'static str| -> Element<'static, Message> {
|
||||
let icon = container(crate::ui::icons::themed::<Message>(
|
||||
crate::ui::icons::osnap(snap_type),
|
||||
16.0,
|
||||
))
|
||||
let cell_icon = |icon: &'static [u8], label: String, msg: Message| -> Element<'static, Message> {
|
||||
let icon = container(crate::ui::icons::themed::<Message>(icon, 16.0))
|
||||
.width(26)
|
||||
.height(26)
|
||||
.align_x(iced::Center)
|
||||
.align_y(iced::Center);
|
||||
let btn = button(icon)
|
||||
.on_press(Message::SnapOverridePick(snap_type))
|
||||
.on_press(msg)
|
||||
.style(|theme: &Theme, status| button::Style {
|
||||
background: matches!(
|
||||
status,
|
||||
@@ -1312,11 +1320,29 @@ pub(super) fn snap_override_overlay(pos: iced::Point) -> Element<'static, Messag
|
||||
.into()
|
||||
};
|
||||
|
||||
// MTP (`_M2P`) goes last, after Parallel: two picks, so not a
|
||||
// `SnapType`, but same icon-only cell with hover tooltip.
|
||||
let mut cells: Vec<Element<'static, Message>> = Vec::with_capacity(
|
||||
crate::snap::ALL_SNAP_MODES.len() + 1,
|
||||
);
|
||||
for &(snap_type, _glyph, label) in crate::snap::ALL_SNAP_MODES {
|
||||
cells.push(cell_icon(
|
||||
crate::ui::icons::osnap(snap_type),
|
||||
label.to_string(),
|
||||
Message::SnapOverridePick(snap_type),
|
||||
));
|
||||
}
|
||||
cells.push(cell_icon(
|
||||
crate::ui::icons::mtp_icon(),
|
||||
t!("Mid Between 2 Points (M2P)").into_owned(),
|
||||
Message::SnapOverrideMtp,
|
||||
));
|
||||
let mut grid = column![].spacing(2);
|
||||
for chunk in crate::snap::ALL_SNAP_MODES.chunks(COLS) {
|
||||
while !cells.is_empty() {
|
||||
let n = cells.len().min(COLS);
|
||||
let mut r = row![].spacing(2);
|
||||
for &(snap_type, _glyph, label) in chunk {
|
||||
r = r.push(cell(snap_type, label));
|
||||
for c in cells.drain(..n) {
|
||||
r = r.push(c);
|
||||
}
|
||||
grid = grid.push(r);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ static OSNAP_NEAREST: &[u8] = include_bytes!("../../assets/icons/osnap/nearest.s
|
||||
static OSNAP_APPARENT: &[u8] = include_bytes!("../../assets/icons/osnap/apparent.svg");
|
||||
static OSNAP_PARALLEL: &[u8] = include_bytes!("../../assets/icons/osnap/parallel.svg");
|
||||
static OSNAP_GRID: &[u8] = include_bytes!("../../assets/icons/osnap/grid.svg");
|
||||
static OSNAP_MTP: &[u8] = include_bytes!("../../assets/icons/osnap/mtp.svg");
|
||||
|
||||
static LAY_ON: &[u8] = include_bytes!("../../assets/icons/layers/layon.svg");
|
||||
static LAY_OFF: &[u8] = include_bytes!("../../assets/icons/layers/layoff.svg");
|
||||
@@ -595,6 +596,11 @@ pub fn osnap(snap: crate::snap::SnapType) -> &'static [u8] {
|
||||
}
|
||||
}
|
||||
|
||||
/// MTP menu icon: modal 2-pick modifier, not a persistent `SnapType` mode.
|
||||
pub fn mtp_icon() -> &'static [u8] {
|
||||
OSNAP_MTP
|
||||
}
|
||||
|
||||
/// Layer visibility icon bytes (on / off).
|
||||
pub fn layer_visible(visible: bool) -> &'static [u8] {
|
||||
if visible {
|
||||
|
||||
Reference in new issue
Block a user