perfect-gui 2.6.0 → 3.0.0

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/README.md CHANGED
@@ -19,24 +19,21 @@ https://thibka.github.io/perfect-gui/public/
19
19
  npm i perfect-gui
20
20
  ```
21
21
  ```javascript
22
- import perfectGUI from 'perfect-gui';
22
+ import GUI from 'perfect-gui';
23
23
  ```
24
24
 
25
25
  ## Usage
26
26
 
27
27
  ```javascript
28
- const gui = new perfectGUI();
28
+ const gui = new GUI();
29
29
 
30
- gui.addButton('Click me', doSomething);
30
+ gui.button('Click me', callback);
31
31
 
32
- function doSomething() {
33
- // ...
34
- }
35
32
  ```
36
33
 
37
34
  ## Options
38
35
  ```javascript
39
- const gui = new perfectGUI({
36
+ const gui = new GUI({
40
37
  name: 'My GUI',
41
38
  // Name of the panel.
42
39
  // Default is null.
@@ -50,14 +47,14 @@ const gui = new perfectGUI({
50
47
  // Default is 290.
51
48
 
52
49
  closed: false,
53
- // Defines whether the panel should be open or closed on load.
50
+ // Defines whether the panel should be open or closed by default.
54
51
  // Default is false (open).
55
52
 
56
53
  position: 'bottom right',
57
54
  // Defines where to place the panel on screen.
58
55
  // Accepted values are 'top', 'bottom', 'left' and 'right'
59
56
  // in no particular order ('bottom right' = 'right bottom').
60
- // If multiple instances have the same position, they will stack horizontally.
57
+ // If multiple instances have the same position, they will be stacked horizontally.
61
58
  // Default is 'top left'.
62
59
 
63
60
  draggable: false,
@@ -74,53 +71,67 @@ const gui = new perfectGUI({
74
71
  ## Methods
75
72
  <table>
76
73
  <tr><th>Method</th><th>Example</th></tr>
77
- <tr><td>addButton</td><td>
74
+ <tr><td>button</td><td>
78
75
 
79
76
  ```javascript
80
- gui.addButton('Click me!', function() {
77
+ gui.button('Click me!', () => {
81
78
  ...
82
79
  });
83
80
  ```
84
81
  </td></tr>
85
- <tr><td>addImage</td><td>
82
+ <tr><td>image</td><td>
86
83
 
87
84
  ```javascript
88
- gui.addImage('Click this', 'path/to/image', function {
85
+ gui.image('Click this', 'path/to/image', () => {
89
86
  ...
90
87
  });
91
88
  ```
92
89
  </td></tr>
93
- <tr><td>addSlider</td><td>
90
+ <tr><td>slider</td><td>
94
91
 
95
92
  ```javascript
96
- gui.addSlider('Slide this', { min: 0, max: 10, value: 5, step: .1 }, function(value) {
93
+ // Simple slider, only returns a value to the callback
94
+ gui.slider('Slide this', { value: 5, min: 0, max: 10, step: .1 }, value => {
97
95
  console.log('Slider value : ' + value);
98
96
  });
97
+
98
+ // Object-based slider, automatically updates the value of the object property.
99
+ // Directly updating the property will also update the slider.
100
+ gui.slider('Slide this', { object: foo, prop: 'bar', min: 0, max: 10, step: .1 });
99
101
  ```
100
102
  </td></tr>
101
- <tr><td>addSwitch</td><td>
103
+ <tr><td>toggle</td><td>
102
104
 
103
105
  ```javascript
104
- gui.addSwitch('Switch me!', true, state => {
105
- console.log('Switched boolean value: ' + state);
106
+ gui.toggle('Toggle me!', true, state => {
107
+ console.log('Toggle boolean value: ' + state);
106
108
  });
107
109
  ```
108
110
  </td></tr>
109
- <tr><td>addList</td><td>
111
+ <tr><td>list</td><td>
110
112
 
111
113
  ```javascript
112
- gui.addList('Select one', ['apple', 'lime', 'peach'], function(item) {
114
+ gui.list('Select one', ['apple', 'lime', 'peach'], function(item) {
113
115
  console.log('Selected item: ' + item);
114
116
  });
115
117
  ```
116
118
  </td></tr>
117
- <tr><td>addFolder</td><td>
119
+ <tr><td>vector2</td><td>
120
+
121
+ ```javascript
122
+ let folder = gui.vector2('Position', {
123
+ x: { object: myObject.position, prop: 'x', min: -10, max: 10 },
124
+ y: { object: myObject.position, prop: 'y', min: -10, max: 10 },
125
+ });
126
+ ```
127
+ </td></tr>
128
+ <tr><td>folder</td><td>
118
129
 
119
130
  ```javascript
120
- let folder = gui.addFolder('folder name', {
131
+ let folder = gui.folder('folder name', {
121
132
  closed: false // default is false
122
133
  });
123
- folder.addButton('click me!', callback);
134
+ folder.button('click me!', callback);
124
135
  ```
125
136
  </td></tr>
126
137
  <tr><td>toggleClose</td><td>
@@ -134,5 +145,4 @@ gui.toggleClose();
134
145
 
135
146
  ## To do
136
147
  - Adding scrollbars if window is too short
137
- - Adding color palette component
138
- - Adding object binding
148
+ - Adding color palette component
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perfect-gui",
3
- "version": "2.6.0",
3
+ "version": "3.0.0",
4
4
  "description": "Nice and simple GUI for JavaScript.",
5
5
  "main": "src/index.js",
6
6
  "directories": {
package/src/index.js CHANGED
@@ -17,9 +17,13 @@ export default class GUI {
17
17
 
18
18
  this.name = (options != undefined && typeof options.name == "string") ? options.name : '';
19
19
 
20
- if (this instanceof GUI) {
21
- if (typeof GUI[GUI.instanceCounter] != 'number') GUI[GUI.instanceCounter] = 0;
22
- else GUI[GUI.instanceCounter]++;
20
+ if ( this instanceof GUI ) {
21
+ if ( typeof GUI[GUI.instanceCounter] != 'number' ) {
22
+ GUI[GUI.instanceCounter] = 0;
23
+ }
24
+ else {
25
+ GUI[GUI.instanceCounter]++;
26
+ }
23
27
  }
24
28
  this.instanceId = GUI[GUI.instanceCounter];
25
29
 
@@ -30,7 +34,9 @@ export default class GUI {
30
34
  document.head.append(this.stylesheet);
31
35
 
32
36
  // Common styles
33
- if (this.instanceId == 0) this._addStyles(`${styles(this.position_type)}`);
37
+ if (this.instanceId == 0) {
38
+ this._addStyles(`${styles(this.position_type)}`);
39
+ }
34
40
 
35
41
  // Instance styles
36
42
  this.screenCorner = this._parseScreenCorner(options.position);
@@ -71,6 +77,8 @@ export default class GUI {
71
77
  if (options.closed) this.toggleClose();
72
78
 
73
79
  this.folders = [];
80
+
81
+ this.propReferences = [];
74
82
  }
75
83
 
76
84
  _folderConstructor(folderOptions) {
@@ -158,7 +166,7 @@ export default class GUI {
158
166
  });
159
167
  }
160
168
 
161
- addButton(text, callback) {
169
+ button(text, callback) {
162
170
  let params = {
163
171
  text: text,
164
172
  callback: callback
@@ -177,7 +185,7 @@ export default class GUI {
177
185
  })
178
186
  }
179
187
 
180
- addImage(text, path, callback) {
188
+ image(text, path, callback) {
181
189
  let params = {
182
190
  text: text,
183
191
  path: path,
@@ -198,27 +206,48 @@ export default class GUI {
198
206
  // Image
199
207
  var image = this._createElement({
200
208
  class: 'p-gui__image',
201
- onclick: () => params.callback(params.path),
202
209
  inline: `background-image: url(${params.path})`,
203
210
  parent: this.imageContainer
204
211
  })
205
-
212
+
206
213
  // Text inside image
207
214
  this._createElement({
208
215
  parent: image,
209
216
  class: 'p-gui__image-text',
210
217
  textContent: params.text
211
218
  })
219
+
220
+ image.onclick = () => params.callback({ path: params.path, text: params.text });
212
221
  }
213
222
 
214
- addSlider (text, sliderParams, callback) {
223
+ slider (text, sliderParams, callback) {
215
224
  this._checkMandatoryParams({
216
225
  min: 'number',
217
226
  max: 'number',
218
- value: 'number',
219
227
  step: 'number'
220
228
  }, sliderParams);
221
229
 
230
+ let isObject = false;
231
+ let propReferenceIndex = null;
232
+ let object;
233
+ let prop;
234
+
235
+ if ( sliderParams.value ) {
236
+ this._checkMandatoryParams({
237
+ value: 'number'
238
+ }, sliderParams);
239
+ } else {
240
+ this._checkMandatoryParams({
241
+ object: 'object',
242
+ prop: 'string'
243
+ }, sliderParams);
244
+
245
+ object = sliderParams.object;
246
+ prop = sliderParams.prop;
247
+ propReferenceIndex = this.propReferences.push(object[prop]) - 1;
248
+ isObject = true;
249
+ }
250
+
222
251
  this.imageContainer = null;
223
252
 
224
253
  var container = this._createElement({
@@ -226,7 +255,7 @@ export default class GUI {
226
255
  textContent: text
227
256
  });
228
257
 
229
- var ctrl = this._createElement({
258
+ var slider_ctrl = this._createElement({
230
259
  parent: container,
231
260
  el: 'input',
232
261
  class: 'p-gui__slider-ctrl',
@@ -239,19 +268,39 @@ export default class GUI {
239
268
  }
240
269
  });
241
270
 
242
- var val = this._createElement({
271
+ var slider_value = this._createElement({
243
272
  parent: container,
244
273
  class: 'p-gui__slider-value',
245
- textContent: sliderParams.value
274
+ textContent: isObject ? String(object[prop]) : sliderParams.value
246
275
  });
247
276
 
248
- ctrl.addEventListener('input', function() {
249
- val.textContent = ctrl.value;
250
- if (typeof callback == "function") callback(ctrl.value);
277
+ slider_ctrl.addEventListener('input', () => {
278
+ slider_value.textContent = slider_ctrl.value;
279
+
280
+ if ( isObject ) {
281
+ object[prop] = slider_ctrl.value;
282
+ }
283
+
284
+ if (typeof callback == "function") {
285
+ callback(slider_ctrl.value);
286
+ }
251
287
  });
288
+
289
+ if ( isObject ) {
290
+ Object.defineProperty( object, prop, {
291
+ set: val => {
292
+ this.propReferences[propReferenceIndex] = val;
293
+ slider_ctrl.value = val;
294
+ slider_value.textContent = String( val );
295
+ },
296
+ get: () => {
297
+ return this.propReferences[propReferenceIndex];
298
+ }
299
+ });
300
+ }
252
301
  }
253
302
 
254
- addSwitch(text, state, callback) {
303
+ toggle(text, state, callback) {
255
304
  let params = {
256
305
  text: text,
257
306
  state: state,
@@ -287,7 +336,7 @@ export default class GUI {
287
336
  });
288
337
  }
289
338
 
290
- addList(text, list, callback) {
339
+ list(text, list, callback) {
291
340
  let params = {
292
341
  text: text,
293
342
  list: list,
@@ -327,7 +376,96 @@ export default class GUI {
327
376
  });
328
377
  }
329
378
 
330
- addFolder(name, options = {}) {
379
+ vector2(text, data, callback) {
380
+ this._checkMandatoryParams({
381
+ text: 'string',
382
+ data: 'object',
383
+ minX: 'number',
384
+ maxX: 'number',
385
+ minY: 'number',
386
+ maxY: 'number',
387
+ }, {
388
+ text,
389
+ data,
390
+ minX: data.x.min,
391
+ maxX: data.x.max,
392
+ minY: data.y.min,
393
+ maxY: data.y.max,
394
+ });
395
+
396
+ const objectX = data.x.object;
397
+ const propX = data.x.prop;
398
+ const propXReferenceIndex = this.propReferences.push(objectX[propX]) - 1;
399
+
400
+ const objectY = data.y.object;
401
+ const propY = data.y.prop;
402
+ const propYReferenceIndex = this.propReferences.push(objectY[propY]) - 1;
403
+
404
+ this.imageContainer = null;
405
+
406
+ const container = this._createElement({
407
+ class: 'p-gui__vector2',
408
+ textContent: text
409
+ });
410
+
411
+ const vector_value = this._createElement({
412
+ parent: container,
413
+ class: 'p-gui__vector-value',
414
+ textContent: objectX[propX] + ', ' + objectY[propY]
415
+ });
416
+
417
+ const area = this._createElement({
418
+ parent: container,
419
+ el: 'div',
420
+ class: 'p-gui__vector2-area',
421
+ onclick: (evt) => {
422
+ objectX[propX] = parseFloat(this._mapLinear(evt.offsetX, 0, area.clientWidth, data.x.min, data.x.max).toFixed(1));
423
+ objectY[propY] = parseFloat(this._mapLinear(evt.offsetY, 0, area.clientHeight, data.y.max, data.y.min).toFixed(1));
424
+ }
425
+ });
426
+
427
+ this._createElement({
428
+ parent: area,
429
+ class: 'p-gui__vector2-line p-gui__vector2-line-x'
430
+ });
431
+
432
+ this._createElement({
433
+ parent: area,
434
+ class: 'p-gui__vector2-line p-gui__vector2-line-y'
435
+ });
436
+
437
+ const dot = this._createElement({
438
+ parent: area,
439
+ class: 'p-gui__vector2-dot'
440
+ });
441
+
442
+ dot.style.left = this._mapLinear(objectX[propX], data.x.min, data.x.max, 0, area.clientWidth) + 'px';
443
+ dot.style.top = this._mapLinear(objectY[propY], data.y.min, data.y.max, area.clientHeight, 0) + 'px';
444
+
445
+ Object.defineProperty( objectX, propX, {
446
+ set: val => {
447
+ this.propReferences[propXReferenceIndex] = val;
448
+ dot.style.left = this._mapLinear(val, data.x.min, data.x.max, 0, area.clientWidth) + 'px';
449
+ vector_value.textContent = String( val ) + ', ' + objectY[propY];
450
+ },
451
+ get: () => {
452
+ return this.propReferences[propXReferenceIndex];
453
+ }
454
+ });
455
+
456
+ Object.defineProperty( objectY, propY, {
457
+ set: val => {
458
+ this.propReferences[propYReferenceIndex] = val;
459
+ dot.style.top = this._mapLinear(val, data.y.min, data.y.max, area.clientHeight, 0) + 'px';
460
+ vector_value.textContent = objectX[propX] + ', ' + String( val );
461
+ },
462
+ get: () => {
463
+ return this.propReferences[propYReferenceIndex];
464
+ }
465
+ });
466
+ }
467
+
468
+ folder(name, options = {}) {
331
469
  let closed = typeof options.closed == 'boolean' ? options.closed : false;
332
470
 
333
471
  let params = {
@@ -418,4 +556,12 @@ export default class GUI {
418
556
  this.closed = !this.closed;
419
557
  this.wrapper.classList.toggle('p-gui--collapsed');
420
558
  }
559
+
560
+ kill() {
561
+ this.wrapper.remove();
562
+ }
563
+
564
+ _mapLinear( x, a1, a2, b1, b2 ) {
565
+ return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
566
+ }
421
567
  }
package/src/styles.js CHANGED
@@ -89,7 +89,8 @@ return /* css */`
89
89
 
90
90
  .p-gui__button,
91
91
  .p-gui__switch,
92
- .p-gui__list {
92
+ .p-gui__list,
93
+ .p-gui__vector2 {
93
94
  width: 100%;
94
95
  margin: 3px;
95
96
  padding: 7px;
@@ -102,6 +103,55 @@ return /* css */`
102
103
  box-sizing: border-box;
103
104
  }
104
105
 
106
+ .p-gui__vector2 {
107
+ border-bottom: 1px solid #ff9999;
108
+ aspect-ratio: 1;
109
+ }
110
+
111
+ .p-gui__vector2-area {
112
+ position: relative;
113
+ background: black;
114
+ margin-top: 11px;
115
+ width: 100%;
116
+ height: calc(100% - 33px);
117
+ }
118
+
119
+ .p-gui__vector2-line {
120
+ position: absolute;
121
+ background: white;
122
+ opacity: .3;
123
+ pointer-events: none;
124
+ }
125
+
126
+ .p-gui__vector2-line-x {
127
+ width: 100%;
128
+ height: 1px;
129
+ left: 0;
130
+ top: 50%;
131
+ transform: translateY(-50%);
132
+ }
133
+
134
+ .p-gui__vector2-line-y {
135
+ width: 1px;
136
+ height: 100%;
137
+ top: 0;
138
+ left: 50%;
139
+ transform: translateX(-50%);
140
+ }
141
+
142
+ .p-gui__vector2-dot {
143
+ position: absolute;
144
+ top: 0;
145
+ left: 0;
146
+ width: 8px;
147
+ height: 8px;
148
+ border-radius: 50%;
149
+ background: #d5d5d5;
150
+ border: 2px solid #ff9999;
151
+ transform: translate(-50%, -50%);
152
+ pointer-events: none;
153
+ }
154
+
105
155
  .p-gui__list {
106
156
  cursor: default;
107
157
  }
@@ -185,7 +235,8 @@ return /* css */`
185
235
  border: 2px solid #00a1ff;
186
236
  }
187
237
 
188
- .p-gui__slider-value {
238
+ .p-gui__slider-value,
239
+ .p-gui__vector-value {
189
240
  display: inline-block;
190
241
  position: absolute;
191
242
  right: 7px;