mirror of
https://github.com/JannisX11/blockbench.git
synced 2024-11-21 01:13:37 +08:00
parent
863e5b40d3
commit
cf243850f6
@ -1029,6 +1029,70 @@
|
||||
background-color: var(--color-bright_ui) !important;
|
||||
filter: brightness(1);
|
||||
}
|
||||
/* GIF Recorder */
|
||||
#gif_recording_frame {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
border: 2px dashed var(--color-accent);
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
#gif_recording_frame.recording {
|
||||
pointer-events: none;
|
||||
}
|
||||
#gif_recording_frame_label {
|
||||
text-align: center;
|
||||
color: var(--color-subtle_text);
|
||||
font-family: var(--font-code);
|
||||
}
|
||||
#gif_recording_controls {
|
||||
pointer-events: initial;
|
||||
background-color: var(--color-ui);
|
||||
box-shadow: 0 0 8px #00000040;
|
||||
height: 30px;
|
||||
width: fit-content;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
.gif_recording_frame_handle {
|
||||
pointer-events: initial;
|
||||
position: absolute;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
cursor: move;
|
||||
}
|
||||
#gif_recording_frame.recording .gif_recording_frame_handle {
|
||||
display: none;
|
||||
}
|
||||
.gif_recording_frame_handle:hover {
|
||||
color: var(--color-light);
|
||||
}
|
||||
#gif_recording_controls .gif_record_button:hover {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
#gif_recording_frame.recording .gif_record_button {
|
||||
animation: record_button_blink 0.5s infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
@keyframes record_button_blink {
|
||||
0% {opacity: 0;}
|
||||
50% {opacity: 0;}
|
||||
51% {opacity: 1;}
|
||||
}
|
||||
.gif_recording_frame_handle.gif_resize_ne {cursor: ne-resize}
|
||||
.gif_recording_frame_handle.gif_resize_nw {cursor: nw-resize}
|
||||
.gif_recording_frame_handle.gif_resize_se {cursor: se-resize}
|
||||
.gif_recording_frame_handle.gif_resize_sw {cursor: sw-resize}
|
||||
.gif_recording_frame_handle.gif_resize_ne i {transform: rotate(135deg);}
|
||||
.gif_recording_frame_handle.gif_resize_nw i {transform: rotate(45deg);}
|
||||
.gif_recording_frame_handle.gif_resize_se i {transform: rotate(225deg);}
|
||||
.gif_recording_frame_handle.gif_resize_sw i {transform: rotate(-45deg);}
|
||||
|
||||
/* Amend Edit Menu */
|
||||
#amend_edit_menu {
|
||||
position: absolute;
|
||||
|
@ -563,6 +563,19 @@ $(document).keyup(function(event) {
|
||||
}
|
||||
})
|
||||
|
||||
Interface.createElement = (tag, attributes = {}, content) => {
|
||||
let el = document.createElement(tag);
|
||||
for (let key in attributes) {
|
||||
el.setAttribute(key, attributes[key]);
|
||||
}
|
||||
if (typeof content == 'string') el.textContent = content;
|
||||
if (content instanceof Array) {
|
||||
content.forEach(node => el.append(node));
|
||||
}
|
||||
if (content instanceof HTMLElement) el.append(content);
|
||||
return el;
|
||||
}
|
||||
|
||||
|
||||
onVueSetup(function() {
|
||||
Interface.status_bar.vue = new Vue({
|
||||
|
@ -21,6 +21,7 @@ const Screencam = {
|
||||
},
|
||||
onConfirm: function(formData) {
|
||||
let background = formData.color.toHex8String() != '#00000000' ? formData.color.toHexString() : undefined;
|
||||
this.hide();
|
||||
Screencam.createGif({
|
||||
length_mode: formData.length_mode,
|
||||
length: limitNumber(formData.length, 0.1, 24000),
|
||||
@ -30,7 +31,6 @@ const Screencam = {
|
||||
play: formData.play,
|
||||
turnspeed: formData.turn,
|
||||
}, Screencam.returnScreenshot)
|
||||
this.hide();
|
||||
}
|
||||
}),
|
||||
screenshotPreview(preview, options, cb) {
|
||||
@ -167,20 +167,43 @@ const Screencam = {
|
||||
cleanCanvas(options, cb) {
|
||||
quad_previews.current.screenshot(options, cb)
|
||||
},
|
||||
createGif(options, cb) {
|
||||
if (typeof options !== 'object') options = {}
|
||||
gif_crop: {top: 0, left: 0, right: 0, bottom: 0},
|
||||
|
||||
async createGif(options = {}, cb) {
|
||||
if (!options.length_mode) options.length_mode = 'seconds';
|
||||
if (!options.length) options.length = 1;
|
||||
|
||||
var preview = quad_previews.current;
|
||||
var animation = Animation.selected;
|
||||
var interval = options.fps ? (1000/options.fps) : 100;
|
||||
var frames = 0;
|
||||
const gif = new GIF({
|
||||
let preview = Preview.selected;
|
||||
let animation = Animation.selected;
|
||||
let interval = options.fps ? (1000/options.fps) : 100;
|
||||
let frames = 0;
|
||||
let gif;
|
||||
let frame, frame_label;
|
||||
let recording = false;
|
||||
let loop = null;
|
||||
let crop = Screencam.gif_crop;
|
||||
|
||||
function getProgress() {
|
||||
switch (options.length_mode) {
|
||||
case 'seconds': return interval*frames/(options.length*1000); break;
|
||||
case 'frames': return frames/options.length; break;
|
||||
case 'turntable': return Math.abs(preview.controls.autoRotateProgress) / (2*Math.PI); break;
|
||||
case 'animation': return Timeline.time / (animation.length-(interval/1000)); break;
|
||||
}
|
||||
}
|
||||
function startRecording() {
|
||||
let canvas = document.createElement('canvas');
|
||||
let ctx = canvas.getContext('2d');
|
||||
canvas.width = Math.clamp((preview.width - crop.left - crop.right) * window.devicePixelRatio, 24, 4000);
|
||||
canvas.height = Math.clamp((preview.height - crop.top - crop.bottom) * window.devicePixelRatio, 24, 4000);
|
||||
|
||||
gif = new GIF({
|
||||
repeat: options.repeat,
|
||||
quality: options.quality,
|
||||
background: options.background ? options.background : {r: 30, g: 0, b: 255},
|
||||
transparent: options.background ? undefined : 0x1e01ff,
|
||||
width: canvas.width,
|
||||
height: canvas.height
|
||||
});
|
||||
|
||||
if (options.turnspeed) {
|
||||
@ -204,37 +227,50 @@ const Screencam = {
|
||||
gif.on('progress', Blockbench.setProgress);
|
||||
}
|
||||
|
||||
function getProgress() {
|
||||
switch (options.length_mode) {
|
||||
case 'seconds': return interval*frames/(options.length*1000); break;
|
||||
case 'frames': return frames/options.length; break;
|
||||
case 'turntable': return Math.abs(preview.controls.autoRotateProgress) / (2*Math.PI); break;
|
||||
case 'animation': return Timeline.time / (animation.length-(interval/1000)); break;
|
||||
}
|
||||
}
|
||||
|
||||
let recording = true;
|
||||
var loop = setInterval(() => {
|
||||
recording = true;
|
||||
loop = setInterval(() => {
|
||||
frames++;
|
||||
Canvas.withoutGizmos(function() {
|
||||
var img = new Image();
|
||||
let img = new Image();
|
||||
preview.render();
|
||||
img.src = preview.canvas.toDataURL();
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(preview.canvas, -crop.left * window.devicePixelRatio, -crop.top * window.devicePixelRatio);
|
||||
img.src = canvas.toDataURL();
|
||||
img.onload = () => {
|
||||
gif.addFrame(img, {delay: interval});
|
||||
}
|
||||
})
|
||||
Blockbench.setProgress(getProgress());
|
||||
frame_label.textContent = frames + ' - ' + (interval*frames/1000).toFixed(2) + 's';
|
||||
|
||||
if (getProgress() >= 1) {
|
||||
endRecording(true)
|
||||
endRecording(true);
|
||||
return;
|
||||
}
|
||||
|
||||
}, interval)
|
||||
gif.on('finished', blob => {
|
||||
var reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
if (!options.silent) {
|
||||
Blockbench.setProgress();
|
||||
Blockbench.setStatusBarText();
|
||||
}
|
||||
Screencam.returnScreenshot(reader.result, cb);
|
||||
}
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
|
||||
frame.classList.add('recording');
|
||||
}
|
||||
function endRecording(render) {
|
||||
if (!recording) return;
|
||||
recording = false;
|
||||
clearInterval(loop)
|
||||
clearInterval(loop);
|
||||
if (frame) {
|
||||
frame.remove();
|
||||
}
|
||||
Blockbench.setProgress();
|
||||
if (render) {
|
||||
gif.render();
|
||||
if (!options.silent) {
|
||||
@ -248,35 +284,79 @@ const Screencam = {
|
||||
preview.controls.autoRotate = false;
|
||||
}
|
||||
}
|
||||
function cancel() {
|
||||
frame.remove();
|
||||
}
|
||||
function updateCrop() {
|
||||
crop.left = Math.clamp(crop.left, 0, preview.width/2 - 12);
|
||||
crop.right = Math.clamp(crop.right, 0, preview.width/2 - 12);
|
||||
crop.top = Math.clamp(crop.top, 0, preview.height/2 - 12);
|
||||
crop.bottom = Math.clamp(crop.bottom, 0, preview.height/2 - 12);
|
||||
frame.style.top = crop.top + 'px';
|
||||
frame.style.left = crop.left + 'px';
|
||||
frame.style.right = crop.right + 'px';
|
||||
frame.style.bottom = crop.bottom + 'px';
|
||||
frame_label.textContent = Math.round(Math.clamp((preview.width - crop.left - crop.right) * window.devicePixelRatio, 24, 4000))
|
||||
+ ' x ' + Math.round(Math.clamp((preview.height - crop.top - crop.bottom) * window.devicePixelRatio, 24, 4000))
|
||||
}
|
||||
|
||||
let toast = Blockbench.showToastNotification({
|
||||
text: 'message.recording_gif',
|
||||
icon: 'local_movies',
|
||||
click() {
|
||||
if (recording) {
|
||||
endRecording(false);
|
||||
} else {
|
||||
gif.abort();
|
||||
}
|
||||
Blockbench.setStatusBarText();
|
||||
Blockbench.setProgress(0);
|
||||
return true;
|
||||
}
|
||||
})
|
||||
// Setup recording UI
|
||||
frame = Interface.createElement('div', {id: 'gif_recording_frame'});
|
||||
preview.node.append(frame);
|
||||
|
||||
gif.on('finished', blob => {
|
||||
var reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
if (!options.silent) {
|
||||
Blockbench.setProgress();
|
||||
Blockbench.setStatusBarText();
|
||||
frame_label = Interface.createElement('div', {id: 'gif_recording_frame_label'});
|
||||
frame.append(frame_label);
|
||||
|
||||
let resizer_top_right = Interface.createElement('div', {style: 'top: -2px; right: -2px;', class: 'gif_recording_frame_handle gif_resize_ne'}, Blockbench.getIconNode('arrow_back_ios'));
|
||||
let resizer_top_left = Interface.createElement('div', {style: 'top: -2px; left: -2px;', class: 'gif_recording_frame_handle gif_resize_nw'}, Blockbench.getIconNode('arrow_back_ios'));
|
||||
let resizer_bottom_right = Interface.createElement('div', {style: 'bottom: -2px; right: -2px;',class: 'gif_recording_frame_handle gif_resize_se'}, Blockbench.getIconNode('arrow_back_ios'));
|
||||
let resizer_bottom_left = Interface.createElement('div', {style: 'bottom: -2px; left: -2px;', class: 'gif_recording_frame_handle gif_resize_sw'}, Blockbench.getIconNode('arrow_back_ios'));
|
||||
|
||||
function drag(e1, x_value, y_value) {
|
||||
let crop_original = Object.assign({}, crop);
|
||||
function move(e2) {
|
||||
convertTouchEvent(e2);
|
||||
crop[x_value] = crop_original[x_value] + (e2.clientX - e1.clientX) * (x_value == 'left' ? 1 : -1);
|
||||
crop[y_value] = crop_original[y_value] + (e2.clientY - e1.clientY) * (y_value == 'top' ? 1 : -1);
|
||||
updateCrop();
|
||||
}
|
||||
Screencam.returnScreenshot(reader.result, cb);
|
||||
function stop(e3) {
|
||||
removeEventListeners(document, 'mousemove touchmove', move);
|
||||
removeEventListeners(document, 'mouseup touchend', stop);
|
||||
}
|
||||
reader.readAsDataURL(blob);
|
||||
toast.delete();
|
||||
addEventListeners(document, 'mousemove touchmove', move);
|
||||
addEventListeners(document, 'mouseup touchend', stop);
|
||||
}
|
||||
addEventListeners(resizer_top_right, 'mousedown touchstart', e => drag(e, 'right', 'top'));
|
||||
addEventListeners(resizer_top_left, 'mousedown touchstart', e => drag(e, 'left', 'top'));
|
||||
addEventListeners(resizer_bottom_right, 'mousedown touchstart', e => drag(e, 'right', 'bottom'));
|
||||
addEventListeners(resizer_bottom_left, 'mousedown touchstart', e => drag(e, 'left', 'bottom'));
|
||||
frame.append(resizer_top_right);
|
||||
frame.append(resizer_top_left);
|
||||
frame.append(resizer_bottom_right);
|
||||
frame.append(resizer_bottom_left);
|
||||
updateCrop();
|
||||
|
||||
let controls = Interface.createElement('div', {id: 'gif_recording_controls'});
|
||||
frame.append(controls);
|
||||
|
||||
let record_button = Interface.createElement('div', {class: 'tool gif_record_button'}, Blockbench.getIconNode('fiber_manual_record', 'var(--color-close)'));
|
||||
record_button.addEventListener('click', event => {
|
||||
startRecording();
|
||||
});
|
||||
controls.append(record_button);
|
||||
|
||||
let stop_button = Interface.createElement('div', {class: 'tool'}, Blockbench.getIconNode('stop'));
|
||||
stop_button.addEventListener('click', event => {
|
||||
recording ? endRecording(true) : cancel();
|
||||
})
|
||||
controls.append(stop_button);
|
||||
|
||||
let cancel_button = Interface.createElement('div', {class: 'tool'}, Blockbench.getIconNode('clear'));
|
||||
cancel_button.addEventListener('click', event => {
|
||||
recording ? endRecording(false) : cancel();
|
||||
})
|
||||
controls.append(cancel_button);
|
||||
},
|
||||
recordTimelapse(options) {
|
||||
if (!options.destination) return;
|
||||
|
@ -190,7 +190,6 @@
|
||||
"message.invalid_link": "Invalid or Expired Model Link",
|
||||
|
||||
"message.drag_background": "Drag the background to move its position. Hold shift and drag up and down to change its size. Click here to save the position.",
|
||||
"message.recording_gif": "GIF creation in progress. Click here to abort.",
|
||||
|
||||
"message.unsaved_textures.title": "Unsaved Textures",
|
||||
"message.unsaved_textures.message": "Your model has unsaved textures. Make sure to save them and paste them into your resource pack in the correct folder.",
|
||||
|
Loading…
Reference in New Issue
Block a user