Multiplayer: add real-time cursor tracking

This commit is contained in:
Nick O'Leary 2024-08-05 16:18:05 +01:00
parent 8af821d380
commit 964271f9c7
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
2 changed files with 135 additions and 63 deletions

View File

@ -100,15 +100,35 @@ RED.multiplayer = (function () {
break break
} }
} }
if (isInWorkspace) {
const chart = $('#red-ui-workspace-chart')
const chartOffset = chart.offset()
const scaleFactor = RED.view.scale()
location.cursor = {
x: (lastPosition[0] - chartOffset.left + chart.scrollLeft()) / scaleFactor,
y: (lastPosition[1] - chartOffset.top + chart.scrollTop()) / scaleFactor
}
}
return location return location
} }
let publishLocationTimeout
let lastPosition = [0,0]
let isInWorkspace = false
function publishLocation () { function publishLocation () {
if (!publishLocationTimeout) {
publishLocationTimeout = setTimeout(() => {
const location = getLocation() const location = getLocation()
if (location.workspace !== 0) { if (location.workspace !== 0) {
log('send', 'multiplayer/location', location) log('send', 'multiplayer/location', location)
RED.comms.send('multiplayer/location', location) RED.comms.send('multiplayer/location', location)
} }
publishLocationTimeout = null
}, 100)
} }
}
function revealUser(location, skipWorkspace) { function revealUser(location, skipWorkspace) {
if (location.node) { if (location.node) {
@ -271,7 +291,16 @@ RED.multiplayer = (function () {
function removeUserLocation (sessionId) { function removeUserLocation (sessionId) {
updateUserLocation(sessionId, {}) updateUserLocation(sessionId, {})
removeUserCursor(sessionId)
} }
function removeUserCursor (sessionId) {
// return
if (sessions[sessionId]?.cursor) {
sessions[sessionId].cursor.parentNode.removeChild(sessions[sessionId].cursor)
delete sessions[sessionId].cursor
}
}
function updateUserLocation (sessionId, location) { function updateUserLocation (sessionId, location) {
let viewTouched = false let viewTouched = false
const oldLocation = sessions[sessionId].location const oldLocation = sessions[sessionId].location
@ -291,6 +320,28 @@ RED.multiplayer = (function () {
// console.log(`updateUserLocation sessionId:${sessionId} oldWS:${oldLocation?.workspace} newWS:${location.workspace}`) // console.log(`updateUserLocation sessionId:${sessionId} oldWS:${oldLocation?.workspace} newWS:${location.workspace}`)
if (location.workspace) { if (location.workspace) {
getWorkspaceTray(location.workspace).addUser(sessionId) getWorkspaceTray(location.workspace).addUser(sessionId)
if (location.cursor && location.workspace === RED.workspaces.active()) {
if (!sessions[sessionId].cursor) {
const user = sessions[sessionId].user
const cursorIcon = document.createElementNS("http://www.w3.org/2000/svg","g");
cursorIcon.setAttribute("class", "red-ui-multiplayer-annotation")
cursorIcon.appendChild(createAnnotationUser(user, true))
$(cursorIcon).css({
transform: `translate( ${location.cursor.x}px, ${location.cursor.y}px)`,
transition: 'transform 0.1s linear'
})
$("#red-ui-workspace-chart svg").append(cursorIcon)
sessions[sessionId].cursor = cursorIcon
} else {
const cursorIcon = sessions[sessionId].cursor
$(cursorIcon).css({
transform: `translate( ${location.cursor.x}px, ${location.cursor.y}px)`
})
}
} else if (sessions[sessionId].cursor) {
removeUserCursor(sessionId)
}
} }
if (location.node) { if (location.node) {
addUserToNode(sessionId, location.node) addUserToNode(sessionId, location.node)
@ -309,17 +360,19 @@ RED.multiplayer = (function () {
// } // }
// } // }
return {
init: function () {
function createAnnotationUser(user) { function createAnnotationUser(user, pointer = false) {
const group = document.createElementNS("http://www.w3.org/2000/svg","g");
const badge = document.createElementNS("http://www.w3.org/2000/svg","circle");
const radius = 20 const radius = 20
badge.setAttribute("cx",radius/2); const halfRadius = radius/2
badge.setAttribute("cy",radius/2); const group = document.createElementNS("http://www.w3.org/2000/svg","g");
badge.setAttribute("r",radius/2); const badge = document.createElementNS("http://www.w3.org/2000/svg","path");
let shapePath
if (!pointer) {
shapePath = `M 0 ${halfRadius} a ${halfRadius} ${halfRadius} 0 1 1 ${radius} 0 a ${halfRadius} ${halfRadius} 0 1 1 -${radius} 0 z`
} else {
shapePath = `M 0 0 h ${halfRadius} a ${halfRadius} ${halfRadius} 0 1 1 -${halfRadius} ${halfRadius} z`
}
badge.setAttribute('d', shapePath)
badge.setAttribute("class", "red-ui-multiplayer-annotation-background") badge.setAttribute("class", "red-ui-multiplayer-annotation-background")
group.appendChild(badge) group.appendChild(badge)
if (user && user.profileColor !== undefined) { if (user && user.profileColor !== undefined) {
@ -359,18 +412,18 @@ RED.multiplayer = (function () {
label.setAttribute("y",radius/2 + 3); label.setAttribute("y",radius/2 + 3);
group.appendChild(label) group.appendChild(label)
} }
const border = document.createElementNS("http://www.w3.org/2000/svg","circle"); const border = document.createElementNS("http://www.w3.org/2000/svg","path");
border.setAttribute("cx",radius/2); border.setAttribute('d', shapePath)
border.setAttribute("cy",radius/2);
border.setAttribute("r",radius/2);
border.setAttribute("class", "red-ui-multiplayer-annotation-border") border.setAttribute("class", "red-ui-multiplayer-annotation-border")
group.appendChild(border) group.appendChild(border)
return group return group
} }
return {
init: function () {
RED.view.annotations.register("red-ui-multiplayer",{ RED.view.annotations.register("red-ui-multiplayer",{
type: 'badge', type: 'badge',
align: 'left', align: 'left',
@ -479,6 +532,24 @@ RED.multiplayer = (function () {
RED.comms.send('multiplayer/disconnect', disconnectInfo) RED.comms.send('multiplayer/disconnect', disconnectInfo)
RED.settings.removeLocal('multiplayer:sessionId') RED.settings.removeLocal('multiplayer:sessionId')
}) })
const chart = $('#red-ui-workspace-chart')
chart.on('mousemove', function (evt) {
lastPosition[0] = evt.clientX
lastPosition[1] = evt.clientY
publishLocation()
})
chart.on('scroll', function (evt) {
publishLocation()
})
chart.on('mouseenter', function () {
isInWorkspace = true
publishLocation()
})
chart.on('mouseleave', function () {
isInWorkspace = false
publishLocation()
})
} }
} }

View File

@ -110,7 +110,8 @@ module.exports = {
const payload = { const payload = {
session: sessionId, session: sessionId,
workspace: opts.data.workspace, workspace: opts.data.workspace,
node: opts.data.node node: opts.data.node,
cursor: opts.data.cursor
} }
runtime.events.emit('comms', { runtime.events.emit('comms', {
topic: 'multiplayer/location', topic: 'multiplayer/location',