neko/src/component/cursors.vue

136 lines
3.4 KiB
Vue
Raw Normal View History

2021-10-27 05:02:01 +13:00
<template>
<canvas ref="overlay" class="neko-cursors" tabindex="0" />
</template>
<style lang="scss" scoped>
.neko-cursors {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
outline: 0;
}
</style>
<script lang="ts">
import { Vue, Component, Ref, Prop, Watch } from 'vue-property-decorator'
2021-11-02 08:56:10 +13:00
import { Cursor, Session } from './types/state'
2021-10-27 05:02:01 +13:00
import { CursorDrawFunction, Dimension } from './types/cursors'
const CANVAS_SCALE = 2
@Component({
name: 'neko-cursors',
})
export default class extends Vue {
@Ref('overlay') readonly _overlay!: HTMLCanvasElement
private _ctx!: CanvasRenderingContext2D
2021-11-02 08:56:10 +13:00
@Prop()
private readonly sessions!: Record<string, Session>
2021-10-27 09:55:22 +13:00
@Prop()
private readonly sessionId!: string
2021-10-27 05:02:01 +13:00
@Prop()
private readonly screenSize!: Dimension
@Prop()
private readonly canvasSize!: Dimension
@Prop()
2021-11-02 08:49:59 +13:00
private readonly cursors!: Cursor[]
2021-10-27 05:02:01 +13:00
@Prop()
private readonly cursorDraw!: CursorDrawFunction | null
mounted() {
// get canvas overlay context
const ctx = this._overlay.getContext('2d')
if (ctx != null) {
this._ctx = ctx
}
// synchronize intrinsic with extrinsic dimensions
const { width, height } = this._overlay.getBoundingClientRect()
this._overlay.width = width * CANVAS_SCALE
this._overlay.height = height * CANVAS_SCALE
}
beforeDestroy() {}
private canvasRequestedFrame = false
@Watch('canvasSize')
onCanvasSizeChange({ width, height }: Dimension) {
this._overlay.width = width * CANVAS_SCALE
this._overlay.height = height * CANVAS_SCALE
this.canvasRequestRedraw()
}
@Watch('cursors')
@Watch('cursorDraw')
canvasRequestRedraw() {
// skip rendering if there is already in progress
if (this.canvasRequestedFrame) return
// request animation frame from a browser
this.canvasRequestedFrame = true
window.requestAnimationFrame(() => {
this.canvasRedraw()
this.canvasRequestedFrame = false
})
}
canvasRedraw() {
if (this.screenSize == null) return
// clear drawings
this.canvasClear()
// get intrinsic dimensions
let { width, height } = this.canvasSize
this._ctx.setTransform(CANVAS_SCALE, 0, 0, CANVAS_SCALE, 0, 0)
// draw cursors
for (let { id, x, y } of this.cursors) {
2021-10-27 09:55:22 +13:00
// ignore own cursor
if (id == this.sessionId) continue
2021-10-27 05:02:01 +13:00
// get cursor position
x = Math.round((x / this.screenSize.width) * width)
y = Math.round((y / this.screenSize.height) * height)
2021-11-02 08:56:10 +13:00
// get cursor tag
const cursorTag = this.sessions[id]?.profile.name || ''
2021-10-27 05:02:01 +13:00
// use custom draw function, if available
if (this.cursorDraw) {
2021-11-02 08:56:10 +13:00
this.cursorDraw(this._ctx, x, y, cursorTag)
2021-10-27 05:02:01 +13:00
continue
}
this._ctx.save()
this._ctx.font = '14px Arial, sans-serif'
this._ctx.textBaseline = 'top'
this._ctx.shadowColor = 'black'
this._ctx.shadowBlur = 2
this._ctx.lineWidth = 2
this._ctx.fillStyle = 'black'
2021-11-02 08:56:10 +13:00
this._ctx.strokeText(cursorTag, x, y)
2021-10-27 05:02:01 +13:00
this._ctx.shadowBlur = 0
this._ctx.fillStyle = 'white'
2021-11-02 08:56:10 +13:00
this._ctx.fillText(cursorTag, x, y)
2021-10-27 05:02:01 +13:00
this._ctx.restore()
}
}
canvasClear() {
const { width, height } = this._overlay
this._ctx.clearRect(0, 0, width, height)
}
}
</script>