mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-05 18:19:57 +00:00
Merge pull request #1424
This commit is contained in:
commit
41b7e31be0
4 files changed
+90
-5
No files matched your search
@@ -486,6 +486,7 @@ Newest first.
|
||||
- **2026-10-16** · Lock Guides and Lock Column Guides are commands in View ▸ Grids & Guides (`view.lockGuides`, ⌥⌘;, and `view.lockColumnGuides`), so the control channel and MCP reach them. Lock Column Guides is an undo step, is saved with the document, on in new ones, and read from and written to IDML's `ColumnGuideLocked`, which export always wrote as locked (#1096). No readiness percentage change.
|
||||
- **2026-10-16** · IDML: a page keeps column guides that were dragged off centre (InDesign's Custom columns). They were divided evenly on import, so the gutter sprang back to the middle; the imported positions now place the columns, with the canvas guides, snapping and text placement following them, and they are written back on export. Layout › Margins and Columns re-divides them evenly when the count or gutter changes, as InDesign's does. No readiness percentage change.
|
||||
- **2026-10-16** · IDML: custom column positions are checked before they are used (#1096). A set must give two edges per column, ascending from the left margin to the right one; any other (a wrong count, unsorted or non-numeric values, a list longer than 216 columns allow, or positions left behind by a margin or page-size change) is the even split, in the canvas, on import and on export, instead of columns drawn outside the margins. No readiness percentage change.
|
||||
- **2026-10-21** · An empty graphic frame's X joins its corners, so a frame slanted with Direct Selection keeps its X corner to corner, as InDesign draws it; it used to stay across the old rectangle. Found in Zero2Graphic's *How to Page Layout design in Adobe InDesign 2022 CC*. No readiness percentage change.
|
||||
- **2026-10-21** · Paragraph Border and Paragraph Shading link their four sides as InDesign's: the stroke weights, offsets and corners have the "Make all settings the same" link, on when the four are the same, so a side typed in sets all four; with the link off a side is set alone (a border with only a thick left side). Found in the Learn Skills Daily course, chapter 40. No readiness percentage change.
|
||||
- **2026-10-21** · Paragraph rules have InDesign's Type (solid, thick/thin stripes, dashed, dotted, straight hash, wavy, or a stroke style of the document), Gap Color and Gap Tint, and Keep In Frame (rule above); the Paragraph Rules page is laid out as InDesign's (Weight and Type, Color and Tint, Gap Color and Gap Tint, Width and Offset, Left and Right Indent). IDML reads and writes them (`RuleAboveType`, `RuleBelowGapColor`, `KeepRuleAboveInFrame`…). Found in the Learn Skills Daily course, chapter 20. No readiness percentage change.
|
||||
- **2026-10-21** · New Character Style with text selected starts from what the text has of its own over its paragraph style, as InDesign's ("[None] + Bold + color"), and Apply Style to Selection gives the text the new style, with no override left. A new style's dialog is titled New Paragraph Style / New Character Style. `type.selectionAttrs` reports the text's own character attributes (`ownChars`). Found in the Learn Skills Daily course, chapter 22. No readiness percentage change.
|
||||
|
||||
@@ -30,7 +30,7 @@ mod view;
|
||||
use column_guides::{COLUMN_DRAG, ColumnDrag, column_guide_at, spread_x_across};
|
||||
use frames::{draw_frames, draw_selection};
|
||||
#[cfg(test)]
|
||||
use frames::{item_canvas_xf, overset_frames, overset_marks, ported_frames, text_frames_in};
|
||||
use frames::{frame_x, item_canvas_xf, overset_frames, overset_marks, ported_frames, text_frames_in};
|
||||
use guides::{GUIDE_DRAG, GUIDE_PRESS, GuideDrag, GuidePress, guide_readout, guide_spot, moving_guides, readout_at};
|
||||
#[cfg(test)]
|
||||
use guides::{guide_at, guide_drag_delta, guide_measure};
|
||||
|
||||
@@ -128,14 +128,38 @@ fn draw_item_edges(painter: &egui::Painter, xf: &Xf, doc: &Document, it: &Item,
|
||||
}
|
||||
// Empty graphic frame: the X.
|
||||
if it.object_style == designcraft_doc::BASIC_GRAPHICS_FRAME && matches!(it.content, Content::Unassigned) {
|
||||
let r = it.inner_bounds();
|
||||
let m = a * it.xf;
|
||||
let p = |x: f64, y: f64| xf.to_screen(m * Point::new(x, y));
|
||||
painter.line_segment([p(r.x0, r.y0), p(r.x1, r.y1)], Stroke::new(hair(painter), col.gamma_multiply(0.75)));
|
||||
painter.line_segment([p(r.x1, r.y0), p(r.x0, r.y1)], Stroke::new(hair(painter), col.gamma_multiply(0.75)));
|
||||
for (p0, p1) in frame_x(it) {
|
||||
painter.line_segment([xf.to_screen(m * p0), xf.to_screen(m * p1)], Stroke::new(hair(painter), col.gamma_multiply(0.75)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The X of an empty graphic frame, in its own space: a four-cornered frame joins its opposite
|
||||
/// corners, so a frame slanted with Direct Selection keeps its X corner to corner, as InDesign
|
||||
/// draws it; other shapes span their bounds.
|
||||
pub(super) fn frame_x(it: &Item) -> [(Point, Point); 2] {
|
||||
// A rectangle (slanted or not: four corner points, no handles). Ellipses, polygons (a
|
||||
// four-sided one is a diamond) and curved paths keep the X across their bounds. Edited points
|
||||
// make any of them a path: a four-cornered one whose corners would cross as a plus (a diamond,
|
||||
// edited) keeps the bounds too.
|
||||
let crosses = |a: Point, b: Point| {
|
||||
let d = b - a;
|
||||
d.x.abs() > 0.18 * d.hypot() && d.y.abs() > 0.18 * d.hypot()
|
||||
};
|
||||
if matches!(it.shape, designcraft_doc::Shape::Rectangle | designcraft_doc::Shape::Path)
|
||||
&& let [sp] = it.path.subpaths.as_slice()
|
||||
&& sp.closed
|
||||
&& let [c0, c1, c2, c3] = sp.anchors.as_slice()
|
||||
&& sp.anchors.iter().all(|a| a.h_in == a.p && a.h_out == a.p)
|
||||
&& (it.shape == designcraft_doc::Shape::Rectangle || (crosses(c0.p, c2.p) && crosses(c1.p, c3.p)))
|
||||
{
|
||||
return [(c0.p, c2.p), (c1.p, c3.p)];
|
||||
}
|
||||
let r = it.inner_bounds();
|
||||
[(Point::new(r.x0, r.y0), Point::new(r.x1, r.y1)), (Point::new(r.x1, r.y0), Point::new(r.x0, r.y1))]
|
||||
}
|
||||
|
||||
/// Layer colour of the first selected item: live-corner marks are outlined in it.
|
||||
pub(super) fn selection_color(app: &DesignApp) -> Color32 {
|
||||
let fallback = Color32::from_rgb(79, 153, 255);
|
||||
|
||||
@@ -137,6 +137,66 @@ fn bleed_and_slug_are_preview_modes_that_show_their_area() {
|
||||
}
|
||||
}
|
||||
|
||||
/// An empty graphic frame's X joins its corners: a rectangle's, and a frame slanted by
|
||||
/// moving its top points, as in the Zero2Graphic tutorial's row of parallelograms.
|
||||
#[test]
|
||||
fn an_empty_frames_x_follows_its_corners() {
|
||||
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
app.run("file.new", json!({})).unwrap();
|
||||
let id = app.run("frame.create", json!({"rect": [100, 100, 200, 160]})).unwrap()["id"].as_u64().unwrap();
|
||||
let x = |app: &DesignApp| {
|
||||
let it = app.session.active().unwrap().doc.item(designcraft_doc::ItemId(id)).unwrap().clone();
|
||||
let m = it.xf;
|
||||
frame_x(&it).map(|(a, b)| {
|
||||
let (a, b) = (m * a, m * b);
|
||||
[(a.x.round(), a.y.round()), (b.x.round(), b.y.round())]
|
||||
})
|
||||
};
|
||||
let mut lines = x(&app).to_vec();
|
||||
lines.iter_mut().for_each(|l| l.sort_by(|a, b| a.partial_cmp(b).unwrap()));
|
||||
lines.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
assert_eq!(lines, vec![[(100.0, 100.0), (200.0, 160.0)], [(100.0, 160.0), (200.0, 100.0)]], "corner to corner");
|
||||
// Slant it: the two top points move 30 pt right.
|
||||
let it = app.session.active().unwrap().doc.item(designcraft_doc::ItemId(id)).unwrap().clone();
|
||||
let top: Vec<Value> =
|
||||
it.path.subpaths[0].anchors.iter().enumerate().filter(|(_, a)| (it.xf * a.p).y < 130.0).map(|(k, _)| json!([0, k])).collect();
|
||||
assert_eq!(top.len(), 2);
|
||||
app.run("path.moveAnchors", json!({"id": id, "anchors": top, "dx": 30, "dy": 0})).unwrap();
|
||||
let mut lines = x(&app).to_vec();
|
||||
lines.iter_mut().for_each(|l| l.sort_by(|a, b| a.partial_cmp(b).unwrap()));
|
||||
lines.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
assert_eq!(lines, vec![[(100.0, 160.0), (230.0, 100.0)], [(130.0, 100.0), (200.0, 160.0)]], "the slanted corners");
|
||||
// An ellipse frame (four smooth anchors) keeps the X across its bounds, not a plus.
|
||||
let e = app.run("frame.create", json!({"rect": [300, 100, 400, 160], "shape": "ellipse"})).unwrap()["id"].as_u64().unwrap();
|
||||
let it = app.session.active().unwrap().doc.item(designcraft_doc::ItemId(e)).unwrap().clone();
|
||||
let mut lines: Vec<[(f64, f64); 2]> = frame_x(&it)
|
||||
.map(|(a, b)| {
|
||||
let (a, b) = (it.xf * a, it.xf * b);
|
||||
[(a.x.round(), a.y.round()), (b.x.round(), b.y.round())]
|
||||
})
|
||||
.to_vec();
|
||||
lines.iter_mut().for_each(|l| l.sort_by(|a, b| a.partial_cmp(b).unwrap()));
|
||||
lines.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
assert_eq!(lines, vec![[(300.0, 100.0), (400.0, 160.0)], [(300.0, 160.0), (400.0, 100.0)]], "the ellipse's bounds");
|
||||
// So does a four-sided polygon frame (a diamond): its corners would cross as a plus.
|
||||
let mut diamond = it.clone();
|
||||
diamond.shape = designcraft_doc::Shape::Polygon;
|
||||
for a in &mut diamond.path.subpaths[0].anchors {
|
||||
(a.h_in, a.h_out) = (a.p, a.p);
|
||||
}
|
||||
let lines: Vec<_> = frame_x(&diamond).to_vec();
|
||||
assert!(lines.iter().all(|(a, b)| (a.x - b.x).abs() > 1e-6 && (a.y - b.y).abs() > 1e-6), "diagonals, not a plus: {lines:?}");
|
||||
// Still after one of its points moves (which makes it a path).
|
||||
let p = app.run("frame.create", json!({"rect": [300, 200, 400, 260], "shape": "polygon", "sides": 4, "starInset": 0})).unwrap()["id"]
|
||||
.as_u64()
|
||||
.unwrap();
|
||||
app.run("path.moveAnchors", json!({"id": p, "anchors": [[0, 0]], "dx": 5, "dy": 0})).unwrap();
|
||||
let it = app.session.active().unwrap().doc.item(designcraft_doc::ItemId(p)).unwrap().clone();
|
||||
assert_eq!(it.shape, designcraft_doc::Shape::Path);
|
||||
let lines: Vec<_> = frame_x(&it).to_vec();
|
||||
assert!(lines.iter().all(|(a, b)| (a.x - b.x).abs() > 1e-6 && (a.y - b.y).abs() > 1e-6), "an edited diamond: {lines:?}");
|
||||
}
|
||||
|
||||
/// Each preview mode's shown area: Preview stops at the trim, Bleed reaches the bleed box and
|
||||
/// Slug the slug box. A slug narrower than the bleed never pulls the edge back in.
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user