mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
M3.88: Show Transparency Grid is per-document engine view state
view.transparencyGrid {on?} -> {on} is now an engine command (DocState.
transparency_grid; not undoable, doesn't dirty the document), so agents can
turn the grid on headless and each open document keeps its own setting. The
menu item, its Show/Hide label, the shortcut and the Properties panel button
use the same id; the canvas draws the grid for the active document only.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
1603729ec3
commit
799b507564
10 files changed
+103
-8
No files matched your search
@@ -1,4 +1,5 @@
|
||||
//! View → New View… / Edit Views…: saved views stored in the document.
|
||||
//! View → New View… / Edit Views…: saved views stored in the document; per-document view
|
||||
//! toggles (Show Transparency Grid).
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::SavedView;
|
||||
@@ -30,9 +31,24 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
edit_views
|
||||
),
|
||||
cmd!(query "view.saved.list", "Saved Views", [], None, "{} → {views: [{name, center, zoom, rotation}]}", has_doc, list_views),
|
||||
cmd!(
|
||||
query "view.transparencyGrid",
|
||||
"Show Transparency Grid",
|
||||
["View"],
|
||||
Some("Cmd+Shift+D"),
|
||||
"{on?: bool (default: toggle)} show the transparency grid behind the active document's artboards (each document has its own setting) → {on}",
|
||||
has_doc,
|
||||
transparency_grid
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn transparency_grid(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let st = s.doc_mut()?;
|
||||
st.transparency_grid = p.get("on").and_then(Value::as_bool).unwrap_or(!st.transparency_grid);
|
||||
Ok(json!({ "on": st.transparency_grid }))
|
||||
}
|
||||
|
||||
fn list_views(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
Ok(json!({ "views": s.doc()?.doc.views }))
|
||||
}
|
||||
|
||||
@@ -104,6 +104,8 @@ pub struct DocState {
|
||||
/// View Opacity Mask (Alt-click the mask thumbnail): the masked object whose mask the canvas
|
||||
/// shows alone, in greyscale, while that mask is edited (see [`DocState::shown_mask`]).
|
||||
pub mask_view: Option<NodeId>,
|
||||
/// View → Show Transparency Grid, per document (view state: not saved, not undoable).
|
||||
pub transparency_grid: bool,
|
||||
}
|
||||
|
||||
static NEXT_DOC_UID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
|
||||
@@ -126,6 +128,7 @@ impl DocState {
|
||||
last_selection_cmd: None,
|
||||
uid: NEXT_DOC_UID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
|
||||
mask_view: None,
|
||||
transparency_grid: false,
|
||||
}
|
||||
}
|
||||
/// Unsaved changes: the document differs from the saved one (selection changes don't count).
|
||||
@@ -884,6 +887,8 @@ mod tests_tileedge;
|
||||
#[cfg(test)]
|
||||
mod tests_tints;
|
||||
#[cfg(test)]
|
||||
mod tests_transparencygrid;
|
||||
#[cfg(test)]
|
||||
mod tests_widthprofiles;
|
||||
#[cfg(test)]
|
||||
mod tests_xform;
|
||||
@@ -0,0 +1,28 @@
|
||||
//! View → Show Transparency Grid is engine view state of each open document (M3.88).
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn transparency_grid_is_per_document_view_state() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({})).unwrap();
|
||||
let grid = |s: &Session| s.doc().unwrap().transparency_grid;
|
||||
assert!(!grid(&s));
|
||||
assert_eq!(s.execute("view.transparencyGrid", &json!({})).unwrap(), json!({"on": true}));
|
||||
assert!(grid(&s));
|
||||
// Not an edit: nothing to undo, the document stays clean, the journal skips it.
|
||||
let st = s.doc().unwrap();
|
||||
assert!(st.history.undo.is_empty() && !st.is_dirty());
|
||||
// Another document has its own setting.
|
||||
s.execute("file.new", &json!({})).unwrap();
|
||||
assert!(!grid(&s));
|
||||
assert_eq!(s.execute("view.transparencyGrid", &json!({"on": false})).unwrap(), json!({"on": false}));
|
||||
s.execute("document.activate", &json!({"index": 0})).unwrap();
|
||||
assert!(grid(&s));
|
||||
assert_eq!(s.execute("view.transparencyGrid", &json!({})).unwrap(), json!({"on": false}));
|
||||
assert_eq!(s.execute("view.transparencyGrid", &json!({"on": true})).unwrap(), json!({"on": true}));
|
||||
s.execute("document.activate", &json!({"index": 1})).unwrap();
|
||||
assert!(!grid(&s));
|
||||
}
|
||||
@@ -157,7 +157,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let shift = |d: f32| q.iter().map(|p| *p + vec2(d, d)).collect::<Vec<_>>();
|
||||
painter.add(Shape::convex_polygon(shift(2.0), Color32::from_black_alpha(26), Stroke::NONE));
|
||||
painter.add(Shape::convex_polygon(shift(1.0), Color32::from_black_alpha(52), Stroke::NONE));
|
||||
if app.ui.view.transparency_grid && xf.rot == 0.0 {
|
||||
if st.transparency_grid && xf.rot == 0.0 {
|
||||
checker(&painter, r);
|
||||
} else {
|
||||
painter.add(Shape::convex_polygon(q, Color32::WHITE, Stroke::NONE));
|
||||
|
||||
@@ -38,6 +38,8 @@ mod tests_labels;
|
||||
mod tests_overprint;
|
||||
#[cfg(test)]
|
||||
mod tests_paintchips;
|
||||
#[cfg(test)]
|
||||
mod tests_transparencygrid;
|
||||
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -125,7 +125,6 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
("view.artboards", "Hide Artboards", "Cmd+Shift+H", "{}"),
|
||||
("view.rulers", "Show Rulers", "Cmd+R", "{}"),
|
||||
("view.boundingBox", "Hide Bounding Box", "Cmd+Shift+B", "{}"),
|
||||
("view.transparencyGrid", "Show Transparency Grid", "Cmd+Shift+D", "{}"),
|
||||
("view.guides", "Hide Guides", "Cmd+;", "{}"),
|
||||
("view.smartGuides", "Smart Guides", "Cmd+U", "{}"),
|
||||
("view.grid", "Show Grid", "Cmd+'", "{}"),
|
||||
@@ -470,7 +469,6 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
|
||||
"view.artboards" => flag(&mut app.ui.view.artboards),
|
||||
"view.rulers" => flag(&mut app.ui.view.rulers),
|
||||
"view.boundingBox" => flag(&mut app.ui.view.bounding_box),
|
||||
"view.transparencyGrid" => flag(&mut app.ui.view.transparency_grid),
|
||||
"view.guides" => flag(&mut app.ui.view.guides),
|
||||
"view.smartGuides" => flag(&mut app.ui.view.smart_guides),
|
||||
"view.grid" => flag(&mut app.ui.view.grid),
|
||||
@@ -756,7 +754,9 @@ pub fn dynamic_label(app: &VectorcraftApp, id: &str, label: &str) -> String {
|
||||
"view.artboards" => if v.artboards { "Hide Artboards" } else { "Show Artboards" }.into(),
|
||||
"view.rulers" => if v.rulers { "Hide Rulers" } else { "Show Rulers" }.into(),
|
||||
"view.boundingBox" => if v.bounding_box { "Hide Bounding Box" } else { "Show Bounding Box" }.into(),
|
||||
"view.transparencyGrid" => if v.transparency_grid { "Hide Transparency Grid" } else { "Show Transparency Grid" }.into(),
|
||||
"view.transparencyGrid" => {
|
||||
if app.session.active().is_some_and(|d| d.transparency_grid) { "Hide Transparency Grid" } else { "Show Transparency Grid" }.into()
|
||||
}
|
||||
"view.guides" => if v.guides { "Hide Guides" } else { "Show Guides" }.into(),
|
||||
"view.grid" => if v.grid { "Hide Grid" } else { "Show Grid" }.into(),
|
||||
"view.guides.lock" => if app.session.guides_locked() { "Unlock Guides" } else { "Lock Guides" }.into(),
|
||||
|
||||
@@ -154,7 +154,8 @@ fn document_sections(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
if widgets::icon_button(ui, "grid-3x3", "Show Grid (⌘')", v.grid, 26.0).clicked() {
|
||||
app.run("view.grid", json!({})).ok();
|
||||
}
|
||||
if widgets::icon_button(ui, "square-dashed", "Show Transparency Grid", v.transparency_grid, 26.0).clicked() {
|
||||
let transparency_grid = app.session.active().is_some_and(|d| d.transparency_grid);
|
||||
if widgets::icon_button(ui, "square-dashed", "Show Transparency Grid", transparency_grid, 26.0).clicked() {
|
||||
app.run("view.transparencyGrid", json!({})).ok();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -115,7 +115,6 @@ pub struct ViewFlags {
|
||||
pub snap_to_grid: bool,
|
||||
pub snap_to_point: bool,
|
||||
pub artboards: bool,
|
||||
pub transparency_grid: bool,
|
||||
pub edges: bool,
|
||||
/// View → Trim View: hide everything outside the artboards.
|
||||
pub trim_view: bool,
|
||||
@@ -144,7 +143,6 @@ impl Default for ViewFlags {
|
||||
snap_to_grid: false,
|
||||
snap_to_point: true,
|
||||
artboards: true,
|
||||
transparency_grid: false,
|
||||
edges: true,
|
||||
trim_view: false,
|
||||
corner_widgets: true,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
//! The canvas draws the transparency grid behind the artboards of the documents that show it
|
||||
//! (M3.88), and the View menu item follows the active document.
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_engine::Session;
|
||||
|
||||
use crate::{VectorcraftApp, canvas, menus};
|
||||
|
||||
/// Whether one headless canvas frame paints the grid's grey squares.
|
||||
fn draws_grid(app: &mut VectorcraftApp) -> bool {
|
||||
fn grey(s: &egui::Shape) -> bool {
|
||||
match s {
|
||||
egui::Shape::Rect(r) => r.fill == egui::Color32::from_gray(204),
|
||||
egui::Shape::Vec(v) => v.iter().any(grey),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
let ctx = egui::Context::default();
|
||||
let raw = egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(800.0, 600.0))), ..Default::default() };
|
||||
let mut out = ctx.run_ui(raw, |ui| canvas::show(app, ui));
|
||||
out.textures_delta.clear();
|
||||
out.shapes.iter().any(|c| grey(&c.shape))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_canvas_shows_the_grid_only_for_documents_that_turn_it_on() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 200, "height": 200})).unwrap();
|
||||
app.run("file.new", json!({"width": 200, "height": 200})).unwrap();
|
||||
assert!(!draws_grid(&mut app));
|
||||
assert_eq!(menus::dynamic_label(&app, "view.transparencyGrid", ""), "Show Transparency Grid");
|
||||
// The menu's id reaches the engine command.
|
||||
assert_eq!(app.run("view.transparencyGrid", json!({})).unwrap(), json!({"on": true}));
|
||||
assert!(draws_grid(&mut app));
|
||||
assert_eq!(menus::dynamic_label(&app, "view.transparencyGrid", ""), "Hide Transparency Grid");
|
||||
app.run("document.activate", json!({"index": 0})).unwrap();
|
||||
assert!(!draws_grid(&mut app), "the other document keeps its own setting");
|
||||
assert_eq!(menus::dynamic_label(&app, "view.transparencyGrid", ""), "Show Transparency Grid");
|
||||
app.run("document.activate", json!({"index": 1})).unwrap();
|
||||
assert!(draws_grid(&mut app));
|
||||
}
|
||||
@@ -230,6 +230,10 @@ then composite against the art below it). SVG has no non-isolated groups, so the
|
||||
mask is edited; called again it shows the artwork, still editing. It is view state of the open document: the canvas
|
||||
(and `ui.screenshot`) shows it, exports never do, and it ends when mask editing ends.
|
||||
|
||||
`view.transparencyGrid {on?}` (View → Show Transparency Grid; default: toggle) is view state of each open document,
|
||||
like the mask view: the grid shows behind the artboards of the documents that turn it on, on the canvas and in
|
||||
`ui.screenshot`, never in exports. It returns `{on}`, isn't undoable and doesn't mark the document changed.
|
||||
|
||||
## Clipping masks
|
||||
|
||||
`object.clippingMask.make` clips the selected objects by the topmost one, which may be a path, a compound path or a
|
||||
|
||||
Reference in new issue
Block a user