perfect-gui 4.12.0 → 4.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "perfect-gui",
3
- "version": "4.12.0",
3
+ "type": "module",
4
+ "version": "4.12.4",
4
5
  "description": "GUI for JavaScript",
5
6
  "main": "src/index.js",
6
7
  "scripts": {
@@ -0,0 +1,44 @@
1
+ export default class Slider {
2
+ constructor(parent, params = {}, callback) {
3
+ this.parent = parent;
4
+
5
+ let label = '';
6
+ if (typeof params != 'string') {
7
+ if (typeof params == 'object' && params?.hasOwnProperty('label')) {
8
+ label = params.label == '' ? ' ' : params.label;
9
+ } else {
10
+ label = ' ';
11
+ }
12
+ } else {
13
+ label = params == '' ? ' ' : params;
14
+ }
15
+
16
+ const tooltip = (typeof params.tooltip === 'string') ? params.tooltip : (params.tooltip === true ? label : null);
17
+
18
+ const el = document.createElement('div');
19
+ el.className = 'p-gui__button';
20
+ el.textContent = label;
21
+ if ( tooltip ) {
22
+ el.setAttribute('title', tooltip);
23
+ }
24
+ el.addEventListener('click', () => {
25
+ if (callback) {
26
+ callback();
27
+ }
28
+
29
+ if (this.parent.onUpdate) {
30
+ this.parent.onUpdate();
31
+ } else if (this.parent.isFolder && this.parent.firstParent.onUpdate) {
32
+ this.parent.firstParent.onUpdate();
33
+ }
34
+ });
35
+
36
+ if (typeof params.color == 'string') {
37
+ el.style.setProperty('--color-accent', params.color);
38
+ el.style.setProperty('--color-accent-hover', params.hoverColor || params.color);
39
+ }
40
+
41
+ this.parent.wrapper.append(el);
42
+ return el;
43
+ }
44
+ }
@@ -0,0 +1,108 @@
1
+ export default class Color {
2
+ constructor(parent, params = {}, callback) {
3
+ this.parent = parent;
4
+
5
+ if (typeof params != 'object') {
6
+ throw Error(`[GUI] color() first parameter must be an object. Received: ${typeof params}.`);
7
+ }
8
+
9
+ let label = typeof params.label == 'string' ? params.label || ' ' : ' ';
10
+
11
+ let isObject = false;
12
+ let propReferenceIndex = null;
13
+ let obj = params.obj;
14
+ let prop = params.prop;
15
+ let value;
16
+ const tooltip = (typeof params.tooltip === 'string') ? params.tooltip : (params.tooltip === true ? label : null);
17
+
18
+ if (typeof params.value == 'string') {
19
+ if (params.value.length != 7 || params.value[0] != '#') {
20
+ console.error(`[GUI] color() 'value' parameter must be an hexadecimal string in the format "#ffffff". Received: "${params.value}".`)
21
+ }
22
+ else {
23
+ value = params.value;
24
+ }
25
+ }
26
+ if (!value) value = '#000000';
27
+
28
+ // callback mode
29
+ if ( params.value !== undefined ) {
30
+ if (prop != undefined || obj != undefined) {
31
+ console.warn(`[GUI] color() "obj" and "prop" parameters are ignored when a "value" parameter is used.`);
32
+ }
33
+ }
34
+
35
+ // object-binding
36
+ else if (prop != undefined && obj != undefined) {
37
+ if (typeof prop != 'string') {
38
+ throw Error(`[GUI] color() "prop" parameter must be an string. Received: ${typeof prop}.`);
39
+ }
40
+ if (typeof obj != 'object') {
41
+ throw Error(`[GUI] color() "obj" parameter must be an object. Received: ${typeof obj}.`);
42
+ }
43
+
44
+ if (label == ' ') {
45
+ label = prop;
46
+ }
47
+
48
+ propReferenceIndex = this.parent.propReferences.push(obj[prop]) - 1;
49
+ isObject = true;
50
+ }
51
+ else {
52
+ if ((prop != undefined && obj == undefined) || (prop == undefined && obj == undefined)) {
53
+ console.warn(`[GUI] color() "obj" and "prop" parameters must be used together.`);
54
+ }
55
+ }
56
+
57
+ const container = document.createElement('div');
58
+ container.className = 'p-gui__color';
59
+ container.textContent = label;
60
+ if ( tooltip ) {
61
+ container.setAttribute('title', tooltip);
62
+ }
63
+ this.parent.wrapper.append(container);
64
+
65
+ const colorpicker = document.createElement('input');
66
+ colorpicker.className = 'p-gui__color-picker';
67
+ colorpicker.setAttribute('type', 'color');
68
+ colorpicker.value = value;
69
+ container.append(colorpicker);
70
+
71
+ if (typeof callback == 'function') {
72
+ colorpicker.addEventListener('input', () => {
73
+ if ( isObject ) {
74
+ obj[prop] = colorpicker.value;
75
+ }
76
+
77
+ else if (typeof callback == 'function') {
78
+ callback(colorpicker.value);
79
+ }
80
+
81
+ if (this.parent.onUpdate) {
82
+ this.parent.onUpdate();
83
+ } else if (this.parent.isFolder && this.parent.firstParent.onUpdate) {
84
+ this.parent.firstParent.onUpdate();
85
+ }
86
+ });
87
+ }
88
+
89
+ if ( isObject ) {
90
+ Object.defineProperty( obj, prop, {
91
+ set: val => {
92
+ this.parent.propReferences[propReferenceIndex] = val;
93
+
94
+ colorpicker.value = val;
95
+
96
+ if (typeof callback == 'function') {
97
+ callback(val);
98
+ }
99
+ },
100
+ get: () => {
101
+ return this.parent.propReferences[propReferenceIndex];
102
+ }
103
+ });
104
+ }
105
+
106
+ return container;
107
+ }
108
+ }
@@ -0,0 +1,87 @@
1
+ export default class Image {
2
+ constructor(parent, params = {}, callback) {
3
+ this.parent = parent;
4
+
5
+ if (typeof params != 'object') {
6
+ throw Error(`[GUI] image() first parameter must be an object. Received: ${typeof params}.`);
7
+ }
8
+
9
+ let path;
10
+ if (typeof params.path == 'string') {
11
+ path = params.path;
12
+ } else {
13
+ if (typeof params.path == undefined) {
14
+ throw Error(`[GUI] image() path must be provided.`);
15
+ } else {
16
+ throw Error(`[GUI] image() path must be a string.`);
17
+ }
18
+ }
19
+ let filename = path.replace(/^.*[\\\/]/, '');
20
+ let label;
21
+ if (params.label == undefined) {
22
+ label = filename;
23
+ } else {
24
+ label = typeof params.label == 'string' ? params.label || ' ' : ' ';
25
+ }
26
+
27
+ const tooltip = (typeof params.tooltip === 'string') ? params.tooltip : (params.tooltip === true ? label : null);
28
+
29
+ const selected = params.selected === true;
30
+ const selectionBorder = params.selectionBorder !== false;
31
+
32
+ // width & height options
33
+ let inline_styles = '';
34
+ if (params.width) {
35
+ if (typeof params.width == 'number') {
36
+ params.width += 'px';
37
+ }
38
+ inline_styles += `flex: 0 0 calc(${params.width} - 5px); `;
39
+ }
40
+
41
+ if (params.height) {
42
+ if (typeof params.height == 'number') {
43
+ params.height += 'px';
44
+ }
45
+ inline_styles += `height: ${params.height}; `;
46
+ }
47
+
48
+ // Image button
49
+ const image = document.createElement('div');
50
+ image.className = 'p-gui__image';
51
+ image.style = 'background-image: url(' + path + '); ' + inline_styles;
52
+ if ( tooltip ) {
53
+ image.setAttribute('title', tooltip);
54
+ }
55
+ this.parent.imageContainer.append(image);
56
+
57
+ if (selected && selectionBorder) {
58
+ image.classList.add('p-gui__image--selected');
59
+ }
60
+
61
+ // Text inside image
62
+ const text = document.createElement('div');
63
+ text.className = 'p-gui__image-text';
64
+ text.textContent = label;
65
+ image.append(text);
66
+
67
+ image.addEventListener('click', () => {
68
+ let selected_items = image.parentElement.querySelectorAll('.p-gui__image--selected');
69
+ for (let i = 0; i < selected_items.length; i++) {
70
+ selected_items[i].classList.remove('p-gui__image--selected');
71
+ }
72
+ if (selectionBorder) {
73
+ image.classList.add('p-gui__image--selected');
74
+ }
75
+ if (typeof callback == 'function') {
76
+ callback({ path, text: label });
77
+ }
78
+ if (this.parent.onUpdate) {
79
+ this.parent.onUpdate();
80
+ } else if (this.parent.isFolder && this.parent.firstParent.onUpdate) {
81
+ this.parent.firstParent.onUpdate();
82
+ }
83
+ });
84
+
85
+ return image;
86
+ }
87
+ }
@@ -0,0 +1,171 @@
1
+ export default class List {
2
+ constructor(parent, params = {}, callback) {
3
+ this.parent = parent;
4
+
5
+ if (typeof params != 'object') {
6
+ throw Error(`[GUI] list() first parameter must be an object. Received: ${typeof params}.`);
7
+ }
8
+
9
+ let label = typeof params.label == 'string' ? params.label : ' ';
10
+ let isObject = false;
11
+ let propReferenceIndex = null;
12
+ let obj = params.obj;
13
+ let prop = params.prop;
14
+ let values = Array.isArray(params.values) ? params.values : null;
15
+ let value;
16
+ let objectValues = typeof values[0] == 'string' ? false : true;
17
+ const tooltip = (typeof params.tooltip === 'string') ? params.tooltip : (params.tooltip === true ? label : null);
18
+
19
+ callback = typeof callback == 'function' ? callback : null;
20
+
21
+ // callback mode
22
+ if ( params.value !== undefined ||
23
+ (params.value === undefined && obj === undefined && prop === undefined)) {
24
+ if (prop != undefined || obj != undefined) {
25
+ console.warn(`[GUI] list() "obj" and "prop" parameters are ignored when a "value" parameter is used.`);
26
+ }
27
+
28
+ value = (() => {
29
+ if (!values) {
30
+ return null;
31
+ }
32
+ if (typeof params.value == 'string') {
33
+ return values.indexOf(params.value);
34
+ }
35
+ if (typeof params.value == 'number') {
36
+ return params.value;
37
+ }
38
+ })();
39
+ }
40
+
41
+ // object-binding mode
42
+ else if (prop != undefined && obj != undefined) {
43
+ if (typeof prop != 'string') {
44
+ throw Error(`[GUI] list() "prop" parameter must be an string. Received: ${typeof prop}.`);
45
+ }
46
+ if (typeof obj != 'object') {
47
+ throw Error(`[GUI] list() "obj" parameter must be an object. Received: ${typeof obj}.`);
48
+ }
49
+
50
+ value = (() => {
51
+ if (!values) {
52
+ return null;
53
+ }
54
+ if (typeof obj[prop] == 'string') {
55
+ if ( !objectValues ) { // values is an array of strings
56
+ return values.indexOf(obj[prop]);
57
+ }
58
+ else { // values is an array of objects
59
+ return values.find(item => item.value === obj[prop]).value;
60
+ }
61
+ }
62
+ if (typeof obj[prop] == 'number') {
63
+ if ( !objectValues ) { // values is an array of strings
64
+ return obj[prop];
65
+ }
66
+ else { // values is an array of objects
67
+ return values.find(item => item.value === obj[prop]).value;
68
+ }
69
+ }
70
+ })();
71
+
72
+ propReferenceIndex = this.parent.propReferences.push(obj[prop]) - 1;
73
+ isObject = true;
74
+ }
75
+
76
+ else {
77
+ if ((prop != undefined && obj == undefined) || (prop == undefined && obj == undefined)) {
78
+ console.warn(`[GUI] list() "obj" and "prop" parameters must be used together.`);
79
+ }
80
+ }
81
+
82
+ let container = document.createElement('div');
83
+ container.className = 'p-gui__list';
84
+ container.textContent = label;
85
+ if (tooltip) {
86
+ container.setAttribute('title', tooltip);
87
+ }
88
+ this.parent.wrapper.append(container);
89
+
90
+ let select = document.createElement('select');
91
+ container.append(select);
92
+ select.className = 'p-gui__list-dropdown';
93
+ select.addEventListener('change', (ev) => {
94
+ if ( isObject ) {
95
+ obj[prop] = ev.target.value;
96
+ }
97
+
98
+ else if (callback) {
99
+ callback(ev.target.value);
100
+ }
101
+
102
+ if (this.parent.onUpdate) {
103
+ this.parent.onUpdate();
104
+ } else if (this.parent.isFolder && this.parent.firstParent.onUpdate) {
105
+ this.parent.firstParent.onUpdate();
106
+ }
107
+ });
108
+
109
+ if (values)
110
+ {
111
+ values.forEach((item, index) =>
112
+ {
113
+ const optionName = objectValues ? item.label : item;
114
+ const optionValue = objectValues ? item.value : item;
115
+ let option = document.createElement('option');
116
+ option.setAttribute('value', optionValue);
117
+ option.textContent = optionName;
118
+ select.append(option);
119
+
120
+ if (!objectValues && value == index || objectValues && value == optionValue) {
121
+ option.setAttribute('selected', '');
122
+ }
123
+ });
124
+ }
125
+
126
+ if ( isObject ) {
127
+ Object.defineProperty( obj, prop, {
128
+ set: val => {
129
+ let newIndex, newValue, newObj;
130
+ if (objectValues) {
131
+ newObj = values.find(item => {
132
+ return item.value == val;
133
+ });
134
+ newValue = newObj?.value || values[0].value;
135
+ newIndex = values.indexOf(newObj);
136
+ } else {
137
+ if (typeof val == 'string') {
138
+ newIndex = values.indexOf(val);
139
+ newValue = val;
140
+ }
141
+ if (typeof val == 'number') {
142
+ newIndex = val;
143
+ newValue = values[val];
144
+ }
145
+ }
146
+
147
+ this.parent.propReferences[propReferenceIndex] = objectValues ? newValue : val;
148
+
149
+ const previousSelection = select.querySelector('[selected]');
150
+ if ( previousSelection ) {
151
+ previousSelection.removeAttribute('selected')
152
+ }
153
+ select.querySelectorAll('option')[newIndex].setAttribute('selected', '');
154
+
155
+ if (typeof callback == 'function') {
156
+ if (objectValues) {
157
+ callback(newObj, newIndex);
158
+ } else {
159
+ callback(newValue, newIndex);
160
+ }
161
+ }
162
+ },
163
+ get: () => {
164
+ return this.parent.propReferences[propReferenceIndex];
165
+ }
166
+ });
167
+ }
168
+
169
+ return container;
170
+ }
171
+ }
@@ -10,7 +10,7 @@ export default class Slider {
10
10
  let label = typeof params.label == 'string' ? params.label || ' ' : ' ';
11
11
  this.isObject = false;
12
12
  let propReferenceIndex = null;
13
- this.obj = params.obj;
13
+ this.obj = params.obj;
14
14
  this.prop = params.prop;
15
15
  let value = typeof params.value == 'number' ? params.value : null;
16
16
  this.min = params.min ?? 0;
@@ -20,7 +20,7 @@ export default class Slider {
20
20
  this.callback = typeof callback == 'function' ? callback : null;
21
21
 
22
22
  // callback mode
23
- if ( value !== null ) {
23
+ if (value !== null) {
24
24
  if (this.prop != undefined || this.obj != undefined) {
25
25
  console.warn(`[GUI] slider() "obj" and "prop" parameters are ignored when a "value" parameter is used.`);
26
26
  }
@@ -50,8 +50,6 @@ export default class Slider {
50
50
  }
51
51
  const tooltip = (typeof params.tooltip === 'string') ? params.tooltip : (params.tooltip === true ? label : null);
52
52
 
53
- this.imageContainer = null;
54
-
55
53
  const container = document.createElement('div');
56
54
  container.className = 'p-gui__slider';
57
55
 
@@ -59,11 +57,13 @@ export default class Slider {
59
57
  container.setAttribute('title', tooltip);
60
58
  }
61
59
 
60
+ this.parent.wrapper.append(container);
61
+
62
62
  const slider_name = document.createElement('div');
63
63
  slider_name.className = 'p-gui__slider-name';
64
64
  slider_name.textContent = label;
65
65
  container.append(slider_name);
66
-
66
+
67
67
  this.ctrlDiv = document.createElement('div');
68
68
  this.ctrlDiv.className = 'p-gui__slider-ctrl';
69
69
  this.ctrlDiv.setAttribute('type', 'range');
@@ -102,7 +102,7 @@ export default class Slider {
102
102
  this._updateHandlePositionFromValue();
103
103
  this._triggerCallbacks();
104
104
  })
105
-
105
+
106
106
  this.ctrlDiv.addEventListener('pointerdown', (evt) => {
107
107
  this.ctrlDiv.pointerDown = true;
108
108
  this.ctrlDiv.prevPosition = evt.clientX;
@@ -114,15 +114,15 @@ export default class Slider {
114
114
  });
115
115
 
116
116
  window.addEventListener('pointermove', (evt) => {
117
- if (this.ctrlDiv.pointerDown) {
117
+ if (this.ctrlDiv.pointerDown) {
118
118
  this.ctrlDiv.pointerDelta = evt.clientX - this.ctrlDiv.prevPosition;
119
119
  this._updateHandlePositionFromPointer(evt);
120
120
  }
121
121
  });
122
122
 
123
- if ( this.isObject ) {
124
- Object.defineProperty( this.obj, this.prop, {
125
- set: val => {
123
+ if (this.isObject) {
124
+ Object.defineProperty(this.obj, this.prop, {
125
+ set: val => {
126
126
  this.propReferences[propReferenceIndex] = val;
127
127
  this.valueInput.value = val;
128
128
 
@@ -130,9 +130,9 @@ export default class Slider {
130
130
 
131
131
  if (this.callback) {
132
132
  this.callback(parseFloat(this.valueInput.value));
133
- }
133
+ }
134
134
  },
135
- get: () => {
135
+ get: () => {
136
136
  return this.propReferences[propReferenceIndex];
137
137
  }
138
138
  });
@@ -147,7 +147,7 @@ export default class Slider {
147
147
  const pointerDelta = evt.clientX - this.ctrlDiv.prevPosition;
148
148
  const currentValue = parseFloat(this.valueInput.value);
149
149
  let handlePosition;
150
-
150
+
151
151
  if (firstDown) {
152
152
  handlePosition = evt.offsetX;
153
153
  } else {
@@ -155,19 +155,19 @@ export default class Slider {
155
155
  }
156
156
 
157
157
  handlePosition = Math.max(handleWidth / 2, Math.min(handlePosition, sliderWidth - handleWidth / 2));
158
-
158
+
159
159
  let newValue = this.min + (this.max - this.min) * (handlePosition - handleWidth / 2) / (sliderWidth - handleWidth);
160
- if ( newValue > currentValue ) {
160
+ if (newValue > currentValue) {
161
161
  newValue = this._quantizeFloor(newValue, this.step);
162
162
  } else {
163
163
  newValue = this._quantizeCeil(newValue, this.step);
164
164
  }
165
-
165
+
166
166
  // toFixed(9) avoids weird javascript infinite decimals
167
167
  newValue = parseFloat(newValue.toFixed(9));
168
168
  const nextValue = parseFloat((currentValue + this.step).toFixed(9));
169
169
  const prevValue = parseFloat((currentValue - this.step).toFixed(9));
170
-
170
+
171
171
  if (newValue >= nextValue || newValue <= prevValue) {
172
172
  newValue = newValue.toFixed(this.decimals);
173
173
 
@@ -179,7 +179,7 @@ export default class Slider {
179
179
  this.handle.position = handlePosition;
180
180
 
181
181
  this.filling.style.width = this.handle.position + 'px';
182
-
182
+
183
183
  this._triggerCallbacks();
184
184
  }
185
185
  }
@@ -188,17 +188,17 @@ export default class Slider {
188
188
  const sliderWidth = this.ctrlDiv.offsetWidth;
189
189
  const handleWidth = this.handle.offsetWidth;
190
190
  let handlePosition = this._mapLinear(this.valueInput.value, this.min, this.max, handleWidth / 2, sliderWidth - handleWidth / 2);
191
-
191
+
192
192
  handlePosition = Math.max(handleWidth / 2, Math.min(handlePosition, sliderWidth - handleWidth / 2));
193
-
193
+
194
194
  this.handle.style.transform = `translate(-50%, -50%) translateX(${handlePosition}px)`;
195
195
  this.handle.position = handlePosition;
196
196
 
197
197
  this.filling.style.width = this.handle.position + 'px';
198
198
  }
199
199
 
200
- _triggerCallbacks() {
201
- if ( this.isObject ) {
200
+ _triggerCallbacks() {
201
+ if (this.isObject) {
202
202
  this.obj[this.prop] = parseFloat(this.valueInput.value);
203
203
  }
204
204
  else {
@@ -213,9 +213,9 @@ export default class Slider {
213
213
  this.parent.firstParent.onUpdate();
214
214
  }
215
215
  }
216
-
217
- _mapLinear( x, a1, a2, b1, b2 ) {
218
- return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
216
+
217
+ _mapLinear(x, a1, a2, b1, b2) {
218
+ return b1 + (x - a1) * (b2 - b1) / (a2 - a1);
219
219
  }
220
220
 
221
221
  _quantize(x, step) {