mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
Fix #321: the perspective grid hides while a perspective tool is chosen
The grid was drawn whenever its `visible` flag was set or a perspective tool was active, and choosing the Perspective Grid tool (Shift+P) didn't set the flag. So with the tool chosen, View > Perspective Grid > Hide Grid (perspective.grid.show) turned the hidden flag on instead of off and the grid stayed on screen however it was toggled. Choosing a perspective tool now shows the grid (sets `visible`, view state, no undo step), and the grid, its widget and the Perspective Grid tool's handles are drawn only while it is visible. Hide Grid hides it with the tool still chosen; choosing the tool again shows it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
f17c34a067
commit
4a676c4d5e
7 files changed
+48
-21
No files matched your search
+1
-1
@@ -126,7 +126,7 @@ grade by behaviour against `plan/illustrator/`, not by whether a menu item exist
|
||||
- **Type:** Text Wrap, Type on a Path effects (Rainbow/Skew/3D Ribbon/Stair Step/Gravity), Character and Paragraph Styles (override-preserving redefine), Area Type Options (rows/columns/inset/first baseline), threaded text across any closed shapes, Fit Headline. Area type resizes like Illustrator's type areas: a bounding-box handle drag (Selection tool) or Area Type Options Width/Height size the frame and the text reflows at its size (threads too); Direct Selection drags a frame corner or edge (`text.reshapeArea`). Point type still scales. Variable fonts list every named instance as a style of their family (Figtree Light to Black and their italics, Bahnschrift, Segoe UI Variable), and the instance chosen is shaped, drawn, outlined and measured at its axis settings; PDF and SVG embed it as that instance. Variation axis sliders are not done.
|
||||
- **Interface languages, vertical type:** VectorCraft ▸ Language (or Preferences ▸ User Interface) picks Automatic (the system locale) or a registered language, persisted as `interfaceLanguage`. Translations come from per-language catalogs (`i18n/*.tsv`): Traditional Chinese covers the whole interface, Czech and Japanese every menu label (tested); untranslated text stays English. Japanese and Chinese glyphs come from the craft-fonts build input (release builds) or the installed fonts, Czech ones from the bundled UI fonts. Vertical Type, Vertical Area Type and Vertical Type on a Path create vertical text (columns right to left, upright CJK glyphs and marks with `vert` alternates, Latin and longer numbers on their side, two- and three-digit numbers set as tate-chu-yoko in one em of the column (kept whole when justified), upright glyphs centred on the column (tracked or justified too), point type's anchor on the first column's centre line); kinsoku (the strict set) applies to horizontal and vertical Japanese; Type ▸ Type Orientation switches existing text; caret, selection, hit testing and arrow keys follow the writing direction. Vertical text exports to PDF as real text and to SVG as outlines.
|
||||
- **Transparency:** opacity masks (clip/invert/disable/link), exported as SVG `<mask>` and PDF soft masks.
|
||||
- **Advanced art:** live Blends (steps/distance/smooth colour, editable spine, anchor-targeted Blend tool, faithful interpolation, knockout), Envelope Distort (warp/mesh/top object, Reset, full Envelope Options, mesh handles, Edit Contents; type, images, symbols, gradients, patterns and appearance distorted everywhere), Liquify tools with options and pen pressure, Puppet Warp with rotating pins, Perspective Grid with presets, movable planes and projected type, Gradient Mesh, Shape Builder, Live Paint (every path an edge, open lines included), Image Trace (12 presets), pattern swatches with pattern editing mode, live Repeat (radial/grid/mirror).
|
||||
- **Advanced art:** live Blends (steps/distance/smooth colour, editable spine, anchor-targeted Blend tool, faithful interpolation, knockout), Envelope Distort (warp/mesh/top object, Reset, full Envelope Options, mesh handles, Edit Contents; type, images, symbols, gradients, patterns and appearance distorted everywhere), Liquify tools with options and pen pressure, Puppet Warp with rotating pins, Perspective Grid with presets, movable planes and projected type (choosing a perspective tool shows the grid; Hide Grid hides it with the tool still chosen), Gradient Mesh, Shape Builder, Live Paint (every path an edge, open lines included), Image Trace (12 presets), pattern swatches with pattern editing mode, live Repeat (radial/grid/mirror).
|
||||
- **Paint and appearance (M3):**
|
||||
- **Swatches:** names follow the colour model and are unique; Swatch Options (process/spot, Global, Gray/RGB/HSB/CMYK/Web, live preview); editing a global or spot swatch recolours every linked fill, stroke and text run in one undo step; multi-select and delete with confirmation; New Swatch and New Color Group dialogs (a group from the selected artwork's colours); swatches in colour groups convert, separate and count like any other. The Swatches panel selects ranges and whole groups, has a find field, and drags swatches to reorder, regroup or paint art; Add Used Colors, Select All Unused, Merge, Ungroup and Sort by Kind; a CMYK document keeps applied colours in CMYK. Swatch libraries: nine generated colour libraries and five gradient libraries in a library panel, saved and loaded as `.vcswatches`, `.gpl` or CSS. Tints of global and spot colours ("Name 40%") stay linked and separate as a percentage of their plate. Spot colours can be defined in Lab (Spot Colors options). A built-in [Registration] swatch prints on every plate.
|
||||
- **Colour:** a Color Picker (colour field, H/S/B/R/G/B channel slider, HSB/RGB/Lab/CMYK fields, hex, web-only snapping, out-of-gamut correction); the Color panel follows each colour's own model and Alt-click paints the other proxy; proxies look through groups and show "?" for mixed paint; Invert, Complement, Apply Last Color (`,`) and Apply Last Gradient (`.`) keep the colour model; every paint command feeds the recent colours. The Color Guide has 21 harmony rules, Steps and Variation options and Limit to Library. Adjust Color Balance (its Global mode shifts tints) and Saturate preview live; Edit Colors and Recolor also reach meshes, images and patterns. Recolor Artwork has colour reduction, five recolour methods, presets, Limit to Library, a colour-wheel Edit tab and Edit Color Group; a local Color Themes panel saves five-colour themes. Fill/Stroke chips open swatch and mixer popovers, panels have their shortcuts, and Eyedropper Options pick up and apply chosen attributes (Alt and Shift+Alt, image pixels). Fills and strokes can overprint (Attributes panel, Overprint Black options), and CMYK documents blend in inks on screen, in PDF and when flattening.
|
||||
|
||||
@@ -452,7 +452,7 @@ impl Session {
|
||||
let w = self.prefs.perspective_widget;
|
||||
let st = self.active().filter(|_| w.show)?;
|
||||
let g = PerspectiveGrid::current(&st.doc);
|
||||
if !g.shown(self.tool_id()) {
|
||||
if !g.visible {
|
||||
return None;
|
||||
}
|
||||
let place = WidgetPlace { screen: view.screen.as_ref(), corner: w.position };
|
||||
@@ -476,6 +476,6 @@ impl Session {
|
||||
ToolKey::Digit(4) => Plane::None,
|
||||
_ => return None,
|
||||
};
|
||||
PerspectiveGrid::current(&self.active()?.doc).shown(self.tool_id()).then_some(plane)
|
||||
PerspectiveGrid::current(&self.active()?.doc).visible.then_some(plane)
|
||||
}
|
||||
}
|
||||
@@ -247,6 +247,26 @@ fn grid_show_and_plane_are_view_state() {
|
||||
assert!(s.overlays(ViewInfo::default()).len() > 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_grid_hides_while_a_perspective_tool_is_chosen() {
|
||||
// #321: choosing the Perspective Grid tool showed the grid for good.
|
||||
let mut s = session();
|
||||
let v = ViewInfo::default();
|
||||
for tool in ["perspectiveGrid", "perspectiveSelection"] {
|
||||
s.select_tool(tool, v).unwrap();
|
||||
assert!(s.overlays(v).len() > 20, "{tool}");
|
||||
let n = undo_len(&s);
|
||||
assert_eq!(s.execute("perspective.grid.show", &json!({})).unwrap(), json!({"visible": false}));
|
||||
assert!(s.overlays(v).is_empty(), "{tool}: the hidden grid still draws");
|
||||
assert_eq!(undo_len(&s), n);
|
||||
// Choosing the tool again shows it again.
|
||||
s.select_tool(tool, v).unwrap();
|
||||
assert!(s.overlays(v).len() > 20, "{tool}");
|
||||
s.execute("perspective.grid.show", &json!({"visible": false})).unwrap();
|
||||
s.select_tool("selection", v).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grid_preset_and_set_are_undoable() {
|
||||
let mut s = session();
|
||||
|
||||
@@ -401,7 +401,7 @@ fn the_plane_widget_stays_on_screen_and_takes_clicks_with_any_tool() {
|
||||
assert_eq!(grid(&s).plane, Plane::Right);
|
||||
assert!(s.doc().unwrap().selection.is_empty());
|
||||
assert_eq!(undo_len(&s), n);
|
||||
// While the grid is hidden only the perspective tools show it (and take its clicks).
|
||||
// A hidden grid takes no clicks; choosing a perspective tool shows it.
|
||||
run(&mut s, "perspective.grid.show", json!({"visible": false}));
|
||||
let q = face(&s, view, WidgetCorner::TopLeft, Plane::Ground);
|
||||
click(&mut s, q, view);
|
||||
|
||||
@@ -58,6 +58,13 @@ impl Session {
|
||||
|
||||
/// Switch tools (finishing any pending tool work first).
|
||||
pub fn select_tool(&mut self, id: &str, view: ViewInfo) -> Result<()> {
|
||||
// Choosing a perspective tool shows the document's perspective grid; Hide Grid hides it
|
||||
// again, the tool staying chosen.
|
||||
if matches!(id, "perspectiveGrid" | "perspectiveSelection")
|
||||
&& self.active().is_some_and(|d| !vectorcraft_tools::distort::perspective::PerspectiveGrid::current(&d.doc).visible)
|
||||
{
|
||||
crate::cmd::distortcmds::silent(self, |g| g.visible = true)?;
|
||||
}
|
||||
if self.tool.id() == id {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -247,7 +254,7 @@ impl Session {
|
||||
if let Some(d) = self.active() {
|
||||
let w = self.prefs.perspective_widget;
|
||||
let place = w.show.then_some(WidgetPlace { screen: view.screen.as_ref(), corner: w.position });
|
||||
v.splice(0..0, vectorcraft_tools::distort::perspective::grid_overlays_in(&d.doc, 1.0 / view.zoom.max(1e-9), self.tool.id(), place));
|
||||
v.splice(0..0, vectorcraft_tools::distort::perspective::grid_overlays_in(&d.doc, 1.0 / view.zoom.max(1e-9), place));
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
@@ -480,11 +480,6 @@ impl PerspectiveGrid {
|
||||
self.extent_right.unwrap_or(self.extent)
|
||||
}
|
||||
|
||||
/// Is the grid drawn (shown, or a perspective tool active)?
|
||||
pub fn shown(&self, tool_id: &str) -> bool {
|
||||
self.visible || matches!(tool_id, "perspectiveGrid" | "perspectiveSelection")
|
||||
}
|
||||
|
||||
/// Page → plane coordinates.
|
||||
pub fn to_plane(&self, plane: Plane, p: Point) -> Option<Point> {
|
||||
self.homography(plane)?.inverse()?.apply(p)
|
||||
@@ -761,16 +756,16 @@ fn in_quad(q: &[Point; 4], p: Point) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Grid overlays (lines, horizon, vanishing points, widget) drawn whenever the grid is visible or
|
||||
/// a perspective tool is active. `tol` = document units per screen pixel.
|
||||
pub fn grid_overlays(doc: &Document, tol: f64, tool_id: &str) -> Vec<Overlay> {
|
||||
grid_overlays_in(doc, tol, tool_id, Some(widget::WidgetPlace::default()))
|
||||
/// Grid overlays (lines, horizon, vanishing points, widget) drawn whenever the grid is visible
|
||||
/// (selecting a perspective tool shows it). `tol` = document units per screen pixel.
|
||||
pub fn grid_overlays(doc: &Document, tol: f64) -> Vec<Overlay> {
|
||||
grid_overlays_in(doc, tol, Some(widget::WidgetPlace::default()))
|
||||
}
|
||||
|
||||
/// [`grid_overlays`] with the Plane Switching Widget at `place` (None: hidden).
|
||||
pub fn grid_overlays_in(doc: &Document, tol: f64, tool_id: &str, place: Option<widget::WidgetPlace>) -> Vec<Overlay> {
|
||||
pub fn grid_overlays_in(doc: &Document, tol: f64, place: Option<widget::WidgetPlace>) -> Vec<Overlay> {
|
||||
let g = PerspectiveGrid::current(doc);
|
||||
if !g.shown(tool_id) {
|
||||
if !g.visible {
|
||||
return vec![];
|
||||
}
|
||||
let mut out = vec![];
|
||||
@@ -1012,6 +1007,10 @@ impl Tool for PerspectiveGridTool {
|
||||
}
|
||||
fn overlays(&self, cx: &ToolContext) -> Vec<Overlay> {
|
||||
let g = PerspectiveGrid::current(cx.doc);
|
||||
// The grid's handles hide with it.
|
||||
if !g.visible {
|
||||
return vec![];
|
||||
}
|
||||
let ink = [0x20, 0x20, 0x20];
|
||||
handles(&g, cx.tol(1.0))
|
||||
.into_iter()
|
||||
@@ -1412,7 +1411,7 @@ mod tests {
|
||||
assert_eq!(cmd, "perspective.grid.set");
|
||||
assert_eq!(v["vpLeft"], json!(g.vp_left - 40.0));
|
||||
assert_eq!(v["horizon"], json!(g.horizon + 10.0));
|
||||
assert!(!grid_overlays(&d, 1.0, "selection").is_empty());
|
||||
assert!(!grid_overlays(&d, 1.0).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+5
-4
@@ -1994,10 +1994,11 @@ keeps one axis), the left and right extents (separate: `extent` and `extentRight
|
||||
whole cells), the vertical extent and the cell size widget (on the line where the planes meet, a cell up, or more
|
||||
cells while they're small) reshape the grid; Lock Grid stops them. The Plane Switching Widget stays put in a corner
|
||||
of the document window (headless: the first artboard's top-left corner); a press on it picks the plane with any tool
|
||||
while the grid shows (a perspective tool shows it), and the keys 1–4 (`key` with `"1"`…`"4"`) pick the left,
|
||||
horizontal, right and no plane. `perspective.widget.options {show?, position?}` (Perspective Grid Options,
|
||||
double-click the tool) hides it or moves it to `topLeft`, `topRight`, `bottomLeft` or `bottomRight`, kept with the
|
||||
preferences (`prefs.get`/`prefs.set` key `perspectiveWidget`).
|
||||
while the grid shows (choosing a perspective tool shows it, and `perspective.grid.show` hides it again with the tool
|
||||
still chosen), and the keys 1–4 (`key` with `"1"`…`"4"`) pick the left, horizontal, right and no plane.
|
||||
`perspective.widget.options {show?, position?}` (Perspective Grid Options, double-click the tool) hides it or moves
|
||||
it to `topLeft`, `topRight`, `bottomLeft` or `bottomRight`, kept with the preferences (`prefs.get`/`prefs.set` key
|
||||
`perspectiveWidget`).
|
||||
|
||||
## Envelopes
|
||||
|
||||
|
||||
Reference in new issue
Block a user