Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c2d8947
feat(shell): route the selection verbs through the engine
Nov 12, 2026
a2e23cc
feat(panels): the Pages panel renames and deletes pages
Nov 12, 2026
3c08b67
feat(panels): the Styles panel redefines and scales styles
Nov 12, 2026
d3e94f8
feat(panels): the States panel reorders the states and fans the selec…
Nov 12, 2026
7b8ded5
feat(panels): the Properties panel writes the text object's layout
Nov 12, 2026
22e2822
docs(tasks): record the reachability campaign on the live board
Nov 12, 2026
48062e2
feat(panels): the text dials, guides, layer move
Nov 12, 2026
a935042
docs(tasks): record the batch F bindings
Nov 12, 2026
6862d8a
feat(panels): the symbol family reaches the engine
Nov 12, 2026
c8dbb2c
docs(tasks): record the symbol family bindings
Nov 12, 2026
765989b
feat(panels): the markup verbs and the button text
Nov 12, 2026
2542ba1
docs(tasks): record the markup and button-text bindings
Nov 12, 2026
42bc466
feat(panels): the filters panel bakes and renames
Nov 12, 2026
7a787ec
docs(tasks): record the filter bake and rename bindings
Nov 12, 2026
a904fbf
fix(engine): read a uniform mask's coverage through the renderer's view
Nov 12, 2026
599bbab
feat(panels): the properties panel carries the element mask
Nov 12, 2026
471ca15
docs(tasks): record the mask bindings
Nov 12, 2026
cd39489
feat(panels): the master page and the shared layers
Nov 12, 2026
cead811
feat(panels): the rectangle, mask, and pattern
Nov 12, 2026
98e6cac
feat(panels): the slices, styles, and channel verbs
Nov 12, 2026
6d9bda2
fix(shell): the panels compile again, and the queue is measured
Nov 12, 2026
15db5ef
feat(shapes): the rounded and chamfered rectangles
Nov 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 48 additions & 5 deletions crates/app/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,11 +290,15 @@ pub enum AutoShape {
Measure,
/// `autoshape.connectorLine`.
ConnectorLine,
/// `autoshape.rect` — the Rounded Rectangle tool.
RoundedRect,
/// `autoshape.rect` — the Chamfer Rectangle tool.
ChamferRect,
}

