mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-31 22:06:49 -06:00
This commit is contained in:
+2
-1
File diff suppressed because one or more lines are too long
@@ -392,6 +392,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "داكن"
|
||||
"Darken" = "تعتيم"
|
||||
"Dash" = "شرطة"
|
||||
"Dashes" = "الشرطات"
|
||||
"Dashes take numbers separated by commas" = "تقبل الشرطات أرقامًا مفصولة بفواصل"
|
||||
"Dashboard" = "لوحة المعلومات"
|
||||
"Date created" = "تاريخ الإنشاء"
|
||||
"Dec" = "ديسمبر"
|
||||
|
||||
@@ -372,6 +372,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "ডার্ক"
|
||||
"Darken" = "ডার্কেন"
|
||||
"Dash" = "ড্যাশ"
|
||||
"Dashes" = "ড্যাশগুলো"
|
||||
"Dashes take numbers separated by commas" = "ড্যাশগুলো কমা দিয়ে আলাদা করা সংখ্যা হবে"
|
||||
"Dashboard" = "ড্যাশবোর্ড"
|
||||
"Date created" = "তৈরির তারিখ"
|
||||
"Dec" = "ডিসেম্বর"
|
||||
|
||||
@@ -395,6 +395,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "Tamna"
|
||||
"Darken" = "Zatamni"
|
||||
"Dash" = "Crtica"
|
||||
"Dashes" = "Crtice"
|
||||
"Dashes take numbers separated by commas" = "Crtice su brojevi odvojeni zarezima"
|
||||
"Dashboard" = "Kontrolna tabla"
|
||||
"Date created" = "Datum kreiranja"
|
||||
"Dec" = "dec."
|
||||
|
||||
@@ -362,6 +362,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "Tmavý"
|
||||
"Darken" = "Ztmavit"
|
||||
"Dash" = "Čárkování"
|
||||
"Dashes" = "Čárkování"
|
||||
"Dashes take numbers separated by commas" = "Čárkování zadejte jako čísla oddělená čárkami"
|
||||
"Dashboard" = "Přehled"
|
||||
"Date created" = "Datum vytvoření"
|
||||
"Dec" = "prosince"
|
||||
|
||||
@@ -362,6 +362,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "Dunkel"
|
||||
"Darken" = "Abdunkeln"
|
||||
"Dash" = "Gestrichelt"
|
||||
"Dashes" = "Striche"
|
||||
"Dashes take numbers separated by commas" = "Striche sind Zahlen, durch Kommas getrennt"
|
||||
"Decoration style" = "Linienstil"
|
||||
"Decoration" = "Auszeichnung"
|
||||
"Deep select" = "Tiefe Auswahl"
|
||||
|
||||
@@ -397,6 +397,8 @@ name = "English"
|
||||
"Dark" = "Dark"
|
||||
"Darken" = "Darken"
|
||||
"Dash" = "Dash"
|
||||
"Dashes" = "Dashes"
|
||||
"Dashes take numbers separated by commas" = "Dashes take numbers separated by commas"
|
||||
"Dashboard" = "Dashboard"
|
||||
"Date created" = "Date created"
|
||||
"Dec" = "Dec"
|
||||
|
||||
@@ -396,6 +396,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "Oscuro"
|
||||
"Darken" = "Oscurecer"
|
||||
"Dash" = "Discontinuo"
|
||||
"Dashes" = "Guiones"
|
||||
"Dashes take numbers separated by commas" = "Los guiones son números separados por comas"
|
||||
"Dashboard" = "Panel de control"
|
||||
"Date created" = "Fecha de creación"
|
||||
"Dec" = "dic"
|
||||
|
||||
@@ -396,6 +396,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "Tumma"
|
||||
"Darken" = "Tummenna"
|
||||
"Dash" = "Katkoviiva"
|
||||
"Dashes" = "Katkoviivat"
|
||||
"Dashes take numbers separated by commas" = "Anna katkoviivat pilkuilla erotettuina lukuina"
|
||||
"Dashboard" = "Koontinäkymä"
|
||||
"Date created" = "Luontipäivä"
|
||||
"Dec" = "jouluk."
|
||||
|
||||
@@ -372,6 +372,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "Sombre"
|
||||
"Darken" = "Assombrir"
|
||||
"Dash" = "Tirets"
|
||||
"Dashes" = "Tirets"
|
||||
"Dashes take numbers separated by commas" = "Les tirets sont des nombres séparés par des virgules"
|
||||
"Dashboard" = "Tableau de bord"
|
||||
"Date created" = "Date de création"
|
||||
"Dec" = "déc."
|
||||
|
||||
@@ -377,6 +377,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "Gelap"
|
||||
"Darken" = "Gelapkan"
|
||||
"Dash" = "Putus-putus"
|
||||
"Dashes" = "Garis putus"
|
||||
"Dashes take numbers separated by commas" = "Garis putus berupa angka yang dipisahkan koma"
|
||||
"Dashboard" = "Dasbor"
|
||||
"Date created" = "Tanggal dibuat"
|
||||
"Dec" = "Des"
|
||||
|
||||
@@ -356,6 +356,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "Scuro"
|
||||
"Darken" = "Scurisci"
|
||||
"Dash" = "Tratteggio"
|
||||
"Dashes" = "Trattini"
|
||||
"Dashes take numbers separated by commas" = "I trattini sono numeri separati da virgole"
|
||||
"Dashboard" = "Dashboard"
|
||||
"Date created" = "Data di creazione"
|
||||
"Dec" = "dic"
|
||||
|
||||
@@ -370,6 +370,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "ダーク"
|
||||
"Darken" = "比較(暗)"
|
||||
"Dash" = "破線"
|
||||
"Dashes" = "ダッシュ"
|
||||
"Dashes take numbers separated by commas" = "ダッシュはカンマ区切りの数値で入力します"
|
||||
"Dashboard" = "ダッシュボード"
|
||||
"Date created" = "作成日"
|
||||
"Dec" = "12月"
|
||||
|
||||
@@ -377,6 +377,8 @@ status = "draft — reviewed by a native speaker, awaiting a maintainer's approv
|
||||
"Dark" = "ბნელი"
|
||||
"Darken" = "ჩამუქება"
|
||||
"Dash" = "ტირე"
|
||||
"Dashes" = "ტირეები"
|
||||
"Dashes take numbers separated by commas" = "ტირეები მძიმით გამოყოფილი რიცხვებია"
|
||||
"Dashboard" = "სამართავი დაფა"
|
||||
"Date created" = "შექმნის თარიღი"
|
||||
"Dec" = "დეკ"
|
||||
|
||||
@@ -358,6 +358,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "다크"
|
||||
"Darken" = "어둡게 하기"
|
||||
"Dash" = "대시"
|
||||
"Dashes" = "대시"
|
||||
"Dashes take numbers separated by commas" = "대시는 쉼표로 구분한 숫자로 입력하세요"
|
||||
"Decoration style" = "장식 스타일"
|
||||
"Decoration" = "장식"
|
||||
"Deep select" = "깊은 선택"
|
||||
|
||||
@@ -374,6 +374,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "Donker"
|
||||
"Darken" = "Darken"
|
||||
"Dash" = "Streep"
|
||||
"Dashes" = "Streepjes"
|
||||
"Dashes take numbers separated by commas" = "Streepjes zijn getallen gescheiden door komma's"
|
||||
"Dashboard" = "Dashboard"
|
||||
"Date created" = "Aanmaakdatum"
|
||||
"Dec" = "dec."
|
||||
|
||||
@@ -373,6 +373,8 @@ name = "Polski"
|
||||
"Dark" = "Ciemny"
|
||||
"Darken" = "Ciemniej"
|
||||
"Dash" = "Kreska"
|
||||
"Dashes" = "Kreski"
|
||||
"Dashes take numbers separated by commas" = "Kreski to liczby oddzielone przecinkami"
|
||||
"Dashboard" = "Pulpit"
|
||||
"Date created" = "Data utworzenia"
|
||||
"Dec" = "Gru"
|
||||
|
||||
@@ -357,6 +357,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "Escuro"
|
||||
"Darken" = "Escurecer"
|
||||
"Dash" = "Tracejado"
|
||||
"Dashes" = "Traços"
|
||||
"Dashes take numbers separated by commas" = "Os traços são números separados por vírgulas"
|
||||
"Decoration style" = "Estilo da decoração"
|
||||
"Decoration" = "Decoração"
|
||||
"Deep select" = "Seleção profunda"
|
||||
|
||||
@@ -373,6 +373,8 @@ name = "Русский"
|
||||
"Dark" = "Тёмная"
|
||||
"Darken" = "Затемнение"
|
||||
"Dash" = "Штрих"
|
||||
"Dashes" = "Штрихи"
|
||||
"Dashes take numbers separated by commas" = "Штрихи задаются числами через запятую"
|
||||
"Dashboard" = "Панель аналитики"
|
||||
"Date created" = "Дата создания"
|
||||
"Dec" = "дек."
|
||||
|
||||
@@ -393,6 +393,8 @@ name = "Svenska"
|
||||
"Dark" = "Mörk"
|
||||
"Darken" = "Mörkare"
|
||||
"Dash" = "Streck"
|
||||
"Dashes" = "Streck"
|
||||
"Dashes take numbers separated by commas" = "Streck anges som tal åtskilda med kommatecken"
|
||||
"Dashboard" = "Instrumentpanel"
|
||||
"Date created" = "Skapandedatum"
|
||||
"Dec" = "Dec"
|
||||
|
||||
@@ -371,6 +371,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "มืด"
|
||||
"Darken" = "Darken"
|
||||
"Dash" = "เส้นประ"
|
||||
"Dashes" = "เส้นประ"
|
||||
"Dashes take numbers separated by commas" = "ใส่เส้นประเป็นตัวเลขคั่นด้วยจุลภาค"
|
||||
"Dashboard" = "แดชบอร์ด"
|
||||
"Date created" = "วันที่สร้าง"
|
||||
"Dec" = "ธ.ค."
|
||||
|
||||
@@ -366,6 +366,8 @@ name = "Türkçe"
|
||||
"Dark" = "Koyu"
|
||||
"Darken" = "Koyulaştır"
|
||||
"Dash" = "Kesik çizgi"
|
||||
"Dashes" = "Kesik çizgiler"
|
||||
"Dashes take numbers separated by commas" = "Kesik çizgiler virgülle ayrılmış sayılardır"
|
||||
"Dashboard" = "Pano"
|
||||
"Date created" = "Oluşturulma tarihi"
|
||||
"Dec" = "Ara"
|
||||
|
||||
@@ -357,6 +357,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "Tối"
|
||||
"Darken" = "Làm tối"
|
||||
"Dash" = "Nét đứt"
|
||||
"Dashes" = "Các nét đứt"
|
||||
"Dashes take numbers separated by commas" = "Nét đứt là các số cách nhau bằng dấu phẩy"
|
||||
"Dashboard" = "Bảng điều khiển"
|
||||
"Date created" = "Ngày tạo"
|
||||
"Dec" = "Thg 12"
|
||||
|
||||
@@ -362,6 +362,8 @@ status = "draft — needs native review"
|
||||
"Dark" = "深色"
|
||||
"Darken" = "变暗"
|
||||
"Dash" = "虚线"
|
||||
"Dashes" = "虚线"
|
||||
"Dashes take numbers separated by commas" = "虚线为以逗号分隔的数字"
|
||||
"Decoration style" = "装饰线样式"
|
||||
"Decoration" = "装饰线"
|
||||
"Deep select" = "深度选择"
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
//! The stroke settings' Dash and Gap fields edit the first pair of a dash pattern and keep the
|
||||
//! rest of it (#1681): an imported `[4, 2, 1, 2]` used to collapse to `[dash, gap]` on the first edit.
|
||||
//! The stroke settings' dash pattern: Dash and Gap edit a pair, and the Custom style's Dashes field
|
||||
//! takes the whole pattern as `dash, gap, dash, gap…` (#1681). A longer pattern shows there, so an
|
||||
//! imported `[4, 2, 1, 2]` stays whole.
|
||||
|
||||
use egui::{Event, Id, Key, Modifiers};
|
||||
use figcraft_doc::NodeId;
|
||||
use figcraft_engine::Session;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::export_preview_tests::{click, labelled};
|
||||
use crate::layers_drag_tests::Ui;
|
||||
|
||||
/// A selected rectangle with a stroke dashed `dashes`, its advanced stroke settings shown.
|
||||
@@ -24,9 +26,14 @@ fn dashes(u: &Ui, r: NodeId) -> Vec<f64> {
|
||||
u.app.session.doc().unwrap().node(r).unwrap().stroke.dashes.clone()
|
||||
}
|
||||
|
||||
/// Type `text` into the number field `id` and press Enter.
|
||||
fn type_into(u: &mut Ui, id: &str, text: &str) {
|
||||
let at = u.ctx.read_response(Id::unique(id).with("field")).unwrap_or_else(|| panic!("{id} shows")).rect.center();
|
||||
/// Whether the field `id` (with `part`) showed in the last frame.
|
||||
fn shows(u: &Ui, id: &str, part: &str) -> bool {
|
||||
u.ctx.read_response(Id::unique(id).with(part)).is_some()
|
||||
}
|
||||
|
||||
/// Click the field `id` (at `part`), type `text` and press Enter.
|
||||
fn type_in(u: &mut Ui, id: &str, part: &str, text: &str) {
|
||||
let at = u.ctx.read_response(Id::unique(id).with(part)).unwrap_or_else(|| panic!("{id} shows")).rect.center();
|
||||
u.button(at, true);
|
||||
u.button(at, false);
|
||||
u.frame(vec![]);
|
||||
@@ -38,13 +45,103 @@ fn type_into(u: &mut Ui, id: &str, text: &str) {
|
||||
u.frame(vec![]);
|
||||
}
|
||||
|
||||
/// Type `text` into the number field `id` and press Enter.
|
||||
fn type_into(u: &mut Ui, id: &str, text: &str) {
|
||||
type_in(u, id, "field", text);
|
||||
}
|
||||
|
||||
/// Type `text` into the Dashes field and press Enter.
|
||||
fn type_list(u: &mut Ui, text: &str) {
|
||||
type_in(u, "stroke_dashes", "click", text);
|
||||
}
|
||||
|
||||
/// Open the Stroke style menu (showing `shown`) and pick `item`.
|
||||
fn pick_style(u: &mut Ui, shown: &str, item: &str) {
|
||||
let at = |u: &mut Ui, text: &str, last: bool| {
|
||||
let all: Vec<_> = labelled(u).into_iter().filter(|(t, _)| t == text).map(|x| x.1).collect();
|
||||
// The menu draws over the panel; in the panel the menu's button comes before its fields.
|
||||
*(if last { all.last() } else { all.first() }).unwrap_or_else(|| panic!("no {text:?}"))
|
||||
};
|
||||
let button = at(u, shown, false);
|
||||
click(u, button);
|
||||
let choice = at(u, item, true);
|
||||
click(u, choice);
|
||||
u.frame(vec![]);
|
||||
}
|
||||
|
||||
fn toast(u: &Ui) -> String {
|
||||
u.app.ui.toast.as_ref().map(|t| t.0.clone()).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editing_the_dash_or_gap_keeps_the_rest_of_the_pattern() {
|
||||
fn a_longer_pattern_shows_as_a_list_and_stays_whole() {
|
||||
let (mut u, r) = scene(json!([4, 2, 1, 2]));
|
||||
type_into(&mut u, "stroke_dash", "6");
|
||||
assert_eq!(dashes(&u, r), [6.0, 2.0, 1.0, 2.0]);
|
||||
type_into(&mut u, "stroke_gap", "3");
|
||||
assert_eq!(dashes(&u, r), [6.0, 3.0, 1.0, 2.0]);
|
||||
assert!(shows(&u, "stroke_dashes", "click") && !shows(&u, "stroke_dash", "field"), "Custom, not Dash and Gap");
|
||||
assert!(labelled(&mut u).iter().any(|(t, _)| t == "4, 2, 1, 2"));
|
||||
assert_eq!(dashes(&u, r), [4.0, 2.0, 1.0, 2.0], "showing it changes nothing");
|
||||
// The reference's own example: dashes and gaps of any length, as many as you like.
|
||||
type_list(&mut u, "10, 20, 10, 20, 80, 20, 10, 100");
|
||||
assert_eq!(dashes(&u, r), [10.0, 20.0, 10.0, 20.0, 80.0, 20.0, 10.0, 100.0]);
|
||||
// Spaces don't matter, and an odd count is kept as typed (it draws repeated).
|
||||
type_list(&mut u, "4,2 , 1.5,");
|
||||
assert_eq!(dashes(&u, r), [4.0, 2.0, 1.5]);
|
||||
assert!(labelled(&mut u).iter().any(|(t, _)| t == "4, 2, 1.5"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_numbers_go_in_the_list() {
|
||||
let (mut u, r) = scene(json!([4, 2, 1, 2]));
|
||||
for bad in ["4, x", "-1, 2", "inf", ",", " "] {
|
||||
u.app.ui.toast = None;
|
||||
type_list(&mut u, bad);
|
||||
assert_eq!(dashes(&u, r), [4.0, 2.0, 1.0, 2.0], "{bad:?} changes nothing");
|
||||
assert_eq!(toast(&u), "Dashes take numbers separated by commas", "{bad:?} says why");
|
||||
}
|
||||
// Leaving the text as it was isn't an edit.
|
||||
u.app.ui.toast = None;
|
||||
type_list(&mut u, "4, 2, 1, 2");
|
||||
assert_eq!(toast(&u), "");
|
||||
}
|
||||
|
||||
/// Custom on a plain pair keeps the pattern and shows it as a list, and stays that way after a
|
||||
/// list of one pair is typed; Dash goes back to the fields with the first pair of the list.
|
||||
#[test]
|
||||
fn the_style_menu_moves_between_the_fields_and_the_list() {
|
||||
let (mut u, r) = scene(json!([6, 3]));
|
||||
assert!(shows(&u, "stroke_dash", "field") && !shows(&u, "stroke_dashes", "click"));
|
||||
pick_style(&mut u, "Dash", "Custom");
|
||||
assert!(shows(&u, "stroke_dashes", "click") && !shows(&u, "stroke_dash", "field"));
|
||||
assert_eq!(dashes(&u, r), [6.0, 3.0], "choosing it changes nothing");
|
||||
type_list(&mut u, "5, 5");
|
||||
assert_eq!(dashes(&u, r), [5.0, 5.0]);
|
||||
assert!(shows(&u, "stroke_dashes", "click"), "still the list");
|
||||
type_list(&mut u, "8, 2, 1, 2");
|
||||
pick_style(&mut u, "Custom", "Dash");
|
||||
assert_eq!(dashes(&u, r), [8.0, 2.0], "the first dash and gap");
|
||||
assert!(shows(&u, "stroke_dash", "field") && !shows(&u, "stroke_dashes", "click"));
|
||||
pick_style(&mut u, "Dash", "Solid");
|
||||
assert_eq!(dashes(&u, r), Vec::<f64>::new());
|
||||
// From Solid, a list starts from the same pair Dash does: four stroke widths.
|
||||
pick_style(&mut u, "Solid", "Custom");
|
||||
assert_eq!(dashes(&u, r), [8.0, 8.0]);
|
||||
assert!(shows(&u, "stroke_dashes", "click"));
|
||||
}
|
||||
|
||||
/// Custom chosen for one layer doesn't follow the selection to another.
|
||||
#[test]
|
||||
fn a_chosen_list_belongs_to_its_selection() {
|
||||
let (mut u, r) = scene(json!([6, 3]));
|
||||
pick_style(&mut u, "Dash", "Custom");
|
||||
let other = NodeId(u.app.run("shape.rectangle", json!({"x": 300, "y": 0, "width": 50, "height": 50})).unwrap()["id"].as_u64().unwrap());
|
||||
u.app.run("node.set", json!({"ids": [other], "props": {"strokes": ["#000000"], "dashes": [6, 3]}}));
|
||||
u.app.run("select.set", json!({"ids": [other]}));
|
||||
u.frame(vec![]);
|
||||
u.frame(vec![]);
|
||||
assert!(shows(&u, "stroke_dash", "field"), "a pair shows as Dash and Gap");
|
||||
u.app.run("select.set", json!({"ids": [r]}));
|
||||
u.frame(vec![]);
|
||||
u.frame(vec![]);
|
||||
assert!(shows(&u, "stroke_dashes", "click"), "back on the first layer, the list");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -52,15 +149,6 @@ fn a_single_value_pattern_is_the_dash_and_the_gap() {
|
||||
let (mut u, r) = scene(json!([5]));
|
||||
type_into(&mut u, "stroke_gap", "2");
|
||||
assert_eq!(dashes(&u, r), [5.0, 2.0]);
|
||||
}
|
||||
|
||||
/// An odd-length pattern draws twice over (`[4, 2, 1]` is `[4, 2, 1, 4, 2, 1]`): editing its Dash
|
||||
/// writes the repeat out, so only the first dash changes and every gap stays as drawn.
|
||||
#[test]
|
||||
fn an_odd_length_pattern_keeps_its_repeat() {
|
||||
let (mut u, r) = scene(json!([4, 2, 1]));
|
||||
type_into(&mut u, "stroke_dash", "6");
|
||||
assert_eq!(dashes(&u, r), [6.0, 2.0, 1.0, 4.0, 2.0, 1.0]);
|
||||
type_into(&mut u, "stroke_gap", "3");
|
||||
assert_eq!(dashes(&u, r), [6.0, 3.0, 1.0, 4.0, 2.0, 1.0]);
|
||||
type_into(&mut u, "stroke_dash", "7");
|
||||
assert_eq!(dashes(&u, r), [7.0, 2.0]);
|
||||
}
|
||||
|
||||
@@ -187,11 +187,9 @@ fn brush_row(app: &mut FigApp, ui: &mut Ui, ids: &[NodeId], brush: Option<Option
|
||||
});
|
||||
}
|
||||
|
||||
/// Dash pattern `dashes` with its first dash (`i` 0) or first gap (`i` 1) set to `v`, keeping the
|
||||
/// rest of the pattern: the panel shows only the first pair, and editing it must not collapse an
|
||||
/// imported `[4, 2, 1, 2]` to two values (#1681). An odd-length pattern is drawn repeated
|
||||
/// (`figcraft_pathops::width::dash_pattern`: `[4, 2, 1]` is `[4, 2, 1, 4, 2, 1]`), so it is
|
||||
/// written out that way first and only the edited entry changes; `[5]` becomes `[5, 5]`.
|
||||
/// Dash pattern `dashes` with its first dash (`i` 0) or first gap (`i` 1) set to `v`. A lone value
|
||||
/// is both (`figcraft_pathops::width::dash_pattern` draws odd lengths repeated), so `[5]` is
|
||||
/// written out as `[5, 5]` first and only the edited entry changes.
|
||||
fn set_dash(dashes: &[f64], i: usize, v: f64) -> Vec<f64> {
|
||||
let mut d = if dashes.is_empty() { vec![4.0] } else { dashes.to_vec() };
|
||||
if d.len() % 2 == 1 {
|
||||
@@ -203,38 +201,92 @@ fn set_dash(dashes: &[f64], i: usize, v: f64) -> Vec<f64> {
|
||||
d
|
||||
}
|
||||
|
||||
/// The first dash and gap of a pattern (a lone value is both).
|
||||
fn first_pair(dashes: &[f64]) -> [f64; 2] {
|
||||
let dash = dashes.first().copied().unwrap_or(4.0);
|
||||
[dash, dashes.get(1).copied().unwrap_or(dash)]
|
||||
}
|
||||
|
||||
/// The Dashes field's text as a pattern: numbers of 0 or more separated by commas (`10, 20, 10`).
|
||||
/// `None` for anything else, or nothing at all.
|
||||
fn parse_dashes(text: &str) -> Option<Vec<f64>> {
|
||||
let number = |s: &str| s.parse::<f64>().ok().filter(|v| v.is_finite() && *v >= 0.0);
|
||||
let values = text.split(',').map(str::trim).filter(|s| !s.is_empty()).map(number).collect::<Option<Vec<f64>>>()?;
|
||||
(!values.is_empty()).then_some(values)
|
||||
}
|
||||
|
||||
/// The Stroke style menu and the fields of the style it shows: Dash and Gap, or a Custom list of
|
||||
/// Dashes (`dash, gap, dash, gap…`). The file keeps only the pattern, so one longer than a pair
|
||||
/// shows as Custom; a shorter one does only while Custom was what the selection `ids` last chose.
|
||||
fn dash_settings(app: &mut FigApp, ui: &mut Ui, ids: &[NodeId], dashes: Option<&[f64]>, weight: f64) {
|
||||
let half = widgets::col_width(ui.available_width());
|
||||
let chosen = Id::unique("stroke_dashes_custom");
|
||||
let pattern = dashes.filter(|d| !d.is_empty());
|
||||
let custom = pattern.is_some_and(|d| d.len() > 2 || ui.data(|m| m.get_temp::<Vec<NodeId>>(chosen)).as_deref() == Some(ids));
|
||||
let a_pair = pattern.map_or([weight * 4.0; 2], first_pair);
|
||||
widgets::sublabels(ui, tr("Stroke style"), None);
|
||||
row(ui, 32.0, |ui| {
|
||||
let style = match (pattern, custom) {
|
||||
(None, _) => tr("Solid"),
|
||||
(_, true) => tr("Custom"),
|
||||
(_, false) => tr("Dash"),
|
||||
};
|
||||
let resp = widgets::dropdown(ui, style, half, None);
|
||||
menu_popup(&resp).show(|ui| {
|
||||
if menu_item(ui, tr("Solid"), "") {
|
||||
app.set_props(ids, json!({"dashes": []}));
|
||||
ui.close();
|
||||
}
|
||||
if menu_item(ui, tr("Dash"), "") {
|
||||
ui.data_mut(|m| m.remove::<Vec<NodeId>>(chosen));
|
||||
app.set_props(ids, json!({"dashes": a_pair}));
|
||||
ui.close();
|
||||
}
|
||||
if menu_item(ui, tr("Custom"), "") {
|
||||
ui.data_mut(|m| m.insert_temp(chosen, ids.to_vec()));
|
||||
if pattern.is_none() {
|
||||
app.set_props(ids, json!({"dashes": a_pair}));
|
||||
}
|
||||
ui.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
let Some(d) = pattern else { return };
|
||||
if custom {
|
||||
widgets::sublabels(ui, tr("Dashes"), None);
|
||||
let text = d.iter().map(|v| widgets::fmt_num(*v)).collect::<Vec<_>>().join(", ");
|
||||
row(ui, 32.0, |ui| {
|
||||
let Some(typed) = widgets::text_field(ui, Id::unique("stroke_dashes"), &text, ui.available_width(), "").filter(|t| *t != text) else {
|
||||
return;
|
||||
};
|
||||
match parse_dashes(&typed) {
|
||||
Some(list) => {
|
||||
// It stays a list even if what's typed is a single pair.
|
||||
ui.data_mut(|m| m.insert_temp(chosen, ids.to_vec()));
|
||||
app.set_props(ids, json!({"dashes": list}));
|
||||
}
|
||||
None => app.toast(tr("Dashes take numbers separated by commas")),
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let [dash, gap] = first_pair(d);
|
||||
widgets::sublabels(ui, tr("Dash"), Some(tr("Gap")));
|
||||
row(ui, 32.0, |ui| {
|
||||
let e = widgets::number_field(ui, Id::unique("stroke_dash"), Lead::None, Some(dash), "", half, 1.0);
|
||||
app.field(ids, "dashes", e, |v| json!(set_dash(d, 0, v)));
|
||||
let e = widgets::number_field(ui, Id::unique("stroke_gap"), Lead::None, Some(gap), "", half, 1.0);
|
||||
app.field(ids, "dashes", e, |v| json!(set_dash(d, 1, v)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Advanced stroke settings: dash pattern, width profile, join and the end points of open paths.
|
||||
pub(super) fn stroke_settings(app: &mut FigApp, ui: &mut Ui, doc: &Document, sel: &[NodeId]) {
|
||||
let ids = sel.to_vec();
|
||||
let half = widgets::col_width(ui.available_width());
|
||||
let st = common(doc, sel, |n| n.stroke.clone());
|
||||
let dashes = st.as_ref().map(|s| s.dashes.clone());
|
||||
let weight = st.as_ref().map(|s| s.weight).unwrap_or(1.0).max(1.0);
|
||||
widgets::sublabels(ui, tr("Stroke style"), None);
|
||||
row(ui, 32.0, |ui| {
|
||||
let dashed = dashes.as_ref().is_some_and(|d| !d.is_empty());
|
||||
let resp = widgets::dropdown(ui, if dashed { tr("Dash") } else { tr("Solid") }, half, None);
|
||||
menu_popup(&resp).show(|ui| {
|
||||
if menu_item(ui, tr("Solid"), "") {
|
||||
app.set_props(&ids, json!({"dashes": []}));
|
||||
ui.close();
|
||||
}
|
||||
if menu_item(ui, tr("Dash"), "") {
|
||||
app.set_props(&ids, json!({"dashes": [weight * 4.0, weight * 4.0]}));
|
||||
ui.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
if let Some(d) = dashes.filter(|d| !d.is_empty()) {
|
||||
let (dash, gap) = (d.first().copied().unwrap_or(4.0), d.get(1).copied().unwrap_or_else(|| d.first().copied().unwrap_or(4.0)));
|
||||
widgets::sublabels(ui, tr("Dash"), Some(tr("Gap")));
|
||||
row(ui, 32.0, |ui| {
|
||||
let e = widgets::number_field(ui, Id::unique("stroke_dash"), Lead::None, Some(dash), "", half, 1.0);
|
||||
app.field(&ids, "dashes", e, |v| json!(set_dash(&d, 0, v)));
|
||||
let e = widgets::number_field(ui, Id::unique("stroke_gap"), Lead::None, Some(gap), "", half, 1.0);
|
||||
app.field(&ids, "dashes", e, |v| json!(set_dash(&d, 1, v)));
|
||||
});
|
||||
}
|
||||
dash_settings(app, ui, &ids, st.as_ref().map(|s| s.dashes.as_slice()), weight);
|
||||
width_profile_row(app, ui, &ids, st.as_ref().map(|s| s.profile().cloned()));
|
||||
brush_row(app, ui, &ids, st.as_ref().map(|s| s.brush.clone()), ui.available_width());
|
||||
let join = st.as_ref().map(|s| s.join);
|
||||
|
||||
+2
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user