perfect-gui 3.5.16 → 4.0.1

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
@@ -6,7 +6,7 @@ Features:
6
6
  - multiple panels
7
7
  - easy positioning
8
8
  - draggable panels
9
- - two-dimensional vector vizualisation
9
+ - two-dimensional vector visualization
10
10
 
11
11
  <img src="https://raw.githubusercontent.com/thibka/thibka.github.io/master/perfect-gui/_data/capture.png" width="580"/>
12
12
 
@@ -72,101 +72,14 @@ const gui = new GUI({
72
72
  });
73
73
  ```
74
74
 
75
- ## Methods
76
- <table>
77
- <tr><th>Method</th><th>Example</th></tr>
78
- <tr><td>button</td><td>
79
-
80
- ```javascript
81
- gui.button('Click me!', callback);
82
- ```
83
- </td></tr>
84
- <tr><td>image</td><td>
85
-
86
- ```javascript
87
- gui.image('Click this', 'image.jpg', e => {
88
- console.log(e.path, e.text)
89
- });
90
- ```
91
- </td></tr>
92
- <tr><td>slider</td><td>
93
-
94
- ```javascript
95
- // Simple slider, only returns a value to the callback
96
- // min and max parameters are optional, default is 0 (min) and 1 (max)
97
- // step parameter is optional, default is (max - min) * 0.01
98
- gui.slider({
99
- name 'Slide this',
100
- value: 5,
101
- min: 0, // default is 0
102
- max: 10, // default is 1
103
- step: .1 // default is (max - min) * 0.01
104
- }, value => { // optional callback
105
- console.log('Slider value : ' + value);
106
- });
107
-
108
- // Object-based slider, automatically updates the value of the object property.
109
- // Directly updating the property will also update the slider.
110
- // callback is optional
111
- gui.slider({
112
- object: foo,
113
- prop: 'bar'
114
- });
115
- ```
116
- </td></tr>
117
- <tr><td>toggle</td><td>
118
-
119
- ```javascript
120
- gui.toggle('Toggle me!', true, state => {
121
- console.log('Toggle boolean value: ' + state);
122
- });
123
- ```
124
- </td></tr>
125
- <tr><td>list</td><td>
126
-
127
- ```javascript
128
- gui.list('Select one', ['apple', 'lime', 'peach'], function(item) {
129
- console.log('Selected item: ' + item);
130
- });
131
- ```
132
- </td></tr>
133
- <tr><td>vector2</td><td>
134
-
135
- ```javascript
136
- // min and max parameters are optional, default is 0 (min) and 1 (max)
137
- gui.vector2('Position', {
138
- x: { object: myObject.position, prop: 'x', min: -10, max: 10 },
139
- y: { object: myObject.position, prop: 'y', min: -10, max: 10 },
140
- });
141
- ```
142
- </td></tr>
143
- <tr><td>color</td><td>
144
-
145
- ```javascript
146
- gui.color('Color', '#ff0000', color => {
147
- console.log('Selected color:', color);
148
- });
149
- ```
150
- </td></tr>
151
- <tr><td>folder</td><td>
152
-
153
- ```javascript
154
- let folder = gui.folder({
155
- name: 'folder name',
156
- closed: false, // default is false,
157
- color: '#226666' // default is #434343
158
- });
159
- folder.button('click me!', callback);
160
- ```
161
- </td></tr>
162
- <tr><td>toggleClose</td><td>
163
-
164
- ```javascript
165
- gui.toggleClose();
166
- ```
167
- </td></tr>
168
- </table>
169
-
170
-
171
- ## To do
172
- - Style list component
75
+ ## [Methods](https://thibka.github.io/perfect-gui/public/)
76
+
77
+ * [button](https://thibka.github.io/perfect-gui/public/#button)
78
+ * [slider](https://thibka.github.io/perfect-gui/public/#slider)
79
+ * [toggle](https://thibka.github.io/perfect-gui/public/#toggle)
80
+ * [list](https://thibka.github.io/perfect-gui/public/#list)
81
+ * [image](https://thibka.github.io/perfect-gui/public/#image)
82
+ * [color](https://thibka.github.io/perfect-gui/public/#color)
83
+ * [vector2](https://thibka.github.io/perfect-gui/public/#vector2)
84
+ * [folder](https://thibka.github.io/perfect-gui/public/#folder)
85
+ * [toggleClose](https://thibka.github.io/perfect-gui/public)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perfect-gui",
3
- "version": "3.5.16",
3
+ "version": "4.0.1",
4
4
  "description": "Nice and simple GUI for JavaScript",
5
5
  "main": "src/index.js",
6
6
  "directories": {
package/src/index.js CHANGED
@@ -73,10 +73,11 @@ export default class GUI {
73
73
  }
74
74
 
75
75
  _styleInstance() {
76
+ let scrollbar_width = this._getScrollbarWidth(this.container);
76
77
  if (this.screenCorner.x == 'left') {
77
78
  this.xOffset = 0;
78
79
  } else {
79
- this.xOffset = this.container.clientWidth - this.wrapperWidth - this._getScrollbarWidth(this.container);
80
+ this.xOffset = this.container.clientWidth - this.wrapperWidth - scrollbar_width;
80
81
  }
81
82
 
82
83
  if (this.instanceId > 0) {
@@ -140,7 +141,8 @@ export default class GUI {
140
141
  return;
141
142
  }
142
143
 
143
- this.xOffset = this.screenCorner.x == 'left' ? 0 : this.container.clientWidth - this.wrapperWidth;
144
+ let scrollbar_width = this._getScrollbarWidth(this.container);
145
+ this.xOffset = this.screenCorner.x == 'left' ? 0 : this.container.clientWidth - this.wrapperWidth - scrollbar_width;
144
146
  if (this.instanceId > 0) {
145
147
  let existingDomInstances = this.container.querySelectorAll(`.p-gui:not(#${this.wrapper.id}):not([data-dragged])`);
146
148
  for (let i = 0; i < existingDomInstances.length; i++) {
@@ -208,36 +210,53 @@ export default class GUI {
208
210
  });
209
211
  }