impl AutoShape {
/// Menu order.
pub const ALL: [AutoShape; 10] = [
pub const ALL: [AutoShape; 12] = [
AutoShape::Star,
AutoShape::Polygon,
AutoShape::Spiral,
Expand All @@ -305,6 +309,8 @@ impl AutoShape {
AutoShape::ArrowLine,
AutoShape::Measure,
AutoShape::ConnectorLine,
AutoShape::RoundedRect,
AutoShape::ChamferRect,
];

/// Menu label.
Expand All @@ -321,6 +327,8 @@ impl AutoShape {
AutoShape::ArrowLine => "Arrow Line",
AutoShape::Measure => "Measure",
AutoShape::ConnectorLine => "Connector Line",
AutoShape::RoundedRect => "Rounded Rectangle",
AutoShape::ChamferRect => "Chamfer Rectangle",
}
}

Expand All @@ -338,14 +346,24 @@ impl AutoShape {
AutoShape::ArrowLine => "autoshape.arrowLine",
AutoShape::Measure => "autoshape.measure",
AutoShape::ConnectorLine => "autoshape.connectorLine",
// The three rectangle tools share one generator and
// differ only in the corner treatment they ask for.
AutoShape::RoundedRect | AutoShape::ChamferRect => "autoshape.rect",
}
}

/// Default-parameter payload for a completed drag: center-based
/// shapes take the frame center + radius, frame-based shapes the
/// normalized frame, line-based shapes the raw endpoints.
#[must_use]
fn payload(self, drag: &Drag, page: usize, layer: usize, frame: usize) -> serde_json::Value {
fn payload(
self,
drag: &Drag,
page: usize,
layer: usize,
frame: usize,
corner: f64,
) -> serde_json::Value {
let (sx, sy) = (drag.start.x, drag.start.y);
let (cx, cy) = (drag.current.x, drag.current.y);
// Frame origin/size, normalized so drags in any direction
Expand All @@ -358,7 +376,7 @@ impl AutoShape {
start: (sx, sy),
end: (cx, cy),
};
self.payload_from(&placement, page, layer, frame)
self.payload_from(&placement, page, layer, frame, corner)
}

/// The payload for a shape inserted without a drag — the Auto
Expand All @@ -373,6 +391,7 @@ impl AutoShape {
x: f64,
y: f64,
size: f64,
corner: f64,
) -> serde_json::Value {
let mid = y + size / 2.0;
let placement = Placement {
Expand All @@ -383,7 +402,7 @@ impl AutoShape {
start: (x, mid),
end: (x + size, mid),
};
self.payload_from(&placement, page, layer, frame)
self.payload_from(&placement, page, layer, frame, corner)
}

/// The registry payload for one shape placement. Every shape
Expand All @@ -397,6 +416,7 @@ impl AutoShape {
page: usize,
layer: usize,
frame: usize,
corner: f64,
) -> serde_json::Value {
let frame_json = serde_json::json!({
"page": page, "layer": layer, "cell": frame, "name": null,
Expand Down Expand Up @@ -461,6 +481,19 @@ impl AutoShape {
"elbowX": cx, "elbowY": (sy + cy) / 2.0,
"corner1": 4.0, "corner2": 4.0, "vertical": false,
}),
// The three rectangle tools share one generator; only the
// corner treatment differs. The radius is clamped so the
// cut never exceeds half the shorter side.
AutoShape::RoundedRect => serde_json::json!({
"x": x, "y": y, "width": width, "height": height,
"corner": corner.min(0.5 * width.min(height)).max(0.0),
"kind": "round",
}),
AutoShape::ChamferRect => serde_json::json!({
"x": x, "y": y, "width": width, "height": height,
"corner": corner.min(0.5 * width.min(height)).max(0.0),
"kind": "chamfer",
}),
};
if let (Some(object), Some(place)) = (value.as_object_mut(), frame_json.as_object()) {
for (key, field) in place {
Expand Down Expand Up @@ -1014,6 +1047,9 @@ pub struct Editor {
pub wand_tolerance: u8,
/// The feather radius the panel's slider applies.
pub feather_radius: u8,
/// The corner radius the rectangle tools cut with (view state — the
/// engine re-reads it from every placement's payload).
pub rect_corner: f64,
}

impl Editor {
Expand Down Expand Up @@ -1088,6 +1124,7 @@ impl Editor {
pixel_mode: PixelSelectMode::Replace,
wand_tolerance: 32,
feather_radius: 2,
rect_corner: 12.0,
}
}

Expand Down Expand Up @@ -2413,7 +2450,13 @@ impl Editor {
Tool::AutoShape(shape) => {
self.apply(
shape.command(),
shape.payload(&drag, self.page, self.active_layer(), self.frame),
shape.payload(
&drag,
self.page,
self.active_layer(),
self.frame,
self.rect_corner,
),
)?;
true
}
Expand Down
56 changes: 56 additions & 0 deletions crates/app/src/shell/glyphs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,62 @@ pub(crate) fn draw_autoshape_glyph(
stroke,
));
}
AutoShape::RoundedRect => {
// The rounded box: four quarter-circle arcs joined by the
// straight edges.
let (half_w, half_h, radius) = (6.0f32, 6.0f32, 3.0f32);
let mut points = Vec::new();
// The four arc centers, clockwise from the top-left.
let centers = [
(c.x - half_w + radius, c.y - half_h + radius),
(c.x + half_w - radius, c.y - half_h + radius),
(c.x + half_w - radius, c.y + half_h - radius),
(c.x - half_w + radius, c.y + half_h - radius),
];
// Each arc runs from its start angle to its end angle.
let arcs = [
(std::f64::consts::PI, 1.5 * std::f64::consts::PI),
(1.5 * std::f64::consts::PI, 2.0 * std::f64::consts::PI),
(0.0, 0.5 * std::f64::consts::PI),
(0.5 * std::f64::consts::PI, std::f64::consts::PI),
];
for (index, (center, (start, end))) in centers.iter().zip(arcs.iter()).enumerate() {
for step in 0..=6 {
let angle = start + (end - start) * step as f64 / 6.0;
points.push(Pos2::new(
(center.0 + (radius as f64 * angle.cos()) as f32),
(center.1 + (radius as f64 * angle.sin()) as f32),
));
}
// The straight edge to the next arc's start.
let next = centers[(index + 1) % 4];
let next_angle = arcs[(index + 1) % 4].0;
points.push(Pos2::new(
(next.0 + (radius as f64 * next_angle.cos()) as f32),
(next.1 + (radius as f64 * next_angle.sin()) as f32),
));
}
points.push(points[0]);
painter.add(egui::Shape::line(points, stroke));
}
AutoShape::ChamferRect => {
// The chamfered box: eight straight sides.
let (half_w, half_h, cut) = (6.0f32, 6.0f32, 3.0f32);
painter.add(egui::Shape::line(
vec![
Pos2::new(c.x - half_w + cut, c.y - half_h),
Pos2::new(c.x + half_w - cut, c.y - half_h),
Pos2::new(c.x + half_w, c.y - half_h + cut),
Pos2::new(c.x + half_w, c.y + half_h - cut),
Pos2::new(c.x + half_w - cut, c.y + half_h),
Pos2::new(c.x - half_w + cut, c.y + half_h),
Pos2::new(c.x - half_w, c.y + half_h - cut),
Pos2::new(c.x - half_w, c.y - half_h + cut),
Pos2::new(c.x - half_w + cut, c.y - half_h),
],
stroke,
));
}
}
}

Expand Down
Loading
Loading