mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
session cursors (WIP).
This commit is contained in:
parent
3227e691ea
commit
cae092fb20
123
src/component/cursors.vue
Normal file
123
src/component/cursors.vue
Normal file
@ -0,0 +1,123 @@
|
||||
<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'
|
||||
|
||||
import { SessionCursor } from './types/state'
|
||||
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
|
||||
|
||||
@Prop()
|
||||
private readonly screenSize!: Dimension
|
||||
|
||||
@Prop()
|
||||
private readonly canvasSize!: Dimension
|
||||
|
||||
@Prop()
|
||||
private readonly cursors!: SessionCursor[]
|
||||
|
||||
@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) {
|
||||
// get cursor position
|
||||
x = Math.round((x / this.screenSize.width) * width)
|
||||
y = Math.round((y / this.screenSize.height) * height)
|
||||
|
||||
// use custom draw function, if available
|
||||
if (this.cursorDraw) {
|
||||
this.cursorDraw(this._ctx, x, y, id)
|
||||
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'
|
||||
this._ctx.strokeText(id, x, y)
|
||||
this._ctx.shadowBlur = 0
|
||||
this._ctx.fillStyle = 'white'
|
||||
this._ctx.fillText(id, x, y)
|
||||
this._ctx.restore()
|
||||
}
|
||||
}
|
||||
|
||||
canvasClear() {
|
||||
const { width, height } = this._overlay
|
||||
this._ctx.clearRect(0, 0, width, height)
|
||||
}
|
||||
}
|
||||
</script>
|
@ -188,6 +188,11 @@ export class NekoMessages extends EventEmitter<NekoEvents> {
|
||||
}
|
||||
}
|
||||
|
||||
protected [EVENT.SESSION_CURSORS](cursors: message.SessionCursor[]) {
|
||||
// TODO: State retention logic.
|
||||
Vue.set(this._state, 'cursors', cursors)
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// Control Events
|
||||
/////////////////////////////
|
||||
|
@ -3,6 +3,7 @@
|
||||
<div ref="container" class="neko-container">
|
||||
<video v-show="!screencast" ref="video" :autoplay="autoplay" :muted="autoplay" playsinline />
|
||||
<neko-screencast v-show="screencast" :enabled="screencast" :api="api.room" />
|
||||
<neko-cursors :screenSize="state.screen.size" :canvasSize="canvasSize" :cursors="state.cursors" />
|
||||
<neko-overlay
|
||||
:webrtc="connection.webrtc"
|
||||
:scroll="state.control.scroll"
|
||||
@ -69,12 +70,14 @@
|
||||
import { Dimension, CursorDrawFunction } from './types/overlay'
|
||||
import Overlay from './overlay.vue'
|
||||
import Screencast from './screencast.vue'
|
||||
import Cursors from './cursors.vue'
|
||||
|
||||
@Component({
|
||||
name: 'neko-canvas',
|
||||
components: {
|
||||
'neko-overlay': Overlay,
|
||||
'neko-screencast': Screencast,
|
||||
'neko-cursors': Cursors,
|
||||
},
|
||||
})
|
||||
export default class extends Vue {
|
||||
@ -159,6 +162,7 @@
|
||||
},
|
||||
session_id: null,
|
||||
sessions: {},
|
||||
cursors: [],
|
||||
} as NekoState
|
||||
|
||||
/////////////////////////////
|
||||
|
@ -249,9 +249,10 @@
|
||||
}
|
||||
|
||||
onMouseMove(e: MouseEvent) {
|
||||
if (!this.isControling) {
|
||||
return
|
||||
}
|
||||
// TODO: Send less events if not controlling.
|
||||
//if (!this.isControling) {
|
||||
// return
|
||||
//}
|
||||
|
||||
this.setMousePos(e)
|
||||
}
|
||||
|
6
src/component/types/cursors.ts
Normal file
6
src/component/types/cursors.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export type CursorDrawFunction = (ctx: CanvasRenderingContext2D, x: number, y: number, id: string) => void
|
||||
|
||||
export interface Dimension {
|
||||
width: number
|
||||
height: number
|
||||
}
|
@ -14,6 +14,7 @@ export const SESSION_CREATED = 'session/created'
|
||||
export const SESSION_DELETED = 'session/deleted'
|
||||
export const SESSION_PROFILE = 'session/profile'
|
||||
export const SESSION_STATE = 'session/state'
|
||||
export const SESSION_CURSORS = 'session/cursors'
|
||||
|
||||
export const CONTROL_HOST = 'control/host'
|
||||
export const CONTROL_RELEASE = 'control/release'
|
||||
|
@ -87,6 +87,12 @@ export interface SessionData {
|
||||
is_watching: boolean
|
||||
}
|
||||
|
||||
export interface SessionCursor {
|
||||
id: string
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// Control
|
||||
/////////////////////////////
|
||||
|
@ -9,6 +9,7 @@ export default interface State {
|
||||
screen: Screen
|
||||
session_id: string | null
|
||||
sessions: Record<string, Session>
|
||||
cursors: SessionCursor[]
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
@ -118,3 +119,9 @@ export interface Session {
|
||||
profile: MemberProfile
|
||||
state: SessionState
|
||||
}
|
||||
|
||||
export interface SessionCursor {
|
||||
id: string
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user