210
212
 
211
- button(text, callback) {
212
- let params = {
213
- text: text,
214
- callback: callback
215
- };
216
- this._checkMandatoryParams({
217
- text: 'string',
218
- callback: 'function'
219
- }, params);
213
+ button(name, callback) {
214
+ if (typeof name != 'string') {
215
+ if (typeof name == 'object' && name?.hasOwnProperty('name')) {
216
+ name = name.name;
217
+ } else {
218
+ name = ' ';
219
+ }
220
+ }
221
+ if (name === '') {
222
+ name = ' ';
223
+ }
220
224
 
221
225
  this.imageContainer = null;
222
226
 
227
+ if (typeof callback != 'function') {
228
+ callback = () => {};
229
+ }
230
+
223
231
  this._createElement({
224
232
  class: 'p-gui__button',
225
- onclick: params.callback,
226
- textContent: params.text
227
- })
233
+ textContent: name,
234
+ onclick: callback
235
+ });
228
236
  }
229
237
 
230
- image(text, path, callback) {
231
- let params = {
232
- text: text,
233
- path: path,
234
- callback: callback
235
- };
236
- this._checkMandatoryParams({
237
- text: 'string',
238
- path: 'string',
239
- callback: 'function'
240
- }, params);
238
+ image(params = {}, callback) {
239
+ if (typeof params != 'object') {
240
+ throw Error(`[GUI] image() first parameter must be an object. Received: ${typeof params}.`);
241
+ }
242
+
243
+ let path;
244
+ if (typeof params.path == 'string') {
245
+ path = params.path;
246
+ } else {
247
+ if (typeof params.path == undefined) {
248
+ throw Error(`[GUI] image() path must be provided.`);
249
+ } else {
250
+ throw Error(`[GUI] image() path must be a string.`);
251
+ }
252
+ }
253
+ let filename = path.replace(/^.*[\\\/]/, '');
254
+ let name;
255
+ if (params.name == undefined) {
256
+ name = filename;
257
+ } else {
258
+ name = typeof params.name == 'string' ? params.name || ' ' : ' ';
259
+ }
241
260
 
242
261
  if (!this.imageContainer) {
243
262
  this.imageContainer = this._createElement({
@@ -248,7 +267,7 @@ export default class GUI {
248
267
  // Image
249
268
  var image = this._createElement({
250
269
  class: 'p-gui__image',
251
- inline: `background-image: url(${params.path})`,
270
+ inline: `background-image: url(${path})`,
252
271
  parent: this.imageContainer
253
272
  })
254
273
 
@@ -256,44 +275,64 @@ export default class GUI {
256
275
  this._createElement({
257
276
  parent: image,
258
277
  class: 'p-gui__image-text',
259
- textContent: params.text
278
+ textContent: name
260
279
  })
261
280
 
262
- image.onclick = () => params.callback({ path: params.path, text: params.text });
281
+ if (typeof callback == 'function') {
282
+ image.onclick = () => callback({ path, text: name });
283
+ }
263
284
  }
264
285
 
265
- slider (params, callback) {
286
+ slider (params = {}, callback) {
287
+ if (typeof params != 'object') {
288
+ throw Error(`[GUI] slider() first parameter must be an object. Received: ${typeof params}.`);
289
+ }
290
+
291
+ let name = typeof params.name == 'string' ? params.name || ' ' : ' ';
266
292
  let isObject = false;
267
293
  let propReferenceIndex = null;
268
- let object;
269
- let prop;
270
-
271
- const min = params.min ?? 0;
272
- const max = params.max ?? 1;
273
- const step = params.step || (max - min) / 100;
274
-
275
- if ( typeof params.value == 'number' ) {
276
- this._checkMandatoryParams({
277
- value: 'number'
278
- }, params);
279
- } else {
280
- object = params.obj || params.object;
281
- prop = params.prop || params.property;
282
-
283
- this._checkMandatoryParams({
284
- object: 'object',
285
- prop: 'string'
286
- }, {object, prop});
294
+ let obj = params.obj || params.object;
295
+ let prop = params.prop || params.property;
296
+ let value = typeof params.value == 'number' ? params.value : null;
297
+ let min = params.min ?? 0;
298
+ let max = params.max ?? 1;
299
+ let step = params.step || (max - min) / 100;
300
+
301
+ // callback mode
302
+ if ( value !== null ) {
303
+ if (prop != undefined || obj != undefined) {
304
+ console.warn(`[GUI] slider() "obj" and "property" parameters are ignored when a "value" parameter is used.`);
305
+ }
306
+ }
307
+ // object-binding
308
+ else if (prop != undefined && obj != undefined) {
309
+ if (typeof prop != 'string') {
310
+ throw Error(`[GUI] slider() "prop" (or "property") parameter must be an string. Received: ${typeof prop}.`);
311
+ }
312
+ if (typeof obj != 'object') {
313
+ throw Error(`[GUI] slider() "obj" (or "object") parameter must be an object. Received: ${typeof obj}.`);
314
+ }
315
+
316
+ if (name == ' ') {
317
+ name = prop;
318
+ }
287
319
 
288
- propReferenceIndex = this.propReferences.push(object[prop]) - 1;
320
+ propReferenceIndex = this.propReferences.push(obj[prop]) - 1;
289
321
  isObject = true;
290
322
  }
323
+ else {
324
+ if ((prop != undefined && obj == undefined) || (prop == undefined && obj == undefined)) {
325
+ console.warn(`[GUI] slider() "obj" and "prop" parameters must be used together.`);
326
+ }
327
+
328
+ value = (max - min) / 2;
329
+ }
291
330
 
292
331
  this.imageContainer = null;
293
332
 
294
333
  var container = this._createElement({
295
334
  class: 'p-gui__slider',
296
- textContent: params.name || prop
335
+ textContent: name
297
336
  });
298
337
 
299
338
  var slider_ctrl = this._createElement({
@@ -305,21 +344,21 @@ export default class GUI {
305
344
  min,
306
345
  max,
307
346
  step,
308
- value: isObject ? object[prop] : params.value
347
+ value: isObject ? obj[prop] : value
309
348
  }
310
349
  });
311
350
 
312
351
  var slider_value = this._createElement({
313
352
  parent: container,
314
353
  class: 'p-gui__slider-value',
315
- textContent: isObject ? String(object[prop]) : String(params.value)
354
+ textContent: isObject ? String(obj[prop]) : String(value)
316
355
  });
317
356
 
318
357
  slider_ctrl.addEventListener('input', () => {
319
358
  slider_value.textContent = slider_ctrl.value;
320
359
 
321
360
  if ( isObject ) {
322
- object[prop] = slider_ctrl.value;
361
+ obj[prop] = slider_ctrl.value;
323
362
  }
324
363
 
325
364
  if (typeof callback == "function") {
@@ -328,7 +367,7 @@ export default class GUI {
328
367
  });
329
368
 
330
369
  if ( isObject ) {
331
- Object.defineProperty( object, prop, {
370
+ Object.defineProperty( obj, prop, {
332
371
  set: val => {
333
372
  this.propReferences[propReferenceIndex] = val;
334
373
  slider_ctrl.value = val;
@@ -341,17 +380,13 @@ export default class GUI {
341
380
  }
342
381
  }
343
382
 
344
- toggle(text, state, callback) {
345
- let params = {
346
- text: text,
347
- state: state,
348
- callback: callback
349
- };
350
- this._checkMandatoryParams({
351
- text: 'string',
352
- state: 'boolean',
353
- callback: 'function'
354
- }, params);
383
+ toggle(params = {}, callback) {
384
+ if (typeof params != 'object') {
385
+ throw Error(`[GUI] toggle() first parameter must be an object. Received: ${typeof params}.`);
386
+ }
387
+
388
+ let name = typeof params.name == 'string' ? params.name || ' ' : ' ';
389
+ let value = params.value === true ? true : false;
355
390
 
356
391
  this.imageContainer = null;
357
392
 
@@ -359,41 +394,40 @@ export default class GUI {
359
394
  class: 'p-gui__switch',
360
395
  onclick: (ev) => {
361
396
  let checkbox = ev.target.childNodes[1],
362
- state = true;
397
+ value = true;
363
398
  if (checkbox.classList.contains('p-gui__switch-checkbox--active')) {
364
- state = false;
399
+ value = false;
400
+ }
401
+ checkbox.classList.toggle('p-gui__switch-checkbox--active');
402
+ if (typeof callback == 'function') {
403
+ callback(value);
365
404
  }
366
- checkbox.classList.toggle('p-gui__switch-checkbox--active')
367
- params.callback(state)
368
405
  },
369
- textContent: params.text
406
+ textContent: name
370
407
  });
371
408
 
372
- let activeClass = state ? " p-gui__switch-checkbox--active" : "";
409
+ let activeClass = value ? ' p-gui__switch-checkbox--active' : '';
373
410
 
374
411
  this._createElement({
375
412
  parent: switchContainer,
376
- class: "p-gui__switch-checkbox" + activeClass
413
+ class: 'p-gui__switch-checkbox' + activeClass
377
414
  });
378
415
  }
379
416
 
380
- list(text, list, callback) {
381
- let params = {
382
- text: text,
383
- list: list,
384
- callback: callback
385
- };
386
- this._checkMandatoryParams({
387
- text: 'string',
388
- list: 'object',
389
- callback: 'function'
390
- }, params);
417
+ list(params = {}, callback) {
418
+ if (typeof params != 'object') {
419
+ throw Error(`[GUI] list() first parameter must be an object. Received: ${typeof params}.`);
420
+ }
421
+
422
+ let name = typeof params.name == 'string' ? params.name : ' ';
423
+ let values = Array.isArray(params.values) ? params.values : null;
424
+ callback = typeof callback == 'function' ? callback : null;
391
425
 
392
426
  this.imageContainer = null;
393
427
 
394
428
  let container = this._createElement({
395
429
  class: 'p-gui__list',
396
- textContent: params.text
430
+ textContent: name
397
431
  });
398
432
 
399
433
  let select = this._createElement({
@@ -401,11 +435,13 @@ export default class GUI {
401
435
  el: 'select',
402
436
  class: 'p-gui__list-dropdown',
403
437
  onchange: (ev) => {
404
- params.callback(ev.target.value);
438
+ if (callback) {
439
+ callback(ev.target.value);
440
+ }
405
441
  }
406
442
  });
407
443
 
408
- list.forEach(item => {
444
+ values.forEach(item => {
409
445
  this._createElement({
410
446
  parent: select,
411
447
  el: 'option',
@@ -417,33 +453,40 @@ export default class GUI {
417
453
  });
418
454
  }
419
455
 
420
- vector2(text, data, callback) {
421
- this._checkMandatoryParams({
422
- text: 'string',
423
- data: 'object',
424
- }, {
425
- text,
426
- data,
427
- });
456
+ options(params, callback) {
457
+ if (typeof params != 'object') {
458
+ throw Error(`[GUI] options() first parameter must be an object. Received: ${typeof params}.`);
459
+ }
460
+ this.list(params, callback);
461
+ }
462
+
463
+ vector2( params = {}, callback) {
464
+ if (typeof params != 'object') {
465
+ throw Error(`[GUI] vector2() first parameter must be an object. Received: ${typeof params}.`);
466
+ }
467
+
468
+ let name = typeof params.name == 'string' ? params.name || ' ' : ' ';
428
469
 
429
- const minX = data.x.min ?? 0;
430
- const maxX = data.x.max ?? 1;
431
- const minY = data.y.min ?? 0;
432
- const maxY = data.y.max ?? 1;
470
+ const minX = params.x.min ?? 0;
471
+ const maxX = params.x.max ?? 1;
472
+ const minY = params.y.min ?? 0;
473
+ const maxY = params.y.max ?? 1;
433
474
 
434
- const objectX = data.x.obj || data.x.object;
435
- const propX = data.x.prop || data.x.property;
475
+ const objectX = params.x.obj || params.x.object;
476
+ const propX = params.x.prop || params.x.property;
436
477
  const propXReferenceIndex = this.propReferences.push(objectX[propX]) - 1;
437
478
 
438
- const objectY = data.y.obj || data.y.object;
439
- const propY = data.y.prop || data.y.property;
479
+ const objectY = params.y.obj || params.y.object;
480
+ const propY = params.y.prop || params.y.property;
440
481
  const propYReferenceIndex = this.propReferences.push(objectY[propY]) - 1;
441
482
 
483
+ callback = typeof callback == 'function' ? callback : null;
484
+
442
485
  this.imageContainer = null;
443
486
 
444
487
  const container = this._createElement({
445
488
  class: 'p-gui__vector2',
446
- textContent: text
489
+ textContent: name
447
490
  });
448
491
 
449
492
  const vector_value = this._createElement({
@@ -459,6 +502,10 @@ export default class GUI {
459
502
  onclick: (evt) => {
460
503
  objectX[propX] = parseFloat(this._mapLinear(evt.offsetX, 0, area.clientWidth, minX, maxX).toFixed(1));
461
504
  objectY[propY] = parseFloat(this._mapLinear(evt.offsetY, 0, area.clientHeight, maxY, minY).toFixed(1));
505
+
506
+ if (callback) {
507
+ callback(objectX[propX], objectX[propY]);
508
+ }
462
509
  },
463
510
  });
464
511
  let pointer_is_down = false;
@@ -472,6 +519,10 @@ export default class GUI {
472
519
  if (pointer_is_down) {
473
520
  objectX[propX] = parseFloat(this._mapLinear(evt.offsetX, 0, area.clientWidth, minX, maxX).toFixed(1));
474
521
  objectY[propY] = parseFloat(this._mapLinear(evt.offsetY, 0, area.clientHeight, maxY, minY).toFixed(1));
522
+
523
+ if (callback) {
524
+ callback(objectX[propX], objectX[propY]);
525
+ }
475
526
  }
476
527
  });
477
528
 
@@ -516,11 +567,27 @@ export default class GUI {
516
567
  });
517
568
  }
518
569
 
519
- color(text, value, callback) {
570
+ color(params = {}, callback) {
571
+ if (typeof params != 'object') {
572
+ throw Error(`[GUI] color() first parameter must be an object. Received: ${typeof params}.`);
573
+ }
574
+
575
+ let name = typeof params.name == 'string' ? params.name || ' ' : ' ';
576
+ let value;
577
+ if (typeof params.value == 'string') {
578
+ if (params.value.length != 7 || params.value[0] != '#') {
579
+ console.error(`[GUI] color() 'value' parameter must be an hexadecimal string in the format "#ffffff". Received: "${params.value}".`)
580
+ }
581
+ else {
582
+ value = params.value;
583
+ }
584
+ }
585
+ if (!value) value = '#000000';
586
+
520
587
  const container = this._createElement({
521
588
  el: 'div',
522
589
  class: 'p-gui__color',
523
- textContent: text,
590
+ textContent: name,
524
591
  });
525
592
 
526
593
  const colorpicker = this._createElement({
@@ -531,7 +598,7 @@ export default class GUI {
531
598
  value
532
599
  });
533
600
 
534
- if (callback) {
601
+ if (typeof callback == 'function') {
535
602
  colorpicker.addEventListener('input', () => {
536
603
  callback(colorpicker.value);
537
604
  });
@@ -576,21 +643,6 @@ export default class GUI {
576
643
  return folder;
577
644
  }
578
645
 
579
- _checkMandatoryParams(mandatoryParams, params) {
580
- var errors = [];
581
- for (var i in mandatoryParams) {
582
- let typeTest = typeof params[i] == mandatoryParams[i];
583
- if (!typeTest) {
584
- errors.push(i);
585
- }
586
- };
587
- if (errors.length > 0) {
588
- errors.forEach(error => {
589
- throw Error(`[GUI] Missing '${error}' parameter`);
590
- })
591
- }
592
- }
593
-
594
646
  _makeDraggable() {
595
647
  var that = this;
596
648
  this.header.addEventListener('mousedown', dragMouseDown);
package/src/styles.js CHANGED
@@ -18,7 +18,7 @@ return /* css */`
18
18
  font-family: Verdana, Arial, sans-serif;
19
19
  width: 290px;
20
20
  overflow: auto;
21
- box-shadow: 0 0 5px black;
21
+ box-shadow: 0 0 2px black;
22
22
  box-sizing: border-box;
23
23
  z-index: 99999;
24
24
  user-select: none;
@@ -67,17 +67,19 @@ return /* css */`
67
67
 
68
68
  .p-gui__image-container {
69
69
  width: 100%;
70
- display: flex;
71
- flex-wrap: wrap;
70
+ display: grid;
71
+ grid-template-columns: repeat(auto-fill, 32%);
72
+ justify-content: space-between;
73
+ padding: 0 2%;
72
74
  }
73
75
 
74
76
  .p-gui__image {
75
- width: calc(33.33% - 10px);
76
77
  aspect-ratio: 1 / 1;
77
78
  background-size: cover;
78
- margin: 1px 5px 19px 5px;
79
79
  cursor: pointer;
80
80
  position: relative;
81
+ margin-top: 1px;
82
+ margin-bottom: 19px;
81
83
  }
82
84
 
83
85
  .p-gui__image-text {
@@ -211,15 +213,23 @@ return /* css */`
211
213
  top: 0;
212
214
  bottom: 0;
213
215
  margin: auto;
214
- height: 18px;
216
+ height: 21px;
215
217
  cursor: pointer;
216
218
  }
217
219
 
220
+ .p-gui__list-dropdown {
221
+ background: rgba(0,0,0,.25);
222
+ color: white;
223
+ border: 1px solid rgba(0,0,0,.5);
224
+ border-radius: 3px;
225
+ padding: 0 12px;
226
+ top: -1px;
227
+ }
228
+
218
229
  .p-gui__color-picker {
219
230
  -webkit-appearance: none;
220
231
  padding: 0;
221
232
  background-color: transparent;
222
- height: 15px;
223
233
  border: 1px solid #222222;
224
234
  }
225
235
 
@@ -290,7 +300,6 @@ return /* css */`
290
300
  background: #434343;
291
301
  overflow: hidden;
292
302
  margin-bottom: 3px;
293
- padding-bottom: 1px;
294
303
  display: flex;
295
304
  flex-wrap: wrap;
296
305
  border-left: 2px solid grey;