module Webglimpse { export interface Painter { ( gl , viewport ) ; } export interface PointerEvent { paneViewport : BoundsUnmodifiable ; i : number ; j : number ; wheelSteps ? : number ; clickCount ? : number ; mouseEvent ? : MouseEvent ; } export function xFrac ( ev ) { return ev . paneViewport . xFrac ( ev . i ) ; } export function yFrac ( ev ) { return ev . paneViewport . yFrac ( ev . j ) ; } export interface LayoutEntry { layoutArg : any ; layoutOptions : any ; viewport : Bounds ; prefSize : Size ; } export interface LayoutPhase1 { ( parentPrefSize , children ) ; } export interface LayoutPhase2 { ( children , parentViewport ) ; } export interface Layout { updatePrefSize ? : LayoutPhase1 ; updateChildViewports ? : LayoutPhase2 ; } export interface Mask2D { ( viewport , i , j ) : boolean ; } class PaneChild implements LayoutEntry { pane : Pane ; layoutArg : any ; layoutOptions : any ; viewport : Bounds ; scissor : Bounds ; prefSize : Size ; constructor ( pane , layoutArg , layoutOptions ) { this . pane = pane ; this . layoutArg = layoutArg ; this . layoutOptions = layoutOptions ; this . viewport = newBoundsFromRect ( 0 , 0 , 0 , 0 ) ; this . scissor = newBoundsFromRect ( 0 , 0 , 0 , 0 ) ; this . prefSize = { w : 0 , h : 0 } ; } } export class Pane { private painters : Painter [ ] ; private consumesInputEvents : boolean ; private isInside : Mask2D ; private _mouseCursor : string ; private _mouseCursorChanged : Notification ; private _childMouseCursorListener : Listener ; private children : OrderedSet < PaneChild > ; private _layout : Layout ; private _viewport : Bounds ; private _scissor : Bounds ; private _viewportChanged : Notification ; private _dispose : Notification ; constructor ( layout , consumesInputEvents = true , isInside = alwaysTrue ) { this . painters = [ ] ; this . consumesInputEvents = consumesInputEvents ; this . isInside = isInside ; this . _mouseCursor = ( consumesInputEvents ? 'default' : null ) ; this . _mouseCursorChanged = new Notification ( ) ; this . _childMouseCursorListener = ( ( ) => this . _mouseCursorChanged . fire ( ) ) ; this . children = new OrderedSet < PaneChild > ( [ ] , ( paneChild ) => getObjectId ( paneChild . pane ) , false ) ; this . _layout = layout ; this . _viewport = newBoundsFromRect ( 0 , 0 , 0 , 0 ) ; this . _scissor = newBoundsFromRect ( 0 , 0 , 0 , 0 ) ; this . _viewportChanged = new Notification ( ) ; this . _dispose = new Notification ( ) ; this . _dispose . on ( ( ) => { this . _mouseUp . dispose ( ) ; this . _mouseDown . dispose ( ) ; this . _mouseMove . dispose ( ) ; this . _mouseWheel . dispose ( ) ; this . _mouseEnter . dispose ( ) ; this . _mouseExit . dispose ( ) ; this . _contextMenu . dispose ( ) ; for ( var i = 0 ; i < this . children . length ; i ++ ) { this . children . valueAt ( i ) . pane . dispose . fire ( ) ; } } ) ; } get layout ( ) : Layout { return this . layout ; } set layout ( layout ) { this . _layout = layout ; } get mouseCursor ( ) : string { return this . _mouseCursor ; } set mouseCursor ( mouseCursor ) { if ( mouseCursor !== this . _mouseCursor ) { this . _mouseCursor = mouseCursor ; this . _mouseCursorChanged . fire ( ) ; } } get mouseCursorChanged ( ) : Notification { return this . _mouseCursorChanged ; } addPainter ( painter ) { this . painters . push ( painter ) ; } addPane ( pane , layoutArg = null , layoutOptions = { } ) { this . children . add ( new PaneChild ( pane , layoutArg , layoutOptions ) ) ; pane . mouseCursorChanged . on ( this . _childMouseCursorListener ) ; } removePane ( pane ) { this . children . removeValue ( this . _childFor ( pane ) ) ; pane . mouseCursorChanged . off ( this . _childMouseCursorListener ) ; } layoutArg ( pane ) { return this . _childFor ( pane ) . layoutArg ; } setLayoutArg ( pane , layoutArg ) { this . _childFor ( pane ) . layoutArg = layoutArg ; } updateLayoutArgs ( updateFn ) { for ( var c = 0 ; c < this . children . length ; c ++ ) { var child = this . children . valueAt ( c ) ; child . layoutArg = updateFn ( child . layoutArg , child . layoutOptions ) ; } } layoutOptions ( pane ) { return this . _childFor ( pane ) . layoutOptions ; } private _childFor ( pane ) { return this . children . valueFor ( getObjectId ( pane ) ) ; } updatePrefSizes ( result ) { for ( var c = 0 ; c < this . children . length ; c ++ ) { var child = this . children . valueAt ( c ) ; child . pane . updatePrefSizes ( child . prefSize ) ; } if ( hasval ( this . _layout ) && hasval ( this . _layout . updatePrefSize ) ) { this . _layout . updatePrefSize ( result , this . children . toArray ( ) ) ; } else { result . w = null ; result . h = null ; } } updateBounds ( viewport , scissor ) { var viewportChanged = ( viewport . iStart !== this . _viewport . iStart || viewport . iEnd !== this . _viewport . iEnd || viewport . jStart !== this . _viewport . jStart || viewport . jEnd !== this . _viewport . jEnd ) ; this . _viewport . setBounds ( viewport ) ; this . _scissor . setBounds ( scissor ) ; if ( hasval ( this . _layout ) && hasval ( this . _layout . updateChildViewports ) ) { this . _layout . updateChildViewports ( this . children . toArray ( ) , viewport ) ; for ( var c = 0 ; c < this . children . length ; c ++ ) { var child = this . children . valueAt ( c ) ; child . scissor . setBounds ( child . viewport . unmod ) ; child . scissor . cropToBounds ( scissor ) ; child . pane . updateBounds ( child . viewport . unmod , child . scissor . unmod ) ; } } else if ( this . children . length > 0 ) { throw new Error ( 'Pane has ' + this . children . length + ' ' + ( this . children . length === 1 ? 'child' : 'children' ) + ', but its layout is ' + this . layout ) ; } if ( viewportChanged ) { this . _viewportChanged . fire ( ) ; } } paint ( gl ) { var viewport = this . _viewport . unmod ; var scissor = this . _scissor . unmod ; if ( scissor . w > 0 && scissor . h > 0 ) { gl . viewport ( viewport . i , viewport . j , viewport . w , viewport . h ) ; gl . enable ( GL . SCISSOR_TEST ) ; gl . scissor ( scissor . i , scissor . j , scissor . w , scissor . h ) ; for ( var p = 0 ; p < this . painters . length ; p ++ ) { this . painters [ p ] ( gl , viewport ) ; } for ( var c = 0 ; c < this . children . length ; c ++ ) { this . children . valueAt ( c ) . pane . paint ( gl ) ; } } } get viewport ( ) : BoundsUnmodifiable { return this . _viewport . unmod ; } get scissor ( ) : BoundsUnmodifiable { return this . _scissor . unmod ; } get viewportChanged ( ) : Notification { return this . _viewportChanged ; } panesAt ( i , j ) { var result = [ ] ; this . _panesAt ( i , j , result ) ; return result ; } private _panesAt ( i , j , result ) { if ( this . _scissor . contains ( i , j ) ) { for ( var c = this . children . length - 1 ; c >= 0 ; c -- ) { var consumed = this . children . valueAt ( c ) . pane . _panesAt ( i , j , result ) ; if ( consumed ) return true ; } if ( this . isInside ( this . _viewport . unmod , i , j ) ) { result . push ( this ) ; return this . consumesInputEvents ; } } return false ; } private _mouseUp : Notification1 < PointerEvent > = new Notification1 < PointerEvent > ( ) ; private _mouseDown : Notification1 < PointerEvent > = new Notification1 < PointerEvent > ( ) ; private _mouseMove : Notification1 < PointerEvent > = new Notification1 < PointerEvent > ( ) ; private _mouseWheel : Notification1 < PointerEvent > = new Notification1 < PointerEvent > ( ) ; private _mouseEnter : Notification1 < PointerEvent > = new Notification1 < PointerEvent > ( ) ; private _mouseExit : Notification1 < PointerEvent > = new Notification1 < PointerEvent > ( ) ; private _contextMenu : Notification1 < PointerEvent > = new Notification1 < PointerEvent > ( ) ; get mouseUp ( ) : Notification1 < PointerEvent > { return this . _mouseUp ; } get mouseDown ( ) : Notification1 < PointerEvent > { return this . _mouseDown ; } get mouseMove ( ) : Notification1 < PointerEvent > { return this . _mouseMove ; } get mouseWheel ( ) : Notification1 < PointerEvent > { return this . _mouseWheel ; } get mouseEnter ( ) : Notification1 < PointerEvent > { return this . _mouseEnter ; } get mouseExit ( ) : Notification1 < PointerEvent > { return this . _mouseExit ; } get contextMenu ( ) : Notification1 < PointerEvent > { return this . _contextMenu ; } fireMouseUp ( i , j , clickCount , mouseEvent ) { return this . _mouseUp . fire ( { paneViewport : this . _viewport . unmod , i : i , j : j , clickCount : clickCount , mouseEvent : mouseEvent } ) ; } fireMouseDown ( i , j , clickCount , mouseEvent ) { return this . _mouseDown . fire ( { paneViewport : this . _viewport . unmod , i : i , j : j , clickCount : clickCount , mouseEvent : mouseEvent } ) ; } fireMouseMove ( i , j , mouseEvent ) { return this . _mouseMove . fire ( { paneViewport : this . _viewport . unmod , i : i , j : j , mouseEvent : mouseEvent } ) ; } fireMouseWheel ( i , j , wheelSteps , mouseEvent ) { return this . _mouseWheel . fire ( { paneViewport : this . _viewport . unmod , i : i , j : j , wheelSteps : wheelSteps , mouseEvent : mouseEvent } ) ; } fireMouseEnter ( i , j , mouseEvent ) { return this . _mouseEnter . fire ( { paneViewport : this . _viewport . unmod , i : i , j : j , mouseEvent : mouseEvent } ) ; } fireMouseExit ( i , j , mouseEvent ) { return this . _mouseExit . fire ( { paneViewport : this . _viewport . unmod , i : i , j : j , mouseEvent : mouseEvent } ) ; } fireContextMenu ( i , j , mouseEvent ) { return this . _contextMenu . fire ( { paneViewport : this . _viewport . unmod , i : i , j : j , mouseEvent : mouseEvent } ) ; } get dispose ( ) : Notification { return this . _dispose ; } } export function requireGL ( canvasElement ) { try { var glA = canvasElement . getContext ( 'webgl' ) ; if ( glA ) return glA ; } catch ( e ) { } try { var glB = canvasElement . getContext ( 'experimental-webgl' ) ; if ( glB ) return glB ; } catch ( e ) { } throw new Error ( 'WebGLContextCreationError' ) ; } function iMouse ( element , ev ) { return ( ev . clientX - element . getBoundingClientRect ( ) . left ) ; } function jMouse ( element , ev ) { return ( element . clientHeight - ( ev . clientY - element . getBoundingClientRect ( ) . top ) ) ; } function mouseWheelSteps ( ev ) { var raw = ( ev . wheelDelta !== undefined ? ev . wheelDelta : - ev . detail ) ; if ( raw > 0 ) { return - 1 ; } if ( raw < 0 ) { return + 1 ; } return 0 ; } export function isLeftMouseDown ( ev ) { if ( ev . buttons !== undefined ) { return ev . buttons === 1 ; } else { return ev . which === 1 ; } } export function isMouseDown ( ev ) { return ev . buttons !== 0 ; } function attachEventListeners ( element , contentPane ) { function detectEntersAndExits ( oldPanes , newPanes , i , j , mouseEvent ) { for ( var n = 0 ; n < oldPanes . length ; n ++ ) { oldPanes [ n ] [ 'isHovered' ] = false ; } for ( var n = 0 ; n < newPanes . length ; n ++ ) { newPanes [ n ] [ 'isHovered' ] = true ; } for ( var n = 0 ; n < oldPanes . length ; n ++ ) { var oldPane = oldPanes [ n ] ; if ( ! oldPane [ 'isHovered' ] ) { oldPane . fireMouseExit ( i , j , mouseEvent ) ; } } for ( var n = 0 ; n < newPanes . length ; n ++ ) { newPanes [ n ] [ 'wasHovered' ] = false ; } for ( var n = 0 ; n < oldPanes . length ; n ++ ) { oldPanes [ n ] [ 'wasHovered' ] = true ; } for ( var n = 0 ; n < newPanes . length ; n ++ ) { var newPane = newPanes [ n ] ; if ( ! newPane [ 'wasHovered' ] ) { newPane . fireMouseEnter ( i , j , mouseEvent ) ; } } } var multiPressTimeout_MILLIS = 250 ; var prevPress_PMILLIS = 0 ; var clickCount = 1 ; var currentPanes = [ ] ; var currentMouseCursor = null ; var dragging = false ; var pendingExit = false ; function refreshMouseCursor ( ) { var newMouseCursor = null ; for ( var n = 0 ; n < currentPanes . length ; n ++ ) { newMouseCursor = currentPanes [ n ] . mouseCursor ; if ( hasval ( newMouseCursor ) ) break ; } if ( ! hasval ( newMouseCursor ) ) { newMouseCursor = 'default' ; } if ( newMouseCursor !== currentMouseCursor ) { element . style . cursor = newMouseCursor ; currentMouseCursor = newMouseCursor ; } } refreshMouseCursor ( ) ; contentPane . mouseCursorChanged . on ( refreshMouseCursor ) ; element . addEventListener ( 'mousedown' , function ( ev ) { var press_PMILLIS = ( new Date ( ) ) . getTime ( ) ; var i = iMouse ( element , ev ) ; var j = jMouse ( element , ev ) ; if ( press_PMILLIS - prevPress_PMILLIS < multiPressTimeout_MILLIS ) { clickCount ++ ; } else { clickCount = 1 ; } prevPress_PMILLIS = press_PMILLIS ; var newPanes = contentPane . panesAt ( i , j ) ; detectEntersAndExits ( currentPanes , newPanes , i , j , ev ) ; currentPanes = newPanes ; for ( var n = 0 ; n < currentPanes . length ; n ++ ) { currentPanes [ n ] . fireMouseDown ( i , j , clickCount , ev ) ; } refreshMouseCursor ( ) ; dragging = true ; ev . preventDefault ( ) ; } ) ; element . addEventListener ( 'mousemove' , function ( ev ) { if ( ! dragging ) { var i = iMouse ( element , ev ) ; var j = jMouse ( element , ev ) ; var newPanes = contentPane . panesAt ( i , j ) ; detectEntersAndExits ( currentPanes , newPanes , i , j , ev ) ; currentPanes = newPanes ; for ( var n = 0 ; n < currentPanes . length ; n ++ ) { currentPanes [ n ] . fireMouseMove ( i , j , ev ) ; } refreshMouseCursor ( ) ; } } ) ; window . addEventListener ( 'mousemove' , function ( ev ) { if ( dragging ) { var i = iMouse ( element , ev ) ; var j = jMouse ( element , ev ) ; for ( var n = 0 ; n < currentPanes . length ; n ++ ) { currentPanes [ n ] . fireMouseMove ( i , j , ev ) ; } } } ) ; element . addEventListener ( 'mouseout' , function ( ev ) { var i = iMouse ( element , ev ) ; var j = jMouse ( element , ev ) ; if ( dragging ) { for ( var n = 0 ; n < currentPanes . length ; n ++ ) { currentPanes [ n ] . fireMouseMove ( i , j , ev ) ; } pendingExit = true ; } else { detectEntersAndExits ( currentPanes , [ ] , i , j , ev ) ; currentPanes = [ ] ; refreshMouseCursor ( ) ; } } ) ; element . addEventListener ( 'mouseover' , function ( ev ) { var i = iMouse ( element , ev ) ; var j = jMouse ( element , ev ) ; if ( dragging ) { pendingExit = false ; } else { var newPanes = contentPane . panesAt ( i , j ) ; detectEntersAndExits ( currentPanes , newPanes , i , j , ev ) ; currentPanes = newPanes ; for ( var n = 0 ; n < currentPanes . length ; n ++ ) { currentPanes [ n ] . fireMouseMove ( i , j , ev ) ; } refreshMouseCursor ( ) ; } } ) ; var endDrag = function ( ev ) { var i = iMouse ( element , ev ) ; var j = jMouse ( element , ev ) ; for ( var n = 0 ; n < currentPanes . length ; n ++ ) { currentPanes [ n ] . fireMouseUp ( i , j , clickCount , ev ) ; } dragging = false ; if ( pendingExit ) { detectEntersAndExits ( currentPanes , [ ] , i , j , ev ) ; currentPanes = [ ] ; pendingExit = false ; refreshMouseCursor ( ) ; } else { var newPanes = contentPane . panesAt ( i , j ) ; detectEntersAndExits ( currentPanes , newPanes , i , j , ev ) ; currentPanes = newPanes ; for ( var n = 0 ; n < currentPanes . length ; n ++ ) { currentPanes [ n ] . fireMouseMove ( i , j , ev ) ; } refreshMouseCursor ( ) ; } } ; window . addEventListener ( 'mouseup' , function ( ev ) { if ( dragging ) { endDrag ( ev ) ; } } ) ; var recentDrag = null ; var handleMissedMouseUp = function ( ev ) { if ( dragging ) { if ( ! isMouseDown ( ev ) && recentDrag ) { var mouseUp = < MouseEvent > document . createEvent ( 'MouseEvents' ) ; mouseUp . initMouseEvent ( 'mouseup' , true , true , window , 0 , recentDrag . screenX , recentDrag . screenY , ev . screenX - window . screenX , ev . screenY - window . screenY , recentDrag . ctrlKey , recentDrag . altKey , recentDrag . shiftKey , recentDrag . metaKey , 0 , null ) ; endDrag ( mouseUp ) ; } recentDrag = ev ; } } ; window . addEventListener ( 'mousemove' , handleMissedMouseUp ) ; var w = window ; while ( w . parent !== w ) { try { w . parent . addEventListener ( 'mousemove' , handleMissedMouseUp ) ; w = w . parent ; } catch ( e ) { w . addEventListener ( 'mouseout' , function ( ev ) { if ( dragging ) { var mouseUp = < MouseEvent > document . createEvent ( 'MouseEvents' ) ; mouseUp . initMouseEvent ( 'mouseup' , true , true , window , 0 , ev . screenX , ev . screenY , ev . screenX - window . screenX , ev . screenY - window . screenY , ev . ctrlKey , ev . altKey , ev . shiftKey , ev . metaKey , 0 , null ) ; endDrag ( mouseUp ) ; } } ) ; break ; } } var handleMouseWheel = function ( ev ) { var i = iMouse ( element , ev ) ; var j = jMouse ( element , ev ) ; if ( dragging ) { for ( var n = 0 ; n < currentPanes . length ; n ++ ) { currentPanes [ n ] . fireMouseMove ( i , j , ev ) ; } } else { var newPanes = contentPane . panesAt ( i , j ) ; detectEntersAndExits ( currentPanes , newPanes , i , j , ev ) ; currentPanes = newPanes ; for ( var n = 0 ; n < currentPanes . length ; n ++ ) { currentPanes [ n ] . fireMouseMove ( i , j , ev ) ; } } var wheelSteps = mouseWheelSteps ( ev ) ; for ( var n = 0 ; n < currentPanes . length ; n ++ ) { currentPanes [ n ] . fireMouseWheel ( i , j , wheelSteps , ev ) ; } } ; element . addEventListener ( 'mousewheel' , handleMouseWheel ) ; element . addEventListener ( 'DOMMouseScroll' , handleMouseWheel , false ) ; element . addEventListener ( 'contextmenu' , function ( ev ) { var i = iMouse ( element , ev ) ; var j = jMouse ( element , ev ) ; if ( dragging ) { for ( var n = 0 ; n < currentPanes . length ; n ++ ) { currentPanes [ n ] . fireMouseMove ( i , j , ev ) ; } } else { var newPanes = contentPane . panesAt ( i , j ) ; detectEntersAndExits ( currentPanes , newPanes , i , j , ev ) ; currentPanes = newPanes ; for ( var n = 0 ; n < currentPanes . length ; n ++ ) { currentPanes [ n ] . fireMouseMove ( i , j , ev ) ; } refreshMouseCursor ( ) ; } for ( var n = 0 ; n < currentPanes . length ; n ++ ) { currentPanes [ n ] . fireContextMenu ( i , j , ev ) ; } ev . preventDefault ( ) ; } ) ; } export interface Drawable { setContentPane ( pane ) ; redraw ( ) ; getPrefSize ( ) : Size ; prefSizeChanged ( ) : Notification1 < Size > ; } export function newDrawable ( canvas ) { var contentPane = null ; var contentPrefSizeNotification = new Notification1 < Size > ( ) ; var contentPrefSize = { w : null , h : null } ; var contentViewport = newBoundsFromRect ( 0 , 0 , 0 , 0 ) ; var gl = requireGL ( canvas ) ; var pendingRequestId = < number > null ; return { setContentPane : function ( pane ) { if ( hasval ( contentPane ) ) { } contentPane = pane ; attachEventListeners ( canvas , contentPane ) ; } , redraw : function ( ) { if ( ! hasval ( pendingRequestId ) ) { pendingRequestId = window . requestAnimationFrame ( function ( ) { if ( hasval ( contentPane ) ) { var oldPrefSize = { w : contentPrefSize . w , h : contentPrefSize . h } ; contentPane . updatePrefSizes ( contentPrefSize ) ; contentViewport . setRect ( 0 , 0 , gl . drawingBufferWidth , gl . drawingBufferHeight ) ; contentPane . updateBounds ( contentViewport . unmod , contentViewport . unmod ) ; contentPane . paint ( gl ) ; } pendingRequestId = null ; if ( oldPrefSize . w !== contentPrefSize . w || oldPrefSize . h !== contentPrefSize . h ) { contentPrefSizeNotification . fire ( { w : contentPrefSize . w , h : contentPrefSize . h } ) ; } } ) ; } } , getPrefSize : function ( ) { return { w : contentPrefSize . w , h : contentPrefSize . h } ; } , prefSizeChanged : function ( ) : Notification1 < Size > { return contentPrefSizeNotification ; } } ; } }