Show active tracking reference tooltip
This commit is contained in:
parent
f1bc5f2cae
commit
a06e8a7a69
7 changed files with 144 additions and 1 deletions
|
|
@ -87,6 +87,7 @@ impl OpenCADStudio {
|
||||||
self.snapper.from_point = None;
|
self.snapper.from_point = None;
|
||||||
self.snapper.clear_tracking();
|
self.snapper.clear_tracking();
|
||||||
self.otrack_active = None;
|
self.otrack_active = None;
|
||||||
|
self.otrack_kind = 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.dyn_coord_absolute = false;
|
||||||
|
|
|
||||||
|
|
@ -430,6 +430,9 @@ pub(super) struct OpenCADStudio {
|
||||||
/// cursor is on a tracking ray. Lets a typed distance place a point along
|
/// cursor is on a tracking ray. Lets a typed distance place a point along
|
||||||
/// the ray from the tracking point (issue #69). `None` when not aligned.
|
/// the ray from the tracking point (issue #69). `None` when not aligned.
|
||||||
otrack_active: Option<(glam::DVec3, glam::DVec3)>,
|
otrack_active: Option<(glam::DVec3, glam::DVec3)>,
|
||||||
|
/// Kind of the currently active tracking reference, kept separately from
|
||||||
|
/// `otrack_active` so typed-distance behavior remains unchanged.
|
||||||
|
otrack_kind: Option<crate::snap::TrackingKind>,
|
||||||
/// Whether Tangent snap was enabled before a tangent-pick command started.
|
/// Whether Tangent snap was enabled before a tangent-pick command started.
|
||||||
pre_cmd_tangent: Option<bool>,
|
pre_cmd_tangent: Option<bool>,
|
||||||
/// Drawing defaults captured by ADDSELECTED before it adopts the template
|
/// Drawing defaults captured by ADDSELECTED before it adopts the template
|
||||||
|
|
@ -3052,6 +3055,7 @@ impl OpenCADStudio {
|
||||||
annotation_auto_scale: -4,
|
annotation_auto_scale: -4,
|
||||||
last_saved_config: None,
|
last_saved_config: None,
|
||||||
otrack_active: None,
|
otrack_active: None,
|
||||||
|
otrack_kind: None,
|
||||||
clean_screen: false,
|
clean_screen: false,
|
||||||
quick_properties: false,
|
quick_properties: false,
|
||||||
quick_properties_anchor: Point::new(12.0, 12.0),
|
quick_properties_anchor: Point::new(12.0, 12.0),
|
||||||
|
|
|
||||||
|
|
@ -255,6 +255,7 @@ impl OpenCADStudio {
|
||||||
{
|
{
|
||||||
self.snapper.clear_tracking();
|
self.snapper.clear_tracking();
|
||||||
self.otrack_active = None;
|
self.otrack_active = None;
|
||||||
|
self.otrack_kind = None;
|
||||||
}
|
}
|
||||||
if let Some(started) = perf_started {
|
if let Some(started) = perf_started {
|
||||||
let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0;
|
let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0;
|
||||||
|
|
@ -280,6 +281,7 @@ impl OpenCADStudio {
|
||||||
pub(in crate::app) fn reset_tracking_after_point(&mut self) {
|
pub(in crate::app) fn reset_tracking_after_point(&mut self) {
|
||||||
self.snapper.clear_tracking();
|
self.snapper.clear_tracking();
|
||||||
self.otrack_active = None;
|
self.otrack_active = None;
|
||||||
|
self.otrack_kind = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_inner(&mut self, msg: Message) -> Task<Message> {
|
fn update_inner(&mut self, msg: Message) -> Task<Message> {
|
||||||
|
|
|
||||||
|
|
@ -1083,6 +1083,7 @@ impl OpenCADStudio {
|
||||||
};
|
};
|
||||||
|
|
||||||
self.otrack_active = otrack_hit.map(|hit| (hit.base, hit.dir));
|
self.otrack_active = otrack_hit.map(|hit| (hit.base, hit.dir));
|
||||||
|
self.otrack_kind = otrack_hit.map(|hit| hit.kind);
|
||||||
|
|
||||||
let mut snapped = if let Some(hit) = otrack_hit {
|
let mut snapped = if let Some(hit) = otrack_hit {
|
||||||
hit.aligned
|
hit.aligned
|
||||||
|
|
@ -1519,6 +1520,7 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.otrack_active = otrack_hit.map(|h| (h.base, h.dir));
|
self.otrack_active = otrack_hit.map(|h| (h.base, h.dir));
|
||||||
|
self.otrack_kind = otrack_hit.map(|h| h.kind);
|
||||||
|
|
||||||
// Parallel snap: with nothing else snapped or tracked, lock
|
// Parallel snap: with nothing else snapped or tracked, lock
|
||||||
// the point onto the line through last_point parallel to the
|
// the point onto the line through last_point parallel to the
|
||||||
|
|
|
||||||
|
|
@ -809,6 +809,17 @@ impl OpenCADStudio {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|c| c.prompt())
|
.map(|c| c.prompt())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let tracking_hint = match self.otrack_kind {
|
||||||
|
Some(crate::snap::TrackingKind::Perpendicular) => {
|
||||||
|
Some("Perpendicular".to_string())
|
||||||
|
}
|
||||||
|
Some(crate::snap::TrackingKind::Extension) => {
|
||||||
|
Some("Extension".to_string())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
Some(crate::ui::overlay::dynamic_input_overlay(
|
Some(crate::ui::overlay::dynamic_input_overlay(
|
||||||
tab.last_cursor_screen,
|
tab.last_cursor_screen,
|
||||||
tab.last_point_screen,
|
tab.last_point_screen,
|
||||||
|
|
@ -816,6 +827,7 @@ impl OpenCADStudio {
|
||||||
tab.dyn_guide,
|
tab.dyn_guide,
|
||||||
boxes,
|
boxes,
|
||||||
prompt,
|
prompt,
|
||||||
|
tracking_hint,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
||||||
19
src/snap.rs
19
src/snap.rs
|
|
@ -15,6 +15,12 @@ const DEFAULT_OSNAP_RADIUS_PX: f32 = 15.0;
|
||||||
|
|
||||||
// ── Snap type ─────────────────────────────────────────────────────────────
|
// ── Snap type ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum TrackingKind {
|
||||||
|
Generic,
|
||||||
|
Extension,
|
||||||
|
Perpendicular,
|
||||||
|
}
|
||||||
/// Every OSNAP mode — mirrors the OpenCADStudio list.
|
/// Every OSNAP mode — mirrors the OpenCADStudio list.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum SnapType {
|
pub enum SnapType {
|
||||||
|
|
@ -92,6 +98,8 @@ pub struct OtrackHit {
|
||||||
pub dir: DVec3,
|
pub dir: DVec3,
|
||||||
/// The tracking point the ray emanates from.
|
/// The tracking point the ray emanates from.
|
||||||
pub base: DVec3,
|
pub base: DVec3,
|
||||||
|
|
||||||
|
pub kind: TrackingKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Snapper ───────────────────────────────────────────────────────────────
|
// ── Snapper ───────────────────────────────────────────────────────────────
|
||||||
|
|
@ -511,6 +519,7 @@ impl Snapper {
|
||||||
origin: DVec3,
|
origin: DVec3,
|
||||||
dir: DVec3,
|
dir: DVec3,
|
||||||
group: usize,
|
group: usize,
|
||||||
|
kind: TrackingKind,
|
||||||
}
|
}
|
||||||
let mut rays: Vec<Ray> = Vec::new();
|
let mut rays: Vec<Ray> = Vec::new();
|
||||||
for (gi, &tp) in self.tracking_points.iter().enumerate() {
|
for (gi, &tp) in self.tracking_points.iter().enumerate() {
|
||||||
|
|
@ -520,6 +529,7 @@ impl Snapper {
|
||||||
origin: tp,
|
origin: tp,
|
||||||
dir: ucs_x * ar.cos() + ucs_y * ar.sin(),
|
dir: ucs_x * ar.cos() + ucs_y * ar.sin(),
|
||||||
group: gi,
|
group: gi,
|
||||||
|
kind: TrackingKind::Generic,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Extension rays along the corner's own edges (world-space geometry
|
// Extension rays along the corner's own edges (world-space geometry
|
||||||
|
|
@ -532,6 +542,7 @@ impl Snapper {
|
||||||
origin: tp,
|
origin: tp,
|
||||||
dir: d,
|
dir: d,
|
||||||
group: gi,
|
group: gi,
|
||||||
|
kind: TrackingKind::Extension,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -547,6 +558,7 @@ impl Snapper {
|
||||||
origin: tp,
|
origin: tp,
|
||||||
dir: d,
|
dir: d,
|
||||||
group: gi,
|
group: gi,
|
||||||
|
kind: TrackingKind::Perpendicular,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -566,6 +578,7 @@ impl Snapper {
|
||||||
origin: bp,
|
origin: bp,
|
||||||
dir: DVec3::new(d.x * inv, d.y * inv, 0.0),
|
dir: DVec3::new(d.x * inv, d.y * inv, 0.0),
|
||||||
group: gi,
|
group: gi,
|
||||||
|
kind: TrackingKind::Generic,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -583,6 +596,7 @@ impl Snapper {
|
||||||
origin: lp,
|
origin: lp,
|
||||||
dir: ucs_x * ar.cos() + ucs_y * ar.sin(),
|
dir: ucs_x * ar.cos() + ucs_y * ar.sin(),
|
||||||
group: POLAR_GROUP,
|
group: POLAR_GROUP,
|
||||||
|
kind: TrackingKind::Generic,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -596,6 +610,7 @@ impl Snapper {
|
||||||
origin: lp,
|
origin: lp,
|
||||||
dir: ucs_x * ar.cos() + ucs_y * ar.sin(),
|
dir: ucs_x * ar.cos() + ucs_y * ar.sin(),
|
||||||
group: ORTHO_GROUP,
|
group: ORTHO_GROUP,
|
||||||
|
kind: TrackingKind::Generic,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -634,7 +649,8 @@ impl Snapper {
|
||||||
aligned: x,
|
aligned: x,
|
||||||
dir: dir_out,
|
dir: dir_out,
|
||||||
base: ot.origin,
|
base: ot.origin,
|
||||||
},
|
kind: ot.kind,
|
||||||
|
}
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -670,6 +686,7 @@ impl Snapper {
|
||||||
aligned,
|
aligned,
|
||||||
dir: dir_out,
|
dir: dir_out,
|
||||||
base: ray.origin,
|
base: ray.origin,
|
||||||
|
kind: ray.kind,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2166,6 +2166,7 @@ pub fn dynamic_input_overlay<'a>(
|
||||||
guide: DynGuide,
|
guide: DynGuide,
|
||||||
boxes: Vec<DynBox>,
|
boxes: Vec<DynBox>,
|
||||||
prompt: String,
|
prompt: String,
|
||||||
|
tracking_hint: Option<String>,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
canvas(DynInputCanvas {
|
canvas(DynInputCanvas {
|
||||||
cursor_screen,
|
cursor_screen,
|
||||||
|
|
@ -2174,6 +2175,7 @@ pub fn dynamic_input_overlay<'a>(
|
||||||
guide,
|
guide,
|
||||||
boxes,
|
boxes,
|
||||||
prompt,
|
prompt,
|
||||||
|
tracking_hint,
|
||||||
})
|
})
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
|
|
@ -2191,6 +2193,8 @@ struct DynInputCanvas {
|
||||||
boxes: Vec<DynBox>,
|
boxes: Vec<DynBox>,
|
||||||
/// The active command's current prompt, drawn just above the boxes.
|
/// The active command's current prompt, drawn just above the boxes.
|
||||||
prompt: String,
|
prompt: String,
|
||||||
|
/// Informational tracking/reference mode currently engaged.
|
||||||
|
tracking_hint: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DynInputCanvas {
|
impl DynInputCanvas {
|
||||||
|
|
@ -2296,6 +2300,49 @@ impl DynInputCanvas {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_tracking_hint(
|
||||||
|
&self,
|
||||||
|
frame: &mut canvas::Frame,
|
||||||
|
pos: Point,
|
||||||
|
theme: &Theme,
|
||||||
|
) {
|
||||||
|
let Some(text) = self.tracking_hint.as_deref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let palette = theme.palette();
|
||||||
|
let pw = (text.len() as f32 * DYN_CHAR_W) + DYN_PAD * 2.0;
|
||||||
|
|
||||||
|
let rect = canvas::Path::rectangle(
|
||||||
|
pos,
|
||||||
|
Size {
|
||||||
|
width: pw,
|
||||||
|
height: DYN_BOX_H,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
frame.fill(&rect, palette.background.strong.color);
|
||||||
|
|
||||||
|
frame.stroke(
|
||||||
|
&rect,
|
||||||
|
canvas::Stroke::default()
|
||||||
|
.with_color(palette.primary.base.color.scale_alpha(0.9))
|
||||||
|
.with_width(1.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
frame.fill_text(canvas::Text {
|
||||||
|
content: text.to_string(),
|
||||||
|
position: Point {
|
||||||
|
x: pos.x + DYN_PAD,
|
||||||
|
y: pos.y + DYN_PAD,
|
||||||
|
},
|
||||||
|
color: palette.background.strong.text,
|
||||||
|
size: iced::Pixels(DYN_FONT),
|
||||||
|
shaping: iced::advanced::text::Shaping::Advanced,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Guided layout: draw the guide geometry anchored at `base`, then place
|
/// Guided layout: draw the guide geometry anchored at `base`, then place
|
||||||
/// each box according to its role.
|
/// each box according to its role.
|
||||||
fn draw_guided(
|
fn draw_guided(
|
||||||
|
|
@ -2496,6 +2543,32 @@ impl DynInputCanvas {
|
||||||
};
|
};
|
||||||
Self::draw_box(frame, b, center, bounds, theme);
|
Self::draw_box(frame, b, center, bounds, theme);
|
||||||
}
|
}
|
||||||
|
// Guided dynamic-input layouts place their editable boxes on the
|
||||||
|
// construction geometry, so keep the tracking-reference pill near
|
||||||
|
// the crosshair instead.
|
||||||
|
if let Some(text) = self.tracking_hint.as_deref() {
|
||||||
|
let hw = (text.len() as f32 * DYN_CHAR_W) + DYN_PAD * 2.0;
|
||||||
|
|
||||||
|
let mut hx = self.cursor_screen.x + DYN_OFFSET_X;
|
||||||
|
let mut hy = self.cursor_screen.y + DYN_OFFSET_X;
|
||||||
|
|
||||||
|
if hx + hw > bounds.width {
|
||||||
|
hx = (self.cursor_screen.x - hw - 4.0).max(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if hy + DYN_BOX_H > bounds.height {
|
||||||
|
hy = (self.cursor_screen.y - DYN_BOX_H - 4.0).max(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.draw_tracking_hint(
|
||||||
|
frame,
|
||||||
|
Point {
|
||||||
|
x: hx,
|
||||||
|
y: hy,
|
||||||
|
},
|
||||||
|
theme,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fallback row layout near the cursor (no anchor / `None` guide).
|
/// Fallback row layout near the cursor (no anchor / `None` guide).
|
||||||
|
|
@ -2563,6 +2636,25 @@ impl DynInputCanvas {
|
||||||
});
|
});
|
||||||
x += w + DYN_GAP;
|
x += w + DYN_GAP;
|
||||||
}
|
}
|
||||||
|
// Informational tracking reference, using the same pill style as
|
||||||
|
// the command prompt. Keep it below the editable value row.
|
||||||
|
if self.tracking_hint.is_some() {
|
||||||
|
let mut hy = by + DYN_BOX_H + 3.0;
|
||||||
|
|
||||||
|
// If there is no room below, place it above the prompt/value block.
|
||||||
|
if hy + DYN_BOX_H > bounds.height {
|
||||||
|
hy = (py - DYN_BOX_H - 3.0).max(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.draw_tracking_hint(
|
||||||
|
frame,
|
||||||
|
Point {
|
||||||
|
x: bx,
|
||||||
|
y: hy,
|
||||||
|
},
|
||||||
|
theme,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2601,6 +2693,19 @@ impl canvas::Program<Message> for DynInputCanvas {
|
||||||
py = (self.cursor_screen.y - DYN_BOX_H - 4.0).max(0.0);
|
py = (self.cursor_screen.y - DYN_BOX_H - 4.0).max(0.0);
|
||||||
}
|
}
|
||||||
self.draw_prompt(&mut frame, Point { x: px, y: py }, theme);
|
self.draw_prompt(&mut frame, Point { x: px, y: py }, theme);
|
||||||
|
|
||||||
|
if self.tracking_hint.is_some() {
|
||||||
|
let hint_y = py + DYN_BOX_H + 3.0;
|
||||||
|
|
||||||
|
self.draw_tracking_hint(
|
||||||
|
&mut frame,
|
||||||
|
Point {
|
||||||
|
x: px,
|
||||||
|
y: hint_y,
|
||||||
|
},
|
||||||
|
theme,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return vec![frame.into_geometry()];
|
return vec![frame.into_geometry()];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue