muigui 0.0.1 → 0.0.2

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.
Files changed (72) hide show
  1. package/README.md +72 -7
  2. package/package.json +9 -6
  3. package/src/controllers/Button.js +34 -0
  4. package/src/controllers/Canvas.js +17 -0
  5. package/src/controllers/Checkbox.js +11 -0
  6. package/src/controllers/Color.js +31 -0
  7. package/src/controllers/ColorChooser.js +12 -0
  8. package/src/controllers/Container.js +58 -0
  9. package/src/controllers/Controller.js +138 -0
  10. package/src/controllers/Direction.js +23 -0
  11. package/src/controllers/Divider.js +9 -0
  12. package/src/controllers/Folder.js +37 -0
  13. package/src/controllers/Label.js +14 -0
  14. package/src/controllers/LabelController.js +32 -0
  15. package/src/controllers/PopDownController.js +84 -0
  16. package/src/controllers/RadioGrid.js +17 -0
  17. package/src/controllers/Range.js +11 -0
  18. package/src/controllers/Select.js +14 -0
  19. package/src/controllers/Slider.js +12 -0
  20. package/src/controllers/TabHolder.js +36 -0
  21. package/src/controllers/Text.js +10 -0
  22. package/src/controllers/TextNumber.js +18 -0
  23. package/src/controllers/ValueController.js +107 -0
  24. package/src/controllers/Vec2.js +50 -0
  25. package/src/controllers/create-controller.js +43 -0
  26. package/src/layout/Column.js +7 -0
  27. package/src/layout/Frame.js +11 -0
  28. package/src/layout/Grid.js +7 -0
  29. package/src/layout/Layout.js +47 -0
  30. package/src/layout/Row.js +7 -0
  31. package/src/libs/assert.js +5 -0
  32. package/src/libs/color-utils.js +406 -0
  33. package/src/libs/conversions.js +14 -0
  34. package/src/libs/css-utils.js +3 -0
  35. package/src/libs/elem.js +8 -3
  36. package/src/libs/emitter.js +68 -0
  37. package/src/libs/iterable-array.js +57 -0
  38. package/src/libs/key-values.js +25 -0
  39. package/src/libs/keyboard.js +32 -0
  40. package/src/libs/resize-helpers.js +22 -0
  41. package/src/libs/svg.js +33 -0
  42. package/src/libs/taskrunner.js +56 -0
  43. package/src/libs/touch.js +50 -0
  44. package/src/libs/utils.js +38 -2
  45. package/src/libs/wheel.js +10 -0
  46. package/src/muigui.js +79 -19
  47. package/src/styles/muigui.css.js +640 -0
  48. package/src/umd.js +3 -0
  49. package/src/views/CheckboxView.js +21 -0
  50. package/src/views/ColorChooserView.js +124 -0
  51. package/src/views/ColorView.js +50 -0
  52. package/src/views/DirectionView.js +127 -0
  53. package/src/views/EditView.js +100 -0
  54. package/src/views/ElementView.js +8 -0
  55. package/src/views/GridView.js +15 -0
  56. package/src/views/NumberView.js +67 -0
  57. package/src/views/RadioGridView.js +46 -0
  58. package/src/views/RangeView.js +73 -0
  59. package/src/views/SelectView.js +23 -0
  60. package/src/views/SliderView.js +194 -0
  61. package/src/views/TextView.js +49 -0
  62. package/src/views/ValueView.js +11 -0
  63. package/src/views/Vec2View.js +51 -0
  64. package/src/views/View.js +56 -0
  65. package/src/widgets/checkbox.js +0 -0
  66. package/src/widgets/divider.js +0 -0
  67. package/src/widgets/menu.js +0 -0
  68. package/src/widgets/radio.js +0 -0
  69. package/src/widgets/select.js +0 -1
  70. package/src/widgets/slider.js +0 -41
  71. package/src/widgets/text.js +0 -0
  72. package/src/widgets/widget.js +0 -51
