fix(input): handle @/# coordinate modes
This commit is contained in:
parent
40b41f9814
commit
d06a1022f2
8 changed files with 101 additions and 11 deletions
|
|
@ -79,6 +79,7 @@ impl OpenCADStudio {
|
||||||
self.otrack_active = None;
|
self.otrack_active = None;
|
||||||
self.axis_lock_dir = None;
|
self.axis_lock_dir = None;
|
||||||
self.dyn_user_reshaped = false;
|
self.dyn_user_reshaped = false;
|
||||||
|
self.dyn_coord_absolute = false;
|
||||||
self.grip_hover = None;
|
self.grip_hover = None;
|
||||||
self.grip_popup = None;
|
self.grip_popup = None;
|
||||||
self.grip_pending = None;
|
self.grip_pending = None;
|
||||||
|
|
@ -252,6 +253,7 @@ impl OpenCADStudio {
|
||||||
};
|
};
|
||||||
self.last_point = Some(*point);
|
self.last_point = Some(*point);
|
||||||
self.dyn_user_reshaped = false;
|
self.dyn_user_reshaped = false;
|
||||||
|
self.dyn_coord_absolute = false;
|
||||||
self.sync_dyn_fields();
|
self.sync_dyn_fields();
|
||||||
self.reset_tracking_after_point();
|
self.reset_tracking_after_point();
|
||||||
self.push_ucs_to_cmd(i);
|
self.push_ucs_to_cmd(i);
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ impl OpenCADStudio {
|
||||||
// default (LIMITS then compares an unintended lower-left point with
|
// default (LIMITS then compares an unintended lower-left point with
|
||||||
// the displayed default upper-right).
|
// the displayed default upper-right).
|
||||||
self.dyn_user_reshaped = false;
|
self.dyn_user_reshaped = false;
|
||||||
|
self.dyn_coord_absolute = false;
|
||||||
self.tabs[i].dyn_fields.clear();
|
self.tabs[i].dyn_fields.clear();
|
||||||
self.tabs[i].dyn_active = 0;
|
self.tabs[i].dyn_active = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -416,6 +416,11 @@ pub(super) struct OpenCADStudio {
|
||||||
/// reverting to the command-default when `has_base` flips. Cleared
|
/// reverting to the command-default when `has_base` flips. Cleared
|
||||||
/// on point commit / command start. See #35.
|
/// on point commit / command start. See #35.
|
||||||
dyn_user_reshaped: bool,
|
dyn_user_reshaped: bool,
|
||||||
|
/// Dynamic cartesian entry mode for the current point. `false` is the
|
||||||
|
/// usual relative-to-last-point mode; typing `#` selects absolute UCS
|
||||||
|
/// coordinates and typing `@` selects relative coordinates again.
|
||||||
|
/// Cleared after a point is committed or a new command starts.
|
||||||
|
dyn_coord_absolute: bool,
|
||||||
/// Grip the cursor is currently dwelling on. Set when the cursor
|
/// Grip the cursor is currently dwelling on. Set when the cursor
|
||||||
/// stops within `GRIP_THRESHOLD_PX` of a grip; cleared when it
|
/// stops within `GRIP_THRESHOLD_PX` of a grip; cleared when it
|
||||||
/// drifts away. The instant lets `ViewportMove` detect when the
|
/// drifts away. The instant lets `ViewportMove` detect when the
|
||||||
|
|
@ -2730,6 +2735,7 @@ impl OpenCADStudio {
|
||||||
ucs_grip_drag: None,
|
ucs_grip_drag: None,
|
||||||
pane_move_from: None,
|
pane_move_from: None,
|
||||||
dyn_user_reshaped: false,
|
dyn_user_reshaped: false,
|
||||||
|
dyn_coord_absolute: false,
|
||||||
grip_hover: None,
|
grip_hover: None,
|
||||||
grip_popup: None,
|
grip_popup: None,
|
||||||
grip_pending: None,
|
grip_pending: None,
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,20 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
self.refresh_active_cmd_preview(i);
|
self.refresh_active_cmd_preview(i);
|
||||||
return self.focus_cmd_input();
|
return self.focus_cmd_input();
|
||||||
}
|
}
|
||||||
|
// In dynamic point entry, `@` and `#` are coordinate-mode
|
||||||
|
// switches rather than command-line text. Consuming them
|
||||||
|
// here keeps the prefix out of the command buffer while
|
||||||
|
// the following digits continue into the dynamic fields.
|
||||||
|
if matches!(s.as_str(), "@" | "#")
|
||||||
|
&& self.command_line.input.is_empty()
|
||||||
|
&& self.dyn_input
|
||||||
|
&& self.dyn_has_coordinate_fields(i)
|
||||||
|
{
|
||||||
|
self.dyn_set_coordinate_mode(s == "#");
|
||||||
|
self.command_line.autocomplete_cursor = None;
|
||||||
|
self.refresh_active_cmd_preview(i);
|
||||||
|
return self.focus_cmd_input();
|
||||||
|
}
|
||||||
// While dynamic input is showing fields, numeric and
|
// While dynamic input is showing fields, numeric and
|
||||||
// expression glyphs edit the focused field instead of
|
// expression glyphs edit the focused field instead of
|
||||||
// the command line. Letters still go to the command line
|
// the command line. Letters still go to the command line
|
||||||
|
|
@ -370,6 +384,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
}
|
}
|
||||||
self.last_point = Some(pt);
|
self.last_point = Some(pt);
|
||||||
self.dyn_user_reshaped = false;
|
self.dyn_user_reshaped = false;
|
||||||
|
self.dyn_coord_absolute = false;
|
||||||
self.sync_dyn_fields();
|
self.sync_dyn_fields();
|
||||||
self.reset_tracking_after_point();
|
self.reset_tracking_after_point();
|
||||||
self.push_ucs_to_cmd(i);
|
self.push_ucs_to_cmd(i);
|
||||||
|
|
@ -415,6 +430,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
}
|
}
|
||||||
self.last_point = Some(wcs_pt);
|
self.last_point = Some(wcs_pt);
|
||||||
self.dyn_user_reshaped = false;
|
self.dyn_user_reshaped = false;
|
||||||
|
self.dyn_coord_absolute = false;
|
||||||
self.sync_dyn_fields();
|
self.sync_dyn_fields();
|
||||||
self.reset_tracking_after_point();
|
self.reset_tracking_after_point();
|
||||||
self.push_ucs_to_cmd(i);
|
self.push_ucs_to_cmd(i);
|
||||||
|
|
|
||||||
|
|
@ -267,6 +267,7 @@ impl OpenCADStudio {
|
||||||
// (The delta is offset-invariant, so only the rotation matters.)
|
// (The delta is offset-invariant, so only the rotation matters.)
|
||||||
let xf = self.tabs[i].ucs_xform();
|
let xf = self.tabs[i].ucs_xform();
|
||||||
let d_ucs = xf.vec_to_ucs(w - base);
|
let d_ucs = xf.vec_to_ucs(w - base);
|
||||||
|
let w_ucs = xf.to_ucs(w);
|
||||||
let dx = d_ucs.x;
|
let dx = d_ucs.x;
|
||||||
let dy = d_ucs.y;
|
let dy = d_ucs.y;
|
||||||
let dz = d_ucs.z;
|
let dz = d_ucs.z;
|
||||||
|
|
@ -319,20 +320,29 @@ impl OpenCADStudio {
|
||||||
// (see #26 / #35). The live cartesian fallback is the cursor
|
// (see #26 / #35). The live cartesian fallback is the cursor
|
||||||
// position relative to base; typed values are relative deltas.
|
// position relative to base; typed values are relative deltas.
|
||||||
let has_base = self.last_point.is_some();
|
let has_base = self.last_point.is_some();
|
||||||
|
let relative = has_base && !self.dyn_coord_absolute;
|
||||||
// Relative result: base + the typed UCS-frame offset mapped to world.
|
// Relative result: base + the typed UCS-frame offset mapped to world.
|
||||||
let rel = |off_ucs: glam::DVec3| base + xf.vec_to_wcs(off_ucs);
|
let rel = |off_ucs: glam::DVec3| base + xf.vec_to_wcs(off_ucs);
|
||||||
match comps.as_slice() {
|
match comps.as_slice() {
|
||||||
[DynComponent::X, DynComponent::Y] if has_base => {
|
[DynComponent::X, DynComponent::Y] if relative => {
|
||||||
Some(rel(glam::DVec3::new(val(0, dx), val(1, dy), 0.0)))
|
Some(rel(glam::DVec3::new(val(0, dx), val(1, dy), 0.0)))
|
||||||
}
|
}
|
||||||
[DynComponent::X, DynComponent::Y] => {
|
[DynComponent::X, DynComponent::Y] => {
|
||||||
Some(glam::DVec3::new(val(0, w.x), val(1, w.y), base.z))
|
Some(xf.to_wcs(glam::DVec3::new(
|
||||||
|
val(0, w_ucs.x),
|
||||||
|
val(1, w_ucs.y),
|
||||||
|
0.0,
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
[DynComponent::X, DynComponent::Y, DynComponent::Z] if has_base => {
|
[DynComponent::X, DynComponent::Y, DynComponent::Z] if relative => {
|
||||||
Some(rel(glam::DVec3::new(val(0, dx), val(1, dy), val(2, dz))))
|
Some(rel(glam::DVec3::new(val(0, dx), val(1, dy), val(2, dz))))
|
||||||
}
|
}
|
||||||
[DynComponent::X, DynComponent::Y, DynComponent::Z] => {
|
[DynComponent::X, DynComponent::Y, DynComponent::Z] => {
|
||||||
Some(glam::DVec3::new(val(0, w.x), val(1, w.y), val(2, base.z)))
|
Some(xf.to_wcs(glam::DVec3::new(
|
||||||
|
val(0, w_ucs.x),
|
||||||
|
val(1, w_ucs.y),
|
||||||
|
val(2, w_ucs.z),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
[DynComponent::Distance, DynComponent::Angle] => {
|
[DynComponent::Distance, DynComponent::Angle] => {
|
||||||
let d = val(0, live_d);
|
let d = val(0, live_d);
|
||||||
|
|
@ -411,6 +421,54 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the current dynamic field set represents a point coordinate
|
||||||
|
/// that can be switched with `@` / `#`.
|
||||||
|
pub(in crate::app) fn dyn_has_coordinate_fields(&self, i: usize) -> bool {
|
||||||
|
use crate::app::document::DynComponent;
|
||||||
|
let comps: Vec<DynComponent> = self.tabs[i]
|
||||||
|
.dyn_fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| field.component)
|
||||||
|
.collect();
|
||||||
|
comps.iter().any(|component| {
|
||||||
|
matches!(
|
||||||
|
component,
|
||||||
|
DynComponent::X | DynComponent::Y | DynComponent::Z
|
||||||
|
)
|
||||||
|
}) || matches!(
|
||||||
|
comps.as_slice(),
|
||||||
|
[DynComponent::Distance, DynComponent::Angle]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Switch the current dynamic point entry between relative (`@`) and
|
||||||
|
/// absolute (`#`) UCS coordinates. Polar fields become cartesian because
|
||||||
|
/// the selected mode applies to coordinate components, not distance/angle.
|
||||||
|
pub(in crate::app) fn dyn_set_coordinate_mode(&mut self, absolute: bool) {
|
||||||
|
use crate::app::document::{DynComponent, DynFieldEntry};
|
||||||
|
let i = self.active_tab;
|
||||||
|
self.dyn_coord_absolute = absolute;
|
||||||
|
|
||||||
|
let comps: Vec<DynComponent> = self.tabs[i]
|
||||||
|
.dyn_fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| field.component)
|
||||||
|
.collect();
|
||||||
|
if matches!(
|
||||||
|
comps.as_slice(),
|
||||||
|
[DynComponent::Distance, DynComponent::Angle]
|
||||||
|
) {
|
||||||
|
let first_buffer = self.tabs[i].dyn_fields[0].buffer.clone();
|
||||||
|
let mut x = DynFieldEntry::new(DynComponent::X);
|
||||||
|
x.buffer = first_buffer;
|
||||||
|
self.tabs[i].dyn_fields = vec![x, DynFieldEntry::new(DynComponent::Y)];
|
||||||
|
self.tabs[i].dyn_active = usize::from(self.tabs[i].dyn_fields[0].locked());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.dyn_user_reshaped = true;
|
||||||
|
self.sync_dyn_fields();
|
||||||
|
}
|
||||||
|
|
||||||
/// If dynamic input has at least one locked (typed) field, resolve the
|
/// If dynamic input has at least one locked (typed) field, resolve the
|
||||||
/// implied point, feed it to the active command as a point pick, reset
|
/// implied point, feed it to the active command as a point pick, reset
|
||||||
/// the field buffers, and return the resulting task. Returns `None`
|
/// the field buffers, and return the resulting task. Returns `None`
|
||||||
|
|
@ -493,6 +551,7 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
self.tabs[i].dyn_active = 0;
|
self.tabs[i].dyn_active = 0;
|
||||||
self.dyn_user_reshaped = false;
|
self.dyn_user_reshaped = false;
|
||||||
|
self.dyn_coord_absolute = false;
|
||||||
self.sync_dyn_fields();
|
self.sync_dyn_fields();
|
||||||
self.reset_tracking_after_point();
|
self.reset_tracking_after_point();
|
||||||
self.push_ucs_to_cmd(i);
|
self.push_ucs_to_cmd(i);
|
||||||
|
|
@ -555,6 +614,7 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
self.last_point = Some(pt);
|
self.last_point = Some(pt);
|
||||||
self.dyn_user_reshaped = false;
|
self.dyn_user_reshaped = false;
|
||||||
|
self.dyn_coord_absolute = false;
|
||||||
self.sync_dyn_fields();
|
self.sync_dyn_fields();
|
||||||
self.reset_tracking_after_point();
|
self.reset_tracking_after_point();
|
||||||
self.push_ucs_to_cmd(i);
|
self.push_ucs_to_cmd(i);
|
||||||
|
|
|
||||||
|
|
@ -2823,6 +2823,7 @@ impl OpenCADStudio {
|
||||||
// restore the running osnap configuration (#337).
|
// restore the running osnap configuration (#337).
|
||||||
self.snapper.clear_override();
|
self.snapper.clear_override();
|
||||||
self.dyn_user_reshaped = false;
|
self.dyn_user_reshaped = false;
|
||||||
|
self.dyn_coord_absolute = false;
|
||||||
self.sync_dyn_fields();
|
self.sync_dyn_fields();
|
||||||
self.reset_tracking_after_point();
|
self.reset_tracking_after_point();
|
||||||
// A running Tangent snap may carry a tangent object; a
|
// A running Tangent snap may carry a tangent object; a
|
||||||
|
|
|
||||||
|
|
@ -185,11 +185,13 @@ pub(super) fn dyn_component_value(
|
||||||
base: Option<glam::DVec3>,
|
base: Option<glam::DVec3>,
|
||||||
xf: &super::super::helpers::UcsXform,
|
xf: &super::super::helpers::UcsXform,
|
||||||
comma_cartesian: bool,
|
comma_cartesian: bool,
|
||||||
|
absolute: bool,
|
||||||
) -> String {
|
) -> String {
|
||||||
if let Some(b) = &f.buffer {
|
if let Some(b) = &f.buffer {
|
||||||
return b.clone();
|
return b.clone();
|
||||||
}
|
}
|
||||||
let b = base.unwrap_or(glam::DVec3::ZERO);
|
let b = base.unwrap_or(glam::DVec3::ZERO);
|
||||||
|
let p = xf.to_ucs(w);
|
||||||
// Relative deltas and the polar angle read in the active UCS plane. The
|
// Relative deltas and the polar angle read in the active UCS plane. The
|
||||||
// delta is offset-invariant, so only the axis rotation matters (identity
|
// delta is offset-invariant, so only the axis rotation matters (identity
|
||||||
// xf reproduces the world-frame deltas).
|
// xf reproduces the world-frame deltas).
|
||||||
|
|
@ -200,21 +202,22 @@ pub(super) fn dyn_component_value(
|
||||||
// fields show relative deltas — matching the typed-value convention
|
// fields show relative deltas — matching the typed-value convention
|
||||||
// in `dyn_resolve_point` so the live preview and the committed
|
// in `dyn_resolve_point` so the live preview and the committed
|
||||||
// coordinate use the same frame. See #35.
|
// coordinate use the same frame. See #35.
|
||||||
let has_base = base.is_some();
|
let relative = base.is_some() && !absolute;
|
||||||
// Width / Height read as unsigned magnitudes (the sign is taken from the
|
// Width / Height read as unsigned magnitudes (the sign is taken from the
|
||||||
// cursor side on commit), matching the rectangle's two-edge entry. But once
|
// cursor side on commit), matching the rectangle's two-edge entry. But once
|
||||||
// the user separates the values with `,` the entry is a cartesian
|
// the user separates the values with `,` the entry is a cartesian
|
||||||
// coordinate pair, so the fields read as signed X/Y deltas to match the
|
// coordinate pair, so the fields read as signed X/Y deltas to match the
|
||||||
// committed point (#269).
|
// committed point (#269).
|
||||||
let wh = matches!(f.role, crate::command::DynRole::Width | crate::command::DynRole::Height)
|
let wh = matches!(f.role, crate::command::DynRole::Width | crate::command::DynRole::Height)
|
||||||
|
&& relative
|
||||||
&& !comma_cartesian;
|
&& !comma_cartesian;
|
||||||
match f.component {
|
match f.component {
|
||||||
DynComponent::X if has_base => format!("{:.4}", if wh { dx.abs() } else { dx }),
|
DynComponent::X if relative => format!("{:.4}", if wh { dx.abs() } else { dx }),
|
||||||
DynComponent::Y if has_base => format!("{:.4}", if wh { dy.abs() } else { dy }),
|
DynComponent::Y if relative => format!("{:.4}", if wh { dy.abs() } else { dy }),
|
||||||
DynComponent::Z if has_base => "0.0000".to_string(),
|
DynComponent::Z if relative => "0.0000".to_string(),
|
||||||
DynComponent::X => format!("{:.4}", w.x),
|
DynComponent::X => format!("{:.4}", p.x),
|
||||||
DynComponent::Y => format!("{:.4}", w.y),
|
DynComponent::Y => format!("{:.4}", p.y),
|
||||||
DynComponent::Z => format!("{:.4}", b.z),
|
DynComponent::Z => format!("{:.4}", p.z),
|
||||||
// Scaled by the role so a diameter box reads twice the radius.
|
// Scaled by the role so a diameter box reads twice the radius.
|
||||||
DynComponent::Distance => {
|
DynComponent::Distance => {
|
||||||
format!("{:.4}", (dx * dx + dy * dy).sqrt() * f.role.value_scale() as f64)
|
format!("{:.4}", (dx * dx + dy * dy).sqrt() * f.role.value_scale() as f64)
|
||||||
|
|
|
||||||
|
|
@ -708,6 +708,7 @@ impl OpenCADStudio {
|
||||||
base,
|
base,
|
||||||
&tab.ucs_xform(),
|
&tab.ucs_xform(),
|
||||||
self.dyn_user_reshaped,
|
self.dyn_user_reshaped,
|
||||||
|
self.dyn_coord_absolute,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
crate::ui::overlay::DynBox {
|
crate::ui::overlay::DynBox {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue