import '../../scss/atlas.scss' ; import Bacon = require ( 'baconjs' ) ; import Hammer = require ( 'hammerjs' ) ; import AtlasModel from '../model/atlas' ; import CoordsModel from '../model/coords' ; import Chunk from 'slime-finder/chunk' ; import Point from 'slime-finder/point' ; declare var $ ; Bacon . $ . init ( $ ) ; export default class AtlasView { protected readonly center : Bacon . Observable < Point > ; protected readonly scale : Bacon . Observable < number > ; protected readonly canvas : HTMLCanvasElement ; protected readonly scaleSlider : HTMLInputElement ; protected redraw ( center , scale ) { const ctx = this . canvas . getContext ( "2d" ) ; if ( ctx ) { ctx . clearRect ( 0 , 0 , this . canvas . width , this . canvas . height ) ; ctx . fillStyle = 'rgba(0, 0, 0, 0)' ; ctx . fill ( ) ; const topLeft = new Point ( center . x - ( this . canvas . width / 2 ) / scale , center . z - ( this . canvas . height / 2 ) / scale ) ; const bottomRight = new Point ( topLeft . x + this . canvas . width / scale , topLeft . z + this . canvas . height / scale ) ; const isPointVisible = ( p ) : boolean => { return p . x >= topLeft . x && p . z >= topLeft . z && p . x < bottomRight . x && p . z < bottomRight . z ; } ; const isChunkVisible = ( c ) : boolean => { return isPointVisible ( c . origin ) || isPointVisible ( c . origin . offset ( 16 , 0 ) ) || isPointVisible ( c . origin . offset ( 0 , 16 ) ) || isPointVisible ( c . origin . offset ( 16 , 16 ) ) ; } ; const worldToAtlas = ( p ) : Point => { return new Point ( ( p . x - topLeft . x ) * scale , ( p . z - topLeft . z ) * scale ) ; } ; ctx . strokeStyle = 'rgba(0, 0, 0, 0.8)' ; ctx . fillStyle = 'rgba(127, 255, 0, 0.4)' ; ctx . lineWidth = 1.0 ; for ( let c = new Chunk ( topLeft ) ; isChunkVisible ( c ) ; c = c . offset ( 0 , 1 ) ) { for ( let d = c ; isChunkVisible ( d ) ; d = d . offset ( 1 , 0 ) ) { const origin = worldToAtlas ( d . origin ) ; ctx . strokeRect ( Math . floor ( origin . x ) , Math . floor ( origin . z ) , 16 * scale , 16 * scale ) ; if ( d . isSlimy ) { ctx . fillRect ( Math . floor ( origin . x ) , Math . floor ( origin . z ) , 16 * scale , 16 * scale ) ; } } } ctx . strokeStyle = 'rgba(255, 0, 0, 0.4)' ; ctx . lineWidth = 3.0 ; ctx . beginPath ( ) ; ctx . arc ( Math . floor ( worldToAtlas ( center ) . x ) , Math . floor ( worldToAtlas ( center ) . z ) , 4.0 , 0 , 2 * Math . PI ) ; ctx . stroke ( ) ; } } constructor ( atlas ) { this . center = atlas . center ; this . scale = atlas . scale ; this . canvas = < HTMLCanvasElement > $ ( '.atlas-container canvas.atlas' ) . get ( 0 ) ; this . scaleSlider = < HTMLInputElement > $ ( '.atlas-container input.atlas-scale' ) . get ( 0 ) ; let getSize = ( ) => { return { w : $ ( window ) . width ( ) , h : $ ( window ) . height ( ) } ; } ; let size = $ ( window ) . asEventStream ( 'resize' ) . throttle ( 50 ) . map ( getSize ) . merge ( Bacon . once ( getSize ( ) ) ) ; Bacon . combineAsArray < any > ( this . center , this . scale , size ) . onValues ( ( c , sc , sz ) => { this . canvas . width = sz . w ; this . canvas . height = Math . floor ( sz . h * ( 50 / 100 ) ) ; $ ( this . scaleSlider ) . height ( Math . floor ( $ ( this . canvas ) . height ( ) * ( 30 / 100 ) ) ) ; this . redraw ( c , sc ) ; } ) ; this . scale . onValue ( ( sc ) => { this . scaleSlider . value = String ( sc ) ; } ) ; let sliderScale = $ ( this . scaleSlider ) . asEventStream ( 'input' ) . map ( ( e ) => { return ( ) => Number ( this . scaleSlider . value ) ; } ) ; atlas . scaleChanges . plug ( sliderScale ) ; let hm = new Hammer . Manager ( this . canvas , { domEvents : true , recognizers : [ [ Hammer . Pan ] , [ Hammer . Pinch ] ] } ) ; function preventDefault ( e ) { e . preventDefault ( ) ; } let wheelScale = $ ( this . canvas ) . asEventStream ( 'wheel' ) . doAction ( preventDefault ) . debounceImmediate ( 10 ) . map ( ( e ) => { const min = Number ( this . scaleSlider . min ) ; const max = Number ( this . scaleSlider . max ) ; const step = Number ( this . scaleSlider . step ) ; return ( s0 ) => { if ( ( < WheelEvent > e . originalEvent ) . deltaY > 0 ) { return Math . min ( s0 + step , max ) ; } else { return Math . max ( s0 - step , min ) ; } } ; } ) ; atlas . scaleChanges . plug ( wheelScale ) ; let pinchStart = $ ( this . canvas ) . asEventStream ( 'pinchstart' ) . doAction ( preventDefault ) ; let pinchMove = $ ( this . canvas ) . asEventStream ( 'pinch' ) . doAction ( preventDefault ) . debounceImmediate ( 50 ) ; let pinchEnd = $ ( this . canvas ) . asEventStream ( 'pinchend pinchcancel' ) . doAction ( preventDefault ) ; let pinch = pinchStart . merge ( pinchMove ) . merge ( pinchEnd ) ; let pinchScale = Bacon . combineAsArray < any > ( this . scale , pinch ) . withStateMachine ( null , ( s0 , ev ) => { if ( ev . hasValue ) { const [ s , jqEvent ] = < [ number , any ] > ev . value ; const domEvent = jqEvent . originalEvent ; const hmEvent = domEvent . gesture ; switch ( domEvent . type ) { case 'pinchstart' : return [ s , [ ] ] ; case 'pinch' : if ( s0 ) { const min = Number ( this . scaleSlider . min ) ; const max = Number ( this . scaleSlider . max ) ; const step = Number ( this . scaleSlider . step ) ; const ds = hmEvent . scale ; const s1 = s0 * ds ; const s1q = Math . round ( s1 * ( 1 / step ) ) / ( 1 / step ) ; const s1qr = Math . min ( Math . max ( s1q , min ) , max ) ; return [ s0 , [ new Bacon . Next ( s1qr ) ] ] ; } } } return [ null , [ ] ] ; } ) . map ( ( s ) => { return ( ) => s ; } ) ; atlas . scaleChanges . plug ( pinchScale ) ; let panStart = $ ( this . canvas ) . asEventStream ( 'panstart' ) . doAction ( preventDefault ) ; let panMove = $ ( this . canvas ) . asEventStream ( 'pan' ) . doAction ( preventDefault ) . debounceImmediate ( 10 ) ; let panEnd = $ ( this . canvas ) . asEventStream ( 'panend pancancel' ) . doAction ( preventDefault ) ; let pan = panStart . merge ( panMove ) . merge ( panEnd ) ; let centerChanges = Bacon . combineAsArray < any > ( this . scale , this . center , pan ) . withStateMachine ( null , ( c0 , ev ) => { if ( ev . hasValue ) { let [ scale , c , jqEvent ] = < [ number , Point , any ] > ev . value ; let domEvent = jqEvent . originalEvent ; let hmEvent = domEvent . gesture ; switch ( domEvent . type ) { case 'panstart' : return [ c , [ ] ] ; case 'pan' : if ( c0 ) { let ax = this . canvas . width / $ ( this . canvas ) . width ( ) ; let az = this . canvas . height / $ ( this . canvas ) . height ( ) ; let dx = - hmEvent . deltaX * ax / scale ; let dz = - hmEvent . deltaY * az / scale ; let c1 = c0 . offset ( dx , dz ) . round ( ) ; return [ c0 , [ new Bacon . Next ( c1 ) ] ] ; } } } return [ null , [ ] ] ; } ) . map ( ( c ) => { return ( ) => c ; } ) ; atlas . centerChanges . plug ( centerChanges ) ; } }