forked from 1b-agent/opencadstudio-upstream
feat(ui): paintbrush cursor while MATCHPROP is armed
MATCHPROP transfers properties from a source object onto others, so the CAD crosshair gives way to a monochrome paintbrush whose tip sits on the pick point - the AutoCAD feel for the same command. The glyph borrows the crosshair's own colour (including a user-customised one) and uses a single stroke weight plus one filled wedge, so it reads as part of the CAD cursor family rather than clip-art. It is drawn instead of the crosshair, never on top of it, and only while the command is armed.
This commit is contained in:
1 parent
acb8c5ea54
commit
1b72115f50
3 files changed
+120
No files matched your search
@@ -20,6 +20,7 @@
|
||||
* **Draggable Tabs & Groups:** Palette tabs can now be reordered by dragging; floating windows support grouped tabs. *(Contributed by [@posnofa](https://github.com/posnofa))*
|
||||
* **Layer Manager Palette:** Integrated `LAYERS` into the docking system as a native pallet. *(Contributed by [@posnofa](https://github.com/posnofa))*
|
||||
* **Graphic Attributes Palette:** Added toggles, controls for open boundaries, color swatches with `*VARIES*` indicator, and persistent docking layout. *(Contributed by [@MarcelP102](https://github.com/MarcelP102))*
|
||||
* **MATCHPROP Paintbrush Cursor:** While `MATCHPROP` is armed the CAD crosshair gives way to a monochrome paintbrush whose tip rides the pick point, echoing the AutoCAD "match properties" cursor. The glyph borrows the crosshair's own colour — including a user-customised one — so only the shape changes, never the ink. *(Contributed by [@Ch4rlos](https://github.com/Ch4rlos))*
|
||||
|
||||
#### Selection & Drafting
|
||||
* **Advanced Selection Filter (`FILTER`):** Added comprehensive selection filtering with normalized logical operators and attribute filters (v2.2). *(Contributed by [@turbuczn](https://github.com/turbuczn))*
|
||||
@@ -110,3 +111,4 @@ Huge thanks to everyone who contributed to this cycle:
|
||||
* **Kevin Griffin** ([@KevinGriffin-new](https://github.com/KevinGriffin-new)) — macOS DMG bundle Applications shortcut link (`#769`).
|
||||
* **francesco fantoni** ([@hvfrancesco](https://github.com/hvfrancesco)) — Translations and UI improvements.
|
||||
* **theluckystrike** ([@theluckystrike](https://github.com/theluckystrike)) — Command line rebound spacebar execution (`#1710`).
|
||||
* **Carlos Quiroga** ([@Ch4rlos](https://github.com/Ch4rlos)) — `MATCHPROP` paintbrush cursor.
|
||||
@@ -959,6 +959,11 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
}),
|
||||
snap_color: self.snap_marker_color,
|
||||
},
|
||||
// MATCHPROP paints properties onto destinations; it gets the
|
||||
// paintbrush cursor instead of the crosshair.
|
||||
tab.active_cmd
|
||||
.as_ref()
|
||||
.is_some_and(|c| c.name() == "MATCHPROP"),
|
||||
crate::ui::overlay::SelectionVisualOptions {
|
||||
area: self.model_space.selection_area,
|
||||
opacity: self.model_space.selection_opacity,
|
||||
|
||||
@@ -71,6 +71,70 @@ fn draw_concentric_constraint_glyph(frame: &mut canvas::Frame, center: Point, co
|
||||
frame.stroke(&canvas::Path::circle(center, 2.15), stroke);
|
||||
}
|
||||
|
||||
/// Strokes a closed four-corner polygon — the outline half of the brush glyph.
|
||||
fn stroke_quad(frame: &mut canvas::Frame, corners: [Point; 4], stroke: &canvas::Stroke<'_>) {
|
||||
let path = canvas::Path::new(|p| {
|
||||
p.move_to(corners[0]);
|
||||
p.line_to(corners[1]);
|
||||
p.line_to(corners[2]);
|
||||
p.line_to(corners[3]);
|
||||
p.close();
|
||||
});
|
||||
frame.stroke(&path, stroke.clone());
|
||||
}
|
||||
|
||||
/// MATCHPROP's paintbrush cursor.
|
||||
///
|
||||
/// While the user is transferring properties the CAD crosshair is replaced by a
|
||||
/// paintbrush whose tip sits exactly on the pick point — the AutoCAD feel. The
|
||||
/// body leans up-and-right so it never covers the object being picked.
|
||||
///
|
||||
/// Deliberately monochrome and geometric: the crosshair's own colour, a single
|
||||
/// stroke weight and one filled wedge, so the glyph reads as part of the CAD
|
||||
/// cursor family rather than clip-art.
|
||||
fn draw_match_brush(frame: &mut canvas::Frame, tip: Point, color: Color) {
|
||||
let inv = std::f32::consts::FRAC_1_SQRT_2;
|
||||
// `along` runs up-and-right (handle direction); `across` is perpendicular.
|
||||
let along = (inv, -inv);
|
||||
let across = (inv, inv);
|
||||
let at = |a: f32, w: f32| {
|
||||
Point::new(
|
||||
tip.x + along.0 * a + across.0 * w,
|
||||
tip.y + along.1 * a + across.1 * w,
|
||||
)
|
||||
};
|
||||
let stroke = canvas::Stroke {
|
||||
width: 1.25,
|
||||
style: canvas::Style::Solid(color),
|
||||
line_join: canvas::LineJoin::Miter,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Bristles: the only solid mass, so the working end carries the weight and
|
||||
// the silhouette still reads at cursor size.
|
||||
let bristles = canvas::Path::new(|p| {
|
||||
p.move_to(at(0.0, 0.0));
|
||||
p.line_to(at(9.0, -3.2));
|
||||
p.line_to(at(12.0, -3.2));
|
||||
p.line_to(at(12.0, 3.2));
|
||||
p.line_to(at(9.0, 3.2));
|
||||
p.close();
|
||||
});
|
||||
frame.fill(&bristles, color);
|
||||
|
||||
// Ferrule and handle stay outline-only: line work, not clip-art.
|
||||
stroke_quad(
|
||||
frame,
|
||||
[at(12.0, -3.2), at(15.5, -3.2), at(15.5, 3.2), at(12.0, 3.2)],
|
||||
&stroke,
|
||||
);
|
||||
stroke_quad(
|
||||
frame,
|
||||
[at(15.5, -2.3), at(30.0, -1.9), at(30.0, 1.9), at(15.5, 2.3)],
|
||||
&stroke,
|
||||
);
|
||||
}
|
||||
|
||||
/// The reference bar's padlock: white with a small orange point marker
|
||||
/// when one point is held, red when a whole curve or segment is.
|
||||
fn draw_fixed_constraint_glyph(
|
||||
@@ -901,6 +965,8 @@ pub fn selection_overlay<'a>(
|
||||
hover_locked: bool,
|
||||
crosshair_bg: [f32; 4],
|
||||
crosshair: CrosshairOptions,
|
||||
// MATCHPROP is armed: paintbrush cursor replaces the crosshair.
|
||||
show_match_brush: bool,
|
||||
selection_visual: SelectionVisualOptions,
|
||||
constraint_glyphs: Arc<[GlyphEntry]>,
|
||||
// Per-glyph selection flags, parallel to `constraint_glyphs`, applied
|
||||
@@ -931,6 +997,7 @@ pub fn selection_overlay<'a>(
|
||||
hover_locked,
|
||||
crosshair_bg,
|
||||
crosshair,
|
||||
show_match_brush,
|
||||
selection_visual,
|
||||
constraint_glyphs,
|
||||
constraint_glyph_selected,
|
||||
@@ -995,6 +1062,9 @@ struct SelectionCanvas {
|
||||
/// rather than the UI theme, which may be light over a dark model viewport.
|
||||
crosshair_bg: [f32; 4],
|
||||
crosshair: CrosshairOptions,
|
||||
/// MATCHPROP is armed: the crosshair gives way to a paintbrush cursor
|
||||
/// whose tip marks the pick point.
|
||||
show_match_brush: bool,
|
||||
selection_visual: SelectionVisualOptions,
|
||||
/// Memoised constraint-glyph entries (precomputed anchor/outward/label +
|
||||
/// layout) shared with the hit-test and tooltip paths — draw reads
|
||||
@@ -1961,11 +2031,54 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
// arrow (see `mouse_interaction`); drawing the CAD crosshair on
|
||||
// top of it would double up the visual feedback.
|
||||
let over_divider = self.divider_under(cursor, bounds);
|
||||
// ── MATCHPROP paintbrush cursor ───────────────────────────────
|
||||
// While MATCHPROP is armed the user is transferring properties from a
|
||||
// source object onto others, so the crosshair gives way to a paintbrush
|
||||
// whose tip rides the pick point — the AutoCAD feel. Drawn *instead of*
|
||||
// the crosshair below, never on top of it.
|
||||
if !over_viewcube
|
||||
&& !over_divider
|
||||
&& self.nav == NavCursor::None
|
||||
&& !self.suppressed
|
||||
&& self.show_match_brush
|
||||
{
|
||||
if let Some(cp) = self
|
||||
.grips
|
||||
.iter()
|
||||
.find(|grip| grip.is_hovered)
|
||||
.map(|grip| grip.pos)
|
||||
.or(self.selection.borrow().input.last_move_pos)
|
||||
{
|
||||
// Inherit the crosshair's own colour (including a custom
|
||||
// crosshair colour) so only the shape changes, never the ink.
|
||||
let [cr, cg, cb, ca] = self.crosshair.color.map_or_else(
|
||||
|| {
|
||||
crate::scene::view::render::adapt_to_bg(
|
||||
[1.0, 1.0, 1.0, 0.90],
|
||||
self.crosshair_bg,
|
||||
)
|
||||
},
|
||||
|[r, g, b]| [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 0.90],
|
||||
);
|
||||
draw_match_brush(
|
||||
&mut frame,
|
||||
cp,
|
||||
Color {
|
||||
r: cr,
|
||||
g: cg,
|
||||
b: cb,
|
||||
a: ca,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// An armed navigation tool replaces the crosshair with its own cursor.
|
||||
if !over_viewcube
|
||||
&& !over_divider
|
||||
&& self.nav == NavCursor::None
|
||||
&& !self.suppressed
|
||||
&& !self.show_match_brush
|
||||
&& self.crosshair.cursor_type == CursorType::Crosshair
|
||||
{
|
||||
// Over a grip the crosshair locks onto it, so the click lands on
|
||||
|
||||
Reference in new issue
Block a user