mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-28 04:06:54 -06:00
fix(paths): path edits refuse a path on a locked layer or in a locked group (#735)
This commit is contained in:
@@ -5,6 +5,7 @@ use designcraft_doc::{Document, ItemId, Shape};
|
||||
use designcraft_geom::{Affine, Anchor, AnchorKind, Point};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::object::path_editable;
|
||||
use super::{CommandSpec, bad, cmd, has_doc, point_param};
|
||||
use crate::{Result, Session};
|
||||
|
||||
@@ -69,6 +70,7 @@ fn erase_or_smooth(s: &mut Session, p: &Value, smooth: bool) -> Result<Value> {
|
||||
.map(|a| a.iter().filter_map(|v| Some(Point::new(v.get(0)?.as_f64()?, v.get(1)?.as_f64()?))).collect())
|
||||
.unwrap_or_default();
|
||||
s.edit(|d, _| {
|
||||
path_editable(d, id, cmd)?;
|
||||
let inv = to_inner(d, id).ok_or(designcraft_doc::DocError::NoItem(id))?;
|
||||
let scale = inv.determinant().abs().sqrt().max(1e-9);
|
||||
let it = d.item_mut(id).ok_or(designcraft_doc::DocError::NoItem(id))?;
|
||||
@@ -135,6 +137,7 @@ fn add_anchor(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let at = point_param(p, "at").ok_or_else(|| bad("path.addAnchor", "missing at"))?;
|
||||
let tol = p.get("tolerance").and_then(Value::as_f64).unwrap_or(4.0);
|
||||
s.edit(|d, _| {
|
||||
path_editable(d, id, "path.addAnchor")?;
|
||||
let inv = to_inner(d, id).ok_or(designcraft_doc::DocError::NoItem(id))?;
|
||||
let it = d.item_mut(id).ok_or(designcraft_doc::DocError::NoItem(id))?;
|
||||
let q = inv * at;
|
||||
@@ -154,6 +157,7 @@ fn add_anchor(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
fn delete_anchor(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let (id, si, ai) = anchor_param(p, "path.deleteAnchor")?;
|
||||
s.edit(|d, sel| {
|
||||
path_editable(d, id, "path.deleteAnchor")?;
|
||||
let it = d.item_mut(id).ok_or(designcraft_doc::DocError::NoItem(id))?;
|
||||
let sp = it.path.subpaths.get_mut(si).ok_or_else(|| bad("path.deleteAnchor", "no such subpath"))?;
|
||||
if ai >= sp.anchors.len() {
|
||||
@@ -181,6 +185,7 @@ fn convert_anchor(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let to = point_param(p, "to");
|
||||
let corner = p.get("corner").and_then(Value::as_bool);
|
||||
s.edit(|d, _| {
|
||||
path_editable(d, id, "path.convertAnchor")?;
|
||||
let inv = to_inner(d, id).ok_or(designcraft_doc::DocError::NoItem(id))?;
|
||||
let it = d.item_mut(id).ok_or(designcraft_doc::DocError::NoItem(id))?;
|
||||
let sp = it.path.subpaths.get_mut(si).ok_or_else(|| bad("path.convertAnchor", "no such subpath"))?;
|
||||
|
||||
@@ -1489,6 +1489,20 @@ fn unlocked(d: &Document, it: &Item) -> bool {
|
||||
!it.locked && d.layer(it.layer).is_none_or(|l| !l.locked)
|
||||
}
|
||||
|
||||
/// Refuses a path edit on `id` when it, a group it sits in, or its layer is locked.
|
||||
pub(super) fn path_editable(d: &Document, id: ItemId, cmd: &str) -> Result<()> {
|
||||
let loc = d.find(id).ok_or(designcraft_doc::DocError::NoItem(id))?;
|
||||
let mut items = d.spread(loc.spread).map(|sp| &sp.items[..]).unwrap_or_default();
|
||||
for &i in &loc.path {
|
||||
let Some(it) = items.get(i) else { break };
|
||||
if !unlocked(d, it) {
|
||||
return Err(bad(cmd, "the path is locked"));
|
||||
}
|
||||
items = it.children();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn has_editable_selection(s: &Session) -> std::result::Result<(), String> {
|
||||
has_selection(s)?;
|
||||
let st = s.active().ok_or_else(|| super::NOTHING_SELECTED.to_string())?;
|
||||
@@ -2075,6 +2089,7 @@ fn path_append(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let a = p.get("anchor").and_then(anchor_from).ok_or_else(|| bad("path.appendAnchor", "missing anchor"))?;
|
||||
let subpath = p.get("subpath").and_then(Value::as_u64);
|
||||
s.edit(|d, _| {
|
||||
path_editable(d, id, "path.appendAnchor")?;
|
||||
let loc = d.find(id).ok_or(designcraft_doc::DocError::NoItem(id))?;
|
||||
let inv = (d.parent_xf(&loc) * d.item_at(&loc).map(|i| i.xf).unwrap_or_default()).inverse();
|
||||
let it = d.item_mut(id).ok_or(designcraft_doc::DocError::NoItem(id))?;
|
||||
@@ -2110,12 +2125,10 @@ fn path_set_anchor(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let subpath = p.get("subpath").and_then(Value::as_u64);
|
||||
let which = p.get("anchor").cloned().unwrap_or(Value::Null);
|
||||
s.edit(|d, _| {
|
||||
path_editable(d, id, ID)?;
|
||||
let loc = d.find(id).ok_or(designcraft_doc::DocError::NoItem(id))?;
|
||||
let inv = (d.parent_xf(&loc) * d.item_at(&loc).map(|i| i.xf).unwrap_or_default()).inverse();
|
||||
let it = d.item_mut(id).ok_or(designcraft_doc::DocError::NoItem(id))?;
|
||||
if it.locked {
|
||||
return Err(bad(ID, "the path is locked"));
|
||||
}
|
||||
let sp = match subpath {
|
||||
Some(i) => usize::try_from(i).ok().and_then(|i| it.path.subpaths.get_mut(i)),
|
||||
None => it.path.subpaths.last_mut(),
|
||||
@@ -2157,12 +2170,19 @@ fn path_reverse(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
{
|
||||
return Err(bad("path.reverse", "no such subpath"));
|
||||
}
|
||||
let ids = if only.is_some() { ids.into_iter().take(1).collect() } else { ids };
|
||||
let ids: Vec<ItemId> = if only.is_some() { ids.into_iter().take(1).collect() } else { ids };
|
||||
let doc = &s.doc()?.doc;
|
||||
let editable: Vec<ItemId> = ids.iter().copied().filter(|id| path_editable(doc, *id, "path.reverse").is_ok()).collect();
|
||||
if editable.is_empty()
|
||||
&& let Some(id) = ids.first()
|
||||
{
|
||||
path_editable(doc, *id, "path.reverse")?;
|
||||
}
|
||||
s.edit(|d, _| {
|
||||
let mut reversed = 0;
|
||||
for id in &ids {
|
||||
for id in &editable {
|
||||
let Some(it) = d.item_mut(*id) else { continue };
|
||||
if it.locked || it.is_group() {
|
||||
if it.is_group() {
|
||||
continue;
|
||||
}
|
||||
for (i, sp) in it.path.subpaths.iter_mut().enumerate() {
|
||||
@@ -2184,6 +2204,7 @@ fn path_close(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let id = super::id_param(p, "id").ok_or_else(|| bad("path.close", "missing id"))?;
|
||||
let only = p.get("subpath").and_then(Value::as_u64).map(|i| usize::try_from(i).unwrap_or(usize::MAX));
|
||||
s.edit(|d, _| {
|
||||
path_editable(d, id, "path.close")?;
|
||||
let it = d.item_mut(id).ok_or(designcraft_doc::DocError::NoItem(id))?;
|
||||
if only.is_some_and(|i| i >= it.path.subpaths.len()) {
|
||||
return Err(bad("path.close", "no such subpath"));
|
||||
@@ -2206,6 +2227,7 @@ fn path_move_anchors(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let d0 = Vec2::new(f64_or(p, "dx", 0.0), f64_or(p, "dy", 0.0));
|
||||
let handle = str_param(p, "handle").map(str::to_string);
|
||||
s.edit(|d, _| {
|
||||
path_editable(d, id, "path.moveAnchors")?;
|
||||
let loc = d.find(id).ok_or(designcraft_doc::DocError::NoItem(id))?;
|
||||
let m = d.parent_xf(&loc) * d.item_at(&loc).map(|i| i.xf).unwrap_or_default();
|
||||
// Spread-space delta → inner-space delta (linear part only).
|
||||
|
||||
@@ -312,6 +312,7 @@ fn split_path(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let id = super::id_param(p, "id").ok_or_else(|| bad("path.split", "missing id"))?;
|
||||
let at = point_param(p, "at").ok_or_else(|| bad("path.split", "missing at"))?;
|
||||
s.edit(|d, sel| {
|
||||
super::object::path_editable(d, id, "path.split")?;
|
||||
let (sr, xf) = spread_xf(d, id).ok_or(designcraft_doc::DocError::NoItem(id))?;
|
||||
let it = d.item(id).cloned().ok_or(designcraft_doc::DocError::NoItem(id))?;
|
||||
if !matches!(it.content, Content::Unassigned) {
|
||||
|
||||
Reference in New Issue
Block a user