feat(hatch): editable pattern props + origin grip
Merge duplicate Type into pattern_type; Type/Associative/Island detection as dropdown/toggle; Angle/Scale/Spacing/Origin X-Y editable; Color via picker. Reorder Pattern group, drop redundant Color/Transparency/Layer override and the Misc section. Wire the pattern origin (per-line base_point) into the render (x0/y0 relative to world_origin) so it affects dashed/offset patterns; add a circle origin grip at the hatch centroid (new GripShape::Circle). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
954a2e8b43
commit
22059e138c
5 changed files with 247 additions and 61 deletions
|
|
@ -141,6 +141,18 @@ pub fn center_grip(id: usize, world: glam::DVec3) -> GripDef {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Circle grip — a round handle, flagged as a whole-object move. Used for
|
||||||
|
/// special anchors like a hatch's pattern origin.
|
||||||
|
pub fn circle_grip(id: usize, world: glam::DVec3) -> GripDef {
|
||||||
|
GripDef {
|
||||||
|
id,
|
||||||
|
world,
|
||||||
|
is_midpoint: true,
|
||||||
|
shape: GripShape::Circle,
|
||||||
|
dir: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Mid-segment stretch grip oriented along `dir` (the segment's in-plane
|
/// Mid-segment stretch grip oriented along `dir` (the segment's in-plane
|
||||||
/// world-XY direction). Drawn as a small rectangle elongated along the
|
/// world-XY direction). Drawn as a small rectangle elongated along the
|
||||||
/// segment so the affordance reads as "stretch perpendicular".
|
/// segment so the affordance reads as "stretch perpendicular".
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use acadrust::entities::{BoundaryEdge, Hatch};
|
||||||
use glam::Vec3;
|
use glam::Vec3;
|
||||||
|
|
||||||
use crate::command::EntityTransform;
|
use crate::command::EntityTransform;
|
||||||
use crate::entities::common::{center_grip, edit_prop as edit, parse_f64, ro_prop as ro};
|
use crate::entities::common::{center_grip, circle_grip, edit_prop as edit, parse_f64, ro_prop as ro};
|
||||||
use crate::entities::traits::{FallbackTess, Grippable, PropertyEditable, Transformable};
|
use crate::entities::traits::{FallbackTess, Grippable, PropertyEditable, Transformable};
|
||||||
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||||
use crate::scene::convert::tess_util::{arc_segments, arc_signed_span, wire_chord_tol, FallbackGeometry};
|
use crate::scene::convert::tess_util::{arc_segments, arc_signed_span, wire_chord_tol, FallbackGeometry};
|
||||||
|
|
@ -56,6 +56,49 @@ fn boundary_area(h: &Hatch) -> f64 {
|
||||||
area
|
area
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mean of every boundary edge point (OCS) — the centroid used to place the
|
||||||
|
/// pattern-origin grip on the hatch body. `None` when there are no boundary
|
||||||
|
/// points.
|
||||||
|
fn boundary_centroid(h: &Hatch) -> Option<(f64, f64)> {
|
||||||
|
let mut sx = 0.0;
|
||||||
|
let mut sy = 0.0;
|
||||||
|
let mut n = 0.0;
|
||||||
|
let mut add = |x: f64, y: f64| {
|
||||||
|
sx += x;
|
||||||
|
sy += y;
|
||||||
|
n += 1.0;
|
||||||
|
};
|
||||||
|
for path in &h.paths {
|
||||||
|
for edge in &path.edges {
|
||||||
|
match edge {
|
||||||
|
BoundaryEdge::Polyline(p) => {
|
||||||
|
for v in &p.vertices {
|
||||||
|
add(v.x, v.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BoundaryEdge::Line(l) => {
|
||||||
|
add(l.start.x, l.start.y);
|
||||||
|
add(l.end.x, l.end.y);
|
||||||
|
}
|
||||||
|
BoundaryEdge::CircularArc(a) => add(a.center.x, a.center.y),
|
||||||
|
BoundaryEdge::EllipticArc(e) => add(e.center.x, e.center.y),
|
||||||
|
BoundaryEdge::Spline(s) => {
|
||||||
|
if !s.fit_points.is_empty() {
|
||||||
|
for p in &s.fit_points {
|
||||||
|
add(p.x, p.y);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for p in &s.control_points {
|
||||||
|
add(p.x, p.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(n > 0.0).then(|| (sx / n, sy / n))
|
||||||
|
}
|
||||||
|
|
||||||
/// Format a color for a read-only row (`ByLayer` / `ByBlock` / index / RGB).
|
/// Format a color for a read-only row (`ByLayer` / `ByBlock` / index / RGB).
|
||||||
fn color_str(c: acadrust::types::Color) -> String {
|
fn color_str(c: acadrust::types::Color) -> String {
|
||||||
c.to_string()
|
c.to_string()
|
||||||
|
|
@ -120,58 +163,83 @@ fn properties(h: &Hatch) -> Vec<PropSection> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Hatch (pattern / solid) ────────────────────────────────────────────
|
// ── Hatch (pattern / solid) ────────────────────────────────────────────
|
||||||
let hatch_type = if h.is_solid { "Solid" } else { "Pattern" };
|
// "Type" = pattern definition source (Predefined / User Defined / Custom).
|
||||||
|
let type_row = Property {
|
||||||
|
label: "Type".into(),
|
||||||
|
field: "pattern_type_label",
|
||||||
|
value: PropValue::Choice {
|
||||||
|
selected: pattern_type.to_string(),
|
||||||
|
options: vec!["Predefined".into(), "User Defined".into(), "Custom".into()],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let pattern_name_row = Property {
|
||||||
|
label: "Pattern name".into(),
|
||||||
|
field: "pattern_name",
|
||||||
|
value: PropValue::HatchPatternChoice(h.pattern.name.clone()),
|
||||||
|
};
|
||||||
|
let associative_row = Property {
|
||||||
|
label: "Associative".into(),
|
||||||
|
field: "associative",
|
||||||
|
value: PropValue::BoolToggle {
|
||||||
|
field: "associative",
|
||||||
|
value: h.is_associative,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let double_row = Property {
|
||||||
|
label: "Double".into(),
|
||||||
|
field: "double",
|
||||||
|
value: PropValue::BoolToggle {
|
||||||
|
field: "double",
|
||||||
|
value: h.is_double,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let island_row = Property {
|
||||||
|
label: "Island detection style".into(),
|
||||||
|
field: "style",
|
||||||
|
value: PropValue::Choice {
|
||||||
|
selected: style.to_string(),
|
||||||
|
options: vec!["Normal".into(), "Outer".into(), "Ignore".into()],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let spacing_row = edit(
|
||||||
|
"Spacing",
|
||||||
|
"spacing",
|
||||||
|
h.pattern
|
||||||
|
.lines
|
||||||
|
.first()
|
||||||
|
.map(|l| l.offset.length())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
// Pattern tiling origin: the base point the pattern lines are anchored to.
|
||||||
|
let (origin_x, origin_y) = h
|
||||||
|
.pattern
|
||||||
|
.lines
|
||||||
|
.first()
|
||||||
|
.map(|l| (l.base_point.x, l.base_point.y))
|
||||||
|
.unwrap_or((0.0, 0.0));
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
PropSection {
|
PropSection {
|
||||||
title: "Pattern".into(),
|
title: "Pattern".into(),
|
||||||
props: vec![
|
props: vec![
|
||||||
ro("Type", "pattern_type_label", hatch_type),
|
type_row,
|
||||||
Property {
|
pattern_name_row,
|
||||||
label: "Pattern name".into(),
|
ro("Annotative", "annotative", String::new()),
|
||||||
field: "pattern_name",
|
|
||||||
value: PropValue::HatchPatternChoice(h.pattern.name.clone()),
|
|
||||||
},
|
|
||||||
ro("Color", "color", color_str(h.common.color)),
|
|
||||||
ro("Background color", "background_color", String::new()),
|
|
||||||
ro(
|
|
||||||
"Transparency",
|
|
||||||
"transparency",
|
|
||||||
format!("{}%", (h.common.transparency.as_percent() * 100.0).round() as i32),
|
|
||||||
),
|
|
||||||
edit("Angle", "pattern_angle", h.pattern_angle.to_degrees()),
|
edit("Angle", "pattern_angle", h.pattern_angle.to_degrees()),
|
||||||
edit("Scale", "pattern_scale", h.pattern_scale),
|
edit("Scale", "pattern_scale", h.pattern_scale),
|
||||||
ro("Origin X", "origin_x", String::new()),
|
edit("Origin X", "origin_x", origin_x),
|
||||||
ro("Origin Y", "origin_y", String::new()),
|
edit("Origin Y", "origin_y", origin_y),
|
||||||
ro("Annotative", "annotative", String::new()),
|
spacing_row,
|
||||||
ro(
|
|
||||||
"Associative",
|
|
||||||
"associative",
|
|
||||||
if h.is_associative { "Yes" } else { "No" },
|
|
||||||
),
|
|
||||||
ro("Layer override", "layer_override", String::new()),
|
|
||||||
Property {
|
|
||||||
label: "Double".into(),
|
|
||||||
field: "double",
|
|
||||||
value: PropValue::BoolToggle {
|
|
||||||
field: "double",
|
|
||||||
value: h.is_double,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ro(
|
|
||||||
"Spacing",
|
|
||||||
"spacing",
|
|
||||||
h.pattern
|
|
||||||
.lines
|
|
||||||
.first()
|
|
||||||
.map(|l| format!("{:.4}", l.offset.length()))
|
|
||||||
.unwrap_or_default(),
|
|
||||||
),
|
|
||||||
ro("ISO pen width", "iso_pen_width", String::new()),
|
ro("ISO pen width", "iso_pen_width", String::new()),
|
||||||
|
double_row,
|
||||||
|
associative_row,
|
||||||
|
island_row,
|
||||||
|
ro("Background color", "background_color", String::new()),
|
||||||
|
// ── Gradient info (read-only) ──────────────────────────────
|
||||||
ro("Gradient colors", "gradient_colors", g.colors.len().to_string()),
|
ro("Gradient colors", "gradient_colors", g.colors.len().to_string()),
|
||||||
ro("Gradient tint", "gradient_tint", format!("{:.4}", g.color_tint)),
|
ro("Gradient tint", "gradient_tint", format!("{:.4}", g.color_tint)),
|
||||||
ro("Gradient color 1", "gradient_color_1", grad_c1),
|
ro("Gradient color 1", "gradient_color_1", grad_c1),
|
||||||
ro("Gradient color 2", "gradient_color_2", grad_c2),
|
ro("Gradient color 2", "gradient_color_2", grad_c2),
|
||||||
ro("Pattern type", "pattern_type", pattern_type),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
PropSection {
|
PropSection {
|
||||||
|
|
@ -182,24 +250,49 @@ fn properties(h: &Hatch) -> Vec<PropSection> {
|
||||||
ro("Cumulative area", "cumulative_area", format!("{:.4}", area)),
|
ro("Cumulative area", "cumulative_area", format!("{:.4}", area)),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
PropSection {
|
|
||||||
title: "Misc".into(),
|
|
||||||
props: vec![
|
|
||||||
ro("Boundary retention", "boundary_retention", String::new()),
|
|
||||||
ro("Island detection style", "style", style),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_geom_prop(h: &mut Hatch, field: &str, value: &str) {
|
fn apply_geom_prop(h: &mut Hatch, field: &str, value: &str) {
|
||||||
if field == "double" {
|
use acadrust::entities::{HatchPatternType, HatchStyleType};
|
||||||
h.is_double = if value == "toggle" {
|
// Non-numeric fields (bool toggles / enum choices) — handled before the
|
||||||
!h.is_double
|
// f64 parse below, which would otherwise reject their string values.
|
||||||
} else {
|
match field {
|
||||||
value == "true"
|
"double" => {
|
||||||
};
|
h.is_double = if value == "toggle" {
|
||||||
return;
|
!h.is_double
|
||||||
|
} else {
|
||||||
|
value == "true"
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
"associative" => {
|
||||||
|
h.is_associative = if value == "toggle" {
|
||||||
|
!h.is_associative
|
||||||
|
} else {
|
||||||
|
value == "true"
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
"pattern_type_label" => {
|
||||||
|
h.pattern_type = match value {
|
||||||
|
"Predefined" => HatchPatternType::Predefined,
|
||||||
|
"User Defined" => HatchPatternType::UserDefined,
|
||||||
|
"Custom" => HatchPatternType::Custom,
|
||||||
|
_ => h.pattern_type,
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
"style" => {
|
||||||
|
h.style = match value {
|
||||||
|
"Normal" => HatchStyleType::Normal,
|
||||||
|
"Outer" => HatchStyleType::Outer,
|
||||||
|
"Ignore" => HatchStyleType::Ignore,
|
||||||
|
_ => h.style,
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
let Some(v) = parse_f64(value) else {
|
let Some(v) = parse_f64(value) else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -208,6 +301,41 @@ fn apply_geom_prop(h: &mut Hatch, field: &str, value: &str) {
|
||||||
"pattern_angle" if h.gradient_color.enabled => h.gradient_color.angle = v.to_radians(),
|
"pattern_angle" if h.gradient_color.enabled => h.gradient_color.angle = v.to_radians(),
|
||||||
"pattern_angle" => h.pattern_angle = v.to_radians(),
|
"pattern_angle" => h.pattern_angle = v.to_radians(),
|
||||||
"pattern_scale" if v > 0.0 => h.pattern_scale = v,
|
"pattern_scale" if v > 0.0 => h.pattern_scale = v,
|
||||||
|
// Scale every pattern line's offset so the first line's spacing = v,
|
||||||
|
// preserving the relative spacing between lines.
|
||||||
|
"spacing" if v > 0.0 => {
|
||||||
|
let cur = h
|
||||||
|
.pattern
|
||||||
|
.lines
|
||||||
|
.first()
|
||||||
|
.map(|l| l.offset.length())
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
if cur > 1e-9 {
|
||||||
|
let s = v / cur;
|
||||||
|
for line in h.pattern.lines.iter_mut() {
|
||||||
|
line.offset.x *= s;
|
||||||
|
line.offset.y *= s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Move the pattern origin: shift every line's base point by the delta
|
||||||
|
// from the current origin (first line), preserving their relative offsets.
|
||||||
|
"origin_x" => {
|
||||||
|
if let Some(cur) = h.pattern.lines.first().map(|l| l.base_point.x) {
|
||||||
|
let d = v - cur;
|
||||||
|
for line in h.pattern.lines.iter_mut() {
|
||||||
|
line.base_point.x += d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"origin_y" => {
|
||||||
|
if let Some(cur) = h.pattern.lines.first().map(|l| l.base_point.y) {
|
||||||
|
let d = v - cur;
|
||||||
|
for line in h.pattern.lines.iter_mut() {
|
||||||
|
line.base_point.y += d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
"elevation" => h.elevation = v,
|
"elevation" => h.elevation = v,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
@ -257,6 +385,15 @@ impl Grippable for Hatch {
|
||||||
let elev = self.elevation;
|
let elev = self.elevation;
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
let mut id = 0usize;
|
let mut id = 0usize;
|
||||||
|
// Grip 0 = pattern-origin handle, shown at the hatch centroid (only when a
|
||||||
|
// pattern exists). Dragging it moves the pattern tiling origin; boundary
|
||||||
|
// grips follow from the next id.
|
||||||
|
if let Some(l0) = self.pattern.lines.first() {
|
||||||
|
let (gx, gy) =
|
||||||
|
boundary_centroid(self).unwrap_or((l0.base_point.x, l0.base_point.y));
|
||||||
|
out.push(circle_grip(id, glam::DVec3::new(gx, gy, elev)));
|
||||||
|
id += 1;
|
||||||
|
}
|
||||||
for path in &self.paths {
|
for path in &self.paths {
|
||||||
for edge in &path.edges {
|
for edge in &path.edges {
|
||||||
match edge {
|
match edge {
|
||||||
|
|
@ -308,7 +445,6 @@ impl Grippable for Hatch {
|
||||||
|
|
||||||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||||||
let elev = self.elevation as f32;
|
let elev = self.elevation as f32;
|
||||||
let mut id = 0usize;
|
|
||||||
|
|
||||||
fn resolve(apply: &GripApply, cur: Vec3) -> (f64, f64) {
|
fn resolve(apply: &GripApply, cur: Vec3) -> (f64, f64) {
|
||||||
let p = match apply {
|
let p = match apply {
|
||||||
|
|
@ -318,6 +454,27 @@ impl Grippable for Hatch {
|
||||||
(p.x, p.y)
|
(p.x, p.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut id = 0usize;
|
||||||
|
// Grip 0 = pattern origin: shift every line's base point by the delta,
|
||||||
|
// preserving relative offsets (mirrors the origin_x / origin_y edits).
|
||||||
|
if let Some((ox, oy)) = self
|
||||||
|
.pattern
|
||||||
|
.lines
|
||||||
|
.first()
|
||||||
|
.map(|l| (l.base_point.x, l.base_point.y))
|
||||||
|
{
|
||||||
|
if grip_id == id {
|
||||||
|
let (nx, ny) = resolve(&apply, Vec3::new(ox as f32, oy as f32, elev));
|
||||||
|
let (dx, dy) = (nx - ox, ny - oy);
|
||||||
|
for line in self.pattern.lines.iter_mut() {
|
||||||
|
line.base_point.x += dx;
|
||||||
|
line.base_point.y += dy;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
id += 1;
|
||||||
|
}
|
||||||
|
|
||||||
'outer: for path in &mut self.paths {
|
'outer: for path in &mut self.paths {
|
||||||
for edge in &mut path.edges {
|
for edge in &mut path.edges {
|
||||||
match edge {
|
match edge {
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ use super::*;
|
||||||
/// angle_offset 0 (see `prebaked` in `hatch_model_from_dxf`). The world-space
|
/// angle_offset 0 (see `prebaked` in `hatch_model_from_dxf`). The world-space
|
||||||
/// offset is rotated into the line's local frame so `pattern_segments` and the
|
/// offset is rotated into the line's local frame so `pattern_segments` and the
|
||||||
/// GPU shader, which rotate `(dx, dy)` back out by the family angle, reproduce
|
/// GPU shader, which rotate `(dx, dy)` back out by the family angle, reproduce
|
||||||
/// the exact stored step. `x0/y0` stay 0: for an infinite line family the phase
|
/// the exact stored step. `x0/y0` are filled in by the caller (from the stored
|
||||||
/// is not observable, and the stored base point is in the source (possibly
|
/// `base_point`, relative to `world_origin`) once the boundary anchor is known;
|
||||||
/// block-local) frame anyway.
|
/// they set the pattern origin, observable for dashed / offset patterns.
|
||||||
fn family_from_stored_line(
|
fn family_from_stored_line(
|
||||||
ln: &acadrust::entities::hatch::HatchPatternLine,
|
ln: &acadrust::entities::hatch::HatchPatternLine,
|
||||||
) -> crate::scene::model::hatch_model::PatFamily {
|
) -> crate::scene::model::hatch_model::PatFamily {
|
||||||
|
|
@ -1268,7 +1268,7 @@ impl Scene {
|
||||||
&& !dxf.gradient_color.is_enabled()
|
&& !dxf.gradient_color.is_enabled()
|
||||||
&& !dxf.pattern.lines.is_empty();
|
&& !dxf.pattern.lines.is_empty();
|
||||||
|
|
||||||
let pattern = if dxf.gradient_color.is_enabled() {
|
let mut pattern = if dxf.gradient_color.is_enabled() {
|
||||||
let color2 = dxf
|
let color2 = dxf
|
||||||
.gradient_color
|
.gradient_color
|
||||||
.colors
|
.colors
|
||||||
|
|
@ -1356,6 +1356,21 @@ impl Scene {
|
||||||
} else {
|
} else {
|
||||||
[0.0, 0.0]
|
[0.0, 0.0]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Anchor each prebaked family at its stored pattern origin (per-line
|
||||||
|
// `base_point`), expressed relative to `world_origin` so it shares the
|
||||||
|
// boundary's coordinate frame. Left at 0 the pattern tiles from the
|
||||||
|
// boundary centre instead: invisible for a solid infinite-line family
|
||||||
|
// but the wrong phase for dashed / offset patterns and for a user-moved
|
||||||
|
// origin (Origin X/Y property + origin grip).
|
||||||
|
if prebaked {
|
||||||
|
if let model::hatch_model::HatchPattern::Pattern(fams) = &mut pattern {
|
||||||
|
for (fam, ln) in fams.iter_mut().zip(dxf.pattern.lines.iter()) {
|
||||||
|
fam.x0 = (ln.base_point.x - world_origin[0]) as f32;
|
||||||
|
fam.y0 = (ln.base_point.y - world_origin[1]) as f32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
let boundary_f32: Vec<[f32; 2]> = boundary
|
let boundary_f32: Vec<[f32; 2]> = boundary
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&[x, y]| {
|
.map(|&[x, y]| {
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ pub enum GripShape {
|
||||||
Square,
|
Square,
|
||||||
Rectangle,
|
Rectangle,
|
||||||
Triangle,
|
Triangle,
|
||||||
|
Circle,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes one grip point for an entity.
|
/// Describes one grip point for an entity.
|
||||||
|
|
|
||||||
|
|
@ -597,6 +597,7 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||||
b.line_to(Point::new(sp.x - h, sp.y + h));
|
b.line_to(Point::new(sp.x - h, sp.y + h));
|
||||||
b.close();
|
b.close();
|
||||||
}),
|
}),
|
||||||
|
GripShape::Circle => canvas::Path::circle(Point::new(sp.x, sp.y), h),
|
||||||
};
|
};
|
||||||
|
|
||||||
if grip.is_hot {
|
if grip.is_hot {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue