Browse Source

Added new mojo module.

Mark Sibly 9 years ago
parent
commit
2af32a1d57
51 changed files with 6738 additions and 0 deletions
  1. 581 0
      modules/mojo/app/app.monkey2
  2. BIN
      modules/mojo/app/assets/DejaVuSansMono.ttf
  3. BIN
      modules/mojo/app/assets/Inconsolata-g.ttf
  4. BIN
      modules/mojo/app/assets/Roboto-Regular.ttf
  5. BIN
      modules/mojo/app/assets/RobotoMono-Regular.ttf
  6. BIN
      modules/mojo/app/assets/checkmark_icons.png
  7. 327 0
      modules/mojo/app/assets/htmlview_master_css.css
  8. BIN
      modules/mojo/app/assets/monkey_font.png
  9. BIN
      modules/mojo/app/assets/treenode_collapsed.png
  10. BIN
      modules/mojo/app/assets/treenode_expanded.png
  11. 204 0
      modules/mojo/app/event.monkey2
  12. 199 0
      modules/mojo/app/keycodes.monkey2
  13. 512 0
      modules/mojo/app/native/process.cpp
  14. 40 0
      modules/mojo/app/native/process.h
  15. 213 0
      modules/mojo/app/native/requesters.cpp
  16. 20 0
      modules/mojo/app/native/requesters.h
  17. 203 0
      modules/mojo/app/native/requesters.mm
  18. 38 0
      modules/mojo/app/process.monkey2
  19. 54 0
      modules/mojo/app/requesters.monkey2
  20. 117 0
      modules/mojo/app/skin.monkey2
  21. 218 0
      modules/mojo/app/style.monkey2
  22. 782 0
      modules/mojo/app/view.monkey2
  23. 291 0
      modules/mojo/app/window.monkey2
  24. BIN
      modules/mojo/bananas/mojotest/assets/RedbrushAlpha.png
  25. 98 0
      modules/mojo/bananas/mojotest/mojotest.monkey2
  26. BIN
      modules/mojo/bananas/rendertoimage/assets/spaceship.png
  27. 58 0
      modules/mojo/bananas/rendertoimage/rendertoimage.monkey2
  28. BIN
      modules/mojo/bananas/spacechimps/assets/spaceship.png
  29. 112 0
      modules/mojo/bananas/spacechimps/spacechimps.monkey2
  30. 196 0
      modules/mojo/bananas/viewlayout/viewlayout.monkey2
  31. BIN
      modules/mojo/graphics/assets/RobotoMono-Regular.ttf
  32. BIN
      modules/mojo/graphics/assets/monkey2-logo-63.png
  33. 31 0
      modules/mojo/graphics/assets/shader_font.glsl
  34. 28 0
      modules/mojo/graphics/assets/shader_matte.glsl
  35. 25 0
      modules/mojo/graphics/assets/shader_null.glsl
  36. 33 0
      modules/mojo/graphics/assets/shader_phong.glsl
  37. 31 0
      modules/mojo/graphics/assets/shader_sprite.glsl
  38. 38 0
      modules/mojo/graphics/assets/shaderenv_ambient.glsl
  39. 37 0
      modules/mojo/graphics/assets/shaderenv_lighting.glsl
  40. 704 0
      modules/mojo/graphics/canvas.monkey2
  41. 299 0
      modules/mojo/graphics/device.monkey2
  42. 106 0
      modules/mojo/graphics/font.monkey2
  43. 103 0
      modules/mojo/graphics/fontloader_freetype.monkey2
  44. 57 0
      modules/mojo/graphics/fontloader_stb.monkey2
  45. 93 0
      modules/mojo/graphics/glutil.monkey2
  46. 214 0
      modules/mojo/graphics/image.monkey2
  47. 48 0
      modules/mojo/graphics/material.monkey2
  48. 333 0
      modules/mojo/graphics/shader.monkey2
  49. 202 0
      modules/mojo/graphics/texture.monkey2
  50. 57 0
      modules/mojo/graphics/vertex.monkey2
  51. 36 0
      modules/mojo/mojo.monkey2

+ 581 - 0
modules/mojo/app/app.monkey2

@@ -0,0 +1,581 @@
+
+Namespace mojo.app
+
+#Import "assets/Roboto-Regular.ttf@/mojo"
+#Import "assets/RobotoMono-Regular.ttf@/mojo"
+
+Global App:AppInstance
+
+Class AppInstance
+	
+	#rem monkeydoc Idle signal.
+	
+	Invoked when the app becomes idle.
+	
+	This is reset to null after being invoked.
+
+	#end
+	Field Idle:Void()
+	
+	#rem monkeydoc @hidden
+	#end
+	Field NextIdle:Void()	
+	
+	#rem monkeydoc Key event filter.
+	
+	Functions should check if the event has already been 'eaten' by checking the event's [[Event.Eaten]] property before processing the event.
+	
+	#end
+	Field KeyEventFilter:Void( event:KeyEvent )
+
+	#rem monkeydoc MouseEvent filter.
+
+	Functions should check if the event has already been 'eaten' by checking the event's [[Event.Eaten]] property before processing the event.
+	
+	#end	
+	Field MouseEventFilter:Void( event:MouseEvent )
+
+	#rem monkeydoc Create a new app instance.
+	#end
+	Method New()
+	
+		App=Self
+	
+		SDL_Init( SDL_INIT_EVERYTHING )
+
+		_glWindow=SDL_CreateWindow( "",0,0,0,0,SDL_WINDOW_HIDDEN|SDL_WINDOW_OPENGL )
+
+		_glContext=SDL_GL_CreateContext( _glWindow )
+
+		SDL_GL_MakeCurrent( _glWindow,_glContext )
+		
+		SDL_GL_SetAttribute( SDL_GL_SHARE_WITH_CURRENT_CONTEXT,1 )
+		
+		_keyMatrix=SDL_GetKeyboardState( Varptr _numKeys )
+			
+		_defaultFont=Font.Open( DefaultFontName,16 )
+		
+		_defaultMonoFont=Font.Open( DefaultMonoFontName,16 )
+		
+		Local style:=Style.GetStyle( "" )
+		style.DefaultFont=_defaultFont
+		style.DefaultColor=Color.White
+	End
+
+	#rem monkeydoc @hidden
+	#end
+	Property DefaultFontName:String()
+		Return "asset::mojo/Roboto-Regular.ttf"
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property DefaultMonoFontName:String()
+		Return "asset::mojo/RobotoMono-Regular.ttf"
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property DefaultFont:Font()
+		Return _defaultFont
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property DefaultMonoFont:Font()
+		Return _defaultMonoFont
+	End
+	
+	#rem monkeydoc True if clipboard text is empty.
+	
+	This is faster than checking whether [[ClipboardText]] returns an empty string.
+	
+	#end
+	Property ClipboardTextEmpty:Bool()
+	
+		Return SDL_HasClipboardText()=SDL_FALSE
+	End
+	
+	#rem monkeydoc Clipboard text.
+	#end
+	Property ClipboardText:String()
+	
+		If SDL_HasClipboardText()=SDL_FALSE Return ""
+	
+		Local p:=SDL_GetClipboardText()
+		
+		Local str:=String.FromUtf8String( p )
+		
+		SDL_free( p )
+		
+		Return str
+		
+	Setter( text:String )
+	
+		SDL_SetClipboardText( text )
+	End
+	
+	#rem monkeydoc The current key view.
+	
+	The key view is the view key events are sent to.
+
+	#end
+	Property KeyView:View()
+	
+		Local window:=ActiveWindow
+		If window Return window.KeyView
+		
+		Return Null
+		
+	Setter( keyView:View )
+
+		Local window:=ActiveWindow
+		If window window.KeyView=keyView
+
+	End
+	
+	#rem monkeydoc The current mouse view.
+	
+	The mouse view is the view that the mouse is currently 'dragging'.
+	
+	#end
+	Property MouseView:View()
+	
+		Return _mouseView
+	End
+	
+	#rem monkeydoc The current hover view.
+	
+	The hover view is the view that the mouse is currently 'hovering' over.
+	
+	#end
+	Property HoverView:View()
+	
+		Return _hoverView
+	End
+
+	#rem monkeydoc The desktop size
+	#end	
+	Property DesktopSize:Vec2i()
+	
+		Local dm:SDL_DisplayMode
+		
+		If SDL_GetDesktopDisplayMode( 0,Varptr dm ) Return New Vec2i
+		
+		Return New Vec2i( dm.w,dm.h )
+	End
+
+	#rem monkeydoc The current active window.
+	
+	The active window is the window that has input focus.
+	
+	#end
+	Property ActiveWindow:Window()
+	
+		Return Window.VisibleWindows()[0]
+	End
+	
+	#rem monkeydoc Gets state of a key.
+	#end
+	Method KeyDown:Bool( key:Key )
+	
+		Local n:=Int( key )
+		If n>=0 And n<_numKeys Return _keyMatrix[n]
+		Return False
+	End
+	
+	#rem monkeydoc Mouse location relative to the active window.
+	
+	@see [[ActiveWindow]], [[MouseX]], [[MouseY]]
+	
+	#end	
+	Property MouseLocation:Vec2i()
+
+		Return _mouseLocation
+	End
+	
+	#rem monkeydoc X coordinate of mouse location.
+	
+	@see [[MouseLocation]]
+	
+	#end
+	Property MouseX:Int()
+	
+		Return _mouseLocation.x
+	End
+	
+	#rem monkeydoc Y coordinate of mouse location.
+
+	@see [[MouseLocation]]
+	
+	#end
+	Property MouseY:Int()
+	
+		Return _mouseLocation.y
+	End
+	
+	#rem monkeydoc Terminate the app.
+	#end
+	Method Terminate()
+
+		libc.exit_( 0 )
+	End
+	
+	#rem monkeydoc Request that the app render itself.
+	#end
+	Method RequestRender()
+
+		_requestRender=True
+	End
+
+	#rem monkeydoc Run the app.
+	#end
+	Method Run()
+	
+		SDL_AddEventWatch( _EventFilter,Null )
+	
+		RequestRender()
+	
+		Repeat
+		
+			If Not _requestRender 
+			
+				SDL_WaitEvent( Null )
+				
+			Endif
+		
+			UpdateEvents()
+		
+			If _requestRender
+
+				_requestRender=False
+				
+				For Local window:=Eachin Window.VisibleWindows()
+					window.Render()
+				Next
+				
+			Endif
+			
+		Forever
+	
+	End
+
+	Private
+	
+	Field _glWindow:SDL_Window Ptr
+	Field _glContext:SDL_GLContext
+
+	Field _defaultFont:Font
+	Field _defaultMonoFont:Font
+		
+	Field _requestRender:Bool
+	
+	Field _hoverView:View
+	Field _mouseView:View
+	
+	Field _numKeys:Int
+	Field _keyMatrix:UByte Ptr
+	
+	Field _window:Window
+	Field _key:Key
+	Field _scanCode:ScanCode
+	Field _modifiers:Modifier
+	Field _keyText:String
+	Field _mouseButton:MouseButton
+	Field _mouseLocation:Vec2i
+	Field _mouseWheel:Vec2i
+	
+	Field _polling:Bool
+	
+	Global _nextCallbackId:Int
+	Global _asyncCallbacks:=New IntMap<Void()>
+	
+	Method UpdateEvents()
+	
+		Local event:SDL_Event
+
+		_polling=True
+			
+		While SDL_PollEvent( Varptr event )
+		
+			DispatchEvent( Varptr event )
+			
+		Wend
+		
+		_polling=False
+		
+		Local idle:=Idle
+		Idle=NextIdle
+		NextIdle=Null
+		idle()
+		
+		For Local window:=Eachin Window.VisibleWindows()
+			window.Update()
+		Next
+
+	End
+	
+	Method SendKeyEvent( type:EventType )
+	
+		Local view:=KeyView
+		If view And Not view.ReallyEnabled view=Null
+	
+		Local event:=New KeyEvent( type,view,_key,_scanCode,_modifiers,_keyText )
+		
+		KeyEventFilter( event )
+		
+		If event.Eaten Return
+		
+		If view 
+			view.SendKeyEvent( event )
+		Else If ActiveWindow
+			ActiveWindow.SendKeyEvent( event )
+		Endif
+	End
+	
+	Method SendMouseEvent( type:EventType,view:View )
+	
+		Local location:=view.TransformWindowPointToView( _mouseLocation )
+		
+		Local event:=New MouseEvent( type,view,location,_mouseButton,_mouseWheel,_modifiers )
+		
+		MouseEventFilter( event )
+		
+		If event.Eaten Return
+		
+		view.SendMouseEvent( event )
+	End
+	
+	Method SendWindowEvent( type:EventType,window:Window )
+	
+		Local shape:Recti
+		
+		Local x:Int,y:Int,w:Int,h:Int
+		SDL_GetWindowPosition( window.NativeWindow,Varptr x,Varptr y )
+		SDL_GetWindowSize( window.NativeWindow,Varptr w,Varptr h )
+		
+		Local event:=New WindowEvent( type,window,New Recti( x,y,x+w,y+h ) )
+		
+		window.SendWindowEvent( event )
+	End
+	
+	Method DispatchEvent( event:SDL_Event Ptr )
+
+		Select event->type
+		
+		Case SDL_KEYDOWN
+		
+			Local t:=Cast<SDL_KeyboardEvent Ptr>( event )
+			
+			_window=Window.WindowForID( t[0].windowID )
+			_key=Cast<Key>( Int( SDL_GetScancodeFromKey( t[0].keysym.sym ) ) )
+			Local tchar:=String.FromChar( t[0].keysym.sym )
+			_modifiers=Cast<Modifier>( t[0].keysym.mod_ )
+			_keyText=String.FromChar( t[0].keysym.sym )
+			
+			SendKeyEvent( EventType.KeyDown )
+			
+		Case SDL_KEYUP
+
+			Local t:=Cast<SDL_KeyboardEvent Ptr>( event )
+			
+			_window=Window.WindowForID( t[0].windowID )
+			_key=Cast<Key>( Int( SDL_GetScancodeFromKey( t[0].keysym.sym ) ) )
+			_modifiers=Cast<Modifier>( t[0].keysym.mod_ )
+			_keyText=String.FromChar( t[0].keysym.sym )
+			
+			SendKeyEvent( EventType.KeyUp )
+
+		Case SDL_TEXTINPUT
+		
+			Local t:=Cast<SDL_TextInputEvent Ptr>( event )
+
+			_window=Window.WindowForID( t[0].windowID )
+			_keyText=String.FromChar( t[0].text[0] )
+			
+			SendKeyEvent( EventType.KeyChar )
+		
+		Case SDL_MOUSEBUTTONDOWN
+		
+			Local mevent:=Cast<SDL_MouseButtonEvent Ptr>( event )
+			
+			_window=Window.WindowForID( mevent->windowID )
+			_mouseLocation=New Vec2i( mevent->x,mevent->y )
+			_mouseButton=Cast<MouseButton>( mevent->button )
+			
+			If Not _mouseView
+			
+				Local view:=_window.FindViewAtWindowPoint( _mouseLocation )
+				If view
+#If __HOSTOS__<>"linux"
+					SDL_CaptureMouse( SDL_TRUE )
+#Endif
+					_mouseView=view
+				Endif
+			Endif
+				
+			If _mouseView SendMouseEvent( EventType.MouseDown,_mouseView )
+		
+		Case SDL_MOUSEBUTTONUP
+		
+			Local mevent:=Cast<SDL_MouseButtonEvent Ptr>( event )
+			
+			_window=Window.WindowForID( mevent->windowID )
+			_mouseLocation=New Vec2i( mevent->x,mevent->y )
+			_mouseButton=Cast<MouseButton>( mevent->button )
+			
+			If _mouseView
+
+				SendMouseEvent( EventType.MouseUp,_mouseView )
+#If __HOSTOS__<>"linux"				
+				SDL_CaptureMouse( SDL_FALSE )
+#Endif
+				_mouseView=Null
+
+				_mouseButton=Null
+			Endif
+			
+		Case SDL_MOUSEMOTION
+		
+			Local mevent:=Cast<SDL_MouseMotionEvent Ptr>( event )
+			
+			_window=Window.WindowForID( mevent->windowID )
+			_mouseLocation=New Vec2i( mevent->x,mevent->y )
+			
+			Local view:=_window.FindViewAtWindowPoint( _mouseLocation )
+
+			If _mouseView And view<>_mouseView view=Null
+			
+			If view<>_hoverView
+			
+				If _hoverView SendMouseEvent( EventType.MouseLeave,_hoverView )
+				
+				_hoverView=view
+				
+				If _hoverView SendMouseEvent( EventType.MouseEnter,_hoverView )
+			Endif
+			
+			If _mouseView
+
+				SendMouseEvent( EventType.MouseMove,_mouseView )
+				
+			Else If _hoverView
+
+				SendMouseEvent( EventType.MouseMove,_hoverView )
+			
+			Endif
+
+		Case SDL_MOUSEWHEEL
+		
+			Local mevent:=Cast<SDL_MouseWheelEvent Ptr>( event )
+			
+			_window=Window.WindowForID( mevent->windowID )
+			_mouseWheel=New Vec2i( mevent->x,mevent->y )
+			
+			If _mouseView
+			
+				SendMouseEvent( EventType.MouseWheel,_mouseView )
+				
+			Else If _hoverView
+
+				SendMouseEvent( EventType.MouseWheel,_hoverView )
+			
+			Endif
+			
+		Case SDL_WINDOWEVENT
+		
+			Local wevent:=Cast<SDL_WindowEvent Ptr>( event )
+			Local window:=Window.WindowForID( wevent->windowID )
+			
+			Select wevent->event
+					
+			Case SDL_WINDOWEVENT_CLOSE
+			
+				SendWindowEvent( EventType.WindowClose,window )
+			
+			Case SDL_WINDOWEVENT_MOVED
+			
+				SendWindowEvent( EventType.WindowMoved,window )
+
+'			Case SDL_WINDOWEVENT_RESIZED,
+			Case SDL_WINDOWEVENT_SIZE_CHANGED
+			
+				SendWindowEvent( EventType.WindowResized,window )
+				
+			Case SDL_WINDOWEVENT_LEAVE
+			
+				If _hoverView
+					SendMouseEvent( EventType.MouseLeave,_hoverView )
+					_hoverView=Null
+				Endif
+				
+			End
+			
+		Case SDL_USEREVENT
+		
+			Local t:=Cast<SDL_UserEvent Ptr>( event )
+			
+			Local code:=t[0].code
+			Local id:=code & $3fffffff
+			
+			If code & $40000000
+				Local func:=_asyncCallbacks[id]
+				If code & $80000000 _asyncCallbacks.Remove( id )
+				func()
+			Else If code & $80000000
+				_asyncCallbacks.Remove( id )
+			Endif
+
+		End
+			
+	End
+	
+	Function _EventFilter:Int( userData:Void Ptr,event:SDL_Event Ptr )
+	
+		Return App.EventFilter( userData,event )
+	End
+	
+	Method EventFilter:Int( userData:Void Ptr,event:SDL_Event Ptr )
+	
+'		If _polling Return 1
+	
+		Select event[0].type
+		Case SDL_WINDOWEVENT
+
+			Local wevent:=Cast<SDL_WindowEvent Ptr>( event )
+			Local window:=Window.WindowForID( wevent->windowID )
+			
+			Select wevent->event
+					
+			Case SDL_WINDOWEVENT_RESIZED
+			
+				SendWindowEvent( EventType.WindowResized,window )
+				
+				If _requestRender
+				
+					_requestRender=False
+					
+					For Local window:=Eachin Window.VisibleWindows()
+						window.Update()
+						window.Render()
+					Next
+					
+				Endif
+
+				Return 0
+
+			End
+		End
+		
+		Return 1
+	End
+	
+	Function AddAsyncCallback:Int( func:Void() )
+		_nextCallbackId+=1
+		Local id:=_nextCallbackId
+		_asyncCallbacks[id]=func
+		Return id
+	End
+	
+End
+

BIN
modules/mojo/app/assets/DejaVuSansMono.ttf


BIN
modules/mojo/app/assets/Inconsolata-g.ttf


BIN
modules/mojo/app/assets/Roboto-Regular.ttf


BIN
modules/mojo/app/assets/RobotoMono-Regular.ttf


BIN
modules/mojo/app/assets/checkmark_icons.png


+ 327 - 0
modules/mojo/app/assets/htmlview_master_css.css

@@ -0,0 +1,327 @@
+html {
+    display: block;
+    height:100%;
+    width:100%;
+	position: relative;
+}
+
+head {
+    display: none
+}
+
+meta {
+    display: none
+}
+
+title {
+    display: none
+}
+
+link {
+    display: none
+}
+
+style {
+    display: none
+}
+
+script {
+    display: none
+}
+
+body {
+	display:block; 
+	margin:8px; 
+    height:100%;
+    width:100%;
+}
+
+p {
+	display:block; 
+	margin-top:1em; 
+	margin-bottom:1em;
+}
+
+b, strong {
+	display:inline; 
+	font-weight:bold;
+}
+
+i, em {
+	display:inline; 
+	font-style:italic;
+}
+
+center 
+{
+	text-align:center;
+	display:block;
+}
+
+a:link
+{
+	text-decoration: underline;
+	color: #00f;
+	cursor: pointer;
+}
+
+h1, h2, h3, h4, h5, h6, div {
+	display:block;
+}
+
+h1 {
+	font-weight:bold; 
+	margin-top:0.67em; 
+	margin-bottom:0.67em; 
+	font-size: 2em;
+}
+
+h2 {
+	font-weight:bold; 
+	margin-top:0.83em; 
+	margin-bottom:0.83em; 
+	font-size: 1.5em;
+}
+
+h3 {
+	font-weight:bold; 
+	margin-top:1em; 
+	margin-bottom:1em; 
+	font-size:1.17em;
+}
+
+h4 {
+	font-weight:bold; 
+	margin-top:1.33em; 
+	margin-bottom:1.33em
+}
+
+h5 {
+	font-weight:bold; 
+	margin-top:1.67em; 
+	margin-bottom:1.67em;
+	font-size:.83em;
+}
+
+h6 {
+	font-weight:bold; 
+	margin-top:2.33em; 
+	margin-bottom:2.33em;
+	font-size:.67em;
+} 
+
+br {
+	display:inline-block;
+}
+
+br[clear="all"]
+{
+	clear:both;
+}
+
+br[clear="left"]
+{
+	clear:left;
+}
+
+br[clear="right"]
+{
+	clear:right;
+}
+
+span {
+	display:inline
+}
+
+img {
+	display: inline-block;
+}
+
+img[align="right"]
+{
+	float: right;
+}
+
+img[align="left"]
+{
+	float: left;
+}
+
+hr {
+    display: block;
+    margin-top: 0.5em;
+    margin-bottom: 0.5em;
+    margin-left: auto;
+    margin-right: auto;
+    border-style: inset;
+    border-width: 1px
+}
+
+
+/***************** TABLES ********************/
+
+table {
+    display: table;
+    border-style: solid;
+    border-collapse: separate;
+    border-spacing: 2px;
+    border-top-color:gray;
+    border-left-color:gray;
+    border-bottom-color:black;
+    border-right-color:black;
+}
+
+tbody, tfoot, thead {
+	display:table-row-group;
+	vertical-align:middle;
+}
+
+tr {
+    display: table-row;
+    vertical-align: inherit;
+    border-color: inherit;
+}
+
+td, th {
+    display: table-cell;
+    vertical-align: inherit;
+    border-width:1px;
+    padding:1px;
+}
+
+th {
+	font-weight: bold;
+}
+
+table[border] {
+    border-style:solid;
+}
+
+table[border|=0] {
+    border-style:none;
+}
+
+table[border] td, table[border] th {
+    border-style:solid;
+    border-top-color:black;
+    border-left-color:black;
+    border-bottom-color:gray;
+    border-right-color:gray;
+}
+
+table[border|=0] td, table[border|=0] th {
+    border-style:none;
+}
+
+caption {
+	display: table-caption;
+}
+
+td[nowrap], th[nowrap] {
+	white-space:nowrap;
+}
+
+tt, code, kbd, samp {
+    font-family: monospace
+}
+
+pre, xmp, plaintext, listing {
+    display: block;
+    font-family: monospace;
+    white-space: pre;
+    margin: 1em 0
+}
+
+/***************** LISTS ********************/
+
+ul, menu, dir {
+    display: block;
+    list-style-type: disc;
+    margin-top: 1em;
+    margin-bottom: 1em;
+    margin-left: 0;
+    margin-right: 0;
+    padding-left: 40px
+}
+
+ol {
+    display: block;
+    list-style-type: decimal;
+    margin-top: 1em;
+    margin-bottom: 1em;
+    margin-left: 0;
+    margin-right: 0;
+    padding-left: 40px
+}
+
+li {
+    display: list-item;
+}
+
+ul ul, ol ul {
+    list-style-type: circle;
+}
+
+ol ol ul, ol ul ul, ul ol ul, ul ul ul {
+    list-style-type: square;
+}
+
+dd {
+    display: block;
+    margin-left: 40px;
+}
+
+dl {
+    display: block;
+    margin-top: 1em;
+    margin-bottom: 1em;
+    margin-left: 0;
+    margin-right: 0;
+}
+
+dt {
+    display: block;
+}
+
+ol ul, ul ol, ul ul, ol ol {
+    margin-top: 0;
+    margin-bottom: 0
+}
+
+blockquote {
+	display: block;
+	margin-top: 1em;
+	margin-bottom: 1em;
+	margin-left: 40px;
+	margin-left: 40px;
+}
+
+/*********** FORM ELEMENTS ************/
+
+form {
+	display: block;
+	margin-top: 0em;
+}
+
+option {
+	display: none;
+}
+
+input, textarea, keygen, select, button, isindex {
+	margin: 0em;
+	color: initial;
+	line-height: normal;
+	text-transform: none;
+	text-indent: 0;
+	text-shadow: none;
+	display: inline-block;
+}
+input[type="hidden"] {
+	display: none;
+}
+
+
+article, aside, footer, header, hgroup, nav, section 
+{
+	display: block;
+}

