mirror of
https://github.com/storytold/cadcraft.git
synced 2026-11-05 09:59:59 -06:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f02ee5c0b8 | ||
|
|
d4a9a37a09 | ||
|
|
3c123fd61c | ||
|
|
5bfcee6299 | ||
|
|
594c30ae2d | ||
|
|
fb6178b178 | ||
|
|
3083b5b47f | ||
|
|
f554de28b5 | ||
|
|
371e8b9e02 | ||
|
|
ff5b55842e | ||
|
|
064f58f252 | ||
|
|
ef4b4f7e6c | ||
|
|
510b7283cb | ||
|
|
0d72152142 | ||
|
|
639c2e9825 | ||
|
|
2099288f54 | ||
|
|
100d812155 | ||
|
|
e90a197365 | ||
|
|
de753897c4 | ||
|
|
39ee2e5960 | ||
|
|
5f8999f303 | ||
|
|
75049718c9 | ||
|
|
76d09aaddf | ||
|
|
d1e6f4df96 | ||
|
|
c2d62a2842 | ||
|
|
68e01dd487 | ||
|
|
6d3507f98d | ||
|
|
26e96cfb56 | ||
|
|
945c7f456b | ||
|
|
f10e41409d | ||
|
|
5bd563caac | ||
|
|
bd22363d5e | ||
|
|
a6ef259fec | ||
|
|
dad7d1c138 | ||
|
|
b1b3754ffa | ||
|
|
0e19f9d1d1 | ||
|
|
8232290830 | ||
|
|
457fc2319e | ||
|
|
15afc47296 | ||
|
|
d3226e629e | ||
|
|
238aa1789c | ||
|
|
f1312113d2 | ||
|
|
8cdcd6f85c | ||
|
|
28421dd1d7 | ||
|
|
162cc4792b | ||
|
|
4c00b6732f | ||
|
|
58bb1e065e | ||
|
|
0955078f56 | ||
|
|
f04be0548f | ||
|
|
852688e000 | ||
|
|
6d31491952 | ||
|
|
c8e1671eb4 | ||
|
|
f7494840dc | ||
|
|
b639b18f69 | ||
|
|
578da3ba9c | ||
|
|
f2afcd4f02 | ||
|
|
1619e24381 | ||
|
|
ffc2c553e8 | ||
|
|
7bb6c2f06f | ||
|
|
e10f394b28 | ||
|
|
9a112830c5 | ||
|
|
b04273e897 | ||
|
|
5ed192555c | ||
|
|
4c00f8b9e3 | ||
|
|
7b6be7835f | ||
|
|
455b2a87cb | ||
|
|
1b7c705fac | ||
|
|
77142aa79e | ||
|
|
31e6e16bc0 | ||
|
|
a4ff30d11e | ||
|
|
9d83f21910 | ||
|
|
7caf369555 | ||
|
|
f691d0b349 | ||
|
|
cbc9db75b1 | ||
|
|
e24c4553d2 | ||
|
|
c602a402c3 | ||
|
|
d180b28ef6 | ||
|
|
2e9761249a | ||
|
|
c956baa349 | ||
|
|
997f152cea | ||
|
|
0d3b3d21c0 | ||
|
|
4482316306 | ||
|
|
f6d84e28f3 | ||
|
|
806654aedf | ||
|
|
6c4984f5ad | ||
|
|
fdfe4d95a8 | ||
|
|
9d4b3bb091 | ||
|
|
7917afce5d | ||
|
|
14296dc4f5 | ||
|
|
2a85653976 | ||
|
|
12fd74a60f | ||
|
|
e1d0a4747e | ||
|
|
b7c62eb93d | ||
|
|
52c1e6de98 | ||
|
|
9107479ade | ||
|
|
1823eeab19 | ||
|
|
97761eb2bf | ||
|
|
8b97b4f47e | ||
|
|
4c61c3c889 | ||
|
|
a13870f376 | ||
|
|
a6ba747329 | ||
|
|
94b6c485b4 | ||
|
|
4c75916c75 | ||
|
|
ecd1bdc214 | ||
|
|
212f6cc988 |
@@ -187,10 +187,15 @@ fn bounds_in<'a>(d: &'a Drawing, e: &Entity, depth: usize, blocks: &mut HashMap<
|
||||
b = Bounds2::from_points(c.polygon().into_iter().map(|p| m.apply(p)));
|
||||
}
|
||||
if ins.cols > 1 || ins.rows > 1 {
|
||||
let off =
|
||||
Vec2::new(ins.col_spacing * f64::from(ins.cols.saturating_sub(1)), ins.row_spacing * f64::from(ins.rows.saturating_sub(1)))
|
||||
.rotate(ins.rotation);
|
||||
b = b.union(&Bounds2::from_points(b.corners().map(|c| c + off)));
|
||||
// The grid's four corner copies (as many as are drawn): turned, the far corner
|
||||
// alone misses the side ones.
|
||||
let (cols, rows) =
|
||||
(f64::from(ins.cols.clamp(1, 10_000).saturating_sub(1)), f64::from(ins.rows.clamp(1, 10_000).saturating_sub(1)));
|
||||
let one = b;
|
||||
for (c, r) in [(cols, 0.0), (0.0, rows), (cols, rows)] {
|
||||
let off = Vec2::new(ins.col_spacing * c, ins.row_spacing * r).rotate(ins.rotation);
|
||||
b = b.union(&Bounds2::from_points(one.corners().map(|p| p + off)));
|
||||
}
|
||||
}
|
||||
}
|
||||
for a in &ins.attribs {
|
||||
@@ -522,4 +527,35 @@ mod tests {
|
||||
assert!(extents.width() > drawn.width());
|
||||
}
|
||||
}
|
||||
|
||||
/// A turned array of references reaches its side copies, not only the first and the last;
|
||||
/// a hostile count measures only the copies that are drawn.
|
||||
#[test]
|
||||
fn a_rotated_array_counts_every_corner_copy() {
|
||||
let mut d = Drawing::new_metric();
|
||||
let mut b = crate::Block::new("B");
|
||||
b.entities.push(Entity::new(crate::Handle(1), EntityKind::Line(crate::Line { a: Vec3::ZERO, b: Vec3::new(1.0, 0.0, 0.0) })));
|
||||
d.blocks.insert("B".into(), std::sync::Arc::new(b));
|
||||
let array = |cols: u32, rows: u32, rotation: f64| {
|
||||
let ins = crate::Insert {
|
||||
block: "B".into(),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
rotation,
|
||||
cols,
|
||||
rows,
|
||||
col_spacing: 10.0,
|
||||
row_spacing: 10.0,
|
||||
..crate::Insert::default()
|
||||
};
|
||||
Entity::new(crate::Handle(2), EntityKind::Insert(ins))
|
||||
};
|
||||
// Turned 45°: the copies sit at (0, 0), (7.07, 7.07), (-7.07, 7.07) and (0, 14.14).
|
||||
let b = entity_bounds(&d, &array(2, 2, std::f64::consts::FRAC_PI_4), 0);
|
||||
let side = 10.0 * std::f64::consts::FRAC_1_SQRT_2;
|
||||
assert!((b.min.x + side).abs() < 1e-9 && b.max.x >= side, "{b:?}");
|
||||
assert!((b.max.y - 2.0 * side - std::f64::consts::FRAC_1_SQRT_2).abs() < 1e-9, "{b:?}");
|
||||
// The renderer draws at most 10,000 columns.
|
||||
let b = entity_bounds(&d, &array(u32::MAX, 1, 0.0), 0);
|
||||
assert!((b.max.x - (10.0 * 9_999.0 + 1.0)).abs() < 1e-6, "{b:?}");
|
||||
}
|
||||
}
|
||||
|
||||
+175
-52
@@ -149,6 +149,15 @@ pub enum Icon {
|
||||
PickAdd,
|
||||
SelectObjects,
|
||||
QuickSelect,
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
MoveTop,
|
||||
MoveUp,
|
||||
MoveDown,
|
||||
MoveBottom,
|
||||
ZoomFit,
|
||||
TextStyle,
|
||||
DimStyle,
|
||||
}
|
||||
|
||||
struct Pen<'a> {
|
||||
@@ -219,6 +228,14 @@ impl Pen<'_> {
|
||||
let (nx, ny) = (-uy, ux);
|
||||
self.tri([(x, y), (x - ux * 3.0 + nx * 1.4, y - uy * 3.0 + ny * 1.4), (x - ux * 3.0 - nx * 1.4, y - uy * 3.0 - ny * 1.4)], c);
|
||||
}
|
||||
/// A larger arrowhead than [`Pen::arrow_tip`]: `len` long, `half` wide either side.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn big_arrow(&self, x: f32, y: f32, dx: f32, dy: f32, len: f32, half: f32, c: Color32) {
|
||||
let l = (dx * dx + dy * dy).sqrt().max(1e-3);
|
||||
let (ux, uy) = (dx / l, dy / l);
|
||||
let (nx, ny) = (-uy, ux);
|
||||
self.tri([(x, y), (x - ux * len + nx * half, y - uy * len + ny * half), (x - ux * len - nx * half, y - uy * len - ny * half)], c);
|
||||
}
|
||||
fn text(&self, x: f32, y: f32, s: &str, size: f32, c: Color32) {
|
||||
self.p.text(self.at(x, y), egui::Align2::CENTER_CENTER, s, egui::FontId::proportional(size * self.sc()), c);
|
||||
}
|
||||
@@ -242,7 +259,7 @@ pub fn paint(p: &Painter, rect: Rect, icon: Icon, dim: bool) {
|
||||
paint_constraints(&pen, icon, b, a, dim);
|
||||
}
|
||||
|
||||
fn paint_file_view(pen: &Pen<'_>, icon: Icon, b: Color32, a: Color32, _dim: bool) {
|
||||
fn paint_file_view(pen: &Pen<'_>, icon: Icon, b: Color32, a: Color32, dim: bool) {
|
||||
use Icon::*;
|
||||
match icon {
|
||||
New => {
|
||||
@@ -258,26 +275,48 @@ fn paint_file_view(pen: &Pen<'_>, icon: Icon, b: Color32, a: Color32, _dim: bool
|
||||
pen.rect(8.0, 4.0, 8.0, 5.0, b);
|
||||
pen.rect(7.0, 13.0, 10.0, 7.0, b);
|
||||
if icon == SaveAs {
|
||||
pen.la(15.0, 22.0, 22.0, 15.0);
|
||||
pen.fill_c(15.0, 22.0, 1.2, a);
|
||||
// A large pencil across the lower-right corner: body, ferrule line and a filled tip.
|
||||
pen.p.add(egui::Shape::convex_polygon(
|
||||
[(12.6, 19.4), (20.6, 11.4), (23.6, 14.4), (15.6, 22.4)].iter().map(|(x, y)| pen.at(*x, *y)).collect(),
|
||||
Tokens::get().panel,
|
||||
Stroke::NONE,
|
||||
));
|
||||
pen.poly(&[(12.6, 19.4), (20.6, 11.4), (23.6, 14.4), (15.6, 22.4)], true, a);
|
||||
pen.la(19.0, 13.0, 22.0, 16.0);
|
||||
pen.tri([(12.6, 19.4), (15.6, 22.4), (10.8, 24.0)], a);
|
||||
}
|
||||
}
|
||||
Undo => {
|
||||
pen.arc(12.0, 13.0, 7.0, 150.0, -40.0, b);
|
||||
pen.arrow_tip(5.0, 9.0, -1.0, 2.5, b);
|
||||
Undo | Redo => {
|
||||
// CADCraft's own: a step back along the drawing's path. A short straight run with a
|
||||
// half-turn above it, the arrowhead at the free end pointing back (Undo: to the
|
||||
// left, Redo: mirrored to the right), in the icon colour.
|
||||
let m = |x: f32| if icon == Undo { x } else { 24.0 - x };
|
||||
let col = if dim { a } else { b };
|
||||
// The straight run along the bottom, then a half circle up and back over it.
|
||||
let (cx, cy, r) = (13.0_f32, 12.0_f32, 5.0_f32);
|
||||
let mut pts: Vec<egui::Pos2> = vec![pen.at(m(6.0), cy + r)];
|
||||
pts.extend((0..=20).map(|i| {
|
||||
let ang = (-90.0 + 180.0 * i as f32 / 20.0).to_radians();
|
||||
pen.at(m(cx + r * ang.cos()), cy - r * ang.sin())
|
||||
}));
|
||||
pts.push(pen.at(m(7.5), cy - r));
|
||||
pen.p.add(egui::Shape::line(pts, Stroke::new(pen.w * 1.8, col)));
|
||||
// The arrowhead at the free end, pointing back along the top run.
|
||||
let dx = if icon == Undo { -1.0 } else { 1.0 };
|
||||
pen.big_arrow(m(5.0), cy - r, dx, 0.0, 5.0, 4.6, col);
|
||||
}
|
||||
Redo => {
|
||||
pen.arc(12.0, 13.0, 7.0, 30.0, 220.0, b);
|
||||
pen.arrow_tip(19.0, 9.0, 1.0, 2.5, b);
|
||||
}
|
||||
Plot | Preview => {
|
||||
Plot => {
|
||||
pen.rect(4.0, 9.0, 16.0, 8.0, b);
|
||||
pen.rect(7.0, 4.0, 10.0, 5.0, b);
|
||||
pen.rect(7.0, 14.0, 10.0, 7.0, b);
|
||||
if icon == Preview {
|
||||
pen.c(17.0, 17.0, 3.0, a);
|
||||
pen.la(19.0, 19.0, 22.0, 22.0);
|
||||
}
|
||||
}
|
||||
Preview => {
|
||||
// A smaller printer on the left and the magnifier clear of it on the right.
|
||||
pen.rect(1.5, 8.0, 12.0, 6.5, b);
|
||||
pen.rect(3.8, 4.0, 7.4, 4.0, b);
|
||||
pen.rect(3.8, 12.0, 7.4, 6.0, b);
|
||||
pen.c(17.5, 13.0, 4.0, a);
|
||||
pen.p.line_segment([pen.at(20.4, 15.9), pen.at(23.0, 18.5)], Stroke::new(pen.w * 1.8, a));
|
||||
}
|
||||
Publish => {
|
||||
pen.rect(3.0, 10.0, 13.0, 7.0, b);
|
||||
@@ -291,12 +330,14 @@ fn paint_file_view(pen: &Pen<'_>, icon: Icon, b: Color32, a: Color32, _dim: bool
|
||||
}
|
||||
Import | Export => {
|
||||
pen.poly(&[(5.0, 3.0), (14.0, 3.0), (18.0, 7.0), (18.0, 21.0), (5.0, 21.0)], true, b);
|
||||
// A large, thick arrow: into the page for Import, out of it for Export.
|
||||
let thick = Stroke::new(pen.w * 2.0, a);
|
||||
if icon == Import {
|
||||
pen.la(1.0, 13.0, 11.0, 13.0);
|
||||
pen.arrow_tip(12.0, 13.0, 1.0, 0.0, a);
|
||||
pen.p.line_segment([pen.at(0.5, 13.0), pen.at(8.0, 13.0)], thick);
|
||||
pen.big_arrow(14.5, 13.0, 1.0, 0.0, 7.0, 5.0, a);
|
||||
} else {
|
||||
pen.la(11.0, 13.0, 21.0, 13.0);
|
||||
pen.arrow_tip(23.0, 13.0, 1.0, 0.0, a);
|
||||
pen.p.line_segment([pen.at(9.0, 13.0), pen.at(17.0, 13.0)], thick);
|
||||
pen.big_arrow(24.0, 13.0, 1.0, 0.0, 7.0, 5.0, a);
|
||||
}
|
||||
}
|
||||
Attach => {
|
||||
@@ -520,20 +561,24 @@ fn paint_edit_tools(pen: &Pen<'_>, icon: Icon, b: Color32, a: Color32, _dim: boo
|
||||
use Icon::*;
|
||||
match icon {
|
||||
Move => {
|
||||
pen.la(12.0, 3.0, 12.0, 21.0);
|
||||
pen.la(3.0, 12.0, 21.0, 12.0);
|
||||
pen.arrow_tip(12.0, 2.0, 0.0, -1.0, a);
|
||||
pen.arrow_tip(12.0, 22.0, 0.0, 1.0, a);
|
||||
pen.arrow_tip(2.0, 12.0, -1.0, 0.0, a);
|
||||
pen.arrow_tip(22.0, 12.0, 1.0, 0.0, a);
|
||||
pen.la(12.0, 6.0, 12.0, 18.0);
|
||||
pen.la(6.0, 12.0, 18.0, 12.0);
|
||||
pen.big_arrow(12.0, 1.5, 0.0, -1.0, 5.5, 3.3, a);
|
||||
pen.big_arrow(12.0, 22.5, 0.0, 1.0, 5.5, 3.3, a);
|
||||
pen.big_arrow(1.5, 12.0, -1.0, 0.0, 5.5, 3.3, a);
|
||||
pen.big_arrow(22.5, 12.0, 1.0, 0.0, 5.5, 3.3, a);
|
||||
}
|
||||
Copy => {
|
||||
pen.rect(3.0, 3.0, 11.0, 13.0, b);
|
||||
pen.rect(10.0, 8.0, 11.0, 13.0, a);
|
||||
}
|
||||
Rotate => {
|
||||
pen.arc(12.0, 12.0, 8.0, 100.0, 380.0, a);
|
||||
pen.arrow_tip(10.6, 4.1, -1.0, 0.1, a);
|
||||
// Most of a circle, counter-clockwise, ending in an arrowhead along it.
|
||||
pen.arc(12.0, 12.0, 8.0, 50.0, 300.0, a);
|
||||
let end = 300f32.to_radians();
|
||||
let (px, py) = (12.0 + 8.0 * end.cos(), 12.0 - 8.0 * end.sin());
|
||||
let (ux, uy) = (-end.sin(), -end.cos());
|
||||
pen.big_arrow(px + ux * 4.5, py + uy * 4.5, ux, uy, 5.5, 3.3, a);
|
||||
pen.dot(12.0, 12.0);
|
||||
}
|
||||
Scale => {
|
||||
@@ -558,20 +603,22 @@ fn paint_edit_tools(pen: &Pen<'_>, icon: Icon, b: Color32, a: Color32, _dim: boo
|
||||
pen.la(4.0, 18.0, 4.0, 22.0);
|
||||
}
|
||||
Trim => {
|
||||
pen.l(8.0, 2.0, 8.0, 22.0);
|
||||
pen.l(16.0, 2.0, 16.0, 22.0);
|
||||
pen.l(2.0, 12.0, 8.0, 12.0);
|
||||
pen.l(16.0, 12.0, 22.0, 12.0);
|
||||
for x in [9.5, 12.0, 14.5] {
|
||||
pen.la(x, 12.0, x + 1.0, 12.0);
|
||||
}
|
||||
pen.poly(&[(10.0, 17.0), (12.0, 15.0), (14.0, 17.0)], false, a);
|
||||
// Scissors cutting a line in two: the line's halves apart under the blades.
|
||||
pen.l(1.5, 20.0, 9.0, 20.0);
|
||||
pen.l(15.0, 20.0, 22.5, 20.0);
|
||||
pen.c(7.5, 4.5, 3.0, a);
|
||||
pen.c(16.5, 4.5, 3.0, a);
|
||||
pen.poly(&[(9.3, 6.9), (14.6, 18.5)], false, a);
|
||||
pen.poly(&[(14.7, 6.9), (9.4, 18.5)], false, a);
|
||||
pen.tri([(14.6, 18.5), (13.0, 13.5), (11.8, 14.6)], a);
|
||||
pen.tri([(9.4, 18.5), (11.0, 13.5), (12.2, 14.6)], a);
|
||||
pen.fill_c(12.0, 12.6, 1.3, pen.pt);
|
||||
}
|
||||
Extend => {
|
||||
pen.l(20.0, 2.0, 20.0, 22.0);
|
||||
pen.l(3.0, 12.0, 11.0, 12.0);
|
||||
pen.la(11.0, 12.0, 19.0, 12.0);
|
||||
pen.arrow_tip(19.5, 12.0, 1.0, 0.0, a);
|
||||
pen.l(21.0, 2.0, 21.0, 22.0);
|
||||
pen.l(2.0, 12.0, 9.0, 12.0);
|
||||
pen.la(9.0, 12.0, 14.0, 12.0);
|
||||
pen.big_arrow(20.5, 12.0, 1.0, 0.0, 7.0, 4.0, a);
|
||||
}
|
||||
Fillet => {
|
||||
pen.l(4.0, 20.0, 4.0, 12.0);
|
||||
@@ -645,7 +692,21 @@ fn paint_edit_tools(pen: &Pen<'_>, icon: Icon, b: Color32, a: Color32, _dim: boo
|
||||
fn paint_dimensions(pen: &Pen<'_>, icon: Icon, b: Color32, a: Color32, _dim: bool) {
|
||||
use Icon::*;
|
||||
match icon {
|
||||
DimLinear | DimAligned | DimBaseline | DimContinue | QDim => {
|
||||
DimAligned => {
|
||||
// The linear dimension's figure turned 13° counter-clockwise, as an aligned
|
||||
// dimension follows a slanted object.
|
||||
let (sin, cos) = 13f32.to_radians().sin_cos();
|
||||
let r = |x: f32, y: f32| (12.0 + (x - 12.0) * cos + (y - 12.0) * sin, 12.0 - (x - 12.0) * sin + (y - 12.0) * cos);
|
||||
let line = |x1: f32, y1: f32, x2: f32, y2: f32, c: Color32| pen.poly(&[r(x1, y1), r(x2, y2)], false, c);
|
||||
let (y0, y1) = (9.0, 19.0);
|
||||
line(4.0, y1, 4.0, 6.0, b);
|
||||
line(20.0, y1, 20.0, 6.0, b);
|
||||
line(4.0, y0, 20.0, y0, a);
|
||||
let ((ax, ay), (bx, by)) = (r(4.0, y0), r(20.0, y0));
|
||||
pen.arrow_tip(ax, ay, -cos, sin, a);
|
||||
pen.arrow_tip(bx, by, cos, -sin, a);
|
||||
}
|
||||
DimLinear | DimBaseline | DimContinue | QDim => {
|
||||
let (y0, y1) = (8.0, 18.0);
|
||||
pen.l(4.0, y1, 4.0, 5.0);
|
||||
pen.l(20.0, y1, 20.0, 5.0);
|
||||
@@ -658,7 +719,6 @@ fn paint_dimensions(pen: &Pen<'_>, icon: Icon, b: Color32, a: Color32, _dim: boo
|
||||
pen.l(12.0, y1, 12.0, 5.0);
|
||||
}
|
||||
QDim => pen.text(12.0, 15.0, "Q", 7.0, b),
|
||||
DimAligned => {}
|
||||
_ => pen.text(12.0, 14.5, "1.5", 6.0, b),
|
||||
}
|
||||
}
|
||||
@@ -845,15 +905,20 @@ fn paint_display_toggles(pen: &Pen<'_>, icon: Icon, b: Color32, _a: Color32, dim
|
||||
pen.l(17.0, 13.0, 17.0, 21.0);
|
||||
}
|
||||
Gear => {
|
||||
pen.c(12.0, 12.0, 4.0, b);
|
||||
// A gear wheel: eight square teeth on a rim, and the hole in the middle.
|
||||
let mut outline = Vec::new();
|
||||
for i in 0..8 {
|
||||
let ang = (i as f32 * 45.0).to_radians();
|
||||
pen.p.line_segment(
|
||||
[pen.at(12.0 + 6.0 * ang.cos(), 12.0 + 6.0 * ang.sin()), pen.at(12.0 + 9.0 * ang.cos(), 12.0 + 9.0 * ang.sin())],
|
||||
Stroke::new(2.2 * pen.sc(), b),
|
||||
);
|
||||
let c = i as f32 * 45.0;
|
||||
for (da, rr) in [(-17.0, 7.0), (-11.0, 9.6), (11.0, 9.6), (17.0, 7.0)] {
|
||||
let a = (c + da).to_radians();
|
||||
outline.push(pen.at(12.0 + rr * a.cos(), 12.0 + rr * a.sin()));
|
||||
}
|
||||
}
|
||||
pen.c(12.0, 12.0, 6.5, b);
|
||||
if let Some(f) = outline.first().copied() {
|
||||
outline.push(f);
|
||||
}
|
||||
pen.p.add(egui::Shape::line(outline, Stroke::new(pen.w * 1.2, b)));
|
||||
pen.c(12.0, 12.0, 3.2, b);
|
||||
}
|
||||
Annotation => {
|
||||
pen.poly(&[(12.0, 3.0), (21.0, 20.0), (3.0, 20.0)], true, b);
|
||||
@@ -885,6 +950,59 @@ fn paint_navigation_controls(pen: &Pen<'_>, icon: Icon, b: Color32, a: Color32,
|
||||
pen.fill_rect(x, y, 8.0, 8.0, if x == 3.0 && y == 3.0 { a } else { b });
|
||||
}
|
||||
}
|
||||
ZoomIn | ZoomOut => {
|
||||
pen.c(10.0, 10.0, 7.0, b);
|
||||
pen.p.line_segment([pen.at(15.0, 15.0), pen.at(21.5, 21.5)], Stroke::new(pen.w * 2.2, b));
|
||||
pen.la(6.5, 10.0, 13.5, 10.0);
|
||||
if icon == ZoomIn {
|
||||
pen.la(10.0, 6.5, 10.0, 13.5);
|
||||
}
|
||||
}
|
||||
MoveTop | MoveBottom => {
|
||||
// A bar at the end the arrow points to.
|
||||
let (bar, tip, tail, dir) = if icon == MoveTop { (4.0, 7.0, 21.0, -1.0) } else { (20.0, 17.0, 3.0, 1.0) };
|
||||
pen.p.line_segment([pen.at(5.0, bar), pen.at(19.0, bar)], Stroke::new(pen.w * 1.6, a));
|
||||
pen.l(12.0, tail, 12.0, tip - 5.0 * dir);
|
||||
pen.big_arrow(12.0, tip, 0.0, dir, 6.0, 4.5, b);
|
||||
}
|
||||
MoveUp | MoveDown => {
|
||||
let dir = if icon == MoveUp { -1.0 } else { 1.0 };
|
||||
pen.l(12.0, 12.0 - 8.0 * dir, 12.0, 12.0 + 2.0 * dir);
|
||||
pen.big_arrow(12.0, 12.0 + 8.0 * dir, 0.0, dir, 7.0, 5.0, b);
|
||||
}
|
||||
ZoomFit => {
|
||||
// Four right-angle corners framing a rectangle.
|
||||
let s = Stroke::new(pen.w * 1.6, b);
|
||||
for (x, y, dx, dy) in [(3.0, 5.0, 1.0, 1.0), (21.0, 5.0, -1.0, 1.0), (3.0, 19.0, 1.0, -1.0), (21.0, 19.0, -1.0, -1.0)] {
|
||||
pen.p.line(vec![pen.at(x + dx * 5.0, y), pen.at(x, y), pen.at(x, y + dy * 4.5)], s);
|
||||
}
|
||||
pen.rect(8.0, 9.0, 8.0, 6.0, a);
|
||||
}
|
||||
TextStyle | DimStyle => {
|
||||
if icon == TextStyle {
|
||||
pen.text(9.5, 10.5, "A", 15.0, b);
|
||||
} else {
|
||||
pen.l(2.0, 4.0, 2.0, 12.0);
|
||||
pen.l(15.0, 4.0, 15.0, 12.0);
|
||||
pen.l(2.0, 8.0, 15.0, 8.0);
|
||||
pen.arrow_tip(2.0, 8.0, -1.0, 0.0, b);
|
||||
pen.arrow_tip(15.0, 8.0, 1.0, 0.0, b);
|
||||
}
|
||||
// A small gear: the style's settings.
|
||||
let mut outline = Vec::new();
|
||||
for i in 0..6 {
|
||||
let c = i as f32 * 60.0;
|
||||
for (da, rr) in [(-20.0, 3.6), (-12.0, 5.0), (12.0, 5.0), (20.0, 3.6)] {
|
||||
let ang = (c + da).to_radians();
|
||||
outline.push(pen.at(17.5 + rr * ang.cos(), 17.5 + rr * ang.sin()));
|
||||
}
|
||||
}
|
||||
if let Some(f) = outline.first().copied() {
|
||||
outline.push(f);
|
||||
}
|
||||
pen.p.add(egui::Shape::line(outline, Stroke::new(pen.w, a)));
|
||||
pen.c(17.5, 17.5, 1.5, a);
|
||||
}
|
||||
Close => {
|
||||
pen.l(6.0, 6.0, 18.0, 18.0);
|
||||
pen.l(18.0, 6.0, 6.0, 18.0);
|
||||
@@ -1025,6 +1143,11 @@ fn paint_constraints(pen: &Pen<'_>, icon: Icon, b: Color32, a: Color32, dim: boo
|
||||
|
||||
/// A shared button interaction with CADCraft's original multicolor icon painting.
|
||||
pub fn button(ui: &mut egui::Ui, icon: Icon, size: f32, tooltip: &str, selected: bool) -> egui::Response {
|
||||
button_bare(ui, icon, size, tooltip, selected).on_hover_text(crate::i18n::t(tooltip))
|
||||
}
|
||||
|
||||
/// [`button`] without its hover text, for callers that show a richer tooltip of their own.
|
||||
pub fn button_bare(ui: &mut egui::Ui, icon: Icon, size: f32, tooltip: &str, selected: bool) -> egui::Response {
|
||||
let t = Tokens::get();
|
||||
let visuals = |fill| ButtonVisuals { fill, stroke: Stroke::NONE, icon: t.icon };
|
||||
let style = IconButtonStyle {
|
||||
@@ -1041,7 +1164,7 @@ pub fn button(ui: &mut egui::Ui, icon: Icon, size: f32, tooltip: &str, selected:
|
||||
paint(ui.painter(), rect.shrink(size * 0.08), icon, !ui.is_enabled());
|
||||
});
|
||||
crate::control::record_widget(ui, &resp, tooltip, "button", Some(selected));
|
||||
resp.on_hover_text(crate::i18n::t(tooltip))
|
||||
resp
|
||||
}
|
||||
|
||||
/// Labels and focus outlines for controls whose existing geometry is painted by the app.
|
||||
@@ -1060,9 +1183,9 @@ pub fn describe_control(ui: &egui::Ui, response: &egui::Response, label: &str, s
|
||||
node.set_selected(selected);
|
||||
}
|
||||
});
|
||||
if response.clicked() {
|
||||
response.request_focus();
|
||||
}
|
||||
// A click leaves the keyboard with the drawing (shortcuts such as Ctrl+V and typed
|
||||
// commands keep working after switching drawings), as the drafting buttons already do.
|
||||
// Keyboard focus still shows its outline below when the tab gets it another way.
|
||||
}
|
||||
if response.has_focus() && response.rect.width() > 4.0 && response.rect.height() > 4.0 {
|
||||
ui.painter().rect_stroke(response.rect.shrink(2.0), 2.0, Stroke::new(1.0, Tokens::get().accent), egui::StrokeKind::Inside);
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
//! Ctrl+C in one open drawing, Ctrl+V in another: the objects are pasted there.
|
||||
|
||||
use cadcraft_engine::doc::EntityKind;
|
||||
use cadcraft_engine::geom::Vec2;
|
||||
use cadcraft_engine::{Input, Session};
|
||||
use cadcraft_ui_egui::{CadApp, Services};
|
||||
use egui::{Event, vec2};
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use serde_json::json;
|
||||
|
||||
fn harness(app: CadApp) -> Harness<'static, CadApp> {
|
||||
let mut h = Harness::builder().with_size(vec2(1600.0, 1000.0)).with_step_dt(1.0 / 60.0).build_ui_state(
|
||||
|ui, app: &mut CadApp| {
|
||||
app.logic(ui.ctx());
|
||||
app.ui(ui);
|
||||
},
|
||||
app,
|
||||
);
|
||||
h.run_steps(5);
|
||||
h
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_in_one_drawing_and_paste_in_another() {
|
||||
// A DXF writer for the clipboard text (the app installs the real one).
|
||||
cadcraft_engine::cmd::file::set_io(cadcraft_engine::cmd::file::IoHooks {
|
||||
read: |_, _| Ok(cadcraft_engine::doc::Drawing::new_metric()),
|
||||
write: |_, _| Ok(b"0\nSECTION\n0\nENDSEC\n0\nEOF\n".to_vec()),
|
||||
plot: None,
|
||||
});
|
||||
let mut app = CadApp::new(Session::new(), Services::default());
|
||||
app.run("line", json!({ "points": [[0, 0], [10, 0]] })).unwrap();
|
||||
app.run("circle", json!({ "center": [5, 5], "radius": 2 })).unwrap();
|
||||
app.run("block", json!({ "name": "DOT", "base": [5, 5], "handles": [app.session.doc().unwrap().model.iter().nth(1).unwrap().handle.hex()], "keep": "convert" }))
|
||||
.unwrap();
|
||||
let first = app.session.active;
|
||||
let all: Vec<_> = app.session.doc().unwrap().model.iter().map(|e| e.handle).collect();
|
||||
app.session.set_selection(all);
|
||||
let mut h = harness(app);
|
||||
h.event(Event::Copy);
|
||||
h.run_steps(2);
|
||||
// The copy is mirrored to the system clipboard as text (what another drawing or another
|
||||
// CADCraft window pastes): checked on a frame of its own, where egui's output is kept.
|
||||
let text = {
|
||||
let ctx = egui::Context::default();
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| cadcraft_ui_egui::clipboard::mirror(h.state(), ui.ctx()));
|
||||
out.textures_delta.clear();
|
||||
out.platform_output.commands.iter().find_map(|c| match c {
|
||||
egui::OutputCommand::CopyText(t) => Some(t.clone()),
|
||||
_ => None,
|
||||
})
|
||||
};
|
||||
let text = text.expect("the copy is on the system clipboard as text");
|
||||
assert_eq!(
|
||||
h.state().session.clipboard.len(),
|
||||
2,
|
||||
"{:?}",
|
||||
h.state().session.doc().unwrap().model.iter().map(|e| e.kind.type_name()).collect::<Vec<_>>()
|
||||
);
|
||||
// Another drawing, made active by clicking its tab (which must not keep the keyboard).
|
||||
h.state_mut().run("new", json!({})).unwrap();
|
||||
h.run_steps(3);
|
||||
let second = h.state().session.active;
|
||||
let title = h.state().session.docs[second].title.clone();
|
||||
h.state_mut().run("document.switch", json!({ "index": first })).unwrap();
|
||||
h.run_steps(3);
|
||||
h.get_by_label(&title).click();
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().session.active, second);
|
||||
assert_ne!(second, first);
|
||||
assert!(h.state().session.doc().unwrap().model.is_empty());
|
||||
assert!(!h.ctx.egui_wants_keyboard_input(), "the clicked tab doesn't hold the keyboard");
|
||||
// Ctrl+V: egui reports it as a Paste event with the system clipboard's text.
|
||||
h.event(Event::Paste(text));
|
||||
h.run_steps(3);
|
||||
assert_eq!(
|
||||
h.state().session.running.as_ref().map(|r| r.id.as_str()),
|
||||
Some("pasteclip"),
|
||||
"{:?}",
|
||||
h.state().session.log.iter().rev().take(5).collect::<Vec<_>>()
|
||||
);
|
||||
h.state_mut().session.input(Input::Point(Vec2::new(100.0, 100.0))).unwrap();
|
||||
h.run_steps(2);
|
||||
let d = h.state().session.doc().unwrap();
|
||||
assert_eq!(d.model.len(), 2);
|
||||
assert!(d.model.iter().any(|e| matches!(&e.kind, EntityKind::Insert(i) if i.block == "DOT")));
|
||||
assert!(d.block("DOT").is_some());
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
mod animation_effects;
|
||||
mod block_description_palette;
|
||||
mod chrome_geometry;
|
||||
mod clipboard_between_drawings;
|
||||
mod context_menu;
|
||||
mod context_menus;
|
||||
mod control_cmdline;
|
||||
|
||||
Reference in New Issue
Block a user