diff --git a/crates/ui-egui/src/dialogs/action_set_options.rs b/crates/ui-egui/src/dialogs/action_set_options.rs new file mode 100644 index 000000000..1312f80d5 --- /dev/null +++ b/crates/ui-egui/src/dialogs/action_set_options.rs @@ -0,0 +1,63 @@ +//! Actions panel › Create New Set (and ≡ › New Set…) and ≡ › Set Options…: the name of a new action +//! set, or of the current one. +//! +//! Fields: `set` (the index of the set renamed; none for a new set) and `name`. OK adds the set and +//! makes it the current one, or renames the set. + +use serde_json::{Value, json}; + +use super::DialogSpec; +use crate::state::Dialog; +use crate::{VectorcraftApp, widgets}; + +/// The dialog kind of New Set and Set Options. +pub const KIND: &str = "actionSetOptions"; + +pub(super) const SPEC: DialogSpec = + DialogSpec { heading, body, confirm, ok_enabled: Some(|_, d| !d.str("name").trim().is_empty()), min_width: 300.0, ..DialogSpec::FORM }; + +fn heading(d: &Dialog) -> String { + if d.fields.contains_key("set") { tl!("Set Options").into() } else { tl!("New Set").into() } +} + +/// Open New Set, named "Set N" with the first number no set uses. +pub fn open_new(app: &mut VectorcraftApp) { + let taken = |n: usize| app.ui.action_sets.iter().any(|s| s.name == format!("Set {n}")); + let n = (1..).find(|&n| !taken(n)).unwrap_or(1); + app.ui.dialog = Some(Dialog::new(KIND, json!({"name": format!("Set {n}")}))); +} + +/// Open Set Options for the set at `set`, if there is one. +pub fn open(app: &mut VectorcraftApp, set: usize) { + if let Some(s) = app.ui.action_sets.get(set) { + app.ui.dialog = Some(Dialog::new(KIND, json!({"set": set, "name": s.name}))); + } +} + +fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { + widgets::label_row(ui, "Name:", 50.0, |ui| { + super::form::text(ui, d, "name", 200.0); + }); + false +} + +fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { + let name = d.str("name").trim().to_string(); + // OK is disabled without a name; `ui.dialog.confirm` keeps the dialog open the same way. + if name.is_empty() { + return Ok(Value::Null); + } + match d.fields.get("set").and_then(Value::as_u64) { + Some(set) => { + if let Some(s) = app.ui.action_sets.get_mut(set as usize) { + s.name = name; + } + } + None => { + app.ui.action_sets.push(crate::panels::actions::ActionSet { name, actions: vec![] }); + app.ui.action_set = Some(app.ui.action_sets.len() - 1); + } + } + app.ui.dialog = None; + Ok(Value::Null) +} diff --git a/crates/ui-egui/src/dialogs/mod.rs b/crates/ui-egui/src/dialogs/mod.rs index cdf831402..827855d28 100644 --- a/crates/ui-egui/src/dialogs/mod.rs +++ b/crates/ui-egui/src/dialogs/mod.rs @@ -8,6 +8,7 @@ mod about; pub mod action_batch; +pub mod action_set_options; pub mod artboard_options; pub mod blend_options; mod brush_art_options; @@ -318,6 +319,7 @@ registry! { PlacementOptions: [placement_options::KIND] => placement_options::SPEC, Package: [package::KIND] => package::SPEC, ActionBatch: [action_batch::KIND] => action_batch::SPEC, + ActionSetOptions: [action_set_options::KIND] => action_set_options::SPEC, SliceOptions: [slices::OPTIONS] => slices::OPTIONS_SPEC, DivideSlices: [slices::DIVIDE] => slices::DIVIDE_SPEC, EpsOptions: [eps_options::KIND] => eps_options::SPEC, diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 2f0d026a7..1a3018469 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -5430,6 +5430,12 @@ axis Y Axis Y ос The plug-in is computing… Приставката изчислява… Inspect Plug-in Проверка на приставка Cancel Plug-in Run Отказ от изпълнението на приставката +# Actions panel: action sets (#3712). + New Set… Нов набор… + New Set Нов набор + Set Options… Опции на набора… + Set Options Опции на набора + Create New Set Създаване на нов набор Stop Global Edit Край на глобалното редактиране Global Edit Options Опции за глобално редактиране Match Съвпадение diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 65e09b949..764a3f77d 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -5481,6 +5481,12 @@ axis Y Axis osa Y @msg a live effect runs while the document is drawn, so it can't be `heavy` živý efekt běží během vykreslování dokumentu, takže nemůže být `heavy` @msg an exporter always gets an export's budget: leave `budget` out exportní zásuvný modul dostává vždy rozpočet exportu: klíč `budget` vynechte @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees „{_1}“ žádá o velký výpočetní rozpočet (každé spuštění až {minutes} minut a {times}krát víc instrukcí než obvykle): nainstalujte ho s `allowHeavy: true`, jakmile uživatel souhlasí +# Actions panel: action sets (#3712). + New Set… Nová sada… + New Set Nová sada + Set Options… Možnosti sady… + Set Options Možnosti sady + Create New Set Vytvořit novou sadu Stop Global Edit Ukončit globální úpravy Global Edit Options Možnosti globálních úprav Match Shoda diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index fe9255991..e60b120dd 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -5446,6 +5446,12 @@ axis Y Axis Y-akse The plug-in is computing… Plug-in'en regner… Inspect Plug-in Undersøg plug-in Cancel Plug-in Run Annullér plug-in-kørsel +# Actions panel: action sets (#3712). + New Set… Nyt sæt… + New Set Nyt sæt + Set Options… Indstillinger for sæt… + Set Options Indstillinger for sæt + Create New Set Opret nyt sæt Stop Global Edit Stop global redigering Global Edit Options Indstillinger for global redigering Match Match diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index ffde1230a..68d1c4b8f 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -5064,6 +5064,12 @@ axis Y Axis Y-Achse @msg a live effect runs while the document is drawn, so it can't be `heavy` ein Live-Effekt läuft, während das Dokument gezeichnet wird, daher kann er nicht `heavy` sein @msg an exporter always gets an export's budget: leave `budget` out ein Export-Plug-in erhält immer das Budget eines Exports: Lassen Sie `budget` weg @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees „{_1}“ verlangt ein großes Rechenbudget (jede Ausführung bis zu {minutes} Minuten und {times}-mal so viele Anweisungen wie üblich): Installieren Sie es mit `allowHeavy: true`, sobald der Benutzer zustimmt +# Actions panel: action sets (#3712). + New Set… Neuer Satz… + New Set Neuer Satz + Set Options… Satzoptionen… + Set Options Satzoptionen + Create New Set Neuen Satz erstellen Stop Global Edit Globale Bearbeitung beenden Global Edit Options Optionen für globale Bearbeitung Match Übereinstimmung diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 3884fa6e4..fa66430f5 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -5433,6 +5433,12 @@ axis Y Axis Άξονας Y @msg a live effect runs while the document is drawn, so it can't be `heavy` ένα δυναμικό εφέ εκτελείται κατά τη σχεδίαση του εγγράφου, οπότε δεν μπορεί να είναι `heavy` @msg an exporter always gets an export's budget: leave `budget` out ένα πρόσθετο εξαγωγής παίρνει πάντα τον προϋπολογισμό μιας εξαγωγής: παραλείψτε το `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees το «{_1}» ζητά μεγάλο υπολογιστικό προϋπολογισμό (κάθε εκτέλεση έως {minutes} λεπτά και {times} φορές τις συνήθεις εντολές): εγκαταστήστε το με `allowHeavy: true` μόλις συμφωνήσει ο χρήστης +# Actions panel: action sets (#3712). + New Set… Νέο σύνολο… + New Set Νέο σύνολο + Set Options… Επιλογές συνόλου… + Set Options Επιλογές συνόλου + Create New Set Δημιουργία νέου συνόλου Stop Global Edit Τερματισμός καθολικής επεξεργασίας Global Edit Options Επιλογές καθολικής επεξεργασίας Match Αντιστοίχιση diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index fd905db09..8249a9e28 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -5423,6 +5423,12 @@ axis Y Axis Eje Y @msg a live effect runs while the document is drawn, so it can't be `heavy` un efecto interactivo se ejecuta mientras se dibuja el documento, así que no puede ser `heavy` @msg an exporter always gets an export's budget: leave `budget` out un plugin de exportación siempre recibe el presupuesto de una exportación: omite `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}” pide un presupuesto de cálculo grande (cada ejecución hasta {minutes} minutos y {times} veces las instrucciones habituales): instálalo con `allowHeavy: true` cuando el usuario lo acepte +# Actions panel: action sets (#3712). + New Set… Nuevo conjunto… + New Set Nuevo conjunto + Set Options… Opciones de conjunto… + Set Options Opciones de conjunto + Create New Set Crear nuevo conjunto Stop Global Edit Detener edición global Global Edit Options Opciones de edición global Match Coincidir diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 5749420b1..b0df6e69c 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -5512,6 +5512,12 @@ axis Y Axis Y-akseli @msg a live effect runs while the document is drawn, so it can't be `heavy` live-tehoste suoritetaan asiakirjaa piirrettäessä, joten se ei voi olla `heavy` @msg an exporter always gets an export's budget: leave `budget` out vientilaajennus saa aina viennin budjetin: jätä `budget` pois @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees ”{_1}” pyytää suurta laskentabudjettia (jokainen ajo enintään {minutes} minuuttia ja {times} kertaa tavallinen määrä käskyjä): asenna se asetuksella `allowHeavy: true`, kun käyttäjä hyväksyy +# Actions panel: action sets (#3712). + New Set… Uusi sarja… + New Set Uusi sarja + Set Options… Sarjan asetukset… + Set Options Sarjan asetukset + Create New Set Luo uusi sarja Stop Global Edit Lopeta yleismuokkaus Global Edit Options Yleismuokkauksen asetukset Match Vastaavuus diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 75d47bf85..57bc29a1e 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -5480,6 +5480,12 @@ axis Y Axis Axe Y @msg a live effect runs while the document is drawn, so it can't be `heavy` un effet dynamique s’exécute pendant le tracé du document, il ne peut donc pas être `heavy` @msg an exporter always gets an export's budget: leave `budget` out un module externe d’exportation reçoit toujours le budget d’une exportation : omettez `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees « {_1} » demande un budget de calcul important (chaque exécution jusqu’à {minutes} minutes et {times} fois les instructions habituelles) : installez-le avec `allowHeavy: true` une fois que l’utilisateur l’accepte +# Actions panel: action sets (#3712). + New Set… Nouveau jeu… + New Set Nouveau jeu + Set Options… Options du jeu… + Set Options Options du jeu + Create New Set Créer un jeu Stop Global Edit Arrêter la modification globale Global Edit Options Options de modification globale Match Correspondance diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 135fb2add..6925617e5 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -5467,6 +5467,12 @@ axis Y Axis Y tengely @msg a live effect runs while the document is drawn, so it can't be `heavy` az élő effektus a dokumentum rajzolása közben fut, ezért nem lehet `heavy` @msg an exporter always gets an export's budget: leave `budget` out az exportáló bővítmény mindig az exportálás keretét kapja: hagyd ki a `budget` kulcsot @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees a(z) „{_1}” nagy számítási keretet kér (futásonként legfeljebb {minutes} perc és a szokásos utasításszám {times}-szorosa): telepítsd az `allowHeavy: true` megadásával, miután a felhasználó beleegyezett +# Actions panel: action sets (#3712). + New Set… Új készlet… + New Set Új készlet + Set Options… Készlet beállításai… + Set Options Készlet beállításai + Create New Set Új készlet létrehozása Stop Global Edit Globális szerkesztés befejezése Global Edit Options Globális szerkesztés beállításai Match Egyezés diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index f2a5f1f2f..c927e6eee 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -5446,6 +5446,12 @@ axis Y Axis Sumbu Y The plug-in is computing… Plug-in sedang menghitung… Inspect Plug-in Periksa plug-in Cancel Plug-in Run Batalkan jalannya plug-in +# Actions panel: action sets (#3712). + New Set… Set baru… + New Set Set baru + Set Options… Opsi set… + Set Options Opsi set + Create New Set Buat set baru Stop Global Edit Hentikan edit global Global Edit Options Opsi edit global Match Cocokkan diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 6b4b6cbb6..a57a38154 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -5098,6 +5098,12 @@ axis Y Axis Asse Y @msg a live effect runs while the document is drawn, so it can't be `heavy` un effetto dinamico viene eseguito mentre il documento viene disegnato, quindi non può essere `heavy` @msg an exporter always gets an export's budget: leave `budget` out un plug-in di esportazione riceve sempre il budget di un’esportazione: ometti `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}” chiede un budget di calcolo elevato (ogni esecuzione fino a {minutes} minuti e {times} volte le istruzioni consuete): installalo con `allowHeavy: true` quando l’utente acconsente +# Actions panel: action sets (#3712). + New Set… Nuovo set… + New Set Nuovo set + Set Options… Opzioni set… + Set Options Opzioni set + Create New Set Crea nuovo set Stop Global Edit Interrompi modifica globale Global Edit Options Opzioni modifica globale Match Corrispondenza diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 26d8ce65f..eebea2207 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -5160,6 +5160,12 @@ axis Y Axis Y 軸 @msg a live effect runs while the document is drawn, so it can't be `heavy` ライブ効果はドキュメントの描画中に実行されるため、`heavy` にはできません @msg an exporter always gets an export's budget: leave `budget` out 書き出しプラグインには常に書き出しの予算が割り当てられます。`budget` は省略してください @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees 「{_1}」は大きな計算予算を求めています (1 回の実行につき最大 {minutes} 分、通常の {times} 倍の命令)。ユーザーが同意したら `allowHeavy: true` を指定してインストールしてください +# Actions panel: action sets (#3712). + New Set… 新規セット… + New Set 新規セット + Set Options… セットオプション… + Set Options セットオプション + Create New Set 新規セットを作成 Stop Global Edit グローバル編集を終了 Global Edit Options グローバル編集オプション Match 一致 diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 1a08e4b99..5e7360795 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -5397,6 +5397,12 @@ axis Y Axis Y ღერძი @msg a live effect runs while the document is drawn, so it can't be `heavy` ცოცხალი ეფექტი დოკუმენტის დახატვისას ეშვება, ამიტომ ის ვერ იქნება `heavy` @msg an exporter always gets an export's budget: leave `budget` out ექსპორტის პლაგინი ყოველთვის ექსპორტის ბიუჯეტს იღებს: გამოტოვეთ `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees „{_1}“ დიდ გამოთვლით ბიუჯეტს ითხოვს (თითო გაშვება {minutes} წუთამდე და ჩვეულებრივზე {times}-ჯერ მეტი ინსტრუქცია): მომხმარებლის თანხმობის შემდეგ დააყენეთ ის `allowHeavy: true` პარამეტრით +# Actions panel: action sets (#3712). + New Set… ახალი ნაკრები… + New Set ახალი ნაკრები + Set Options… ნაკრების პარამეტრები… + Set Options ნაკრების პარამეტრები + Create New Set ახალი ნაკრების შექმნა Stop Global Edit გლობალური რედაქტირების დასრულება Global Edit Options გლობალური რედაქტირების პარამეტრები Match დამთხვევა diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 5c30daf54..29bf525b9 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -5061,6 +5061,12 @@ axis Y Axis Y축 @msg a live effect runs while the document is drawn, so it can't be `heavy` 라이브 효과는 문서를 그리는 동안 실행되므로 `heavy`일 수 없습니다 @msg an exporter always gets an export's budget: leave `budget` out 내보내기 플러그인은 항상 내보내기 예산을 받습니다. `budget`은 생략하세요 @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}”은(는) 큰 계산 예산을 요청합니다(실행당 최대 {minutes}분, 평소의 {times}배 명령어). 사용자가 동의하면 `allowHeavy: true`로 설치하세요 +# Actions panel: action sets (#3712). + New Set… 새 세트… + New Set 새 세트 + Set Options… 세트 옵션… + Set Options 세트 옵션 + Create New Set 새 세트 만들기 Stop Global Edit 전체 편집 중지 Global Edit Options 전체 편집 옵션 Match 일치 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 43bcc5525..1c9f28788 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -5489,6 +5489,12 @@ axis Y Axis Y-as The plug-in is computing… De plug-in is aan het rekenen… Inspect Plug-in Plug-in inspecteren Cancel Plug-in Run Plug-in-run annuleren +# Actions panel: action sets (#3712). + New Set… Nieuwe set… + New Set Nieuwe set + Set Options… Setopties… + Set Options Setopties + Create New Set Nieuwe set maken Stop Global Edit Globale bewerking stoppen Global Edit Options Opties voor globale bewerking Match Overeenkomst diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 715448366..cb5246e7b 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -5333,6 +5333,12 @@ axis Y Axis Oś Y @msg a live effect runs while the document is drawn, so it can't be `heavy` efekt na żywo działa podczas rysowania dokumentu, więc nie może być `heavy` @msg an exporter always gets an export's budget: leave `budget` out wtyczka eksportu zawsze dostaje budżet eksportu: pomiń `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees „{_1}” prosi o duży budżet obliczeń (każde uruchomienie do {minutes} minut i {times} razy więcej instrukcji niż zwykle): zainstaluj wtyczkę z `allowHeavy: true`, gdy użytkownik się zgodzi +# Actions panel: action sets (#3712). + New Set… Nowy zestaw… + New Set Nowy zestaw + Set Options… Opcje zestawu… + Set Options Opcje zestawu + Create New Set Utwórz nowy zestaw Stop Global Edit Zakończ edycję globalną Global Edit Options Opcje edycji globalnej Match Dopasowanie diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 38c859cea..1f38dba67 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -5398,6 +5398,12 @@ axis Y Axis Eixo Y @msg a live effect runs while the document is drawn, so it can't be `heavy` um efeito dinâmico é executado enquanto o documento é desenhado, então não pode ser `heavy` @msg an exporter always gets an export's budget: leave `budget` out um plug-in de exportação sempre recebe o orçamento de uma exportação: omita `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}” pede um orçamento de processamento grande (cada execução até {minutes} minutos e {times} vezes as instruções habituais): instale-o com `allowHeavy: true` depois que o usuário concordar +# Actions panel: action sets (#3712). + New Set… Novo Conjunto… + New Set Novo Conjunto + Set Options… Opções do Conjunto… + Set Options Opções do Conjunto + Create New Set Criar Novo Conjunto Stop Global Edit Parar Edição Global Global Edit Options Opções de Edição Global Match Corresponder diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index ce318e477..8bc6a82ae 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -5094,6 +5094,12 @@ axis Y Axis Ось Y @msg a live effect runs while the document is drawn, so it can't be `heavy` динамический эффект выполняется во время отрисовки документа, поэтому он не может быть `heavy` @msg an exporter always gets an export's budget: leave `budget` out модуль экспорта всегда получает бюджет экспорта: не указывайте `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees «{_1}» запрашивает большой вычислительный бюджет (каждый запуск до {minutes} минут и в {times} раз больше инструкций, чем обычно): установите его с `allowHeavy: true`, когда пользователь согласится +# Actions panel: action sets (#3712). + New Set… Новый набор… + New Set Новый набор + Set Options… Параметры набора… + Set Options Параметры набора + Create New Set Создать новый набор Stop Global Edit Завершить глобальное редактирование Global Edit Options Параметры глобального редактирования Match Совпадение diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index f04ff921c..a38f6bdac 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -5437,6 +5437,12 @@ axis Y Axis Y-axel @msg a live effect runs while the document is drawn, so it can't be `heavy` en live-effekt körs medan dokumentet ritas, så den kan inte vara `heavy` @msg an exporter always gets an export's budget: leave `budget` out ett exportinsticksprogram får alltid en exports budget: utelämna `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees ”{_1}” begär en stor beräkningsbudget (varje körning upp till {minutes} minuter och {times} gånger de vanliga instruktionerna): installera det med `allowHeavy: true` när användaren har godkänt +# Actions panel: action sets (#3712). + New Set… Ny uppsättning… + New Set Ny uppsättning + Set Options… Alternativ för uppsättning… + Set Options Alternativ för uppsättning + Create New Set Skapa ny uppsättning Stop Global Edit Avsluta global redigering Global Edit Options Alternativ för global redigering Match Matcha diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 2074c6375..478773e92 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -5444,6 +5444,12 @@ axis Y Axis Y ekseni @msg a live effect runs while the document is drawn, so it can't be `heavy` canlı efekt belge çizilirken çalışır, bu yüzden `heavy` olamaz @msg an exporter always gets an export's budget: leave `budget` out dışa aktarma eklentisi her zaman bir dışa aktarmanın bütçesini alır: `budget` anahtarını çıkarın @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}” büyük bir hesaplama bütçesi istiyor (her çalıştırma en fazla {minutes} dakika ve olağan komutların {times} katı): kullanıcı onayladıktan sonra `allowHeavy: true` ile yükleyin +# Actions panel: action sets (#3712). + New Set… Yeni Set… + New Set Yeni Set + Set Options… Set Seçenekleri… + Set Options Set Seçenekleri + Create New Set Yeni Set Oluştur Stop Global Edit Genel Düzenlemeyi Durdur Global Edit Options Genel Düzenleme Seçenekleri Match Eşleştir diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index b08cf96b5..82a033d6f 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -5078,6 +5078,12 @@ axis Y Axis Вісь Y @msg a live effect runs while the document is drawn, so it can't be `heavy` динамічний ефект виконується під час відмальовування документа, тому він не може бути `heavy` @msg an exporter always gets an export's budget: leave `budget` out модуль експорту завжди отримує бюджет експорту: не вказуйте `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees «{_1}» запитує великий обчислювальний бюджет (кожен запуск до {minutes} хвилин і в {times} разів більше інструкцій, ніж зазвичай): встановіть його з `allowHeavy: true`, коли користувач погодиться +# Actions panel: action sets (#3712). + New Set… Новий набір… + New Set Новий набір + Set Options… Параметри набору… + Set Options Параметри набору + Create New Set Створити новий набір Stop Global Edit Завершити глобальне редагування Global Edit Options Параметри глобального редагування Match Збіг diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 38c398d82..3f8d8558a 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -5078,6 +5078,12 @@ axis Y Axis Trục Y @msg a live effect runs while the document is drawn, so it can't be `heavy` hiệu ứng trực tiếp chạy trong lúc tài liệu được vẽ nên không thể là `heavy` @msg an exporter always gets an export's budget: leave `budget` out plug-in xuất luôn nhận ngân sách của một lần xuất: hãy bỏ `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}” yêu cầu ngân sách tính toán lớn (mỗi lần chạy tới {minutes} phút và gấp {times} lần số lệnh thông thường): hãy cài đặt với `allowHeavy: true` khi người dùng đồng ý +# Actions panel: action sets (#3712). + New Set… Bộ mới… + New Set Bộ mới + Set Options… Tùy chọn bộ… + Set Options Tùy chọn bộ + Create New Set Tạo bộ mới Stop Global Edit Dừng chỉnh sửa toàn cục Global Edit Options Tùy chọn chỉnh sửa toàn cục Match Khớp diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index dbcbe27e5..3d13c209f 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -5321,6 +5321,12 @@ axis Y Axis Y 轴 @msg a live effect runs while the document is drawn, so it can't be `heavy` 实时效果在绘制文档时运行,因此不能为 `heavy` @msg an exporter always gets an export's budget: leave `budget` out 导出增效工具始终使用导出的预算:请省略 `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}”需要大量计算预算(每次运行最多 {minutes} 分钟,指令数为通常的 {times} 倍):请在用户同意后使用 `allowHeavy: true` 安装 +# Actions panel: action sets (#3712). + New Set… 新建组… + New Set 新建组 + Set Options… 组选项… + Set Options 组选项 + Create New Set 创建新组 Stop Global Edit 停止全局编辑 Global Edit Options 全局编辑选项 Match 匹配 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 24b7fd367..2074a9ce5 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -5117,6 +5117,12 @@ axis Y Axis Y 軸 @msg a live effect runs while the document is drawn, so it can't be `heavy` 即時效果會在繪製文件時執行,因此不能是 `heavy` @msg an exporter always gets an export's budget: leave `budget` out 匯出增效模組一律使用匯出的預算:請移除 `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees 「{_1}」需要大量運算預算(每次執行最多 {minutes} 分鐘,指令數為一般的 {times} 倍):請在使用者同意後以 `allowHeavy: true` 安裝 +# Actions panel: action sets (#3712). + New Set… 新增組… + New Set 新增組 + Set Options… 組選項… + Set Options 組選項 + Create New Set 建立新組 Stop Global Edit 停止整體編輯 Global Edit Options 整體編輯選項 Match 符合 diff --git a/crates/ui-egui/src/panels/actions.rs b/crates/ui-egui/src/panels/actions.rs index c7f6974ae..105d18f44 100644 --- a/crates/ui-egui/src/panels/actions.rs +++ b/crates/ui-egui/src/panels/actions.rs @@ -55,6 +55,15 @@ pub fn play(app: &mut VectorcraftApp, set: usize, idx: usize) -> Result = pstate(ui.ctx(), "actions-selected"); let has = selected.and_then(|(s, a)| app.ui.action_sets.get(s).and_then(|set| set.actions.get(a))).is_some(); + if widgets::menu_item(ui, tl!("New Set…"), true, false) { + crate::dialogs::action_set_options::open_new(app); + } + let current = app.ui.action_set.filter(|&s| s < app.ui.action_sets.len()); + if widgets::menu_item(ui, tl!("Set Options…"), current.is_some(), false) + && let Some(s) = current + { + crate::dialogs::action_set_options::open(app, s); + } if widgets::menu_item(ui, tl!("Batch…"), has, false) && let Some((s, a)) = selected && let Err(e) = crate::dialogs::action_batch::open(app, s, a) @@ -75,10 +84,26 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) { // value per type, so reading the bare pair never found it and no action stayed selected (#1629). const SELECTED: &str = "actions-selected"; let mut selected: Option<(usize, usize)> = pstate(ui.ctx(), SELECTED); + // A set made with New Set, or whose name was clicked, is current with none of its actions + // selected. + if selected.is_some_and(|(s, _)| app.ui.action_set != Some(s)) { + selected = None; + } + let mut current = app.ui.action_set; let mut play_req = None; egui::ScrollArea::vertical().max_height(260.0).show(ui, |ui| { for (si, set) in app.ui.action_sets.iter().enumerate() { - ui.label(egui::RichText::new(format!("▾ {}", set.name)).color(t.text)); + let (r, resp) = ui.allocate_exact_size(egui::vec2(ui.available_width(), 20.0), egui::Sense::click()); + if current == Some(si) && selected.is_none() { + ui.painter().rect_filled(r, 0.0, t.row_selected); + } else if resp.hovered() { + ui.painter().rect_filled(r, 0.0, t.hover); + } + ui.painter().text(r.left_center(), egui::Align2::LEFT_CENTER, format!("▾ {}", set.name), egui::FontId::proportional(12.5), t.text); + if resp.clicked() { + current = Some(si); + selected = None; + } for (ai, a) in set.actions.iter().enumerate() { let sel = selected == Some((si, ai)); let (r, resp) = ui.allocate_exact_size(egui::vec2(ui.available_width(), 22.0), egui::Sense::click()); @@ -103,6 +128,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) { ); if resp.clicked() { selected = Some((si, ai)); + current = Some(si); } if resp.double_clicked() { play_req = Some((si, ai)); @@ -111,8 +137,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) { } }); set_pstate(ui.ctx(), SELECTED, selected); + app.ui.action_set = current; widgets::divider(ui); - // Bottom bar: stop, record, play, new, delete. + // Bottom bar: stop, record, play, new set, delete. ui.horizontal(|ui| { let recording = app.ui.recording.is_some(); if widgets::icon_button(ui, "square", tl!("Stop Playing/Recording"), false, 24.0).clicked() @@ -129,11 +156,11 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) { let (r, resp) = ui.allocate_exact_size(egui::vec2(24.0, 24.0), egui::Sense::click()); ui.painter().circle_filled(r.center(), 6.0, if recording { egui::Color32::from_rgb(0xe0, 0x30, 0x30) } else { t.icon }); if resp.on_hover_text(tl!("Begin Recording")).clicked() && !recording { - let n = app.ui.action_sets.first().map(|s| s.actions.len()).unwrap_or(0) + 1; if app.ui.action_sets.is_empty() { app.ui.action_sets.push(ActionSet { name: "Set 1".into(), actions: vec![] }); } - let set = selected.map(|s| s.0).unwrap_or(0); + let set = current.filter(|&s| s < app.ui.action_sets.len()).unwrap_or(0); + let n = app.ui.action_sets.get(set).map_or(0, |s| s.actions.len()) + 1; let start = app.session.journal.len(); // Keep every step from here on until recording stops, however long it runs. app.session.journal.hold(Some(start)); @@ -144,6 +171,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) { { play_req = Some(s); } + if widgets::icon_button(ui, "dc-folder", tl!("Create New Set"), false, 24.0).clicked() { + crate::dialogs::action_set_options::open_new(app); + } if widgets::icon_button(ui, "trash-2", tl!("Delete Selection"), false, 24.0).clicked() && let Some((si, ai)) = selected && let Some(s) = app.ui.action_sets.get_mut(si) @@ -197,10 +227,10 @@ mod tests { click(&ctx, &mut app, text_rect(&texts, "Reflect Horizontal").center(), show); frame_events(&ctx, &mut app, vec![], show); assert_eq!(pstate::>(&ctx, "actions-selected"), Some((0, 5)), "selected on the frames after the click"); - // The bottom bar's 24-point buttons, left to right: stop, record, play, delete. + // The bottom bar's 24-point buttons, left to right: stop, record, play, new set, delete. let mut bar = crate::toolbar::tests::widget_rects(&ctx, egui::vec2(24.0, 24.0)); bar.sort_by(|a, b| a.left().total_cmp(&b.left())); - let [_, _, play, delete] = bar[..] else { panic!("the bottom bar: {bar:?}") }; + let [_, _, play, _, delete] = bar[..] else { panic!("the bottom bar: {bar:?}") }; click(&ctx, &mut app, text_rect(&texts, "Duplicate and Offset 10 pt").center(), show); click(&ctx, &mut app, play.center(), show); assert_eq!(app.session.active().unwrap().selection.len(), 1); @@ -213,6 +243,72 @@ mod tests { assert_eq!(names(&app), before[1..], "nothing selected after a delete: nothing more goes"); } + /// #3712: Create New Set asks for a name (Set 1 when free), the new set is the current one, so a + /// recording goes into it, and a click on another set's name makes that one current. The sets + /// are saved with the UI preferences. + #[test] + fn create_new_set_names_a_set_that_recordings_go_into() { + use super::super::tests_appearance::{click, frame_events, text_rect}; + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 200, "height": 100})).unwrap(); + let ctx = egui::Context::default(); + // The bottom bar's 24-point buttons, left to right: stop, record, play, new set, delete. A new + // set or action moves the bar down. + let bar = |ctx: &egui::Context, app: &mut VectorcraftApp| { + frame_events(ctx, app, vec![], show); + let mut bar = crate::toolbar::tests::widget_rects(ctx, egui::vec2(24.0, 24.0)); + bar.sort_by(|a, b| a.left().total_cmp(&b.left())); + let [stop, record, _, new_set, _] = bar[..] else { panic!("the bottom bar: {bar:?}") }; + (stop, record, new_set) + }; + let (_, _, new_set) = bar(&ctx, &mut app); + click(&ctx, &mut app, new_set.center(), show); + let d = app.ui.dialog.as_ref().expect("New Set asks for a name"); + assert_eq!((d.kind.as_str(), d.str("name")), ("actionSetOptions", "Set 1".to_string())); + app.ui.dialog.as_mut().unwrap().fields.insert("name".into(), json!("Print")); + crate::dialogs::confirm(&mut app).unwrap(); + assert!(app.ui.dialog.is_none()); + let sets = |app: &VectorcraftApp| app.ui.action_sets.iter().map(|s| (s.name.clone(), s.actions.len())).collect::>(); + assert_eq!(sets(&app), [("Default Actions".to_string(), 6), ("Print".into(), 0)]); + let rect = json!({"x": 10, "y": 10, "width": 20, "height": 20}); + let (stop, record, _) = bar(&ctx, &mut app); + click(&ctx, &mut app, record.center(), show); + app.run("shape.rectangle", rect.clone()).unwrap(); + click(&ctx, &mut app, stop.center(), show); + assert_eq!(sets(&app), [("Default Actions".to_string(), 6), ("Print".into(), 1)], "recorded into the new set"); + assert_eq!(app.ui.action_sets[1].actions[0].name, "Action 1", "numbered within its set"); + let texts = frame_events(&ctx, &mut app, vec![], show); + click(&ctx, &mut app, text_rect(&texts, "▾ Default Actions").center(), show); + let (stop, record, _) = bar(&ctx, &mut app); + click(&ctx, &mut app, record.center(), show); + app.run("shape.rectangle", rect).unwrap(); + click(&ctx, &mut app, stop.center(), show); + assert_eq!(sets(&app), [("Default Actions".to_string(), 7), ("Print".into(), 1)], "recorded into the set clicked"); + let saved = serde_json::to_value(&app.ui).unwrap(); + let restored = serde_json::from_value::(saved).unwrap(); + assert_eq!(restored.action_sets.iter().map(|s| s.name.as_str()).collect::>(), ["Default Actions", "Print"]); + } + + /// #3712: the panel menu's Set Options… renames the current set and keeps its actions. + #[test] + fn set_options_renames_the_current_set() { + use super::super::tests_appearance::{click, frame_events, text_rect}; + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.ui.action_sets.push(ActionSet { name: "Mine".into(), actions: vec![] }); + let ctx = egui::Context::default(); + let texts = frame_events(&ctx, &mut app, vec![], show); + click(&ctx, &mut app, text_rect(&texts, "▾ Mine").center(), show); + let texts = frame_events(&ctx, &mut app, vec![], menu); + let item = texts.iter().find(|(s, _)| s.trim() == "Set Options…").map(|(_, r)| *r).expect("Set Options… in the menu"); + click(&ctx, &mut app, item.center(), menu); + let d = app.ui.dialog.as_ref().expect("Set Options opens"); + assert_eq!((d.kind.as_str(), d.str("name")), ("actionSetOptions", "Mine".to_string())); + app.ui.dialog.as_mut().unwrap().fields.insert("name".into(), json!("Web")); + crate::dialogs::confirm(&mut app).unwrap(); + let names = app.ui.action_sets.iter().map(|s| (s.name.as_str(), s.actions.len())).collect::>(); + assert_eq!(names, [("Default Actions", 6), ("Web", 0)], "the clicked set is renamed, the other kept"); + } + /// A recording keeps its steps when a canceled undo group drops journal entries made before the /// recording began. #[test] @@ -231,10 +327,10 @@ mod tests { } let ctx = egui::Context::default(); frame_events(&ctx, &mut app, vec![], show); - // The bottom bar's 24-point buttons, left to right: stop, record, play, delete. + // The bottom bar's 24-point buttons, left to right: stop, record, play, new set, delete. let mut bar = crate::toolbar::tests::widget_rects(&ctx, egui::vec2(24.0, 24.0)); bar.sort_by(|a, b| a.left().total_cmp(&b.left())); - let [stop, record, _, _] = bar[..] else { panic!("the bottom bar: {bar:?}") }; + let [stop, record, _, _, _] = bar[..] else { panic!("the bottom bar: {bar:?}") }; click(&ctx, &mut app, record.center(), show); for (id, p) in [("document.activate", json!({"index": 1})), ("shape.rectangle", rect(50)), ("edit.undoGroup.end", json!({"cancel": true}))] { app.run(id, p).unwrap(); diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 4899251c6..4d8387f58 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -425,6 +425,10 @@ pub struct UiState { /// Recorded actions (Actions panel), persisted with the UI preferences. #[serde(default = "crate::panels::actions::default_sets")] pub action_sets: Vec, + /// The current set of the Actions panel (its name clicked, one of its actions clicked, or made + /// with New Set): a recording goes into it and Set Options renames it. Not saved. + #[serde(skip)] + pub action_set: Option, /// Recording in progress: (set index, action name, journal length when recording started). #[serde(skip)] pub recording: Option<(usize, String, usize)>, @@ -860,6 +864,7 @@ impl Default for UiState { draw_mode: 0, about: false, action_sets: crate::panels::actions::default_sets(), + action_set: None, recording: None, shortcut_overrides: Default::default(), shortcut_set: default_shortcut_set(), diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 7c3c5ad5a..7975c3591 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -276,6 +276,7 @@ empty), without opening the dialog. | Live trace result bounds and selection highlights exclude the retained bitmap; source-image views retain it | done | All TraceView modes and rotated result bounds regression-covered; ordinary hidden group behavior retained | | 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: 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)) | partial | #3712; the New Action dialog (name, set menu, shortcut, colour), dragging actions between sets and deleting a set remain | | Properties panel per context (every selection type's Quick Actions) | partial | one clip group: Edit Clipping Path and Edit Contents, at the top of the panel rather than in Quick Actions (G1) | | Select › Start Global Edit / Stop Global Edit; Properties › Quick Actions with the Global Edit Options popover (Match: Appearance, Size; On Artboards: All Artboards or a range, Include Objects On Canvas; Result(s)); Escape stops it; the matched objects are highlighted in their layer colour while it runs | partial | #3521, `select.globalEdit` / `select.globalEditOptions`, `crates/engine/src/tests_globaledit.rs`; paths, compound paths and symbol instances; the Control panel's copy of the options, Shift-click to leave a match out, highlighting matches before the start and similar groups remain ([public source](https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-similar-objects.html)) | | Contextual task bar | done | | @@ -342,6 +343,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development | Date | Change | Summary | |---|---|---| +| 2026-10-23 | minor | Actions panel: Create New Set and Set Options… name action sets, and a recording goes into the set clicked or just made (#3712). Scores and estimates unchanged. | | 2026-10-23 | minor | Select › Start Global Edit / Stop Global Edit (menu label flips), Properties › Quick Actions with the Global Edit Options popover (Match: Appearance, Size; On Artboards: All Artboards or a range, Include Objects On Canvas; Result(s)), Escape stops it, the matches are highlighted on the canvas (#3521). Scores and estimates unchanged. | | 2026-10-23 | minor | Symbol libraries: Window › Symbol Libraries and the Symbols panel's Symbol Libraries Menu open library panels (Basic Shapes, User Defined, Other Library…); click adds, drag places, Save Symbols / Save Symbol Library writes a library (`panels::tests_symbol_libraries`). | | 2026-10-23 | minor | Selection tool: every selected area type and type on a path shows its in and out ports in their states (empty, arrow, red "+"); the out port of type that fits loads the text too, and the in port threads a new or clicked frame before the clicked one (#2572). Scores and estimates unchanged. |