Merge pull request #6367 from dotCooCoo/feat/lock-guides-by-default

Guides: Lock Guides by Default in Preferences locks guides when the app starts
This commit is contained in:
BFlat
2026-11-10 11:23:42 +07:00
committed by GitHub
35 changed files with 252 additions and 17 deletions
+23 -16
View File
@@ -183,7 +183,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Lock Guides",
["View", "Guides"],
Some("Cmd+Alt+;"),
"{locked?: bool} toggle (or set) the session's guide lock; locking deselects the ruler guides and guide paths in every open document → {locked}",
"{locked?: bool} toggle (or set) the session's guide lock, leaving the lockGuidesByDefault preference as it is; locking deselects the ruler guides and guide paths in every open document; guide.add and a guide dragged out of a ruler still make new guides while locked → {locked}",
has_doc,
lock_guides
),
@@ -277,6 +277,27 @@ impl Session {
pub fn guides_locked(&self) -> bool {
self.menu.guides_locked
}
/// Set View → Guides → Lock Guides. Locked guides can't stay selected, ruler guides or guide
/// paths, in any open document ([`Session::select`] lets those go in the active one).
pub(crate) fn set_guides_locked(&mut self, locked: bool) {
self.menu.guides_locked = locked;
if !locked {
return;
}
let guides_selected =
self.doc().is_ok_and(|st| !st.selection.guides.is_empty() || st.selection.objects.iter().any(|id| crate::is_guide_path(&st.doc, *id)));
// It fails only without a document, and then no guide is selected.
if guides_selected {
let _ = self.select(|_, sel| sel.guides.clear());
}
let active = self.active;
for (i, st) in self.docs.iter_mut().enumerate() {
if Some(i) != active && crate::deselect_hidden(&self.menu, &st.doc, &mut st.selection) {
st.revision += 1;
}
}
}
}
// ---------- Select ----------
@@ -717,21 +738,7 @@ fn release_guides(s: &mut Session, p: &Value) -> Result<Value> {
fn lock_guides(s: &mut Session, p: &Value) -> Result<Value> {
let v = switch_or(p, "locked", !s.menu.guides_locked, "view.guides.lock")?;
s.menu.guides_locked = v;
// Locked guides can't stay selected, ruler guides or guide paths, in any open document
// ([`Session::select`] lets those go in the active one).
let st = s.doc()?;
if v && (!st.selection.guides.is_empty() || st.selection.objects.iter().any(|id| crate::is_guide_path(&st.doc, *id))) {
s.select(|_, sel| sel.guides.clear())?;
}
if v {
let active = s.active;
for (i, st) in s.docs.iter_mut().enumerate() {
if Some(i) != active && crate::deselect_hidden(&s.menu, &st.doc, &mut st.selection) {
st.revision += 1;
}
}
}
s.set_guides_locked(v);
Ok(json!({ "locked": v }))
}
+7
View File
@@ -227,6 +227,7 @@ pub const PREF_SPECS: &[PrefSpec] = &[
// Guides & Grid
p!("guideColor", "Guides & Grid", "Guides", "Color", color),
p!("guideStyle", "Guides & Grid", "Guides", "Style", choice(LINE_STYLE)),
p!("lockGuidesByDefault", "Guides & Grid", "Guides", "Lock Guides by Default", bool),
p!("gridColor", "Guides & Grid", "Grid", "Color", color),
p!("gridStyle", "Guides & Grid", "Grid", "Style", choice(LINE_STYLE)),
p!("gridlineEvery", "Guides & Grid", "Grid", "Gridline every", len(1.0, 16383.0, General)),
@@ -690,10 +691,16 @@ impl Session {
let fonts_folder_changed = p.fonts_folder != self.prefs.fonts_folder;
let hyphen_exceptions_changed = p.hyphenation_exceptions != self.prefs.hyphenation_exceptions;
let corner_radius_changed = p.corner_radius != self.prefs.corner_radius;
let lock_guides_changed = p.lock_guides_by_default != self.prefs.lock_guides_by_default;
if p.trace_presets != self.prefs.trace_presets {
self.trace_preset_changes.replaced();
}
self.prefs = p;
// Guides & Grid › Lock Guides by Default sets the lock when it changes, the saved value at
// start included. Otherwise the lock stays as View › Guides › Lock Guides left it.
if lock_guides_changed {
self.set_guides_locked(self.prefs.lock_guides_by_default);
}
if corner_radius_changed {
// General › Corner Radius is the Rounded Rectangle tool's radius (#394): a tool already
// made (active, or lent a selection tool for a drag) takes the new value.
+3
View File
@@ -114,6 +114,8 @@ pub struct Prefs {
// Guides & Grid
pub guide_color: String,
pub guide_style: String,
/// Lock Guides by Default: the app starts with View › Guides › Lock Guides on.
pub lock_guides_by_default: bool,
pub grid_color: String,
pub grid_style: String,
pub gridline_every: f64,
@@ -421,6 +423,7 @@ impl Default for Prefs {
identify_objects_by: s("objectName"),
guide_color: s("#4affff"),
guide_style: s("lines"),
lock_guides_by_default: false,
grid_color: s("#c8c8c8"),
grid_style: s("lines"),
gridline_every: 72.0,
+34
View File
@@ -3687,6 +3687,40 @@ fn ruler_guides_and_lock() {
assert!(s.execute("guide.remove", &json!({"index": 0})).is_err());
}
/// While guides are locked, a guide dragged out of a ruler and `guide.add` still make new guides,
/// each in one undo step and not selected. The ruler guides and guide paths in the document, the
/// new guides included, can't be selected, moved or deleted.
#[test]
fn new_guides_can_be_made_while_guides_are_locked() {
use vectorcraft_tools::{PointerEvent, PointerKind};
let mut s = session();
let path = guide_path(&mut s, 300.0);
s.execute("guide.add", &json!({"vertical": false, "pos": 50})).unwrap();
s.execute("select.none", &json!({})).unwrap();
s.execute("view.guides.lock", &json!({"locked": true})).unwrap();
let v = ViewInfo { smart_guides: false, ..Default::default() };
let n = undo_len(&s);
s.ruler_guide(true, &PointerEvent::new(PointerKind::Drag, 153.0, 30.0), true, v).unwrap();
s.ruler_guide(true, &PointerEvent::new(PointerKind::Up, 153.0, 30.0), true, v).unwrap();
assert_eq!(guides(&s), [(false, 50.0), (true, 153.0)]);
assert_eq!(undo_len(&s), n + 1);
assert_eq!(s.doc().unwrap().history.undo.last().unwrap().label, "New Guide");
assert_eq!(s.execute("guide.add", &json!({"vertical": true, "pos": 200})).unwrap()["index"], 2);
assert_eq!(guides(&s).len(), 3);
assert_eq!(selected_guides(&s), Vec::<usize>::new());
for (id, params) in [
("guide.select", json!({"indexes": [1]})),
("guide.move", json!({"index": 1, "pos": 170})),
("guide.remove", json!({"index": 1})),
("guide.setLayer", json!({"index": 1, "layer": s.doc().unwrap().current_layer().unwrap().0})),
] {
assert!(matches!(s.execute(id, &params), Err(EngineError::Disabled(..))), "{id}");
}
assert_eq!(guides(&s).len(), 3);
sel(&mut s, &[path]);
assert!(selected(&s).is_empty(), "a guide path can't be selected");
}
#[test]
fn invalid_guide_lock_parameters_keep_guides_selected() {
let mut s = session();
+104
View File
@@ -199,6 +199,110 @@ fn reset_category_and_all() {
assert!(s.execute("prefs.reset", &json!({"category": "Bogus"})).is_err());
}
/// A rectangle made into a guide path; nothing is selected after.
fn guide_path(s: &mut Session, x: f64) -> NodeId {
let id = square(s, x, 0.0);
s.execute("select.set", &json!({"ids": [id.0]})).unwrap();
s.execute("view.guides.make", &json!({})).unwrap();
id
}
/// Lock Guides by Default is off, and preferences saved before it existed leave guides unlocked.
#[test]
fn lock_guides_by_default_is_off_and_older_preferences_leave_guides_unlocked() {
assert!(!Prefs::default().lock_guides_by_default);
let older: Prefs = serde_json::from_value(json!({"keyboardIncrement": 4})).unwrap();
assert!(!older.lock_guides_by_default);
let mut s = Session::new();
s.apply_prefs(older);
assert!(!s.guides_locked());
assert!(s.execute("prefs.set", &json!({"key": "lockGuidesByDefault", "value": "yes"})).is_err());
assert!(!s.guides_locked() && !s.prefs.lock_guides_by_default);
}
/// Preferences saved with the option on start a session with guides locked. A document opened in
/// it has its ruler guides and guide paths locked, and new guides can still be made. View › Guides
/// › Lock Guides then unlocks them for the session and leaves the preference on.
#[test]
fn saved_lock_guides_by_default_locks_a_new_session_and_the_documents_it_opens() {
let file = std::env::temp_dir().join(format!("vc-lock-guides-{}.vectorcraft", std::process::id()));
let file = file.to_string_lossy().to_string();
let mut unlocked = new_doc();
assert!(!unlocked.guides_locked(), "a session without the preference");
let path = guide_path(&mut unlocked, 100.0);
unlocked.execute("guide.add", &json!({"vertical": true, "pos": 50})).unwrap();
unlocked.execute("document.save", &json!({"path": file})).unwrap();
// The preference is set with no document open, as a headless session can.
let mut first = Session::new();
first.execute("prefs.set", &json!({"key": "lockGuidesByDefault", "value": true})).unwrap();
let saved = first.prefs.to_json();
assert_eq!(saved["lockGuidesByDefault"], json!(true));
let mut s = Session::new();
s.apply_prefs(serde_json::from_value(saved).unwrap());
assert!(s.guides_locked());
s.execute("document.open", &json!({"path": file})).unwrap();
let _ = std::fs::remove_file(&file);
assert!(s.guides_locked(), "opening a document keeps the lock");
assert!(matches!(s.execute("guide.select", &json!({"indexes": [0]})), Err(EngineError::Disabled(..))));
s.execute("select.set", &json!({"ids": [path.0]})).unwrap();
assert!(s.doc().unwrap().selection.objects.is_empty(), "a guide path can't be selected");
assert_eq!(s.execute("guide.add", &json!({"vertical": false, "pos": 20})).unwrap()["index"], 1);
assert_eq!(s.execute("view.guides.lock", &json!({})).unwrap(), json!({"locked": false}));
assert!(s.prefs.lock_guides_by_default, "the menu leaves the preference as it is");
s.execute("guide.select", &json!({"indexes": [0]})).unwrap();
s.execute("select.set", &json!({"ids": [path.0]})).unwrap();
assert_eq!(s.doc().unwrap().selection.objects, [path]);
}
/// Changing the preference sets the lock at once and deselects guides in every open document.
/// A change to any other preference, or setting it to the value it has, keeps the session's lock.
#[test]
fn changing_lock_guides_by_default_sets_the_lock_and_other_changes_keep_it() {
let mut s = new_doc();
s.execute("guide.add", &json!({"vertical": true, "pos": 50})).unwrap();
s.execute("guide.select", &json!({"indexes": [0]})).unwrap();
s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap();
let path = guide_path(&mut s, 100.0);
s.execute("select.set", &json!({"ids": [path.0]})).unwrap();
let undo = s.doc().unwrap().history.undo.len();
set_pref(&mut s, "lockGuidesByDefault", json!(true));
assert!(s.guides_locked());
assert!(s.doc().unwrap().selection.objects.is_empty(), "the guide path in the active document");
assert_eq!(s.doc().unwrap().history.undo.len(), undo, "no undo step");
s.execute("document.activate", &json!({"index": 0})).unwrap();
assert!(s.doc().unwrap().selection.guides.is_empty(), "the ruler guide in the other document");
s.execute("view.guides.lock", &json!({"locked": false})).unwrap();
set_pref(&mut s, "keyboardIncrement", json!(4));
set_pref(&mut s, "lockGuidesByDefault", json!(true));
assert!(!s.guides_locked(), "the session's unlock stays");
s.execute("view.guides.lock", &json!({"locked": true})).unwrap();
set_pref(&mut s, "lockGuidesByDefault", json!(false));
assert!(!s.guides_locked(), "turning the preference off unlocks guides");
}
/// Reset turns the option off and unlocks guides. With the option already off, a reset keeps a
/// lock set from the View menu.
#[test]
fn resetting_lock_guides_by_default_unlocks_guides_only_when_it_was_on() {
let mut s = new_doc();
for params in [json!({"category": "Guides & Grid"}), json!({})] {
set_pref(&mut s, "lockGuidesByDefault", json!(true));
assert!(s.guides_locked());
s.execute("prefs.reset", &params).unwrap();
assert!(!s.prefs.lock_guides_by_default, "{params}");
assert!(!s.guides_locked(), "{params}");
}
s.execute("view.guides.lock", &json!({"locked": true})).unwrap();
s.execute("prefs.reset", &json!({})).unwrap();
assert!(s.guides_locked());
}
/// `prefs.set` refuses a recovery or plug-ins folder that is missing or is a file. Nothing changes,
/// other values in the same call included, and the document gets no undo step.
#[test]
+1
View File
@@ -1826,6 +1826,7 @@ font-technology Variable Променливи
Lock (⌘2) Заключване (⌘2)
Lock Grid Заключване на мрежата
Lock Guides Заключване на водачите
Lock Guides by Default Заключване на водачите по подразбиране
Lock Others Заключване на другите
Lock Slices Заключване на фрагментите
Lock Station Point Заключване на базовата точка
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -600,6 +600,7 @@
Hide Guides Sakrij vodilice
Show Guides Prikaži vodilice
Lock Guides Zaključaj vodilice
Lock Guides by Default Zaključaj vodilice po zadanom
Unlock Guides Otključaj vodilice
Make Guides Napravi vodilice
Release Guides Oslobodi vodilice
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2339,6 +2339,7 @@ digits Malayalam Malaiàlam
Lock Artboards Bloca els taulers
Lock Grid Bloca la quadrícula
Lock Guides Bloca les guies
Lock Guides by Default Bloca les guies per defecte
Lock Others Bloca la resta
Lock Selected Bloca la selecció
Lock Slices Bloca els talls
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -609,6 +609,7 @@
Hide Guides Skrýt vodítka
Show Guides Zobrazit vodítka
Lock Guides Zamknout vodítka
Lock Guides by Default Ve výchozím stavu zamknout vodítka
Unlock Guides Odemknout vodítka
Make Guides Vytvořit vodítka
Release Guides Uvolnit vodítka
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -559,6 +559,7 @@
Hide Guides Skjul hjælpelinjer
Show Guides Vis hjælpelinjer
Lock Guides Lås hjælpelinjer
Lock Guides by Default Lås hjælpelinjer som standard
Unlock Guides Lås hjælpelinjer op
Make Guides Opret hjælpelinjer
Release Guides Frigør hjælpelinjer
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1893,6 +1893,7 @@ file-filter Templates Vorlagen
Lock (⌘2) Sperren (⌘2)
Lock Grid Raster sperren
Lock Guides Hilfslinien sperren
Lock Guides by Default Hilfslinien standardmäßig sperren
Lock Others Andere sperren
Lock Slices Slices sperren
Lock Station Point Standpunkt sperren
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1725,6 +1725,7 @@ font-technology Variable Μεταβλητές
Location: Θέση:
Lock Κλείδωμα
Lock Guides Κλείδωμα οδηγών
Lock Guides by Default Κλείδωμα οδηγών από προεπιλογή
Lock Slices Κλείδωμα τομών
Low Χαμηλή
Luminosity Φωτεινότητα
Can't render this file because it is too large.
+1
View File
@@ -1896,6 +1896,7 @@ font-technology Variable Variables
Lock (⌘2) Bloquear (⌘2)
Lock Grid Bloquear cuadrícula
Lock Guides Bloquear guías
Lock Guides by Default Bloquear guías de forma predeterminada
Lock Others Bloquear otros
Lock Slices Bloquear sectores
Lock Station Point Bloquear punto de estación
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -600,6 +600,7 @@
Hide Guides Piilota apuviivat
Show Guides Näytä apuviivat
Lock Guides Lukitse apuviivat
Lock Guides by Default Lukitse apuviivat oletuksena
Unlock Guides Avaa apuviivojen lukitus
Make Guides Luo apuviivat
Release Guides Vapauta apuviivat
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1890,6 +1890,7 @@ font-technology Variable Variables
Lock (⌘2) Verrouiller (⌘2)
Lock Grid Verrouiller la grille
Lock Guides Verrouiller les repères
Lock Guides by Default Verrouiller les repères par défaut
Lock Others Verrouiller les autres
Lock Slices Verrouiller les tranches
Lock Station Point Verrouiller le point d’observation
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2555,6 +2555,7 @@ antiAlias Sharp शार्प
Lock Artboards लॉक आर्टबोर्ड
Lock Grid लॉक ग्रिड
Lock Guides गाइड्स लॉक करें
Lock Guides by Default गाइड्स डिफ़ॉल्ट रूप से लॉक करें
Lock Others लॉक अन्य
Lock Selected लॉक सिलेक्टेड
Lock Slices स्लाइस लॉक करें
Can't render this file because it is too large.
+1
View File
@@ -2377,6 +2377,7 @@ file-filter Templates Sablonok
Lock (⌘2) Zárolás (⌘2)
Lock Grid Rács zárolása
Lock Guides Segédvonalak zárolása
Lock Guides by Default Segédvonalak zárolása alapértelmezetten
Lock Others Többi zárolása
Lock Slices Szeletek zárolása
Lock Station Point Nézőpont zárolása
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -559,6 +559,7 @@
Hide Guides Sembunyikan panduan
Show Guides Tampilkan panduan
Lock Guides Kunci panduan
Lock Guides by Default Kunci panduan secara default
Unlock Guides Buka kunci panduan
Make Guides Buat panduan
Release Guides Lepaskan panduan
1 # Indonesian (id) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
559
560
561
562
563
564
565
+1
View File
@@ -1925,6 +1925,7 @@ file-filter Templates Modelli
Lock (⌘2) Blocca (⌘2)
Lock Grid Blocca griglia
Lock Guides Blocca guide
Lock Guides by Default Blocca guide per impostazione predefinita
Lock Others Blocca altri
Lock Slices Blocca sezioni
Lock Station Point Blocca punto di osservazione
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -627,6 +627,7 @@ digits Malayalam マラヤーラム数字
Guides ガイド
Hide Guides ガイドを隠す
Lock Guides ガイドをロック
Lock Guides by Default ガイドを初期設定でロック
Make Guides ガイドを作成
Release Guides ガイドを解除
Clear Guides ガイドを消去
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -521,6 +521,7 @@
Hide Guides დამხმარე ხაზების დამალვა
Show Guides დამხმარე ხაზების ჩვენება
Lock Guides დამხმარე ხაზების ჩაკეტვა
Lock Guides by Default დამხმარე ხაზების ნაგულისხმევად ჩაკეტვა
Unlock Guides დამხმარე ხაზების განბლოკვა
Make Guides დამხმარე ხაზების შექმნა
Release Guides დამხმარე ხაზების მოხსნა
Can't render this file because it is too large.
+1
View File
@@ -622,6 +622,7 @@ digits Malayalam 말라얄람어
Guides 안내선
Hide Guides 안내선 숨기기
Lock Guides 안내선 잠그기
Lock Guides by Default 기본적으로 안내선 잠그기
Make Guides 안내선 만들기
Release Guides 안내선 해제
Clear Guides 안내선 지우기
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -573,6 +573,7 @@
Hide Guides Hulplijnen verbergen
Show Guides Hulplijnen tonen
Lock Guides Hulplijnen vergrendelen
Lock Guides by Default Hulplijnen standaard vergrendelen
Unlock Guides Hulplijnen ontgrendelen
Make Guides Hulplijnen maken
Release Guides Hulplijnen vrijgeven
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -566,6 +566,7 @@
Hide Guides Ukryj linie pomocnicze
Show Guides Pokaż linie pomocnicze
Lock Guides Zablokuj linie pomocnicze
Lock Guides by Default Domyślnie blokuj linie pomocnicze
Unlock Guides Odblokuj linie pomocnicze
Make Guides Utwórz linie pomocnicze
Release Guides Uwolnij linie pomocnicze
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1617,6 +1617,7 @@ antiAlias Sharp Nítido
Lock (⌘2) Bloquear (⌘2)
Lock Grid Bloquear Grade
Lock Guides Bloquear Guias
Lock Guides by Default Bloquear Guias por Padrão
Lock Slices Bloquear Fatias
Lock Station Point Bloquear Ponto de Estação
Lossless (lossy WebP isn't available yet) Sem Perdas (WebP com perdas ainda não está disponível)
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -1922,6 +1922,7 @@ file-filter Templates Шаблоны
Lock (⌘2) Заблокировать (⌘2)
Lock Grid Заблокировать сетку
Lock Guides Заблокировать направляющие
Lock Guides by Default Блокировать направляющие по умолчанию
Lock Others Заблокировать остальные
Lock Slices Заблокировать фрагменты
Lock Station Point Заблокировать точку стояния
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1837,6 +1837,7 @@ font-technology Variable Variabla
Lock (⌘2) Lås (⌘2)
Lock Grid Lås rutnät
Lock Guides Lås stödlinjer
Lock Guides by Default Lås stödlinjer som standard
Lock Others Lås övriga
Lock Slices Lås skivor
Lock Station Point Lås betraktarpunkt
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -1730,6 +1730,7 @@ font-technology Variable Değişken
Lock (⌘2) Kilitle (⌘2)
Lock Grid Izgarayı Kilitle
Lock Guides Kilit Kılavuzları
Lock Guides by Default Kılavuzları Varsayılan Olarak Kilitle
Lock Others Diğerlerini Kilitle
Lock Slices Dilimleri Kilitle
Lock Station Point Kilit İstasyon Noktası
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -1919,6 +1919,7 @@ file-filter Templates Шаблони
Lock (⌘2) Заблокувати (⌘2)
Lock Grid Заблокувати сітку
Lock Guides Заблокувати напрямні
Lock Guides by Default Блокувати напрямні за замовчуванням
Lock Others Заблокувати решту
Lock Slices Заблокувати фрагменти
Lock Station Point Заблокувати точку стояння
Can't render this file because it contains an unexpected character in line 182 and column 5.
+1
View File
@@ -2203,6 +2203,7 @@ digits Hindi Hindi
Lock (⌘2) Khóa (⌘2)
Lock Grid Khóa lưới
Lock Guides Khóa đường căn
Lock Guides by Default Khóa đường căn theo mặc định
Lock Others Khóa các mục khác
Lock Slices Khóa lát cắt
Lock Station Point Khóa điểm trạm
Can't render this file because it contains an unexpected character in line 204 and column 5.
+1
View File
@@ -1806,6 +1806,7 @@ file-filter Templates 模板
Lock (⌘2) 锁定 (⌘2)
Lock Grid 锁定网格
Lock Guides 锁定参考线
Lock Guides by Default 默认锁定参考线
Lock Slices 锁定切片
Lock Station Point 锁定基准点
Lossless (lossy WebP isn't available yet) 无失真 (尚未提供有失真的 WebP)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2800,6 +2800,7 @@ file-filter Templates 範本
Lock (⌘2) 鎖定 (⌘2)
Lock Grid 鎖定格點
Lock Guides 鎖定參考線
Lock Guides by Default 預設鎖定參考線
Lock Others 鎖定其他
Lock Slices 鎖定切片
Lock Station Point 鎖定基準點
Can't render this file because it contains an unexpected character in line 6 and column 47.
+48
View File
@@ -225,6 +225,54 @@ fn prefs_persist_through_ui_state() {
assert_eq!(b.session.prefs.gridline_every, 50.0);
}
/// Guides & Grid › Lock Guides by Default: shown in its category, applied by OK, saved with the
/// UI state and read back as locked guides. A file without the key leaves guides unlocked.
#[test]
fn lock_guides_by_default_shows_applies_and_restores() {
let mut a = app();
a.run("file.new", json!({"width": 400, "height": 400})).unwrap();
open(&mut a, Some("Guides & Grid"));
let text = crate::tests_labels::painted_text(&mut a, |a, ui| show(a, ui.ctx()));
assert!(text.contains("Lock Guides by Default"), "{text}");
assert_eq!(a.ui.dialog.as_ref().unwrap().fields["lockGuidesByDefault"], json!(false));
a.ui.dialog.as_mut().unwrap().fields.insert("lockGuidesByDefault".into(), json!(true));
confirm(&mut a).unwrap();
assert!(a.session.guides_locked());
assert_eq!(crate::menus::dynamic_label(&a, "view.guides.lock", ""), "Unlock Guides");
assert_eq!(a.ui.engine_prefs["lockGuidesByDefault"], json!(true));
let saved = serde_json::to_vec(&a.ui).unwrap();
let mut b = app();
assert!(!b.session.guides_locked());
b.ui = serde_json::from_slice::<crate::UiState>(&saved).unwrap().sanitized();
restore(&mut b);
assert!(b.session.guides_locked());
let mut older = app();
older.ui.engine_prefs = json!({"gridlineEvery": 50});
restore(&mut older);
assert!(!older.session.guides_locked());
}
/// OK with the option unchanged keeps the lock from the View menu. OK after turning the option
/// off, as Reset Preferences does in the dialog, unlocks guides.
#[test]
fn preferences_ok_changes_the_guide_lock_only_with_the_option() {
let mut a = app();
a.run("file.new", json!({"width": 400, "height": 400})).unwrap();
a.run("view.guides.lock", json!({"locked": true})).unwrap();
open(&mut a, Some("Guides & Grid"));
confirm(&mut a).unwrap();
assert!(a.session.guides_locked(), "the option did not change");
a.run("view.guides.lock", json!({"locked": false})).unwrap();
a.run("prefs.set", json!({"key": "lockGuidesByDefault", "value": true})).unwrap();
assert!(a.session.guides_locked());
open(&mut a, Some("Guides & Grid"));
a.ui.dialog.as_mut().unwrap().fields.insert("lockGuidesByDefault".into(), Prefs::default().to_json()["lockGuidesByDefault"].clone());
confirm(&mut a).unwrap();
assert!(!a.session.guides_locked(), "the option went off");
}
#[test]
fn old_pref_files_keep_their_brightness() {
let mut a = app();
+5
View File
@@ -1826,6 +1826,11 @@ View › Guides › Lock Guides (`view.guides.lock`) deselects them and the path
document, and refuses these commands until unlocked. While it is on, a command that selects art, such as `edit.undo`,
`edit.redo`, `object.exitIsolation`, `object.unlockAll`, `object.showAll` or `edit.paste`, leaves the guides out.
`view.guides.lock` rejects a `locked` value that is not a boolean. Omitting `locked` toggles the current value.
While guides are locked, `guide.add` and a guide dragged out of a ruler still make new guides, which are locked too.
Preferences › Guides & Grid › Lock Guides by Default (`lockGuidesByDefault`, off by default) locks guides when the
desktop app starts. Changing the preference, with `prefs.set` or `prefs.reset`, sets the lock to the new value at once.
`view.guides.lock` changes the lock for the session and does not change the preference. A headless session starts
with guides unlocked, as the preference is off until `prefs.set` turns it on.
`view.guides.release {index?, ids?}` (View › Guides › Release Guides) turns guides into paths → `{count}`. With
`index` it releases that ruler guide, and with `ids` it releases those guide paths (and the guide paths inside them).
+1 -1
View File
@@ -974,7 +974,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
The Blob Brush Tool Options' tip preview is the Angle slider for screen readers, with the Angle in degrees as its value: a click or a drag in it points the Angle at the pointer and gives it the keyboard focus, Tab reaches it after the Roundness field, and the arrow keys step the Angle by 1° (10° with Shift, Home and End to −180° and 180°).
The Blob Brush Tool Options also have Fidelity, which the reference app's [Blob Brush options](https://web.archive.org/web/20261012042029/https://helpx.adobe.com/illustrator/desktop/paint-and-fill/learn-painting-basics/blob-brush-options-and-best-practices.html) list as setting how far the pointer moves before the path gets a new anchor point. The range and default are VectorCraft's own values: the Pencil's logarithmic slider from Accurate (0.01 pt) to Smooth (20 pt) and its field (up to 100 pt), 1.5 pt by default and kept with the tool's options. The brush follows the stroke fitted at the Fidelity, as a Pencil stroke is fitted: a low value follows the drag closely, jitter included, and a higher one gives smoother curves (`path.blob` `fidelity`).
A Blob Brush stroke merges with the topmost path of its fill color and no stroke that it touches, and with the other such paths it touches in the same group or layer that are next to that path in stacking order with no other kind of object between them; matching paths in other groups or layers are left alone, as the Blob Brush tool guidelines in [Paint with fills and strokes](https://web.archive.org/web/20241219085603/https://helpx.adobe.com/illustrator/using/painting-fills-strokes.html) describe (`path.blob`).
- **Ruler guides as layer objects:** a guide goes on the current layer, shows, hides, locks and is deleted with it, is listed in the Layers panel (its row selects it) and moves to another layer (`guide.setLayer`, or its row dragged onto that layer's rows); guides on a hidden or locked layer can't be selected, moved or deleted, Duplicate copies a layer's guides and Merge Selected and Flatten Artwork keep them on the merged layer; guides from files saved before guides had layers go on the top visible, unlocked layer when the document opens. Not done: guides stacked among the art of their layer (they are listed above it), a guide row's own eye and lock. Lock Guides also locks the objects made into guides: they can't be selected, moved or deleted (#1543); they are drawn in the guide colour and style of Preferences, and a dragged selection snaps to ruler guides with Smart Guides on (#1583, fixes #1534). The Transform panel shows a selected ruler guide's X or Y, and typing a value moves it (#1659, part of #1597). This is not established by the documentation we could find: the public help pages on guides, rulers and the Transform, Control and Properties panels (read again on 2026-10-14) describe selecting and dragging unlocked guides and the panels' X and Y for a selection, not a guide's position in a panel, and the one public statement of it is a user's [community answer](https://community.adobe.com/questions-652/adding-a-new-custom-guide-785586), which is not documentation. It is kept as a convenience pending the owner's decision, with no parity claim and nothing counted towards a score.
- **Ruler guides as layer objects:** a guide goes on the current layer, shows, hides, locks and is deleted with it, is listed in the Layers panel (its row selects it) and moves to another layer (`guide.setLayer`, or its row dragged onto that layer's rows); guides on a hidden or locked layer can't be selected, moved or deleted, Duplicate copies a layer's guides and Merge Selected and Flatten Artwork keep them on the merged layer; guides from files saved before guides had layers go on the top visible, unlocked layer when the document opens. Not done: guides stacked among the art of their layer (they are listed above it), a guide row's own eye and lock. Lock Guides also locks the objects made into guides: they can't be selected, moved or deleted (#1543); they are drawn in the guide colour and style of Preferences, and a dragged selection snaps to ruler guides with Smart Guides on (#1583, fixes #1534). Preferences › Guides & Grid › Lock Guides by Default locks guides when the app starts; it is off by default, and new guides can still be dragged out of a ruler while guides are locked. The Transform panel shows a selected ruler guide's X or Y, and typing a value moves it (#1659, part of #1597). This is not established by the documentation we could find: the public help pages on guides, rulers and the Transform, Control and Properties panels (read again on 2026-10-14) describe selecting and dragging unlocked guides and the panels' X and Y for a selection, not a guide's position in a panel, and the one public statement of it is a user's [community answer](https://community.adobe.com/questions-652/adding-a-new-custom-guide-785586), which is not documentation. It is kept as a convenience pending the owner's decision, with no parity claim and nothing counted towards a score.
- **Releasing ruler guides:** View › Guides › Release Guides turns the selected ruler guides into paths with no fill or stroke. An artboard guide becomes a line across its artboard, and a canvas guide becomes a line across the artboards (`view.guides.release {index?, ids?}`). A Cmd/Ctrl+Shift double-click with the Selection, Direct Selection or Group Selection tool releases the ruler guide or guide path under the pointer.
- **Layers panel:** every layer, sublayer, group and object is a clickable row at any depth: a click highlights it and makes its layer current (new art goes there), Shift-click a range, Ctrl/Cmd-click toggles. Sublayers are no longer treated as objects: clicks, marquees, Select All and the Select menu take the art inside them, and each sublayer selects in its own colour. Selection squares on rows with selected art (click selects a row's art, Shift adds or removes; dragging the square moves the selection to another row, Alt copies), the eye and lock on every row with drag-down-the-column (one undo step), Ctrl/Cmd-click an eye for per-layer Outline, Alt-click an eye or lock to hide or lock the other layers (again: show or unlock them all), triangles with Alt to open or close everything inside, drag and drop rows above, below or into layers and groups with a drop line (Alt copies) and a dimmed copy of the row under the pointer (layers dropped in the empty space under the last row go below the bottom layer, #2260; dropped on the trash rows are deleted, on New Layer duplicated, as in Illustrator), inline rename, Delete key on highlighted rows. Buttons and the full panel menu: New Layer/Sublayer (Alt: options, Ctrl: on top), Duplicate, Delete, Options for Selection, clipping mask, isolation, Locate Object, Merge Selected, Flatten Artwork, Collect in New Layer, Release to Layers (Sequence and Build), Reverse Order, Template, Hide/Outline/Lock Others and Show/Preview/Unlock All, Paste Remembers Layers, Panel Options (row size, thumbnails per kind, Show Layers Only). Layer Options: name, colour (presets, each with a chip of its colour, or custom), Template, Lock, Show, Print, Preview (outline drawing and clicking) and Dim Images to %. Every operation is a command (`layer.move`, `layer.highlight`, `layer.merge`…) with validation against cycles and invalid nesting. A document opens with only its top-level layers open; the rows left open are saved in the native file (nothing is written for the default) and reopen that way. Only the rows scrolled into view are laid out, from a row list made once per change: a layer of 5,000 groups of 10 paths, all open, draws the panel in 0.4 ms a frame instead of about 29 s (#638).
The Filter button at the right of Search All keeps the rows that are of an object kind ticked under By Object (paths, shapes, text, images, effects, groups, linked files, guides) and in a state ticked under By Layer (locked, hidden), with the rows that hold them; a section with nothing ticked takes every row. The filter combines with the search, the button is lit while a filter is on, and Clear All in its popup turns the filter off (`ui.layersFilter`). The filter lasts the session.