1- import React , { useLayoutEffect , useRef } from 'react' ;
1+ import React , { useLayoutEffect , useRef , useState } from 'react' ;
22import {
33 LIGHTHOUSE_COLOR_CHANNELS ,
44 LIGHTHOUSE_COLS ,
@@ -7,6 +7,11 @@ import {
77
88export const DISPLAY_ASPECT_RATIO = 0.8634 ;
99
10+ export interface MousePos {
11+ x : number ;
12+ y : number ;
13+ }
14+
1015export interface DisplayProps {
1116 frame : Uint8Array ;
1217 width ?: number ;
@@ -16,6 +21,10 @@ export interface DisplayProps {
1621 relativeBezelWidth ?: number ;
1722 relativeGutterWidth ?: number ;
1823 className ?: string ;
24+ strictBoundsChecking ?: boolean ;
25+ onMouseDown ?: ( p : MousePos ) => void ;
26+ onMouseUp ?: ( p : MousePos ) => void ;
27+ onMouseDrag ?: ( p : MousePos ) => void ;
1928}
2029
2130export function Display ( {
@@ -27,9 +36,15 @@ export function Display({
2736 relativeBezelWidth = 0.0183 ,
2837 relativeGutterWidth = 0.0064 ,
2938 className,
39+ strictBoundsChecking = false ,
40+ onMouseDown = ( p : MousePos ) => { } ,
41+ onMouseUp = ( p : MousePos ) => { } ,
42+ onMouseDrag = ( p : MousePos ) => { } ,
3043} : DisplayProps ) {
3144 const canvasRef = useRef < HTMLCanvasElement > ( null ) ;
3245
46+ const [ drag , setDrag ] = useState ( false ) ;
47+ const [ prevCoords , setPrevCoords ] = useState < number [ ] | null > ( null ) ;
3348 // Set up rendering
3449 useLayoutEffect ( ( ) => {
3550 const canvas = canvasRef . current ! ;
@@ -71,18 +86,111 @@ export function Display({
7186 ctx . fillRect ( x , 0 , gutterWidth , height ) ;
7287 }
7388
89+ const midPoints : number [ ] [ ] = [ ] ;
7490 // Draw windows
7591 for ( let j = 0 ; j < columns ; j ++ ) {
7692 const x = bezelWidth + j * windowWidth + ( j + 1 ) * gutterWidth ;
7793
7894 for ( let i = 0 ; i < rows ; i ++ ) {
7995 const y = i * ( 1 + spacersPerRow ) * windowHeight ;
96+ midPoints . push ( [ x + windowWidth / 2 , y + windowHeight / 2 ] ) ;
8097 const k = ( i * LIGHTHOUSE_COLS + j ) * LIGHTHOUSE_COLOR_CHANNELS ;
8198 const rgb = frame . slice ( k , k + LIGHTHOUSE_COLOR_CHANNELS ) ;
8299 ctx . fillStyle = `rgb(${ rgb . join ( ',' ) } )` ;
83100 ctx . fillRect ( x , y , windowWidth , windowHeight ) ;
84101 }
85102 }
103+
104+ const dist = ( [ x1 , y1 ] : number [ ] , [ x2 , y2 ] : number [ ] ) => {
105+ const xDiff = x1 - x2 ;
106+ const yDiff = y1 - y2 ;
107+ return Math . sqrt ( xDiff * xDiff + yDiff * yDiff ) ;
108+ } ;
109+
110+ const mouseToWindowCoords = ( mouseCoords : number [ ] ) => {
111+ const closestPointIdx = midPoints
112+ . map ( p => dist ( p , mouseCoords ) )
113+ . reduce (
114+ ( [ aIdx , acc ] , val , idx ) => [
115+ val < acc ? idx : aIdx ,
116+ Math . min ( acc , val ) ,
117+ ] ,
118+ [ - 1 , Infinity ]
119+ ) [ 0 ] ;
120+ const closestPoint = midPoints [ closestPointIdx ] ;
121+ const x = closestPoint [ 0 ] - windowWidth / 2 ;
122+ const y = closestPoint [ 1 ] - windowHeight / 2 ;
123+ if (
124+ strictBoundsChecking &&
125+ ! (
126+ mouseCoords [ 0 ] >= x &&
127+ mouseCoords [ 0 ] <= x + windowWidth &&
128+ mouseCoords [ 1 ] >= y &&
129+ mouseCoords [ 1 ] <= y + windowHeight
130+ )
131+ ) {
132+ return null ;
133+ }
134+ const j = Math . round (
135+ ( x - bezelWidth - gutterWidth ) / ( windowWidth + gutterWidth )
136+ ) ;
137+ const i = Math . round ( y / ( windowHeight * ( 1 + spacersPerRow ) ) ) ;
138+ return [ i , j ] ;
139+ } ;
140+
141+ const onMouseDownHandler = ( event : MouseEvent ) => {
142+ setDrag ( true ) ;
143+
144+ const rect = canvas . getBoundingClientRect ( ) ;
145+ const mouseCoords = [ event . clientX - rect . left , event . clientY - rect . top ] ;
146+
147+ const windowCoords = mouseToWindowCoords ( mouseCoords ) ;
148+ if ( ! windowCoords ) return ; // in case of strict bounds checking
149+ setPrevCoords ( windowCoords ) ; // for consecutive drag
150+
151+ onMouseDown ( { x : windowCoords [ 1 ] , y : windowCoords [ 0 ] } ) ;
152+ } ;
153+ const onMouseUpHandler = ( event : MouseEvent ) => {
154+ setDrag ( false ) ;
155+
156+ const rect = canvas . getBoundingClientRect ( ) ;
157+ const mouseCoords = [ event . clientX - rect . left , event . clientY - rect . top ] ;
158+
159+ const windowCoords = mouseToWindowCoords ( mouseCoords ) ;
160+ if ( ! windowCoords ) return ; // in case of strict bounds checking
161+ setPrevCoords ( windowCoords ) ; // for consecutive drag
162+
163+ onMouseUp ( { x : windowCoords [ 1 ] , y : windowCoords [ 0 ] } ) ;
164+ } ;
165+
166+ const onMouseDragHandler = ( event : MouseEvent ) => {
167+ if ( ! drag ) return ;
168+ const rect = canvas . getBoundingClientRect ( ) ;
169+ const mouseCoords = [ event . clientX - rect . left , event . clientY - rect . top ] ;
170+
171+ const windowCoords = mouseToWindowCoords ( mouseCoords ) ;
172+ if ( ! windowCoords ) return ; // in case of strict bounds checking
173+
174+ // don't emit drag events if coords haven't changed
175+ if (
176+ prevCoords &&
177+ prevCoords [ 0 ] === windowCoords [ 0 ] &&
178+ prevCoords [ 1 ] === windowCoords [ 1 ]
179+ ) {
180+ return ;
181+ }
182+ setPrevCoords ( windowCoords ) ;
183+ onMouseDrag ( { x : windowCoords [ 1 ] , y : windowCoords [ 0 ] } ) ;
184+ } ;
185+ canvas . style . cursor = 'crosshair' ;
186+ canvas . addEventListener ( 'mousedown' , onMouseDownHandler ) ;
187+ canvas . addEventListener ( 'mousemove' , onMouseDragHandler ) ;
188+ canvas . addEventListener ( 'mouseup' , onMouseUpHandler ) ;
189+ return ( ) => {
190+ canvas . removeEventListener ( 'mousedown' , onMouseDownHandler ) ;
191+ canvas . removeEventListener ( 'mousemove' , onMouseDragHandler ) ;
192+ canvas . removeEventListener ( 'mouseup' , onMouseUpHandler ) ;
193+ } ;
86194 } , [
87195 customWidth ,
88196 aspectRatio ,
@@ -91,6 +199,14 @@ export function Display({
91199 columns ,
92200 relativeBezelWidth ,
93201 relativeGutterWidth ,
202+ strictBoundsChecking ,
203+ setDrag ,
204+ drag ,
205+ setPrevCoords ,
206+ prevCoords ,
207+ onMouseDown ,
208+ onMouseUp ,
209+ onMouseDrag ,
94210 ] ) ;
95211
96212 return < canvas ref = { canvasRef } className = { className } /> ;
0 commit comments