├── .gitignore
├── LICENSE
├── README.md
├── dist
├── perfect-gui.mjs
└── perfect-gui.umd.js
├── package-lock.json
├── package.json
├── src
├── addons
│ └── three.js
├── components
│ └── Slider.js
├── index.js
└── styles
│ ├── _button.css.js
│ ├── _color.css.js
│ ├── _folder.css.js
│ ├── _image.css.js
│ ├── _list.css.js
│ ├── _slider.css.js
│ ├── _switch.css.js
│ ├── _vector2.css.js
│ └── styles.js
└── vite.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | Thumbs.db
4 | build
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Thibaut Foussard
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Perfect GUI
2 | A nice, simple and (probably not) perfect GUI for JavaScript.
3 |
4 | ## API
5 | [Documentation](https://thibka.github.io/perfect-gui/public/)
6 |
7 |
8 | ## Features
9 | - image buttons
10 | - multiple panels
11 | - easy positioning
12 | - draggable panels
13 | - two-dimensional vector visualization
14 |
15 |
16 |
17 | ## Install
18 |
19 | ### With NPM
20 |
21 | ```bash
22 | npm i perfect-gui
23 | ```
24 |
25 | ### Import from a CDN
26 |
27 | ```javascript
28 |
35 | ```
36 |
37 | ## Hello world
38 |
39 | ```javascript
40 | import GUI from 'perfect-gui';
41 |
42 | const gui = new GUI();
43 |
44 | gui.button('Click me', callback);
45 | ```
46 |
47 | ## Options
48 | ```javascript
49 | const gui = new GUI({
50 | name: 'My GUI',
51 | // Name of the panel.
52 | // Default is null.
53 |
54 | container: '#container',
55 | // Element containing the GUI
56 | // Default is document.body
57 |
58 | width: 250,
59 | // Width of the panel (in pixels).
60 | // Default is 290.
61 |
62 | maxHeight: 500,
63 | // Maximum height beyond which scrolling is necessary.
64 | // Default is the smallest value between the height of the window and the height of the container.
65 |
66 | closed: false,
67 | // Defines whether the panel should be closed by default.
68 | // Default is false.
69 |
70 | position: 'bottom right',
71 | // Defines where to place the panel on screen.
72 | // Accepted values are 'top', 'bottom', 'left' and 'right' in no particular order ('bottom right' = 'right bottom').
73 | // If multiple instances have the same position, they will be stacked horizontally.
74 | // Default is 'top right'.
75 |
76 | draggable: false,
77 | // Defines if the panel can be manually moved across the screen.
78 | // Default is false.
79 |
80 | autoRepositioning: true,
81 | // If set to true, the panel position will be reset when the screen is resized.
82 | // If a panel has been dragged, it won't be be affected.
83 | // Default is true.
84 |
85 | color: '#bada55',
86 | // Default is #2e2e2e
87 |
88 | onUpdate: () => {
89 | // Callback function triggered each time this GUI instance is updated.
90 | }
91 | });
92 | ```
93 |
94 | ## [Methods](https://thibka.github.io/perfect-gui/public/)
95 |
96 | * [button](https://thibka.github.io/perfect-gui/public/#button)
97 | * [slider](https://thibka.github.io/perfect-gui/public/#slider)
98 | * [toggle](https://thibka.github.io/perfect-gui/public/#toggle)
99 | * [list](https://thibka.github.io/perfect-gui/public/#list)
100 | * [image](https://thibka.github.io/perfect-gui/public/#image)
101 | * [color](https://thibka.github.io/perfect-gui/public/#color)
102 | * [vector2](https://thibka.github.io/perfect-gui/public/#vector2)
103 | * [folder](https://thibka.github.io/perfect-gui/public/#folder)
104 | * [toggleClose](https://thibka.github.io/perfect-gui/public)
--------------------------------------------------------------------------------
/dist/perfect-gui.mjs:
--------------------------------------------------------------------------------
1 | class R {
2 | constructor(e, t = {}, i) {
3 | if (this.parent = e, this.propReferences = [], typeof t != "object")
4 | throw Error(`[GUI] slider() first parameter must be an object. Received: ${typeof t}.`);
5 | let a = typeof t.name == "string" && t.name || " ";
6 | this.isObject = !1;
7 | let s = null;
8 | this.obj = t.obj, this.prop = t.prop;
9 | let o = typeof t.value == "number" ? t.value : null;
10 | if (this.min = t.min ?? 0, this.max = t.max ?? 1, this.step = t.step || (this.max - this.min) / 100, this.decimals = this.parent._countDecimals(this.step), this.callback = typeof i == "function" ? i : null, o !== null)
11 | (this.prop != null || this.obj != null) && console.warn('[GUI] slider() "obj" and "prop" parameters are ignored when a "value" parameter is used.');
12 | else if (this.prop != null && this.obj != null) {
13 | if (typeof this.prop != "string")
14 | throw Error(`[GUI] slider() "prop" parameter must be an string. Received: ${typeof this.prop}.`);
15 | if (typeof this.obj != "object")
16 | throw Error(`[GUI] slider() "obj" parameter must be an object. Received: ${typeof this.obj}.`);
17 | a == " " && (a = this.prop), s = this.propReferences.push(this.obj[this.prop]) - 1, this.isObject = !0;
18 | } else
19 | (this.prop != null && this.obj == null || this.prop == null && this.obj != null) && console.warn('[GUI] slider() "obj" and "prop" parameters must be used together.'), o = (this.max - this.min) / 2;
20 | const r = typeof t.tooltip == "string" ? t.tooltip : t.tooltip === !0 ? a : null;
21 | this.imageContainer = null;
22 | const n = document.createElement("div");
23 | n.className = "p-gui__slider", r && n.setAttribute("title", r);
24 | const c = document.createElement("div");
25 | c.className = "p-gui__slider-name", c.textContent = a, n.append(c), this.ctrlDiv = document.createElement("div"), this.ctrlDiv.className = "p-gui__slider-ctrl", this.ctrlDiv.setAttribute("type", "range"), this.ctrlDiv.setAttribute("min", this.min), this.ctrlDiv.setAttribute("max", this.max), n.append(this.ctrlDiv);
26 | const l = document.createElement("div");
27 | return l.className = "p-gui__slider-bar", this.ctrlDiv.append(l), this.handle = document.createElement("div"), this.handle.className = "p-gui__slider-handle", this.ctrlDiv.append(this.handle), this.filling = document.createElement("div"), this.filling.className = "p-gui__slider-filling", l.append(this.filling), this.valueInput = document.createElement("input"), this.valueInput.className = "p-gui__slider-value", this.valueInput.value = this.isObject ? this.obj[this.prop] : o, n.append(this.valueInput), setTimeout(() => {
28 | const d = this.handle.offsetWidth;
29 | this.handle.position = this._mapLinear(this.valueInput.value, this.min, this.max, d / 2, 88 - d / 2), this.handle.position = Math.min(this.handle.position, 88 - d / 2), this.handle.position = Math.max(this.handle.position, d / 2), this.handle.style.transform = `translate(-50%, -50%) translateX(${this.handle.position}px)`, this.filling.style.width = `${this.handle.position}px`;
30 | }, 0), this.valueInput.addEventListener("change", () => {
31 | this._updateHandlePositionFromValue(), this._triggerCallbacks();
32 | }), this.ctrlDiv.addEventListener("pointerdown", (d) => {
33 | this.ctrlDiv.pointerDown = !0, this.ctrlDiv.prevPosition = d.clientX, this._updateHandlePositionFromPointer(d, !0);
34 | }), window.addEventListener("pointerup", (d) => {
35 | this.ctrlDiv.pointerDown = !1;
36 | }), window.addEventListener("pointermove", (d) => {
37 | this.ctrlDiv.pointerDown && (this.ctrlDiv.pointerDelta = d.clientX - this.ctrlDiv.prevPosition, this._updateHandlePositionFromPointer(d));
38 | }), this.isObject && Object.defineProperty(this.obj, this.prop, {
39 | set: (d) => {
40 | this.propReferences[s] = d, this.valueInput.value = d, this._updateHandlePositionFromValue(), this.callback && this.callback(parseFloat(this.valueInput.value));
41 | },
42 | get: () => this.propReferences[s]
43 | }), n;
44 | }
45 | _updateHandlePositionFromPointer(e, t = !1) {
46 | const i = this.ctrlDiv.offsetWidth, a = this.handle.offsetWidth, s = e.clientX - this.ctrlDiv.prevPosition, o = parseFloat(this.valueInput.value);
47 | let r;
48 | t ? r = e.offsetX : r = this.handle.position + s, r = Math.max(a / 2, Math.min(r, i - a / 2));
49 | let n = this.min + (this.max - this.min) * (r - a / 2) / (i - a);
50 | n > o ? n = this._quantizeFloor(n, this.step) : n = this._quantizeCeil(n, this.step), n = parseFloat(n.toFixed(9));
51 | const c = parseFloat((o + this.step).toFixed(9)), l = parseFloat((o - this.step).toFixed(9));
52 | (n >= c || n <= l) && (n = n.toFixed(this.decimals), this.valueInput.value = n, this.ctrlDiv.prevPosition = e.clientX, this.handle.style.transform = `translate(-50%, -50%) translateX(${r}px)`, this.handle.position = r, this.filling.style.width = this.handle.position + "px", this._triggerCallbacks());
53 | }
54 | _updateHandlePositionFromValue() {
55 | const e = this.ctrlDiv.offsetWidth, t = this.handle.offsetWidth;
56 | let i = this._mapLinear(this.valueInput.value, this.min, this.max, t / 2, e - t / 2);
57 | i = Math.max(t / 2, Math.min(i, e - t / 2)), this.handle.style.transform = `translate(-50%, -50%) translateX(${i}px)`, this.handle.position = i, this.filling.style.width = this.handle.position + "px";
58 | }
59 | _triggerCallbacks() {
60 | this.isObject ? this.obj[this.prop] = parseFloat(this.valueInput.value) : this.callback && this.callback(parseFloat(this.valueInput.value)), this.parent.onUpdate ? this.parent.onUpdate() : this.parent.isFolder && this.parent.firstParent.onUpdate && this.parent.firstParent.onUpdate();
61 | }
62 | _mapLinear(e, t, i, a, s) {
63 | return a + (e - t) * (s - a) / (i - t);
64 | }
65 | _quantize(e, t) {
66 | return t * Math.round(e / t);
67 | }
68 | _quantizeCeil(e, t) {
69 | return t * Math.ceil(e / t);
70 | }
71 | _quantizeFloor(e, t) {
72 | return t * Math.floor(e / t);
73 | }
74 | }
75 | const P = (
76 | /* css */
77 | `
78 | .p-gui__button {
79 | background: var(--color-accent);
80 | text-align: center;
81 | color: white;
82 | border: none;
83 | border: 1px solid transparent;
84 | box-sizing: border-box;
85 | transition: var(--transition) background, var(--transition) border-color;
86 | }
87 |
88 | .p-gui__button:hover {
89 | background: var(--color-accent-hover);
90 | color: var(--color-text-light);
91 | border-color: rgba(255, 255, 255, 0.2);
92 | }
93 |
94 | .p-gui__folder .p-gui__button {
95 | margin-inline: 0;
96 | }
97 | `
98 | ), O = (
99 | /* css */
100 | `
101 | .p-gui__slider {
102 | position: relative;
103 | min-height: 14px;
104 | display: flex;
105 | align-items: center;
106 | justify-content: space-between;
107 | gap: 10px;
108 | color: var(--color-text-dark);
109 | transition: color var(--transition);
110 | }
111 |
112 | .p-gui__slider:hover {
113 | color: var(--color-text-light);
114 | }
115 |
116 | .p-gui__slider-name {
117 | width: 50%;
118 | text-overflow: ellipsis;
119 | overflow: hidden;
120 | }
121 |
122 | .p-gui__slider-ctrl {
123 | -webkit-appearance: none;
124 | padding: 0;
125 | font: inherit;
126 | outline: none;
127 | box-sizing: border-box;
128 | cursor: pointer;
129 | position: relative;
130 | right: 0;
131 | height: 14px;
132 | margin: 0 0 0 auto;
133 | width: 37%;
134 | }
135 |
136 | .p-gui__slider-bar {
137 | position: absolute;
138 | top: 50%;
139 | left: 0;
140 | height: 2px;
141 | background: rgba(255, 255, 255, .2);
142 | width: 100%;
143 | transform: translateY(-50%);
144 | }
145 |
146 | .p-gui__slider-filling {
147 | position: absolute;
148 | top: -25%;
149 | left: 0;
150 | height: 150%;
151 | background: var(--color-accent);
152 | width: 0;
153 | }
154 |
155 | .p-gui__slider:hover .p-gui__slider-filling {
156 | background: var(--color-accent-hover);
157 | }
158 |
159 | .p-gui__slider-handle {
160 | width: 15px;
161 | height: 8px;
162 | position: absolute;
163 | top: 50%;
164 | left: 0;
165 | border-radius: 2px;
166 | transform: translate(-50%, -50%);
167 | pointer-events: none;
168 | background: var(--color-text-dark);
169 | box-shadow: 0 0 2px rgba(0, 0, 0, .5);
170 | }
171 |
172 | .p-gui__slider:hover .p-gui__slider-handle {
173 | background: var(--color-text-light);
174 | }
175 |
176 | .p-gui__slider-value {
177 | display: inline-block;
178 | right: 7px;
179 | width: 13%;
180 | border: none;
181 | color: white;
182 | border-radius: 2px;
183 | background: rgba(255, 255, 255, 0.1);
184 | padding: 2px 4px;
185 | color: inherit;
186 | }
187 |
188 | .p-gui__slider-value:focus {
189 | outline: none;
190 | }
191 | `
192 | ), $ = (
193 | /* css */
194 | `
195 | .p-gui__list {
196 | cursor: default;
197 | color: var(--color-text-dark);
198 | transition: var(--transition) color;
199 | }
200 |
201 | .p-gui__list:hover {
202 | color: var(--color-text-light);
203 | }
204 |
205 | .p-gui__list-dropdown {
206 | background: rgba(255, 255, 255,.05);
207 | color: white;
208 | padding: 0 12px 0 5px;
209 | top: 0px;
210 | }
211 |
212 | .p-gui__list-dropdown {
213 | position: absolute;
214 | right: 5px;
215 | top: 0;
216 | bottom: 0;
217 | margin: auto;
218 | height: 21px;
219 | cursor: pointer;
220 | border-radius: 3px;
221 | border: 1px solid var(--color-border-2);
222 | outline: none;
223 | }
224 |
225 | .p-gui__list-dropdown:hover {
226 | background: rgba(255, 255, 255, .1);
227 | }
228 | `
229 | ), D = (
230 | /* css */
231 | `
232 | .p-gui__switch {
233 | background: rgba(255, 255, 255, .05);
234 | color: var(--color-text-dark);
235 | transition: var(--transition) background, var(--transition) color;
236 | }
237 |
238 | .p-gui__switch:hover {
239 | background: rgba(255, 255, 255, .1);
240 | color: var(--color-text-light);
241 | }
242 |
243 | .p-gui__folder .p-gui__switch {
244 | margin-inline: 0;
245 | }
246 |
247 | .p-gui__switch-checkbox {
248 | width: 5px;
249 | height: 5px;
250 | background-color: rgba(0, 0, 0, .5);
251 | border: 1px solid grey;
252 | position: absolute;
253 | top: 0;
254 | right: 10px;
255 | bottom: 0;
256 | margin: auto;
257 | border-radius: 50%;
258 | pointer-events: none;
259 | }
260 |
261 | .p-gui__switch-checkbox--active {
262 | background-color: #00ff89;
263 | box-shadow: 0 0 7px #00ff89;
264 | }
265 | `
266 | ), F = (
267 | /* css */
268 | `
269 | .p-gui__color {
270 | cursor: default;
271 | color: var(--color-text-dark);
272 | transition: var(--transition) color;
273 | }
274 |
275 | .p-gui__color:hover {
276 | color: var(--color-text-light);
277 | }
278 |
279 | .p-gui__color-picker {
280 | position: absolute;
281 | right: 5px;
282 | top: 0;
283 | bottom: 0;
284 | margin: auto;
285 | height: 21px;
286 | cursor: pointer;
287 | border-radius: 3px;
288 | border: 1px solid var(--color-border-2);
289 | outline: none;
290 | -webkit-appearance: none;
291 | padding: 0;
292 | background-color: transparent;
293 | border: 1px solid #222222;
294 | overflow: hidden;
295 | }
296 |
297 | .p-gui__color-picker::-webkit-color-swatch-wrapper {
298 | padding: 0;
299 | }
300 | .p-gui__color-picker::-webkit-color-swatch {
301 | border: none;
302 | }
303 | `
304 | ), L = (
305 | /* css */
306 | `
307 | .p-gui__vector2 {
308 | background: transparent;
309 | aspect-ratio: 1;
310 | padding-bottom: 0;
311 | color: var(--color-text-dark);
312 | }
313 |
314 | .p-gui__vector2:hover {
315 | color: var(--color-text-light);
316 | }
317 |
318 | .p-gui__vector2-area {
319 | position: relative;
320 | background: rgba(0, 0, 0, .3);
321 | margin-top: 8px;
322 | width: 100%;
323 | height: calc(100% - 28px);
324 | touch-action: none;
325 | }
326 |
327 | .p-gui__vector2-line {
328 | position: absolute;
329 | background: white;
330 | opacity: .3;
331 | pointer-events: none;
332 | }
333 |
334 | .p-gui__vector2-line-x {
335 | width: 100%;
336 | height: 1px;
337 | left: 0;
338 | top: 50%;
339 | transform: translateY(-50%);
340 | }
341 |
342 | .p-gui__vector2-line-y {
343 | width: 1px;
344 | height: 100%;
345 | top: 0;
346 | left: 50%;
347 | transform: translateX(-50%);
348 | }
349 |
350 | .p-gui__vector2-dot {
351 | position: absolute;
352 | top: 0;
353 | left: 0;
354 | width: 8px;
355 | height: 8px;
356 | border-radius: 50%;
357 | background: #d5d5d5;
358 | border: 2px solid #ff9999;
359 | transform: translate(-50%, -50%);
360 | pointer-events: none;
361 | }
362 |
363 | .p-gui__vector-value {
364 | display: inline-block;
365 | right: 7px;
366 | position: absolute;
367 | }
368 | `
369 | ), H = (
370 | /* css */
371 | `
372 | .p-gui__image-container {
373 | width: 100%;
374 | padding: 3px;
375 | display: flex;
376 | justify-content: flex-start;
377 | flex-wrap: wrap;
378 | box-sizing: border-box;
379 | }
380 |
381 | .p-gui__image {
382 | background-size: cover;
383 | cursor: pointer;
384 | position: relative;
385 | margin: 1px 2.5px 19px 2.5px;
386 | border-radius: var(--main-border-radius);
387 | flex: 0 0 calc(33.333% - 5px);
388 | height: 90px;
389 | background-position: center;
390 | color: var(--color-text-dark);
391 | transition: var(--transition) color;
392 | }
393 |
394 | .p-gui__image:hover {
395 | color: var(--color-text-light);
396 | }
397 |
398 | .p-gui__image::after {
399 | position: absolute;
400 | top: 0;
401 | left: 0;
402 | width: 100%;
403 | height: 100%;
404 | content: '';
405 | border: 1px solid transparent;
406 | box-sizing: border-box;
407 | border-radius: var(--main-border-radius);
408 | transition: var(--transition) border-color;
409 | }
410 | .p-gui__image--selected::after {
411 | border-color: #06FF89;
412 | }
413 |
414 | .p-gui__image-text {
415 | position: absolute;
416 | bottom: -15px;
417 | text-shadow: 0 -1px 0 #111;
418 | white-space: nowrap;
419 | width: 100%;
420 | overflow: hidden;
421 | text-overflow: ellipsis;
422 |
423 | }
424 | `
425 | ), N = (
426 | /* css */
427 | `
428 | .p-gui__folder {
429 | width: 100%;
430 | position: relative;
431 | background: #434343;
432 | overflow: auto;
433 | margin-bottom: 2px;
434 | display: flex;
435 | flex-wrap: wrap;
436 | border: 1px solid grey;
437 | padding: 0 3px 0 3px;
438 | border-radius: var(--main-border-radius);
439 | box-sizing: border-box;
440 | }
441 |
442 | .p-gui__folder--first {
443 | margin-top: 0;
444 | }
445 |
446 | .p-gui__folder--closed {
447 | height: 32px;
448 | overflow: hidden;
449 | }
450 |
451 | .p-gui__folder-header {
452 | padding: 10px 5px;
453 | background-color: rgba(0, 0, 0, .5);
454 | color: white;
455 | cursor: pointer;
456 | width: 100%;
457 | margin: 0 -2px 2px -3px;
458 | }
459 |
460 | .p-gui__folder-header:hover {
461 | background-color: rgba(0, 0, 0, .75);
462 | }
463 |
464 | .p-gui__folder-arrow {
465 | width: 8px;
466 | height: 8px;
467 | display: inline-block;
468 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAHlBMVEUAAAD///////////////////////////////////8kfJuVAAAACXRSTlMA9Z1fCdMo1yxEJnA0AAAAK0lEQVQI12PABlRgjKkJUMZMYRhjpgqMAZSEMICSaIzpDWiKhdENhEhgAgATSg5jyWnYewAAAABJRU5ErkJggg==);
469 | background-size: contain;
470 | margin-right: 5px;
471 | transform: rotate(90deg)
472 | }
473 |
474 | .p-gui__folder--closed .p-gui__folder-arrow {
475 | transform: rotate(0deg);
476 | }
477 | `
478 | );
479 | function M(U) {
480 | return (
481 | /* css */
482 | `
483 | .p-gui {
484 | --main-border-radius: 5px;
485 | --color-bg: #121212;
486 | --color-border: #484848;
487 | --color-border-2: rgba(255,255,255,.1);
488 | --color-text-light: #ffffff;
489 | --color-text-dark: #bbbbbb;
490 | --color-accent: #1681ca;
491 | --color-accent-hover: #218fda;
492 | --transition: .1s linear;
493 |
494 | position: ${U};
495 | top: 0;
496 | left: 0;
497 | transform: translate3d(0,0,0);
498 | padding-top: 21px;
499 | padding-inline: 3px;
500 | background: var(--color-bg);
501 | display: block;
502 | justify-content: center;
503 | flex-wrap: wrap;
504 | font-family: "Arial Rounded MT Bold", Arial, sans-serif;
505 | width: 290px;
506 | overflow: auto;
507 | box-shadow: 0 0 2px black;
508 | box-sizing: border-box;
509 | z-index: 99999;
510 | user-select: none;
511 | border-bottom-right-radius: 3px;
512 | border-bottom-left-radius: 3px;
513 | cursor: auto;
514 | border-radius: var(--main-border-radius);
515 | border: 1px solid var(--color-border);
516 | line-height: normal;
517 | transition: var(--transition) opacity;
518 | }
519 |
520 | .p-gui:hover {
521 | opacity: 1!important;
522 | }
523 |
524 | .p-gui * {
525 | font-size: 11px;
526 | }
527 |
528 | .p-gui::-webkit-scrollbar,
529 | .p-gui *::-webkit-scrollbar {
530 | width: 10px;
531 | }
532 |
533 | .p-gui::-webkit-scrollbar-track,
534 | .p-gui *::-webkit-scrollbar-track {
535 | background: #2f2f2f;
536 | border-radius: 3px;
537 | }
538 |
539 | .p-gui::-webkit-scrollbar-thumb,
540 | .p-gui *::-webkit-scrollbar-thumb {
541 | background: #757576;
542 | border-radius: 10px;
543 | box-sizing: border-box;
544 | border: 1px solid #2f2f2f;
545 | }
546 |
547 | .p-gui--collapsed {
548 | height: 0;
549 | padding: 21px 10px 0 10px;
550 | overflow: hidden;
551 | }
552 |
553 | .p-gui__header {
554 | position: absolute;
555 | top: 0;
556 | left: 0;
557 | width: 100%;
558 | height: 20px;
559 | background-color: rgba(0, 0, 0, .8);
560 | cursor: grab;
561 | color: grey;
562 | font-size: 10px;
563 | line-height: 20px;
564 | padding-left: 12px;
565 | box-sizing: border-box;
566 | touch-action: none;
567 | }
568 |
569 | .p-gui__header-close {
570 | width: 20px;
571 | height: 20px;
572 | position: absolute;
573 | top: 0;
574 | right: 5px;
575 | cursor: pointer;
576 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUAQMAAAC3R49OAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAABFJREFUCNdjIAb8//8BjIkAAOrOBd3TR0jRAAAAAElFTkSuQmCC);
577 | background-size: 50% 50%;
578 | background-position: center;
579 | background-repeat: no-repeat;
580 | }
581 |
582 | .p-gui--collapsed .p-gui__header-close {
583 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUAQMAAAC3R49OAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAABVJREFUCNdjYEhgIIj///8AwsSoBQD43QydY5mb0QAAAABJRU5ErkJggg==);
584 | }
585 |
586 | .p-gui__slider,
587 | .p-gui__button,
588 | .p-gui__switch,
589 | .p-gui__list,
590 | .p-gui__vector2,
591 | .p-gui__color {
592 | width: 100%;
593 | padding: 7px;
594 | cursor: pointer;
595 | position: relative;
596 | box-sizing: border-box;
597 | margin-block: 3px;
598 | border: 1px solid var(--color-border-2);
599 | border-radius: var(--main-border-radius);
600 | transition: var(--transition) border-color;
601 | }
602 |
603 | .p-gui__slider:hover,
604 | .p-gui__button:hover,
605 | .p-gui__switch:hover,
606 | .p-gui__list:hover,
607 | .p-gui__vector2:hover,
608 | .p-gui__color:hover {
609 | border-color: rgba(255,255,255,.2);
610 | }
611 |
612 | ${P}
613 |
614 | ${H}
615 |
616 | ${$}
617 |
618 | ${D}
619 |
620 | ${O}
621 |
622 | ${F}
623 |
624 | ${L}
625 |
626 | ${N}
627 | `
628 | );
629 | }
630 | class C {
631 | constructor(e = {}) {
632 | if (this.firstParent = this, e.container ? (this.container = typeof e.container == "string" ? document.querySelector(e.container) : e.container, this.position_type = "absolute") : (this.container = document.body, this.position_type = "fixed"), this.propReferences = [], this.folders = [], e.isFolder) {
633 | this._folderConstructor(e.folderOptions);
634 | return;
635 | }
636 | typeof e.onUpdate == "function" && (this.onUpdate = e.onUpdate), this.name = e != null && typeof e.name == "string" ? e.name : "", this.backgroundColor = e.color || null, this.opacity = e.opacity || 1, this.container == document.body ? this.maxHeight = window.innerHeight : this.maxHeight = Math.min(this.container.clientHeight, window.innerHeight), e.maxHeight && (this.initMaxHeight = e.maxHeight, this.maxHeight = Math.min(this.initMaxHeight, this.maxHeight)), this.screenCorner = this._parseScreenCorner(e.position), window.perfectGUI || (window.perfectGUI = {}), window.perfectGUI.instanceCounter == null ? window.perfectGUI.instanceCounter = 0 : window.perfectGUI.instanceCounter++, this.instanceId = window.perfectGUI.instanceCounter, this.wrapperWidth = e.width || 290, this.stylesheet = document.createElement("style"), this.stylesheet.setAttribute("type", "text/css"), this.stylesheet.setAttribute("id", "lm-gui-stylesheet"), document.head.append(this.stylesheet), this.instanceId == 0 && this._addStyles(`${M(this.position_type)}`), this._styleInstance(), this._addWrapper(), this.wrapper.setAttribute("data-corner-x", this.screenCorner.x), this.wrapper.setAttribute("data-corner-y", this.screenCorner.y), e.autoRepositioning != !1 && window.addEventListener("resize", this._handleResize.bind(this)), this._handleResize(), this.hasBeenDragged = !1, e.draggable == !0 && this._makeDraggable(), this.closed = !1, e.closed && this.toggleClose();
637 | }
638 | _styleInstance() {
639 | let e = this._getScrollbarWidth(this.container);
640 | if (this.screenCorner.x == "left" ? this.xOffset = 0 : this.xOffset = this.container.clientWidth - this.wrapperWidth - e, this.instanceId > 0) {
641 | let t = this.container.querySelectorAll(".p-gui");
642 | for (let i = 0; i < t.length; i++)
643 | this.screenCorner.y == t[i].dataset.cornerY && (this.screenCorner.x == "left" && t[i].dataset.cornerX == "left" ? this.xOffset += t[i].offsetWidth : this.screenCorner.x == "right" && t[i].dataset.cornerX == "right" && (this.xOffset -= t[i].offsetWidth));
644 | }
645 | this.yOffset = 0, this.position = {
646 | prevX: this.xOffset,
647 | prevY: this.yOffset,
648 | x: this.xOffset,
649 | y: this.yOffset
650 | }, this._addStyles(`#p-gui-${this.instanceId} {
651 | width: ${this.wrapperWidth}px;
652 | max-height: ${this.maxHeight}px;
653 | transform: translate3d(${this.xOffset}px,${this.yOffset}px,0);
654 | ${this.screenCorner.y == "top" ? "" : "top: auto; bottom: 0;"}
655 | ${this.backgroundColor ? "background: " + this.backgroundColor + ";" : ""}
656 | opacity: ${this.opacity};
657 | }`);
658 | }
659 | _folderConstructor(e) {
660 | this.wrapper = e.wrapper, this.isFolder = !0, this.parent = e.parent, this.firstParent = e.firstParent;
661 | }
662 | _parseScreenCorner(e) {
663 | let t = { x: "right", y: "top" };
664 | return e == null || (typeof e != "string" && console.error("[perfect-gui] Position must be a string."), e.includes("left") && (t.x = "left"), e.includes("bottom") && (t.y = "bottom")), t;
665 | }
666 | _getScrollbarWidth(e) {
667 | return e === document.body ? window.innerWidth - document.documentElement.clientWidth : e.offsetWidth - e.clientWidth;
668 | }
669 | _handleResize() {
670 | if (this.container == document.body ? this.maxHeight = window.innerHeight : this.maxHeight = Math.min(this.container.clientHeight, window.innerHeight), this.initMaxHeight && (this.maxHeight = Math.min(this.initMaxHeight, this.maxHeight)), this.wrapper.style.maxHeight = this.maxHeight + "px", this.hasBeenDragged)
671 | return;
672 | let e = this._getScrollbarWidth(this.container);
673 | if (this.xOffset = this.screenCorner.x == "left" ? 0 : this.container.clientWidth - this.wrapperWidth - e, this.instanceId > 0) {
674 | let t = this.container.querySelectorAll(`.p-gui:not(#${this.wrapper.id}):not([data-dragged])`);
675 | for (let i = 0; i < t.length && !(parseInt(t[i].id.replace("p-gui-", "")) > this.instanceId); i++)
676 | this.screenCorner.y == t[i].dataset.cornerY && (this.screenCorner.x == "left" && t[i].dataset.cornerX == "left" ? this.xOffset += t[i].offsetWidth : this.screenCorner.x == "right" && t[i].dataset.cornerX == "right" && (this.xOffset -= t[i].offsetWidth));
677 | }
678 | this.position = { prevX: this.xOffset, prevY: this.yOffset, x: this.xOffset, y: this.yOffset }, this.wrapper.style.transform = `translate3d(${this.position.x}px, ${this.position.y}px, 0)`;
679 | }
680 | _addStyles(e) {
681 | this.stylesheet.innerHTML += e;
682 | }
683 | _addWrapper() {
684 | this.wrapper = document.createElement("div"), this.wrapper.id = "p-gui-" + this.instanceId, this.wrapper.className = "p-gui", this.container.append(this.wrapper), this.header = document.createElement("div"), this.header.className = "p-gui__header", this.header.textContent = this.name, this.header.style = `${this.backgroundColor ? "border-color: " + this.backgroundColor + ";" : ""}`, this.wrapper.append(this.header);
685 | const e = document.createElement("div");
686 | e.className = "p-gui__header-close", e.addEventListener("click", this.toggleClose.bind(this)), this.header.append(e);
687 | }
688 | button(e, t) {
689 | let i = "";
690 | typeof e != "string" ? typeof e == "object" && (e != null && e.hasOwnProperty("name")) ? i = e.name == "" ? " " : e.name : i = " " : i = e == "" ? " " : e;
691 | const a = typeof e.tooltip == "string" ? e.tooltip : e.tooltip === !0 ? i : null;
692 | this.imageContainer = null;
693 | const s = document.createElement("div");
694 | s.className = "p-gui__button", s.textContent = i, a && s.setAttribute("title", a), s.addEventListener("click", () => {
695 | t && t(), this.onUpdate ? this.onUpdate() : this.isFolder && this.firstParent.onUpdate && this.firstParent.onUpdate();
696 | }), this.wrapper.append(s), typeof e.color == "string" && (s.style.setProperty("--color-accent", e.color), s.style.setProperty("--color-accent-hover", e.hoverColor || e.color));
697 | }
698 | image(e = {}, t) {
699 | if (typeof e != "object")
700 | throw Error(`[GUI] image() first parameter must be an object. Received: ${typeof e}.`);
701 | let i;
702 | if (typeof e.path == "string")
703 | i = e.path;
704 | else
705 | throw typeof e.path == null ? Error("[GUI] image() path must be provided.") : Error("[GUI] image() path must be a string.");
706 | let a = i.replace(/^.*[\\\/]/, ""), s;
707 | e.name == null ? s = a : s = typeof e.name == "string" && e.name || " ";
708 | const o = typeof e.tooltip == "string" ? e.tooltip : e.tooltip === !0 ? s : null, r = e.selected === !0, n = e.selectionBorder !== !1;
709 | let c = "";
710 | e.width && (typeof e.width == "number" && (e.width += "px"), c += `flex: 0 0 calc(${e.width} - 5px); `), e.height && (typeof e.height == "number" && (e.height += "px"), c += `height: ${e.height}; `), this.imageContainer || (this.imageContainer = document.createElement("div"), this.imageContainer.className = "p-gui__image-container", this.wrapper.append(this.imageContainer));
711 | const l = document.createElement("div");
712 | l.className = "p-gui__image", l.style = "background-image: url(" + i + "); " + c, o && l.setAttribute("title", o), this.imageContainer.append(l), r && n && l.classList.add("p-gui__image--selected");
713 | const d = document.createElement("div");
714 | return d.className = "p-gui__image-text", d.textContent = s, l.append(d), l.addEventListener("click", () => {
715 | let p = l.parentElement.querySelectorAll(".p-gui__image--selected");
716 | for (let h = 0; h < p.length; h++)
717 | p[h].classList.remove("p-gui__image--selected");
718 | n && l.classList.add("p-gui__image--selected"), typeof t == "function" && t({ path: i, text: s }), this.onUpdate ? this.onUpdate() : this.isFolder && this.firstParent.onUpdate && this.firstParent.onUpdate();
719 | }), l;
720 | }
721 | slider(e = {}, t) {
722 | const i = new R(this, e, t);
723 | this.wrapper.append(i);
724 | }
725 | toggle(e = {}, t) {
726 | if (typeof e != "object")
727 | throw Error(`[GUI] toggle() first parameter must be an object. Received: ${typeof e}.`);
728 | let i = typeof e.name == "string" && e.name || " ", a = !1, s = null, o = e.obj, r = e.prop, n = typeof e.value == "boolean" ? e.value : null;
729 | if (n !== null)
730 | (r != null || o != null) && console.warn('[GUI] toggle() "obj" and "prop" parameters are ignored when a "value" parameter is used.');
731 | else if (r != null && o != null) {
732 | if (typeof r != "string")
733 | throw Error(`[GUI] toggle() "prop" parameter must be an string. Received: ${typeof r}.`);
734 | if (typeof o != "object")
735 | throw Error(`[GUI] toggle() "obj" parameter must be an object. Received: ${typeof o}.`);
736 | i == " " && (i = r), s = this.propReferences.push(o[r]) - 1, a = !0;
737 | } else
738 | (r != null && o == null || r == null && o == null) && console.warn('[GUI] toggle() "obj" and "prop" parameters must be used together.');
739 | const c = typeof e.tooltip == "string" ? e.tooltip : e.tooltip === !0 ? i : null;
740 | this.imageContainer = null;
741 | const l = document.createElement("div");
742 | l.textContent = i, l.className = "p-gui__switch", c && l.setAttribute("title", c), this.wrapper.append(l), l.addEventListener("click", (h) => {
743 | const u = h.target.childNodes[1];
744 | let f = !0;
745 | u.classList.contains("p-gui__switch-checkbox--active") && (f = !1), u.classList.toggle("p-gui__switch-checkbox--active"), a ? o[r] = f : typeof t == "function" && t(f), this.onUpdate ? this.onUpdate() : this.isFolder && this.firstParent.onUpdate && this.firstParent.onUpdate();
746 | });
747 | let d = (() => a ? o[r] ? " p-gui__switch-checkbox--active" : "" : n ? " p-gui__switch-checkbox--active" : "")();
748 | const p = document.createElement("div");
749 | p.className = "p-gui__switch-checkbox" + d, l.append(p), a && Object.defineProperty(o, r, {
750 | set: (h) => {
751 | this.propReferences[s] = h, h ? p.classList.add("p-gui__switch-checkbox--active") : p.classList.remove("p-gui__switch-checkbox--active"), typeof t == "function" && t(h);
752 | },
753 | get: () => this.propReferences[s]
754 | });
755 | }
756 | list(e = {}, t) {
757 | if (typeof e != "object")
758 | throw Error(`[GUI] list() first parameter must be an object. Received: ${typeof e}.`);
759 | let i = typeof e.name == "string" ? e.name : " ", a = !1, s = null, o = e.obj, r = e.prop, n = Array.isArray(e.values) ? e.values : null, c, l = typeof n[0] != "string";
760 | const d = typeof e.tooltip == "string" ? e.tooltip : e.tooltip === !0 ? i : null;
761 | if (t = typeof t == "function" ? t : null, e.value !== void 0 || e.value === void 0 && o === void 0 && r === void 0)
762 | (r != null || o != null) && console.warn('[GUI] list() "obj" and "prop" parameters are ignored when a "value" parameter is used.'), c = (() => {
763 | if (!n)
764 | return null;
765 | if (typeof e.value == "string")
766 | return n.indexOf(e.value);
767 | if (typeof e.value == "number")
768 | return e.value;
769 | })();
770 | else if (r != null && o != null) {
771 | if (typeof r != "string")
772 | throw Error(`[GUI] list() "prop" parameter must be an string. Received: ${typeof r}.`);
773 | if (typeof o != "object")
774 | throw Error(`[GUI] list() "obj" parameter must be an object. Received: ${typeof o}.`);
775 | c = (() => {
776 | if (!n)
777 | return null;
778 | if (typeof o[r] == "string")
779 | return l ? n.find((u) => u.value === o[r]).value : n.indexOf(o[r]);
780 | if (typeof o[r] == "number")
781 | return l ? n.find((u) => u.value === o[r]).value : o[r];
782 | })(), s = this.propReferences.push(o[r]) - 1, a = !0;
783 | } else
784 | (r != null && o == null || r == null && o == null) && console.warn('[GUI] list() "obj" and "prop" parameters must be used together.');
785 | this.imageContainer = null;
786 | let p = document.createElement("div");
787 | p.className = "p-gui__list", p.textContent = i, d && p.setAttribute("title", d), this.wrapper.append(p);
788 | let h = document.createElement("select");
789 | p.append(h), h.className = "p-gui__list-dropdown", h.addEventListener("change", (u) => {
790 | a ? o[r] = u.target.value : t && t(u.target.value), this.onUpdate ? this.onUpdate() : this.isFolder && this.firstParent.onUpdate && this.firstParent.onUpdate();
791 | }), n && n.forEach((u, f) => {
792 | const g = l ? u.name : u, m = l ? u.value : u;
793 | let x = document.createElement("option");
794 | x.setAttribute("value", m), x.textContent = g, h.append(x), (!l && c == f || l && c == m) && x.setAttribute("selected", "");
795 | }), a && Object.defineProperty(o, r, {
796 | set: (u) => {
797 | let f, g, m;
798 | l ? (m = n.find((v) => v.value == u), g = (m == null ? void 0 : m.value) || n[0].value, f = n.indexOf(m)) : (typeof u == "string" && (f = n.indexOf(u), g = u), typeof u == "number" && (f = u, g = n[u])), this.propReferences[s] = l ? g : u;
799 | const x = h.querySelector("[selected]");
800 | x && x.removeAttribute("selected"), h.querySelectorAll("option")[f].setAttribute("selected", ""), typeof t == "function" && t(l ? m : g, f);
801 | },
802 | get: () => this.propReferences[s]
803 | });
804 | }
805 | color(e = {}, t) {
806 | if (typeof e != "object")
807 | throw Error(`[GUI] color() first parameter must be an object. Received: ${typeof e}.`);
808 | let i = typeof e.name == "string" && e.name || " ", a = !1, s = null, o = e.obj, r = e.prop, n;
809 | const c = typeof e.tooltip == "string" ? e.tooltip : e.tooltip === !0 ? i : null;
810 | if (typeof e.value == "string" && (e.value.length != 7 || e.value[0] != "#" ? console.error(`[GUI] color() 'value' parameter must be an hexadecimal string in the format "#ffffff". Received: "${e.value}".`) : n = e.value), n || (n = "#000000"), e.value !== void 0)
811 | (r != null || o != null) && console.warn('[GUI] color() "obj" and "prop" parameters are ignored when a "value" parameter is used.');
812 | else if (r != null && o != null) {
813 | if (typeof r != "string")
814 | throw Error(`[GUI] color() "prop" parameter must be an string. Received: ${typeof r}.`);
815 | if (typeof o != "object")
816 | throw Error(`[GUI] color() "obj" parameter must be an object. Received: ${typeof o}.`);
817 | i == " " && (i = r), s = this.propReferences.push(o[r]) - 1, a = !0;
818 | } else
819 | (r != null && o == null || r == null && o == null) && console.warn('[GUI] color() "obj" and "prop" parameters must be used together.');
820 | this.imageContainer = null;
821 | const l = document.createElement("div");
822 | l.className = "p-gui__color", l.textContent = i, c && l.setAttribute("title", c), this.wrapper.append(l);
823 | const d = document.createElement("input");
824 | d.className = "p-gui__color-picker", d.setAttribute("type", "color"), d.value = n, l.append(d), typeof t == "function" && d.addEventListener("input", () => {
825 | a ? o[r] = d.value : typeof t == "function" && t(d.value), this.onUpdate ? this.onUpdate() : this.isFolder && this.firstParent.onUpdate && this.firstParent.onUpdate();
826 | }), a && Object.defineProperty(o, r, {
827 | set: (p) => {
828 | this.propReferences[s] = p, d.value = p, typeof t == "function" && t(p);
829 | },
830 | get: () => this.propReferences[s]
831 | });
832 | }
833 | vector2(e = {}, t) {
834 | if (typeof e != "object")
835 | throw Error(`[GUI] vector2() first parameter must be an object. Received: ${typeof e}.`);
836 | let i = typeof e.name == "string" && e.name || " ";
837 | const a = e.x.min ?? 0, s = e.x.max ?? 1, o = e.y.min ?? 0, r = e.y.max ?? 1, n = e.x.step || (s - a) / 100, c = e.y.step || (r - o) / 100, l = this._countDecimals(n), d = this._countDecimals(c), p = e.x.obj, h = e.x.prop, u = this.propReferences.push(p[h]) - 1, f = e.y.obj, g = e.y.prop, m = this.propReferences.push(f[g]) - 1, x = typeof e.tooltip == "string" ? e.tooltip : e.tooltip === !0 ? i : null;
838 | t = typeof t == "function" ? t : null, this.imageContainer = null;
839 | const v = document.createElement("div");
840 | v.className = "p-gui__vector2", v.textContent = i, x && v.setAttribute("title", x), this.wrapper.append(v);
841 | const y = document.createElement("div");
842 | y.className = "p-gui__vector-value", y.textContent = p[h] + ", " + f[g], v.append(y);
843 | const b = document.createElement("div");
844 | b.className = "p-gui__vector2-area", v.append(b), b.addEventListener("click", (_) => {
845 | const k = parseFloat(this._mapLinear(_.offsetX, 0, b.clientWidth, a, s)), E = parseFloat(this._mapLinear(_.offsetY, 0, b.clientHeight, r, o));
846 | p[h] = k.toFixed(l), f[g] = E.toFixed(d), t && t(p[h], p[g]), this.onUpdate ? this.onUpdate() : this.isFolder && this.firstParent.onUpdate && this.firstParent.onUpdate();
847 | });
848 | let A = !1;
849 | b.addEventListener("pointerdown", (_) => {
850 | A = !0;
851 | }), b.addEventListener("pointerup", () => {
852 | A = !1;
853 | }), b.addEventListener("pointermove", (_) => {
854 | if (A) {
855 | const k = parseFloat(this._mapLinear(_.offsetX, 0, b.clientWidth, a, s)), E = parseFloat(this._mapLinear(_.offsetY, 0, b.clientHeight, r, o));
856 | p[h] = k.toFixed(l), f[g] = E.toFixed(d), t && t(p[h], p[g]), this.onUpdate ? this.onUpdate() : this.isFolder && this.firstParent.onUpdate && this.firstParent.onUpdate();
857 | }
858 | });
859 | const j = document.createElement("div");
860 | j.className = "p-gui__vector2-line p-gui__vector2-line-x", b.append(j);
861 | const I = document.createElement("div");
862 | I.className = "p-gui__vector2-line p-gui__vector2-line-y", b.append(I);
863 | const w = document.createElement("div");
864 | w.className = "p-gui__vector2-dot", b.append(w), w.style.left = this._mapLinear(p[h], a, s, 0, b.clientWidth) + "px", w.style.top = this._mapLinear(f[g], o, r, b.clientHeight, 0) + "px", Object.defineProperty(p, h, {
865 | set: (_) => {
866 | this.propReferences[u] = _, w.style.left = this._mapLinear(_, a, s, 0, b.clientWidth) + "px", y.textContent = String(_) + ", " + f[g];
867 | },
868 | get: () => this.propReferences[u]
869 | }), Object.defineProperty(f, g, {
870 | set: (_) => {
871 | this.propReferences[m] = _, w.style.top = this._mapLinear(_, o, r, b.clientHeight, 0) + "px", y.textContent = p[h] + ", " + String(_);
872 | },
873 | get: () => this.propReferences[m]
874 | });
875 | }
876 | folder(e = {}) {
877 | let t = typeof e.closed == "boolean" ? e.closed : !1, i = e.name || "", a = e.color || null, s = e.maxHeight || null;
878 | this.imageContainer = null;
879 | let o = "p-gui__folder";
880 | this.folders.length == 0 && (o += " p-gui__folder--first"), t && (o += " p-gui__folder--closed");
881 | let r = a ? `background-color: ${a};` : "";
882 | r += s ? `max-height: ${s}px;` : "";
883 | const n = document.createElement("div");
884 | n.className = o, n.style = r, this.wrapper.append(n);
885 | const c = document.createElement("div");
886 | c.innerHTML = `${i}`, c.className = "p-gui__folder-header", n.append(c), c.addEventListener("click", () => {
887 | n.classList.toggle("p-gui__folder--closed");
888 | });
889 | let l = new C({ isFolder: !0, folderOptions: {
890 | wrapper: n,
891 | parent: this,
892 | firstParent: this.firstParent
893 | } });
894 | return this.folders.push(l), l;
895 | }
896 | _makeDraggable() {
897 | var e = this;
898 | this.header.addEventListener("pointerdown", t), this.header.addEventListener("pointerup", a);
899 | function t(s) {
900 | s.preventDefault(), e.position.initX = e.position.x, e.position.initY = e.position.y, e.position.prevX = s.clientX, e.position.prevY = s.clientY, document.addEventListener("pointermove", i);
901 | }
902 | function i(s) {
903 | s.preventDefault(), e.hasBeenDragged || (e.hasBeenDragged = !0, e.wrapper.setAttribute("data-dragged", "true")), e.position.x = e.position.initX + s.clientX - e.position.prevX, e.position.y = e.position.initY + s.clientY - e.position.prevY, e.wrapper.style.transform = "translate3d(" + e.position.x + "px," + e.position.y + "px,0)";
904 | }
905 | function a(s) {
906 | document.removeEventListener("pointermove", i);
907 | }
908 | }
909 | toggleClose() {
910 | this.closed = !this.closed, this.closed ? (this.previousInnerScroll = this.wrapper.scrollTop, this.wrapper.scrollTo(0, 0)) : this.wrapper.scrollTo(0, this.previousInnerScroll), this.wrapper.classList.toggle("p-gui--collapsed");
911 | }
912 | kill() {
913 | this.wrapper.remove();
914 | }
915 | _mapLinear(e, t, i, a, s) {
916 | return a + (e - t) * (s - a) / (i - t);
917 | }
918 | _countDecimals(e) {
919 | const t = e.toString(), i = t.indexOf(".");
920 | return i === -1 ? 0 : t.length - i - 1;
921 | }
922 | }
923 | export {
924 | C as default
925 | };
926 |
--------------------------------------------------------------------------------
/dist/perfect-gui.umd.js:
--------------------------------------------------------------------------------
1 | (function(y,A){typeof exports=="object"&&typeof module<"u"?module.exports=A():typeof define=="function"&&define.amd?define(A):(y=typeof globalThis<"u"?globalThis:y||self,y["Perfect GUI"]=A())})(this,function(){"use strict";class y{constructor(e,t={},i){if(this.parent=e,this.propReferences=[],typeof t!="object")throw Error(`[GUI] slider() first parameter must be an object. Received: ${typeof t}.`);let a=typeof t.name=="string"&&t.name||" ";this.isObject=!1;let s=null;this.obj=t.obj,this.prop=t.prop;let o=typeof t.value=="number"?t.value:null;if(this.min=t.min??0,this.max=t.max??1,this.step=t.step||(this.max-this.min)/100,this.decimals=this.parent._countDecimals(this.step),this.callback=typeof i=="function"?i:null,o!==null)(this.prop!=null||this.obj!=null)&&console.warn('[GUI] slider() "obj" and "prop" parameters are ignored when a "value" parameter is used.');else if(this.prop!=null&&this.obj!=null){if(typeof this.prop!="string")throw Error(`[GUI] slider() "prop" parameter must be an string. Received: ${typeof this.prop}.`);if(typeof this.obj!="object")throw Error(`[GUI] slider() "obj" parameter must be an object. Received: ${typeof this.obj}.`);a==" "&&(a=this.prop),s=this.propReferences.push(this.obj[this.prop])-1,this.isObject=!0}else(this.prop!=null&&this.obj==null||this.prop==null&&this.obj!=null)&&console.warn('[GUI] slider() "obj" and "prop" parameters must be used together.'),o=(this.max-this.min)/2;const r=typeof t.tooltip=="string"?t.tooltip:t.tooltip===!0?a:null;this.imageContainer=null;const n=document.createElement("div");n.className="p-gui__slider",r&&n.setAttribute("title",r);const c=document.createElement("div");c.className="p-gui__slider-name",c.textContent=a,n.append(c),this.ctrlDiv=document.createElement("div"),this.ctrlDiv.className="p-gui__slider-ctrl",this.ctrlDiv.setAttribute("type","range"),this.ctrlDiv.setAttribute("min",this.min),this.ctrlDiv.setAttribute("max",this.max),n.append(this.ctrlDiv);const l=document.createElement("div");return l.className="p-gui__slider-bar",this.ctrlDiv.append(l),this.handle=document.createElement("div"),this.handle.className="p-gui__slider-handle",this.ctrlDiv.append(this.handle),this.filling=document.createElement("div"),this.filling.className="p-gui__slider-filling",l.append(this.filling),this.valueInput=document.createElement("input"),this.valueInput.className="p-gui__slider-value",this.valueInput.value=this.isObject?this.obj[this.prop]:o,n.append(this.valueInput),setTimeout(()=>{const d=this.handle.offsetWidth;this.handle.position=this._mapLinear(this.valueInput.value,this.min,this.max,d/2,88-d/2),this.handle.position=Math.min(this.handle.position,88-d/2),this.handle.position=Math.max(this.handle.position,d/2),this.handle.style.transform=`translate(-50%, -50%) translateX(${this.handle.position}px)`,this.filling.style.width=`${this.handle.position}px`},0),this.valueInput.addEventListener("change",()=>{this._updateHandlePositionFromValue(),this._triggerCallbacks()}),this.ctrlDiv.addEventListener("pointerdown",d=>{this.ctrlDiv.pointerDown=!0,this.ctrlDiv.prevPosition=d.clientX,this._updateHandlePositionFromPointer(d,!0)}),window.addEventListener("pointerup",d=>{this.ctrlDiv.pointerDown=!1}),window.addEventListener("pointermove",d=>{this.ctrlDiv.pointerDown&&(this.ctrlDiv.pointerDelta=d.clientX-this.ctrlDiv.prevPosition,this._updateHandlePositionFromPointer(d))}),this.isObject&&Object.defineProperty(this.obj,this.prop,{set:d=>{this.propReferences[s]=d,this.valueInput.value=d,this._updateHandlePositionFromValue(),this.callback&&this.callback(parseFloat(this.valueInput.value))},get:()=>this.propReferences[s]}),n}_updateHandlePositionFromPointer(e,t=!1){const i=this.ctrlDiv.offsetWidth,a=this.handle.offsetWidth,s=e.clientX-this.ctrlDiv.prevPosition,o=parseFloat(this.valueInput.value);let r;t?r=e.offsetX:r=this.handle.position+s,r=Math.max(a/2,Math.min(r,i-a/2));let n=this.min+(this.max-this.min)*(r-a/2)/(i-a);n>o?n=this._quantizeFloor(n,this.step):n=this._quantizeCeil(n,this.step),n=parseFloat(n.toFixed(9));const c=parseFloat((o+this.step).toFixed(9)),l=parseFloat((o-this.step).toFixed(9));(n>=c||n<=l)&&(n=n.toFixed(this.decimals),this.valueInput.value=n,this.ctrlDiv.prevPosition=e.clientX,this.handle.style.transform=`translate(-50%, -50%) translateX(${r}px)`,this.handle.position=r,this.filling.style.width=this.handle.position+"px",this._triggerCallbacks())}_updateHandlePositionFromValue(){const e=this.ctrlDiv.offsetWidth,t=this.handle.offsetWidth;let i=this._mapLinear(this.valueInput.value,this.min,this.max,t/2,e-t/2);i=Math.max(t/2,Math.min(i,e-t/2)),this.handle.style.transform=`translate(-50%, -50%) translateX(${i}px)`,this.handle.position=i,this.filling.style.width=this.handle.position+"px"}_triggerCallbacks(){this.isObject?this.obj[this.prop]=parseFloat(this.valueInput.value):this.callback&&this.callback(parseFloat(this.valueInput.value)),this.parent.onUpdate?this.parent.onUpdate():this.parent.isFolder&&this.parent.firstParent.onUpdate&&this.parent.firstParent.onUpdate()}_mapLinear(e,t,i,a,s){return a+(e-t)*(s-a)/(i-t)}_quantize(e,t){return t*Math.round(e/t)}_quantizeCeil(e,t){return t*Math.ceil(e/t)}_quantizeFloor(e,t){return t*Math.floor(e/t)}}const A=`
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 | `,O=`
22 | .p-gui__slider {
23 | position: relative;
24 | min-height: 14px;
25 | display: flex;
26 | align-items: center;
27 | justify-content: space-between;
28 | gap: 10px;
29 | color: var(--color-text-dark);
30 | transition: color var(--transition);
31 | }
32 |
33 | .p-gui__slider:hover {
34 | color: var(--color-text-light);
35 | }
36 |
37 | .p-gui__slider-name {
38 | width: 50%;
39 | text-overflow: ellipsis;
40 | overflow: hidden;
41 | }
42 |
43 | .p-gui__slider-ctrl {
44 | -webkit-appearance: none;
45 | padding: 0;
46 | font: inherit;
47 | outline: none;
48 | box-sizing: border-box;
49 | cursor: pointer;
50 | position: relative;
51 | right: 0;
52 | height: 14px;
53 | margin: 0 0 0 auto;
54 | width: 37%;
55 | }
56 |
57 | .p-gui__slider-bar {
58 | position: absolute;
59 | top: 50%;
60 | left: 0;
61 | height: 2px;
62 | background: rgba(255, 255, 255, .2);
63 | width: 100%;
64 | transform: translateY(-50%);
65 | }
66 |
67 | .p-gui__slider-filling {
68 | position: absolute;
69 | top: -25%;
70 | left: 0;
71 | height: 150%;
72 | background: var(--color-accent);
73 | width: 0;
74 | }
75 |
76 | .p-gui__slider:hover .p-gui__slider-filling {
77 | background: var(--color-accent-hover);
78 | }
79 |
80 | .p-gui__slider-handle {
81 | width: 15px;
82 | height: 8px;
83 | position: absolute;
84 | top: 50%;
85 | left: 0;
86 | border-radius: 2px;
87 | transform: translate(-50%, -50%);
88 | pointer-events: none;
89 | background: var(--color-text-dark);
90 | box-shadow: 0 0 2px rgba(0, 0, 0, .5);
91 | }
92 |
93 | .p-gui__slider:hover .p-gui__slider-handle {
94 | background: var(--color-text-light);
95 | }
96 |
97 | .p-gui__slider-value {
98 | display: inline-block;
99 | right: 7px;
100 | width: 13%;
101 | border: none;
102 | color: white;
103 | border-radius: 2px;
104 | background: rgba(255, 255, 255, 0.1);
105 | padding: 2px 4px;
106 | color: inherit;
107 | }
108 |
109 | .p-gui__slider-value:focus {
110 | outline: none;
111 | }
112 | `,$=`
113 | .p-gui__list {
114 | cursor: default;
115 | color: var(--color-text-dark);
116 | transition: var(--transition) color;
117 | }
118 |
119 | .p-gui__list:hover {
120 | color: var(--color-text-light);
121 | }
122 |
123 | .p-gui__list-dropdown {
124 | background: rgba(255, 255, 255,.05);
125 | color: white;
126 | padding: 0 12px 0 5px;
127 | top: 0px;
128 | }
129 |
130 | .p-gui__list-dropdown {
131 | position: absolute;
132 | right: 5px;
133 | top: 0;
134 | bottom: 0;
135 | margin: auto;
136 | height: 21px;
137 | cursor: pointer;
138 | border-radius: 3px;
139 | border: 1px solid var(--color-border-2);
140 | outline: none;
141 | }
142 |
143 | .p-gui__list-dropdown:hover {
144 | background: rgba(255, 255, 255, .1);
145 | }
146 | `,D=`
147 | .p-gui__switch {
148 | background: rgba(255, 255, 255, .05);
149 | color: var(--color-text-dark);
150 | transition: var(--transition) background, var(--transition) color;
151 | }
152 |
153 | .p-gui__switch:hover {
154 | background: rgba(255, 255, 255, .1);
155 | color: var(--color-text-light);
156 | }
157 |
158 | .p-gui__folder .p-gui__switch {
159 | margin-inline: 0;
160 | }
161 |
162 | .p-gui__switch-checkbox {
163 | width: 5px;
164 | height: 5px;
165 | background-color: rgba(0, 0, 0, .5);
166 | border: 1px solid grey;
167 | position: absolute;
168 | top: 0;
169 | right: 10px;
170 | bottom: 0;
171 | margin: auto;
172 | border-radius: 50%;
173 | pointer-events: none;
174 | }
175 |
176 | .p-gui__switch-checkbox--active {
177 | background-color: #00ff89;
178 | box-shadow: 0 0 7px #00ff89;
179 | }
180 | `,F=`
181 | .p-gui__color {
182 | cursor: default;
183 | color: var(--color-text-dark);
184 | transition: var(--transition) color;
185 | }
186 |
187 | .p-gui__color:hover {
188 | color: var(--color-text-light);
189 | }
190 |
191 | .p-gui__color-picker {
192 | position: absolute;
193 | right: 5px;
194 | top: 0;
195 | bottom: 0;
196 | margin: auto;
197 | height: 21px;
198 | cursor: pointer;
199 | border-radius: 3px;
200 | border: 1px solid var(--color-border-2);
201 | outline: none;
202 | -webkit-appearance: none;
203 | padding: 0;
204 | background-color: transparent;
205 | border: 1px solid #222222;
206 | overflow: hidden;
207 | }
208 |
209 | .p-gui__color-picker::-webkit-color-swatch-wrapper {
210 | padding: 0;
211 | }
212 | .p-gui__color-picker::-webkit-color-swatch {
213 | border: none;
214 | }
215 | `,L=`
216 | .p-gui__vector2 {
217 | background: transparent;
218 | aspect-ratio: 1;
219 | padding-bottom: 0;
220 | color: var(--color-text-dark);
221 | }
222 |
223 | .p-gui__vector2:hover {
224 | color: var(--color-text-light);
225 | }
226 |
227 | .p-gui__vector2-area {
228 | position: relative;
229 | background: rgba(0, 0, 0, .3);
230 | margin-top: 8px;
231 | width: 100%;
232 | height: calc(100% - 28px);
233 | touch-action: none;
234 | }
235 |
236 | .p-gui__vector2-line {
237 | position: absolute;
238 | background: white;
239 | opacity: .3;
240 | pointer-events: none;
241 | }
242 |
243 | .p-gui__vector2-line-x {
244 | width: 100%;
245 | height: 1px;
246 | left: 0;
247 | top: 50%;
248 | transform: translateY(-50%);
249 | }
250 |
251 | .p-gui__vector2-line-y {
252 | width: 1px;
253 | height: 100%;
254 | top: 0;
255 | left: 50%;
256 | transform: translateX(-50%);
257 | }
258 |
259 | .p-gui__vector2-dot {
260 | position: absolute;
261 | top: 0;
262 | left: 0;
263 | width: 8px;
264 | height: 8px;
265 | border-radius: 50%;
266 | background: #d5d5d5;
267 | border: 2px solid #ff9999;
268 | transform: translate(-50%, -50%);
269 | pointer-events: none;
270 | }
271 |
272 | .p-gui__vector-value {
273 | display: inline-block;
274 | right: 7px;
275 | position: absolute;
276 | }
277 | `,H=`
278 | .p-gui__image-container {
279 | width: 100%;
280 | padding: 3px;
281 | display: flex;
282 | justify-content: flex-start;
283 | flex-wrap: wrap;
284 | box-sizing: border-box;
285 | }
286 |
287 | .p-gui__image {
288 | background-size: cover;
289 | cursor: pointer;
290 | position: relative;
291 | margin: 1px 2.5px 19px 2.5px;
292 | border-radius: var(--main-border-radius);
293 | flex: 0 0 calc(33.333% - 5px);
294 | height: 90px;
295 | background-position: center;
296 | color: var(--color-text-dark);
297 | transition: var(--transition) color;
298 | }
299 |
300 | .p-gui__image:hover {
301 | color: var(--color-text-light);
302 | }
303 |
304 | .p-gui__image::after {
305 | position: absolute;
306 | top: 0;
307 | left: 0;
308 | width: 100%;
309 | height: 100%;
310 | content: '';
311 | border: 1px solid transparent;
312 | box-sizing: border-box;
313 | border-radius: var(--main-border-radius);
314 | transition: var(--transition) border-color;
315 | }
316 | .p-gui__image--selected::after {
317 | border-color: #06FF89;
318 | }
319 |
320 | .p-gui__image-text {
321 | position: absolute;
322 | bottom: -15px;
323 | text-shadow: 0 -1px 0 #111;
324 | white-space: nowrap;
325 | width: 100%;
326 | overflow: hidden;
327 | text-overflow: ellipsis;
328 |
329 | }
330 | `,N=`
331 | .p-gui__folder {
332 | width: 100%;
333 | position: relative;
334 | background: #434343;
335 | overflow: auto;
336 | margin-bottom: 2px;
337 | display: flex;
338 | flex-wrap: wrap;
339 | border: 1px solid grey;
340 | padding: 0 3px 0 3px;
341 | border-radius: var(--main-border-radius);
342 | box-sizing: border-box;
343 | }
344 |
345 | .p-gui__folder--first {
346 | margin-top: 0;
347 | }
348 |
349 | .p-gui__folder--closed {
350 | height: 32px;
351 | overflow: hidden;
352 | }
353 |
354 | .p-gui__folder-header {
355 | padding: 10px 5px;
356 | background-color: rgba(0, 0, 0, .5);
357 | color: white;
358 | cursor: pointer;
359 | width: 100%;
360 | margin: 0 -2px 2px -3px;
361 | }
362 |
363 | .p-gui__folder-header:hover {
364 | background-color: rgba(0, 0, 0, .75);
365 | }
366 |
367 | .p-gui__folder-arrow {
368 | width: 8px;
369 | height: 8px;
370 | display: inline-block;
371 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAHlBMVEUAAAD///////////////////////////////////8kfJuVAAAACXRSTlMA9Z1fCdMo1yxEJnA0AAAAK0lEQVQI12PABlRgjKkJUMZMYRhjpgqMAZSEMICSaIzpDWiKhdENhEhgAgATSg5jyWnYewAAAABJRU5ErkJggg==);
372 | background-size: contain;
373 | margin-right: 5px;
374 | transform: rotate(90deg)
375 | }
376 |
377 | .p-gui__folder--closed .p-gui__folder-arrow {
378 | transform: rotate(0deg);
379 | }
380 | `;function M(C){return`
381 | .p-gui {
382 | --main-border-radius: 5px;
383 | --color-bg: #121212;
384 | --color-border: #484848;
385 | --color-border-2: rgba(255,255,255,.1);
386 | --color-text-light: #ffffff;
387 | --color-text-dark: #bbbbbb;
388 | --color-accent: #1681ca;
389 | --color-accent-hover: #218fda;
390 | --transition: .1s linear;
391 |
392 | position: ${C};
393 | top: 0;
394 | left: 0;
395 | transform: translate3d(0,0,0);
396 | padding-top: 21px;
397 | padding-inline: 3px;
398 | background: var(--color-bg);
399 | display: block;
400 | justify-content: center;
401 | flex-wrap: wrap;
402 | font-family: "Arial Rounded MT Bold", Arial, sans-serif;
403 | width: 290px;
404 | overflow: auto;
405 | box-shadow: 0 0 2px black;
406 | box-sizing: border-box;
407 | z-index: 99999;
408 | user-select: none;
409 | border-bottom-right-radius: 3px;
410 | border-bottom-left-radius: 3px;
411 | cursor: auto;
412 | border-radius: var(--main-border-radius);
413 | border: 1px solid var(--color-border);
414 | line-height: normal;
415 | transition: var(--transition) opacity;
416 | }
417 |
418 | .p-gui:hover {
419 | opacity: 1!important;
420 | }
421 |
422 | .p-gui * {
423 | font-size: 11px;
424 | }
425 |
426 | .p-gui::-webkit-scrollbar,
427 | .p-gui *::-webkit-scrollbar {
428 | width: 10px;
429 | }
430 |
431 | .p-gui::-webkit-scrollbar-track,
432 | .p-gui *::-webkit-scrollbar-track {
433 | background: #2f2f2f;
434 | border-radius: 3px;
435 | }
436 |
437 | .p-gui::-webkit-scrollbar-thumb,
438 | .p-gui *::-webkit-scrollbar-thumb {
439 | background: #757576;
440 | border-radius: 10px;
441 | box-sizing: border-box;
442 | border: 1px solid #2f2f2f;
443 | }
444 |
445 | .p-gui--collapsed {
446 | height: 0;
447 | padding: 21px 10px 0 10px;
448 | overflow: hidden;
449 | }
450 |
451 | .p-gui__header {
452 | position: absolute;
453 | top: 0;
454 | left: 0;
455 | width: 100%;
456 | height: 20px;
457 | background-color: rgba(0, 0, 0, .8);
458 | cursor: grab;
459 | color: grey;
460 | font-size: 10px;
461 | line-height: 20px;
462 | padding-left: 12px;
463 | box-sizing: border-box;
464 | touch-action: none;
465 | }
466 |
467 | .p-gui__header-close {
468 | width: 20px;
469 | height: 20px;
470 | position: absolute;
471 | top: 0;
472 | right: 5px;
473 | cursor: pointer;
474 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUAQMAAAC3R49OAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAABFJREFUCNdjIAb8//8BjIkAAOrOBd3TR0jRAAAAAElFTkSuQmCC);
475 | background-size: 50% 50%;
476 | background-position: center;
477 | background-repeat: no-repeat;
478 | }
479 |
480 | .p-gui--collapsed .p-gui__header-close {
481 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUAQMAAAC3R49OAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAABVJREFUCNdjYEhgIIj///8AwsSoBQD43QydY5mb0QAAAABJRU5ErkJggg==);
482 | }
483 |
484 | .p-gui__slider,
485 | .p-gui__button,
486 | .p-gui__switch,
487 | .p-gui__list,
488 | .p-gui__vector2,
489 | .p-gui__color {
490 | width: 100%;
491 | padding: 7px;
492 | cursor: pointer;
493 | position: relative;
494 | box-sizing: border-box;
495 | margin-block: 3px;
496 | border: 1px solid var(--color-border-2);
497 | border-radius: var(--main-border-radius);
498 | transition: var(--transition) border-color;
499 | }
500 |
501 | .p-gui__slider:hover,
502 | .p-gui__button:hover,
503 | .p-gui__switch:hover,
504 | .p-gui__list:hover,
505 | .p-gui__vector2:hover,
506 | .p-gui__color:hover {
507 | border-color: rgba(255,255,255,.2);
508 | }
509 |
510 | ${A}
511 |
512 | ${H}
513 |
514 | ${$}
515 |
516 | ${D}
517 |
518 | ${O}
519 |
520 | ${F}
521 |
522 | ${L}
523 |
524 | ${N}
525 | `}class E{constructor(e={}){if(this.firstParent=this,e.container?(this.container=typeof e.container=="string"?document.querySelector(e.container):e.container,this.position_type="absolute"):(this.container=document.body,this.position_type="fixed"),this.propReferences=[],this.folders=[],e.isFolder){this._folderConstructor(e.folderOptions);return}typeof e.onUpdate=="function"&&(this.onUpdate=e.onUpdate),this.name=e!=null&&typeof e.name=="string"?e.name:"",this.backgroundColor=e.color||null,this.opacity=e.opacity||1,this.container==document.body?this.maxHeight=window.innerHeight:this.maxHeight=Math.min(this.container.clientHeight,window.innerHeight),e.maxHeight&&(this.initMaxHeight=e.maxHeight,this.maxHeight=Math.min(this.initMaxHeight,this.maxHeight)),this.screenCorner=this._parseScreenCorner(e.position),window.perfectGUI||(window.perfectGUI={}),window.perfectGUI.instanceCounter==null?window.perfectGUI.instanceCounter=0:window.perfectGUI.instanceCounter++,this.instanceId=window.perfectGUI.instanceCounter,this.wrapperWidth=e.width||290,this.stylesheet=document.createElement("style"),this.stylesheet.setAttribute("type","text/css"),this.stylesheet.setAttribute("id","lm-gui-stylesheet"),document.head.append(this.stylesheet),this.instanceId==0&&this._addStyles(`${M(this.position_type)}`),this._styleInstance(),this._addWrapper(),this.wrapper.setAttribute("data-corner-x",this.screenCorner.x),this.wrapper.setAttribute("data-corner-y",this.screenCorner.y),e.autoRepositioning!=!1&&window.addEventListener("resize",this._handleResize.bind(this)),this._handleResize(),this.hasBeenDragged=!1,e.draggable==!0&&this._makeDraggable(),this.closed=!1,e.closed&&this.toggleClose()}_styleInstance(){let e=this._getScrollbarWidth(this.container);if(this.screenCorner.x=="left"?this.xOffset=0:this.xOffset=this.container.clientWidth-this.wrapperWidth-e,this.instanceId>0){let t=this.container.querySelectorAll(".p-gui");for(let i=0;i0){let t=this.container.querySelectorAll(`.p-gui:not(#${this.wrapper.id}):not([data-dragged])`);for(let i=0;ithis.instanceId);i++)this.screenCorner.y==t[i].dataset.cornerY&&(this.screenCorner.x=="left"&&t[i].dataset.cornerX=="left"?this.xOffset+=t[i].offsetWidth:this.screenCorner.x=="right"&&t[i].dataset.cornerX=="right"&&(this.xOffset-=t[i].offsetWidth))}this.position={prevX:this.xOffset,prevY:this.yOffset,x:this.xOffset,y:this.yOffset},this.wrapper.style.transform=`translate3d(${this.position.x}px, ${this.position.y}px, 0)`}_addStyles(e){this.stylesheet.innerHTML+=e}_addWrapper(){this.wrapper=document.createElement("div"),this.wrapper.id="p-gui-"+this.instanceId,this.wrapper.className="p-gui",this.container.append(this.wrapper),this.header=document.createElement("div"),this.header.className="p-gui__header",this.header.textContent=this.name,this.header.style=`${this.backgroundColor?"border-color: "+this.backgroundColor+";":""}`,this.wrapper.append(this.header);const e=document.createElement("div");e.className="p-gui__header-close",e.addEventListener("click",this.toggleClose.bind(this)),this.header.append(e)}button(e,t){let i="";typeof e!="string"?typeof e=="object"&&(e!=null&&e.hasOwnProperty("name"))?i=e.name==""?" ":e.name:i=" ":i=e==""?" ":e;const a=typeof e.tooltip=="string"?e.tooltip:e.tooltip===!0?i:null;this.imageContainer=null;const s=document.createElement("div");s.className="p-gui__button",s.textContent=i,a&&s.setAttribute("title",a),s.addEventListener("click",()=>{t&&t(),this.onUpdate?this.onUpdate():this.isFolder&&this.firstParent.onUpdate&&this.firstParent.onUpdate()}),this.wrapper.append(s),typeof e.color=="string"&&(s.style.setProperty("--color-accent",e.color),s.style.setProperty("--color-accent-hover",e.hoverColor||e.color))}image(e={},t){if(typeof e!="object")throw Error(`[GUI] image() first parameter must be an object. Received: ${typeof e}.`);let i;if(typeof e.path=="string")i=e.path;else throw typeof e.path==null?Error("[GUI] image() path must be provided."):Error("[GUI] image() path must be a string.");let a=i.replace(/^.*[\\\/]/,""),s;e.name==null?s=a:s=typeof e.name=="string"&&e.name||" ";const o=typeof e.tooltip=="string"?e.tooltip:e.tooltip===!0?s:null,r=e.selected===!0,n=e.selectionBorder!==!1;let c="";e.width&&(typeof e.width=="number"&&(e.width+="px"),c+=`flex: 0 0 calc(${e.width} - 5px); `),e.height&&(typeof e.height=="number"&&(e.height+="px"),c+=`height: ${e.height}; `),this.imageContainer||(this.imageContainer=document.createElement("div"),this.imageContainer.className="p-gui__image-container",this.wrapper.append(this.imageContainer));const l=document.createElement("div");l.className="p-gui__image",l.style="background-image: url("+i+"); "+c,o&&l.setAttribute("title",o),this.imageContainer.append(l),r&&n&&l.classList.add("p-gui__image--selected");const d=document.createElement("div");return d.className="p-gui__image-text",d.textContent=s,l.append(d),l.addEventListener("click",()=>{let p=l.parentElement.querySelectorAll(".p-gui__image--selected");for(let h=0;h{const u=h.target.childNodes[1];let f=!0;u.classList.contains("p-gui__switch-checkbox--active")&&(f=!1),u.classList.toggle("p-gui__switch-checkbox--active"),a?o[r]=f:typeof t=="function"&&t(f),this.onUpdate?this.onUpdate():this.isFolder&&this.firstParent.onUpdate&&this.firstParent.onUpdate()});let d=(()=>a?o[r]?" p-gui__switch-checkbox--active":"":n?" p-gui__switch-checkbox--active":"")();const p=document.createElement("div");p.className="p-gui__switch-checkbox"+d,l.append(p),a&&Object.defineProperty(o,r,{set:h=>{this.propReferences[s]=h,h?p.classList.add("p-gui__switch-checkbox--active"):p.classList.remove("p-gui__switch-checkbox--active"),typeof t=="function"&&t(h)},get:()=>this.propReferences[s]})}list(e={},t){if(typeof e!="object")throw Error(`[GUI] list() first parameter must be an object. Received: ${typeof e}.`);let i=typeof e.name=="string"?e.name:" ",a=!1,s=null,o=e.obj,r=e.prop,n=Array.isArray(e.values)?e.values:null,c,l=typeof n[0]!="string";const d=typeof e.tooltip=="string"?e.tooltip:e.tooltip===!0?i:null;if(t=typeof t=="function"?t:null,e.value!==void 0||e.value===void 0&&o===void 0&&r===void 0)(r!=null||o!=null)&&console.warn('[GUI] list() "obj" and "prop" parameters are ignored when a "value" parameter is used.'),c=(()=>{if(!n)return null;if(typeof e.value=="string")return n.indexOf(e.value);if(typeof e.value=="number")return e.value})();else if(r!=null&&o!=null){if(typeof r!="string")throw Error(`[GUI] list() "prop" parameter must be an string. Received: ${typeof r}.`);if(typeof o!="object")throw Error(`[GUI] list() "obj" parameter must be an object. Received: ${typeof o}.`);c=(()=>{if(!n)return null;if(typeof o[r]=="string")return l?n.find(u=>u.value===o[r]).value:n.indexOf(o[r]);if(typeof o[r]=="number")return l?n.find(u=>u.value===o[r]).value:o[r]})(),s=this.propReferences.push(o[r])-1,a=!0}else(r!=null&&o==null||r==null&&o==null)&&console.warn('[GUI] list() "obj" and "prop" parameters must be used together.');this.imageContainer=null;let p=document.createElement("div");p.className="p-gui__list",p.textContent=i,d&&p.setAttribute("title",d),this.wrapper.append(p);let h=document.createElement("select");p.append(h),h.className="p-gui__list-dropdown",h.addEventListener("change",u=>{a?o[r]=u.target.value:t&&t(u.target.value),this.onUpdate?this.onUpdate():this.isFolder&&this.firstParent.onUpdate&&this.firstParent.onUpdate()}),n&&n.forEach((u,f)=>{const g=l?u.name:u,m=l?u.value:u;let x=document.createElement("option");x.setAttribute("value",m),x.textContent=g,h.append(x),(!l&&c==f||l&&c==m)&&x.setAttribute("selected","")}),a&&Object.defineProperty(o,r,{set:u=>{let f,g,m;l?(m=n.find(v=>v.value==u),g=(m==null?void 0:m.value)||n[0].value,f=n.indexOf(m)):(typeof u=="string"&&(f=n.indexOf(u),g=u),typeof u=="number"&&(f=u,g=n[u])),this.propReferences[s]=l?g:u;const x=h.querySelector("[selected]");x&&x.removeAttribute("selected"),h.querySelectorAll("option")[f].setAttribute("selected",""),typeof t=="function"&&t(l?m:g,f)},get:()=>this.propReferences[s]})}color(e={},t){if(typeof e!="object")throw Error(`[GUI] color() first parameter must be an object. Received: ${typeof e}.`);let i=typeof e.name=="string"&&e.name||" ",a=!1,s=null,o=e.obj,r=e.prop,n;const c=typeof e.tooltip=="string"?e.tooltip:e.tooltip===!0?i:null;if(typeof e.value=="string"&&(e.value.length!=7||e.value[0]!="#"?console.error(`[GUI] color() 'value' parameter must be an hexadecimal string in the format "#ffffff". Received: "${e.value}".`):n=e.value),n||(n="#000000"),e.value!==void 0)(r!=null||o!=null)&&console.warn('[GUI] color() "obj" and "prop" parameters are ignored when a "value" parameter is used.');else if(r!=null&&o!=null){if(typeof r!="string")throw Error(`[GUI] color() "prop" parameter must be an string. Received: ${typeof r}.`);if(typeof o!="object")throw Error(`[GUI] color() "obj" parameter must be an object. Received: ${typeof o}.`);i==" "&&(i=r),s=this.propReferences.push(o[r])-1,a=!0}else(r!=null&&o==null||r==null&&o==null)&&console.warn('[GUI] color() "obj" and "prop" parameters must be used together.');this.imageContainer=null;const l=document.createElement("div");l.className="p-gui__color",l.textContent=i,c&&l.setAttribute("title",c),this.wrapper.append(l);const d=document.createElement("input");d.className="p-gui__color-picker",d.setAttribute("type","color"),d.value=n,l.append(d),typeof t=="function"&&d.addEventListener("input",()=>{a?o[r]=d.value:typeof t=="function"&&t(d.value),this.onUpdate?this.onUpdate():this.isFolder&&this.firstParent.onUpdate&&this.firstParent.onUpdate()}),a&&Object.defineProperty(o,r,{set:p=>{this.propReferences[s]=p,d.value=p,typeof t=="function"&&t(p)},get:()=>this.propReferences[s]})}vector2(e={},t){if(typeof e!="object")throw Error(`[GUI] vector2() first parameter must be an object. Received: ${typeof e}.`);let i=typeof e.name=="string"&&e.name||" ";const a=e.x.min??0,s=e.x.max??1,o=e.y.min??0,r=e.y.max??1,n=e.x.step||(s-a)/100,c=e.y.step||(r-o)/100,l=this._countDecimals(n),d=this._countDecimals(c),p=e.x.obj,h=e.x.prop,u=this.propReferences.push(p[h])-1,f=e.y.obj,g=e.y.prop,m=this.propReferences.push(f[g])-1,x=typeof e.tooltip=="string"?e.tooltip:e.tooltip===!0?i:null;t=typeof t=="function"?t:null,this.imageContainer=null;const v=document.createElement("div");v.className="p-gui__vector2",v.textContent=i,x&&v.setAttribute("title",x),this.wrapper.append(v);const k=document.createElement("div");k.className="p-gui__vector-value",k.textContent=p[h]+", "+f[g],v.append(k);const b=document.createElement("div");b.className="p-gui__vector2-area",v.append(b),b.addEventListener("click",_=>{const j=parseFloat(this._mapLinear(_.offsetX,0,b.clientWidth,a,s)),I=parseFloat(this._mapLinear(_.offsetY,0,b.clientHeight,r,o));p[h]=j.toFixed(l),f[g]=I.toFixed(d),t&&t(p[h],p[g]),this.onUpdate?this.onUpdate():this.isFolder&&this.firstParent.onUpdate&&this.firstParent.onUpdate()});let U=!1;b.addEventListener("pointerdown",_=>{U=!0}),b.addEventListener("pointerup",()=>{U=!1}),b.addEventListener("pointermove",_=>{if(U){const j=parseFloat(this._mapLinear(_.offsetX,0,b.clientWidth,a,s)),I=parseFloat(this._mapLinear(_.offsetY,0,b.clientHeight,r,o));p[h]=j.toFixed(l),f[g]=I.toFixed(d),t&&t(p[h],p[g]),this.onUpdate?this.onUpdate():this.isFolder&&this.firstParent.onUpdate&&this.firstParent.onUpdate()}});const R=document.createElement("div");R.className="p-gui__vector2-line p-gui__vector2-line-x",b.append(R);const P=document.createElement("div");P.className="p-gui__vector2-line p-gui__vector2-line-y",b.append(P);const w=document.createElement("div");w.className="p-gui__vector2-dot",b.append(w),w.style.left=this._mapLinear(p[h],a,s,0,b.clientWidth)+"px",w.style.top=this._mapLinear(f[g],o,r,b.clientHeight,0)+"px",Object.defineProperty(p,h,{set:_=>{this.propReferences[u]=_,w.style.left=this._mapLinear(_,a,s,0,b.clientWidth)+"px",k.textContent=String(_)+", "+f[g]},get:()=>this.propReferences[u]}),Object.defineProperty(f,g,{set:_=>{this.propReferences[m]=_,w.style.top=this._mapLinear(_,o,r,b.clientHeight,0)+"px",k.textContent=p[h]+", "+String(_)},get:()=>this.propReferences[m]})}folder(e={}){let t=typeof e.closed=="boolean"?e.closed:!1,i=e.name||"",a=e.color||null,s=e.maxHeight||null;this.imageContainer=null;let o="p-gui__folder";this.folders.length==0&&(o+=" p-gui__folder--first"),t&&(o+=" p-gui__folder--closed");let r=a?`background-color: ${a};`:"";r+=s?`max-height: ${s}px;`:"";const n=document.createElement("div");n.className=o,n.style=r,this.wrapper.append(n);const c=document.createElement("div");c.innerHTML=`${i}`,c.className="p-gui__folder-header",n.append(c),c.addEventListener("click",()=>{n.classList.toggle("p-gui__folder--closed")});let l=new E({isFolder:!0,folderOptions:{wrapper:n,parent:this,firstParent:this.firstParent}});return this.folders.push(l),l}_makeDraggable(){var e=this;this.header.addEventListener("pointerdown",t),this.header.addEventListener("pointerup",a);function t(s){s.preventDefault(),e.position.initX=e.position.x,e.position.initY=e.position.y,e.position.prevX=s.clientX,e.position.prevY=s.clientY,document.addEventListener("pointermove",i)}function i(s){s.preventDefault(),e.hasBeenDragged||(e.hasBeenDragged=!0,e.wrapper.setAttribute("data-dragged","true")),e.position.x=e.position.initX+s.clientX-e.position.prevX,e.position.y=e.position.initY+s.clientY-e.position.prevY,e.wrapper.style.transform="translate3d("+e.position.x+"px,"+e.position.y+"px,0)"}function a(s){document.removeEventListener("pointermove",i)}}toggleClose(){this.closed=!this.closed,this.closed?(this.previousInnerScroll=this.wrapper.scrollTop,this.wrapper.scrollTo(0,0)):this.wrapper.scrollTo(0,this.previousInnerScroll),this.wrapper.classList.toggle("p-gui--collapsed")}kill(){this.wrapper.remove()}_mapLinear(e,t,i,a,s){return a+(e-t)*(s-a)/(i-t)}_countDecimals(e){const t=e.toString(),i=t.indexOf(".");return i===-1?0:t.length-i-1}}return E});
533 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "perfect-gui",
3 | "version": "4.9.3",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "perfect-gui",
9 | "version": "4.9.3",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "vite": "^4.5.0"
13 | }
14 | },
15 | "node_modules/@esbuild/android-arm": {
16 | "version": "0.18.20",
17 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
18 | "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
19 | "cpu": [
20 | "arm"
21 | ],
22 | "dev": true,
23 | "optional": true,
24 | "os": [
25 | "android"
26 | ],
27 | "engines": {
28 | "node": ">=12"
29 | }
30 | },
31 | "node_modules/@esbuild/android-arm64": {
32 | "version": "0.18.20",
33 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
34 | "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
35 | "cpu": [
36 | "arm64"
37 | ],
38 | "dev": true,
39 | "optional": true,
40 | "os": [
41 | "android"
42 | ],
43 | "engines": {
44 | "node": ">=12"
45 | }
46 | },
47 | "node_modules/@esbuild/android-x64": {
48 | "version": "0.18.20",
49 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
50 | "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
51 | "cpu": [
52 | "x64"
53 | ],
54 | "dev": true,
55 | "optional": true,
56 | "os": [
57 | "android"
58 | ],
59 | "engines": {
60 | "node": ">=12"
61 | }
62 | },
63 | "node_modules/@esbuild/darwin-arm64": {
64 | "version": "0.18.20",
65 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
66 | "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
67 | "cpu": [
68 | "arm64"
69 | ],
70 | "dev": true,
71 | "optional": true,
72 | "os": [
73 | "darwin"
74 | ],
75 | "engines": {
76 | "node": ">=12"
77 | }
78 | },
79 | "node_modules/@esbuild/darwin-x64": {
80 | "version": "0.18.20",
81 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
82 | "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
83 | "cpu": [
84 | "x64"
85 | ],
86 | "dev": true,
87 | "optional": true,
88 | "os": [
89 | "darwin"
90 | ],
91 | "engines": {
92 | "node": ">=12"
93 | }
94 | },
95 | "node_modules/@esbuild/freebsd-arm64": {
96 | "version": "0.18.20",
97 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
98 | "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
99 | "cpu": [
100 | "arm64"
101 | ],
102 | "dev": true,
103 | "optional": true,
104 | "os": [
105 | "freebsd"
106 | ],
107 | "engines": {
108 | "node": ">=12"
109 | }
110 | },
111 | "node_modules/@esbuild/freebsd-x64": {
112 | "version": "0.18.20",
113 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
114 | "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
115 | "cpu": [
116 | "x64"
117 | ],
118 | "dev": true,
119 | "optional": true,
120 | "os": [
121 | "freebsd"
122 | ],
123 | "engines": {
124 | "node": ">=12"
125 | }
126 | },
127 | "node_modules/@esbuild/linux-arm": {
128 | "version": "0.18.20",
129 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
130 | "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
131 | "cpu": [
132 | "arm"
133 | ],
134 | "dev": true,
135 | "optional": true,
136 | "os": [
137 | "linux"
138 | ],
139 | "engines": {
140 | "node": ">=12"
141 | }
142 | },
143 | "node_modules/@esbuild/linux-arm64": {
144 | "version": "0.18.20",
145 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
146 | "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
147 | "cpu": [
148 | "arm64"
149 | ],
150 | "dev": true,
151 | "optional": true,
152 | "os": [
153 | "linux"
154 | ],
155 | "engines": {
156 | "node": ">=12"
157 | }
158 | },
159 | "node_modules/@esbuild/linux-ia32": {
160 | "version": "0.18.20",
161 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
162 | "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
163 | "cpu": [
164 | "ia32"
165 | ],
166 | "dev": true,
167 | "optional": true,
168 | "os": [
169 | "linux"
170 | ],
171 | "engines": {
172 | "node": ">=12"
173 | }
174 | },
175 | "node_modules/@esbuild/linux-loong64": {
176 | "version": "0.18.20",
177 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
178 | "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
179 | "cpu": [
180 | "loong64"
181 | ],
182 | "dev": true,
183 | "optional": true,
184 | "os": [
185 | "linux"
186 | ],
187 | "engines": {
188 | "node": ">=12"
189 | }
190 | },
191 | "node_modules/@esbuild/linux-mips64el": {
192 | "version": "0.18.20",
193 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
194 | "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
195 | "cpu": [
196 | "mips64el"
197 | ],
198 | "dev": true,
199 | "optional": true,
200 | "os": [
201 | "linux"
202 | ],
203 | "engines": {
204 | "node": ">=12"
205 | }
206 | },
207 | "node_modules/@esbuild/linux-ppc64": {
208 | "version": "0.18.20",
209 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
210 | "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
211 | "cpu": [
212 | "ppc64"
213 | ],
214 | "dev": true,
215 | "optional": true,
216 | "os": [
217 | "linux"
218 | ],
219 | "engines": {
220 | "node": ">=12"
221 | }
222 | },
223 | "node_modules/@esbuild/linux-riscv64": {
224 | "version": "0.18.20",
225 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
226 | "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
227 | "cpu": [
228 | "riscv64"
229 | ],
230 | "dev": true,
231 | "optional": true,
232 | "os": [
233 | "linux"
234 | ],
235 | "engines": {
236 | "node": ">=12"
237 | }
238 | },
239 | "node_modules/@esbuild/linux-s390x": {
240 | "version": "0.18.20",
241 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
242 | "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
243 | "cpu": [
244 | "s390x"
245 | ],
246 | "dev": true,
247 | "optional": true,
248 | "os": [
249 | "linux"
250 | ],
251 | "engines": {
252 | "node": ">=12"
253 | }
254 | },
255 | "node_modules/@esbuild/linux-x64": {
256 | "version": "0.18.20",
257 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
258 | "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
259 | "cpu": [
260 | "x64"
261 | ],
262 | "dev": true,
263 | "optional": true,
264 | "os": [
265 | "linux"
266 | ],
267 | "engines": {
268 | "node": ">=12"
269 | }
270 | },
271 | "node_modules/@esbuild/netbsd-x64": {
272 | "version": "0.18.20",
273 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
274 | "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
275 | "cpu": [
276 | "x64"
277 | ],
278 | "dev": true,
279 | "optional": true,
280 | "os": [
281 | "netbsd"
282 | ],
283 | "engines": {
284 | "node": ">=12"
285 | }
286 | },
287 | "node_modules/@esbuild/openbsd-x64": {
288 | "version": "0.18.20",
289 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
290 | "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
291 | "cpu": [
292 | "x64"
293 | ],
294 | "dev": true,
295 | "optional": true,
296 | "os": [
297 | "openbsd"
298 | ],
299 | "engines": {
300 | "node": ">=12"
301 | }
302 | },
303 | "node_modules/@esbuild/sunos-x64": {
304 | "version": "0.18.20",
305 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
306 | "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
307 | "cpu": [
308 | "x64"
309 | ],
310 | "dev": true,
311 | "optional": true,
312 | "os": [
313 | "sunos"
314 | ],
315 | "engines": {
316 | "node": ">=12"
317 | }
318 | },
319 | "node_modules/@esbuild/win32-arm64": {
320 | "version": "0.18.20",
321 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
322 | "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
323 | "cpu": [
324 | "arm64"
325 | ],
326 | "dev": true,
327 | "optional": true,
328 | "os": [
329 | "win32"
330 | ],
331 | "engines": {
332 | "node": ">=12"
333 | }
334 | },
335 | "node_modules/@esbuild/win32-ia32": {
336 | "version": "0.18.20",
337 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
338 | "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
339 | "cpu": [
340 | "ia32"
341 | ],
342 | "dev": true,
343 | "optional": true,
344 | "os": [
345 | "win32"
346 | ],
347 | "engines": {
348 | "node": ">=12"
349 | }
350 | },
351 | "node_modules/@esbuild/win32-x64": {
352 | "version": "0.18.20",
353 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
354 | "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
355 | "cpu": [
356 | "x64"
357 | ],
358 | "dev": true,
359 | "optional": true,
360 | "os": [
361 | "win32"
362 | ],
363 | "engines": {
364 | "node": ">=12"
365 | }
366 | },
367 | "node_modules/esbuild": {
368 | "version": "0.18.20",
369 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
370 | "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
371 | "dev": true,
372 | "hasInstallScript": true,
373 | "bin": {
374 | "esbuild": "bin/esbuild"
375 | },
376 | "engines": {
377 | "node": ">=12"
378 | },
379 | "optionalDependencies": {
380 | "@esbuild/android-arm": "0.18.20",
381 | "@esbuild/android-arm64": "0.18.20",
382 | "@esbuild/android-x64": "0.18.20",
383 | "@esbuild/darwin-arm64": "0.18.20",
384 | "@esbuild/darwin-x64": "0.18.20",
385 | "@esbuild/freebsd-arm64": "0.18.20",
386 | "@esbuild/freebsd-x64": "0.18.20",
387 | "@esbuild/linux-arm": "0.18.20",
388 | "@esbuild/linux-arm64": "0.18.20",
389 | "@esbuild/linux-ia32": "0.18.20",
390 | "@esbuild/linux-loong64": "0.18.20",
391 | "@esbuild/linux-mips64el": "0.18.20",
392 | "@esbuild/linux-ppc64": "0.18.20",
393 | "@esbuild/linux-riscv64": "0.18.20",
394 | "@esbuild/linux-s390x": "0.18.20",
395 | "@esbuild/linux-x64": "0.18.20",
396 | "@esbuild/netbsd-x64": "0.18.20",
397 | "@esbuild/openbsd-x64": "0.18.20",
398 | "@esbuild/sunos-x64": "0.18.20",
399 | "@esbuild/win32-arm64": "0.18.20",
400 | "@esbuild/win32-ia32": "0.18.20",
401 | "@esbuild/win32-x64": "0.18.20"
402 | }
403 | },
404 | "node_modules/fsevents": {
405 | "version": "2.3.3",
406 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
407 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
408 | "dev": true,
409 | "hasInstallScript": true,
410 | "optional": true,
411 | "os": [
412 | "darwin"
413 | ],
414 | "engines": {
415 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
416 | }
417 | },
418 | "node_modules/nanoid": {
419 | "version": "3.3.6",
420 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
421 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
422 | "dev": true,
423 | "funding": [
424 | {
425 | "type": "github",
426 | "url": "https://github.com/sponsors/ai"
427 | }
428 | ],
429 | "bin": {
430 | "nanoid": "bin/nanoid.cjs"
431 | },
432 | "engines": {
433 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
434 | }
435 | },
436 | "node_modules/picocolors": {
437 | "version": "1.0.0",
438 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
439 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
440 | "dev": true
441 | },
442 | "node_modules/postcss": {
443 | "version": "8.4.31",
444 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
445 | "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
446 | "dev": true,
447 | "funding": [
448 | {
449 | "type": "opencollective",
450 | "url": "https://opencollective.com/postcss/"
451 | },
452 | {
453 | "type": "tidelift",
454 | "url": "https://tidelift.com/funding/github/npm/postcss"
455 | },
456 | {
457 | "type": "github",
458 | "url": "https://github.com/sponsors/ai"
459 | }
460 | ],
461 | "dependencies": {
462 | "nanoid": "^3.3.6",
463 | "picocolors": "^1.0.0",
464 | "source-map-js": "^1.0.2"
465 | },
466 | "engines": {
467 | "node": "^10 || ^12 || >=14"
468 | }
469 | },
470 | "node_modules/rollup": {
471 | "version": "3.29.4",
472 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
473 | "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
474 | "dev": true,
475 | "bin": {
476 | "rollup": "dist/bin/rollup"
477 | },
478 | "engines": {
479 | "node": ">=14.18.0",
480 | "npm": ">=8.0.0"
481 | },
482 | "optionalDependencies": {
483 | "fsevents": "~2.3.2"
484 | }
485 | },
486 | "node_modules/source-map-js": {
487 | "version": "1.0.2",
488 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
489 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
490 | "dev": true,
491 | "engines": {
492 | "node": ">=0.10.0"
493 | }
494 | },
495 | "node_modules/vite": {
496 | "version": "4.5.0",
497 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz",
498 | "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==",
499 | "dev": true,
500 | "dependencies": {
501 | "esbuild": "^0.18.10",
502 | "postcss": "^8.4.27",
503 | "rollup": "^3.27.1"
504 | },
505 | "bin": {
506 | "vite": "bin/vite.js"
507 | },
508 | "engines": {
509 | "node": "^14.18.0 || >=16.0.0"
510 | },
511 | "funding": {
512 | "url": "https://github.com/vitejs/vite?sponsor=1"
513 | },
514 | "optionalDependencies": {
515 | "fsevents": "~2.3.2"
516 | },
517 | "peerDependencies": {
518 | "@types/node": ">= 14",
519 | "less": "*",
520 | "lightningcss": "^1.21.0",
521 | "sass": "*",
522 | "stylus": "*",
523 | "sugarss": "*",
524 | "terser": "^5.4.0"
525 | },
526 | "peerDependenciesMeta": {
527 | "@types/node": {
528 | "optional": true
529 | },
530 | "less": {
531 | "optional": true
532 | },
533 | "lightningcss": {
534 | "optional": true
535 | },
536 | "sass": {
537 | "optional": true
538 | },
539 | "stylus": {
540 | "optional": true
541 | },
542 | "sugarss": {
543 | "optional": true
544 | },
545 | "terser": {
546 | "optional": true
547 | }
548 | }
549 | }
550 | }
551 | }
552 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "perfect-gui",
3 | "version": "4.11.9",
4 | "description": "GUI for JavaScript",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "build": "vite build"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/thibka/perfect-gui.git"
12 | },
13 | "keywords": [
14 | "gui"
15 | ],
16 | "author": "thibka",
17 | "license": "MIT",
18 | "bugs": {
19 | "url": "https://github.com/thibka/perfect-gui/issues"
20 | },
21 | "homepage": "https://thibka.github.io/perfect-gui/public/",
22 | "devDependencies": {
23 | "vite": "^4.5.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/addons/three.js:
--------------------------------------------------------------------------------
1 | import GUI from '../index.js';
2 |
3 | /*
4 | GUI.prototype.materialFolder = materialFolder;
5 | gui.materialFolder({name: 'material', obj: material})
6 | */
7 | export function materialFolder(params = {}) {
8 | const material = params.obj;
9 |
10 | if (material.isMeshStandardMaterial) {
11 | console.error('[GUI materialFolder]', 'materialFolder() only works with meshStandardMaterial');
12 | }
13 |
14 | const folder = this.folder({ name: params.name });
15 |
16 | folder.slider({ obj: material, prop: 'roughness' });
17 | folder.slider({ obj: material, prop: 'metalness' });
18 | folder.color({ name: 'color', obj: material, value: '#'+material.color.getHexString() }, val => {
19 | material.color.set(val)
20 | });
21 | folder.slider({ obj: material, prop: 'envMapIntensity' });
22 | return folder;
23 | }
24 |
25 | export function threejsColor() {
26 |
27 | }
--------------------------------------------------------------------------------
/src/components/Slider.js:
--------------------------------------------------------------------------------
1 | export default class Slider {
2 | constructor(parent, params = {}, callback) {
3 | this.parent = parent;
4 | this.propReferences = [];
5 |
6 | if (typeof params != 'object') {
7 | throw Error(`[GUI] slider() first parameter must be an object. Received: ${typeof params}.`);
8 | }
9 |
10 | let name = typeof params.name == 'string' ? params.name || ' ' : ' ';
11 | this.isObject = false;
12 | let propReferenceIndex = null;
13 | this.obj = params.obj;
14 | this.prop = params.prop;
15 | let value = typeof params.value == 'number' ? params.value : null;
16 | this.min = params.min ?? 0;
17 | this.max = params.max ?? 1;
18 | this.step = params.step || (this.max - this.min) / 100;
19 | this.decimals = this.parent._countDecimals(this.step);
20 | this.callback = typeof callback == 'function' ? callback : null;
21 |
22 | // callback mode
23 | if ( value !== null ) {
24 | if (this.prop != undefined || this.obj != undefined) {
25 | console.warn(`[GUI] slider() "obj" and "prop" parameters are ignored when a "value" parameter is used.`);
26 | }
27 | }
28 | // object-binding
29 | else if (this.prop != undefined && this.obj != undefined) {
30 | if (typeof this.prop != 'string') {
31 | throw Error(`[GUI] slider() "prop" parameter must be an string. Received: ${typeof this.prop}.`);
32 | }
33 | if (typeof this.obj != 'object') {
34 | throw Error(`[GUI] slider() "obj" parameter must be an object. Received: ${typeof this.obj}.`);
35 | }
36 |
37 | if (name == ' ') {
38 | name = this.prop;
39 | }
40 |
41 | propReferenceIndex = this.propReferences.push(this.obj[this.prop]) - 1;
42 | this.isObject = true;
43 | }
44 | else {
45 | if ((this.prop != undefined && this.obj == undefined) || (this.prop == undefined && this.obj != undefined)) {
46 | console.warn(`[GUI] slider() "obj" and "prop" parameters must be used together.`);
47 | }
48 |
49 | value = (this.max - this.min) / 2;
50 | }
51 | const tooltip = (typeof params.tooltip === 'string') ? params.tooltip : (params.tooltip === true ? name : null);
52 |
53 | this.imageContainer = null;
54 |
55 | const container = document.createElement('div');
56 | container.className = 'p-gui__slider';
57 |
58 | if (tooltip) {
59 | container.setAttribute('title', tooltip);
60 | }
61 |
62 | const slider_name = document.createElement('div');
63 | slider_name.className = 'p-gui__slider-name';
64 | slider_name.textContent = name;
65 | container.append(slider_name);
66 |
67 | this.ctrlDiv = document.createElement('div');
68 | this.ctrlDiv.className = 'p-gui__slider-ctrl';
69 | this.ctrlDiv.setAttribute('type', 'range');
70 | this.ctrlDiv.setAttribute('min', this.min);
71 | this.ctrlDiv.setAttribute('max', this.max);
72 | container.append(this.ctrlDiv);
73 |
74 | const slider_bar = document.createElement('div');
75 | slider_bar.className = 'p-gui__slider-bar';
76 | this.ctrlDiv.append(slider_bar);
77 |
78 | this.handle = document.createElement('div');
79 | this.handle.className = 'p-gui__slider-handle';
80 | this.ctrlDiv.append(this.handle);
81 |
82 | this.filling = document.createElement('div');
83 | this.filling.className = 'p-gui__slider-filling';
84 | slider_bar.append(this.filling);
85 |
86 | this.valueInput = document.createElement('input');
87 | this.valueInput.className = 'p-gui__slider-value';
88 | this.valueInput.value = this.isObject ? this.obj[this.prop] : value;
89 | container.append(this.valueInput);
90 |
91 | // init position
92 | setTimeout(() => {
93 | const handleWidth = this.handle.offsetWidth;
94 | this.handle.position = this._mapLinear(this.valueInput.value, this.min, this.max, handleWidth / 2, 88 - handleWidth / 2);
95 | this.handle.position = Math.min(this.handle.position, 88 - handleWidth / 2);
96 | this.handle.position = Math.max(this.handle.position, handleWidth / 2);
97 | this.handle.style.transform = `translate(-50%, -50%) translateX(${this.handle.position}px)`;
98 | this.filling.style.width = `${this.handle.position}px`;
99 | }, 0); // wait for render
100 |
101 | this.valueInput.addEventListener('change', () => {
102 | this._updateHandlePositionFromValue();
103 | this._triggerCallbacks();
104 | })
105 |
106 | this.ctrlDiv.addEventListener('pointerdown', (evt) => {
107 | this.ctrlDiv.pointerDown = true;
108 | this.ctrlDiv.prevPosition = evt.clientX;
109 | this._updateHandlePositionFromPointer(evt, true);
110 | });
111 |
112 | window.addEventListener('pointerup', (evt) => {
113 | this.ctrlDiv.pointerDown = false;
114 | });
115 |
116 | window.addEventListener('pointermove', (evt) => {
117 | if (this.ctrlDiv.pointerDown) {
118 | this.ctrlDiv.pointerDelta = evt.clientX - this.ctrlDiv.prevPosition;
119 | this._updateHandlePositionFromPointer(evt);
120 | }
121 | });
122 |
123 | if ( this.isObject ) {
124 | Object.defineProperty( this.obj, this.prop, {
125 | set: val => {
126 | this.propReferences[propReferenceIndex] = val;
127 | this.valueInput.value = val;
128 |
129 | this._updateHandlePositionFromValue();
130 |
131 | if (this.callback) {
132 | this.callback(parseFloat(this.valueInput.value));
133 | }
134 | },
135 | get: () => {
136 | return this.propReferences[propReferenceIndex];
137 | }
138 | });
139 | }
140 |
141 | return container;
142 | }
143 |
144 | _updateHandlePositionFromPointer(evt, firstDown = false) {
145 | const sliderWidth = this.ctrlDiv.offsetWidth;
146 | const handleWidth = this.handle.offsetWidth;
147 | const pointerDelta = evt.clientX - this.ctrlDiv.prevPosition;
148 | const currentValue = parseFloat(this.valueInput.value);
149 | let handlePosition;
150 |
151 | if (firstDown) {
152 | handlePosition = evt.offsetX;
153 | } else {
154 | handlePosition = this.handle.position + pointerDelta;
155 | }
156 |
157 | handlePosition = Math.max(handleWidth / 2, Math.min(handlePosition, sliderWidth - handleWidth / 2));
158 |
159 | let newValue = this.min + (this.max - this.min) * (handlePosition - handleWidth / 2) / (sliderWidth - handleWidth);
160 | if ( newValue > currentValue ) {
161 | newValue = this._quantizeFloor(newValue, this.step);
162 | } else {
163 | newValue = this._quantizeCeil(newValue, this.step);
164 | }
165 |
166 | // toFixed(9) avoids weird javascript infinite decimals
167 | newValue = parseFloat(newValue.toFixed(9));
168 | const nextValue = parseFloat((currentValue + this.step).toFixed(9));
169 | const prevValue = parseFloat((currentValue - this.step).toFixed(9));
170 |
171 | if (newValue >= nextValue || newValue <= prevValue) {
172 | newValue = newValue.toFixed(this.decimals);
173 |
174 | this.valueInput.value = newValue;
175 |
176 | this.ctrlDiv.prevPosition = evt.clientX;
177 |
178 | this.handle.style.transform = `translate(-50%, -50%) translateX(${handlePosition}px)`;
179 | this.handle.position = handlePosition;
180 |
181 | this.filling.style.width = this.handle.position + 'px';
182 |
183 | this._triggerCallbacks();
184 | }
185 | }
186 |
187 | _updateHandlePositionFromValue() {
188 | const sliderWidth = this.ctrlDiv.offsetWidth;
189 | const handleWidth = this.handle.offsetWidth;
190 | let handlePosition = this._mapLinear(this.valueInput.value, this.min, this.max, handleWidth / 2, sliderWidth - handleWidth / 2);
191 |
192 | handlePosition = Math.max(handleWidth / 2, Math.min(handlePosition, sliderWidth - handleWidth / 2));
193 |
194 | this.handle.style.transform = `translate(-50%, -50%) translateX(${handlePosition}px)`;
195 | this.handle.position = handlePosition;
196 |
197 | this.filling.style.width = this.handle.position + 'px';
198 | }
199 |
200 | _triggerCallbacks() {
201 | if ( this.isObject ) {
202 | this.obj[this.prop] = parseFloat(this.valueInput.value);
203 | }
204 | else {
205 | if (this.callback) {
206 | this.callback(parseFloat(this.valueInput.value));
207 | }
208 | }
209 |
210 | if (this.parent.onUpdate) {
211 | this.parent.onUpdate();
212 | } else if (this.parent.isFolder && this.parent.firstParent.onUpdate) {
213 | this.parent.firstParent.onUpdate();
214 | }
215 | }
216 |
217 | _mapLinear( x, a1, a2, b1, b2 ) {
218 | return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
219 | }
220 |
221 | _quantize(x, step) {
222 | return step * Math.round(x / step);
223 | }
224 |
225 | _quantizeCeil(x, step) {
226 | return step * Math.ceil(x / step);
227 | }
228 |
229 | _quantizeFloor(x, step) {
230 | return step * Math.floor(x / step);
231 | }
232 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Slider from './components/Slider';
2 | import styles from './styles/styles';
3 |
4 | export default class GUI {
5 | constructor(options = {}) {
6 | this.firstParent = this;
7 |
8 | if ( options.container ) {
9 | this.container = typeof options.container == "string" ? document.querySelector(options.container) : options.container;
10 | this.position_type = 'absolute';
11 | } else {
12 | this.container = document.body;
13 | this.position_type = 'fixed';
14 | }
15 |
16 | this.propReferences = [];
17 | this.folders = [];
18 |
19 | if ( options.isFolder ) {
20 | this._folderConstructor(options.folderOptions);
21 | return;
22 | }
23 |
24 | if ( typeof options.onUpdate == 'function' ) {
25 | this.onUpdate = options.onUpdate;
26 | }
27 |
28 | this.name = (options != undefined && typeof options.name == "string") ? options.name : '';
29 |
30 | this.backgroundColor = options.color || null;
31 | this.opacity = options.opacity || 1;
32 |
33 | if (this.container == document.body) {
34 | this.maxHeight = window.innerHeight;
35 | } else {
36 | this.maxHeight = Math.min(this.container.clientHeight, window.innerHeight)
37 | }
38 | if ( options.maxHeight ) {
39 | this.initMaxHeight = options.maxHeight;
40 | this.maxHeight = Math.min(this.initMaxHeight, this.maxHeight);
41 | }
42 |
43 | this.screenCorner = this._parseScreenCorner(options.position);
44 |
45 | if (!window.perfectGUI) {
46 | window.perfectGUI = {};
47 | }
48 | if ( window.perfectGUI.instanceCounter == undefined ) {
49 | window.perfectGUI.instanceCounter = 0;
50 | } else {
51 | window.perfectGUI.instanceCounter++;
52 | }
53 | this.instanceId = window.perfectGUI.instanceCounter;
54 |
55 | this.wrapperWidth = options.width || 290;
56 | this.stylesheet = document.createElement('style');
57 | this.stylesheet.setAttribute('type', 'text/css');
58 | this.stylesheet.setAttribute('id', 'lm-gui-stylesheet');
59 | document.head.append(this.stylesheet);
60 |
61 | // Common styles
62 | if (this.instanceId == 0) {
63 | this._addStyles(`${styles(this.position_type)}`);
64 | }
65 |
66 | // Instance specific styles
67 | this._styleInstance();
68 |
69 | this._addWrapper();
70 | this.wrapper.setAttribute('data-corner-x', this.screenCorner.x);
71 | this.wrapper.setAttribute('data-corner-y', this.screenCorner.y);
72 |
73 | if (options.autoRepositioning != false) {
74 | window.addEventListener('resize', this._handleResize.bind(this));
75 | }
76 | this._handleResize();
77 |
78 | this.hasBeenDragged = false;
79 | if (options.draggable == true) this._makeDraggable();
80 |
81 | this.closed = false;
82 | if (options.closed) this.toggleClose();
83 | }
84 |
85 | _styleInstance() {
86 | let scrollbar_width = this._getScrollbarWidth(this.container);
87 | if (this.screenCorner.x == 'left') {
88 | this.xOffset = 0;
89 | } else {
90 | this.xOffset = this.container.clientWidth - this.wrapperWidth - scrollbar_width;
91 | }
92 |
93 | if (this.instanceId > 0) {
94 | let existingDomInstances = this.container.querySelectorAll('.p-gui');
95 | for (let i = 0; i < existingDomInstances.length; i++) {
96 | if (this.screenCorner.y == existingDomInstances[i].dataset.cornerY) {
97 | if (this.screenCorner.x == 'left' && existingDomInstances[i].dataset.cornerX == 'left') {
98 | this.xOffset += existingDomInstances[i].offsetWidth;
99 | }
100 | else if (this.screenCorner.x == 'right' && existingDomInstances[i].dataset.cornerX == 'right') {
101 | this.xOffset -= existingDomInstances[i].offsetWidth;
102 | }
103 | }
104 | }
105 | }
106 | this.yOffset = 0;
107 | this.position = {
108 | prevX: this.xOffset,
109 | prevY: this.yOffset,
110 | x: this.xOffset,
111 | y: this.yOffset
112 | };
113 |
114 | this._addStyles(`#p-gui-${this.instanceId} {
115 | width: ${this.wrapperWidth}px;
116 | max-height: ${this.maxHeight}px;
117 | transform: translate3d(${this.xOffset}px,${this.yOffset}px,0);
118 | ${ this.screenCorner.y == 'top' ? '' : 'top: auto; bottom: 0;' }
119 | ${ this.backgroundColor ? 'background: ' + this.backgroundColor + ';' : '' }
120 | opacity: ${this.opacity};
121 | }`);
122 | }
123 |
124 | _folderConstructor(folderOptions) {
125 | this.wrapper = folderOptions.wrapper;
126 | this.isFolder = true;
127 | this.parent = folderOptions.parent;
128 | this.firstParent = folderOptions.firstParent;
129 | }
130 |
131 | _parseScreenCorner(position) {
132 | let parsedPosition = {x: 'right', y: 'top'};
133 |
134 | if (position == undefined) return parsedPosition;
135 | else if (typeof position != 'string') console.error('[perfect-gui] Position must be a string.');
136 |
137 | if (position.includes('left')) parsedPosition.x = 'left';
138 | if (position.includes('bottom')) parsedPosition.y = 'bottom';
139 |
140 | return parsedPosition;
141 | }
142 |
143 | _getScrollbarWidth(element) {
144 | if (element === document.body) {
145 | return window.innerWidth - document.documentElement.clientWidth;
146 | } else {
147 | return element.offsetWidth - element.clientWidth;
148 | }
149 | }
150 |
151 | _handleResize() {
152 | if (this.container == document.body) {
153 | this.maxHeight = window.innerHeight;
154 | } else {
155 | this.maxHeight = Math.min(this.container.clientHeight, window.innerHeight)
156 | }
157 | if (this.initMaxHeight) {
158 | this.maxHeight = Math.min(this.initMaxHeight, this.maxHeight);
159 | }
160 | this.wrapper.style.maxHeight = this.maxHeight + 'px';
161 |
162 | if (this.hasBeenDragged) {
163 | return;
164 | }
165 |
166 | let scrollbar_width = this._getScrollbarWidth(this.container);
167 | this.xOffset = this.screenCorner.x == 'left' ? 0 : this.container.clientWidth - this.wrapperWidth - scrollbar_width;
168 | if (this.instanceId > 0) {
169 | let existingDomInstances = this.container.querySelectorAll(`.p-gui:not(#${this.wrapper.id}):not([data-dragged])`);
170 | for (let i = 0; i < existingDomInstances.length; i++) {
171 | let instanceId = parseInt(existingDomInstances[i].id.replace('p-gui-', ''));
172 | if (instanceId > this.instanceId) break;
173 | if (this.screenCorner.y == existingDomInstances[i].dataset.cornerY) {
174 | if (this.screenCorner.x == 'left' && existingDomInstances[i].dataset.cornerX == 'left') {
175 | this.xOffset += existingDomInstances[i].offsetWidth;
176 | }
177 | else if (this.screenCorner.x == 'right' && existingDomInstances[i].dataset.cornerX == 'right') {
178 | this.xOffset -= existingDomInstances[i].offsetWidth;
179 | }
180 | }
181 | }
182 | }
183 | this.position = {prevX:this.xOffset, prevY:this.yOffset, x:this.xOffset, y:this.yOffset};
184 | this.wrapper.style.transform = `translate3d(${this.position.x}px, ${this.position.y}px, 0)`;
185 | }
186 |
187 | _addStyles(styles) {
188 | this.stylesheet.innerHTML += styles;
189 | }
190 |
191 | _addWrapper() {
192 | this.wrapper = document.createElement('div');
193 | this.wrapper.id = 'p-gui-'+this.instanceId;
194 | this.wrapper.className = 'p-gui';
195 | this.wrapper.setAttribute('data-lenis-prevent', '');
196 | this.container.append(this.wrapper);
197 |
198 | this.header = document.createElement('div');
199 | this.header.className = 'p-gui__header';
200 | this.header.textContent = this.name;
201 | this.header.style = `${ this.backgroundColor ? 'border-color: ' + this.backgroundColor + ';' : ''}`;
202 | this.wrapper.append(this.header);
203 |
204 | const close_btn = document.createElement('div');
205 | close_btn.className = 'p-gui__header-close';
206 | close_btn.addEventListener('click', this.toggleClose.bind(this));
207 | this.header.append(close_btn);
208 | }
209 |
210 | button(options, callback) {
211 | let name = '';
212 | if (typeof options != 'string') {
213 | if (typeof options == 'object' && options?.hasOwnProperty('name')) {
214 | name = options.name == '' ? ' ' : options.name;
215 | } else {
216 | name = ' ';
217 | }
218 | } else {
219 | name = options == '' ? ' ' : options;
220 | }
221 |
222 | const tooltip = (typeof options.tooltip === 'string') ? options.tooltip : (options.tooltip === true ? name : null);
223 |
224 | this.imageContainer = null;
225 |
226 | const el = document.createElement('div');
227 | el.className = 'p-gui__button';
228 | el.textContent = name;
229 | if ( tooltip ) {
230 | el.setAttribute('title', tooltip);
231 | }
232 | el.addEventListener('click', () => {
233 | if (callback) {
234 | callback();
235 | }
236 |
237 | if (this.onUpdate) {
238 | this.onUpdate();
239 | } else if (this.isFolder && this.firstParent.onUpdate) {
240 | this.firstParent.onUpdate();
241 | }
242 | });
243 | this.wrapper.append(el);
244 |
245 | if (typeof options.color == 'string') {
246 | el.style.setProperty('--color-accent', options.color);
247 | el.style.setProperty('--color-accent-hover', options.hoverColor || options.color);
248 | }
249 | }
250 |
251 | image(params = {}, callback) {
252 | if (typeof params != 'object') {
253 | throw Error(`[GUI] image() first parameter must be an object. Received: ${typeof params}.`);
254 | }
255 |
256 | let path;
257 | if (typeof params.path == 'string') {
258 | path = params.path;
259 | } else {
260 | if (typeof params.path == undefined) {
261 | throw Error(`[GUI] image() path must be provided.`);
262 | } else {
263 | throw Error(`[GUI] image() path must be a string.`);
264 | }
265 | }
266 | let filename = path.replace(/^.*[\\\/]/, '');
267 | let name;
268 | if (params.name == undefined) {
269 | name = filename;
270 | } else {
271 | name = typeof params.name == 'string' ? params.name || ' ' : ' ';
272 | }
273 |
274 | const tooltip = (typeof params.tooltip === 'string') ? params.tooltip : (params.tooltip === true ? name : null);
275 |
276 | const selected = params.selected === true;
277 | const selectionBorder = params.selectionBorder !== false;
278 |
279 | // width & height options
280 | let inline_styles = '';
281 | if (params.width) {
282 | if (typeof params.width == 'number') {
283 | params.width += 'px';
284 | }
285 | inline_styles += `flex: 0 0 calc(${params.width} - 5px); `;
286 | }
287 |
288 | if (params.height) {
289 | if (typeof params.height == 'number') {
290 | params.height += 'px';
291 | }
292 | inline_styles += `height: ${params.height}; `;
293 | }
294 |
295 | if (!this.imageContainer) {
296 | this.imageContainer = document.createElement('div');
297 | this.imageContainer.className = 'p-gui__image-container';
298 | this.wrapper.append(this.imageContainer);
299 | }
300 |
301 | // Image button
302 | const image = document.createElement('div');
303 | image.className = 'p-gui__image';
304 | image.style = 'background-image: url(' + path + '); ' + inline_styles;
305 | if ( tooltip ) {
306 | image.setAttribute('title', tooltip);
307 | }
308 | this.imageContainer.append(image);
309 |
310 | if (selected && selectionBorder) {
311 | image.classList.add('p-gui__image--selected');
312 | }
313 |
314 | // Text inside image
315 | const text = document.createElement('div');
316 | text.className = 'p-gui__image-text';
317 | text.textContent = name;
318 | image.append(text);
319 |
320 | image.addEventListener('click', () => {
321 | let selected_items = image.parentElement.querySelectorAll('.p-gui__image--selected');
322 | for (let i = 0; i < selected_items.length; i++) {
323 | selected_items[i].classList.remove('p-gui__image--selected');
324 | }
325 | if (selectionBorder) {
326 | image.classList.add('p-gui__image--selected');
327 | }
328 | if (typeof callback == 'function') {
329 | callback({ path, text: name });
330 | }
331 | if (this.onUpdate) {
332 | this.onUpdate();
333 | } else if (this.isFolder && this.firstParent.onUpdate) {
334 | this.firstParent.onUpdate();
335 | }
336 | });
337 |
338 | return image;
339 | }
340 |
341 | slider (params = {}, callback) {
342 | const el = new Slider(this, params, callback);
343 | this.wrapper.append(el);
344 | }
345 |
346 | toggle(params = {}, callback) {
347 | if (typeof params != 'object') {
348 | throw Error(`[GUI] toggle() first parameter must be an object. Received: ${typeof params}.`);
349 | }
350 |
351 | let name = typeof params.name == 'string' ? params.name || ' ' : ' ';
352 | let isObject = false;
353 | let propReferenceIndex = null;
354 | let obj = params.obj;
355 | let prop = params.prop;
356 | let value = typeof params.value === 'boolean' ? params.value : null;
357 |
358 | // callback mode
359 | if ( value !== null ) {
360 | if (prop != undefined || obj != undefined) {
361 | console.warn(`[GUI] toggle() "obj" and "prop" parameters are ignored when a "value" parameter is used.`);
362 | }
363 | }
364 |
365 | // object-binding
366 | else if (prop != undefined && obj != undefined) {
367 | if (typeof prop != 'string') {
368 | throw Error(`[GUI] toggle() "prop" parameter must be an string. Received: ${typeof prop}.`);
369 | }
370 | if (typeof obj != 'object') {
371 | throw Error(`[GUI] toggle() "obj" parameter must be an object. Received: ${typeof obj}.`);
372 | }
373 |
374 | if (name == ' ') {
375 | name = prop;
376 | }
377 |
378 | propReferenceIndex = this.propReferences.push(obj[prop]) - 1;
379 | isObject = true;
380 | }
381 | else {
382 | if ((prop != undefined && obj == undefined) || (prop == undefined && obj == undefined)) {
383 | console.warn(`[GUI] toggle() "obj" and "prop" parameters must be used together.`);
384 | }
385 | }
386 |
387 | const tooltip = (typeof params.tooltip === 'string') ? params.tooltip : (params.tooltip === true ? name : null);
388 |
389 | this.imageContainer = null;
390 |
391 | const container = document.createElement('div');
392 | container.textContent = name;
393 | container.className = 'p-gui__switch';
394 | if ( tooltip ) {
395 | container.setAttribute('title', tooltip);
396 | }
397 | this.wrapper.append(container);
398 |
399 | container.addEventListener('click', (ev) => {
400 | const checkbox = ev.target.childNodes[1];
401 |
402 | let value = true;
403 |
404 | if (checkbox.classList.contains('p-gui__switch-checkbox--active')) {
405 | value = false;
406 | }
407 |
408 | checkbox.classList.toggle('p-gui__switch-checkbox--active');
409 |
410 | if ( isObject ) {
411 | obj[prop] = value;
412 | }
413 |
414 | else {
415 | if (typeof callback == 'function') {
416 | callback(value);
417 | }
418 | }
419 |
420 | if (this.onUpdate) {
421 | this.onUpdate();
422 | } else if (this.isFolder && this.firstParent.onUpdate) {
423 | this.firstParent.onUpdate();
424 | }
425 | });
426 |
427 | let activeClass = (() => {
428 | if (!isObject) {
429 | return value ? ' p-gui__switch-checkbox--active' : '';
430 | } else {
431 | return obj[prop] ? ' p-gui__switch-checkbox--active' : '';
432 | }
433 | })();
434 |
435 | const checkbox = document.createElement('div');
436 | checkbox.className = 'p-gui__switch-checkbox' + activeClass;
437 | container.append(checkbox);
438 |
439 | if ( isObject ) {
440 | Object.defineProperty( obj, prop, {
441 | set: val => {
442 | this.propReferences[propReferenceIndex] = val;
443 |
444 | if (val) {
445 | checkbox.classList.add('p-gui__switch-checkbox--active');
446 | } else {
447 | checkbox.classList.remove('p-gui__switch-checkbox--active');
448 | }
449 |
450 | if (typeof callback == 'function') {
451 | callback(val);
452 | }
453 | },
454 | get: () => {
455 | return this.propReferences[propReferenceIndex];
456 | }
457 | });
458 | }
459 | }
460 |
461 | list(params = {}, callback) {
462 | if (typeof params != 'object') {
463 | throw Error(`[GUI] list() first parameter must be an object. Received: ${typeof params}.`);
464 | }
465 |
466 | let name = typeof params.name == 'string' ? params.name : ' ';
467 | let isObject = false;
468 | let propReferenceIndex = null;
469 | let obj = params.obj;
470 | let prop = params.prop;
471 | let values = Array.isArray(params.values) ? params.values : null;
472 | let value;
473 | let objectValues = typeof values[0] == 'string' ? false : true;
474 | const tooltip = (typeof params.tooltip === 'string') ? params.tooltip : (params.tooltip === true ? name : null);
475 |
476 | callback = typeof callback == 'function' ? callback : null;
477 |
478 | // callback mode
479 | if ( params.value !== undefined ||
480 | (params.value === undefined && obj === undefined && prop === undefined)) {
481 | if (prop != undefined || obj != undefined) {
482 | console.warn(`[GUI] list() "obj" and "prop" parameters are ignored when a "value" parameter is used.`);
483 | }
484 |
485 | value = (() => {
486 | if (!values) {
487 | return null;
488 | }
489 | if (typeof params.value == 'string') {
490 | return values.indexOf(params.value);
491 | }
492 | if (typeof params.value == 'number') {
493 | return params.value;
494 | }
495 | })();
496 | }
497 |
498 | // object-binding mode
499 | else if (prop != undefined && obj != undefined) {
500 | if (typeof prop != 'string') {
501 | throw Error(`[GUI] list() "prop" parameter must be an string. Received: ${typeof prop}.`);
502 | }
503 | if (typeof obj != 'object') {
504 | throw Error(`[GUI] list() "obj" parameter must be an object. Received: ${typeof obj}.`);
505 | }
506 |
507 | value = (() => {
508 | if (!values) {
509 | return null;
510 | }
511 | if (typeof obj[prop] == 'string') {
512 | if ( !objectValues ) { // values is an array of strings
513 | return values.indexOf(obj[prop]);
514 | }
515 | else { // values is an array of objects
516 | return values.find(item => item.value === obj[prop]).value;
517 | }
518 | }
519 | if (typeof obj[prop] == 'number') {
520 | if ( !objectValues ) { // values is an array of strings
521 | return obj[prop];
522 | }
523 | else { // values is an array of objects
524 | return values.find(item => item.value === obj[prop]).value;
525 | }
526 | }
527 | })();
528 |
529 | propReferenceIndex = this.propReferences.push(obj[prop]) - 1;
530 | isObject = true;
531 | }
532 |
533 | else {
534 | if ((prop != undefined && obj == undefined) || (prop == undefined && obj == undefined)) {
535 | console.warn(`[GUI] list() "obj" and "prop" parameters must be used together.`);
536 | }
537 | }
538 |
539 | this.imageContainer = null;
540 |
541 | let container = document.createElement('div');
542 | container.className = 'p-gui__list';
543 | container.textContent = name;
544 | if (tooltip) {
545 | container.setAttribute('title', tooltip);
546 | }
547 | this.wrapper.append(container);
548 |
549 | let select = document.createElement('select');
550 | container.append(select);
551 | select.className = 'p-gui__list-dropdown';
552 | select.addEventListener('change', (ev) => {
553 | if ( isObject ) {
554 | obj[prop] = ev.target.value;
555 | }
556 |
557 | else if (callback) {
558 | callback(ev.target.value);
559 | }
560 |
561 | if (this.onUpdate) {
562 | this.onUpdate();
563 | } else if (this.isFolder && this.firstParent.onUpdate) {
564 | this.firstParent.onUpdate();
565 | }
566 | });
567 |
568 | if (values)
569 | {
570 | values.forEach((item, index) =>
571 | {
572 | const optionName = objectValues ? item.name : item;
573 | const optionValue = objectValues ? item.value : item;
574 | let option = document.createElement('option');
575 | option.setAttribute('value', optionValue);
576 | option.textContent = optionName;
577 | select.append(option);
578 |
579 | if (!objectValues && value == index || objectValues && value == optionValue) {
580 | option.setAttribute('selected', '');
581 | }
582 | });
583 | }
584 |
585 | if ( isObject ) {
586 | Object.defineProperty( obj, prop, {
587 | set: val => {
588 | let newIndex, newValue, newObj;
589 | if (objectValues) {
590 | newObj = values.find(item => {
591 | return item.value == val;
592 | });
593 | newValue = newObj?.value || values[0].value;
594 | newIndex = values.indexOf(newObj);
595 | } else {
596 | if (typeof val == 'string') {
597 | newIndex = values.indexOf(val);
598 | newValue = val;
599 | }
600 | if (typeof val == 'number') {
601 | newIndex = val;
602 | newValue = values[val];
603 | }
604 | }
605 |
606 | this.propReferences[propReferenceIndex] = objectValues ? newValue : val;
607 |
608 | const previousSelection = select.querySelector('[selected]');
609 | if ( previousSelection ) {
610 | previousSelection.removeAttribute('selected')
611 | }
612 | select.querySelectorAll('option')[newIndex].setAttribute('selected', '');
613 |
614 | if (typeof callback == 'function') {
615 | if (objectValues) {
616 | callback(newObj, newIndex);
617 | } else {
618 | callback(newValue, newIndex);
619 | }
620 | }
621 | },
622 | get: () => {
623 | return this.propReferences[propReferenceIndex];
624 | }
625 | });
626 | }
627 | }
628 |
629 | color(params = {}, callback) {
630 | if (typeof params != 'object') {
631 | throw Error(`[GUI] color() first parameter must be an object. Received: ${typeof params}.`);
632 | }
633 |
634 | let name = typeof params.name == 'string' ? params.name || ' ' : ' ';
635 |
636 | let isObject = false;
637 | let propReferenceIndex = null;
638 | let obj = params.obj;
639 | let prop = params.prop;
640 | let value;
641 | const tooltip = (typeof params.tooltip === 'string') ? params.tooltip : (params.tooltip === true ? name : null);
642 |
643 | if (typeof params.value == 'string') {
644 | if (params.value.length != 7 || params.value[0] != '#') {
645 | console.error(`[GUI] color() 'value' parameter must be an hexadecimal string in the format "#ffffff". Received: "${params.value}".`)
646 | }
647 | else {
648 | value = params.value;
649 | }
650 | }
651 | if (!value) value = '#000000';
652 |
653 | // callback mode
654 | if ( params.value !== undefined ) {
655 | if (prop != undefined || obj != undefined) {
656 | console.warn(`[GUI] color() "obj" and "prop" parameters are ignored when a "value" parameter is used.`);
657 | }
658 | }
659 |
660 | // object-binding
661 | else if (prop != undefined && obj != undefined) {
662 | if (typeof prop != 'string') {
663 | throw Error(`[GUI] color() "prop" parameter must be an string. Received: ${typeof prop}.`);
664 | }
665 | if (typeof obj != 'object') {
666 | throw Error(`[GUI] color() "obj" parameter must be an object. Received: ${typeof obj}.`);
667 | }
668 |
669 | if (name == ' ') {
670 | name = prop;
671 | }
672 |
673 | propReferenceIndex = this.propReferences.push(obj[prop]) - 1;
674 | isObject = true;
675 | }
676 | else {
677 | if ((prop != undefined && obj == undefined) || (prop == undefined && obj == undefined)) {
678 | console.warn(`[GUI] color() "obj" and "prop" parameters must be used together.`);
679 | }
680 | }
681 |
682 | this.imageContainer = null;
683 |
684 | const container = document.createElement('div');
685 | container.className = 'p-gui__color';
686 | container.textContent = name;
687 | if ( tooltip ) {
688 | container.setAttribute('title', tooltip);
689 | }
690 | this.wrapper.append(container);
691 |
692 | const colorpicker = document.createElement('input');
693 | colorpicker.className = 'p-gui__color-picker';
694 | colorpicker.setAttribute('type', 'color');
695 | colorpicker.value = value;
696 | container.append(colorpicker);
697 |
698 | if (typeof callback == 'function') {
699 | colorpicker.addEventListener('input', () => {
700 | if ( isObject ) {
701 | obj[prop] = colorpicker.value;
702 | }
703 |
704 | else if (typeof callback == 'function') {
705 | callback(colorpicker.value);
706 | }
707 |
708 | if (this.onUpdate) {
709 | this.onUpdate();
710 | } else if (this.isFolder && this.firstParent.onUpdate) {
711 | this.firstParent.onUpdate();
712 | }
713 | });
714 | }
715 |
716 | if ( isObject ) {
717 | Object.defineProperty( obj, prop, {
718 | set: val => {
719 | this.propReferences[propReferenceIndex] = val;
720 |
721 | colorpicker.value = val;
722 |
723 | if (typeof callback == 'function') {
724 | callback(val);
725 | }
726 | },
727 | get: () => {
728 | return this.propReferences[propReferenceIndex];
729 | }
730 | });
731 | }
732 | }
733 |
734 | vector2( params = {}, callback) {
735 | if (typeof params != 'object') {
736 | throw Error(`[GUI] vector2() first parameter must be an object. Received: ${typeof params}.`);
737 | }
738 |
739 | let name = typeof params.name == 'string' ? params.name || ' ' : ' ';
740 |
741 | const minX = params.x.min ?? 0;
742 | const maxX = params.x.max ?? 1;
743 | const minY = params.y.min ?? 0;
744 | const maxY = params.y.max ?? 1;
745 | const stepX = params.x.step || (maxX - minX) / 100;
746 | const stepY = params.y.step || (maxY - minY) / 100;
747 | const decimalsX = this._countDecimals(stepX);
748 | const decimalsY = this._countDecimals(stepY);
749 |
750 | const objectX = params.x.obj;
751 | const propX = params.x.prop;
752 | const propXReferenceIndex = this.propReferences.push(objectX[propX]) - 1;
753 |
754 | const objectY = params.y.obj;
755 | const propY = params.y.prop;
756 | const propYReferenceIndex = this.propReferences.push(objectY[propY]) - 1;
757 |
758 | const tooltip = (typeof params.tooltip === 'string') ? params.tooltip : (params.tooltip === true ? name : null);
759 |
760 | callback = typeof callback == 'function' ? callback : null;
761 |
762 | this.imageContainer = null;
763 |
764 | const container = document.createElement('div');
765 | container.className = 'p-gui__vector2';
766 | container.textContent = name;
767 | if ( tooltip ) {
768 | container.setAttribute('title', tooltip);
769 | }
770 | this.wrapper.append(container);
771 |
772 | const vector_value = document.createElement('div');
773 | vector_value.className = 'p-gui__vector-value';
774 | vector_value.textContent = objectX[propX] + ', ' + objectY[propY];
775 | container.append(vector_value);
776 |
777 | const area = document.createElement('div');
778 | area.className = 'p-gui__vector2-area';
779 | container.append(area);
780 | area.addEventListener('click', evt => {
781 | const newX = parseFloat(this._mapLinear(evt.offsetX, 0, area.clientWidth, minX, maxX));
782 | const newY = parseFloat(this._mapLinear(evt.offsetY, 0, area.clientHeight, maxY, minY));
783 | objectX[propX] = newX.toFixed(decimalsX);
784 | objectY[propY] = newY.toFixed(decimalsY);
785 |
786 | if (callback) {
787 | callback(objectX[propX], objectX[propY]);
788 | }
789 |
790 | if (this.onUpdate) {
791 | this.onUpdate();
792 | } else if (this.isFolder && this.firstParent.onUpdate) {
793 | this.firstParent.onUpdate();
794 | }
795 | });
796 |
797 | let pointer_is_down = false;
798 | area.addEventListener('pointerdown', (evt) => {
799 | pointer_is_down = true;
800 | });
801 | area.addEventListener('pointerup', () => {
802 | pointer_is_down = false;
803 | });
804 | area.addEventListener('pointermove', (evt) => {
805 | if (pointer_is_down) {
806 | const newX = parseFloat(this._mapLinear(evt.offsetX, 0, area.clientWidth, minX, maxX));
807 | const newY = parseFloat(this._mapLinear(evt.offsetY, 0, area.clientHeight, maxY, minY));
808 | objectX[propX] = newX.toFixed(decimalsX);
809 | objectY[propY] = newY.toFixed(decimalsY);
810 |
811 | if (callback) {
812 | callback(objectX[propX], objectX[propY]);
813 | }
814 |
815 | if (this.onUpdate) {
816 | this.onUpdate();
817 | } else if (this.isFolder && this.firstParent.onUpdate) {
818 | this.firstParent.onUpdate();
819 | }
820 | }
821 | });
822 |
823 | const line_x = document.createElement('div');
824 | line_x.className = 'p-gui__vector2-line p-gui__vector2-line-x';
825 | area.append(line_x);
826 |
827 | const line_y = document.createElement('div');
828 | line_y.className = 'p-gui__vector2-line p-gui__vector2-line-y';
829 | area.append(line_y);
830 |
831 | const dot = document.createElement('div');
832 | dot.className = 'p-gui__vector2-dot';
833 | area.append(dot);
834 |
835 | dot.style.left = this._mapLinear(objectX[propX], minX, maxX, 0, area.clientWidth) + 'px';
836 | dot.style.top = this._mapLinear(objectY[propY], minY, maxY, area.clientHeight, 0) + 'px';
837 |
838 | Object.defineProperty( objectX, propX, {
839 | set: val => {
840 | this.propReferences[propXReferenceIndex] = val;
841 | dot.style.left = this._mapLinear(val, minX, maxX, 0, area.clientWidth) + 'px';
842 | vector_value.textContent = String( val ) + ', ' + objectY[propY];
843 | },
844 | get: () => {
845 | return this.propReferences[propXReferenceIndex];
846 | }
847 | });
848 |
849 | Object.defineProperty( objectY, propY, {
850 | set: val => {
851 | this.propReferences[propYReferenceIndex] = val;
852 | dot.style.top = this._mapLinear(val, minY, maxY, area.clientHeight, 0) + 'px';
853 | vector_value.textContent = objectX[propX] + ', ' + String( val );
854 | },
855 | get: () => {
856 | return this.propReferences[propYReferenceIndex];
857 | }
858 | });
859 | }
860 |
861 | folder(options = {}) {
862 | let closed = typeof options.closed == 'boolean' ? options.closed : false;
863 | let name = options.name || '';
864 | let color = options.color || null;
865 | let maxHeight = options.maxHeight || null;
866 |
867 | this.imageContainer = null;
868 |
869 | let className = 'p-gui__folder';
870 |
871 | if (this.folders.length == 0) {
872 | className += ' p-gui__folder--first';
873 | }
874 |
875 | if (closed) {
876 | className += ' p-gui__folder--closed';
877 | }
878 |
879 | let container_style = color ? `background-color: ${color};` : '';
880 | container_style += maxHeight ? `max-height: ${maxHeight}px;` : '';
881 |
882 | const container = document.createElement('div');
883 | container.className = className;
884 | container.style = container_style;
885 | this.wrapper.append(container);
886 |
887 | const folderHeader = document.createElement('div');
888 | folderHeader.innerHTML = `${name}`;
889 | folderHeader.className = 'p-gui__folder-header';
890 | container.append(folderHeader);
891 | folderHeader.addEventListener('click', () => {
892 | container.classList.toggle('p-gui__folder--closed');
893 | });
894 |
895 | let folder = new GUI({isFolder: true, folderOptions: {
896 | wrapper: container,
897 | parent: this,
898 | firstParent: this.firstParent
899 | }});
900 | this.folders.push(folder);
901 | return folder;
902 | }
903 |
904 | _makeDraggable() {
905 | var that = this;
906 | this.header.addEventListener('pointerdown', dragMouseDown);
907 | this.header.addEventListener('pointerup', dragMouseUp);
908 |
909 | function dragMouseDown(ev) {
910 | ev.preventDefault();
911 |
912 | that.position.initX = that.position.x;
913 | that.position.initY = that.position.y;
914 |
915 | that.position.prevX = ev.clientX;
916 | that.position.prevY = ev.clientY;
917 |
918 | document.addEventListener('pointermove', dragElement);
919 | }
920 |
921 | function dragElement(ev) {
922 | ev.preventDefault();
923 | if (!that.hasBeenDragged) {
924 | that.hasBeenDragged = true;
925 | that.wrapper.setAttribute('data-dragged', 'true')
926 | }
927 |
928 | that.position.x = that.position.initX + ev.clientX - that.position.prevX;
929 | that.position.y = that.position.initY + ev.clientY - that.position.prevY;
930 |
931 | that.wrapper.style.transform = "translate3d("+that.position.x + "px,"+that.position.y + "px,0)";
932 | }
933 |
934 | function dragMouseUp(ev) {
935 | document.removeEventListener('pointermove', dragElement);
936 | }
937 | }
938 |
939 | toggleClose() {
940 | this.closed = !this.closed;
941 |
942 | if (this.closed) {
943 | this.previousInnerScroll = this.wrapper.scrollTop;
944 | this.wrapper.scrollTo(0,0);
945 | } else {
946 | this.wrapper.scrollTo(0,this.previousInnerScroll);
947 | }
948 |
949 | this.wrapper.classList.toggle('p-gui--collapsed');
950 | }
951 |
952 | kill() {
953 | this.wrapper.remove();
954 | }
955 |
956 | _mapLinear( x, a1, a2, b1, b2 ) {
957 | return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
958 | }
959 |
960 | _countDecimals(num) {
961 | // Convert the number to a string
962 | const numStr = num.toString();
963 |
964 | // Find the position of the decimal point
965 | const decimalIndex = numStr.indexOf('.');
966 |
967 | // If there is no decimal point, return 0
968 | if (decimalIndex === -1) {
969 | return 0;
970 | }
971 |
972 | // Calculate the number of digits after the decimal point
973 | const decimalPlaces = numStr.length - decimalIndex - 1;
974 |
975 | return decimalPlaces;
976 | }
977 | }
--------------------------------------------------------------------------------
/src/styles/_button.css.js:
--------------------------------------------------------------------------------
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 | `;
--------------------------------------------------------------------------------
/src/styles/_color.css.js:
--------------------------------------------------------------------------------
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 | `;
--------------------------------------------------------------------------------
/src/styles/_folder.css.js:
--------------------------------------------------------------------------------
1 | export default /* css */ `
2 | .p-gui__folder {
3 | width: 100%;
4 | position: relative;
5 | background: #434343;
6 | overflow: auto;
7 | margin-bottom: 2px;
8 | display: flex;
9 | flex-wrap: wrap;
10 | border: 1px solid grey;
11 | padding: 0 3px 0 3px;
12 | border-radius: var(--main-border-radius);
13 | box-sizing: border-box;
14 | }
15 |
16 | .p-gui__folder--first {
17 | margin-top: 0;
18 | }
19 |
20 | .p-gui__folder--closed {
21 | height: 32px;
22 | overflow: hidden;
23 | }
24 |
25 | .p-gui__folder-header {
26 | padding: 10px 5px;
27 | background-color: rgba(0, 0, 0, .5);
28 | color: white;
29 | cursor: pointer;
30 | width: 100%;
31 | margin: 0 -2px 2px -3px;
32 | }
33 |
34 | .p-gui__folder-header:hover {
35 | background-color: rgba(0, 0, 0, .75);
36 | }
37 |
38 | .p-gui__folder-arrow {
39 | width: 8px;
40 | height: 8px;
41 | display: inline-block;
42 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAHlBMVEUAAAD///////////////////////////////////8kfJuVAAAACXRSTlMA9Z1fCdMo1yxEJnA0AAAAK0lEQVQI12PABlRgjKkJUMZMYRhjpgqMAZSEMICSaIzpDWiKhdENhEhgAgATSg5jyWnYewAAAABJRU5ErkJggg==);
43 | background-size: contain;
44 | margin-right: 5px;
45 | transform: rotate(90deg)
46 | }
47 |
48 | .p-gui__folder--closed .p-gui__folder-arrow {
49 | transform: rotate(0deg);
50 | }
51 | `;
--------------------------------------------------------------------------------
/src/styles/_image.css.js:
--------------------------------------------------------------------------------
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-light);
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 | `;
--------------------------------------------------------------------------------
/src/styles/_list.css.js:
--------------------------------------------------------------------------------
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 | `;
--------------------------------------------------------------------------------
/src/styles/_slider.css.js:
--------------------------------------------------------------------------------
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 | `;
--------------------------------------------------------------------------------
/src/styles/_switch.css.js:
--------------------------------------------------------------------------------
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 | `;
--------------------------------------------------------------------------------
/src/styles/_vector2.css.js:
--------------------------------------------------------------------------------
1 | export default /* css */ `
2 | .p-gui__vector2 {
3 | background: transparent;
4 | aspect-ratio: 1;
5 | padding-bottom: 0;
6 | color: var(--color-text-dark);
7 | }
8 |
9 | .p-gui__vector2:hover {
10 | color: var(--color-text-light);
11 | }
12 |
13 | .p-gui__vector2-area {
14 | position: relative;
15 | background: rgba(0, 0, 0, .3);
16 | margin-top: 8px;
17 | width: 100%;
18 | height: calc(100% - 28px);
19 | touch-action: none;
20 | }
21 |
22 | .p-gui__vector2-line {
23 | position: absolute;
24 | background: white;
25 | opacity: .3;
26 | pointer-events: none;
27 | }
28 |
29 | .p-gui__vector2-line-x {
30 | width: 100%;
31 | height: 1px;
32 | left: 0;
33 | top: 50%;
34 | transform: translateY(-50%);
35 | }
36 |
37 | .p-gui__vector2-line-y {
38 | width: 1px;
39 | height: 100%;
40 | top: 0;
41 | left: 50%;
42 | transform: translateX(-50%);
43 | }
44 |
45 | .p-gui__vector2-dot {
46 | position: absolute;
47 | top: 0;
48 | left: 0;
49 | width: 8px;
50 | height: 8px;
51 | border-radius: 50%;
52 | background: #d5d5d5;
53 | border: 2px solid #ff9999;
54 | transform: translate(-50%, -50%);
55 | pointer-events: none;
56 | }
57 |
58 | .p-gui__vector-value {
59 | display: inline-block;
60 | right: 7px;
61 | position: absolute;
62 | }
63 | `;
--------------------------------------------------------------------------------
/src/styles/styles.js:
--------------------------------------------------------------------------------
1 | import _button from "./_button.css.js"
2 | import _slider from "./_slider.css.js"
3 | import _list from "./_list.css.js"
4 | import _switch from "./_switch.css.js"
5 | import _color from "./_color.css.js"
6 | import _vector2 from "./_vector2.css.js"
7 | import _image from "./_image.css.js"
8 | import _folder from "./_folder.css.js"
9 |
10 | /**
11 | * JS instead of CSS to avoid
12 | * depending on a css loader
13 | */
14 |
15 | export default function( position_type ) {
16 | return /* css */`
17 | .p-gui {
18 | --main-border-radius: 5px;
19 | --color-bg: #121212;
20 | --color-border: #484848;
21 | --color-border-2: rgba(255,255,255,.1);
22 | --color-text-light: #ffffff;
23 | --color-text-dark: #bbbbbb;
24 | --color-accent: #1681ca;
25 | --color-accent-hover: #218fda;
26 | --transition: .1s linear;
27 |
28 | position: ${ position_type };
29 | top: 0;
30 | left: 0;
31 | transform: translate3d(0,0,0);
32 | padding-top: 21px;
33 | padding-inline: 3px;
34 | background: var(--color-bg);
35 | display: block;
36 | justify-content: center;
37 | flex-wrap: wrap;
38 | font-family: "Arial Rounded MT Bold", Arial, sans-serif;
39 | width: 290px;
40 | overflow: auto;
41 | box-shadow: 0 0 2px black;
42 | box-sizing: border-box;
43 | z-index: 99999;
44 | user-select: none;
45 | border-bottom-right-radius: 3px;
46 | border-bottom-left-radius: 3px;
47 | cursor: auto;
48 | border-radius: var(--main-border-radius);
49 | border: 1px solid var(--color-border);
50 | line-height: normal;
51 | transition: var(--transition) opacity;
52 | }
53 |
54 | .p-gui:hover {
55 | opacity: 1!important;
56 | }
57 |
58 | .p-gui * {
59 | font-size: 11px;
60 | }
61 |
62 | .p-gui::-webkit-scrollbar,
63 | .p-gui *::-webkit-scrollbar {
64 | width: 10px;
65 | }
66 |
67 | .p-gui::-webkit-scrollbar-track,
68 | .p-gui *::-webkit-scrollbar-track {
69 | background: #2f2f2f;
70 | border-radius: 3px;
71 | }
72 |
73 | .p-gui::-webkit-scrollbar-thumb,
74 | .p-gui *::-webkit-scrollbar-thumb {
75 | background: #757576;
76 | border-radius: 10px;
77 | box-sizing: border-box;
78 | border: 1px solid #2f2f2f;
79 | }
80 |
81 | .p-gui--collapsed {
82 | height: 0;
83 | padding: 21px 10px 0 10px;
84 | overflow: hidden;
85 | }
86 |
87 | .p-gui__header {
88 | position: absolute;
89 | top: 0;
90 | left: 0;
91 | width: 100%;
92 | height: 20px;
93 | background-color: rgba(0, 0, 0, .8);
94 | cursor: grab;
95 | color: grey;
96 | font-size: 10px;
97 | line-height: 20px;
98 | padding-left: 12px;
99 | box-sizing: border-box;
100 | touch-action: none;
101 | }
102 |
103 | .p-gui__header-close {
104 | width: 20px;
105 | height: 20px;
106 | position: absolute;
107 | top: 0;
108 | right: 5px;
109 | cursor: pointer;
110 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUAQMAAAC3R49OAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAABFJREFUCNdjIAb8//8BjIkAAOrOBd3TR0jRAAAAAElFTkSuQmCC);
111 | background-size: 50% 50%;
112 | background-position: center;
113 | background-repeat: no-repeat;
114 | }
115 |
116 | .p-gui--collapsed .p-gui__header-close {
117 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUAQMAAAC3R49OAAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAABVJREFUCNdjYEhgIIj///8AwsSoBQD43QydY5mb0QAAAABJRU5ErkJggg==);
118 | }
119 |
120 | .p-gui__slider,
121 | .p-gui__button,
122 | .p-gui__switch,
123 | .p-gui__list,
124 | .p-gui__vector2,
125 | .p-gui__color {
126 | width: 100%;
127 | padding: 7px;
128 | cursor: pointer;
129 | position: relative;
130 | box-sizing: border-box;
131 | margin-block: 3px;
132 | border: 1px solid var(--color-border-2);
133 | border-radius: var(--main-border-radius);
134 | transition: var(--transition) border-color;
135 | }
136 |
137 | .p-gui__slider:hover,
138 | .p-gui__button:hover,
139 | .p-gui__switch:hover,
140 | .p-gui__list:hover,
141 | .p-gui__vector2:hover,
142 | .p-gui__color:hover {
143 | border-color: rgba(255,255,255,.2);
144 | }
145 |
146 | ${ _button }
147 |
148 | ${ _image }
149 |
150 | ${ _list }
151 |
152 | ${ _switch }
153 |
154 | ${ _slider }
155 |
156 | ${ _color }
157 |
158 | ${ _vector2 }
159 |
160 | ${ _folder }
161 | `};
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { resolve } from "path";
2 | import { defineConfig } from "vite";
3 |
4 | export default defineConfig({
5 | build: {
6 | lib: {
7 | entry: resolve(__dirname, "src/index.js"),
8 | name: "Perfect GUI"
9 | },
10 | minify: true
11 | },
12 | });
13 |
--------------------------------------------------------------------------------