forked from 1b-agent/opencadstudio-upstream
ATTDEF: dialog, -ATTDEF prompts, tag display, Properties and ribbon
Attribute Definition dialog and Edit Attribute Definition dialog, -ATTDEF with the reference prompts and modes (AFLAGS), tag shown in the drawing, justification-aware grips, reworked Properties with validation, Home Block slide-out and Insert Block Definition panel, and the codec patch for ATTDEF DXF output and the R2013+ solid wire cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3dcebf0dc6
commit
c5790c74a9
48 files changed
+2501
-435
No files matched your search
Generated
+1
-1
@@ -4205,7 +4205,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "opencadcodec"
|
name = "opencadcodec"
|
||||||
version = "0.5.5"
|
version = "0.5.5"
|
||||||
source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=5ef9376#5ef9376e02996097ff649cd9268c343327413787"
|
source = "git+https://github.com/ramox81/cadcodec.git?rev=282b130#282b1303e4d096febd37a898850a988595fd8d0e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash 0.8.12",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|||||||
@@ -94,6 +94,10 @@ objc2-foundation = { version = "0.2", features = ["NSArray", "NSURL", "NSString"
|
|||||||
iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
||||||
iced_widget = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
iced_widget = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
||||||
|
|
||||||
|
# ATTDEF/ATTRIB DXF output and the R2013+ solid wire cache (cadcodec PR pending).
|
||||||
|
[patch."https://github.com/HakanSeven12/opencadcodec.git"]
|
||||||
|
opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "282b130" }
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
getrandom = "0.3"
|
getrandom = "0.3"
|
||||||
ocs_plugin_api = { path = "crates/ocs_plugin_api", features = ["host"] }
|
ocs_plugin_api = { path = "crates/ocs_plugin_api", features = ["host"] }
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ PC, *PASTECLIP
|
|||||||
|
|
||||||
; ── Blocks / references ───────────────────────────────────────────────────
|
; ── Blocks / references ───────────────────────────────────────────────────
|
||||||
ATT, *ATTDEF
|
ATT, *ATTDEF
|
||||||
|
-ATT, *-ATTDEF
|
||||||
BE, *BEDIT
|
BE, *BEDIT
|
||||||
IAT, *IMAGEATTACH
|
IAT, *IMAGEATTACH
|
||||||
XA, *XATTACH
|
XA, *XATTACH
|
||||||
|
|||||||
@@ -4032,6 +4032,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = إنشاء
|
.create = إنشاء
|
||||||
.boolean = عمليات منطقية
|
.boolean = عمليات منطقية
|
||||||
|
|||||||
@@ -4019,6 +4019,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = Създаване
|
.create = Създаване
|
||||||
.boolean = Булеви операции
|
.boolean = Булеви операции
|
||||||
|
|||||||
@@ -4017,6 +4017,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = Vytvořit
|
.create = Vytvořit
|
||||||
.boolean = Booleovské operace
|
.boolean = Booleovské operace
|
||||||
|
|||||||
@@ -4018,6 +4018,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = Erstellen
|
.create = Erstellen
|
||||||
.boolean = Boolesche Operationen
|
.boolean = Boolesche Operationen
|
||||||
|
|||||||
@@ -4020,6 +4020,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = Δημιουργία
|
.create = Δημιουργία
|
||||||
.boolean = Δυαδική πράξη
|
.boolean = Δυαδική πράξη
|
||||||
|
|||||||
@@ -4021,6 +4021,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = Create
|
.create = Create
|
||||||
.boolean = Boolean
|
.boolean = Boolean
|
||||||
|
|||||||
@@ -4020,6 +4020,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = Crear
|
.create = Crear
|
||||||
.boolean = Booleanas
|
.boolean = Booleanas
|
||||||
|
|||||||
@@ -4019,6 +4019,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = Luo
|
.create = Luo
|
||||||
.boolean = Boolen operaatiot
|
.boolean = Boolen operaatiot
|
||||||
|
|||||||
@@ -4020,6 +4020,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = Créer
|
.create = Créer
|
||||||
.boolean = Opérations booléennes
|
.boolean = Opérations booléennes
|
||||||
|
|||||||
@@ -4008,6 +4008,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = बनाएँ
|
.create = बनाएँ
|
||||||
.boolean = बूलियन संक्रियाएँ
|
.boolean = बूलियन संक्रियाएँ
|
||||||
|
|||||||
@@ -4019,6 +4019,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = Létrehozás
|
.create = Létrehozás
|
||||||
.boolean = Logikai műveletek
|
.boolean = Logikai műveletek
|
||||||
|
|||||||
@@ -4019,6 +4019,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = Crea
|
.create = Crea
|
||||||
.boolean = Operazioni booleane
|
.boolean = Operazioni booleane
|
||||||
|
|||||||
@@ -4008,6 +4008,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = 作成
|
.create = 作成
|
||||||
.boolean = ブール演算
|
.boolean = ブール演算
|
||||||
|
|||||||
@@ -4019,6 +4019,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = 생성
|
.create = 생성
|
||||||
.boolean = 불리언 연산
|
.boolean = 불리언 연산
|
||||||
|
|||||||
@@ -4018,6 +4018,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = Maken
|
.create = Maken
|
||||||
.boolean = Booleaanse bewerkingen
|
.boolean = Booleaanse bewerkingen
|
||||||
|
|||||||
@@ -4019,6 +4019,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = Utwórz
|
.create = Utwórz
|
||||||
.boolean = Operacje boolowskie
|
.boolean = Operacje boolowskie
|
||||||
|
|||||||
@@ -4020,6 +4020,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = Criar
|
.create = Criar
|
||||||
.boolean = Operações booleanas
|
.boolean = Operações booleanas
|
||||||
|
|||||||
@@ -4021,6 +4021,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = Создание
|
.create = Создание
|
||||||
.boolean = Булевы операции
|
.boolean = Булевы операции
|
||||||
|
|||||||
@@ -3980,6 +3980,18 @@ insert =
|
|||||||
.pcs-positive = Pozitif bir sayı gerekir.
|
.pcs-positive = Pozitif bir sayı gerekir.
|
||||||
.pcs-positive-int = Pozitif bir tamsayı gerekir.
|
.pcs-positive-int = Pozitif bir tamsayı gerekir.
|
||||||
.pcs-help = Canlı kesitin korunan tarafındaki noktaları çizgilerle izler.
|
.pcs-help = Canlı kesitin korunan tarafındaki noktaları çizgilerle izler.
|
||||||
|
.attdef-on-screen = Ekranda belirt
|
||||||
|
.attdef-insertion-point = Ekleme Noktası
|
||||||
|
.attdef-insert-field = Alan ekle
|
||||||
|
.attdef-text-settings = Metin Ayarları
|
||||||
|
.attdef-align-below = Önceki öznitelik tanımının altına hizala
|
||||||
|
.attdef-define = Tanımla
|
||||||
|
.attdef-edit-title = Öznitelik Tanımını Düzenle
|
||||||
|
.attdef-edit-help = Bir öznitelik tanımının etiketini, istemini ve varsayılan değerini değiştirir.
|
||||||
|
.attdef-help = Bir öznitelik tanımlar: blok tanımına metin olarak yerleştirilen etiket, istem ve varsayılan değer.
|
||||||
|
.attdef-define-attributes = Öznitelik Tanımla
|
||||||
|
.attdef-manage-attributes = Öznitelikleri Yönet
|
||||||
|
.attdef-set-base-point = Taban Noktası Belirle
|
||||||
model =
|
model =
|
||||||
.create = Oluştur
|
.create = Oluştur
|
||||||
.boolean = Boole işlemleri
|
.boolean = Boole işlemleri
|
||||||
|
|||||||
@@ -4003,6 +4003,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = 创建
|
.create = 创建
|
||||||
.boolean = 布尔运算
|
.boolean = 布尔运算
|
||||||
|
|||||||
@@ -4015,6 +4015,18 @@ insert =
|
|||||||
.pcs-positive = Requires a positive number.
|
.pcs-positive = Requires a positive number.
|
||||||
.pcs-positive-int = Requires a positive integer.
|
.pcs-positive-int = Requires a positive integer.
|
||||||
.pcs-help = Traces the points on the kept side of a live section as lines.
|
.pcs-help = Traces the points on the kept side of a live section as lines.
|
||||||
|
.attdef-on-screen = Specify on-screen
|
||||||
|
.attdef-insertion-point = Insertion Point
|
||||||
|
.attdef-insert-field = Insert field
|
||||||
|
.attdef-text-settings = Text Settings
|
||||||
|
.attdef-align-below = Align below previous attribute definition
|
||||||
|
.attdef-define = Define
|
||||||
|
.attdef-edit-title = Edit Attribute Definition
|
||||||
|
.attdef-edit-help = Changes the tag, prompt and default value of an attribute definition.
|
||||||
|
.attdef-help = Defines an attribute: a tag, prompt and default value placed as text in a block definition.
|
||||||
|
.attdef-define-attributes = Define Attributes
|
||||||
|
.attdef-manage-attributes = Manage Attributes
|
||||||
|
.attdef-set-base-point = Set Base Point
|
||||||
model =
|
model =
|
||||||
.create = 建立
|
.create = 建立
|
||||||
.boolean = 布林運算
|
.boolean = 布林運算
|
||||||
|
|||||||
+2
-1
@@ -80,7 +80,7 @@ const WEB_ALIAS_KEY: &str = "opencadstudio.aliases";
|
|||||||
/// migrated forward (see `introduced_at` / `migrate_aliases`). Version 1 shipped
|
/// migrated forward (see `introduced_at` / `migrate_aliases`). Version 1 shipped
|
||||||
/// the pre-REDRAW table; version 2 introduced the REDRAW-family aliases; version 3
|
/// the pre-REDRAW table; version 2 introduced the REDRAW-family aliases; version 3
|
||||||
/// introduced HB (HATCHTOBACK); version 4 introduced ER (EXTERNALREFERENCES).
|
/// introduced HB (HATCHTOBACK); version 4 introduced ER (EXTERNALREFERENCES).
|
||||||
const DEFAULT_ALIASES_VERSION: u32 = 4;
|
const DEFAULT_ALIASES_VERSION: u32 = 5;
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
const WEB_ALIAS_VERSION_KEY: &str = "opencadstudio.aliases.version";
|
const WEB_ALIAS_VERSION_KEY: &str = "opencadstudio.aliases.version";
|
||||||
@@ -99,6 +99,7 @@ fn introduced_at(version: u32) -> &'static [(&'static str, &'static str)] {
|
|||||||
],
|
],
|
||||||
3 => &[("HB", "HATCHTOBACK")],
|
3 => &[("HB", "HATCHTOBACK")],
|
||||||
4 => &[("ER", "EXTERNALREFERENCES")],
|
4 => &[("ER", "EXTERNALREFERENCES")],
|
||||||
|
5 => &[("-ATT", "-ATTDEF")],
|
||||||
_ => &[],
|
_ => &[],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,320 @@
|
|||||||
|
//! ATTDEF (dialog), -ATTDEF (command line), AFLAGS, and the Edit Attribute
|
||||||
|
//! Definition dialog opened by double-click / TEXTEDIT / DDEDIT.
|
||||||
|
|
||||||
|
use crate::app::{Message, OpenCADStudio};
|
||||||
|
use crate::command::CadCommand;
|
||||||
|
use crate::modules::draw::draw::attdef::{
|
||||||
|
self as attdef, AttdefCommand, AttdefPickCommand, AttdefPlaceCommand, AttdefSpec, Justify,
|
||||||
|
};
|
||||||
|
use crate::ui::window::attdef_dialog::{
|
||||||
|
tag_error, AttdefDialogMsg, AttdefDialogState, AttdefEditState,
|
||||||
|
};
|
||||||
|
use iced::Task;
|
||||||
|
|
||||||
|
fn short(v: f64) -> String {
|
||||||
|
let t = format!("{v:.4}");
|
||||||
|
t.trim_end_matches('0').trim_end_matches('.').to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenCADStudio {
|
||||||
|
pub(super) fn dispatch_attdef(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
||||||
|
if matches!(cmd, "ATTDEF" | "-ATTDEF") {
|
||||||
|
// Placement measures the tag with its style's font.
|
||||||
|
crate::entities::common::set_fixed_text_heights(&self.tabs[i].scene.document);
|
||||||
|
}
|
||||||
|
match cmd {
|
||||||
|
"ATTDEF" => {
|
||||||
|
self.open_attdef_dialog(i);
|
||||||
|
Some(Task::none())
|
||||||
|
}
|
||||||
|
"-ATTDEF" => {
|
||||||
|
let defaults =
|
||||||
|
crate::scene::creation_style::current_text_defaults(&self.tabs[i].scene.document);
|
||||||
|
let styles: Vec<_> = self.tabs[i].scene.document.text_styles.iter().cloned().collect();
|
||||||
|
let command = AttdefCommand::new(
|
||||||
|
"-ATTDEF",
|
||||||
|
defaults.style_name,
|
||||||
|
defaults.height,
|
||||||
|
defaults.width_factor,
|
||||||
|
defaults.oblique_angle,
|
||||||
|
styles,
|
||||||
|
);
|
||||||
|
self.reset_command_start_state(i);
|
||||||
|
self.command_line.push_output(&command.intro());
|
||||||
|
self.command_line.push_info(&command.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||||
|
self.push_ucs_to_cmd(i);
|
||||||
|
Some(Task::none())
|
||||||
|
}
|
||||||
|
cmd if cmd == "_ATTDEF_PICKED" || cmd.starts_with("_ATTDEF_PICKED ") => {
|
||||||
|
let mut parts = cmd.split_whitespace().skip(1);
|
||||||
|
if let (Some(kind), Some(value), Some(state)) =
|
||||||
|
(parts.next(), parts.next(), self.attdef_dialog.as_mut())
|
||||||
|
{
|
||||||
|
match kind {
|
||||||
|
"H" => state.height = value.to_string(),
|
||||||
|
"R" => state.rotation = value.to_string(),
|
||||||
|
_ => state.width = value.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.attdef_dialog.is_some() {
|
||||||
|
self.active_modal = Some(crate::app::ModalKind::AttDef);
|
||||||
|
}
|
||||||
|
Some(Task::none())
|
||||||
|
}
|
||||||
|
_ => self.dispatch_aflags(cmd),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// AFLAGS: the session's attribute modes (0–63, default 16).
|
||||||
|
fn dispatch_aflags(&mut self, cmd: &str) -> Option<Task<Message>> {
|
||||||
|
let rest = cmd.strip_prefix("SETVAR ").map(str::trim).unwrap_or(cmd);
|
||||||
|
let mut parts = rest.splitn(2, char::is_whitespace);
|
||||||
|
if !parts.next().unwrap_or("").eq_ignore_ascii_case("AFLAGS") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let current = attdef::session().aflags;
|
||||||
|
match parts.next().map(str::trim).filter(|v| !v.is_empty()) {
|
||||||
|
None => {
|
||||||
|
self.command_line
|
||||||
|
.push_output(&format!("Enter new value for AFLAGS <{current}>:"));
|
||||||
|
self.pending_setvar = Some("AFLAGS".to_string());
|
||||||
|
}
|
||||||
|
Some(v) => match v.parse::<u8>().ok().filter(|v| *v <= 63) {
|
||||||
|
Some(v) => attdef::session().aflags = v,
|
||||||
|
None => {
|
||||||
|
self.command_line.push_error("Requires an integer between 0 and 63.");
|
||||||
|
self.command_line
|
||||||
|
.push_output(&format!("Enter new value for AFLAGS <{current}>:"));
|
||||||
|
self.pending_setvar = Some("AFLAGS".to_string());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Some(Task::none())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_attdef_dialog(&mut self, i: usize) {
|
||||||
|
let document = &self.tabs[i].scene.document;
|
||||||
|
let defaults = crate::scene::creation_style::current_text_defaults(document);
|
||||||
|
let mut styles: Vec<String> = document
|
||||||
|
.text_styles
|
||||||
|
.iter()
|
||||||
|
.filter(|s| !s.name.is_empty())
|
||||||
|
.map(|s| s.name.clone())
|
||||||
|
.collect();
|
||||||
|
styles.sort_by_key(|s| s.to_lowercase());
|
||||||
|
let session = attdef::session();
|
||||||
|
self.attdef_dialog = Some(AttdefDialogState {
|
||||||
|
aflags: session.aflags,
|
||||||
|
annotative: session.annotative,
|
||||||
|
tag: String::new(),
|
||||||
|
prompt: String::new(),
|
||||||
|
default: String::new(),
|
||||||
|
justify: Justify::Left,
|
||||||
|
style: defaults.style_name,
|
||||||
|
styles,
|
||||||
|
height: short(defaults.height),
|
||||||
|
rotation: "0".into(),
|
||||||
|
width: "0".into(),
|
||||||
|
on_screen: true,
|
||||||
|
coords: ["0".into(), "0".into(), "0".into()],
|
||||||
|
align_below: false,
|
||||||
|
can_align_below: session.last.is_some(),
|
||||||
|
});
|
||||||
|
drop(session);
|
||||||
|
self.tabs[i].active_cmd = None;
|
||||||
|
self.active_modal = Some(crate::app::ModalKind::AttDef);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Edit Attribute Definition dialog for `handle`.
|
||||||
|
pub(in crate::app) fn open_attdef_edit(&mut self, handle: codec::Handle) {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let Some(codec::EntityType::AttributeDefinition(a)) = self.tabs[i].scene.document.get_entity(handle)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.attdef_edit = Some(AttdefEditState {
|
||||||
|
handle,
|
||||||
|
tag: a.tag.clone(),
|
||||||
|
prompt: a.prompt.clone(),
|
||||||
|
default: a.default_value.clone(),
|
||||||
|
constant: a.flags.constant,
|
||||||
|
});
|
||||||
|
self.active_modal = Some(crate::app::ModalKind::AttDefEdit);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::app) fn on_attdef_dialog(&mut self, m: AttdefDialogMsg) -> Task<Message> {
|
||||||
|
let i = self.active_tab;
|
||||||
|
if let Some(edit) = self.attdef_edit.as_mut() {
|
||||||
|
match m {
|
||||||
|
AttdefDialogMsg::EditTag(v) => edit.tag = v,
|
||||||
|
AttdefDialogMsg::EditPrompt(v) => edit.prompt = v,
|
||||||
|
AttdefDialogMsg::EditDefault(v) => edit.default = v,
|
||||||
|
AttdefDialogMsg::Help => self.command_line.push_info(
|
||||||
|
crate::t!("Changes the tag, prompt and default value of an attribute definition.")
|
||||||
|
.as_ref(),
|
||||||
|
),
|
||||||
|
AttdefDialogMsg::EditOk => return self.attdef_edit_ok(i),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
let Some(state) = self.attdef_dialog.as_mut() else {
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
match m {
|
||||||
|
AttdefDialogMsg::Mode(bit, on) => {
|
||||||
|
if on {
|
||||||
|
state.aflags |= bit;
|
||||||
|
} else {
|
||||||
|
state.aflags &= !bit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AttdefDialogMsg::Annotative(v) => state.annotative = v,
|
||||||
|
AttdefDialogMsg::Tag(v) => state.tag = v,
|
||||||
|
AttdefDialogMsg::Prompt(v) => state.prompt = v,
|
||||||
|
AttdefDialogMsg::Default(v) => state.default = v,
|
||||||
|
AttdefDialogMsg::Justify(c) => state.justify = c.0,
|
||||||
|
AttdefDialogMsg::Style(v) => state.style = v,
|
||||||
|
AttdefDialogMsg::Height(v) => state.height = v,
|
||||||
|
AttdefDialogMsg::Rotation(v) => state.rotation = v,
|
||||||
|
AttdefDialogMsg::Width(v) => state.width = v,
|
||||||
|
AttdefDialogMsg::OnScreen(v) => state.on_screen = v,
|
||||||
|
AttdefDialogMsg::Coord(k, v) => state.coords[k] = v,
|
||||||
|
AttdefDialogMsg::AlignBelow(v) => state.align_below = v,
|
||||||
|
AttdefDialogMsg::PickHeight | AttdefDialogMsg::PickRotation | AttdefDialogMsg::PickWidth => {
|
||||||
|
let (kind, current) = match m {
|
||||||
|
AttdefDialogMsg::PickHeight => ("H", state.height.clone()),
|
||||||
|
AttdefDialogMsg::PickRotation => ("R", state.rotation.clone()),
|
||||||
|
_ => ("W", state.width.clone()),
|
||||||
|
};
|
||||||
|
// Keep the dialog's fields; it reopens when the value is in.
|
||||||
|
self.active_modal = None;
|
||||||
|
let command = AttdefPickCommand::new(kind, current);
|
||||||
|
self.command_line.push_info(&command.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||||
|
self.push_ucs_to_cmd(i);
|
||||||
|
return self.focus_cmd_input();
|
||||||
|
}
|
||||||
|
AttdefDialogMsg::Help => self.command_line.push_info(
|
||||||
|
crate::t!("Defines an attribute: a tag, prompt and default value placed as text in a block definition.")
|
||||||
|
.as_ref(),
|
||||||
|
),
|
||||||
|
AttdefDialogMsg::Ok => return self.attdef_dialog_ok(i),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attdef_dialog_ok(&mut self, i: usize) -> Task<Message> {
|
||||||
|
let Some(state) = self.attdef_dialog.clone() else {
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
if let Some(error) = tag_error(&state.tag) {
|
||||||
|
self.command_line.push_error(error);
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
let number = |s: &str| s.trim().parse::<f64>().ok().filter(|v| v.is_finite());
|
||||||
|
let height = match number(&state.height) {
|
||||||
|
Some(v) if v > 0.0 => v,
|
||||||
|
_ => {
|
||||||
|
self.command_line.push_error("Value must be positive and nonzero.");
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let Some(rotation) = number(&state.rotation) else {
|
||||||
|
self.command_line.push_error("Requires valid numeric angle or second point.");
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
let width = number(&state.width).unwrap_or(0.0).max(0.0);
|
||||||
|
let defaults =
|
||||||
|
crate::scene::creation_style::current_text_defaults(&self.tabs[i].scene.document);
|
||||||
|
let spec = AttdefSpec {
|
||||||
|
aflags: state.aflags,
|
||||||
|
annotative: state.annotative,
|
||||||
|
tag: state.tag.clone(),
|
||||||
|
prompt: state.prompt.clone(),
|
||||||
|
value: state.default.clone(),
|
||||||
|
justify: state.justify,
|
||||||
|
style: state.style.clone(),
|
||||||
|
height,
|
||||||
|
rotation: rotation.to_radians(),
|
||||||
|
width_factor: defaults.width_factor,
|
||||||
|
oblique_angle: defaults.oblique_angle,
|
||||||
|
boundary_width: width,
|
||||||
|
line_spacing: 1.0,
|
||||||
|
};
|
||||||
|
self.attdef_dialog = None;
|
||||||
|
self.close_active_modal();
|
||||||
|
{
|
||||||
|
let mut session = attdef::session();
|
||||||
|
session.aflags = spec.aflags;
|
||||||
|
session.annotative = spec.annotative;
|
||||||
|
}
|
||||||
|
self.reset_command_start_state(i);
|
||||||
|
if state.align_below {
|
||||||
|
let previous = attdef::session().last.clone();
|
||||||
|
if let Some(previous) = previous {
|
||||||
|
let entity = attdef::build_below(&spec, &previous);
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(AttdefPlaceCommand::new(spec.clone())));
|
||||||
|
attdef::remember(&spec, &entity);
|
||||||
|
return self.apply_cmd_result(crate::command::CmdResult::CommitAndExit(entity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let plane = if self.tabs[i].editing_model_space() {
|
||||||
|
self.tabs[i].ucs_xform().working_plane()
|
||||||
|
} else {
|
||||||
|
crate::command::WorkingPlane::default()
|
||||||
|
};
|
||||||
|
let mut command = AttdefPlaceCommand::new(spec);
|
||||||
|
command.set_working_plane(plane);
|
||||||
|
if state.on_screen {
|
||||||
|
self.command_line.push_info(&command.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||||
|
return self.focus_cmd_input();
|
||||||
|
}
|
||||||
|
let local = glam::DVec3::new(
|
||||||
|
number(&state.coords[0]).unwrap_or(0.0),
|
||||||
|
number(&state.coords[1]).unwrap_or(0.0),
|
||||||
|
number(&state.coords[2]).unwrap_or(0.0),
|
||||||
|
);
|
||||||
|
let result = command.on_point(plane.to_world(local));
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||||
|
self.apply_cmd_result(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attdef_edit_ok(&mut self, i: usize) -> Task<Message> {
|
||||||
|
let Some(state) = self.attdef_edit.clone() else {
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
if let Some(error) = tag_error(&state.tag) {
|
||||||
|
self.command_line.push_error(error);
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
self.attdef_edit = None;
|
||||||
|
self.close_active_modal();
|
||||||
|
let changed = matches!(
|
||||||
|
self.tabs[i].scene.document.get_entity(state.handle),
|
||||||
|
Some(codec::EntityType::AttributeDefinition(a))
|
||||||
|
if a.tag != attdef::normalize_tag(&state.tag) || a.prompt != state.prompt || a.default_value != state.default
|
||||||
|
);
|
||||||
|
if changed && !self.reject_locked_edit(i, state.handle) {
|
||||||
|
self.push_undo_snapshot(i, "DDEDIT");
|
||||||
|
if let Some(codec::EntityType::AttributeDefinition(a)) =
|
||||||
|
self.tabs[i].scene.document.get_entity_mut(state.handle)
|
||||||
|
{
|
||||||
|
a.tag = attdef::normalize_tag(&state.tag);
|
||||||
|
if !a.flags.constant {
|
||||||
|
a.prompt = state.prompt.clone();
|
||||||
|
}
|
||||||
|
a.default_value = state.default.clone();
|
||||||
|
}
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.bump_entities(&[(state.handle, crate::scene::ChangeKind::Modified)]);
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.refresh_properties();
|
||||||
|
}
|
||||||
|
self.post_editor_closed(changed)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,21 +79,6 @@ impl OpenCADStudio {
|
|||||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||||
}
|
}
|
||||||
|
|
||||||
"ATTDEF" => {
|
|
||||||
use crate::modules::draw::draw::attdef::AttdefCommand;
|
|
||||||
let defaults = crate::scene::creation_style::current_text_defaults(
|
|
||||||
&self.tabs[i].scene.document,
|
|
||||||
);
|
|
||||||
let cmd = AttdefCommand::with_text_defaults(
|
|
||||||
defaults.height,
|
|
||||||
defaults.style_name,
|
|
||||||
defaults.width_factor,
|
|
||||||
defaults.oblique_angle,
|
|
||||||
);
|
|
||||||
self.command_line.push_info(&cmd.prompt());
|
|
||||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command-line attribute editing on selected Insert entities. Bare
|
// Command-line attribute editing on selected Insert entities. Bare
|
||||||
// ATTEDIT and the ATE alias launch the interactive editor instead
|
// ATTEDIT and the ATE alias launch the interactive editor instead
|
||||||
// (see the ATTEDIT arm in the inquiry family); the dash form is the
|
// (see the ATTEDIT arm in the inquiry family); the dash form is the
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use crate::scene::Scene;
|
|||||||
use iced::Task;
|
use iced::Task;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
mod attdef;
|
||||||
mod blocks;
|
mod blocks;
|
||||||
mod xref_attach;
|
mod xref_attach;
|
||||||
mod dim;
|
mod dim;
|
||||||
@@ -278,6 +279,9 @@ impl OpenCADStudio {
|
|||||||
if let Some(t) = self.dispatch_pc_extract(cmd, i) {
|
if let Some(t) = self.dispatch_pc_extract(cmd, i) {
|
||||||
return Some(t);
|
return Some(t);
|
||||||
}
|
}
|
||||||
|
if let Some(t) = self.dispatch_attdef(cmd, i) {
|
||||||
|
return Some(t);
|
||||||
|
}
|
||||||
if let Some(t) = self.dispatch_draw(cmd, i) {
|
if let Some(t) = self.dispatch_draw(cmd, i) {
|
||||||
return Some(t);
|
return Some(t);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ pub(super) const NAMES: &[&str] = &[
|
|||||||
"ribbon_tab",
|
"ribbon_tab",
|
||||||
"ribbon_dropdown",
|
"ribbon_dropdown",
|
||||||
"dialog_ok",
|
"dialog_ok",
|
||||||
|
"attdef_dialog",
|
||||||
"close_document",
|
"close_document",
|
||||||
"toggle_properties",
|
"toggle_properties",
|
||||||
"toggle_layers",
|
"toggle_layers",
|
||||||
@@ -788,6 +789,47 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
return Ok(Task::none());
|
return Ok(Task::none());
|
||||||
}
|
}
|
||||||
|
// One field of the open attribute definition dialog: `tag=…`,
|
||||||
|
// `prompt=…`, `default=…`, `justify=MC`, `style=…`, `height=…`,
|
||||||
|
// `rotation=…`, `width=…`, `on_screen=0|1`, `x=…` / `y=…` / `z=…`,
|
||||||
|
// `mode=<bit>:0|1`, `annotative=0|1`, `align_below=0|1`.
|
||||||
|
"attdef_dialog" => {
|
||||||
|
use crate::ui::window::attdef_dialog::{AttdefDialogMsg as M, JustifyChoice};
|
||||||
|
let value = string(req, "value")?;
|
||||||
|
let (key, v) = value.split_once('=').unwrap_or((value, ""));
|
||||||
|
let on = v == "1";
|
||||||
|
let editing = self.active_modal == Some(crate::app::ModalKind::AttDefEdit);
|
||||||
|
Message::AttdefDialog(match key {
|
||||||
|
"tag" if editing => M::EditTag(v.into()),
|
||||||
|
"prompt" if editing => M::EditPrompt(v.into()),
|
||||||
|
"default" if editing => M::EditDefault(v.into()),
|
||||||
|
"tag" => M::Tag(v.into()),
|
||||||
|
"prompt" => M::Prompt(v.into()),
|
||||||
|
"default" => M::Default(v.into()),
|
||||||
|
"style" => M::Style(v.into()),
|
||||||
|
"height" => M::Height(v.into()),
|
||||||
|
"rotation" => M::Rotation(v.into()),
|
||||||
|
"width" => M::Width(v.into()),
|
||||||
|
"on_screen" => M::OnScreen(on),
|
||||||
|
"annotative" => M::Annotative(on),
|
||||||
|
"align_below" => M::AlignBelow(on),
|
||||||
|
"x" => M::Coord(0, v.into()),
|
||||||
|
"y" => M::Coord(1, v.into()),
|
||||||
|
"z" => M::Coord(2, v.into()),
|
||||||
|
"justify" => M::Justify(JustifyChoice(
|
||||||
|
crate::modules::draw::draw::attdef::Justify::from_keyword(v)
|
||||||
|
.ok_or_else(|| failure("invalid_value", "Unknown justification"))?,
|
||||||
|
)),
|
||||||
|
"mode" => {
|
||||||
|
let (bit, state) = v.split_once(':').unwrap_or((v, "1"));
|
||||||
|
let bit = bit
|
||||||
|
.parse::<u8>()
|
||||||
|
.map_err(|_| failure("invalid_value", "mode=<bit>:0|1"))?;
|
||||||
|
M::Mode(bit, state == "1")
|
||||||
|
}
|
||||||
|
_ => return Err(failure("invalid_value", "Unknown attdef dialog field")),
|
||||||
|
})
|
||||||
|
}
|
||||||
"ribbon_dropdown" => Message::ToggleRibbonDropdown(string(req, "value")?.into()),
|
"ribbon_dropdown" => Message::ToggleRibbonDropdown(string(req, "value")?.into()),
|
||||||
"pdf_dialog_ok" => {
|
"pdf_dialog_ok" => {
|
||||||
use crate::ui::window::pdf_dialogs::PdfDialogMsg;
|
use crate::ui::window::pdf_dialogs::PdfDialogMsg;
|
||||||
@@ -808,6 +850,12 @@ impl OpenCADStudio {
|
|||||||
crate::ui::window::xref_attach::XrefAttachMsg::Apply,
|
crate::ui::window::xref_attach::XrefAttachMsg::Apply,
|
||||||
),
|
),
|
||||||
Some(crate::app::ModalKind::BlockDefinition) => Message::BlockDefApply,
|
Some(crate::app::ModalKind::BlockDefinition) => Message::BlockDefApply,
|
||||||
|
Some(crate::app::ModalKind::AttDef) => Message::AttdefDialog(
|
||||||
|
crate::ui::window::attdef_dialog::AttdefDialogMsg::Ok,
|
||||||
|
),
|
||||||
|
Some(crate::app::ModalKind::AttDefEdit) => Message::AttdefDialog(
|
||||||
|
crate::ui::window::attdef_dialog::AttdefDialogMsg::EditOk,
|
||||||
|
),
|
||||||
_ => return Err(failure("no_dialog", "No dialog with an OK button is open")),
|
_ => return Err(failure("no_dialog", "No dialog with an OK button is open")),
|
||||||
},
|
},
|
||||||
"close_document" => Message::TabClose(self.tabs[self.active_tab].id),
|
"close_document" => Message::TabClose(self.tabs[self.active_tab].id),
|
||||||
|
|||||||
@@ -498,6 +498,9 @@ pub(super) struct OpenCADStudio {
|
|||||||
/// PDF dialogs' working copies; `None` while closed.
|
/// PDF dialogs' working copies; `None` while closed.
|
||||||
pdf_attach: Option<crate::ui::window::pdf_dialogs::PdfAttachState>,
|
pdf_attach: Option<crate::ui::window::pdf_dialogs::PdfAttachState>,
|
||||||
point_cloud_attach: Option<crate::ui::window::pdf_dialogs::PointCloudAttachState>,
|
point_cloud_attach: Option<crate::ui::window::pdf_dialogs::PointCloudAttachState>,
|
||||||
|
/// ATTDEF dialog and Edit Attribute Definition dialog; `None` while closed.
|
||||||
|
attdef_dialog: Option<crate::ui::window::attdef_dialog::AttdefDialogState>,
|
||||||
|
attdef_edit: Option<crate::ui::window::attdef_dialog::AttdefEditState>,
|
||||||
point_cloud_color_map: Option<crate::ui::window::pdf_dialogs::PointCloudColorMapState>,
|
point_cloud_color_map: Option<crate::ui::window::pdf_dialogs::PointCloudColorMapState>,
|
||||||
pc_section: Option<crate::ui::window::pdf_dialogs::PcSectionState>,
|
pc_section: Option<crate::ui::window::pdf_dialogs::PcSectionState>,
|
||||||
underlay_layers: Option<crate::ui::window::pdf_dialogs::UnderlayLayersState>,
|
underlay_layers: Option<crate::ui::window::pdf_dialogs::UnderlayLayersState>,
|
||||||
@@ -1917,6 +1920,8 @@ pub enum ModalKind {
|
|||||||
XrefAttach,
|
XrefAttach,
|
||||||
WriteBlock,
|
WriteBlock,
|
||||||
GeometricTolerance,
|
GeometricTolerance,
|
||||||
|
AttDef,
|
||||||
|
AttDefEdit,
|
||||||
DraftingSettings,
|
DraftingSettings,
|
||||||
AutoConstrainSettings,
|
AutoConstrainSettings,
|
||||||
LayerStateEditor,
|
LayerStateEditor,
|
||||||
@@ -3864,6 +3869,7 @@ pub enum Message {
|
|||||||
PdfImportPick,
|
PdfImportPick,
|
||||||
/// An edit in one of the PDF dialogs.
|
/// An edit in one of the PDF dialogs.
|
||||||
PdfDialog(crate::ui::window::pdf_dialogs::PdfDialogMsg),
|
PdfDialog(crate::ui::window::pdf_dialogs::PdfDialogMsg),
|
||||||
|
AttdefDialog(crate::ui::window::attdef_dialog::AttdefDialogMsg),
|
||||||
/// A click or search in the Point Cloud Manager.
|
/// A click or search in the Point Cloud Manager.
|
||||||
PcManager(crate::ui::window::pc_manager::PcManagerMsg),
|
PcManager(crate::ui::window::pc_manager::PcManagerMsg),
|
||||||
PdfImportPickResult(Result<(std::path::PathBuf, std::sync::Arc<Vec<u8>>), String>),
|
PdfImportPickResult(Result<(std::path::PathBuf, std::sync::Arc<Vec<u8>>), String>),
|
||||||
@@ -4057,6 +4063,8 @@ impl OpenCADStudio {
|
|||||||
block_definition: None,
|
block_definition: None,
|
||||||
pdf_attach: None,
|
pdf_attach: None,
|
||||||
point_cloud_attach: None,
|
point_cloud_attach: None,
|
||||||
|
attdef_dialog: None,
|
||||||
|
attdef_edit: None,
|
||||||
point_cloud_color_map: None,
|
point_cloud_color_map: None,
|
||||||
pc_section: None,
|
pc_section: None,
|
||||||
underlay_layers: None,
|
underlay_layers: None,
|
||||||
|
|||||||
@@ -155,6 +155,13 @@ impl super::OpenCADStudio {
|
|||||||
self.open_tolerance_dialog(Some(target));
|
self.open_tolerance_dialog(Some(target));
|
||||||
return iced::Task::none();
|
return iced::Task::none();
|
||||||
}
|
}
|
||||||
|
if matches!(
|
||||||
|
self.tabs[i].scene.document.get_entity(target),
|
||||||
|
Some(EntityType::AttributeDefinition(_))
|
||||||
|
) {
|
||||||
|
self.open_attdef_edit(target);
|
||||||
|
return iced::Task::none();
|
||||||
|
}
|
||||||
// Snapshot what we need before borrowing `self` mutably to open.
|
// Snapshot what we need before borrowing `self` mutably to open.
|
||||||
let Some(entity) = self.tabs[i].scene.document.get_entity(target) else {
|
let Some(entity) = self.tabs[i].scene.document.get_entity(target) else {
|
||||||
return iced::Task::none();
|
return iced::Task::none();
|
||||||
|
|||||||
@@ -3627,6 +3627,14 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if field.starts_with("att_") {
|
||||||
|
if let Err(message) =
|
||||||
|
crate::entities::attribute::validate_definition_property(field, &val)
|
||||||
|
{
|
||||||
|
self.command_line.push_error(message);
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
}
|
||||||
if field.starts_with("ul_") {
|
if field.starts_with("ul_") {
|
||||||
if let Err(message) =
|
if let Err(message) =
|
||||||
crate::entities::underlay::validate_property(field, &val)
|
crate::entities::underlay::validate_property(field, &val)
|
||||||
|
|||||||
@@ -1806,6 +1806,7 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Message::PdfDialog(message) => self.update_pdf_dialog(message),
|
Message::PdfDialog(message) => self.update_pdf_dialog(message),
|
||||||
|
Message::AttdefDialog(message) => self.on_attdef_dialog(message),
|
||||||
Message::PcManager(message) => self.update_pc_manager(message),
|
Message::PcManager(message) => self.update_pc_manager(message),
|
||||||
Message::RibbonSelectTab(idx) => {
|
Message::RibbonSelectTab(idx) => {
|
||||||
self.ribbon.select(idx);
|
self.ribbon.select(idx);
|
||||||
@@ -8496,6 +8497,15 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Message::CloseModal => {
|
Message::CloseModal => {
|
||||||
|
if matches!(
|
||||||
|
self.active_modal,
|
||||||
|
Some(super::ModalKind::AttDef | super::ModalKind::AttDefEdit)
|
||||||
|
) {
|
||||||
|
self.attdef_dialog = None;
|
||||||
|
let editing = self.attdef_edit.take().is_some();
|
||||||
|
self.close_active_modal();
|
||||||
|
return if editing { self.post_editor_closed(false) } else { Task::none() };
|
||||||
|
}
|
||||||
// Cancelling the section settings drops the waiting extraction.
|
// Cancelling the section settings drops the waiting extraction.
|
||||||
if self.active_modal == Some(super::ModalKind::PcSection) {
|
if self.active_modal == Some(super::ModalKind::PcSection) {
|
||||||
crate::modules::insert::pc_extract::drop_job();
|
crate::modules::insert::pc_extract::drop_job();
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ impl OpenCADStudio {
|
|||||||
Some(K::XrefAttach) => crate::t!("Attach External Reference").into_owned(),
|
Some(K::XrefAttach) => crate::t!("Attach External Reference").into_owned(),
|
||||||
Some(K::WriteBlock) => crate::t!("Write Block").into_owned(),
|
Some(K::WriteBlock) => crate::t!("Write Block").into_owned(),
|
||||||
Some(K::GeometricTolerance) => crate::t!("Geometric Tolerance").into_owned(),
|
Some(K::GeometricTolerance) => crate::t!("Geometric Tolerance").into_owned(),
|
||||||
|
Some(K::AttDef) => crate::t!("Attribute Definition").into_owned(),
|
||||||
|
Some(K::AttDefEdit) => crate::t!("Edit Attribute Definition").into_owned(),
|
||||||
Some(K::DraftingSettings) => crate::t!("Drafting Settings").into_owned(),
|
Some(K::DraftingSettings) => crate::t!("Drafting Settings").into_owned(),
|
||||||
Some(K::AutoConstrainSettings) => crate::t!("Constraint Settings").into_owned(),
|
Some(K::AutoConstrainSettings) => crate::t!("Constraint Settings").into_owned(),
|
||||||
Some(K::LayerStateEditor) => crate::tr!("modal", "edit-layer-state"),
|
Some(K::LayerStateEditor) => crate::tr!("modal", "edit-layer-state"),
|
||||||
@@ -572,6 +574,14 @@ impl OpenCADStudio {
|
|||||||
crate::ui::window::wblock::view_window(state, flow)
|
crate::ui::window::wblock::view_window(state, flow)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
super::super::ModalKind::AttDef => {
|
||||||
|
let state = self.attdef_dialog.as_ref()?;
|
||||||
|
sized_flow(ex, 720, 520, |flow| crate::ui::window::attdef_dialog::view(state, flow))
|
||||||
|
}
|
||||||
|
super::super::ModalKind::AttDefEdit => {
|
||||||
|
let state = self.attdef_edit.as_ref()?;
|
||||||
|
sized_flow(ex, 460, 220, |flow| crate::ui::window::attdef_dialog::view_edit(state, flow))
|
||||||
|
}
|
||||||
super::super::ModalKind::GeometricTolerance => {
|
super::super::ModalKind::GeometricTolerance => {
|
||||||
let state = self.geometric_tolerance.as_ref()?;
|
let state = self.geometric_tolerance.as_ref()?;
|
||||||
sized_flow(ex, 670, 530, |flow| {
|
sized_flow(ex, 670, 530, |flow| {
|
||||||
|
|||||||
+377
-195
@@ -339,17 +339,11 @@ fn build_attr_render(input: AttrTextInputs<'_>, document: &codec::CadDocument) -
|
|||||||
|
|
||||||
impl RenderConvertible for AttributeDefinition {
|
impl RenderConvertible for AttributeDefinition {
|
||||||
fn to_render(&self, document: &codec::CadDocument) -> Option<RenderEntity> {
|
fn to_render(&self, document: &codec::CadDocument) -> Option<RenderEntity> {
|
||||||
// An attribute definition previews its tag when it has no default
|
// A definition shows its tag; the value appears only on the block
|
||||||
// value, so the placeholder is visible where a block will prompt for
|
// references (block content renders a constant one's value itself).
|
||||||
// input. (Passed as a non-empty value, so it renders as-is.)
|
|
||||||
let display_value = if self.default_value.is_empty() {
|
|
||||||
self.tag.clone()
|
|
||||||
} else {
|
|
||||||
self.default_value.clone()
|
|
||||||
};
|
|
||||||
Some(build_attr_render(
|
Some(build_attr_render(
|
||||||
AttrTextInputs {
|
AttrTextInputs {
|
||||||
value: &display_value,
|
value: &self.tag,
|
||||||
insertion_point: self.insertion_point,
|
insertion_point: self.insertion_point,
|
||||||
alignment_point: self.alignment_point,
|
alignment_point: self.alignment_point,
|
||||||
height: self.height,
|
height: self.height,
|
||||||
@@ -361,82 +355,262 @@ impl RenderConvertible for AttributeDefinition {
|
|||||||
horizontal_alignment: self.horizontal_alignment,
|
horizontal_alignment: self.horizontal_alignment,
|
||||||
vertical_alignment: self.vertical_alignment,
|
vertical_alignment: self.vertical_alignment,
|
||||||
normal: self.normal,
|
normal: self.normal,
|
||||||
mtext_flag: self.mtext_flag,
|
// The tag is one line even on a multi-line definition.
|
||||||
is_multiline: self.is_multiline,
|
mtext_flag: MTextFlag::SingleLine,
|
||||||
line_count: self.line_count,
|
is_multiline: false,
|
||||||
|
line_count: 1,
|
||||||
},
|
},
|
||||||
document,
|
document,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_plain_left(a: &AttributeDefinition) -> bool {
|
||||||
|
a.is_multiline
|
||||||
|
|| matches!(
|
||||||
|
(a.horizontal_alignment, a.vertical_alignment),
|
||||||
|
(AHA::Left, AVA::Baseline)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_two_point(a: &AttributeDefinition) -> bool {
|
||||||
|
!a.is_multiline && matches!(a.horizontal_alignment, AHA::Aligned | AHA::Fit)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dv(p: Vector3) -> glam::DVec3 {
|
||||||
|
glam::DVec3::new(p.x, p.y, p.z)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shift(p: &mut Vector3, d: glam::DVec3) {
|
||||||
|
p.x += d.x;
|
||||||
|
p.y += d.y;
|
||||||
|
p.z += d.z;
|
||||||
|
}
|
||||||
|
|
||||||
impl Grippable for AttributeDefinition {
|
impl Grippable for AttributeDefinition {
|
||||||
|
/// Left text has one grip at its start; every other justification adds
|
||||||
|
/// the alignment point. Lock position only pins attributes on block
|
||||||
|
/// references, never the definition itself.
|
||||||
fn grips(&self) -> Vec<GripDef> {
|
fn grips(&self) -> Vec<GripDef> {
|
||||||
// lock_position blocks the position grip. Show an empty grip set so
|
let mut grips = vec![square_grip(0, dv(self.insertion_point))];
|
||||||
// the renderer can't drag the entity in either AttributeFlags or
|
if !is_plain_left(self) {
|
||||||
// top-level lock_position is on.
|
grips.push(square_grip(1, dv(self.alignment_point)));
|
||||||
if self.lock_position || self.flags.locked_position {
|
|
||||||
return vec![];
|
|
||||||
}
|
}
|
||||||
vec![square_grip(
|
grips
|
||||||
0,
|
|
||||||
glam::DVec3::new(
|
|
||||||
self.insertion_point.x,
|
|
||||||
self.insertion_point.y,
|
|
||||||
self.insertion_point.z,
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||||||
if self.lock_position || self.flags.locked_position {
|
let current = if grip_id == 1 { self.alignment_point } else { self.insertion_point };
|
||||||
|
let d = match apply {
|
||||||
|
GripApply::Translate(d) => glam::DVec3::new(d.x as f64, d.y as f64, d.z as f64),
|
||||||
|
GripApply::Absolute(p) => glam::DVec3::new(p.x as f64, p.y as f64, p.z as f64) - dv(current),
|
||||||
|
};
|
||||||
|
// A two-point definition stretches the moved endpoint; any other
|
||||||
|
// grip moves the whole text.
|
||||||
|
if is_two_point(self) {
|
||||||
|
if grip_id == 1 {
|
||||||
|
shift(&mut self.alignment_point, d);
|
||||||
|
} else {
|
||||||
|
shift(&mut self.insertion_point, d);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if grip_id == 0 {
|
shift(&mut self.insertion_point, d);
|
||||||
match apply {
|
shift(&mut self.alignment_point, d);
|
||||||
GripApply::Translate(d) => {
|
if let Some(m) = self.embedded_mtext.as_mut() {
|
||||||
self.insertion_point.x += d.x as f64;
|
shift(&mut m.insertion_point, d);
|
||||||
self.insertion_point.y += d.y as f64;
|
|
||||||
self.insertion_point.z += d.z as f64;
|
|
||||||
self.alignment_point.x += d.x as f64;
|
|
||||||
self.alignment_point.y += d.y as f64;
|
|
||||||
self.alignment_point.z += d.z as f64;
|
|
||||||
}
|
|
||||||
GripApply::Absolute(p) => {
|
|
||||||
self.insertion_point.x = p.x as f64;
|
|
||||||
self.insertion_point.y = p.y as f64;
|
|
||||||
self.insertion_point.z = p.z as f64;
|
|
||||||
self.alignment_point.x = p.x as f64;
|
|
||||||
self.alignment_point.y = p.y as f64;
|
|
||||||
self.alignment_point.z = p.z as f64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const JUSTIFY_OPTIONS: [&str; 15] = [
|
||||||
|
"Left",
|
||||||
|
"Align",
|
||||||
|
"Fit",
|
||||||
|
"Center",
|
||||||
|
"Middle",
|
||||||
|
"Right",
|
||||||
|
"Top left",
|
||||||
|
"Top center",
|
||||||
|
"Top right",
|
||||||
|
"Middle left",
|
||||||
|
"Middle center",
|
||||||
|
"Middle right",
|
||||||
|
"Bottom left",
|
||||||
|
"Bottom center",
|
||||||
|
"Bottom right",
|
||||||
|
];
|
||||||
|
|
||||||
|
fn justify_label(a: &AttributeDefinition) -> &'static str {
|
||||||
|
match (a.horizontal_alignment, a.vertical_alignment) {
|
||||||
|
(AHA::Aligned, _) => "Align",
|
||||||
|
(AHA::Fit, _) => "Fit",
|
||||||
|
(AHA::Middle, _) => "Middle",
|
||||||
|
(AHA::Left, AVA::Baseline) => "Left",
|
||||||
|
(AHA::Center, AVA::Baseline) => "Center",
|
||||||
|
(AHA::Right, AVA::Baseline) => "Right",
|
||||||
|
(AHA::Left, AVA::Top) => "Top left",
|
||||||
|
(AHA::Center, AVA::Top) => "Top center",
|
||||||
|
(AHA::Right, AVA::Top) => "Top right",
|
||||||
|
(AHA::Left, AVA::Middle) => "Middle left",
|
||||||
|
(AHA::Center, AVA::Middle) => "Middle center",
|
||||||
|
(AHA::Right, AVA::Middle) => "Middle right",
|
||||||
|
(AHA::Left, AVA::Bottom) => "Bottom left",
|
||||||
|
(AHA::Center, AVA::Bottom) => "Bottom center",
|
||||||
|
(AHA::Right, AVA::Bottom) => "Bottom right",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_justify(s: &str) -> Option<(AHA, AVA)> {
|
||||||
|
Some(match s {
|
||||||
|
"Left" => (AHA::Left, AVA::Baseline),
|
||||||
|
"Align" | "Aligned" => (AHA::Aligned, AVA::Baseline),
|
||||||
|
"Fit" => (AHA::Fit, AVA::Baseline),
|
||||||
|
"Center" => (AHA::Center, AVA::Baseline),
|
||||||
|
"Middle" => (AHA::Middle, AVA::Baseline),
|
||||||
|
"Right" => (AHA::Right, AVA::Baseline),
|
||||||
|
"Top left" => (AHA::Left, AVA::Top),
|
||||||
|
"Top center" => (AHA::Center, AVA::Top),
|
||||||
|
"Top right" => (AHA::Right, AVA::Top),
|
||||||
|
"Middle left" => (AHA::Left, AVA::Middle),
|
||||||
|
"Middle center" => (AHA::Center, AVA::Middle),
|
||||||
|
"Middle right" => (AHA::Right, AVA::Middle),
|
||||||
|
"Bottom left" => (AHA::Left, AVA::Bottom),
|
||||||
|
"Bottom center" => (AHA::Center, AVA::Bottom),
|
||||||
|
"Bottom right" => (AHA::Right, AVA::Bottom),
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Offset of the justification anchor from the baseline start, in the
|
||||||
|
/// text's own frame (the shown tag measured with its style's font).
|
||||||
|
fn justify_offset(a: &AttributeDefinition) -> Option<(f64, f64)> {
|
||||||
|
let b = text_local_bounds(
|
||||||
|
&crate::entities::common::style_font(&a.text_style),
|
||||||
|
&a.tag,
|
||||||
|
a.height as f32,
|
||||||
|
a.width_factor as f32,
|
||||||
|
a.oblique_angle as f32,
|
||||||
|
)?;
|
||||||
|
let ax = match a.horizontal_alignment {
|
||||||
|
AHA::Left => 0.0,
|
||||||
|
AHA::Center | AHA::Middle => b.advance as f64 * 0.5,
|
||||||
|
AHA::Right | AHA::Aligned | AHA::Fit => b.advance as f64,
|
||||||
|
};
|
||||||
|
let ay = match a.vertical_alignment {
|
||||||
|
AVA::Baseline => 0.0,
|
||||||
|
AVA::Bottom => b.ink_min[1] as f64,
|
||||||
|
AVA::Middle => (b.ink_min[1] + b.ink_max[1]) as f64 * 0.5,
|
||||||
|
AVA::Top => b.ink_max[1] as f64,
|
||||||
|
};
|
||||||
|
Some((ax, ay))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate(a: &AttributeDefinition, (x, y): (f64, f64)) -> (f64, f64) {
|
||||||
|
let (sin, cos) = a.rotation.sin_cos();
|
||||||
|
(x * cos - y * sin, x * sin + y * cos)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The baseline start a justified definition stores as its insertion point
|
||||||
|
/// (the alignment point minus the justification offset).
|
||||||
|
pub fn definition_text_start(a: &AttributeDefinition) -> Vector3 {
|
||||||
|
let Some(offset) = justify_offset(a) else {
|
||||||
|
return a.alignment_point;
|
||||||
|
};
|
||||||
|
let (dx, dy) = rotate(a, offset);
|
||||||
|
let p = a.alignment_point;
|
||||||
|
Vector3::new(p.x - dx, p.y - dy, p.z)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn yes_no(flag: bool) -> PropValue {
|
||||||
|
PropValue::Choice {
|
||||||
|
selected: if flag { t!("Yes") } else { t!("No") }.into_owned(),
|
||||||
|
options: vec![t!("Yes").into_owned(), t!("No").into_owned()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_yes(value: &str) -> bool {
|
||||||
|
value == t!("Yes").as_ref() || value.eq_ignore_ascii_case("yes")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn row(label: &str, field: &'static str, value: PropValue) -> Property {
|
||||||
|
Property { label: label.into(), field, value }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_bit(flags: i16, bit: i16, on: bool) -> i16 {
|
||||||
|
if on {
|
||||||
|
flags | bit
|
||||||
|
} else {
|
||||||
|
flags & !bit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What the Properties palette refuses for a definition, with the message
|
||||||
|
/// the reference shows.
|
||||||
|
pub fn validate_definition_property(field: &str, value: &str) -> Result<(), &'static str> {
|
||||||
|
match field {
|
||||||
|
"att_tag" if value.trim().is_empty() || value.trim().contains(char::is_whitespace) => {
|
||||||
|
Err("Invalid argument Tag in setting TagString")
|
||||||
|
}
|
||||||
|
"att_h" | "att_wf" if !parse_f64(value).is_some_and(|v| v > 0.0) => Err("Invalid input"),
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Multiple lines on: the value moves into an embedded MText anchored at the
|
||||||
|
/// start point. Off: back to single-line Left text.
|
||||||
|
fn set_multiline(a: &mut AttributeDefinition, on: bool) {
|
||||||
|
if on == a.is_multiline {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
a.is_multiline = on;
|
||||||
|
if on {
|
||||||
|
a.mtext_flag = MTextFlag::ConstantMultiLine;
|
||||||
|
a.horizontal_alignment = AHA::Left;
|
||||||
|
a.vertical_alignment = AVA::Top;
|
||||||
|
a.alignment_point = a.insertion_point;
|
||||||
|
a.line_count = a.default_value.split("\\P").count().max(1) as i16;
|
||||||
|
let mut mtext = codec::entities::MText::new();
|
||||||
|
mtext.value = a.default_value.clone();
|
||||||
|
mtext.insertion_point = a.insertion_point;
|
||||||
|
mtext.height = a.height;
|
||||||
|
mtext.rotation = a.rotation;
|
||||||
|
mtext.style = a.text_style.clone();
|
||||||
|
a.embedded_mtext = Some(Box::new(mtext));
|
||||||
|
} else {
|
||||||
|
a.mtext_flag = MTextFlag::SingleLine;
|
||||||
|
a.horizontal_alignment = AHA::Left;
|
||||||
|
a.vertical_alignment = AVA::Baseline;
|
||||||
|
a.line_count = 1;
|
||||||
|
a.default_value = a.default_value.replace("\\P", " ");
|
||||||
|
a.embedded_mtext = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PropertyEditable for AttributeDefinition {
|
impl PropertyEditable for AttributeDefinition {
|
||||||
fn geometry_properties(&self, text_style_names: &[String]) -> Vec<PropSection> {
|
fn geometry_properties(&self, text_style_names: &[String]) -> Vec<PropSection> {
|
||||||
let mut text_props = vec![
|
let left = is_plain_left(self);
|
||||||
Property {
|
// Text alignment is the anchor of every justification but Left.
|
||||||
label: t!("Tag").into_owned(),
|
let alignment = |label: std::borrow::Cow<'static, str>, field: &'static str, v: f64| {
|
||||||
field: "att_tag",
|
if left {
|
||||||
value: PropValue::PlainText(self.tag.clone()),
|
ro(label.as_ref(), field, String::new())
|
||||||
|
} else {
|
||||||
|
edit(label.as_ref(), field, v)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let text_props = vec![
|
||||||
|
row(t!("Tag").as_ref(), "att_tag", PropValue::PlainText(self.tag.clone())),
|
||||||
|
row(t!("Annotative").as_ref(), "att_annotative", yes_no(self.flags.annotative)),
|
||||||
|
if self.flags.constant {
|
||||||
|
ro(t!("Prompt").as_ref(), "att_prompt", self.prompt.clone())
|
||||||
|
} else {
|
||||||
|
row(t!("Prompt").as_ref(), "att_prompt", PropValue::PlainText(self.prompt.clone()))
|
||||||
},
|
},
|
||||||
Property {
|
row(t!("Value").as_ref(), "att_default", PropValue::PlainText(self.default_value.clone())),
|
||||||
label: t!("Prompt").into_owned(),
|
row(
|
||||||
field: "att_prompt",
|
t!("Style").as_ref(),
|
||||||
value: PropValue::PlainText(self.prompt.clone()),
|
"att_style",
|
||||||
},
|
PropValue::Choice {
|
||||||
Property {
|
|
||||||
label: t!("Value").into_owned(),
|
|
||||||
field: "att_default",
|
|
||||||
value: PropValue::PlainText(self.default_value.clone()),
|
|
||||||
},
|
|
||||||
Property {
|
|
||||||
label: t!("Style").into_owned(),
|
|
||||||
field: "att_style",
|
|
||||||
value: PropValue::Choice {
|
|
||||||
selected: if self.text_style.trim().is_empty() {
|
selected: if self.text_style.trim().is_empty() {
|
||||||
"Standard".into()
|
"Standard".into()
|
||||||
} else {
|
} else {
|
||||||
@@ -444,33 +618,18 @@ impl PropertyEditable for AttributeDefinition {
|
|||||||
},
|
},
|
||||||
options: text_style_names.to_vec(),
|
options: text_style_names.to_vec(),
|
||||||
},
|
},
|
||||||
},
|
|
||||||
ro(
|
|
||||||
t!("Annotative").as_ref(),
|
|
||||||
"att_annotative",
|
|
||||||
bool_yn(self.flags.annotative),
|
|
||||||
),
|
),
|
||||||
Property {
|
if self.is_multiline {
|
||||||
label: t!("Justify").into_owned(),
|
ro(t!("Justify").as_ref(), "att_justify", t!(justify_label(self)).into_owned())
|
||||||
field: "att_halign",
|
} else {
|
||||||
value: PropValue::Choice {
|
row(
|
||||||
selected: halign_str(self.horizontal_alignment).to_string(),
|
t!("Justify").as_ref(),
|
||||||
options: ["Left", "Center", "Right", "Aligned", "Middle", "Fit"]
|
"att_justify",
|
||||||
.into_iter()
|
PropValue::Choice {
|
||||||
.map(str::to_string)
|
selected: justify_label(self).to_string(),
|
||||||
.collect(),
|
options: JUSTIFY_OPTIONS.into_iter().map(str::to_string).collect(),
|
||||||
},
|
},
|
||||||
},
|
)
|
||||||
Property {
|
|
||||||
label: t!("V-Align").into_owned(),
|
|
||||||
field: "att_valign",
|
|
||||||
value: PropValue::Choice {
|
|
||||||
selected: valign_str(self.vertical_alignment).to_string(),
|
|
||||||
options: ["Baseline", "Bottom", "Middle", "Top"]
|
|
||||||
.into_iter()
|
|
||||||
.map(str::to_string)
|
|
||||||
.collect(),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// Fixed by the style when the style fixes its height.
|
// Fixed by the style when the style fixes its height.
|
||||||
crate::entities::common::num_prop(
|
crate::entities::common::num_prop(
|
||||||
@@ -482,125 +641,148 @@ impl PropertyEditable for AttributeDefinition {
|
|||||||
edit_angle(t!("Rotation").as_ref(), "att_rot", self.rotation.to_degrees()),
|
edit_angle(t!("Rotation").as_ref(), "att_rot", self.rotation.to_degrees()),
|
||||||
edit(t!("Width factor").as_ref(), "att_wf", self.width_factor),
|
edit(t!("Width factor").as_ref(), "att_wf", self.width_factor),
|
||||||
edit_angle(t!("Obliquing").as_ref(), "att_ob", self.oblique_angle.to_degrees()),
|
edit_angle(t!("Obliquing").as_ref(), "att_ob", self.oblique_angle.to_degrees()),
|
||||||
edit(t!("Text alignment X").as_ref(), "att_ax", self.alignment_point.x),
|
ro(t!("Direction").as_ref(), "att_direction", t!("By style").into_owned()),
|
||||||
edit(t!("Text alignment Y").as_ref(), "att_ay", self.alignment_point.y),
|
if self.is_multiline {
|
||||||
edit(t!("Text alignment Z").as_ref(), "att_az", self.alignment_point.z),
|
edit(
|
||||||
ro(
|
t!("Boundary width").as_ref(),
|
||||||
t!("Boundary width").as_ref(),
|
"att_bwidth",
|
||||||
"att_field_len",
|
self.embedded_mtext.as_ref().map_or(0.0, |m| m.rectangle_width),
|
||||||
self.field_length.to_string(),
|
)
|
||||||
),
|
} else {
|
||||||
ro(
|
ro(t!("Boundary width").as_ref(), "att_bwidth", String::new())
|
||||||
t!("Upside down").as_ref(),
|
},
|
||||||
"att_upside_down",
|
alignment(t!("Text alignment X"), "att_ax", self.alignment_point.x),
|
||||||
bool_yn(self.text_generation_flags & 0x4 != 0),
|
alignment(t!("Text alignment Y"), "att_ay", self.alignment_point.y),
|
||||||
),
|
alignment(t!("Text alignment Z"), "att_az", self.alignment_point.z),
|
||||||
ro(
|
|
||||||
t!("Backward").as_ref(),
|
|
||||||
"att_backward",
|
|
||||||
bool_yn(self.text_generation_flags & 0x2 != 0),
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
// Constant attributes can't be edited at insert time — surface that
|
|
||||||
// by marking the Value field read-only.
|
|
||||||
if self.flags.constant {
|
|
||||||
if let Some(p) = text_props.iter_mut().find(|p| p.field == "att_default") {
|
|
||||||
p.value = PropValue::ReadOnly(self.default_value.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vec![
|
vec![
|
||||||
PropSection {
|
PropSection { title: t!("Text").into_owned(), props: text_props },
|
||||||
title: t!("Text").into_owned(),
|
|
||||||
props: text_props,
|
|
||||||
},
|
|
||||||
PropSection {
|
|
||||||
title: t!("Geometry").into_owned(),
|
|
||||||
props: vec![
|
|
||||||
edit(t!("Position X").as_ref(), "att_ix", self.insertion_point.x),
|
|
||||||
edit(t!("Position Y").as_ref(), "att_iy", self.insertion_point.y),
|
|
||||||
edit(t!("Position Z").as_ref(), "att_iz", self.insertion_point.z),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
PropSection {
|
PropSection {
|
||||||
title: t!("Misc").into_owned(),
|
title: t!("Misc").into_owned(),
|
||||||
props: vec![
|
props: vec![
|
||||||
ro(t!("Invisible").as_ref(), "att_invisible", bool_yn(self.flags.invisible)),
|
row(t!("Upside down").as_ref(), "att_upside_down", yes_no(self.text_generation_flags & 0x4 != 0)),
|
||||||
ro(t!("Constant").as_ref(), "att_constant", bool_yn(self.flags.constant)),
|
row(t!("Backward").as_ref(), "att_backward", yes_no(self.text_generation_flags & 0x2 != 0)),
|
||||||
ro(t!("Verify").as_ref(), "att_verify", bool_yn(self.flags.verify)),
|
row(t!("Invisible").as_ref(), "att_invisible", yes_no(self.flags.invisible)),
|
||||||
ro(t!("Preset").as_ref(), "att_preset", bool_yn(self.flags.preset)),
|
row(t!("Constant").as_ref(), "att_constant", yes_no(self.flags.constant)),
|
||||||
ro(t!("Lock position").as_ref(), "att_lock_pos", bool_yn(self.lock_position)),
|
row(t!("Verify").as_ref(), "att_verify", yes_no(self.flags.verify)),
|
||||||
Property {
|
row(t!("Preset").as_ref(), "att_preset", yes_no(self.flags.preset)),
|
||||||
label: t!("Multiple lines").into_owned(),
|
row(t!("Multiple lines").as_ref(), "att_multiline", yes_no(self.is_multiline)),
|
||||||
field: "att_mtext_flag",
|
row(
|
||||||
value: PropValue::Choice {
|
t!("Lock position").as_ref(),
|
||||||
selected: mtext_flag_str(self.mtext_flag).to_string(),
|
"att_lock_pos",
|
||||||
options: ["SingleLine", "MultiLine", "ConstantMultiLine"]
|
yes_no(self.lock_position || self.flags.locked_position),
|
||||||
.into_iter()
|
),
|
||||||
.map(str::to_string)
|
|
||||||
.collect(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
||||||
// String / enum fields first.
|
if validate_definition_property(field, value).is_err() {
|
||||||
if field == "att_tag" {
|
|
||||||
self.tag = value.to_string();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if field == "att_prompt" {
|
|
||||||
self.prompt = value.to_string();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if field == "att_default" {
|
|
||||||
if !self.flags.constant {
|
|
||||||
self.default_value = value.to_string();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if field == "att_style" {
|
|
||||||
self.text_style = value.to_string();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if field == "att_halign" {
|
|
||||||
if let Some(a) = parse_halign(value) {
|
|
||||||
self.horizontal_alignment = a;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if field == "att_valign" {
|
|
||||||
if let Some(a) = parse_valign(value) {
|
|
||||||
self.vertical_alignment = a;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if field == "att_mtext_flag" {
|
|
||||||
self.mtext_flag = match value {
|
|
||||||
"MultiLine" => MTextFlag::MultiLine,
|
|
||||||
"ConstantMultiLine" => MTextFlag::ConstantMultiLine,
|
|
||||||
_ => MTextFlag::SingleLine,
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Numeric scalars.
|
|
||||||
let Some(v) = parse_f64(value) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
match field {
|
match field {
|
||||||
"att_ix" => self.insertion_point.x = v,
|
"att_tag" => self.tag = value.trim().to_string(),
|
||||||
"att_iy" => self.insertion_point.y = v,
|
"att_prompt" if !self.flags.constant => self.prompt = value.to_string(),
|
||||||
"att_iz" => self.insertion_point.z = v,
|
"att_default" => {
|
||||||
"att_ax" => self.alignment_point.x = v,
|
self.default_value = value.to_string();
|
||||||
"att_ay" => self.alignment_point.y = v,
|
if let Some(m) = self.embedded_mtext.as_mut() {
|
||||||
"att_az" => self.alignment_point.z = v,
|
m.value = value.to_string();
|
||||||
"att_h" if v > 0.0 => self.height = v,
|
}
|
||||||
"att_rot" => self.rotation = v.to_radians(),
|
}
|
||||||
"att_wf" if v.abs() > 1e-9 => self.width_factor = v,
|
"att_style" => {
|
||||||
"att_ob" => self.oblique_angle = v.to_radians(),
|
self.text_style = value.to_string();
|
||||||
_ => {}
|
if let Some(m) = self.embedded_mtext.as_mut() {
|
||||||
|
m.style = value.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"att_annotative" => self.flags.annotative = is_yes(value),
|
||||||
|
"att_invisible" => self.flags.invisible = is_yes(value),
|
||||||
|
"att_constant" => {
|
||||||
|
self.flags.constant = is_yes(value);
|
||||||
|
if self.flags.constant {
|
||||||
|
self.prompt.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"att_verify" => self.flags.verify = is_yes(value),
|
||||||
|
"att_preset" => self.flags.preset = is_yes(value),
|
||||||
|
"att_lock_pos" => {
|
||||||
|
self.lock_position = is_yes(value);
|
||||||
|
self.flags.locked_position = self.lock_position;
|
||||||
|
}
|
||||||
|
"att_upside_down" => {
|
||||||
|
self.text_generation_flags = set_bit(self.text_generation_flags, 0x4, is_yes(value))
|
||||||
|
}
|
||||||
|
"att_backward" => {
|
||||||
|
self.text_generation_flags = set_bit(self.text_generation_flags, 0x2, is_yes(value))
|
||||||
|
}
|
||||||
|
"att_multiline" => set_multiline(self, is_yes(value)),
|
||||||
|
"att_justify" => {
|
||||||
|
let Some((h, v)) = parse_justify(value) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let was_two_point = is_two_point(self);
|
||||||
|
self.horizontal_alignment = h;
|
||||||
|
self.vertical_alignment = v;
|
||||||
|
if is_plain_left(self) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// The text stays where it is: the baseline start is kept and
|
||||||
|
// the alignment point moves to the new anchor.
|
||||||
|
if !is_two_point(self) {
|
||||||
|
let (dx, dy) = justify_offset(self).map_or((0.0, 0.0), |o| rotate(self, o));
|
||||||
|
let s = self.insertion_point;
|
||||||
|
self.alignment_point = Vector3::new(s.x + dx, s.y + dy, s.z);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let span = (self.alignment_point.x - self.insertion_point.x)
|
||||||
|
.hypot(self.alignment_point.y - self.insertion_point.y);
|
||||||
|
if !was_two_point || span < 1.0e-9 {
|
||||||
|
// A baseline as long as the tag.
|
||||||
|
let length = justify_offset(self).map_or(self.height, |(advance, _)| advance);
|
||||||
|
let (sin, cos) = self.rotation.sin_cos();
|
||||||
|
self.alignment_point = Vector3::new(
|
||||||
|
self.insertion_point.x + cos * length,
|
||||||
|
self.insertion_point.y + sin * length,
|
||||||
|
self.insertion_point.z,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let Some(v) = parse_f64(value) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
match field {
|
||||||
|
"att_ax" => self.alignment_point.x = v,
|
||||||
|
"att_ay" => self.alignment_point.y = v,
|
||||||
|
"att_az" => self.alignment_point.z = v,
|
||||||
|
"att_h" => {
|
||||||
|
self.height = v;
|
||||||
|
if let Some(m) = self.embedded_mtext.as_mut() {
|
||||||
|
m.height = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"att_rot" => {
|
||||||
|
self.rotation = v.to_radians();
|
||||||
|
if let Some(m) = self.embedded_mtext.as_mut() {
|
||||||
|
m.rotation = self.rotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"att_wf" => self.width_factor = v,
|
||||||
|
"att_ob" => self.oblique_angle = v.to_radians(),
|
||||||
|
"att_bwidth" if v >= 0.0 => {
|
||||||
|
if let Some(m) = self.embedded_mtext.as_mut() {
|
||||||
|
m.rectangle_width = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A one-point justified definition keeps its stored baseline start in
|
||||||
|
// step with the anchor, height, style and rotation.
|
||||||
|
if !is_plain_left(self) && !is_two_point(self) {
|
||||||
|
self.insertion_point = definition_text_start(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ thread_local! {
|
|||||||
/// the same place.
|
/// the same place.
|
||||||
static FIXED_TEXT_HEIGHTS: std::cell::RefCell<rustc_hash::FxHashMap<String, f64>> =
|
static FIXED_TEXT_HEIGHTS: std::cell::RefCell<rustc_hash::FxHashMap<String, f64>> =
|
||||||
std::cell::RefCell::new(rustc_hash::FxHashMap::default());
|
std::cell::RefCell::new(rustc_hash::FxHashMap::default());
|
||||||
|
/// Resolved font of every text style, for text measured without the
|
||||||
|
/// document at hand (justification changes, attribute placement).
|
||||||
|
static STYLE_FONTS: std::cell::RefCell<rustc_hash::FxHashMap<String, String>> =
|
||||||
|
std::cell::RefCell::new(rustc_hash::FxHashMap::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Record which text styles fix their height, from the drawing's style table.
|
/// Record which text styles fix their height, from the drawing's style table.
|
||||||
@@ -81,6 +85,23 @@ pub fn set_fixed_text_heights(document: &codec::CadDocument) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
STYLE_FONTS.with(|cell| {
|
||||||
|
let mut map = cell.borrow_mut();
|
||||||
|
map.clear();
|
||||||
|
for style in document.text_styles.iter() {
|
||||||
|
let font = crate::entities::text_support::resolve_text_style(&style.name, document).font_name;
|
||||||
|
map.insert(style.name.to_ascii_lowercase(), font);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The resolved font of `style` (Standard when unnamed); empty when unknown.
|
||||||
|
pub fn style_font(style: &str) -> String {
|
||||||
|
let key = match style.trim() {
|
||||||
|
"" => "standard".to_string(),
|
||||||
|
name => name.to_ascii_lowercase(),
|
||||||
|
};
|
||||||
|
STYLE_FONTS.with(|cell| cell.borrow().get(&key).cloned().unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The height `style` fixes, or `None` when it leaves the height to the entity.
|
/// The height `style` fixes, or `None` when it leaves the height to the entity.
|
||||||
|
|||||||
@@ -163,6 +163,18 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
|||||||
"Requires a positive number." => Some(("insert", "pcs-positive")),
|
"Requires a positive number." => Some(("insert", "pcs-positive")),
|
||||||
"Requires a positive integer." => Some(("insert", "pcs-positive-int")),
|
"Requires a positive integer." => Some(("insert", "pcs-positive-int")),
|
||||||
"Traces the points on the kept side of a live section as lines." => Some(("insert", "pcs-help")),
|
"Traces the points on the kept side of a live section as lines." => Some(("insert", "pcs-help")),
|
||||||
|
"Specify on-screen" => Some(("insert", "attdef-on-screen")),
|
||||||
|
"Insertion Point" => Some(("insert", "attdef-insertion-point")),
|
||||||
|
"Insert field" => Some(("insert", "attdef-insert-field")),
|
||||||
|
"Text Settings" => Some(("insert", "attdef-text-settings")),
|
||||||
|
"Align below previous attribute definition" => Some(("insert", "attdef-align-below")),
|
||||||
|
"Define" => Some(("insert", "attdef-define")),
|
||||||
|
"Edit Attribute Definition" => Some(("insert", "attdef-edit-title")),
|
||||||
|
"Changes the tag, prompt and default value of an attribute definition." => Some(("insert", "attdef-edit-help")),
|
||||||
|
"Defines an attribute: a tag, prompt and default value placed as text in a block definition." => Some(("insert", "attdef-help")),
|
||||||
|
"Define Attributes" => Some(("insert", "attdef-define-attributes")),
|
||||||
|
"Manage Attributes" => Some(("insert", "attdef-manage-attributes")),
|
||||||
|
"Set Base Point" => Some(("insert", "attdef-set-base-point")),
|
||||||
"Stylization" => Some(("properties", "stylization")),
|
"Stylization" => Some(("properties", "stylization")),
|
||||||
"Color scheme" => Some(("properties", "color-scheme")),
|
"Color scheme" => Some(("properties", "color-scheme")),
|
||||||
"Scan Colors" => Some(("properties", "scan-colors")),
|
"Scan Colors" => Some(("properties", "scan-colors")),
|
||||||
|
|||||||
+984
-182
File diff suppressed because it is too large.
Load diff
@@ -31,7 +31,7 @@ impl CadModule for DrawModule {
|
|||||||
use crate::modules::annotate::{
|
use crate::modules::annotate::{
|
||||||
angular_dim, leader_cmd, linear_dim, mleader_cmd, mtext, radius_dim, text,
|
angular_dim, leader_cmd, linear_dim, mleader_cmd, mtext, radius_dim, text,
|
||||||
};
|
};
|
||||||
use crate::modules::insert::{create_block, insert_block};
|
use crate::modules::insert::{attedit, create_block, edit_block, insert_block};
|
||||||
use clipboard::{copy_clip, cut, paste};
|
use clipboard::{copy_clip, cut, paste};
|
||||||
use draw::{arc, circle, ellipse, hatch, line, polyline, shapes};
|
use draw::{arc, circle, ellipse, hatch, line, polyline, shapes};
|
||||||
use groups::{group, ungroup};
|
use groups::{group, ungroup};
|
||||||
@@ -200,9 +200,13 @@ impl CadModule for DrawModule {
|
|||||||
},
|
},
|
||||||
RibbonGroup {
|
RibbonGroup {
|
||||||
title: "Block",
|
title: "Block",
|
||||||
|
// Slide-out: Define / Manage / Synchronize Attributes and
|
||||||
|
// Set Base Point.
|
||||||
tools: vec![
|
tools: vec![
|
||||||
RibbonItem::LargeTool(create_block::tool()),
|
|
||||||
RibbonItem::LargeTool(insert_block::tool()),
|
RibbonItem::LargeTool(insert_block::tool()),
|
||||||
|
RibbonItem::Tool(create_block::tool()),
|
||||||
|
RibbonItem::Tool(edit_block::tool()),
|
||||||
|
RibbonItem::Tool(attedit::tool()),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
RibbonGroup {
|
RibbonGroup {
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
|
||||||
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/attsync.svg"));
|
|
||||||
pub fn tool() -> ToolDef {
|
|
||||||
ToolDef {
|
|
||||||
id: "ATTSYNC",
|
|
||||||
label: "Synchronize",
|
|
||||||
icon: ICON,
|
|
||||||
event: ModuleEvent::Command("ATTSYNC".to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,8 @@
|
|||||||
use crate::t;
|
use crate::t;
|
||||||
|
|
||||||
use crate::command::{CadCommand, CmdResult};
|
use crate::command::{CadCommand, CmdResult};
|
||||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
|
||||||
use glam::DVec3;
|
use glam::DVec3;
|
||||||
|
|
||||||
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/base_point.svg"));
|
|
||||||
pub fn tool() -> ToolDef {
|
|
||||||
ToolDef {
|
|
||||||
id: "BASE",
|
|
||||||
label: "Set Base\nPoint",
|
|
||||||
icon: ICON,
|
|
||||||
event: ModuleEvent::Command("BASE".to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Command implementation ────────────────────────────────────────────────
|
// ── Command implementation ────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Picks one point and delegates to the inline `BASE <x> <y> <z>` handler,
|
/// Picks one point and delegates to the inline `BASE <x> <y> <z>` handler,
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
// Insert module — references, point clouds, blocks, attributes, import, content.
|
// Insert module — references, point clouds, blocks, attributes, import, content.
|
||||||
|
|
||||||
mod attdef;
|
mod attdef;
|
||||||
mod attedit;
|
pub(crate) mod attedit;
|
||||||
mod attman;
|
mod attman;
|
||||||
mod attsync;
|
|
||||||
pub mod base_point;
|
pub mod base_point;
|
||||||
mod content_browser;
|
mod content_browser;
|
||||||
pub(crate) mod create_block;
|
pub(crate) mod create_block;
|
||||||
mod design_center;
|
mod design_center;
|
||||||
mod edit_block;
|
pub(crate) mod edit_block;
|
||||||
pub(crate) mod image_transparency;
|
pub(crate) mod image_transparency;
|
||||||
pub(crate) mod insert_block;
|
pub(crate) mod insert_block;
|
||||||
mod landxml;
|
mod landxml;
|
||||||
@@ -106,19 +105,17 @@ impl CadModule for InsertModule {
|
|||||||
tools: vec![
|
tools: vec![
|
||||||
RibbonItem::LargeTool(mview_block::tool()),
|
RibbonItem::LargeTool(mview_block::tool()),
|
||||||
RibbonItem::LargeTool(insert_block::tool()),
|
RibbonItem::LargeTool(insert_block::tool()),
|
||||||
RibbonItem::Tool(create_block::tool()),
|
RibbonItem::LargeTool(attedit::tool()),
|
||||||
RibbonItem::Tool(edit_block::tool()),
|
|
||||||
RibbonItem::Tool(base_point::tool()),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// ── Attributes ────────────────────────────────────────────────────
|
// ── Block Definition (slide-out: Set Base Point, Synchronize) ─────
|
||||||
RibbonGroup {
|
RibbonGroup {
|
||||||
title: "Attributes",
|
title: "Block Definition",
|
||||||
tools: vec![
|
tools: vec![
|
||||||
|
RibbonItem::LargeTool(create_block::tool()),
|
||||||
RibbonItem::LargeTool(attdef::tool()),
|
RibbonItem::LargeTool(attdef::tool()),
|
||||||
RibbonItem::LargeTool(attedit::tool()),
|
RibbonItem::LargeTool(attman::tool()),
|
||||||
RibbonItem::Tool(attman::tool()),
|
RibbonItem::LargeTool(edit_block::tool()),
|
||||||
RibbonItem::Tool(attsync::tool()),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// ── Import ────────────────────────────────────────────────────────
|
// ── Import ────────────────────────────────────────────────────────
|
||||||
|
|||||||
Vendored
+12
@@ -617,6 +617,18 @@ fn build_defn(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
// A standalone definition draws its tag; as block content a
|
||||||
|
// constant one draws its value.
|
||||||
|
let shown;
|
||||||
|
let entity = match entity {
|
||||||
|
EntityType::AttributeDefinition(ad) => {
|
||||||
|
let mut value = ad.clone();
|
||||||
|
value.tag = ad.default_value.clone();
|
||||||
|
shown = EntityType::AttributeDefinition(value);
|
||||||
|
&shown
|
||||||
|
}
|
||||||
|
other => other,
|
||||||
|
};
|
||||||
// A wide polyline inside a block carries its `world_width` on
|
// A wide polyline inside a block carries its `world_width` on
|
||||||
// the LocalWire; `emit_wire` scales it by the insert transform
|
// the LocalWire; `emit_wire` scales it by the insert transform
|
||||||
// so the shader band matches the scaled geometry (same band the
|
// so the shader band matches the scaled geometry (same band the
|
||||||
|
|||||||
@@ -106,6 +106,9 @@ const TOOLS: &[Tool] = &[
|
|||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
struct Panel {
|
struct Panel {
|
||||||
id: &'static str,
|
id: &'static str,
|
||||||
|
/// A command the owning ribbon group must hold, for titles more than one
|
||||||
|
/// tab uses ("Block" on Home and Insert).
|
||||||
|
anchor: Option<&'static str>,
|
||||||
title_id: &'static str,
|
title_id: &'static str,
|
||||||
title: &'static str,
|
title: &'static str,
|
||||||
tools: &'static [Tool],
|
tools: &'static [Tool],
|
||||||
@@ -121,25 +124,60 @@ const REFERENCE_TOOLS: &[Tool] = &[Tool {
|
|||||||
}];
|
}];
|
||||||
pub(super) const REFERENCE_PANEL_ID: &str = "reference_extension";
|
pub(super) const REFERENCE_PANEL_ID: &str = "reference_extension";
|
||||||
|
|
||||||
|
const fn icon_tool(command: &'static str, label: &'static str, icon: &'static [u8]) -> Tool {
|
||||||
|
Tool { command, label, icon, options: &[] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Home > Block slide-out.
|
||||||
|
const HOME_BLOCK_TOOLS: &[Tool] = &[
|
||||||
|
icon_tool("ATTDEF", "Define Attributes", include_bytes!("../../../assets/icons/attdef.svg")),
|
||||||
|
icon_tool("ATTMAN", "Manage Attributes", include_bytes!("../../../assets/icons/attman.svg")),
|
||||||
|
icon_tool("ATTSYNC", "Synchronize", include_bytes!("../../../assets/icons/attsync.svg")),
|
||||||
|
icon_tool("BASE", "Set Base Point", include_bytes!("../../../assets/icons/base_point.svg")),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Insert > Block Definition slide-out.
|
||||||
|
const BLOCK_DEFINITION_TOOLS: &[Tool] = &[
|
||||||
|
icon_tool("BASE", "Set Base Point", include_bytes!("../../../assets/icons/base_point.svg")),
|
||||||
|
icon_tool("ATTSYNC", "Synchronize", include_bytes!("../../../assets/icons/attsync.svg")),
|
||||||
|
];
|
||||||
|
|
||||||
const PANELS: &[Panel] = &[
|
const PANELS: &[Panel] = &[
|
||||||
Panel {
|
Panel {
|
||||||
id: PANEL_ID,
|
id: PANEL_ID,
|
||||||
|
anchor: None,
|
||||||
title_id: TITLE_ID,
|
title_id: TITLE_ID,
|
||||||
title: "Draw",
|
title: "Draw",
|
||||||
tools: TOOLS,
|
tools: TOOLS,
|
||||||
},
|
},
|
||||||
Panel {
|
Panel {
|
||||||
id: "modify_extension",
|
id: "modify_extension",
|
||||||
|
anchor: None,
|
||||||
title_id: "modify_extension_title",
|
title_id: "modify_extension_title",
|
||||||
title: "Modify",
|
title: "Modify",
|
||||||
tools: super::modify_panel::TOOLS,
|
tools: super::modify_panel::TOOLS,
|
||||||
},
|
},
|
||||||
Panel {
|
Panel {
|
||||||
id: REFERENCE_PANEL_ID,
|
id: REFERENCE_PANEL_ID,
|
||||||
|
anchor: None,
|
||||||
title_id: "reference_extension_title",
|
title_id: "reference_extension_title",
|
||||||
title: "Reference",
|
title: "Reference",
|
||||||
tools: REFERENCE_TOOLS,
|
tools: REFERENCE_TOOLS,
|
||||||
},
|
},
|
||||||
|
Panel {
|
||||||
|
id: "home_block_extension",
|
||||||
|
anchor: Some("BEDIT"),
|
||||||
|
title_id: "home_block_extension_title",
|
||||||
|
title: "Block",
|
||||||
|
tools: HOME_BLOCK_TOOLS,
|
||||||
|
},
|
||||||
|
Panel {
|
||||||
|
id: "block_definition_extension",
|
||||||
|
anchor: None,
|
||||||
|
title_id: "block_definition_extension_title",
|
||||||
|
title: "Block Definition",
|
||||||
|
tools: BLOCK_DEFINITION_TOOLS,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Command, label and icon of every tool in the expanded Draw and Modify
|
/// Command, label and icon of every tool in the expanded Draw and Modify
|
||||||
@@ -169,10 +207,16 @@ pub(super) fn parent_panel(id: &str) -> Option<&'static str> {
|
|||||||
panel_for_dropdown(id).map(|panel| panel.id)
|
panel_for_dropdown(id).map(|panel| panel.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn group_title<'a>(title: &'static str, open: &Option<String>) -> Element<'a, Message> {
|
pub(super) fn group_title<'a>(
|
||||||
let Some(panel) = PANELS
|
title: &'static str,
|
||||||
.iter()
|
group_ids: &[&'static str],
|
||||||
.find(|panel| panel.title == title && !panel.tools.is_empty())
|
open: &Option<String>,
|
||||||
|
) -> Element<'a, Message> {
|
||||||
|
let Some(panel) = PANELS.iter().copied().find(|panel| {
|
||||||
|
panel.title == title
|
||||||
|
&& !panel.tools.is_empty()
|
||||||
|
&& panel.anchor.is_none_or(|anchor| group_ids.contains(&anchor))
|
||||||
|
})
|
||||||
else {
|
else {
|
||||||
return container(text(t!(title)).size(9).style(muted_text_style))
|
return container(text(t!(title)).size(9).style(muted_text_style))
|
||||||
.padding([1, 4])
|
.padding([1, 4])
|
||||||
|
|||||||
@@ -1397,7 +1397,11 @@ fn render_group<'a>(
|
|||||||
|
|
||||||
column![
|
column![
|
||||||
tools_el,
|
tools_el,
|
||||||
draw_panel::group_title(group.title, open_dd),
|
draw_panel::group_title(
|
||||||
|
group.title,
|
||||||
|
&group.tools.iter().filter_map(item_id).collect::<Vec<_>>(),
|
||||||
|
open_dd,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
.align_x(iced::Center)
|
.align_x(iced::Center)
|
||||||
.spacing(0)
|
.spacing(0)
|
||||||
|
|||||||
@@ -0,0 +1,350 @@
|
|||||||
|
//! Attribute Definition (ATTDEF) and Edit Attribute Definition dialogs:
|
||||||
|
//! cards with accent headings, a help button on the left and the named
|
||||||
|
//! action on the right, like the other drawing dialogs.
|
||||||
|
|
||||||
|
use iced::widget::{button, checkbox, column, container, pick_list, row, text, text_input, Space};
|
||||||
|
use iced::{Element, Fill, Length};
|
||||||
|
|
||||||
|
use crate::app::Message;
|
||||||
|
use crate::modules::draw::draw::attdef::{
|
||||||
|
Justify, AFLAG_CONSTANT, AFLAG_INVISIBLE, AFLAG_LOCK, AFLAG_MULTILINE, AFLAG_PRESET,
|
||||||
|
AFLAG_VERIFY,
|
||||||
|
};
|
||||||
|
use crate::t;
|
||||||
|
use crate::ui::style::common::muted_style;
|
||||||
|
use crate::ui::style::form::{button_style, dialog_button, field_style};
|
||||||
|
use crate::ui::window::pdf_dialogs::{accent_text, card_style};
|
||||||
|
|
||||||
|
/// One edit in either attribute definition dialog.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum AttdefDialogMsg {
|
||||||
|
Mode(u8, bool),
|
||||||
|
Annotative(bool),
|
||||||
|
Tag(String),
|
||||||
|
Prompt(String),
|
||||||
|
Default(String),
|
||||||
|
Justify(JustifyChoice),
|
||||||
|
Style(String),
|
||||||
|
Height(String),
|
||||||
|
Rotation(String),
|
||||||
|
Width(String),
|
||||||
|
OnScreen(bool),
|
||||||
|
Coord(usize, String),
|
||||||
|
AlignBelow(bool),
|
||||||
|
PickHeight,
|
||||||
|
PickRotation,
|
||||||
|
PickWidth,
|
||||||
|
Ok,
|
||||||
|
Help,
|
||||||
|
EditTag(String),
|
||||||
|
EditPrompt(String),
|
||||||
|
EditDefault(String),
|
||||||
|
EditOk,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A justification as the dialog list shows it (translated label).
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct JustifyChoice(pub Justify);
|
||||||
|
|
||||||
|
impl std::fmt::Display for JustifyChoice {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(&t!(self.0.label()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The ATTDEF dialog's fields.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AttdefDialogState {
|
||||||
|
pub aflags: u8,
|
||||||
|
pub annotative: bool,
|
||||||
|
pub tag: String,
|
||||||
|
pub prompt: String,
|
||||||
|
pub default: String,
|
||||||
|
pub justify: Justify,
|
||||||
|
pub style: String,
|
||||||
|
pub styles: Vec<String>,
|
||||||
|
pub height: String,
|
||||||
|
pub rotation: String,
|
||||||
|
pub width: String,
|
||||||
|
pub on_screen: bool,
|
||||||
|
pub coords: [String; 3],
|
||||||
|
pub align_below: bool,
|
||||||
|
/// A definition was placed before in this session.
|
||||||
|
pub can_align_below: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Edit Attribute Definition dialog's fields.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AttdefEditState {
|
||||||
|
pub handle: codec::Handle,
|
||||||
|
pub tag: String,
|
||||||
|
pub prompt: String,
|
||||||
|
pub default: String,
|
||||||
|
pub constant: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn msg(m: AttdefDialogMsg) -> Message {
|
||||||
|
Message::AttdefDialog(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn card<'a>(title: String, content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
|
||||||
|
container(column![text(title.to_uppercase()).size(10).style(accent_text), content.into()].spacing(8))
|
||||||
|
.padding([10, 12])
|
||||||
|
.width(Fill)
|
||||||
|
.style(card_style)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn labelled<'a>(label: String, field: Element<'a, Message>, enabled: bool) -> Element<'a, Message> {
|
||||||
|
let label = text(label).size(12).width(Length::Fixed(118.0));
|
||||||
|
let label = if enabled { label } else { label.style(muted_style) };
|
||||||
|
row![label, field].spacing(8).align_y(iced::Center).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input<'a>(value: &str, enabled: bool, on: fn(String) -> AttdefDialogMsg) -> Element<'a, Message> {
|
||||||
|
let mut field = text_input("", value).size(12).padding([5, 8]).width(Fill).style(field_style);
|
||||||
|
if enabled {
|
||||||
|
field = field.on_input(move |v| msg(on(v)));
|
||||||
|
}
|
||||||
|
field.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The small button beside a field that takes the value from the drawing.
|
||||||
|
fn pick<'a>(glyph: &'static str, tip: String, message: Option<AttdefDialogMsg>) -> Element<'a, Message> {
|
||||||
|
let b = button(text(glyph).size(13).align_x(iced::Center).width(Fill))
|
||||||
|
.style(button_style(false))
|
||||||
|
.padding([4, 0])
|
||||||
|
.width(Length::Fixed(28.0));
|
||||||
|
let b = match message {
|
||||||
|
Some(m) => b.on_press(msg(m)),
|
||||||
|
None => b,
|
||||||
|
};
|
||||||
|
iced::widget::tooltip(b, text(tip).size(11), iced::widget::tooltip::Position::Bottom).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mode<'a>(label: String, on: bool, bit: u8) -> Element<'a, Message> {
|
||||||
|
checkbox(on)
|
||||||
|
.label(label)
|
||||||
|
.text_size(12)
|
||||||
|
.size(14)
|
||||||
|
.on_toggle(move |v| msg(AttdefDialogMsg::Mode(bit, v)))
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn footer<'a>(action: std::borrow::Cow<'static, str>, ok: AttdefDialogMsg) -> Element<'a, Message> {
|
||||||
|
row![
|
||||||
|
button(text("?").size(12))
|
||||||
|
.on_press(msg(AttdefDialogMsg::Help))
|
||||||
|
.style(button_style(false))
|
||||||
|
.padding([5, 11]),
|
||||||
|
Space::new().width(Fill),
|
||||||
|
dialog_button(t!("Cancel"), Message::CloseModal, false),
|
||||||
|
dialog_button(action, msg(ok), true),
|
||||||
|
]
|
||||||
|
.spacing(6)
|
||||||
|
.align_y(iced::Center)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view<'a>(state: &'a AttdefDialogState, sizing: crate::ui::modal::ModalSizing) -> Element<'a, Message> {
|
||||||
|
let f = state.aflags;
|
||||||
|
let constant = f & AFLAG_CONSTANT != 0;
|
||||||
|
let multiline = f & AFLAG_MULTILINE != 0;
|
||||||
|
let modes = card(
|
||||||
|
t!("Mode").into_owned(),
|
||||||
|
column![
|
||||||
|
mode(t!("Invisible").into_owned(), f & AFLAG_INVISIBLE != 0, AFLAG_INVISIBLE),
|
||||||
|
mode(t!("Constant").into_owned(), constant, AFLAG_CONSTANT),
|
||||||
|
mode(t!("Verify").into_owned(), f & AFLAG_VERIFY != 0, AFLAG_VERIFY),
|
||||||
|
mode(t!("Preset").into_owned(), f & AFLAG_PRESET != 0, AFLAG_PRESET),
|
||||||
|
mode(t!("Lock position").into_owned(), f & AFLAG_LOCK != 0, AFLAG_LOCK),
|
||||||
|
mode(t!("Multiple lines").into_owned(), multiline, AFLAG_MULTILINE),
|
||||||
|
]
|
||||||
|
.spacing(7),
|
||||||
|
);
|
||||||
|
let coords_on = !state.on_screen && !state.align_below;
|
||||||
|
let coord = |i: usize, axis: &'static str| -> Element<'a, Message> {
|
||||||
|
let mut field = text_input("", &state.coords[i]).size(12).padding([5, 8]).width(Fill).style(field_style);
|
||||||
|
if coords_on {
|
||||||
|
field = field.on_input(move |v| msg(AttdefDialogMsg::Coord(i, v)));
|
||||||
|
}
|
||||||
|
let label = text(axis).size(12).width(Length::Fixed(14.0));
|
||||||
|
let label = if coords_on { label } else { label.style(muted_style) };
|
||||||
|
row![label, field].spacing(8).align_y(iced::Center).into()
|
||||||
|
};
|
||||||
|
let mut on_screen = checkbox(state.on_screen)
|
||||||
|
.label(t!("Specify on-screen").into_owned())
|
||||||
|
.text_size(12)
|
||||||
|
.size(14);
|
||||||
|
if !state.align_below {
|
||||||
|
on_screen = on_screen.on_toggle(|v| msg(AttdefDialogMsg::OnScreen(v)));
|
||||||
|
}
|
||||||
|
let insertion = card(
|
||||||
|
t!("Insertion Point").into_owned(),
|
||||||
|
column![on_screen, coord(0, "X"), coord(1, "Y"), coord(2, "Z")].spacing(7),
|
||||||
|
);
|
||||||
|
let attribute = card(
|
||||||
|
t!("Attribute").into_owned(),
|
||||||
|
column![
|
||||||
|
labelled(t!("Tag").into_owned(), input(&state.tag, true, AttdefDialogMsg::Tag), true),
|
||||||
|
labelled(
|
||||||
|
t!("Prompt").into_owned(),
|
||||||
|
input(&state.prompt, !constant, AttdefDialogMsg::Prompt),
|
||||||
|
!constant
|
||||||
|
),
|
||||||
|
labelled(
|
||||||
|
if constant { t!("Value") } else { t!("Default") }.into_owned(),
|
||||||
|
row![
|
||||||
|
input(&state.default, true, AttdefDialogMsg::Default),
|
||||||
|
// No field picker in this application yet.
|
||||||
|
pick("ƒ", t!("Insert field").into_owned(), None),
|
||||||
|
]
|
||||||
|
.spacing(6)
|
||||||
|
.into(),
|
||||||
|
true
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.spacing(8),
|
||||||
|
);
|
||||||
|
let text_on = !state.align_below;
|
||||||
|
let justify: Element<'a, Message> = if text_on && !multiline {
|
||||||
|
pick_list(
|
||||||
|
Some(JustifyChoice(state.justify)),
|
||||||
|
Justify::ALL.map(JustifyChoice).to_vec(),
|
||||||
|
|c: &JustifyChoice| c.to_string(),
|
||||||
|
)
|
||||||
|
.on_select(|c| msg(AttdefDialogMsg::Justify(c)))
|
||||||
|
.text_size(12)
|
||||||
|
.padding([5, 8])
|
||||||
|
.width(Fill)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
container(text(JustifyChoice(state.justify).to_string()).size(12).style(muted_style))
|
||||||
|
.padding([5, 8])
|
||||||
|
.width(Fill)
|
||||||
|
.into()
|
||||||
|
};
|
||||||
|
let style: Element<'a, Message> = if text_on {
|
||||||
|
pick_list(Some(state.style.clone()), state.styles.clone(), |s: &String| s.clone())
|
||||||
|
.on_select(|s| msg(AttdefDialogMsg::Style(s)))
|
||||||
|
.text_size(12)
|
||||||
|
.padding([5, 8])
|
||||||
|
.width(Fill)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
container(text(state.style.clone()).size(12).style(muted_style)).padding([5, 8]).width(Fill).into()
|
||||||
|
};
|
||||||
|
let mut annotative = checkbox(state.annotative).label(t!("Annotative").into_owned()).text_size(12).size(14);
|
||||||
|
if text_on {
|
||||||
|
annotative = annotative.on_toggle(|v| msg(AttdefDialogMsg::Annotative(v)));
|
||||||
|
}
|
||||||
|
let size_on = text_on && state.justify != Justify::Align;
|
||||||
|
let rot_on = text_on && !state.justify.two_point();
|
||||||
|
let settings = card(
|
||||||
|
t!("Text Settings").into_owned(),
|
||||||
|
column![
|
||||||
|
labelled(t!("Justification").into_owned(), justify, text_on && !multiline),
|
||||||
|
labelled(t!("Text style").into_owned(), style, text_on),
|
||||||
|
row![Space::new().width(Length::Fixed(126.0)), annotative],
|
||||||
|
labelled(
|
||||||
|
t!("Text height").into_owned(),
|
||||||
|
row![
|
||||||
|
input(&state.height, size_on, AttdefDialogMsg::Height),
|
||||||
|
pick("⌖", t!("Specify on-screen").into_owned(), size_on.then_some(AttdefDialogMsg::PickHeight)),
|
||||||
|
]
|
||||||
|
.spacing(6)
|
||||||
|
.into(),
|
||||||
|
size_on
|
||||||
|
),
|
||||||
|
labelled(
|
||||||
|
t!("Rotation").into_owned(),
|
||||||
|
row![
|
||||||
|
input(&state.rotation, rot_on, AttdefDialogMsg::Rotation),
|
||||||
|
pick("⌖", t!("Specify on-screen").into_owned(), rot_on.then_some(AttdefDialogMsg::PickRotation)),
|
||||||
|
]
|
||||||
|
.spacing(6)
|
||||||
|
.into(),
|
||||||
|
rot_on
|
||||||
|
),
|
||||||
|
labelled(
|
||||||
|
t!("Boundary width").into_owned(),
|
||||||
|
row![
|
||||||
|
input(&state.width, text_on && multiline, AttdefDialogMsg::Width),
|
||||||
|
pick(
|
||||||
|
"⌖",
|
||||||
|
t!("Specify on-screen").into_owned(),
|
||||||
|
(text_on && multiline).then_some(AttdefDialogMsg::PickWidth)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.spacing(6)
|
||||||
|
.into(),
|
||||||
|
text_on && multiline
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.spacing(8),
|
||||||
|
);
|
||||||
|
let mut align = checkbox(state.align_below)
|
||||||
|
.label(t!("Align below previous attribute definition").into_owned())
|
||||||
|
.text_size(12)
|
||||||
|
.size(14);
|
||||||
|
if state.can_align_below {
|
||||||
|
align = align.on_toggle(|v| msg(AttdefDialogMsg::AlignBelow(v)));
|
||||||
|
}
|
||||||
|
let body = row![
|
||||||
|
column![modes, insertion].spacing(10).width(Length::Fixed(200.0)),
|
||||||
|
column![attribute, settings].spacing(10).width(Fill),
|
||||||
|
]
|
||||||
|
.spacing(10);
|
||||||
|
column![body, align, footer(t!("Define"), AttdefDialogMsg::Ok)]
|
||||||
|
.spacing(10)
|
||||||
|
.padding([10, 12])
|
||||||
|
.width(sizing.width)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_edit<'a>(state: &'a AttdefEditState, sizing: crate::ui::modal::ModalSizing) -> Element<'a, Message> {
|
||||||
|
let field = |label: String, value: &'a str, enabled: bool, on: fn(String) -> AttdefDialogMsg| {
|
||||||
|
let mut input = text_input("", value).size(12).padding([5, 8]).width(Fill).style(field_style);
|
||||||
|
if enabled {
|
||||||
|
input = input.on_input(move |v| msg(on(v)));
|
||||||
|
}
|
||||||
|
let label = text(label).size(12).width(Length::Fixed(80.0));
|
||||||
|
let label = if enabled { label } else { label.style(muted_style) };
|
||||||
|
row![label, input].spacing(8).align_y(iced::Center)
|
||||||
|
};
|
||||||
|
let content = card(
|
||||||
|
t!("Attribute Definition").into_owned(),
|
||||||
|
column![
|
||||||
|
field(t!("Tag").into_owned(), &state.tag, true, AttdefDialogMsg::EditTag),
|
||||||
|
field(t!("Prompt").into_owned(), &state.prompt, !state.constant, AttdefDialogMsg::EditPrompt),
|
||||||
|
row![
|
||||||
|
field(
|
||||||
|
if state.constant { t!("Value") } else { t!("Default") }.into_owned(),
|
||||||
|
&state.default,
|
||||||
|
true,
|
||||||
|
AttdefDialogMsg::EditDefault
|
||||||
|
),
|
||||||
|
pick("ƒ", t!("Insert field").into_owned(), None),
|
||||||
|
]
|
||||||
|
.spacing(6)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
]
|
||||||
|
.spacing(8),
|
||||||
|
);
|
||||||
|
column![content, footer(t!("Apply"), AttdefDialogMsg::EditOk)]
|
||||||
|
.spacing(10)
|
||||||
|
.padding([10, 12])
|
||||||
|
.width(sizing.width)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The tag messages the reference shows for the dialogs.
|
||||||
|
pub fn tag_error(tag: &str) -> Option<&'static str> {
|
||||||
|
if tag.trim().is_empty() {
|
||||||
|
Some("The tag cannot be empty.")
|
||||||
|
} else if tag.trim().contains(char::is_whitespace) {
|
||||||
|
Some("The tag may not contain spaces.")
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
pub mod about;
|
pub mod about;
|
||||||
|
pub mod attdef_dialog;
|
||||||
pub mod block_definition;
|
pub mod block_definition;
|
||||||
pub mod block_palette;
|
pub mod block_palette;
|
||||||
pub mod browser;
|
pub mod browser;
|
||||||
|
|||||||
@@ -233,13 +233,13 @@ pub fn click_page(selected: &mut Vec<usize>, anchor: &mut usize, page: usize, ct
|
|||||||
// accent-coloured card titles, segmented choices and toggle chips, with the
|
// accent-coloured card titles, segmented choices and toggle chips, with the
|
||||||
// help button on the left of the footer and the named action on the right.
|
// help button on the left of the footer and the named action on the right.
|
||||||
|
|
||||||
fn accent_text(theme: &Theme) -> text::Style {
|
pub(crate) fn accent_text(theme: &Theme) -> text::Style {
|
||||||
text::Style {
|
text::Style {
|
||||||
color: Some(theme.palette().primary.base.color),
|
color: Some(theme.palette().primary.base.color),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn card_style(theme: &Theme) -> container::Style {
|
pub(crate) fn card_style(theme: &Theme) -> container::Style {
|
||||||
container::Style {
|
container::Style {
|
||||||
background: Some(Background::Color(theme.palette().background.weak.color)),
|
background: Some(Background::Color(theme.palette().background.weak.color)),
|
||||||
border: Border {
|
border: Border {
|
||||||
|
|||||||
Reference in new issue
Block a user