Major new version #2
@ -126,8 +126,9 @@ let lastScrollRange = { startVW: null, endVW: null }
|
|||||||
let pendingRebuild = false
|
let pendingRebuild = false
|
||||||
// Week label column drag scrolling state (no momentum)
|
// Week label column drag scrolling state (no momentum)
|
||||||
const isWeekColDragging = ref(false)
|
const isWeekColDragging = ref(false)
|
||||||
let weekColDragStartY = 0
|
|
||||||
let weekColDragStartScroll = 0
|
let weekColDragStartScroll = 0
|
||||||
|
let weekColAccum = 0
|
||||||
|
let weekColPointerLocked = false
|
||||||
|
|
||||||
function scheduleRebuild(reason) {
|
function scheduleRebuild(reason) {
|
||||||
if (pendingRebuild) return
|
if (pendingRebuild) return
|
||||||
@ -519,8 +520,9 @@ function handleWeekColMouseDown(e) {
|
|||||||
if (!rect) return
|
if (!rect) return
|
||||||
if (e.clientX < rect.left || e.clientX > rect.right) return
|
if (e.clientX < rect.left || e.clientX > rect.right) return
|
||||||
isWeekColDragging.value = true
|
isWeekColDragging.value = true
|
||||||
weekColDragStartY = e.clientY
|
|
||||||
weekColDragStartScroll = viewport.value.scrollTop
|
weekColDragStartScroll = viewport.value.scrollTop
|
||||||
|
weekColAccum = 0
|
||||||
|
if (viewport.value.requestPointerLock) viewport.value.requestPointerLock()
|
||||||
window.addEventListener('mousemove', handleWeekColMouseMove, { passive: false })
|
window.addEventListener('mousemove', handleWeekColMouseMove, { passive: false })
|
||||||
window.addEventListener('mouseup', handleWeekColMouseUp, { passive: false })
|
window.addEventListener('mouseup', handleWeekColMouseUp, { passive: false })
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@ -529,9 +531,13 @@ function handleWeekColMouseDown(e) {
|
|||||||
|
|
||||||
function handleWeekColMouseMove(e) {
|
function handleWeekColMouseMove(e) {
|
||||||
if (!isWeekColDragging.value || !viewport.value) return
|
if (!isWeekColDragging.value || !viewport.value) return
|
||||||
const dy = e.clientY - weekColDragStartY
|
const dy = weekColPointerLocked ? e.movementY : e.clientY // movementY if locked
|
||||||
// Natural: drag down moves view to earlier content (scroll up)
|
weekColAccum += dy
|
||||||
viewport.value.scrollTop = Math.max(0, weekColDragStartScroll - dy)
|
let desired = weekColDragStartScroll - weekColAccum
|
||||||
|
if (desired < 0) desired = 0
|
||||||
|
const maxScroll = Math.max(0, contentHeight.value - viewportHeight.value)
|
||||||
|
if (desired > maxScroll) desired = maxScroll
|
||||||
|
viewport.value.scrollTop = desired
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -542,9 +548,17 @@ function handleWeekColMouseUp(e) {
|
|||||||
isWeekColDragging.value = false
|
isWeekColDragging.value = false
|
||||||
window.removeEventListener('mousemove', handleWeekColMouseMove)
|
window.removeEventListener('mousemove', handleWeekColMouseMove)
|
||||||
window.removeEventListener('mouseup', handleWeekColMouseUp)
|
window.removeEventListener('mouseup', handleWeekColMouseUp)
|
||||||
|
if (weekColPointerLocked && document.exitPointerLock) document.exitPointerLock()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handlePointerLockChange() {
|
||||||
|
weekColPointerLocked = document.pointerLockElement === viewport.value
|
||||||
|
if (!weekColPointerLocked && isWeekColDragging.value) {
|
||||||
|
handleWeekColMouseUp(new MouseEvent('mouseup'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onScroll = () => {
|
const onScroll = () => {
|
||||||
if (viewport.value) scrollTop.value = viewport.value.scrollTop
|
if (viewport.value) scrollTop.value = viewport.value.scrollTop
|
||||||
scheduleRebuild('scroll')
|
scheduleRebuild('scroll')
|
||||||
@ -567,6 +581,7 @@ onMounted(() => {
|
|||||||
// Capture mousedown in viewport to allow dragging via week label column
|
// Capture mousedown in viewport to allow dragging via week label column
|
||||||
viewport.value.addEventListener('mousedown', handleWeekColMouseDown, true)
|
viewport.value.addEventListener('mousedown', handleWeekColMouseDown, true)
|
||||||
}
|
}
|
||||||
|
document.addEventListener('pointerlockchange', handlePointerLockChange)
|
||||||
|
|
||||||
const timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
calendarStore.updateCurrentDate()
|
calendarStore.updateCurrentDate()
|
||||||
@ -598,6 +613,7 @@ onBeforeUnmount(() => {
|
|||||||
rowProbeObserver.disconnect()
|
rowProbeObserver.disconnect()
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
document.removeEventListener('pointerlockchange', handlePointerLockChange)
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleDayMouseDown = (d) => {
|
const handleDayMouseDown = (d) => {
|
||||||
|
@ -21,9 +21,10 @@ const jogwheelContent = ref(null)
|
|||||||
const syncLock = ref(null)
|
const syncLock = ref(null)
|
||||||
// Drag state (no momentum, 1:1 mapping)
|
// Drag state (no momentum, 1:1 mapping)
|
||||||
const isDragging = ref(false)
|
const isDragging = ref(false)
|
||||||
let dragStartY = 0
|
|
||||||
let mainStartScroll = 0
|
let mainStartScroll = 0
|
||||||
let dragScale = 1 // mainScrollPixels per mouse pixel
|
let dragScale = 1 // mainScrollPixels per mouse pixel
|
||||||
|
let accumDelta = 0
|
||||||
|
let pointerLocked = false
|
||||||
|
|
||||||
// Jogwheel content height is 1/10th of main calendar
|
// Jogwheel content height is 1/10th of main calendar
|
||||||
const jogwheelHeight = computed(() => {
|
const jogwheelHeight = computed(() => {
|
||||||
@ -38,8 +39,8 @@ const handleJogwheelScroll = () => {
|
|||||||
function onDragMouseDown(e) {
|
function onDragMouseDown(e) {
|
||||||
if (e.button !== 0) return
|
if (e.button !== 0) return
|
||||||
isDragging.value = true
|
isDragging.value = true
|
||||||
dragStartY = e.clientY
|
|
||||||
mainStartScroll = props.scrollTop
|
mainStartScroll = props.scrollTop
|
||||||
|
accumDelta = 0
|
||||||
// Precompute scale between jogwheel scrollable range and main scrollable range
|
// Precompute scale between jogwheel scrollable range and main scrollable range
|
||||||
const mainScrollable = Math.max(0, props.totalVirtualWeeks * props.rowHeight - props.viewportHeight)
|
const mainScrollable = Math.max(0, props.totalVirtualWeeks * props.rowHeight - props.viewportHeight)
|
||||||
let jogScrollable = 0
|
let jogScrollable = 0
|
||||||
@ -48,6 +49,10 @@ function onDragMouseDown(e) {
|
|||||||
}
|
}
|
||||||
dragScale = jogScrollable > 0 ? mainScrollable / jogScrollable : 1
|
dragScale = jogScrollable > 0 ? mainScrollable / jogScrollable : 1
|
||||||
if (!isFinite(dragScale) || dragScale <= 0) dragScale = 1
|
if (!isFinite(dragScale) || dragScale <= 0) dragScale = 1
|
||||||
|
// Attempt pointer lock for relative movement
|
||||||
|
if (jogwheelViewport.value && jogwheelViewport.value.requestPointerLock) {
|
||||||
|
jogwheelViewport.value.requestPointerLock()
|
||||||
|
}
|
||||||
window.addEventListener('mousemove', onDragMouseMove, { passive: false })
|
window.addEventListener('mousemove', onDragMouseMove, { passive: false })
|
||||||
window.addEventListener('mouseup', onDragMouseUp, { passive: false })
|
window.addEventListener('mouseup', onDragMouseUp, { passive: false })
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@ -55,9 +60,9 @@ function onDragMouseDown(e) {
|
|||||||
|
|
||||||
function onDragMouseMove(e) {
|
function onDragMouseMove(e) {
|
||||||
if (!isDragging.value) return
|
if (!isDragging.value) return
|
||||||
const dy = e.clientY - dragStartY
|
const dy = pointerLocked ? e.movementY : e.clientY // movementY only valid in pointer lock
|
||||||
// Natural content drag (drag down => scrollTop decreases)
|
accumDelta += dy
|
||||||
let desired = mainStartScroll - dy * dragScale
|
let desired = mainStartScroll - accumDelta * dragScale
|
||||||
if (desired < 0) desired = 0
|
if (desired < 0) desired = 0
|
||||||
const maxScroll = Math.max(0, props.totalVirtualWeeks * props.rowHeight - props.viewportHeight)
|
const maxScroll = Math.max(0, props.totalVirtualWeeks * props.rowHeight - props.viewportHeight)
|
||||||
if (desired > maxScroll) desired = maxScroll
|
if (desired > maxScroll) desired = maxScroll
|
||||||
@ -70,13 +75,23 @@ function onDragMouseUp(e) {
|
|||||||
isDragging.value = false
|
isDragging.value = false
|
||||||
window.removeEventListener('mousemove', onDragMouseMove)
|
window.removeEventListener('mousemove', onDragMouseMove)
|
||||||
window.removeEventListener('mouseup', onDragMouseUp)
|
window.removeEventListener('mouseup', onDragMouseUp)
|
||||||
|
if (pointerLocked && document.exitPointerLock) document.exitPointerLock()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handlePointerLockChange() {
|
||||||
|
pointerLocked = document.pointerLockElement === jogwheelViewport.value
|
||||||
|
if (!pointerLocked && isDragging.value) {
|
||||||
|
// Pointer lock lost (Esc) -> end drag gracefully
|
||||||
|
onDragMouseUp(new MouseEvent('mouseup'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (jogwheelViewport.value) {
|
if (jogwheelViewport.value) {
|
||||||
jogwheelViewport.value.addEventListener('mousedown', onDragMouseDown)
|
jogwheelViewport.value.addEventListener('mousedown', onDragMouseDown)
|
||||||
}
|
}
|
||||||
|
document.addEventListener('pointerlockchange', handlePointerLockChange)
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
@ -85,6 +100,7 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
window.removeEventListener('mousemove', onDragMouseMove)
|
window.removeEventListener('mousemove', onDragMouseMove)
|
||||||
window.removeEventListener('mouseup', onDragMouseUp)
|
window.removeEventListener('mouseup', onDragMouseUp)
|
||||||
|
document.removeEventListener('pointerlockchange', handlePointerLockChange)
|
||||||
})
|
})
|
||||||
|
|
||||||
const syncFromJogwheel = () => {
|
const syncFromJogwheel = () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user