BIN
modules/mojo/app/assets/monkey_font.png


BIN
modules/mojo/app/assets/treenode_collapsed.png


BIN
modules/mojo/app/assets/treenode_expanded.png


+ 204 - 0
modules/mojo/app/event.monkey2

@@ -0,0 +1,204 @@
+
+Namespace mojo.app
+
+#rem monkeydoc Event types.
+
+| EventType			| Description
+|:------------------|:-----------
+| KeyDown			| Key down event.
+| KeyUp				| Key up event.
+| KeyChar			| Key char event.
+| MouseDown			| Mouse button down event.
+| MouseUp			| Mouse button up event.
+| MouseMove			| Mouse movement event.
+| MouseWheel		| Mouse wheel event.
+| MouseEnter		| Mouse enter event.
+| MouseLeave		| Mouse leave event.
+| WindowClose		| Window close clicked event.
+| WindowMoved		| Window moved event.
+| WindowResized		| Window resized event.
+
+#end
+Enum EventType
+
+	KeyDown,
+	KeyUp,
+	KeyChar,
+	
+	MouseDown,
+	MouseUp,
+	MouseMove,
+	MouseWheel,
+	MouseEnter,
+	MouseLeave,
+
+	WindowClose,
+	WindowMoved,
+	WindowResized,
+	
+	Eaten=$80000000
+End
+
+#rem monkeydoc Event class
+#end
+Class Event Abstract
+
+	#rem monkedoc The event type.
+	#end
+	Property Type:EventType()
+		Return _type
+	End
+	
+	#rem monkeydoc The event view.
+	#end
+	Property View:View()
+		Return _view
+	End
+	
+	#rem monkeydoc True if event has been eaten.
+	
+	#end
+	Property Eaten:Bool()
+		Return (_type & EventType.Eaten)<>Null
+	End
+	
+	#rem monkeydoc Eats the event.
+	#end
+	Method Eat()
+		_type|=EventType.Eaten
+	End
+	
+	Protected
+	
+	#rem monkeydoc @hidden
+	#end
+	Field _type:EventType
+
+	#rem monkeydoc @hidden
+	#end
+	Field _view:View
+	
+	#rem monkeydoc @hidden
+	#end
+	Method New( type:EventType,view:View )
+		_type=type
+		_view=view
+	End
+End
+
+#rem monkeydoc The KeyEvent class
+#end
+Class KeyEvent Extends Event
+
+	#rem monkeydoc @hidden
+	#end
+	Method New( type:EventType,view:View,key:Key,scanCode:ScanCode,modifiers:Modifier,text:String )
+		Super.New( type,view )
+		_key=key
+		_scanCode=ScanCode
+		_modifiers=modifiers
+		_text=text
+	End
+	
+	#rem monkeydoc The key involved in the event.
+	#end
+	Property Key:Key()
+		Return _key
+	End
+	
+	#rem monkeydoc The keyboard scan code of the key.
+	#end
+	Property ScanCode:ScanCode()
+		Return _scanCode
+	End
+	
+	#rem monkeydoc The modifiers at the time of the event.
+	#end
+	Property Modifiers:Modifier()
+		Return _modifiers
+	End
+	
+	#rem monkeydoc The text for [[EventType.KeyChar]] events.
+	#end
+	Property Text:String()
+		Return _text
+	End
+	
+	Private
+	
+	Field _key:Key
+	Field _scanCode:ScanCode
+	Field _modifiers:Modifier
+	Field _text:String
+	
+End
+
+#rem monkeydoc The MouseEvent class.
+#end
+Class MouseEvent Extends Event
+
+	#rem monkeydoc @hidden
+	#end
+	Method New( type:EventType,view:View,location:Vec2i,button:MouseButton,wheel:Vec2i,modifiers:Modifier )
+		Super.New( type,view )
+		_location=location
+		_button=button
+		_wheel=wheel
+		_modifiers=modifiers
+	End
+	
+	#rem monkeydoc Mouse location.
+	#end
+	Property Location:Vec2i()
+		Return _location
+	End
+	
+	#rem monkeydoc Mouse button.
+	#end
+	Property Button:MouseButton()
+		Return _button
+	End
+
+	#rem monkeydoc Mouse wheel deltas.
+	#end	
+	Property Wheel:Vec2i()
+		Return _wheel
+	End
+	
+	#rem monkeydoc Event modifiers.
+	#end
+	Property Modifiers:Modifier()
+		Return _modifiers
+	End
+	
+	Private
+	
+	Field _location:Vec2i
+	Field _button:MouseButton
+	Field _wheel:Vec2i
+	Field _modifiers:Modifier
+	
+End
+
+#rem monkeydoc The WindowEvent class.
+#end
+Class WindowEvent Extends Event
+
+	#rem monkeydoc @hidden
+	#end
+	Method New( type:EventType,view:View,rect:Recti )
+		Super.New( type,view )
+		_rect=rect
+	End
+	
+	#rem monkeydoc The window rect for [[EventType.WindowMoved]] and [[EventType.WindowResized]] events.
+	#end
+	Property Rect:Recti()
+		Return _rect
+	End
+	
+	Private
+	
+	Field _rect:Recti
+	
+End

+ 199 - 0
modules/mojo/app/keycodes.monkey2

@@ -0,0 +1,199 @@
+
+Namespace mojo.app
+
+'These are actually SDL 'scan codes', ie: what's written on US keyboard keys...
+'
+#rem monkeydoc Key codes.
+
+| Key
+|:---
+| A
+| B
+| C
+| D
+| E
+| F
+| G
+| H
+| I
+| J 
+| K
+| L
+| M
+| N
+| O
+| P
+| Q
+| R
+| S
+| T
+| U
+| V
+| W
+| X
+| Y
+| Z
+| Key0
+| Key1
+| Key2
+| Key3
+| Key4
+| Key5
+| Key6
+| Key7
+| Key8
+| Key9
+| Enter
+| Escape
+| Backspace
+| Tab
+| Space
+| Minus
+| Equals
+| LeftBracket
+| RightBracket
+| Backslash
+| Semicolon
+| Apostrophe
+| Grave
+| Comma
+| Period
+| Slash
+| CapsLock
+| F1
+| F2
+| F3
+| F4
+| F5
+| F6
+| F7
+| F8
+| F9
+| F10
+| F11
+| F12
+| PrintScreem
+| ScrollLock
+| Pause
+| Insert
+| Home
+| PageUp
+| KeyDelete
+| KeyEnd
+| PageDown
+| Right
+| Left
+| Down
+| Up
+| LeftControl
+| LeftShift
+| LeftAlt
+| LeftGui
+| RightControl
+| RightShift
+| RightAlt
+| RightGui
+
+#end
+Enum Key
+
+	None=0
+
+	A=4,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z
+	
+	Key1=30,Key2,Key3,Key4,Key5,Key6,Key7,Key8,Key9,Key0
+	
+	Enter=40,Escape,Backspace,Tab,Space
+	
+	Minus=45,Equals,LeftBracket,RightBracket,Blackslash
+	
+	Semicolon=51,Apostrophe,Grave,Comma,Period,Slash
+	
+	CapsLock=57
+	
+	F1=58,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12
+	
+	PrintScreen=70,ScrollLock,Pause,Insert
+	
+	Home=74,PageUp,KeyDelete,KeyEnd,PageDown,Right,Left,Down,Up
+
+	LeftControl=224,LeftShift,LeftAlt,LeftGui
+	
+	RightControl=228,RightShift,RightAlt,RightGui
+
+End
+
+#rem monkeydoc @hidden
+#end
+Enum ScanCode
+End
+
+#rem monkeydoc Modifiers.
+
+| Modifier 		| Description 
+|:--------------|:-----------
+| LeftShift		| Left shift key.
+| RightShift	| Right shift key.
+| LeftControl	| Left control key.
+| RightControl	| Right control key.
+| LeftAlt		| Left alt key.
+| RightAlt		| Right alt key.
+| LeftGui		| Left gui key.
+| RightGui		| Right gui key.
+| NumLock		| Num lock key.
+| CapsLock		| Caps lock key.
+| Shift			| LeftShit | RightShift mask.
+| Control		| LeftControl | RightControl mask.
+| Alt			| LeftAlt | RightAlt mask.
+| Gui			| LeftGui | RightGui mask.
+
+#end
+Enum Modifier
+
+	None=			$0000
+	LeftShift=		$0001
+	RightShift=		$0002
+	LeftControl=	$0040
+	RightControl=	$0080
+	LeftAlt=		$0100
+	RightAlt=		$0200
+	LeftGui=		$0400
+	RightGui=		$0800
+	NumLock=		$1000
+	CapsLock=		$2000
+	
+	Shift=			LeftShift|RightShift
+	Control=		LeftControl|RightControl
+	Alt=			LeftAlt|RightAlt
+	Gui=			LeftGui|RightGui
+End
+
+#rem monkeydoc Mouse buttons.
+
+| MouseButton	| Description
+|:--------------|------------
+| Left			| Left mouse button.
+| Middle		| Middle mouse button.
+| Right			| Right mouse button.
+
+#end
+Enum MouseButton
+	None=0
+	Left=1
+	Middle=2
+	Right=3
+	X1=4
+	X2=5
+End
+
+#rem monkeydoc Gets the name of a key.
+#end
+Function KeyName:String( key:Key )
+	Local ikey:=Int( key )
+
+	If ikey>=Int( Key.A ) And ikey<=Int( Key.Z ) Return String.FromChar( ikey-Int( Key.A )+65 )
+
+	If ikey>=Int( Key.F1 ) And ikey<=Int( Key.F12 ) Return "F"+( ikey-Int( Key.F1 )+1 )
+	
+	Return "?"
+End

+ 512 - 0
modules/mojo/app/native/process.cpp

@@ -0,0 +1,512 @@
+
+#include "process.h"
+
+#ifndef EMSCRIPTEN
+
+#include <thread>
+#include <atomic>
+#include <mutex>
+#include <condition_variable>
+
+#include <SDL.h>
+
+bbInt g_mojo_app_AppInstance_AddAsyncCallback(bbFunction<void()> l_func);
+
+struct semaphore{
+
+	int count=0;
+	std::mutex mutex;
+	std::condition_variable cond_var;
+	
+	void wait(){
+		std::unique_lock<std::mutex> lock( mutex );
+		while( !count ) cond_var.wait( lock );
+		--count;
+	}
+	
+	void signal(){
+		std::unique_lock<std::mutex> lock( mutex );
+		++count;
+		cond_var.notify_one();
+	}
+};
+
+#if _WIN32
+
+#include <windows.h>
+#include <tlhelp32.h>
+
+#else
+
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+#endif
+
+namespace{
+
+	const int INVOKE=0x40000000;
+	const int REMOVE=0x80000000;
+
+	void postEvent( int code ){
+		SDL_UserEvent event;
+		event.type=SDL_USEREVENT;
+		event.code=code;
+		event.data1=0;
+		event.data2=0;
+		if( SDL_PeepEvents( (SDL_Event*)&event,1,SDL_ADDEVENT,SDL_FIRSTEVENT,SDL_LASTEVENT )!=1 ){
+			printf(" SDL_PeepEvents error!\n" );fflush( stdout );
+		}
+	}
+	
+#if _WIN32
+
+	void terminateChildren( DWORD procid,HANDLE snapshot,int exitCode ){
+	
+		PROCESSENTRY32 procinfo;
+			
+		procinfo.dwSize=sizeof( procinfo );
+		
+		int gotinfo=Process32First( snapshot,&procinfo );
+			
+		while( gotinfo ){
+		
+			if( procinfo.th32ParentProcessID==procid ){
+			
+//				printf("process=%i parent=%i module=%x path=%s\n",procinfo.th32ProcessID,procinfo.th32ParentProcessID,procinfo.th32ModuleID,procinfo.szExeFile);
+
+				terminateChildren( procinfo.th32ProcessID,snapshot,exitCode );
+				 
+				HANDLE child=OpenProcess( PROCESS_ALL_ACCESS,0,procinfo.th32ProcessID );
+				
+				if( child ){
+					int res=TerminateProcess( child,exitCode );
+					CloseHandle( child );
+				}
+			}
+			
+			gotinfo=Process32Next( snapshot,&procinfo );
+		}	
+	}
+	
+	int TerminateProcessGroup( HANDLE prochandle,int exitCode ){
+
+		HANDLE snapshot;
+		
+		int procid=GetProcessId( prochandle );
+		
+		snapshot=CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS,0 );
+		
+		if( snapshot!=INVALID_HANDLE_VALUE ){
+		
+			terminateChildren( GetProcessId( prochandle ),snapshot,exitCode );
+
+			CloseHandle( snapshot );
+		}
+			
+		int res=TerminateProcess( prochandle,exitCode );
+		return res;
+	}
+	
+#endif	
+
+#ifndef _WIN32
+
+	char **makeargv( const char *cmd ){
+	    int n,c;
+	    char *p;
+	    static char *args,**argv;
+	
+	    if( args ) free( args );
+	    if( argv ) free( argv );
+	    args=(char*)malloc( strlen(cmd)+1 );
+	    strcpy( args,cmd );
+	
+	    n=0;
+	    p=args;
+	    while( (c=*p++) ){
+	        if( c==' ' ){
+	            continue;
+	        }else if( c=='\"' ){
+	            while( *p && *p!='\"' ) ++p;
+	        }else{
+	            while( *p && *p!=' ' ) ++p;
+	        }
+	        if( *p ) ++p;
+	        ++n;
+	    }
+	    argv=(char**)malloc( (n+1)*sizeof(char*) );
+	    n=0;
+	    p=args;
+	    while( (c=*p++) ){
+	        if( c==' ' ){
+	            continue;
+	        }else if( c=='\"' ){
+	            argv[n]=p;
+	            while( *p && *p!='\"' ) ++p;
+	        }else{
+	            argv[n]=p-1;
+	            while( *p && *p!=' ' ) ++p;
+	        }
+	        if( *p ) *p++=0;
+	        ++n;
+	    }
+	    argv[n]=0;
+	    return argv;
+	}
+	
+#endif
+
+}
+
+struct bbProcess::Rep{
+
+	std::atomic_int refs;
+	
+	semaphore stdoutSema;
+	char stdoutBuf[4096];
+	char *stdoutGet;
+	int stdoutAvail=0;
+	bool terminated=false;
+	int exit;
+
+#if _WIN32
+
+	HANDLE proc;
+	HANDLE in;
+	HANDLE out;
+	HANDLE err;
+	
+	Rep( HANDLE proc,HANDLE in,HANDLE out,HANDLE err ):proc( proc ),in( in ),out( out ),err( err ),exit( -1 ),refs( 1 ){
+	}
+	
+	void close(){
+		CloseHandle( in );
+		CloseHandle( out );
+		CloseHandle( err );
+	}
+
+#else
+
+	int proc;
+	int in;
+	int out;
+	int err;
+
+	Rep( int proc,int in,int out,int err ):proc( proc ),in( in ),out( out ),err( err ),exit( -1 ),refs( 1 ){
+	}
+	
+	void close(){
+		::close( in );
+		::close( out );
+		::close( err );
+	}
+
+#endif
+	
+	void retain(){
+		++refs;
+	}
+	
+	void release(){
+		if( --refs ) return;
+		
+		close();
+		
+		delete this;
+	}
+};
+
+bbProcess::bbProcess():_rep( nullptr ){
+}
+
+bbProcess::~bbProcess(){
+
+	if( _rep ) _rep->release();
+}
+
+bbBool bbProcess::start( bbString cmd ){
+
+	if( _rep ) return false;
+	
+#if _WIN32
+
+	HANDLE in[2],out[2],err[2];
+	SECURITY_ATTRIBUTES sa={sizeof(sa),0,1};
+	CreatePipe( &in[0],&in[1],&sa,0 );
+	CreatePipe( &out[0],&out[1],&sa,0 );
+	CreatePipe( &err[0],&err[1],&sa,0 );
+
+	STARTUPINFOA si={sizeof(si)};
+	si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
+	si.hStdInput=in[0];
+	si.hStdOutput=out[1];
+	si.hStdError=err[1];
+	si.wShowWindow=SW_HIDE;
+
+	PROCESS_INFORMATION pi={0};
+    
+	DWORD flags=CREATE_NEW_PROCESS_GROUP;
+    
+	int res=CreateProcessA( 0,(LPSTR)cmd.c_str(),0,0,TRUE,flags,0,0,&si,&pi );
+
+	CloseHandle( in[0] );
+	CloseHandle( out[1] );
+	CloseHandle( err[1] );
+
+	if( !res ){
+		CloseHandle( in[1] );
+		CloseHandle( out[0] );
+		CloseHandle( err[0] );
+		return false;
+	}
+
+	CloseHandle( pi.hThread );
+	
+	Rep *rep=new Rep( pi.hProcess,in[1],out[0],err[0] );
+    
+#else
+  
+	int in[2],out[2],err[2];
+
+	pipe( in );
+	pipe( out );
+	pipe( err );
+
+	char **argv=makeargv( bbCString( cmd ) );
+	
+	bool failed=false;
+
+	int proc=vfork();
+
+	if( !proc ){
+
+#if __linux
+		setsid();
+#else
+		setpgid(0,0);
+#endif
+
+		dup2( in[0],0 );
+		dup2( out[1],1 );
+		dup2( err[1],2 );
+
+		execvp( argv[0],argv );
+		
+		failed=true;
+
+		_exit( 127 );
+	}
+	
+	if( failed ) proc=-1;
+
+	close( in[0] );
+	close( out[1] );
+	close( err[1] );
+
+	if( proc==-1 ){
+		close( in[1] );
+		close( out[0] );
+		close( err[0] );
+		return false;
+	}
+  
+	Rep *rep=new Rep( proc,in[1],out[0],err[0] );
+	
+#endif
+
+	//Create finished thread    
+    rep->retain();
+
+    int callback=g_mojo_app_AppInstance_AddAsyncCallback( finished );
+    
+    std::thread( [=](){
+    
+		#if _WIN32
+		
+	    	WaitForSingleObject( rep->proc,INFINITE );
+	    	
+	    	GetExitCodeProcess( rep->proc,(DWORD*)&rep->exit );
+	    		
+	    	CloseHandle( rep->proc );
+	    	
+		#else
+		
+			int status;
+			waitpid( rep->proc,&status,0 );
+			
+			if( WIFEXITED( status ) ){
+				rep->exit=WEXITSTATUS( status );
+			}else{
+				rep->exit=-1;
+			}
+			
+		#endif
+    		
+	    	postEvent( callback|INVOKE|REMOVE );
+    		
+    		rep->release();
+
+	} ).detach();
+	
+	
+	//Create stdoutReady thread
+	rep->retain();
+	
+	int callback2=g_mojo_app_AppInstance_AddAsyncCallback( stdoutReady );
+	
+	std::thread( [=](){
+	
+		for(;;){
+		
+#if _WIN32		
+			DWORD n=0;
+			if( !ReadFile( rep->out,rep->stdoutBuf,4096,&n,0 ) ) break;
+			if( n<=0 ) break;
+#else
+			int n=read( rep->out,rep->stdoutBuf,4096 );
+			if( n<=0 ) break;
+#endif
+			rep->stdoutGet=rep->stdoutBuf;
+			
+			rep->stdoutAvail=n;
+			
+			postEvent( callback2|INVOKE );
+			
+			rep->stdoutSema.wait();
+			
+			if( rep->stdoutAvail ) break;
+		}
+		
+		rep->stdoutAvail=0;
+		
+		postEvent( callback2|INVOKE|REMOVE );
+		
+		rep->release();
+
+	} ).detach();
+	
+	_rep=rep;
+    
+    return true;
+}
+
+int bbProcess::exitCode(){
+
+	if( !_rep ) return -1;
+
+	return _rep->exit;
+}
+
+bbInt bbProcess::stdoutAvail(){
+
+	if( !_rep ) return 0;
+
+	return _rep->stdoutAvail;
+}
+
+bbString bbProcess::readStdout(){
+
+	if( !_rep || !_rep->stdoutAvail ) return "";
+
+	bbString str=bbString::fromCString( _rep->stdoutGet,_rep->stdoutAvail );
+	
+	_rep->stdoutAvail=0;
+	
+	_rep->stdoutSema.signal();
+	
+	return str;
+}
+
+bbInt bbProcess::readStdout( void *buf,int count ){
+
+	if( !_rep || count<=0 || !_rep->stdoutAvail ) return 0;
+
+	if( count>_rep->stdoutAvail ) count=_rep->stdoutAvail;
+	
+	memcpy( buf,_rep->stdoutGet,count );
+	
+	_rep->stdoutGet+=count;
+
+	_rep->stdoutAvail-=count;
+	
+	if( !_rep->stdoutAvail ) _rep->stdoutSema.signal();
+	
+	return count;
+}
+
+void bbProcess::writeStdin( bbString str ){
+
+	if( !_rep ) return;
+
+#if _WIN32	
+	WriteFile( _rep->in,str.c_str(),str.length(),0,0 );
+#else
+	write( _rep->in,str.c_str(),str.length() );
+#endif
+}
+
+void bbProcess::sendBreak(){
+
+	if( !_rep ) return;
+	
+#if _WIN32
+	GenerateConsoleCtrlEvent( CTRL_BREAK_EVENT,GetProcessId( _rep->proc ) );
+#else
+	killpg( _rep->proc,SIGTSTP );
+#endif
+}
+
+void bbProcess::terminate(){
+
+	if( !_rep ) return;
+
+#if _WIN32
+	TerminateProcessGroup( _rep->proc,-1 );
+#else
+	killpg( _rep->proc,SIGTERM );
+#endif
+}
+
+#else
+
+//***** Dummy emscripten version *****
+
+struct bbProcess::Rep{
+};
+
+void bbProcess::discard(){
+}
+
+bbBool bbProcess::start( bbString cmd ){
+	return false;
+}
+	
+bbInt bbProcess::exitCode(){
+	return -1;
+}
+	
+bbInt bbProcess::stdoutAvail(){
+	return 0;
+}
+	
+bbString bbProcess::readStdout(){
+	return "";
+}
+
+bbInt bbProcess::readStdout( void *buf,bbInt count ){
+	return 0;
+}
+
+void bbProcess::writeStdin( bbString str ){
+}
+
+void bbProcess::sendBreak(){
+}
+
+void bbProcess::terminate(){
+}
+
+#endif

+ 40 - 0
modules/mojo/app/native/process.h

@@ -0,0 +1,40 @@
+
+#ifndef BB_STD_PROCESS_H
+#define BB_STD_PROCESS_H
+
+#include <bbmonkey.h>
+
+class bbProcess : public bbObject{
+public:
+
+	bbProcess();
+	~bbProcess();
+	
+	void discard();
+	
+	bbFunction<void()> finished;
+	bbFunction<void()> stdoutReady;
+	
+	bbBool start( bbString cmd );
+	
+	bbInt exitCode();
+	
+	bbInt stdoutAvail();
+	
+	bbString readStdout();
+	
+	bbInt readStdout( void *buf,bbInt count );
+	
+	void writeStdin( bbString str );
+	
+	void sendBreak();
+	
+	void terminate();
+	
+private:
+	struct Rep;
+	
+	Rep *_rep;
+};
+
+#endif

+ 213 - 0
modules/mojo/app/native/requesters.cpp

@@ -0,0 +1,213 @@
+
+#include "requesters.h"
+
+#if _WIN32
+
+#include <windows.h>
+#include <shlobj.h>
+
+namespace{
+
+	HWND focHwnd;
+
+	void beginPanel(){
+		focHwnd=GetFocus();
+	}
+
+	void endPanel(){
+		SetFocus( focHwnd );
+	}
+
+	int panel( bbString title,bbString text,int flags ){
+		beginPanel();
+		int n=MessageBoxW( GetActiveWindow(),bbWString( text ),bbWString( title ),flags );
+		endPanel();
+		return n;
+	}
+	
+	WCHAR *tmpWString( bbString str ){
+		WCHAR *p=(WCHAR*)malloc( str.length()*2+2 );
+		memcpy( p,str.data(),str.length()*2 );
+		p[str.length()]=0;
+		return p;
+	}
+	
+	int CALLBACK BrowseForFolderCallbackW( HWND hwnd,UINT uMsg,LPARAM lp,LPARAM pData ){
+		wchar_t szPath[MAX_PATH];
+		switch( uMsg ){
+		case BFFM_INITIALIZED:
+			SendMessageW( hwnd,BFFM_SETSELECTIONW,TRUE,pData );
+			break;
+		case BFFM_SELCHANGED: 
+			if( SHGetPathFromIDListW( (LPITEMIDLIST)lp,szPath ) ){
+				SendMessageW( hwnd,BFFM_SETSTATUSTEXTW,0,(LPARAM)szPath );
+			}
+			break;
+		}
+		return 0;
+	}
+	
+	int CALLBACK BrowseForFolderCallbackA( HWND hwnd,UINT uMsg,LPARAM lp,LPARAM pData ){
+		char szPath[MAX_PATH];
+		switch( uMsg ){
+		case BFFM_INITIALIZED:
+			SendMessageA( hwnd,BFFM_SETSELECTIONA,TRUE,pData );
+			break;
+		case BFFM_SELCHANGED: 
+			if( SHGetPathFromIDListA( (LPITEMIDLIST)lp,szPath ) ){
+				SendMessageA( hwnd,BFFM_SETSTATUSTEXTA,0,(LPARAM)szPath );
+			}
+			break;
+		}
+		return 0;
+	}
+}
+	
+void bbRequesters::Notify( bbString title,bbString text,bbBool serious ){
+	int flags=(serious ? MB_ICONWARNING : MB_ICONINFORMATION)|MB_OK|MB_APPLMODAL|MB_TOPMOST;
+	panel( title,text,flags );
+}
+
+bbBool bbRequesters::Confirm( bbString title,bbString text,bbBool serious ){
+	int flags=(serious ? MB_ICONWARNING : MB_ICONINFORMATION)|MB_OKCANCEL|MB_APPLMODAL|MB_TOPMOST;
+	int n=panel( title,text,flags );
+	if( n==IDOK ) return 1;
+	return 0;
+}
+
+int bbRequesters::Proceed( bbString title,bbString text,bbBool serious ){
+	int flags=(serious ? MB_ICONWARNING : MB_ICONINFORMATION)|MB_YESNOCANCEL|MB_APPLMODAL|MB_TOPMOST;
+	int n=panel( title,text,flags );
+	if( n==IDYES ) return 1;
+	if( n==IDNO ) return 0;
+	return -1;
+}
+
+bbString bbRequesters::RequestFile( bbString title,bbString exts,bbBool save,bbString path ){
+
+	bbString file,dir;
+	path=path.replace( "/","\\" );
+		
+	int i=path.findLast( "\\" );
+	if( i!=-1 ){
+		dir=path.slice( 0,i );
+		file=path.slice( 1+1 );
+	}else{
+		file=path;
+	}
+
+	if( file.length()>MAX_PATH ) return "";
+
+	if( exts.length() ){
+		if( exts.find( ":" )==-1 ){
+			exts=bbString( "Files\0*.",8 )+exts;
+		}else{
+			exts=exts.replace( ":",bbString( "\0*.",3 ) );
+		}
+		exts=exts.replace( ";",bbString( "\0",1 ) );
+		exts=exts.replace( ",",";*." )+bbString( "\0",1 );
+	}
+
+	WCHAR buf[MAX_PATH+1];
+	memcpy( buf,file.data(),file.length()*2 );
+	buf[file.length()]=0;
+
+	OPENFILENAMEW of={sizeof(of)};
+
+	of.hwndOwner=GetActiveWindow();
+	of.lpstrTitle=tmpWString( title );
+	of.lpstrFilter=tmpWString( exts );
+	of.lpstrFile=buf;
+	of.lpstrInitialDir=dir.length() ? tmpWString( dir ) : 0;
+	of.nMaxFile=MAX_PATH;
+	of.Flags=OFN_HIDEREADONLY|OFN_NOCHANGEDIR;
+	
+	bbString str;
+	
+	beginPanel();
+	
+	if( save ){
+		of.lpstrDefExt=L"";
+		of.Flags|=OFN_OVERWRITEPROMPT;
+		if( GetSaveFileNameW( &of ) ){
+			str=bbString( buf );
+		}
+	}else{
+		of.Flags|=OFN_FILEMUSTEXIST;
+		if( GetOpenFileNameW( &of ) ){
+			str=bbString( buf );
+		}
+	}
+	
+	endPanel();
+	
+	free( (void*)of.lpstrTitle );
+	free( (void*)of.lpstrFilter );
+	free( (void*)of.lpstrInitialDir );
+	
+	str=str.replace( "\\","/" );
+	
+	return str;
+}
+
+bbString bbRequesters::RequestDir( bbString title,bbString dir ){
+
+	CoInitialize( 0 );
+	
+	dir=dir.replace( "/","\\" );
+
+	LPMALLOC shm;
+	BROWSEINFOW bi={0};
+	
+	WCHAR buf[MAX_PATH],*p;
+	GetFullPathNameW( bbWString( dir ),MAX_PATH,buf,&p );
+	
+	bi.hwndOwner=GetActiveWindow();
+	bi.lpszTitle=tmpWString( title );
+	bi.ulFlags=BIF_RETURNONLYFSDIRS|BIF_NEWDIALOGSTYLE;
+	bi.lpfn=BrowseForFolderCallbackW;
+	bi.lParam=(LPARAM)buf;
+	
+	beginPanel();
+
+	bbString str;
+	
+	if( ITEMIDLIST *idlist=SHBrowseForFolderW( &bi ) ){
+		SHGetPathFromIDListW( idlist,buf );
+		str=bbString( buf );
+		//SHFree( idlist );	//?!?
+	}
+	
+	endPanel();
+	
+	free( (void*)bi.lpszTitle );
+
+	str=str.replace( "\\","/" );
+	if( !str.endsWith( "/" ) ) str+="/";
+
+	return str;
+}
+
+#elif __linux
+
+#include <limits.h>
+
+bbString bbRequesters::RequestFile( bbString title,bbString exts,bbBool save,bbString path ){
+
+	bbString cmd=BB_T("zenity --title=\"")+title+BB_T("\" --file-selection");
+
+	FILE *f=popen( cmd.c_str(),"r" );
+	if( !f ) return "";
+	
+	char buf[PATH_MAX];
+	int n=fread( buf,1,PATH_MAX,f );
+	pclose( f );
+	
+	if( n<0 || n>PATH_MAX ) return "";
+	
+	while( n && buf[n-1]<=32 ) --n;
+	
+	return bbString::fromCString( buf,n );
+}
+
+#endif

+ 20 - 0
modules/mojo/app/native/requesters.h

@@ -0,0 +1,20 @@
+
+#ifndef BB_REQUESTERS_H
+#define BB_REQUESTERS_H
+
+#include <bbmonkey.h>
+
+namespace bbRequesters{
+
+	void Notify( bbString title,bbString text,bbBool serious );
+
+	bbBool Confirm( bbString title,bbString text,bbBool serious );
+
+	bbInt Proceed( bbString title,bbString text,bbBool serious );
+
+	bbString RequestFile( bbString title,bbString filters,bbBool save,bbString path );
+
+	bbString RequestDir( bbString title,bbString dir );
+}
+
+#endif

+ 203 - 0
modules/mojo/app/native/requesters.mm

@@ -0,0 +1,203 @@
+
+#include "requesters.h"
+
+#import <Cocoa/Cocoa.h>
+
+namespace{
+
+	typedef int (*AlertPanel)( 
+		NSString *title,
+		NSString *msg,
+		NSString *defaultButton,
+		NSString *alternateButton,
+		NSString *otherButton );
+	
+	NSWindow *keyWin;
+	
+	void beginPanel(){
+		keyWin=[NSApp keyWindow];
+		if( !keyWin ) [NSApp activateIgnoringOtherApps:YES];
+	}
+	
+	void endPanel(){
+		if( keyWin ) [keyWin makeKeyWindow];
+	}
+	
+	NSString *ConvString( bbString str ){
+		return [NSString stringWithCharacters:(const unichar*)str.data() length:str.length()];
+	}
+	
+	bbString ConvString( NSString *str ){
+		int n=[str length];
+		unichar *buf=new unichar[ n ];
+		[str getCharacters:buf range:NSMakeRange( 0,n )];
+		bbString t=bbString( buf,n );
+		delete[] buf;
+		return t;
+	}
+}
+
+void bbRequesters::Notify( bbString title,bbString text,bbBool serious ){
+
+	AlertPanel panel=(AlertPanel) ( serious ? (void*)NSRunCriticalAlertPanel : (void*)NSRunAlertPanel );
+	
+	beginPanel();
+	
+	panel( ConvString( title ),ConvString( text ),@"OK",0,0 );
+	
+	endPanel();
+}
+
+bbBool bbRequesters::Confirm( bbString title,bbString text,bbBool serious ){
+
+	AlertPanel panel=(AlertPanel) ( serious ? (void*)NSRunCriticalAlertPanel : (void*)NSRunAlertPanel );
+	
+	beginPanel();
+	
+	int n=panel( ConvString( title ),ConvString( text ),@"OK",@"Cancel",0 );
+
+	endPanel();
+	
+	switch( n ){
+	case NSAlertDefaultReturn:return 1;
+	}
+	return 0;
+}
+
+int bbRequesters::Proceed( bbString title,bbString text,bbBool serious ){
+
+	AlertPanel panel=(AlertPanel) ( serious ? (void*)NSRunCriticalAlertPanel : (void*)NSRunAlertPanel );
+	
+	beginPanel();
+	
+	int n=panel( ConvString( title ),ConvString( text ),@"Yes",@"No",@"Cancel" );
+	
+	endPanel();
+	
+	switch( n ){
+	case NSAlertDefaultReturn:return 1;
+	case NSAlertAlternateReturn:return 0;
+	}
+	return -1;
+}
+
+bbString bbRequesters::RequestFile( bbString title,bbString filter,bbBool save,bbString path ){
+
+	bbString file,dir;
+	int i=path.findLast( "\\" );
+	if( i!=-1 ){
+		dir=path.slice( 0,i );
+		file=path.slice( 1+1 );
+	}else{
+		file=path;
+	}
+	
+	NSMutableArray *nsfilter=0;
+	bool allowOthers=true;
+
+	if( filter.length() ){
+	
+		allowOthers=false;
+	
+		nsfilter=[NSMutableArray arrayWithCapacity:10];
+		
+		int i0=0;
+		while( i0<filter.length() ){
+		
+			int i1=filter.find( ":",i0 )+1;
+			if( !i1 ) break;
+			
+			int i2=filter.find( ";",i1 );
+			if( i2==-1 ) i2=filter.length();
+			
+			while( i1<i2 ){
+			
+				int i3=filter.find( ",",i1 );
+				if( i3==-1 ) i3=i2;
+				
+				bbString ext=filter.slice( i1,i3 );
+				if( ext==BB_T("*") ){
+					allowOthers=true;
+				}else{
+					[nsfilter addObject:ConvString( ext )];
+				}
+				i1=i3+1;
+			}
+			i0=i2+1;
+		}
+	}
+
+	NSString *nsdir=0;
+	NSString *nsfile=0;
+	NSString *nstitle=0;
+	NSMutableArray *nsexts=0;
+
+	if( dir.length() ) nsdir=ConvString( dir );
+	if( file.length() ) nsfile=ConvString( file );
+	if( title.length() ) nstitle=ConvString( title );
+
+	beginPanel();
+	
+	bbString str;
+
+	if( save ){
+		NSSavePanel *panel=[NSSavePanel savePanel];
+		
+		if( nstitle ) [panel setTitle:nstitle];
+		
+		if( nsfilter ){
+			[panel setAllowedFileTypes:nsfilter];
+			[panel setAllowsOtherFileTypes:allowOthers];
+		}
+		
+		if( [panel runModalForDirectory:nsdir file:nsfile]==NSFileHandlingPanelOKButton ){
+			str=ConvString( [panel filename] );
+		}
+
+	}else{
+		NSOpenPanel *panel=[NSOpenPanel openPanel];
+
+		if( nstitle ) [panel setTitle:nstitle];
+		
+		if( allowOthers ) nsfilter=0;
+		
+		if( [panel runModalForDirectory:nsdir file:nsfile types:nsfilter]==NSFileHandlingPanelOKButton ){
+			str=ConvString( [panel filename] );
+		}
+	}
+	endPanel();
+
+	return str;
+}
+
+bbString bbRequesters::RequestDir( bbString title,bbString dir ){
+
+	NSString *nsdir=0;
+	NSString *nstitle=0;
+	NSOpenPanel *panel;
+	
+	if( dir.length() ) nsdir=ConvString( dir );
+	if( title.length() ) nstitle=ConvString( title );
+
+	panel=[NSOpenPanel openPanel];
+	
+	[panel setCanChooseFiles:NO];
+	[panel setCanChooseDirectories:YES];
+	[panel setCanCreateDirectories:YES];
+	
+	if( nstitle ) [panel setTitle:nstitle];
+
+	beginPanel();
+	
+	bbString str;
+	
+	if( [panel runModalForDirectory:nsdir file:0 types:0]==NSFileHandlingPanelOKButton ){
+	
+		str=ConvString( [panel filename] );
+	}
+
+	endPanel();
+	
+	return str;
+}
+

+ 38 - 0
modules/mojo/app/process.monkey2

@@ -0,0 +1,38 @@
+
+Namespace mojo.app
+
+#Import "native/process.h"
+#Import "native/process.cpp"
+
+Extern
+
+Class Process="bbProcess"
+
+	Field Finished:Void()="finished"
+	
+	Field StdoutReady:Void()="stdoutReady"
+	
+	Field StderrReady:Void()="stderrReady"
+
+	Property ExitCode:Int()="exitCode"
+	
+	Property StdoutAvail:Int()="stdoutAvail"
+
+	Property StderrAvail:Int()="stderrAvail"
+	
+	Method Start:Bool( cmd:String )="start"
+	
+	Method ReadStdout:String()="readStdout"
+	
+	Method ReadStdout:Int( buf:Void Ptr,count:Int )="readStdout"
+
+	Method ReadStderr:String()="readStderr"
+	
+	Method ReadStderr:Int( buf:Void Ptr,count:Int )="readStderr"
+
+	Method WriteStdin( str:String )="writeStdin"
+	
+	Method SendBreak()="sendBreak"
+	
+	Method Terminate:Void()="terminate"	
+End

+ 54 - 0
modules/mojo/app/requesters.monkey2

@@ -0,0 +1,54 @@
+
+Namespace mojo.app
+
+#If __TARGET__="desktop"
+
+#Import "native/requesters.h"
+
+#If __HOSTOS__="windows"
+#Import "native/requesters.cpp"
+#Import "<libcomdlg32.a>"
+#Endif
+
+#If __HOSTOS__="macos"
+#Import "native/requesters.mm"
+#Endif
+
+#If __HOSTOS__="linux"
+#Import "native/requesters.cpp"
+#Endif
+
+Extern
+
+Function Notify:Void( title:String,text:String,serious:Bool=False )="bbRequesters::Notify"
+
+Function Confirm:Bool( title:String,text:String,serious:Bool=False )="bbRequesters::Confirm"
+
+Function Proceed:Int( title:String,text:String,serious:Bool=False )="bbRequesters::Proceed"
+
+Function RequestFile:String( title:String,filter:String="",save:Bool=False,file:String="" )="bbRequesters::RequestFile"
+
+Function RequestDir:String( title:String,dir:String="" )="bbRequesters::RequestDir"
+
+#Else
+
+Function Notify( title:String,text:String,serious:Bool=False )
+End
+
+Function Confirm:Bool( title:String,text:String,serious:Bool=False )
+	Return False
+End
+
+Function Proceed:Int( title:String,text:String,serious:Bool=False )
+	Return -1
+End
+
+Function RequestFile:String( title:String,filter:String="",save:Bool=False,file:String="" )
+	Return ""
+End
+
+Function RequestDir:String( title:String,dir:String="" )
+	Return ""
+End
+
+#Endif

+ 117 - 0
modules/mojo/app/skin.monkey2

@@ -0,0 +1,117 @@
+
+Namespace mojo.app
+
+Class Skin
+
+	Property Image:Image()
+		Return _image
+	End
+	
+	Property Bounds:Recti()
+		Return _bounds
+	End
+
+	Method Draw( canvas:Canvas,rect:Recti )
+	
+		Local x0:=rect.Left
+		Local x1:=rect.Left+_x1
+		Local x2:=rect.Right-(_x3-_x2)
+		Local x3:=rect.Right
+		
+		Local y0:=rect.Top
+		Local y1:=rect.Top+_y1
+		Local y2:=rect.Bottom-(_y3-_y2)
+		Local y3:=rect.Bottom
+		
+		canvas.DrawRect( x0,y0,x1-x0,y1-y0,_image,_x0,_y0,_x1-_x0,_y1-_y0 )
+		canvas.DrawRect( x1,y0,x2-x1,y1-y0,_image,_x1,_y0,_x2-_x1,_y1-_y0 )
+		canvas.DrawRect( x2,y0,x3-x2,y1-y0,_image,_x2,_y0,_x3-_x2,_y1-_y0 )
+		
+		canvas.DrawRect( x0,y1,x1-x0,y2-y1,_image,_x0,_y1,_x1-_x0,_y2-_y1 )
+		canvas.DrawRect( x1,y1,x2-x1,y2-y1,_image,_x1,_y1,_x2-_x1,_y2-_y1 )
+		canvas.DrawRect( x2,y1,x3-x2,y2-y1,_image,_x2,_y1,_x3-_x2,_y2-_y1 )
+		
+		canvas.DrawRect( x0,y2,x1-x0,y3-y2,_image,_x0,_y2,_x1-_x0,_y3-_y2 )
+		canvas.DrawRect( x1,y2,x2-x1,y3-y2,_image,_x1,_y2,_x2-_x1,_y3-_y2 )
+		canvas.DrawRect( x2,y2,x3-x2,y3-y2,_image,_x2,_y2,_x3-_x2,_y3-_y2 )
+	
+	End
+
+	Function Load:Skin( path:String )
+	
+		Local pixmap:=Pixmap.Load( path )
+		If Not pixmap Return Null
+		
+		pixmap.PremultiplyAlpha()
+		
+		Return New Skin( pixmap )
+	End
+	
+	Private
+	
+	Field _image:Image
+	Field _bounds:Recti
+	Field _rect:Recti
+	
+	Field _x0:Int,_x1:Int,_x2:Int,_x3:Int
+	Field _y0:Int,_y1:Int,_y2:Int,_y3:Int
+	
+	Method New( pixmap:Pixmap )
+	
+		Local _scale:Recti
+		Local _fill:Recti
+	
+		For Local x:=1 Until pixmap.Width-1
+			Local p:=pixmap.GetPixelARGB( x,0 )
+			If p=UInt( $ff000000 )
+				If Not _scale.min.x _scale.min.x=x
+				_scale.max.x=x+1
+			Endif
+			p=pixmap.GetPixelARGB( x,pixmap.Height-1 )
+			If p=UInt( $ff000000 )
+				If Not _fill.min.x _fill.min.x=x
+				_fill.max.x=x+1
+			Endif
+		Next
+		
+		For Local y:=1 Until pixmap.Height-1
+			Local p:=pixmap.GetPixelARGB( 0,y )
+			If p=UInt( $ff000000 )
+				If Not _scale.min.y _scale.min.y=y
+				_scale.max.y=y+1
+			Endif
+			p=pixmap.GetPixelARGB( pixmap.Width-1,y )
+			If p=UInt( $ff000000 )
+				If Not _fill.min.y _fill.min.y=y
+				_fill.max.y=y+1
+			Endif
+		Next
+		
+		If _scale.min.x And _scale.min.y
+			pixmap=pixmap.Window( 1,1,pixmap.Width-2,pixmap.Height-2 )
+			If Not _fill.min.x Or Not _fill.min.y _fill=_scale
+			_scale-=New Vec2i( 1,1 )
+			_fill-=New Vec2i( 1,1 )
+		Else
+			_scale=New Recti( pixmap.Width/3,pixmap.Height/3,pixmap.Width*2/3,pixmap.Height*2/3 )
+			_fill=_scale
+		Endif
+		
+		_rect=New Recti( 0,0,pixmap.Width,pixmap.Height )
+		
+		_x0=0
+		_x1=_scale.min.x
+		_x2=_scale.max.x
+		_x3=_rect.max.x
+		
+		_y0=0
+		_y1=_scale.min.y
+		_y2=_scale.max.y
+		_y3=_rect.max.y
+		
+		_image=New Image( pixmap )
+		_bounds=New Recti( -_fill.min,_rect.max-_fill.max )
+	
+	End
+End
+

+ 218 - 0
modules/mojo/app/style.monkey2

@@ -0,0 +1,218 @@
+
+Namespace mojo.app
+
+Class Style
+
+	Method New()
+		Init( "",Null,False )
+	End
+	
+	Method New( name:String )
+		Init( name,Null,False )
+	End
+	
+	Method New( style:Style )
+		Init( "",style,True )
+	End
+	
+	Method New( name:String,style:Style )
+		Init( name,style,True )
+	End
+	
+	Method AddState:Style( state:String,srcState:String="" )
+	
+		Local style:=New Style
+		
+		style.Init( "",GetState( srcState ),False )
+		
+		_states[state]=style
+		
+		Return style
+	End
+	
+	Method GetState:Style( state:String )
+	
+		Local style:=_states[state]
+		If style Return style
+		
+		Return Self
+	End
+
+	Property BackgroundColor:Color()
+		Return _bgcolor
+	Setter( backgroundColor:Color )
+		_bgcolor=backgroundColor
+	End
+	
+	Property Padding:Recti()
+		Return _padding
+	Setter( padding:Recti )
+		_padding=padding
+	End
+	
+	Property Skin:Skin()
+		Return _skin
+	Setter( skin:Skin )
+		_skin=skin
+	End
+	
+	Property SkinColor:Color()
+		Return _skcolor
+	Setter( skinColor:Color )
+		_skcolor=skinColor
+	End
+		
+	Property Border:Recti()
+		Return _border
+	Setter( border:Recti )
+		_border=border
+	End
+	
+	Property BorderColor:Color()
+		Return _bdcolor
+	Setter( borderColor:Color )
+		_bdcolor=borderColor
+	End
+	
+	Property Margin:Recti()
+		Return _margin
+	Setter( margin:Recti )
+		_margin=margin
+	End
+	
+	Property DefaultColor:Color()
+		Return _color
+	Setter( color:Color )
+		_color=color
+	End
+	
+	Property DefaultFont:Font()
+		Return _font
+	Setter( font:Font )
+		_font=font
+	End
+	
+	Method SetImage( name:String,image:Image )
+		_images[name]=image
+	End
+	
+	Method GetImage:Image( name:String )
+		Return _images[name]
+	End
+	
+	Property Bounds:Recti()
+		Local bounds:=Padding
+		Local skin:=Skin
+		If skin bounds+=Skin.Bounds
+		bounds+=Border
+		bounds+=Margin
+		Return bounds
+	End
+	
+	Method Render( canvas:Canvas,bounds:Recti )
+	
+		canvas.BlendMode=BlendMode.Alpha
+	
+		bounds-=Margin
+		
+		Local border:=Border
+		Local bdcolor:=BorderColor
+		
+		If (border.Width Or border.Height) And bdcolor.a
+		
+			canvas.Color=bdcolor
+			
+			Local x:=bounds.X,y:=bounds.Y
+			Local w:=bounds.Width,h:=bounds.Height
+			Local l:=-border.min.x,r:=border.max.x
+			Local t:=-border.min.y,b:=border.max.y
+			
+			canvas.DrawRect( x,y,l,h-b )
+			canvas.DrawRect( x+l,y,w-l,t )
+			canvas.DrawRect( x+w-r,y+t,r,h-t )
+			canvas.DrawRect( x,y+h-b,w-r,b )
+
+		Endif
+		
+		bounds-=border
+		
+		Local bgcolor:=BackgroundColor
+		If bgcolor.a
+			canvas.Color=bgcolor
+			canvas.DrawRect( bounds.X,bounds.Y,bounds.Width,bounds.Height )
+		Endif
+		
+		Local skin:=Skin
+		Local skcolor:=SkinColor
+		
+		If skin And skcolor.a
+			canvas.Color=skcolor
+			skin.Draw( canvas,bounds )
+		Endif
+		
+		Local font:=DefaultFont
+		Local color:=DefaultColor
+		
+		canvas.Font=font
+		canvas.Color=color
+		
+	End
+	
+	Function GetStyle:Style( name:String )
+	
+		Local style:=_styles[name]
+		If style Return style
+		
+		Local i:=name.Find( ":" )
+		If i<>-1 Return GetStyle( name.Slice( 0,i ) )
+		
+		Return _defaultStyle
+	End
+
+	Private
+	
+	Global _defaultStyle:=New Style
+	Global _styles:=New StringMap<Style>
+	
+	Field _states:=New StringMap<Style>
+	
+	Field _bgcolor:Color=Color.None
+	Field _padding:Recti
+	Field _skin:Skin
+	Field _skcolor:Color=Color.White
+	Field _border:Recti
+	Field _bdcolor:Color=Color.Black
+	Field _margin:Recti
+	Field _color:Color
+	Field _font:Font
+	Field _images:=New StringMap<Image>
+	
+	Method Init( name:String,style:Style,copyStates:Bool )
+	
+		If Not style style=_defaultStyle
+		
+		If style
+			_bgcolor=style._bgcolor
+			_padding=style._padding
+			_skin=style._skin
+			_skcolor=style._skcolor
+			_border=style._border
+			_bdcolor=style._bdcolor
+			_margin=style._margin
+			_color=style._color
+			_font=style._font
+			_images=style._images.Copy()
+			
+			If copyStates
+				For Local it:=Eachin style._states
+					_states[it.Key]=New Style( it.Value )
+				Next
+			Endif
+		Endif
+		
+		If name
+			_styles[name]=Self
+		Endif
+	End
+	
+End

+ 782 - 0
modules/mojo/app/view.monkey2

@@ -0,0 +1,782 @@
+
+Namespace mojo.app
+
+Class View
+
+	Method New()
+	
+		_style=New Style
+	End
+
+	#rem monkeydoc View visible flag.
+	
+	Use [[ReallyVisible]] to test if the view is really visible.
+	
+	#end
+	Property Visible:Bool()
+	
+		Return _visible
+	
+	Setter( visible:Bool )
+		If visible=_visible Return
+	
+		_visible=visible
+	End
+
+	#rem monkeydoc View visibility state.
+	
+	True if the view's visibility flag is set AND all its parent visibility flags up to the root window are also set.
+	
+	#end
+	Property ReallyVisible:Bool()
+	
+		Return _visible And (Not _parent Or _parent.ReallyVisible)
+	End
+	
+	#rem monkeydoc View enabled flag.
+	
+	Use [[ReallyEnabled]] to test if the view is really enabled.
+	
+	#end
+	Property Enabled:Bool()
+	
+		Return _enabled
+	
+	Setter( enabled:Bool )
+		If enabled=_enabled Return
+	
+		_enabled=enabled
+		
+		InvalidateStyle()
+	End
+
+	#rem monkeydoc View enabled state.
+	
+	True if the view's enabled flag is set AND all its parent enabled flags are set AND [[ReallyVisible]] is also true. 
+	
+	#end
+	Property ReallyEnabled:Bool()
+	
+		Return _enabled And _visible And (Not _parent Or _parent.ReallyEnabled)
+	End
+	
+	#rem monkeydoc View style.
+	#end
+	Property Style:Style()
+	
+		Return _style
+		
+	Setter( style:Style )
+		If style=_style Return
+	
+		_style=style
+		
+		InvalidateStyle()
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property StyleState:String()
+	
+		Return _styleState
+	
+	Setter( styleState:String )
+		If styleState=_styleState Return
+
+		_styleState=styleState
+		
+		InvalidateStyle()
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property RenderStyle:Style()
+	
+		ValidateStyle()
+		
+		Return _rstyle
+	End
+	
+	#rem monkeydoc Layout mode.
+	
+	The following layout modes are supported
+	
+	| Layout mode		| Description
+	|:------------------|:-----------
+	| "resize"			| View is resized to fit its layout frame.
+	| "stretch"			| View is stretched to fit its layout frame.
+	| "letterbox"		| View is uniformly stretched on both axii and centered within its layout frame.
+	| "float"			| View floats within its layout frame according to the view [[Gravity]].
+	
+	#end
+	Property Layout:String()
+
+		Return _layout
+
+	Setter( layout:String )
+		If layout=_layout Return
+
+		_layout=layout
+	End
+
+	#rem monkeydoc View frame rect.
+	
+	The 'frame' the view is contained in.
+	
+	Note that the frame rect is in 'parent space' coordinates, and is usually set by the parent view when layout occurs.
+	
+	#end	
+	Property Frame:Recti()
+	
+		Return _frame
+	
+	Setter( frame:Recti )
+		If frame=_frame Return
+	
+		_frame=frame
+	End
+	
+	#rem monkeydoc Gravity for floating views.
+	
+	#end
+	Property Gravity:Vec2f()
+
+		Return _gravity
+
+	Setter( gravity:Vec2f )
+		If gravity=_gravity Return
+
+		_gravity=gravity
+	End
+
+	#rem monkeydoc @hidden
+	#end	
+	Property Offset:Vec2i()
+	
+		Return _offset
+		
+	Setter( offset:Vec2i )
+		If offset=_offset Return
+			
+		_offset=offset
+	End
+	
+	#rem monkeydoc Minimum view size.
+	#end
+	Property MinSize:Vec2i()
+	
+		Return _minSize
+	
+	Setter( minSize:Vec2i )
+	
+		_minSize=minSize
+		
+		InvalidateStyle()
+	End
+	
+	#rem monkeydoc Maximum view size.
+	#end
+	Property MaxSize:Vec2i()
+	
+		Return _maxSize
+	
+	Setter( maxSize:Vec2i )
+	
+		_maxSize=maxSize
+		
+		InvalidateStyle()
+	End
+	
+	#rem monkeydoc View content rect.
+	
+	The content rect represents the rendering area of the view.
+	
+	The content rect is in view local coordinates and its origin is always (0,0).
+	
+	#end
+	Property Rect:Recti()
+	
+		Return _rect
+	End
+	
+	#rem monkeydoc Width of the view content rect.
+	#end
+	Property Width:Int()
+
+		Return _rect.Width
+	End
+	
+	#rem monkeydoc Height of the view content rect.
+	#end
+	Property Height:Int()
+	
+		Return _rect.Height
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property Bounds:Recti()
+	
+		Return _bounds
+	End
+	
+	#rem monkeydoc View clip rect.
+	
+	The clip rect represents the part of the content rect NOT obscured by an parent views.
+	
+	The clip rect is in view local coordinates.
+	
+	#end
+	Property ClipRect:Recti()
+	
+		Return _clip
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property RenderRect:Recti()
+	
+		Return _rclip
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property RenderBounds:Recti()
+	
+		Return _rbounds
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property LocalMatrix:AffineMat3f()
+	
+		Return _matrix
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property RenderMatrix:AffineMat3f()
+	
+		Return _rmatrix
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property Parent:View()
+	
+		Return _parent
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method AddChild( view:View )
+	
+		If Not view Return
+		
+		Assert( Not view._parent )
+		
+		_children.Add( view )
+		
+		view._parent=Self
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method RemoveChild( view:View )
+	
+		If Not view Return
+		
+		Assert( view._parent=Self )
+		
+		_children.Remove( view )
+		
+		view._parent=Null
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method FindViewAtWindowPoint:View( point:Vec2i )
+	
+		If Not _visible Return Null
+	
+		If Not _rbounds.Contains( point ) Return Null
+		
+		For Local i:=0 Until _children.Length
+		
+			Local child:=_children[_children.Length-i-1]
+
+			Local view:=child.FindViewAtWindowPoint( point )
+			If view Return view
+		
+		Next
+		
+		Return Self
+	End
+	
+	#rem monkeydoc Transforms a point to another view.
+	
+	Transforms `point` in coordinates local to this view to coordinates local to `view`.
+	
+	@param point The point to transform.
+	
+	@param view View to transform point to.
+	
+	#end
+	Method TransformPointToView:Vec2i( point:Vec2i,view:View )
+	
+		Local t:=_rmatrix * New Vec2f( point.x,point.y )
+		
+		If view t=-view._rmatrix * t
+		
+		Return New Vec2i( Round( t.x ),Round( t.y ) )
+	End
+	
+	#rem monkeydoc Transforms a point from another view.
+	
+	Transforms `point` in coordinates local to 'view' to coodinates local to this view.
+	
+	@param point The point to transform.
+	
+	@param view View to transform point from.
+	
+	#end
+	Method TransformPointFromView:Vec2i( point:Vec2i,view:View )
+	
+		Local t:=New Vec2f( point.x,point.y )
+		
+		If view t=view._matrix * t
+		
+		t=-_rmatrix * t
+		
+		Return New Vec2i( Round( t.x ),Round( t.y ) )
+	End
+	
+	#rem monkeydoc Transforms a rect to another view.
+	
+	Transforms `rect` from coordinates local to this view to coordinates local to `view`.
+	
+	@param rect The rect to transform.
+
+	@param view View to transform rect to.
+	
+	#end
+	Method TransformRectToView:Recti( rect:Recti,view:View )
+	
+		Return New Recti( TransformPointToView( rect.min,view ),TransformPointToView( rect.max,view ) )
+	End
+	
+	#rem monkeydoc Transforms a rect from another view.
+	
+	Transform `rect` from coordinates local to `view` to coordinates local to this view.
+	
+	@param rect The rect to transform.
+	
+	@param view The view to transform rect from.
+	
+	#end
+	Method TransformRectFromView:Recti( rect:Recti,view:View )
+	
+		Return New Recti( TransformPointFromView( rect.min,view ),TransformPointFromView( rect.max,view ) )
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method TransformWindowPointToView:Vec2i( point:Vec2i )
+	
+		Local t:=-_rmatrix * New Vec2f( point.x,point.y )
+		
+		Return New Vec2i( Round( t.x ),Round( t.y ) )
+	End
+	
+	
+	#rem monkeydoc Makes this view the 'key' view.
+	
+	The key view is the view that receives keyboard events.
+	
+	#end
+	Method MakeKeyView()
+	
+		If Not ReallyEnabled Return
+	
+		OnMakeKeyView()
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method SendMouseEvent( event:MouseEvent )
+	
+		If Not ReallyEnabled
+			Select event.Type
+			Case EventType.MouseUp,EventType.MouseLeave
+				OnMouseEvent( event )
+			End
+			Return
+		Endif
+	
+		OnMouseEvent( event )
+		
+		If event.Eaten Return
+	
+		Select event.Type
+		Case EventType.MouseWheel
+			Local view:=_parent
+			While view
+				view.OnMouseEvent( event )
+				If event.Eaten Return
+				view=view._parent
+			Wend
+		End
+		
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method SendKeyEvent( event:KeyEvent )
+	
+		If Not ReallyEnabled Return
+	
+		OnKeyEvent( event )
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property Container:View() Virtual
+	
+		Return Self
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method FindWindow:Window() Virtual
+	
+		If _parent Return _parent.FindWindow()
+		
+		Return Null
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method IsChildOf:Bool( view:View )
+		
+		If view=Self Return True
+		
+		If _parent Return _parent.IsChildOf( view )
+		
+		Return False
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method InvalidateStyle()
+	
+		_dirty|=Dirty.Style
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method ValidateStyle()
+	
+		If Not (_dirty & Dirty.Style) Return
+		
+		_rstyle=_style
+		
+		If Not ReallyEnabled 
+			_rstyle=_style.GetState( "disabled" )
+		Else If _styleState
+			_rstyle=_style.GetState( _styleState )
+		Endif
+		
+		_styleBounds=_rstyle.Bounds
+
+		_dirty&=~Dirty.Style
+				
+		OnValidateStyle()
+	End
+	
+	Protected
+	
+	#rem monkeydoc @hidden
+	#end
+	Method Measure()
+	
+		If Not _visible Return
+		
+		For Local view:=Eachin _children
+		
+			view.Measure()
+
+		Next
+		
+		ValidateStyle()
+		
+		Local size:=OnMeasure()
+		
+		If _minSize.x size.x=Max( size.x,_minSize.x )
+		If _minSize.y size.y=Max( size.y,_minSize.y )
+		If _maxSize.x size.x=Min( size.x,_maxSize.x )
+		If _maxSize.y size.y=Min( size.y,_maxSize.y )
+		
+		_measuredSize=size
+		
+		_layoutSize=size+_styleBounds.Size
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method UpdateLayout()
+	
+		_rect=New Recti( 0,0,_measuredSize )
+		
+		_bounds=_rect+_styleBounds
+		
+		_matrix=New AffineMat3f
+		
+		If _parent _matrix=_matrix.Translate( _frame.min.x,_frame.min.y )
+		
+		_matrix=_matrix.Translate( _offset.x,_offset.y )
+		
+		Select _layout
+		Case "fill","resize"
+		
+			_rect=New Recti( 0,0,_frame.Size-_styleBounds.Size )
+
+			_bounds=_rect+_styleBounds
+			
+		Case "fill-x"
+		
+			_rect.max.x=_frame.Width-_styleBounds.Width
+			
+			_bounds.min.x=_rect.min.x+_styleBounds.min.x
+			_bounds.max.x=_rect.max.x+_styleBounds.max.x
+			
+			_matrix=_matrix.Translate( 0,(_frame.Height-_bounds.Height)*_gravity.y )
+			
+		Case "float"
+		
+			_matrix=_matrix.Translate( (_frame.Width-_bounds.Width)*_gravity.x,(_frame.Height-_bounds.Height)*_gravity.y )
+			
+		Case "stretch"
+		
+			Local sx:=Float(_frame.Width)/_bounds.Width
+			Local sy:=Float(_frame.Height)/_bounds.Height
+			_matrix=_matrix.Scale( sx,sy )
+			
+		Case "scale","letterbox"
+		
+			Local sx:=Float(_frame.Width)/_bounds.Width
+			Local sy:=Float(_frame.Height)/_bounds.Height
+			If sx<sy
+				_matrix=_matrix.Translate( 0,(_frame.Height-_bounds.Height*sx)*_gravity.y )
+				_matrix=_matrix.Scale( sx,sx )
+			Else
+				_matrix=_matrix.Translate( (_frame.Width-_bounds.Width*sy)*_gravity.x,0 )
+				_matrix=_matrix.Scale( sy,sy )
+			Endif
+			
+		End
+
+		_matrix=_matrix.Translate( -_bounds.min.x,-_bounds.min.y )
+		
+		If _parent _rmatrix=_parent._rmatrix * _matrix Else _rmatrix=_matrix
+		
+		_rmatrix.t.x=Round( _rmatrix.t.x )
+		_rmatrix.t.y=Round( _rmatrix.t.y )
+		
+		_rclip=TransformRecti( _rect,_rmatrix )
+		_rbounds=TransformRecti( _bounds,_rmatrix )
+		
+		If _parent
+			_rclip&=_parent._rclip
+			_rbounds&=_parent._rclip
+			_clip=TransformRecti( _rclip,-_rmatrix )
+		Else
+			_clip=_rclip
+		End
+		
+		OnLayout()
+		
+		For Local view:=Eachin _children
+			view.UpdateLayout()
+		Next
+	End
+
+	#rem monkeydoc @hidden
+	#end
+	Method Render( canvas:Canvas )
+		
+		If Not _visible Return
+		
+		Local rmatrix:=canvas.RenderMatrix
+		Local rbounds:=canvas.RenderBounds
+		Local viewport:=canvas.Viewport
+
+		canvas.RenderMatrix=_rmatrix
+		canvas.RenderBounds=_rbounds
+		canvas.Viewport=_bounds
+		
+		_rstyle.Render( canvas,New Recti( 0,0,_bounds.Size ) )
+		
+		canvas.RenderMatrix=_rmatrix
+		canvas.RenderBounds=_rclip
+		canvas.Viewport=_rect
+		
+		OnRender( canvas )
+		canvas.ClearMatrix()
+		
+		For Local view:=Eachin _children
+			view.Render( canvas )
+		Next
+
+		canvas.RenderMatrix=_rmatrix		
+		canvas.RenderBounds=_rbounds
+		canvas.Viewport=_bounds
+		
+		OnRenderBounds( canvas )
+		canvas.ClearMatrix()
+
+		canvas.RenderMatrix=rmatrix
+		canvas.RenderBounds=rbounds
+		canvas.Viewport=viewport
+	End
+	
+	Protected
+	
+	#rem monkeydoc @hidden
+	#end
+	Method OnValidateStyle() Virtual
+	
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method OnMeasure:Vec2i() Virtual
+	
+		Return New Vec2i( 0,0 )
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method OnMeasure2:Vec2i( size:Vec2i ) Virtual
+	
+		Return New Vec2i( 0,0 )
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method OnLayout() Virtual
+	
+		For Local view:=Eachin _children
+			view.Frame=Rect
+		Next
+
+	End
+	
+	#rem monkeydoc Render this view.
+	
+	Called when the view should render itself.
+	
+	#end
+	Method OnRender( canvas:Canvas ) Virtual
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method OnRenderBounds( canvas:Canvas ) Virtual
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method OnMakeKeyView() Virtual
+	
+		Local window:=FindWindow()
+		If window window.KeyView=Self
+	
+	End
+	
+	#rem monkeydoc Keyboard event handler.
+	
+	Called when a keyboard event is sent to this view.
+	
+	#end
+	Method OnKeyEvent( event:KeyEvent ) Virtual
+	End
+	
+	#rem monkeydoc Mouse event handler.
+	
+	Called when a mouse event is sent to this view.
+	
+	#end
+	Method OnMouseEvent( event:MouseEvent ) Virtual
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property MeasuredSize:Vec2i()
+	
+		Return _measuredSize
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property LayoutSize:Vec2i()
+	
+		Return _layoutSize
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property StyleBounds:Recti()
+	
+		Return _styleBounds
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method Measure2:Vec2i( size:Vec2i )
+		size=OnMeasure2( size-_styleBounds.Size )
+		If size.x And size.y _layoutSize=size+_styleBounds.Size
+		Return _layoutSize
+	End
+	
+	Private
+	
+	Enum Dirty
+		Style=1
+		All=1
+	End
+	
+	Field _dirty:Dirty=Dirty.All
+
+	Field _parent:View
+	Field _children:=New Stack<View>
+	
+	Field _visible:Bool=True
+	Field _enabled:Bool=True
+	Field _style:Style
+	Field _styleState:String
+
+	Field _layout:String
+	Field _gravity:=New Vec2f( .5,.5 )
+	Field _offset:=New Vec2i( 0,0 )
+
+	Field _minSize:Vec2i
+	Field _maxSize:Vec2i
+	
+	Field _frame:Recti
+	
+	'After Measuring...
+	Field _rstyle:Style
+	Field _styleBounds:Recti
+	Field _measuredSize:Vec2i
+	Field _layoutSize:Vec2i
+	
+	'After layout
+	Field _rect:Recti
+	Field _bounds:Recti
+	Field _matrix:AffineMat3f
+	Field _rmatrix:AffineMat3f
+	Field _rbounds:Recti
+	Field _rclip:Recti
+	Field _clip:Recti
+	
+End

+ 291 - 0
modules/mojo/app/window.monkey2

@@ -0,0 +1,291 @@
+
+Namespace mojo.app
+
+#rem monkeydoc Window creation flags.
+
+| WindowFlags	| Description
+|:--------------|:-----------
+| CenterX		| Center window horizontally.
+| CenterY		| Center window vertically.
+| Center		| Center window.
+| Hidden		| Window is initally hidden.
+| Resizable		| Window is resizable.
+| Fullscreen	| Window is a fullscreen window.
+
+#end
+Enum WindowFlags
+	CenterX=1
+	CenterY=2
+	Hidden=4
+	Resizable=8
+	Borderless=16
+	Fullscreen=32
+	Center=CenterX|CenterY
+End
+
+Class Window Extends View
+
+	#rem monkeydoc @hidden
+	#end
+	Field WindowClose:Void()
+	
+	#rem monkeydoc @hidden
+	#end
+	Field WindowMoved:Void()
+	
+	#rem monkeydoc @hidden
+	#end
+	Field WindowResized:Void()
+	
+	Method New()
+		Init( "Window",New Recti( 0,0,640,480 ),WindowFlags.Center )
+	End
+	
+	Method New( title:String="Window",width:Int=640,height:Int=480,flags:WindowFlags=Null )
+		Init( title,New Recti( 0,0,width,height ),flags|WindowFlags.Center )
+	End
+
+	Method New( title:String,rect:Recti,flags:WindowFlags=Null )
+		Init( title,rect,flags )
+	End
+	
+	#rem monkeydoc Window clear color
+	
+	#end
+	Property ClearColor:Color()
+
+		Return _clearColor
+
+	Setter( clearColor:Color )
+
+		_clearColor=clearColor
+	End
+	
+	#rem monkeydoc @hidden - Frame should work here...
+	#end
+	Property WindowRect:Recti()
+	
+		Local pos:Vec2i,size:Vec2i
+		SDL_GetWindowPosition( _sdlWindow,Varptr pos.x,Varptr pos.y )
+		SDL_GetWindowSize( _sdlWindow,Varptr size.x,Varptr size.y )
+		
+		Return New Recti( pos,pos+size )
+	End
+	
+	Setter( windowRect:Recti )
+	
+		Local pos:=windowRect.Origin
+		Local size:=windowRect.Size
+		SDL_SetWindowPosition( _sdlWindow,pos.x,pos.y )
+		SDL_SetWindowSize( _sdlWindow,size.x,size.y )
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method Update()
+	
+		'a bit ugyl...fixme!
+		#rem
+		If MinSize<>_minSize Or MaxSize<>_maxSize Or Frame<>_frame
+			_minSize=MinSize
+			_maxSize=MaxSize
+			_frame=Frame
+			SDL_SetWindowMinimumSize( _sdlWindow,_minSize.x,_minSize.y )
+			SDL_SetWindowMinimumSize( _sdlWindow,_maxSize.x,_maxSize.y )
+			SDL_SetWindowPosition( _sdlWindow,_frame.X,_frame.Y )
+			SDL_SetWindowSize( _sdlWindow,_frame.Width,_frame.Height )
+		Endif
+		#end
+		
+		Measure()
+		
+		UpdateLayout()
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method Render()
+	
+		SDL_GL_MakeCurrent( _sdlWindow,_sdlGLContext )
+		
+		Local viewport:=New Recti( 0,0,Frame.Size )
+		
+		_canvas.Resize( viewport.Size )
+		
+'		_canvas.BeginRender()
+		
+		_canvas.RenderColor=Color.White
+		_canvas.RenderMatrix=New AffineMat3f
+		_canvas.RenderBounds=viewport
+		
+		_canvas.Viewport=viewport
+		_canvas.Scissor=New Recti( 0,0,16384,16384 )
+		_canvas.ViewMatrix=New Mat4f
+		_canvas.ModelMatrix=New Mat4f
+
+		_canvas.BlendMode=BlendMode.Alpha
+		_canvas.Color=Color.White
+		_canvas.Font=Null
+		_canvas.Matrix=New AffineMat3f
+		
+		_canvas.Clear( _clearColor )
+		
+		Render( _canvas )
+
+		_canvas.Flush()
+		
+'		_canvas.EndRender()
+		
+		SDL_GL_SwapWindow( _sdlWindow )
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method SendWindowEvent( event:WindowEvent )
+	
+		OnWindowEvent( event )
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method FindWindow:Window() Override
+	
+		Return Self
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property NativeWindow:SDL_Window Ptr()
+	
+		Return _sdlWindow
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Function AllWindows:Window[]()
+	
+		Return _allWindows.ToArray()
+	End
+
+	#rem monkeydoc @hidden
+	#end
+	Function VisibleWindows:Window[]()
+	
+		Return _visibleWindows.ToArray()
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Function WindowForID:Window( id:UInt )
+	
+		Return _windowsByID[id]
+	End
+
+	'***** INTERNAL *****
+	
+	#rem monkeydoc @hidden
+	#end
+	Property KeyView:View()
+	
+		Return _keyView
+		
+	Setter( keyView:View )
+	
+		_keyView=keyView
+	End
+	
+	Protected
+	
+	Method OnValidateStyle() Override
+	
+		SDL_SetWindowMinimumSize( _sdlWindow,MinSize.x,MinSize.y )
+		SDL_SetWindowMaximumSize( _sdlWindow,MaxSize.x,MaxSize.y )
+	
+	End
+	
+	#rem monkeydoc Window event handler.
+	
+	Called when the window is sent a window event.
+	
+	#end
+	Method OnWindowEvent( event:WindowEvent ) Virtual
+	
+		Select event.Type
+		Case EventType.WindowClose
+			WindowClose()
+		Case EventType.WindowMoved
+			Frame=event.Rect
+			WindowMoved()
+		Case EventType.WindowResized
+			Frame=event.Rect
+'			Frame=New Recti( 0,0,event.Rect.Size )
+			App.RequestRender()
+			WindowResized()
+		End
+		
+	End
+	
+	Private
+	
+	Field _sdlWindow:SDL_Window Ptr
+	Field _sdlGLContext:SDL_GLContext
+	
+	Field _canvas:Canvas
+
+	Field _clearColor:=Color.Grey
+	Field _keyView:View
+	
+	Field _minSize:Vec2i
+	Field _maxSize:Vec2i
+	Field _frame:Recti
+	
+	Global _allWindows:=New Stack<Window>
+	Global _visibleWindows:=New Stack<Window>
+	Global _windowsByID:=New Map<UInt,Window>
+	
+	Method Init( title:String,rect:Recti,flags:WindowFlags )
+	
+		Layout="fill"
+	
+		Local x:=(flags & WindowFlags.CenterX) ? SDL_WINDOWPOS_CENTERED Else rect.X
+		Local y:=(flags & WindowFlags.CenterY) ? SDL_WINDOWPOS_CENTERED Else rect.Y
+		
+		Local sdlFlags:SDL_WindowFlags=SDL_WINDOW_OPENGL
+		
+		If flags & WindowFlags.Hidden sdlFlags|=SDL_WINDOW_HIDDEN
+		If flags & WindowFlags.Resizable sdlFlags|=SDL_WINDOW_RESIZABLE
+		If flags & WindowFlags.Borderless sdlFlags|=SDL_WINDOW_BORDERLESS
+		If flags & WindowFlags.Fullscreen sdlFlags|=SDL_WINDOW_FULLSCREEN
+	
+		_sdlWindow=SDL_CreateWindow( title,x,y,rect.Width,rect.Height,sdlFlags )
+
+		_allWindows.Push( Self )
+		If Not (flags & WindowFlags.Hidden) _visibleWindows.Push( Self )
+		_windowsByID[SDL_GetWindowID( _sdlWindow )]=Self
+		
+		Local tx:Int,ty:Int,tw:Int,th:Int
+		SDL_GetWindowMinimumSize( _sdlWindow,Varptr tw,Varptr th )
+		_minSize=New Vec2i( tw,th )
+
+		SDL_GetWindowMaximumSize( _sdlWindow,Varptr tw,Varptr th )
+		_maxSize=New Vec2i( tw,th )
+		
+		SDL_GetWindowPosition( _sdlWindow,Varptr tx,Varptr ty )
+		SDL_GetWindowSize( _sdlWindow,Varptr tw,Varptr th )
+		_frame=New Recti( tx,ty,tx+tw,ty+th )
+		
+		MinSize=_minSize
+		MaxSize=_maxSize
+		Frame=_frame
+		
+		WindowClose=App.Terminate
+		
+		'Create GLContext and canvas
+		
+		_sdlGLContext=SDL_GL_CreateContext( _sdlWindow )
+		
+		SDL_GL_MakeCurrent( _sdlWindow,_sdlGLContext )
+		
+		_canvas=New Canvas( rect.Width,rect.Height )
+	End
+End

BIN
modules/mojo/bananas/mojotest/assets/RedbrushAlpha.png


+ 98 - 0
modules/mojo/bananas/mojotest/mojotest.monkey2

@@ -0,0 +1,98 @@
+
+Namespace mojotest
+
+#Import "<std>"
+#Import "<mojo>"
+
+Using std..
+Using mojo..
+
+#Import "assets/RedbrushAlpha.png"
+
+Class MojoTest Extends Window
+
+	Field image:Image
+	
+	Field tx:Float,ty:Float
+	Field r:Float=1,g:Float=1,b:Float=1
+
+	Method New()
+		
+		ClearColor=New Color( 0,0,.5 )
+	
+		image=Image.Load( "asset::RedbrushAlpha.png" )
+		image.Handle=New Vec2f( .5,.5 )
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		App.RequestRender()
+	
+		Local sz:=Sin( Millisecs()*.0007 ) * 32
+		Local sx:=32+sz,sy:=32,sw:=Width-(64+sz*2),sh:=Height-(64+sz)
+		
+		canvas.Scissor=New Recti( sx,sy,sx+sw,sy+sh )
+		canvas.Clear( New Color( 1,32.0/255.0,0,1 ) )
+		
+		canvas.PushMatrix()
+		
+		canvas.Translate( tx,ty )
+		canvas.Scale( Width/640.0,Height/480.0 )
+		canvas.Translate( 320,240 )
+		canvas.Rotate( Millisecs()*.0003 )
+		canvas.Translate( -320,-240 )
+		
+		canvas.Color=New Color( .5,1,0 )
+		canvas.DrawRect( 32,32,640-64,480-64 )
+
+		canvas.Color=Color.Yellow
+		For Local y:=0 Until 480
+			For Local x:=16 Until 640 Step 32
+				canvas.Alpha=Min( Abs( y-240.0 )/120.0,1.0 )
+				canvas.DrawPoint( x,y )
+			Next
+		Next
+		canvas.Alpha=1
+		
+		canvas.Color=New Color( 0,.5,1 )
+'		canvas.DrawOval( 64,64,640-128,480-128 )	'TODO!
+
+		canvas.Color=New Color( 1,0,.5 )
+		canvas.DrawLine( 32,32,640-32,480-32 )
+		canvas.DrawLine( 640-32,32,32,480-32 )
+
+		canvas.Color=New Color( r,g,b,Sin( Millisecs()*.003 ) *.5 +.5 )
+		canvas.DrawImage( image,320,240 )
+
+		canvas.Color=New Color( 1,0,.5 )
+'		canvas.DrawPoly( New Float[]( 140.0,232.0, 320.0,224.0, 500.0,232.0, 500.0,248.0, 320.0,256.0, 140.0,248.0 ) )	'TODO!
+				
+		canvas.Color=New Color( 1,.5,0 )'Color.Red
+		canvas.DrawText( "The Quick Brown Fox Jumps Over The Lazy Dog",320,240,.5,.5 )
+		
+		canvas.PopMatrix()
+		
+		canvas.Scissor=Rect
+		canvas.Color=Color.Red
+		canvas.DrawRect( 0,0,Width,1 )
+		canvas.DrawRect( Width-1,0,1,Height )
+		canvas.DrawRect( 0,Height-1,Width,1 )
+		canvas.DrawRect( 0,0,1,Height-1 )
+	
+
+		canvas.Color=Color.Blue
+		canvas.DrawText( Seconds(),0,0 )
+	
+	End
+
+End
+
+Function Main()
+
+	New AppInstance
+	
+	New MojoTest
+	
+	App.Run()
+
+End

BIN
modules/mojo/bananas/rendertoimage/assets/spaceship.png


+ 58 - 0
modules/mojo/bananas/rendertoimage/rendertoimage.monkey2

@@ -0,0 +1,58 @@
+
+Namespace test
+
+#Import "<std>"
+#Import "<mojo>"
+
+Using std..
+Using mojo..
+
+#Import "assets/spaceship.png"
+
+Class MyWindow Extends mojo.app.Window
+
+	Field image:Image
+	
+	Field icanvas:Canvas
+
+	Method New()
+	
+		image=New Image( 256,256 )
+		
+		image.Handle=New Vec2f( .5,.5 )
+		
+		icanvas=New Canvas( image )
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		App.RequestRender()
+	
+		'render to image...
+		For Local x:=0 Until 16
+			For Local y:=0 Until 16
+				If (x~y)&1
+					icanvas.Color=New Color( Sin( Millisecs()*.01 )*.5+.5,Cos( Millisecs()*.02 )*.5+.5,.5 )
+				Else
+					icanvas.Color=Color.Yellow
+				Endif
+				icanvas.DrawRect( x*16,y*16,16,16 )
+			Next
+		Next
+		icanvas.Color=Color.White
+		icanvas.DrawText( "This way up!",icanvas.Viewport.Width/2,0,.5,0 )
+		icanvas.Flush()
+		
+		canvas.DrawImage( image,App.MouseLocation.x,App.MouseLocation.y )
+	End
+	
+End
+
+Function Main()
+
+	New AppInstance
+	
+	New MyWindow
+	
+	App.Run()
+End

BIN
modules/mojo/bananas/spacechimps/assets/spaceship.png


+ 112 - 0
modules/mojo/bananas/spacechimps/spacechimps.monkey2

@@ -0,0 +1,112 @@
+
+Namespace spacechimps
+
+#Import "<std>"
+#Import "<mojo>"
+
+Using std..
+Using mojo..
+
+#Import "assets/spaceship.png"
+
+Class MyWindow Extends Window
+
+	Field image:Image
+	
+	Field pos:Vec2f
+	Field vel:Vec2f
+	Field rot:Float
+
+	Method New( title:String,width:Int,height:Int )
+	
+		'Call super class constructor - this just passes the arguments 'up' to the Window class constructor.
+		'
+		Super.New( title,width,height )
+
+		
+		'Black 'coz we're in space!
+		'
+		ClearColor=Color.Black
+		
+		
+		'Load and setup our image...
+		'
+		'Note: Scaling image here is faster than scaling in DrawImage.
+		'
+		image=Image.Load( "asset::spaceship.png" )
+		
+		image.Handle=New Vec2f( .5,.5 )
+		
+		image.Scale=New Vec2f( .125,.125 )
+		
+		
+		'Set initial image pos
+		'
+		pos=New Vec2f( width/2,height/2 )
+	
+	End
+
+	Method OnRender( canvas:Canvas ) Override
+	
+		'This is necessary for 'continuous' rendering.
+		'
+		'Without it, OnRender will only be called when necessary, eg: when the window is resized.
+		'
+		App.RequestRender()
+		
+		
+		'Gamey stuff below
+		'
+		If App.KeyDown( Key.Left )
+			rot+=.1
+		Else If App.KeyDown( Key.Right )
+			rot-=.1
+		Endif
+		
+		'wrap rot to [-Pi,Pi)
+		rot=(rot+Pi*3) Mod TwoPi-Pi
+
+		'calc forward vector..
+		Local dir:=New Vec2f( Cos( rot ),-Sin( rot ) )
+		
+		If App.KeyDown( Key.Up )
+			vel+=(dir * 5 - vel) *.025	'arcadey thruster
+'			vel+=dir * .03				'realistic...
+		Else
+			vel*=.999
+		End
+		
+		'add velocity to position
+		pos+=vel
+		
+		'wrap pos to [0,size)
+		pos.x=(pos.x+Width) Mod Width
+		pos.y=(pos.y+Height) Mod Height
+
+		canvas.DrawText( "Arrow keys to fly",Width/2,8,.5,0 )
+		
+		'Draw image
+		'
+		Local r:=rot-Pi/2
+		canvas.DrawImage( image,pos,r )
+		
+		'Draw wrap around(s)
+		'
+		If pos.x-image.Radius<0 canvas.DrawImage( image,pos.x+Width,pos.y,r )
+		If pos.x+image.Radius>Width canvas.DrawImage( image,pos.x-Width,pos.y,r )
+		
+		If pos.y-image.Radius<0 canvas.DrawImage( image,pos.x,pos.y+Height,r )
+		If pos.y+image.Radius>Height canvas.DrawImage( image,pos.x,pos.y-Height,r )
+
+	End
+	
+End
+
+Function Main()
+
+	New AppInstance
+	
+	New MyWindow( "Chimps in Space!",App.DesktopSize.x/2,App.DesktopSize.y/2 )
+	
+	App.Run()
+End

+ 196 - 0
modules/mojo/bananas/viewlayout/viewlayout.monkey2

@@ -0,0 +1,196 @@
+
+#rem
+
+A slightly more complicated window example.
+
+The example implements a resizable window with virtual resolution support via the "letterbox" and "stretch" layout modes, and shows
+some simple keyboard/mouse event handling.
+
+#end
+
+Namespace test
+
+#Import "<std>"
+#Import "<mojo>"
+
+Using std..
+Using mojo..
+
+Class MyWindow Extends Window
+
+	Field virtualRes:=New Vec2i( 320,240 )
+
+	Method New( title:String,width:Int,height:Int,flags:WindowFlags=WindowFlags.Resizable )
+	
+		'Call super class constructor
+		'
+		Super.New( title,width,height,flags )
+		
+		'Set initial layout (this is the default for Windows).
+		'
+		Layout="fill"
+		
+
+		'Window clear color - for "letterbox" and "float" layouts, this is effectively the border color.
+		'
+		ClearColor=Color.Black
+		
+		
+		'Set minimum view size
+		'
+		MinSize=New Vec2i( 200,140 )
+
+		
+		'Set view background color.
+		'
+		Style.BackgroundColor=Color.DarkGrey
+		
+		
+		'One way to detect window resizing (you can also override OnWindowEvent).
+		'
+		'Note: you don't have to use a lambda here, a method or function will also work fine.
+		'
+		WindowResized=Lambda()
+		
+			Print "Window Resized to:"+Frame.Width+","+Frame.Height
+			
+		End
+		
+		
+		'One way to detect window close click (you can also override OnWindowEvent).
+		'
+		'Note: WindowClose is connected to App.Terminate by default, so this doesn't do a lot.
+		'
+		WindowClose=Lambda()
+		
+			Print "WindowClose - outta here!"
+			
+			App.Terminate()
+		End
+	
+	End
+
+	Method OnRender( canvas:Canvas ) Override
+	
+		'This is necessary for 'continuous' rendering.
+		'
+		'Without it, OnRender will only be called when necessary, eg: when window is resized.
+		'
+		App.RequestRender()
+		
+		
+		'Get mouse location in 'view' coordinates.
+		'
+		'Note: this is only necessary if Layout is not "fill". If Layout="fill" (the default), you can just use App.MouseLocation directly.
+		'
+		Local mouse:=TransformPointFromView( App.MouseLocation,Null )
+
+
+		'Render!
+		'		
+		Local h:=canvas.Font.Height
+		
+		canvas.DrawText( "Size="+Rect.Size.ToString(),0,0 )
+		canvas.DrawText( "Mouse="+mouse.ToString(),0,h )
+		canvas.DrawText( "Layout=~q"+Layout+"~q  ('L' to cycle)",0,h*2 )
+		
+		If Layout="float"
+			canvas.DrawText( "Resolution="+virtualRes.ToString()+"  ('R' to cycle)",0,h*3 )
+			canvas.DrawText( "Gravity="+Gravity.ToString()+"  ('G' to cycle)",0,h*4 )
+		Else If Layout="letterbox"
+			canvas.DrawText( "Resolution="+virtualRes.ToString()+"  ('R' to cycle)",0,h*3 )
+			canvas.DrawText( "Gravity="+Gravity.ToString()+"  ('G' to cycle)",0,h*4 )
+		Else If Layout="stretch"
+			canvas.DrawText( "Resolution="+virtualRes.ToString()+"  ('R' to cycle)",0,h*3 )
+		Endif
+		
+		canvas.DrawText( "Hello World!",Width/2,Height/2,.5,.5 )
+	End
+	
+	'Measured out view.
+	'
+	'This is used by the "float", "letterbox" and "stretch" layouts.
+	'
+	Method OnMeasure:Vec2i() Override
+	
+		Return virtualRes
+	End
+	
+	'Process a KeyEvent.
+	'
+	'Needed because there's no App.KeyHit yet!
+	'
+	Method OnKeyEvent( event:KeyEvent ) Override
+	
+		Select event.Type
+		Case EventType.KeyDown
+			Select event.Key
+			Case Key.L
+				CycleLayout()
+			Case Key.G
+				CycleGravity()
+			Case Key.R
+				CycleVirtualRes()
+			End
+		End
+		
+	End
+	
+	'Process a MouseEvent.
+	'
+	'Note: event.Location property is in 'view space' coordinates.
+	'
+	Method OnMouseEvent( event:MouseEvent ) Override
+	End
+	
+	Method CycleLayout()
+		Select Layout
+		Case "fill"
+			Layout="letterbox"
+		Case "letterbox"
+			Layout="stretch"
+		Case "stretch"
+			Layout="float"
+		Case "float"
+			Layout="fill"
+		End
+		virtualRes=New Vec2i( 320,240 )
+		Gravity=New Vec2f( .5,.5 )
+	End
+	
+	Method CycleGravity()
+		Local gravity:=Gravity
+		gravity.x+=.5
+		If gravity.x>1
+			gravity.x=0
+			gravity.y+=.5
+			If gravity.y>1 gravity.y=0
+		Endif
+		Gravity=gravity
+	End
+	
+	Method CycleVirtualRes()
+		Select virtualRes.x
+		Case 320
+			virtualRes=New Vec2i( 640,480 )		'4:3
+		Case 640
+			virtualRes=New Vec2i( 1024,768 )	'4:3
+		Case 1024
+			virtualRes=New Vec2i( 1280,720 )	'16:9
+		Case 1280
+			virtualRes=New Vec2i( 1920,1080 )	'16:9
+		Case 1920
+			virtualRes=New Vec2i( 320,240 )		'4:3
+		End
+	End
+	
+End
+
+Function Main()
+
+	New AppInstance
+	
+	New MyWindow( "Simple Window Demo!",640,512 )
+	
+	App.Run()
+End

BIN
modules/mojo/graphics/assets/RobotoMono-Regular.ttf


BIN
modules/mojo/graphics/assets/monkey2-logo-63.png


+ 31 - 0
modules/mojo/graphics/assets/shader_font.glsl

@@ -0,0 +1,31 @@
+
+// ***** alpha surface shader *****
+
+varying vec4 Color;
+
+varying vec2 TexCoord0;
+
+//@vertex
+
+void transform( out vec4 viewpos ){
+
+	viewpos=mx2_ModelViewMatrix * mx2_VertexPosition;
+	
+	Color=mx2_VertexColor * mx2_RenderColor;
+	
+	TexCoord0=mx2_VertexTexCoord0;
+}
+
+//@fragment
+
+uniform sampler2D u_Texture0;
+
+void ambientPass( out vec4 ambient ){
+
+	ambient=vec4( texture2D( u_Texture0,TexCoord0 ).a ) * Color;
+}
+
+void lightingPass( out vec4 diffuse,out vec4 specular,out vec3 normal ){
+
+}
+

+ 28 - 0
modules/mojo/graphics/assets/shader_matte.glsl

@@ -0,0 +1,28 @@
+
+// ***** matte surface shader *****
+
+varying vec2 texCoord0;
+
+//@vertex
+
+void transform( out vec4 viewpos ){
+
+	viewpos=mx2_ModelViewMatrix * mx2_Vertex;
+
+	texCoord0=mx2_TexCoord0;
+}
+
+//@fragment
+
+uniform sampler2D ColorTexture;		//default=white
+uniform sampler2D SpecularTexture;	//default=black
+uniform sampler2D NormalTexture;	//default=black
+
+void ambientPass( out vec4 ambient ){
+	
+	ambient=texture2D( ColorTexture,texCoord0 ) * mx2_AmbientLight;
+}
+
+void lightingPass( out vec4 diffuse,out vec4 specular,out vec4 normal ){
+
+}

+ 25 - 0
modules/mojo/graphics/assets/shader_null.glsl

@@ -0,0 +1,25 @@
+
+// ***** alpha surface shader *****
+
+varying vec4 Color;
+
+//@vertex
+
+void transform( out vec4 viewpos ){
+
+	viewpos=mx2_ModelViewMatrix * mx2_VertexPosition;
+	
+	Color=mx2_VertexColor * mx2_RenderColor;
+}
+
+//@fragment
+
+void ambientPass( out vec4 ambient ){
+
+	ambient=Color;
+}
+
+void lightingPass( out vec4 diffuse,out vec4 specular,out vec3 normal ){
+
+}
+

+ 33 - 0
modules/mojo/graphics/assets/shader_phong.glsl

@@ -0,0 +1,33 @@
+
+// ***** phong surface shader *****
+
+varying vec2 texCoord0;
+
+//@vertex
+
+void transform( out vec4 viewpos ){
+
+	viewpos=mx2_ModelViewMatrix * mx2_Vertex;
+
+	texCoord0=mx2_TexCoord0;
+}
+
+//@fragment
+
+uniform sampler2D ColorTexture;			//default=white
+uniform sampler2D SpecularTexture;		//default=white
+uniform sampler2D NormalTexture;		//default=smooth
+
+void ambientPass( out vec4 ambient ){
+
+	diffuse=texture2D( ColorTexture,texCoord0 ) * mx2_Color * mx2_AmbientLight;
+}
+
+void lightingPass( out vec4 diffuse,out vec4 specular,out vec4 normal ){
+
+	diffuse=texture2D( ColorTexture,texCoord0 ) * mx2_Color;
+	
+	specular=texture2D( SpecularTexture,texCoord0 );
+	
+	normal=normalize( mat3( mx2_ModelViewMatrix ) * texture2D( NormalTexture,texCoord0 ).xyz );
+}

+ 31 - 0
modules/mojo/graphics/assets/shader_sprite.glsl

@@ -0,0 +1,31 @@
+
+// ***** matte surface shader *****
+
+varying vec4 Color;
+
+varying vec2 TexCoord0;
+
+//@vertex
+
+void transform( out vec4 viewpos ){
+
+	viewpos=mx2_ModelViewMatrix * mx2_VertexPosition;
+	
+	Color=mx2_VertexColor * mx2_RenderColor;
+	
+	TexCoord0=mx2_VertexTexCoord0;
+}
+
+//@fragment
+
+uniform sampler2D u_Texture0;
+
+void ambientPass( out vec4 ambient ){
+
+	ambient=texture2D( u_Texture0,TexCoord0 ) * Color;
+}
+
+void lightingPass( out vec4 diffuse,out vec4 specular,out vec3 normal ){
+
+}
+

+ 38 - 0
modules/mojo/graphics/assets/shaderenv_ambient.glsl

@@ -0,0 +1,38 @@
+
+// ***** ambient shaderenv *****
+
+uniform mat4 mx2_ModelViewMatrix;
+uniform mat4 mx2_ProjectionMatrix;
+uniform vec4 mx2_AmbientLight;
+uniform vec4 mx2_RenderColor;
+
+//@vertex
+
+attribute vec4 mx2_VertexPosition;
+attribute vec2 mx2_VertexTexCoord0;
+attribute vec2 mx2_VertexTangent;
+attribute vec4 mx2_VertexColor;
+
+void transform( out vec4 viewpos );
+
+void main(){
+
+	vec4 position;
+	
+	transform( position );
+	
+	gl_Position=mx2_ProjectionMatrix * position;
+}
+
+//@fragment
+
+void ambientPass( out vec4 ambient );
+
+void main(){
+
+	vec4 ambient;
+	
+	ambientPass( ambient );
+	
+	gl_FragColor=ambient;
+}

+ 37 - 0
modules/mojo/graphics/assets/shaderenv_lighting.glsl

@@ -0,0 +1,37 @@
+
+// ***** ambient shaderenv *****
+
+uniform mat4 mx2_ModelViewMatrix;
+uniform mat4 mx2_ProjectionMatrix;
+uniform vec4 mx2_AmbientLight;
+uniform vec4 mx2_RenderColor;
+
+varying vec4 mx2_Color;
+
+//@vertex
+
+attribute vec4 mx2_Vertex;
+attribute vec2 mx2_TexCoord0;
+attribute vec2 mx2_Tangent;
+attribute vec4 mx2_VertexColor;
+
+void transform( out vec4 viewpos );
+
+void main(){
+	vec4 position;
+	transform( position );
+	gl_Position=mx2_ProjectionMatrix * position;
+	mx2_color=mx2_VertexColor * mx2_RenderColor;
+}
+
+//@fragment
+
+void lightingPass( out vec4 diffuse,out vec4 specular,out vec3 normal );
+
+void main(){
+
+	vec4 diffuse,specular;
+	vec3 normal;
+	
+	lightingPass( ambient,diffuse,specular,normal );
+}

+ 704 - 0
modules/mojo/graphics/canvas.monkey2

@@ -0,0 +1,704 @@
+
+Namespace mojo.graphics
+
+#Import "assets/shaderenv_ambient.glsl@/mojo"
+#Import "assets/RobotoMono-Regular.ttf@/mojo"
+
+#rem monkeydoc @hidden
+#end	
+Class DrawOp
+	Field blendMode:BlendMode
+	Field material:Material
+	Field order:Int
+	Field count:Int
+End
+
+#rem monkeydoc The Canvas class.
+#end
+Class Canvas
+
+	Method New( image:Image )
+	
+		Init( image.Texture,image.Texture.Rect.Size,image.RenderRect )
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method New( texture:Texture )
+	
+		Init( texture,texture.Rect.Size,New Recti( 0,0,texture.Rect.Size ) )
+	End
+
+	#rem monkeydoc @hidden
+	#end
+	Method New( width:Int,height:Int )
+	
+		Init( Null,New Vec2i( width,height ),New Recti( 0,0,width,height ) )
+	End
+	
+	Property Viewport:Recti()
+	
+		Return _viewport
+		
+	Setter( viewport:Recti )
+	
+		Flush()
+		
+		_viewport=viewport
+		
+		_dirty|=Dirty.Scissor|Dirty.EnvParams
+	End
+	
+	Property Scissor:Recti()
+	
+		Return _scissor
+	
+	Setter( scissor:Recti )
+	
+		Flush()
+		
+		_scissor=scissor
+		
+		_dirty|=Dirty.Scissor
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property ViewMatrix:Mat4f()
+	
+		Return _viewMatrix
+	
+	Setter( viewMatrix:Mat4f )
+	
+		Flush()
+	
+		_viewMatrix=viewMatrix
+		
+		_dirty|=Dirty.EnvParams
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property ModelMatrix:Mat4f()
+	
+		Return _modelMatrix
+	
+	Setter( modelMatrix:Mat4f )
+	
+		Flush()
+	
+		_modelMatrix=modelMatrix
+		
+		_dirty|=Dirty.EnvParams
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property AmbientLight:Color()
+	
+		Return _ambientLight
+
+	Setter( ambientLight:Color )
+	
+		Flush()
+	
+		_ambientLight=ambientLight
+		
+		_dirty|=Dirty.EnvParams
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property RenderColor:Color()
+	
+		Return _renderColor
+	
+	Setter( renderColor:Color )
+
+		Flush()
+			
+		_renderColor=renderColor
+		
+		_dirty|=Dirty.EnvParams
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property RenderMatrix:AffineMat3f()
+	
+		Return _renderMatrix
+		
+	Setter( renderMatrix:AffineMat3f )
+	
+		Flush()
+		
+		_renderMatrix=renderMatrix
+		
+		_dirty|=Dirty.Scissor|Dirty.EnvParams
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property RenderBounds:Recti()
+	
+		Return _renderBounds
+		
+	Setter( renderBounds:Recti )
+	
+		Flush()
+		
+		_renderBounds=renderBounds
+				
+		_dirty|=Dirty.Scissor
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Method Resize( size:Vec2i )
+	
+		Flush()
+		
+		_targetSize=size
+		
+		_targetRect=New Recti( 0,0,size )
+		
+		_dirty|=Dirty.Target
+	End
+	
+	Method Clear( color:Color )
+
+		Flush()
+		
+		_device.Clear( color )
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Method BeginRender()
+	
+		DebugAssert( Not _rendering )
+		
+		_rendering=True
+	
+		_device.ShaderEnv=_ambientEnv
+		
+		_dirty=Dirty.All
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Method EndRender()
+	
+		Flush()
+		
+		_rendering=False
+	End
+	
+	Method Flush()
+	
+		If Not _rendering Return
+		
+		Validate()
+		
+		RenderDrawOps()
+		
+		ClearDrawOps()
+	End
+	
+	'***** DrawList *****
+	
+	Property Font:Font()
+	
+		Return _font
+	
+	Setter( font:Font )
+	
+		If Not font font=_defaultFont
+	
+		_font=font
+	End
+	
+	Property Alpha:Float()
+	
+		Return _alpha
+		
+	Setter( alpha:Float )
+	
+		_alpha=alpha
+		
+		Local a:=_color.a * _alpha * 255.0
+		_pmcolor=UInt(a) Shl 24 | UInt(_color.b*a) Shl 16 | UInt(_color.g*a) Shl 8 | UInt(_color.r*a)
+	End
+	
+	Property Color:Color()
+	
+		Return _color
+	
+	Setter( color:Color )
+	
+		_color=color
+		
+		Local a:=_color.a * _alpha * 255.0
+		_pmcolor=UInt(a) Shl 24 | UInt(_color.b*a) Shl 16 | UInt(_color.g*a) Shl 8 | UInt(_color.r*a)
+	End
+	
+	Property Matrix:AffineMat3f()
+	
+		Return _matrix
+	
+	Setter( matrix:AffineMat3f )
+	
+		_matrix=matrix
+	End
+	
+	Property BlendMode:BlendMode()
+	
+		Return _blendMode
+	
+	Setter( blendMode:BlendMode )
+	
+		_blendMode=blendMode
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property PointMaterial:Material()
+	
+		Return _pointMaterial
+	
+	Setter( pointMaterial:Material )
+	
+		_pointMaterial=pointMaterial
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property LineMaterial:Material()
+	
+		Return _lineMaterial
+		
+	Setter( lineMaterial:Material )
+	
+		_lineMaterial=lineMaterial
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property TriangleMaterial:Material()
+	
+		Return _triangleMaterial
+	
+	Setter( triangleMaterial:Material )
+	
+		_triangleMaterial=triangleMaterial
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property QuadMaterial:Material()
+
+		Return _quadMaterial
+
+	Setter( quadMaterial:Material )
+
+		_quadMaterial=quadMaterial
+	End
+	
+	Method PushMatrix()
+	
+		_matrixStack.Push( Matrix )
+	End
+	
+	Method PopMatrix()
+	
+		Matrix=_matrixStack.Pop()
+	End
+	
+	Method ClearMatrix()
+	
+		_matrixStack.Clear()
+		
+		Matrix=New AffineMat3f
+	End
+	
+	Method Translate( tx:Float,ty:Float )
+	
+		Matrix=Matrix.Translate( tx,ty )
+	End
+	
+	Method Rotate( rz:Float )
+	
+		Matrix=Matrix.Rotate( rz )
+	End
+	
+	Method Scale( sx:Float,sy:Float )
+	
+		Matrix=Matrix.Scale( sx,sy )
+	End
+	
+	Method DrawPoint( v0:Vec2f )
+		AddDrawOp( _pointMaterial,1,1 )
+		AddVertex( v0.x+.5,v0.y+.5,0,0 )
+	End
+	
+	Method DrawPoint( x0:Float,y0:Float )
+		AddDrawOp( _pointMaterial,1,1 )
+		AddVertex( x0+.5,y0+.5,0,0 )
+	End
+	
+	Method DrawLine( v0:Vec2f,v1:Vec2f )
+		AddDrawOp( _lineMaterial,2,1 )
+		AddVertex( v0.x+.5,v0.y+.5,0,0 )
+		AddVertex( v1.x+.5,v1.y+.5,1,1 )
+	End
+	
+	Method DrawLine( x0:Float,y0:Float,x1:Float,y1:Float )
+		AddDrawOp( _lineMaterial,2,1 )
+		AddVertex( x0+.5,y0+.5,0,0 )
+		AddVertex( x1+.5,y1+.5,1,1 )
+	End
+	
+	Method DrawTriangle( v0:Vec2f,v1:Vec2f,v2:Vec2f )
+		AddDrawOp( _triangleMaterial,3,1 )
+		AddVertex( v0.x,v0.y,.5,0 )
+		AddVertex( v1.x,v1.y,1,1 )
+		AddVertex( v2.x,v2.y,0,1 )
+	End
+	
+	Method DrawTriangle( x0:Float,y0:Float,x1:Float,y1:Float,x2:Float,y2:Float )
+		AddDrawOp( _triangleMaterial,3,1 )
+		AddVertex( x0,y0,0,0 )
+		AddVertex( x1,y1,1,0 )
+		AddVertex( x2,y2,1,1 )
+	End
+	
+	Method DrawQuad( x0:Float,y0:Float,x1:Float,y1:Float,x2:Float,y2:Float,x3:Float,y3:Float )
+		AddDrawOp( _quadMaterial,4,1 )
+		AddVertex( x0,y0,0,0 )
+		AddVertex( x1,y1,1,0 )
+		AddVertex( x2,y2,1,1 )
+		AddVertex( x3,y3,0,1 )
+	End
+	
+	Method DrawRect( rect:Rectf )
+		AddDrawOp( _quadMaterial,4,1 )
+		AddVertex( rect.min.x,rect.min.y,0,0 )
+		AddVertex( rect.max.x,rect.min.y,1,0 )
+		AddVertex( rect.max.x,rect.max.y,1,1 )
+		AddVertex( rect.min.x,rect.max.y,0,1 )
+	End
+	
+	Method DrawRect( x:Float,y:Float,width:Float,height:Float )
+		DrawRect( New Rectf( x,y,x+width,y+height ) )
+	End
+	
+	Method DrawRect( rect:Rectf,srcImage:Image )
+		AddDrawOp( srcImage.Material,4,1 )
+		AddVertex( rect.min.x,rect.min.y,srcImage.S0,srcImage.T0 )
+		AddVertex( rect.max.x,rect.min.y,srcImage.S1,srcImage.T0 )
+		AddVertex( rect.max.x,rect.max.y,srcImage.S1,srcImage.T1 )
+		AddVertex( rect.min.x,rect.max.y,srcImage.S0,srcImage.T1 )
+	End
+	
+	Method DrawRect( x:Float,y:Float,width:Float,height:Float,srcImage:Image )
+		DrawRect( New Rectf( x,y,x+width,y+height ),srcImage )
+	End
+	
+	Method DrawRect( rect:Rectf,srcImage:Image,srcRect:Recti )
+		Local s0:=Float(srcImage.RenderRect.min.x+srcRect.min.x)/srcImage.Texture.Width
+		Local t0:=Float(srcImage.RenderRect.min.y+srcRect.min.y)/srcImage.Texture.Height
+		Local s1:=Float(srcImage.RenderRect.min.x+srcRect.max.x)/srcImage.Texture.Width
+		Local t1:=Float(srcImage.RenderRect.min.y+srcRect.max.y)/srcImage.Texture.Height
+		AddDrawOp( srcImage.Material,4,1 )
+		AddVertex( rect.min.x,rect.min.y,s0,t0 )
+		AddVertex( rect.max.x,rect.min.y,s1,t0 )
+		AddVertex( rect.max.x,rect.max.y,s1,t1 )
+		AddVertex( rect.min.x,rect.max.y,s0,t1 )
+	End
+	
+	Method DrawRect( x:Float,y:Float,width:Float,height:Float,srcImage:Image,srcX:Int,srcY:Int,srcWidth:Int,srcHeight:Int )
+		DrawRect( New Rectf( x,y,x+width,y+height ),srcImage,New Recti( srcX,srcY,srcX+srcWidth,srcY+srcHeight ) )
+	End
+	
+	Method DrawImage( image:Image,tx:Float,ty:Float )
+		AddDrawOp( image.Material,4,1 )
+		AddVertex( image.X0+tx,image.Y0+ty,image.S0,image.T0 )
+		AddVertex( image.X1+tx,image.Y0+ty,image.S1,image.T0 )
+		AddVertex( image.X1+tx,image.Y1+ty,image.S1,image.T1 )
+		AddVertex( image.X0+tx,image.Y1+ty,image.S0,image.T1 )
+	End
+	
+	Method DrawImage( image:Image,trans:Vec2f )
+		DrawImage( image,trans.x,trans.y )
+	End
+
+	Method DrawImage( image:Image,tx:Float,ty:Float,rz:Float )
+		Local matrix:=_matrix
+		Translate( tx,ty )
+		Rotate( rz )
+		AddDrawOp( image.Material,4,1 )
+		AddVertex( image.X0,image.Y0,image.S0,image.T0 )
+		AddVertex( image.X1,image.Y0,image.S1,image.T0 )
+		AddVertex( image.X1,image.Y1,image.S1,image.T1 )
+		AddVertex( image.X0,image.Y1,image.S0,image.T1 )
+		_matrix=matrix
+	End
+
+	Method DrawImage( image:Image,trans:Vec2f,rz:Float )
+		DrawImage( image,trans.x,trans.y,rz )
+	End
+
+	
+	Method DrawImage( image:Image,tx:Float,ty:Float,rz:Float,sx:Float,sy:Float )
+		Local matrix:=_matrix
+		Translate( tx,ty )
+		Rotate( rz )
+		Scale( sx,sy )
+		AddDrawOp( image.Material,4,1 )
+		AddVertex( image.X0,image.Y0,image.S0,image.T0 )
+		AddVertex( image.X1,image.Y0,image.S1,image.T0 )
+		AddVertex( image.X1,image.Y1,image.S1,image.T1 )
+		AddVertex( image.X0,image.Y1,image.S0,image.T1 )
+		_matrix=matrix
+	End
+
+	Method DrawImage( image:Image,trans:Vec2f,rz:Float,scale:Vec2f )
+		DrawImage( image,trans.x,trans.y,rz,scale.x,scale.y )
+	End
+	
+	Method DrawText( text:String,tx:Float,ty:Float,handleX:Float=0,handleY:Float=0 )
+	
+		tx-=_font.TextWidth( text ) * handleX
+		ty-=_font.Height * handleY
+	
+		Local image:=_font.Image
+		Local sx:=image.RenderRect.min.x,sy:=image.RenderRect.min.y
+		Local tw:=image.Texture.Width,th:=image.Texture.Height
+		
+		AddDrawOp( image.Material,4,text.Length )
+		
+		For Local char:=Eachin text
+		
+			Local g:=_font.GetGlyph( char )
+			
+			Local s0:=Float(g.rect.min.x+sx)/tw
+			Local t0:=Float(g.rect.min.y+sy)/th
+			Local s1:=Float(g.rect.max.x+sx)/tw
+			Local t1:=Float(g.rect.max.y+sy)/th
+
+			Local x0:=tx+g.offset.x,x1:=x0+g.rect.Width
+			Local y0:=ty+g.offset.y,y1:=y0+g.rect.Height
+			
+			'Integerize font coods!
+			x0=Round( x0 );y0=Round( y0 );x1=Round( x1 );y1=Round( y1 )
+			
+			AddVertex( x0,y0,s0,t0 )
+			AddVertex( x1,y0,s1,t0 )
+			AddVertex( x1,y1,s1,t1 )
+			AddVertex( x0,y1,s0,t1 )
+			
+			tx+=g.advance
+		Next
+	End
+	
+	Private
+	
+	Enum Dirty
+		Target=1
+		Scissor=2
+		EnvParams=4
+		All=7
+	End
+	
+	Global _ambientEnv:ShaderEnv
+	Global _nullShader:Shader
+	Global _defaultFont:Font
+
+	Field _dirty:Dirty
+	Field _rendering:Bool
+	Field _target:Texture
+	Field _targetSize:Vec2i
+	Field _targetRect:Recti
+	Field _envParams:ParamBuffer
+	Field _device:GraphicsDevice
+	
+	Field _viewport:Recti
+	Field _scissor:Recti
+	Field _viewMatrix:Mat4f
+	Field _modelMatrix:Mat4f
+	Field _ambientLight:Color
+	Field _renderColor:Color
+	
+	Field _renderMatrix:AffineMat3f
+	Field _renderBounds:Recti
+	
+	Field _font:Font
+	Field _alpha:Float
+	Field _color:Color
+	Field _pmcolor:UInt
+	Field _matrix:AffineMat3f
+	Field _blendMode:BlendMode
+	Field _matrixStack:=New Stack<AffineMat3f>
+	
+	Field _ops:=New Stack<DrawOp>
+	Field _op:=New DrawOp
+	
+	Field _vertices:=New Stack<Vertex2f>
+	Field _vertexData:Vertex2f[]
+	Field _vertex:Int
+	
+	Field _pointMaterial:Material
+	Field _lineMaterial:Material
+	Field _triangleMaterial:Material
+	Field _quadMaterial:Material
+	
+	Method Init( target:Texture,size:Vec2i,viewport:Recti )
+	
+		If Not _device
+			_ambientEnv=New ShaderEnv( stringio.LoadString( "asset::mojo/shaderenv_ambient.glsl" ) )
+			_defaultFont=Font.Load( "asset::mojo/RobotoMono-Regular.ttf",16 )
+			_nullShader=Shader.GetShader( "null" )
+		Endif
+
+		_target=target
+		_targetSize=size
+		_targetRect=viewport
+		
+		_envParams=New ParamBuffer
+		_device=New GraphicsDevice
+		_rendering=False
+		
+		_viewport=New Recti( 0,0,_targetRect.Width,_targetRect.Height )
+		_scissor=New Recti( 0,0,16384,16384 )
+		_viewMatrix=New Mat4f
+		_modelMatrix=New Mat4f
+		_ambientLight=Color.Black
+		_renderColor=Color.White
+		_renderMatrix=New AffineMat3f
+		_renderBounds=New Recti( 0,0,_targetRect.Width,_targetRect.Height )
+		
+		Font=Null
+		Alpha=1
+		Color=Color.White
+		Matrix=New AffineMat3f
+		BlendMode=BlendMode.Alpha
+		PointMaterial=New Material( _nullShader )
+		LineMaterial=New Material( _nullShader )
+		TriangleMaterial=New Material( _nullShader )
+		QuadMaterial=New Material( _nullShader )
+		
+		BeginRender()
+	End
+	
+	Method Validate()
+
+		If Not _dirty Return
+		
+		If _dirty & Dirty.Target
+
+			Local projMatrix:Mat4f
+			Local viewport:=_targetRect
+	
+			If _target
+				projMatrix=Mat4f.Ortho( 0,viewport.Width,0,viewport.Height,-1,1 )
+			Else
+				viewport.min.y=_targetSize.y-viewport.max.y
+				viewport.max.y=viewport.min.y+viewport.Height
+				projMatrix=Mat4f.Ortho( 0,viewport.Width,viewport.Height,0,-1,1 )
+			Endif
+		
+			_device.RenderTarget=_target
+			_device.Viewport=_targetRect
+			_envParams.SetMatrix( "mx2_ProjectionMatrix",projMatrix )
+			
+			_dirty|=Dirty.EnvParams
+			
+		Endif
+		
+		If _dirty & Dirty.Scissor
+		
+'			Local viewport:=TransformRecti( _viewport & _scissor,_renderMatrix )
+			Local viewport:=TransformRecti( _viewport & (_scissor+_viewport.Origin),_renderMatrix )
+			
+			Local scissor:=(viewport & _renderBounds)+_targetRect.Origin
+			
+			If Not _target
+				Local h:=scissor.Height
+				scissor.min.y=_targetSize.y-scissor.max.y
+				scissor.max.y=scissor.min.y+h
+			Endif
+			
+			_device.Scissor=scissor
+		
+		Endif
+		
+		If _dirty & Dirty.EnvParams
+		
+			Local renderMatrix:=_renderMatrix.Translate( New Vec2f( _viewport.X,_viewport.Y ) )
+
+			Local modelViewMatrix:=_viewMatrix * _modelMatrix * New Mat4f( renderMatrix )
+			
+			_envParams.SetMatrix( "mx2_ModelViewMatrix",modelViewMatrix )
+			_envParams.SetColor( "mx2_AmbientLight",_ambientLight )
+			_envParams.SetColor( "mx2_RenderColor",_renderColor )
+
+			_device.EnvParams=_envParams
+		Endif
+		
+		_dirty=Null
+	End
+	
+	Method RenderDrawOps()
+	
+		Local p:=_vertexData.Data
+	
+		For Local op:=Eachin _ops
+			_device.BlendMode=op.blendMode
+			_device.Shader=op.material.Shader
+			_device.Params=op.material.Params
+			_device.Render( p,op.order,op.count )
+			p=p+op.order*op.count
+		Next
+	
+	End
+	
+	Method ClearDrawOps()
+		_ops.Clear()
+		_vertices.Clear()
+		_vertexData=_vertices.Data
+		_op=New DrawOp
+		_vertex=0
+	End
+	
+	Method AddDrawOp( material:Material,order:Int,count:Int )
+	
+		_vertices.Resize( _vertex+order*count )
+		_vertexData=_vertices.Data
+		
+		If _blendMode=_op.blendMode And material=_op.material And order=_op.order
+			_op.count+=count
+			Return
+		End
+		
+		_op=New DrawOp
+		_op.blendMode=_blendMode
+		_op.material=material
+		_op.order=order
+		_op.count=count
+		_ops.Add( _op )
+	End
+	
+	Method AddVertex( tx:Float,ty:Float,s0:Float,t0:Float )
+	
+		_vertexData[_vertex].x=_matrix.i.x * tx + _matrix.j.x * ty + _matrix.t.x
+		_vertexData[_vertex].y=_matrix.i.y * tx + _matrix.j.y * ty + _matrix.t.y
+		_vertexData[_vertex].s0=s0
+		_vertexData[_vertex].t0=t0
+		_vertexData[_vertex].ix=_matrix.i.x
+		_vertexData[_vertex].iy=_matrix.i.y
+		_vertexData[_vertex].color=_pmcolor
+
+		_vertex+=1
+	End
+
+End

+ 299 - 0
modules/mojo/graphics/device.monkey2

@@ -0,0 +1,299 @@
+
+Namespace mojo.graphics
+
+#rem monkeydoc Blend modes.
+
+Blend modes are used with the [[Canvas.BlendMode]] property.
+
+| BlendMode	| Description
+|:----------|:-----------
+| Opaque	| Blending disabled.
+| Alpha		| Alpha blending.
+| Multiply	| Multiply blending.
+| Additive	| Additive blending.
+
+#end
+Enum BlendMode
+	Opaque=0
+	Alpha=1
+	Additive=2
+	Multiply=3
+End
+
+#rem monkeydoc @hidden
+#end
+Class GraphicsDevice
+
+	Method New()
+		RenderTarget=Null
+		Viewport=New Recti( 0,0,640,480 )
+		Scissor=New Recti( 0,0,16384,16384 )
+		BlendMode=BlendMode.Alpha
+	End
+	
+	Property RenderTarget:Texture()
+	
+		Return _target
+	
+	Setter( renderTarget:Texture )
+	
+		_target=renderTarget
+		
+		_dirty|=Dirty.Target
+	End
+	
+	Property Viewport:Recti()
+	
+		Return _viewport
+	
+	Setter( viewport:Recti )
+	
+		_viewport=viewport
+		
+		_dirty|=Dirty.Viewport|Dirty.Scissor
+	End
+	
+	Property Scissor:Recti()
+	
+		Return _scissor
+	
+	Setter( scissor:Recti )
+	
+		_scissor=scissor
+		
+		_dirty|=Dirty.Scissor
+	End
+	
+	Property BlendMode:BlendMode()
+	
+		Return _blendMode
+	
+	Setter( blendMode:BlendMode )
+	
+		_blendMode=blendMode
+		
+		_dirty|=Dirty.BlendMode
+	End
+	
+	Property ShaderEnv:ShaderEnv()
+	
+		Return _shaderEnv
+	
+	Setter( shaderEnv:ShaderEnv )
+	
+		_shaderEnv=shaderEnv
+		
+		_dirty|=Dirty.Shader|Dirty.EnvParams|Dirty.Params
+	End
+	
+	Property EnvParams:ParamBuffer()
+	
+		Return _envParams
+	
+	Setter( envParams:ParamBuffer )
+	
+		_envParams=envParams
+		
+		_dirty|=Dirty.EnvParams
+	End
+	
+	Property Shader:Shader()
+	
+		Return _shader
+	
+	Setter( shader:Shader )
+	
+		_shader=shader
+		
+		_dirty|=Dirty.Shader|Dirty.EnvParams|Dirty.Params
+	End
+	
+	Property Params:ParamBuffer()
+	
+		Return _params
+	
+	Setter( params:ParamBuffer )
+	
+		_params=params
+		
+		_dirty|=Dirty.Params
+	End
+	
+	Method Clear( color:Color )
+	
+		Validate()
+		
+		If _rscissor<>_windowRect
+			glEnable( GL_SCISSOR_TEST )
+			glScissor( _rscissor.X,_rscissor.Y,_rscissor.Width,_rscissor.Height )
+		Else
+			glDisable( GL_SCISSOR_TEST )
+		Endif
+		
+		glClearColor( color.r,color.g,color.b,color.a )
+
+		glClear( GL_COLOR_BUFFER_BIT )
+		
+		If _rscissor<>_viewport
+			glEnable( GL_SCISSOR_TEST )
+			glScissor( _rscissor.X,_rscissor.Y,_rscissor.Width,_rscissor.Height )
+		Else
+			glDisable( GL_SCISSOR_TEST )
+		Endif
+	End
+	
+	Method Render( vertices:Vertex2f Ptr,order:Int,count:Int )
+	
+		Validate()
+		
+		Local n:=order*count
+		
+		If n>_vertices.Length 
+			_vertices=New Vertex2f[n]
+			Local p:=Cast<UByte Ptr>( _vertices.Data )
+			glEnableVertexAttribArray( 0 ) ; glVertexAttribPointer( 0,2,GL_FLOAT,False,BYTES_PER_VERTEX,p )
+			glEnableVertexAttribArray( 1 ) ; glVertexAttribPointer( 1,2,GL_FLOAT,False,BYTES_PER_VERTEX,p+8 )
+			glEnableVertexAttribArray( 2 ) ; glVertexAttribPointer( 2,2,GL_FLOAT,False,BYTES_PER_VERTEX,p+16 )
+			glEnableVertexAttribArray( 3 ) ; glVertexAttribPointer( 3,4,GL_UNSIGNED_BYTE,True,BYTES_PER_VERTEX,p+24 )
+		Endif
+
+		libc.memcpy( _vertices.Data,vertices,n*BYTES_PER_VERTEX )
+		
+		Select order
+		Case 1
+			glDrawArrays( GL_POINTS,0,n )
+		Case 2
+			glDrawArrays( GL_LINES,0,n )
+		Case 3
+			glDrawArrays( GL_TRIANGLES,0,n )
+		Case 4
+			Local n:=count*6
+			If n>_qindices.Length
+				_qindices=New UShort[n]
+				For Local i:=0 Until count
+					_qindices[i*6+0]=i*4
+					_qindices[i*6+1]=i*4+1
+					_qindices[i*6+2]=i*4+2
+					_qindices[i*6+3]=i*4
+					_qindices[i*6+4]=i*4+2
+					_qindices[i*6+5]=i*4+3
+				Next
+			Endif
+			glDrawElements( GL_TRIANGLES,n,GL_UNSIGNED_SHORT,_qindices.Data )
+		End
+		
+	End
+
+	Private
+	
+	Enum Dirty
+		Target=			$0001
+		Viewport=		$0002
+		Scissor=		$0004
+		BlendMode=		$0008
+		Shader=			$0010
+		EnvParams=		$0020
+		Params=			$0040
+		All=			$007f
+	End
+	
+	Field _dirty:Dirty=Dirty.All
+	Field _target:Texture
+	Field _windowRect:Recti
+	Field _viewport:Recti
+	Field _scissor:Recti
+	Field _blendMode:BlendMode
+	Field _shaderEnv:ShaderEnv
+	Field _envParams:ParamBuffer
+	Field _shader:Shader
+	Field _params:ParamBuffer
+	
+	Field _rscissor:Recti
+
+	Global _seq:Int
+	Global _current:GraphicsDevice
+	Global _defaultFbo:GLint
+	
+	Global _vertices:Vertex2f[]
+	Global _qindices:UShort[]
+	
+	Const BYTES_PER_VERTEX:=28
+	
+	Function InitGLState()
+		glGetIntegerv( GL_FRAMEBUFFER_BINDING,Varptr _defaultFbo )
+	End
+	
+	Method Validate()
+	
+		If _seq<>glGraphicsSeq
+			_seq=glGraphicsSeq
+			_current=Null
+			InitGLState()
+		Endif
+	
+		If _current=Self 
+			If Not _dirty Return
+		Else
+			_current=Self
+			_dirty=Dirty.All
+		Endif
+		
+		If _dirty & Dirty.Target
+			If _target
+				glBindFramebuffer( GL_FRAMEBUFFER,_target.GLFramebuffer )
+			Else
+				glBindFramebuffer( GL_FRAMEBUFFER,_defaultFbo )
+			Endif
+		Endif
+		
+		If _dirty & Dirty.Viewport
+			glViewport( _viewport.X,_viewport.Y,_viewport.Width,_viewport.Height )
+		Endif
+		
+		If _dirty & Dirty.Scissor
+			_rscissor=_scissor & _viewport
+			If _rscissor<>_viewport
+				glEnable( GL_SCISSOR_TEST )
+				glScissor( _rscissor.X,_rscissor.Y,_rscissor.Width,_rscissor.Height )
+			Else
+				glDisable( GL_SCISSOR_TEST )
+			Endif
+		Endif
+
+		If _dirty & Dirty.BlendMode		
+			Select _blendMode
+			Case BlendMode.Opaque
+				glDisable( GL_BLEND )
+			Case BlendMode.Alpha
+				glEnable( GL_BLEND )
+				glBlendFunc( GL_ONE,GL_ONE_MINUS_SRC_ALPHA )
+			Case BlendMode.Additive
+				glEnable( GL_BLEND )
+				glBlendFunc( GL_ONE,GL_ONE )
+			Case BlendMode.Multiply
+				glEnable( GL_BLEND )
+				glBlendFunc( GL_DST_COLOR,GL_ONE_MINUS_SRC_ALPHA )
+			End
+		Endif
+		
+		If _shader And _shaderEnv And _envParams And _params
+		
+			If _dirty & Dirty.Shader
+				_shader.Bind( _shaderEnv )
+			Endif
+			
+			If _dirty & Dirty.EnvParams
+				_shader.BindEnvParams( _envParams )
+			Endif
+			
+			If _dirty & Dirty.Params
+				_shader.BindParams( _params )
+			End
+			
+		Endif
+		
+		_dirty=Null
+		
+	End
+
+End

+ 106 - 0
modules/mojo/graphics/font.monkey2

@@ -0,0 +1,106 @@
+
+Namespace mojo.graphics
+
+#rem monkeydoc The Glyph struct.
+#end
+Struct Glyph
+
+	Field rect:Recti
+	Field offset:Vec2f
+	Field advance:Float
+	
+	Method New( rect:Recti,offset:Vec2f,advance:Float )
+		Self.rect=rect
+		Self.offset=offset
+		Self.advance=advance
+	End
+
+End
+
+#rem monkeydoc The Font class.
+#end
+Class Font
+
+	Method New( image:Image,height:Float,firstChar:Int,glyphs:Glyph[] )
+		_image=image
+		_height=height
+		_firstChar=firstChar
+		_glyphs=glyphs
+	End
+	
+	Property Image:Image()
+	
+		Return _image
+	End
+	
+	Property Height:Float()
+	
+		Return _height
+	End
+	
+	Property FirstChar:Int()
+	
+		Return _firstChar
+	End
+	
+	Property NumChars:Int()
+	
+		Return _glyphs.Length
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property Glyphs:Glyph[]()
+	
+		Return _glyphs
+	End
+	
+	Method GetGlyph:Glyph( char:Int )
+		If char>=_firstChar And char<_firstChar+_glyphs.Length Return _glyphs[ char-_firstChar ]
+		Return _glyphs[0]
+	End
+	
+	Method TextWidth:Float( text:String )
+		Local w:=0.0
+		For Local char:=Eachin text
+			w+=GetGlyph( char ).advance
+		Next
+		Return w
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Function Open:Font( path:String,height:Float )
+	
+		Local tag:=RealPath( path )+":"+height
+		
+		Local font:=_openFonts[tag]
+		If Not font
+			font=Load( path,height )
+			_openFonts[tag]=font
+		Endif
+		
+		Return font
+	End
+
+	'Make this ALWAYS work!	
+	Function Load:Font( path:String,height:Float )
+	
+		Local font:=fontloader.LoadFont( path,height )
+		
+		Return font
+	End
+
+	Private
+	
+	Private
+	
+	Field _image:Image
+	Field _height:Float
+	Field _firstChar:Int
+	Field _glyphs:Glyph[]
+	
+	Global _openFonts:=New StringMap<Font>
+
+End
+

+ 103 - 0
modules/mojo/graphics/fontloader_freetype.monkey2

@@ -0,0 +1,103 @@
+
+Namespace mojo.graphics.fontloader
+
+#Import "<freetype>"
+
+Private
+
+Using freetype
+
+Global FreeType:FT_Library
+
+Public
+
+#rem monkeydoc @hidden
+#end
+Function LoadFont:Font( path:String,fheight:Float )
+
+	If Not FreeType And FT_Init_FreeType( Varptr FreeType ) Return Null
+	
+	Local data:=DataBuffer.Load( path )
+	If Not data Return Null
+	
+	Local face:FT_Face
+	
+	If FT_New_Memory_Face( FreeType,data.Data,data.Length,0,Varptr face ) 
+		data.Discard()
+		Return Null
+	Endif
+	
+	Local size_req:FT_Size_RequestRec
+	
+	size_req.type=FT_SIZE_REQUEST_TYPE_REAL_DIM
+	size_req.width=0
+	size_req.height=fheight * 64
+	size_req.horiResolution=0
+	size_req.vertResolution=0
+	
+	If FT_Request_Size( face,Varptr size_req )
+		data.Discard()
+		Return Null
+	Endif
+	
+	Local height:=(face->size->metrics.height+32) Shr 6
+	Local ascent:=(face->size->metrics.ascender+32) Shr 6
+
+'	Print face->size->metrics.height/64.0
+'	Print face->size->metrics.ascender/64.0
+'	Print face->size->metrics.descender/64.0
+
+	Local firstChar:=32,numChars:=96
+
+	Local glyphs:=New Glyph[numChars]
+	Local pixmap:=New Pixmap( 512,512,PixelFormat.A8 )
+	pixmap.Clear( Color.None )
+	
+	Local slot:=face->glyph
+	
+	Local x:=0,y:=0,h:=0
+	
+	For Local i:=0 Until numChars
+	
+		If FT_Load_Char( face,firstChar+i,FT_LOAD_RENDER )
+'		If FT_Load_Char( face,firstChar+i,FT_LOAD_RENDER|FT_LOAD_NO_HINTING )
+'		If FT_Load_Char( face,firstChar+i,FT_LOAD_RENDER|FT_LOAD_FORCE_AUTOHINT )
+			Continue
+		Endif
+		
+		#rem
+		If FT_Render_Glyph( slot,FT_RENDER_MODE_NORMAL )
+			Continue
+		Endif
+		#end
+		
+		Local gw:=slot->bitmap.width
+		Local gh:=slot->bitmap.rows
+		
+		If x+gw+1>pixmap.Width
+			y+=h
+			h=0
+			x=0
+		Endif
+		
+		Local tmp:=New Pixmap( gw,gh,PixelFormat.A8,slot->bitmap.buffer,slot->bitmap.pitch,Null )
+		
+		pixmap.Paste( tmp,x,y )
+		
+		glyphs[i]=New Glyph( New Recti( x,y,x+gw,y+gh ),New Vec2f( slot->bitmap_left,ascent-slot->bitmap_top ),slot->advance.x Shr 6 )
+
+		h=Max( Int(h),Int(gh)+1 )
+		x+=gw+1
+	Next
+	
+	FT_Done_Face( face )
+	
+	data.Discard()
+	
+	Local image:=New Image( pixmap,Shader.GetShader( "font" ) )
+	
+	Local font:=New Font( image,height,firstChar,glyphs )
+	
+	Return font
+
+End

+ 57 - 0
modules/mojo/graphics/fontloader_stb.monkey2

@@ -0,0 +1,57 @@
+
+Namespace mojo.graphics.fontloader
+
+#Import "<stb-truetype>"
+
+Using stb.truetype
+
+#rem monkeydoc @hidden
+#end
+Function LoadFont:Font( path:String,height:Float )
+
+	Local firstChar:=32
+	Local numChars:=96
+
+	Local data:=DataBuffer.Load( path )
+	If Not data Return Null
+	
+	'Get font info
+	Local f:stbtt_fontinfo,fp:=Varptr f
+	If Not stbtt_InitFont( fp,data.Data,0 ) Return Null
+
+	Local ascenti:Int,descenti:Int,linegapi:Int
+	stbtt_GetFontVMetrics( fp,Varptr ascenti,Varptr descenti,Varptr linegapi )
+	
+	Local scale:=stbtt_ScaleForPixelHeight( fp,height )
+
+	Local ascent:=ascenti*scale,descent:=descenti*scale,linegap:=linegapi*scale
+	
+	Local fheight:=(ascenti-descenti+linegapi)*scale
+	
+	'Bake the chars		
+	Local bakedChars:=New stbtt_bakedchar[numChars]
+	Local pixmap:=New Pixmap( 512,512,PixelFormat.A8 )
+	stbtt_BakeFontBitmap( data.Data,0,height,pixmap.Data,512,512,firstChar,numChars,bakedChars.Data )
+
+	Local image:=New Image( pixmap,Shader.GetShader( "font" ) )
+	
+	Local glyphs:=New Glyph[numChars]
+	
+	For Local i:=0 Until numChars
+	
+		Local x:=bakedChars[i].x0
+		Local y:=bakedChars[i].y0
+		Local w:=bakedChars[i].x1-x
+		Local h:=bakedChars[i].y1-y
+		Local xoffset:=bakedChars[i].xoff
+		Local yoffset:=bakedChars[i].yoff+ascent
+		Local advance:=bakedChars[i].xadvance
+		
+		glyphs[i]=New Glyph( New Recti( x,y,x+w,y+h ),New Vec2f( xoffset,yoffset ),advance )
+		
+	Next
+	
+	Local font:=New Font( image,fheight,firstChar,glyphs )
+	
+	Return font
+End

+ 93 - 0
modules/mojo/graphics/glutil.monkey2

@@ -0,0 +1,93 @@
+
+Namespace mojo.graphics.glutil
+
+Private
+
+Global tmpi:Int
+
+Public
+
+#rem monkeydoc @hidden
+#end
+Global glGraphicsSeq:Int=1
+
+#rem monkeydoc @hidden
+#end
+Function glCheck()
+	Local err:=glGetError()
+	If err=GL_NO_ERROR Return
+	Assert( False,"GL ERROR! err="+err )
+End
+
+#rem monkeydoc @hidden
+#end
+Function glFormat:GLenum( format:PixelFormat )
+	Select format
+	Case PixelFormat.A8 Return GL_ALPHA
+	Case PixelFormat.I8 Return GL_LUMINANCE
+	Case PixelFormat.IA16 Return GL_LUMINANCE_ALPHA
+	Case PixelFormat.RGB24 Return GL_RGB
+	Case PixelFormat.RGBA32 Return GL_RGBA
+	End
+	Assert( False,"Invalidate PixelFormat" )
+	Return GL_RGBA
+End
+
+#rem monkeydoc @hidden
+#end
+Function glPushTexture2d:Void( tex:Int )
+	glGetIntegerv( GL_TEXTURE_BINDING_2D,Varptr tmpi )
+	glBindTexture( GL_TEXTURE_2D,tex )
+End
+
+#rem monkeydoc @hidden
+#end
+Function glPopTexture2d:Void()
+	glBindTexture( GL_TEXTURE_2D,tmpi )
+End
+
+#rem monkeydoc @hidden
+#end
+Function glPushFramebuffer:Void( framebuf:Int )
+	glGetIntegerv( GL_FRAMEBUFFER_BINDING,Varptr tmpi )
+	glBindFramebuffer( GL_FRAMEBUFFER,framebuf )
+End
+
+#rem monkeydoc @hidden
+#end
+Function glPopFramebuffer:Void()
+	glBindFramebuffer( GL_FRAMEBUFFER,tmpi )
+End
+
+#rem monkeydoc @hidden
+#end
+Function glCompile:Int( type:Int,source:String )
+
+	#If __TARGET__="emscripten"	Or (__TARGET__="desktop" And __HOSTOS__="windows")
+'	#If TARGET<>"glfw" Or GLFW_USE_ANGLE_GLES20
+		source="precision mediump float;~n"+source
+	#Endif
+	
+	Local shader:=glCreateShader( type )
+	glShaderSourceEx( shader,source )
+	glCompileShader( shader )
+	glGetShaderiv( shader,GL_COMPILE_STATUS,Varptr tmpi )
+	If Not tmpi
+		Print "Failed to compile fragment shader:"+glGetShaderInfoLogEx( shader )
+		Print source
+'		Local lines:=source.Split( "~n" )
+'		For Local i:=0 Until lines.Length
+'			Print (i+1)+":~t"+lines[i]
+'		Next
+		Assert( False,"Compile fragment shader failed" )
+	Endif
+	Return shader
+End
+
+#rem monkeydoc @hidden
+#end
+Function glLink:Void( program:Int )
+	glLinkProgram( program )
+	glGetProgramiv( program,GL_LINK_STATUS,Varptr tmpi )
+	If Not tmpi Assert( False,"Failed to link program:"+glGetProgramInfoLogEx( program ) )
+End

+ 214 - 0
modules/mojo/graphics/image.monkey2

@@ -0,0 +1,214 @@
+
+Namespace mojo.graphics
+
+#rem monkeydoc The Image class.
+#end
+Class Image
+
+	Method New( pixmap:Pixmap,shader:Shader=Null )
+	
+		Local texture:=New Texture( pixmap )
+		
+		Init( Null,texture,texture.Rect,shader )
+	End
+	
+	Method New( width:Int,height:Int,shader:Shader=Null )
+	
+		Local texture:=New Texture( width,height )
+		
+		Init( Null,texture,texture.Rect,shader )
+	End
+	
+	Method New( image:Image,rect:Recti )
+	
+		Init( image._material,image._texture,rect,Null )
+	End
+
+	#rem monkeydoc @hidden
+	#end
+	Method New( texture:Texture,shader:Shader=Null )
+	
+		Init( Null,texture,texture.Rect,shader )
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method New( material:Material,texture:Texture,rect:Recti )
+	
+		Init( material,texture,rect,Null )
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property Material:Material()
+	
+		Return _material
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property Texture:Texture()
+	
+		Return _texture
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property RenderRect:Recti()
+	
+		Return _renderRect
+	End
+	
+	Property Handle:Vec2f()
+	
+		Return _handle
+		
+	Setter( handle:Vec2f )
+	
+		_handle=handle
+		
+		UpdateVertices()
+	End
+	
+	Property Scale:Vec2f()
+	
+		Return _scale
+	
+	Setter( scale:Vec2f )
+	
+		_scale=scale
+		
+		UpdateVertices()
+	End
+	
+	Property Width:Float()
+	
+		Return _x1-_x0
+	End
+	
+	Property Height:Float()
+	
+		Return _y1-_y0
+	End
+	
+	Property Radius:Float()
+	
+		Return _radius
+	End
+	
+	Property X0:Float()
+	
+		Return _x0
+	End
+	
+	Property Y0:Float()
+	
+		Return _y0
+	End
+	
+	Property X1:Float()
+	
+		Return _x1
+	End
+	
+	Property Y1:Float()
+	
+		Return _y1
+	End
+	
+	Property S0:Float()
+	
+		Return _s0
+	End
+	
+	Property T0:Float()
+	
+		Return _t0
+	End
+	
+	Property S1:Float()
+	
+		Return _s1
+	End
+	
+	Property T1:Float()
+	
+		Return _t1
+	End
+	
+	Function Load:Image( path:String,shader:Shader=Null )
+	
+		Local texture:=mojo.graphics.Texture.Load( path )
+		If Not texture Return Null
+		
+		Local file:=StripExt( path )
+		Local ext:=ExtractExt( path )
+		
+		Local specular:=mojo.graphics.Texture.Load( file+"_SPECULAR"+ext )
+		Local normal:=mojo.graphics.Texture.Load( file+"_NORMALS"+ext )
+		
+		If Not shader shader=Shader.GetShader( "sprite" )
+		
+		Local material:=New Material( shader )
+		
+		material.SetTexture( "u_Texture0",texture )
+		If specular material.SetTexture( "u_Texture1",specular )
+		If normal material.SetTexture( "u_Texture2",normal )
+		
+		Return New Image( material,texture,texture.Rect )
+	End
+	
+	Private
+	
+	Field _material:Material
+	Field _texture:Texture
+	Field _renderRect:Recti
+	
+	Field _handle:=New Vec2f( 0,0 )
+	Field _scale:=New Vec2f( 1,1 )
+	Field _radius:Float
+	Field _x0:Float
+	Field _y0:Float
+	Field _x1:Float
+	Field _y1:Float
+	Field _s0:Float
+	Field _t0:Float
+	Field _s1:Float
+	Field _t1:Float
+	
+	Method Init( material:Material,texture:Texture,rect:Recti,shader:Shader )
+		
+		If Not material
+			If Not shader shader=Shader.GetShader( "sprite" )
+			material=New Material( shader )
+			material.SetTexture( "u_Texture0",texture )
+		Endif
+		
+		_material=material
+		_texture=texture
+		_renderRect=rect
+		
+		UpdateVertices()
+		UpdateTexCoords()
+	End
+	
+	Method UpdateVertices()
+		_x0=Float(_renderRect.Width)*(0-_handle.x)*_scale.x
+		_y0=Float(_renderRect.Height)*(0-_handle.y)*_scale.y
+		_x1=Float(_renderRect.Width)*(1-_handle.x)*_scale.x
+		_y1=Float(_renderRect.Height)*(1-_handle.y)*_scale.y
+		_radius=_x0*_x0+_y0*_y0
+		_radius=Max( _radius,_x1*_x1+_y0*_y0 )
+		_radius=Max( _radius,_x1*_x1+_y1*_y1 )
+		_radius=Max( _radius,_x0*_x0+_y1*_y1 )
+		_radius=Sqrt( _radius )
+	End
+	
+	Method UpdateTexCoords()
+		_s0=Float(_renderRect.min.x)/_texture.Width
+		_t0=Float(_renderRect.min.y)/_texture.Height
+		_s1=Float(_renderRect.max.x)/_texture.Width
+		_t1=Float(_renderRect.max.y)/_texture.Height
+	End
+	
+End

+ 48 - 0
modules/mojo/graphics/material.monkey2

@@ -0,0 +1,48 @@
+
+Namespace mojo.graphics
+
+Class Material
+
+	Method New( shader:Shader )
+
+		_shader=shader
+	End
+	
+	Property Shader:Shader()
+	
+		Return _shader
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Property Params:ParamBuffer()
+	
+		Return _params
+	End
+	
+	Method SetVector( name:String,value:Vec4f )
+	
+		_params.SetVector( name,value )
+	End
+
+	Method SetMatrix( name:String,value:Mat4f )
+	
+		_params.SetMatrix( name,value )
+	End
+
+	Method SetTexture( name:String,value:Texture )
+	
+		_params.SetTexture( name,value )
+	End
+
+	Method SetColor( name:String,value:Color )
+	
+		_params.SetColor( name,value )
+	End
+	
+	Private
+	
+	Field _shader:Shader
+	Field _params:=New ParamBuffer
+	
+End

+ 333 - 0
modules/mojo/graphics/shader.monkey2

@@ -0,0 +1,333 @@
+
+Namespace mojo.graphics
+
+#Import "assets/shader_sprite.glsl@/mojo"
+#Import "assets/shader_font.glsl@/mojo"
+#Import "assets/shader_null.glsl@/mojo"
+
+Private
+
+Function BindUniforms( uniforms:Uniform[],params:ParamBuffer )
+
+	For Local u:=Eachin uniforms
+	
+		Local p:=params._params[u.id]
+		
+		Select u.type
+		Case GL_FLOAT
+			glUniform1f( u.location,p.scalar )
+		Case GL_FLOAT_VEC4
+			glUniform4fv( u.location,1,Varptr p.vector.x )
+		Case GL_FLOAT_MAT4
+			glUniformMatrix4fv( u.location,1,False,Varptr p.matrix.i.x )
+		Case GL_SAMPLER_2D
+			Local tex:=p.texture
+			DebugAssert( tex,"Can't bind shader texture uniform '"+u.name+"' - no texture!" )
+			glActiveTexture( GL_TEXTURE0+u.texunit )
+			glBindTexture( GL_TEXTURE_2D,tex.GLTexture )
+			glUniform1i( u.location,u.texunit )
+		Default
+			Assert( False,"Unsupported uniform type for param:"+u.name )
+		End
+
+	Next
+	
+	glActiveTexture( GL_TEXTURE0 )
+End
+
+Class Uniform
+
+	Field name:String
+	Field id:Int
+	Field location:Int
+	Field texunit:Int
+	Field size:Int
+	Field type:Int
+	
+	Method New( name:String,location:Int,texunit:Int,size:Int,type:Int )
+		Self.name=name
+		Self.id=ShaderParam.ParamId( Self.name )
+		Self.texunit=texunit
+		Self.location=location
+		Self.size=size
+		Self.type=type
+	End
+	
+End
+
+Class ShaderProgram
+
+	Method New( sources:String[] )
+
+		_sources=sources
+	End
+	
+	Property Sources:String[]()
+
+		Return _sources
+	End
+	
+	Property EnvUniforms:Uniform[]()
+	
+		Return _envUniforms
+	End
+	
+	Property Uniforms:Uniform[]()
+
+		Return _uniforms
+	End
+	
+	Property GLProgram:GLuint()
+	
+		If _seq=glGraphicsSeq
+			Return _glProgram
+		Endif
+		
+		BuildProgram()
+
+		EnumUniforms()
+		
+		_seq=glGraphicsSeq
+		
+		Return _glProgram
+	End
+	
+	Private
+	
+	Field _sources:String[]
+	
+	Field _seq:Int
+	Field _glProgram:GLuint
+	Field _envUniforms:Uniform[]
+	Field _uniforms:Uniform[]
+		
+	Method BuildProgram()
+
+		Local csource:=""
+		Local vsource:=""
+		Local fsource:=""
+		
+		For Local source:=Eachin _sources
+			Local i0:=source.Find( "//@vertex" )
+			If i0=-1 
+				Print "Shader source:~n"+source
+				Assert( False,"Can't find //@vertex chunk" )
+			Endif
+			Local i1:=source.Find( "//@fragment" )
+			If i1=-1
+				Print "Shader source:~n"+source
+				Assert( False,"Can't find //@fragment chunk" )
+			Endif
+			
+			csource+=source.Slice( 0,i0 )+"~n"
+			vsource+=source.Slice( i0,i1 )+"~n"
+			fsource+=source.Slice( i1 )+"~n"
+		Next
+		
+		vsource=csource+vsource
+		fsource=csource+fsource
+		
+		Local vshader:=glCompile( GL_VERTEX_SHADER,vsource )
+		Local fshader:=glCompile( GL_FRAGMENT_SHADER,fsource )
+		
+		_glProgram=glCreateProgram()
+	
+		glAttachShader( _glProgram,vshader )
+		glAttachShader( _glProgram,fshader )
+		glDeleteShader( vshader )
+		glDeleteShader( fshader )
+		
+		glBindAttribLocation( _glProgram,0,"mx2_VertexPosition" )
+		glBindAttribLocation( _glProgram,1,"mx2_VertexTexCoord0" )
+		glBindAttribLocation( _glProgram,2,"mx2_VertexTangent" )
+		glBindAttribLocation( _glProgram,3,"mx2_VertexColor" )
+		
+		glLink( _glProgram )
+	End
+	
+	Method EnumUniforms()
+
+		Local envUniforms:=New Stack<Uniform>
+		Local uniforms:=New Stack<Uniform>
+		
+		Local n:Int
+		glGetProgramiv( _glProgram,GL_ACTIVE_UNIFORMS,Varptr n )
+
+		Local size:Int,type:UInt,length:Int,nameBuf:=New Byte[256],texunit:=0
+		
+		For Local i:=0 Until n
+		
+			glGetActiveUniform( _glProgram,i,nameBuf.Length,Varptr length,Varptr size,Varptr type,Cast<GLchar Ptr>( Varptr nameBuf[0] ) )
+			
+			Local name:=String.FromCString( nameBuf.Data )
+			
+			Local location:=glGetUniformLocation( _glProgram,name )
+			If location=-1 Continue  'IE fix...
+			
+'			Print "Uniform "+name+" location="+location
+			
+			Local u:=New Uniform( name,location,texunit,size,type )
+			If name.StartsWith( "mx2_" )
+				envUniforms.Push( u )
+			Else
+				uniforms.Push( u )
+			Endif
+			Select type
+			Case GL_SAMPLER_2D
+				texunit+=1
+			End
+		Next
+		
+		_envUniforms=envUniforms.ToArray()
+		_uniforms=uniforms.ToArray()
+	End
+	
+End
+
+Public
+
+#rem monkeydoc @hidden
+#end
+Struct ShaderParam
+
+	Field scalar:Float
+	Field vector:Vec4f
+	Field matrix:Mat4f
+	Field texture:Texture
+
+	Function ParamId:Int( name:String )
+		Local id:=_ids[name]
+		If id Return id
+		_nextId+=1
+		_ids[name]=_nextId
+'		Print "Shader param "+name+"="+_nextId
+		Return _nextId
+	End
+	
+	Private
+	
+	Global _nextId:Int
+	Global _ids:=New StringMap<Int>
+End
+
+#rem monkeydoc @hidden
+#end
+Class ShaderEnv
+
+	Method New( sourceCode:String )
+		_source=sourceCode
+		_id=_nextId
+		_nextId+=1
+	End
+	
+	Property SourceCode:String()
+		Return _source
+	End
+	
+	Property Id:Int()
+		Return _id
+	End
+	
+	Private
+	
+	Field _source:String
+	Field _id:Int
+	
+	Global _nextId:=0
+	
+End
+
+#rem monkeydoc @hidden
+#end
+Class ParamBuffer
+
+	Method SetVector( name:String,value:Vec4f )
+		_params[ ShaderParam.ParamId( name ) ].vector=value
+	End
+
+	Method SetMatrix( name:String,value:Mat4f )
+		_params[ ShaderParam.ParamId( name ) ].matrix=value
+	End
+
+	Method SetTexture( name:String,value:Texture )
+		_params[ ShaderParam.ParamId( name ) ].texture=value
+	End
+
+	Method SetColor( name:String,value:Color )
+		_params[ ShaderParam.ParamId( name ) ].vector=New Vec4f( value.r,value.g,value.b,value.a )
+	End
+
+	Private
+	
+	Field _params:=New ShaderParam[32]
+End
+
+Class Shader
+
+	Method New( sourceCode:String )
+		_source=sourceCode
+	End
+	
+	Property SourceCode:String()
+		Return _source
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method Bind( env:ShaderEnv )
+	
+		If _seq<>glGraphicsSeq
+			_seq=glGraphicsSeq
+			_bound=Null
+		Endif
+		
+		Local p:=_programs[env.Id]
+		If Not p
+			p=New ShaderProgram( New String[]( env._source,_source ) )
+			_programs[env.Id]=p
+		Endif
+		
+		If _bound=p Return
+		glUseProgram( p.GLProgram )
+		_bound=p
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method BindEnvParams( params:ParamBuffer )
+	
+		BindUniforms( _bound._envUniforms,params )
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Method BindParams( params:ParamBuffer )
+	
+		BindUniforms( _bound._uniforms,params )
+	End
+	
+	#rem monkeydoc @hidden
+	#end
+	Function GetShader:Shader( name:String )
+
+		If Not _shaders
+			_shaders=New StringMap<Shader>
+			_shaders["sprite"]=New Shader( stringio.LoadString( "asset::mojo/shader_sprite.glsl" ) )
+			_shaders["font"]=New Shader( stringio.LoadString( "asset::mojo/shader_font.glsl" ) )
+			_shaders["null"]=New Shader( stringio.LoadString( "asset::mojo/shader_null.glsl" ) )
+		Endif
+		
+		Return _shaders[name]
+	End
+
+	Private
+	
+	Field _source:String
+	Field _programs:=New ShaderProgram[16]
+
+	Global _seq:Int
+	Global _bound:ShaderProgram
+	
+	Global _shaders:StringMap<Shader>
+
+End

+ 202 - 0
modules/mojo/graphics/texture.monkey2

@@ -0,0 +1,202 @@
+
+Namespace mojo.graphics
+
+#rem monkeydoc Texture flags.
+
+| TextureFlags	| Description
+|:--------------|:-----------
+| Filter		| Filter texture.
+| Mipmap		| Mipmap texture.
+| ClampS		| Clamp texture S coordinate.
+| ClampT		| Clamp texture T coordinate.
+| Clamp			| Clamp texture coordinates.
+| Managed		| Managed by mojo.
+| RenderTarget	| Texture can be used as a render target.
+| DefaultFlags	| Use default flags.
+
+#end
+Enum TextureFlags
+
+	Filter=			$0001
+	Mipmap=			$0002
+	ClampS=			$0004
+	ClampT=			$0008
+	Managed=		$0010
+	RenderTarget=	$0020
+	ClampST=		ClampS|ClampT
+
+	DefaultFlags=	$ffff
+End
+
+Class Texture
+
+	Method New( pixmap:Pixmap,flags:TextureFlags=TextureFlags.DefaultFlags )
+
+		If flags=TextureFlags.DefaultFlags 
+			flags=TextureFlags.Filter|TextureFlags.Mipmap|TextureFlags.ClampST|TextureFlags.Managed
+		Endif
+		
+		_rect=New Recti( 0,0,pixmap.Width,pixmap.Height )
+		_format=pixmap.Format
+		_flags=flags
+		
+		If _flags & TextureFlags.Managed
+			_managed=pixmap
+		Endif
+	End
+	
+	Method New( width:Int,height:Int,format:PixelFormat=PixelFormat.RGBA32,flags:TextureFlags=TextureFlags.DefaultFlags )
+	
+		If flags=TextureFlags.DefaultFlags 
+			flags=TextureFlags.Filter|TextureFlags.ClampST|TextureFlags.RenderTarget
+		Endif
+
+		_rect=New Recti( 0,0,width,height )
+		_format=format
+		_flags=flags
+		
+		If _flags & TextureFlags.Managed
+			_managed=New Pixmap( Width,Height,_format )
+			_managed.Clear( Color.Magenta )
+		Endif
+	End
+	
+	Property Rect:Recti()
+		Return _rect
+	End
+	
+	Property Width:Int()
+		Return _rect.Width
+	End
+	
+	Property Height:Int()
+		Return _rect.Height
+	End
+	
+	Property Format:PixelFormat()
+		Return _format
+	End
+	
+	Property Flags:TextureFlags()
+		Return _flags
+	End
+	
+	Function Load:Texture( path:String,flags:TextureFlags=TextureFlags.DefaultFlags )
+	
+		Local pixmap:=Pixmap.Load( path )
+		If Not pixmap Return Null
+		
+		pixmap.PremultiplyAlpha()
+		
+		Return New Texture( pixmap,flags )
+	End
+
+	Function ColorTexture:Texture( color:Color )
+		Local texture:=_colorTextures[color]
+		If Not texture
+			Local pixmap:=New Pixmap( 1,1 )
+			pixmap.Clear( color )
+			texture=New Texture( pixmap )
+			_colorTextures[color]=texture
+		Endif
+		Return texture
+	End
+
+	#rem monkeydoc @hidden
+	#end	
+	Property GLTexture:GLuint()
+	
+		If _texSeq=glGraphicsSeq 
+			Return _glTexture
+		Endif
+		
+		glGenTextures( 1,Varptr _glTexture )
+		
+		glPushTexture2d( _glTexture )
+		
+		If _flags & TextureFlags.Filter
+			glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR )
+		Else
+			glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST )
+		Endif
+		
+		If (_flags & TextureFlags.Mipmap) And (_flags & TextureFlags.Filter)
+			glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR )
+		Else If _flags & TextureFlags.Mipmap
+			glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST_MIPMAP_NEAREST )
+		Else If _flags & TextureFlags.Filter
+			glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR )
+		Else
+			glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST )
+		Endif
+
+		If _flags & TextureFlags.ClampS glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE )
+		If _flags & TextureFlags.ClampT glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE )
+		
+		If _managed
+		
+			If True'_managed.Pitch & 4
+			
+				glTexImage2D( GL_TEXTURE_2D,0,glFormat( _format ),Width,Height,0,glFormat( _format ),GL_UNSIGNED_BYTE,Null )
+				
+				For Local y:=0 Until Height
+					glTexSubImage2D( GL_TEXTURE_2D,0,0,y,Width,1,glFormat( _format ),GL_UNSIGNED_BYTE,_managed.PixelPtr( 0,y ) )
+				Next
+			Else
+				glTexImage2D( GL_TEXTURE_2D,0,glFormat( _format ),Width,Height,0,glFormat( _format ),GL_UNSIGNED_BYTE,_managed.Data )
+			Endif
+			
+			glFlush()	'macos nvidia bug!
+		
+			If _flags & TextureFlags.Mipmap glGenerateMipmap( GL_TEXTURE_2D )
+
+		Else
+		
+			glTexImage2D( GL_TEXTURE_2D,0,glFormat( _format ),Width,Height,0,glFormat( _format ),GL_UNSIGNED_BYTE,Null )
+		
+		Endif
+		
+		glPopTexture2d()
+		
+		_texSeq=glGraphicsSeq
+		
+		Return _glTexture
+	End
+	
+	#rem monkeydoc @hidden
+	#end	
+	Property GLFramebuffer:GLuint()
+	
+		If _fbSeq=glGraphicsSeq Return _glFramebuffer
+		
+		glGenFramebuffers( 1,Varptr _glFramebuffer )
+			
+		glPushFramebuffer( _glFramebuffer )
+			
+		glBindFramebuffer( GL_FRAMEBUFFER,_glFramebuffer )
+		glFramebufferTexture2D( GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,GLTexture,0 )
+			
+		If glCheckFramebufferStatus( GL_FRAMEBUFFER )<>GL_FRAMEBUFFER_COMPLETE Assert( False,"Incomplete framebuffer" )
+			
+		glPopFramebuffer()
+		
+		_fbSeq=glGraphicsSeq
+
+		Return _glFramebuffer
+	End
+	
+	Private
+	
+	Field _rect:Recti
+	Field _format:PixelFormat
+	Field _flags:TextureFlags
+	Field _managed:Pixmap
+	
+	Field _texSeq:Int
+	Field _glTexture:GLuint
+	Field _fbSeq:Int
+	Field _glFramebuffer:GLuint
+	
+	Global _colorTextures:Map<Color,Texture>
+	
+End

+ 57 - 0
modules/mojo/graphics/vertex.monkey2

@@ -0,0 +1,57 @@
+
+Namespace mojo.graphics
+
+#rem monkeydoc @hidden
+#end	
+Struct Vertex2f
+
+	Field x:Float,y:Float	'mx2_Vertex
+	Field s0:Float,t0:Float	'mx2_TexCoord0
+	Field ix:Float,iy:Float	'mx2_Tangent
+	Field color:UInt		'mx2_Color
+	
+	Method New()
+	End
+	
+	Method New( position:Vec2f,texCoord0:Vec2f=New Vec2f,tangent:Vec2f=New Vec2f,colorARGB:UInt=$ffffffff )
+		Position=position
+		TexCoord0=texCoord0
+		Tangent=tangent
+		ColorARGB=colorARGB
+	End
+	
+	Method New( x:Float,y:Float,s0:Float=0,t0:Float=0,ix:Float=0,iy:Float=0,colorARGB:UInt=$ffffffff )
+		Self.x=x;Self.y=y
+		Self.s0=s0;Self.t0=t0
+		Self.ix=ix;Self.iy=iy
+		Self.color=colorARGB
+	End
+	
+	Property Position:Vec2f()
+		Return New Vec2f( x,y )
+	Setter( position:Vec2f )
+		x=position.x
+		y=position.y
+	End
+	
+	Property TexCoord0:Vec2f()
+		Return New Vec2f( s0,t0 )
+	Setter( texCoord0:Vec2f )
+		s0=texCoord0.x
+		t0=texCoord0.y
+	End
+	
+	Property Tangent:Vec2f()
+		Return New Vec2f( ix,iy )
+	Setter( tangent:Vec2f )
+		ix=tangent.x
+		iy=tangent.y
+	End
+	
+	Property ColorARGB:UInt()
+		Return color
+	Setter( colorARGB:UInt )
+		color=colorARGB
+	End
+	
+End

+ 36 - 0
modules/mojo/mojo.monkey2

@@ -0,0 +1,36 @@
+
+Namespace mojo
+
+#Import "<std>"
+#Import "<sdl2>"
+
+#Import "app/app"
+#Import "app/event"
+#Import "app/keycodes"
+#Import "app/skin"
+#Import "app/style"
+#Import "app/view"
+#Import "app/window"
+#Import "app/process"
+#Import "app/requesters"
+
+#Import "graphics/canvas"
+#Import "graphics/device"
+#Import "graphics/font"
+#Import "graphics/fontloader_freetype"
+'#Import "graphics/fontloader_stb"
+#Import "graphics/glutil"
+#Import "graphics/image"
+#Import "graphics/material"
+#Import "graphics/shader"
+#Import "graphics/texture"
+#Import "graphics/vertex"
+
+Using std..
+Using sdl2..
+Using gles20..
+Using mojo..
+
+Function Main()
+End
+