dazscript-framework 1.0.21 → 1.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dazscript-framework",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "author": "Freddy Diaz",
5
5
  "license": "MPL-2.0",
6
6
  "description": "",
@@ -23,7 +23,11 @@ export abstract class BasicDialog {
23
23
 
24
24
  run(): boolean {
25
25
  this.init()
26
- return this.dialog.exec()
26
+ this.builder.restoreObjectName()
27
+ this.builder.traceGeometry('beforeExec')
28
+ let result = this.dialog.exec()
29
+ this.builder.traceGeometry('afterExec', { result })
30
+ return result
27
31
  }
28
32
 
29
33
  ok(): boolean {
@@ -1,8 +1,10 @@
1
1
  import { DialogProperties } from '../shared';
2
2
  import { WidgetBuilderContext, WidgetsBuilder } from './widgets-builder';
3
+ import { debug } from '@dsf/common/log';
3
4
 
4
5
  export class DialogBuilder extends WidgetsBuilder {
5
6
  public properties: DialogProperties
7
+ private objectName: string
6
8
 
7
9
  constructor(private title: string, private id?: string) {
8
10
  super(new WidgetBuilderContext(new DzBasicDialog()))
@@ -15,28 +17,43 @@ export class DialogBuilder extends WidgetsBuilder {
15
17
 
16
18
  build(setup: () => void): DzBasicDialog {
17
19
  this.init()
20
+ this.traceGeometry('init')
18
21
  setup()
22
+ this.restoreObjectName()
23
+ this.traceGeometry('afterSetup')
19
24
  this.resize()
25
+ this.restoreObjectName()
26
+ this.traceGeometry('afterResize')
20
27
  return this.context.dialog
21
28
  }
22
29
 
23
30
  private init() {
24
31
  this.context.dialog.caption = this.title
25
- let dialogWidget = this.context.dialog.getWidget()
26
- var key = (this.id ?? this.context.dialog.caption).replace(/ /g, "") + "Dlg"
27
- dialogWidget.objectName = key
32
+ this.objectName = (this.id ?? this.context.dialog.caption).replace(/ /g, "") + "Dlg"
33
+ this.restoreObjectName()
34
+ }
35
+
36
+ restoreObjectName() {
37
+ if (!this.objectName) return
38
+ this.context.dialog.getWidget().objectName = this.objectName
28
39
  }
29
40
 
30
41
  private resize() {
31
42
  let dialogWidget = this.context.dialog.getWidget();
32
43
  let sizeHint = dialogWidget.minimumSizeHint;
33
44
  let maxHeight = this.properties.maxHeight;
34
- let height = this.properties.height ?? sizeHint.height;
35
- if (maxHeight !== undefined && height > maxHeight) height = maxHeight;
36
- this.context.dialog.minHeight = maxHeight !== undefined
37
- ? Math.min(sizeHint.height, maxHeight)
38
- : sizeHint.height;
39
- this.context.dialog.height = height;
45
+ let hasExplicitHeight = this.properties.height !== undefined;
46
+ let height = this.properties.height;
47
+ if (height !== undefined && maxHeight !== undefined && height > maxHeight) height = maxHeight;
48
+
49
+ if (maxHeight !== undefined) {
50
+ this.context.dialog.minHeight = Math.min(sizeHint.height, maxHeight);
51
+ }
52
+ else if (height !== undefined) {
53
+ this.context.dialog.minHeight = height;
54
+ }
55
+
56
+ if (height !== undefined) this.context.dialog.height = height;
40
57
 
41
58
  if (this.properties.width) this.context.dialog.width = this.properties.width;
42
59
  if (this.properties.maxWidth !== undefined) this.context.dialog.maxWidth = this.properties.maxWidth;
@@ -48,8 +65,68 @@ export class DialogBuilder extends WidgetsBuilder {
48
65
  else {
49
66
  if (this.properties.width)
50
67
  this.context.dialog.setFixedWidth(this.properties.width);
51
- this.context.dialog.setFixedHeight(height);
68
+ if (hasExplicitHeight && height !== undefined)
69
+ this.context.dialog.setFixedHeight(height);
52
70
  }
53
71
  }
54
- }
55
72
 
73
+ traceGeometry(phase: string, extra: { [key: string]: string | number | boolean | undefined } = {}) {
74
+ let dialog = this.context.dialog
75
+ let dialogWidget = dialog.getWidget()
76
+ let objectName = this.read(dialogWidget, 'objectName')
77
+ let caption = this.read(dialog, 'caption')
78
+ let sizeHint = this.read(dialogWidget, 'minimumSizeHint')
79
+ let key = objectName ?? ''
80
+ let fields: { [key: string]: string | number | boolean | undefined } = {
81
+ event: 'dialog.geometry',
82
+ phase,
83
+ title: this.title,
84
+ id: this.id ?? '',
85
+ caption,
86
+ objectName,
87
+ windowGeometryKey: key,
88
+ dialogX: this.read(dialog, 'x'),
89
+ dialogY: this.read(dialog, 'y'),
90
+ dialogWidth: this.read(dialog, 'width'),
91
+ dialogHeight: this.read(dialog, 'height'),
92
+ widgetX: this.read(dialogWidget, 'x'),
93
+ widgetY: this.read(dialogWidget, 'y'),
94
+ widgetWidth: this.read(dialogWidget, 'width'),
95
+ widgetHeight: this.read(dialogWidget, 'height'),
96
+ sizeHintWidth: this.read(sizeHint, 'width'),
97
+ sizeHintHeight: this.read(sizeHint, 'height'),
98
+ propertyWidth: this.properties.width,
99
+ propertyHeight: this.properties.height,
100
+ propertyMaxWidth: this.properties.maxWidth,
101
+ propertyMaxHeight: this.properties.maxHeight,
102
+ resizable: this.properties.resizable === true,
103
+ registrySubKey: key ? `WindowGeometries\\${key}` : ''
104
+ }
105
+
106
+ Object.keys(extra).forEach((name) => fields[name] = extra[name])
107
+ debug(`[DialogGeometry] ${this.formatGeometryTrace(fields)}`)
108
+ }
109
+
110
+ private read(source: any, field: string): any {
111
+ if (!source) return undefined
112
+ try {
113
+ return source[field]
114
+ }
115
+ catch (err) {
116
+ return undefined
117
+ }
118
+ }
119
+
120
+ private formatGeometryTrace(fields: { [key: string]: string | number | boolean | undefined }): string {
121
+ return Object.keys(fields)
122
+ .filter((name) => fields[name] !== undefined)
123
+ .map((name) => `${name}=${this.formatGeometryValue(name, fields[name])}`)
124
+ .join(' ')
125
+ }
126
+
127
+ private formatGeometryValue(name: string, value: string | number | boolean | undefined): string {
128
+ if (name === 'event' || name === 'phase') return String(value)
129
+ if (typeof value === 'string') return JSON.stringify(value)
130
+ return String(value)
131
+ }
132
+ }
@@ -0,0 +1,207 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { BasicDialog } from './basic-dialog'
3
+ import { setLogLevel } from '../common/log'
4
+
5
+ class TestDialog extends BasicDialog {
6
+ protected build(): void {
7
+ this.builder.options({ resizable: true })
8
+ }
9
+ }
10
+
11
+ class HeaderDialog extends BasicDialog {
12
+ protected build(): void {
13
+ this.builder.header({ text: 'Header' }).build()
14
+ this.builder.options({ resizable: true })
15
+ }
16
+ }
17
+
18
+ class NonResizableDefaultSizeDialog extends BasicDialog {
19
+ protected build(): void { }
20
+ }
21
+
22
+ class ExplicitFixedHeightDialog extends BasicDialog {
23
+ protected build(): void {
24
+ this.builder.options({ height: 35 })
25
+ }
26
+ }
27
+
28
+ describe('dialog geometry trace', () => {
29
+ beforeEach(() => {
30
+ ; (globalThis as any).App = {
31
+ debug: vi.fn(),
32
+ log: vi.fn(),
33
+ warning: vi.fn(),
34
+ flushLogBuffer: vi.fn(),
35
+ statusLine: vi.fn()
36
+ }
37
+ ; (globalThis as any).DzBasicDialog = class {
38
+ caption = ''
39
+ width = 300
40
+ height = 200
41
+ minHeight = 0
42
+ maxHeight = 0
43
+ maxWidth = 0
44
+ sizeGripEnabled = false
45
+ fixedHeight: number | undefined
46
+ fixedWidth: number | undefined
47
+ private widget = {
48
+ objectName: '',
49
+ x: 10,
50
+ y: 20,
51
+ width: 300,
52
+ height: 200,
53
+ minimumSizeHint: { width: 300, height: 200 }
54
+ }
55
+
56
+ getWidget() {
57
+ return this.widget
58
+ }
59
+
60
+ inherits(typeName: string) {
61
+ return typeName === 'DzWidget'
62
+ }
63
+
64
+ addLayout() { }
65
+
66
+ exec() {
67
+ if (this.sizeGripEnabled) {
68
+ this.width = 640
69
+ this.height = 480
70
+ this.widget.width = 640
71
+ this.widget.height = 480
72
+ return true
73
+ }
74
+
75
+ this.widget.minimumSizeHint = { width: 196, height: 76 }
76
+ this.width = this.fixedWidth ?? this.widget.minimumSizeHint.width
77
+ this.height = this.fixedHeight ?? this.widget.minimumSizeHint.height
78
+ this.widget.width = this.width
79
+ this.widget.height = this.height
80
+ return true
81
+ }
82
+
83
+ setFixedHeight(value: number) {
84
+ this.fixedHeight = value
85
+ this.height = value
86
+ }
87
+
88
+ setFixedWidth(value: number) {
89
+ this.fixedWidth = value
90
+ this.width = value
91
+ }
92
+ }
93
+ const Layout = class {
94
+ direction = 0
95
+ constructor(public parent: unknown) { }
96
+ inherits(typeName: string) {
97
+ return typeName === 'DzLayout'
98
+ }
99
+ addWidget() { }
100
+ }
101
+ class Widget {
102
+ private _name = ''
103
+ height = 30
104
+ width = 100
105
+ minHeight = 0
106
+ maxHeight = 0
107
+ toolTip = ''
108
+ whatsThis = ''
109
+ constructor(private parent: any) { }
110
+ set name(value: string) {
111
+ this._name = value
112
+ if (this.parent?.getWidget) this.parent.getWidget().objectName = value
113
+ }
114
+ get name() {
115
+ return this._name
116
+ }
117
+ getWidget() {
118
+ return this
119
+ }
120
+ inherits(typeName: string) {
121
+ return typeName === 'DzWidget'
122
+ }
123
+ setFixedHeight(value: number) {
124
+ this.height = value
125
+ }
126
+ setFixedWidth(value: number) {
127
+ this.width = value
128
+ }
129
+ show() { }
130
+ hide() { }
131
+ }
132
+ ; (globalThis as any).DzVBoxLayout = Layout
133
+ ; (globalThis as any).DzHBoxLayout = Layout
134
+ ; (globalThis as any).DzWidget = Widget
135
+ ; (globalThis as any).DzLabel = Widget
136
+ ; (globalThis as any).DzTextBrowser = class extends Widget {
137
+ html = ''
138
+ readOnly = false
139
+ lineWrapMode = 0
140
+ wordWrapMode = 0
141
+ }
142
+ ; (globalThis as any).DzTextEdit = { WidgetWidth: 1, WordWrap: 2 }
143
+ ; (globalThis as any).DzSplitter = { Vertical: 1, Horizontal: 2 }
144
+ setLogLevel('trace')
145
+ })
146
+
147
+ it('logs dialog objectName and geometry before and after exec', () => {
148
+ new TestDialog('Power Loader', 'Vholf3D Power Loader').run()
149
+
150
+ const messages = (globalThis as any).App.debug.mock.calls.map((call: unknown[]) => String(call[0]))
151
+
152
+ expect(messages.some((message: string) =>
153
+ message.includes('event=dialog.geometry phase=init') &&
154
+ message.includes('objectName="Vholf3DPowerLoaderDlg"')
155
+ )).toBe(true)
156
+ expect(messages.some((message: string) =>
157
+ message.includes('event=dialog.geometry phase=beforeExec') &&
158
+ message.includes('dialogWidth=300')
159
+ )).toBe(true)
160
+ expect(messages.some((message: string) =>
161
+ message.includes('event=dialog.geometry phase=afterExec') &&
162
+ message.includes('dialogWidth=640') &&
163
+ message.includes('result=true')
164
+ )).toBe(true)
165
+ })
166
+
167
+ it('keeps the DAZ geometry objectName stable after building a header widget', () => {
168
+ new HeaderDialog('Power Menu - Active Tool', 'Vholf3D Power Menu - Active Tool').run()
169
+
170
+ const messages = (globalThis as any).App.debug.mock.calls.map((call: unknown[]) => String(call[0]))
171
+
172
+ expect(messages.some((message: string) =>
173
+ message.includes('event=dialog.geometry phase=afterSetup') &&
174
+ message.includes('objectName="Vholf3DPowerMenu-ActiveToolDlg"')
175
+ )).toBe(true)
176
+ expect(messages.some((message: string) =>
177
+ message.includes('event=dialog.geometry phase=beforeExec') &&
178
+ message.includes('objectName="Vholf3DPowerMenu-ActiveToolDlg"')
179
+ )).toBe(true)
180
+ })
181
+
182
+ it('does not freeze a non-resizable dialog to an early implicit size hint', () => {
183
+ new NonResizableDefaultSizeDialog('Store Active Camera').run()
184
+
185
+ const messages = (globalThis as any).App.debug.mock.calls.map((call: unknown[]) => String(call[0]))
186
+
187
+ expect(messages.some((message: string) =>
188
+ message.includes('event=dialog.geometry phase=afterExec') &&
189
+ message.includes('objectName="StoreActiveCameraDlg"') &&
190
+ message.includes('dialogHeight=76') &&
191
+ message.includes('sizeHintHeight=76')
192
+ )).toBe(true)
193
+ })
194
+
195
+ it('keeps explicit non-resizable dialog height fixed', () => {
196
+ new ExplicitFixedHeightDialog('Explicit Height').run()
197
+
198
+ const messages = (globalThis as any).App.debug.mock.calls.map((call: unknown[]) => String(call[0]))
199
+
200
+ expect(messages.some((message: string) =>
201
+ message.includes('event=dialog.geometry phase=afterExec') &&
202
+ message.includes('objectName="ExplicitHeightDlg"') &&
203
+ message.includes('dialogHeight=35') &&
204
+ message.includes('sizeHintHeight=76')
205
+ )).toBe(true)
206
+ })
207
+ })