lexgui 0.7.9 → 0.7.10
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/build/extensions/codeeditor.js +4 -0
- package/build/extensions/timeline.js +72 -19
- package/build/extensions/videoeditor.js +262 -172
- package/build/lexgui.css +33 -5
- package/build/lexgui.js +141 -87
- package/build/lexgui.min.css +2 -2
- package/build/lexgui.min.js +1 -1
- package/build/lexgui.module.js +165 -111
- package/build/lexgui.module.min.js +1 -1
- package/changelog.md +28 -1
- package/examples/area-tabs.html +1 -1
- package/examples/asset-view.html +27 -1
- package/examples/timeline.html +23 -13
- package/examples/video-editor.html +152 -3
- package/examples/video-editor2.html +5 -5
- package/package.json +1 -1
package/changelog.md
CHANGED
|
@@ -2,7 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
## dev
|
|
4
4
|
|
|
5
|
-
## 0.7.
|
|
5
|
+
## 0.7.10 (master)
|
|
6
|
+
|
|
7
|
+
Removed Fit-Tabs thumb CSS transition on `Tabs.add`.
|
|
8
|
+
Added Popover documentation.
|
|
9
|
+
Fixed `LX.codeSnippet` linesAdded/Removed options.
|
|
10
|
+
Fixed `Area.extend()` and `Area.reduce()` transitions.
|
|
11
|
+
|
|
12
|
+
AssetView:
|
|
13
|
+
- Added support for video elements.
|
|
14
|
+
- Fixed support for images using url with params.
|
|
15
|
+
- Added more supported image extensions.
|
|
16
|
+
- Updated documentation.
|
|
17
|
+
|
|
18
|
+
CodeEditor:
|
|
19
|
+
- Added support to `options.callback` to call when the editor is completely loaded.
|
|
20
|
+
- Docs updated.
|
|
21
|
+
|
|
22
|
+
Timeline:
|
|
23
|
+
- Added `Timeline.setKeyframeSize` and `Timeline.setTrackHeight`.
|
|
24
|
+
- Curve keyframes are visually clamped to the tracks' boundaries.
|
|
25
|
+
- Fixed `Add Keyframe` option in context menu.
|
|
26
|
+
|
|
27
|
+
VideoEditor:
|
|
28
|
+
- Minor functions refactor.
|
|
29
|
+
- Speed and loop buttons added to video controls.
|
|
30
|
+
- Fixed resize controls area bug.
|
|
31
|
+
|
|
32
|
+
## 0.7.9
|
|
6
33
|
|
|
7
34
|
Allow wheel/middle click in menubar buttons.
|
|
8
35
|
Pass RadioGroup name as argument to radio option callback.
|
package/examples/area-tabs.html
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
let area = await LX.init();
|
|
27
27
|
|
|
28
28
|
// split main area
|
|
29
|
-
var [leftArea, rightArea] = area.split({ sizes: ["75%", "25%"] });
|
|
29
|
+
var [ leftArea, rightArea ] = area.split({ sizes: ["75%", "25%"], minimizable: true });
|
|
30
30
|
|
|
31
31
|
// Get new content area to fill it
|
|
32
32
|
const leftTabs = leftArea.addTabs();
|
package/examples/asset-view.html
CHANGED
|
@@ -56,7 +56,33 @@
|
|
|
56
56
|
{
|
|
57
57
|
id: "dog.png",
|
|
58
58
|
type: "image",
|
|
59
|
-
|
|
59
|
+
src: "https://pngfre.com/wp-content/uploads/1653714420512-1-904x1024.png"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "greenland_grid_velo.bmp",
|
|
63
|
+
type: "image",
|
|
64
|
+
src: "https://people.math.sc.edu/Burkardt/data/bmp/greenland_grid_velo.bmp"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "Fox Monochrome.avif",
|
|
68
|
+
type: "image",
|
|
69
|
+
src: "https://raw.githubusercontent.com/link-u/avif-sample-images/refs/heads/master/fox.profile0.10bpc.yuv420.monochrome.avif"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "Avatar",
|
|
73
|
+
type: "Avatar",
|
|
74
|
+
preview: "https://models.readyplayer.me/66e30a18eca8fb70dcadde68.png?background=68,68,245"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "video-test",
|
|
78
|
+
type: "video",
|
|
79
|
+
preview: "https://godotengine.org/assets/press/logo_vertical_color_dark.png",
|
|
80
|
+
src: "https://catsl.eelvex.net/static/vid/teacher-video-Ψ.mp4"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "nyan-pyportal.gif",
|
|
84
|
+
type: "image",
|
|
85
|
+
src: "../data/nyan-pyportal.gif"
|
|
60
86
|
},
|
|
61
87
|
{
|
|
62
88
|
id: "sigml_test.sigml",
|
package/examples/timeline.html
CHANGED
|
@@ -48,15 +48,17 @@
|
|
|
48
48
|
const types = { KEYFRAMES: 0, CLIPS: 1, CURVES: 2};
|
|
49
49
|
let position = [250, 350];
|
|
50
50
|
let trackName = "";
|
|
51
|
+
let kfTimeline = null;
|
|
51
52
|
|
|
52
53
|
let panel = new LX.Panel();
|
|
53
54
|
panel = rightArea.addPanel(panel);
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
let activeTimeline = null
|
|
57
57
|
createKeyframeTimeline( bottomTabs );
|
|
58
58
|
createClipsTimeline( bottomTabs );
|
|
59
59
|
createCurvesTimeline( bottomTabs );
|
|
60
|
+
|
|
61
|
+
fillPanel( panel );
|
|
60
62
|
|
|
61
63
|
LX.doAsync( activeTimeline.resize.bind( activeTimeline ) );
|
|
62
64
|
|
|
@@ -144,8 +146,17 @@
|
|
|
144
146
|
track.edited[keyframe] = true;
|
|
145
147
|
}
|
|
146
148
|
}, { min: 0, max: 1024 });
|
|
147
|
-
panel.branch_open = true;
|
|
148
149
|
panel.merge();
|
|
150
|
+
|
|
151
|
+
panel.addRange("Keyframe Size", kfTimeline.keyframeSize / kfTimeline.trackHeight, (v,e)=>{
|
|
152
|
+
kfTimeline.keyframeSize = v * kfTimeline.trackHeight;
|
|
153
|
+
}, { step: 0.001, min: 0, max: 1 });
|
|
154
|
+
|
|
155
|
+
panel.addNumber("Track Height", kfTimeline.trackHeight, (v,e)=>{
|
|
156
|
+
let ratio = kfTimeline.keyframeSize / kfTimeline.trackHeight;
|
|
157
|
+
kfTimeline.setTrackHeight(v);
|
|
158
|
+
kfTimeline.setKeyframeSize( kfTimeline.trackHeight * ratio, kfTimeline.trackHeight * ratio + 6 );
|
|
159
|
+
}, { step: 1, min: parseFloat(getComputedStyle(document.documentElement).fontSize) * 0.25 });
|
|
149
160
|
}
|
|
150
161
|
|
|
151
162
|
|
|
@@ -206,7 +217,7 @@
|
|
|
206
217
|
keyframesPanel.onresize = () => {
|
|
207
218
|
kfTimeline.resize();
|
|
208
219
|
}
|
|
209
|
-
|
|
220
|
+
kfTimeline = new LX.KeyFramesTimeline('Keyframes Timeline', {
|
|
210
221
|
disableNewTracks: true,
|
|
211
222
|
onCreateBeforeTopBar: (panel) => {
|
|
212
223
|
panel.addSelect("Animation", ["Anim 1", "Anim 2", "Anim 3"], "Anim 1", ()=> {})
|
|
@@ -224,25 +235,24 @@
|
|
|
224
235
|
kfTimeline.show();
|
|
225
236
|
|
|
226
237
|
|
|
227
|
-
kfTimeline.onSelectKeyFrame = (
|
|
228
|
-
if(
|
|
238
|
+
kfTimeline.onSelectKeyFrame = (currentSelection) => {
|
|
239
|
+
if(kfTimeline.lastKeyFramesSelected.length > 1) {
|
|
229
240
|
rightArea.hide();
|
|
230
241
|
return;
|
|
231
242
|
}
|
|
232
243
|
rightArea.show();
|
|
233
|
-
|
|
234
|
-
|
|
244
|
+
const track = kfTimeline.animationClip.tracks[currentSelection[0]];
|
|
245
|
+
trackName = track.id ;
|
|
246
|
+
if( track.groupId != "Font" || track.id != "position") {
|
|
235
247
|
return
|
|
236
248
|
}
|
|
237
|
-
const
|
|
238
|
-
let values =
|
|
239
|
-
const dim =
|
|
249
|
+
const idx = track.trackIdx;
|
|
250
|
+
let values = track.values;
|
|
251
|
+
const dim = track.dim;
|
|
240
252
|
|
|
241
|
-
//currentSelection == [track.name, track.idx, frameIdx, track.clipIdx, track.times[frameIdx]]
|
|
242
253
|
const keyFrameIndex = currentSelection[1];
|
|
243
254
|
position = values.slice(keyFrameIndex * dim, keyFrameIndex * dim + dim);
|
|
244
255
|
fillPanel(panel);
|
|
245
|
-
//kfTimeline.animationClip.tracks[currentSelection[0][0]];
|
|
246
256
|
}
|
|
247
257
|
|
|
248
258
|
kfTimeline.onSetTime = ( time ) => {
|
|
@@ -21,6 +21,155 @@
|
|
|
21
21
|
|
|
22
22
|
import { LX } from 'lexgui';
|
|
23
23
|
import { VideoEditor } from 'lexgui/extensions/videoeditor.js';
|
|
24
|
+
|
|
25
|
+
class Window {
|
|
26
|
+
constructor( timeline, options = {} ) {
|
|
27
|
+
this.timeline = timeline;
|
|
28
|
+
this.timeline.padding = 0;
|
|
29
|
+
|
|
30
|
+
this.start = options.start != undefined ? options.start : 0.0; // sec
|
|
31
|
+
this.end = options.end || 3.0; // sec
|
|
32
|
+
this.center = options.center != undefined ? options.center : this.start
|
|
33
|
+
|
|
34
|
+
this.canvas = this.timeline.canvas;
|
|
35
|
+
this.windowHeight = options.windowHeight || this.canvas.height - 15;
|
|
36
|
+
this.handleWidth = options.handleWidth || 6;
|
|
37
|
+
|
|
38
|
+
this.allowDragging = options.dragging ?? false;
|
|
39
|
+
this.dragging = false;
|
|
40
|
+
this.dragOffset = 0;
|
|
41
|
+
this.resizingLeft = false;
|
|
42
|
+
this.resizingRight = false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
draw() {
|
|
46
|
+
const ctx = this.canvas.getContext('2d');
|
|
47
|
+
let startX = Math.max( this.timeline.startX, this.timeline.timeToX(this.start) );
|
|
48
|
+
let endX = Math.min( this.timeline.endX, this.timeline.timeToX(this.end) );
|
|
49
|
+
const width = endX - startX;
|
|
50
|
+
|
|
51
|
+
// Window background
|
|
52
|
+
ctx.fillStyle = "rgba(200, 200, 255, 0.15)";
|
|
53
|
+
ctx.roundRect(startX , this.canvas.height / 2 - this.windowHeight / 2, width, this.windowHeight, 5);
|
|
54
|
+
ctx.fill();
|
|
55
|
+
// Border
|
|
56
|
+
ctx.strokeStyle = "rgba(200, 200, 255, 0.5)";
|
|
57
|
+
ctx.lineWidth = 1;
|
|
58
|
+
ctx.beginPath();
|
|
59
|
+
ctx.roundRect(startX, this.canvas.height / 2 - this.windowHeight / 2, width, this.windowHeight, 5);
|
|
60
|
+
ctx.stroke();
|
|
61
|
+
|
|
62
|
+
// Handlers
|
|
63
|
+
const offsetW = 2;
|
|
64
|
+
const offsetH = 8;
|
|
65
|
+
if( startX > this.timeline.startX ) {
|
|
66
|
+
ctx.fillStyle = "whitesmoke"//"#579aff";
|
|
67
|
+
ctx.fillRect(startX, this.canvas.height / 2 - this.windowHeight / 2, this.handleWidth, this.windowHeight);
|
|
68
|
+
ctx.fillStyle = "#579aff";
|
|
69
|
+
ctx.fillRect(startX + this.handleWidth/2 - offsetW/2, this.canvas.height / 2 - this.windowHeight / 2 + offsetH / 2 , offsetW, this.windowHeight - offsetH);
|
|
70
|
+
}
|
|
71
|
+
if( endX < this.timeline.endX ) {
|
|
72
|
+
ctx.fillStyle = "whitesmoke"//"#579aff";
|
|
73
|
+
ctx.fillRect(endX - this.handleWidth, this.canvas.height / 2 - this.windowHeight / 2, this.handleWidth, this.windowHeight);
|
|
74
|
+
ctx.fillStyle = "#579aff";
|
|
75
|
+
ctx.fillRect(endX - this.handleWidth/2 - offsetW/2, this.canvas.height / 2 - this.windowHeight / 2 + offsetH / 2, offsetW, this.windowHeight - offsetH);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
onMouse( e ) {
|
|
80
|
+
switch(e.type) {
|
|
81
|
+
case "mousedown":
|
|
82
|
+
this.onMouseDown( e );
|
|
83
|
+
break;
|
|
84
|
+
case "mousemove":
|
|
85
|
+
this.onMouseMove( e );
|
|
86
|
+
break;
|
|
87
|
+
case "mouseup":
|
|
88
|
+
this.onMouseUp( e );
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
onMouseDown( e ) {
|
|
94
|
+
const x = e.offsetX;
|
|
95
|
+
const startX = this.timeline.timeToX(this.start);
|
|
96
|
+
const endX = this.timeline.timeToX(this.end);
|
|
97
|
+
|
|
98
|
+
if( x >= startX && x <= startX + this.handleWidth ) {
|
|
99
|
+
this.resizingLeft = true;
|
|
100
|
+
e.cancelBubble = true;
|
|
101
|
+
}
|
|
102
|
+
else if( x >= endX - this.handleWidth && x <= endX ) {
|
|
103
|
+
this.resizingRight = true;
|
|
104
|
+
e.cancelBubble = true;
|
|
105
|
+
}
|
|
106
|
+
else if( this.allowDragging && x >= startX && x <= endX ) {
|
|
107
|
+
this.dragging = true;
|
|
108
|
+
this.dragOffset = x - startX;
|
|
109
|
+
e.cancelBubble = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
onMouseMove( e ) {
|
|
114
|
+
const x = e.offsetX;
|
|
115
|
+
let startX = this.timeline.timeToX(this.start);
|
|
116
|
+
let endX = this.timeline.timeToX(this.end);
|
|
117
|
+
const centerX = this.timeline.timeToX(this.center);
|
|
118
|
+
if( this.resizingLeft ) {
|
|
119
|
+
startX = Math.min( centerX, Math.max( this.timeline.startX, x ) );//Math.max(startX - 0.1, (x - this.timeline.padding) / (this.canvas.width - 2 * this.timeline.padding) * this.timeline.endX);
|
|
120
|
+
this.start = this.timeline.xToTime( startX );
|
|
121
|
+
}
|
|
122
|
+
else if( this.resizingRight ) {
|
|
123
|
+
endX = Math.max( centerX, Math.min( this.timeline.endX, x ) ); //Math.min(endX + 0.1, (x - this.timeline.padding) / (this.canvas.width - 2 * this.timeline.padding) * this.timeline.endX);
|
|
124
|
+
this.end = this.timeline.xToTime( endX );
|
|
125
|
+
}
|
|
126
|
+
else if( this.dragging ) {
|
|
127
|
+
const time = this.timeline.xToTime( x - this.dragOffset );
|
|
128
|
+
this.moveWindow( time )
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
const x = e.offsetX;
|
|
132
|
+
const startX = this.timeline.timeToX(this.start);
|
|
133
|
+
const endX = this.timeline.timeToX(this.end);
|
|
134
|
+
|
|
135
|
+
if( x >= startX && x <= startX + this.handleWidth ) {
|
|
136
|
+
this.canvas.style.cursor = "col-resize";
|
|
137
|
+
e.cancelBubble = true;
|
|
138
|
+
}
|
|
139
|
+
else if( x >= endX - this.handleWidth && x <= endX ) {
|
|
140
|
+
this.canvas.style.cursor = "col-resize";
|
|
141
|
+
e.cancelBubble = true;
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
e.cancelBubble = true;
|
|
146
|
+
this.timeline._draw();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
onMouseUp( e ) {
|
|
150
|
+
this.dragging = false;
|
|
151
|
+
this.resizingLeft = false;
|
|
152
|
+
this.resizingRight = false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
moveWindow( time ) {
|
|
156
|
+
|
|
157
|
+
const leftOffset = this.start - this.center;
|
|
158
|
+
const rightOffset = this.end - this.center;
|
|
159
|
+
|
|
160
|
+
// const startX = this.timeline.timeToX( time + leftOffset );
|
|
161
|
+
// const endX = this.timeline.timeToX( rightOffset );
|
|
162
|
+
|
|
163
|
+
// const windowLength = this.end - this.start;
|
|
164
|
+
|
|
165
|
+
// newStart = Math.max(0, Math.min(this.timeline.endX - windowLength, startX));
|
|
166
|
+
this.start = time + leftOffset;
|
|
167
|
+
this.end = time + rightOffset;
|
|
168
|
+
this.center = time;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
}
|
|
172
|
+
|
|
24
173
|
// init library and get main area
|
|
25
174
|
let area = await LX.init();
|
|
26
175
|
|
|
@@ -33,9 +182,9 @@
|
|
|
33
182
|
video.src = "../data/video.mp4";
|
|
34
183
|
videoArea.attach(video);
|
|
35
184
|
|
|
36
|
-
const videoEditor = new LX.VideoEditor(leftArea, { videoArea, video, crop: true })
|
|
37
|
-
videoEditor.
|
|
38
|
-
|
|
185
|
+
const videoEditor = new LX.VideoEditor(leftArea, { videoArea, video, crop: true, loop: true })
|
|
186
|
+
videoEditor.loadVideo();
|
|
187
|
+
|
|
39
188
|
const canvas = document.createElement('canvas');
|
|
40
189
|
canvas.width = video.clientWidth;
|
|
41
190
|
canvas.height = video.clientHeight;
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
// Split main area
|
|
31
31
|
let [leftArea, rightArea] = area.split({ sizes: ["75%", "25%"], minimizable: true });
|
|
32
32
|
let [topArea, bottomArea] = leftArea.split({ sizes: ["80%", null], minimizable: false, resize: false, type: "vertical" });
|
|
33
|
-
let [controlsArea, buttonsArea] = bottomArea.split({ sizes: ["50%", null], minimizable: false,
|
|
33
|
+
let [controlsArea, buttonsArea] = bottomArea.split({ sizes: ["50%", null], minimizable: false, type: "vertical" });
|
|
34
34
|
area.extend();
|
|
35
35
|
|
|
36
36
|
/* Create video area with a menubar */
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
video.src = "../data/video.mp4";
|
|
42
42
|
videoArea.attach(video);
|
|
43
43
|
const videoEditor = new LX.VideoEditor(topArea, { videoArea, video, controlsArea })
|
|
44
|
-
videoEditor.
|
|
44
|
+
videoEditor.loadVideo();
|
|
45
|
+
|
|
45
46
|
/* Add canvas above video */
|
|
46
47
|
const canvas = document.createElement('canvas');
|
|
47
48
|
canvas.width = video.clientWidth;
|
|
@@ -72,7 +73,7 @@
|
|
|
72
73
|
icon: "Info",
|
|
73
74
|
name: "Properties",
|
|
74
75
|
callback: (v, e) => {
|
|
75
|
-
if (area.
|
|
76
|
+
if (area.splitExtended) {
|
|
76
77
|
area.reduce();
|
|
77
78
|
}
|
|
78
79
|
else {
|
|
@@ -92,8 +93,7 @@
|
|
|
92
93
|
/* Create right panel */
|
|
93
94
|
let panel = new LX.Panel({ id: "Properties" });
|
|
94
95
|
panel = rightArea.addPanel({ id: "Properties" });
|
|
95
|
-
createBlendShapesInspector({ "Name 1": 0, "Name 2": 0, "Name 3": 0.5, "Name 4": 0, "Name 5": 1, },
|
|
96
|
-
{ inspector: panel });
|
|
96
|
+
createBlendShapesInspector({ "Name 1": 0, "Name 2": 0, "Name 3": 0.5, "Name 4": 0, "Name 5": 1, }, { inspector: panel });
|
|
97
97
|
|
|
98
98
|
/* Functions */
|
|
99
99
|
function createBlendShapesInspector(bsNames, options = {}) {
|