Draw stage without scrollbar, also fix zoom to mouse cursor

This commit is contained in:
Skyler Lehmkuhl 2024-12-22 04:55:54 -05:00
parent 802646f685
commit 48cf15e825
1 changed files with 130 additions and 105 deletions

View File

@ -61,14 +61,14 @@ let lastSaveIndex = 0;
let layoutElements = [] let layoutElements = []
let minFileVersion = "1.2" // Version changes:
// 1.4: addShape uses frame as a reference instead of object
let minFileVersion = "1.3"
let maxFileVersion = "2.0" let maxFileVersion = "2.0"
let filePath = undefined let filePath = undefined
let fileExportPath = undefined let fileExportPath = undefined
// let fileWidth = 1500
// let fileHeight = 1000
// let fileFps = 12
let state = "normal" let state = "normal"
@ -261,7 +261,8 @@ let config = {
fileWidth: 800, fileWidth: 800,
fileHeight: 600, fileHeight: 600,
framerate: 24, framerate: 24,
recentFiles: [] recentFiles: [],
scrollSpeed: 1
} }
function getShortcut(shortcut) { function getShortcut(shortcut) {
@ -1132,9 +1133,11 @@ function vectorDist(a, b) {
function getMousePos(canvas, evt) { function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect(); var rect = canvas.getBoundingClientRect();
const offsetX = canvas.offsetX || 0;
const offsetY = canvas.offsetY || 0;
return { return {
x: (evt.clientX - rect.left) / context.zoomLevel, x: (evt.clientX + offsetX - rect.left) / context.zoomLevel,
y: (evt.clientY - rect.top) / context.zoomLevel y: (evt.clientY + offsetY - rect.top) / context.zoomLevel
}; };
} }
@ -2470,12 +2473,6 @@ function _newFile(width, height, fps) {
saveConfig() saveConfig()
undoStack = [] undoStack = []
redoStack = [] redoStack = []
for (let stage of document.querySelectorAll(".stage")) {
stage.width = width
stage.height = height
stage.style.width = `${width}px`
stage.style.height = `${height}px`
}
updateUI() updateUI()
updateLayers() updateLayers()
updateMenu() updateMenu()
@ -2490,7 +2487,7 @@ async function newFile() {
async function _save(path) { async function _save(path) {
try { try {
const fileData = { const fileData = {
version: "1.3", version: "1.4",
width: config.fileWidth, width: config.fileWidth,
height: config.fileHeight, height: config.fileHeight,
fps: config.framerate, fps: config.framerate,
@ -2790,24 +2787,12 @@ async function render() {
function updateScrollPosition(zoomFactor) { function updateScrollPosition(zoomFactor) {
if (context.mousePos) { if (context.mousePos) {
for (let scroller of document.querySelectorAll(".scroll")) { for (let canvas of canvases) {
let stage = scroller.querySelector('.stage') canvas.offsetX = (canvas.offsetX + context.mousePos.x) * zoomFactor - context.mousePos.x;
let scrollLeft = scroller.scrollLeft; canvas.offsetY = (canvas.offsetY + context.mousePos.y) * zoomFactor - context.mousePos.y;
let scrollTop = scroller.scrollTop;
// Get the mouse position relative to the scroller (taking into account the scroller's position)
let scrollerMouseX = context.mousePos.x + scrollLeft;
let scrollerMouseY = context.mousePos.y + scrollTop;
let newScrollLeft = scrollerMouseX - (context.mousePos.x * zoomFactor);
let newScrollTop = scrollerMouseY - (context.mousePos.y * zoomFactor);
// Apply the scroll position adjustments
console.log(context.mousePos, scrollerMouseX, scrollerMouseY, newScrollLeft, newScrollTop)
scroller.scrollLeft = -newScrollLeft;
scroller.scrollTop = -newScrollTop;
} }
} }
} }
function zoomIn() { function zoomIn() {
@ -2830,77 +2815,117 @@ function zoomOut() {
} }
function resetZoom() { function resetZoom() {
context.zoomLevel = 1; context.zoomLevel = 1;
for (let canvas of canvases) {
canvas.offsetX = canvas.offsetY = 0;
}
updateUI() updateUI()
updateMenu() updateMenu()
} }
function stage() { function stage() {
let stage = document.createElement("canvas") let stage = document.createElement("canvas")
let scroller = document.createElement("div") // let scroller = document.createElement("div")
let stageWrapper = document.createElement("div") // let stageWrapper = document.createElement("div")
stage.className = "stage" stage.className = "stage"
stage.width = config.fileWidth // stage.width = config.fileWidth
stage.height = config.fileHeight // stage.height = config.fileHeight
scroller.className = "scroll" stage.offsetX = 0
stageWrapper.className = "stageWrapper" stage.offsetY = 0
let selectionRect = document.createElement("div")
selectionRect.className = "selectionRect" let lastResizeTime = 0;
for (let i of ["nw", "ne", "se", "sw"]) { const throttleIntervalMs = 20;
let cornerRotateRect = document.createElement("div")
cornerRotateRect.classList.add("cornerRotateRect") function updateStageCanvasSize() {
cornerRotateRect.classList.add(i) const canvasStyles = window.getComputedStyle(stage);
cornerRotateRect.addEventListener('mouseup', (e) => {
const newEvent = new MouseEvent(e.type, e); stage.width = parseInt(canvasStyles.width);
stage.dispatchEvent(newEvent) stage.height = parseInt(canvasStyles.height);
}) updateUI()
cornerRotateRect.addEventListener('mousemove', (e) => {
const newEvent = new MouseEvent(e.type, e);
stage.dispatchEvent(newEvent)
})
selectionRect.appendChild(cornerRotateRect)
}
for (let i of ["nw", "n", "ne", "e", "se", "s", "sw", "w"]) {
let cornerRect = document.createElement("div")
cornerRect.classList.add("cornerRect")
cornerRect.classList.add(i)
cornerRect.addEventListener('mousedown', (e) => {
let bbox = undefined;
let selection = {}
for (let item of context.selection) {
if (bbox==undefined) {
bbox = structuredClone(item.bbox())
} else {
growBoundingBox(bbox, item.bbox())
}
selection[item.idx] = {x: item.x, y: item.y, scale_x: item.scale_x, scale_y: item.scale_y}
}
if (bbox != undefined) {
context.dragDirection = i
context.activeTransform = {
initial: {
x: {min: bbox.x.min, max: bbox.x.max},
y: {min: bbox.y.min, max: bbox.y.max},
selection: selection
},
current: {
x: {min: bbox.x.min, max: bbox.x.max},
y: {min: bbox.y.min, max: bbox.y.max},
selection: structuredClone(selection)
}
}
context.activeObject.currentFrame.saveState()
}
})
cornerRect.addEventListener('mouseup', (e) => {
const newEvent = new MouseEvent(e.type, e);
stage.dispatchEvent(newEvent)
})
cornerRect.addEventListener('mousemove', (e) => {
const newEvent = new MouseEvent(e.type, e);
stage.dispatchEvent(newEvent)
})
selectionRect.appendChild(cornerRect)
} }
const resizeObserver = new ResizeObserver(() => {
const currentTime = Date.now();
if (currentTime - lastResizeTime > throttleIntervalMs) {
lastResizeTime = currentTime;
updateStageCanvasSize();
}
});
resizeObserver.observe(stage);
updateStageCanvasSize()
stage.addEventListener('wheel', (event) => {
event.preventDefault()
const deltaX = event.deltaX * config.scrollSpeed;
const deltaY = event.deltaY * config.scrollSpeed;
stage.offsetX += deltaX
stage.offsetY += deltaY
const currentTime = Date.now();
if (currentTime - lastResizeTime > throttleIntervalMs) {
lastResizeTime = currentTime;
updateUI();
}
})
// scroller.className = "scroll"
// stageWrapper.className = "stageWrapper"
// let selectionRect = document.createElement("div")
// selectionRect.className = "selectionRect"
// for (let i of ["nw", "ne", "se", "sw"]) {
// let cornerRotateRect = document.createElement("div")
// cornerRotateRect.classList.add("cornerRotateRect")
// cornerRotateRect.classList.add(i)
// cornerRotateRect.addEventListener('mouseup', (e) => {
// const newEvent = new MouseEvent(e.type, e);
// stage.dispatchEvent(newEvent)
// })
// cornerRotateRect.addEventListener('mousemove', (e) => {
// const newEvent = new MouseEvent(e.type, e);
// stage.dispatchEvent(newEvent)
// })
// selectionRect.appendChild(cornerRotateRect)
// }
// for (let i of ["nw", "n", "ne", "e", "se", "s", "sw", "w"]) {
// let cornerRect = document.createElement("div")
// cornerRect.classList.add("cornerRect")
// cornerRect.classList.add(i)
// cornerRect.addEventListener('mousedown', (e) => {
// let bbox = undefined;
// let selection = {}
// for (let item of context.selection) {
// if (bbox==undefined) {
// bbox = structuredClone(item.bbox())
// } else {
// growBoundingBox(bbox, item.bbox())
// }
// selection[item.idx] = {x: item.x, y: item.y, scale_x: item.scale_x, scale_y: item.scale_y}
// }
// if (bbox != undefined) {
// context.dragDirection = i
// context.activeTransform = {
// initial: {
// x: {min: bbox.x.min, max: bbox.x.max},
// y: {min: bbox.y.min, max: bbox.y.max},
// selection: selection
// },
// current: {
// x: {min: bbox.x.min, max: bbox.x.max},
// y: {min: bbox.y.min, max: bbox.y.max},
// selection: structuredClone(selection)
// }
// }
// context.activeObject.currentFrame.saveState()
// }
// })
// cornerRect.addEventListener('mouseup', (e) => {
// const newEvent = new MouseEvent(e.type, e);
// stage.dispatchEvent(newEvent)
// })
// cornerRect.addEventListener('mousemove', (e) => {
// const newEvent = new MouseEvent(e.type, e);
// stage.dispatchEvent(newEvent)
// })
// selectionRect.appendChild(cornerRect)
// }
stage.addEventListener("drop", (e) => { stage.addEventListener("drop", (e) => {
e.preventDefault() e.preventDefault()
@ -2951,9 +2976,9 @@ function stage() {
e.preventDefault() e.preventDefault()
}) })
canvases.push(stage) canvases.push(stage)
stageWrapper.appendChild(stage) // stageWrapper.appendChild(stage)
stageWrapper.appendChild(selectionRect) // stageWrapper.appendChild(selectionRect)
scroller.appendChild(stageWrapper) // scroller.appendChild(stageWrapper)
stage.addEventListener("mousedown", (e) => { stage.addEventListener("mousedown", (e) => {
let mouse = getMousePos(stage, e) let mouse = getMousePos(stage, e)
mouse = context.activeObject.transformMouse(mouse) mouse = context.activeObject.transformMouse(mouse)
@ -3391,7 +3416,7 @@ function stage() {
break; break;
} }
}) })
return scroller return stage
} }
function toolbar() { function toolbar() {
@ -3693,11 +3718,10 @@ function timeline() {
}); });
resizeObserver.observe(timeline_cvs); resizeObserver.observe(timeline_cvs);
let scrollSpeed = 1;
timeline_cvs.addEventListener('wheel', (event) => { timeline_cvs.addEventListener('wheel', (event) => {
event.preventDefault(); event.preventDefault();
const deltaX = event.deltaX * scrollSpeed; const deltaX = event.deltaX * config.scrollSpeed;
const deltaY = event.deltaY * scrollSpeed; const deltaY = event.deltaY * config.scrollSpeed;
let maxScroll = context.activeObject.layers.length * layerHeight + gutterHeight - timeline_cvs.height let maxScroll = context.activeObject.layers.length * layerHeight + gutterHeight - timeline_cvs.height
@ -4066,14 +4090,15 @@ function updateLayout(element) {
function updateUI() { function updateUI() {
for (let canvas of canvases) { for (let canvas of canvases) {
canvas.width = config.fileWidth * context.zoomLevel
canvas.height = config.fileHeight * context.zoomLevel
canvas.style.width = `${config.fileWidth * context.zoomLevel}px`
canvas.style.height = `${config.fileHeight * context.zoomLevel}px`
let ctx = canvas.getContext("2d") let ctx = canvas.getContext("2d")
ctx.resetTransform(); ctx.resetTransform();
ctx.scale(context.zoomLevel, context.zoomLevel)
ctx.beginPath() ctx.beginPath()
ctx.fillStyle = backgroundColor
ctx.fillRect(0,0,canvas.width, canvas.height)
ctx.translate(-canvas.offsetX, -canvas.offsetY)
ctx.scale(context.zoomLevel, context.zoomLevel)
ctx.fillStyle = "white" ctx.fillStyle = "white"
ctx.fillRect(0,0,config.fileWidth,config.fileHeight) ctx.fillRect(0,0,config.fileWidth,config.fileHeight)