Actions: Playback Options plays actions accelerated, step by step or with a pause between steps

The Actions panel menu's Playback Options... sets how actions play (ui.action.playbackOptions {speed?, seconds?}). Accelerated, the default, plays every step at once as one undo step, as before. Step by Step plays one step per frame so the canvas draws each step's result, and Pause For waits 1 to 60 seconds between steps; each paced step is its own undo step. Stop Playing/Recording ends a paced playback, and another action does not play while one is paced. The setting is kept with the UI preferences. Batch... plays at full speed.
This commit is contained in:
dotCooCoo
2026-11-04 20:12:13 +07:00
committed by bflatastic
parent 33b57fb581
commit 9d8be643f4
36 changed files with 590 additions and 12 deletions
+4
View File
@@ -348,6 +348,10 @@ impl VectorcraftApp {
// The timer and heartbeat run in an idle window too.
ctx.request_repaint_after(std::time::Duration::from_secs_f64(wait));
}
// An action playing at Step by Step or Pause For runs its next step when it is due.
if let Some(wait) = crate::panels::actions::tick_playback(self, now) {
ctx.request_repaint_after(std::time::Duration::from_secs_f64(wait));
}
crate::keymap::note_os(ctx);
shortcut_editor::sync(&self.ui);
crate::panels::actions::sync_function_keys(&self.ui);
+2
View File
@@ -9,6 +9,7 @@
mod about;
pub mod action_batch;
pub mod action_new;
pub mod action_playback;
pub mod action_set_options;
pub mod artboard_options;
pub mod blend_options;
@@ -365,6 +366,7 @@ registry! {
ActionBatch: [action_batch::KIND] => action_batch::SPEC,
ActionSetOptions: [action_set_options::KIND] => action_set_options::SPEC,
ActionNew: [action_new::KIND] => action_new::SPEC,
ActionPlayback: [action_playback::KIND] => action_playback::SPEC,
SliceOptions: [slices::OPTIONS] => slices::OPTIONS_SPEC,
DivideSlices: [slices::DIVIDE] => slices::DIVIDE_SPEC,
EpsOptions: [eps_options::KIND] => eps_options::SPEC,
@@ -0,0 +1,48 @@
//! Actions panel menu › Playback Options…: the speed actions play at (`ui.action.playbackOptions`).
//!
//! Fields: `speed` (`accelerated`, `step` or `pause`) and `seconds`, the pause between steps with
//! `pause`. OK sets them; an invalid value is an error, and the dialog stays open.
use serde_json::{Value, json};
use super::DialogSpec;
use crate::state::Dialog;
use crate::{VectorcraftApp, widgets};
/// The dialog kind of Playback Options.
pub const KIND: &str = "actionPlayback";
pub(super) const SPEC: DialogSpec = DialogSpec { heading: |_| tl!("Playback Options").into(), body, confirm, min_width: 260.0, ..DialogSpec::FORM };
/// Open Playback Options with the speed and pause actions play with now.
pub fn open(app: &mut VectorcraftApp) {
let o = app.ui.action_playback;
app.ui.dialog = Some(Dialog::new(KIND, json!({"speed": o.speed, "seconds": o.seconds})));
}
fn body(_app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
let speed = d.str("speed");
for (value, label) in [("accelerated", tl!("Accelerated")), ("step", tl!("Step by Step"))] {
if widgets::radio(ui, label, speed == value, true) {
d.fields.insert("speed".into(), json!(value));
}
}
ui.horizontal(|ui| {
let pause = tl!("Pause For");
if widgets::radio(ui, pause, speed == "pause", true) {
d.fields.insert("speed".into(), json!("pause"));
}
let range = f64::from(*crate::panels::actions::PAUSE_SECONDS.start())..=f64::from(*crate::panels::actions::PAUSE_SECONDS.end());
if let Some(n) = widgets::range_field_named(ui, "action-pause-seconds", Some(pause), d.f64("seconds", *range.start()), range, "s", 0, 50.0) {
d.fields.insert("seconds".into(), json!(n as u64));
}
});
false
}
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
let params = json!({"speed": d.str("speed"), "seconds": d.fields.get("seconds").cloned().unwrap_or(Value::Null)});
let r = app.run(crate::panels::actions::PLAYBACK_OPTIONS, params)?;
app.ui.dialog = None;
Ok(r)
}
+8
View File
@@ -5502,6 +5502,9 @@ theme Appearance Външен вид
Movie Clip Филмов клип
@msg name is empty името е празно
@msg an action is already recording вече се записва действие
@msg an action is already playing вече се възпроизвежда действие
@msg speed must be accelerated, step or pause speed трябва да е accelerated, step или pause
@msg seconds must be a whole number from 1 to 60 seconds трябва да е цяло число от 1 до 60
@msg no such function key няма такъв функционален клавиш
@msg no such color няма такъв цвят
@msg {_1} cannot be used on Windows {_1} не може да се използва в Windows
@@ -5652,6 +5655,11 @@ axis Y Axis Y ос
Set Options Опции на набора
Action Options… Опции на действието…
Action Options Опции на действието
Playback Options… Опции за възпроизвеждане…
Playback Options Опции за възпроизвеждане
Accelerated Ускорено
Step by Step Стъпка по стъпка
Pause For Пауза за
Rename Action Преименуване на действие
New Action… Ново действие…
New Action Ново действие
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5506,6 +5506,9 @@ mark-style Japanese Japanski
Movie Clip Filmski klip
@msg name is empty naziv je prazan
@msg an action is already recording akcija se već snima
@msg an action is already playing akcija se već reproducira
@msg speed must be accelerated, step or pause speed mora biti accelerated, step ili pause
@msg seconds must be a whole number from 1 to 60 seconds mora biti cijeli broj od 1 do 60
@msg no such function key nema takve funkcijske tipke
@msg no such color nema takve boje
@msg {_1} cannot be used on Windows {_1} se ne može koristiti na Windowsu
@@ -5675,6 +5678,11 @@ axis Y Axis Y osa
Set Options Opcije skupa
Action Options… Opcije akcije…
Action Options Opcije akcije
Playback Options… Opcije reprodukcije…
Playback Options Opcije reprodukcije
Accelerated Ubrzano
Step by Step Korak po korak
Pause For Pauza od
Rename Action Preimenuj akciju
New Action… Nova akcija…
New Action Nova akcija
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -3791,6 +3791,11 @@ digits Malayalam Malaiàlam
Set Options Defineix les opcions
Action Options… Opcions de l’acció…
Action Options Opcions de l’acció
Playback Options… Opcions de reproducció…
Playback Options Opcions de reproducció
Accelerated Accelerada
Step by Step Pas a pas
Pause For Pausa de
Rename Action Canvia el nom de l’acció
New Action… Acció nova…
New Action Acció nova
@@ -5187,6 +5192,9 @@ digits Malayalam Malaiàlam
@msg Multichannel PSD files can't be read: save it in RGB, CMYK or Grayscale mode Els fitxers PSD multicanal no es poden llegir: deseu-los en mode RGB, CMYK o escala de grisos
@msg name is empty el nom és buit
@msg an action is already recording ja s’està enregistrant una acció
@msg an action is already playing ja s’està reproduint una acció
@msg speed must be accelerated, step or pause speed ha de ser accelerated, step o pause
@msg seconds must be a whole number from 1 to 60 seconds ha de ser un nombre enter de l’1 al 60
@msg no such function key no existeix aquesta tecla de funció
@msg no such color no existeix aquest color
@msg {_1} cannot be used on Windows {_1} no es pot fer servir al Windows
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5592,6 +5592,9 @@ mark-style Japanese Japonské
Movie Clip Filmový klip
@msg name is empty název je prázdný
@msg an action is already recording akce se už zaznamenává
@msg an action is already playing akce se už přehrává
@msg speed must be accelerated, step or pause speed musí být accelerated, step nebo pause
@msg seconds must be a whole number from 1 to 60 seconds musí být celé číslo od 1 do 60
@msg no such function key taková funkční klávesa neexistuje
@msg no such color taková barva neexistuje
@msg {_1} cannot be used on Windows Zkratku {_1} nelze ve Windows použít
@@ -5766,6 +5769,11 @@ axis Y Axis osa Y
Set Options Možnosti sady
Action Options… Možnosti akce…
Action Options Možnosti akce
Playback Options… Možnosti přehrávání…
Playback Options Možnosti přehrávání
Accelerated Zrychleně
Step by Step Krok za krokem
Pause For Pozastavit na
Rename Action Přejmenovat akci
New Action… Nová akce…
New Action Nová akce
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5387,6 +5387,9 @@ swatch Bright Violet Kraftig violet
Movie Clip Filmklip
@msg name is empty navnet er tomt
@msg an action is already recording en handling optages allerede
@msg an action is already playing en handling afspilles allerede
@msg speed must be accelerated, step or pause speed skal være accelerated, step eller pause
@msg seconds must be a whole number from 1 to 60 seconds skal være et heltal fra 1 til 60
@msg no such function key ingen sådan funktionstast
@msg no such color ingen sådan farve
@msg {_1} cannot be used on Windows {_1} kan ikke bruges i Windows
@@ -5669,6 +5672,11 @@ axis Y Axis Y-akse
Set Options Indstillinger for sæt
Action Options… Indstillinger for handling…
Action Options Indstillinger for handling
Playback Options… Afspilningsindstillinger…
Playback Options Afspilningsindstillinger
Accelerated Accelereret
Step by Step Trin for trin
Pause For Pause på
Rename Action Omdøb handling
New Action… Ny handling…
New Action Ny handling
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5170,6 +5170,9 @@ effect Soft Weich
Movie Clip Movieclip
@msg name is empty Name ist leer
@msg an action is already recording eine Aktion wird bereits aufgezeichnet
@msg an action is already playing eine Aktion wird bereits abgespielt
@msg speed must be accelerated, step or pause speed muss accelerated, step oder pause sein
@msg seconds must be a whole number from 1 to 60 seconds muss eine ganze Zahl von 1 bis 60 sein
@msg no such function key keine solche Funktionstaste
@msg no such color keine solche Farbe
@msg {_1} cannot be used on Windows {_1} kann unter Windows nicht verwendet werden
@@ -5348,6 +5351,11 @@ axis Y Axis Y-Achse
Set Options Satzoptionen
Action Options… Aktionsoptionen…
Action Options Aktionsoptionen
Playback Options… Wiedergabeoptionen…
Playback Options Wiedergabeoptionen
Accelerated Beschleunigt
Step by Step Schrittweise
Pause For Pause von
Rename Action Aktion umbenennen
New Action… Neue Aktion…
New Action Neue Aktion
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5543,6 +5543,9 @@ effect Points Σημεία
Movie Clip Κλιπ ταινίας
@msg name is empty το όνομα είναι κενό
@msg an action is already recording μια ενέργεια καταγράφεται ήδη
@msg an action is already playing μια ενέργεια αναπαράγεται ήδη
@msg speed must be accelerated, step or pause το speed πρέπει να είναι accelerated, step ή pause
@msg seconds must be a whole number from 1 to 60 το seconds πρέπει να είναι ακέραιος από 1 έως 60
@msg no such function key δεν υπάρχει τέτοιο πλήκτρο λειτουργίας
@msg no such color δεν υπάρχει τέτοιο χρώμα
@msg {_1} cannot be used on Windows Το {_1} δεν μπορεί να χρησιμοποιηθεί στα Windows
@@ -5717,6 +5720,11 @@ axis Y Axis Άξονας Y
Set Options Επιλογές συνόλου
Action Options… Επιλογές ενέργειας…
Action Options Επιλογές ενέργειας
Playback Options… Επιλογές αναπαραγωγής…
Playback Options Επιλογές αναπαραγωγής
Accelerated Επιταχυμένα
Step by Step Βήμα προς βήμα
Pause For Παύση για
Rename Action Μετονομασία ενέργειας
New Action… Νέα ενέργεια…
New Action Νέα ενέργεια
Can't render this file because it contains an unexpected character in line 163 and column 5.
+8
View File
@@ -5533,6 +5533,9 @@ mark-style Japanese Japonesas
Movie Clip Clip de película
@msg name is empty el nombre está vacío
@msg an action is already recording ya se está grabando una acción
@msg an action is already playing ya se está reproduciendo una acción
@msg speed must be accelerated, step or pause speed debe ser accelerated, step o pause
@msg seconds must be a whole number from 1 to 60 seconds debe ser un número entero de 1 a 60
@msg no such function key no existe esa tecla de función
@msg no such color no existe ese color
@msg {_1} cannot be used on Windows {_1} no se puede usar en Windows
@@ -5707,6 +5710,11 @@ axis Y Axis Eje Y
Set Options Opciones de conjunto
Action Options… Opciones de acción…
Action Options Opciones de acción
Playback Options… Opciones de reproducción…
Playback Options Opciones de reproducción
Accelerated Acelerada
Step by Step Paso a paso
Pause For Pausa de
Rename Action Cambiar nombre de acción
New Action… Nueva acción…
New Action Nueva acción
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5555,6 +5555,9 @@ mark-style Japanese Japanilainen
Movie Clip Elokuvaleike
@msg name is empty nimi on tyhjä
@msg an action is already recording toimintoa nauhoitetaan jo
@msg an action is already playing toimintoa toistetaan jo
@msg speed must be accelerated, step or pause speed on oltava accelerated, step tai pause
@msg seconds must be a whole number from 1 to 60 seconds on oltava kokonaisluku 1:stä 60:een
@msg no such function key toimintonäppäintä ei ole
@msg no such color väriä ei ole
@msg {_1} cannot be used on Windows {_1} ei ole käytettävissä Windowsissa
@@ -5796,6 +5799,11 @@ axis Y Axis Y-akseli
Set Options Sarjan asetukset
Action Options… Toiminnon asetukset…
Action Options Toiminnon asetukset
Playback Options… Toistoasetukset…
Playback Options Toistoasetukset
Accelerated Nopeutettu
Step by Step Vaihe kerrallaan
Pause For Tauko
Rename Action Nimeä toiminto uudelleen
New Action… Uusi toiminto…
New Action Uusi toiminto
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5587,6 +5587,9 @@ theme Appearance Apparence
Movie Clip Clip
@msg name is empty le nom est vide
@msg an action is already recording un script est déjà en cours d’enregistrement
@msg an action is already playing un script est déjà en cours d’exécution
@msg speed must be accelerated, step or pause speed doit valoir accelerated, step ou pause
@msg seconds must be a whole number from 1 to 60 seconds doit être un nombre entier de 1 à 60
@msg no such function key touche de fonction inexistante
@msg no such color couleur inexistante
@msg {_1} cannot be used on Windows {_1} ne peut pas être utilisé sous Windows
@@ -5761,6 +5764,11 @@ axis Y Axis Axe Y
Set Options Options du jeu
Action Options… Options de script…
Action Options Options de script
Playback Options… Options d’exécution…
Playback Options Options d’exécution
Accelerated Accélérée
Step by Step Pas à pas
Pause For Pause de
Rename Action Renommer le script
New Action… Nouveau script…
New Action Nouveau script
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5575,6 +5575,9 @@ imageTrace Outline Körvonal
Movie Clip Mozgóklip
@msg name is empty a név üres
@msg an action is already recording már folyik egy művelet felvétele
@msg an action is already playing már folyik egy művelet lejátszása
@msg speed must be accelerated, step or pause a speed értéke accelerated, step vagy pause lehet
@msg seconds must be a whole number from 1 to 60 a seconds értéke 1 és 60 közötti egész szám lehet
@msg no such function key nincs ilyen funkcióbillentyű
@msg no such color nincs ilyen szín
@msg {_1} cannot be used on Windows A(z) {_1} nem használható Windows rendszeren
@@ -5749,6 +5752,11 @@ axis Y Axis Y tengely
Set Options Készlet beállításai
Action Options… Művelet beállításai…
Action Options Művelet beállításai
Playback Options… Lejátszási beállítások…
Playback Options Lejátszási beállítások
Accelerated Gyorsított
Step by Step Lépésenként
Pause For Szünet
Rename Action Művelet átnevezése
New Action… Új művelet…
New Action Új művelet
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5387,6 +5387,9 @@ swatch Bright Violet Violet cerah
Movie Clip Klip film
@msg name is empty nama kosong
@msg an action is already recording sebuah tindakan sedang direkam
@msg an action is already playing sebuah tindakan sedang diputar
@msg speed must be accelerated, step or pause speed harus accelerated, step, atau pause
@msg seconds must be a whole number from 1 to 60 seconds harus bilangan bulat dari 1 sampai 60
@msg no such function key tidak ada tombol fungsi seperti itu
@msg no such color tidak ada warna seperti itu
@msg {_1} cannot be used on Windows {_1} tidak dapat digunakan di Windows
@@ -5669,6 +5672,11 @@ axis Y Axis Sumbu Y
Set Options Opsi set
Action Options… Opsi tindakan…
Action Options Opsi tindakan
Playback Options… Opsi pemutaran…
Playback Options Opsi pemutaran
Accelerated Dipercepat
Step by Step Langkah demi langkah
Pause For Jeda selama
Rename Action Ganti nama tindakan
New Action… Tindakan baru…
New Action Tindakan baru
Can't render this file because it contains an unexpected character in line 2230 and column 5.
+8
View File
@@ -5205,6 +5205,9 @@ lighting Light Luce
Movie Clip Clip filmato
@msg name is empty il nome è vuoto
@msg an action is already recording è già in corso la registrazione di un’azione
@msg an action is already playing è già in corso la riproduzione di un’azione
@msg speed must be accelerated, step or pause speed deve essere accelerated, step o pause
@msg seconds must be a whole number from 1 to 60 seconds deve essere un numero intero da 1 a 60
@msg no such function key tasto funzione inesistente
@msg no such color colore inesistente
@msg {_1} cannot be used on Windows {_1} non può essere usato su Windows
@@ -5382,6 +5385,11 @@ axis Y Axis Asse Y
Set Options Opzioni set
Action Options… Opzioni azione…
Action Options Opzioni azione
Playback Options… Opzioni di riproduzione…
Playback Options Opzioni di riproduzione
Accelerated Accelerata
Step by Step Passo per passo
Pause For Pausa di
Rename Action Rinomina azione
New Action… Nuova azione…
New Action Nuova azione
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5248,6 +5248,9 @@ lighting Light ライト
Movie Clip ムービークリップ
@msg name is empty 名前が空です
@msg an action is already recording すでにアクションを記録中です
@msg an action is already playing すでにアクションを再生中です
@msg speed must be accelerated, step or pause speed には accelerated、step または pause を指定してください
@msg seconds must be a whole number from 1 to 60 seconds には 1 から 60 までの整数を指定してください
@msg no such function key 指定されたファンクションキーが見つかりません
@msg no such color 指定されたカラーが見つかりません
@msg {_1} cannot be used on Windows {_1} は Windows では使用できません
@@ -5447,6 +5450,11 @@ axis Y Axis Y 軸
Set Options セットオプション
Action Options… アクションオプション…
Action Options アクションオプション
Playback Options… 再生オプション…
Playback Options 再生オプション
Accelerated 高速
Step by Step ステップ実行
Pause For 一時停止
Rename Action アクション名を変更
New Action… 新規アクション…
New Action 新規アクション
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5300,6 +5300,9 @@ imageTrace Outline კონტური
Movie Clip ანიმაციური კლიპი
@msg name is empty სახელი ცარიელია
@msg an action is already recording მოქმედება უკვე იწერება
@msg an action is already playing მოქმედება უკვე სრულდება
@msg speed must be accelerated, step or pause speed უნდა იყოს accelerated, step ან pause
@msg seconds must be a whole number from 1 to 60 seconds უნდა იყოს მთელი რიცხვი 1-დან 60-მდე
@msg no such function key ასეთი ფუნქციური კლავიში არ არის
@msg no such color ასეთი ფერი არ არის
@msg {_1} cannot be used on Windows {_1} ვერ გამოიყენება Windows-ში
@@ -5688,6 +5691,11 @@ axis Y Axis Y ღერძი
Set Options ნაკრების პარამეტრები
Action Options… მოქმედების პარამეტრები…
Action Options მოქმედების პარამეტრები
Playback Options… შესრულების პარამეტრები…
Playback Options შესრულების პარამეტრები
Accelerated დაჩქარებული
Step by Step ნაბიჯ-ნაბიჯ
Pause For პაუზა
Rename Action მოქმედების სახელის შეცვლა
New Action… ახალი მოქმედება…
New Action ახალი მოქმედება
Can't render this file because it is too large.
+8
View File
@@ -5166,6 +5166,9 @@ lighting Light 광원
Movie Clip 무비 클립
@msg name is empty 이름이 비어 있습니다
@msg an action is already recording 이미 액션을 기록하고 있습니다
@msg an action is already playing 이미 액션을 재생하고 있습니다
@msg speed must be accelerated, step or pause speed는 accelerated, step 또는 pause여야 합니다
@msg seconds must be a whole number from 1 to 60 seconds는 1에서 60 사이의 정수여야 합니다
@msg no such function key 해당 기능 키가 없습니다
@msg no such color 해당 색상이 없습니다
@msg {_1} cannot be used on Windows {_1}은(는) Windows에서 사용할 수 없습니다
@@ -5343,6 +5346,11 @@ axis Y Axis Y축
Set Options 세트 옵션
Action Options… 액션 옵션…
Action Options 액션 옵션
Playback Options… 재생 옵션…
Playback Options 재생 옵션
Accelerated 가속
Step by Step 단계별
Pause For 일시 정지
Rename Action 액션 이름 바꾸기
New Action… 새 액션…
New Action 새 액션
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5395,6 +5395,9 @@ swatch Bright Violet Felviolet
Movie Clip Filmclip
@msg name is empty naam is leeg
@msg an action is already recording er wordt al een actie opgenomen
@msg an action is already playing er wordt al een actie afgespeeld
@msg speed must be accelerated, step or pause speed moet accelerated, step of pause zijn
@msg seconds must be a whole number from 1 to 60 seconds moet een geheel getal van 1 tot 60 zijn
@msg no such function key functietoets bestaat niet
@msg no such color kleur bestaat niet
@msg {_1} cannot be used on Windows {_1} kan niet worden gebruikt in Windows
@@ -5713,6 +5716,11 @@ axis Y Axis Y-as
Set Options Setopties
Action Options… Actieopties…
Action Options Actieopties
Playback Options… Afspeelopties…
Playback Options Afspeelopties
Accelerated Versneld
Step by Step Stap voor stap
Pause For Pauzeren
Rename Action Actie hernoemen
New Action… Nieuwe actie…
New Action Nieuwe actie
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5445,6 +5445,9 @@ mark-style Japanese Japońskie
Movie Clip Klip filmowy
@msg name is empty nazwa jest pusta
@msg an action is already recording operacja jest już nagrywana
@msg an action is already playing operacja jest już odtwarzana
@msg speed must be accelerated, step or pause speed musi mieć wartość accelerated, step lub pause
@msg seconds must be a whole number from 1 to 60 seconds musi być liczbą całkowitą od 1 do 60
@msg no such function key brak takiego klawisza funkcyjnego
@msg no such color brak takiego koloru
@msg {_1} cannot be used on Windows Skrótu {_1} nie można używać w systemie Windows
@@ -5619,6 +5622,11 @@ axis Y Axis Oś Y
Set Options Opcje zestawu
Action Options… Opcje operacji…
Action Options Opcje operacji
Playback Options… Opcje odtwarzania…
Playback Options Opcje odtwarzania
Accelerated Przyspieszone
Step by Step Krok po kroku
Pause For Pauza
Rename Action Zmień nazwę operacji
New Action… Nowa operacja…
New Action Nowa operacja
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5507,6 +5507,9 @@ mark-style Japanese Japonesas
# Symbol Options › Export Type
@msg name is empty o nome está vazio
@msg an action is already recording uma ação já está sendo gravada
@msg an action is already playing uma ação já está sendo reproduzida
@msg speed must be accelerated, step or pause speed deve ser accelerated, step ou pause
@msg seconds must be a whole number from 1 to 60 seconds deve ser um número inteiro de 1 a 60
@msg no such function key tecla de função inexistente
@msg no such color cor inexistente
@msg {_1} cannot be used on Windows {_1} não pode ser usado no Windows
@@ -5681,6 +5684,11 @@ axis Y Axis Eixo Y
Set Options Opções do Conjunto
Action Options… Opções da Ação…
Action Options Opções da Ação
Playback Options… Opções de Reprodução…
Playback Options Opções de Reprodução
Accelerated Acelerada
Step by Step Passo a Passo
Pause For Pausar Por
Rename Action Renomear Ação
New Action… Nova Ação…
New Action Nova Ação
Can't render this file because it contains an unexpected character in line 6 and column 47.
+8
View File
@@ -5200,6 +5200,9 @@ lighting Light Свет
Movie Clip Видеоклип
@msg name is empty имя пустое
@msg an action is already recording действие уже записывается
@msg an action is already playing действие уже воспроизводится
@msg speed must be accelerated, step or pause speed должен быть accelerated, step или pause
@msg seconds must be a whole number from 1 to 60 seconds должно быть целым числом от 1 до 60
@msg no such function key такой функциональной клавиши нет
@msg no such color такого цвета нет
@msg {_1} cannot be used on Windows Сочетание {_1} нельзя использовать в Windows
@@ -5377,6 +5380,11 @@ axis Y Axis Ось Y
Set Options Параметры набора
Action Options… Параметры действия…
Action Options Параметры действия
Playback Options… Параметры воспроизведения…
Playback Options Параметры воспроизведения
Accelerated Ускоренно
Step by Step Пошагово
Pause For Пауза
Rename Action Переименовать действие
New Action… Новое действие…
New Action Новое действие
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5547,6 +5547,9 @@ theme Appearance Utseende
Movie Clip Filmklipp
@msg name is empty namnet är tomt
@msg an action is already recording en åtgärd spelas redan in
@msg an action is already playing en åtgärd spelas redan upp
@msg speed must be accelerated, step or pause speed måste vara accelerated, step eller pause
@msg seconds must be a whole number from 1 to 60 seconds måste vara ett heltal från 1 till 60
@msg no such function key funktionstangenten finns inte
@msg no such color färgen finns inte
@msg {_1} cannot be used on Windows {_1} kan inte användas i Windows
@@ -5789,6 +5792,11 @@ axis Y Axis Y-axel
Set Options Alternativ för uppsättning
Action Options… Alternativ för åtgärd…
Action Options Alternativ för åtgärd
Playback Options… Uppspelningsalternativ…
Playback Options Uppspelningsalternativ
Accelerated Accelererad
Step by Step Steg för steg
Pause For Paus i
Rename Action Byt namn på åtgärd
New Action… Ny åtgärd…
New Action Ny åtgärd
Can't render this file because it contains an unexpected character in line 5 and column 25.
+8
View File
@@ -5256,6 +5256,9 @@ theme Appearance Görünüm
Movie Clip Film Klibi
@msg name is empty isim boş
@msg an action is already recording zaten bir eylem kaydediliyor
@msg an action is already playing zaten bir eylem oynatılıyor
@msg speed must be accelerated, step or pause speed accelerated, step veya pause olmalıdır
@msg seconds must be a whole number from 1 to 60 seconds 1 ile 60 arasında bir tam sayı olmalıdır
@msg no such function key öyle bir işlev tuşu yok
@msg no such color öyle bir renk yok
@msg {_1} cannot be used on Windows {_1} Windows'ta kullanılamaz
@@ -5723,6 +5726,11 @@ axis Y Axis Y ekseni
Set Options Set Seçenekleri
Action Options… Eylem Seçenekleri…
Action Options Eylem Seçenekleri
Playback Options… Oynatma Seçenekleri…
Playback Options Oynatma Seçenekleri
Accelerated Hızlandırılmış
Step by Step Adım Adım
Pause For Duraklat
Rename Action Eylemi Yeniden Adlandır
New Action… Yeni Eylem…
New Action Yeni Eylem
Can't render this file because it contains an unexpected character in line 5 and column 25.
+8
View File
@@ -5185,6 +5185,9 @@ lighting Light Світло
Movie Clip Кліп
@msg name is empty ім’я порожнє
@msg an action is already recording дія вже записується
@msg an action is already playing дія вже відтворюється
@msg speed must be accelerated, step or pause speed має бути accelerated, step або pause
@msg seconds must be a whole number from 1 to 60 seconds має бути цілим числом від 1 до 60
@msg no such function key такої функціональної клавіші немає
@msg no such color такого кольору немає
@msg {_1} cannot be used on Windows Сполучення {_1} не можна використовувати у Windows
@@ -5362,6 +5365,11 @@ axis Y Axis Вісь Y
Set Options Параметри набору
Action Options… Параметри дії…
Action Options Параметри дії
Playback Options… Параметри відтворення…
Playback Options Параметри відтворення
Accelerated Прискорено
Step by Step Покроково
Pause For Пауза
Rename Action Перейменувати дію
New Action… Нова дія…
New Action Нова дія
Can't render this file because it contains an unexpected character in line 177 and column 5.
+8
View File
@@ -5186,6 +5186,9 @@ theme Appearance Giao diện
Movie Clip Clip phim
@msg name is empty tên trống
@msg an action is already recording đang ghi một hành động
@msg an action is already playing đang phát một hành động
@msg speed must be accelerated, step or pause speed phải là accelerated, step hoặc pause
@msg seconds must be a whole number from 1 to 60 seconds phải là số nguyên từ 1 đến 60
@msg no such function key không có phím chức năng như vậy
@msg no such color không có màu như vậy
@msg {_1} cannot be used on Windows không thể dùng {_1} trên Windows
@@ -5363,6 +5366,11 @@ axis Y Axis Trục Y
Set Options Tùy chọn bộ
Action Options… Tùy chọn hành động…
Action Options Tùy chọn hành động
Playback Options… Tùy chọn phát lại…
Playback Options Tùy chọn phát lại
Accelerated Tăng tốc
Step by Step Từng bước
Pause For Tạm dừng
Rename Action Đổi tên hành động
New Action… Hành động mới…
New Action Hành động mới
Can't render this file because it contains an unexpected character in line 199 and column 5.
+8
View File
@@ -5425,6 +5425,9 @@ lighting Light 光源
Movie Clip 影片剪辑
@msg name is empty 名称为空
@msg an action is already recording 已在记录动作
@msg an action is already playing 已在播放动作
@msg speed must be accelerated, step or pause speed 必须为 accelerated、step 或 pause
@msg seconds must be a whole number from 1 to 60 seconds 必须为 1 到 60 之间的整数
@msg no such function key 没有此功能键
@msg no such color 没有此颜色
@msg {_1} cannot be used on Windows {_1} 在 Windows 上不能使用
@@ -5604,6 +5607,11 @@ axis Y Axis Y 轴
Set Options 组选项
Action Options… 动作选项…
Action Options 动作选项
Playback Options… 播放选项…
Playback Options 播放选项
Accelerated 加速
Step by Step 逐步
Pause For 暂停
Rename Action 重命名动作
New Action… 新建动作…
New Action 新建动作
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -5243,6 +5243,9 @@ lighting Light 光源
Movie Clip 影片片段
@msg name is empty 名稱為空
@msg an action is already recording 已在錄製動作
@msg an action is already playing 已在播放動作
@msg speed must be accelerated, step or pause speed 必須為 accelerated、step 或 pause
@msg seconds must be a whole number from 1 to 60 seconds 必須為 1 到 60 之間的整數
@msg no such function key 沒有這個功能鍵
@msg no such color 沒有這個顏色
@msg {_1} cannot be used on Windows {_1} 無法在 Windows 上使用
@@ -5400,6 +5403,11 @@ axis Y Axis Y 軸
Set Options 組選項
Action Options… 動作選項…
Action Options 動作選項
Playback Options… 播放選項…
Playback Options 播放選項
Accelerated 加速
Step by Step 逐步
Pause For 暫停
Rename Action 重新命名動作
New Action… 新增動作…
New Action 新增動作
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -826,6 +826,7 @@ fn run_ui_part11(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Result
crate::panels::actions::NEW_ACTION => crate::panels::actions::new_action(app, p),
crate::panels::actions::ACTION_OPTIONS => crate::panels::actions::action_options(app, p),
crate::panels::actions::PLAY_ACTION => crate::panels::actions::play_action(app, p),
crate::panels::actions::PLAYBACK_OPTIONS => crate::panels::actions::playback_options(app, p),
crate::panels::actions::START_RECORDING => crate::panels::actions::record(app, p),
crate::panels::actions::STOP_RECORDING => crate::panels::actions::stop(app, p),
"ui.batchDialog" => {
+8 -2
View File
@@ -1239,7 +1239,13 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
crate::panels::actions::PLAY_ACTION,
"Play Action",
"",
"{action: name, set?: name} play the Actions panel's action named action as one undoable batch; set names the set to look in (default: every set). A name no action has, or one that more than one action in the sets looked in has, is an error and nothing plays → what command.batch returns",
"{action: name, set?: name} play the Actions panel's action named action at the speed ui.action.playbackOptions sets; set names the set to look in (default: every set). Accelerated plays it as one undoable batch → what command.batch returns. Step by Step and Pause For play its first step now and the others one at a time, each its own undo step, until the last or ui.action.stop → {results: [the first step's result], pending: the steps left}. A name no action has, or one that more than one action in the sets looked in has, or an action played while another is still playing step by step or with pauses, is an error and nothing plays",
),
(
crate::panels::actions::PLAYBACK_OPTIONS,
"Playback Options",
"",
"{speed?: accelerated|step|pause, seconds?: 1-60} set how the Actions panel plays actions: accelerated (the default) plays every step at once, step plays one step at a time and draws the window after each, and pause waits seconds (a whole number, 1 to 60, VectorCraft's own range) between steps. A param left out keeps its value; the setting is kept with the UI preferences. Any other speed or seconds is an error and changes nothing. The panel menu's Playback Options… (dialog `actionPlayback` with speed and seconds) runs this on OK → {speed, seconds}",
),
(
crate::panels::actions::START_RECORDING,
@@ -1251,7 +1257,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
crate::panels::actions::STOP_RECORDING,
"Stop Playing/Recording",
"",
"{} end the Actions panel's recording and keep its steps, in the action ui.action.new made for it, else in a new action after the last action of its set, as the panel's Stop Playing/Recording button does. With nothing recording it changes nothing → {stopped}",
"{} end the Actions panel's recording and keep its steps, in the action ui.action.new made for it, else in a new action after the last action of its set, as the panel's Stop Playing/Recording button does. It also ends an action playing step by step or with pauses: the steps played stay and the rest do not run. With nothing recording or playing it changes nothing → {stopped}",
),
(
"ui.batchDialog",
+292 -5
View File
@@ -203,14 +203,154 @@ pub fn default_sets() -> Vec<ActionSet> {
)]
}
/// Run an action as a single undoable batch.
/// Plays an action at the speed [`UiState::action_playback`](crate::UiState::action_playback) sets.
/// Accelerated runs every included step at once as a single undoable batch → what `command.batch`
/// returns. Step by Step and Pause For run the first step now, each as its own undo step, and leave
/// the rest to [`tick_playback`] → {results: [the first step's result], pending: the steps left}.
/// While a paced playback is in progress, another action does not play.
pub fn play(app: &mut VectorcraftApp, set: usize, idx: usize) -> Result<Value, String> {
let Some(a) = app.ui.action_sets.get(set).and_then(|s| s.actions.get(idx)).cloned() else { return Err("no such action".into()) };
let commands = a.included_commands();
if commands.is_empty() {
return Err("the action has no included steps".into());
}
app.run("command.batch", json!({"label": a.name, "commands": commands}))
if app.ui.action_playing.is_some() {
return Err("an action is already playing".into());
}
let options = app.ui.action_playback;
if options.speed == PlaybackSpeed::Accelerated {
return app.run("command.batch", json!({"label": a.name, "commands": commands}));
}
let mut steps: std::collections::VecDeque<Value> = commands.into();
let Some(first) = steps.pop_front() else { return Err("the action has no included steps".into()) };
let result = play_step(app, &a.name, first)?;
let pending = steps.len();
if pending > 0 {
app.ui.action_playing = Some(Playing { label: a.name, steps, options, next_at: None });
if let Some(ctx) = &app.ui_ctx {
ctx.request_repaint();
}
}
Ok(json!({"results": [result], "pending": pending}))
}
/// Runs one step of a paced playback as its own undo step, named `label` → the step's result.
fn play_step(app: &mut VectorcraftApp, label: &str, step: Value) -> Result<Value, String> {
let r = app.run("command.batch", json!({"label": label, "commands": [step]}))?;
Ok(r.get("results").and_then(|r| r.get(0)).cloned().unwrap_or(Value::Null))
}
/// The speed actions play at (Actions panel menu › Playback Options…).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum PlaybackSpeed {
/// Every included step at once.
#[default]
Accelerated,
/// One step per frame, so the window draws each step's result before the next step runs.
Step,
/// [`PlaybackOptions::seconds`] between steps.
Pause,
}
/// The pause between steps that Pause For takes, in whole seconds: VectorCraft's own range.
pub const PAUSE_SECONDS: std::ops::RangeInclusive<u32> = 1..=60;
/// How actions play, kept with the UI preferences.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct PlaybackOptions {
pub speed: PlaybackSpeed,
/// The pause between steps with [`PlaybackSpeed::Pause`], within [`PAUSE_SECONDS`]. It is kept
/// while another speed is chosen.
pub seconds: u32,
}
impl Default for PlaybackOptions {
fn default() -> Self {
Self { speed: PlaybackSpeed::Accelerated, seconds: *PAUSE_SECONDS.start() }
}
}
/// Reads saved [`PlaybackOptions`]: a value that is not a valid one loads as the default.
pub fn deserialize_playback<'de, D: serde::Deserializer<'de>>(d: D) -> Result<PlaybackOptions, D::Error> {
let v = <Value as serde::Deserialize>::deserialize(d)?;
Ok(serde_json::from_value::<PlaybackOptions>(v).ok().filter(|o| PAUSE_SECONDS.contains(&o.seconds)).unwrap_or_default())
}
/// A paced playback in progress: the steps still to run.
#[derive(Clone, Debug)]
pub struct Playing {
/// The action's name, which names each step's undo step.
label: String,
steps: std::collections::VecDeque<Value>,
/// The options the action started playing with.
options: PlaybackOptions,
/// With Pause For, when the next step runs (seconds on the app's clock); none until the frame
/// after the first step, which starts the first pause.
next_at: Option<f64>,
}
/// Runs the next step of the paced playback in progress when it is due at `now` (seconds on the
/// app's clock): with Step by Step at each call, so each frame runs one step; with Pause For once
/// the pause after the step before has passed. A step that fails ends the playback and shows its
/// error. → the seconds until the window should draw again while playback goes on
pub fn tick_playback(app: &mut VectorcraftApp, now: f64) -> Option<f64> {
let playing = app.ui.action_playing.as_mut()?;
let pause = f64::from(playing.options.seconds);
if playing.options.speed == PlaybackSpeed::Pause {
let due = *playing.next_at.get_or_insert(now + pause);
if now < due {
return Some(due - now);
}
// The next pause starts when this step runs.
playing.next_at = Some(now + pause);
}
let Some(step) = playing.steps.pop_front() else {
app.ui.action_playing = None;
return None;
};
let label = playing.label.clone();
if playing.steps.is_empty() {
app.ui.action_playing = None;
}
if let Err(e) = play_step(app, &label, step) {
app.ui.action_playing = None;
app.status(e);
return None;
}
let playing = app.ui.action_playing.as_ref()?;
Some(if playing.options.speed == PlaybackSpeed::Pause { pause } else { 0.0 })
}
/// The command behind Playback Options… ([`playback_options`]).
pub const PLAYBACK_OPTIONS: &str = "ui.action.playbackOptions";
/// `ui.action.playbackOptions {speed?, seconds?}`: sets how actions play. `speed` is
/// `accelerated` (every step at once, the default), `step` (one step at a time, the window drawn
/// after each) or `pause` (`seconds` between steps); `seconds` is a whole number in
/// [`PAUSE_SECONDS`]. A param left out keeps its value. Any other value is an error, and then
/// nothing changes. An action already playing keeps its speed. → {speed, seconds}
pub fn playback_options(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
let current = app.ui.action_playback;
let speed = match p.get("speed") {
None => current.speed,
Some(v) => match v.as_str() {
Some("accelerated") => PlaybackSpeed::Accelerated,
Some("step") => PlaybackSpeed::Step,
Some("pause") => PlaybackSpeed::Pause,
_ => return Err("speed must be accelerated, step or pause".into()),
},
};
let seconds = match p.get("seconds") {
None => current.seconds,
Some(v) => v
.as_u64()
.and_then(|s| u32::try_from(s).ok())
.filter(|s| PAUSE_SECONDS.contains(s))
.ok_or("seconds must be a whole number from 1 to 60")?,
};
app.ui.action_playback = PlaybackOptions { speed, seconds };
Ok(json!({"speed": speed, "seconds": seconds}))
}
/// The command behind the Actions panel's set, action and step checkmarks.
@@ -444,11 +584,14 @@ pub fn record(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
/// `ui.action.stop {}`: ends the recording in progress and keeps its steps, in the action New
/// Action made for it, else in a new action after the last action of its set, as the bottom bar's
/// Stop Playing/Recording does. With nothing recording it changes nothing. → {stopped}
/// Stop Playing/Recording does. It also ends a playback at Step by Step or Pause For: the steps
/// already played stay, and the rest do not run. With nothing recording or playing it changes
/// nothing. → {stopped}
pub fn stop(app: &mut VectorcraftApp, _: &Value) -> Result<Value, String> {
let stopped = app.ui.recording.is_some();
let playing = app.ui.action_playing.take().is_some();
let recording = app.ui.recording.is_some();
stop_recording(app);
Ok(json!({ "stopped": stopped }))
Ok(json!({ "stopped": recording || playing }))
}
/// The command that `chord` runs under `km`, pressed once or pressed twice.
@@ -801,6 +944,9 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut egui::Ui) {
{
crate::dialogs::action_set_options::open_action(app, s, a);
}
if widgets::menu_item(ui, tl!("Playback Options…"), true, false) {
crate::dialogs::action_playback::open(app);
}
ui.separator();
// As commands, so a file dialog shown off the UI thread saves or loads when it answers.
// Save Actions… names the current set by its id, so a file dialog that answers later saves that set.
@@ -2662,4 +2808,145 @@ mod tests {
"the command keeps its shortcut"
);
}
/// A document with one rectangle selected and the set "Moves" with one action that moves it
/// right three times, by 1, 2 and 4 pt → (the app, the rectangle's left edge as it moves).
fn app_with_three_moves() -> (VectorcraftApp, impl Fn(&VectorcraftApp) -> f64) {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 200, "height": 100})).unwrap();
let id = vectorcraft_doc::NodeId(
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 20, "height": 20})).unwrap()["id"].as_u64().unwrap(),
);
app.run("select.set", json!({"ids": [id.0]})).unwrap();
let step = |dx: u32| ("object.move".to_string(), json!({"dx": dx, "dy": 0}));
app.ui.action_sets = vec![ActionSet::new("Moves", vec![Action::new("Three Moves", vec![step(1), step(2), step(4)])])];
let x = move |app: &VectorcraftApp| app.session.active().unwrap().doc.node(id).unwrap().geometric_bounds().unwrap().x0 - 10.0;
(app, x)
}
fn undo_steps(app: &VectorcraftApp) -> usize {
app.session.active().unwrap().history.undo.len()
}
#[test]
fn playback_options_sets_each_speed_and_refuses_bad_values() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
assert_eq!(app.ui.action_playback, PlaybackOptions { speed: PlaybackSpeed::Accelerated, seconds: 1 }, "Accelerated is the default");
assert_eq!(app.run(PLAYBACK_OPTIONS, json!({"speed": "step"})).unwrap(), json!({"speed": "step", "seconds": 1}));
assert_eq!(app.run(PLAYBACK_OPTIONS, json!({"speed": "pause", "seconds": 2})).unwrap(), json!({"speed": "pause", "seconds": 2}));
assert_eq!(
app.run(PLAYBACK_OPTIONS, json!({"speed": "accelerated"})).unwrap(),
json!({"speed": "accelerated", "seconds": 2}),
"seconds kept"
);
assert_eq!(app.run(PLAYBACK_OPTIONS, json!({"seconds": 60})).unwrap(), json!({"speed": "accelerated", "seconds": 60}), "speed kept");
let kept = app.ui.action_playback;
for bad in [json!({"speed": "fast"}), json!({"speed": null}), json!({"speed": 1})] {
assert_eq!(app.run(PLAYBACK_OPTIONS, bad.clone()).unwrap_err(), "speed must be accelerated, step or pause", "{bad}");
}
for bad in [json!(0), json!(61), json!(2.5), json!(-1), json!("2"), json!(null)] {
let p = json!({"speed": "pause", "seconds": bad});
assert_eq!(app.run(PLAYBACK_OPTIONS, p).unwrap_err(), "seconds must be a whole number from 1 to 60", "{bad}");
}
assert_eq!(app.ui.action_playback, kept, "a refused value changes nothing");
// Kept with the UI preferences; a saved value that is not valid loads as the default.
app.run(PLAYBACK_OPTIONS, json!({"speed": "pause", "seconds": 5})).unwrap();
let mut saved = serde_json::to_value(&app.ui).unwrap();
let ui = serde_json::from_value::<crate::UiState>(saved.clone()).unwrap();
assert_eq!(ui.action_playback, PlaybackOptions { speed: PlaybackSpeed::Pause, seconds: 5 });
for bad in [json!({"speed": "pause", "seconds": 0}), json!({"speed": "slow", "seconds": 5}), json!("step")] {
saved["action_playback"] = bad;
assert_eq!(serde_json::from_value::<crate::UiState>(saved.clone()).unwrap().action_playback, PlaybackOptions::default());
}
}
#[test]
fn the_playback_options_dialog_sets_the_speed_and_pause() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run(PLAYBACK_OPTIONS, json!({"speed": "step", "seconds": 3})).unwrap();
crate::dialogs::action_playback::open(&mut app);
let d = app.ui.dialog.as_mut().unwrap();
assert_eq!((d.kind.as_str(), d.str("speed"), d.fields["seconds"].clone()), ("actionPlayback", "step".to_string(), json!(3)));
d.fields.insert("speed".into(), json!("pause"));
d.fields.insert("seconds".into(), json!(4));
crate::dialogs::confirm(&mut app).unwrap();
assert!(app.ui.dialog.is_none());
assert_eq!(app.ui.action_playback, PlaybackOptions { speed: PlaybackSpeed::Pause, seconds: 4 });
}
#[test]
fn accelerated_playback_plays_every_step_as_one_undo_step() {
let (mut app, x) = app_with_three_moves();
let before = undo_steps(&app);
play(&mut app, 0, 0).unwrap();
assert_eq!(x(&app), 7.0);
assert_eq!(undo_steps(&app), before + 1);
assert!(app.ui.action_playing.is_none());
assert_eq!(tick_playback(&mut app, 0.0), None, "nothing waits for a frame");
}
#[test]
fn step_by_step_plays_one_step_per_frame_and_redraws_between_steps() {
let (mut app, x) = app_with_three_moves();
app.run(PLAYBACK_OPTIONS, json!({"speed": "step"})).unwrap();
let before = undo_steps(&app);
assert_eq!(play(&mut app, 0, 0).unwrap()["pending"], json!(2));
assert_eq!(x(&app), 1.0, "the first step runs at once");
assert_eq!(tick_playback(&mut app, 0.0), Some(0.0), "the window draws again before the next step");
assert_eq!(x(&app), 3.0, "one more step in this frame");
assert_eq!(tick_playback(&mut app, 0.0), None, "the last step ends the playback");
assert_eq!(x(&app), 7.0);
assert!(app.ui.action_playing.is_none());
assert_eq!(tick_playback(&mut app, 0.0), None);
assert_eq!(x(&app), 7.0);
assert_eq!(undo_steps(&app), before + 3, "each step is its own undo step");
assert_eq!(app.session.active().unwrap().history.undo.last().unwrap().label, "Three Moves");
}
#[test]
fn pause_for_waits_the_seconds_between_steps() {
let (mut app, x) = app_with_three_moves();
app.run(PLAYBACK_OPTIONS, json!({"speed": "pause", "seconds": 2})).unwrap();
play(&mut app, 0, 0).unwrap();
assert_eq!(x(&app), 1.0);
assert_eq!(tick_playback(&mut app, 10.0), Some(2.0), "the pause starts in the frame after the first step");
assert_eq!(tick_playback(&mut app, 11.5), Some(0.5));
assert_eq!(x(&app), 1.0, "nothing runs before the pause has passed");
assert_eq!(tick_playback(&mut app, 12.0), Some(2.0));
assert_eq!(x(&app), 3.0, "the second step runs after 2 s");
assert!(tick_playback(&mut app, 13.9).is_some_and(|wait| (wait - 0.1).abs() < 1e-9));
assert_eq!(x(&app), 3.0);
assert_eq!(tick_playback(&mut app, 14.0), None);
assert_eq!(x(&app), 7.0, "the third step runs 2 s after the second");
assert!(app.ui.action_playing.is_none());
}
#[test]
fn stop_ends_a_paced_playback_and_another_action_waits_for_it() {
let (mut app, x) = app_with_three_moves();
app.run(PLAYBACK_OPTIONS, json!({"speed": "pause", "seconds": 1})).unwrap();
play(&mut app, 0, 0).unwrap();
assert_eq!(play(&mut app, 0, 0).unwrap_err(), "an action is already playing");
assert_eq!(app.run(PLAY_ACTION, json!({"action": "Three Moves"})).unwrap_err(), "an action is already playing");
assert_eq!(x(&app), 1.0);
assert_eq!(app.run(STOP_RECORDING, json!({})).unwrap(), json!({"stopped": true}));
assert_eq!(tick_playback(&mut app, 100.0), None);
assert_eq!(x(&app), 1.0, "the steps after Stop do not run; the one played stays");
assert_eq!(app.run(STOP_RECORDING, json!({})).unwrap(), json!({"stopped": false}));
play(&mut app, 0, 0).unwrap();
assert_eq!(x(&app), 2.0, "an action plays again after Stop");
}
#[test]
fn a_failing_step_ends_a_paced_playback_with_its_error() {
let (mut app, x) = app_with_three_moves();
app.run(PLAYBACK_OPTIONS, json!({"speed": "step"})).unwrap();
app.ui.action_sets[0].actions[0].steps[1] = ("no.such.command".into(), json!({}));
play(&mut app, 0, 0).unwrap();
assert_eq!(tick_playback(&mut app, 0.0), None);
assert!(app.ui.action_playing.is_none());
assert!(!app.ui.status.is_empty(), "the step's error shows");
assert_eq!(tick_playback(&mut app, 0.0), None);
assert_eq!(x(&app), 1.0, "the step after the failing one does not run");
}
}
+8
View File
@@ -496,6 +496,12 @@ pub struct UiState {
/// adds a new action, as after Begin Recording.
#[serde(skip)]
pub recording_into: Option<u64>,
/// How actions play (Actions panel menu › Playback Options…), kept with the UI preferences.
#[serde(default, deserialize_with = "crate::panels::actions::deserialize_playback")]
pub action_playback: crate::panels::actions::PlaybackOptions,
/// The playback at Step by Step or Pause For in progress, if any. Not saved.
#[serde(skip)]
pub action_playing: Option<crate::panels::actions::Playing>,
/// Keyboard shortcut overrides: command id, `tool:<id>` or `panel:<id>` → chord ("" = none),
/// stored in the form every earlier version reads.
#[serde(default, with = "crate::keymap::stored")]
@@ -970,6 +976,8 @@ impl Default for UiState {
action_sets_selected: vec![],
recording: None,
recording_into: None,
action_playback: Default::default(),
action_playing: None,
shortcut_overrides: Default::default(),
shortcut_set: default_shortcut_set(),
workspace: default_workspace(),
+17 -5
View File
@@ -1848,12 +1848,24 @@ already walks Image Trace. Short agent entry points:
a text field has the keyboard, or while a tool is busy. Editing command shortcuts (`shortcuts.set`, Keyboard Shortcuts), loading preferences and
`ui.actionSet.load` preserve action function keys. When several actions have the same key, the first in panel order
plays. A stored key reserved on the current platform does not play an action.
- Actions: `ui.action.play {action, set?}` plays the action named `action` as one undoable batch; `set` names the set
to look in (default: every set), and a `set` that is not a string is an error. A name that no action has, or that
more than one action in the sets looked in has, or whose every step is excluded, is an error, and then nothing plays.
- Actions: `ui.action.play {action, set?}` plays the action named `action` at the speed `ui.action.playbackOptions`
sets; `set` names the set to look in (default: every set), and a `set` that is not a string is an error. At
Accelerated it plays the action as one undoable batch and returns what `command.batch` returns. At Step by Step and
Pause For it plays the first step at once and returns `{results: [the first step's result], pending}`, where
`pending` counts the steps still to play; the app plays them one at a time, each as its own undo step. A name that no
action has, or that more than one action in the sets looked in has, or whose every step is excluded, is an error, and
then nothing plays. So is an action played while another is still playing at Step by Step or Pause For.
- Actions: `ui.action.playbackOptions {speed?, seconds?}` sets how the Actions panel plays actions and returns
`{speed, seconds}`. `speed` is `accelerated` (the default: every step at once), `step` (one step at a time, with the
window drawn after each step before the next runs) or `pause` (a pause of `seconds` between steps). `seconds` is a
whole number from 1 to 60, VectorCraft's own range, and stays set while another speed is chosen. A param left out
keeps its value. Any other `speed` or `seconds` is an error, and then nothing changes. The setting is kept with the UI
preferences. The panel menu's Playback Options… opens the `actionPlayback` dialog (`speed`, `seconds`), whose OK runs
this. Batch… (`action.batch`) plays every step at once whatever the setting.
- Actions: `ui.action.stop {}` ends the recording in progress and keeps its steps, in the action `ui.action.new` made
for it, else in a new action after the last action of its set, and returns `{stopped}`. With nothing recording it
changes nothing. The panel's Stop Playing/Recording button runs this.
for it, else in a new action after the last action of its set, and returns `{stopped}`. It also ends an action
playing at Step by Step or Pause For: the steps already played stay, and the rest do not play. With nothing
recording or playing it changes nothing. The panel's Stop Playing/Recording button runs this.
- Action sets: `ui.actionSet.delete {sets?: [index], ids?: [id]}` deletes the Actions panel's sets at these indexes
(0 is the top set) with their actions, by default the current set, and returns `{deleted}`. An index past the last
set is an error, and then no set is deleted. `ids` holds the panel's own set ids, which its question passes: the
+1
View File
@@ -581,6 +581,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- Actions panel (record/playback, persisted), generic parameter dialogs for every "…" command.
- Actions panel: the current set is kept with the UI preferences, so after a restart a recording goes into the set that was current, not into the first set. With no set current, Begin Recording records into the first set and makes it current.
- Actions panel: expand an action to show its recorded steps, then clear a step's checkmark to keep it in the action but skip it during playback. The action and set checkmarks include or exclude all their steps; a mixed checkmark includes all. Alt/Option-clicking a step checkmark keeps that step as it was and applies the opposite inclusion state to the other steps in its action. This follows the public [Exclude commands from an action](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/exclude-commands-from-actions.html) guide; the scope of the mixed-state and Alt-click behavior is VectorCraft's design. The exclusion survives UI restart, action duplication, and `.vcactions` save/load. Existing version 1 action set files load with every step included.
- Actions panel: the panel menu's Playback Options… sets how actions play: Accelerated (the default) plays every step at once, Step by Step plays one step at a time with the canvas drawn after each, and Pause For waits the given number of seconds between steps ([Specify playback speed](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/specify-playback-speed.html); `ui.action.playbackOptions`). VectorCraft's own rules, which the page does not cover: the pause is a whole number of seconds from 1 to 60, the setting is kept with the UI preferences, Stop Playing/Recording ends a paced playback, each step of a paced playback is its own undo step, and Batch… plays at full speed.
- Actions panel: Delete Selection deletes the selected action set with its actions and keeps the other sets ([Actions panel overview](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/actions-panel-overview.html); `ui.actionSet.delete`). VectorCraft's own rules, which the overview does not cover: deleting a set asks first, Shift-click on a set's name selects a range of sets to delete together, the panel menu's Delete does the same, and Default Actions and the last set can be deleted. Undo does not bring a deleted set back.
- Actions panel: the panel menu's Duplicate copies the selected action, else the current action set with all its actions ([Actions panel overview](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/actions-panel-overview.html); `ui.actionSet.duplicate`). VectorCraft's own rules, which the overview does not cover: the copy goes right after the original, is named after it with " copy" added and is selected.
- Batch… over data sets names each file after the document and the data set (`action.batch`). It writes `/`, `\`, `:`, `<`, `>`, `"`, `|`, `?`, `*` and control characters in the data set's name as `-`, and adds `-2`, `-3`… to a name an earlier data set used.
+1
View File
@@ -320,6 +320,7 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po
| Toolbar without group labels by default, one or two columns; All Tools drawer by flyout group with shortcuts, its menu (Reset, Basic, Advanced) and Show section | done | #1751; a preference brings the labels back; `toolbar.show` sets the Fill/Stroke, Coloring, Draw Mode and Screen Mode controls |
| Actions panel: ≡ › Save Actions… saves the current set with all its actions to a file and ≡ › Load Actions… adds a saved set ([Manage a set of actions](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/manage-a-set-of-actions.html)) | partial | `ui.actionSet.save`, `ui.actionSet.load`; `panels/actions.rs` tests. The file is VectorCraft's own `.vcactions` JSON; the reference app's `.aia` files and Reset, Clear and Replace Actions remain |
| Actions panel: ≡ › Action Options… renames the selected action and sets its function key and color ([Actions panel overview](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/actions-panel-overview.html)) | partial | `ui.action.options`; `panels/actions.rs` tests (part of #1629). The color shows nowhere until Button Mode exists |
| Actions panel: ≡ › Playback Options… plays actions Accelerated, Step by Step or with a pause between steps ([Specify playback speed](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/specify-playback-speed.html)) | done | `ui.action.playbackOptions`; `panels/actions.rs` tests. The pause is a whole number of seconds from 1 to 60 (VectorCraft's own range); Batch… plays at full speed |
| Actions panel: a clicked action stays selected for Play Current Selection and Delete Selection | partial | #1853 (part of #1629); editing actions and action shortcuts remain |
| Actions panel: Create New Set (bottom bar, ≡ › New Set…) names a new set and Set Options… renames the current one; a click on a set's name makes it current and a recording goes into the current set ([helpx: Automation with actions](https://helpx.adobe.com/illustrator/using/automation-actions.html)); the current set is kept with the UI preferences, and with no set current a recording goes into the first set, which becomes current | partial | #3712; dragging actions between sets remains |
| Actions panel: Create New Action (bottom bar, ≡ › New Action…) asks for the name, set, function key and color, and Record adds the action and records into it ([Create new actions](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/record-actions.html)) | partial | `ui.action.new`, `ui.action.play`; `panels/actions.rs` tests. A function key plays its action, even when a command has the same shortcut, and the command keeps it. Button Mode, which shows the color, remains |