forked from 1b-agent/opencadstudio-upstream
Select Color: Document and Color books tabs
Document tab: every concrete colour the drawing uses (objects, block definitions and layers), found in one pass when the panel opens, as a grid of swatches sortable by recently used, most used, colour and colour number. The recently-used list is now kept per drawing, so the ribbon's Recent row shows this drawing's colours. Color books tab: load AutoCAD .acb colour books (remembered in the settings on desktop), browse them page by page with the page strip, search by name and see the RGB equivalent. A book colour is applied as RGB plus its BOOK$NAME identity on objects and layers instead of dropping the name. Strings translated into all 22 locales. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013rGmDUNr1hUf4zo1X3dak8
This commit is contained in:
35 files changed
+1684
-129
No files matched your search
@@ -1356,6 +1356,21 @@ common =
|
|||||||
.color-hex = سداسي عشري
|
.color-hex = سداسي عشري
|
||||||
.nearest-index-color = أقرب فهرس
|
.nearest-index-color = أقرب فهرس
|
||||||
.exact-index-match = تطابق تام مع الفهرس
|
.exact-index-match = تطابق تام مع الفهرس
|
||||||
|
.color-recently-used = المستخدمة مؤخرًا
|
||||||
|
.color-most-used = الأكثر استخدامًا
|
||||||
|
.color-number = رقم اللون
|
||||||
|
.color-in-drawing = الألوان في هذا الرسم
|
||||||
|
.color-sort = فرز
|
||||||
|
.color-no-drawing-colors = لا توجد ألوان في هذا الرسم بعد
|
||||||
|
.color-books = كتب الألوان
|
||||||
|
.color-no-books = لم يتم تحميل كتب ألوان
|
||||||
|
.color-load-book = تحميل كتاب ألوان (.acb)
|
||||||
|
.color-book-label = كتاب الألوان
|
||||||
|
.color-rgb-equivalent = مكافئ RGB
|
||||||
|
.color-no-match = لا توجد ألوان مطابقة
|
||||||
|
.color-not-a-book = ليس كتاب ألوان
|
||||||
|
.color-book-encrypted = كتاب الألوان هذا مشفر ولا يمكن قراءته
|
||||||
|
.color-book-empty = لا يحتوي كتاب الألوان على ألوان
|
||||||
.cad-color-index-aci-10-249 = فهرس ألوان ACI 10–249
|
.cad-color-index-aci-10-249 = فهرس ألوان ACI 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = الرسم متساوي القياس
|
.isometric-drafting = الرسم متساوي القياس
|
||||||
|
|||||||
@@ -1343,6 +1343,21 @@ common =
|
|||||||
.color-hex = Hex
|
.color-hex = Hex
|
||||||
.nearest-index-color = Най-близък индекс
|
.nearest-index-color = Най-близък индекс
|
||||||
.exact-index-match = Точно съвпадение с индекс
|
.exact-index-match = Точно съвпадение с индекс
|
||||||
|
.color-recently-used = Наскоро използвани
|
||||||
|
.color-most-used = Най-често използвани
|
||||||
|
.color-number = Номер на цвят
|
||||||
|
.color-in-drawing = Цветове в този чертеж
|
||||||
|
.color-sort = Сортиране
|
||||||
|
.color-no-drawing-colors = Все още няма цветове в този чертеж
|
||||||
|
.color-books = Цветови каталози
|
||||||
|
.color-no-books = Няма заредени цветови каталози
|
||||||
|
.color-load-book = Зареждане на цветови каталог (.acb)
|
||||||
|
.color-book-label = Цветови каталог
|
||||||
|
.color-rgb-equivalent = RGB еквивалент
|
||||||
|
.color-no-match = Няма съвпадащи цветове
|
||||||
|
.color-not-a-book = Не е цветови каталог
|
||||||
|
.color-book-encrypted = Този цветови каталог е шифрован и не може да бъде прочетен
|
||||||
|
.color-book-empty = Цветовият каталог няма цветове
|
||||||
.cad-color-index-aci-10-249 = ACI Цветови индекс 10–249
|
.cad-color-index-aci-10-249 = ACI Цветови индекс 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = Изометрично чертане
|
.isometric-drafting = Изометрично чертане
|
||||||
|
|||||||
@@ -1343,6 +1343,21 @@ common =
|
|||||||
.color-hex = Hex
|
.color-hex = Hex
|
||||||
.nearest-index-color = Nejbližší index
|
.nearest-index-color = Nejbližší index
|
||||||
.exact-index-match = Přesná shoda s indexem
|
.exact-index-match = Přesná shoda s indexem
|
||||||
|
.color-recently-used = Naposledy použité
|
||||||
|
.color-most-used = Nejpoužívanější
|
||||||
|
.color-number = Číslo barvy
|
||||||
|
.color-in-drawing = Barvy v tomto výkresu
|
||||||
|
.color-sort = Řadit
|
||||||
|
.color-no-drawing-colors = V tomto výkresu zatím nejsou žádné barvy
|
||||||
|
.color-books = Barevné vzorníky
|
||||||
|
.color-no-books = Nejsou načteny žádné barevné vzorníky
|
||||||
|
.color-load-book = Načíst barevný vzorník (.acb)
|
||||||
|
.color-book-label = Barevný vzorník
|
||||||
|
.color-rgb-equivalent = Ekvivalent RGB
|
||||||
|
.color-no-match = Žádné odpovídající barvy
|
||||||
|
.color-not-a-book = Nejde o barevný vzorník
|
||||||
|
.color-book-encrypted = Tento barevný vzorník je zašifrovaný a nelze jej přečíst
|
||||||
|
.color-book-empty = Barevný vzorník neobsahuje žádné barvy
|
||||||
.cad-color-index-aci-10-249 = Barevný index ACI 10–249
|
.cad-color-index-aci-10-249 = Barevný index ACI 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = Izometrické kreslení
|
.isometric-drafting = Izometrické kreslení
|
||||||
|
|||||||
@@ -1344,6 +1344,21 @@ common =
|
|||||||
.color-hex = Hex
|
.color-hex = Hex
|
||||||
.nearest-index-color = Nächster Index
|
.nearest-index-color = Nächster Index
|
||||||
.exact-index-match = Exakter Index-Treffer
|
.exact-index-match = Exakter Index-Treffer
|
||||||
|
.color-recently-used = Zuletzt verwendet
|
||||||
|
.color-most-used = Am häufigsten verwendet
|
||||||
|
.color-number = Farbnummer
|
||||||
|
.color-in-drawing = Farben in dieser Zeichnung
|
||||||
|
.color-sort = Sortieren
|
||||||
|
.color-no-drawing-colors = Noch keine Farben in dieser Zeichnung
|
||||||
|
.color-books = Farbbücher
|
||||||
|
.color-no-books = Keine Farbbücher geladen
|
||||||
|
.color-load-book = Farbbuch laden (.acb)
|
||||||
|
.color-book-label = Farbbuch
|
||||||
|
.color-rgb-equivalent = RGB-Entsprechung
|
||||||
|
.color-no-match = Keine passenden Farben
|
||||||
|
.color-not-a-book = Kein Farbbuch
|
||||||
|
.color-book-encrypted = Dieses Farbbuch ist verschlüsselt und kann nicht gelesen werden
|
||||||
|
.color-book-empty = Das Farbbuch enthält keine Farben
|
||||||
.cad-color-index-aci-10-249 = ACI Farbindex 10–249
|
.cad-color-index-aci-10-249 = ACI Farbindex 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = Isometrisches Zeichnen
|
.isometric-drafting = Isometrisches Zeichnen
|
||||||
|
|||||||
@@ -1345,6 +1345,21 @@ common =
|
|||||||
.color-hex = Hex
|
.color-hex = Hex
|
||||||
.nearest-index-color = Πλησιέστερος δείκτης
|
.nearest-index-color = Πλησιέστερος δείκτης
|
||||||
.exact-index-match = Ακριβής αντιστοίχιση δείκτη
|
.exact-index-match = Ακριβής αντιστοίχιση δείκτη
|
||||||
|
.color-recently-used = Πρόσφατα χρησιμοποιημένα
|
||||||
|
.color-most-used = Πιο συχνά χρησιμοποιημένα
|
||||||
|
.color-number = Αριθμός χρώματος
|
||||||
|
.color-in-drawing = Χρώματα σε αυτό το σχέδιο
|
||||||
|
.color-sort = Ταξινόμηση
|
||||||
|
.color-no-drawing-colors = Δεν υπάρχουν ακόμη χρώματα σε αυτό το σχέδιο
|
||||||
|
.color-books = Βιβλία χρωμάτων
|
||||||
|
.color-no-books = Δεν έχουν φορτωθεί βιβλία χρωμάτων
|
||||||
|
.color-load-book = Φόρτωση βιβλίου χρωμάτων (.acb)
|
||||||
|
.color-book-label = Βιβλίο χρωμάτων
|
||||||
|
.color-rgb-equivalent = Ισοδύναμο RGB
|
||||||
|
.color-no-match = Κανένα χρώμα δεν ταιριάζει
|
||||||
|
.color-not-a-book = Δεν είναι βιβλίο χρωμάτων
|
||||||
|
.color-book-encrypted = Αυτό το βιβλίο χρωμάτων είναι κρυπτογραφημένο και δεν μπορεί να διαβαστεί
|
||||||
|
.color-book-empty = Το βιβλίο χρωμάτων δεν έχει χρώματα
|
||||||
.cad-color-index-aci-10-249 = Δείκτης χρώματος ACI 10–249
|
.cad-color-index-aci-10-249 = Δείκτης χρώματος ACI 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = Ισομετρική σχεδίαση
|
.isometric-drafting = Ισομετρική σχεδίαση
|
||||||
|
|||||||
@@ -1344,6 +1344,21 @@ common =
|
|||||||
.color-hex = Hex
|
.color-hex = Hex
|
||||||
.nearest-index-color = Nearest index
|
.nearest-index-color = Nearest index
|
||||||
.exact-index-match = Exact index match
|
.exact-index-match = Exact index match
|
||||||
|
.color-recently-used = Recently used
|
||||||
|
.color-most-used = Most used
|
||||||
|
.color-number = Color number
|
||||||
|
.color-in-drawing = Colors in this drawing
|
||||||
|
.color-sort = Sort
|
||||||
|
.color-no-drawing-colors = No colors in this drawing yet
|
||||||
|
.color-books = Color Books
|
||||||
|
.color-no-books = No color books loaded
|
||||||
|
.color-load-book = Load color book (.acb)
|
||||||
|
.color-book-label = Color book
|
||||||
|
.color-rgb-equivalent = RGB equivalent
|
||||||
|
.color-no-match = No colors match
|
||||||
|
.color-not-a-book = Not a color book
|
||||||
|
.color-book-encrypted = This color book is encrypted and cannot be read
|
||||||
|
.color-book-empty = The color book has no colors
|
||||||
.cad-color-index-aci-10-249 = CAD Color Index (ACI) 10–249
|
.cad-color-index-aci-10-249 = CAD Color Index (ACI) 10–249
|
||||||
.grayscale-250-255 = Grayscale 250–255
|
.grayscale-250-255 = Grayscale 250–255
|
||||||
.isometric-drafting = Isometric drafting
|
.isometric-drafting = Isometric drafting
|
||||||
|
|||||||
@@ -1344,6 +1344,21 @@ common =
|
|||||||
.color-hex = Hex
|
.color-hex = Hex
|
||||||
.nearest-index-color = Índice más cercano
|
.nearest-index-color = Índice más cercano
|
||||||
.exact-index-match = Coincide con un índice
|
.exact-index-match = Coincide con un índice
|
||||||
|
.color-recently-used = Usados recientemente
|
||||||
|
.color-most-used = Más usados
|
||||||
|
.color-number = Número de color
|
||||||
|
.color-in-drawing = Colores de este dibujo
|
||||||
|
.color-sort = Ordenar
|
||||||
|
.color-no-drawing-colors = Aún no hay colores en este dibujo
|
||||||
|
.color-books = Libros de colores
|
||||||
|
.color-no-books = No hay libros de colores cargados
|
||||||
|
.color-load-book = Cargar libro de colores (.acb)
|
||||||
|
.color-book-label = Libro de colores
|
||||||
|
.color-rgb-equivalent = Equivalente RGB
|
||||||
|
.color-no-match = Ningún color coincide
|
||||||
|
.color-not-a-book = No es un libro de colores
|
||||||
|
.color-book-encrypted = Este libro de colores está cifrado y no se puede leer
|
||||||
|
.color-book-empty = El libro de colores no tiene colores
|
||||||
.cad-color-index-aci-10-249 = Índice de color ACI 10–249
|
.cad-color-index-aci-10-249 = Índice de color ACI 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = Dibujo isométrico
|
.isometric-drafting = Dibujo isométrico
|
||||||
|
|||||||
@@ -1343,6 +1343,21 @@ common =
|
|||||||
.color-hex = Hex
|
.color-hex = Hex
|
||||||
.nearest-index-color = Lähin indeksi
|
.nearest-index-color = Lähin indeksi
|
||||||
.exact-index-match = Täsmälleen indeksiväri
|
.exact-index-match = Täsmälleen indeksiväri
|
||||||
|
.color-recently-used = Viimeksi käytetyt
|
||||||
|
.color-most-used = Eniten käytetyt
|
||||||
|
.color-number = Värin numero
|
||||||
|
.color-in-drawing = Tämän piirustuksen värit
|
||||||
|
.color-sort = Lajittele
|
||||||
|
.color-no-drawing-colors = Tässä piirustuksessa ei ole vielä värejä
|
||||||
|
.color-books = Värikirjat
|
||||||
|
.color-no-books = Värikirjoja ei ole ladattu
|
||||||
|
.color-load-book = Lataa värikirja (.acb)
|
||||||
|
.color-book-label = Värikirja
|
||||||
|
.color-rgb-equivalent = RGB-vastine
|
||||||
|
.color-no-match = Ei vastaavia värejä
|
||||||
|
.color-not-a-book = Ei värikirja
|
||||||
|
.color-book-encrypted = Tämä värikirja on salattu eikä sitä voi lukea
|
||||||
|
.color-book-empty = Värikirjassa ei ole värejä
|
||||||
.cad-color-index-aci-10-249 = ACI väriindeksi 10–249
|
.cad-color-index-aci-10-249 = ACI väriindeksi 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = Isometrinen piirtäminen
|
.isometric-drafting = Isometrinen piirtäminen
|
||||||
|
|||||||
@@ -1344,6 +1344,21 @@ common =
|
|||||||
.color-hex = Hex
|
.color-hex = Hex
|
||||||
.nearest-index-color = Index le plus proche
|
.nearest-index-color = Index le plus proche
|
||||||
.exact-index-match = Correspondance exacte d'index
|
.exact-index-match = Correspondance exacte d'index
|
||||||
|
.color-recently-used = Utilisées récemment
|
||||||
|
.color-most-used = Les plus utilisées
|
||||||
|
.color-number = Numéro de couleur
|
||||||
|
.color-in-drawing = Couleurs de ce dessin
|
||||||
|
.color-sort = Trier
|
||||||
|
.color-no-drawing-colors = Aucune couleur dans ce dessin pour l'instant
|
||||||
|
.color-books = Carnets de couleurs
|
||||||
|
.color-no-books = Aucun carnet de couleurs chargé
|
||||||
|
.color-load-book = Charger un carnet de couleurs (.acb)
|
||||||
|
.color-book-label = Carnet de couleurs
|
||||||
|
.color-rgb-equivalent = Équivalent RVB
|
||||||
|
.color-no-match = Aucune couleur correspondante
|
||||||
|
.color-not-a-book = Ce n'est pas un carnet de couleurs
|
||||||
|
.color-book-encrypted = Ce carnet de couleurs est chiffré et ne peut pas être lu
|
||||||
|
.color-book-empty = Le carnet de couleurs ne contient aucune couleur
|
||||||
.cad-color-index-aci-10-249 = ACI Indice de couleur 10–249
|
.cad-color-index-aci-10-249 = ACI Indice de couleur 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = Dessin isométrique
|
.isometric-drafting = Dessin isométrique
|
||||||
|
|||||||
@@ -1332,6 +1332,21 @@ common =
|
|||||||
.color-hex = हेक्स
|
.color-hex = हेक्स
|
||||||
.nearest-index-color = निकटतम इंडेक्स
|
.nearest-index-color = निकटतम इंडेक्स
|
||||||
.exact-index-match = इंडेक्स से सटीक मेल
|
.exact-index-match = इंडेक्स से सटीक मेल
|
||||||
|
.color-recently-used = हाल ही में उपयोग किए गए
|
||||||
|
.color-most-used = सबसे अधिक उपयोग किए गए
|
||||||
|
.color-number = रंग संख्या
|
||||||
|
.color-in-drawing = इस ड्रॉइंग के रंग
|
||||||
|
.color-sort = क्रमबद्ध करें
|
||||||
|
.color-no-drawing-colors = इस ड्रॉइंग में अभी तक कोई रंग नहीं है
|
||||||
|
.color-books = रंग पुस्तकें
|
||||||
|
.color-no-books = कोई रंग पुस्तक लोड नहीं है
|
||||||
|
.color-load-book = रंग पुस्तक लोड करें (.acb)
|
||||||
|
.color-book-label = रंग पुस्तक
|
||||||
|
.color-rgb-equivalent = RGB समतुल्य
|
||||||
|
.color-no-match = कोई रंग मेल नहीं खाता
|
||||||
|
.color-not-a-book = यह रंग पुस्तक नहीं है
|
||||||
|
.color-book-encrypted = यह रंग पुस्तक एन्क्रिप्टेड है और पढ़ी नहीं जा सकती
|
||||||
|
.color-book-empty = रंग पुस्तक में कोई रंग नहीं है
|
||||||
.cad-color-index-aci-10-249 = ACI रंग सूचकांक 10–249
|
.cad-color-index-aci-10-249 = ACI रंग सूचकांक 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = सममितीय आरेखन
|
.isometric-drafting = सममितीय आरेखन
|
||||||
|
|||||||
@@ -1343,6 +1343,21 @@ common =
|
|||||||
.color-hex = Hex
|
.color-hex = Hex
|
||||||
.nearest-index-color = Legközelebbi index
|
.nearest-index-color = Legközelebbi index
|
||||||
.exact-index-match = Pontos indexegyezés
|
.exact-index-match = Pontos indexegyezés
|
||||||
|
.color-recently-used = Legutóbb használt
|
||||||
|
.color-most-used = Leggyakrabban használt
|
||||||
|
.color-number = Színszám
|
||||||
|
.color-in-drawing = Színek ebben a rajzban
|
||||||
|
.color-sort = Rendezés
|
||||||
|
.color-no-drawing-colors = Ebben a rajzban még nincsenek színek
|
||||||
|
.color-books = Színkönyvek
|
||||||
|
.color-no-books = Nincs betöltött színkönyv
|
||||||
|
.color-load-book = Színkönyv betöltése (.acb)
|
||||||
|
.color-book-label = Színkönyv
|
||||||
|
.color-rgb-equivalent = RGB-megfelelő
|
||||||
|
.color-no-match = Nincs egyező szín
|
||||||
|
.color-not-a-book = Nem színkönyv
|
||||||
|
.color-book-encrypted = Ez a színkönyv titkosított, és nem olvasható
|
||||||
|
.color-book-empty = A színkönyv nem tartalmaz színeket
|
||||||
.cad-color-index-aci-10-249 = ACI színindex 10–249
|
.cad-color-index-aci-10-249 = ACI színindex 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = Izometrikus rajzolás
|
.isometric-drafting = Izometrikus rajzolás
|
||||||
|
|||||||
@@ -1343,6 +1343,21 @@ common =
|
|||||||
.color-hex = Hex
|
.color-hex = Hex
|
||||||
.nearest-index-color = Indice più vicino
|
.nearest-index-color = Indice più vicino
|
||||||
.exact-index-match = Corrispondenza esatta indice
|
.exact-index-match = Corrispondenza esatta indice
|
||||||
|
.color-recently-used = Usati di recente
|
||||||
|
.color-most-used = Più usati
|
||||||
|
.color-number = Numero colore
|
||||||
|
.color-in-drawing = Colori in questo disegno
|
||||||
|
.color-sort = Ordina
|
||||||
|
.color-no-drawing-colors = Ancora nessun colore in questo disegno
|
||||||
|
.color-books = Album colori
|
||||||
|
.color-no-books = Nessun album colori caricato
|
||||||
|
.color-load-book = Carica album colori (.acb)
|
||||||
|
.color-book-label = Album colori
|
||||||
|
.color-rgb-equivalent = Equivalente RGB
|
||||||
|
.color-no-match = Nessun colore corrispondente
|
||||||
|
.color-not-a-book = Non è un album colori
|
||||||
|
.color-book-encrypted = Questo album colori è crittografato e non può essere letto
|
||||||
|
.color-book-empty = L'album colori non contiene colori
|
||||||
.cad-color-index-aci-10-249 = ACI Indice colori 10–249
|
.cad-color-index-aci-10-249 = ACI Indice colori 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = Disegno isometrico
|
.isometric-drafting = Disegno isometrico
|
||||||
|
|||||||
@@ -1334,6 +1334,21 @@ common =
|
|||||||
.color-hex = 16進
|
.color-hex = 16進
|
||||||
.nearest-index-color = 最も近いインデックス
|
.nearest-index-color = 最も近いインデックス
|
||||||
.exact-index-match = インデックスと完全一致
|
.exact-index-match = インデックスと完全一致
|
||||||
|
.color-recently-used = 最近使用した色
|
||||||
|
.color-most-used = 使用頻度の高い色
|
||||||
|
.color-number = 色番号
|
||||||
|
.color-in-drawing = この図面の色
|
||||||
|
.color-sort = 並べ替え
|
||||||
|
.color-no-drawing-colors = この図面にはまだ色がありません
|
||||||
|
.color-books = カラー ブック
|
||||||
|
.color-no-books = カラー ブックが読み込まれていません
|
||||||
|
.color-load-book = カラー ブックを読み込む (.acb)
|
||||||
|
.color-book-label = カラー ブック
|
||||||
|
.color-rgb-equivalent = RGB 相当値
|
||||||
|
.color-no-match = 一致する色がありません
|
||||||
|
.color-not-a-book = カラー ブックではありません
|
||||||
|
.color-book-encrypted = このカラー ブックは暗号化されているため読み込めません
|
||||||
|
.color-book-empty = カラー ブックに色がありません
|
||||||
.cad-color-index-aci-10-249 = ACI カラーインデックス 10–249
|
.cad-color-index-aci-10-249 = ACI カラーインデックス 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = アイソメ図作成
|
.isometric-drafting = アイソメ図作成
|
||||||
|
|||||||
@@ -1343,6 +1343,21 @@ common =
|
|||||||
.color-hex = 16진수
|
.color-hex = 16진수
|
||||||
.nearest-index-color = 가장 가까운 인덱스
|
.nearest-index-color = 가장 가까운 인덱스
|
||||||
.exact-index-match = 인덱스와 정확히 일치
|
.exact-index-match = 인덱스와 정확히 일치
|
||||||
|
.color-recently-used = 최근 사용
|
||||||
|
.color-most-used = 가장 많이 사용
|
||||||
|
.color-number = 색상 번호
|
||||||
|
.color-in-drawing = 이 도면의 색상
|
||||||
|
.color-sort = 정렬
|
||||||
|
.color-no-drawing-colors = 이 도면에는 아직 색상이 없습니다
|
||||||
|
.color-books = 색상표
|
||||||
|
.color-no-books = 로드된 색상표가 없습니다
|
||||||
|
.color-load-book = 색상표 로드 (.acb)
|
||||||
|
.color-book-label = 색상표
|
||||||
|
.color-rgb-equivalent = RGB 등가
|
||||||
|
.color-no-match = 일치하는 색상이 없습니다
|
||||||
|
.color-not-a-book = 색상표가 아닙니다
|
||||||
|
.color-book-encrypted = 이 색상표는 암호화되어 있어 읽을 수 없습니다
|
||||||
|
.color-book-empty = 색상표에 색상이 없습니다
|
||||||
.cad-color-index-aci-10-249 = ACI 컬러 인덱스 10–249
|
.cad-color-index-aci-10-249 = ACI 컬러 인덱스 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = 등각 제도
|
.isometric-drafting = 등각 제도
|
||||||
|
|||||||
@@ -1349,6 +1349,21 @@ common =
|
|||||||
.color-hex = Hex
|
.color-hex = Hex
|
||||||
.nearest-index-color = ດັດຊະນີທີ່ໃກ້ທີ່ສຸດ
|
.nearest-index-color = ດັດຊະນີທີ່ໃກ້ທີ່ສຸດ
|
||||||
.exact-index-match = ກົງກັບດັດຊະນີພໍດີ
|
.exact-index-match = ກົງກັບດັດຊະນີພໍດີ
|
||||||
|
.color-recently-used = ໃຊ້ຫຼ້າສຸດ
|
||||||
|
.color-most-used = ໃຊ້ຫຼາຍທີ່ສຸດ
|
||||||
|
.color-number = ເລກສີ
|
||||||
|
.color-in-drawing = ສີໃນແບບແຕ້ມນີ້
|
||||||
|
.color-sort = ຈັດລຽງ
|
||||||
|
.color-no-drawing-colors = ຍັງບໍ່ມີສີໃນແບບແຕ້ມນີ້
|
||||||
|
.color-books = ປຶ້ມສີ
|
||||||
|
.color-no-books = ບໍ່ໄດ້ໂຫຼດປຶ້ມສີ
|
||||||
|
.color-load-book = ໂຫຼດປຶ້ມສີ (.acb)
|
||||||
|
.color-book-label = ປຶ້ມສີ
|
||||||
|
.color-rgb-equivalent = ທຽບເທົ່າ RGB
|
||||||
|
.color-no-match = ບໍ່ມີສີທີ່ກົງກັນ
|
||||||
|
.color-not-a-book = ບໍ່ແມ່ນປຶ້ມສີ
|
||||||
|
.color-book-encrypted = ປຶ້ມສີນີ້ຖືກເຂົ້າລະຫັດ ແລະອ່ານບໍ່ໄດ້
|
||||||
|
.color-book-empty = ປຶ້ມສີບໍ່ມີສີ
|
||||||
.cad-color-index-aci-10-249 = CAD Color Index (ACI) 10–249
|
.cad-color-index-aci-10-249 = CAD Color Index (ACI) 10–249
|
||||||
.grayscale-250-255 = ລະດັບສີເທົາ 250–255
|
.grayscale-250-255 = ລະດັບສີເທົາ 250–255
|
||||||
.isometric-drafting = ການຂຽນແບບໄອໂຊເມຕຣິກ
|
.isometric-drafting = ການຂຽນແບບໄອໂຊເມຕຣິກ
|
||||||
|
|||||||
@@ -1342,6 +1342,21 @@ common =
|
|||||||
.color-hex = Hex
|
.color-hex = Hex
|
||||||
.nearest-index-color = Dichtstbijzijnde index
|
.nearest-index-color = Dichtstbijzijnde index
|
||||||
.exact-index-match = Exacte indexkleur
|
.exact-index-match = Exacte indexkleur
|
||||||
|
.color-recently-used = Recent gebruikt
|
||||||
|
.color-most-used = Meest gebruikt
|
||||||
|
.color-number = Kleurnummer
|
||||||
|
.color-in-drawing = Kleuren in deze tekening
|
||||||
|
.color-sort = Sorteren
|
||||||
|
.color-no-drawing-colors = Nog geen kleuren in deze tekening
|
||||||
|
.color-books = Kleurboeken
|
||||||
|
.color-no-books = Geen kleurboeken geladen
|
||||||
|
.color-load-book = Kleurboek laden (.acb)
|
||||||
|
.color-book-label = Kleurboek
|
||||||
|
.color-rgb-equivalent = RGB-equivalent
|
||||||
|
.color-no-match = Geen kleuren gevonden
|
||||||
|
.color-not-a-book = Geen kleurboek
|
||||||
|
.color-book-encrypted = Dit kleurboek is versleuteld en kan niet worden gelezen
|
||||||
|
.color-book-empty = Het kleurboek bevat geen kleuren
|
||||||
.cad-color-index-aci-10-249 = ACI Kleurindex 10–249
|
.cad-color-index-aci-10-249 = ACI Kleurindex 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = Isometrisch tekenen
|
.isometric-drafting = Isometrisch tekenen
|
||||||
|
|||||||
@@ -1343,6 +1343,21 @@ common =
|
|||||||
.color-hex = Hex
|
.color-hex = Hex
|
||||||
.nearest-index-color = Najbliższy indeks
|
.nearest-index-color = Najbliższy indeks
|
||||||
.exact-index-match = Dokładne dopasowanie indeksu
|
.exact-index-match = Dokładne dopasowanie indeksu
|
||||||
|
.color-recently-used = Ostatnio używane
|
||||||
|
.color-most-used = Najczęściej używane
|
||||||
|
.color-number = Numer koloru
|
||||||
|
.color-in-drawing = Kolory w tym rysunku
|
||||||
|
.color-sort = Sortuj
|
||||||
|
.color-no-drawing-colors = Brak kolorów w tym rysunku
|
||||||
|
.color-books = Wzorniki kolorów
|
||||||
|
.color-no-books = Nie wczytano wzorników kolorów
|
||||||
|
.color-load-book = Wczytaj wzornik kolorów (.acb)
|
||||||
|
.color-book-label = Wzornik kolorów
|
||||||
|
.color-rgb-equivalent = Odpowiednik RGB
|
||||||
|
.color-no-match = Brak pasujących kolorów
|
||||||
|
.color-not-a-book = To nie jest wzornik kolorów
|
||||||
|
.color-book-encrypted = Ten wzornik kolorów jest zaszyfrowany i nie można go odczytać
|
||||||
|
.color-book-empty = Wzornik kolorów nie zawiera kolorów
|
||||||
.cad-color-index-aci-10-249 = Indeks kolorów ACI 10–249
|
.cad-color-index-aci-10-249 = Indeks kolorów ACI 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = Rysowanie izometryczne
|
.isometric-drafting = Rysowanie izometryczne
|
||||||
|
|||||||
@@ -1344,6 +1344,21 @@ common =
|
|||||||
.color-hex = Hex
|
.color-hex = Hex
|
||||||
.nearest-index-color = Índice mais próximo
|
.nearest-index-color = Índice mais próximo
|
||||||
.exact-index-match = Corresponde exatamente ao índice
|
.exact-index-match = Corresponde exatamente ao índice
|
||||||
|
.color-recently-used = Usadas recentemente
|
||||||
|
.color-most-used = Mais usadas
|
||||||
|
.color-number = Número da cor
|
||||||
|
.color-in-drawing = Cores neste desenho
|
||||||
|
.color-sort = Ordenar
|
||||||
|
.color-no-drawing-colors = Ainda não há cores neste desenho
|
||||||
|
.color-books = Catálogos de cores
|
||||||
|
.color-no-books = Nenhum catálogo de cores carregado
|
||||||
|
.color-load-book = Carregar catálogo de cores (.acb)
|
||||||
|
.color-book-label = Catálogo de cores
|
||||||
|
.color-rgb-equivalent = Equivalente RGB
|
||||||
|
.color-no-match = Nenhuma cor corresponde
|
||||||
|
.color-not-a-book = Não é um catálogo de cores
|
||||||
|
.color-book-encrypted = Este catálogo de cores é criptografado e não pode ser lido
|
||||||
|
.color-book-empty = O catálogo de cores não tem cores
|
||||||
.cad-color-index-aci-10-249 = Índice de cor ACI 10–249
|
.cad-color-index-aci-10-249 = Índice de cor ACI 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = Desenho isométrico
|
.isometric-drafting = Desenho isométrico
|
||||||
|
|||||||
@@ -1347,6 +1347,21 @@ common =
|
|||||||
.color-hex = Hex
|
.color-hex = Hex
|
||||||
.nearest-index-color = Ближайший индекс
|
.nearest-index-color = Ближайший индекс
|
||||||
.exact-index-match = Точное совпадение с индексом
|
.exact-index-match = Точное совпадение с индексом
|
||||||
|
.color-recently-used = Недавно использованные
|
||||||
|
.color-most-used = Чаще всего используемые
|
||||||
|
.color-number = Номер цвета
|
||||||
|
.color-in-drawing = Цвета в этом чертеже
|
||||||
|
.color-sort = Сортировка
|
||||||
|
.color-no-drawing-colors = В этом чертеже пока нет цветов
|
||||||
|
.color-books = Альбомы цветов
|
||||||
|
.color-no-books = Альбомы цветов не загружены
|
||||||
|
.color-load-book = Загрузить альбом цветов (.acb)
|
||||||
|
.color-book-label = Альбом цветов
|
||||||
|
.color-rgb-equivalent = Эквивалент RGB
|
||||||
|
.color-no-match = Нет подходящих цветов
|
||||||
|
.color-not-a-book = Это не альбом цветов
|
||||||
|
.color-book-encrypted = Этот альбом цветов зашифрован и не может быть прочитан
|
||||||
|
.color-book-empty = В альбоме цветов нет цветов
|
||||||
.cad-color-index-aci-10-249 = Индекс цвета ACI 10–249
|
.cad-color-index-aci-10-249 = Индекс цвета ACI 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = Изометрическое черчение
|
.isometric-drafting = Изометрическое черчение
|
||||||
|
|||||||
@@ -1330,6 +1330,21 @@ common =
|
|||||||
.color-hex = Hex
|
.color-hex = Hex
|
||||||
.nearest-index-color = En yakın indeks
|
.nearest-index-color = En yakın indeks
|
||||||
.exact-index-match = Tam indeks eşleşmesi
|
.exact-index-match = Tam indeks eşleşmesi
|
||||||
|
.color-recently-used = Son kullanılanlar
|
||||||
|
.color-most-used = En çok kullanılanlar
|
||||||
|
.color-number = Renk numarası
|
||||||
|
.color-in-drawing = Bu çizimdeki renkler
|
||||||
|
.color-sort = Sırala
|
||||||
|
.color-no-drawing-colors = Bu çizimde henüz renk yok
|
||||||
|
.color-books = Renk kitapları
|
||||||
|
.color-no-books = Yüklenmiş renk kitabı yok
|
||||||
|
.color-load-book = Renk kitabı yükle (.acb)
|
||||||
|
.color-book-label = Renk kitabı
|
||||||
|
.color-rgb-equivalent = RGB karşılığı
|
||||||
|
.color-no-match = Eşleşen renk yok
|
||||||
|
.color-not-a-book = Bir renk kitabı değil
|
||||||
|
.color-book-encrypted = Bu renk kitabı şifreli ve okunamıyor
|
||||||
|
.color-book-empty = Renk kitabında renk yok
|
||||||
.cad-color-index-aci-10-249 = ACI renk dizini 10–249
|
.cad-color-index-aci-10-249 = ACI renk dizini 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = İzometrik çizim
|
.isometric-drafting = İzometrik çizim
|
||||||
|
|||||||
@@ -1331,6 +1331,21 @@ common =
|
|||||||
.color-hex = 十六进制
|
.color-hex = 十六进制
|
||||||
.nearest-index-color = 最接近的索引色
|
.nearest-index-color = 最接近的索引色
|
||||||
.exact-index-match = 与索引色完全匹配
|
.exact-index-match = 与索引色完全匹配
|
||||||
|
.color-recently-used = 最近使用
|
||||||
|
.color-most-used = 最常使用
|
||||||
|
.color-number = 颜色编号
|
||||||
|
.color-in-drawing = 此图形中的颜色
|
||||||
|
.color-sort = 排序
|
||||||
|
.color-no-drawing-colors = 此图形中还没有颜色
|
||||||
|
.color-books = 配色系统
|
||||||
|
.color-no-books = 未加载配色系统
|
||||||
|
.color-load-book = 加载配色系统 (.acb)
|
||||||
|
.color-book-label = 配色系统
|
||||||
|
.color-rgb-equivalent = RGB 等效值
|
||||||
|
.color-no-match = 没有匹配的颜色
|
||||||
|
.color-not-a-book = 不是配色系统
|
||||||
|
.color-book-encrypted = 此配色系统已加密,无法读取
|
||||||
|
.color-book-empty = 配色系统中没有颜色
|
||||||
.cad-color-index-aci-10-249 = ACI 颜色索引 10–249
|
.cad-color-index-aci-10-249 = ACI 颜色索引 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = 等轴测绘图
|
.isometric-drafting = 等轴测绘图
|
||||||
|
|||||||
@@ -1343,6 +1343,21 @@ common =
|
|||||||
.color-hex = 十六進位
|
.color-hex = 十六進位
|
||||||
.nearest-index-color = 最接近的索引色
|
.nearest-index-color = 最接近的索引色
|
||||||
.exact-index-match = 與索引色完全相符
|
.exact-index-match = 與索引色完全相符
|
||||||
|
.color-recently-used = 最近使用
|
||||||
|
.color-most-used = 最常使用
|
||||||
|
.color-number = 顏色編號
|
||||||
|
.color-in-drawing = 此圖面中的顏色
|
||||||
|
.color-sort = 排序
|
||||||
|
.color-no-drawing-colors = 此圖面中還沒有顏色
|
||||||
|
.color-books = 配色系統
|
||||||
|
.color-no-books = 未載入配色系統
|
||||||
|
.color-load-book = 載入配色系統 (.acb)
|
||||||
|
.color-book-label = 配色系統
|
||||||
|
.color-rgb-equivalent = RGB 等效值
|
||||||
|
.color-no-match = 沒有相符的顏色
|
||||||
|
.color-not-a-book = 不是配色系統
|
||||||
|
.color-book-encrypted = 此配色系統已加密,無法讀取
|
||||||
|
.color-book-empty = 配色系統中沒有顏色
|
||||||
.cad-color-index-aci-10-249 = ACI 顏色索引 10–249
|
.cad-color-index-aci-10-249 = ACI 顏色索引 10–249
|
||||||
.grayscale-250-255 = { common.grayscale } 250–255
|
.grayscale-250-255 = { common.grayscale } 250–255
|
||||||
.isometric-drafting = 等角製圖
|
.isometric-drafting = 等角製圖
|
||||||
|
|||||||
@@ -277,6 +277,8 @@ pub(super) struct DocumentTab {
|
|||||||
/// Model-space corners of the last crossing window drawn with no command
|
/// Model-space corners of the last crossing window drawn with no command
|
||||||
/// running: STRETCH on that selection stretches through it (#1068).
|
/// running: STRETCH on that selection stretches through it (#1068).
|
||||||
pub(super) last_crossing_window: Option<([f64; 2], [f64; 2])>,
|
pub(super) last_crossing_window: Option<([f64; 2], [f64; 2])>,
|
||||||
|
/// Colours picked in this drawing this session, newest first. Not saved.
|
||||||
|
pub(super) recent_colors: Vec<codec::types::Color>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DocumentTab {
|
impl DocumentTab {
|
||||||
@@ -679,6 +681,7 @@ impl DocumentTab {
|
|||||||
suspended_cmd: None,
|
suspended_cmd: None,
|
||||||
transparent_resume: false,
|
transparent_resume: false,
|
||||||
last_crossing_window: None,
|
last_crossing_window: None,
|
||||||
|
recent_colors: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-4
@@ -965,9 +965,12 @@ pub(super) struct OpenCADStudio {
|
|||||||
// ── Floating panel windows ────────────────────────────────────────────
|
// ── Floating panel windows ────────────────────────────────────────────
|
||||||
/// The open Select Color panel, if any.
|
/// The open Select Color panel, if any.
|
||||||
color_picker: Option<crate::ui::color_select::ColorPicker>,
|
color_picker: Option<crate::ui::color_select::ColorPicker>,
|
||||||
|
/// Colour books loaded this session, read lazily from `color_book_paths`.
|
||||||
|
color_books: std::sync::Arc<Vec<crate::ui::color_select::book::ColorBook>>,
|
||||||
|
color_book_paths: Vec<String>,
|
||||||
|
color_books_read: bool,
|
||||||
/// Recently committed real colours, newest first.
|
/// Recently committed real colours, newest first.
|
||||||
/// ACI and True Color remain semantically distinct even when their RGB matches.
|
/// ACI and True Color remain semantically distinct even when their RGB matches.
|
||||||
recent_colors: Vec<AcadColor>,
|
|
||||||
/// The open in-canvas modal dialog, if any (Plan B: shared overlay instead
|
/// The open in-canvas modal dialog, if any (Plan B: shared overlay instead
|
||||||
/// of OS windows).
|
/// of OS windows).
|
||||||
active_modal: Option<ModalKind>,
|
active_modal: Option<ModalKind>,
|
||||||
@@ -4168,8 +4171,11 @@ pub enum Message {
|
|||||||
ColorPicker(crate::ui::color_select::ColorPickerMsg),
|
ColorPicker(crate::ui::color_select::ColorPickerMsg),
|
||||||
/// Close the colour picker without choosing.
|
/// Close the colour picker without choosing.
|
||||||
CloseColorPicker,
|
CloseColorPicker,
|
||||||
/// Commit the colour chosen in the shared picker.
|
/// Commit the colour chosen in the shared picker, with its `BOOK$NAME`
|
||||||
ColorWindowPick(codec::types::Color),
|
/// when it comes from a colour book.
|
||||||
|
ColorWindowPick(codec::types::Color, Option<String>),
|
||||||
|
/// An `.acb` file was read: the book and, on desktop, where it lives.
|
||||||
|
ColorBookLoaded(Result<(crate::ui::color_select::book::ColorBook, Option<String>), String>),
|
||||||
/// Set a block/linetype Handle field on the selected dim style from a
|
/// Set a block/linetype Handle field on the selected dim style from a
|
||||||
/// dropdown of available block-records / linetypes (by name).
|
/// dropdown of available block-records / linetypes (by name).
|
||||||
DsSetHandle {
|
DsSetHandle {
|
||||||
@@ -4601,7 +4607,9 @@ impl OpenCADStudio {
|
|||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
web_autosave_clear_after: Vec::new(),
|
web_autosave_clear_after: Vec::new(),
|
||||||
color_picker: None,
|
color_picker: None,
|
||||||
recent_colors: Vec::new(),
|
color_books: Default::default(),
|
||||||
|
color_book_paths: Vec::new(),
|
||||||
|
color_books_read: false,
|
||||||
active_modal: None,
|
active_modal: None,
|
||||||
hyperlink_editor_handles: Vec::new(),
|
hyperlink_editor_handles: Vec::new(),
|
||||||
hyperlink_editor_url: String::new(),
|
hyperlink_editor_url: String::new(),
|
||||||
|
|||||||
@@ -619,6 +619,9 @@ pub struct UserSettings {
|
|||||||
/// Blocks palette: library folders or drawings, last used first.
|
/// Blocks palette: library folders or drawings, last used first.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub block_libraries: Vec<String>,
|
pub block_libraries: Vec<String>,
|
||||||
|
/// Colour book (.acb) files loaded in the Select Color panel.
|
||||||
|
#[serde(default)]
|
||||||
|
pub color_books: Vec<String>,
|
||||||
/// Blocks palette view: 0 extra large, 1 large, 2 medium, 3 small icons,
|
/// Blocks palette view: 0 extra large, 1 large, 2 medium, 3 small icons,
|
||||||
/// 4 details, 5 list.
|
/// 4 details, 5 list.
|
||||||
#[serde(default = "default_block_view")]
|
#[serde(default = "default_block_view")]
|
||||||
@@ -901,6 +904,7 @@ impl Default for UserSettings {
|
|||||||
block_recent: Vec::new(),
|
block_recent: Vec::new(),
|
||||||
block_favorites: Vec::new(),
|
block_favorites: Vec::new(),
|
||||||
block_libraries: Vec::new(),
|
block_libraries: Vec::new(),
|
||||||
|
color_books: Vec::new(),
|
||||||
block_palette_view: 1,
|
block_palette_view: 1,
|
||||||
block_insert: BlockInsertOptions::default(),
|
block_insert: BlockInsertOptions::default(),
|
||||||
block_mru_list: 50,
|
block_mru_list: 50,
|
||||||
|
|||||||
@@ -860,6 +860,7 @@ impl OpenCADStudio {
|
|||||||
block_recent: self.block_palette.recent.clone(),
|
block_recent: self.block_palette.recent.clone(),
|
||||||
block_favorites: self.block_palette.favorites.clone(),
|
block_favorites: self.block_palette.favorites.clone(),
|
||||||
block_libraries: self.block_palette.libraries.clone(),
|
block_libraries: self.block_palette.libraries.clone(),
|
||||||
|
color_books: self.color_book_paths.clone(),
|
||||||
block_palette_view: self.block_palette.view as u8,
|
block_palette_view: self.block_palette.view as u8,
|
||||||
block_insert: self.block_palette.options.clone(),
|
block_insert: self.block_palette.options.clone(),
|
||||||
block_mru_list: self.block_mru_list,
|
block_mru_list: self.block_mru_list,
|
||||||
@@ -1025,6 +1026,7 @@ impl OpenCADStudio {
|
|||||||
self.block_palette.recent = s.block_recent.clone();
|
self.block_palette.recent = s.block_recent.clone();
|
||||||
self.block_palette.favorites = s.block_favorites.clone();
|
self.block_palette.favorites = s.block_favorites.clone();
|
||||||
self.block_palette.libraries = s.block_libraries.clone();
|
self.block_palette.libraries = s.block_libraries.clone();
|
||||||
|
self.color_book_paths = s.color_books.clone();
|
||||||
self.block_palette.view =
|
self.block_palette.view =
|
||||||
crate::ui::window::block_palette::ViewMode::from_u8(s.block_palette_view);
|
crate::ui::window::block_palette::ViewMode::from_u8(s.block_palette_view);
|
||||||
self.block_palette.set_options(s.block_insert.clone());
|
self.block_palette.set_options(s.block_insert.clone());
|
||||||
|
|||||||
+181
-69
@@ -4180,38 +4180,7 @@ impl OpenCADStudio {
|
|||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::LayerColorSet(color) => {
|
Message::LayerColorSet(color) => self.set_layer_color(color, None),
|
||||||
let i = self.active_tab;
|
|
||||||
// Apply to every selected layer (multi-select), not just one.
|
|
||||||
let names = self.selected_layer_names(i);
|
|
||||||
if !names.is_empty() {
|
|
||||||
let undo = self.begin_layer_undo(i, "LAYER COLOR", &names);
|
|
||||||
for name in &names {
|
|
||||||
if let Some(dl) = self.tabs[i].scene.document.layers.get_mut(name) {
|
|
||||||
dl.color = color;
|
|
||||||
dl.color_name = None;
|
|
||||||
dl.book_name = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for pl in self.tabs[i].layers.layers.iter_mut() {
|
|
||||||
if names.contains(&pl.name) {
|
|
||||||
pl.color = color;
|
|
||||||
pl.color_name = None;
|
|
||||||
pl.book_name = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.tabs[i].dirty = true;
|
|
||||||
self.commit_layer_undo(i, undo);
|
|
||||||
// ByLayer color is baked into the cached wires at
|
|
||||||
// tessellation time, so bump the geometry epoch to
|
|
||||||
// invalidate the wire cache and repaint with the new color.
|
|
||||||
self.tabs[i].scene.invalidate_layer_dependencies(&names);
|
|
||||||
self.tabs[i].layers.color_picker_row = None;
|
|
||||||
self.tabs[i].layers.color_full_palette = false;
|
|
||||||
self.sync_ribbon_layers();
|
|
||||||
}
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
|
|
||||||
Message::LayerLinetypeSet(lt) => {
|
Message::LayerLinetypeSet(lt) => {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
@@ -7264,22 +7233,7 @@ impl OpenCADStudio {
|
|||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::PropColorChanged(color) => {
|
Message::PropColorChanged(color) => self.set_property_color(color, None),
|
||||||
let i = self.active_tab;
|
|
||||||
let handles = self.property_target_handles(i);
|
|
||||||
if handles.is_empty() {
|
|
||||||
let task = self.on_ribbon_color_changed(color);
|
|
||||||
self.refresh_properties();
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
self.apply_property_op(i, "CHPROP", &handles, |app, handle| {
|
|
||||||
if let Some(entity) = app.tabs[i].scene.document.get_entity_mut(handle) {
|
|
||||||
crate::scene::view::dispatch::apply_color(entity, color);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
self.tabs[i].properties.color_picker_open = false;
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
|
|
||||||
Message::PropLwChanged(lw) => {
|
Message::PropLwChanged(lw) => {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
@@ -11605,10 +11559,19 @@ impl OpenCADStudio {
|
|||||||
) {
|
) {
|
||||||
self.graphic_attributes.open_menu = None;
|
self.graphic_attributes.open_menu = None;
|
||||||
}
|
}
|
||||||
let (current, varies) = self.color_picker_current(&target, color);
|
let (current, varies, name) = self.color_picker_current(&target, color);
|
||||||
self.color_picker = Some(crate::ui::color_select::ColorPicker::new(
|
let mut picker = crate::ui::color_select::ColorPicker::new(target, current, varies);
|
||||||
target, current, varies,
|
if !matches!(picker.target, crate::app::ColorPickTarget::Interface(_)) {
|
||||||
));
|
self.read_color_books();
|
||||||
|
let tab = &self.tabs[self.active_tab];
|
||||||
|
picker = picker.with_drawing(
|
||||||
|
crate::ui::color_select::document::collect(&tab.scene.document),
|
||||||
|
&tab.recent_colors,
|
||||||
|
std::sync::Arc::clone(&self.color_books),
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.color_picker = Some(picker);
|
||||||
self.modal_offset = iced::Vector::ZERO;
|
self.modal_offset = iced::Vector::ZERO;
|
||||||
self.ds_color_open = None;
|
self.ds_color_open = None;
|
||||||
self.mls_color_open = None;
|
self.mls_color_open = None;
|
||||||
@@ -11628,9 +11591,28 @@ impl OpenCADStudio {
|
|||||||
return Task::none();
|
return Task::none();
|
||||||
};
|
};
|
||||||
match picker.update(msg) {
|
match picker.update(msg) {
|
||||||
Some(crate::ui::color_select::Outcome::Commit(color)) => {
|
Some(crate::ui::color_select::Outcome::Commit(color, name)) => {
|
||||||
self.update(Message::ColorWindowPick(color))
|
self.update(Message::ColorWindowPick(color, name))
|
||||||
}
|
}
|
||||||
|
Some(crate::ui::color_select::Outcome::LoadBook) => Task::perform(
|
||||||
|
async {
|
||||||
|
let file = crate::sys::file_dialog()
|
||||||
|
.add_filter(crate::t!("Color book").as_ref(), &["acb", "ACB"])
|
||||||
|
.pick_file()
|
||||||
|
.await
|
||||||
|
.ok_or_else(|| "Cancelled".to_string())?;
|
||||||
|
let book = crate::ui::color_select::book::parse(
|
||||||
|
&String::from_utf8_lossy(&file.read().await),
|
||||||
|
)?;
|
||||||
|
// The web build has no paths to read again later.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let path = Some(crate::sys::handle_path(&file).display().to_string());
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
let path = None;
|
||||||
|
Ok((book, path))
|
||||||
|
},
|
||||||
|
Message::ColorBookLoaded,
|
||||||
|
),
|
||||||
Some(crate::ui::color_select::Outcome::Cancel) => {
|
Some(crate::ui::color_select::Outcome::Cancel) => {
|
||||||
self.color_picker = None;
|
self.color_picker = None;
|
||||||
Task::none()
|
Task::none()
|
||||||
@@ -11656,7 +11638,7 @@ impl OpenCADStudio {
|
|||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::ColorWindowPick(color) => {
|
Message::ColorWindowPick(color, name) => {
|
||||||
// Interface colours are not drawing colours: keep them out of
|
// Interface colours are not drawing colours: keep them out of
|
||||||
// the recent list.
|
// the recent list.
|
||||||
if !matches!(
|
if !matches!(
|
||||||
@@ -11665,7 +11647,35 @@ impl OpenCADStudio {
|
|||||||
) {
|
) {
|
||||||
self.note_recent_color(color);
|
self.note_recent_color(color);
|
||||||
}
|
}
|
||||||
self.on_color_window_pick(color)
|
self.on_color_window_pick(color, name)
|
||||||
|
}
|
||||||
|
Message::ColorBookLoaded(Ok((book, path))) => {
|
||||||
|
// A book loaded again replaces the earlier copy.
|
||||||
|
let books = std::sync::Arc::make_mut(&mut self.color_books);
|
||||||
|
let index = match books.iter().position(|b| b.name == book.name) {
|
||||||
|
Some(index) => {
|
||||||
|
books[index] = book;
|
||||||
|
index
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
books.push(book);
|
||||||
|
books.len() - 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(path) = path.filter(|path| !self.color_book_paths.contains(path)) {
|
||||||
|
self.color_book_paths.push(path);
|
||||||
|
self.save_config();
|
||||||
|
}
|
||||||
|
if let Some(picker) = self.color_picker.as_mut() {
|
||||||
|
picker.show_book(std::sync::Arc::clone(&self.color_books), index);
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::ColorBookLoaded(Err(error)) => {
|
||||||
|
if let Some(picker) = self.color_picker.as_mut().filter(|_| error != "Cancelled") {
|
||||||
|
picker.set_status(error);
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::DsSetHandle { field, value } => self.on_ds_set_handle(field, value),
|
Message::DsSetHandle { field, value } => self.on_ds_set_handle(field, value),
|
||||||
}
|
}
|
||||||
@@ -11674,16 +11684,33 @@ impl OpenCADStudio {
|
|||||||
/// The colour the Select Color panel shows as "Current", and whether the
|
/// The colour the Select Color panel shows as "Current", and whether the
|
||||||
/// selection it edits has mixed colours. A mixed selection starts from the
|
/// selection it edits has mixed colours. A mixed selection starts from the
|
||||||
/// colour of its most recently drawn object (the highest handle).
|
/// colour of its most recently drawn object (the highest handle).
|
||||||
|
/// Also returns the colour-book identity (`BOOK$NAME`) of that colour.
|
||||||
fn color_picker_current(
|
fn color_picker_current(
|
||||||
&self,
|
&self,
|
||||||
target: &crate::app::ColorPickTarget,
|
target: &crate::app::ColorPickTarget,
|
||||||
color: codec::types::Color,
|
color: codec::types::Color,
|
||||||
) -> (codec::types::Color, bool) {
|
) -> (codec::types::Color, bool, Option<String>) {
|
||||||
if !matches!(target, crate::app::ColorPickTarget::Properties) {
|
|
||||||
return (color, false);
|
|
||||||
}
|
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let document = &self.tabs[i].scene.document;
|
let document = &self.tabs[i].scene.document;
|
||||||
|
let name_of = |handle| {
|
||||||
|
document
|
||||||
|
.get_entity(handle)
|
||||||
|
.and_then(|entity| entity.common().color_name.clone())
|
||||||
|
};
|
||||||
|
match target {
|
||||||
|
crate::app::ColorPickTarget::Properties => {}
|
||||||
|
crate::app::ColorPickTarget::Layer(layer) => {
|
||||||
|
let name = document.layers.get(layer).and_then(|layer| {
|
||||||
|
match (&layer.book_name, &layer.color_name) {
|
||||||
|
(Some(book), Some(name)) => Some(format!("{book}${name}")),
|
||||||
|
(None, Some(name)) => Some(name.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return (color, false, name);
|
||||||
|
}
|
||||||
|
_ => return (color, false, None),
|
||||||
|
}
|
||||||
let mut colors = self
|
let mut colors = self
|
||||||
.property_target_handles(i)
|
.property_target_handles(i)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -11693,15 +11720,99 @@ impl OpenCADStudio {
|
|||||||
.map(|entity| (handle, entity.common().color))
|
.map(|entity| (handle, entity.common().color))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let Some(&(_, first)) = colors.first() else {
|
let Some(&(first_handle, first)) = colors.first() else {
|
||||||
return (color, false);
|
return (color, false, None);
|
||||||
};
|
};
|
||||||
if colors.iter().all(|&(_, c)| c == first) {
|
if colors.iter().all(|&(_, c)| c == first) {
|
||||||
return (first, false);
|
return (first, false, name_of(first_handle));
|
||||||
}
|
}
|
||||||
colors.sort_by_key(|&(handle, _)| handle);
|
colors.sort_by_key(|&(handle, _)| handle);
|
||||||
let newest = colors.last().map_or(color, |&(_, c)| c);
|
let (newest_handle, newest) = colors.last().copied().unwrap_or((first_handle, color));
|
||||||
(newest, true)
|
(newest, true, name_of(newest_handle))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the colour books remembered in the settings, once per session.
|
||||||
|
fn read_color_books(&mut self) {
|
||||||
|
if std::mem::replace(&mut self.color_books_read, true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
{
|
||||||
|
let books: Vec<_> = self
|
||||||
|
.color_book_paths
|
||||||
|
.iter()
|
||||||
|
.filter_map(|path| std::fs::read_to_string(path).ok())
|
||||||
|
.filter_map(|text| crate::ui::color_select::book::parse(&text).ok())
|
||||||
|
.collect();
|
||||||
|
self.color_books = std::sync::Arc::new(books);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Colour the selected objects (or set the current colour when nothing
|
||||||
|
/// is selected). `name` is a colour-book identity, `BOOK$NAME`.
|
||||||
|
pub(super) fn set_property_color(
|
||||||
|
&mut self,
|
||||||
|
color: codec::types::Color,
|
||||||
|
name: Option<String>,
|
||||||
|
) -> Task<Message> {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let handles = self.property_target_handles(i);
|
||||||
|
if handles.is_empty() {
|
||||||
|
let task = self.on_ribbon_color_changed(color);
|
||||||
|
self.refresh_properties();
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
self.apply_property_op(i, "CHPROP", &handles, |app, handle| {
|
||||||
|
if let Some(entity) = app.tabs[i].scene.document.get_entity_mut(handle) {
|
||||||
|
crate::scene::view::dispatch::apply_named_color(entity, color, name.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.tabs[i].properties.color_picker_open = false;
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Colour every selected layer (multi-select), not just one. `name` is a
|
||||||
|
/// colour-book identity, `BOOK$NAME`.
|
||||||
|
pub(super) fn set_layer_color(
|
||||||
|
&mut self,
|
||||||
|
color: codec::types::Color,
|
||||||
|
name: Option<&str>,
|
||||||
|
) -> Task<Message> {
|
||||||
|
let i = self.active_tab;
|
||||||
|
// Layers keep the book and the colour name apart.
|
||||||
|
let (book_name, color_name) = match name.map(|name| name.split_once('$')) {
|
||||||
|
Some(Some((book, name))) => (Some(book), Some(name)),
|
||||||
|
Some(None) => (None, name),
|
||||||
|
None => (None, None),
|
||||||
|
};
|
||||||
|
let names = self.selected_layer_names(i);
|
||||||
|
if !names.is_empty() {
|
||||||
|
let undo = self.begin_layer_undo(i, "LAYER COLOR", &names);
|
||||||
|
for name in &names {
|
||||||
|
if let Some(dl) = self.tabs[i].scene.document.layers.get_mut(name) {
|
||||||
|
dl.color = color;
|
||||||
|
dl.color_name = color_name.map(str::to_string);
|
||||||
|
dl.book_name = book_name.map(str::to_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for pl in self.tabs[i].layers.layers.iter_mut() {
|
||||||
|
if names.contains(&pl.name) {
|
||||||
|
pl.color = color;
|
||||||
|
pl.color_name = color_name.map(str::to_string);
|
||||||
|
pl.book_name = book_name.map(str::to_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.commit_layer_undo(i, undo);
|
||||||
|
// ByLayer color is baked into the cached wires at
|
||||||
|
// tessellation time, so bump the geometry epoch to
|
||||||
|
// invalidate the wire cache and repaint with the new color.
|
||||||
|
self.tabs[i].scene.invalidate_layer_dependencies(&names);
|
||||||
|
self.tabs[i].layers.color_picker_row = None;
|
||||||
|
self.tabs[i].layers.color_full_palette = false;
|
||||||
|
self.sync_ribbon_layers();
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn note_recent_color(&mut self, color: codec::types::Color) {
|
pub(crate) fn note_recent_color(&mut self, color: codec::types::Color) {
|
||||||
@@ -11712,12 +11823,13 @@ impl OpenCADStudio {
|
|||||||
codec::types::Color::Index(_) | codec::types::Color::Rgb { .. }
|
codec::types::Color::Index(_) | codec::types::Color::Rgb { .. }
|
||||||
) {
|
) {
|
||||||
// No duplicates: selecting an existing colour moves it to the front.
|
// No duplicates: selecting an existing colour moves it to the front.
|
||||||
if let Some(pos) = self.recent_colors.iter().position(|c| c == &color) {
|
let recent = &mut self.tabs[self.active_tab].recent_colors;
|
||||||
self.recent_colors.remove(pos);
|
if let Some(pos) = recent.iter().position(|c| c == &color) {
|
||||||
|
recent.remove(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.recent_colors.insert(0, color);
|
recent.insert(0, color);
|
||||||
self.recent_colors.truncate(12);
|
recent.truncate(12);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-5
@@ -1136,7 +1136,11 @@ impl OpenCADStudio {
|
|||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn on_color_window_pick(&mut self, color: codec::types::Color) -> Task<Message> {
|
pub(super) fn on_color_window_pick(
|
||||||
|
&mut self,
|
||||||
|
color: codec::types::Color,
|
||||||
|
name: Option<String>,
|
||||||
|
) -> Task<Message> {
|
||||||
if let Some(crate::app::ColorPickTarget::Interface(target)) = self
|
if let Some(crate::app::ColorPickTarget::Interface(target)) = self
|
||||||
.color_picker
|
.color_picker
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -1201,7 +1205,9 @@ impl OpenCADStudio {
|
|||||||
field: f,
|
field: f,
|
||||||
value: s,
|
value: s,
|
||||||
}),
|
}),
|
||||||
Some(crate::app::ColorPickTarget::Properties) => Some(Message::PropColorChanged(color)),
|
Some(crate::app::ColorPickTarget::Properties) => {
|
||||||
|
return self.set_property_color(color, name);
|
||||||
|
}
|
||||||
Some(crate::app::ColorPickTarget::PropertiesBg) => {
|
Some(crate::app::ColorPickTarget::PropertiesBg) => {
|
||||||
Some(Message::PropBgColorChanged(color))
|
Some(Message::PropBgColorChanged(color))
|
||||||
}
|
}
|
||||||
@@ -1210,12 +1216,12 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
Some(crate::app::ColorPickTarget::MText) => Some(Message::MTextColorChanged(color)),
|
Some(crate::app::ColorPickTarget::MText) => Some(Message::MTextColorChanged(color)),
|
||||||
Some(crate::app::ColorPickTarget::Ribbon) => Some(Message::RibbonColorChanged(color)),
|
Some(crate::app::ColorPickTarget::Ribbon) => Some(Message::RibbonColorChanged(color)),
|
||||||
Some(crate::app::ColorPickTarget::Layer(name)) => {
|
Some(crate::app::ColorPickTarget::Layer(layer)) => {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
match self.layer_panel_index(i, &name) {
|
match self.layer_panel_index(i, &layer) {
|
||||||
Some(idx) => {
|
Some(idx) => {
|
||||||
self.tabs[i].layers.selected = Some(idx);
|
self.tabs[i].layers.selected = Some(idx);
|
||||||
Some(Message::LayerColorSet(color))
|
return self.set_layer_color(color, name.as_deref());
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -2187,7 +2187,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
|||||||
&redo_labels,
|
&redo_labels,
|
||||||
self.win_size,
|
self.win_size,
|
||||||
self.tabs[self.active_tab].is_start,
|
self.tabs[self.active_tab].is_start,
|
||||||
&self.recent_colors,
|
&self.tabs[self.active_tab].recent_colors,
|
||||||
)
|
)
|
||||||
.unwrap_or_else(|| iced::widget::Space::new().width(0).height(0).into())
|
.unwrap_or_else(|| iced::widget::Space::new().width(0).height(0).into())
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4720,6 +4720,21 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
|||||||
"Hex" => Some(("common", "color-hex")),
|
"Hex" => Some(("common", "color-hex")),
|
||||||
"Nearest index" => Some(("common", "nearest-index-color")),
|
"Nearest index" => Some(("common", "nearest-index-color")),
|
||||||
"Exact index match" => Some(("common", "exact-index-match")),
|
"Exact index match" => Some(("common", "exact-index-match")),
|
||||||
|
"Recently used" => Some(("common", "color-recently-used")),
|
||||||
|
"Most used" => Some(("common", "color-most-used")),
|
||||||
|
"Color number" => Some(("common", "color-number")),
|
||||||
|
"Colors in this drawing" => Some(("common", "color-in-drawing")),
|
||||||
|
"Sort" => Some(("common", "color-sort")),
|
||||||
|
"No colors in this drawing yet" => Some(("common", "color-no-drawing-colors")),
|
||||||
|
"Color Books" => Some(("common", "color-books")),
|
||||||
|
"No color books loaded" => Some(("common", "color-no-books")),
|
||||||
|
"Load color book (.acb)" => Some(("common", "color-load-book")),
|
||||||
|
"Color book" => Some(("common", "color-book-label")),
|
||||||
|
"RGB equivalent" => Some(("common", "color-rgb-equivalent")),
|
||||||
|
"No colors match" => Some(("common", "color-no-match")),
|
||||||
|
"Not a color book" => Some(("common", "color-not-a-book")),
|
||||||
|
"This color book is encrypted and cannot be read" => Some(("common", "color-book-encrypted")),
|
||||||
|
"The color book has no colors" => Some(("common", "color-book-empty")),
|
||||||
"CAD Color Index (ACI) 10–249" => Some(("common", "cad-color-index-aci-10-249")),
|
"CAD Color Index (ACI) 10–249" => Some(("common", "cad-color-index-aci-10-249")),
|
||||||
"Grayscale 250–255" => Some(("common", "grayscale-250-255")),
|
"Grayscale 250–255" => Some(("common", "grayscale-250-255")),
|
||||||
"Isometric drafting" => Some(("common", "isometric-drafting")),
|
"Isometric drafting" => Some(("common", "isometric-drafting")),
|
||||||
|
|||||||
@@ -322,6 +322,12 @@ pub fn apply_color(entity: &mut EntityType, color: AcadColor) {
|
|||||||
common.color_book_handle = None;
|
common.color_book_handle = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like [`apply_color`], but keeps a colour-book identity (`BOOK$NAME`).
|
||||||
|
pub fn apply_named_color(entity: &mut EntityType, color: AcadColor, name: Option<String>) {
|
||||||
|
apply_color(entity, color);
|
||||||
|
entity.common_mut().color_name = name;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_line_weight(entity: &mut EntityType, lw: LineWeight) {
|
pub fn apply_line_weight(entity: &mut EntityType, lw: LineWeight) {
|
||||||
entity.as_entity_mut().set_line_weight(lw);
|
entity.as_entity_mut().set_line_weight(lw);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
//! Colour books: AutoCAD `.acb` files.
|
||||||
|
//!
|
||||||
|
//! An `.acb` is XML: a `bookName` and `colorPage`s of `colorEntry`s, each a
|
||||||
|
//! `colorName` and an `RGB8` value. Books from some vendors encrypt their
|
||||||
|
//! values; those cannot be read.
|
||||||
|
|
||||||
|
/// One named colour.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct BookColor {
|
||||||
|
pub name: String,
|
||||||
|
pub rgb: (u8, u8, u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct ColorBook {
|
||||||
|
pub name: String,
|
||||||
|
/// Every colour, page after page.
|
||||||
|
pub colors: Vec<BookColor>,
|
||||||
|
/// Where each page starts in `colors`.
|
||||||
|
page_starts: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pages for a book without `colorPage`s, as AutoCAD shows them.
|
||||||
|
const PAGE_SIZE: usize = 7;
|
||||||
|
|
||||||
|
impl ColorBook {
|
||||||
|
pub fn page_count(&self) -> usize {
|
||||||
|
self.page_starts.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The colours of one page, and the index of its first colour.
|
||||||
|
pub fn page(&self, page: usize) -> (usize, &[BookColor]) {
|
||||||
|
let start = self.page_starts.get(page).copied().unwrap_or(0);
|
||||||
|
let end = self
|
||||||
|
.page_starts
|
||||||
|
.get(page + 1)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(self.colors.len());
|
||||||
|
(start, &self.colors[start..end])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The page holding colour `index`.
|
||||||
|
pub fn page_of(&self, index: usize) -> usize {
|
||||||
|
self.page_starts
|
||||||
|
.partition_point(|&start| start <= index)
|
||||||
|
.saturating_sub(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The colour with this name, as stored after the `$` of `BOOK$NAME`.
|
||||||
|
pub fn find(&self, name: &str) -> Option<usize> {
|
||||||
|
self.colors.iter().position(|c| c.name == name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `BOOK$NAME` identity objects and layers store.
|
||||||
|
pub fn identity(&self, index: usize) -> String {
|
||||||
|
format!("{}${}", self.name, self.colors[index].name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read an `.acb` file's text.
|
||||||
|
pub fn parse(text: &str) -> Result<ColorBook, String> {
|
||||||
|
let doc = roxmltree::Document::parse(text.trim_start_matches('\u{feff}'))
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
let root = doc.root_element();
|
||||||
|
if !root.has_tag_name("colorBook") {
|
||||||
|
return Err(crate::t!("Not a color book").into_owned());
|
||||||
|
}
|
||||||
|
let mut book = ColorBook {
|
||||||
|
name: text_of(root, "bookName").unwrap_or_default().to_string(),
|
||||||
|
colors: Vec::new(),
|
||||||
|
page_starts: Vec::new(),
|
||||||
|
};
|
||||||
|
let mut encrypted = false;
|
||||||
|
let mut read_entries = |parent: roxmltree::Node<'_, '_>, book: &mut ColorBook| {
|
||||||
|
for entry in parent.children().filter(|n| n.has_tag_name("colorEntry")) {
|
||||||
|
let rgb = child(entry, "RGB8").and_then(|rgb| {
|
||||||
|
let channel = |tag| text_of(rgb, tag)?.parse::<u8>().ok();
|
||||||
|
Some((channel("red")?, channel("green")?, channel("blue")?))
|
||||||
|
});
|
||||||
|
match (text_of(entry, "colorName"), rgb) {
|
||||||
|
(Some(name), Some(rgb)) => book.colors.push(BookColor {
|
||||||
|
name: name.to_string(),
|
||||||
|
rgb,
|
||||||
|
}),
|
||||||
|
_ => encrypted = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut pages = root
|
||||||
|
.children()
|
||||||
|
.filter(|n| n.has_tag_name("colorPage"))
|
||||||
|
.peekable();
|
||||||
|
if pages.peek().is_some() {
|
||||||
|
for page in pages {
|
||||||
|
let start = book.colors.len();
|
||||||
|
read_entries(page, &mut book);
|
||||||
|
if book.colors.len() > start {
|
||||||
|
book.page_starts.push(start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
read_entries(root, &mut book);
|
||||||
|
book.page_starts = (0..book.colors.len()).step_by(PAGE_SIZE).collect();
|
||||||
|
}
|
||||||
|
if book.colors.is_empty() {
|
||||||
|
return Err(if encrypted {
|
||||||
|
crate::t!("This color book is encrypted and cannot be read")
|
||||||
|
} else {
|
||||||
|
crate::t!("The color book has no colors")
|
||||||
|
}
|
||||||
|
.into_owned());
|
||||||
|
}
|
||||||
|
Ok(book)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn child<'a, 'i>(node: roxmltree::Node<'a, 'i>, tag: &str) -> Option<roxmltree::Node<'a, 'i>> {
|
||||||
|
node.children().find(|n| n.has_tag_name(tag))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_of<'a>(node: roxmltree::Node<'a, '_>, tag: &str) -> Option<&'a str> {
|
||||||
|
child(node, tag).and_then(|n| n.text()).map(str::trim)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn entry(name: &str, (r, g, b): (u8, u8, u8)) -> String {
|
||||||
|
format!(
|
||||||
|
"<colorEntry><colorName>{name}</colorName>\
|
||||||
|
<RGB8><red>{r}</red><green>{g}</green><blue>{b}</blue></RGB8></colorEntry>"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reads_pages_and_colours() {
|
||||||
|
let text = format!(
|
||||||
|
"\u{feff}<?xml version=\"1.0\"?><colorBook><bookName>RAL CLASSIC</bookName>\
|
||||||
|
<colorPage><pageColor><RGB8><red>1</red><green>2</green><blue>3</blue></RGB8></pageColor>{}{}</colorPage>\
|
||||||
|
<colorPage>{}</colorPage></colorBook>",
|
||||||
|
entry("RAL 1000", (190, 189, 127)),
|
||||||
|
entry("RAL 1001", (194, 176, 120)),
|
||||||
|
entry("RAL 5015", (34, 113, 179)),
|
||||||
|
);
|
||||||
|
let book = parse(&text).unwrap();
|
||||||
|
assert_eq!(book.name, "RAL CLASSIC");
|
||||||
|
assert_eq!(book.page_count(), 2);
|
||||||
|
assert_eq!(book.page(1), (2, &book.colors[2..]));
|
||||||
|
assert_eq!(book.page_of(1), 0);
|
||||||
|
assert_eq!(book.page_of(2), 1);
|
||||||
|
let blue = book.find("RAL 5015").unwrap();
|
||||||
|
assert_eq!(book.colors[blue].rgb, (34, 113, 179));
|
||||||
|
assert_eq!(book.identity(blue), "RAL CLASSIC$RAL 5015");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_book_without_pages_gets_pages_of_seven() {
|
||||||
|
let entries: String = (0..9).map(|i| entry(&format!("C{i}"), (i, i, i))).collect();
|
||||||
|
let book = parse(&format!(
|
||||||
|
"<colorBook><bookName>B</bookName>{entries}</colorBook>"
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(book.page_count(), 2);
|
||||||
|
assert_eq!(book.page(1).1.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_unreadable_books() {
|
||||||
|
assert!(parse("not xml").is_err());
|
||||||
|
assert!(parse("<palette/>").is_err());
|
||||||
|
let encrypted = "<colorBook><bookName>P</bookName><colorPage><colorEntry>\
|
||||||
|
<colorName>X</colorName><RGB8Encrypt>AbC</RGB8Encrypt></colorEntry>\
|
||||||
|
</colorPage></colorBook>";
|
||||||
|
assert!(parse(encrypted).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
//! The colours a drawing uses, for the Document tab.
|
||||||
|
//!
|
||||||
|
//! One pass over every object (block definitions included) and every layer
|
||||||
|
//! when the panel opens. Keys borrow from the drawing, so the pass allocates
|
||||||
|
//! only per distinct colour, never per object.
|
||||||
|
|
||||||
|
use super::math;
|
||||||
|
use codec::types::{Color as AcadColor, Handle};
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
|
/// One distinct colour: a concrete colour plus, for a colour-book colour,
|
||||||
|
/// its `BOOK$NAME` identity.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct DocColor {
|
||||||
|
pub color: AcadColor,
|
||||||
|
pub name: Option<String>,
|
||||||
|
/// Objects and layers using it. Only used for sorting, never shown.
|
||||||
|
uses: u32,
|
||||||
|
/// The newest object or layer using it.
|
||||||
|
newest: Handle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DocColor {
|
||||||
|
/// The few characters drawn on the swatch: the index number, `RGB`, or
|
||||||
|
/// the first word of the colour book.
|
||||||
|
pub fn tag(&self) -> String {
|
||||||
|
match (&self.name, self.color) {
|
||||||
|
(Some(name), _) => name
|
||||||
|
.split(['$', ' '])
|
||||||
|
.next()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.chars()
|
||||||
|
.take(5)
|
||||||
|
.collect(),
|
||||||
|
(None, AcadColor::Index(i)) => i.to_string(),
|
||||||
|
_ => "RGB".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
pub enum DocSort {
|
||||||
|
#[default]
|
||||||
|
Recent,
|
||||||
|
Usage,
|
||||||
|
Hue,
|
||||||
|
Number,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DocSort {
|
||||||
|
pub const ALL: [DocSort; 4] = [
|
||||||
|
DocSort::Recent,
|
||||||
|
DocSort::Usage,
|
||||||
|
DocSort::Hue,
|
||||||
|
DocSort::Number,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for DocSort {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(&match self {
|
||||||
|
DocSort::Recent => crate::t!("Recently used"),
|
||||||
|
DocSort::Usage => crate::t!("Most used"),
|
||||||
|
DocSort::Hue => crate::t!("Color"),
|
||||||
|
DocSort::Number => crate::t!("Color number"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A colour as a hash key: index colours keep their number, true colours
|
||||||
|
/// sit above 255.
|
||||||
|
fn key(color: AcadColor) -> Option<u32> {
|
||||||
|
match color {
|
||||||
|
AcadColor::Index(i) => Some(u32::from(i)),
|
||||||
|
AcadColor::Rgb { r, g, b } => {
|
||||||
|
Some(1 << 24 | u32::from(r) << 16 | u32::from(g) << 8 | u32::from(b))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Every concrete colour in the drawing. ByLayer, ByBlock and None are
|
||||||
|
/// states rather than colours and are skipped.
|
||||||
|
pub fn collect(document: &codec::CadDocument) -> Vec<DocColor> {
|
||||||
|
// Layers keep book and colour name apart; join them like objects do.
|
||||||
|
let layer_names: Vec<Option<String>> = document
|
||||||
|
.layers
|
||||||
|
.iter()
|
||||||
|
.map(|layer| match (&layer.book_name, &layer.color_name) {
|
||||||
|
(Some(book), Some(name)) => Some(format!("{book}${name}")),
|
||||||
|
(None, Some(name)) => Some(name.clone()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let objects = document.entities().map(|entity| {
|
||||||
|
let common = entity.common();
|
||||||
|
(common.color, common.color_name.as_deref(), common.handle)
|
||||||
|
});
|
||||||
|
let layers = document
|
||||||
|
.layers
|
||||||
|
.iter()
|
||||||
|
.zip(&layer_names)
|
||||||
|
.map(|(layer, name)| (layer.color, name.as_deref(), layer.handle));
|
||||||
|
tally(objects.chain(layers))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tally<'a>(uses: impl Iterator<Item = (AcadColor, Option<&'a str>, Handle)>) -> Vec<DocColor> {
|
||||||
|
let mut index: FxHashMap<(u32, Option<&str>), usize> = FxHashMap::default();
|
||||||
|
let mut colors: Vec<DocColor> = Vec::new();
|
||||||
|
for (color, name, handle) in uses {
|
||||||
|
let Some(k) = key(color) else { continue };
|
||||||
|
let name = name.filter(|name| !name.is_empty());
|
||||||
|
let slot = *index.entry((k, name)).or_insert_with(|| {
|
||||||
|
colors.push(DocColor {
|
||||||
|
color,
|
||||||
|
name: name.map(str::to_string),
|
||||||
|
uses: 0,
|
||||||
|
newest: handle,
|
||||||
|
});
|
||||||
|
colors.len() - 1
|
||||||
|
});
|
||||||
|
let entry = &mut colors[slot];
|
||||||
|
entry.uses += 1;
|
||||||
|
entry.newest = entry.newest.max(handle);
|
||||||
|
}
|
||||||
|
colors
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sort for display. `recent` is this drawing's pick history, newest first;
|
||||||
|
/// colours not picked this session follow, newest object first.
|
||||||
|
pub fn sort(colors: &mut [DocColor], by: DocSort, recent: &[AcadColor]) {
|
||||||
|
let rank = |c: &DocColor| {
|
||||||
|
recent
|
||||||
|
.iter()
|
||||||
|
.position(|&r| r == c.color)
|
||||||
|
.unwrap_or(usize::MAX)
|
||||||
|
};
|
||||||
|
match by {
|
||||||
|
DocSort::Recent => {
|
||||||
|
colors.sort_by(|a, b| rank(a).cmp(&rank(b)).then(b.newest.cmp(&a.newest)))
|
||||||
|
}
|
||||||
|
DocSort::Usage => colors.sort_by(|a, b| b.uses.cmp(&a.uses).then(b.newest.cmp(&a.newest))),
|
||||||
|
DocSort::Hue => colors.sort_by_cached_key(|c| {
|
||||||
|
let (r, g, b) = math::color_rgb(c.color).unwrap_or_default();
|
||||||
|
let hsb = math::rgb_to_hsb(r, g, b);
|
||||||
|
// Grays after the colours; within a hue band, light to dark.
|
||||||
|
let gray = hsb.s < 0.12 || hsb.b < 0.08;
|
||||||
|
let band = if gray {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
(hsb.h / 15.0).round() as u32
|
||||||
|
};
|
||||||
|
(gray, band, std::cmp::Reverse((hsb.b * 1000.0) as u32))
|
||||||
|
}),
|
||||||
|
DocSort::Number => colors.sort_by(|a, b| {
|
||||||
|
let kind = |c: &DocColor| match (&c.name, c.color) {
|
||||||
|
(None, AcadColor::Index(_)) => 0,
|
||||||
|
(None, _) => 1,
|
||||||
|
_ => 2,
|
||||||
|
};
|
||||||
|
kind(a)
|
||||||
|
.cmp(&kind(b))
|
||||||
|
.then_with(|| a.name.cmp(&b.name))
|
||||||
|
.then_with(|| key(a.color).cmp(&key(b.color)))
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const RED: AcadColor = AcadColor::Index(1);
|
||||||
|
const BLUE: AcadColor = AcadColor::Index(5);
|
||||||
|
const SKY: AcadColor = AcadColor::Rgb {
|
||||||
|
r: 57,
|
||||||
|
g: 191,
|
||||||
|
b: 252,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn sample() -> Vec<DocColor> {
|
||||||
|
let h = Handle::new;
|
||||||
|
tally(
|
||||||
|
[
|
||||||
|
(RED, None, h(1)),
|
||||||
|
(RED, None, h(2)),
|
||||||
|
(RED, None, h(3)),
|
||||||
|
(AcadColor::ByLayer, None, h(4)),
|
||||||
|
(SKY, None, h(5)),
|
||||||
|
(SKY, Some("RAL CLASSIC$RAL 5015"), h(6)),
|
||||||
|
(BLUE, Some(""), h(7)),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn collects_each_concrete_colour_once() {
|
||||||
|
let colors = sample();
|
||||||
|
assert_eq!(colors.len(), 4, "ByLayer skipped, book colour kept apart");
|
||||||
|
assert_eq!(colors[0].uses, 3);
|
||||||
|
assert_eq!(colors[0].newest, Handle::new(3));
|
||||||
|
assert_eq!(colors[3].name, None, "an empty name is no name");
|
||||||
|
assert_eq!(colors[2].tag(), "RAL");
|
||||||
|
assert_eq!(colors[0].tag(), "1");
|
||||||
|
assert_eq!(colors[1].tag(), "RGB");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sorts_four_ways() {
|
||||||
|
let order = |by, recent: &[AcadColor]| {
|
||||||
|
let mut colors = sample();
|
||||||
|
sort(&mut colors, by, recent);
|
||||||
|
colors
|
||||||
|
.iter()
|
||||||
|
.map(|c| (c.color, c.name.is_some()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
};
|
||||||
|
// Picked this session first, then newest object first.
|
||||||
|
assert_eq!(
|
||||||
|
order(DocSort::Recent, &[RED]),
|
||||||
|
[(RED, false), (BLUE, false), (SKY, true), (SKY, false)]
|
||||||
|
);
|
||||||
|
assert_eq!(order(DocSort::Usage, &[])[0], (RED, false));
|
||||||
|
assert_eq!(
|
||||||
|
order(DocSort::Number, &[]),
|
||||||
|
[(RED, false), (BLUE, false), (SKY, false), (SKY, true)]
|
||||||
|
);
|
||||||
|
// Red (hue 0) before sky blue (hue ~199) before blue (hue 240).
|
||||||
|
assert_eq!(order(DocSort::Hue, &[])[0], (RED, false));
|
||||||
|
assert_eq!(order(DocSort::Hue, &[])[3], (BLUE, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
//! properties panel and every style editor so colour selection looks and
|
//! properties panel and every style editor so colour selection looks and
|
||||||
//! behaves the same everywhere.
|
//! behaves the same everywhere.
|
||||||
|
|
||||||
|
pub mod book;
|
||||||
|
pub mod document;
|
||||||
pub mod editor;
|
pub mod editor;
|
||||||
pub mod eyedropper;
|
pub mod eyedropper;
|
||||||
pub mod math;
|
pub mod math;
|
||||||
|
|||||||
+708
-50
@@ -5,6 +5,8 @@
|
|||||||
//! Opened for an interface colour (any colour in Options) it shows only the
|
//! Opened for an interface colour (any colour in Options) it shows only the
|
||||||
//! colour editor: no tabs, no index colours.
|
//! colour editor: no tabs, no index colours.
|
||||||
|
|
||||||
|
use super::book::ColorBook;
|
||||||
|
use super::document::{self, DocColor, DocSort};
|
||||||
use super::editor::{rgb_color, ColorEditor, EditorMsg};
|
use super::editor::{rgb_color, ColorEditor, EditorMsg};
|
||||||
use super::eyedropper::{Eyedropper, EyedropperMsg};
|
use super::eyedropper::{Eyedropper, EyedropperMsg};
|
||||||
use super::math;
|
use super::math;
|
||||||
@@ -12,20 +14,29 @@ use crate::app::{ArrowKey, ColorPickTarget, Message};
|
|||||||
use crate::t;
|
use crate::t;
|
||||||
use crate::ui::properties::acad_color_display;
|
use crate::ui::properties::acad_color_display;
|
||||||
use codec::types::Color as AcadColor;
|
use codec::types::Color as AcadColor;
|
||||||
use iced::widget::{button, column, container, row, text, Space};
|
use iced::widget::{
|
||||||
|
button, column, container, mouse_area, pick_list, row, scrollable, svg, text, text_input, Space,
|
||||||
|
};
|
||||||
use iced::{Background, Border, Color, Element, Length, Theme};
|
use iced::{Background, Border, Color, Element, Length, Theme};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
const PANEL_WIDTH: f32 = 400.0;
|
const PANEL_WIDTH: f32 = 400.0;
|
||||||
/// Scope for Tab / Shift+Tab, so focus cycles through the panel's own
|
/// Scope for Tab / Shift+Tab, so focus cycles through the panel's own
|
||||||
/// fields instead of the dialogs and command line underneath.
|
/// fields instead of the dialogs and command line underneath.
|
||||||
const PANEL_ID: &str = "select-color-panel";
|
const PANEL_ID: &str = "select-color-panel";
|
||||||
const BODY_HEIGHT: f32 = 352.0;
|
const BODY_HEIGHT: f32 = 352.0;
|
||||||
|
/// Swatches per row in the Document tab.
|
||||||
|
const DOC_COLUMNS: usize = 8;
|
||||||
|
/// Search matches shown at once in the Color books tab.
|
||||||
|
const PAGE_BARS: usize = 7;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
pub enum ColorPickerTab {
|
pub enum ColorPickerTab {
|
||||||
|
Document,
|
||||||
#[default]
|
#[default]
|
||||||
Index,
|
Index,
|
||||||
TrueColor,
|
TrueColor,
|
||||||
|
Books,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -40,17 +51,32 @@ pub enum ColorPickerMsg {
|
|||||||
UseNearest,
|
UseNearest,
|
||||||
Arrow(ArrowKey),
|
Arrow(ArrowKey),
|
||||||
Eyedropper(EyedropperMsg),
|
Eyedropper(EyedropperMsg),
|
||||||
|
/// A colour of the Document tab, by its place in the list.
|
||||||
|
PickDocument(usize),
|
||||||
|
DocSort(DocSort),
|
||||||
|
/// Show another loaded colour book.
|
||||||
|
Book(usize),
|
||||||
|
BookPage(usize),
|
||||||
|
/// Scrolling over the page: one page per notch.
|
||||||
|
BookScroll(f32),
|
||||||
|
BookQuery(String),
|
||||||
|
/// A colour of the shown book, by its index in the book.
|
||||||
|
PickBook(usize),
|
||||||
|
LoadBook,
|
||||||
Ok,
|
Ok,
|
||||||
Cancel,
|
Cancel,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What the caller has to do after an update.
|
/// What the caller has to do after an update.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Outcome {
|
pub enum Outcome {
|
||||||
Commit(AcadColor),
|
/// The colour, and its `BOOK$NAME` when it comes from a colour book.
|
||||||
|
Commit(AcadColor, Option<String>),
|
||||||
Cancel,
|
Cancel,
|
||||||
/// Capture the window for the eyedropper.
|
/// Capture the window for the eyedropper.
|
||||||
Screenshot,
|
Screenshot,
|
||||||
|
/// Ask for an `.acb` file.
|
||||||
|
LoadBook,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ColorPicker {
|
pub struct ColorPicker {
|
||||||
@@ -60,9 +86,22 @@ pub struct ColorPicker {
|
|||||||
current: AcadColor,
|
current: AcadColor,
|
||||||
varies: bool,
|
varies: bool,
|
||||||
pending: AcadColor,
|
pending: AcadColor,
|
||||||
|
/// `BOOK$NAME` of the current and the new colour, for colour-book colours.
|
||||||
|
current_name: Option<String>,
|
||||||
|
pending_name: Option<String>,
|
||||||
tab: ColorPickerTab,
|
tab: ColorPickerTab,
|
||||||
editor: ColorEditor,
|
editor: ColorEditor,
|
||||||
eyedropper: Option<Eyedropper>,
|
eyedropper: Option<Eyedropper>,
|
||||||
|
doc: Vec<DocColor>,
|
||||||
|
doc_sort: DocSort,
|
||||||
|
/// This drawing's colours picked this session, newest first.
|
||||||
|
recent: Vec<AcadColor>,
|
||||||
|
books: Arc<Vec<ColorBook>>,
|
||||||
|
book: usize,
|
||||||
|
book_page: usize,
|
||||||
|
book_query: String,
|
||||||
|
/// Why the last colour book did not load.
|
||||||
|
status: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ColorPicker {
|
impl ColorPicker {
|
||||||
@@ -77,12 +116,85 @@ impl ColorPicker {
|
|||||||
current,
|
current,
|
||||||
varies,
|
varies,
|
||||||
pending: current,
|
pending: current,
|
||||||
|
current_name: None,
|
||||||
|
pending_name: None,
|
||||||
tab,
|
tab,
|
||||||
editor: ColorEditor::new(math::color_rgb(current).unwrap_or((255, 255, 255))),
|
editor: ColorEditor::new(math::color_rgb(current).unwrap_or((255, 255, 255))),
|
||||||
eyedropper: None,
|
eyedropper: None,
|
||||||
|
doc: Vec::new(),
|
||||||
|
doc_sort: DocSort::default(),
|
||||||
|
recent: Vec::new(),
|
||||||
|
books: Arc::default(),
|
||||||
|
book: 0,
|
||||||
|
book_page: 0,
|
||||||
|
book_query: String::new(),
|
||||||
|
status: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fill the Document and Color books tabs. `current_name` is the
|
||||||
|
/// `BOOK$NAME` of the current colour; a colour from a loaded book opens
|
||||||
|
/// on its page.
|
||||||
|
pub fn with_drawing(
|
||||||
|
mut self,
|
||||||
|
mut doc: Vec<DocColor>,
|
||||||
|
recent: &[AcadColor],
|
||||||
|
books: Arc<Vec<ColorBook>>,
|
||||||
|
current_name: Option<String>,
|
||||||
|
) -> Self {
|
||||||
|
document::sort(&mut doc, self.doc_sort, recent);
|
||||||
|
self.doc = doc;
|
||||||
|
self.recent = recent.to_vec();
|
||||||
|
self.books = books;
|
||||||
|
if let Some(name) = current_name.filter(|name| !name.is_empty()) {
|
||||||
|
if self.show_identity(&name) {
|
||||||
|
self.tab = ColorPickerTab::Books;
|
||||||
|
}
|
||||||
|
self.current_name = Some(name.clone());
|
||||||
|
self.pending_name = Some(name);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The loaded books changed; show book `index`.
|
||||||
|
pub fn show_book(&mut self, books: Arc<Vec<ColorBook>>, index: usize) {
|
||||||
|
self.books = books;
|
||||||
|
self.book = index;
|
||||||
|
self.book_page = 0;
|
||||||
|
self.book_query.clear();
|
||||||
|
self.status = None;
|
||||||
|
self.tab = ColorPickerTab::Books;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_status(&mut self, status: String) {
|
||||||
|
self.status = Some(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turn to the page holding `BOOK$NAME`. False when no loaded book has it.
|
||||||
|
fn show_identity(&mut self, identity: &str) -> bool {
|
||||||
|
let Some((book_name, name)) = identity.split_once('$') else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
for (i, book) in self.books.iter().enumerate() {
|
||||||
|
if let Some(color) = book.find(name).filter(|_| book.name == book_name) {
|
||||||
|
self.book = i;
|
||||||
|
self.book_page = book.page_of(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The colour of the shown book that is the new colour, if any.
|
||||||
|
fn book_selection(&self) -> Option<usize> {
|
||||||
|
let (book_name, name) = self.pending_name.as_deref()?.split_once('$')?;
|
||||||
|
let book = self
|
||||||
|
.books
|
||||||
|
.get(self.book)
|
||||||
|
.filter(|book| book.name == book_name)?;
|
||||||
|
book.find(name)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pending(&self) -> AcadColor {
|
pub fn pending(&self) -> AcadColor {
|
||||||
self.pending
|
self.pending
|
||||||
}
|
}
|
||||||
@@ -98,6 +210,7 @@ impl ColorPicker {
|
|||||||
|
|
||||||
fn set_pending(&mut self, color: AcadColor) {
|
fn set_pending(&mut self, color: AcadColor) {
|
||||||
self.pending = color;
|
self.pending = color;
|
||||||
|
self.pending_name = None;
|
||||||
if let Some(rgb) = math::color_rgb(color) {
|
if let Some(rgb) = math::color_rgb(color) {
|
||||||
self.editor.set_rgb(rgb);
|
self.editor.set_rgb(rgb);
|
||||||
}
|
}
|
||||||
@@ -131,24 +244,82 @@ impl ColorPicker {
|
|||||||
match msg {
|
match msg {
|
||||||
ColorPickerMsg::Tab(tab) => self.tab = tab,
|
ColorPickerMsg::Tab(tab) => self.tab = tab,
|
||||||
ColorPickerMsg::Pick(color) => self.set_pending(color),
|
ColorPickerMsg::Pick(color) => self.set_pending(color),
|
||||||
ColorPickerMsg::Revert => self.set_pending(self.current),
|
ColorPickerMsg::Revert => {
|
||||||
|
self.set_pending(self.current);
|
||||||
|
self.pending_name = self.current_name.clone();
|
||||||
|
}
|
||||||
ColorPickerMsg::Editor(editor) => {
|
ColorPickerMsg::Editor(editor) => {
|
||||||
if self.editor.update(editor) {
|
if self.editor.update(editor) {
|
||||||
let (r, g, b) = self.editor.rgb();
|
let (r, g, b) = self.editor.rgb();
|
||||||
self.pending = AcadColor::Rgb { r, g, b };
|
self.pending = AcadColor::Rgb { r, g, b };
|
||||||
|
self.pending_name = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ColorPickerMsg::UseNearest => {
|
ColorPickerMsg::UseNearest => {
|
||||||
let (r, g, b) = self.rgb();
|
let (r, g, b) = self.rgb();
|
||||||
self.set_pending(AcadColor::Index(math::nearest_aci(r, g, b)));
|
self.set_pending(AcadColor::Index(math::nearest_aci(r, g, b)));
|
||||||
}
|
}
|
||||||
ColorPickerMsg::Arrow(direction) => {
|
ColorPickerMsg::Arrow(direction) => match (self.tab, self.pending) {
|
||||||
if let (ColorPickerTab::Index, AcadColor::Index(index)) = (self.tab, self.pending) {
|
(ColorPickerTab::Index, AcadColor::Index(index)) => {
|
||||||
if let Some(next) = step_index(index, direction) {
|
if let Some(next) = step_index(index, direction) {
|
||||||
self.set_pending(AcadColor::Index(next));
|
self.set_pending(AcadColor::Index(next));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(ColorPickerTab::Document, _) => {
|
||||||
|
let at = self
|
||||||
|
.doc
|
||||||
|
.iter()
|
||||||
|
.position(|c| c.color == self.pending && c.name == self.pending_name);
|
||||||
|
if let Some(next) = step_list(at, self.doc.len(), DOC_COLUMNS, direction) {
|
||||||
|
self.update(ColorPickerMsg::PickDocument(next));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(ColorPickerTab::Books, _) => {
|
||||||
|
let len = self
|
||||||
|
.books
|
||||||
|
.get(self.book)
|
||||||
|
.map_or(0, |book| book.colors.len());
|
||||||
|
if let Some(next) = step_list(self.book_selection(), len, 1, direction) {
|
||||||
|
self.pick_book(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
ColorPickerMsg::PickDocument(i) => {
|
||||||
|
if let Some(entry) = self.doc.get(i) {
|
||||||
|
let (color, name) = (entry.color, entry.name.clone());
|
||||||
|
self.set_pending(color);
|
||||||
|
self.pending_name = name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
ColorPickerMsg::DocSort(by) => {
|
||||||
|
self.doc_sort = by;
|
||||||
|
document::sort(&mut self.doc, by, &self.recent);
|
||||||
|
}
|
||||||
|
ColorPickerMsg::Book(i) => {
|
||||||
|
if i < self.books.len() {
|
||||||
|
self.book = i;
|
||||||
|
self.book_page = 0;
|
||||||
|
self.book_query.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ColorPickerMsg::BookPage(page) => {
|
||||||
|
if let Some(book) = self.books.get(self.book) {
|
||||||
|
self.book_page = page.min(book.page_count().saturating_sub(1));
|
||||||
|
self.book_query.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ColorPickerMsg::BookScroll(delta) => {
|
||||||
|
let page = if delta < 0.0 {
|
||||||
|
self.book_page + 1
|
||||||
|
} else {
|
||||||
|
self.book_page.saturating_sub(1)
|
||||||
|
};
|
||||||
|
self.update(ColorPickerMsg::BookPage(page));
|
||||||
|
}
|
||||||
|
ColorPickerMsg::BookQuery(query) => self.book_query = query,
|
||||||
|
ColorPickerMsg::PickBook(i) => self.pick_book(i),
|
||||||
|
ColorPickerMsg::LoadBook => return Some(Outcome::LoadBook),
|
||||||
ColorPickerMsg::Eyedropper(EyedropperMsg::Start) => {
|
ColorPickerMsg::Eyedropper(EyedropperMsg::Start) => {
|
||||||
self.eyedropper = Some(Eyedropper::default());
|
self.eyedropper = Some(Eyedropper::default());
|
||||||
}
|
}
|
||||||
@@ -156,13 +327,26 @@ impl ColorPicker {
|
|||||||
ColorPickerMsg::Ok => {
|
ColorPickerMsg::Ok => {
|
||||||
// Apply a value still being typed, as if Enter had been pressed.
|
// Apply a value still being typed, as if Enter had been pressed.
|
||||||
self.update(ColorPickerMsg::Editor(EditorMsg::HexSubmit));
|
self.update(ColorPickerMsg::Editor(EditorMsg::HexSubmit));
|
||||||
return Some(Outcome::Commit(self.pending));
|
return Some(Outcome::Commit(self.pending, self.pending_name.clone()));
|
||||||
}
|
}
|
||||||
ColorPickerMsg::Cancel => return Some(Outcome::Cancel),
|
ColorPickerMsg::Cancel => return Some(Outcome::Cancel),
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pick_book(&mut self, index: usize) {
|
||||||
|
let books = Arc::clone(&self.books);
|
||||||
|
let Some(book) = books.get(self.book) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(color) = book.colors.get(index) {
|
||||||
|
let (r, g, b) = color.rgb;
|
||||||
|
self.set_pending(AcadColor::Rgb { r, g, b });
|
||||||
|
self.pending_name = Some(book.identity(index));
|
||||||
|
self.book_page = book.page_of(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The full-window eyedropper layer, while it is active.
|
/// The full-window eyedropper layer, while it is active.
|
||||||
pub fn eyedropper_view(&self) -> Option<Element<'_, Message>> {
|
pub fn eyedropper_view(&self) -> Option<Element<'_, Message>> {
|
||||||
self.eyedropper.as_ref().map(Eyedropper::view)
|
self.eyedropper.as_ref().map(Eyedropper::view)
|
||||||
@@ -170,19 +354,33 @@ impl ColorPicker {
|
|||||||
|
|
||||||
pub fn view(&self) -> Element<'_, Message> {
|
pub fn view(&self) -> Element<'_, Message> {
|
||||||
let editor_only = self.editor_only();
|
let editor_only = self.editor_only();
|
||||||
let body = if editor_only || self.tab == ColorPickerTab::TrueColor {
|
let body = match self.tab {
|
||||||
self.true_color_tab()
|
_ if editor_only => self.true_color_tab(),
|
||||||
} else {
|
ColorPickerTab::Document => self.document_tab(),
|
||||||
self.index_tab()
|
ColorPickerTab::Index => self.index_tab(),
|
||||||
|
ColorPickerTab::TrueColor => self.true_color_tab(),
|
||||||
|
ColorPickerTab::Books => self.books_tab(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// The interface picker shrinks to the editor; the CAD panel keeps one
|
// The interface picker shrinks to the editor; the CAD panel keeps one
|
||||||
// height across its tabs, with the info line at the bottom.
|
// height across its tabs, with the info line at the bottom.
|
||||||
let mut body = column![body].spacing(8);
|
let mut body = column![body].spacing(8);
|
||||||
if !editor_only {
|
if !editor_only {
|
||||||
body = body.push(Space::new().height(Length::Fill));
|
// Document and Color books fill the height themselves.
|
||||||
|
if matches!(self.tab, ColorPickerTab::Index | ColorPickerTab::TrueColor) {
|
||||||
|
body = body.push(Space::new().height(Length::Fill));
|
||||||
|
}
|
||||||
let fill =
|
let fill =
|
||||||
math::color_rgb(self.pending).map_or(Color::from_rgb8(128, 128, 128), rgb_color);
|
math::color_rgb(self.pending).map_or(Color::from_rgb8(128, 128, 128), rgb_color);
|
||||||
|
let info = match (&self.status, &self.pending_name) {
|
||||||
|
(Some(status), _) => status.clone(),
|
||||||
|
(None, Some(name)) => {
|
||||||
|
let (r, g, b) = math::color_rgb(self.pending).unwrap_or_default();
|
||||||
|
format!("{name} · RGB {r}, {g}, {b}")
|
||||||
|
}
|
||||||
|
(None, None) => math::describe(self.pending),
|
||||||
|
};
|
||||||
|
let error = self.status.is_some();
|
||||||
body = body
|
body = body
|
||||||
.push(
|
.push(
|
||||||
container(Space::new())
|
container(Space::new())
|
||||||
@@ -196,9 +394,11 @@ impl ColorPicker {
|
|||||||
.width(12)
|
.width(12)
|
||||||
.height(12)
|
.height(12)
|
||||||
.style(move |_theme: &Theme| swatch_style(fill)),
|
.style(move |_theme: &Theme| swatch_style(fill)),
|
||||||
text(math::describe(self.pending))
|
text(info).size(11).font(iced::Font::MONOSPACE).style(
|
||||||
.size(11)
|
move |theme: &Theme| text::Style {
|
||||||
.font(iced::Font::MONOSPACE),
|
color: error.then(|| theme.palette().danger.base.color),
|
||||||
|
}
|
||||||
|
),
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.align_y(iced::Center),
|
.align_y(iced::Center),
|
||||||
@@ -247,8 +447,10 @@ impl ColorPicker {
|
|||||||
};
|
};
|
||||||
container(
|
container(
|
||||||
row![
|
row![
|
||||||
|
tab(t!("Document").into_owned(), ColorPickerTab::Document),
|
||||||
tab(t!("Index Color").into_owned(), ColorPickerTab::Index),
|
tab(t!("Index Color").into_owned(), ColorPickerTab::Index),
|
||||||
tab(t!("True Color").into_owned(), ColorPickerTab::TrueColor),
|
tab(t!("True Color").into_owned(), ColorPickerTab::TrueColor),
|
||||||
|
tab(t!("Color Books").into_owned(), ColorPickerTab::Books),
|
||||||
]
|
]
|
||||||
.spacing(2),
|
.spacing(2),
|
||||||
)
|
)
|
||||||
@@ -261,11 +463,7 @@ impl ColorPicker {
|
|||||||
/// under the middle of the tab bar. Clicking Current reverts.
|
/// under the middle of the tab bar. Clicking Current reverts.
|
||||||
fn preview_row(&self) -> Element<'_, Message> {
|
fn preview_row(&self) -> Element<'_, Message> {
|
||||||
let half = |label: String, value: String, fill: Color| {
|
let half = |label: String, value: String, fill: Color| {
|
||||||
let ink = if ink_is_dark(fill) {
|
let ink = ink(fill);
|
||||||
Color::from_rgb8(0x11, 0x12, 0x14)
|
|
||||||
} else {
|
|
||||||
Color::WHITE
|
|
||||||
};
|
|
||||||
column![
|
column![
|
||||||
text(label).size(11).color(ink.scale_alpha(0.85)),
|
text(label).size(11).color(ink.scale_alpha(0.85)),
|
||||||
Space::new().height(Length::Fill),
|
Space::new().height(Length::Fill),
|
||||||
@@ -277,10 +475,18 @@ impl ColorPicker {
|
|||||||
};
|
};
|
||||||
let (current_bg, _) = acad_color_display(self.current);
|
let (current_bg, _) = acad_color_display(self.current);
|
||||||
let (new_bg, _) = acad_color_display(self.pending);
|
let (new_bg, _) = acad_color_display(self.pending);
|
||||||
|
// A colour-book colour shows its own name, without the book.
|
||||||
|
let name = |color: AcadColor, identity: &Option<String>| match identity {
|
||||||
|
Some(identity) => identity
|
||||||
|
.split_once('$')
|
||||||
|
.map_or(identity.as_str(), |(_, name)| name)
|
||||||
|
.to_string(),
|
||||||
|
None => math::short_name(color),
|
||||||
|
};
|
||||||
let current_value = if self.varies {
|
let current_value = if self.varies {
|
||||||
"*VARIES*".to_string()
|
"*VARIES*".to_string()
|
||||||
} else {
|
} else {
|
||||||
math::short_name(self.current)
|
name(self.current, &self.current_name)
|
||||||
};
|
};
|
||||||
let current = button(half(t!("Current").into_owned(), current_value, current_bg))
|
let current = button(half(t!("Current").into_owned(), current_value, current_bg))
|
||||||
.on_press(picker_msg(ColorPickerMsg::Revert))
|
.on_press(picker_msg(ColorPickerMsg::Revert))
|
||||||
@@ -301,7 +507,7 @@ impl ColorPicker {
|
|||||||
.style(|_theme: &Theme| filled(Color::from_rgba(0.0, 0.0, 0.0, 0.35)));
|
.style(|_theme: &Theme| filled(Color::from_rgba(0.0, 0.0, 0.0, 0.35)));
|
||||||
let new = container(half(
|
let new = container(half(
|
||||||
t!("New").into_owned(),
|
t!("New").into_owned(),
|
||||||
math::short_name(self.pending),
|
name(self.pending, &self.pending_name),
|
||||||
new_bg,
|
new_bg,
|
||||||
))
|
))
|
||||||
.style(move |_theme: &Theme| container::Style {
|
.style(move |_theme: &Theme| container::Style {
|
||||||
@@ -364,29 +570,272 @@ impl ColorPicker {
|
|||||||
button(Space::new().width(width).height(height))
|
button(Space::new().width(width).height(height))
|
||||||
.on_press(picker_msg(ColorPickerMsg::Pick(AcadColor::Index(index))))
|
.on_press(picker_msg(ColorPickerMsg::Pick(AcadColor::Index(index))))
|
||||||
.padding(0)
|
.padding(0)
|
||||||
.style(move |theme: &Theme, status| {
|
.style(move |theme: &Theme, status| swatch_button_style(theme, status, fill, selected))
|
||||||
let hovered = matches!(status, button::Status::Hovered | button::Status::Pressed);
|
.into()
|
||||||
button::Style {
|
}
|
||||||
background: Some(fill.into()),
|
|
||||||
|
fn document_tab(&self) -> Element<'_, Message> {
|
||||||
|
let header = row![
|
||||||
|
text(t!("Colors in this drawing")).size(11),
|
||||||
|
text(format!("· {}", self.doc.len()))
|
||||||
|
.size(11)
|
||||||
|
.style(muted_text),
|
||||||
|
Space::new().width(Length::Fill),
|
||||||
|
text(t!("Sort")).size(11).style(muted_text),
|
||||||
|
pick_list(Some(self.doc_sort), &DocSort::ALL[..], DocSort::to_string)
|
||||||
|
.on_select(|by| picker_msg(ColorPickerMsg::DocSort(by)))
|
||||||
|
.text_size(12)
|
||||||
|
.padding([3, 6]),
|
||||||
|
]
|
||||||
|
.spacing(6)
|
||||||
|
.align_y(iced::Center);
|
||||||
|
if self.doc.is_empty() {
|
||||||
|
return column![
|
||||||
|
header,
|
||||||
|
empty_note(t!("No colors in this drawing yet").into_owned())
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
let grid = self.doc.chunks(DOC_COLUMNS).enumerate().fold(
|
||||||
|
column![].spacing(4),
|
||||||
|
|grid, (r, chunk)| {
|
||||||
|
let line = chunk
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.fold(row![].spacing(4), |line, (c, entry)| {
|
||||||
|
line.push(self.doc_swatch(r * DOC_COLUMNS + c, entry))
|
||||||
|
});
|
||||||
|
let line = (chunk.len()..DOC_COLUMNS)
|
||||||
|
.fold(line, |line, _| line.push(Space::new().width(Length::Fill)));
|
||||||
|
grid.push(line)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
column![
|
||||||
|
header,
|
||||||
|
scrollable(grid).height(Length::Fill).spacing(4).direction(
|
||||||
|
scrollable::Direction::Vertical(
|
||||||
|
scrollable::Scrollbar::new().width(4).scroller_width(4)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn doc_swatch<'a>(&self, i: usize, entry: &'a DocColor) -> Element<'a, Message> {
|
||||||
|
let fill = math::color_rgb(entry.color).map_or(Color::from_rgb8(128, 128, 128), rgb_color);
|
||||||
|
let selected = self.pending == entry.color && self.pending_name == entry.name;
|
||||||
|
button(
|
||||||
|
container(
|
||||||
|
text(entry.tag())
|
||||||
|
.size(10)
|
||||||
|
.font(iced::Font::MONOSPACE)
|
||||||
|
.color(ink(fill)),
|
||||||
|
)
|
||||||
|
.align_y(iced::Bottom)
|
||||||
|
.padding([1, 4])
|
||||||
|
.height(Length::Fill),
|
||||||
|
)
|
||||||
|
.on_press(picker_msg(ColorPickerMsg::PickDocument(i)))
|
||||||
|
.padding(0)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(30)
|
||||||
|
.style(move |theme: &Theme, status| swatch_button_style(theme, status, fill, selected))
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn books_tab(&self) -> Element<'_, Message> {
|
||||||
|
let load_button = |content: Element<'static, Message>| {
|
||||||
|
button(content)
|
||||||
|
.on_press(picker_msg(ColorPickerMsg::LoadBook))
|
||||||
|
.style(button::secondary)
|
||||||
|
};
|
||||||
|
let Some(book) = self.books.get(self.book) else {
|
||||||
|
return container(
|
||||||
|
column![
|
||||||
|
text(t!("No color books loaded")).size(12).style(muted_text),
|
||||||
|
load_button(text(t!("Load color book (.acb)")).size(12).into())
|
||||||
|
.padding([6, 14]),
|
||||||
|
]
|
||||||
|
.spacing(10)
|
||||||
|
.align_x(iced::Center),
|
||||||
|
)
|
||||||
|
.center(Length::Fill)
|
||||||
|
.into();
|
||||||
|
};
|
||||||
|
let label = |label: std::borrow::Cow<'static, str>| text(label).size(11).style(muted_text);
|
||||||
|
let books = Arc::clone(&self.books);
|
||||||
|
let top = row![
|
||||||
|
column![
|
||||||
|
label(t!("Color book")),
|
||||||
|
pick_list(
|
||||||
|
Some(self.book),
|
||||||
|
(0..self.books.len()).collect::<Vec<_>>(),
|
||||||
|
move |&i| books[i].name.clone(),
|
||||||
|
)
|
||||||
|
.on_select(|i| picker_msg(ColorPickerMsg::Book(i)))
|
||||||
|
.text_size(12)
|
||||||
|
.padding([4, 6])
|
||||||
|
.width(Length::Fill),
|
||||||
|
]
|
||||||
|
.spacing(4)
|
||||||
|
.width(Length::Fill),
|
||||||
|
column![
|
||||||
|
label(t!("Find")),
|
||||||
|
text_input("", &self.book_query)
|
||||||
|
.on_input(|query| picker_msg(ColorPickerMsg::BookQuery(query)))
|
||||||
|
.size(12)
|
||||||
|
.padding([5, 8])
|
||||||
|
.font(iced::Font::MONOSPACE)
|
||||||
|
.width(116),
|
||||||
|
]
|
||||||
|
.spacing(4),
|
||||||
|
load_button(
|
||||||
|
svg(crate::ui::icons::themed_handle(
|
||||||
|
crate::ui::icons::FOLDER_OPEN
|
||||||
|
))
|
||||||
|
.width(15)
|
||||||
|
.height(15)
|
||||||
|
.into()
|
||||||
|
)
|
||||||
|
.padding([6, 7]),
|
||||||
|
]
|
||||||
|
.spacing(8)
|
||||||
|
.align_y(iced::Bottom);
|
||||||
|
|
||||||
|
// The page's colours, or the first matches while searching.
|
||||||
|
let selected = self.book_selection();
|
||||||
|
let query = self.book_query.trim().to_lowercase();
|
||||||
|
let mut bars = column![].spacing(2).height(Length::Fill);
|
||||||
|
let mut shown = 0;
|
||||||
|
if query.is_empty() {
|
||||||
|
let (start, colors) = book.page(self.book_page);
|
||||||
|
for (i, color) in colors.iter().enumerate() {
|
||||||
|
bars = bars.push(book_bar(start + i, color, selected));
|
||||||
|
shown += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (i, color) in book
|
||||||
|
.colors
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, color)| color.name.to_lowercase().contains(&query))
|
||||||
|
.take(PAGE_BARS)
|
||||||
|
{
|
||||||
|
bars = bars.push(book_bar(i, color, selected));
|
||||||
|
shown += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let bars: Element<'_, Message> = if shown == 0 {
|
||||||
|
empty_note(t!("No colors match").into_owned())
|
||||||
|
} else {
|
||||||
|
mouse_area(bars)
|
||||||
|
.on_scroll(|delta| {
|
||||||
|
let (iced::mouse::ScrollDelta::Lines { y, .. }
|
||||||
|
| iced::mouse::ScrollDelta::Pixels { y, .. }) = delta;
|
||||||
|
picker_msg(ColorPickerMsg::BookScroll(y))
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Every page as one band of its middle colour; the shown page framed.
|
||||||
|
let strip = (0..book.page_count()).fold(column![], |strip, page| {
|
||||||
|
let (_, colors) = book.page(page);
|
||||||
|
let fill = rgb_color(colors[colors.len() / 2].rgb);
|
||||||
|
let shown = query.is_empty() && page == self.book_page;
|
||||||
|
strip.push(
|
||||||
|
button(Space::new())
|
||||||
|
.on_press(picker_msg(ColorPickerMsg::BookPage(page)))
|
||||||
|
.padding(0)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.style(move |_theme: &Theme, _status| button::Style {
|
||||||
|
background: Some(fill.into()),
|
||||||
|
border: Border {
|
||||||
|
color: Color::WHITE,
|
||||||
|
width: if shown { 2.0 } else { 0.0 },
|
||||||
|
radius: 2.0.into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let arrow = |path: &'static str, page: usize| {
|
||||||
|
button(
|
||||||
|
svg(svg::Handle::from_memory(
|
||||||
|
format!(
|
||||||
|
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 7 4'>\
|
||||||
|
<path d='{path}' fill='black'/></svg>"
|
||||||
|
)
|
||||||
|
.into_bytes(),
|
||||||
|
))
|
||||||
|
.width(9)
|
||||||
|
.height(5)
|
||||||
|
.style(|theme: &Theme, _status| svg::Style {
|
||||||
|
color: Some(theme.palette().background.base.text),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.on_press(picker_msg(ColorPickerMsg::BookPage(page)))
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(18)
|
||||||
|
.padding([6, 8])
|
||||||
|
.style(button::secondary)
|
||||||
|
};
|
||||||
|
let strip = column![
|
||||||
|
arrow("M0 4L3.5 0L7 4z", self.book_page.saturating_sub(1)),
|
||||||
|
container(strip)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.padding(1)
|
||||||
|
.style(|theme: &Theme| container::Style {
|
||||||
border: Border {
|
border: Border {
|
||||||
color: if selected || hovered {
|
color: theme.palette().background.strong.color,
|
||||||
theme.palette().primary.base.color
|
width: 1.0,
|
||||||
} else {
|
radius: 3.0.into(),
|
||||||
Color::from_rgba(0.0, 0.0, 0.0, 0.35)
|
|
||||||
},
|
|
||||||
width: if selected {
|
|
||||||
2.5
|
|
||||||
} else if hovered {
|
|
||||||
1.5
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
},
|
|
||||||
radius: 2.0.into(),
|
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}),
|
||||||
})
|
arrow("M0 0L7 0L3.5 4z", self.book_page + 1),
|
||||||
.into()
|
]
|
||||||
|
.spacing(3)
|
||||||
|
.width(26);
|
||||||
|
|
||||||
|
// RGB equivalent of the selected colour.
|
||||||
|
let chosen = selected.map(|i| &book.colors[i]);
|
||||||
|
let channel = |name: std::borrow::Cow<'static, str>, value: Option<u8>| {
|
||||||
|
row![
|
||||||
|
text(name).size(12).style(muted_text),
|
||||||
|
Space::new().width(Length::Fill),
|
||||||
|
text(value.map_or("—".to_string(), |v| v.to_string()))
|
||||||
|
.size(12)
|
||||||
|
.font(iced::Font::MONOSPACE),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let equivalent = column![
|
||||||
|
text(t!("RGB equivalent")).size(11),
|
||||||
|
channel(t!("Red"), chosen.map(|c| c.rgb.0)),
|
||||||
|
channel(t!("Green"), chosen.map(|c| c.rgb.1)),
|
||||||
|
channel(t!("Blue"), chosen.map(|c| c.rgb.2)),
|
||||||
|
container(Space::new())
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(1)
|
||||||
|
.style(|theme: &Theme| filled(theme.palette().background.weak.color)),
|
||||||
|
text(format!("{} / {}", self.book_page + 1, book.page_count()))
|
||||||
|
.size(11)
|
||||||
|
.style(muted_text),
|
||||||
|
text(chosen.map_or(String::new(), |c| c.name.clone())).size(12),
|
||||||
|
]
|
||||||
|
.spacing(6)
|
||||||
|
.width(104);
|
||||||
|
|
||||||
|
column![
|
||||||
|
top,
|
||||||
|
row![bars, strip, equivalent]
|
||||||
|
.spacing(10)
|
||||||
|
.height(Length::Fill)
|
||||||
|
]
|
||||||
|
.spacing(12)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn true_color_tab(&self) -> Element<'_, Message> {
|
fn true_color_tab(&self) -> Element<'_, Message> {
|
||||||
@@ -494,6 +943,21 @@ fn step_index(index: u8, direction: ArrowKey) -> Option<u8> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Arrow keys over a list laid out `columns` wide. Nothing selected yet
|
||||||
|
/// starts at the first entry.
|
||||||
|
fn step_list(at: Option<usize>, len: usize, columns: usize, direction: ArrowKey) -> Option<usize> {
|
||||||
|
let Some(at) = at else {
|
||||||
|
return (len > 0).then_some(0);
|
||||||
|
};
|
||||||
|
let next = match direction {
|
||||||
|
ArrowKey::Left => at.checked_sub(1),
|
||||||
|
ArrowKey::Right => Some(at + 1),
|
||||||
|
ArrowKey::Up => at.checked_sub(columns),
|
||||||
|
ArrowKey::Down => Some(at + columns),
|
||||||
|
};
|
||||||
|
next.filter(|&next| next < len)
|
||||||
|
}
|
||||||
|
|
||||||
/// Move keyboard focus to the next (or previous) field inside the panel.
|
/// Move keyboard focus to the next (or previous) field inside the panel.
|
||||||
pub fn focus_step<T>(forward: bool) -> iced::Task<T> {
|
pub fn focus_step<T>(forward: bool) -> iced::Task<T> {
|
||||||
use iced::advanced::widget::operation::{focusable, scope};
|
use iced::advanced::widget::operation::{focusable, scope};
|
||||||
@@ -514,6 +978,77 @@ fn ink_is_dark(color: Color) -> bool {
|
|||||||
math::ink_is_dark(r, g, b)
|
math::ink_is_dark(r, g, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Text that reads on top of `fill`.
|
||||||
|
fn ink(fill: Color) -> Color {
|
||||||
|
if ink_is_dark(fill) {
|
||||||
|
Color::from_rgb8(0x11, 0x12, 0x14)
|
||||||
|
} else {
|
||||||
|
Color::WHITE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One colour of a colour book: a full-width bar with its name.
|
||||||
|
fn book_bar(
|
||||||
|
i: usize,
|
||||||
|
color: &super::book::BookColor,
|
||||||
|
selected: Option<usize>,
|
||||||
|
) -> Element<'_, Message> {
|
||||||
|
let fill = rgb_color(color.rgb);
|
||||||
|
button(
|
||||||
|
container(
|
||||||
|
text(&color.name)
|
||||||
|
.size(12)
|
||||||
|
.font(iced::Font::MONOSPACE)
|
||||||
|
.color(ink(fill)),
|
||||||
|
)
|
||||||
|
.center(Length::Fill),
|
||||||
|
)
|
||||||
|
.on_press(picker_msg(ColorPickerMsg::PickBook(i)))
|
||||||
|
.padding(0)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.style(move |theme: &Theme, status| {
|
||||||
|
swatch_button_style(theme, status, fill, selected == Some(i))
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A short gray note in the middle of the free space.
|
||||||
|
fn empty_note<'a>(note: String) -> Element<'a, Message> {
|
||||||
|
container(text(note).size(12).style(muted_text))
|
||||||
|
.center(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A clickable colour: ringed in the accent colour when selected or hovered.
|
||||||
|
fn swatch_button_style(
|
||||||
|
theme: &Theme,
|
||||||
|
status: button::Status,
|
||||||
|
fill: Color,
|
||||||
|
selected: bool,
|
||||||
|
) -> button::Style {
|
||||||
|
let hovered = matches!(status, button::Status::Hovered | button::Status::Pressed);
|
||||||
|
button::Style {
|
||||||
|
background: Some(fill.into()),
|
||||||
|
border: Border {
|
||||||
|
color: if selected || hovered {
|
||||||
|
theme.palette().primary.base.color
|
||||||
|
} else {
|
||||||
|
Color::from_rgba(0.0, 0.0, 0.0, 0.35)
|
||||||
|
},
|
||||||
|
width: if selected {
|
||||||
|
2.5
|
||||||
|
} else if hovered {
|
||||||
|
1.5
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
},
|
||||||
|
radius: 2.0.into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn filled(color: Color) -> container::Style {
|
fn filled(color: Color) -> container::Style {
|
||||||
container::Style {
|
container::Style {
|
||||||
background: Some(color.into()),
|
background: Some(color.into()),
|
||||||
@@ -641,7 +1176,7 @@ mod tests {
|
|||||||
p.update(ColorPickerMsg::Pick(AcadColor::Index(5)));
|
p.update(ColorPickerMsg::Pick(AcadColor::Index(5)));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
p.update(ColorPickerMsg::Ok),
|
p.update(ColorPickerMsg::Ok),
|
||||||
Some(Outcome::Commit(AcadColor::Index(5)))
|
Some(Outcome::Commit(AcadColor::Index(5), None))
|
||||||
);
|
);
|
||||||
assert_eq!(p.update(ColorPickerMsg::Cancel), Some(Outcome::Cancel));
|
assert_eq!(p.update(ColorPickerMsg::Cancel), Some(Outcome::Cancel));
|
||||||
}
|
}
|
||||||
@@ -652,11 +1187,14 @@ mod tests {
|
|||||||
edit(&mut p, EditorMsg::HexInput("#3bf".into()));
|
edit(&mut p, EditorMsg::HexInput("#3bf".into()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
p.update(ColorPickerMsg::Ok),
|
p.update(ColorPickerMsg::Ok),
|
||||||
Some(Outcome::Commit(AcadColor::Rgb {
|
Some(Outcome::Commit(
|
||||||
r: 0x33,
|
AcadColor::Rgb {
|
||||||
g: 0xBB,
|
r: 0x33,
|
||||||
b: 0xFF
|
g: 0xBB,
|
||||||
}))
|
b: 0xFF
|
||||||
|
},
|
||||||
|
None
|
||||||
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -724,6 +1262,89 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ral() -> Arc<Vec<ColorBook>> {
|
||||||
|
let entry = |name: &str, (r, g, b): (u8, u8, u8)| {
|
||||||
|
format!(
|
||||||
|
"<colorEntry><colorName>{name}</colorName><RGB8><red>{r}</red>\
|
||||||
|
<green>{g}</green><blue>{b}</blue></RGB8></colorEntry>"
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let text = format!(
|
||||||
|
"<colorBook><bookName>RAL CLASSIC</bookName><colorPage>{}{}</colorPage>\
|
||||||
|
<colorPage>{}</colorPage></colorBook>",
|
||||||
|
entry("RAL 1000", (190, 189, 127)),
|
||||||
|
entry("RAL 3020", (204, 6, 5)),
|
||||||
|
entry("RAL 5015", (34, 113, 179)),
|
||||||
|
);
|
||||||
|
Arc::new(vec![super::super::book::parse(&text).unwrap()])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn book_colours_keep_their_name_until_edited() {
|
||||||
|
let mut p = picker(AcadColor::Index(30)).with_drawing(Vec::new(), &[], ral(), None);
|
||||||
|
p.update(ColorPickerMsg::PickBook(2));
|
||||||
|
assert_eq!(p.book_page, 1, "turns to the colour's page");
|
||||||
|
assert_eq!(
|
||||||
|
p.update(ColorPickerMsg::Ok),
|
||||||
|
Some(Outcome::Commit(
|
||||||
|
AcadColor::Rgb {
|
||||||
|
r: 34,
|
||||||
|
g: 113,
|
||||||
|
b: 179
|
||||||
|
},
|
||||||
|
Some("RAL CLASSIC$RAL 5015".into())
|
||||||
|
))
|
||||||
|
);
|
||||||
|
p.update(ColorPickerMsg::Arrow(ArrowKey::Up));
|
||||||
|
assert_eq!(p.pending_name.as_deref(), Some("RAL CLASSIC$RAL 3020"));
|
||||||
|
edit(&mut p, EditorMsg::Hue(120.0));
|
||||||
|
assert_eq!(
|
||||||
|
p.pending_name, None,
|
||||||
|
"an edited colour is no longer the book's"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn opens_a_book_colour_on_its_page() {
|
||||||
|
let p = picker(AcadColor::Rgb {
|
||||||
|
r: 34,
|
||||||
|
g: 113,
|
||||||
|
b: 179,
|
||||||
|
})
|
||||||
|
.with_drawing(Vec::new(), &[], ral(), Some("RAL CLASSIC$RAL 5015".into()));
|
||||||
|
assert_eq!(p.tab(), ColorPickerTab::Books);
|
||||||
|
assert_eq!(p.book_selection(), Some(2));
|
||||||
|
assert_eq!(p.book_page, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn document_colours_pick_and_walk() {
|
||||||
|
// A new drawing's layer 0 is white.
|
||||||
|
let doc = document::collect(&codec::CadDocument::new());
|
||||||
|
assert!(doc.iter().any(|c| c.color == AcadColor::Index(7)));
|
||||||
|
let mut p = picker(AcadColor::Index(30)).with_drawing(doc, &[], Arc::default(), None);
|
||||||
|
p.update(ColorPickerMsg::Tab(ColorPickerTab::Document));
|
||||||
|
p.update(ColorPickerMsg::Arrow(ArrowKey::Right));
|
||||||
|
assert_eq!(
|
||||||
|
p.pending(),
|
||||||
|
p.doc[0].color,
|
||||||
|
"the first key press starts at the top"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(step_list(None, 3, 8, ArrowKey::Down), Some(0));
|
||||||
|
assert_eq!(step_list(Some(1), 20, 8, ArrowKey::Down), Some(9));
|
||||||
|
assert_eq!(step_list(Some(1), 5, 8, ArrowKey::Down), None);
|
||||||
|
assert_eq!(step_list(Some(0), 5, 8, ArrowKey::Left), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn loading_a_book_is_left_to_the_app() {
|
||||||
|
let mut p = picker(AcadColor::Index(30));
|
||||||
|
assert_eq!(p.update(ColorPickerMsg::LoadBook), Some(Outcome::LoadBook));
|
||||||
|
p.show_book(ral(), 0);
|
||||||
|
assert_eq!(p.tab(), ColorPickerTab::Books);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Headless UI tests ───────────────────────────────────────────────
|
// ── Headless UI tests ───────────────────────────────────────────────
|
||||||
//
|
//
|
||||||
// These run the real widgets through iced's simulator. Setting
|
// These run the real widgets through iced's simulator. Setting
|
||||||
@@ -809,7 +1430,31 @@ mod tests {
|
|||||||
ColorPickerTab,
|
ColorPickerTab,
|
||||||
EditorMode,
|
EditorMode,
|
||||||
bool,
|
bool,
|
||||||
); 7] = [
|
); 10] = [
|
||||||
|
(
|
||||||
|
"document",
|
||||||
|
ColorPickTarget::Properties,
|
||||||
|
AcadColor::Index(7),
|
||||||
|
ColorPickerTab::Document,
|
||||||
|
EditorMode::Hsb,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"books",
|
||||||
|
ColorPickTarget::Properties,
|
||||||
|
AcadColor::Index(30),
|
||||||
|
ColorPickerTab::Books,
|
||||||
|
EditorMode::Hsb,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"no-books",
|
||||||
|
ColorPickTarget::Properties,
|
||||||
|
AcadColor::Index(30),
|
||||||
|
ColorPickerTab::Books,
|
||||||
|
EditorMode::Hsb,
|
||||||
|
false,
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"index",
|
"index",
|
||||||
ColorPickTarget::Properties,
|
ColorPickTarget::Properties,
|
||||||
@@ -876,8 +1521,21 @@ mod tests {
|
|||||||
crate::ui::style::fusion_theme::fusion_white(),
|
crate::ui::style::fusion_theme::fusion_white(),
|
||||||
] {
|
] {
|
||||||
for (name, target, color, tab, mode, varies) in views.clone() {
|
for (name, target, color, tab, mode, varies) in views.clone() {
|
||||||
let mut p = ColorPicker::new(target, color, varies);
|
let books = if name == "no-books" {
|
||||||
|
Arc::default()
|
||||||
|
} else {
|
||||||
|
ral()
|
||||||
|
};
|
||||||
|
let mut p = ColorPicker::new(target, color, varies).with_drawing(
|
||||||
|
document::collect(&codec::CadDocument::new()),
|
||||||
|
&[],
|
||||||
|
books,
|
||||||
|
None,
|
||||||
|
);
|
||||||
p.update(ColorPickerMsg::Tab(tab));
|
p.update(ColorPickerMsg::Tab(tab));
|
||||||
|
if name == "books" {
|
||||||
|
p.update(ColorPickerMsg::PickBook(1));
|
||||||
|
}
|
||||||
edit(&mut p, EditorMsg::Mode(mode));
|
edit(&mut p, EditorMsg::Mode(mode));
|
||||||
let mut ui = simulator(&p);
|
let mut ui = simulator(&p);
|
||||||
let snapshot = ui.snapshot(&theme).expect("snapshot");
|
let snapshot = ui.snapshot(&theme).expect("snapshot");
|
||||||
|
|||||||
Reference in new issue
Block a user