forked from 1b-agent/opencadstudio-upstream
fix(stretch): free crossing-window corners from Ortho, draw filled marquee (#291)
STRETCH picks its crossing window as two corner points. After the first corner set last_point, Ortho/Polar constrained the opposite corner to an axis through it, collapsing the window to a line and making selection impossible. Add CadCommand::window_corner_pick(); STRETCH returns true for its two window steps, and the viewport skips the ortho/polar (and OTRACK) lock for that point in both the preview and commit paths — the corners are now free. Also render the window like a normal box selection instead of a bare green outline: add SelectionState::preview_box (a hit-test-free marquee), draw it with the shared draw_marquee helper (green crossing fill + stroke), and drive it from the command's new window_first_corner(). Cleared on command end. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
824a508555
commit
e015fbe328
6 files changed
+105
-50
No files matched your search
@@ -909,6 +909,15 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
.as_ref()
|
||||
.map(|c| c.is_selection_gathering())
|
||||
.unwrap_or(false);
|
||||
// A selection-window corner (STRETCH crossing window) is a
|
||||
// free point — Ortho/Polar must not pin it to an axis through
|
||||
// the first corner, or the rectangle collapses to a line
|
||||
// (#291).
|
||||
let is_window_corner = self.tabs[i]
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
.map(|c| c.window_corner_pick())
|
||||
.unwrap_or(false);
|
||||
let needs_tan = self.tabs[i]
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
@@ -956,7 +965,10 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
Instant::now(),
|
||||
);
|
||||
if self.tabs[i].snap_result.is_none() {
|
||||
let step = if self.polar_mode {
|
||||
// A window corner is a free point, so neither the
|
||||
// Polar increment nor the Ortho lock may pull the
|
||||
// OTRACK alignment onto an axis (#291).
|
||||
let step = if self.polar_mode && !is_window_corner {
|
||||
Some(self.polar_increment_deg)
|
||||
} else {
|
||||
None
|
||||
@@ -969,7 +981,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
bounds,
|
||||
step,
|
||||
self.last_point,
|
||||
self.ortho_mode,
|
||||
self.ortho_mode && !is_window_corner,
|
||||
ucs,
|
||||
)
|
||||
} else {
|
||||
@@ -1018,7 +1030,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
let osnap_locked = self.tabs[i]
|
||||
.snap_result
|
||||
.is_some_and(|s| s.snap_type != crate::snap::SnapType::Grid);
|
||||
if !osnap_locked {
|
||||
if !osnap_locked && !is_window_corner {
|
||||
if let Some(base) = self.last_point {
|
||||
let ucs_xf = self.tabs[i].ucs_xform();
|
||||
if self.ortho_mode {
|
||||
@@ -1075,6 +1087,20 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
self.tabs[i].last_point_screen = lps;
|
||||
self.tabs[i].dyn_ref_screen = drs;
|
||||
|
||||
// Point-picked selection window (STRETCH): draw a filled
|
||||
// crossing marquee from the first corner to the cursor so it
|
||||
// reads like a normal box selection, not a bare outline (#291).
|
||||
let window_first = self.tabs[i]
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
.and_then(|c| c.window_first_corner());
|
||||
self.tabs[i].scene.selection.borrow_mut().preview_box =
|
||||
if is_window_corner {
|
||||
window_first.map(|c1| (proj(c1.as_vec3()), p_full, true))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Entity-pick previews (TRIM/EXTEND/FILLET…) compare the
|
||||
// cursor against WCS document entities and return WCS wires.
|
||||
// `effective` is offset-relative, so build a WCS copy for
|
||||
@@ -1812,6 +1838,13 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
.as_ref()
|
||||
.map(|c| c.is_selection_gathering())
|
||||
.unwrap_or(false);
|
||||
// A committed window corner must stay free of the Ortho/Polar
|
||||
// lock too, so the picked rectangle isn't flattened (#291).
|
||||
let is_window_corner = self.tabs[i]
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
.map(|c| c.window_corner_pick())
|
||||
.unwrap_or(false);
|
||||
|
||||
if is_down && is_click && self.tabs[i].active_cmd.is_some() && !is_gathering {
|
||||
let (vw, vh) = (tile_vw, tile_vh);
|
||||
@@ -1876,14 +1909,14 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
// OTRACK alignment wins over ortho/polar; otherwise apply
|
||||
// ortho/polar relative to the last point.
|
||||
let otrack = if snap_hit.is_none() {
|
||||
let step = if self.polar_mode {
|
||||
let step = if self.polar_mode && !is_window_corner {
|
||||
Some(self.polar_increment_deg)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ucs = self.tabs[i].scene.viewcube_ucs_mat();
|
||||
self.snapper
|
||||
.otrack_snap(raw.as_vec3(), view_rot, eye, bounds, step, self.last_point, self.ortho_mode, ucs)
|
||||
.otrack_snap(raw.as_vec3(), view_rot, eye, bounds, step, self.last_point, self.ortho_mode && !is_window_corner, ucs)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -1892,8 +1925,9 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
if self.tabs[i].active_ucs.is_none() {
|
||||
pt.z = 0.0;
|
||||
}
|
||||
} else if !snap_hit
|
||||
.is_some_and(|s| s.snap_type != crate::snap::SnapType::Grid)
|
||||
} else if !is_window_corner
|
||||
&& !snap_hit
|
||||
.is_some_and(|s| s.snap_type != crate::snap::SnapType::Grid)
|
||||
{
|
||||
// Object snap wins over ortho/polar — a snapped point
|
||||
// commits as-is. Grid snap still combines. (#132)
|
||||
|
||||
Reference in new issue
Block a user