function _getMousePos ( canvas , evt ) { const rect = canvas . getBoundingClientRect ( ) ; return new Vector2 ( evt . clientX - rect . left , evt . clientY - rect . top ) ; } export class Vector2 { x : number ; y : number ; constructor ( x , y ) { this . x = x ; this . y = y ; } clone ( ) { return new Vector2 ( this . x , this . y ) ; } static add ( a , b ) { return new Vector2 ( a . x + b . x , a . y + b . y ) ; } static subtract ( a , b ) { return new Vector2 ( a . x - b . x , a . y - b . y ) ; } } export class Rect { public x = 0 ; public y = 0 ; public width : number ; public height : number ; color : string ; public constructor ( ) { this . width = 1 ; this . height = 1 ; this . color = "rgb(255, 50, 50)" ; } public setSize ( w , h ) { this . width = w ; this . height = h ; } public isPointInside ( px , py ) { if ( px >= this . x && px <= this . x + this . width && py >= this . y && py <= this . y + this . height ) return true ; return false ; } public setCenter ( x , y ) { this . x = x - this . width / 2 ; this . y = y - this . height / 2 ; } public centerX ( ) { return this . x + this . width / 2 ; } public centerY ( ) { return this . y + this . height / 2 ; } public move ( dx , dy ) { this . x += dx ; this . y += dy ; } public get left ( ) { return this . x ; } public get top ( ) { return this . y ; } public get right ( ) { return this . x + this . width ; } public get bottom ( ) { return this . y + this . height ; } public get center ( ) { return new Vector2 ( this . centerX ( ) , this . centerY ( ) ) ; } public intersects ( other ) { if ( this . left > other . right ) return false ; if ( this . right < other . left ) return false ; if ( this . bottom < other . top ) return false ; if ( this . top > other . bottom ) return false ; return true ; } public expand ( uniformSize ) { const halfSize = uniformSize * 0.5 ; this . x -= halfSize ; this . y -= halfSize ; this . width += halfSize * 2 ; this . height += halfSize * 2 ; } public expandByRect ( rect ) { this . x = Math . min ( this . x , rect . x ) ; this . y = Math . min ( this . y , rect . y ) ; this . width = Math . max ( this . width , rect . width ) ; this . height = Math . max ( this . height , rect . height ) ; } clone ( ) { const rect = new Rect ( ) ; rect . x = this . x ; rect . y = this . y ; rect . width = this . width ; rect . height = this . height ; return rect ; } } export class SceneView { canvas : HTMLCanvasElement ; context : CanvasRenderingContext2D ; globalMousePos : Vector2 ; mousePos : Vector2 ; prevMousePos : Vector2 ; mouseDownPos : Vector2 ; mouseDragDiff : Vector2 ; zoomFactor : number ; offset : Vector2 ; panning : boolean ; panStart : SVGPoint ; constructor ( canvas ) { this . canvas = canvas ; this . context = this . canvas . getContext ( "2d" ) ; const self = this ; canvas . addEventListener ( "mousemove" , function ( evt ) { self . onMouseMove ( evt ) ; } ) ; canvas . addEventListener ( "mousedown" , function ( evt ) { self . onMouseDown ( evt ) ; } ) ; canvas . addEventListener ( "mouseup" , function ( evt ) { self . onMouseUp ( evt ) ; } ) ; canvas . addEventListener ( "mouseout" , function ( evt ) { self . onMouseOut ( evt ) ; } ) ; canvas . addEventListener ( "mousewheel" , function ( evt ) { self . onMouseScroll ( evt ) ; } ) ; canvas . addEventListener ( "contextmenu" , function ( evt ) { evt . preventDefault ( ) ; } ) ; document . addEventListener ( "mousemove" , function ( evt ) { self . onGlobalMouseMove ( evt ) ; } ) ; this . zoomFactor = 1 ; this . offset = new Vector2 ( 0 , 0 ) ; this . mousePos = new Vector2 ( 0 , 0 ) ; this . globalMousePos = new Vector2 ( 0 , 0 ) ; } getAbsPos ( ) { return new Vector2 ( this . canvas . offsetLeft , this . canvas . offsetTop ) ; } isMouseOverCanvas ( ) { const rect = this . canvas . getBoundingClientRect ( ) ; if ( this . globalMousePos . x < rect . left ) return false ; if ( this . globalMousePos . y < rect . top ) return false ; if ( this . globalMousePos . x > rect . right ) return false ; if ( this . globalMousePos . y > rect . bottom ) return false ; return true ; } onMouseDown ( evt ) { if ( evt . button == 1 || evt . button == 2 ) { this . panning = true ; this . mouseDownPos = _getMousePos ( this . canvas , evt ) ; } this . mousePos = _getMousePos ( this . canvas , evt ) ; } onMouseUp ( evt ) { if ( evt . button == 1 || evt . button == 2 ) { this . panning = false ; } } onMouseMove ( evt ) { this . prevMousePos = this . mousePos ; this . mousePos = _getMousePos ( this . canvas , evt ) ; if ( this . panning ) { const prev = this . canvasToScene ( this . prevMousePos ) ; const cur = this . canvasToScene ( this . mousePos ) ; const diff = new Vector2 ( prev . x - cur . x , prev . y - cur . y ) ; this . mouseDragDiff = diff ; const factor = this . zoomFactor ; this . offset . x -= diff . x * factor ; this . offset . y -= diff . y * factor ; } } onGlobalMouseMove ( evt ) { this . globalMousePos = new Vector2 ( evt . pageX , evt . pageY ) ; } onMouseScroll ( evt ) { if ( this . panning ) return ; const pos = _getMousePos ( this . canvas , evt ) ; const delta = ( < any > evt ) . wheelDelta > 0 ? 1.1 : 1.0 / 1.1 ; this . zoomFactor *= delta ; this . offset . x = pos . x - ( pos . x - this . offset . x ) * delta ; this . offset . y = pos . y - ( pos . y - this . offset . y ) * delta ; evt . preventDefault ( ) ; return false ; } onMouseOut ( evt ) { this . panning = false ; } get sceneCenter ( ) : Vector2 { return this . canvasToSceneXY ( this . canvas . width / 2 , this . canvas . height / 2 ) ; } zoom ( x , y , level ) { } clear ( context , style = "rgb(50,50,50)" ) { const ctx = context ; ctx . setTransform ( 1 , 0 , 0 , 1 , 0 , 0 ) ; ctx . fillStyle = style ; ctx . fillRect ( 0 , 0 , this . canvas . width , this . canvas . height ) ; } setViewMatrix ( context ) { context . setTransform ( this . zoomFactor , 0 , 0 , this . zoomFactor , this . offset . x , this . offset . y ) ; } drawGrid ( ctx , GRID_SIZE , strokeStyle , lineWidth ) { const tl = this . canvasToSceneXY ( 0 , 0 ) ; const br = this . canvasToSceneXY ( this . canvas . width , this . canvas . height ) ; ctx . strokeStyle = strokeStyle ; ctx . lineWidth = lineWidth ; const vCount = ( br . x - tl . x ) / GRID_SIZE + 1.0 ; const xStart = tl . x - ( tl . x % GRID_SIZE ) ; for ( let i = 0 ; i < vCount ; i ++ ) { ctx . beginPath ( ) ; ctx . moveTo ( xStart + i * GRID_SIZE , tl . y ) ; ctx . lineTo ( xStart + i * GRID_SIZE , br . y ) ; ctx . stroke ( ) ; } const hCount = ( br . y - tl . y ) / GRID_SIZE + 1.0 ; const yStart = tl . y - ( tl . y % GRID_SIZE ) ; for ( let i = 0 ; i < hCount ; i ++ ) { ctx . beginPath ( ) ; ctx . moveTo ( tl . x , yStart + i * GRID_SIZE ) ; ctx . lineTo ( br . x , yStart + i * GRID_SIZE ) ; ctx . stroke ( ) ; } } canvasToScene ( pos ) { return new Vector2 ( ( pos . x - this . offset . x ) * ( 1.0 / this . zoomFactor ) , ( pos . y - this . offset . y ) * ( 1.0 / this . zoomFactor ) ) ; } canvasToSceneXY ( x , y ) { return new Vector2 ( ( x - this . offset . x ) * ( 1.0 / this . zoomFactor ) , ( y - this . offset . y ) * ( 1.0 / this . zoomFactor ) ) ; } globalToCanvasXY ( x , y ) { const rect = this . canvas . getBoundingClientRect ( ) ; return new Vector2 ( x - rect . left , y - rect . top ) ; } getMouseSceneSpace ( ) { return this . canvasToScene ( this . mousePos ) ; } getMouseDeltaCanvasSpace ( ) { const prev = this . prevMousePos ; const cur = this . mousePos ; const diff = new Vector2 ( cur . x - prev . x , cur . y - prev . y ) ; return diff ; } getMouseDeltaSceneSpace ( ) { const prev = this . canvasToScene ( this . prevMousePos ) ; const cur = this . canvasToScene ( this . mousePos ) ; const diff = new Vector2 ( cur . x - prev . x , cur . y - prev . y ) ; return diff ; } }