perfect-gui 4.9.8 → 4.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perfect-gui",
3
- "version": "4.9.8",
3
+ "version": "4.10.0",
4
4
  "description": "GUI for JavaScript",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/Slider.js ADDED
@@ -0,0 +1,242 @@
1
+ export default class Slider {
2
+ constructor(params = {}, callback) {
3
+ this.propReferences = [];
4
+
5
+ if (typeof params != 'object') {
6
+ throw Error(`[GUI] slider() first parameter must be an object. Received: ${typeof params}.`);
7
+ }
8
+
9
+ let name = typeof params.name == 'string' ? params.name || ' ' : ' ';
10
+ this.isObject = false;
11
+ let propReferenceIndex = null;
12
+ this.obj = params.obj;
13
+ this.prop = params.prop;
14
+ let value = typeof params.value == 'number' ? params.value : null;
15
+ this.min = params.min ?? 0;
16
+ this.max = params.max ?? 1;
17
+ this.step = params.step || (this.max - this.min) / 100;
18
+
19
+ // callback mode
20
+ if ( value !== null ) {
21
+ if (this.prop != undefined || this.obj != undefined) {
22
+ console.warn(`[GUI] slider() "obj" and "prop" parameters are ignored when a "value" parameter is used.`);
23
+ }
24
+ }
25
+ // object-binding
26
+ else if (this.prop != undefined && this.obj != undefined) {
27
+ if (typeof this.prop != 'string') {
28
+ throw Error(`[GUI] slider() "prop" parameter must be an string. Received: ${typeof this.prop}.`);
29
+ }
30
+ if (typeof this.obj != 'object') {
31
+ throw Error(`[GUI] slider() "obj" parameter must be an object. Received: ${typeof this.obj}.`);
32
+ }
33
+
34
+ if (name == ' ') {
35
+ name = this.prop;
36
+ }
37
+
38
+ propReferenceIndex = this.propReferences.push(this.obj[this.prop]) - 1;
39
+ this.isObject = true;
40
+ }
41
+ else {
42
+ if ((this.prop != undefined && this.obj == undefined) || (this.prop == undefined && this.obj != undefined)) {
43
+ console.warn(`[GUI] slider() "obj" and "prop" parameters must be used together.`);
44
+ }
45
+
46
+ value = (this.max - this.min) / 2;
47
+ }
48
+
49
+ this.imageContainer = null;
50
+
51
+ const container = document.createElement('div');
52
+ container.className = 'p-gui__slider';
53
+
54
+ const slider_name = document.createElement('div');
55
+ slider_name.className = 'p-gui__slider-name';
56
+ slider_name.textContent = name;
57
+ container.append(slider_name);
58
+
59
+ this.ctrlDiv = document.createElement('div');
60
+ this.ctrlDiv.className = 'p-gui__slider-ctrl';
61
+ this.ctrlDiv.setAttribute('type', 'range');
62
+ this.ctrlDiv.setAttribute('min', this.min);
63
+ this.ctrlDiv.setAttribute('max', this.max);
64
+ container.append(this.ctrlDiv);
65
+
66
+ const slider_bar = document.createElement('div');
67
+ slider_bar.className = 'p-gui__slider-bar';
68
+ this.ctrlDiv.append(slider_bar);
69
+
70
+ this.handle = document.createElement('div');
71
+ this.handle.className = 'p-gui__slider-handle';
72
+ this.ctrlDiv.append(this.handle);
73
+
74
+ this.filling = document.createElement('div');
75
+ this.filling.className = 'p-gui__slider-filling';
76
+ slider_bar.append(this.filling);
77
+
78
+ this.valueInput = document.createElement('input');
79
+ this.valueInput.className = 'p-gui__slider-value';
80
+ this.valueInput.value = this.isObject ? this.obj[this.prop] : value;
81
+ container.append(this.valueInput);
82
+
83
+ // init position
84
+ setTimeout(() => {
85
+ const handleWidth = this.handle.offsetWidth;
86
+ this.handle.position = this._mapLinear(this.valueInput.value, this.min, this.max, handleWidth / 2, 88 - handleWidth / 2);
87
+ this.handle.style.transform = `translate(-50%, -50%) translateX(${this.handle.position}px)`;
88
+ this.filling.style.width = `${this.handle.position}px`;
89
+ }, 0); // wait for render
90
+
91
+ this.valueInput.addEventListener('change', () => {
92
+ this._updateHandlePositionFromValue();
93
+ this._triggerCallbacks();
94
+ })
95
+
96
+ this.ctrlDiv.addEventListener('pointerdown', (evt) => {
97
+ this.ctrlDiv.pointerDown = true;
98
+ this.ctrlDiv.prevPosition = evt.clientX;
99
+ this._updateHandlePositionFromPointer(evt, true);
100
+ });
101
+
102
+ window.addEventListener('pointerup', (evt) => {
103
+ this.ctrlDiv.pointerDown = false;
104
+ });
105
+
106
+ window.addEventListener('pointermove', (evt) => {
107
+ if (this.ctrlDiv.pointerDown) {
108
+ this.ctrlDiv.pointerDelta = evt.clientX - this.ctrlDiv.prevPosition;
109
+ this._updateHandlePositionFromPointer(evt);
110
+ }
111
+ });
112
+
113
+ if ( this.isObject ) {
114
+ Object.defineProperty( this.obj, this.prop, {
115
+ set: val => {
116
+ this.propReferences[propReferenceIndex] = val;
117
+ this.valueInput.value = val;
118
+
119
+ this._updateHandlePositionFromValue();
120
+
121
+ if (typeof callback == "function") {
122
+ callback(parseFloat(this.valueInput.value));
123
+ }
124
+ },
125
+ get: () => {
126
+ return this.propReferences[propReferenceIndex];
127
+ }
128
+ });
129
+ }
130
+
131
+ return container;
132
+ }
133
+
134
+ _updateHandlePositionFromPointer(evt, firstDown = false) {
135
+ const sliderWidth = this.ctrlDiv.offsetWidth;
136
+ const handleWidth = this.handle.offsetWidth;
137
+ const pointerDelta = evt.clientX - this.ctrlDiv.prevPosition;
138
+ let handlePosition;
139
+ const currentValue = parseFloat(this.valueInput.value);
140
+
141
+ if (firstDown) {
142
+ handlePosition = evt.offsetX;
143
+ } else {
144
+ handlePosition = this.handle.position + pointerDelta;
145
+ }
146
+
147
+ handlePosition = Math.max(handleWidth / 2, Math.min(handlePosition, sliderWidth - handleWidth / 2));
148
+
149
+ let newValue = this.min + (this.max - this.min) * (handlePosition - handleWidth / 2) / (sliderWidth - handleWidth);
150
+ if ( newValue > currentValue ) {
151
+ newValue = this._quantizeFloor(newValue, this.step);
152
+ } else {
153
+ newValue = this._quantizeCeil(newValue, this.step);
154
+ }
155
+
156
+ // toFixed(9) avoids weird javascript infinite decimals
157
+ newValue = parseFloat(newValue.toFixed(9));
158
+ const nextValue = parseFloat((currentValue + this.step).toFixed(9));
159
+ const prevValue = parseFloat((currentValue - this.step).toFixed(9));
160
+
161
+ if (newValue >= nextValue || newValue <= prevValue) {
162
+ const decimals = this._countDecimals(this.step);
163
+ newValue = newValue.toFixed(decimals);
164
+
165
+ this.valueInput.value = newValue;
166
+
167
+ this.ctrlDiv.prevPosition = evt.clientX;
168
+
169
+ this.handle.style.transform = `translate(-50%, -50%) translateX(${handlePosition}px)`;
170
+ this.handle.position = handlePosition;
171
+
172
+ this.filling.style.width = this.handle.position + 'px';
173
+
174
+ this._triggerCallbacks();
175
+ }
176
+ }
177
+
178
+ _countDecimals(num) {
179
+ // Convert the number to a string
180
+ const numStr = num.toString();
181
+
182
+ // Find the position of the decimal point
183
+ const decimalIndex = numStr.indexOf('.');
184
+
185
+ // If there is no decimal point, return 0
186
+ if (decimalIndex === -1) {
187
+ return 0;
188
+ }
189
+
190
+ // Calculate the number of digits after the decimal point
191
+ const decimalPlaces = numStr.length - decimalIndex - 1;
192
+
193
+ return decimalPlaces;
194
+ }
195
+
196
+ _updateHandlePositionFromValue() {
197
+ const sliderWidth = this.ctrlDiv.offsetWidth;
198
+ const handleWidth = this.handle.offsetWidth;
199
+ let handlePosition = this._mapLinear(this.valueInput.value, this.min, this.max, handleWidth / 2, sliderWidth - handleWidth / 2);
200
+
201
+ handlePosition = Math.max(handleWidth / 2, Math.min(handlePosition, sliderWidth - handleWidth / 2));
202
+
203
+ this.handle.style.transform = `translate(-50%, -50%) translateX(${handlePosition}px)`;
204
+ this.handle.position = handlePosition;
205
+
206
+ this.filling.style.width = this.handle.position + 'px';
207
+ }
208
+
209
+ _triggerCallbacks() {
210
+
211
+ if ( this.isObject ) {
212
+ this.obj[this.prop] = parseFloat(this.valueInput.value);
213
+ }
214
+ else {
215
+ if (typeof callback == "function") {
216
+ callback(parseFloat(this.valueInput.value));
217
+ }
218
+ }
219
+
220
+ if (this.onUpdate) {
221
+ this.onUpdate();
222
+ } else if (this.isFolder && this.firstParent.onUpdate) {
223
+ this.firstParent.onUpdate();
224
+ }
225
+ }
226
+
227
+ _mapLinear( x, a1, a2, b1, b2 ) {
228
+ return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
229
+ }
230
+
231
+ _quantize(x, step) {
232
+ return step * Math.round(x / step);
233
+ }
234
+
235
+ _quantizeCeil(x, step) {
236
+ return step * Math.ceil(x / step);
237
+ }
238
+
239
+ _quantizeFloor(x, step) {
240
+ return step * Math.floor(x / step);
241
+ }
242
+ }
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
- import styles from './styles';
1
+ import Slider from './Slider';
2
+ import styles from './styles/styles';
2
3
 