@@ -0,0 +1,194 @@
1
+ import { createElem } from '../libs/elem.js';
2
+ import { addKeyboardEvents } from '../libs/keyboard.js';
3
+ import { addTouchEvents } from '../libs/touch.js';
4
+ import { createWheelHelper } from '../libs/wheel.js';
5
+ import { onResizeSVGNoScale } from '../libs/resize-helpers.js';
6
+ import { clamp, copyExistingProperties, stepify } from '../libs/utils.js';
7
+ import EditView from './EditView.js';
8
+
9
+ const svg = `
10
+ <svg tabindex="0" viewBox="-32 -32 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
11
+ <g id="muigui-orientation">
12
+ <g id="muigui-origin">
13
+ <g transform="translate(0, 4)">
14
+ <path id="muigui-ticks" class="muigui-ticks"/>
15
+ <path id="muigui-thicks" class="muigui-thicks"/>
16
+ </g>
17
+ <g transform="translate(0, 14)">
18
+ <g id="muigui-number-orientation">
19
+ <g id="muigui-numbers" transform="translate(0, -3)" class="muigui-svg-text"/>
20
+ </g>
21
+ </g>
22
+ </g>
23
+ <linearGradient id="muigui-bg-to-transparent">
24
+ <stop stop-color="var(--value-bg-color)" offset="0%"/>
25
+ <stop stop-color="var(--value-bg-color)" stop-opacity="0" offset="100%"/>
26
+ </linearGradient>
27
+ <linearGradient id="muigui-transparent-to-bg">
28
+ <stop stop-color="var(--value-bg-color)" stop-opacity="0" offset="0%"/>
29
+ <stop stop-color="var(--value-bg-color)" offset="100%"/>
30
+ </linearGradient>
31
+ <!--<circle cx="0" cy="2" r="2" class="muigui-mark"/>-->
32
+ <!--<rect x="-1" y="0" width="2" height="10" class="muigui-mark"/>-->
33
+ <path d="M0 4L-2 0L2 0" class="muigui-mark"/>
34
+ </g>
35
+ <rect id="muigui-left-grad" x="0" y="0" width="20" height="20" fill="url(#muigui-bg-to-transparent)"/>
36
+ <rect id="muigui-right-grad" x="48" y="0" width="20" height="20" fill="url(#muigui-transparent-to-bg)"/>
37
+ </svg>
38
+ `;
39
+
40
+ function createSVGTicks(start, end, step, min, max, height) {
41
+ const p = [];
42
+ if (start < min) {
43
+ start += stepify(min - start, v => v, step);
44
+ }
45
+ end = Math.min(end, max);
46
+ for (let i = start; i <= end; i += step) {
47
+ p.push(`M${i} 0 l0 ${height}`);
48
+ }
49
+ return p.join(' ');
50
+ }
51
+
52
+ function createSVGNumbers(start, end, unitSize, unit, minusSize, min, max, labelFn) {
53
+ const texts = [];
54
+ if (start < min) {
55
+ start += stepify(min - start, v => v, unitSize);
56
+ }
57
+ end = Math.min(end, max);
58
+ const digits = Math.max(0, -Math.log10(unit));
59
+ const f = v => labelFn(v.toFixed(digits));
60
+ for (let i = start; i <= end; i += unitSize) {
61
+ texts.push(`<text text-anchor="middle" dominant-baseline="hanging" x="${i >= 0 ? i : (i - minusSize / 2) }" y="0">${f(i / unitSize * unit)}</text>`);
62
+ }
63
+ return texts.join('\n');
64
+ }
65
+
66
+ function computeSizeOfMinus(elem) {
67
+ const oldHTML = elem.innerHTML;
68
+ elem.innerHTML = '<text>- </text>';
69
+ const text = elem.querySelector('text');
70
+ const size = text.getComputedTextLength();
71
+ elem.innerHTML = oldHTML;
72
+ return size;
73
+ }
74
+
75
+ export default class SliderView extends EditView {
76
+ #svgElem;
77
+ #originElem;
78
+ #ticksElem;
79
+ #thicksElem;
80
+ #numbersElem;
81
+ #leftGradElem;
82
+ #rightGradElem;
83
+ #width;
84
+ #height;
85
+ #lastV;
86
+ #minusSize;
87
+ #options = {
88
+ min: -100,
89
+ max: 100,
90
+ step: 1,
91
+ unit: 10,
92
+ unitSize: 10,
93
+ ticksPerUnit: 5,
94
+ labelFn: v => v,
95
+ tickHeight: 1,
96
+ limits: true,
97
+ thicksColor: undefined,
98
+ orientation: undefined,
99
+ };
100
+
101
+ constructor(setter, options) {
102
+ const wheelHelper = createWheelHelper();
103
+ super(createElem('div', {
104
+ innerHTML: svg,
105
+ onWheel: e => {
106
+ e.preventDefault();
107
+ const {min, max, step} = this.#options;
108
+ const delta = wheelHelper(e, step);
109
+ const newV = clamp(stepify(this.#lastV + delta, v => v, step), min, max);
110
+ setter.setValue(newV);
111
+ },
112
+ }));
113
+ this.#svgElem = this.$('svg');
114
+ this.#originElem = this.$('#muigui-origin');
115
+ this.#ticksElem = this.$('#muigui-ticks');
116
+ this.#thicksElem = this.$('#muigui-thicks');
117
+ this.#numbersElem = this.$('#muigui-numbers');
118
+ this.#leftGradElem = this.$('#muigui-left-grad');
119
+ this.#rightGradElem = this.$('#muigui-right-grad');
120
+ this.setOptions(options);
121
+ let startV;
122
+ addTouchEvents(this.domElement, {
123
+ onDown: () => {
124
+ startV = this.#lastV;
125
+ },
126
+ onMove: (e) => {
127
+ const {min, max, unitSize, unit, step} = this.#options;
128
+ const newV = clamp(stepify(startV - e.dx / unitSize * unit, v => v, step), min, max);
129
+ setter.setValue(newV);
130
+ },
131
+ });
132
+ addKeyboardEvents(this.domElement, {
133
+ onDown: (e) => {
134
+ const {min, max, step} = this.#options;
135
+ const newV = clamp(stepify(this.#lastV + e.dx * step, v => v, step), min, max);
136
+ setter.setValue(newV);
137
+ },
138
+ });
139
+ onResizeSVGNoScale(this.#svgElem, 0.5, 0, ({rect: {width}}) => {
140
+ this.#leftGradElem.setAttribute('x', -width / 2);
141
+ this.#rightGradElem.setAttribute('x', width / 2 - 20);
142
+ this.#minusSize = computeSizeOfMinus(this.#numbersElem);
143
+ this.#width = width;
144
+ this.#updateSlider();
145
+ });
146
+ }
147
+ // |--------V--------|
148
+ // . . | . . . | . . . |
149
+ //
150
+ #updateSlider() {
151
+ // There's no size if ResizeObserver has not fired yet.
152
+ if (!this.#width || this.#lastV === undefined) {
153
+ return;
154
+ }
155
+ const {
156
+ labelFn,
157
+ limits,
158
+ min,
159
+ max,
160
+ orientation,
161
+ tickHeight,
162
+ ticksPerUnit,
163
+ unit,
164
+ unitSize,
165
+ thicksColor,
166
+ } = this.#options;
167
+ const unitsAcross = Math.ceil(this.#width / unitSize);
168
+ const center = this.#lastV;
169
+ const centerUnitSpace = center / unit;
170
+ const startUnitSpace = Math.round(centerUnitSpace - unitsAcross);
171
+ const endUnitSpace = startUnitSpace + unitsAcross * 2;
172
+ const start = startUnitSpace * unitSize;
173
+ const end = endUnitSpace * unitSize;
174
+ const minUnitSpace = limits ? min * unitSize / unit : start;
175
+ const maxUnitSpace = limits ? max * unitSize / unit : end;
176
+ const height = labelFn(1) === '' ? 10 : 5;
177
+ if (ticksPerUnit > 1) {
178
+ this.#ticksElem.setAttribute('d', createSVGTicks(start, end, unitSize / ticksPerUnit, minUnitSpace, maxUnitSpace, height * tickHeight));
179
+ }
180
+ this.#thicksElem.style.stroke = thicksColor; //setAttribute('stroke', thicksColor);
181
+ this.#thicksElem.setAttribute('d', createSVGTicks(start, end, unitSize, minUnitSpace, maxUnitSpace, height));
182
+ this.#numbersElem.innerHTML = createSVGNumbers(start, end, unitSize, unit, this.#minusSize, minUnitSpace, maxUnitSpace, labelFn);
183
+ this.#originElem.setAttribute('transform', `translate(${-this.#lastV * unitSize / unit} 0)`);
184
+ this.#svgElem.classList.toggle('muigui-slider-up', orientation === 'up');
185
+ }
186
+ updateDisplay(v) {
187
+ this.#lastV = v;
188
+ this.#updateSlider();
189
+ }
190
+ setOptions(options) {
191
+ copyExistingProperties(this.#options, options);
192
+ return this;
193
+ }
194
+ }
@@ -0,0 +1,49 @@
1
+ import { createElem } from '../libs/elem.js';
2
+ import { identity } from '../libs/conversions.js';
3
+ import EditView from './EditView.js';
4
+ import { copyExistingProperties } from '../libs/utils.js';
5
+
6
+ export default class TextView extends EditView {
7
+ #to;
8
+ #from;
9
+ #skipUpdate;
10
+ #options = {
11
+ converters: identity,
12
+ };
13
+
14
+ constructor(setter, options) {
15
+ const setValue = setter.setValue.bind(setter);
16
+ const setFinalValue = setter.setFinalValue.bind(setter);
17
+ super(createElem('input', {
18
+ type: 'text',
19
+ onInput: () => this.#handleInput(setValue, true),
20
+ onChange: () => this.#handleInput(setFinalValue, false),
21
+ }));
22
+ this.setOptions(options);
23
+ }
24
+ #handleInput(setFn, skipUpdate) {
25
+ const [valid, newV] = this.#from(this.domElement.value);
26
+ if (valid) {
27
+ this.#skipUpdate = skipUpdate;
28
+ setFn(newV);
29
+ }
30
+ this.domElement.style.color = valid ? '' : 'var(--invalid-color)';
31
+
32
+ }
33
+ updateDisplay(v) {
34
+ if (!this.#skipUpdate) {
35
+ this.domElement.value = this.#to(v);
36
+ this.domElement.style.color = '';
37
+ }
38
+ this.#skipUpdate = false;
39
+ }
40
+ setOptions(options) {
41
+ copyExistingProperties(this.#options, options);
42
+ const {
43
+ converters: {to, from},
44
+ } = this.#options;
45
+ this.#to = to;
46
+ this.#from = from;
47
+ return this;
48
+ }
49
+ }
@@ -0,0 +1,11 @@
1
+ import { createElem } from '../libs/elem.js';
2
+ import View from './View.js';
3
+
4
+ export default class ValueView extends View {
5
+ constructor(className = '') {
6
+ super(createElem('div', {className: 'muigui-value'}));
7
+ if (className) {
8
+ this.domElement.classList.add(className);
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,51 @@
1
+ import { createElem } from '../libs/elem.js';
2
+ import { addTouchEvents } from '../libs/touch.js';
3
+ import { onResizeSVGNoScale } from '../libs/resize-helpers.js';
4
+ import EditView from './EditView.js';
5
+
6
+ const svg = `
7
+ <svg tabindex="0" viewBox="-32 -32 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
8
+ <path d="m-3200,0L3200,0M0,-3200L0,3200" class="muigui-vec2-axis"/>
9
+ <path id="muigui-arrow" d="" class="muigui-vec2-line"/>
10
+ <g id="muigui-circle" transform="translate(0, 0)">
11
+ <circle r="3" class="muigui-vec2-axis"/>
12
+ </g>
13
+ </svg>
14
+ `;
15
+
16
+ export default class Vec2View extends EditView {
17
+ #svgElem;
18
+ #arrowElem;
19
+ #circleElem;
20
+ #lastV = [];
21
+
22
+ constructor(setter) {
23
+ super(createElem('div', {
24
+ innerHTML: svg,
25
+ }));
26
+ const onTouch = (e) => {
27
+ const {width, height} = this.#svgElem.getBoundingClientRect();
28
+ const nx = e.nx * 2 - 1;
29
+ const ny = e.ny * 2 - 1;
30
+ setter.setValue([nx * width * 0.5, ny * height * 0.5]);
31
+ };
32
+ addTouchEvents(this.domElement, {
33
+ onDown: onTouch,
34
+ onMove: onTouch,
35
+ });
36
+ this.#svgElem = this.$('svg');
37
+ this.#arrowElem = this.$('#muigui-arrow');
38
+ this.#circleElem = this.$('#muigui-circle');
39
+ onResizeSVGNoScale(this.#svgElem, 0.5, 0.5, () => this.#updateDisplayImpl);
40
+ }
41
+ #updateDisplayImpl() {
42
+ const [x, y] = this.#lastV;
43
+ this.#arrowElem.setAttribute('d', `M0,0L${x},${y}`);
44
+ this.#circleElem.setAttribute('transform', `translate(${x}, ${y})`);
45
+ }
46
+ updateDisplay(v) {
47
+ this.#lastV[0] = v[0];
48
+ this.#lastV[1] = v[1];
49
+ this.#updateDisplayImpl();
50
+ }
51
+ }
@@ -0,0 +1,56 @@
1
+ import { removeArrayElem } from '../libs/utils.js';
2
+
3
+ export default class View {
4
+ #childDestElem;
5
+ #views = [];
6
+
7
+ constructor(elem) {
8
+ this.domElement = elem;
9
+ this.#childDestElem = elem;
10
+ }
11
+ addElem(elem) {
12
+ this.#childDestElem.appendChild(elem);
13
+ return elem;
14
+ }
15
+ removeElem(elem) {
16
+ this.#childDestElem.removeChild(elem);
17
+ return elem;
18
+ }
19
+ pushSubElem(elem) {
20
+ this.#childDestElem.appendChild(elem);
21
+ this.#childDestElem = elem;
22
+ }
23
+ popSubElem() {
24
+ this.#childDestElem = this.#childDestElem.parentElement;
25
+ }
26
+ add(view) {
27
+ this.#views.push(view);
28
+ this.addElem(view.domElement);
29
+ return view;
30
+ }
31
+ remove(view) {
32
+ this.removeElem(view.domElement);
33
+ removeArrayElem(this.#views, view);
34
+ return view;
35
+ }
36
+ pushSubView(view) {
37
+ this.pushSubElem(view.domElement);
38
+ }
39
+ popSubView() {
40
+ this.popSubElem();
41
+ }
42
+ setOptions(options) {
43
+ for (const view of this.#views) {
44
+ view.setOptions(options);
45
+ }
46
+ }
47
+ updateDisplayIfNeeded(newV, ignoreCache) {
48
+ for (const view of this.#views) {
49
+ view.updateDisplayIfNeeded(newV, ignoreCache);
50
+ }
51
+ return this;
52
+ }
53
+ $(selector) {
54
+ return this.domElement.querySelector(selector);
55
+ }
56
+ }
File without changes
File without changes
File without changes
File without changes
@@ -1 +0,0 @@
1
- export function createSlider()
@@ -1,41 +0,0 @@
1
- import {
2
- createElem,
3
- } from '../libs/elem.js';
4
- import Widget from './widget.js';
5
-
6
- export default class Slider extends Widget {
7
- constructor(object, property, min = 0, max = 1, step = 0.01) {
8
- super(object, property, 'muigui-slider');
9
- const root = this.elem;
10
- const id = this.id;
11
-
12
- const value = this.getValue();
13
- this._rangeElem = createElem('input', {
14
- type: 'range',
15
- min,
16
- max,
17
- value,
18
- step,
19
- id,
20
- onInput: (e) => {
21
- this.setValue(this._rangeElem.value);
22
- }
23
- });
24
- root.appendChild(this._rangeElem);
25
-
26
- this._textElem = createElem('input', {
27
- type: 'number',
28
- value: value,
29
- onInput: (e) => {
30
- this.setValue(parseFloat(this._textElem.value));
31
- },
32
- });
33
- root.appendChild(this._textElem);
34
- }
35
- setValue(v) {
36
- super.setValue(v);
37
- const newV = super.getValue();
38
- this._textElem.value = newV;
39
- this._rangeElem.value = newV;
40
- }
41
- }
File without changes
@@ -1,51 +0,0 @@
1
- import { createElem } from '../libs/elem.js';
2
- import { makeId } from '../libs/ids.js';
3
- import { idToLabel } from '../libs/utils.js';
4
-
5
- export default class Widget {
6
- constructor(object, property, className = '') {
7
- this._object = object;
8
- this._property = property;
9
- this._changeFns = [];
10
- this._id = makeId();
11
- this._nameElem = createElem('label', {textContent: property, for: this._id});
12
- this._root = createElem('div', {className: `muigui-widget`}, [this._nameElem]);
13
- // we need the specialization to come last so it takes precedence.
14
- if (className) {
15
- this._root.classList.add(className);
16
- }
17
- }
18
- get elem() {
19
- return this._root;
20
- }
21
- get id() {
22
- return this._id;
23
- }
24
- setJustValue(v) {
25
- this._object[this._property] = v;
26
- }
27
- setValue(v) {
28
- this._object[this._property] = v;
29
- }
30
- getValue(v) {
31
- return this._object[this._property];
32
- }
33
- value(v) {
34
- this.setValue(v);
35
- return this;
36
- }
37
- onChange(fn) {
38
- this.removeChange(fn);
39
- this._changeFns.push(fn);
40
- return this;
41
- }
42
- removeChange(fn) {
43
- this._changeFns.remove(fn);
44
- return this;
45
- }
46
- name(name) {
47
- this._nameElem.textContent = name;
48
- return this;
49
- }
50
- }
51
-