mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 07:42:08 +00:00
Merge pull request #6172 from dotCooCoo/feat/hold-to-use-tool-keys
Shortcuts: holding a tool's single-key shortcut uses the tool until the key is let go
This commit is contained in:
@@ -152,6 +152,7 @@ pub const PREF_SPECS: &[PrefSpec] = &[
|
||||
p!("zoomWithMouseWheel", "General", "Options", "Zoom with Mouse Wheel", bool),
|
||||
p!("scrubNumericFields", "General", "Options", "Scrub Numeric Fields by Dragging", bool),
|
||||
p!("trackpadRotateView", "General", "Options", "Trackpad Gesture to Rotate View", bool),
|
||||
p!("holdToolKeys", "General", "Options", "Hold a Tool Shortcut to Use the Tool Temporarily", bool),
|
||||
// Selection & Anchor Display
|
||||
p!("selectionTolerance", "Selection & Anchor Display", "Selection", "Tolerance", num(1.0, 8.0, "px")),
|
||||
p!("objectSelectionByPathOnly", "Selection & Anchor Display", "Selection", "Object Selection by Path Only", bool),
|
||||
|
||||
@@ -58,6 +58,9 @@ pub struct Prefs {
|
||||
pub scrub_numeric_fields: bool,
|
||||
/// A two-finger rotation on a Mac trackpad turns the view about the pointer once it reaches 5°.
|
||||
pub trackpad_rotate_view: bool,
|
||||
/// A single-key tool shortcut held down chooses its tool until the key is let go, and then the
|
||||
/// tool active before comes back; a quick press chooses the tool for good.
|
||||
pub hold_tool_keys: bool,
|
||||
/// Offset for Paste / duplicate (Illustrator pastes to the view centre; we offset by this).
|
||||
pub paste_offset: f64,
|
||||
// Selection & Anchor Display
|
||||
@@ -366,6 +369,7 @@ impl Default for Prefs {
|
||||
zoom_with_mouse_wheel: false,
|
||||
scrub_numeric_fields: true,
|
||||
trackpad_rotate_view: true,
|
||||
hold_tool_keys: true,
|
||||
paste_offset: 10.0,
|
||||
selection_tolerance: 3.0,
|
||||
object_selection_by_path_only: false,
|
||||
|
||||
@@ -171,6 +171,7 @@ impl VectorcraftApp {
|
||||
tick_iteration: None,
|
||||
input_frames: vec![],
|
||||
shortcuts_frames: vec![],
|
||||
held_tool: Vec::new(),
|
||||
synthetic_injected: false,
|
||||
injected_events: vec![],
|
||||
injected_generation: InputGeneration::default(),
|
||||
|
||||
@@ -370,8 +370,10 @@ impl VectorcraftApp {
|
||||
|
||||
/// Select a tool (also used by the toolbar and shortcuts).
|
||||
pub fn select_tool(&mut self, id: &str) {
|
||||
// The tip of a pen whose eraser end selected the Eraser doesn't undo this choice.
|
||||
// The tip of a pen whose eraser end selected the Eraser doesn't undo this choice, and
|
||||
// neither does letting go of a held tool shortcut.
|
||||
self.pen_end.forget_tool();
|
||||
self.held_tool.clear();
|
||||
self.activate_tool(id, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -363,6 +363,10 @@ fn end_art_move(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
|
||||
/// Send a pointer event to the active tool and act on UI requests (dialogs, tool switches).
|
||||
pub fn dispatch(app: &mut VectorcraftApp, ev: &PointerEvent, view: vectorcraft_engine::ViewInfo) {
|
||||
// A press lent to a selection tool (Cmd/Ctrl held) does not use the tool of a held key.
|
||||
if ev.kind == PointerKind::Down && app.session.lent_tool_id(&ev.mods).is_none() {
|
||||
crate::shortcuts::note_tool_use(app);
|
||||
}
|
||||
let r = app.session.pointer(ev, view);
|
||||
// The artboard the Artboard tool makes active is the active one (the navigator's, the
|
||||
// Artboards panel's).
|
||||
|
||||
@@ -290,6 +290,11 @@ fn begin_drag(app: &mut VectorcraftApp, ui: &Ui, f: &Pointing, p: Pos2) {
|
||||
crate::document_drag::begin(app, ui.ctx(), ev.pos);
|
||||
Drag::Tool
|
||||
};
|
||||
// A drag of the Zoom, Hand or Rotate View tool uses the tool of a held shortcut; held Space's
|
||||
// view tools are not that tool.
|
||||
if !space && matches!(d, Drag::ZoomBox { .. } | Drag::ZoomScrub { .. } | Drag::Pan { .. } | Drag::RotateView { .. }) {
|
||||
crate::shortcuts::note_tool_use(app);
|
||||
}
|
||||
// Panning, zooming or turning the view takes the pointer from the tool's hover.
|
||||
if !tool_holds(d) {
|
||||
app.session.tool_hover_ended(view);
|
||||
|
||||
@@ -2755,6 +2755,7 @@ digits Malayalam Малаялам
|
||||
Rotate Tool Инструмент „Завъртане“
|
||||
Rotate View Tool Инструмент „Завъртане на изглед“
|
||||
Trackpad Gesture to Rotate View Завъртане на изгледа с жест на тракпада
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Задържане на клавиша на инструмент за временното му използване
|
||||
Rotate… Завъртане…
|
||||
Rotation Завъртане
|
||||
Roughen… Грапавост…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1298,6 +1298,7 @@ lighting Light Svjetlo
|
||||
Rotate Tool Alat za rotaciju
|
||||
Rotate View Tool Alat za rotaciju prikaza
|
||||
Trackpad Gesture to Rotate View Pokret dodirne ploče za rotaciju prikaza
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Držite prečicu alata za privremenu upotrebu alata
|
||||
Rotate View Rotiraj prikaz
|
||||
Rounded Rectangle Tool Alat zaobljeni pravougaonik
|
||||
Scale Tool Alat za skaliranje
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4429,6 +4429,7 @@ digits Malayalam Malaiàlam
|
||||
Tracing Result with Outlines Resultat del traçat amb contorns
|
||||
Tracking Espaiat
|
||||
Trackpad Gesture to Rotate View Gesticulació del trackpad per girar la vista
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Mantén premuda la drecera d’una eina per usar-la temporalment
|
||||
Trademark Symbol Símbol de marca comercial
|
||||
Transform Transforma
|
||||
Transform Again Transforma de nou
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1319,6 +1319,7 @@ lighting Light Světlo
|
||||
Rotate Tool Nástroj Otočení
|
||||
Rotate View Tool Nástroj Otočení zobrazení
|
||||
Trackpad Gesture to Rotate View Otáčet zobrazení gestem na trackpadu
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Podržením klávesové zkratky nástroj dočasně použít
|
||||
Rotate View Otočení zobrazení
|
||||
Rounded Rectangle Tool Nástroj Zaoblený obdélník
|
||||
Scale Tool Nástroj Změna velikosti
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1232,6 +1232,7 @@ lighting Light Lys
|
||||
Rotate Tool Roteringsværktøj
|
||||
Rotate View Tool Visningsroteringsværktøj
|
||||
Trackpad Gesture to Rotate View Rotér visning med bevægelse på pegefeltet
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Hold et værktøjs genvejstast nede for at bruge værktøjet midlertidigt
|
||||
Rotate View Rotér visning
|
||||
Rounded Rectangle Tool Afrundet rektangelværktøj
|
||||
Scale Tool Skaleringsværktøj
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2783,6 +2783,7 @@ digits Malayalam Malayalam
|
||||
Rotate Tool Drehen-Werkzeug
|
||||
Rotate View Tool Ansichtdrehung-Werkzeug
|
||||
Trackpad Gesture to Rotate View Ansicht mit Trackpad-Geste drehen
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Werkzeug-Tastaturbefehl gedrückt halten, um das Werkzeug vorübergehend zu verwenden
|
||||
Rotate… Drehen…
|
||||
Rotation Drehung
|
||||
Roughen… Aufrauen…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1895,6 +1895,7 @@ font-technology Variable Μεταβλητές
|
||||
Rotate Περιστροφή
|
||||
Rotate View Tool Εργαλείο περιστροφής προβολής (Rotate View Tool)
|
||||
Trackpad Gesture to Rotate View Περιστροφή προβολής με χειρονομία στην επιφάνεια αφής
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Προσωρινή χρήση εργαλείου κρατώντας πατημένη τη συντόμευσή του
|
||||
Rotate… Περιστροφή…
|
||||
Rotation Περιστροφή
|
||||
Rows: Γραμμές:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 168 and column 5.
|
@@ -2825,6 +2825,7 @@ digits Malayalam Malayálam
|
||||
Rotate Tool Herramienta Rotar
|
||||
Rotate View Tool Herramienta Rotar vista
|
||||
Trackpad Gesture to Rotate View Rotar vista con un gesto del trackpad
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Mantener pulsado el atajo de una herramienta para usarla temporalmente
|
||||
Rotate… Rotar…
|
||||
Rotation Rotación
|
||||
Roughen… Hacer áspero…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1295,6 +1295,7 @@ lighting Light Valo
|
||||
Rotate Tool Kiertotyökalu
|
||||
Rotate View Tool Näkymän kiertotyökalu
|
||||
Trackpad Gesture to Rotate View Kierrä näkymää ohjauslevyn eleellä
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Käytä työkalua tilapäisesti pitämällä sen pikanäppäintä painettuna
|
||||
Rotate View Kierrä näkymää
|
||||
Rounded Rectangle Tool Pyöristetyn suorakulmion työkalu
|
||||
Scale Tool Skaalaustyökalu
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2817,6 +2817,7 @@ digits Malayalam Malayalam
|
||||
Rotate Tool Outil Rotation
|
||||
Rotate View Tool Outil Faire pivoter l’affichage
|
||||
Trackpad Gesture to Rotate View Faire pivoter l’affichage avec un geste du trackpad
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Maintenir le raccourci d’un outil pour l’utiliser temporairement
|
||||
Rotate… Rotation…
|
||||
Rotation Rotation
|
||||
Roughen… Rugosité…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4833,6 +4833,7 @@
|
||||
Tracing the colors… रंग अनुरेखण…
|
||||
Tracking ट्रैकिंग
|
||||
Trackpad Gesture to Rotate View दृश्य घुमाने के लिए ट्रैकपैड जेस्चर
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily किसी टूल का शॉर्टकट दबाए रखकर उसे अस्थायी रूप से उपयोग करें
|
||||
Trademark Symbol ट्रेडमार्क सिंबल
|
||||
Transform रूपांतरण करें
|
||||
Transform Again फिर से बदलना
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -3392,6 +3392,7 @@ mark-style Japanese Japán
|
||||
Rotate View Nézet elforgatása
|
||||
Rotate View Tool Nézet elforgatása
|
||||
Trackpad Gesture to Rotate View Nézet elforgatása kézmozdulattal az érintőpadon
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Eszköz ideiglenes használata a billentyűparancsa nyomva tartásával
|
||||
Rotate… Elforgatás…
|
||||
Rotation Elforgatás
|
||||
Rotation relative to: Elforgatás viszonyítási alapja:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1232,6 +1232,7 @@ lighting Light Cahaya
|
||||
Rotate Tool Alat putar
|
||||
Rotate View Tool Alat putar tampilan
|
||||
Trackpad Gesture to Rotate View Putar tampilan dengan gestur trackpad
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Tahan pintasan alat untuk menggunakan alat itu sementara
|
||||
Rotate View Putar tampilan
|
||||
Rounded Rectangle Tool Alat persegi panjang bulat
|
||||
Scale Tool Alat skala
|
||||
|
||||
|
@@ -2859,6 +2859,7 @@ digits Malayalam Malayalam
|
||||
Rotate Tool Strumento Ruota
|
||||
Rotate View Tool Strumento Ruota vista
|
||||
Trackpad Gesture to Rotate View Ruota vista con un gesto sul trackpad
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Tieni premuta la scorciatoia di uno strumento per usarlo temporaneamente
|
||||
Rotate… Ruota…
|
||||
Rotation Rotazione
|
||||
Roughen… Irruvidisci…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2816,6 +2816,7 @@ mark-style Japanese 日本式
|
||||
Rotate Tool 回転ツール
|
||||
Rotate View Tool 回転ビューツール
|
||||
Trackpad Gesture to Rotate View トラックパッドのジェスチャーでビューを回転
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily ツールのショートカットを押している間だけツールを使用
|
||||
Rotation 回転
|
||||
Round Cap 丸型線端
|
||||
Round Join ラウンド結合
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5523,6 +5523,7 @@ imageTrace Outline კონტური
|
||||
The name in the save panel was “{typed}”; VectorCraft added the file type's extension. Replacing the file overwrites its contents. შენახვის პანელში სახელი იყო „{typed}“; VectorCraft-მა ფაილის ტიპის გაფართოება დაამატა. ფაილის ჩანაცვლება მის შიგთავსს გადააწერს.
|
||||
Thread Text ტექსტის დაკავშირება
|
||||
Trackpad Gesture to Rotate View ხედის შემობრუნება ტრეკპადის ჟესტით
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily ხელსაწყოს დროებით გამოყენება მისი მალსახმობის დაჭერით
|
||||
Typing აკრეფა
|
||||
Uniformly Scaled თანაბრად მასშტაბირებული
|
||||
Unlock Variables ცვლადების განბლოკვა
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -2805,6 +2805,7 @@ mark-style Japanese 일본식
|
||||
Rotate Tool 회전 도구
|
||||
Rotate View Tool 보기 회전 도구
|
||||
Trackpad Gesture to Rotate View 트랙패드 제스처로 보기 회전
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily 도구 단축키를 누르고 있는 동안 도구를 임시로 사용
|
||||
Rotation 회전
|
||||
Round Cap 둥근 끝
|
||||
Round Join 둥근 연결
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1246,6 +1246,7 @@ lighting Light Licht
|
||||
Rotate Tool Draaien-gereedschap
|
||||
Rotate View Tool Weergave draaien-gereedschap
|
||||
Trackpad Gesture to Rotate View Weergave draaien met trackpadgebaar
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Sneltoets van een gereedschap ingedrukt houden om het tijdelijk te gebruiken
|
||||
Rotate View Weergave draaien
|
||||
Rounded Rectangle Tool Afgeronde rechthoek-gereedschap
|
||||
Scale Tool Schalen-gereedschap
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1241,6 +1241,7 @@ lighting Light Światło
|
||||
Rotate Tool Narzędzie Obrót
|
||||
Rotate View Tool Narzędzie Obrót widoku
|
||||
Trackpad Gesture to Rotate View Obracaj widok gestem na gładziku
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Używaj narzędzia tymczasowo, przytrzymując jego skrót
|
||||
Rounded Rectangle Tool Narzędzie Zaokrąglony prostokąt
|
||||
Scale Tool Narzędzie Skalowanie
|
||||
Scallop Tool Narzędzie Ząbkowanie
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2360,6 +2360,7 @@ digits Malayalam Malaiala
|
||||
Rotate Tool Ferramenta Girar
|
||||
Rotate View Tool Ferramenta Girar Visualização
|
||||
Trackpad Gesture to Rotate View Girar Visualização com Gesto do Trackpad
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Manter o Atalho de uma Ferramenta Pressionado para Usá-la Temporariamente
|
||||
Rotate… Girar…
|
||||
Rotation Rotação
|
||||
Roughen… Enrugar…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2854,6 +2854,7 @@ digits Malayalam Малаялам
|
||||
Rotate Tool Инструмент «Поворот»
|
||||
Rotate View Tool Инструмент «Поворот вида»
|
||||
Trackpad Gesture to Rotate View Поворачивать вид жестом трекпада
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Временно использовать инструмент, удерживая его клавишу
|
||||
Rotate… Повернуть…
|
||||
Rotation Поворот
|
||||
Roughen… Огрубление…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2761,6 +2761,7 @@ digits Malayalam Malayalam
|
||||
Rotate Tool Rotationsverktyg
|
||||
Rotate View Tool Verktyget Rotera vy
|
||||
Trackpad Gesture to Rotate View Rotera vyn med en gest på styrplattan
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Håll ned ett verktygs kortkommando för att använda verktyget tillfälligt
|
||||
Rotate… Rotera…
|
||||
Rotation Rotation
|
||||
Roughen… Ojämn…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -5440,6 +5440,7 @@ theme Appearance Görünüm
|
||||
The name in the save panel was “{typed}”; VectorCraft added the file type's extension. Replacing the file overwrites its contents. Kayıt panelindeki ad “{typed}” idi; VectorCraft dosya türünün uzantısını ekledi. Dosyayı değiştirmek içeriğinin üzerine yazar.
|
||||
Thread Text Metni Bağla
|
||||
Trackpad Gesture to Rotate View Görünümü Döndürmek İçin İzleme Dörtgeni Hareketi
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Aracı Geçici Olarak Kullanmak İçin Kısayolunu Basılı Tutma
|
||||
Typing Yazma
|
||||
Uniformly Scaled Tekdüze Ölçeklenmiş
|
||||
Unlock Variables Değişkenlerin Kilidini Aç
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -2851,6 +2851,7 @@ digits Malayalam Малаялам
|
||||
Rotate Tool Інструмент «Поворот»
|
||||
Rotate View Tool Інструмент «Поворот виду»
|
||||
Trackpad Gesture to Rotate View Повертати вид жестом трекпада
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Тимчасово використовувати інструмент, утримуючи його клавішу
|
||||
Rotate… Повернути…
|
||||
Rotation Поворот
|
||||
Roughen… Надати нерівності…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 182 and column 5.
|
@@ -3147,6 +3147,7 @@ mark-style Japanese Kiểu Nhật
|
||||
Rotate Tool Công cụ Xoay
|
||||
Rotate View Tool Công cụ Xoay chế độ xem
|
||||
Trackpad Gesture to Rotate View Xoay chế độ xem bằng cử chỉ trên bàn di chuột
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily Giữ phím tắt của công cụ để tạm thời dùng công cụ đó
|
||||
Rotate… Xoay…
|
||||
Rotation Góc xoay
|
||||
Rotation relative to: Xoay tương đối với:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 204 and column 5.
|
@@ -2728,6 +2728,7 @@ digits Malayalam 马拉雅拉姆数字
|
||||
Rotate Tool 旋转工具
|
||||
Rotate View Tool 旋转视图工具
|
||||
Trackpad Gesture to Rotate View 使用触控板手势旋转视图
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily 按住工具快捷键以临时使用该工具
|
||||
Rotate… 旋转…
|
||||
Rotation 旋转
|
||||
Roughen… 粗糙效果…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3724,6 +3724,7 @@ digits Malayalam 馬來亞拉姆數字
|
||||
Rotate Tool 旋轉工具
|
||||
Rotate View Tool 旋轉檢視工具
|
||||
Trackpad Gesture to Rotate View 使用觸控式軌跡板手勢旋轉檢視
|
||||
Hold a Tool Shortcut to Use the Tool Temporarily 按住工具快速鍵以暫時使用該工具
|
||||
Rotate… 旋轉…
|
||||
Rotation 旋轉
|
||||
Roughen… 粗糙效果…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -315,6 +315,9 @@ pub struct VectorcraftApp {
|
||||
/// The context, viewport and frame `shortcuts::handle` already took in this host iteration.
|
||||
/// Interleaving root and auxiliary viewport passes cannot make an older frame look new again.
|
||||
pub(crate) shortcuts_frames: Vec<InputFrame>,
|
||||
/// The single-key tool shortcuts held down, the last held last, and the tool each goes back to
|
||||
/// when it is let go ([`shortcuts::HeldTool`]).
|
||||
pub(crate) held_tool: Vec<shortcuts::HeldTool>,
|
||||
/// A synthetic batch is in the host's raw input and no settled UI pass has taken it yet.
|
||||
synthetic_injected: bool,
|
||||
/// The exact events of [`Self::synthetic_injected`], to remove them if the state they belong
|
||||
|
||||
@@ -11,10 +11,12 @@ use vectorcraft_tools::{Mods, SurroundingUnit, ToolKey};
|
||||
|
||||
use crate::VectorcraftApp;
|
||||
|
||||
mod held_tool;
|
||||
mod paste;
|
||||
mod presentation;
|
||||
mod typing;
|
||||
|
||||
pub(crate) use held_tool::{HeldTool, note_tool_use};
|
||||
use paste::unclip_chords;
|
||||
pub(crate) use paste::{PasteChord, paste_command};
|
||||
pub(crate) use presentation::presentation_step;
|
||||
@@ -186,7 +188,10 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
}
|
||||
ctx.input(|i| crate::shortcut_editor::note_backtick_text(&i.events));
|
||||
note_key_surfaces(app, ctx);
|
||||
if !(ctx.input(|input| input.focused) || app.forwarded_frame && ctx.viewport_id() == egui::ViewportId::ROOT) {
|
||||
let focused = ctx.input(|input| input.focused) || app.forwarded_frame && ctx.viewport_id() == egui::ViewportId::ROOT;
|
||||
// A held tool shortcut that is let go returns to the tool active before it.
|
||||
held_tool::follow(app, ctx, focused);
|
||||
if !focused {
|
||||
crate::ime::controls_without_editor(app, ctx);
|
||||
return;
|
||||
}
|
||||
@@ -317,6 +322,7 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
ctx.input_mut(|i| i.consume_key(Modifiers::NONE, Key::Enter));
|
||||
}
|
||||
}
|
||||
held_tool::note_enter_dialog(app, k);
|
||||
}
|
||||
}
|
||||
if typing {
|
||||
@@ -480,26 +486,26 @@ fn remember_escape_pair(ctx: &egui::Context, key: Key, escape_pair: Option<bool>
|
||||
fn single_key_shortcuts(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
// Single-key tool shortcuts (no Cmd/Ctrl/Alt). With a layout without Latin letters (Persian,
|
||||
// Arabic, Russian, Greek…) the key's position stands in for what it types (#793).
|
||||
// A text's key is the press that typed it ([`typing_presses`]).
|
||||
let events: Vec<(String, Modifiers, Option<Key>)> = ctx.input(|i| {
|
||||
// A text's key is the press that typed it ([`typing_presses`]), and the text repeats with it.
|
||||
let events: Vec<(String, Modifiers, Option<Key>, bool)> = ctx.input(|i| {
|
||||
let typed_by = typing_presses(&i.events);
|
||||
i.events
|
||||
.iter()
|
||||
.zip(typed_by)
|
||||
.filter_map(|(e, by)| match e {
|
||||
egui::Event::Text(t) => {
|
||||
let (physical, key) = match by.and_then(|k| i.events.get(k)) {
|
||||
Some(egui::Event::Key { key, physical_key, .. }) => (*physical_key, Some(*key)),
|
||||
_ => (None, None),
|
||||
let (physical, key, repeat) = match by.and_then(|k| i.events.get(k)) {
|
||||
Some(egui::Event::Key { key, physical_key, repeat, .. }) => (*physical_key, Some(*key), *repeat),
|
||||
_ => (None, None, false),
|
||||
};
|
||||
Some((latin(t, physical), i.modifiers, key))
|
||||
Some((latin(t, physical), i.modifiers, key, repeat))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
let keymap = crate::shortcut_editor::current();
|
||||
for (text, m, pressed) in events {
|
||||
for (text, m, pressed, repeat) in events {
|
||||
if m.command || m.alt || m.ctrl {
|
||||
continue;
|
||||
}
|
||||
@@ -521,7 +527,7 @@ fn single_key_shortcuts(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
// tools.
|
||||
let Some(hit) = hits.iter().find(|c| !c.id.starts_with("tool:")).or(hits.first()) else { continue };
|
||||
match hit.id.strip_prefix("tool:") {
|
||||
Some(tool) => app.select_tool(tool),
|
||||
Some(tool) => held_tool::choose(app, ctx, tool, pressed, repeat),
|
||||
None if let Some((id, p)) = menu_target(hit) => crate::menus::invoke(app, &id, p),
|
||||
None if hit.params.is_null() => {
|
||||
let _ = app.run(&hit.id, json!({}));
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
//! A single-key tool shortcut held down (General › Hold a Tool Shortcut to Use the Tool
|
||||
//! Temporarily, `holdToolKeys`): the tool it chose stays until the key is let go, and then the tool
|
||||
//! active before comes back. A quick press chooses the tool for good.
|
||||
|
||||
use super::{Key, VectorcraftApp};
|
||||
|
||||
/// A key let go this many seconds or more after its press returns to the tool active before it.
|
||||
/// VectorCraft's own value.
|
||||
pub(crate) const HOLD_SECONDS: f64 = 0.25;
|
||||
|
||||
/// A tool shortcut's key held down since it chose its tool.
|
||||
pub(crate) struct HeldTool {
|
||||
/// The key that chose the tool.
|
||||
key: Key,
|
||||
/// The viewport that saw the press: other viewports do not see the key.
|
||||
viewport: egui::ViewportId,
|
||||
/// The tool the key chose.
|
||||
tool: &'static str,
|
||||
/// The tool to go back to.
|
||||
previous: &'static str,
|
||||
/// When the key went down (`egui::InputState::time`).
|
||||
since: f64,
|
||||
/// The tool took a drag, a click or a dialog while the key was down.
|
||||
used: bool,
|
||||
/// When a frame first saw the key let go. The decision can come in a later frame (after a
|
||||
/// drag, a dialog or a press of the same frame), and it uses this time.
|
||||
released: Option<f64>,
|
||||
}
|
||||
|
||||
/// Chooses `tool` for a press of its single-key shortcut `key` (`None`: no key press typed it). A
|
||||
/// press that changes the tool, with the preference on, starts a hold. A hold started while another
|
||||
/// key is held goes on top of it: letting go of the later key returns to the earlier key's tool,
|
||||
/// and letting go of the earlier key then returns to the tool before both. With the preference on,
|
||||
/// a key's repeats choose nothing: a key held down chooses its tool once. With it off, each repeat
|
||||
/// chooses the tool again.
|
||||
pub(super) fn choose(app: &mut VectorcraftApp, ctx: &egui::Context, tool: &str, key: Option<Key>, repeat: bool) {
|
||||
if repeat && app.session.prefs.hold_tool_keys {
|
||||
return;
|
||||
}
|
||||
let previous = app.session.tool_id();
|
||||
// `select_tool` ends the holds of a tool chosen any other way; the keys held stay held here.
|
||||
let holds = std::mem::take(&mut app.held_tool);
|
||||
app.select_tool(tool);
|
||||
app.held_tool = holds;
|
||||
let chosen = app.session.tool_id();
|
||||
if chosen == previous {
|
||||
return;
|
||||
}
|
||||
let viewport = ctx.viewport_id();
|
||||
let (down, now) = key.map_or((false, 0.0), |k| ctx.input(|i| (i.key_down(k), i.time)));
|
||||
match key {
|
||||
// A key let go in the frame that pressed it was a quick press.
|
||||
Some(key) if down && app.session.prefs.hold_tool_keys && chosen == tool => {
|
||||
app.held_tool.retain(|h| h.viewport == viewport && h.key != key);
|
||||
app.held_tool.push(HeldTool { key, viewport, tool: chosen, previous, since: now, used: false, released: None });
|
||||
}
|
||||
_ => app.held_tool.clear(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A press that reaches the active tool, a drag of the Zoom, Hand or Rotate View tool, or the
|
||||
/// tool's dialog opened with Enter, uses the tool of the key held last when that tool is active,
|
||||
/// also when that and the key's release come between two frames of [`follow`].
|
||||
pub(crate) fn note_tool_use(app: &mut VectorcraftApp) {
|
||||
let active = app.session.tool_id();
|
||||
if let Some(held) = app.held_tool.last_mut().filter(|h| h.tool == active) {
|
||||
held.used = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// After the tool takes `key`: a dialog that Enter left open is the tool's, so it uses the tool.
|
||||
pub(super) fn note_enter_dialog(app: &mut VectorcraftApp, key: Key) {
|
||||
if key == Key::Enter && app.ui.dialog.is_some() {
|
||||
note_tool_use(app);
|
||||
}
|
||||
}
|
||||
|
||||
/// Once per frame of the viewport that saw the press: when the key held last is let go, goes back
|
||||
/// to the tool active before it, unless the press was quick and the tool was not used. A key let
|
||||
/// go during a drag, with the button held or with a dialog open, goes back once that ends. Choosing
|
||||
/// another tool, a quick press, turning the preference off or the window losing the keyboard ends
|
||||
/// every hold without going back. A frame of another viewport that has the keyboard also ends them.
|
||||
pub(super) fn follow(app: &mut VectorcraftApp, ctx: &egui::Context, focused: bool) {
|
||||
// Every key let go is timed in the frame that first sees it up, under the key held last too.
|
||||
let (viewport, time) = (ctx.viewport_id(), ctx.input(|i| i.time));
|
||||
for held in app.held_tool.iter_mut().filter(|h| h.viewport == viewport && h.released.is_none()) {
|
||||
if !ctx.input(|i| i.key_down(held.key)) {
|
||||
held.released = Some(time);
|
||||
}
|
||||
}
|
||||
// Each pass looks at the key held last; one that goes back hands over to the key under it.
|
||||
loop {
|
||||
let gesture = app.session.tool_busy() || app.session.pointer_held();
|
||||
// A dialog, or a canvas drag that does not reach the tool (a pan, zoom or turn of the view),
|
||||
// defers the decision.
|
||||
let dialog = app.ui.dialog.is_some() || crate::canvas::dragging(ctx);
|
||||
let active = app.session.tool_id();
|
||||
let on = app.session.prefs.hold_tool_keys;
|
||||
let Some(held) = app.held_tool.last_mut() else { return };
|
||||
if !on {
|
||||
app.held_tool.clear();
|
||||
return;
|
||||
}
|
||||
if held.viewport != ctx.viewport_id() {
|
||||
// This viewport has the keyboard, so the viewport that saw the press lost it.
|
||||
if focused {
|
||||
app.held_tool.clear();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if !focused {
|
||||
app.held_tool.clear();
|
||||
return;
|
||||
}
|
||||
let (down, now) = ctx.input(|i| (i.key_down(held.key), i.time));
|
||||
if gesture || dialog {
|
||||
// A gesture of the held tool with the key down uses the tool; a gesture lent to a
|
||||
// selection tool does not. A dialog uses it only when Enter opened it, and a view drag
|
||||
// when it is the held tool's ([`note_tool_use`]).
|
||||
held.used |= gesture && down && active == held.tool;
|
||||
return;
|
||||
}
|
||||
if active != held.tool {
|
||||
app.held_tool.clear();
|
||||
return;
|
||||
}
|
||||
if down {
|
||||
return;
|
||||
}
|
||||
// The canvas takes a pointer press of the same frame, and the tool an Enter press, after
|
||||
// this: the decision waits for the next frame, measured from this one.
|
||||
let pressed = ctx.input(|i| {
|
||||
i.events
|
||||
.iter()
|
||||
.any(|e| matches!(e, egui::Event::PointerButton { pressed: true, .. } | egui::Event::Key { key: Key::Enter, pressed: true, .. }))
|
||||
});
|
||||
let released = held.released.unwrap_or(now);
|
||||
if pressed && released == now {
|
||||
ctx.request_repaint();
|
||||
return;
|
||||
}
|
||||
let back = held.used || released - held.since >= HOLD_SECONDS;
|
||||
let previous = held.previous;
|
||||
if !back {
|
||||
app.held_tool.clear();
|
||||
return;
|
||||
}
|
||||
app.held_tool.pop();
|
||||
app.return_to_tool(previous);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -0,0 +1,462 @@
|
||||
use serde_json::json;
|
||||
use vectorcraft_tools::{PointerEvent, PointerKind};
|
||||
|
||||
use super::super::handle;
|
||||
use super::*;
|
||||
|
||||
/// A keyboard on one context, so egui keeps the keys held down from frame to frame and marks a
|
||||
/// held key's later presses as repeats, with the frame times the test sets.
|
||||
struct Keyboard {
|
||||
ctx: egui::Context,
|
||||
}
|
||||
|
||||
impl Keyboard {
|
||||
fn new() -> Self {
|
||||
Self { ctx: egui::Context::default() }
|
||||
}
|
||||
|
||||
/// One frame at `time` seconds delivering `events`; with `field`, a text field that takes the
|
||||
/// keyboard is drawn after the shortcuts.
|
||||
fn frame(&self, app: &mut VectorcraftApp, time: f64, events: Vec<egui::Event>, field: Option<&mut String>) {
|
||||
self.frame_with_focus(app, time, events, field, true);
|
||||
}
|
||||
|
||||
/// [`Self::frame`], with the window having the keyboard (`focused`) or not.
|
||||
fn frame_with_focus(&self, app: &mut VectorcraftApp, time: f64, events: Vec<egui::Event>, mut field: Option<&mut String>, focused: bool) {
|
||||
let screen_rect = Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(800.0, 600.0)));
|
||||
let mut out = self.ctx.run_ui(egui::RawInput { events, screen_rect, time: Some(time), focused, ..Default::default() }, |ui| {
|
||||
app.logic(ui.ctx());
|
||||
handle(app, ui.ctx());
|
||||
if let Some(text) = field.as_deref_mut() {
|
||||
ui.add(egui::TextEdit::singleline(text).id(egui::Id::unique("held-tool-field"))).request_focus();
|
||||
}
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
}
|
||||
|
||||
/// One frame at `time` seconds delivering `events`, with the canvas drawn after the shortcuts.
|
||||
fn on_canvas(&self, app: &mut VectorcraftApp, time: f64, events: Vec<egui::Event>) {
|
||||
let screen_rect = Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(800.0, 600.0)));
|
||||
let mut out = self.ctx.run_ui(egui::RawInput { events, screen_rect, time: Some(time), focused: true, ..Default::default() }, |ui| {
|
||||
app.logic(ui.ctx());
|
||||
handle(app, ui.ctx());
|
||||
crate::canvas::show(app, ui);
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
}
|
||||
|
||||
/// `key` pressed (or repeated while it is down) at `time`, typing `letter`.
|
||||
fn press(&self, app: &mut VectorcraftApp, time: f64, key: Key, letter: &str) {
|
||||
let press = egui::Event::Key { key, physical_key: Some(key), pressed: true, repeat: false, modifiers: egui::Modifiers::NONE };
|
||||
self.frame(app, time, vec![press, egui::Event::Text(letter.into())], None);
|
||||
}
|
||||
|
||||
fn release(&self, app: &mut VectorcraftApp, time: f64, key: Key) {
|
||||
let release = egui::Event::Key { key, physical_key: Some(key), pressed: false, repeat: false, modifiers: egui::Modifiers::NONE };
|
||||
self.frame(app, time, vec![release], None);
|
||||
}
|
||||
|
||||
fn press_s(&self, app: &mut VectorcraftApp, time: f64) {
|
||||
self.press(app, time, Key::S, "s");
|
||||
}
|
||||
|
||||
fn release_s(&self, app: &mut VectorcraftApp, time: f64) {
|
||||
self.release(app, time, Key::S);
|
||||
}
|
||||
}
|
||||
|
||||
/// A document with a selected rectangle and the Pen tool chosen.
|
||||
fn pen_app() -> VectorcraftApp {
|
||||
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
|
||||
crate::shortcut_editor::sync(&app.ui);
|
||||
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
|
||||
app.run("shape.rectangle", json!({"x": 100, "y": 100, "width": 100, "height": 50})).unwrap();
|
||||
app.select_tool("pen");
|
||||
app
|
||||
}
|
||||
|
||||
/// S held from 0 s, its repeats at `repeats`, let go at `release`.
|
||||
fn hold_s(app: &mut VectorcraftApp, keys: &Keyboard, repeats: &[f64], release: f64) {
|
||||
keys.press_s(app, 0.0);
|
||||
assert_eq!(app.session.tool_id(), "scale", "the press chooses Scale");
|
||||
for &t in repeats {
|
||||
keys.press_s(app, t);
|
||||
assert_eq!(app.session.tool_id(), "scale", "a repeat at {t} s keeps Scale");
|
||||
}
|
||||
keys.release_s(app, release);
|
||||
}
|
||||
|
||||
/// A quick press of S chooses Scale for good.
|
||||
#[test]
|
||||
fn a_quick_press_of_a_tool_key_chooses_the_tool_for_good() {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
hold_s(&mut app, &keys, &[], HOLD_SECONDS / 2.0);
|
||||
assert_eq!(app.session.tool_id(), "scale");
|
||||
keys.frame(&mut app, 5.0, vec![], None);
|
||||
assert_eq!(app.session.tool_id(), "scale", "Scale stays chosen");
|
||||
assert!(app.held_tool.is_empty());
|
||||
}
|
||||
|
||||
/// A press and release of S in one frame is a quick press, also when the next frame comes long
|
||||
/// after it.
|
||||
#[test]
|
||||
fn a_press_and_release_in_one_frame_chooses_the_tool_for_good() {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
let key = |pressed| egui::Event::Key { key: Key::S, physical_key: Some(Key::S), pressed, repeat: false, modifiers: egui::Modifiers::NONE };
|
||||
keys.frame(&mut app, 0.0, vec![key(true), egui::Event::Text("s".into()), key(false)], None);
|
||||
keys.frame(&mut app, 5.0, vec![], None);
|
||||
assert_eq!(app.session.tool_id(), "scale");
|
||||
}
|
||||
|
||||
/// S held past the threshold, with its key repeats, and let go returns to the Pen.
|
||||
#[test]
|
||||
fn a_held_tool_key_returns_to_the_previous_tool_when_let_go() {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
hold_s(&mut app, &keys, &[0.1, 0.2, 0.3, 0.4], 0.5);
|
||||
assert_eq!(app.session.tool_id(), "pen");
|
||||
assert!(app.held_tool.is_empty());
|
||||
}
|
||||
|
||||
/// S held from the Pen and then V held: letting go of V returns to Scale, and letting go of S
|
||||
/// then returns to the Pen.
|
||||
#[test]
|
||||
fn overlapping_held_tool_keys_return_one_at_a_time() {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
keys.press_s(&mut app, 0.0);
|
||||
keys.press(&mut app, 0.1, Key::V, "v");
|
||||
assert_eq!(app.session.tool_id(), "selection");
|
||||
keys.release(&mut app, 0.4, Key::V);
|
||||
assert_eq!(app.session.tool_id(), "scale", "letting go of V returns to Scale while S is down");
|
||||
keys.release_s(&mut app, 0.6);
|
||||
assert_eq!(app.session.tool_id(), "pen", "letting go of S returns to the Pen");
|
||||
assert!(app.held_tool.is_empty());
|
||||
}
|
||||
|
||||
/// S let go during a Scale drag returns to the Pen once the drag ends, also when the key was let go
|
||||
/// before the threshold.
|
||||
#[test]
|
||||
fn a_tool_key_let_go_during_a_drag_returns_after_the_drag() {
|
||||
for release in [HOLD_SECONDS / 2.0, 1.0] {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
keys.press_s(&mut app, 0.0);
|
||||
let view = app.view_info();
|
||||
app.session.pointer(&PointerEvent::new(PointerKind::Down, 200.0, 150.0), view).unwrap();
|
||||
app.session.pointer(&PointerEvent::new(PointerKind::Drag, 260.0, 190.0), view).unwrap();
|
||||
assert!(app.session.tool_busy(), "Scale is dragging");
|
||||
keys.frame(&mut app, release / 2.0, vec![], None);
|
||||
keys.release_s(&mut app, release);
|
||||
assert_eq!(app.session.tool_id(), "scale", "let go at {release} s: Scale stays during the drag");
|
||||
keys.frame(&mut app, release + 0.1, vec![], None);
|
||||
assert_eq!(app.session.tool_id(), "scale", "let go at {release} s: still dragging");
|
||||
app.session.pointer(&PointerEvent::new(PointerKind::Up, 260.0, 190.0), view).unwrap();
|
||||
keys.frame(&mut app, release + 0.2, vec![], None);
|
||||
assert_eq!(app.session.tool_id(), "pen", "let go at {release} s: the Pen is back after the drag");
|
||||
}
|
||||
}
|
||||
|
||||
/// A click on the canvas between two frames while S is down uses Scale, so letting go of S before
|
||||
/// the threshold returns to the Pen.
|
||||
#[test]
|
||||
fn a_click_between_frames_counts_as_using_the_held_tool() {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
keys.press_s(&mut app, 0.0);
|
||||
let view = app.view_info();
|
||||
for kind in [PointerKind::Down, PointerKind::Up] {
|
||||
crate::canvas::dispatch(&mut app, &PointerEvent::new(kind, 150.0, 125.0), view);
|
||||
}
|
||||
keys.release_s(&mut app, HOLD_SECONDS / 2.0);
|
||||
assert_eq!(app.session.tool_id(), "pen");
|
||||
}
|
||||
|
||||
/// A Cmd/Ctrl click while S is down goes to the selection tool lent to Scale, so it does not use
|
||||
/// Scale: letting go of S before the threshold keeps Scale, whether or not a frame comes during
|
||||
/// the click.
|
||||
#[test]
|
||||
fn a_click_lent_to_a_selection_tool_does_not_use_the_held_tool() {
|
||||
for frame_during_click in [false, true] {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
keys.press_s(&mut app, 0.0);
|
||||
let mods = vectorcraft_tools::Mods { cmd: true, ..Default::default() };
|
||||
assert!(app.session.lent_tool_id(&mods).is_some(), "Scale lends a selection tool to a Cmd press");
|
||||
let view = app.view_info();
|
||||
let event = |kind| PointerEvent { mods, ..PointerEvent::new(kind, 150.0, 125.0) };
|
||||
crate::canvas::dispatch(&mut app, &event(PointerKind::Down), view);
|
||||
if frame_during_click {
|
||||
keys.frame(&mut app, HOLD_SECONDS / 4.0, vec![], None);
|
||||
}
|
||||
crate::canvas::dispatch(&mut app, &event(PointerKind::Up), view);
|
||||
keys.release_s(&mut app, HOLD_SECONDS / 2.0);
|
||||
assert_eq!(app.session.tool_id(), "scale", "frame during the click: {frame_during_click}");
|
||||
assert!(app.held_tool.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
/// A pan with the Hand tool while H is down uses the Hand: letting go of H quickly after the pan
|
||||
/// returns to the Pen, and letting go of H during the pan returns once the pan ends.
|
||||
#[test]
|
||||
fn a_view_tool_drag_counts_as_using_the_held_tool() {
|
||||
for let_go_during_pan in [false, true] {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
keys.on_canvas(&mut app, 0.0, vec![]);
|
||||
let at = app.canvas_rect.expect("the canvas was drawn").center();
|
||||
let to = at + egui::vec2(30.0, 20.0);
|
||||
let button =
|
||||
|pos, pressed| egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: egui::Modifiers::NONE };
|
||||
let key = |pressed| egui::Event::Key { key: Key::H, physical_key: Some(Key::H), pressed, repeat: false, modifiers: egui::Modifiers::NONE };
|
||||
keys.on_canvas(&mut app, 0.01, vec![egui::Event::PointerMoved(at), key(true), egui::Event::Text("h".into())]);
|
||||
assert_eq!(app.session.tool_id(), "hand", "let go during the pan: {let_go_during_pan}");
|
||||
keys.on_canvas(&mut app, 0.02, vec![button(at, true)]);
|
||||
keys.on_canvas(&mut app, 0.03, vec![egui::Event::PointerMoved(to)]);
|
||||
assert!(crate::canvas::dragging(&keys.ctx), "the Hand pans");
|
||||
if let_go_during_pan {
|
||||
keys.on_canvas(&mut app, 0.05, vec![key(false)]);
|
||||
assert_eq!(app.session.tool_id(), "hand", "the Hand stays during the pan");
|
||||
keys.on_canvas(&mut app, 0.06, vec![button(to, false)]);
|
||||
} else {
|
||||
keys.on_canvas(&mut app, 0.05, vec![button(to, false)]);
|
||||
keys.on_canvas(&mut app, 0.06, vec![key(false)]);
|
||||
}
|
||||
keys.on_canvas(&mut app, 0.07, vec![]);
|
||||
assert_eq!(app.session.tool_id(), "pen", "let go during the pan: {let_go_during_pan}");
|
||||
assert!(app.held_tool.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
/// A pointer press in the frame that lets go of S: a press that reaches Scale on the canvas returns
|
||||
/// to the Pen, and a press elsewhere (no canvas press follows) keeps Scale, also when the next frame
|
||||
/// comes late.
|
||||
#[test]
|
||||
fn a_press_in_the_frame_that_lets_go_waits_for_the_canvas() {
|
||||
for canvas in [true, false] {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
keys.press_s(&mut app, 0.0);
|
||||
let at = egui::pos2(150.0, 125.0);
|
||||
let press = egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed: true, modifiers: egui::Modifiers::NONE };
|
||||
let release = egui::Event::Key { key: Key::S, physical_key: Some(Key::S), pressed: false, repeat: false, modifiers: egui::Modifiers::NONE };
|
||||
keys.frame(&mut app, 0.1, vec![egui::Event::PointerMoved(at), press, release], None);
|
||||
assert_eq!(app.session.tool_id(), "scale", "canvas {canvas}: no decision before the canvas");
|
||||
if canvas {
|
||||
let view = app.view_info();
|
||||
for kind in [PointerKind::Down, PointerKind::Up] {
|
||||
crate::canvas::dispatch(&mut app, &PointerEvent::new(kind, 150.0, 125.0), view);
|
||||
}
|
||||
}
|
||||
keys.frame(&mut app, 2.0, vec![], None);
|
||||
assert_eq!(app.session.tool_id(), if canvas { "pen" } else { "scale" }, "canvas {canvas}");
|
||||
}
|
||||
}
|
||||
|
||||
/// S and then V pressed quickly, both let go in the frame of a canvas click with the Selection tool:
|
||||
/// V was used, so Scale comes back, and S was a quick press, so Scale stays, also when the next
|
||||
/// frame comes late.
|
||||
#[test]
|
||||
fn keys_let_go_together_are_timed_in_the_frame_that_lets_them_go() {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
keys.press_s(&mut app, 0.0);
|
||||
keys.press(&mut app, 0.02, Key::V, "v");
|
||||
let at = egui::pos2(150.0, 125.0);
|
||||
let up = |key| egui::Event::Key { key, physical_key: Some(key), pressed: false, repeat: false, modifiers: egui::Modifiers::NONE };
|
||||
let press = egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed: true, modifiers: egui::Modifiers::NONE };
|
||||
keys.frame(&mut app, 0.05, vec![egui::Event::PointerMoved(at), press, up(Key::V), up(Key::S)], None);
|
||||
let view = app.view_info();
|
||||
for kind in [PointerKind::Down, PointerKind::Up] {
|
||||
crate::canvas::dispatch(&mut app, &PointerEvent::new(kind, 150.0, 125.0), view);
|
||||
}
|
||||
keys.frame(&mut app, 2.0, vec![], None);
|
||||
assert_eq!(app.session.tool_id(), "scale");
|
||||
assert!(app.held_tool.is_empty());
|
||||
}
|
||||
|
||||
/// With S held and then V held, a click with the Selection tool uses Selection only: V let go
|
||||
/// quickly returns to Scale, and S let go quickly then keeps Scale.
|
||||
#[test]
|
||||
fn a_click_uses_only_the_tool_of_the_key_held_last() {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
keys.press_s(&mut app, 0.0);
|
||||
keys.press(&mut app, 0.02, Key::V, "v");
|
||||
let view = app.view_info();
|
||||
for kind in [PointerKind::Down, PointerKind::Up] {
|
||||
crate::canvas::dispatch(&mut app, &PointerEvent::new(kind, 150.0, 125.0), view);
|
||||
}
|
||||
keys.release(&mut app, 0.05, Key::V);
|
||||
assert_eq!(app.session.tool_id(), "scale", "V was used, so Scale is back");
|
||||
keys.release_s(&mut app, 0.1);
|
||||
assert_eq!(app.session.tool_id(), "scale", "Scale was not used, so it stays");
|
||||
}
|
||||
|
||||
/// Enter with S down opens the Scale dialog, and with V down the Move dialog; the key let go quickly
|
||||
/// while the dialog is open returns to the Pen once the dialog closes.
|
||||
#[test]
|
||||
fn a_dialog_opened_during_the_hold_counts_as_using_the_tool() {
|
||||
for (key, letter, tool, dialog) in [(Key::S, "s", "scale", "scale"), (Key::V, "v", "selection", "move")] {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
keys.press(&mut app, 0.0, key, letter);
|
||||
let enter =
|
||||
egui::Event::Key { key: Key::Enter, physical_key: Some(Key::Enter), pressed: true, repeat: false, modifiers: egui::Modifiers::NONE };
|
||||
keys.frame(&mut app, 0.05, vec![enter], None);
|
||||
assert_eq!(app.ui.dialog.as_ref().map(|d| d.kind.as_str()), Some(dialog), "Enter opens the {dialog} dialog");
|
||||
keys.release(&mut app, 0.1, key);
|
||||
assert_eq!(app.session.tool_id(), tool, "{tool} stays while its dialog is open");
|
||||
crate::dialogs::cancel(&mut app);
|
||||
keys.frame(&mut app, 0.15, vec![], None);
|
||||
assert_eq!(app.session.tool_id(), "pen", "after the {dialog} dialog");
|
||||
}
|
||||
}
|
||||
|
||||
/// Enter pressed in the frame that lets go of S after the threshold opens the Scale dialog, and
|
||||
/// Scale stays until the dialog closes.
|
||||
#[test]
|
||||
fn enter_in_the_frame_that_lets_go_reaches_the_held_tool() {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
keys.press_s(&mut app, 0.0);
|
||||
let enter = egui::Event::Key { key: Key::Enter, physical_key: Some(Key::Enter), pressed: true, repeat: false, modifiers: egui::Modifiers::NONE };
|
||||
let release = egui::Event::Key { key: Key::S, physical_key: Some(Key::S), pressed: false, repeat: false, modifiers: egui::Modifiers::NONE };
|
||||
keys.frame(&mut app, 1.0, vec![release, enter], None);
|
||||
assert_eq!(app.ui.dialog.as_ref().map(|d| d.kind.as_str()), Some("scale"), "Enter opens the Scale dialog");
|
||||
keys.frame(&mut app, 1.1, vec![], None);
|
||||
assert_eq!(app.session.tool_id(), "scale", "Scale stays while its dialog is open");
|
||||
crate::dialogs::cancel(&mut app);
|
||||
keys.frame(&mut app, 1.2, vec![], None);
|
||||
assert_eq!(app.session.tool_id(), "pen");
|
||||
}
|
||||
|
||||
/// Another dialog (Preferences) opened while S is down and S let go quickly while it is open: Scale
|
||||
/// was not used, so it stays once the dialog closes.
|
||||
#[test]
|
||||
fn another_dialog_opened_during_the_hold_does_not_use_the_tool() {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
keys.press_s(&mut app, 0.0);
|
||||
crate::prefs_dialog::open(&mut app, None);
|
||||
keys.frame(&mut app, 0.05, vec![], None);
|
||||
keys.release_s(&mut app, 0.1);
|
||||
crate::dialogs::cancel(&mut app);
|
||||
keys.frame(&mut app, 0.15, vec![], None);
|
||||
assert_eq!(app.session.tool_id(), "scale");
|
||||
}
|
||||
|
||||
/// S typed and held in a text field types its letter and chooses no tool.
|
||||
#[test]
|
||||
fn a_tool_key_held_in_a_text_field_types_and_keeps_the_tool() {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
let mut text = String::new();
|
||||
keys.frame(&mut app, 0.0, vec![], Some(&mut text));
|
||||
let press = egui::Event::Key { key: Key::S, physical_key: Some(Key::S), pressed: true, repeat: false, modifiers: egui::Modifiers::NONE };
|
||||
let release = egui::Event::Key { key: Key::S, physical_key: Some(Key::S), pressed: false, repeat: false, modifiers: egui::Modifiers::NONE };
|
||||
keys.frame(&mut app, 0.1, vec![press.clone(), egui::Event::Text("s".into())], Some(&mut text));
|
||||
keys.frame(&mut app, 0.6, vec![press, egui::Event::Text("s".into())], Some(&mut text));
|
||||
keys.frame(&mut app, 1.0, vec![release], Some(&mut text));
|
||||
assert_eq!(text, "ss");
|
||||
assert_eq!(app.session.tool_id(), "pen");
|
||||
assert!(app.held_tool.is_empty());
|
||||
}
|
||||
|
||||
/// With General › Hold a Tool Shortcut to Use the Tool Temporarily off, a held S chooses Scale for
|
||||
/// good, as a quick press does.
|
||||
#[test]
|
||||
fn with_the_preference_off_a_held_tool_key_chooses_the_tool_for_good() {
|
||||
let mut app = pen_app();
|
||||
app.run("prefs.set", json!({"key": "holdToolKeys", "value": false})).unwrap();
|
||||
let keys = Keyboard::new();
|
||||
hold_s(&mut app, &keys, &[0.1, 0.2, 0.3, 0.4], 0.5);
|
||||
assert_eq!(app.session.tool_id(), "scale");
|
||||
}
|
||||
|
||||
/// With the preference off, each repeat of a held S chooses Scale again, as before: a flyout opened
|
||||
/// in between closes.
|
||||
#[test]
|
||||
fn with_the_preference_off_each_repeat_chooses_the_tool_again() {
|
||||
let mut app = pen_app();
|
||||
app.run("prefs.set", json!({"key": "holdToolKeys", "value": false})).unwrap();
|
||||
let keys = Keyboard::new();
|
||||
keys.press_s(&mut app, 0.0);
|
||||
app.ui.flyout = Some(0);
|
||||
keys.press_s(&mut app, 0.3);
|
||||
assert_eq!(app.ui.flyout, None, "the repeat chose Scale again");
|
||||
assert_eq!(app.session.tool_id(), "scale");
|
||||
}
|
||||
|
||||
/// A hold whose viewport stopped drawing ends when another viewport has the keyboard or the
|
||||
/// preference is turned off, and stays while the other viewport does not have the keyboard.
|
||||
#[test]
|
||||
fn a_hold_of_another_viewport_ends_when_this_one_has_the_keyboard() {
|
||||
let other = egui::ViewportId::from_hash_of("held-tool-other");
|
||||
let hold = || HeldTool { key: Key::S, viewport: other, tool: "scale", previous: "pen", since: 0.0, used: false, released: None };
|
||||
let mut app = pen_app();
|
||||
app.select_tool("scale");
|
||||
let keys = Keyboard::new();
|
||||
app.held_tool.push(hold());
|
||||
keys.frame_with_focus(&mut app, 0.5, vec![], None, false);
|
||||
assert_eq!(app.held_tool.len(), 1, "a viewport without the keyboard leaves the hold alone");
|
||||
keys.frame(&mut app, 1.0, vec![], None);
|
||||
assert!(app.held_tool.is_empty(), "a viewport with the keyboard ends the hold");
|
||||
assert_eq!(app.session.tool_id(), "scale");
|
||||
app.held_tool.push(hold());
|
||||
app.run("prefs.set", json!({"key": "holdToolKeys", "value": false})).unwrap();
|
||||
keys.frame_with_focus(&mut app, 1.5, vec![], None, false);
|
||||
assert!(app.held_tool.is_empty(), "turning the preference off ends the hold");
|
||||
assert_eq!(app.session.tool_id(), "scale");
|
||||
}
|
||||
|
||||
/// Scale chosen again on the toolbar while S is held stays chosen when S is let go.
|
||||
#[test]
|
||||
fn the_held_tool_chosen_again_stays_when_the_key_is_let_go() {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
keys.press_s(&mut app, 0.0);
|
||||
app.select_tool("scale");
|
||||
// A tool chosen closes an open flyout: the repeats of S, still down, choose nothing.
|
||||
app.ui.flyout = Some(0);
|
||||
keys.press_s(&mut app, 0.3);
|
||||
assert_eq!(app.ui.flyout, Some(0), "a repeat chooses no tool");
|
||||
keys.release_s(&mut app, 0.5);
|
||||
assert_eq!(app.session.tool_id(), "scale");
|
||||
}
|
||||
|
||||
/// The window losing the keyboard while S is held, also during a drag, ends the hold: Scale stays.
|
||||
#[test]
|
||||
fn losing_the_keyboard_ends_the_hold() {
|
||||
for drag in [false, true] {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
keys.press_s(&mut app, 0.0);
|
||||
let view = app.view_info();
|
||||
if drag {
|
||||
app.session.pointer(&PointerEvent::new(PointerKind::Down, 200.0, 150.0), view).unwrap();
|
||||
app.session.pointer(&PointerEvent::new(PointerKind::Drag, 260.0, 190.0), view).unwrap();
|
||||
}
|
||||
keys.frame_with_focus(&mut app, 0.3, vec![egui::Event::WindowFocused(false)], None, false);
|
||||
if drag {
|
||||
app.session.pointer(&PointerEvent::new(PointerKind::Up, 260.0, 190.0), view).unwrap();
|
||||
}
|
||||
keys.frame(&mut app, 0.5, vec![egui::Event::WindowFocused(true)], None);
|
||||
keys.release_s(&mut app, 0.6);
|
||||
assert_eq!(app.session.tool_id(), "scale", "drag {drag}: Scale stays");
|
||||
}
|
||||
}
|
||||
|
||||
/// Another tool chosen while S is held stays chosen when S is let go.
|
||||
#[test]
|
||||
fn a_tool_chosen_during_the_hold_stays_when_the_key_is_let_go() {
|
||||
let mut app = pen_app();
|
||||
let keys = Keyboard::new();
|
||||
keys.press_s(&mut app, 0.0);
|
||||
app.select_tool("rectangle");
|
||||
keys.frame(&mut app, 0.3, vec![], None);
|
||||
keys.release_s(&mut app, 0.5);
|
||||
assert_eq!(app.session.tool_id(), "rectangle");
|
||||
}
|
||||
+5
-1
@@ -3637,7 +3637,11 @@ control channel's `ui.drag` scrubs), `showHomeScreen` (on by default: the Home s
|
||||
off, an empty window, and the Home button or `app.home` still shows the screen), `autoCollapseIconPanels` (off by
|
||||
default: on, a click away from a panel popped out of the icon column puts it away), `displayPrintSize` (off by
|
||||
default: View › Actual Size / `view.actualSize` is one document point per screen point; on, one document inch fills
|
||||
96 screen points, an inch at the system's reference density whatever the display scaling). `highlightAlternateGlyphs` (off by default: on, the canvas paints a pink cell behind glyphs OpenType features
|
||||
96 screen points, an inch at the system's reference density whatever the display scaling). The tool shortcut
|
||||
preference `holdToolKeys` also needs the desktop app. It is on by default: a single-key tool shortcut held for 0.25 s
|
||||
or more, or held while its tool is used, chooses the tool until the key is let go and then returns to the tool active
|
||||
before, after any drag in progress; a quicker press chooses the tool for good. With it off, every press and every key
|
||||
repeat chooses the tool. `highlightAlternateGlyphs` (off by default: on, the canvas paints a pink cell behind glyphs OpenType features
|
||||
substituted — ligatures, contextual forms — on screen; Document Setup's Highlight Substituted Glyphs stays
|
||||
separate). `highlightSubstitutedFonts` (on by default: the canvas paints pink behind text drawn in a substitute for a
|
||||
missing font, in every document). `blackOnScreen`
|
||||
|
||||
@@ -876,7 +876,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
Composition's leading-space reduction stays along the line when a character rotates. An opening bracket trimmed by Line-end Punctuation Half Width Mojikumi keeps that placement under Character Rotation; its turning point follows the composition adjustment, and removing the rotation keeps the trim. Scores and estimates are unchanged.
|
||||
A character rotated near or at a quarter turn follows a Touch Type drag across its line as well as along it: baseline shift moves a character up from its line at any rotation, and the character turns about its shifted pen position.
|
||||
Right-to-left characters can also move along their line when a preceding logical character is on that line. Positive `text.touchType` `dx` follows reading order, toward the left in horizontal right-to-left text; the first logical character still moves only up and down. These direction rules are VectorCraft's choices; the public Touch Type page describes moving a character without specifying this case. Scores and estimates are unchanged.
|
||||
- **Temporary tools:** Cmd with any other tool drags with the selection tool used last (Selection, Direct Selection or Group Selection). Held Space pans and Cmd+Space zooms (Cmd+Alt+Space out) whatever the tool.
|
||||
- **Temporary tools:** Cmd with any other tool drags with the selection tool used last (Selection, Direct Selection or Group Selection). Held Space pans and Cmd+Space zooms (Cmd+Alt+Space out) whatever the tool. A single-key tool shortcut held for 0.25 s or more, or held while its tool is used, chooses that tool only until the key is let go, and the tool active before comes back (after a drag in progress ends); a quicker press chooses the tool for good. This is VectorCraft's own rule and its own threshold, and General › Hold a Tool Shortcut to Use the Tool Temporarily (`holdToolKeys`, on by default) turns it off.
|
||||
Space held while the Zoom tool drags a marquee moves the marquee and keeps its size ([Default keyboard shortcuts](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html)). When Space is let go, the drag sizes the marquee again from where it moved to.
|
||||
A Space+Shift drag rotates the view whatever the tool, as a Rotate View tool drag does, and Shift does not round the angle to 15°.
|
||||
- **Swatches panel clicks:** Ctrl/Cmd-clicking New Swatch makes a spot color, and Ctrl/Cmd+Shift-clicking it makes a global process color.
|
||||
|
||||
Reference in New Issue
Block a user