perfect-gui 2.6.1 → 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.1",
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,
@@ -212,14 +220,34 @@ export default class GUI {
212
220
  image.onclick = () => params.callback({ path: params.path, text: params.text });
213
221
  }
214
222
 
215
- addSlider (text, sliderParams, callback) {
223
+ slider (text, sliderParams, callback) {
216
224
  this._checkMandatoryParams({
217
225
  min: 'number',
218
226
  max: 'number',
219
- value: 'number',
220
227
  step: 'number'
221
228
  }, sliderParams);
222
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
+
223
251
  this.imageContainer = null;
224
252
 
225
253
  var container = this._createElement({
@@ -227,7 +255,7 @@ export default class GUI {
227
255
  textContent: text
228
256
  });
229
257
 
230
- var ctrl = this._createElement({
258
+ var slider_ctrl = this._createElement({
231
259
  parent: container,
232
260
  el: 'input',
233
261
  class: 'p-gui__slider-ctrl',
@@ -240,19 +268,39 @@ export default class GUI {
240
268
  }
241
269
  });
242
270
 
243
- var val = this._createElement({
271
+ var slider_value = this._createElement({
244
272
  parent: container,
245
273
  class: 'p-gui__slider-value',
246
- textContent: sliderParams.value
274
+ textContent: isObject ? String(object[prop]) : sliderParams.value
247
275
  });
248
276
 
249
- ctrl.addEventListener('input', function() {
250
- val.textContent = ctrl.value;
251
- 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
+ }
252
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
+ }
253
301
  }
254
302
 
255
- addSwitch(text, state, callback) {
303
+ toggle(text, state, callback) {
256
304
  let params = {
257
305
  text: text,
258
306
  state: state,
@@ -288,7 +336,7 @@ export default class GUI {
288
336
  });
289
337
  }
290
338
 
291
- addList(text, list, callback) {
339
+ list(text, list, callback) {
292
340
  let params = {
293
341
  text: text,
294
342
  list: list,
@@ -328,7 +376,96 @@ export default class GUI {
328
376
  });
329
377
  }
330
378
 
331
- 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 = {}) {
332
469
  let closed = typeof options.closed == 'boolean' ? options.closed : false;
333
470
 
334
471
  let params = {
@@ -419,4 +556,12 @@ export default class GUI {
419
556
  this.closed = !this.closed;
420
557
  this.wrapper.classList.toggle('p-gui--collapsed');
421
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
+ }
422
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;