Merge pull request #913 from pravdomil/claude/selection-tool-glyph-fix-69364a
UI: draw concave icons as convex pieces (fixes Selection arrow, Speaker, Sparkle glyphs)
This commit is contained in:
commit
54d68a90a6
|
|
@ -146,6 +146,8 @@ impl Pen16<'_> {
|
|||
let v: Vec<Pos2> = pts.iter().map(|(x, y)| self.p(*x, *y)).collect();
|
||||
self.painter.add(PathShape::closed_line(v, PathStroke::new(self.width * self.s(), self.color)));
|
||||
}
|
||||
/// Fill a **convex** outline: the tessellator fans a concave one into slivers across its
|
||||
/// notches, so a concave glyph is drawn as overlapping convex pieces (see [`SELECTION_ARROW`]).
|
||||
fn fill(&self, pts: &[(f32, f32)]) {
|
||||
let v: Vec<Pos2> = pts.iter().map(|(x, y)| self.p(*x, *y)).collect();
|
||||
self.painter.add(PathShape::convex_polygon(v, self.color, Stroke::NONE));
|
||||
|
|
@ -205,6 +207,25 @@ impl Pen16<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// The convex pieces of each concave filled glyph. Pieces overlap rather than abut: abutting
|
||||
/// antialiased edges would leave a faint seam.
|
||||
type Pieces = &'static [&'static [(f32, f32)]];
|
||||
|
||||
/// The Selection tool's arrow pointer (tip upper left, a notch each side of the tail): the head
|
||||
/// with its left wing, the right wing, and the tail. Only the head reaches the tip: slivers
|
||||
/// meeting there would antialias into a spike past it.
|
||||
const SELECTION_ARROW: Pieces = &[
|
||||
&[(4.5, 2.0), (4.5, 13.0), (7.2, 10.5), (10.1, 7.39)],
|
||||
&[(7.5, 9.0), (8.9, 9.9), (12.5, 9.7), (10.1, 7.39)],
|
||||
&[(7.8, 8.6), (7.2, 10.5), (9.2, 14.6), (10.8, 13.9), (8.9, 9.9)],
|
||||
];
|
||||
/// A loudspeaker: the magnet box, running a little into the cone.
|
||||
const SPEAKER: Pieces = &[&[(2.0, 6.0), (5.5, 6.0), (5.5, 10.0), (2.0, 10.0)], &[(5.0, 6.0), (9.0, 2.5), (9.0, 13.5), (5.0, 10.0)]];
|
||||
/// A four-pointed star: an upright and a level diamond, whose edges cross at the inner corners.
|
||||
const SPARKLE: Pieces = &[&[(8.0, 1.5), (9.784, 8.0), (8.0, 14.5), (6.216, 8.0)], &[(1.5, 8.0), (8.0, 6.216), (14.5, 8.0), (8.0, 9.784)]];
|
||||
#[cfg(test)]
|
||||
const CONCAVE: [Pieces; 3] = [SELECTION_ARROW, SPEAKER, SPARKLE];
|
||||
|
||||
/// Paint `icon` centred in `rect`.
|
||||
pub fn paint(painter: &Painter, rect: Rect, icon: Icon, color: Color32) {
|
||||
let pen = Pen16 { painter, rect, color, width: 1.25 };
|
||||
|
|
@ -214,7 +235,7 @@ pub fn paint(painter: &Painter, rect: Rect, icon: Icon, color: Color32) {
|
|||
pen.closed(&[(2.5, 7.5), (8.0, 2.5), (13.5, 7.5), (13.5, 13.5), (2.5, 13.5)]);
|
||||
pen.rect(6.5, 9.5, 9.5, 13.5);
|
||||
}
|
||||
Selection => pen.fill(&[(4.5, 2.0), (4.5, 13.0), (7.2, 10.5), (9.2, 14.6), (10.8, 13.9), (8.9, 9.9), (12.5, 9.7)]),
|
||||
Selection => SELECTION_ARROW.iter().for_each(|p| pen.fill(p)),
|
||||
Hand => {
|
||||
pen.line(&[(5.0, 9.0), (5.0, 4.0)]);
|
||||
pen.line(&[(7.3, 8.0), (7.3, 2.6)]);
|
||||
|
|
@ -358,7 +379,7 @@ pub fn paint(painter: &Painter, rect: Rect, icon: Icon, color: Color32) {
|
|||
pen.dot(8.0, 8.0, 2.2);
|
||||
}
|
||||
Speaker => {
|
||||
pen.fill(&[(2.0, 6.0), (5.0, 6.0), (9.0, 2.5), (9.0, 13.5), (5.0, 10.0), (2.0, 10.0)]);
|
||||
SPEAKER.iter().for_each(|p| pen.fill(p));
|
||||
pen.arc(9.0, 8.0, 3.0, -50.0, 50.0);
|
||||
pen.arc(9.0, 8.0, 5.5, -50.0, 50.0);
|
||||
}
|
||||
|
|
@ -634,7 +655,7 @@ pub fn paint(painter: &Painter, rect: Rect, icon: Icon, color: Color32) {
|
|||
pen.line(&[(9.4, 2.8), (6.6, 13.2)]);
|
||||
}
|
||||
Sparkle => {
|
||||
pen.fill(&[(8.0, 1.5), (9.4, 6.6), (14.5, 8.0), (9.4, 9.4), (8.0, 14.5), (6.6, 9.4), (1.5, 8.0), (6.6, 6.6)]);
|
||||
SPARKLE.iter().for_each(|p| pen.fill(p));
|
||||
}
|
||||
Monitor => {
|
||||
pen.rrect(1.8, 2.5, 14.2, 11.0, 1.2);
|
||||
|
|
@ -740,6 +761,22 @@ fn keyframe_shapes(
|
|||
mod tests {
|
||||
use effectcraft_engine::keyframe::KeyHalf as H;
|
||||
|
||||
/// The Selection arrow (and the Speaker and Sparkle) are concave; filled as one polygon they
|
||||
/// rendered as a broken fan. Each is drawn as convex pieces instead.
|
||||
#[test]
|
||||
fn concave_glyphs_are_filled_as_convex_pieces() {
|
||||
for piece in super::CONCAVE.iter().flat_map(|g| g.iter()) {
|
||||
let n = piece.len();
|
||||
let turns: Vec<f32> = (0..n)
|
||||
.map(|i| {
|
||||
let (a, b, c) = (piece[i], piece[(i + 1) % n], piece[(i + 2) % n]);
|
||||
(b.0 - a.0) * (c.1 - b.1) - (b.1 - a.1) * (c.0 - b.0)
|
||||
})
|
||||
.collect();
|
||||
assert!(turns.iter().all(|t| *t < 0.0) || turns.iter().all(|t| *t > 0.0), "{piece:?} is not convex: {turns:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// #284: keyframes have no dark border (overlapping keys looked messy): each half is filled
|
||||
/// and edged in the key's own colour.
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Reference in New Issue