3
4
  export default class GUI {
4
5
  constructor(options = {}) {
@@ -322,8 +323,13 @@ export default class GUI {
322
323
 
323
324
  return image;
324
325
  }
325
-
326
+
326
327
  slider (params = {}, callback) {
328
+ const el = new Slider(params, callback);
329
+ this.wrapper.append(el);
330
+ }
331
+
332
+ sliderOld (params = {}, callback) {
327
333
  if (typeof params != 'object') {
328
334
  throw Error(`[GUI] slider() first parameter must be an object. Received: ${typeof params}.`);
329
335
  }
@@ -372,8 +378,12 @@ export default class GUI {
372
378
 
373
379
  const container = document.createElement('div');
374
380
  container.className = 'p-gui__slider';
375
- container.textContent = name;
376
381
  this.wrapper.append(container);
382
+
383
+ const slider_name = document.createElement('div');
384
+ slider_name.className = 'p-gui__slider-name';
385
+ slider_name.textContent = name;
386
+ container.append(slider_name);
377
387
 
378
388
  const slider_ctrl = document.createElement('input');
379
389
  slider_ctrl.className = 'p-gui__slider-ctrl';
@@ -384,13 +394,17 @@ export default class GUI {
384
394
  slider_ctrl.setAttribute('value', isObject ? obj[prop] : value);
385
395
  container.append(slider_ctrl);
386
396
 
387
- const slider_value = document.createElement('div');
397
+ const slider_value = document.createElement('input');
388
398
  slider_value.className = 'p-gui__slider-value';
389
- slider_value.textContent = isObject ? String(obj[prop]) : String(value);
399
+ slider_value.value = isObject ? String(obj[prop]) : String(value);
390
400
  container.append(slider_value);
401
+
402
+ slider_value.addEventListener('change', () => {
403
+ slider_ctrl.value = parseFloat(slider_value.value);
404
+ })
391
405
 
392
406
  slider_ctrl.addEventListener('input', () => {
393
- slider_value.textContent = slider_ctrl.value;
407
+ slider_value.value = slider_ctrl.value;
394
408
 
395
409
  if ( isObject ) {
396
410
  obj[prop] = parseFloat(slider_ctrl.value);
@@ -995,6 +1009,14 @@ export default class GUI {
995
1009
 
996
1010
  toggleClose() {
997
1011
  this.closed = !this.closed;
1012
+
1013
+ if (this.closed) {
1014
+ this.previousInnerScroll = this.wrapper.scrollTop;
1015
+ this.wrapper.scrollTo(0,0);
1016
+ } else {
1017
+ this.wrapper.scrollTo(0,this.previousInnerScroll);
1018
+ }
1019
+
998
1020
  this.wrapper.classList.toggle('p-gui--collapsed');
999
1021
  }
1000
1022
 
@@ -0,0 +1,21 @@
1
+ export default /* css */ `
2
+ .p-gui__button {
3
+ background: var(--color-accent);
4
+ text-align: center;
5
+ color: white;
6
+ border: none;
7
+ border: 1px solid transparent;
8
+ box-sizing: border-box;
9
+ transition: var(--transition) background, var(--transition) border-color;
10
+ }
11
+
12
+ .p-gui__button:hover {
13
+ background: var(--color-accent-hover);
14
+ color: var(--color-text-light);
15
+ border-color: rgba(255, 255, 255, 0.2);
16
+ }
17
+
18
+ .p-gui__folder .p-gui__button {
19
+ margin-inline: 0;
20
+ }
21
+ `;
@@ -0,0 +1,36 @@
1
+ export default /* css */ `
2
+ .p-gui__color {
3
+ cursor: default;
4
+ color: var(--color-text-dark);
5
+ transition: var(--transition) color;
6
+ }
7
+
8
+ .p-gui__color:hover {
9
+ color: var(--color-text-light);
10
+ }
11
+
12
+ .p-gui__color-picker {
13
+ position: absolute;
14
+ right: 5px;
15
+ top: 0;
16
+ bottom: 0;
17
+ margin: auto;
18
+ height: 21px;
19
+ cursor: pointer;
20
+ border-radius: 3px;
21
+ border: 1px solid var(--color-border-2);
22
+ outline: none;
23
+ -webkit-appearance: none;
24
+ padding: 0;
25
+ background-color: transparent;
26
+ border: 1px solid #222222;
27
+ overflow: hidden;
28
+ }
29
+
30
+ .p-gui__color-picker::-webkit-color-swatch-wrapper {
31
+ padding: 0;
32
+ }
33
+ .p-gui__color-picker::-webkit-color-swatch {
34
+ border: none;
35
+ }
36
+ `;
@@ -0,0 +1,56 @@
1
+ export default /* css */ `
2
+ .p-gui__folder {
3
+ width: 100%;
4
+ position: relative;
5
+ background: #434343;
6
+ overflow: auto;
7
+ margin-bottom: 3px;
8
+ display: flex;
9
+ flex-wrap: wrap;
10
+ border: 1px solid grey;
11
+ padding: 0 3px 3px 3px;
12
+ border-radius: var(--main-border-radius);
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ .p-gui__folder:last-of-type {
17
+ margin-bottom: 0;
18
+ border-bottom: none;
19
+ }
20
+
21
+ .p-gui__folder--first {
22
+ margin-top: 0;
23
+ }
24
+
25
+ .p-gui__folder--closed {
26
+ height: 32px;
27
+ overflow: hidden;
28
+ }
29
+
30
+ .p-gui__folder-header {
31
+ padding: 10px 5px;
32
+ background-color: rgba(0, 0, 0, .5);
33
+ color: white;
34
+ cursor: pointer;
35
+ width: 100%;
36
+ margin: 0 -2px 2px -3px;
37
+ }
38
+
39
+ .p-gui__folder-header:hover {
40
+ background-color: rgba(0, 0, 0, .75);
41
+ }
42
+
43
+ .p-gui__folder-arrow {
44
+ width: 8px;
45
+ height: 8px;
46
+ display: inline-block;
47
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAHlBMVEUAAAD///////////////////////////////////8kfJuVAAAACXRSTlMA9Z1fCdMo1yxEJnA0AAAAK0lEQVQI12PABlRgjKkJUMZMYRhjpgqMAZSEMICSaIzpDWiKhdENhEhgAgATSg5jyWnYewAAAABJRU5ErkJggg==);
48
+ background-size: contain;
49
+ margin-right: 5px;
50
+ transform: rotate(90deg)
51
+ }
52
+
53
+ .p-gui__folder--closed .p-gui__folder-arrow {
54
+ transform: rotate(0deg);
55
+ }
56
+ `;
@@ -0,0 +1,54 @@
1
+ export default /* css */ `
2
+ .p-gui__image-container {
3
+ width: 100%;
4
+ padding: 3px;
5
+ display: flex;
6
+ justify-content: flex-start;
7
+ flex-wrap: wrap;
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ .p-gui__image {
12
+ background-size: cover;
13
+ cursor: pointer;
14
+ position: relative;
15
+ margin: 1px 2.5px 19px 2.5px;
16
+ border-radius: var(--main-border-radius);
17
+ flex: 0 0 calc(33.333% - 5px);
18
+ height: 90px;
19
+ background-position: center;
20
+ color: var(--color-text-dark);
21
+ transition: var(--transition) color;
22
+ }
23
+
24
+ .p-gui__image:hover {
25
+ color: var(--color-text-text);
26
+ }
27
+
28
+ .p-gui__image::after {
29
+ position: absolute;
30
+ top: 0;
31
+ left: 0;
32
+ width: 100%;
33
+ height: 100%;
34
+ content: '';
35
+ border: 1px solid transparent;
36
+ box-sizing: border-box;
37
+ border-radius: var(--main-border-radius);
38
+ transition: var(--transition) border-color;
39
+ }
40
+ .p-gui__image--selected::after {
41
+ border-color: #06FF89;
42
+ }
43
+
44
+ .p-gui__image-text {
45
+ position: absolute;
46
+ bottom: -15px;
47
+ text-shadow: 0 -1px 0 #111;
48
+ white-space: nowrap;
49
+ width: 100%;
50
+ overflow: hidden;
51
+ text-overflow: ellipsis;
52
+
53
+ }
54
+ `;
@@ -0,0 +1,35 @@
1
+ export default /* css */ `
2
+ .p-gui__list {
3
+ cursor: default;
4
+ color: var(--color-text-dark);
5
+ transition: var(--transition) color;
6
+ }
7
+
8
+ .p-gui__list:hover {
9
+ color: var(--color-text-light);
10
+ }
11
+
12
+ .p-gui__list-dropdown {
13
+ background: rgba(255, 255, 255,.05);
14
+ color: white;
15
+ padding: 0 12px 0 5px;
16
+ top: 0px;
17
+ }
18
+
19
+ .p-gui__list-dropdown {
20
+ position: absolute;
21
+ right: 5px;
22
+ top: 0;
23
+ bottom: 0;
24
+ margin: auto;
25
+ height: 21px;
26
+ cursor: pointer;
27
+ border-radius: 3px;
28
+ border: 1px solid var(--color-border-2);
29
+ outline: none;
30
+ }
31
+
32
+ .p-gui__list-dropdown:hover {
33
+ background: rgba(255, 255, 255, .1);
34
+ }
35
+ `;
@@ -0,0 +1,92 @@
1
+ export default /* css */ `
2
+ .p-gui__slider {
3
+ position: relative;
4
+ min-height: 14px;
5
+ display: flex;
6
+ align-items: center;
7
+ justify-content: space-between;
8
+ gap: 10px;
9
+ color: var(--color-text-dark);
10
+ transition: color var(--transition);
11
+ }
12
+
13
+ .p-gui__slider:hover {
14
+ color: var(--color-text-light);
15
+ }
16
+
17
+ .p-gui__slider-name {
18
+ width: 50%;
19
+ text-overflow: ellipsis;
20
+ overflow: hidden;
21
+ }
22
+
23
+ .p-gui__slider-ctrl {
24
+ -webkit-appearance: none;
25
+ padding: 0;
26
+ font: inherit;
27
+ outline: none;
28
+ box-sizing: border-box;
29
+ cursor: pointer;
30
+ position: relative;
31
+ right: 0;
32
+ height: 14px;
33
+ margin: 0 0 0 auto;
34
+ width: 37%;
35
+ }
36
+
37
+ .p-gui__slider-bar {
38
+ position: absolute;
39
+ top: 50%;
40
+ left: 0;
41
+ height: 2px;
42
+ background: rgba(255, 255, 255, .2);
43
+ width: 100%;
44
+ transform: translateY(-50%);
45
+ }
46
+
47
+ .p-gui__slider-filling {
48
+ position: absolute;
49
+ top: -25%;
50
+ left: 0;
51
+ height: 150%;
52
+ background: var(--color-accent);
53
+ width: 0;
54
+ }
55
+
56
+ .p-gui__slider:hover .p-gui__slider-filling {
57
+ background: var(--color-accent-hover);
58
+ }
59
+
60
+ .p-gui__slider-handle {
61
+ width: 15px;
62
+ height: 8px;
63
+ position: absolute;
64
+ top: 50%;
65
+ left: 0;
66
+ border-radius: 2px;
67
+ transform: translate(-50%, -50%);
68
+ pointer-events: none;
69
+ background: var(--color-text-dark);
70
+ box-shadow: 0 0 2px rgba(0, 0, 0, .5);
71
+ }
72
+
73
+ .p-gui__slider:hover .p-gui__slider-handle {
74
+ background: var(--color-text-light);
75
+ }
76
+
77
+ .p-gui__slider-value {
78
+ display: inline-block;
79
+ right: 7px;
80
+ width: 13%;
81
+ border: none;
82
+ color: white;
83
+ border-radius: 2px;
84
+ background: rgba(255, 255, 255, 0.1);
85
+ padding: 2px 4px;
86
+ color: inherit;
87
+ }
88
+
89
+ .p-gui__slider-value:focus {
90
+ outline: none;
91
+ }
92
+ `;
@@ -0,0 +1,35 @@
1
+ export default /* css */ `
2
+ .p-gui__switch {
3
+ background: rgba(255, 255, 255, .05);
4
+ color: var(--color-text-dark);
5
+ transition: var(--transition) background, var(--transition) color;
6
+ }
7
+
8
+ .p-gui__switch:hover {
9
+ background: rgba(255, 255, 255, .1);
10
+ color: var(--color-text-light);
11
+ }
12
+
13
+ .p-gui__folder .p-gui__switch {
14
+ margin-inline: 0;
15
+ }
16
+
17
+ .p-gui__switch-checkbox {
18
+ width: 5px;
19
+ height: 5px;
20
+ background-color: rgba(0, 0, 0, .5);
21
+ border: 1px solid grey;
22
+ position: absolute;
23
+ top: 0;
24
+ right: 10px;
25
+ bottom: 0;
26
+ margin: auto;
27
+ border-radius: 50%;
28
+ pointer-events: none;
29
+ }
30
+
31
+ .p-gui__switch-checkbox--active {
32
+ background-color: #00ff89;
33
+ box-shadow: 0 0 7px #00ff89;
34
+ }
35
+ `;