use std::f64::consts::{FRAC_1_SQRT_2, PI};
use crate::js::math::{cos, max, min, round, sin};
use crate::scene::{Painting, Part, Path, Rect};
pub(crate) const PAD_X: f64 = 18.0;
pub(crate) const PAD_Y: f64 = 12.0;
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct ShapeSize {
pub(crate) w: f64,
pub(crate) h: f64,
pub(crate) dy: f64,
}
type Inset = fn(f64, f64, usize, f64) -> f64;
struct ShapeDef {
size: fn(f64, f64) -> ShapeSize,
draw: fn(&mut Drawing, f64, f64),
inset: Option<Inset>,
span: Option<fn(f64, f64, bool) -> f64>,
no_label: bool,
bare: bool,
}
const DEF: ShapeDef = ShapeDef {
size: box_size,
draw: |_, _, _| {},
inset: None,
span: None,
no_label: false,
bare: false,
};
const CORNER: f64 = 12.0;
fn flat(w: f64, h: f64, sideways: bool) -> f64 {
(if sideways { h } else { w }) - 2.0 * CORNER
}
fn flat_across(w: f64, cut: f64, sideways: bool) -> f64 {
if sideways {
0.0
} else {
w - 2.0 * cut - 2.0 * CORNER
}
}
fn bulge(t: f64, half: f64, depth: f64) -> f64 {
let k = min(1.0, t.abs() / half);
depth * (1.0 - (1.0 - k * k).sqrt())
}
fn round_inset(w: f64, h: f64, side: usize, t: f64) -> f64 {
if side & 1 != 0 {
bulge(t, h / 2.0, w / 2.0)
} else {
bulge(t, w / 2.0, h / 2.0)
}
}
fn round_span(w: f64, h: f64, sideways: bool) -> f64 {
(if sideways { h } else { w }) * 0.6
}
fn cap_ry(w: f64) -> f64 {
min(12.0, max(6.0, w / 14.0))
}
fn cap_inset(w: f64, _h: f64, side: usize, t: f64) -> f64 {
if side & 1 != 0 {
0.0
} else {
bulge(t, w / 2.0, cap_ry(w))
}
}
fn tri_size(tw: f64, th: f64, flip: bool) -> ShapeSize {
let w = max(1.6 * tw + 40.0, 80.0);
let h = (th + 10.0) / (1.0 - (tw + 8.0) / w);
let dy = h / 2.0 - 10.0 - th / 2.0;
ShapeSize {
w,
h,
dy: if flip { -dy } else { dy },
}
}
fn lean_inset(_w: f64, h: f64, side: usize, _t: f64) -> f64 {
if side & 1 != 0 { h / 8.0 } else { 0.0 }
}
fn box_size(tw: f64, th: f64) -> ShapeSize {
ShapeSize {
w: max(tw + 2.0 * PAD_X, 2.0 * PAD_X + 12.0),
h: th + 2.0 * PAD_Y,
dy: 0.0,
}
}
fn padded(tw: f64, th: f64, w: f64, h: f64, dy: f64) -> ShapeSize {
ShapeSize {
w: tw + 2.0 * PAD_X + w,
h: th + 2.0 * PAD_Y + h,
dy,
}
}
fn by_height(tw: f64, th: f64, k: f64) -> ShapeSize {
let h = th + 2.0 * PAD_Y;
ShapeSize {
w: tw + 2.0 * PAD_X + h * k,
h,
dy: 0.0,
}
}
fn fixed(w: f64, h: f64) -> ShapeSize {
ShapeSize { w, h, dy: 0.0 }
}
const LEANING: ShapeDef = ShapeDef {
inset: Some(lean_inset),
span: Some(|w, h, sideways| flat_across(w, h / 4.0, sideways)),
size: |tw, th| by_height(tw, th, 0.5),
..DEF
};
const SHAPES: &[(&str, ShapeDef)] = &[
(
"rect",
ShapeDef {
span: Some(flat),
draw: |d, w, h| d.rounded(-w / 2.0, -h / 2.0, w, h),
..DEF
},
),
(
"rounded",
ShapeDef {
span: Some(flat),
draw: |d, w, h| {
d.rect(
-w / 2.0,
-h / 2.0,
w,
h,
min(12.0, h / 2.0),
Painting::FillAndStroke,
)
},
..DEF
},
),
(
"stadium",
ShapeDef {
inset: Some(|_, h, side, t| {
if side & 1 != 0 {
bulge(t, h / 2.0, h / 2.0)
} else {
0.0
}
}),
span: Some(|w, h, sideways| if sideways { h * 0.6 } else { w - h }),
size: |tw, th| {
let h = th + 2.0 * PAD_Y;
ShapeSize {
w: tw + h * 0.5 + 2.0 * PAD_X - 8.0,
h,
dy: 0.0,
}
},
draw: |d, w, h| d.rect(-w / 2.0, -h / 2.0, w, h, h / 2.0, Painting::FillAndStroke),
..DEF
},
),
(
"fr-rect",
ShapeDef {
span: Some(flat),
size: |tw, th| padded(tw, th, 16.0, 0.0, 0.0),
draw: |d, w, h| {
let x = w / 2.0 - 8.0;
d.rounded(-w / 2.0, -h / 2.0, w, h);
d.pen()
.move_to(-x, -h / 2.0)
.v(h / 2.0)
.move_to(x, -h / 2.0)
.v(h / 2.0)
.done(Painting::StrokeOnly);
},
..DEF
},
),
(
"cyl",
ShapeDef {
inset: Some(cap_inset),
span: Some(|w, h, sideways| {
if sideways {
h - 2.0 * CORNER - 12.0
} else {
w * 0.6
}
}),
size: |tw, th| {
let w = max(tw + 2.0 * PAD_X, 56.0);
let ry = cap_ry(w);
ShapeSize {
w,
h: th + 2.0 * PAD_Y + 2.0 * ry,
dy: ry / 2.0,
}
},
draw: |d, w, h| {
let (rx, ry) = (w / 2.0, cap_ry(w));
let (top, bottom) = (-h / 2.0 + ry, h / 2.0 - ry);
d.pen()
.move_to(-rx, top)
.arc(rx, ry, false, true, rx, top)
.v(bottom)
.arc(rx, ry, false, true, -rx, bottom)
.close()
.done(Painting::FillAndStroke);
d.pen()
.move_to(-rx, top)
.arc(rx, ry, false, false, rx, top)
.done(Painting::StrokeOnly);
},
..DEF
},
),
(
"circle",
ShapeDef {
inset: Some(round_inset),
span: Some(round_span),
size: |tw, th| {
let d = max((tw * tw + th * th).sqrt() + 16.0, 44.0);
fixed(d, d)
},
draw: |d, w, _| d.circle(0.0, 0.0, w / 2.0, Painting::FillAndStroke),
..DEF
},
),
(
"dbl-circ",
ShapeDef {
inset: Some(round_inset),
span: Some(round_span),
size: |tw, th| {
let d = max((tw * tw + th * th).sqrt() + 26.0, 54.0);
fixed(d, d)
},
draw: |d, w, _| {
d.circle(0.0, 0.0, w / 2.0, Painting::FillAndStroke);
d.circle(0.0, 0.0, w / 2.0 - 5.0, Painting::StrokeOnly);
},
..DEF
},
),
(
"ellipse",
ShapeDef {
inset: Some(round_inset),
span: Some(round_span),
size: |tw, th| ShapeSize {
w: tw * 1.3 + 2.0 * PAD_X,
h: th * 1.2 + 2.0 * PAD_Y,
dy: 0.0,
},
draw: |d, w, h| d.ellipse(0.0, 0.0, w / 2.0, h / 2.0, Painting::FillAndStroke),
..DEF
},
),
(
"diam",
ShapeDef {
inset: Some(|w, h, side, t| {
if side & 1 != 0 {
(t.abs() * w) / h
} else {
(t.abs() * h) / w
}
}),
span: Some(round_span),
size: |tw, th| {
let (a, b) = (tw + 16.0, th + 8.0);
ShapeSize {
w: a + 1.6 * b,
h: a / 1.6 + b,
dy: 0.0,
}
},
draw: |d, w, h| {
d.poly(
Painting::FillAndStroke,
&[0.0, -h / 2.0, w / 2.0, 0.0, 0.0, h / 2.0, -w / 2.0, 0.0],
)
},
..DEF
},
),
(
"hex",
ShapeDef {
inset: Some(|_, _, side, t| if side & 1 != 0 { t.abs() / 2.0 } else { 0.0 }),
span: Some(|w, h, sideways| {
if sideways {
h * 0.5
} else {
w - h / 2.0 - CORNER
}
}),
size: |tw, th| by_height(tw, th, 0.5),
draw: |d, w, h| {
let m = h / 4.0;
let (x, y) = (w / 2.0, h / 2.0);
d.poly(
Painting::FillAndStroke,
&[-x + m, -y, x - m, -y, x, 0.0, x - m, y, -x + m, y, -x, 0.0],
);
},
..DEF
},
),
(
"odd",
ShapeDef {
inset: Some(|_, h, side, _| if side == 3 { h / 4.0 } else { 0.0 }),
span: Some(|w, _, sideways| flat_across(w, 0.0, sideways)),
size: |tw, th| by_height(tw, th, 0.25),
draw: |d, w, h| {
let notch = h / 4.0;
let (x, y) = (w / 2.0, h / 2.0);
d.poly(
Painting::FillAndStroke,
&[-x, -y, x, -y, x, y, -x, y, -x + notch, 0.0],
);
},
..DEF
},
),
(
"lean-r",
ShapeDef {
draw: |d, w, h| {
let (s, x, y) = (h / 4.0, w / 2.0, h / 2.0);
d.poly(
Painting::FillAndStroke,
&[-x + s, -y, x, -y, x - s, y, -x, y],
);
},
..LEANING
},
),
(
"lean-l",
ShapeDef {
draw: |d, w, h| {
let (s, x, y) = (h / 4.0, w / 2.0, h / 2.0);
d.poly(
Painting::FillAndStroke,
&[-x, -y, x - s, -y, x, y, -x + s, y],
);
},
..LEANING
},
),
(
"trap-b",
ShapeDef {
draw: |d, w, h| {
let (s, x, y) = (h / 4.0, w / 2.0, h / 2.0);
d.poly(
Painting::FillAndStroke,
&[-x + s, -y, x - s, -y, x, y, -x, y],
);
},
..LEANING
},
),
(
"trap-t",
ShapeDef {
draw: |d, w, h| {
let (s, x, y) = (h / 4.0, w / 2.0, h / 2.0);
d.poly(
Painting::FillAndStroke,
&[-x, -y, x, -y, x - s, y, -x + s, y],
);
},
..LEANING
},
),
(
"text",
ShapeDef {
bare: true,
span: Some(flat),
size: |tw, th| fixed(tw + 8.0, th + 8.0),
..DEF
},
),
(
"sm-circ",
ShapeDef {
no_label: true,
inset: Some(round_inset),
span: Some(round_span),
size: |_, _| fixed(14.0, 14.0),
draw: |d, w, _| d.circle(0.0, 0.0, w / 2.0, Painting::FillAndStroke),
..DEF
},
),
(
"f-circ",
ShapeDef {
no_label: true,
inset: Some(round_inset),
span: Some(round_span),
size: |_, _| fixed(14.0, 14.0),
draw: |d, w, _| d.circle(0.0, 0.0, w / 2.0, Painting::Solid),
..DEF
},
),
(
"fr-circ",
ShapeDef {
no_label: true,
inset: Some(round_inset),
span: Some(round_span),
size: |_, _| fixed(18.0, 18.0),
draw: |d, w, _| {
d.circle(0.0, 0.0, w / 2.0, Painting::FillAndStroke);
d.circle(0.0, 0.0, w / 2.0 - 4.0, Painting::Solid);
},
..DEF
},
),
(
"fork",
ShapeDef {
no_label: true,
span: Some(|w, h, sideways| (if sideways { h } else { w }) - 8.0),
size: |_, _| fixed(72.0, 8.0),
draw: |d, w, h| d.rect(-w / 2.0, -h / 2.0, w, h, 2.0, Painting::Solid),
..DEF
},
),
(
"notch-rect",
ShapeDef {
span: Some(flat),
draw: |d, w, h| {
let (c, x, y) = (10.0, w / 2.0, h / 2.0);
d.poly(
Painting::FillAndStroke,
&[-x + c, -y, x, -y, x, y, -x, y, -x, -y + c],
);
},
..DEF
},
),
(
"lin-rect",
ShapeDef {
span: Some(flat),
size: |tw, th| padded(tw, th, 8.0, 0.0, 0.0),
draw: |d, w, h| {
d.rounded(-w / 2.0, -h / 2.0, w, h);
d.pen()
.move_to(-w / 2.0 + 8.0, -h / 2.0)
.v(h / 2.0)
.done(Painting::StrokeOnly);
},
..DEF
},
),
(
"div-rect",
ShapeDef {
span: Some(flat),
size: |tw, th| padded(tw, th, 0.0, 8.0, 4.0),
draw: |d, w, h| {
d.rounded(-w / 2.0, -h / 2.0, w, h);
d.pen()
.move_to(-w / 2.0, -h / 2.0 + 8.0)
.h(w / 2.0)
.done(Painting::StrokeOnly);
},
..DEF
},
),
(
"win-pane",
ShapeDef {
span: Some(flat),
size: |tw, th| padded(tw, th, 8.0, 8.0, 4.0),
draw: |d, w, h| {
d.rounded(-w / 2.0, -h / 2.0, w, h);
d.pen()
.move_to(-w / 2.0 + 8.0, -h / 2.0)
.v(h / 2.0)
.move_to(-w / 2.0, -h / 2.0 + 8.0)
.h(w / 2.0)
.done(Painting::StrokeOnly);
},
..DEF
},
),
(
"tri",
ShapeDef {
inset: Some(|w, _, side, _| if side & 1 != 0 { w / 4.0 } else { 0.0 }),
size: |tw, th| tri_size(tw, th, false),
draw: |d, w, h| {
d.poly(
Painting::FillAndStroke,
&[0.0, -h / 2.0, w / 2.0, h / 2.0, -w / 2.0, h / 2.0],
)
},
..DEF
},
),
(
"flip-tri",
ShapeDef {
inset: Some(|w, _, side, _| if side & 1 != 0 { w / 4.0 } else { 0.0 }),
size: |tw, th| tri_size(tw, th, true),
draw: |d, w, h| {
d.poly(
Painting::FillAndStroke,
&[-w / 2.0, -h / 2.0, w / 2.0, -h / 2.0, 0.0, h / 2.0],
)
},
..DEF
},
),
(
"sl-rect",
ShapeDef {
inset: Some(|_, _, side, _| if side == 0 { 5.0 } else { 0.0 }),
size: |tw, th| padded(tw, th, 0.0, 10.0, 5.0),
draw: |d, w, h| {
let (x, y) = (w / 2.0, h / 2.0);
d.poly(
Painting::FillAndStroke,
&[-x, -y + 10.0, x, -y, x, y, -x, y],
);
},
..DEF
},
),
(
"notch-pent",
ShapeDef {
span: Some(flat),
size: |tw, th| padded(tw, th, 0.0, 0.0, 0.0),
draw: |d, w, h| {
let (c, x, y) = (10.0, w / 2.0, h / 2.0);
d.poly(
Painting::FillAndStroke,
&[-x + c, -y, x - c, -y, x, -y + c, x, y, -x, y, -x, -y + c],
);
},
..DEF
},
),
(
"hourglass",
ShapeDef {
no_label: true,
inset: Some(|w, _, side, _| if side & 1 != 0 { w / 2.0 } else { 0.0 }),
size: |_, _| fixed(32.0, 40.0),
draw: |d, w, h| {
let (x, y) = (w / 2.0, h / 2.0);
d.poly(Painting::FillAndStroke, &[-x, -y, x, -y, -x, y, x, y]);
},
..DEF
},
),
(
"delay",
ShapeDef {
size: |tw, th| by_height(tw, th, 0.25),
draw: |d, w, h| {
let r = h / 2.0;
d.pen()
.move_to(-w / 2.0, -r)
.h(w / 2.0 - r)
.arc(r, r, false, true, w / 2.0 - r, r)
.h(-w / 2.0)
.close()
.done(Painting::FillAndStroke);
},
..DEF
},
),
(
"h-cyl",
ShapeDef {
size: |tw, th| {
let h = th + 2.0 * PAD_Y;
let rx = min(12.0, h / 4.0);
ShapeSize {
w: tw + 2.0 * PAD_X + 2.0 * rx,
h,
dy: 0.0,
}
},
draw: |d, w, h| {
let (ry, rx) = (h / 2.0, min(12.0, h / 4.0));
let (l, r) = (-w / 2.0 + rx, w / 2.0 - rx);
d.pen()
.move_to(l, -ry)
.h(r)
.arc(rx, ry, false, true, r, ry)
.h(l)
.arc(rx, ry, false, true, l, -ry)
.close()
.done(Painting::FillAndStroke);
d.pen()
.move_to(r, -ry)
.arc(rx, ry, false, false, r, ry)
.done(Painting::StrokeOnly);
},
..DEF
},
),
(
"cross-circ",
ShapeDef {
no_label: true,
inset: Some(round_inset),
span: Some(round_span),
size: |_, _| fixed(44.0, 44.0),
draw: |d, w, _| {
let r = w / 2.0;
let k = r * FRAC_1_SQRT_2;
d.circle(0.0, 0.0, r, Painting::FillAndStroke);
d.pen()
.move_to(-k, -k)
.line_to(k, k)
.move_to(k, -k)
.line_to(-k, k)
.done(Painting::StrokeOnly);
},
..DEF
},
),
(
"st-rect",
ShapeDef {
span: Some(|w, h, sideways| (if sideways { h } else { w }) - 2.0 * CORNER - 8.0),
size: |tw, th| padded(tw, th, 8.0, 8.0, 4.0),
draw: |d, w, h| {
let (bw, bh) = (w - 8.0, h - 8.0);
for i in 0..3 {
d.rounded(
-w / 2.0 + f64::from(2 - i) * 4.0,
-h / 2.0 + f64::from(i) * 4.0,
bw,
bh,
);
}
},
..DEF
},
),
(
"doc",
ShapeDef {
inset: Some(|_, _, side, _| if side == 2 { 10.0 } else { 0.0 }),
size: |tw, th| padded(tw, th, 0.0, 10.0, -5.0),
draw: |d, w, h| d.wave(w, h),
..DEF
},
),
(
"flag",
ShapeDef {
inset: Some(|_, _, side, _| if side & 1 != 0 { 0.0 } else { 8.0 }),
size: |tw, th| padded(tw, th, 0.0, 16.0, 0.0),
draw: |d, w, h| {
let (x, y, q) = (w / 2.0, h / 2.0 - 8.0, w / 4.0);
d.pen()
.move_to(-x, -y)
.rel_quad(q, -10.0, 2.0 * q, 0.0)
.rel_smooth(2.0 * q, 0.0)
.v(y)
.rel_quad(-q, 10.0, -2.0 * q, 0.0)
.rel_smooth(-2.0 * q, 0.0)
.close()
.done(Painting::FillAndStroke);
},
..DEF
},
),
(
"datastore",
ShapeDef {
span: Some(flat),
draw: |d, w, h| {
let r = Rect::new(d.cx - w / 2.0, d.cy - h / 2.0, w, h);
d.parts.push(Part::rect(r, false, Painting::FillOnly));
d.pen()
.move_to(-w / 2.0, -h / 2.0)
.h(w / 2.0)
.move_to(-w / 2.0, h / 2.0)
.h(w / 2.0)
.done(Painting::StrokeOnly);
},
..DEF
},
),
(
"folder",
ShapeDef {
size: |tw, th| ShapeSize {
w: max(tw + 2.0 * PAD_X, 64.0),
h: th + 2.0 * PAD_Y + 8.0,
dy: 4.0,
},
draw: |d, w, h| {
let (x, y) = (w / 2.0, h / 2.0);
let tab = min(w * 0.4, 48.0);
d.pen()
.move_to(-x, -y)
.h(-x + tab)
.line_to(-x + tab + 8.0, -y + 8.0)
.h(x)
.v(y)
.h(-x)
.close()
.done(Painting::FillAndStroke);
},
..DEF
},
),
(
"bucket",
ShapeDef {
size: |tw, th| padded(tw, th, 20.0, 10.0, 3.0),
inset: Some(|_, h, side, _| if side & 1 != 0 { h / 12.0 } else { 0.0 }),
draw: |d, w, h| {
let (x, y, ry) = (w / 2.0, h / 2.0, 5.0);
let taper = h / 6.0;
d.pen()
.move_to(-x, -y + ry)
.arc(x, ry, false, true, x, -y + ry)
.line_to(x - taper, y - ry)
.arc(x - taper, ry, false, true, -x + taper, y - ry)
.close()
.done(Painting::FillAndStroke);
d.pen()
.move_to(-x, -y + ry)
.arc(x, ry, false, false, x, -y + ry)
.done(Painting::StrokeOnly);
},
..DEF
},
),
(
"console",
ShapeDef {
span: Some(flat),
size: |tw, th| ShapeSize {
w: max(tw + 2.0 * PAD_X, 72.0),
h: th + 2.0 * PAD_Y + 12.0,
dy: 6.0,
},
draw: |d, w, h| {
d.rounded(-w / 2.0, -h / 2.0, w, h);
d.pen()
.move_to(-w / 2.0, -h / 2.0 + 12.0)
.h(w / 2.0)
.move_to(-w / 2.0 + 7.0, -h / 2.0 + 4.0)
.rel_line(3.0, 2.5)
.rel_line(-3.0, 2.5)
.move_to(-w / 2.0 + 13.0, -h / 2.0 + 9.0)
.rel_h(4.0)
.done(Painting::StrokeOnly);
},
..DEF
},
),
(
"browser",
ShapeDef {
span: Some(flat),
size: |tw, th| ShapeSize {
w: max(tw + 2.0 * PAD_X, 72.0),
h: th + 2.0 * PAD_Y + 12.0,
dy: 6.0,
},
draw: |d, w, h| {
d.rounded(-w / 2.0, -h / 2.0, w, h);
d.pen()
.move_to(-w / 2.0, -h / 2.0 + 12.0)
.h(w / 2.0)
.done(Painting::StrokeOnly);
for i in 0..3 {
d.circle(
-w / 2.0 + 8.0 + f64::from(i) * 7.0,
-h / 2.0 + 6.0,
1.5,
Painting::StrokeOnly,
);
}
},
..DEF
},
),
(
"person",
ShapeDef {
size: |tw, th| ShapeSize {
w: max(tw + 2.0 * PAD_X, 56.0),
h: th + 2.0 * PAD_Y + 26.0,
dy: 13.0,
},
draw: |d, w, h| {
let top = -h / 2.0;
d.rect(
-w / 2.0,
top + 26.0,
w,
h - 26.0,
12.0,
Painting::FillAndStroke,
);
d.circle(0.0, top + 14.0, 13.0, Painting::FillAndStroke);
},
..DEF
},
),
(
"bang",
ShapeDef {
size: |tw, th| padded(tw, th, 28.0, 24.0, 0.0),
inset: Some(|_, _, _, _| 3.0),
draw: |d, w, h| {
const SPIKES: f64 = 14.0;
let mut pts = Vec::with_capacity(56);
for i in 0..28 {
let angle = (PI * f64::from(i)) / SPIKES - PI / 2.0;
let k = if i % 2 == 0 { 1.0 } else { 0.82 };
pts.push(cos(angle) * (w / 2.0) * k);
pts.push(sin(angle) * (h / 2.0) * k);
}
d.poly(Painting::FillAndStroke, &pts);
},
..DEF
},
),
(
"cloud",
ShapeDef {
size: |tw, th| padded(tw, th, 32.0, 24.0, 0.0),
inset: Some(|_, _, side, _| if side & 1 != 0 { 2.0 } else { 1.0 }),
draw: |d, w, h| {
let (x, y) = (w / 2.0, h / 2.0);
let r = y * 0.55;
d.pen()
.move_to(-x + r, y)
.arc(r, r, false, true, -x + r * 0.6, -y * 0.1)
.arc(x * 0.42, y * 0.7, false, true, -x * 0.1, -y * 0.72)
.arc(x * 0.4, y * 0.62, false, true, x * 0.62, -y * 0.42)
.arc(r * 1.05, r * 1.05, false, true, x - r, y)
.close()
.done(Painting::FillAndStroke);
},
..DEF
},
),
(
"brace",
ShapeDef {
bare: true,
size: |tw, th| padded(tw, th, 12.0, 0.0, 0.0),
draw: |d, w, h| d.brace(-w / 2.0 + 12.0, h, -1.0),
..DEF
},
),
(
"brace-r",
ShapeDef {
bare: true,
size: |tw, th| padded(tw, th, 12.0, 0.0, 0.0),
draw: |d, w, h| d.brace(w / 2.0 - 12.0, h, 1.0),
..DEF
},
),
(
"braces",
ShapeDef {
bare: true,
size: |tw, th| padded(tw, th, 24.0, 0.0, 0.0),
draw: |d, w, h| {
d.brace(-w / 2.0 + 12.0, h, -1.0);
d.brace(w / 2.0 - 12.0, h, 1.0);
},
..DEF
},
),
(
"bolt",
ShapeDef {
no_label: true,
size: |_, _| fixed(36.0, 56.0),
inset: Some(|w, _, side, _| if side & 1 != 0 { w / 4.0 } else { 0.0 }),
draw: |d, w, h| {
d.poly(
Painting::FillAndStroke,
&[
w * 0.15,
-h / 2.0,
-w / 2.0,
h * 0.08,
-w * 0.05,
h * 0.08,
-w * 0.15,
h / 2.0,
w / 2.0,
-h * 0.08,
w * 0.05,
-h * 0.08,
],
)
},
..DEF
},
),
(
"lin-cyl",
ShapeDef {
inset: Some(cap_inset),
span: Some(|w, h, sideways| {
if sideways {
h - 2.0 * CORNER - 20.0
} else {
w * 0.6
}
}),
size: |tw, th| {
let w = max(tw + 2.0 * PAD_X, 56.0);
let ry = cap_ry(w);
ShapeSize {
w,
h: th + 2.0 * PAD_Y + 2.0 * ry + 8.0,
dy: ry / 2.0 + 4.0,
}
},
draw: |d, w, h| {
let (rx, ry) = (w / 2.0, cap_ry(w));
let (top, bottom) = (-h / 2.0 + ry, h / 2.0 - ry);
d.pen()
.move_to(-rx, top)
.arc(rx, ry, false, true, rx, top)
.v(bottom)
.arc(rx, ry, false, true, -rx, bottom)
.close()
.done(Painting::FillAndStroke);
d.pen()
.move_to(-rx, top)
.arc(rx, ry, false, false, rx, top)
.move_to(-rx, top + 8.0)
.arc(rx, ry, false, false, rx, top + 8.0)
.done(Painting::StrokeOnly);
},
..DEF
},
),
(
"curv-trap",
ShapeDef {
size: |tw, th| by_height(tw, th, 0.6),
draw: |d, w, h| {
let (x, y, k) = (w / 2.0, h / 2.0, h * 0.3);
d.pen()
.move_to(-x + k, -y)
.h(x - k)
.arc(k, y, false, true, x - k, y)
.h(-x + k)
.line_to(-x, 0.0)
.close()
.done(Painting::FillAndStroke);
},
..DEF
},
),
(
"docs",
ShapeDef {
size: |tw, th| padded(tw, th, 8.0, 18.0, -1.0),
inset: Some(|_, _, side, _| if side == 2 { 10.0 } else { 0.0 }),
draw: |d, w, h| {
for i in 0..3 {
let mut moved = Drawing {
cx: d.cx + f64::from(1 - i) * 4.0,
cy: d.cy + f64::from(i - 1) * 4.0,
parts: Vec::new(),
};
moved.wave(w - 8.0, h - 8.0);
d.parts.append(&mut moved.parts);
}
},
..DEF
},
),
(
"bow-rect",
ShapeDef {
size: |tw, th| by_height(tw, th, 0.4),
inset: Some(|_, h, side, _| if side == 1 { h * 0.2 } else { 0.0 }),
draw: |d, w, h| {
let (x, y, k) = (w / 2.0, h / 2.0, h * 0.2);
d.pen()
.move_to(-x + k, -y)
.h(x)
.arc(k, y, false, false, x, y)
.h(-x + k)
.arc(k, y, false, true, -x + k, -y)
.close()
.done(Painting::FillAndStroke);
},
..DEF
},
),
(
"tag-rect",
ShapeDef {
span: Some(flat),
draw: |d, w, h| {
d.rounded(-w / 2.0, -h / 2.0, w, h);
d.pen()
.move_to(w / 2.0 - 12.0, h / 2.0)
.line_to(w / 2.0, h / 2.0 - 12.0)
.done(Painting::StrokeOnly);
},
..DEF
},
),
(
"tag-doc",
ShapeDef {
size: |tw, th| padded(tw, th, 0.0, 10.0, -5.0),
inset: Some(|_, _, side, _| if side == 2 { 10.0 } else { 0.0 }),
draw: |d, w, h| {
d.wave(w, h);
d.pen()
.move_to(w / 2.0 - 12.0, h / 2.0 - 14.0)
.line_to(w / 2.0, h / 2.0 - 26.0)
.done(Painting::StrokeOnly);
},
..DEF
},
),
(
"lin-doc",
ShapeDef {
size: |tw, th| padded(tw, th, 8.0, 10.0, -5.0),
inset: Some(|_, _, side, _| if side == 2 { 10.0 } else { 0.0 }),
draw: |d, w, h| {
d.wave(w, h);
d.pen()
.move_to(-w / 2.0 + 8.0, -h / 2.0)
.v(h / 2.0 - 6.0)
.done(Painting::StrokeOnly);
},
..DEF
},
),
(
"collapsedGroup",
ShapeDef {
size: |tw, th| padded(tw, th, 8.0, 8.0, -2.0),
draw: |d, w, h| {
d.rounded(-w / 2.0 + 6.0, -h / 2.0 + 8.0, w - 12.0, h - 8.0);
d.rounded(-w / 2.0, -h / 2.0, w, h - 4.0);
},
..DEF
},
),
];
fn by_name(shape: &str) -> Option<&'static ShapeDef> {
SHAPES
.iter()
.find(|(name, _)| *name == shape)
.map(|(_, def)| def)
}
fn or_fallback(shape: &str) -> &'static ShapeDef {
by_name(shape).unwrap_or(&SHAPES[0].1)
}
#[cfg(test)]
pub(crate) fn shape_names() -> impl Iterator<Item = &'static str> {
SHAPES.iter().map(|(name, _)| *name)
}
pub(crate) fn shape_size(shape: &str, tw: f64, th: f64) -> ShapeSize {
(or_fallback(shape).size)(tw, th)
}
pub(crate) fn draw_shape(shape: &str, cx: f64, cy: f64, w: f64, h: f64) -> Vec<Part> {
let mut d = Drawing {
cx,
cy,
parts: Vec::new(),
};
(or_fallback(shape).draw)(&mut d, w, h);
d.parts
}
pub(crate) fn shape_inset(shape: &str, w: f64, h: f64, side: usize, t: f64) -> f64 {
by_name(shape)
.and_then(|s| s.inset)
.map_or(0.0, |inset| inset(w, h, side, t))
}
pub(crate) fn shape_span(shape: &str, w: f64, h: f64, sideways: bool) -> f64 {
max(
0.0,
by_name(shape)
.and_then(|s| s.span)
.map_or(0.0, |span| span(w, h, sideways)),
)
}
pub(crate) fn shape_has_label(shape: &str) -> bool {
by_name(shape).is_none_or(|s| !s.no_label)
}
pub(crate) fn shape_filled(shape: &str) -> bool {
!or_fallback(shape).bare
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub(crate) struct Node {
pub(crate) x: f64,
pub(crate) y: f64,
pub(crate) w: f64,
pub(crate) h: f64,
pub(crate) group: bool,
}
pub(crate) fn end_side(route: &[f64], at: usize, c: &Node) -> Option<usize> {
let dx = route[at] - c.x;
let dy = route[at + 1] - c.y;
if (dy.abs() - c.h / 2.0).abs() < 0.5 {
return Some(if dy < 0.0 { 0 } else { 2 });
}
if (dx.abs() - c.w / 2.0).abs() < 0.5 {
return Some(if dx < 0.0 { 3 } else { 1 });
}
None
}
pub(crate) fn inset_route(route: &mut [f64], at: usize, shape: &str, w: f64, h: f64, c: &Node) {
if c.group {
return;
}
match end_side(route, at, c) {
Some(side @ (0 | 2)) => {
let amount = shape_inset(shape, w, h, side, route[at] - c.x) + (c.h - h) / 2.0;
route[at + 1] += if side == 0 { amount } else { -amount };
}
Some(side) => {
let amount = shape_inset(shape, w, h, side, route[at + 1] - c.y) + (c.w - w) / 2.0;
route[at] += if side == 3 { amount } else { -amount };
}
None => {}
}
}
struct Drawing {
cx: f64,
cy: f64,
parts: Vec<Part>,
}
impl Drawing {
fn rounded(&mut self, x: f64, y: f64, w: f64, h: f64) {
let r = Rect::new(self.cx + x, self.cy + y, w, h);
self.parts
.push(Part::rect(r, true, Painting::FillAndStroke));
}
#[allow(clippy::too_many_arguments)]
fn rect(&mut self, x: f64, y: f64, w: f64, h: f64, rx: f64, paint: Painting) {
let (rx, ry) = (min(rx, w / 2.0), min(rx, h / 2.0));
if rx <= 0.0 {
self.poly(paint, &[x, y, x + w, y, x + w, y + h, x, y + h]);
return;
}
self.pen()
.move_to(x + rx, y)
.h(x + w - rx)
.arc(rx, ry, false, true, x + w, y + ry)
.v(y + h - ry)
.arc(rx, ry, false, true, x + w - rx, y + h)
.h(x + rx)
.arc(rx, ry, false, true, x, y + h - ry)
.v(y + ry)
.arc(rx, ry, false, true, x + rx, y)
.close()
.done(paint);
}
fn circle(&mut self, x: f64, y: f64, r: f64, paint: Painting) {
self.ellipse(x, y, r, r, paint);
}
fn ellipse(&mut self, x: f64, y: f64, rx: f64, ry: f64, paint: Painting) {
let mut p = Path::new();
p.ellipse(self.cx + x, self.cy + y, rx, ry);
self.parts.push(Part::path(p, paint));
}
fn poly(&mut self, paint: Painting, xy: &[f64]) {
let at: Vec<(f64, f64)> = xy
.chunks(2)
.map(|p| (self.cx + p[0], self.cy + p[1]))
.collect();
let mut p = Path::new();
p.polygon(&at);
self.parts.push(Part::path(p, paint));
}
fn wave(&mut self, w: f64, h: f64) {
let (x, top, base, q) = (w / 2.0, -h / 2.0, h / 2.0 - 10.0, w / 4.0);
self.pen()
.move_to(-x, top)
.h(x)
.v(base)
.rel_quad(-q, -12.0, -2.0 * q, 0.0)
.rel_smooth(-2.0 * q, 0.0)
.close()
.done(Painting::FillAndStroke);
}
fn brace(&mut self, x: f64, h: f64, dir: f64) {
let (y, k) = (h / 2.0, 6.0 * dir);
self.pen()
.move_to(x, -y)
.rel_quad(k, 0.0, k, 6.0)
.v(-6.0)
.rel_quad(0.0, 6.0, k, 6.0)
.rel_quad(-k, 0.0, -k, 6.0)
.v(y - 6.0)
.rel_quad(0.0, 6.0, -k, 6.0)
.done(Painting::StrokeOnly);
}
fn pen(&mut self) -> Pen<'_> {
Pen {
d: self,
p: Path::new(),
x: 0.0,
y: 0.0,
x0: 0.0,
y0: 0.0,
qx: 0.0,
qy: 0.0,
quad: false,
}
}
}
struct Pen<'a> {
d: &'a mut Drawing,
p: Path,
x: f64,
y: f64,
x0: f64,
y0: f64,
qx: f64,
qy: f64,
quad: bool,
}
impl Pen<'_> {
fn at(&mut self, x: f64, y: f64) -> &mut Self {
(self.x, self.y, self.quad) = (x, y, false);
self
}
fn move_to(&mut self, x: f64, y: f64) -> &mut Self {
let (x, y) = (r2(x), r2(y));
self.p.move_to(self.d.cx + x, self.d.cy + y);
(self.x0, self.y0) = (x, y);
self.at(x, y)
}
fn line_to(&mut self, x: f64, y: f64) -> &mut Self {
self.line(r2(x), r2(y))
}
fn h(&mut self, x: f64) -> &mut Self {
self.line(r2(x), self.y)
}
fn v(&mut self, y: f64) -> &mut Self {
self.line(self.x, r2(y))
}
fn rel_line(&mut self, dx: f64, dy: f64) -> &mut Self {
self.line(self.x + r2(dx), self.y + r2(dy))
}
fn rel_h(&mut self, dx: f64) -> &mut Self {
self.line(self.x + r2(dx), self.y)
}
fn line(&mut self, x: f64, y: f64) -> &mut Self {
self.p.line_to(self.d.cx + x, self.d.cy + y);
self.at(x, y)
}
fn arc(&mut self, rx: f64, ry: f64, large: bool, sweep: bool, x: f64, y: f64) -> &mut Self {
let (x, y) = (r2(x), r2(y));
self.p.arc_to(
r2(rx),
r2(ry),
0.0,
large,
sweep,
self.d.cx + x,
self.d.cy + y,
);
self.at(x, y)
}
fn rel_quad(&mut self, dcx: f64, dcy: f64, dx: f64, dy: f64) -> &mut Self {
let (x, y) = (self.x, self.y);
self.quad_to(x + r2(dcx), y + r2(dcy), x + r2(dx), y + r2(dy))
}
fn rel_smooth(&mut self, dx: f64, dy: f64) -> &mut Self {
let (cx, cy) = if self.quad {
(2.0 * self.x - self.qx, 2.0 * self.y - self.qy)
} else {
(self.x, self.y)
};
let (x, y) = (self.x, self.y);
self.quad_to(cx, cy, x + r2(dx), y + r2(dy))
}
fn quad_to(&mut self, cx: f64, cy: f64, x: f64, y: f64) -> &mut Self {
let (ox, oy) = (self.d.cx, self.d.cy);
self.p.quad_to(ox + cx, oy + cy, ox + x, oy + y);
self.at(x, y);
(self.qx, self.qy, self.quad) = (cx, cy, true);
self
}
fn close(&mut self) -> &mut Self {
self.p.close();
let (x, y) = (self.x0, self.y0);
self.at(x, y)
}
fn done(&mut self, paint: Painting) {
let p = std::mem::take(&mut self.p);
self.d.parts.push(Part::path(p, paint));
}
}
fn r2(v: f64) -> f64 {
round(v * 100.0) / 100.0
}