mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Layers: with Locate Selected Objects on, the panel shows the row of art selected on the canvas
By default the Layers panel opens the layers around a row and scrolls the row into view when its highlighted rows change, for example after a click in the panel, a new layer or Locate Object. When no row is highlighted and a selection makes another layer or sublayer the active one, the panel does the same for that layer's row. Selecting art on the canvas or by a command highlights no row, so by default the panel does not open the layer or sublayer that holds the art or the groups around it, and does not scroll to the art's row. Panel Options gets Locate Selected Objects, off by default and kept with the UI state. With it on, when the last selected object changes on the canvas or by a command, the panel opens the layers and groups around it and scrolls to its row, as after Locate Object, but does not highlight the row. When the selection also changes the active layer, the panel scrolls to the object's row instead of the layer's row. When Show Layers Only, a search or the filter leaves the object without a row, the panel scrolls as it does with the option off. A change made with the panel's own rows, buttons or menu is not located, and a change made during a drag is located when the drag ends. Locate Object is unchanged. On a layer of 5,000 groups of 10 paths with every row open, the first panel frame after a selection change took a median of 1.6 ms with the option on and took a median of 1.4 ms with it off, in a release build. Docs: docs/mcp.md (locateSelected), docs/target-app-parity.md.
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
//! Layers panel › Panel Options…: Show Layers Only, the row size and which rows show thumbnails.
|
||||
//! Layers panel › Panel Options…: Show Layers Only, Locate Selected Objects, the row size and
|
||||
//! which rows show thumbnails.
|
||||
//!
|
||||
//! Fields: `layersOnly`, `rowSize` (`small`, `medium`, `large` or `other`), `otherSize` (points,
|
||||
//! 12–100, for `other`), `thumbLayers`, `thumbGroups` and `thumbObjects`. OK applies them to the
|
||||
//! panel (kept with the UI state).
|
||||
//! Fields: `layersOnly`, `locateSelected`, `rowSize` (`small`, `medium`, `large` or `other`),
|
||||
//! `otherSize` (points, 12–100, for `other`), `thumbLayers`, `thumbGroups` and `thumbObjects`. OK
|
||||
//! applies them to the panel (kept with the UI state).
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
@@ -25,7 +26,7 @@ pub fn open(app: &mut VectorcraftApp) -> Result<Value, String> {
|
||||
let o = &app.ui.layers_panel;
|
||||
let size = SIZES.iter().find(|s| (s.2 - o.row()).abs() < 0.01).map_or("other", |s| s.0);
|
||||
let fields = json!({
|
||||
"layersOnly": o.layers_only, "rowSize": size, "otherSize": o.row(),
|
||||
"layersOnly": o.layers_only, "locateSelected": o.locate_selected, "rowSize": size, "otherSize": o.row(),
|
||||
"thumbLayers": o.thumb_layers, "thumbGroups": o.thumb_groups, "thumbObjects": o.thumb_objects,
|
||||
});
|
||||
app.ui.dialog = Some(Dialog::new(KIND, fields));
|
||||
@@ -41,6 +42,7 @@ fn check(ui: &mut egui::Ui, d: &mut Dialog, key: &str, text: &str) {
|
||||
|
||||
fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
||||
check(ui, d, "layersOnly", tl!("Show Layers Only"));
|
||||
check(ui, d, "locateSelected", tl!("Locate Selected Objects"));
|
||||
ui.add_space(8.0);
|
||||
grid(ui, |ui| {
|
||||
label(ui, tl!("Row Size:"));
|
||||
@@ -73,6 +75,7 @@ pub(crate) fn options(d: &Dialog) -> PanelOptions {
|
||||
let row_size = SIZES.iter().find(|s| s.0 == size).map_or_else(|| (d.f64("otherSize", f64::from(ROW_MEDIUM)) as f32).clamp(12.0, 100.0), |s| s.2);
|
||||
PanelOptions {
|
||||
layers_only: d.bool("layersOnly"),
|
||||
locate_selected: d.bool("locateSelected"),
|
||||
row_size: if row_size.is_finite() { row_size } else { ROW_MEDIUM },
|
||||
thumb_layers: d.bool("thumbLayers"),
|
||||
thumb_groups: d.bool("thumbGroups"),
|
||||
@@ -115,4 +118,23 @@ mod tests {
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
assert_eq!(app.ui.layers_panel.row(), ROW_LARGE);
|
||||
}
|
||||
|
||||
/// Locate Selected Objects is off by default and in options saved without it; OK turns it on
|
||||
/// and keeps it with the UI state, and the dialog opens on it again.
|
||||
#[test]
|
||||
fn locate_selected_objects_is_off_by_default_and_kept_with_the_ui_state() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.session.execute("file.new", &json!({"width": 100, "height": 100})).unwrap();
|
||||
app.run("ui.layersPanelOptions", json!({})).unwrap();
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
assert_eq!(d.fields.get("locateSelected"), Some(&json!(false)));
|
||||
d.fields.insert("locateSelected".into(), json!(true));
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
let back: crate::UiState = serde_json::from_value(serde_json::to_value(&app.ui).unwrap()).unwrap();
|
||||
assert!(back.layers_panel.locate_selected, "kept with the UI state");
|
||||
app.run("ui.layersPanelOptions", json!({})).unwrap();
|
||||
assert!(app.ui.dialog.as_ref().unwrap().bool("locateSelected"));
|
||||
let old: crate::UiState = serde_json::from_value(json!({"layers_panel": {"layersOnly": true}})).unwrap();
|
||||
assert!(old.layers_panel.layers_only && !old.layers_panel.locate_selected, "options saved without it");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1672,6 +1672,7 @@
|
||||
Load Text into Clipboard Зареждане на текст в клипборда
|
||||
Local Libraries Локални библиотеки
|
||||
Locate Object Намиране на обект
|
||||
Locate Selected Objects Намиране на избраните обекти
|
||||
Location Местоположение
|
||||
Location: Местоположение:
|
||||
Lock Заключване
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2261,6 +2261,7 @@ lighting Light Světlo
|
||||
Layers Panel Options Možnosti panelu Vrstvy
|
||||
Row Size: Velikost řádků:
|
||||
Show Layers Only Zobrazit pouze vrstvy
|
||||
Locate Selected Objects Najít vybrané objekty
|
||||
Thumbnails: Miniatury:
|
||||
Anchor Points Kotevní body
|
||||
Brush Affects: Štětec ovlivňuje:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2081,6 +2081,7 @@ lighting Light Lys
|
||||
Layers Panel Options Indstillinger for lagpanelet
|
||||
Row Size: Rækkestørrelse:
|
||||
Show Layers Only Vis kun lag
|
||||
Locate Selected Objects Lokalisér markerede objekter
|
||||
Thumbnails: Miniaturer:
|
||||
Anchor Points Ankerpunkter
|
||||
Brush Affects: Penslen påvirker:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1721,6 +1721,7 @@ file-filter Templates Vorlagen
|
||||
Load Text into Clipboard Text in die Zwischenablage laden
|
||||
Local Libraries Lokale Bibliotheken
|
||||
Locate Object Objekt ausfindig machen
|
||||
Locate Selected Objects Ausgewählte Objekte ausfindig machen
|
||||
Location Speicherort
|
||||
Location: Ort:
|
||||
Lock Sperren
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2458,6 +2458,7 @@ theme Appearance Εμφάνιση
|
||||
Load Text into Clipboard Φόρτωση κειμένου στο πρόχειρο
|
||||
Local Libraries Τοπικές βιβλιοθήκες
|
||||
Locate Object Εντοπισμός αντικειμένου
|
||||
Locate Selected Objects Εντοπισμός επιλεγμένων αντικειμένων
|
||||
Lock (⌘2) Κλείδωμα (⌘2)
|
||||
Lock Grid Κλείδωμα πλέγματος
|
||||
Lock Others Κλείδωμα υπόλοιπων
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2007 and column 19.
|
@@ -1713,6 +1713,7 @@
|
||||
Load Text into Clipboard Cargar texto en el portapapeles
|
||||
Local Libraries Bibliotecas locales
|
||||
Locate Object Localizar objeto
|
||||
Locate Selected Objects Localizar objetos seleccionados
|
||||
Location Ubicación
|
||||
Location: Ubicación:
|
||||
Lock Bloquear
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2216,6 +2216,7 @@ lighting Light Valo
|
||||
Layers Panel Options Tasot-paneelin asetukset
|
||||
Row Size: Rivin koko:
|
||||
Show Layers Only Näytä vain tasot
|
||||
Locate Selected Objects Paikanna valitut objektit
|
||||
Thumbnails: Pikkukuvat:
|
||||
Anchor Points Ankkuripisteet
|
||||
Brush Affects: Sivellin vaikuttaa:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1712,6 +1712,7 @@
|
||||
Load Text into Clipboard Charger du texte dans le presse-papiers
|
||||
Local Libraries Bibliothèques locales
|
||||
Locate Object Localiser l’objet
|
||||
Locate Selected Objects Localiser les objets sélectionnés
|
||||
Location Emplacement
|
||||
Location: Emplacement :
|
||||
Lock Verrouiller
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2162,6 +2162,7 @@ file-filter Templates Sablonok
|
||||
Loaded Texture Betöltött textúra
|
||||
Local Libraries Helyi könyvtárak
|
||||
Locate Object Objektum megkeresése
|
||||
Locate Selected Objects Kijelölt objektumok megkeresése
|
||||
Location Hely
|
||||
Location: Hely:
|
||||
Lock Zárolás
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2081,6 +2081,7 @@ lighting Light Cahaya
|
||||
Layers Panel Options Opsi panel lapisan
|
||||
Row Size: Ukuran baris:
|
||||
Show Layers Only Tampilkan lapisan saja
|
||||
Locate Selected Objects Temukan objek terpilih
|
||||
Thumbnails: Miniatur:
|
||||
Anchor Points Titik jangkar
|
||||
Brush Affects: Kuas memengaruhi:
|
||||
|
||||
|
@@ -1739,6 +1739,7 @@ file-filter Templates Modelli
|
||||
Load Text into Clipboard Carica testo negli appunti
|
||||
Local Libraries Librerie locali
|
||||
Locate Object Individua oggetto
|
||||
Locate Selected Objects Individua oggetti selezionati
|
||||
Location Posizione
|
||||
Location: Posizione:
|
||||
Lock Blocca
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1911,6 +1911,7 @@
|
||||
Load Text into Clipboard テキストをクリップボードに読み込む
|
||||
Local Libraries ローカルライブラリ
|
||||
Locate Object オブジェクトを検索
|
||||
Locate Selected Objects 選択したオブジェクトを検索
|
||||
Location 場所
|
||||
Location: 位置:
|
||||
Lock (⌘2) ロック (⌘2)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1979,6 +1979,7 @@ lighting Light სინათლე
|
||||
Layers Panel Options შრეების პანელის პარამეტრები
|
||||
Row Size: სტრიქონის ზომა:
|
||||
Show Layers Only მხოლოდ შრეების ჩვენება
|
||||
Locate Selected Objects მონიშნული ობიექტების მოძებნა
|
||||
Thumbnails: ესკიზები:
|
||||
Anchor Points საყრდენი წერტილები
|
||||
Brush Affects: ფუნჯი მოქმედებს:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1906,6 +1906,7 @@
|
||||
Load Text into Clipboard 텍스트를 클립보드로 불러오기
|
||||
Local Libraries 로컬 라이브러리
|
||||
Locate Object 개체 찾기
|
||||
Locate Selected Objects 선택한 개체 찾기
|
||||
Location 위치
|
||||
Location: 위치:
|
||||
Lock (⌘2) 잠금 (⌘2)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2095,6 +2095,7 @@ lighting Light Licht
|
||||
Layers Panel Options Opties voor het paneel Lagen
|
||||
Row Size: Rijgrootte:
|
||||
Show Layers Only Alleen lagen weergeven
|
||||
Locate Selected Objects Geselecteerde objecten lokaliseren
|
||||
Thumbnails: Miniaturen:
|
||||
Anchor Points Ankerpunten
|
||||
Brush Affects: Penseel beïnvloedt:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2175,6 +2175,7 @@ lighting Light Światło
|
||||
Layers Panel Options Opcje panelu Warstwy
|
||||
Row Size: Rozmiar wiersza:
|
||||
Show Layers Only Pokaż tylko warstwy
|
||||
Locate Selected Objects Znajdź zaznaczone obiekty
|
||||
Thumbnails: Miniatury:
|
||||
Anchor Points Punkty kontrolne
|
||||
Brush Affects: Pędzel wpływa na:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3115,6 +3115,7 @@ lighting Light Luz
|
||||
Layers Panel Options Opções do painel Camadas
|
||||
Left-to-Right Paragraph Direction Direção do parágrafo da esquerda para a direita
|
||||
Line-end Punctuation Half Width Pontuação de fim de linha em meia largura
|
||||
Locate Selected Objects Localizar objetos selecionados
|
||||
Lock Others Bloquear outros
|
||||
Merge Selected Mesclar selecionados
|
||||
Model Modelo
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1737,6 +1737,7 @@ file-filter Templates Шаблоны
|
||||
Load Text into Clipboard Загрузить текст в буфер обмена
|
||||
Local Libraries Локальные библиотеки
|
||||
Locate Object Найти объект
|
||||
Locate Selected Objects Находить выделенные объекты
|
||||
Location Расположение
|
||||
Location: Расположение:
|
||||
Lock Заблокировать
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1672,6 +1672,7 @@
|
||||
Load Text into Clipboard Läs in text till urklipp
|
||||
Local Libraries Lokala bibliotek
|
||||
Locate Object Hitta objekt
|
||||
Locate Selected Objects Hitta markerade objekt
|
||||
Location Plats
|
||||
Location: Plats:
|
||||
Lock Lås
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -1733,6 +1733,7 @@ file-filter Templates Шаблони
|
||||
Load Text into Clipboard Завантажити текст у буфер обміну
|
||||
Local Libraries Локальні бібліотеки
|
||||
Locate Object Знайти об’єкт
|
||||
Locate Selected Objects Знаходити виділені об’єкти
|
||||
Location Розташування
|
||||
Location: Розташування:
|
||||
Lock Заблокувати
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 725 and column 40.
|
@@ -1986,6 +1986,7 @@ file-filter Templates Mẫu
|
||||
Loaded Texture Kết cấu đã tải
|
||||
Local Libraries Thư viện cục bộ
|
||||
Locate Object Xác định vị trí đối tượng
|
||||
Locate Selected Objects Xác định vị trí đối tượng đã chọn
|
||||
Location Vị trí
|
||||
Location: Vị trí:
|
||||
Lock Khóa
|
||||
|
||||
|
@@ -1630,6 +1630,7 @@ file-filter Templates 模板
|
||||
Load Text into Clipboard 将文本载入剪贴板
|
||||
Local Libraries 本地库
|
||||
Locate Object 查找对象
|
||||
Locate Selected Objects 查找所选对象
|
||||
Location 位置
|
||||
Location: 位置:
|
||||
Lock 锁定
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2441,6 +2441,7 @@ file-filter Templates 範本
|
||||
Load Text into Clipboard 將文字載入剪貼簿
|
||||
Local Libraries 本機資料庫
|
||||
Locate Object 尋找物件
|
||||
Locate Selected Objects 尋找選取的物件
|
||||
Location 位置
|
||||
Location: 位置:
|
||||
Lock 鎖定
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -553,7 +553,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"ui.layersPanelOptions",
|
||||
"Panel Options…",
|
||||
"",
|
||||
"{} open the Layers panel's Panel Options: dialog `layersPanelOptions`, fields layersOnly, rowSize (small|medium|large|other), otherSize (12–100 pt), thumbLayers, thumbGroups, thumbObjects; OK applies them (kept with the UI state)",
|
||||
"{} open the Layers panel's Panel Options: dialog `layersPanelOptions`, fields layersOnly, locateSelected (Locate Selected Objects, off by default: when the last selected object changes outside the panel, on the canvas or by a command, the panel opens the layers and groups around it and scrolls to its row, as after layer.locate, without highlighting the row; a change made during a drag is located when the drag ends), rowSize (small|medium|large|other), otherSize (12–100 pt), thumbLayers, thumbGroups, thumbObjects; OK applies them (kept with the UI state)",
|
||||
),
|
||||
(
|
||||
"ui.layersExpand",
|
||||
|
||||
@@ -66,6 +66,10 @@ const SQUARE_X: f32 = 11.0;
|
||||
pub struct PanelOptions {
|
||||
/// Show Layers Only: no rows for groups and objects.
|
||||
pub layers_only: bool,
|
||||
/// Locate Selected Objects (off by default): when the last selected object changes outside the
|
||||
/// panel, on the canvas or by a command, the panel opens the rows around it and scrolls to its
|
||||
/// row, as after Locate Object, without highlighting the row.
|
||||
pub locate_selected: bool,
|
||||
/// Row height in points: [`ROW_SMALL`], [`ROW_MEDIUM`], [`ROW_LARGE`] or another (12–100).
|
||||
pub row_size: f32,
|
||||
/// Thumbnails on layer, group and object rows.
|
||||
@@ -80,7 +84,7 @@ pub const ROW_LARGE: f32 = 40.0;
|
||||
|
||||
impl Default for PanelOptions {
|
||||
fn default() -> Self {
|
||||
Self { layers_only: false, row_size: ROW_MEDIUM, thumb_layers: true, thumb_groups: true, thumb_objects: true }
|
||||
Self { layers_only: false, locate_selected: false, row_size: ROW_MEDIUM, thumb_layers: true, thumb_groups: true, thumb_objects: true }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,16 +542,11 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let seen: Vec<NodeId> = ui.data(|d| d.get_temp(key("seen"))).unwrap_or_default();
|
||||
let mut reveal = None;
|
||||
if rows != seen {
|
||||
let around: Vec<NodeId> =
|
||||
rows.iter().flat_map(|r| doc.ancestry(*r).unwrap_or_default().split_last().map(|(_, a)| a.to_vec()).unwrap_or_default()).collect();
|
||||
if let Some(st) = app.session.active_mut() {
|
||||
for a in around {
|
||||
st.layers_open.set(a, true);
|
||||
}
|
||||
}
|
||||
open_around(app, &doc, &rows);
|
||||
reveal = rows.first().copied();
|
||||
ui.data_mut(|d| d.insert_temp(key("seen"), rows.clone()));
|
||||
}
|
||||
let highlighted = reveal.is_some();
|
||||
// A new active layer (a canvas selection highlights no row): reveal it too, unless a row
|
||||
// highlighted in the panel holds the view.
|
||||
let seen_current: Option<NodeId> = ui.data(|d| d.get_temp(key("seen-current"))).flatten();
|
||||
@@ -565,6 +564,12 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
ui.data_mut(|d| d.insert_temp(key("seen-current"), current));
|
||||
}
|
||||
// With Locate Selected Objects on, an object selected anew outside the panel has the rows
|
||||
// around it opened, and its row is revealed instead of the active layer's row (below).
|
||||
let located = newly_selected(app, ui).filter(|_| !highlighted);
|
||||
if let Some(id) = located {
|
||||
open_around(app, &doc, &[id]);
|
||||
}
|
||||
// Search All, and the Filter button at its right.
|
||||
let query = ui
|
||||
.horizontal(|ui| {
|
||||
@@ -579,6 +584,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
.to_lowercase();
|
||||
let Some(st) = app.session.active() else { return };
|
||||
let list = rows_of_doc(ui, st, &app.ui.layers_panel, &query, &app.ui.layers_filter);
|
||||
// The located object's row when it is listed (Show Layers Only, a search or the filter can
|
||||
// leave it out), otherwise the row revealed above.
|
||||
let reveal = located.filter(|id| list.iter().any(|r| r.node.id == *id)).or(reveal);
|
||||
let view = View {
|
||||
doc: &doc,
|
||||
thumbs: ThumbSource::of(app, st, &doc),
|
||||
@@ -618,6 +626,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
st.layers_open.set(id, open);
|
||||
}
|
||||
}
|
||||
let selected = last_selected(app);
|
||||
// The column drag: live while it lasts, one undo step when the button is released.
|
||||
let released = ui.input(|i| i.pointer.any_released());
|
||||
// Layers dragged into the empty space under the last row go below the bottom layer (there is
|
||||
@@ -687,6 +696,46 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
for (c, p) in out.actions {
|
||||
app.run(&c, p).ok();
|
||||
}
|
||||
see_selection(app, ui, selected);
|
||||
}
|
||||
|
||||
/// Open the layers and groups around rows `ids`.
|
||||
fn open_around(app: &mut VectorcraftApp, doc: &Document, ids: &[NodeId]) {
|
||||
let around: Vec<NodeId> =
|
||||
ids.iter().flat_map(|r| doc.ancestry(*r).unwrap_or_default().split_last().map(|(_, a)| a.to_vec()).unwrap_or_default()).collect();
|
||||
if let Some(st) = app.session.active_mut() {
|
||||
for a in around {
|
||||
st.layers_open.set(a, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The active document's uid and its last selected object, the one Locate Object locates.
|
||||
fn last_selected(app: &VectorcraftApp) -> Option<(u64, Option<NodeId>)> {
|
||||
app.session.active().map(|st| (st.uid, st.selection.objects.last().copied()))
|
||||
}
|
||||
|
||||
/// Locate Selected Objects: the object to locate in this frame. With the option on, that is the
|
||||
/// last selected object when it differs from the one noted in an earlier frame for the same
|
||||
/// document. [`see_selection`] notes the panel's own changes, so only a change made on the canvas
|
||||
/// or by a command is located. This function notes nothing during a drag, so a selection made on
|
||||
/// the canvas during one is located when the drag ends. When another document becomes active,
|
||||
/// nothing is located.
|
||||
fn newly_selected(app: &VectorcraftApp, ui: &Ui) -> Option<NodeId> {
|
||||
let busy = app.session.active().is_some_and(|st| st.interaction.is_some()) || app.session.tool_busy();
|
||||
let now = last_selected(app).filter(|_| !busy)?;
|
||||
let was = ui.data(|d| d.get_temp::<(u64, Option<NodeId>)>(key("selected")));
|
||||
ui.data_mut(|d| d.insert_temp(key("selected"), now));
|
||||
let anew = was.is_some_and(|w| w.0 == now.0 && w.1 != now.1);
|
||||
now.1.filter(|_| anew && app.ui.layers_panel.locate_selected)
|
||||
}
|
||||
|
||||
/// Note the selection as seen when the panel's rows, buttons or menu changed it (`before`: what
|
||||
/// [`last_selected`] gave before they ran), so Locate Selected Objects does not locate it.
|
||||
fn see_selection(app: &VectorcraftApp, ui: &Ui, before: Option<(u64, Option<NodeId>)>) {
|
||||
if let Some(now) = last_selected(app).filter(|now| Some(*now) != before) {
|
||||
ui.data_mut(|d| d.insert_temp(key("selected"), now));
|
||||
}
|
||||
}
|
||||
|
||||
/// The Filter button, lit while a filter is on, and its popup: Clear All, the object kinds under
|
||||
@@ -1586,7 +1635,9 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
Some(("Save Selection", "", ..)) => save_selection_menu(app, ui),
|
||||
Some((label, cmd, p, enabled, checked)) => {
|
||||
if widgets::menu_item(ui, label, enabled, checked) {
|
||||
let selected = last_selected(app);
|
||||
app.run(cmd, p).ok();
|
||||
see_selection(app, ui, selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2741,6 +2792,233 @@ mod tests {
|
||||
assert_eq!(app.session.active().unwrap().current_layer(), Some(sub));
|
||||
}
|
||||
|
||||
// ---- Locate Selected Objects (Panel Options) ----
|
||||
|
||||
/// A layer of `n` groups of one path each, with only the layer open, as a document opens →
|
||||
/// (app, layer, the paths in stacking order, so the first is in the bottom row). The path in
|
||||
/// group `i` is named `Path {i}`.
|
||||
fn groups_of_one(n: usize) -> (VectorcraftApp, NodeId, Vec<NodeId>) {
|
||||
use vectorcraft_doc::Appearance;
|
||||
use vectorcraft_geom::{Rect, shapes};
|
||||
let mut d = Document::new(1000.0, 1000.0);
|
||||
let mut groups = vec![];
|
||||
let mut paths = vec![];
|
||||
for i in 0..n {
|
||||
let r = Rect::new(0.0, i as f64, 1.0, i as f64 + 1.0);
|
||||
let mut path = Node::path(d.alloc_id(), shapes::rectangle(r), Appearance::default());
|
||||
path.name = Some(format!("Path {i}"));
|
||||
paths.push(path.id);
|
||||
groups.push(Arc::new(Node::group(d.alloc_id(), vec![Arc::new(path)])));
|
||||
}
|
||||
*Arc::make_mut(&mut d.layers[0]).children_mut().unwrap() = groups;
|
||||
let layer = d.layers[0].id;
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.session.add_document(d, None);
|
||||
(app, layer, paths)
|
||||
}
|
||||
|
||||
/// One frame of the panel in a 300 × 800 window, one second after the previous frame: the
|
||||
/// centers of the target circles, top row first, and whether `name` is drawn.
|
||||
fn tall_frame(app: &mut VectorcraftApp, ctx: &egui::Context, name: &str) -> (Vec<egui::Pos2>, bool) {
|
||||
let time = ctx.input(|i| i.time) + 1.0;
|
||||
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, vec2(300.0, 800.0));
|
||||
let mut out = ctx.run_ui(egui::RawInput { screen_rect: Some(screen), time: Some(time), ..Default::default() }, |ui| show(app, ui));
|
||||
out.textures_delta.clear();
|
||||
let found = out.shapes.iter().any(|c| matches!(&c.shape, egui::Shape::Text(t) if t.galley.text() == name));
|
||||
let mut circles: Vec<egui::Pos2> = out
|
||||
.shapes
|
||||
.iter()
|
||||
.filter_map(|c| match &c.shape {
|
||||
egui::Shape::Circle(cs) if cs.radius == 5.0 => Some(cs.center),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
circles.sort_by(|a, b| a.y.total_cmp(&b.y));
|
||||
(circles, found)
|
||||
}
|
||||
|
||||
/// Without Locate Selected Objects (the default), an object selected outside the panel leaves
|
||||
/// the open rows and the scroll position as they were.
|
||||
#[test]
|
||||
fn without_the_option_a_selection_leaves_the_rows_and_the_scroll_as_they_were() {
|
||||
let (mut app, layer, paths) = groups_of_one(500);
|
||||
let ctx = egui::Context::default();
|
||||
let (before, _) = tall_frame(&mut app, &ctx, "");
|
||||
// The bottom row's path: out of view, in a closed group.
|
||||
app.session.execute("select.set", &json!({"ids": [paths[0].0]})).unwrap();
|
||||
for _ in 0..4 {
|
||||
let (circles, found) = tall_frame(&mut app, &ctx, "Path 0");
|
||||
assert!(!found);
|
||||
assert_eq!(circles, before, "nothing opens or scrolls");
|
||||
}
|
||||
let st = app.session.active().unwrap();
|
||||
let group = st.doc.parent_of(paths[0]).unwrap();
|
||||
assert!(st.layers_open.contains(layer) && !st.layers_open.contains(group));
|
||||
}
|
||||
|
||||
/// With Locate Selected Objects on, an object selected outside the panel is located as Locate
|
||||
/// Object locates it: the layer and the group around it open and its row is scrolled into
|
||||
/// view. The highlighted row stays as it was.
|
||||
#[test]
|
||||
fn with_the_option_a_selected_object_is_opened_and_scrolled_into_view() {
|
||||
let (mut app, layer, paths) = groups_of_one(500);
|
||||
app.ui.layers_panel.locate_selected = true;
|
||||
let ctx = egui::Context::default();
|
||||
let top = app.session.active().unwrap().doc.parent_of(paths[499]).unwrap();
|
||||
app.session.execute("layer.highlight", &json!({"ids": [top.0]})).unwrap();
|
||||
tall_frame(&mut app, &ctx, "");
|
||||
expand(&mut app, &json!({"ids": [layer.0], "open": false})).unwrap();
|
||||
assert_eq!(tall_frame(&mut app, &ctx, "").0.len(), 1, "only the layer's row");
|
||||
app.session.execute("select.set", &json!({"ids": [paths[0].0]})).unwrap();
|
||||
let found = (0..4).any(|_| tall_frame(&mut app, &ctx, "Path 0").1);
|
||||
let st = app.session.active().unwrap();
|
||||
let group = st.doc.parent_of(paths[0]).unwrap();
|
||||
assert!(st.layers_open.contains(layer) && st.layers_open.contains(group), "the layer and the group open");
|
||||
assert!(found, "the row is scrolled into view");
|
||||
assert_eq!(st.highlighted_rows(), [top], "the highlighted row stays");
|
||||
}
|
||||
|
||||
/// With Locate Selected Objects on, the panel's own clicks locate nothing: a Shift-click
|
||||
/// highlights the rows down to the one it clicks, and Shift-clicking selection squares selects
|
||||
/// art in a closed sublayer without opening it. An object then selected outside the panel is
|
||||
/// located, and the highlighted rows stay.
|
||||
#[test]
|
||||
fn with_the_option_the_panels_own_clicks_locate_nothing() {
|
||||
let (mut app, [layer, sub, g, b, _, c]) = nested();
|
||||
app.ui.layers_panel.locate_selected = true;
|
||||
expand(&mut app, &json!({"ids": [sub.0, g.0], "open": false})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
// Rows top down: Layer 1, Sub (closed), c.
|
||||
let (circles, _) = frame(&mut app, &ctx, vec![], false);
|
||||
assert_eq!(circles.len(), 3);
|
||||
let name = |i: usize| egui::pos2(150.0, circles[i].y);
|
||||
let square = |i: usize| circles[i] + vec2(TARGET_X - SQUARE_X, 0.0);
|
||||
click_with(&mut app, &ctx, name(0), egui::Modifiers::NONE);
|
||||
click_with(&mut app, &ctx, name(2), egui::Modifiers::SHIFT);
|
||||
assert_eq!(rows_of(&app), [layer, sub, c], "both rows clicked and the one between them");
|
||||
click_with(&mut app, &ctx, square(2), egui::Modifiers::SHIFT);
|
||||
click_with(&mut app, &ctx, square(1), egui::Modifiers::SHIFT);
|
||||
let st = app.session.active().unwrap();
|
||||
assert!(st.selection.objects.len() >= 3 && st.selection.objects.contains(&c), "c and Sub's art are selected");
|
||||
let (after, _) = frame(&mut app, &ctx, vec![], false);
|
||||
assert_eq!(after, circles, "no row opens and nothing scrolls");
|
||||
assert_eq!(rows_of(&app), [layer, sub, c]);
|
||||
// Selected outside the panel: b's sublayer and group open, and the rows stay highlighted.
|
||||
app.session.execute("select.set", &json!({"ids": [b.0]})).unwrap();
|
||||
assert_eq!(frame(&mut app, &ctx, vec![], false).0.len(), 6);
|
||||
assert_eq!(rows_of(&app), [layer, sub, c]);
|
||||
}
|
||||
|
||||
/// With Locate Selected Objects on, a selection the panel menu changes is not located: Delete
|
||||
/// Selection deletes the highlighted object, and the object left selected stays in its closed
|
||||
/// sublayer and group.
|
||||
#[test]
|
||||
fn with_the_option_the_panel_menu_locates_nothing() {
|
||||
let (mut app, [_, sub, g, b, _, c]) = nested();
|
||||
app.ui.layers_panel.locate_selected = true;
|
||||
app.session.execute("select.set", &json!({"ids": [b.0, c.0]})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
frame(&mut app, &ctx, vec![], false);
|
||||
expand(&mut app, &json!({"ids": [sub.0, g.0], "open": false})).unwrap();
|
||||
app.session.execute("layer.highlight", &json!({"ids": [c.0]})).unwrap();
|
||||
assert_eq!(frame(&mut app, &ctx, vec![], false).0.len(), 3, "Layer 1, Sub (closed) and c");
|
||||
let menu_frame = |app: &mut VectorcraftApp, events| super::super::tests_appearance::frame_events(&ctx, app, events, menu);
|
||||
let at = menu_frame(&mut app, vec![]).iter().find(|(t, _)| t.trim() == "Delete Selection").map(|(_, r)| r.center()).unwrap();
|
||||
for e in [egui::Event::PointerMoved(at), button(at, true), button(at, false)] {
|
||||
menu_frame(&mut app, vec![e]);
|
||||
}
|
||||
assert_eq!(app.session.active().unwrap().selection.objects, [b], "c is deleted and b stays selected");
|
||||
assert_eq!(frame(&mut app, &ctx, vec![], false).0.len(), 2, "Sub stays closed");
|
||||
}
|
||||
|
||||
/// With Locate Selected Objects on, the object selected in a document that becomes active is
|
||||
/// not located; an object selected after that is. Neither document has highlighted rows, so
|
||||
/// the switch reveals no row of its own.
|
||||
#[test]
|
||||
fn with_the_option_a_document_becoming_active_locates_nothing() {
|
||||
let (mut app, [_, sub, g, b, a, _]) = nested();
|
||||
app.ui.layers_panel.locate_selected = true;
|
||||
app.session.execute("layer.highlight", &json!({"ids": []})).unwrap();
|
||||
app.session.execute("select.set", &json!({"ids": [b.0]})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
frame(&mut app, &ctx, vec![], false);
|
||||
expand(&mut app, &json!({"ids": [sub.0, g.0], "open": false})).unwrap();
|
||||
app.session.execute("file.new", &json!({"width": 100, "height": 100})).unwrap();
|
||||
app.session.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap();
|
||||
frame(&mut app, &ctx, vec![], false);
|
||||
app.session.execute("document.activate", &json!({"index": 0})).unwrap();
|
||||
assert_eq!(frame(&mut app, &ctx, vec![], false).0.len(), 3, "b's sublayer and group stay closed");
|
||||
app.session.execute("select.set", &json!({"ids": [a.0]})).unwrap();
|
||||
assert_eq!(frame(&mut app, &ctx, vec![], false).0.len(), 5, "a's sublayer opens");
|
||||
}
|
||||
|
||||
/// With Locate Selected Objects on, the copies an Alt-drag selects are located once the drag
|
||||
/// ends, not while it lasts.
|
||||
#[test]
|
||||
fn with_the_option_a_selection_made_during_a_drag_is_located_when_it_ends() {
|
||||
let (mut app, [_, sub, g, b, ..]) = nested();
|
||||
app.ui.layers_panel.locate_selected = true;
|
||||
app.session.execute("select.set", &json!({"ids": [b.0]})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
frame(&mut app, &ctx, vec![], false);
|
||||
expand(&mut app, &json!({"ids": [sub.0, g.0], "open": false})).unwrap();
|
||||
app.session.begin_interaction("Move").unwrap();
|
||||
for i in 1..=3 {
|
||||
app.session.preview("object.transform", &json!({"matrix": [1, 0, 0, 1, i * 10, 0], "copy": true})).unwrap();
|
||||
assert_eq!(frame(&mut app, &ctx, vec![], false).0.len(), 3, "nothing opens while the drag lasts");
|
||||
}
|
||||
app.session.commit_interaction().unwrap();
|
||||
let st = app.session.active().unwrap();
|
||||
let copy = st.selection.objects.last().copied().unwrap();
|
||||
assert!(copy != b && st.doc.parent_of(copy) == Some(g), "the copy is selected, in b's group");
|
||||
assert_eq!(frame(&mut app, &ctx, vec![], false).0.len(), 7, "Sub and the group open on the copy's row");
|
||||
}
|
||||
|
||||
/// With Locate Selected Objects on, art selected outside the panel in another layer is
|
||||
/// located: the selection makes that layer the active one, and the panel scrolls to the art's
|
||||
/// row, not to the layer's row.
|
||||
#[test]
|
||||
fn with_the_option_the_located_object_is_revealed_instead_of_its_active_layer() {
|
||||
let (mut app, layer, paths) = groups_of_one(500);
|
||||
app.ui.layers_panel.locate_selected = true;
|
||||
app.session.execute("layer.new", &json!({})).unwrap();
|
||||
assert_ne!(app.session.active().unwrap().current_layer(), Some(layer));
|
||||
let ctx = egui::Context::default();
|
||||
tall_frame(&mut app, &ctx, "");
|
||||
app.session.execute("select.set", &json!({"ids": [paths[0].0]})).unwrap();
|
||||
assert_eq!(app.session.active().unwrap().current_layer(), Some(layer), "the path's layer is the active one");
|
||||
let found = (0..4).any(|_| tall_frame(&mut app, &ctx, "Path 0").1);
|
||||
let st = app.session.active().unwrap();
|
||||
let group = st.doc.parent_of(paths[0]).unwrap();
|
||||
assert!(st.layers_open.contains(group), "the group opens");
|
||||
assert!(found, "the path's row is scrolled into view");
|
||||
}
|
||||
|
||||
/// With Locate Selected Objects and Show Layers Only on, art selected outside the panel has no
|
||||
/// row, so the panel scrolls to the row of the layer that the selection makes the active one.
|
||||
#[test]
|
||||
fn with_the_option_and_show_layers_only_the_active_layer_is_revealed() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.ui.layers_panel.locate_selected = true;
|
||||
app.ui.layers_panel.layers_only = true;
|
||||
let run = |app: &mut VectorcraftApp, id: &str, p: serde_json::Value| app.session.execute(id, &p).unwrap();
|
||||
run(&mut app, "file.new", json!({"width": 100, "height": 100}));
|
||||
let rect = |app: &mut VectorcraftApp| run(app, "shape.rectangle", json!({"x": 0, "y": 0, "width": 5, "height": 5}))["id"].as_u64().unwrap();
|
||||
let bottom = rect(&mut app);
|
||||
for _ in 0..59 {
|
||||
run(&mut app, "layer.new", json!({}));
|
||||
rect(&mut app);
|
||||
}
|
||||
run(&mut app, "select.none", json!({}));
|
||||
let doc = app.session.active().unwrap().doc.clone();
|
||||
let name = doc.node(doc.layer_of(NodeId(bottom)).unwrap()).unwrap().display_name();
|
||||
let ctx = egui::Context::default();
|
||||
assert!(!(0..4).any(|_| tall_frame(&mut app, &ctx, &name).1), "the bottom layer's row is out of view");
|
||||
run(&mut app, "select.set", json!({"ids": [bottom]}));
|
||||
assert!((0..4).any(|_| tall_frame(&mut app, &ctx, &name).1), "the active layer's row is scrolled into view");
|
||||
}
|
||||
|
||||
/// A layer of 2,000 groups of 10 paths with every row open: one frame lays out only the rows
|
||||
/// in view, and a row highlighted far below them is scrolled to.
|
||||
#[test]
|
||||
|
||||
@@ -501,7 +501,7 @@ pub struct UiState {
|
||||
/// becoming active, leaves it.
|
||||
#[serde(skip)]
|
||||
pub home: Option<(Option<u64>, usize)>,
|
||||
/// Layers panel › Panel Options… (row size, thumbnails, Show Layers Only).
|
||||
/// Layers panel › Panel Options… (row size, thumbnails, Show Layers Only, Locate Selected Objects).
|
||||
#[serde(default)]
|
||||
pub layers_panel: crate::panels::layers::PanelOptions,
|
||||
/// The Layers panel's Filter (`ui.layersFilter`). It is not saved and lasts the session, as the
|
||||
|
||||
+5
-1
@@ -1696,8 +1696,12 @@ and `layerRows`, the rows highlighted in the panel.
|
||||
- **Dialogs and panel state** (control channel and the app's MCP): `ui.layerOptions {ids?}` and `ui.newLayer
|
||||
{sublayer?}` open Layer Options (dialog `layerOptions`: name, color, template, locked, visible, printable, preview,
|
||||
dimImages, dimPercent; OK is one undo step), `ui.layersPanelOptions` opens Panel Options (`layersPanelOptions`:
|
||||
layersOnly, rowSize small|medium|large|other, otherSize, thumbLayers, thumbGroups, thumbObjects), and
|
||||
layersOnly, locateSelected, rowSize small|medium|large|other, otherSize, thumbLayers, thumbGroups, thumbObjects), and
|
||||
`ui.layersExpand {ids?, open?}` opens or closes rows as their triangles do (Alt-click: everything inside).
|
||||
With `locateSelected` on (Locate Selected Objects, off by default), when the last selected object changes on the canvas
|
||||
or by a command, the panel opens the layers and groups around it and scrolls to its row, as after `layer.locate`, but
|
||||
does not highlight the row. A change made with the panel's own rows, buttons or menu is not located, and a change
|
||||
made during a drag is located when the drag ends.
|
||||
A document opens with only its top-level layers open; the open rows are saved in the native file and reopen that way.
|
||||
- **Search and filter** (control channel and the app's MCP): the Search All field keeps the rows whose names hold the
|
||||
text typed, and `ui.layersFilter {objects?, layers?}` sets the choices of the Filter button beside it. `objects` (By
|
||||
|
||||
@@ -574,6 +574,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
Ctrl/Cmd+Alt-clicking an eye switches the other layers to Outline and the row's layer to Preview, or switches all layers back to Preview when the others are in Outline (`layer.outlineOthers`, `layer.previewAll`).
|
||||
Object › Arrange › Send to Current Layer returns the error "the target layer is locked or hidden" and leaves the selected objects where they are when the current layer is hidden or locked, or is inside a hidden or locked layer.
|
||||
Selected objects are deselected, the key object included, when a row drag moves them into a hidden layer or group, when `node.move` moves them into a hidden or locked layer or group, when Merge Selected moves them into a hidden or locked layer, when Collect in New Layer moves them into a new layer inside a hidden or locked layer, when Flatten Artwork moves them into a locked layer, and when applying a data set hides them.
|
||||
Panel Options › Locate Selected Objects (beyond Illustrator, off by default): when the last selected object changes on the canvas or by a command, the panel opens the layers and groups around it and scrolls to its row, as Locate Object does, without highlighting the row.
|
||||
- **Drawing aids:** Smart Guides and snapping (moving, with the Selection, Group Selection or Direct Selection tool, and resizing by the bounding-box handles with the Selection or Free Transform tool, Shift and Alt too: the dragged corner or side lands on the other objects' anchors and centres ("anchor", "center") or lines up with their edges and centres (a line and "align"); with Smart Guides › Spacing Guides, a moved selection that lines up with nothing along an axis lands as far from its neighbour as two other objects of its row or column are apart, or halfway between its two neighbours, the equal gaps marked; with View › Snap to Grid on, a moved selection lands on the grid instead, by whichever of its edges or its centre is nearest a gridline on each axis, #740; the Rotate, Scale, Shear and Reflect tools drag from the anchor they grab and snap the pointer the same way, so a corner dragged onto another object's anchor scales exactly onto it, #482; anchors dragged with Direct Selection: the one pressed on lands on other anchors, its own path's that stay put among them, on path segments or in line with them, the others following, Shift keeps 45° steps, and with Smart Guides off Snap to Point pulls it onto anchors; the Artboard tool snaps artboards, and their bleed, to other artboards, bleeds and art, the grid or whole pixels while moving, copying and resizing them, and the Transform panel shows and edits the active artboard's X/Y/W/H; drawn points, #506: hovering shows where the press goes, the point lands on the other objects' anchors, centres and paths ("anchor", "center", "path") or lines up with their anchors, edges and centres ("align"), the Pen's on the path being drawn too, near a construction guide through the anchor before it on that guide (Preferences › Smart Guides › Construction Guides and Angles), and with Shift kept to its 45° step or a square's diagonal it slides along that way into line; the targets are gathered once per document state, from the art in the window, and the anchors, centres and lines near the pointer are found by bisection, so hovering never rescans the document), guides dragged out of the rulers (#451: they snap as moved guides do, Shift to the ruler's ticks; with the Artboard tool they are artboard guides, which run across their artboard only and move, copy and delete with it, `guide.add {artboard}`) that the Selection, Direct Selection and Group Selection tools then pick and drag (#414: the selected guides highlight and move together, Alt copies them, Shift snaps the dragged one to the ticks, otherwise it snaps to pixels, the grid or the edges, centres (so the sides' midpoints) and anchors of the art and the artboards, or with Smart Guides off Snap to Point pulls it onto anchors; Shift-clicked they join the selected art, and Shift-added art keeps them, so guides and art are selected together in either order, Delete then deleting both in one step; the arrow keys nudge them, Delete deletes them, dragging one back onto its ruler deletes it, Lock Guides and Hide Guides leave them alone; `guide.list`, `guide.select`, `guide.move` and `guide.remove` reach agents), a right-click on either ruler or the origin box to pick the document's units (the current one checked; `document.setUnits`, one undo step, #528), and Draw Normal / Behind / Inside modes. Smart Guides: a point drawn with the Pen or a shape tool, and an anchor dragged with Direct Selection, snaps to where paths and ruler guides cross, labelled "intersect" (#1658, part of #1602; a moved selection's own anchors don't).
|
||||
A move that Shift keeps level, upright or diagonal stays on that line when it snaps, with the Selection, Group Selection or Direct Selection tool. It slides along the line until the selection lands on the grid or on whole pixels, lines up with the other objects or is spaced evenly with them, or until the line passes an anchor or crosses a ruler guide near the point the selection was grabbed by. A level or upright move snaps along its own axis only.
|
||||
A bounding-box handle dragged with the Selection or Free Transform tool snaps what it moves as a dragged point snaps (#2054). With View › Snap to Grid on, it snaps to the nearest gridline, and with Snap to Pixel on, it snaps to whole pixels. With Smart Guides off and Snap to Point on, it snaps to the other objects' anchors and the ruler guides within the Snap to Point distance. With Smart Guides and Snap to Point both on, it lines up with the ruler guides as well as the other objects. Shift still keeps the proportions, and Alt still scales about the center.
|
||||
|
||||
Reference in New Issue
Block a user