Browse Source

Added src/createrelease, src/launcher and src/ted2.

Mark Sibly 9 years ago
parent
commit
0d55df6dc3
66 changed files with 9285 additions and 0 deletions
  1. 92 0
      src/createrelease/createrelease.monkey2
  2. 27 0
      src/launcher/info.plist
  3. 31 0
      src/launcher/launcher.monkey2
  4. BIN
      src/launcher/monkey.icns
  5. BIN
      src/launcher/monkey.ico
  6. BIN
      src/launcher/monkey2_64.png
  7. BIN
      src/launcher/monkey2_icon.ico
  8. BIN
      src/launcher/resource.o
  9. 1 0
      src/launcher/resource.rc
  10. 239 0
      src/ted2/assets/about.html
  11. BIN
      src/ted2/assets/debug_icons.png
  12. 54 0
      src/ted2/assets/newfiles/Letterboxed_Mojo_App.monkey2
  13. 12 0
      src/ted2/assets/newfiles/Simple_Console_App.monkey2
  14. 27 0
      src/ted2/assets/newfiles/Simple_Mojo_App.monkey2
  15. 24 0
      src/ted2/assets/scripts.json
  16. 387 0
      src/ted2/debugview.monkey2
  17. 95 0
      src/ted2/finddialog.monkey2
  18. 202 0
      src/ted2/helpview.monkey2
  19. 104 0
      src/ted2/imgdocument.monkey2
  20. 1431 0
      src/ted2/mainwindow.monkey2
  21. 168 0
      src/ted2/mojox/action.monkey2
  22. BIN
      src/ted2/mojox/assets/DejaVuSansMono.ttf
  23. BIN
      src/ted2/mojox/assets/HelveticaNeue.ttf
  24. BIN
      src/ted2/mojox/assets/Inconsolata-g.ttf
  25. BIN
      src/ted2/mojox/assets/RobotoMono-Regular.ttf
  26. BIN
      src/ted2/mojox/assets/checkmark_icons.png
  27. 327 0
      src/ted2/mojox/assets/htmlview_master_css.css
  28. 24 0
      src/ted2/mojox/assets/markdown_wrapper.html
  29. BIN
      src/ted2/mojox/assets/monkey_font.png
  30. BIN
      src/ted2/mojox/assets/treenode_collapsed.png
  31. BIN
      src/ted2/mojox/assets/treenode_expanded.png
  32. 128 0
      src/ted2/mojox/button.monkey2
  33. 135 0
      src/ted2/mojox/console.monkey2
  34. 214 0
      src/ted2/mojox/dialog.monkey2
  35. 329 0
      src/ted2/mojox/dockingview.monkey2
  36. 137 0
      src/ted2/mojox/filebrowser.monkey2
  37. 352 0
      src/ted2/mojox/htmlview.monkey2
  38. 123 0
      src/ted2/mojox/label.monkey2
  39. 217 0
      src/ted2/mojox/menu.monkey2
  40. 89 0
      src/ted2/mojox/mojo2.monkey2
  41. BIN
      src/ted2/mojox/mojo_window.9.png
  42. 29 0
      src/ted2/mojox/mojox.monkey2
  43. BIN
      src/ted2/mojox/monkey2-logo-63.png
  44. 512 0
      src/ted2/mojox/native/process.cpp
  45. 40 0
      src/ted2/mojox/native/process.h
  46. 213 0
      src/ted2/mojox/native/requesters.cpp
  47. 20 0
      src/ted2/mojox/native/requesters.h
  48. 203 0
      src/ted2/mojox/native/requesters.mm
  49. 192 0
      src/ted2/mojox/scrollbar.monkey2
  50. 269 0
      src/ted2/mojox/scrollview.monkey2
  51. 11 0
      src/ted2/mojox/separator.monkey2
  52. 180 0
      src/ted2/mojox/tabview.monkey2
  53. 41 0
      src/ted2/mojox/test.monkey2
  54. 15 0
      src/ted2/mojox/test2.monkey2
  55. 39 0
      src/ted2/mojox/textfield.monkey2
  56. 1066 0
      src/ted2/mojox/textview.monkey2
  57. 258 0
      src/ted2/mojox/theme.monkey2
  58. 32 0
      src/ted2/mojox/toolbar.monkey2
  59. 354 0
      src/ted2/mojox/treeview.monkey2
  60. 310 0
      src/ted2/mx2document.monkey2
  61. 147 0
      src/ted2/mx2highlighter.monkey2
  62. 91 0
      src/ted2/projectview.monkey2
  63. 48 0
      src/ted2/ted2.monkey2
  64. 97 0
      src/ted2/ted2document.monkey2
  65. 45 0
      src/ted2/test.monkey2
  66. 104 0
      src/ted2/txtdocument.monkey2

+ 92 - 0
src/createrelease/createrelease.monkey2

@@ -0,0 +1,92 @@
+
+#Import "<libc>"
+#Import "<std>"
+
+Using libc..
+Using std..
+
+Const MX2CC_VERSION:="1.0.0"
+
+Global desktop:String
+Global output:String
+
+Function Copy( file:String )
+
+	Print file
+	
+	CopyFile( file,output+file )
+End
+
+Function CopyFiles( dir:String )
+
+	Print dir
+
+	CreateDir( output+dir )
+	
+	For Local file:=Eachin LoadDir( dir )
+
+		If file.Contains( "_macos" ) Continue
+		If file.Contains( "_linux" ) Continue
+			
+		Local src:=dir+"/"+file
+		
+		Select GetFileType( src )
+		Case FileType.Directory
+		
+			If file.Contains( ".buildv" )
+				If Not dir.StartsWith( "modules/" ) Continue
+				If Not file.EndsWith( ".buildv"+MX2CC_VERSION ) Continue
+			Endif
+			
+			If file="build_cache" Continue
+			
+			CopyFiles( src )
+		
+		Case FileType.File
+		
+			If file="ted2.state.json" Continue
+		
+			Local dst:=output+dir+"/"+file
+			
+			CopyFile( src,dst )
+		End
+	Next
+End
+
+Function Main()
+
+	Print "Hello World!"
+
+	ChangeDir( AppDir() )
+		
+	While GetFileType( "bin/mx2cc_windows.exe" )<>FileType.File
+
+		If IsRootDir( CurrentDir() )
+			Print "Error initializing Ted2 - can't find working dir!"
+			libc.exit_( -1 )
+		Endif
+		ChangeDir( ExtractDir( CurrentDir() ) )
+	Wend
+
+	desktop=(String.FromCString( getenv( "HOMEDRIVE" ) )+String.FromCString( getenv( "HOMEPATH" ) )+"\Desktop").Replace( "\","/" )+"/"
+	
+	Print "current="+CurrentDir()
+	Print "desktop="+desktop
+	
+	output=desktop+"monkey2v1.0/"
+
+	DeleteDir( output,True )
+	CreateDir( output )
+	CopyFiles( "bin" )
+	CopyFiles( "docs" )
+	CopyFiles( "modules" )
+	CopyFiles( "bananas" )
+	CopyFiles( "src" )
+	
+	Copy( "hello-world.monkey2" )
+	Copy( "Monkey2 (Windows).exe" )
+	Copy( "LICENSE.TXT" )
+	Copy( "README.TXT" )
+	Copy( "TODO.TXT" )
+	
+End

+ 27 - 0
src/launcher/info.plist

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDocumentTypes</key>
+	<array>
+		<dict>
+			<key>CFBundleTypeExtensions</key>
+			<array>
+				<string>monkey</string>
+			</array>
+			<key>CFBundleTypeName</key>
+			<string>Monkey source file</string>
+			<key>CFBundleTypeIconFile</key>
+			<string>monkey</string>
+			<key>CFBundleTypeRole</key>
+			<string>Editor</string>
+		</dict>
+	</array>
+	<key>CFBundleExecutable</key>
+	<string>Monkey</string>
+	<key>CFBundleIconFile</key>
+	<string>monkey</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+</dict>
+</plist>

+ 31 - 0
src/launcher/launcher.monkey2

@@ -0,0 +1,31 @@
+
+#Import "<libc>"
+#Import "<std>"
+
+#If __HOSTOS__="windows"
+'
+'to build resource.o from monkey.ico:
+'
+'windres resource.rc resource.o
+'
+#Import "resource.o"
+'
+#Endif
+
+Function Main()
+#If __HOSTOS__="windows"
+
+	libc.system( "bin\ted2_windows\ted2.exe" )
+	
+#Else If __HOSTOS__="macos"
+
+	libc.system( "open ~q"+std.filesystem.AppDir()+"../../../bin/ted2_macos.app~q" )
+
+#Else If __HOSTOS__="linux"
+
+	libc.system( "bin/ted2_linux/ted2 >/dev/null 2>/dev/null &" )
+
+
+#Endif
+
+End

BIN
src/launcher/monkey.icns


BIN
src/launcher/monkey.ico


BIN
src/launcher/monkey2_64.png


BIN
src/launcher/monkey2_icon.ico


BIN
src/launcher/resource.o


+ 1 - 0
src/launcher/resource.rc

@@ -0,0 +1 @@
+AppIcon ICON "monkey2_icon.ico"

+ 239 - 0
src/ted2/assets/about.html

@@ -0,0 +1,239 @@
+<!doctype html>
+
+<html>
+
+<head>
+
+<style>
+
+body{
+	background: #222;
+	color: #fff;
+	font-size: 20px;
+	margin: 8px;
+	padding: 0px;
+}
+
+a{
+	color: #ff0;
+	text-decoration: none;
+}
+
+h1{
+	font-size: 26px;
+	text-align: center;
+}
+
+div.awesome{
+	color: #0f0;
+}
+
+</style>
+
+</head>
+
+<body>
+
+<h1>Welcome to Monkey2!</h1>
+
+<p>Monkey2 is an open-source, user-friendly, cross-platform programming language designed primarily for game programming.
+
+<p>All documentation is currently hosted online at <a href="http://monkey2.monkey-x.com">monkey2.monkey-x.com</a>.
+
+<p>Several sample monkey2 apps can be found in the 'bananas' directory of the monkey2 release.
+
+<p>The monkey2 release includes a VERY simple IDE coded in pure monkey2 called 'Ted2'. Ted2 is not a fully fledged IDE (yet) but is intended to provide a 'works anywhere' IDE solution for using monkey2 on any platform you can get it building on.
+
+<p>Monkey2 is a crowd funded project. If you like what you see, please consider contributing to development either via paypal donation or by becoming a Patreon supporter.
+
+<p>So a HUGE shout out to everyone who has contributed to Monkey2 to date! Monkey2 would not have been possible without your generosity - thank you very much!
+
+<h2>Current Contributors</h2>
+<div class="awesome">
+Shane Raffa,
+Danilo,
+Blaine Hodge,
+Lucien Bulles,
+Daniel,
+Martin,
+Tony Smits,
+Peter Rigby,
+Jake Birkett,
+Pharmhaus,
+James Boyd,
+David Maziarka,
+starfruit,
+Stephen	Greener,
+Jonathan Pittock,
+Aaron Woodard,
+Lee Wade,
+abakobo,
+York Burkhardt,
+Rene Damm,
+Capn Lee,
+Jesse M Perez,
+Matthew,
+neuro,
+Paul Apgar,
+Matthew	Camerer,
+Difference,
+Kirsty,
+kmac,
+Richard	Betson,
+Javier,
+Adam,
+Phil7,	
+Erik Thon,
+Noel Cole,
+Ole Jostein Røtne,
+Patrik Strandell,
+Darryl,
+Lars Kristian Gretnes,
+C.E.,
+Roger Lockerbie,
+Philippe Back,
+Bill Stanbrook,
+Hotcakes,
+Christian Leth Jeppesen,
+Makarienkov Vladimir,
+Alan Broad,
+David,
+Christian,
+Leo Santos,
+Pierrou,
+Arthur,
+Richard McLoughlin,
+zui,
+Dennis Rohn,
+Daniele Prandini,
+Hezkore,
+Bill Murray,
+Felipe Alfonso,
+Annika Terrortante,
+Panayiotis Yianni,
+RenK,
+Zachary Cassity,
+Jules Duquette,
+Ian Martin,
+Richard Keam,
+James Freeman,
+Paul Huckstepp,
+Graham,
+Hubert BAYRE,
+Jochen Heizmann,
+Boz	
+</div>
+
+<h3>All Contributors to date</h3>
+<div>
+Aroldo Carvalho,
+Mattias Hansson,
+Catalin Lucaciu,
+Jochen Heizmann,
+Boz,
+Zachary Cassity,
+Hubert BAYRE,
+Cory Estes,
+Paul Huckstepp,
+Stephen Maden,
+degac,
+sal gunduz,
+Ole Jostein Røtne,
+Alan Rawkins,
+Dennis Rohn,
+Christian,
+Philipp Moeller,
+Roger Lockerbie,
+Pierrou,
+Philippe Back,
+Patrik Strandell,
+Bill Stanbrook,
+Erik Thon,
+Richard Keam,
+James Freeman,
+Danilo,
+Juan Camilo,
+Lars Kristian Gretnes,
+Noel Cole,
+Michael Hartlef,
+Jules Duquette,
+Ian Martin,
+Leigh Bowers,
+Daniele Prandini,
+Alan Broad,
+Christian Leth Jeppesen!,
+Rene Damm,
+Richard McLoughlin,
+Kennie,
+ ussell King,
+gcmartijn,
+Darryl,
+Michael Hauck,
+Graham,
+sal gunduz,
+Danilo,
+Daniele Prandini,
+York Burkhardt,
+Peter Rigby,
+Michael Hartlef,
+Leonardo Teixeira,
+Daniel Murphy,
+Gloomywood,
+Adam,
+Arthur,
+Richard Betson,
+Jake Birkett,
+Simon Harrison,
+Daniel Born,
+David Maziarka,
+starfruit,
+David,
+Markus L,
+Capn Lee,
+Jesus M Perez,
+Hotcakes,
+Amon,
+Blaine Hodge,
+Jeffrey Nofsinger,
+Felipe Alfonso,
+Matthew,
+Robin Columbus,
+Alan Rawkins,
+TeaBoy,
+Brian Kramer,
+Bill Murray,
+neuro,
+degac,
+Javier San Juan Cervera,
+Sander Voorn,
+Makarienkov Vladimir,
+impixi,
+kmac,
+Paul Apgar,
+Shane Woolcock,
+Stephen Greener,
+Tony Smits,
+Lucien Bulles,
+SecondGear,
+Matthew Camerer,
+Jonathan Pittock,
+Annika Terrortante,
+Josh Klint,
+Aaron woodard,
+Lee Wade,
+taumel,
+Arthur Bikmullin,
+Difference,
+Michael Denathorn,
+Jose Faeti,
+Martin Leidel,
+abakobo,
+Kirsty,
+Benjamin Aregger,
+James Boyd,
+Simon Armstrong
+</div>
+
+</body>
+
+</html>

BIN
src/ted2/assets/debug_icons.png


+ 54 - 0
src/ted2/assets/newfiles/Letterboxed_Mojo_App.monkey2

@@ -0,0 +1,54 @@
+
+Namespace myapp
+
+#Import "<std>"
+#Import "<mojo>"
+
+Using std..
+Using mojo..
+
+Const Size:=New Vec2i( 640,360 )
+
+Class MyWindow Extends Window
+
+	Method New()
+		Super.New( "My Window",640,480,WindowFlags.Resizable )
+	
+		Layout="letterbox"
+	
+	End
+
+	Method OnRender( canvas:Canvas ) Override
+	
+		App.RequestRender()
+	
+		canvas.DrawText( "Hello World",Width/2,Height/2,.5,.5 )
+	
+	End
+	
+	Method OnKeyEvent( event:KeyEvent ) Override
+	
+		If event.Type=EventType.KeyDown And event.Key=Key.Enter And event.Modifiers & Modifier.Alt
+		
+			Fullscreen=Not Fullscreen
+			
+		Endif
+	
+	End
+	
+	Method OnMeasure:Vec2i() Override
+	
+		Return Size
+		
+	End
+	
+End
+
+Function Main()
+
+	New AppInstance
+	
+	New MyWindow
+	
+	App.Run()
+End

+ 12 - 0
src/ted2/assets/newfiles/Simple_Console_App.monkey2

@@ -0,0 +1,12 @@
+
+Namespace myapp
+
+#Import "<std>"
+
+Using std..
+
+Function Main()
+
+	Print "Hello World"
+	
+End

+ 27 - 0
src/ted2/assets/newfiles/Simple_Mojo_App.monkey2

@@ -0,0 +1,27 @@
+
+Namespace myapp
+
+#Import "<std>"
+#Import "<mojo>"
+
+Using std..
+Using mojo..
+
+Class MyWindow Extends Window
+
+	Method OnRender( canvas:Canvas ) Override
+	
+		canvas.DrawText( "Hello World",Width/2,Height/2,.5,.5 )
+	
+	End
+	
+End
+
+Function Main()
+
+	New AppInstance
+	
+	New MyWindow
+	
+	App.Run()
+End

+ 24 - 0
src/ted2/assets/scripts.json

@@ -0,0 +1,24 @@
+{
+	"scripts":[
+		{
+			"name":"Make docs",
+			"script":"makedocs"
+		},
+		{
+			"name":"Update modules",
+			"script":"updatemods"
+		},
+		{
+			"name":"Rebuild modules",
+			"script":"rebuildmods"
+		},
+		{
+			"name":"Update mx2cc",
+			"script":"updatemx2cc"
+		},
+		{
+			"name":"Rebuild mx2cc",
+			"script":"rebuildmx2cc"
+		}
+	]
+}

+ 387 - 0
src/ted2/debugview.monkey2

@@ -0,0 +1,387 @@
+
+Namespace ted2
+
+#Import "assets/debug_icons.png@/ted2"
+
+Private
+
+Global icons:=New Image[6]
+
+Public
+
+Class DebugView Extends DockingView
+
+	Method New()
+	
+		If Not icons[0] icons=Theme.LoadIcons( "asset::ted2/debug_icons.png",16 )
+	
+		Local tools:=New ToolBar
+		
+		'run/pause
+		_run=tools.AddAction( "",icons[2] )
+		_run.Triggered=Lambda()
+			If Not _debugging Return
+		
+			If _stopped
+				MainWindow._console.WriteStdin( "r~n" )
+				Resume()
+			Else
+				MainWindow._console.Process.SendBreak()
+			Endif
+		
+		End
+
+		'step over		
+		_step=tools.AddAction( "",icons[3] )
+		_step.Triggered=Lambda()
+			If Not _debugging Return
+			
+			If _stopped
+				MainWindow._console.WriteStdin( "s~n" )
+				Resume()
+			Endif
+			
+		End
+		
+		'step into
+		_enter=tools.AddAction( "",icons[4] )
+		_enter.Triggered=Lambda()
+			If Not _debugging Return
+		
+			If _stopped
+				MainWindow._console.WriteStdin( "e~n" )
+				Resume()
+			Endif
+			
+		End
+
+		'step out
+		_leave=tools.AddAction( "",icons[5] )
+		_leave.Triggered=Lambda()
+			If Not _debugging Return
+		
+			If _stopped
+				MainWindow._console.WriteStdin( "l~n" )
+				Resume()
+			Endif
+			
+		End
+		
+		'kill
+		_kill=tools.AddAction( "",icons[1] )
+		_kill.Triggered=Lambda()
+			If Not _debugging Return
+		
+			If _stopped
+				MainWindow._console.WriteStdin( "q~n" )
+				Resume()
+			Else
+				MainWindow._console.Process.SendBreak()
+				_killme=True
+			Endif
+		
+		End
+		
+		AddView( tools,"top",0 )
+		
+		_tree=New TreeView
+		_tree.RootNodeVisible=False
+		_tree.RootNode.Expanded=True
+		
+		_tree.NodeClicked=Lambda( tnode:TreeView.Node,event:MouseEvent )
+		
+			Local node:=Cast<Node>( tnode )
+			If Not node Return
+			
+			If node.srcFile
+			
+				Local doc:=Cast<Mx2Document>( MainWindow.OpenDocument( node.srcFile ) )
+				If Not doc Return
+
+				doc.DebugLine=node.srcLine-1
+				
+			Else
+				
+			Endif
+		
+		End
+		
+		_tree.NodeToggled=Lambda( tnode:TreeView.Node,event:MouseEvent )
+			If Not _stopped Return
+			
+			Local node:=Cast<Node>( tnode )
+			If Not node Or Not node.Expanded Or Not node.scope Return
+			
+			New Fiber( Lambda()
+				UpdateExpanded( node )
+			End )
+		End
+		
+		ContentView=New ScrollView( _tree )
+		
+		UpdateActions()
+	End
+	
+	Method UpdateExpanded( node:Node )
+	
+		If Not node.scope Return
+
+		Local lines:=New StringStack
+		
+		MainWindow._console.WriteStdin( node.value+"~n" )
+		
+		Repeat
+			Local line:=MainWindow._console.ReadStdout().Trim()
+			If Not line Exit
+			
+			lines.Push( line )
+		Forever
+		
+		For Local i:=0 Until lines.Length
+		
+			Local line:=lines[i]
+			Local child:=Cast<Node>( node.GetChild( i ) )
+			
+			If child
+				child.Update( line )
+				If child.Expanded UpdateExpanded( child )
+			Else
+				New Node( line,node )
+			Endif
+		
+		Next
+		
+		node.RemoveChildren( lines.Length )
+	End
+	
+	Method UpdateTree()
+	
+		Local root:=_tree.RootNode
+		
+		Local funcIndex:=0
+		
+		Local func:Node
+		Local varIndex:=0
+		
+		Local first:Node
+		
+		Local expanded:=New Stack<Node>
+		
+		Repeat
+		
+			Local line:=MainWindow._console.ReadStdout().Trim()
+			If Not line Exit
+			
+			If line.StartsWith( ">" )
+			
+				If func func.RemoveChildren( varIndex )
+			
+				Local bits:=line.Split( ";" )
+				Local label:=bits[0].Slice( 1 )
+				Local seq:=Int( bits[3] )
+
+				func=Null
+				For Local i:=funcIndex Until root.NumChildren
+					Local tfunc:=Cast<Node>( root.GetChild( i ) )
+					If Not tfunc Or tfunc.seq<>seq Continue
+					root.RemoveChildren( funcIndex,i )
+					func=tfunc
+					Exit
+				Next
+				
+				If func
+					func.Label=label
+					func.srcLine=Int( bits[2] )
+				Else
+					func=New Node( label+"*",root,seq,funcIndex )
+					func.srcFile=bits[1]
+					func.srcLine=Int( bits[2] )
+					func.seq=seq
+
+					If Not first func.Expanded=True
+				Endif
+				
+				If Not first
+					Local doc:=Cast<Mx2Document>( MainWindow.OpenDocument( func.srcFile ) )
+					If doc doc.DebugLine=func.srcLine-1
+					first=func
+				Endif
+				
+				funcIndex+=1
+				
+				varIndex=0
+			
+				Continue
+				
+			Endif
+			
+			Local node:=Cast<Node>( func.GetChild( varIndex ) )
+			
+			If node
+				node.Update( line )
+				If node.Expanded expanded.Push( node )
+			Else
+				New Node( line,func )
+			Endif
+			
+			varIndex+=1
+
+		Forever
+		
+		For Local node:=Eachin expanded
+			UpdateExpanded( node )
+		Next
+		
+		If func func.RemoveChildren( varIndex )
+		
+		root.RemoveChildren( funcIndex )
+	End
+	
+	Method DebugBegin()
+	
+		Assert( Not _stopped )
+		
+		_tree.RootNode.RemoveAllChildren()
+		
+		_killme=False
+		_debugging=True
+
+		UpdateActions()
+	End
+	
+	Method DebugEnd()
+	
+		Assert( Not _stopped )
+		
+		_debugging=False
+		UpdateActions()
+	End
+	
+	Method DebugStop()
+	
+		Assert( Not _stopped )
+	
+		UpdateTree()
+		
+		If _killme
+			MainWindow._console.WriteStdin( "q~n" )
+			Return
+		Endif
+		
+		_resume=New Future<Bool>
+		_stopped=True
+		
+		UpdateActions()
+		
+		MainWindow._buildForceStop.Triggered+=Resume
+		
+		_resume.Get()
+		
+		MainWindow._buildForceStop.Triggered-=Resume
+	End
+	
+	Method Resume()
+	
+		Assert( _stopped )
+		
+		_stopped=False
+		
+		UpdateActions()
+		
+		_resume.Set( True )
+		_resume=Null
+	End
+	
+	Private
+	
+	Class Node Extends TreeView.Node
+
+		Field srcFile:String
+		Field srcLine:Int
+		Field seq:Int
+		
+		Field name:String
+		Field type:String
+		Field value:String
+		Field scope:Bool
+	
+		Method New( label:String,parent:TreeView.Node=Null,seq:Int=0,index:Int=-1 )
+			Super.New( "",parent,index )
+			Self.seq=seq
+			
+			Update( label )
+		End
+		
+		Method Update( label:String )
+		
+			Local tname:=name
+			Local ttype:=type
+			Local tvalue:=value
+		
+			name=""
+			type=""
+			value=""
+			scope=False
+			
+			Local i0:=label.Find( ":" )
+			If i0<>-1
+				name=label.Slice( 0,i0 )
+				Local i1:=label.Find( "=",i0+1 )
+				If i1=-1
+					type=label.Slice( i0+1 )
+				Else
+					type=label.Slice( i0+1,i1 )
+					value=label.Slice( i1+1 )
+					
+					If value.StartsWith( "@" )
+					
+						label=name+":"+type
+						
+						If value.Contains( ":" )
+							scope=True
+						Else If FromHex( value.Slice( 1 ) )
+							label+="="+value
+							scope=True
+						Else
+							label+=" (null)"
+						Endif
+
+					Endif
+
+				Endif
+			Endif
+			
+			Label=label
+			
+			If name=tname And type=ttype And value=tvalue Return
+			
+			RemoveAllChildren()
+			
+			If scope New Node( "",Self )
+		End
+	
+	End
+	
+	Field _tree:TreeView
+
+	Field _debugging:Bool
+	Field _stopped:Bool
+	Field _killme:Bool
+	Field _resume:Future<Bool>
+	
+	Field _run:Action
+	Field _step:Action
+	Field _enter:Action
+	Field _leave:Action
+	Field _kill:Action
+	
+	Method UpdateActions()
+		_run.Icon=_stopped ? icons[0] Else icons[2]
+		_run.Enabled=_debugging
+		_step.Enabled=_stopped And _debugging
+		_enter.Enabled=_stopped And _debugging
+		_leave.Enabled=_stopped And _debugging
+		_kill.Enabled=_debugging
+	End
+	
+End

+ 95 - 0
src/ted2/finddialog.monkey2

@@ -0,0 +1,95 @@
+
+Namespace ted2
+
+Class FindDialog Extends Dialog
+
+	Method New()
+	
+		_findField=New TextField
+		
+		_replaceField=New TextField
+		
+		_findField.EnterHit=MainWindow.OnFindNext
+		_findField.TabHit=Lambda()
+			_replaceField.MakeKeyView()
+			_replaceField.SelectAll()
+		End
+
+		_replaceField.EnterHit=MainWindow.OnFindNext
+		_replaceField.TabHit=Lambda()
+			_findField.MakeKeyView()
+			_findField.SelectAll()
+		End
+		
+		_caseSensitive=New Button( "Case sensitive: " )
+		_caseSensitive.Checkable=True
+		
+		_escapedText=New Button( "Escaped text: ")
+		_escapedText.Checkable=True
+		
+		Local find:=New DockingView
+		find.AddView( New Label( "Find:" ),"left",80,False )
+		find.ContentView=_findField
+		
+		Local replace:=New DockingView
+		replace.AddView( New Label( "Replace:" ),"left",80,False )
+		replace.ContentView=_replaceField
+		
+		_docker=New DockingView
+		_docker.AddView( find,"top" )
+		_docker.AddView( replace,"top" )
+		_docker.AddView( _caseSensitive,"top" )
+'		_docker.AddView( _escapedText,"top" )
+		_docker.AddView( New Label( " " ),"top" )
+		
+		Title="Find/Replace"
+		
+		MaxSize=New Vec2i( 512,0 )
+		
+		ContentView=_docker
+		
+		AddAction( MainWindow._findNext )
+		
+		AddAction( MainWindow._findPrevious )
+		
+		AddAction( MainWindow._findReplace )
+		
+		AddAction( MainWindow._findReplaceAll )
+		
+		AddAction( "Close" ).Triggered=Lambda()
+			Close()
+			MainWindow.UpdateKeyView()
+		End
+
+		Opened=Lambda()
+			_findField.MakeKeyView()
+			_findField.SelectAll()
+		End
+
+	End
+	
+	Property FindText:String()
+	
+		Return _findField.Text
+	End
+	
+	Property ReplaceText:String()
+	
+		Return _replaceField.Text
+	End
+	
+	Property CaseSensitive:Bool()
+	
+		Return _caseSensitive.Checked
+	End
+	
+	Private
+	
+	Field _findField:TextField
+	Field _replaceField:TextField
+	Field _caseSensitive:Button
+	Field _escapedText:Button
+
+	Field _docker:DockingView
+
+End

+ 202 - 0
src/ted2/helpview.monkey2

@@ -0,0 +1,202 @@
+
+Namespace ted2
+
+Function EnumModules:String[]()
+
+	Local mods:=New StringStack
+	
+	For Local line:=Eachin stringio.LoadString( "modules/modules.txt" ).Split( "~n" )
+	
+		Local i:=line.Find( "'" )
+		If i<>-1 line=line.Slice( 0,i )
+		
+		line=line.Trim()
+		If line mods.Push( line )
+		
+	Next
+	
+	Return mods.ToArray()
+End
+
+Class HelpView Extends DockingView
+
+	Field PageClicked:Void( url:String )
+
+	Method New()
+	
+		_findField=New TextField
+		_findField.TabHit=Lambda()
+		
+			If _findField.Document.Text<>_matchText Or Not _matches.Length Return
+			
+			_matchId=(_matchId+1) Mod _matches.Length
+			Go( _matches[_matchId] )
+
+		End
+		_findField.Document.TextChanged=Lambda()
+			UpdateMatches( _findField.Text )
+			If _matches.Length Go( _matches[0] )
+		End
+		
+		Local findBar:=New DockingView
+	
+		findBar.AddView( New Label( "Find:" ),"left" )
+		findBar.ContentView=_findField
+		
+		_helpTree=New HelpTree
+		
+		_helpTree.NodeClicked=Lambda( tnode:TreeView.Node,event:MouseEvent )
+		
+			Local node:=Cast<HelpTree.Node>( tnode )
+			If Not node Return
+			
+			Go( node.Page )
+		End
+		
+		_htmlView=New HtmlView
+
+		_htmlView.AnchorClicked=Lambda( url:String )
+		
+			'dodgy work around for mx2 docs!
+			If url.StartsWith( "javascript:void('" ) And url.EndsWith( "')" )
+				Local page:=url.Slice( url.Find( "'" )+1,url.FindLast( "'" ) )
+				Go( page )
+				Return
+			Endif
+
+			_htmlView.Go( url )
+		End
+		
+		AddView( findBar,"top" )
+		
+		AddView( _helpTree,"top",128 )
+		
+		ContentView=New ScrollView( _htmlView )
+	End
+	
+	Property HelpTree:HelpTree()
+		Return _helpTree
+	End
+	
+	Property HtmlView:HtmlView()
+		Return _htmlView
+	End
+	
+	Method Go( page:String )
+		Local url:="modules/"+page.Replace( ":","/docs/__PAGES__/" ).Replace( ".","-" )+".html"
+		_htmlView.Go( RealPath( url ) )
+	End
+	
+	Method UpdateMatches( text:String )
+
+		_matchId=0
+		_matchText=text
+		_matches.Clear()
+
+		For Local page:=Eachin _helpTree.Index
+			If page.Contains( text ) _matches.Push( page )
+		Next
+
+		_matches.Sort()
+		
+		_helpTree.Matches.RemoveAllChildren()
+		For Local page:=Eachin _matches
+			New HelpTree.Node( page,_helpTree.Matches,_helpTree )
+		Next
+	End
+	
+	Private
+	
+	Field _findField:TextField
+	Field _helpTree:HelpTree
+	Field _htmlView:HtmlView
+	Field _scroller:ScrollView
+	
+	Field _matchId:Int
+	Field _matchText:String
+	Field _matches:=New StringStack
+End
+
+Class HelpTree Extends TreeView
+
+	Class Node Extends TreeView.Node
+	
+		Method New( page:String,parent:TreeView.Node,tree:HelpTree )
+			Super.New( page,parent )
+			
+			_page=page
+		End
+	
+		Method New( obj:JsonObject,parent:TreeView.Node,tree:HelpTree )
+			Super.New( "",parent )
+		
+			Label=obj["text"].ToString()
+			
+			If obj.Contains( "data" )
+				Local data:=obj["data"].ToObject()
+				Local page:=data["page"].ToString()
+				tree._index.Add( page )
+				_page=page
+			Endif
+			
+			If obj.Contains( "children" )
+				For Local child:=Eachin obj["children"].ToArray()
+					New Node( Cast<JsonObject>( child ),Self,tree )
+				Next
+			Endif
+
+		End
+		
+		Property Page:String()
+			Return _page
+		End
+		
+		Private
+		
+		Field _page:String
+	
+	End
+	
+	Method New()
+	
+		RootNodeVisible=False
+		RootNode.Expanded=True
+		
+		_matches=New TreeView.Node( "Matches",RootNode )
+		_modules=New TreeView.Node( "Modules",RootNode )
+	
+		For Local modname:=Eachin EnumModules()
+		
+			Local index:="modules/"+modname+"/docs/__PAGES__/index.js"
+
+			Local obj:=JsonObject.Load( index )
+			If Not obj
+				Print "Error! file="+index
+				Continue
+			Endif
+			
+			New Node( obj,_modules,Self )
+		Next
+		
+	End
+	
+	Property Matches:TreeView.Node()
+		Return _matches
+	End
+	
+	Property Modules:TreeView.Node()
+		Return _modules
+	End
+	
+	Property Index:StringStack()
+		Return _index
+	End
+	
+	Private
+	
+	Field _matches:TreeView.Node
+	Field _modules:TreeView.Node
+	
+	Field _index:=New StringStack
+	
+End

+ 104 - 0
src/ted2/imgdocument.monkey2

@@ -0,0 +1,104 @@
+
+Namespace ted2
+
+Class ImgView Extends View
+
+	Method New( doc:ImgDocument )
+		_doc=doc
+		
+		Layout="fill"
+	End
+	
+	Protected
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		For Local x:=0 Until Width Step 64
+			For Local y:=0 Until Height Step 64
+				canvas.Color=(x~y) & 64 ? New Color( .1,.1,.1 ) Else New Color( .2,.2,.2 )
+				canvas.DrawRect( x,y,64,64 )
+			Next
+		Next
+		
+		If Not _doc.Image Return
+		
+		canvas.Color=Color.White
+		
+		canvas.Translate( Width/2,Height/2 )
+		
+		canvas.Scale( _zoom,_zoom )
+	
+		canvas.DrawImage( _doc.Image,0,0 )
+	End
+	
+	Method OnMouseEvent( event:MouseEvent ) Override
+	
+		Select event.Type
+		Case EventType.MouseWheel
+			If event.Wheel.Y>0
+				_zoom*=2
+			Else If event.Wheel.Y<0
+				_zoom/=2
+			Endif
+		End
+	
+	End
+	
+	Private
+
+	Field _zoom:Float=1
+		
+	Field _doc:ImgDocument
+End
+
+Class ImgDocument Extends Ted2Document
+
+	Method New( path:String )
+		Super.New( path )
+		
+		_view=New ImgView( Self )
+	End
+	
+	Property Image:Image()
+	
+		Return _image
+	End
+	
+	Protected
+	
+	Method OnLoad:Bool() Override
+	
+		Print "Loading image:"+Path
+	
+		_image=Image.Load( Path )
+		If Not _image Return False
+		
+		Print "OK!"
+		
+		_image.Handle=New Vec2f( .5,.5 )
+		
+		Return True
+	End
+	
+	Method OnSave:Bool() Override
+
+		Return False
+	End
+	
+	Method OnClose() Override
+	
+		If _image _image.Discard()
+	End
+	
+	Method OnCreateView:ImgView() Override
+	
+		Return _view
+	End
+	
+	Private
+	
+	Field _image:Image
+	
+	Field _view:ImgView
+	
+End

+ 1431 - 0
src/ted2/mainwindow.monkey2

@@ -0,0 +1,1431 @@
+
+Namespace ted2
+
+#Import "assets/about.html@/ted2"
+#Import "assets/scripts.json@/ted2"
+
+#Import "assets/newfiles/Simple_Console_App.monkey2@/ted2/newfiles"
+#Import "assets/newfiles/Simple_Mojo_App.monkey2@/ted2/newfiles"
+#Import "assets/newfiles/Letterboxed_Mojo_App.monkey2@/ted2/newfiles"
+
+Global MainWindow:MainWindowInstance
+
+Class MainWindowInstance Extends Window
+
+	Field _debugging:String
+	
+	'paths
+	Field _tmp:String
+	Field _mx2cc:String
+
+	'actions
+	Field _fileNew:Action
+	Field _fileOpen:Action
+	Field _fileClose:Action
+	Field _fileCloseAll:Action
+	Field _fileSave:Action
+	Field _fileSaveAs:Action
+	Field _fileSaveAll:Action
+	Field _fileNextFile:Action
+	Field _filePrevFile:Action
+	Field _fileOpenProject:Action
+	Field _fileQuit:Action
+	
+	Field _editUndo:Action
+	Field _editRedo:Action
+	Field _editCut:Action
+	Field _editCopy:Action
+	Field _editPaste:Action
+	Field _editSelectAll:Action
+	Field _editFind:Action
+	
+	Field _buildDebug:Action
+	Field _buildRelease:Action
+	Field _buildForceStop:Action
+	Field _buildLockFile:Action
+	Field _buildNextError:Action
+	
+	Field _helpOnlineHelp:Action
+	Field _helpOfflineHelp:Action
+	Field _helpAbout:Action
+	
+	Field _findNext:Action
+	Field _findPrevious:Action
+	Field _findReplace:Action
+	Field _findReplaceAll:Action
+	
+	Field _escape:Action
+	
+	'menus
+	Field _menuBar:MenuBar
+	
+	Field _newFiles:Menu
+	Field _recentFiles:Menu
+	Field _closeProject:Menu
+	Field _scripts:Menu
+	
+	Field _fileMenu:Menu
+	Field _editMenu:Menu
+	Field _viewMenu:Menu
+	Field _buildMenu:Menu
+	Field _helpMenu:Menu
+	
+	Field _recent:=New Stack<String>
+	Field _projects:=New Stack<String>
+
+	'dialogs	
+	Field _findDialog:FindDialog
+	
+	'browsers
+	Field _browser:TabView
+	Field _projectView:ProjectView
+	Field _debugView:DebugView
+	Field _helpView:HelpView
+	
+	Field _console:Console
+
+	'documents
+	Field _docTabber:TabView
+	Field _currentDoc:Ted2Document
+	Field _currentTextView:TextView
+	Field _openDocs:=New Stack<Ted2Document>
+	Field _lockedDoc:Ted2Document
+	
+	'main docking view
+	Field _docker:DockingView
+	
+	Field _errors:=New List<Mx2Error>
+	
+	Method OnFileNew()
+	
+		OpenDocument( "" )
+	End
+	
+	Method OnFileOpen()
+
+		Local path:=RequestFile( "Open file...","",False )
+		If Not path Return
+	
+		OpenDocument( path,True )
+		SaveState()
+	End
+	
+	Method OnFileClose()
+	
+		If Not _currentDoc Return
+		
+		If _currentDoc.Dirty
+		
+			Local buttons:=New String[]( "Save","Discard Changes","Cancel" )
+		
+			Select TextDialog.Run( "Close","File '"+_currentDoc.Path+"' has been modified.",buttons )
+			Case 0
+				If Not SaveDocument( _currentDoc ) Return
+			Case 2
+				Return
+			End
+
+		Endif
+		
+		CloseDocument( _currentDoc )
+		
+		SaveState()
+	End
+	
+	Method OnFileCloseAll()
+
+		Local close:=New Stack<Ted2Document>
+	
+		For Local doc:=Eachin _openDocs
+		
+			If doc.Dirty
+			
+				MakeCurrent( doc )
+
+				Local buttons:=New String[]( "Save","Discard Changes","Cancel" )
+			
+				Select TextDialog.Run( "Close All","File '"+doc.Path+"' has been modified.",buttons )
+				Case 0
+					If Not SaveDocument( doc ) Return
+				Case 2
+					Return
+				End
+			
+			Endif
+			
+			close.Add( doc )
+
+		Next
+		
+		For Local doc:=Eachin close
+			CloseDocument( doc )
+		Next
+		
+		SaveState()
+	End
+	
+	Method OnFileSave()
+	
+		If Not _currentDoc Return
+			
+		SaveDocument( _currentDoc )
+	End
+	
+	Method OnFileSaveAs()
+
+		If Not _currentDoc Return
+			
+		Local path:=RequestFile( "Save As","",True )
+		If Not path Return
+		
+		RenameDocument( _currentDoc,path )
+		SaveDocument( _currentDoc )
+	End
+	
+	Method OnFileSaveAll()
+		For Local doc:=Eachin _openDocs
+			SaveDocument( doc )
+		Next
+	End
+	
+	Method OnFileNextFile()
+		If _docTabber.Count<2 Return
+		
+		Local i:=_docTabber.CurrentIndex+1
+		If i=_docTabber.Count i=0
+		
+		Local doc:=FindDocument( _docTabber.ViewAtIndex( i ) )
+		If Not doc Return
+		
+		MakeCurrent( doc )
+	End
+	
+	Method OnFilePrevFile()
+		If _docTabber.Count<2 Return
+		
+		Local i:=_docTabber.CurrentIndex-1
+		If i=-1 i=_docTabber.Count-1
+		
+		Local doc:=FindDocument( _docTabber.ViewAtIndex( i ) )
+		If Not doc Return
+		
+		MakeCurrent( doc )
+	End
+	
+	Method OnFileOpenProject()
+	
+		Local dir:=RequestDir( "Select Project Directory...","" )
+		If Not dir Return
+		
+		If Not _projectView.OpenProject( dir ) Return
+		
+		_projects.Push( dir )
+		UpdateCloseProjectMenu()
+		
+	End
+	
+	Method OnFileQuit()
+	
+		For Local doc:=Eachin _openDocs
+		
+			If doc.Dirty
+			
+				MakeCurrent( doc )
+
+				Local buttons:=New String[]( "Save","Discard Changes","Cancel" )
+			
+				Select TextDialog.Run( "Quit","File '"+doc.Path+"' has been modified.",buttons )
+				Case 0
+					If Not SaveDocument( doc ) Return
+				Case 2
+					Return
+				End
+			
+			Endif
+		
+		Next
+		
+		SaveState()
+		
+		_console.Terminate()
+		
+		App.Terminate()
+	End
+	
+	Method OnEditUndo()
+
+		Local textView:=Cast<TextView>( App.KeyView )
+		
+		If textView textView.Undo()
+	End
+	
+	Method OnEditRedo()
+	
+		Local textView:=Cast<TextView>( App.KeyView )
+		
+		If textView textView.Redo()
+	End
+	
+	Method OnEditCut()
+	
+		Local textView:=Cast<TextView>( App.KeyView )
+		
+		If textView textView.Cut()
+	End
+
+	Method OnEditCopy()
+
+		Local textView:=Cast<TextView>( App.KeyView )
+		
+		If textView textView.Copy()
+	End
+
+	Method OnEditPaste()		
+		
+		Local textView:=Cast<TextView>( App.KeyView )
+		
+		If textView textView.Paste()
+	End
+	
+	Method OnEditSelectAll()
+		
+		Local textView:=Cast<TextView>( App.KeyView )
+		
+		If textView textView.SelectAll()
+	End
+	
+	Method OnEditFind()
+	
+		_findDialog.Open()
+	End
+	
+	Method OnBuildDebug()
+	
+		Build( "debug" )
+	End
+	
+	Method OnBuildRelease()
+	
+		Build( "release" )
+	End
+	
+	Method OnBuildForceStop()
+	
+		_console.Terminate()
+	End
+	
+	Method OnBuildLockFile()
+	
+		LockDoc( _currentDoc )
+	End
+	
+	Method OnBuildNextError()
+
+		While Not _errors.Empty And _errors.First.removed
+			_errors.RemoveFirst()
+		Wend
+		
+		If _errors.Empty Return
+		
+		_errors.AddLast( _errors.RemoveFirst() )
+			
+		GotoError( _errors.First )
+	End
+	
+	Method OnHelpOnlineHelp()
+	
+		App.Idle+=Lambda()
+			requesters.OpenUrl( "http://monkey2.monkey-x.com/modules-reference/" )
+		End
+		
+	End
+	
+	Method OnHelpOfflineHelp()
+	
+		App.Idle+=Lambda()
+			requesters.OpenUrl( "file://"+CurrentDir()+"docs/index.html" )
+		End
+
+	End
+	
+	Method OnHelpAbout()
+	
+		Local htmlView:=New HtmlView
+		htmlView.Go( "asset::ted2/about.html" )
+
+		Local dialog:=New Dialog( "About Monkey2" )
+		dialog.ContentView=htmlView
+		
+		dialog.MinSize=New Vec2i( 640,600 )
+		
+		dialog.MaxSize=Rect.Size
+		
+		dialog.AddAction( "Okay!" ).Triggered=Lambda()
+		
+			dialog.Close()
+		End
+		
+		dialog.Open()
+	
+	End
+	
+	Method OnFindNext()
+	
+		If Not _currentTextView Return
+		
+		Local text:=_findDialog.FindText
+		If Not text Return
+		
+		Local tvtext:=_currentTextView.Text
+		Local cursor:=Max( _currentTextView.Anchor,_currentTextView.Cursor )
+		
+		If Not _findDialog.CaseSensitive
+			tvtext=tvtext.ToLower()
+			text=text.ToLower()
+		Endif
+		
+		Local i:=tvtext.Find( text,cursor )
+		If i=-1
+			i=tvtext.Find( text )
+			If i=-1 Return
+		Endif
+		
+		_currentTextView.SelectText( i,i+text.Length )
+	End
+	
+	Method OnFindPrevious()
+		
+		If Not _currentTextView Return
+		
+		Local text:=_findDialog.FindText
+		If Not text Return
+
+		Local tvtext:=_currentTextView.Text
+		Local cursor:=Min( _currentTextView.Anchor,_currentTextView.Cursor )
+		
+		If Not _findDialog.CaseSensitive
+			tvtext=tvtext.ToLower()
+			text=text.ToLower()
+		Endif
+		
+		Local i:=tvtext.Find( text )
+		If i=-1 Return
+		
+		If i>=cursor
+			i=tvtext.FindLast( text )
+		Else
+			Repeat
+				Local n:=tvtext.Find( text,i+text.Length )
+				If n>=cursor Exit
+				i=n
+			Forever
+		End
+		
+		_currentTextView.SelectText( i,i+text.Length )
+	End
+	
+	Method OnFindReplace()
+
+		If Not _currentTextView Return
+		
+		Local text:=_findDialog.FindText
+		If Not text Return
+		
+		Local min:=Min( _currentTextView.Anchor,_currentTextView.Cursor )
+		Local max:=Max( _currentTextView.Anchor,_currentTextView.Cursor )
+		
+		Local tvtext:=_currentTextView.Text.Slice( min,max )
+
+		If Not _findDialog.CaseSensitive
+			tvtext=tvtext.ToLower()
+			text=text.ToLower()
+		Endif
+		
+		If tvtext<>text Return
+		
+		_currentTextView.ReplaceText( _findDialog.ReplaceText )
+		
+		OnFindNext()
+
+	End
+	
+	Method OnFindReplaceAll()
+	
+		If Not _currentTextView Return
+		
+		Local text:=_findDialog.FindText
+		If Not text Return
+		
+		Local rtext:=_findDialog.ReplaceText
+		
+		Local tvtext:=_currentTextView.Text
+
+		If Not _findDialog.CaseSensitive
+			tvtext=tvtext.ToLower()
+			text=text.ToLower()
+		Endif
+		
+		Local anchor:=_currentTextView.Anchor
+		Local cursor:=_currentTextView.Cursor
+		
+		Local i:=0,t:=0
+		Repeat
+		
+			Local i:=tvtext.Find( text,i )
+			If i=-1 Exit
+			
+			_currentTextView.SelectText( i+t,i+text.Length+t )
+			_currentTextView.ReplaceText( rtext )
+			
+			t+=rtext.Length-text.Length
+			i+=text.Length
+			
+		Forever
+		
+		_currentTextView.SelectText( anchor,cursor )
+		
+	End
+	
+	Method OnEscape()
+		
+		If _findDialog.Visible
+			_findDialog.Close()
+			UpdateKeyView()
+			Return
+		Endif
+		
+		_console.Visible=Not _console.Visible
+		
+	End
+	
+	Method InitPaths()
+	
+		_tmp="tmp/"
+		
+#If __HOSTOS__="macos"
+		_mx2cc="bin/mx2cc_macos"
+#Else If __HOSTOS__="windows"
+		_mx2cc="bin/mx2cc_windows.exe"
+#Else
+		_mx2cc="bin/mx2cc_linux"
+#Endif
+		
+		CreateDir( _tmp )
+	End
+	
+	Method InitActions()
+	
+		_fileNew=New Action( "New" )
+		_fileNew.HotKey=Key.N
+		_fileNew.HotKeyModifiers=Modifier.Control
+		_fileNew.Triggered=OnFileNew
+		
+		_fileOpen=New Action( "Open" )
+		_fileOpen.HotKey=Key.O
+		_fileOpen.HotKeyModifiers=Modifier.Control
+		_fileOpen.Triggered=OnFileOpen
+
+		_fileClose=New Action( "Close" )
+		_fileClose.HotKey=Key.F4
+		_fileClose.HotKeyModifiers=Modifier.Control
+		_fileClose.Triggered=OnFileClose
+
+		_fileCloseAll=New Action( "Close All" )
+		_fileCloseAll.Triggered=OnFileCloseAll
+		
+		_fileSave=New Action( "Save" )
+		_fileSave.HotKey=Key.S
+		_fileSave.HotKeyModifiers=Modifier.Control
+		_fileSave.Triggered=OnFileSave
+		
+		_fileSaveAs=New Action( "Save As" )
+		_fileSaveAs.Triggered=OnFileSaveAs
+		
+		_fileSaveAll=New Action( "Save All" )
+		_fileSaveAll.Triggered=OnFileSaveAll
+		
+		_fileNextFile=New Action( "Next File" )
+		_fileNextFile.Triggered=OnFileNextFile
+		_fileNextFile.HotKey=Key.Tab
+		_fileNextFile.HotKeyModifiers=Modifier.Control
+		
+		_filePrevFile=New Action( "Previous File" )
+		_filePrevFile.Triggered=OnFilePrevFile
+		_filePrevFile.HotKey=Key.Tab
+		_filePrevFile.HotKeyModifiers=Modifier.Control|Modifier.Shift
+		
+		_fileOpenProject=New Action( "Open project" )
+		_fileOpenProject.Triggered=OnFileOpenProject
+		
+		_fileQuit=New Action( "Quit" )
+		_fileQuit.Triggered=OnFileQuit
+		
+		_editUndo=New Action( "Undo" )
+		_editUndo.HotKey=Key.Z
+		_editUndo.HotKeyModifiers=Modifier.Control
+		_editUndo.Triggered=OnEditUndo
+		
+		_editRedo=New Action( "Redo" )
+		_editRedo.HotKey=Key.Y
+		_editRedo.HotKeyModifiers=Modifier.Control
+		_editRedo.Triggered=OnEditRedo
+		
+		_editCut=New Action( "Cut" )
+'		_editCut.HotKey=Key.X
+'		_editCut.HotKeyModifiers=Modifier.Control
+		_editCut.Triggered=OnEditCut
+		
+		_editCopy=New Action( "Copy" )
+'		_editCopy.HotKey=Key.C
+'		_editCopy.HotKeyModifiers=Modifier.Control
+		_editCopy.Triggered=OnEditCopy
+		
+		_editPaste=New Action( "Paste" )
+'		_editPaste.HotKey=Key.V
+'		_editPaste.HotKeyModifiers=Modifier.Control
+		_editPaste.Triggered=OnEditPaste
+		
+		_editSelectAll=New Action( "Select All" )
+'		_editSelectAll.HotKey=Key.A
+'		_editSelectAll.HotKeyModifiers=Modifier.Control
+		_editSelectAll.Triggered=OnEditSelectAll
+		
+		_editFind=New Action( "Find" )
+		_editFind.HotKey=Key.F
+		_editFind.HotKeyModifiers=Modifier.Control
+		_editFind.Triggered=OnEditFind
+		
+		_findNext=New Action( "Find Next" )
+		_findNext.HotKey=Key.F3
+		_findNext.Triggered=OnFindNext
+		
+		_findPrevious=New Action( "Find Previous" )
+		_findPrevious.HotKey=Key.F3
+		_findPrevious.HotKeyModifiers=Modifier.Shift
+		_findPrevious.Triggered=OnFindPrevious
+		
+		_findReplace=New Action( "Replace" )
+		_findReplace.Triggered=OnFindReplace
+		
+		_findReplaceAll=New Action( "Replace All" )
+		_findReplaceAll.Triggered=OnFindReplaceAll
+		
+		_buildDebug=New Action( "Debug" )
+		_buildDebug.HotKey=Key.F5
+		_buildDebug.Triggered=OnBuildDebug
+		
+		_buildRelease=New Action( "Release" )
+		_buildRelease.HotKey=Key.F6
+		_buildRelease.Triggered=OnBuildRelease
+		
+		_buildForceStop=New Action( "Force Stop" )
+		_buildForceStop.HotKey=Key.F5
+		_buildForceStop.HotKeyModifiers=Modifier.Shift
+		_buildForceStop.Triggered=OnBuildForceStop
+
+		_buildLockFile=New Action( "Lock Build File" )
+		_buildLockFile.HotKey=Key.L
+		_buildLockFile.HotKeyModifiers=Modifier.Control
+		_buildLockFile.Triggered=OnBuildLockFile
+		
+		_buildNextError=New Action( "Next Error" )
+		_buildNextError.HotKey=Key.F4
+		_buildNextError.Triggered=OnBuildNextError
+		
+		_helpOnlineHelp=New Action( "Online Help" )
+		_helpOnlineHelp.Triggered=OnHelpOnlineHelp
+
+		_helpOfflineHelp=New Action( "Offline Help" )
+		_helpOfflineHelp.Triggered=OnHelpOfflineHelp
+		
+		_helpAbout=New Action( "About Monkey2" )
+		_helpAbout.Triggered=OnHelpAbout
+		
+		_escape=New Action( "" )
+		_escape.HotKey=Key.Escape
+		_escape.Triggered=OnEscape
+	End
+	
+	Method InitMenus()
+	
+		_newFiles=New Menu( "New..." )
+		Local p:=AssetsDir()+"ted2/newfiles/"
+		For Local f:=Eachin LoadDir( p )
+			Local src:=stringio.LoadString( p+f )
+			_newFiles.AddAction( StripExt( f.Replace( "_"," " ) ) ).Triggered=Lambda()
+				Local doc:=Cast<Mx2Document>( OpenDocument( "" ) )
+				If doc
+					doc.TextDocument.Text=src
+					doc.Save()
+				Endif
+			End
+		Next
+		
+		_scripts=New Menu( "Scripts..." )
+		Local obj:=JsonObject.Load( "asset::ted2/scripts.json" )
+		If obj
+			For Local obj2:=Eachin obj["scripts"].ToArray()
+				Local name:=obj2.ToObject()["name"].ToString()
+				Local script:=obj2.ToObject()["script"].ToString()
+				Local action:=_scripts.AddAction( name )
+				action.Triggered=Lambda()
+					RunScript( script )
+				End
+			Next
+		Endif
+		
+		_recentFiles=New Menu( "Recent Files..." )
+		
+		_closeProject=New Menu( "Close Project..." )
+		
+		_fileMenu=New Menu( "File" )
+		_fileMenu.AddAction( _fileNew )
+		_fileMenu.AddSubMenu( _newFiles )
+		_fileMenu.AddAction( _fileOpen )
+		_fileMenu.AddSubMenu( _recentFiles )
+		_fileMenu.AddSeparator()
+		_fileMenu.AddAction( _fileClose )
+		_fileMenu.AddAction( _fileCloseAll )
+		_fileMenu.AddSeparator()
+		_fileMenu.AddAction( _fileSave )
+		_fileMenu.AddAction( _fileSaveAs )
+		_fileMenu.AddAction( _fileSaveAll )
+		_fileMenu.AddSeparator()
+		_fileMenu.AddAction( _fileNextFile )
+		_fileMenu.AddAction( _filePrevFile )
+		_fileMenu.AddSeparator()
+		_fileMenu.AddAction( _fileOpenProject )
+		_fileMenu.AddSubMenu( _closeProject )
+		_fileMenu.AddSeparator()
+		_fileMenu.AddAction( _fileQuit )
+		
+		_editMenu=New Menu( "Edit" )
+		_editMenu.AddAction( _editUndo )
+		_editMenu.AddAction( _editRedo )
+		_editMenu.AddSeparator()
+		_editMenu.AddAction( _editCut )
+		_editMenu.AddAction( _editCopy )
+		_editMenu.AddAction( _editPaste )
+		_editMenu.AddSeparator()
+		_editMenu.AddAction( _editSelectAll )
+		_editMenu.AddSeparator()
+		_editMenu.AddAction( _editFind )
+		_editMenu.AddAction( _findNext )
+		_editMenu.AddAction( _findPrevious )
+		_editMenu.AddAction( _findReplace )
+		_editMenu.AddAction( _findReplaceAll )
+		
+		_buildMenu=New Menu( "Build" )
+		_buildMenu.AddAction( _buildDebug )
+		_buildMenu.AddAction( _buildRelease )
+		_buildMenu.AddSeparator()
+		_buildMenu.AddAction( _buildNextError )
+		_buildMenu.AddSeparator()
+		_buildMenu.AddAction( _buildLockFile )
+		_buildMenu.AddSeparator()
+		_buildMenu.AddSubMenu( _scripts )
+		_buildMenu.AddSeparator()
+		_buildMenu.AddAction( _buildForceStop )
+		
+		_helpMenu=New Menu( "Help" )
+		_helpMenu.AddAction( _helpOnlineHelp )
+		_helpMenu.AddAction( _helpOfflineHelp )
+		_helpMenu.AddAction( _helpAbout )
+		
+		_menuBar=New MenuBar
+		_menuBar.AddMenu( _fileMenu )
+		_menuBar.AddMenu( _editMenu )
+		_menuBar.AddMenu( _buildMenu )
+		_menuBar.AddMenu( _helpMenu )
+	End
+	
+	Method InitViews()
+	
+		_findDialog=New FindDialog
+		
+		_projectView=New ProjectView		
+		_debugView=New DebugView
+		_helpView=New HelpView
+
+		_browser=New TabView
+		_browser.AddTab( "Project",_projectView )
+		_browser.AddTab( "Debug",_debugView )
+'		_browser.AddTab( "Help",_helpView )
+		_browser.CurrentView=_projectView
+		
+		_console=New Console
+		_console.ReadOnly=True
+		
+		_docTabber=New TabView
+		_docTabber.CurrentChanged=Lambda()
+			MakeCurrent( FindDocument( _docTabber.CurrentView ) )
+		End
+		
+		_docker=New DockingView
+		_docker.ContentView=_docTabber
+		_docker.AddView( _menuBar,"top",0 )
+		_docker.AddView( _browser,"right",250 )
+		_docker.AddView( _console,"bottom",200 )
+		
+	End
+	
+	Method New( title:String,rect:Recti,flags:WindowFlags )
+		Super.New( title,rect,flags )
+		
+		MainWindow=Self
+		
+		ClearColor=Theme.ClearColor
+		
+		SwapInterval=1
+		
+		InitPaths()
+		InitActions()
+		InitMenus()
+		InitViews()
+
+		AddChild( _docker )
+		
+		LoadState()
+		
+		DeleteTmps()
+		
+		UpdateRecentFilesMenu()
+		
+		UpdateCloseProjectMenu()
+		
+		If Not _projects.Length
+			Local dir:=CurrentDir()
+			If _projectView.OpenProject( dir ) _projects.Push( dir )
+		Endif
+
+		App.Idle+=AppIdle
+		
+		Update()
+		
+		If Not _docTabber.Count OnHelpAbout()
+		
+	End
+	
+	Method OnWindowEvent( event:WindowEvent ) Override
+	
+		Select event.Type
+		Case EventType.WindowClose
+			_fileQuit.Trigger()
+		Default
+			Super.OnWindowEvent( event )
+		End
+		
+	End
+		
+	Method ToRecti:Recti( value:JsonValue )
+		Local json:=value.ToArray()
+		Return New Recti( json[0].ToNumber(),json[1].ToNumber(),json[2].ToNumber(),json[3].ToNumber() )
+	End
+	
+	Method UpdateCloseProjectMenu()
+		_closeProject.Clear()
+		For Local dir:=Eachin _projects
+			_closeProject.AddAction( dir ).Triggered=Lambda()
+				_projectView.CloseProject( dir )
+				_projects.Remove( dir )
+				UpdateCloseProjectMenu()
+			End
+		Next
+	End
+	
+	Method UpdateRecentFilesMenu()
+		_recentFiles.Clear()
+		For Local path:=Eachin _recent
+			_recentFiles.AddAction( path ).Triggered=Lambda()
+				OpenDocument( path )
+			End
+		Next
+	End
+	
+	Method LoadState()
+	
+		Local obj:=JsonObject.Load( "bin/ted2.state.json" )
+		If Not obj Return
+		
+		If obj.Contains( "openDocuments" )
+			For Local doc:=Eachin obj["openDocuments"].ToArray()
+				OpenDocument( doc.ToString() )
+			Next
+		Endif
+		
+		If obj.Contains( "recentFiles" )
+			_recent.Clear()
+			For Local path:=Eachin obj["recentFiles"].ToArray()
+				_recent.Push( path.ToString() )
+			Next
+		End
+		
+		If obj.Contains( "openProjects" )
+			_projects.Clear()
+			For Local jdir:=Eachin obj["openProjects"].ToArray()
+				Local dir:=jdir.ToString()
+				If _projectView.OpenProject( dir ) _projects.Push( dir )
+			Next
+		Endif
+		
+		If obj.Contains( "windowRect" )
+			Frame=ToRecti( obj["windowRect"] )
+		Endif
+		
+		If obj.Contains( "consoleSize" )
+			_docker.SetViewSize( _console,obj["consoleSize"].ToNumber() )
+		Endif
+		
+		If obj.Contains( "browserSize" )
+			_docker.SetViewSize( _browser,obj["browserSize"].ToNumber() )
+		Endif
+		
+		If obj.Contains( "helpTreeSize" )
+			_helpView.SetViewSize( _helpView.HelpTree,obj["helpTreeSize"].ToNumber() )
+		Endif
+		
+		If obj.Contains( "lockedDocument" )
+			Local doc:=OpenDocument( obj["lockedDocument"].ToString() )
+			If doc LockDoc( doc )
+		Endif
+		
+	End
+	
+	Method ToJson:JsonValue( rect:Recti )
+		Return New JsonArray( New JsonValue[]( New JsonNumber( rect.min.x ),New JsonNumber( rect.min.y ),New JsonNumber( rect.max.x ),New JsonNumber( rect.max.y ) ) )
+	End
+	
+	Method SaveState()
+	
+		Local obj:=New JsonObject
+		
+		Local docs:=New JsonArray
+		For Local doc:=Eachin _openDocs
+			docs.Add( New JsonString( doc.Path ) )
+		Next
+		obj["openDocuments"]=docs
+		
+		Local recent:=New JsonArray
+		For Local path:=Eachin _recent
+			recent.Add( New JsonString( path ) )
+		End
+		obj["recentFiles"]=recent
+		
+		Local projects:=New JsonArray
+		For Local dir:=Eachin _projects
+			projects.Add( New JsonString( dir ) )
+		Next
+		obj["openProjects"]=projects
+		
+		obj["windowRect"]=ToJson( Frame )
+		
+		obj["consoleSize"]=New JsonNumber( _docker.GetViewSize( _console ) )
+		
+		obj["browserSize"]=New JsonNumber( _docker.GetViewSize( _browser ) )
+		
+		obj["helpTreeSize"]=New JsonNumber( _helpView.GetViewSize( _helpView.HelpTree ) )
+		
+		If _lockedDoc obj["lockedDocument"]=New JsonString( _lockedDoc.Path )
+		
+		SaveString( obj.ToJson(),"bin/ted2.state.json" )
+	
+	End
+	
+	Method Notify( text:String,title:String="Ted2" )
+		Local buttons:=New String[]( "Okay" )
+		TextDialog.Run( title,text,buttons )
+	End
+	
+	Method Confirm:Bool( text:String,title:String="Ted2" )
+		Local buttons:=New String[]( "Okay","Cancel" )
+		Return TextDialog.Run( title,text,buttons )=0
+	End
+	
+	Method DeleteTmps()
+		For Local i:=1 Until 10
+			Local path:=RealPath( _tmp+"untitled"+i+".monkey2" )
+			If GetFileType( path )=FileType.File 
+				If Not FindDocument( path ) DeleteFile( path )
+			Endif
+		Next
+	End
+	
+	Method AllocTmpPath:String()
+		For Local i:=1 Until 10
+			Local path:=_tmp+"untitled"+i+".monkey2"
+			If GetFileType( path )=FileType.None Return path
+		Next
+		Return ""
+	End
+	
+	Method IsTmpPath:Bool( path:String )
+		Return path.StartsWith( _tmp )
+	End
+	
+	Method BuildDoc:Ted2Document()
+		If _lockedDoc Return _lockedDoc
+		Return _currentDoc
+	End
+	
+	Method LockDoc( doc:Ted2Document )
+
+		If _lockedDoc And _lockedDoc=doc
+			_lockedDoc=Null
+			UpdateTabLabel( doc )
+			Return
+		Endif
+
+		If doc And Not Cast<TextView>( doc.View ) doc=Null
+	
+		Local old:=_lockedDoc
+		_lockedDoc=doc
+		
+		If _lockedDoc=old Return
+		
+		UpdateTabLabel( old )
+		UpdateTabLabel( _lockedDoc )
+	
+	End
+	
+	Method UpdateKeyView()
+	
+		If Not _currentDoc Return
+		
+		_currentDoc.View.MakeKeyView()
+	End
+	
+	Method MakeCurrent( doc:Ted2Document )
+	
+		If doc=_currentDoc Return
+		
+		If doc And _docTabber.CurrentView<>doc.View
+			_docTabber.CurrentView=doc.View
+		Endif
+		
+		_currentDoc=doc
+		_currentTextView=Null
+		If doc _currentTextView=Cast<TextView>( doc.View )
+		
+		
+		App.Idle+=Lambda()
+			If _currentDoc
+				Title="Ted2 - "+_currentDoc.Path
+			Else
+				Title="Ted2"
+			Endif
+		End
+
+		UpdateKeyView()
+		
+		Update()
+	End
+	
+	Method FindDocument:Ted2Document( path:String )
+		For Local doc:=Eachin _openDocs
+			If doc.Path=path Return doc
+		Next
+		Return Null
+	End	
+	
+	Method FindDocument:Ted2Document( view:View )
+		For Local doc:=Eachin _openDocs
+			If doc.View=view Return doc
+		Next
+		Return Null
+	End	
+
+	Method ReadError( path:String )
+		Notify( "I/O Error reading file '"+path+"'" )
+	End
+	
+	Method WriteError( path:String )
+		Notify( "I/O Error writing file '"+path+"'" )
+	End
+	
+	Method DocumentTabLabel:String( doc:Ted2Document )
+		Local label:=StripDir( doc.Path )
+		If ExtractExt( doc.Path ).ToLower()=".monkey2"  label=StripExt( label )
+		If IsTmpPath( doc.Path ) label="<"+label+">"
+		If doc=_lockedDoc label="+"+label
+		If doc.Dirty label+="*"
+		Return label
+	End
+	
+	Method UpdateTabLabel( doc:Ted2Document )
+		If doc _docTabber.SetTabLabel( doc.View,DocumentTabLabel( doc ) )
+	End
+	
+	Method OpenDocument:Ted2Document( path:String,addRecent:Bool=False,makeCurrent:Bool=True )
+	
+		Local doc:Ted2Document
+
+		If path
+
+			path=RealPath( path )
+			
+			Local ext:=ExtractExt( path ).ToLower()
+			
+			Select ext
+			Case ".monkey2"
+			Case ".png",".jpg",".bmp"
+			Case ".h",".hpp",".hxx",".c",".cpp",".cxx",".m",".mm"
+			Case ".html",".md",".json",".xml"
+			Case ".sh",".bat"
+			Case ".glsl"
+			Case ".txt"
+			Default
+				Notify( "Unrecognized file type extension for file '"+path+"'" )
+				Return Null
+			End
+		
+			doc=FindDocument( path )
+			If doc
+				If makeCurrent MakeCurrent( doc )
+				Return doc
+			Endif
+			
+			Select ext
+			Case ".monkey2"
+				doc=New Mx2Document( path )
+			Case ".png",".jpg"
+				doc=New ImgDocument( path )
+			Default
+				doc=New TxtDocument( path )
+			End
+			
+		Else
+		
+			path=AllocTmpPath()
+			If Not path
+				Notify( "Can't create temporary file" )
+				Return Null
+			Endif
+			SaveString( "",path )
+			
+			doc=New Mx2Document( path )
+		
+		Endif
+		
+		If Not doc.Load()
+			ReadError( path )
+			Return Null
+		End
+		
+		doc.DirtyChanged=Lambda()
+			UpdateTabLabel( doc )
+		End
+		
+		_docTabber.AddTab( DocumentTabLabel( doc ),doc.View )
+		_openDocs.Add( doc )
+		
+		If addRecent
+			_recent.Remove( path )
+			_recent.Insert( 0,path )
+			If _recent.Length>20 _recent.Resize( 20 )
+			UpdateRecentFilesMenu()
+		Endif
+		
+		If makeCurrent MakeCurrent( doc )
+
+		Return doc
+	End
+	
+	Method RenameDocument( doc:Ted2Document,path:String )
+
+		doc.Rename( path )
+
+		UpdateTabLabel( doc )
+
+	End
+	
+	Method SaveDocument:Bool( doc:Ted2Document )
+	
+		If IsTmpPath( doc.Path )
+
+			Local path:=RequestFile( "Save As","",True )
+
+			If Not path Return False
+			
+			RenameDocument( doc,path )
+		Endif
+		
+		If doc.Save() Return True
+		
+		WriteError( doc.Path )
+		
+		Return False
+	End
+	
+	Method CloseDocument( doc:Ted2Document )
+	
+		Local index:=_docTabber.IndexOfView( doc.View )
+		
+		_docTabber.RemoveTab( doc.View )
+
+		_openDocs.Remove( doc )
+		
+		doc.Close()
+		
+		If IsTmpPath( doc.Path ) DeleteFile( doc.Path )
+		
+		If doc=_lockedDoc _lockedDoc=Null
+		
+		If doc<>_currentDoc Return
+		
+		If Not _docTabber.Count 
+			MakeCurrent( Null )
+			Return
+		Endif
+		
+		If index=_docTabber.Count index-=1
+		
+		MakeCurrent( FindDocument( _docTabber.ViewAtIndex( index ) ) )
+	End
+	
+	Method GotoError( err:Mx2Error )
+	
+		Local doc:=Cast<Mx2Document>( OpenDocument( err.path ) )
+		If Not doc Return
+		
+		Local tv:=Cast<TextView>( doc.View )
+		If Not tv Return
+		
+		Local sol:=tv.Document.StartOfLine( err.line )
+		tv.SelectText( sol,sol )
+		
+		Return
+	End
+	
+	Method Build( config:String )
+
+		If _console.Running Return
+		
+		Local buildDoc:=Cast<Mx2Document>( BuildDoc() )
+		If Not buildDoc Return
+		
+		For Local doc:=Eachin _openDocs
+			Local mx2Doc:=Cast<Mx2Document>( doc )
+			If mx2Doc mx2Doc.Errors.Clear()
+		Next
+		
+		For Local doc:=Eachin _openDocs
+			If doc.Save() Continue
+			WriteError( doc.Path )
+			Return
+		Next
+
+		_console.Clear()
+		
+		Local cmd:=_mx2cc+" makeapp -apptype=gui -build -config="+config+" ~q"+buildDoc.Path+"~q"
+		
+		If Not _console.Start( cmd )
+			Notify( "Failed to start process: '"+cmd+"'" )
+			Return
+		Endif
+		
+		Local appFile:String
+		
+		Local dialog:=New TextDialog( "Ted2","Building "+buildDoc.Path+"..." )
+		
+		dialog.AddAction( "Cancel" ).Triggered=_console.Terminate
+		
+		dialog.Open()
+
+		_errors.Clear()
+		
+		Repeat
+		
+			Local stdout:=_console.ReadStdout()
+			If Not stdout Exit
+			
+			If stdout.StartsWith( "Application built:" )
+				appFile=stdout.Slice( stdout.Find( ":" )+1 ).Trim()
+			Else
+				Local i:=stdout.Find( "] : Error : " )
+				If i<>-1
+					Local j:=stdout.Find( " [" )
+					If j<>-1
+						Local path:=stdout.Slice( 0,j )
+						Local line:=Int( stdout.Slice( j+2,i ) )-1
+						Local msg:=stdout.Slice( i+12 )
+						
+						Local err:=New Mx2Error( path,line,msg )
+						Local doc:=Cast<Mx2Document>( OpenDocument( path,False,False ) )
+						
+						If doc
+							doc.Errors.Add( err )
+							If _errors.Empty GotoError( err )
+							_errors.Add( err )
+						Endif
+						
+					Endif
+				Endif
+			Endif
+			
+			_console.Write( stdout )
+		
+		Forever
+		
+		dialog.Close()
+		
+		If Not appFile Return
+		
+		cmd=appFile
+		
+		If Not _console.Start( cmd )
+			Notify( "Failed to start process: '"+cmd+"'" )
+			Return
+		Endif
+		
+		_console.Clear()
+		
+		Local tab:=_browser.CurrentView
+			
+		If config="debug"
+			_console.Write( "Debugging app:"+appFile+"~n" )
+			_browser.CurrentView=_debugView
+			_debugView.DebugBegin()
+		Else
+			_console.Write( "Running app:"+appFile+"~n" )
+		Endif
+		
+		Repeat
+			
+			Local stdout:=_console.ReadStdout()
+			If Not stdout Exit
+			
+			If config="debug" And stdout="{{!DEBUG!}}~n"
+				_debugView.DebugStop()
+				Continue
+			End
+			
+			_console.Write( stdout )
+		
+		Forever
+		
+		If config="debug"
+			_debugView.DebugEnd()
+		Endif
+		
+		For Local doc:=Eachin _openDocs
+			Local mx2Doc:=Cast<Mx2Document>( doc )
+			If mx2Doc mx2Doc.DebugLine=-1
+		Next
+		
+		_browser.CurrentView=tab
+		
+		_console.Write( "Done.~n" )
+
+	End
+	
+	Method RunScript( script:String )
+	
+		If _console.Running Return
+		
+		For Local doc:=Eachin _openDocs
+			If doc.Save() Continue
+			WriteError( doc.Path )
+			Return
+		Next
+		
+		_console.Clear()
+
+#If __HOSTOS__="windows"
+		script+=".bat"
+#Else
+		script+=".sh"
+#Endif
+		Local cmd:=script
+		
+		Local cd:=CurrentDir()
+		ChangeDir( "scripts" )
+		Local r:=_console.Start( cmd )
+		ChangeDir( cd )
+		
+		If Not r
+			Notify( "Failed to start process: '"+cmd+"'" )
+			Return
+		Endif
+		
+		Repeat
+
+			Local stdout:=_console.ReadStdout()
+			If Not stdout Exit
+
+			_console.Write( stdout )
+		Forever
+			
+		_console.Write( "Done.~n" )
+	End
+	
+	Method RequestDir:String( title:String,dir:String )
+
+		Local future:=New Future<String>
+		
+		App.Idle+=Lambda()
+			future.Set( mojo.requesters.RequestDir( title,dir ) )
+		End
+		
+		Return future.Get()
+	End
+
+	Method RequestFile:String( title:String,filters:String,save:Bool,path:String="" )
+
+		Local future:=New Future<String>
+		
+		App.Idle+=Lambda()
+			future.Set( mojo.requesters.RequestFile( title,filters,save,path ) )
+		End
+		
+		Return future.Get()
+	End
+
+	Method UpdateActions()
+	
+		Local keyView:=Cast<TextView>( App.KeyView )
+	
+		Local dirtyDocs:Bool
+		For Local doc:=Eachin _openDocs
+			If doc.Dirty dirtyDocs=True
+		Next
+		
+		While Not _errors.Empty And _errors.First.removed
+			_errors.RemoveFirst()
+		Wend
+	
+		_fileClose.Enabled=_currentDoc<>Null
+		_fileCloseAll.Enabled=_openDocs.Length<>0
+		_fileSave.Enabled=_currentDoc<>Null And _currentDoc.Dirty
+		_fileSaveAs.Enabled=_currentDoc<>Null
+		_fileSaveAll.Enabled=dirtyDocs
+		_fileNextFile.Enabled=_docTabber.Count>1
+		_filePrevFile.Enabled=_docTabber.Count>1
+		
+		_editUndo.Enabled=keyView And keyView.CanUndo
+		_editRedo.Enabled=keyView And keyView.CanRedo
+		_editCut.Enabled=keyView And keyView.CanCut
+		_editCopy.Enabled=keyView And keyView.CanCopy
+		_editPaste.Enabled=keyView And keyView.CanPaste
+		_editSelectAll.Enabled=keyView<>Null
+		
+		_buildDebug.Enabled=BuildDoc()<>Null And Not _console.Running
+		_buildRelease.Enabled=BuildDoc()<>Null And Not _console.Running
+		_buildLockFile.Enabled=_currentDoc<>Null
+		_buildForceStop.Enabled=_console.Running
+		_buildNextError.Enabled=Not _errors.Empty
+		
+		_scripts.Enabled=Not _console.Running
+	End
+	
+	Method AppIdle()
+	
+		UpdateActions()
+		
+		App.RequestRender()
+	
+		App.Idle+=AppIdle
+		
+		GCCollect()	'thrash that GC!
+	End
+	
+End

+ 168 - 0
src/ted2/mojox/action.monkey2

@@ -0,0 +1,168 @@
+
+Namespace mojox
+
+Class Action
+
+	Field Triggered:Void()
+	
+	Field Modified:Void()
+
+	Method New( label:String,icon:Image=Null )
+	
+		_label=label
+		_icon=icon
+	End
+	
+	Property Label:String()
+	
+		Return _label
+	
+	Setter( label:String )
+		If label=_label Return
+	
+		_label=label
+		
+		Modified()
+	End
+	
+	Property Icon:Image()
+	
+		Return _icon
+	
+	Setter( icon:Image )
+		If icon=_icon Return
+		
+		_icon=icon
+		
+		Modified()
+	End
+	
+	Property Enabled:Bool()
+	
+		Return _enabled
+	
+	Setter( enabled:Bool )
+		If enabled=_enabled Return
+		
+		_enabled=enabled
+		
+		If _enabled EnableHotKey() Else DisableHotKey()
+		
+		Modified()
+	End
+	
+	Property Async:Bool()
+	
+		Return _async
+	
+	Setter( async:Bool )
+	
+		_async=async
+	End
+	
+	Property HotKey:Key()
+	
+		Return _hotKey
+	
+	Setter( hotKey:Key )
+		If hotKey=_hotKey Return
+	
+		If _enabled DisableHotKey()
+	
+		_hotKey=hotKey
+		
+		If _enabled EnableHotKey()
+	End
+	
+	Property HotKeyModifiers:Modifier()
+	
+		Return _hotKeyMods
+	
+	Setter( hotKeyModifiers:Modifier )
+	
+		_hotKeyMods=hotKeyModifiers
+	End
+	
+	Property HotKeyLabel:String()
+
+		If Not _hotKey Return ""
+		
+		Local label:=""
+		If _hotKeyMods & Modifier.Shift label+="Shift"
+		If _hotKeyMods & Modifier.Control label+="+Ctrl"
+		If _hotKeyMods & Modifier.Alt label+="+Alt"
+		label+="+"+Keyboard.KeyName( _hotKey )
+		If label.StartsWith( "+" ) label=label.Slice( 1 )
+		Return label
+	End
+	
+	Method Trigger()
+	
+		If _async
+			New Fiber( Triggered )
+		Else
+			Triggered()
+		Endif
+		
+	End
+	
+	Private
+	
+	Field _enabled:Bool=True
+	Field _label:String
+	Field _icon:Image
+	Field _hotKey:Key
+	Field _hotKeyMods:Modifier
+	Field _async:Bool=True
+	
+	Global _hotKeys:Map<Key,Stack<Action>>
+	
+	Method DisableHotKey()
+	
+		If Not _hotKey Return
+		
+		_hotKeys[_hotKey].Remove( Self )
+	End
+	
+	Method EnableHotKey()
+	
+		If Not _hotKey Return
+		
+		If Not _hotKeys
+			
+			_hotKeys=New Map<Key,Stack<Action>>
+			
+			App.KeyEventFilter+=Lambda( event:KeyEvent )
+			
+				If event.Eaten Return
+				
+				If event.Type<>EventType.KeyDown Return
+
+				Local actions:=_hotKeys[event.Key]
+				If Not actions Return
+
+				Local mods:=event.Modifiers
+				mods|=Cast<Modifier>( (Int(mods) & $541) Shl 1 | (Int(mods) & $a82) Shr 1 )
+
+				For Local action:=Eachin actions
+					If event.Key<>action._hotKey Continue
+					If mods<>action._hotKeyMods Continue
+					action.Trigger()
+					event.Eat()
+					Return
+				Next
+				
+			End
+			
+		Endif
+		
+		Local actions:=_hotKeys[_hotKey]
+		If Not actions
+			actions=New Stack<Action>
+			_hotKeys[_hotKey]=actions
+		Endif
+		
+		actions.Add( Self )
+	End
+
+End

BIN
src/ted2/mojox/assets/DejaVuSansMono.ttf


BIN
src/ted2/mojox/assets/HelveticaNeue.ttf


BIN
src/ted2/mojox/assets/Inconsolata-g.ttf


BIN
src/ted2/mojox/assets/RobotoMono-Regular.ttf


BIN
src/ted2/mojox/assets/checkmark_icons.png


+ 327 - 0
src/ted2/mojox/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;
+}

+ 24 - 0
src/ted2/mojox/assets/markdown_wrapper.html

@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+
+<head>
+
+<style>
+
+body{
+	background: #888;
+	color: #fff;
+}
+
+</style>
+
+</head>
+
+<html>
+
+<body>
+
+${CONTENT}
+
+</body>
+
+</html>

BIN
src/ted2/mojox/assets/monkey_font.png


BIN
src/ted2/mojox/assets/treenode_collapsed.png


BIN
src/ted2/mojox/assets/treenode_expanded.png


+ 128 - 0
src/ted2/mojox/button.monkey2

@@ -0,0 +1,128 @@
+
+Namespace mojox
+
+Class Button Extends Label
+
+	Method New()
+		Layout="float"
+		Style=Style.GetStyle( "mojo.Button" )
+		TextGravity=New Vec2f( .5,.5 )
+	End
+	
+	Method New( icon:Image )
+		Self.New()
+		
+		Icon=icon
+	End
+	
+	Method New( text:String,icon:Image=Null )
+		Self.New()
+
+		Text=text
+		Icon=icon
+	End
+	
+	Method New( action:Action )
+		Self.New()
+		
+		Text=action.Label
+		Icon=action.Icon
+		
+		Clicked=Lambda()
+			action.Trigger()
+		End
+		
+		action.Modified=Lambda()
+			Enabled=action.Enabled
+			Text=action.Label
+			Icon=action.Icon
+		End
+	End
+	
+	Property Checkable:Bool()
+	
+		Return _checkable
+	
+	Setter( checkable:Bool)
+	
+		_checkable=checkable
+	End
+	
+	Property Checked:Bool()
+	
+		Return _checked
+	
+	Setter( checked:Bool )
+	
+		_checked=checked
+	End
+	
+	Property Selected:Bool()
+	
+		Return _selected
+	
+	Setter( selected:Bool )
+	
+		_selected=selected
+		
+		UpdateStyleState()
+	End
+	
+	Protected
+	
+	Method OnValidateStyle() Override
+	
+		If _checkable And _checked
+			CheckMark=RenderStyle.GetImage( "checkmark:checked" )
+		Else If _checkable
+			CheckMark=RenderStyle.GetImage( "checkmark:unchecked" )
+		Else
+			CheckMark=Null
+		End
+	End
+	
+	Method OnMouseEvent( event:MouseEvent ) Override
+	
+		Select event.Type
+		Case EventType.MouseDown
+			_org=event.Location
+			_active=True
+		Case EventType.MouseUp
+			If _active And _hover
+				If _checkable _checked=Not _checked
+				Clicked()
+			Endif
+			_active=False
+		Case EventType.MouseEnter
+			_hover=True
+		Case EventType.MouseLeave
+			_hover=False
+		Case EventType.MouseMove
+			If _active Dragged( event.Location-_org )
+		End
+		
+		UpdateStyleState()
+	End
+	
+	Method UpdateStyleState()
+
+		If _selected
+			StyleState="selected"
+		Else If _active And _hover
+			StyleState="active"
+		Else If _active Or _hover
+			StyleState="hover"
+		Else
+			StyleState=""
+		Endif
+	
+	End
+
+	Field _selected:Bool
+	Field _checkable:Bool
+	Field _checked:Bool
+	Field _active:Bool
+	Field _hover:Bool
+	Field _org:Vec2i
+
+End

+ 135 - 0
src/ted2/mojox/console.monkey2

@@ -0,0 +1,135 @@
+
+Namespace mojox
+
+Class Console Extends TextView
+
+	Field Finished:Void( exitCode:Int )
+	
+	Property Running:Bool()
+
+		Return _running
+	End
+
+	Property Process:Process()
+	
+		If _running Return _process
+		
+		Return Null
+	End
+	
+	Method Start:Bool( cmd:String )
+	
+		If _running Return False
+	
+		_process=New Process
+		
+		_process.Finished=Lambda()
+			_procOpen=False
+			UpdateRunning()
+		End
+		
+		_process.StdoutReady=Lambda()
+		
+			Local stdout:=_process.ReadStdout()
+			
+			If Not stdout
+				If _stdout _stdoutBuf.Add( _stdout )
+				_stdoutOpen=False
+				UpdateRunning()
+				Return
+			Endif
+			
+			stdout=_stdout+stdout.Replace( "~r~n","~n" )
+			
+			Local i0:=0
+			Repeat
+				Local i:=stdout.Find( "~n",i0 )
+				If i=-1
+					_stdout=stdout.Slice( i0 )
+					Exit
+				Endif
+				_stdoutBuf.Add( stdout.Slice( i0,i+1 ) )
+				i0=i+1
+			Forever
+			
+			If _stdoutWaiting And Not _stdoutBuf.Empty _stdoutWaiting.Set( True )
+			
+		End
+		
+		If Not _process.Start( cmd ) Return False
+		
+		_running=True
+		_procOpen=True
+		_stdoutOpen=True
+		
+		_stdout=""
+		_stdoutBuf.Clear()
+		_stdoutWaiting=Null
+		
+		Return True
+	End
+	
+	Method ReadStdout:String()
+	
+		While _stdoutBuf.Empty
+		
+			If Not _stdoutOpen
+				If _procOpen
+					_stdoutWaiting=New Future<Bool>
+					_stdoutWaiting.Get()
+					_stdoutWaiting=Null
+				Endif
+				Return ""
+			Endif
+			
+			_stdoutWaiting=New Future<Bool>
+			_stdoutWaiting.Get()
+			_stdoutWaiting=Null
+		Wend
+		
+		Return _stdoutBuf.RemoveFirst()
+	End
+	
+	Method WriteStdin( str:String )
+	
+		If Not _procOpen Return
+	
+		_process.WriteStdin( str )
+	End
+	
+	Method Terminate()
+	
+		If Not _procOpen Return
+	
+		_process.Terminate()
+	End
+	
+	Method Write( text:String )
+	
+		ReplaceText( text )
+	End
+	
+	Private
+	
+	Field _process:Process
+	
+	Field _running:Bool
+	Field _procOpen:Bool
+	
+	Field _stdoutOpen:Bool
+	Field _stdout:String
+	Field _stdoutBuf:=New StringList
+	Field _stdoutWaiting:Future<Bool>
+	
+	Method UpdateRunning()
+	
+		If Not _running Or _procOpen Or _stdoutOpen Return
+		
+		_running=False
+		
+		Finished( _process.ExitCode )
+		
+		If _stdoutWaiting _stdoutWaiting.Set( True )
+	End
+
+End

+ 214 - 0
src/ted2/mojox/dialog.monkey2

@@ -0,0 +1,214 @@
+
+Namespace mojox
+
+Class DialogTitle Extends Label
+
+	Field Dragged:Void( v:Vec2i )
+
+	Method New( text:String="" )
+		Super.New( text )
+'		Layout="fill"
+		Style=Style.GetStyle( "mojo.DialogTitle" )
+	End
+	
+	Private
+	
+	Field _org:Vec2i
+	Field _drag:Bool
+	Field _hover:Bool
+	
+	Method OnMouseEvent( event:MouseEvent ) Override
+	
+		Select event.Type
+		Case EventType.MouseDown
+			_drag=True
+			_org=event.Location
+		Case EventType.MouseUp
+			_drag=False
+		Case EventType.MouseEnter
+			_hover=True
+		Case EventType.MouseLeave
+			_hover=False
+		Case EventType.MouseMove
+			If _drag Dragged( event.Location-_org )
+		End
+		
+		If _drag
+			StyleState="active"
+		Else If _hover
+			StyleState="hover"
+		Else
+			StyleState=""
+		Endif
+		
+	End
+
+End
+
+Class Dialog Extends View
+
+	Field Opened:Void()
+	Field Closed:Void()
+
+	Method New()
+		Layout="float"
+		Style=Style.GetStyle( "mojo.Dialog" )
+		Gravity=New Vec2f( 0,0 )
+		Visible=False
+		
+		_title=New DialogTitle
+		_title.Layout="fill"
+		_title.Style=Style.GetStyle( "mojo.DialogTitle" )
+		_title.Dragged=Lambda( vec:Vec2i )
+			Offset+=vec
+		End
+		
+		_content=New DockingView
+		_content.Style=Style.GetStyle( "mojo.DialogContent" )
+		
+		_actions=New DockingView
+		_actions.Style=Style.GetStyle( "mojo.DialogActions" )
+		_actions.Layout="float"
+		
+		_docker=New DockingView
+
+		_docker.AddView( _title,"top" )
+		_docker.ContentView=_content
+		_docker.AddView( _actions,"bottom" )
+		
+		AddChild( _docker )
+	End
+	
+	Method New( title:String )
+		Self.New()
+		
+		Title=title
+	End
+
+	Property Title:String()
+	
+		Return _title.Text
+	
+	Setter( title:String )
+	
+		_title.Text=title
+	End
+	
+	Property ContentView:View()
+	
+		Return _content.ContentView
+	
+	Setter( contentView:View )
+	
+		_content.ContentView=contentView
+	End
+	
+	Method AddAction( action:Action )
+
+		Local button:=New Button( action )
+
+		_actions.AddView( button,"left" )
+	End
+	
+	Method AddAction:Action( label:String,icon:Image=Null )
+	
+		Local action:=New Action( label,icon )
+		AddAction( action )
+		Return action
+	End
+	
+	Method Open()
+	
+		Visible=True
+	
+		_window=App.ActiveWindow
+		
+		Measure()
+		
+		Offset=(_window.Rect.Size-MeasuredSize)/New Vec2i( 2,3 )
+		
+		_window.AddChild( Self )
+
+		Visible=True
+		
+		Opened()
+	End
+	
+	Method Close()
+	
+		_window.RemoveChild( Self )
+		
+		_window=Null
+	
+		Visible=False
+		
+		Closed()
+	End
+	
+	Private
+	
+	Field _title:DialogTitle
+	Field _content:DockingView
+	Field _actions:DockingView
+	Field _docker:DockingView
+	Field _window:Window
+	
+	Method OnMeasure:Vec2i() Override
+	
+		Return _docker.LayoutSize
+	End
+	
+End
+
+Class TextDialog Extends Dialog
+
+	Method New( title:String="",text:String="" )
+		Super.New( title )
+		
+		_label=New Label( text )
+		
+		ContentView=_label
+	End
+	
+	Property Text:String()
+	
+		Return _label.Text
+	
+	Setter( text:String )
+		
+		_label.Text=text
+		
+	End
+	
+	Function Run:Int( title:String,text:String,buttons:String[] )
+	
+		Local dialog:=New TextDialog( title,text )
+		
+		Local result:=New Future<Int>
+		
+		For Local i:=0 Until buttons.Length
+		
+			dialog.AddAction( buttons[i] ).Triggered=Lambda()
+			
+				result.Set( i )
+			End
+		Next
+		
+		dialog.Open()
+		
+		App.BeginModal( dialog )
+		
+		Local r:=result.Get()
+		
+		App.EndModal()
+		
+		dialog.Close()
+		
+		Return r
+	End
+	
+	Private
+	
+	Field _label:Label
+	
+End

+ 329 - 0
src/ted2/mojox/dockingview.monkey2

@@ -0,0 +1,329 @@
+
+Namespace mojox
+
+Class DragKnob Extends View
+
+	Field Dragged:Void( v:Vec2i )
+
+	Method New()
+		Layout="fill"
+		Style=Style.GetStyle( "mojo.DragKnob" )
+	End
+	
+	Private
+	
+	Field _org:Vec2i
+	
+	Field _drag:Bool
+	Field _hover:Bool
+	
+	Method OnMeasure:Vec2i() Override
+	
+		Return New Vec2i( 0,0 )
+	End
+	
+	Method OnMouseEvent( event:MouseEvent ) Override
+	
+		Select event.Type
+		Case EventType.MouseDown
+			_drag=True
+			_org=event.Location
+		Case EventType.MouseUp
+			_drag=False
+		Case EventType.MouseEnter
+			_hover=True
+		Case EventType.MouseLeave
+			_hover=False
+		Case EventType.MouseMove
+			If _drag Dragged( event.Location-_org )
+		End
+		
+		If _drag
+			StyleState="active"
+		Else If _hover
+			StyleState="hover"
+		Else
+			StyleState=""
+		Endif
+		
+	End
+	
+End
+
+Class DockView Extends View
+
+	Method New( view:View,location:String,size:Int,resizable:Bool )
+		_view=view
+		_location=location
+		_size=size
+		_resizable=resizable
+		
+		Layout="fill"
+		Style=Style.GetStyle( "mojo.DockView" )
+		
+		AddChild( _view.Container )
+
+		If _size And _resizable
+			_knob=New DragKnob
+			_knob.Dragged=Lambda( v:Vec2i )
+				Select _location
+				Case "top"
+					_size+=v.y
+				Case "bottom"
+					_size-=v.y
+				Case "left"
+					_size+=v.x
+				Case "right"
+					_size-=v.x
+				End
+				_size=Max( _size,0 )
+			End
+			AddChild( _knob )
+		Endif
+	End
+	
+	Property View:View()
+	
+		Return _view
+		
+	Setter( view:View )
+	
+		If _view RemoveChild( _view.Container )
+		
+		_view=view
+		
+		If _view AddChild( _view.Container )
+	End
+	
+	Property Location:String()
+	
+		Return _location
+	End
+	
+	Property Size:Int()
+	
+		Return _size
+		
+	Setter( size:Int )
+	
+		_size=size
+	End
+	
+	Private
+	
+	Field _view:View
+	Field _knob:DragKnob
+	Field _location:String
+	Field _size:Int
+	Field _resizable:Bool
+	
+	Method OnMeasure:Vec2i() Override
+	
+		Local size:=_view.Container.LayoutSize
+		
+		If _knob
+			Local w:=_knob.LayoutSize.x
+			Local h:=_knob.LayoutSize.y
+			Select _location
+			Case "top","bottom"
+				size.y=_size+h
+			Case "left","right"
+				size.x=_size+w
+			End
+		Else If _size
+			Select _location
+			Case "top","bottom"
+				size.y=_size
+			Case "left","right"
+				size.x=_size
+			End
+		Endif
+		
+		Return size
+	End
+	
+	Method OnLayout:Void() Override
+	
+		Local rect:=Rect
+		
+		If _knob
+			Local w:=_knob.LayoutSize.x
+			Local h:=_knob.LayoutSize.y
+			Select _location
+			Case "top"
+				_knob.Frame=New Recti( 0,Height-h,Width,Height )
+				rect.Bottom-=h
+			Case "bottom"
+				_knob.Frame=New Recti( 0,0,Width,h )
+				rect.Top+=h
+			Case "left"
+				_knob.Frame=New Recti( Width-w,0,Width,Height )
+				rect.Right-=w
+			Case "right"
+				_knob.Frame=New Recti( 0,0,w,Height )
+				rect.Left+=w
+			End
+		Endif
+		
+		_view.Container.Frame=rect
+	End
+
+End
+
+Class DockingView Extends View
+
+	Method New()
+		Layout="fill"
+	End
+	
+	Property ContentView:View()
+
+		Return _content
+			
+	Setter( contentView:View )
+	
+		If _content RemoveChild( _content.Container )
+		
+		_content=contentView
+		
+		If _content AddChild( _content.Container )
+	End
+	
+	Method AddView( view:View,location:String,size:Int=0,resizable:Bool=True )
+	
+		Local dock:=New DockView( view,location,size,resizable )
+
+		_docks.Add( dock )
+		
+		AddChild( dock )
+	End
+	
+	Method RemoveView( view:View )
+	
+		Local dock:=FindView( view )
+		If Not dock Return
+		
+		dock.View=Null
+		
+		RemoveChild( dock )
+		
+		_docks.Remove( dock )
+	End
+	
+	Method GetViewSize:Int( view:View )
+	
+		Return FindView( view ).Size
+	End
+	
+	Method SetViewSize( view:View,size:Int )
+	
+		FindView( view ).Size=size
+	End
+	
+	Method ClearViews()
+	
+		For Local dock:=Eachin _docks
+		
+			dock.View=Null
+			
+			RemoveChild( dock )
+		Next
+		
+		_docks.Clear()
+	End
+	
+	Method OnMeasure:Vec2i() Override
+
+		Local size:=New Vec2i
+		
+		If _content size=_content.Container.LayoutSize
+	
+		For Local dock:=Eachin _docks
+
+			'FIXME - silly place to do this...		
+			dock.Visible=dock.View.Visible
+			If Not dock.Visible Continue
+	
+			Select dock.Location
+			Case "top","bottom"
+				size.x=Max( size.x,dock.LayoutSize.x )
+				size.y+=dock.LayoutSize.y
+			Case "left","right"
+				size.x+=dock.LayoutSize.x
+				size.y=Max( size.y,dock.LayoutSize.y )
+			End
+
+		Next
+		
+		Return size
+
+	End
+	
+	Method OnLayout() Override
+	
+		Local rect:=Rect
+		
+		For Local dock:=Eachin _docks
+
+			If Not dock.Visible Continue
+		
+			Local size:=dock.LayoutSize
+		
+			Select dock.Location
+			Case "top"
+
+				Local top:=rect.Top+size.y
+				If top>rect.Bottom top=rect.Bottom
+				dock.Frame=New Recti( rect.Left,rect.Top,rect.Right,top )
+				rect.Top=top
+				
+'				dock.Frame=New Recti( rect.Left,rect.Top,rect.Right,rect.Top+size.y )
+'				rect.Top+=size.y
+			Case "bottom"
+			
+				Local bottom:=rect.Bottom-size.y
+				If bottom<rect.Top bottom=rect.Top
+				dock.Frame=New Recti( rect.Left,bottom,rect.Right,rect.Bottom )
+				rect.Bottom=bottom
+				
+'				dock.Frame=New Recti( rect.Left,rect.Bottom-size.y,rect.Right,rect.Bottom )
+'				rect.Bottom-=size.y
+			Case "left"
+			
+				Local left:=rect.Left+size.x
+				If left>rect.Right left=rect.Right
+				dock.Frame=New Recti( rect.Left,rect.Top,left,rect.Bottom )
+				rect.Left=left
+				
+'				dock.Frame=New Recti( rect.Left,rect.Top,rect.Left+size.x,rect.Bottom )
+'				rect.Left+=size.x
+			Case "right"
+				Local right:=rect.Right-size.x
+				If right<rect.Left right=rect.Left
+				dock.Frame=New Recti( right,rect.Top,rect.Right,rect.Bottom )
+				rect.Right=right
+				
+'				dock.Frame=New Recti( rect.Right-size.x,rect.Top,rect.Right,rect.Bottom )
+'				rect.Right-=size.x
+			End
+
+		Next
+		
+		If _content _content.Container.Frame=rect
+	End
+	
+	Private
+	
+	Field _content:View
+	Field _docks:=New Stack<DockView>
+	
+	Method FindView:DockView( view:View )
+	
+		For Local dock:=Eachin _docks
+			If dock.View=view Return dock
+		Next
+		
+		Return Null
+	End
+
+End

+ 137 - 0
src/ted2/mojox/filebrowser.monkey2

@@ -0,0 +1,137 @@
+
+Namespace mojox
+
+Class FileBrowser Extends TreeView
+
+	Field FileClicked:Void( path:String,event:MouseEvent )
+
+	Method New( rootPath:String="." )
+	
+		Style=Style.GetStyle( "mojo.FileBrowser" )
+	
+		_rootNode=New Node( Null )
+		
+		RootPath=rootPath
+
+		NodeClicked=OnNodeClicked
+		NodeToggled=OnNodeToggled
+		
+		RootNode=_rootNode
+		
+		Update()
+	End
+	
+	Property RootPath:String()
+	
+		Return _rootPath
+	
+	Setter( path:String )
+	
+		_rootPath=path
+		
+		_rootNode._path=path
+		_rootNode.Label=_rootPath
+	End
+	
+	Method Update()
+	
+		UpdateNode( _rootNode,_rootPath,True )
+	End
+
+	Private
+	
+	Class Node Extends TreeView.Node
+	
+		Method New( parent:Node )
+			Super.New( "",parent )
+		End
+		
+		Private
+		
+		Field _path:String
+	
+	End
+	
+	Field _rootNode:Node
+	Field _rootPath:String
+	
+	Method OnNodeClicked( tnode:TreeView.Node,event:MouseEvent )
+	
+		Local node:=Cast<Node>( tnode )
+		If Not node Return
+		
+		FileClicked( node._path,event )
+	End
+	
+	Method OnNodeToggled( tnode:TreeView.Node,event:MouseEvent )
+
+		Local node:=Cast<Node>( tnode )
+		If Not node Return
+	
+		If node.Expanded
+			UpdateNode( node,node._path,True )
+		Else
+			For Local child:=Eachin node.Children
+				child.RemoveAllChildren()
+			Next
+		Endif
+	
+		Update()
+	End
+	
+	Method UpdateNode( node:Node,path:String,recurse:Bool )
+	
+		Local dir:=filesystem.LoadDir( path )
+		
+		Local dirs:=New Stack<String>
+		Local files:=New Stack<String>
+		
+		For Local f:=Eachin dir
+			Local fpath:=path+"/"+f
+			Select GetFileType( fpath )
+			Case FileType.Directory
+				dirs.Push( f )
+			Default
+				files.Push( f )
+			End
+		Next
+		
+		dirs.Sort()
+		files.Sort()
+		
+		Local i:=0,children:=node.Children
+		
+		While i<dir.Length
+		
+			Local f:=""
+			If i<dirs.Length f=dirs[i] Else f=files[i-dirs.Length]
+			
+			Local child:Node
+			
+			If i<children.Length
+				child=Cast<Node>( children[i] )
+			Else
+				child=New Node( node )
+			Endif
+			
+			Local fpath:=path+"/"+f
+			
+			child.Label=f
+			child._path=fpath
+			
+			If i<dirs.Length
+				If child.Expanded Or recurse
+					UpdateNode( child,fpath,child.Expanded )
+				Endif
+			Else
+				child.RemoveAllChildren()
+			Endif
+			
+			i+=1
+		Wend
+		
+		node.RemoveChildren( i )
+		
+	End
+	
+End

+ 352 - 0
src/ted2/mojox/htmlview.monkey2

@@ -0,0 +1,352 @@
+
+Namespace mojox
+
+#Import "assets/htmlview_master_css.css@/mojox"
+#Import "assets/markdown_wrapper.html@/mojox"
+
+Class HtmlView Extends View
+
+	Field AnchorClicked:Void( url:String )
+
+	Method New()
+		Layout="fill"
+		
+		Style=Style.GetStyle( "mojo.HtmlView" )
+		
+		_context=New litehtml.context
+		_context.load_master_stylesheet( stringio.LoadString( "asset::mojox/htmlview_master_css.css" ) )
+
+		_container=New document_container( Self )
+		
+		_baseUrl=filesystem.CurrentDir()
+		
+		AnchorClicked=Go
+	End
+	
+	Property BaseUrl:String()
+		Return _baseUrl
+	Setter( baseUrl:String )
+		If Not baseUrl.EndsWith( "/" ) baseUrl+="/"
+		_baseUrl=baseUrl
+	End
+	
+	Property HtmlSource:String()
+		Return _source
+	Setter( htmlSource:String )
+		_source=htmlSource
+		_document=New litehtml.document( _source,_container,_context )
+		_layoutSize=New Vec2i( 0,0 )
+		_renderSize=New Vec2i( 0,0 )
+	End
+	
+	Property Container:View() Override
+		If Not _scroller
+			_scroller=New ScrollView( Self )
+		Endif
+		Return _scroller
+	End
+	
+	Method Go( url:String )
+	
+		If url.Contains( "#" )
+			Return
+		Endif
+		
+		Local root:=ExtractRootDir( url )
+		
+		If root="http://" Or root="https://"
+			requesters.OpenUrl( url )
+			Return
+		Endif
+		
+		If Not root
+			url=BaseUrl+url
+		Endif
+		
+		Local src:=stringio.LoadString( url )
+		
+		If ExtractExt( url )=".md"
+			src=hoedown.MarkdownToHtml( src )
+			Local wrapper:=stringio.LoadString( "asset::mojox/markdown_wrapper.html" )
+			src=wrapper.Replace( "${CONTENT}",src )
+		End
+		
+		BaseUrl=ExtractDir( url )
+		
+		HtmlSource=src
+	End
+	
+	Private
+	
+	Field _context:litehtml.context
+	Field _container:litehtml.document_container
+	Field _anchorClicked:String
+	
+	Field _baseUrl:String
+	Field _source:String
+	Field _document:litehtml.document
+	Field _layoutSize:Vec2i
+	Field _renderSize:Vec2i
+
+	Field _scroller:ScrollView
+	
+	Method OnMeasure2:Vec2i( size:Vec2i ) Override
+	
+		If Not _document Return New Vec2i( 0,0 )
+		
+		If size.x=_layoutSize.x Return _renderSize
+		
+		_layoutSize=size
+		
+		_document.render( size.x )
+		
+		_renderSize=New Vec2i( _document.width(),_document.height() )
+		
+		Return _renderSize
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		If Not _document Return
+		
+		Local clip:litehtml.position
+		clip.x=ClipRect.X
+		clip.y=ClipRect.Y
+		clip.width=ClipRect.Width
+		clip.height=ClipRect.Height
+
+		_document.draw( canvas,0,0,Varptr clip )
+	End
+
+	Method OnMouseEvent( event:MouseEvent ) Override
+	
+		If Not _document Return
+	
+		Local x:=event.Location.X
+		Local y:=event.Location.Y
+		
+		_anchorClicked=""
+		
+		Select event.Type
+		Case EventType.MouseMove
+			_document.on_mouse_over( x,y,x,y )
+		Case EventType.MouseDown
+			_document.on_lbutton_down( x,y,x,y )
+		Case EventType.MouseUp
+			_document.on_lbutton_up( x,y,x,y )
+			_document.on_mouse_leave()
+		End
+		
+		If _anchorClicked AnchorClicked( _anchorClicked )
+	End
+	
+End
+
+Class document_container Extends litehtml.document_container
+
+	Field _view:HtmlView
+	
+	Global _fontScale:=1
+	Global _imageCache:=New StringMap<Image>
+	
+	Method New( view:HtmlView )
+	
+		_view=view
+	End
+	
+	Method set_color( canvas:Canvas,color:litehtml.web_color )
+	
+		canvas.Color=New Color( color.red/255.0,color.green/255.0,color.blue/255.0,1 )
+	End
+	
+	Method make_url:String( href:String )
+		Return _view._baseUrl+href
+	End
+
+	Method create_font:Object( faceName:String,size:Int,weight:Int,style:litehtml.font_style,decoration:UInt,fm:litehtml.font_metrics Ptr ) Override
+	
+		Local font:Font
+		
+		If faceName.Contains( "monospace" )
+			font=Font.Open( App.DefaultMonoFontName,size )
+		Else
+			font=Font.Open( App.DefaultFontName,size )
+		Endif
+		
+		Local height:=size
+
+		fm[0].height=height
+		fm[0].ascent=height
+		fm[0].descent=0
+		fm[0].x_height=height
+		fm[0].draw_spaces=True
+		
+		Return font
+	End
+
+	Method delete_font( font:Object ) Override
+	End
+	
+	Method text_width:Int( text:String,hfont:Object ) Override
+	
+		Local font:=Cast<Font>( hfont )
+		
+		Return font.TextWidth( text ) * _fontScale
+	End
+	
+	Method draw_text( hdc:Object,text:String,hfont:Object,color:litehtml.web_color Ptr,pos:litehtml.position Ptr ) Override
+	
+		Local canvas:=Cast<Canvas>( hdc )
+		
+		Local font:=Cast<Font>( hfont )
+		
+		canvas.Font=font
+
+		set_color( canvas,color[0] )
+		
+		canvas.DrawText( text,pos[0].x,pos[0].y )
+		
+		Return
+#rem		
+		canvas.PushMatrix()
+		canvas.Translate( pos[0].x,pos[0].y )
+		canvas.Scale( _fontScale,1 )
+		canvas.DrawText( text,0,0 )
+		canvas.PopMatrix()
+#end
+	End
+	
+	Method pt_to_px:Int( pt:Int ) Override
+		Return 0
+	End
+	
+	Method get_default_font_size:Int() Override
+		Return 16
+	End
+	
+	Method get_default_font_name:String() Override
+		Return "mojo"
+	End
+	
+	Method draw_list_marker( hdc:Object,marker:litehtml.list_marker Ptr ) Override
+	
+		If marker[0].marker_type=litehtml.list_style_type_none Return
+	
+		Local canvas:=Cast<Canvas>( hdc )
+	
+		set_color( canvas,marker[0].color )
+		
+		canvas.DrawRect( marker[0].pos.x,marker[0].pos.y,marker[0].pos.width,marker[0].pos.height )
+	End
+	
+	Method load_image( src:String,baseurl:String,redraw_on_ready:Bool ) Override
+		If _imageCache.Contains( src ) Return
+		Local image:=Image.Load( make_url( src ) )
+		_imageCache.Set( src,image )
+	End
+	
+	Method get_image_size( src:String,baseurl:String,sz:litehtml.size Ptr ) Override
+		Local image:=_imageCache.Get( src )
+		If Not image Return
+		sz[0].width=image.Width
+		sz[0].height=image.Height
+	End
+
+	Method draw_background( hdc:Object,img_src:String,img_baseurl:String,bg:litehtml.background_paint Ptr ) Override
+	
+		Local canvas:=Cast<Canvas>( hdc )
+		
+		Local image:=_imageCache.Get( img_src )
+		If image
+			canvas.Color=Color.White
+			canvas.DrawImage( image,bg[0].position_x,bg[0].position_y )
+			Return
+		Endif
+
+		set_color( canvas,bg[0].color )
+		
+'		canvas.DrawRect( bg[0].clip_box.x,bg[0].clip_box.y,bg[0].clip_box.width,bg[0].clip_box.height )
+		canvas.DrawRect( bg[0].border_box.x,bg[0].border_box.y,bg[0].border_box.width,bg[0].border_box.height )
+
+	End
+	
+	Method draw_border( canvas:Canvas,border:litehtml.border,x:Int,y:Int,w:Int,h:Int )
+
+		If border.style<>litehtml.border_style_solid Or border.width<1 Return
+		
+		set_color( canvas,border.color )
+		
+		canvas.DrawRect( x,y,w,h )
+	End
+	
+	Method draw_borders( hdc:Object,borders:litehtml.borders Ptr,pos:litehtml.position Ptr,root:Bool ) Override
+	
+		Local canvas:=Cast<Canvas>( hdc )
+		
+		Local x:=pos[0].x,y:=pos[0].y
+		
+		Local w:=pos[0].width,h:=pos[0].height
+		
+		draw_border( canvas,borders[0].left,x,y,1,h )
+		
+		draw_border( canvas,borders[0].top,x,y,w,1 )
+		
+		draw_border( canvas,borders[0].right,x+w-1,y,1,h )
+		
+		draw_border( canvas,borders[0].bottom,x,y+h-1,w,1 )
+	End
+
+	Method set_caption( caption:String ) Override
+	End
+	
+	Method set_base_url( baseurl:String ) Override
+	End
+	
+	Method on_anchor_click( url:String ) Override
+		_view._anchorClicked=url
+	End
+		
+	Method set_cursor( cursor:String ) Override
+	End
+	
+	Method import_css:String( url:String,baseurl:String ) Override
+		Local css:=stringio.LoadString( make_url( url ) )
+		Return css
+	End
+	
+	Method set_clip( pos:litehtml.position Ptr,radiuses:litehtml.border_radiuses Ptr ) Override
+	End
+	
+	Method del_clip() Override
+	End
+	
+	Method get_client_rect( client:litehtml.position Ptr ) Override
+'		If _view._rendering Print "get client rect"
+		client[0].x=0
+		client[0].y=0
+		client[0].width=_view._layoutSize.x
+		client[0].height=_view._layoutSize.y
+	End
+	
+	Method get_media_features( media:litehtml.media_features Ptr ) Override
+'		If _view._rendering Print "get media features"
+		media[0].type=litehtml.media_type_screen
+		media[0].width=_view._layoutSize.x
+		media[0].height=_view._layoutSize.y
+		media[0].device_width=1920
+		media[0].device_height=1080
+		media[0].color=8
+		media[0].color_index=0
+		media[0].monochrome=0
+		media[0].resolution=96
+	End
+	
+	Method get_language:String() Override
+		Return ""
+	End
+	
+	Method get_culture:String() Override
+		Return ""
+	End
+	
+End

+ 123 - 0
src/ted2/mojox/label.monkey2

@@ -0,0 +1,123 @@
+
+Namespace mojox
+
+Class Label Extends View
+
+	Field Clicked:Void()
+	
+	Field Dragged:Void( offset:Vec2i )
+
+	Method New()
+		Layout="float"
+		Style=Style.GetStyle( "mojo.Label" )
+		Gravity=New Vec2f( 0,.5 )
+		TextGravity=New Vec2f( 0,.5 )
+	End
+	
+	Method New( icon:Image )
+		Self.New()
+		
+		Icon=icon
+	End
+	
+	Method New( text:String,icon:Image=Null )
+		Self.New()
+		
+		Text=text
+		Icon=icon
+	End
+	
+	Property Text:String()
+	
+		Return _text
+		
+	Setter( text:String )
+	
+		_text=text
+	End
+	
+	Property Icon:Image()
+	
+		Return _icon
+	
+	Setter( icon:Image )
+	
+		_icon=icon
+	End
+	
+	Property CheckMark:Image()
+	
+		Return _check
+		
+	Setter( checkMark:Image )
+	
+		_check=checkMark
+	End
+	
+	Property TextGravity:Vec2f()
+	
+		Return _textGravity
+	
+	Setter( textGravity:Vec2f )
+	
+		_textGravity=textGravity
+	End
+	
+	Protected
+
+	Method OnMeasure:Vec2i() Override
+	
+		Local size:=New Vec2i
+		
+		If _text
+			size.x=RenderStyle.DefaultFont.TextWidth( _text )
+			size.y=RenderStyle.DefaultFont.Height
+		Endif
+
+		If _icon
+			size.x+=_icon.Width
+			size.y=Max( size.y,Int( _icon.Height ) )
+		Endif
+		
+		If _check
+			size.x+=_check.Width
+			size.y=Max( size.y,Int( _check.Height ) )
+		Endif
+		
+		Return size
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		Local x:=0,w:=0
+		
+		If _icon
+			Local y:=(MeasuredSize.y-_icon.Height)/2
+			canvas.DrawImage( _icon,0,y )
+			w+=_icon.Width
+			x=_icon.Width
+		Endif
+		
+		If _check
+			Local y:=(MeasuredSize.y-_check.Height)/2
+			canvas.DrawImage( _check,Width-_check.Width,y )
+			w+=_check.Width
+		Endif
+		
+		If _text
+			Local tx:=((Width-w)-(MeasuredSize.x-w)) * _textGravity.x
+			Local ty:=(Height-MeasuredSize.y) * _textGravity.y
+			canvas.DrawText( _text,tx+x,ty )
+		Endif
+
+	End
+	
+	Private
+	
+	Field _text:String
+	Field _textGravity:Vec2f
+	Field _icon:Image
+	Field _check:Image
+	
+End
+

+ 217 - 0
src/ted2/mojox/menu.monkey2

@@ -0,0 +1,217 @@
+
+Namespace mojox
+
+Class MenuButton Extends Button
+
+	Method New( text:String )
+		Super.New( text )
+		
+		Layout="fill"
+		Style=Style.GetStyle( "mojo.MenuButton" )
+		TextGravity=New Vec2f( 0,.5 )
+
+'		MinSize=New Vec2i( 128,0 )
+	End
+	
+	Method New( action:Action )
+		Super.New( action )
+		
+		Layout="fill"
+		Style=Style.GetStyle( "mojo.MenuButton" )
+		TextGravity=New Vec2f( 0,.5 )
+		
+		MinSize=New Vec2i( 160,0 )
+
+		_action=action
+	End
+	
+	Method OnMeasure:Vec2i() Override
+	
+		Local size:=Super.OnMeasure()
+		
+		If _action
+			Local hotKey:=_action.HotKeyLabel
+			If hotKey size.x+=Style.DefaultFont.TextWidth( "         "+hotKey )
+		Endif
+		
+		Return size
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		Super.OnRender( canvas )
+		
+		If _action
+			Local hotKey:=_action.HotKeyLabel
+			If hotKey
+				Local w:=Style.DefaultFont.TextWidth( hotKey )
+				Local tx:=(Width-w)
+				Local ty:=(Height-MeasuredSize.y) * TextGravity.y
+				canvas.DrawText( hotKey,tx,ty )
+			Endif
+		Endif
+	
+	End
+	
+	Field _action:Action
+
+End
+
+Class Menu Extends DockingView
+
+	Method New( label:String )
+		_label=label
+		Visible=False
+		Style=mojo.app.Style.GetStyle( "mojo.Menu" )
+		Layout="float"
+		Gravity=New Vec2f( 0,0 )
+	End
+	
+	Property Label:String()
+		Return _label
+	End
+	
+	Method Clear()
+		Super.ClearViews()
+	End
+	
+	Method AddAction( action:Action )
+		Local button:=New MenuButton( action )
+		button.Clicked+=Lambda()
+			_open[0].Close()
+		End
+		AddView( button,"top",0 )
+	End
+	
+	Method AddAction:Action( label:String )
+		Local action:=New Action( label )
+		AddAction( action )
+		Return action
+	End
+	
+	Method AddSeparator()
+		AddView( New Separator,"top",0 )
+	End
+	
+	Method AddSubMenu( menu:Menu )
+	
+		Local label:=New MenuButton( menu.Label )
+
+		label.Clicked=Lambda()
+			If menu.Visible
+				menu.Close()
+			Else
+				Local location:=New Vec2i( label.Bounds.Right,label.Bounds.Top )
+				menu.Open( location,label,Self )
+			Endif
+		End
+		
+		AddView( label,"top",0 )
+	End
+	
+	Method Open( location:Vec2i,view:View,owner:View )
+	
+		Assert( Not Visible )
+		
+		While Not _open.Empty And _open.Top<>owner
+			_open.Top.Close()
+		Wend
+		
+		If _open.Empty
+			_filter=App.MouseEventFilter
+			App.MouseEventFilter=MouseEventFilter
+		Endif
+		
+		Local window:=view.FindWindow()
+		location=view.TransformPointToView( location,window )
+		
+		window.AddChild( Self )
+		Offset=location
+		Visible=True
+		
+		_owner=owner
+		_open.Push( Self )
+	End
+	
+	Method Close()
+	
+		Assert( Visible )
+		
+		While Not _open.Empty
+		
+			Local menu:=_open.Pop()
+			menu.Parent.RemoveChild( menu )
+			menu.Visible=False
+			menu._owner=Null
+			
+			If menu=Self Exit
+		Wend
+		
+		If Not _open.Empty Return
+		
+		App.MouseEventFilter=_filter
+		_filter=Null
+	End
+	
+	Private
+	
+	Field _label:String
+	Field _owner:View
+	
+	Global _open:=New Stack<Menu>
+	Global _filter:Void( MouseEvent )
+	
+	Function MouseEventFilter( event:MouseEvent )
+	
+		If event.Eaten Return
+		
+		Local view:=event.View
+		
+		If view<>_open[0]._owner
+			
+			For Local menu:=Eachin _open
+				If view.IsChildOf( menu ) Return
+			Next
+		
+			If view.IsChildOf( _open[0]._owner ) Return
+
+		Endif
+		
+		If event.Type<>EventType.MouseDown
+			event.Eat()
+			Return
+		Endif
+		
+		event.Eat()
+		
+		_open[0].Close()
+	End
+		
+End
+
+Class MenuBar Extends DockingView
+
+	Method New()
+		Style=Style.GetStyle( "mojo.MenuBar" )
+		Layout="fill"
+	End
+	
+	Method AddMenu( menu:Menu )
+	
+		Local label:=New MenuButton( menu.Label )
+
+		label.Clicked=Lambda()
+		
+			If Not menu.Visible
+				Local location:=New Vec2i( label.Bounds.Left,label.Bounds.Bottom )
+				menu.Open( location,label,Self )
+			Else
+				menu.Close()
+				Return
+			Endif
+		End
+		
+		AddView( label,"left",0 )
+	End
+	
+End

+ 89 - 0
src/ted2/mojox/mojo2.monkey2

@@ -0,0 +1,89 @@
+
+
+Class Image
+
+	Property Vertices:Vertex2f[]()
+	
+End
+
+Class Light
+
+	Property Color:Color()
+	
+End
+
+Class Vertex2f
+	Field x:Float
+	Field y:Float
+	Field u:Float
+	Field v:Float
+	Field tx:Float
+	Field ty:Float
+	Field rgba:UInt
+End
+
+Enum BlendMode
+	Opaque
+	Alpha
+	Multiply
+	Additive
+End
+
+Class RenderOp
+	Field shader:Shader
+	Field material:Material
+	Field blendMode:BlendMode
+	Field geom:Int
+	Field first:Int
+	Field count:Int
+End
+
+Class DrawList
+
+	Property Color:Color()
+	
+	Property Matrix:AffineMat3f()
+	
+	Property BlendMode:BlendMode()
+	
+	Property DefaultMaterial:Material()
+	
+	Method AddLight( light:Light )
+	
+	Method AddShadowCaster( shadowCaster:ShadowCaster )
+	
+	Method DrawImage( image:Image )
+	
+	Method DrawCircle( tx:Float,ty:Float,radius:Float,material:Material=Null )
+	
+	Method DrawPolygons( vertices:Vertex2f,geom:Int,count:Int,material:Material=Null )
+
+	Private
+	
+	Field _color:Color
+	Field _matrix:Matrix
+	Field _lights:=New Stack<Light>
+	Field _casters:=New Stack<ShadowCaster>
+	Field _vertices:=New Stack<Vertex2f>
+	Field _renderOps:=New Stack<RenderOp>
+	
+	Field _currentOp:RenderOp
+	
+	Method AddRenderOp:Int( material:Material,geom:Int,count:Int )
+		If _currentOp.material=material And _currentOp.geom=geom And _currentOp.blendMode=_blendMode
+			_currentOp.count+=count
+			Local i:=_vertices.Length
+			_vertices.Resize( i+count )
+			Return i
+		End
+		_currentOp=New RenderOp
+		_currentOp.blendMode=_blendMode
+		_currentOp.material=material
+		_currentOp.geom=geom
+		_currentOp.first=_vertices.Length
+		_currentOp.count=count*geom
+		_vertices.Resize( _currentOp.first+_currentOp.count )
+		Return _currentOp.first
+	End
+	
+End

BIN
src/ted2/mojox/mojo_window.9.png


+ 29 - 0
src/ted2/mojox/mojox.monkey2

@@ -0,0 +1,29 @@
+
+Namespace mojox
+
+#Import "<std>"
+#Import "<mojo>"
+#Import "<litehtml>"
+#Import "<hoedown>"
+
+#Import "action"
+#Import "scrollbar"
+#Import "scrollview"
+#Import "label"
+#Import "button"
+#Import "dockingview"
+#Import "menu"
+#Import "tabview"
+#Import "htmlview"
+#Import "treeview"
+#Import "filebrowser"
+#Import "textview"
+#Import "console"
+#Import "dialog"
+#Import "toolbar"
+#Import "separator"
+#Import "textfield"
+#Import "theme"
+
+Using std..
+Using mojo..

BIN
src/ted2/mojox/monkey2-logo-63.png


+ 512 - 0
src/ted2/mojox/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_mojox_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_mojox_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_mojox_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
src/ted2/mojox/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
src/ted2/mojox/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
src/ted2/mojox/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
src/ted2/mojox/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;
+}
+

+ 192 - 0
src/ted2/mojox/scrollbar.monkey2

@@ -0,0 +1,192 @@
+
+Namespace mojox
+
+Class ScrollBar Extends View
+
+	Field ValueChanged:Void( value:Int )
+
+	Method New( axis:Axis=std.geom.Axis.X )
+
+		_axis=axis
+
+		Layout="fill"
+		
+		Local taxis:=_axis=Axis.X ? "x" Else "y"
+		
+		Style=Style.GetStyle( "mojo.ScrollBar:"+taxis )
+
+		_knobStyle=Style.GetStyle( "mojo.ScrollKnob:"+taxis )
+	End
+	
+	Property Axis:Axis()
+	
+		Return _axis
+		
+	Setter( axis:Axis )
+	
+		_axis=axis
+	End
+	
+	Property PageSize:Int()
+	
+		Return _pageSize
+		
+	Setter( pageSize:Int )
+	
+		_pageSize=pageSize
+	End
+	
+	Property Value:Int()
+	
+		Return _value
+		
+	Setter( value:Int )
+	
+		_value=value
+		_value=Clamp( _value,_minimum,_maximum )
+	End
+	
+	Property Minimum:Int()
+	
+		Return _minimum
+		
+	Setter( minimum:Int )
+	
+		_minimum=minimum
+		_value=Max( _value,_minimum )
+	End
+	
+	Property Maximum:Int()
+	
+		Return _maximum
+		
+	Setter( maximum:Int )
+	
+		_maximum=maximum
+		_value=Min( _value,_maximum )
+	End
+	
+	Protected
+	
+	Field _axis:Axis
+	Field _value:Int
+	Field _minimum:Int
+	Field _maximum:Int
+	Field _pageSize:Int=1
+	
+	Field _knobStyle:Style
+	Field _knobRect:Recti
+	
+	Field _drag:Bool
+	Field _hover:Bool
+	
+	Field _offset:Int
+	
+	Method OnMeasure:Vec2i() Override
+	
+		Return _knobStyle.Bounds.Size
+	End
+	
+	Method OnLayout() Override
+	
+		Local range:=_maximum-_minimum+_pageSize
+		
+		Select _axis
+		Case Axis.X
+		
+			Local sz:=range ? Max( _pageSize*Width/range,16 ) Else Width
+			Local pos:=_maximum>_minimum ? (_value-_minimum)*(Width-sz)/(_maximum-_minimum) Else 0
+			
+			_knobRect=New Recti( pos,0,pos+sz,Height )
+		
+'			Local min:=(_value-_minimum)*Width/range
+'			Local max:=(_value-_minimum+_pageSize)*Width/range
+			
+'			_knobRect=New Recti( min,0,max,16 )
+			
+		Case Axis.Y
+		
+			Local sz:=range ? Max( _pageSize*Height/range,16 ) Else Height
+			Local pos:=_maximum>_minimum ? (_value-_minimum)*(Height-sz)/(_maximum-_minimum) Else 0
+			
+			_knobRect=New Recti( 0,pos,Width,pos+sz )
+			
+'			Local min:=(_value-_minimum)*Height/range
+'			Local max:=(_value-_minimum+_pageSize)*Height/range
+			
+'			_knobRect=New Recti( 0,min,16,max )
+		End
+		
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		If _maximum=_minimum Return
+		
+		Local style:=_knobStyle
+		
+		If _drag style=style.GetState( "active" ) Else If _hover style=style.GetState( "hover" )
+
+		style.Render( canvas,_knobRect )
+	End
+
+	Method OnMouseEvent( event:MouseEvent ) Override
+	
+		Local p:=event.Location
+		
+		Local value:=_value
+		
+		Local range:=_maximum-_minimum+_pageSize
+	
+		Select event.Type
+		Case EventType.MouseDown
+			If _knobRect.Contains( p )
+				Select _axis
+				Case Axis.X
+					_offset=p.x*range/Rect.Width-_value
+				Case Axis.Y
+					_offset=p.y*range/Rect.Height-_value
+				End
+				_drag=True
+			Else If _axis=Axis.X
+				If p.x<_knobRect.Left 
+					_value-=_pageSize
+				Else If p.x>=_knobRect.Right
+					_value+=_pageSize
+				Endif
+			Else If _axis=Axis.Y
+				If p.y<_knobRect.Top
+					_value-=_pageSize
+				Else If p.y>=_knobRect.Bottom
+					_value+=_pageSize
+				Endif
+			Endif
+		Case EventType.MouseMove
+			If _drag
+				Local range:=_maximum-_minimum+_pageSize
+				Select _axis
+				Case Axis.X
+					_value=p.x*range/Rect.Width-_offset
+				Case Axis.Y
+					_value=p.y*range/Rect.Height-_offset
+				End
+			Else If _knobRect.Contains( p )
+				_hover=True
+			Else
+				_hover=False
+			Endif
+		Case EventType.MouseUp
+			_drag=False
+		Case EventType.MouseLeave
+			_hover=False
+		End
+		
+		_value=Clamp( _value,_minimum,_maximum )
+		
+		If _value<>value
+			ValueChanged( _value )
+		Endif
+
+	End
+
+End

+ 269 - 0
src/ted2/mojox/scrollview.monkey2

@@ -0,0 +1,269 @@
+
+Namespace mojox
+
+Class ClipView Extends View
+
+	Method New()
+	
+		Layout="fill"
+	End
+	
+	Property ContentView:View()
+	
+		Return _content
+		
+	Setter( contentView:View )
+	
+		If _content RemoveChild( _content )
+		
+		_content=contentView
+		
+		If _content AddChild( _content )
+	End
+	
+	Property ContentFrame:Recti()
+	
+		Return _contentFrame
+	
+	Setter( contentFrame:Recti )
+	
+		_contentFrame=contentFrame
+	End
+	
+	Private
+	
+	Field _content:View
+	Field _contentFrame:Recti
+	
+	Method OnMeasure:Vec2i() Override
+	
+		If _content Return _content.LayoutSize
+		
+		Return New Vec2i
+	End
+	
+	Method OnLayout() Override
+	
+		If _content _content.Frame=_contentFrame
+	End
+	
+End
+
+Class ScrollView Extends View
+
+	Method New()
+		Layout="fill"
+		Style=Style.GetStyle( "mojo.ScrollView" )
+		
+		_clipper=New ClipView
+		AddChild( _clipper )
+
+		_scrollx=New ScrollBar( Axis.X )
+		_scrollx.ValueChanged=Lambda( value:Int )
+			_scroll.x=value
+		End
+		AddChild( _scrollx )
+		
+		_scrolly=New ScrollBar( Axis.Y )
+		_scrolly.ValueChanged=Lambda( value:Int )
+			_scroll.y=value
+		End
+		AddChild( _scrolly )
+	End
+
+	Method New( contentView:View )
+		Self.New()
+		
+		ContentView=contentView
+	End
+
+	Property ContentView:View()
+	
+		Return _content
+		
+	Setter( contentView:View )
+	
+		_content=contentView
+		
+		_clipper.ContentView=_content
+	End
+	
+	Property ContentMargin:Recti()
+	
+		Return _contentMargin
+	
+	Setter( contentMargin:Recti )
+	
+		_contentMargin=contentMargin
+	End
+	
+	Property ContentClipRect:Recti()
+	
+		Return New Recti( _scroll,_scroll+_clipper.Frame.Size )
+'		Return New Recti( -_clipper.ContentFrame.Origin,_clipper.Frame.Size )
+	End
+	
+	Property ScrollBarsVisible:Bool()
+	
+		Return _scrollBarsVisible
+	
+	Setter( scrollBarsVisible:Bool )
+	
+		_scrollBarsVisible=scrollBarsVisible
+	End
+	
+	Method ScrollTo( scroll:Vec2i )
+
+		If Not _clipper.Frame.Width Or Not _clipper.Frame.Height Return
+		
+		Local frame:=_clipper.Frame
+		If _content frame-=_content.Style.Bounds
+		
+		scroll.x=Min( scroll.x,_content.Width-frame.Width )
+		scroll.x=Max( scroll.x,0 )
+		
+		scroll.y=Min( scroll.y,_content.Height-frame.Height )
+		scroll.y=Max( scroll.y,0 )
+		
+		_scroll=scroll
+	End
+	
+	Method EnsureVisible( rect:Recti )
+
+		If Not _clipper.Frame.Width Or Not _clipper.Frame.Height Return
+		
+		Local frame:=_clipper.Frame
+		If _content frame-=_content.Style.Bounds
+		
+		If rect.Right>_scroll.x+frame.Width
+			_scroll.x=rect.Right-frame.Width
+		Endif
+		
+		If rect.Left<_scroll.x
+			_scroll.x=rect.Left
+		Endif
+		
+		If rect.Bottom>_scroll.y+frame.Height
+			_scroll.y=rect.Bottom-frame.Height
+		Endif
+		
+		If rect.Top<_scroll.y
+			_scroll.y=rect.Top
+		Endif
+			
+	End
+	
+	Protected
+	
+	Method OnMeasure:Vec2i() Override
+	
+		Return _clipper.LayoutSize+_contentMargin.Size
+	End
+	
+	Method OnLayout() Override
+	
+		If Not _content Return
+	
+		Local size:=Rect.Size
+		
+		Local csize:=size-_contentMargin.Size
+		Local vsize:=_content.Measure2( size-New Vec2i( _scrolly.LayoutSize.x,0 ) )
+		
+		If _scrollBarsVisible
+
+	'		Print "size="+size.ToString()
+	'		Print "csize="+csize.ToString()
+	'		Print "vsize="+vsize.ToString()
+			
+			Local xbar:=_scrollx.LayoutSize.y
+			Local ybar:=_scrolly.LayoutSize.x
+			
+			If vsize.y<=csize.y
+				If vsize.x<=csize.x xbar=0
+			Else
+				If vsize.x<=csize.x-ybar xbar=0
+			Endif
+			
+			If vsize.y<=csize.y-xbar ybar=0
+			
+			csize.x-=ybar
+			csize.y-=xbar
+			
+			If xbar
+				_scrollx.Visible=True
+				_scrollx.Frame=New Recti( 0,size.y-xbar,size.x-ybar,size.y )
+				_scrollx.PageSize=csize.x
+				_scrollx.Maximum=vsize.x-csize.x
+				_scrollx.Value=_scroll.x
+			Else
+				_scrollx.Visible=False
+				_scrollx.Value=0
+				vsize.x=csize.x
+			Endif
+			
+			If ybar
+				_scrolly.Visible=True
+				_scrolly.Frame=New Recti( size.x-ybar,0,size.x,size.y-xbar )
+				_scrolly.PageSize=csize.y
+				_scrolly.Maximum=vsize.y-csize.y
+				_scrolly.Value=_scroll.y
+			Else
+				_scrolly.Visible=False
+				_scrolly.Value=0
+				vsize.y=csize.y
+			Endif
+
+		Else
+			_scrollx.Visible=False
+			_scrollx.PageSize=csize.x
+			_scrollx.Maximum=vsize.x-csize.x
+			_scrollx.Value=_scroll.x
+			
+			_scrolly.Visible=False
+			_scrolly.PageSize=csize.y
+			_scrolly.Maximum=vsize.y-csize.y
+			_scrolly.Value=_scroll.y
+
+		Endif
+		
+		_scroll.x=_scrollx.Value
+		_scroll.y=_scrolly.Value
+		
+		_clipper.ContentFrame=New Recti( -_scroll,-_scroll+vsize )
+		
+		_clipper.Frame=New Recti( 0,0,csize )-_contentMargin.Origin
+
+	End
+	
+	Method OnKeyEvent( event:KeyEvent ) Override
+	
+		If _content _content.SendKeyEvent( event )
+	End
+	
+	Method OnMouseEvent( event:MouseEvent ) Override
+	
+		Select event.Type
+		Case EventType.MouseWheel
+			_scroll.y-=event.Wheel.Y*16
+		End
+		
+	End
+
+	Private
+	
+	Field _content:View
+	Field _contentMargin:Recti
+	
+	Field _scroll:Vec2i
+
+	Field _clipper:ClipView	
+	Field _scrollx:ScrollBar
+	Field _scrolly:ScrollBar
+	
+	Field _scrollBarsVisible:Bool=True
+	
+End
+
+	
+	
+	

+ 11 - 0
src/ted2/mojox/separator.monkey2

@@ -0,0 +1,11 @@
+
+Namespace mojox
+
+Class Separator Extends View
+
+	Method New()
+		Layout="fill"
+		Style=Style.GetStyle( "mojo.Separator" )
+	End
+
+End

+ 180 - 0
src/ted2/mojox/tabview.monkey2

@@ -0,0 +1,180 @@
+
+Namespace mojox
+
+Class TabButton Extends Button
+
+	Method New( text:String,view:View )
+		Super.New( text )
+		Style=Style.GetStyle( "mojo.TabButton" )
+		TextGravity=New Vec2f( 0,.5 )
+		_view=view
+	End
+	
+	Property View:View()
+	
+		Return _view
+	End
+	
+	Private
+	
+	Field _view:View
+
+End
+
+Class TabView Extends View
+
+	Field CurrentChanged:Void()
+
+	Method New()
+		Style=Style.GetStyle( "mojo.TabView" )
+		Layout="fill"
+	End
+	
+	Property Count:Int()
+	
+		Return _tabs.Length
+	End
+	
+	Property CurrentIndex:Int()
+
+		If _current Return IndexOfView( _current.View )
+		Return -1
+		
+	Setter( currentIndex:Int )
+	
+		MakeCurrent( _tabs[currentIndex] )
+	End
+	
+	Property CurrentView:View()
+	
+		If _current Return _current.View
+		Return Null
+	
+	Setter( currentView:View )
+	
+		For Local tab:=Eachin _tabs
+			If tab.View<>currentView Continue
+			MakeCurrent( tab )
+			Return
+		Next
+	End
+	
+	Method ViewAtIndex:View( index:Int )
+		Return _tabs[index].View
+	End
+
+	Method IndexOfView:Int( view:View )
+		For Local i:=0 Until _tabs.Length
+			If _tabs[i].View=view Return i
+		Next
+		Return -1
+	End
+
+	Method AddTab:Int( text:String,view:View,makeCurrent:Bool=False )
+
+		Local index:=_tabs.Length
+
+		Local tab:=New TabButton( text,view )
+		tab.Clicked=Lambda()
+			MakeCurrent( tab )
+		End
+		_tabs.Add( tab )
+
+		AddChild( tab )
+
+		If makeCurrent MakeCurrent( tab )
+
+		Return index
+	End
+	
+	Method RemoveTab( view:View )
+	
+		RemoveTab( IndexOfView( view ) )
+	End
+	
+	Method RemoveTab( index:Int )
+	
+		If _current=_tabs[index]
+			_current.Selected=False
+			RemoveChild( _current.View.Container )
+			_current=Null
+		Endif
+		
+		RemoveChild( _tabs[index] )
+		
+		_tabs.Erase( index )
+	End
+	
+	Method SetTabLabel( view:View,label:String )
+	
+		SetTabLabel( IndexOfView( view ),label )
+	
+	End
+	
+	Method SetTabLabel( index:Int,label:String )
+	
+		_tabs[index].Text=label
+	End
+	
+	Private
+	
+	Field _tabs:=New Stack<TabButton>
+	
+	Field _current:TabButton
+	
+	Field _buttonsSize:Vec2i
+	
+	Method MakeCurrent( tab:TabButton )
+	
+		If tab=_current Return
+		
+		If _current 
+			_current.Selected=False
+			RemoveChild( _current.View.Container )
+		Endif
+
+		_current=tab
+
+		If _current
+			_current.Selected=True
+			AddChild( _current.View.Container )
+		Endif
+		
+		CurrentChanged()
+			
+	End
+	
+	Method OnMeasure:Vec2i() Override
+	
+		Local size:=New Vec2i
+		
+		For Local tab:=Eachin _tabs
+			size.x+=tab.LayoutSize.x
+			size.y=Max( size.y,tab.LayoutSize.y )
+		Next
+		
+		_buttonsSize=size
+		
+		If _current
+			size.x=Max( size.x,_current.View.LayoutSize.x )
+			size.y+=_current.View.LayoutSize.y
+		Endif
+		
+		Return size
+	
+	End
+	
+	Method OnLayout() Override
+	
+		Local x:=0
+		
+		For Local tab:=Eachin _tabs
+			tab.Frame=New Recti( x,0,x+tab.LayoutSize.x,_buttonsSize.y )
+			x+=tab.LayoutSize.x
+		Next
+		
+		If _current _current.View.Container.Frame=New Recti( 0,_buttonsSize.y,Width,Height )
+	End
+	
+End
+

+ 41 - 0
src/ted2/mojox/test.monkey2

@@ -0,0 +1,41 @@
+
+Namespace ted
+
+#Import "mojox"
+
+Using std..
+Using mojo..
+Using mojo2..
+
+Class MainWindow Extends Window
+
+	Method New( title:String,rect:Recti,flags:WindowFlags )
+		Super.New( title,rect,flags )
+		
+		Style=New Style( Style )
+		Style.BackgroundColor=Color.Magenta
+		
+		App.RequestRender()
+		
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		canvas.DrawText( "Hello World",0,0 )
+		
+		App.RequestRender()
+		
+		GCCollect()
+	End
+
+End
+
+Function Main()
+
+	New AppInstance
+	
+	New MainWindow( "Ted2",New Recti( 16,16,640+16,640+16 ),WindowFlags.Resizable )
+	
+	App.Run()
+	
+End

+ 15 - 0
src/ted2/mojox/test2.monkey2

@@ -0,0 +1,15 @@
+
+Global t:=New Float[100]
+
+Function Main()
+
+	Local sum:Float
+	
+	For Local i:=0 Until 100
+	
+		sum+=t[i]
+		
+		DebugAssert( True,"This better work!" )
+	Next
+	
+End

+ 39 - 0
src/ted2/mojox/textfield.monkey2

@@ -0,0 +1,39 @@
+
+Namespace mojox
+
+Class TextField Extends TextView
+
+	Field EnterHit:Void()
+	
+	Field TabHit:Void()
+
+	Method New()
+		Style=Style.GetStyle( "mojo.TextField" )
+		Local scroller:=Cast<ScrollView>( Container )
+		scroller.ScrollBarsVisible=False
+		scroller.Layout="fill-x"
+	End
+	
+	Protected
+	
+	Method OnKeyEvent( event:KeyEvent ) Override
+	
+		Select event.Type
+		Case EventType.KeyDown
+			Select event.Key
+			Case Key.Enter
+'				SelectText( 0,0 )
+				EnterHit()
+				Return
+			Case Key.Tab
+'				SelectText( 0,0 )
+				TabHit()
+				Return
+			End
+		End
+		
+		Super.OnKeyEvent( event)
+	
+	End
+	
+End

+ 1066 - 0
src/ted2/mojox/textview.monkey2

@@ -0,0 +1,1066 @@
+
+Namespace mojox
+
+Alias TextHighlighter:Int( text:String,colors:Byte[],sol:Int,eol:Int,state:Int )
+
+Class TextDocument
+
+	Field LinesModified:Void( first:Int,removed:Int,inserted:Int )
+
+	Field TextChanged:Void()
+	
+	Method New()
+	
+		_lines.Push( New Line )
+	End
+
+	Property Text:String()
+	
+		Return _text
+		
+	Setter( text:String )
+	
+		text=text.Replace( "~r~n","~n" )
+		text=text.Replace( "~r","~n" )
+	
+		ReplaceText( 0,_text.Length,text )
+	End
+	
+	Property TextLength:Int()
+	
+		Return _text.Length
+	End
+	
+	Property LineCount:Int()
+	
+		Return _lines.Length
+	End
+	
+	Property Colors:Byte[]()
+	
+		Return _colors.Data
+	End
+	
+	Property TextHighlighter:TextHighlighter()
+	
+		Return _highlighter
+	
+	Setter( textHighlighter:TextHighlighter )
+	
+		_highlighter=textHighlighter
+	End
+	
+	Method LineState:Int( line:Int )
+		If line>=0 And line<_lines.Length Return _lines[line].state
+		Return -1
+	End
+	
+	Method StartOfLine:Int( line:Int )
+		If line<=0 Return 0
+		If line<_lines.Length Return _lines[line-1].eol+1
+		Return _text.Length
+	End
+	
+	Method EndOfLine:Int( line:Int )
+		If line<0 Return 0
+		If line<_lines.Length Return _lines[line].eol
+		Return _text.Length
+	End
+	
+	Method FindLine:Int( index:Int )
+	
+		If index<=0 Return 0
+		If index>=_text.Length Return _lines.Length-1
+		
+		Local min:=0,max:=_lines.Length-1
+		
+		Repeat
+			Local line:=(min+max)/2
+			If index>_lines[line].eol
+				min=line+1
+			Else If max-min<2
+				Return min
+			Else
+				max=line
+			Endif
+		Forever
+
+		Return 0		
+	End
+
+	Method GetLine:String( line:Int )
+		Return _text.Slice( StartOfLine( line ),EndOfLine( line ) )
+	End
+	
+	Method AppendText( text:String )
+	
+		ReplaceText( _text.Length,_text.Length,text )
+	End
+	
+	Method ReplaceText( anchor:Int,cursor:Int,text:String )
+	
+		Local min:=Min( anchor,cursor )
+		Local max:=Max( anchor,cursor )
+		
+		Local eols1:=0,eols2:=0
+		For Local i:=min Until max
+			If _text[i]=10 eols1+=1
+		Next
+		For Local i:=0 Until text.Length
+			If text[i]=10 eols2+=1
+		Next
+		
+		Local dlines:=eols2-eols1
+		Local dchars:=text.Length-(max-min)
+		
+		Local line0:=FindLine( min )
+		Local line:=line0
+		Local eol:=StartOfLine( line )-1
+		
+'		Print "eols1="+eols1+", eols2="+eols2+", dlines="+dlines+", dchars="+dchars+" text="+text.Length
+		
+		'Move data!
+		'
+		Local oldlen:=_text.Length
+		_text=_text.Slice( 0,min )+text+_text.Slice( max )
+		
+		_colors.Resize( _text.Length )
+		Local p:=_colors.Data.Data
+		libc.memmove( p + min + text.Length, p + max , oldlen-max )
+		libc.memset( p + min , 0 , text.Length )
+		
+		'Update lines
+		'
+		If dlines>=0
+		
+			_lines.Resize( _lines.Length+dlines )
+
+			Local i:=_lines.Length
+			While i>line+eols2+1
+				i-=1
+				_lines.Data[i].eol=_lines[i-dlines].eol+dchars
+				_lines.Data[i].state=_lines[i-dlines].state
+			Wend
+		
+		Endif
+
+		For Local i:=0 Until eols2+1
+			eol=_text.Find( "~n",eol+1 )
+			If eol=-1 eol=_text.Length
+			_lines.Data[line+i].eol=eol
+			_lines.Data[line+i].state=-1
+		Next
+		
+		If dlines<0
+
+			Local i:=line+eols2+1
+			While i<_lines.Length+dlines
+				_lines.Data[i].eol=_lines[i-dlines].eol+dchars
+				_lines.Data[i].state=_lines[i-dlines].state
+				i+=1
+			Wend
+
+			_lines.Resize( _lines.Length+dlines )
+		Endif
+
+		If _highlighter<>Null
+		
+			'update highlighting
+			'
+			Local state:=-1
+			If line state=_lines[line-1].state
+			
+			For Local i:=0 Until eols2+1
+				state=_highlighter( _text,_colors.Data,StartOfLine( line ),EndOfLine( line ),state )
+				_lines.Data[line].state=state
+				line+=1
+			Next
+			
+			While line<_lines.Length 'And state<>_lines[line].state
+				state=_highlighter( _text,_colors.Data,StartOfLine( line ),EndOfLine( line ),state )
+				_lines.Data[line].state=state
+				line+=1
+			End
+		Endif
+		
+'		Print "lines="+_lines.Length+", chars="+_text.Length
+
+		LinesModified( line0,eols1+1,eols2+1 )
+		
+		TextChanged()
+	End
+	
+	#rem
+		_lines.Resize( _lines.Length+dlines )
+		
+		
+		'eols1=eols deleted, eols2=eols inserted, dchars=delta chars
+		
+		
+		Local oldlen:=Text.Length
+		_text=_text.Slice( 0,min )+text+_text.Slice( max )
+		
+		_colors.Resize( _text.Length )
+		
+		Local p:=Varptr( _colors.Data[0] )
+		libc.memmove( p+min+text.Length,p+max,oldlen-max )
+		libc.memset( p+min,0,text.Length )
+		
+		UpdateEols()
+		
+		If eols1>eols2
+			LinesDeleted( FindLine( min ),eols1-eols2 )
+		Else If eols2>eols1
+			LinesInserted( FindLine( min ),eols2-eols1 )
+		Endif
+		
+		TextChanged()
+	End
+	#end
+	
+	Method HighlightLine( line:Int )
+#rem	
+		Return
+	
+		If _highlighter=Null Return
+		
+		If _lines[line].state<>-1 Return
+	
+		Local sol:=StartOfLine( line )
+		Local eol:=EndOfLine( line )
+		If eol>sol
+			Local colors:=_colors.Data
+			_highlighter( _text,colors,sol,eol )
+		Endif
+		
+		_lines.Data[line].state=0
+#end
+
+	End
+	
+	Private
+	
+	Struct Line
+		Field eol:Int
+		Field state:Int
+	End
+	
+	Field _text:String
+	
+	Field _lines:=New Stack<Line>
+	Field _colors:=New Stack<Byte>
+	Field _highlighter:TextHighlighter
+	
+	#rem
+	'not very efficient - scans entire document and recalcs all EOLs.
+	'
+	Method UpdateEols()
+	
+		_nlines=1
+		Local eol:=-1
+		
+		Repeat
+			eol=_text.Find( "~n",eol+1 )
+			If eol=-1 Exit
+			_nlines+=1
+		Forever
+		
+		_eols.Resize( _nlines )
+		
+		Local line:=0
+		Repeat
+			eol=_text.Find( "~n",eol+1 )
+			If eol=-1
+				_eols.Data[line].eol=_text.Length
+				Exit
+			Endif
+			
+			_eols.Data[line].eol=eol
+			line+=1
+		Forever
+		
+		'invalidate all line coloring
+		'
+		_colors.Resize( _text.Length )
+		
+		For Local i:=0 Until _nlines
+			Local sol:=StartOfLine( i )
+			If sol>=_colors.Length Exit
+			_colors[ sol ]=-1
+		Next
+		
+'		Print "lines="+_nlines
+'		For Local i:=0 Until _nlines
+'			Print "eol="+_eols[i]
+'		Next
+	End
+	
+	#end
+	
+End
+
+Class TextView Extends View
+
+	Field CursorMoved:Void()
+
+	Field FieldEntered:Void()
+	
+	Field FieldTabbed:Void()
+
+	Method New()
+	
+		Layout="fill"
+	
+		Style=Style.GetStyle( "mojo.TextView" )
+
+		_doc=New TextDocument
+		
+'		_textColors=New Color[]( New Color( 0,0,0,1 ),New Color( 0,0,.5,1 ),New Color( 0,.5,0,1 ),New Color( .5,0,0,1 ),New Color( .5,0,.5,1 ) )
+		_textColors=New Color[]( New Color( 1,1,1,1 ),New Color( 0,1,0,1 ),New Color( 1,1,0,1 ),New Color( 0,.5,1,1 ),New Color( 0,1,.5,1 ) )
+	End
+	
+	Method New( doc:TextDocument )
+		Self.New()
+	
+		_doc=doc
+	End
+	
+	Property Document:TextDocument()
+	
+		Return _doc
+		
+	Setter( doc:TextDocument )
+	
+		_doc=doc
+		
+		_cursor=Clamp( _cursor,0,_doc.TextLength )
+		_anchor=_cursor
+		
+		UpdateCursor()
+	End
+	
+	Property TextColors:Color[]()
+	
+		Return _textColors
+	
+	Setter( textColors:Color[] )
+	
+		_textColors=textColors
+	End
+	
+	Property SelectionColor:Color()
+	
+		Return _selColor
+		
+	Setter( selectionColor:Color )
+	
+		_selColor=selectionColor
+	End
+	
+	Property CursorColor:Color()
+	
+		Return _cursorColor
+	
+	Setter( cursorColor:Color )
+	
+		_cursorColor=cursorColor
+	End
+	
+	Property BlockCursor:Bool()
+	
+		Return _blockCursor
+	
+	Setter( blockCursor:Bool )
+	
+		_blockCursor=blockCursor
+	End
+
+	Property Text:String()
+	
+		Return _doc.Text
+		
+	Setter( text:String )
+	
+		_doc.Text=text
+	End
+	
+	Property ReadOnly:Bool()
+	
+		Return _readOnly
+	
+	Setter( readOnly:Bool )
+	
+		_readOnly=readOnly
+	End
+	
+	Property TabsStop:Int()
+	
+		Return _tabStop
+		
+	Setter( tabStop:Int )
+	
+		_tabStop=tabStop
+		_tabSpaces=" "
+		For Local i:=1 Until tabStop
+			_tabSpaces+=" "
+		Next
+	End
+	
+	Property Cursor:Int()
+	
+		Return _cursor
+	End
+	
+	Property Anchor:Int()
+	
+		Return _anchor
+	End
+	
+	Property CursorColumn:Int()
+	
+		Return Column( _cursor )
+	End
+	
+	Property CursorRow:Int()
+	
+		Return Row( _cursor )
+	End
+	
+	Property CursorRect:Recti()
+
+		Return _cursorRect
+	End
+	
+	Property LineHeight:Int()
+	
+		Return _charh
+	End
+	
+	Property CanUndo:Bool()
+	
+		Return Not _readOnly And Not _undos.Empty
+	End
+	
+	Property CanRedo:Bool()
+	
+		Return Not _readOnly And Not _redos.Empty
+	End
+	
+	Property CanCut:Bool()
+	
+		Return Not _readOnly And _anchor<>_cursor
+	End
+	
+	Property CanCopy:Bool()
+	
+		Return _anchor<>_cursor
+	End
+	
+	Property CanPaste:Bool()
+	
+		Return Not _readOnly And Not App.ClipboardTextEmpty
+	End
+	
+	Property Container:View() Override
+	
+		If Not _scroller
+		
+			_scroller=New ScrollView
+			_scroller.ContentView=Self
+			
+			CursorMoved+=Lambda()
+				_scroller.EnsureVisible( CursorRect-New Vec2i( _gutterw,0 ) )
+			End
+			
+		Endif
+		
+		Return _scroller
+	End
+	
+	Method Clear()
+		SelectAll()
+		ReplaceText( "" )
+	End
+	
+	Method SelectText( anchor:Int,cursor:Int )
+		_anchor=Clamp( anchor,0,_doc.TextLength )
+		_cursor=Clamp( cursor,0,_doc.TextLength )
+		UpdateCursor()
+	End
+	
+	Method ReplaceText( text:String )
+	
+		Local undo:=New UndoOp
+		undo.text=_doc.Text.Slice( Min( _anchor,_cursor ),Max( _anchor,_cursor ) )
+		undo.anchor=Min( _anchor,_cursor )
+		undo.cursor=undo.anchor+text.Length
+		_undos.Push( undo )
+		
+		ReplaceText( _anchor,_cursor,text )
+	End
+	
+	'non-undoable
+	Method ReplaceText( anchor:Int,cursor:Int,text:String )
+			
+		_redos.Clear()
+	
+		_doc.ReplaceText( anchor,cursor,text )
+		_cursor=Min( anchor,cursor )+text.Length
+		_anchor=_cursor
+		
+		UpdateCursor()
+	End
+	
+	Method Undo()
+		If _readOnly Return
+	
+		If _undos.Empty Return
+		
+		Local undo:=_undos.Pop()
+
+		Local text:=undo.text
+		Local anchor:=undo.anchor
+		Local cursor:=undo.cursor
+		
+		undo.text=_doc.Text.Slice( anchor,cursor )
+		undo.cursor=anchor+text.Length
+		
+		_redos.Push( undo )
+		
+		_doc.ReplaceText( anchor,cursor,text )
+		_cursor=anchor+text.Length
+		_anchor=_cursor
+		
+		UpdateCursor()
+	End
+	
+	Method Redo()
+		If _readOnly Return
+		
+		If _redos.Empty Return
+
+		Local undo:=_redos.Pop()
+		
+		Local text:=undo.text
+		Local anchor:=undo.anchor
+		Local cursor:=undo.cursor
+		
+		undo.text=_doc.Text.Slice( anchor,cursor )
+		undo.cursor=anchor+text.Length
+		
+		_undos.Push( undo )
+		
+		_doc.ReplaceText( anchor,cursor,text )
+		_cursor=anchor+text.Length
+		_anchor=_cursor
+		
+		UpdateCursor()
+	End
+	
+	Method SelectAll()
+		SelectText( 0,_doc.TextLength )
+	End
+	
+	Method Cut()
+		If _readOnly Return
+		Copy()
+		ReplaceText( "" )
+	End
+	
+	Method Copy()
+		Local min:=Min( _anchor,_cursor )
+		Local max:=Max( _anchor,_cursor )
+		Local text:=_doc.Text.Slice( min,max )
+		App.ClipboardText=text
+	End
+	
+	Method Paste()
+	
+		If _readOnly Return
+		
+		If App.ClipboardTextEmpty Return
+		
+		Local text:String=App.ClipboardText
+		text=text.Replace( "~r~n","~n" )
+		text=text.Replace( "~r","~n" )
+		
+		If text ReplaceText( text )
+	End
+	
+	Private
+	
+	Class UndoOp
+		Field text:String
+		Field anchor:Int
+		Field cursor:Int
+	End
+	
+	Field _doc:TextDocument
+	Field _tabStop:Int=4
+	Field _tabSpaces:String="    "
+	Field _cursorColor:Color=New Color( 0,.5,1,1 )
+	Field _selColor:Color=New Color( 1,1,1,.25 )
+	Field _blockCursor:Bool=True
+	
+	Field _textColors:Color[]
+	
+	Field _anchor:Int
+	Field _cursor:Int
+	
+	Field _tabw:Int
+	Field _charw:Int
+	Field _charh:Int
+	Field _gutterw:Int
+	Field _columnX:Int
+	Field _cursorRect:Recti
+	
+	Field _contentMargin:Recti
+	
+	Field _undos:=New Stack<UndoOp>
+	Field _redos:=New Stack<UndoOp>
+	
+	Field _dragging:Bool
+	
+	Field _scroller:ScrollView
+	
+	Field _readOnly:Bool
+	
+	Method Row:Int( index:Int )
+		Return _doc.FindLine( index )
+	End
+	
+	Method Column:Int( index:Int )
+		Return index-_doc.StartOfLine( _doc.FindLine( index ) )
+	End
+	
+	Method UpdateCursor()
+	
+		Local rect:=MakeCursorRect( _cursor )
+		If rect=_cursorRect Return
+		
+		_cursorRect=rect
+		_columnX=rect.X
+		
+		CursorMoved()
+	End
+	
+	Method MakeCursorRect:Recti( cursor:Int )
+	
+		ValidateStyle()
+		
+		Local line:=_doc.FindLine( cursor )
+		Local text:=_doc.GetLine( line )
+		
+		Local x:=0.0,i0:=0,e:=cursor-_doc.StartOfLine( line )
+		
+		While i0<e
+		
+			Local i1:=text.Find( "~t",i0 )
+			If i1=-1 i1=e
+			
+			If i1>i0
+				If i1>e i1=e
+				x+=Style.DefaultFont.TextWidth( text.Slice( i0,i1 ) )
+				If i1=e Exit
+			Endif
+			
+			x=Int( (x+_tabw)/_tabw ) * _tabw
+			i0=i1+1
+			
+		Wend
+		
+		Local w:=_charw
+		
+		If e<text.Length
+			If text[e]=9
+'				w=Int( (x+_tabw)/_tabw ) * _tabw-x
+			Else
+				w=Style.DefaultFont.TextWidth( text.Slice( e,e+1 ) )
+			Endif
+		Endif
+		
+		x+=_gutterw
+		Local y:=line*_charh
+		
+		Return New Recti( x,y,x+w,y+_charh )
+	End
+	
+	Method PointXToIndex:Int( px:Int,line:Int )
+	
+		ValidateStyle()
+
+		px=Max( px-_gutterw,0 )
+		
+		Local text:=_doc.GetLine( line )
+		Local sol:=_doc.StartOfLine( line )
+		
+		Local x:=0.0,i0:=0,e:=text.Length
+		
+		While i0<e
+		
+			Local i1:=text.Find( "~t",i0 )
+			If i1=-1 i1=e
+			
+			If i1>i0
+				For Local i:=i0 Until i1
+					x+=Style.DefaultFont.TextWidth( text.Slice( i,i+1 ) )
+					If px<x Return sol+i
+				Next
+				If i1=e Exit
+			Endif
+			
+			x=Int( (x+_tabw)/_tabw ) * _tabw
+			If px<x Return sol+i0
+			
+			i0=i1+1
+		Wend
+		
+		Return sol+e
+	
+	End
+	
+	Method PointToIndex:Int( p:Vec2i )
+	
+		If p.y<0 Return 0
+		
+		Local line:=p.y/_charh
+		If line>_doc.LineCount Return _doc.TextLength
+		
+		Return PointXToIndex( p.x,line )
+	End
+	
+	Method MoveLine( delta:Int )
+	
+		Local line:=Clamp( Row( _cursor )+delta,0,_doc.LineCount-1 )
+		
+		_cursor=PointXToIndex( _columnX,line )
+		
+		Local x:=_columnX
+		
+		UpdateCursor()
+		
+		_columnX=x
+	End
+	
+	Protected
+	
+	Property GutterWidth:Int()
+	
+		Return _gutterw
+	
+	Setter( gutterWidth:Int )
+	
+		_gutterw=gutterWidth
+	End
+	
+	Property ContentMargin:Recti()
+	
+		Return _contentMargin
+	
+	Setter( contentMargin:Recti )
+	
+		_contentMargin=contentMargin
+	End
+	
+	Method OnValidateStyle() Override
+	
+		_charw=Style.DefaultFont.TextWidth( "X" )
+		_charh=Style.DefaultFont.Height
+		_tabw=_charw*_tabStop
+		
+		UpdateCursor()
+	End
+	
+	Method OnMeasure:Vec2i() Override
+		Return New Vec2i( 320*_charw+_gutterw,_doc.LineCount*_charh )+_contentMargin.Size
+	End
+
+	Method OnRender( canvas:Canvas ) Override
+	
+		Local firstVisLine:=ClipRect.Top/_charh
+		Local lastVisLine:=Min( (ClipRect.Bottom-1)/_charh+1,_doc.LineCount )
+		
+		If _cursor<>_anchor
+		
+			Local min:=MakeCursorRect( Min( _anchor,_cursor ) )
+			Local max:=MakeCursorRect( Max( _anchor,_cursor ) )
+			
+			canvas.Color=_selColor
+			
+			If min.Y=max.Y
+				canvas.DrawRect( min.Left,min.Top,max.Left-min.Left,min.Height )
+			Else
+				canvas.DrawRect( min.Left,min.Top,(ClipRect.Right-min.Left),min.Height )
+				canvas.DrawRect( _gutterw,min.Bottom,ClipRect.Right-_gutterw,max.Top-min.Bottom )
+				canvas.DrawRect( _gutterw,max.Top,max.Left-_gutterw,max.Height )
+			Endif
+			
+		Else If Not _readOnly And App.KeyView=Self
+		
+			canvas.Color=_cursorColor
+			
+			If _blockCursor
+				canvas.DrawRect( _cursorRect.X,_cursorRect.Y,_cursorRect.Width,_cursorRect.Height )
+			Else
+				canvas.DrawRect( _cursorRect.X-0,_cursorRect.Y,2,_cursorRect.Height )
+				canvas.DrawRect( _cursorRect.X-2,_cursorRect.Y,6,2 )
+				canvas.DrawRect( _cursorRect.X-2,_cursorRect.Y+_cursorRect.Height-2,6,2 )
+			Endif
+			
+		Endif
+
+		_textColors[0]=Style.DefaultColor
+		
+		For Local line:=firstVisLine Until lastVisLine
+		
+			_doc.HighlightLine( line )
+		
+			Local sol:=_doc.StartOfLine( line )
+			Local eol:=_doc.EndOfLine( line )
+
+			Local text:=_doc.Text.Slice( sol,eol )
+			Local colors:=_doc.Colors
+			
+			Local x:=0,y:=line*_charh,i0:=0
+			
+			While i0<text.Length
+			
+				Local i1:=text.Find( "~t",i0 )
+				If i1=-1 i1=text.Length
+				
+				If i1>i0
+					
+					Local color:=colors[sol+i0]
+					Local start:=i0
+					
+					Repeat
+						
+						While i0<i1 And colors[sol+i0]=color
+							i0+=1
+						Wend
+						
+						If i0>start
+							If color<0 Or color>=_textColors.Length color=0
+							canvas.Color=_textColors[color]
+							
+							Local t:=text.Slice( start,i0 )
+							canvas.DrawText( t,x+_gutterw,y )
+							x+=Style.DefaultFont.TextWidth( t )
+						Endif
+						
+						If i0=i1 Exit
+						
+						color=colors[sol+i0]
+						start=i0
+						i0+=1
+						
+					Forever
+				
+					If i1=text.Length Exit
+					
+				Endif
+				
+				x=Int( (x+_tabw) / _tabw ) * _tabw
+				
+				i0=i1+1
+			
+			Wend
+			
+		Next
+		
+#rem		
+		If _flags & TextViewFlags.ShowLineNumbers
+		
+			Local GutterColor:=New Color( .9,.9,.9,1 )
+			
+			canvas.SetColor( GutterColor.r,GutterColor.g,GutterColor.b,GutterColor.a )
+			canvas.DrawRect( ClipRect.X,ClipRect.Y,_gutterw-_charw,ClipRect.Height )
+		
+			canvas.SetColor( Style.DefaultColor.r,Style.DefaultColor.g,Style.DefaultColor.b,Style.DefaultColor.a*.5 )
+
+			For Local i:=firstVisLine Until lastVisLine
+				canvas.DrawText( (i+1),ClipRect.X+_gutterw-_charh,i*_charh,1,0 )
+			Next
+		End
+#end
+		
+	End
+	
+	Method OnKeyEvent( event:KeyEvent ) Override
+	
+		If _readOnly Return
+	
+		Select event.Type
+		
+		Case EventType.KeyDown,EventType.KeyRepeat
+
+			Local control:=event.Modifiers & Modifier.Control
+		
+			Select event.Key
+			
+			Case Key.A
+			
+				If control SelectAll()
+				Return
+				
+			Case Key.X
+			
+				If control Cut()
+				Return
+				
+			Case Key.C
+			
+				If control Copy()
+				Return
+			
+			Case Key.V
+			
+				If control Paste()
+				Return
+				
+			Case Key.Z
+			
+				If control Undo()
+				Return
+			
+			Case Key.Backspace
+			
+				If _anchor=_cursor And _cursor>0 SelectText( _cursor-1,_cursor )
+				ReplaceText( "" )
+				
+			Case Key.KeyDelete
+			
+				If _anchor=_cursor And _cursor<_doc.Text.Length SelectText( _cursor,_cursor+1 )
+				ReplaceText( "" )
+				
+			Case Key.Tab
+			
+				ReplaceText( "~t" )
+				
+			Case Key.Enter
+			
+				ReplaceText( "~n" )
+				
+				'auto indent!
+				Local line:=CursorRow
+				If line>0
+				
+					Local ptext:=_doc.GetLine( line-1 )
+					
+					Local indent:=ptext
+					For Local i:=0 Until ptext.Length
+						If ptext[i]<=32 Continue
+						indent=ptext.Slice( 0,i )
+						Exit
+					Next
+					
+					If indent ReplaceText( indent )
+				
+				Endif
+				
+			Case Key.Left
+			
+				If _cursor 
+					_cursor-=1
+					UpdateCursor()
+				Endif
+				
+			Case Key.Right
+			
+				If _cursor<_doc.Text.Length
+					_cursor+=1
+					UpdateCursor()
+				Endif
+				
+			Case Key.Home
+			
+				If control
+					_cursor=0
+				Else				
+					_cursor=_doc.StartOfLine( Row( _cursor ) )
+				Endif
+				
+				UpdateCursor()
+				
+			Case Key.KeyEnd
+			
+				If control
+					_cursor=_doc.TextLength
+				Else
+					_cursor=_doc.EndOfLine( Row( _cursor ) )
+				Endif
+				
+				UpdateCursor()
+
+			Case Key.Up
+			
+				MoveLine( -1 )
+			
+			Case Key.Down
+			
+				MoveLine( 1 )
+				
+			Case Key.PageUp
+			
+				Local n:=ClipRect.Height/_charh-1		'shouldn't really use cliprect here...
+				
+				MoveLine( -n )
+				
+			Case Key.PageDown
+			
+				Local n:=ClipRect.Height/_charh-1
+				
+				MoveLine( n )
+				
+			Default
+			
+				Return
+			End
+			
+			If Not (event.Modifiers & Modifier.Shift) _anchor=_cursor
+			
+		Case EventType.KeyChar
+		
+			If _undos.Length
+				Local undo:=_undos.Top
+				If Not undo.text And _cursor=undo.cursor
+					ReplaceText( _anchor,_cursor,event.Text )
+					undo.cursor=_cursor
+					Return
+				Endif
+			Endif
+		
+			ReplaceText( event.Text )
+			
+		End
+	End
+	
+	Method OnMouseEvent( event:MouseEvent ) Override
+	
+		Select event.Type
+		Case EventType.MouseDown
+			App.KeyView=Self
+			_cursor=PointToIndex( event.Location )
+			_anchor=_cursor
+			_dragging=True
+			UpdateCursor()
+		Case EventType.MouseUp
+			_dragging=False
+		Case EventType.MouseMove
+			If _dragging
+				_cursor=PointToIndex( event.Location )
+				UpdateCursor()
+			Endif
+		Case EventType.MouseWheel
+			Super.OnMouseEvent( event )
+			Return
+		End
+		
+	End
+	
+	Method OnMakeKeyView() Override
+		Super.OnMakeKeyView()
+		UpdateCursor()
+	End
+	
+End

+ 258 - 0
src/ted2/mojox/theme.monkey2

@@ -0,0 +1,258 @@
+
+Namespace mojox
+
+#Import "assets/checkmark_icons.png@/mojox"
+#Import "assets/treenode_expanded.png@/mojox"
+#Import "assets/treenode_collapsed.png@/mojox"
+
+Const Theme:=New ThemeInstance
+
+Class ThemeInstance
+
+	Property Name:String()
+		Return _name
+	End
+
+	Property ClearColor:Color()
+		Return _clearColor
+	End
+	
+	Method Load()
+	
+		_name="dark"
+		_fontSize=16
+		_monoFontSize=16
+		
+		Local obj:=JsonObject.Load( "bin/ted2.config.json" )
+		If obj
+			If obj.Contains( "theme" )
+				_name=obj["theme"].ToString()
+			Endif
+			If obj.Contains( "fontSize" )
+				_fontSize=obj["fontSize"].ToNumber()
+			Endif
+			If obj.Contains( "monoFontSize" )
+				_monoFontSize=obj["monoFontSize"].ToNumber()
+			Endif
+		Endif
+
+		Select _name
+		Case "light"
+		
+			_textColor=New Color( 0,0,0 )
+			_disabledColor=New Color( .5,.5,.5 )
+			_clearColor=New Color( .8,.8,.8 )
+			_contentColor=New Color( .95,.95,.95 )
+			_panelColor=New Color( .9,.9,.9 )
+			_gutterColor=New Color( .8,.8,.8 )
+			_borderColor=New Color( .7,.7,.7 )
+			_lineColor=New Color( .95,.95,.95 )
+			_knobColor=New Color( .7,.7,.7 )
+			_hoverColor=New Color( .6,.6,.6 )
+			_activeColor=New Color( .5,.5,.5 )
+
+		Default
+				
+			_textColor=New Color( 1,1,1 )
+			_disabledColor=New Color( .5,.5,.5 )
+			_clearColor=New Color( .1,.1,.1 )
+			_contentColor=New Color( .2,.2,.2 )
+			_panelColor=New Color( .25,.25,.25 )
+			_gutterColor=New Color( .1,.1,.1 )
+			_borderColor=New Color( .1,.1,.1 )
+			_lineColor=New Color( .2,.2,.2 )
+			_knobColor=New Color( .4,.4,.4 )
+			_hoverColor=New Color( .6,.6,.6 )
+			_activeColor=New Color( .7,.7,.7 )'1,1,1 )
+			
+		End
+		
+		_defaultFont=Font.Open( App.DefaultFontName,_fontSize )
+		_defaultMonoFont=Font.Open( App.DefaultMonoFontName,_monoFontSize )
+
+		Local style:Style,state:Style
+		
+		style=Style.GetStyle( "" )
+		style.DefaultColor=_textColor
+		style.DefaultFont=_defaultFont
+
+		'Label		
+		style=New Style( "mojo.Label" )
+		style.DefaultColor=_textColor
+		style.Padding=New Recti( -8,-4,8,4 )
+		
+		'Button
+		style=New Style( "mojo.Button",Style.GetStyle( "mojo.Label" ) )
+		
+		Local icons:=LoadIcons( "asset::mojox/checkmark_icons.png",16 )
+		style.SetImage( "checkmark:unchecked",icons[0] )
+		style.SetImage( "checkmark:checked",icons[1] )
+		
+		state=style.AddState( "disabled" )
+		state.DefaultColor=_disabledColor
+		
+		state=style.AddState( "hover" )
+		state.BackgroundColor=_hoverColor
+		
+		state=style.AddState( "active" )
+		state.BackgroundColor=_activeColor
+		
+		'Menu
+		style=New Style( "mojo.Menu" )
+		style.Padding=New Recti( -2,-2,2,2 )
+		style.Border=New Recti( -1,-1,1,1 )
+		style.BackgroundColor=_panelColor
+		style.BorderColor=_borderColor
+		
+		'MenuButton
+		style=New Style( "mojo.MenuButton",Style.GetStyle( "mojo.Button" ) )
+		
+		'MenuBar
+		style=New Style( "mojo.MenuBar" )
+		style.Padding=New Recti( -2,-2,2,2 )
+		style.BackgroundColor=_panelColor
+		
+		'DockingView
+		style=New Style( "mojo.DockingView" )
+		
+		'DockView
+		style=New Style( "mojo.DockView" )
+		
+		'DragKnob
+		style=New Style( "mojo.DragKnob" )
+		style.Padding=New Recti( -3,-3,3,3 )
+		style.BackgroundColor=_knobColor
+		
+		state=style.AddState( "hover" )
+		state.BackgroundColor=_hoverColor
+		
+		state=style.AddState( "active" )
+		state.BackgroundColor=_activeColor
+		
+		'ScrollView
+		style=New Style( "mojo.ScrollView" )
+	
+		'ScrollBar
+		style=New Style( "mojo.ScrollBar" )
+		style.BackgroundColor=_gutterColor
+		
+		'ScrollKnob
+		style=New Style( "mojo.ScrollKnob" )
+		style.Padding=New Recti( -6,-6,6,6 )
+		style.Border=New Recti( -1,-1,1,1 )
+		style.BackgroundColor=_knobColor
+		
+		state=style.AddState( "hover" )
+		state.BackgroundColor=_hoverColor
+		
+		state=style.AddState( "active" )
+		state.BackgroundColor=_activeColor
+		
+		'TabView
+		style=New Style( "mojo.TabView" )
+		
+		'TabButton
+		style=New Style( "mojo.TabButton",Style.GetStyle( "mojo.Button" ) )
+		style.Border=New Recti( 0,-2,2,0 )
+		
+		state=style.AddState( "selected" )
+		state.BackgroundColor=_contentColor
+		
+		state=style.AddState( "hover" )
+		state.BackgroundColor=_hoverColor
+		
+		state=style.AddState( "active" )
+		state.BackgroundColor=_activeColor
+		
+		'HtmlView
+		style=New Style( "mojo.HtmlView" )
+
+		'TreeView
+		style=New Style( "mojo.TreeView" )
+		style.SetImage( "node:expanded",Image.Load( "asset::mojox/treenode_expanded.png" ) )
+		style.SetImage( "node:collapsed",Image.Load( "asset::mojox/treenode_collapsed.png" ) )
+		style.BackgroundColor=_contentColor
+		style.DefaultColor=_textColor
+		
+		'FileBrowser
+		style=New Style( "mojo.FileBrowser",Style.GetStyle( "mojo.TreeView" ) )
+		
+		'TextView
+		style=New Style( "mojo.TextView" )
+		style.DefaultFont=_defaultMonoFont
+		style.Padding=New Recti( -4,-4,4,4 )
+		style.BackgroundColor=_contentColor
+		style.DefaultColor=_textColor
+		
+		'Dialog
+		style=New Style( "mojo.Dialog" )
+		style.Border=New Recti( -1,-1,1,1 )
+		style.BackgroundColor=_panelColor
+		style.BorderColor=_borderColor
+		
+		'DialogTitle
+		style=New Style( "mojo.DialogTitle",Style.GetStyle( "mojo.Label" ) )
+		style.BackgroundColor=_knobColor
+		
+		style=New Style( "mojo.DialogContent" )
+		style.Padding=New Recti( -8,-8,8,4 )
+		
+		style=New Style( "mojo.DialogActions" )
+		style.Padding=New Recti( -8,-4,8,4 )
+		
+		'ToolBar
+		style=New Style( "mojo.ToolBar",Style.GetStyle( "mojo.MenuBar" ) )
+		
+		style=New Style( "mojo.ToolButton",Style.GetStyle( "mojo.Button" ) )
+		
+		'Separator
+		style=New Style( "mojo.Separator" )
+		style.Padding=New Recti( 0,0,1,1 )
+		style.Border=New Recti( -8,-8,7,7 )
+		style.BackgroundColor=_lineColor
+		
+		'TextField
+		style=New Style( "mojo.TextField",Style.GetStyle( "mojo.TextView" ) )
+		style.Padding=New Recti( -2,-2,2,2 )
+		style.Margin=New Recti( -2,-2,2,2 )
+		
+	End
+	
+	Method LoadIcons:Image[]( path:String,size:Int )
+	
+		Local pixmap:=Pixmap.Load( path )
+		If Not pixmap Return Null
+		
+		Local n:=pixmap.Width/size
+		
+		Local icons:=New Image[n]
+		
+		For Local i:=0 Until n
+			icons[i]=New Image( pixmap.Window( i*size,0,size,pixmap.Height ) )
+		Next
+		
+		Return icons
+	End
+
+	Private
+	
+	Field _name:String
+	Field _fontSize:Int
+	Field _monoFontSize:Int
+	
+	Field _textColor:Color
+	Field _defaultFont:Font
+	Field _defaultMonoFont:Font
+	
+	Field _disabledColor:Color
+	Field _clearColor:Color
+	Field _contentColor:Color
+	Field _panelColor:Color
+	Field _gutterColor:Color
+	Field _borderColor:Color
+	Field _lineColor:Color
+	Field _knobColor:Color
+	Field _hoverColor:Color
+	Field _activeColor:Color
+	
+End

+ 32 - 0
src/ted2/mojox/toolbar.monkey2

@@ -0,0 +1,32 @@
+
+Namespace mojox
+
+Class ToolButton Extends Button
+
+	Method New( action:Action )
+		Super.New( action )
+		Style=Style.GetStyle( "mojo.ToolButton" )
+	End
+	
+End
+
+Class ToolBar Extends DockingView
+
+	Method New()
+		Layout="fill"
+		Style=Style.GetStyle( "mojo.ToolBar" )
+	End
+
+	Method AddAction( action:Action )
+		Local button:=New ToolButton( action )
+		AddView( button,"left",0 )
+	End
+	
+	
+	Method AddAction:Action( label:String="",icon:Image=Null )
+		Local action:=New Action( label,icon )
+		AddAction( action )
+		Return action
+	End
+
+End

+ 354 - 0
src/ted2/mojox/treeview.monkey2

@@ -0,0 +1,354 @@
+
+Namespace mojox
+
+Class TreeView Extends View
+
+	Field NodeClicked:Void( node:Node,event:MouseEvent )
+
+	Field NodeToggled:Void( node:Node,event:MouseEvent )
+
+	Class Node
+	
+		Method New( label:String,parent:Node=Null,index:Int=-1 )
+		
+			If parent parent.AddChild( Self,index )
+			
+			Label=label
+		End
+		
+		Property Label:String()
+		
+			Return _label
+			
+		Setter( label:String )
+		
+			_label=label
+			
+			Dirty()
+		End
+
+		Property Parent:Node()
+		
+			Return _parent
+		End
+		
+		Property NumChildren:Int()
+		
+			Return _children.Length
+		End
+		
+		Property Children:Node[]()
+		
+			Return _children.ToArray()
+		End
+	
+		Property Expanded:Bool()
+		
+			Return _expanded
+			
+		Setter( expanded:Bool )
+		
+			_expanded=expanded
+			
+			Dirty()
+		End
+		
+		Property Rect:Recti()
+		
+			Return _rect
+		End
+		
+		Property Bounds:Recti()
+		
+			Return _bounds
+		End
+		
+		Method AddChild( node:Node,index:Int=-1 )
+		
+			If node._parent Return
+			
+			If index=-1
+				index=_children.Length
+			Else
+				Assert( index>=0 And index<=_children.Length )
+			Endif
+			
+			node._parent=Self
+			
+			_children.Insert( index,node )
+			
+			node.Dirty()
+		End
+		
+		Method RemoveChildren( index1:Int,index2:Int )
+		
+			Assert( index1>=0 And index2>=index1 And index1<=_children.Length And index2<=_children.Length )
+		
+			For Local i:=index1 Until index2
+				_children[i]._parent=Null
+			Next
+			
+			_children.Erase( index1,index2 )
+			
+			Dirty()
+		End
+		
+		Method RemoveChild( node:Node )
+		
+			If node._parent<>Self Return
+			
+			_children.Remove( node )
+			
+			node._parent=Null
+			
+			Dirty()
+		End
+		
+		Method RemoveChild( index:Int )
+		
+			RemoveChild( GetChild( index ) )
+		End
+		
+		Method RemoveChildren( first:Int )
+		
+			RemoveChildren( first,_children.Length )
+		End
+
+		Method RemoveAllChildren()
+		
+			RemoveChildren( 0,_children.Length )
+		End
+
+		Method Remove()
+		
+			If _parent _parent.RemoveChild( Self )
+		End
+		
+		Method GetChild:Node( index:Int )
+		
+			If index>=0 And index<_children.Length Return _children[index]
+			
+			Return Null
+		End
+		
+		Private
+		
+		Field _parent:Node
+		Field _children:=New Stack<Node>
+		Field _label:String
+		Field _expanded:Bool
+		Field _bounds:Recti
+		Field _rect:Recti
+		Field _dirty:Bool
+		
+		Method Dirty()
+			_dirty=True
+			Local node:=_parent
+			While node
+				node._dirty=True
+				node=node._parent
+			Wend
+		End
+		
+	End
+	
+	Method New()
+		Layout="fill"
+		Style=Style.GetStyle( "mojo.TreeView" )
+		_rootNode=New Node( Null )
+	End
+	
+	Property RootNode:Node()
+	
+		Return _rootNode
+	
+	Setter( node:Node)
+	
+		_rootNode=node
+	End
+	
+	Property RootNodeVisible:Bool()
+	
+		Return _rootNodeVisible
+	
+	Setter( rootNodeVisible:Bool )
+		
+		_rootNodeVisible=rootNodeVisible
+	End
+	
+	Method FindNodeAtPoint:Node( point:Vec2i )
+	
+		Return FindNodeAtPoint( _rootNode,point )
+	End
+	
+	Property Container:View() Override
+	
+		If Not _scroller
+			_scroller=New ScrollView( Self )
+		Endif
+		Return _scroller
+	End
+	
+	Private
+	
+	Field _rootNode:Node
+	Field _rootNodeVisible:=True
+	Field _scroller:ScrollView
+	
+	Field _expandedIcon:Image
+	Field _collapsedIcon:Image
+	Field _nodeSize:Int
+		
+	Method FindNodeAtPoint:Node( node:Node,point:Vec2i )
+	
+		If node._rect.Contains( point ) Return node
+	
+		If node._expanded And node._bounds.Contains( point )
+		
+			For Local child:=Eachin node._children
+			
+				Local cnode:=FindNodeAtPoint( child,point )
+				If cnode Return cnode
+
+			Next
+
+		Endif
+		
+		Return Null
+	End
+	
+	Method MeasureNode( node:Node,origin:Vec2i,dirty:Bool )
+	
+		If Not node._dirty And Not dirty Return
+
+		node._dirty=False
+	
+		Local size:Vec2i,nodeSize:=0
+		
+		If _rootNodeVisible Or node<>_rootNode
+		
+			size=New Vec2i( Style.DefaultFont.TextWidth( node.Label )+_nodeSize,_nodeSize )
+			nodeSize=_nodeSize
+			
+		Endif
+		
+		Local rect:=New Recti( origin,origin+size )
+		
+		node._rect=rect
+		
+		If node._expanded
+		
+			origin.x+=nodeSize
+		
+			For Local child:=Eachin node._children
+			
+				origin.y=rect.Bottom
+			
+				MeasureNode( child,origin,True )
+				
+				rect|=child._bounds
+			Next
+		
+		Endif
+		
+		node._bounds=rect
+	End
+	
+	Method RenderNode( canvas:Canvas,node:Node )
+	
+		If Not node._bounds.Intersects( ClipRect ) return
+	
+		If _rootNodeVisible Or node<>_rootNode
+		
+			If node._children.Length
+			
+				Local icon:=_collapsedIcon
+				If node._expanded icon=_expandedIcon
+				
+				Local x:=(_nodeSize-icon.Width)/2
+				Local y:=(_nodeSize-icon.Height)/2
+				
+				canvas.Color=Color.White
+				canvas.DrawImage( icon,node._rect.X+x,node._rect.Y+y )
+				
+			Endif
+			
+			canvas.Color=Style.DefaultColor
+			canvas.DrawText( node._label,node._rect.X+_nodeSize,node._rect.Y )
+		
+		Endif
+			
+		If node._expanded
+
+			For Local child:=Eachin node._children
+			
+				RenderNode( canvas,child )
+				
+			Next
+		Endif
+		
+	End
+	
+	Method OnValidateStyle() Override
+	
+		_collapsedIcon=Style.GetImage( "node:collapsed" )
+		_expandedIcon=Style.GetImage( "node:expanded" )
+		
+		_nodeSize=Style.DefaultFont.Height
+		_nodeSize=Max( _nodeSize,Int( _expandedIcon.Height ) )
+		_nodeSize=Max( _nodeSize,Int( _collapsedIcon.Height ) )
+	End
+	
+	Method OnMeasure:Vec2i() Override
+	
+		If Not _rootNode Return New Vec2i( 0,0 )
+		
+		Local origin:Vec2i
+		
+		'If Not _rootNodeVisible origin=New Vec2i( -_nodeSize,-_nodeSize )
+	
+		MeasureNode( _rootNode,origin,false )
+		
+		Return _rootNode._bounds.Size
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		If Not _rootNode Return
+	
+		RenderNode( canvas,_rootNode )
+
+'		Print "TreeView ClipRect="+ClipRect.ToString()
+
+	End
+	
+	Method OnMouseEvent( event:MouseEvent) Override
+	
+		Select event.Type
+		Case EventType.MouseDown
+		
+			Local node:=FindNodeAtPoint( event.Location )
+
+			If node
+			
+				Local p:=event.Location-node._rect.Origin
+				
+				If p.x<_nodeSize And p.y<_nodeSize
+					node.Expanded=Not node._expanded
+					NodeToggled( node,event )
+				Else
+					NodeClicked( node,event )
+				Endif
+				
+			Endif
+			
+		Case EventType.MouseWheel
+		
+			Super.OnMouseEvent( event )
+			Return
+		End
+	
+	End
+	
+End
+

+ 310 - 0
src/ted2/mx2document.monkey2

@@ -0,0 +1,310 @@
+
+Namespace ted2
+
+Global Mx2Keywords:=New StringMap<String>
+
+Private
+
+Function InitKeywords()
+	Local kws:=""
+
+	kws+="Namespace;Using;Import;Extern;"
+	kws+="Public;Private;Protected;Friend;"
+	kws+="Void;Bool;Byte;UByte;Short;UShort;Int;UInt;Long;ULong;Float;Double;String;Object;Continue;Exit;"
+	kws+="New;Self;Super;Eachin;True;False;Null;Where;"
+	kws+="Alias;Const;Local;Global;Field;Method;Function;Property;Getter;Setter;Operator;Lambda;"
+	kws+="Enum;Class;Interface;Struct;Extends;Implements;Virtual;Override;Abstract;Final;Inline;"
+	kws+="Var;Varptr;Ptr;"
+	kws+="Not;Mod;And;Or;Shl;Shr;End;"
+	kws+="If;Then;Else;Elseif;Endif;"
+	kws+="While;Wend;"
+	kws+="Repeat;Until;Forever;"
+	kws+="For;To;Step;Next;"
+	kws+="Select;Case;Default;"
+	kws+="Try;Catch;Throw;Throwable;"
+	kws+="Return;Print;Static;Cast"
+	
+	For Local kw:=Eachin kws.Split( ";" )
+		Mx2Keywords[kw.ToLower()]=kw
+	Next
+End
+
+Public
+
+Class Mx2Error
+
+	Field path:String
+	Field line:Int
+	Field msg:String
+	Field removed:Bool
+	
+	Method New( path:String,line:Int,msg:String )
+		Self.path=path
+		Self.line=line
+		Self.msg=msg
+	End
+
+	Operator<=>:Int( err:Mx2Error )
+		If line<err.line Return -1
+		If line>err.line Return 1
+		Return 0
+	End
+	
+End
+
+Class Mx2TextView Extends TextView
+
+	Method New( mx2Doc:Mx2Document )
+		
+		_mx2Doc=mx2Doc
+		
+		Document=_mx2Doc.TextDocument
+		
+		GutterWidth=64
+		
+		Local _editorColors:=New Color[8]
+		
+		Select Theme.Name
+		Case "light"
+			_editorColors[COLOR_IDENT]=New Color( .1,.1,.1 )
+			_editorColors[COLOR_KEYWORD]=New Color( 0,0,1 )
+			_editorColors[COLOR_STRING]=New Color( 0,.5,0 )
+			_editorColors[COLOR_NUMBER]=New Color( 0,0,.5 )
+			_editorColors[COLOR_COMMENT]=New Color( 0,.5,.5 )
+			_editorColors[COLOR_PREPROC]=New Color( .8,.65,0 )
+			_editorColors[COLOR_OTHER]=New Color( .1,.1,.1 )
+		Default
+			_editorColors[COLOR_IDENT]=New Color( 1,1,1 )
+			_editorColors[COLOR_KEYWORD]=New Color( 1,1,0 )
+			_editorColors[COLOR_STRING]=New Color( 0,1,.5 )
+			_editorColors[COLOR_NUMBER]=New Color( 0,1,.5 )
+			_editorColors[COLOR_COMMENT]=New Color( 0,1,1 )
+			_editorColors[COLOR_PREPROC]=New Color( 1,.75,0 )
+			_editorColors[COLOR_OTHER]=New Color( 1,1,1 )
+		End
+		
+		TextColors=_editorColors
+		CursorColor=New Color( 0,.5,1 )
+		SelectionColor=New Color( .4,.4,.4 )
+	End
+	
+	Protected
+	
+	Method OnValidateStyle() Override
+	
+		Super.OnValidateStyle()
+		
+'		GutterWidth=RenderStyle.DefaultFont.TextWidth( "999999 " )
+	End
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		Local color:=canvas.Color
+	
+		Local clip:Recti
+		clip.min.x=-Frame.min.x
+		clip.min.y=-Frame.min.y
+		clip.max.x=clip.min.x+GutterWidth
+		clip.max.y=clip.min.y+ClipRect.Height
+		
+		If _mx2Doc._errors.Length
+		
+			canvas.Color=New Color( .5,0,0 )
+			
+			For Local err:=Eachin _mx2Doc._errors
+				canvas.DrawRect( 0,err.line*LineHeight,Width,LineHeight )
+			Next
+			
+		Endif
+		
+		If _mx2Doc._debugLine<>-1
+
+			Local line:=_mx2Doc._debugLine
+			If line<0 Or line>=Document.LineCount Return
+			
+			canvas.Color=New Color( .5,.5,0 )
+			canvas.DrawRect( 0,line*LineHeight,Width,LineHeight )
+			
+		Endif
+		
+		canvas.Color=color
+		
+		Super.OnRender( canvas )
+		
+		'OK, VERY ugly! Draw gutter stuff...
+		
+		Local viewport:=clip
+		viewport.min+=RenderStyle.Bounds.min
+		canvas.Viewport=viewport
+		canvas.Color=RenderStyle.BackgroundColor
+		canvas.DrawRect( 0,0,viewport.Width,viewport.Height )
+		
+		canvas.Viewport=Rect
+		
+		Local line0:=clip.Top/LineHeight
+		Local line1:=(clip.Bottom-1)/LineHeight+1
+		
+		canvas.Color=Color.Grey
+
+		For Local i:=line0 Until line1
+			canvas.DrawText( String( i+1 ),clip.X+GutterWidth-8,i*LineHeight,1,0 )
+		Next
+		
+	End
+	
+	Private
+	
+	Field _mx2Doc:Mx2Document
+	
+	Method Capitalize( typing:Bool )
+	
+		Local cursor:=Cursor
+		
+		Local state:=Document.LineState( Document.FindLine( cursor ) )
+		If state<>-1 Return
+		
+		Local text:=Text
+		Local start:=cursor
+		While start And IsIdent( text[start-1] )
+			start-=1
+		Wend
+		While start<text.Length And IsDigit( text[start] )
+			start+=1
+		Wend
+		
+		If start<text.Length 
+			Local color:=Document.Colors[start]
+			If color<>COLOR_KEYWORD Return'color<>COLOR_IDENT Return
+		Endif
+		
+		Local ident:=text.Slice( start,cursor )
+		If Not ident Return
+		
+		Local kw:=Mx2Keywords[ident.ToLower()]
+		If kw And kw<>ident Document.ReplaceText( Cursor-ident.Length,Cursor,kw )
+		
+	End
+	
+	Method OnKeyEvent( event:KeyEvent ) Override
+	
+		Select event.Type
+		Case EventType.KeyDown
+		
+			Select event.Key
+			Case Key.Tab,Key.Enter
+				Capitalize( True )
+			Case Key.Up,Key.Down
+				Capitalize( False )
+			End
+		
+		Case EventType.KeyChar
+		
+			If Not IsIdent( event.Text[0] )
+				Capitalize( True )
+			Endif
+		End
+
+		Super.OnKeyEvent( event )
+
+	End
+
+End
+
+Class Mx2Document Extends Ted2Document
+
+	Method New( path:String )
+		Super.New( path )
+	
+		InitKeywords()
+		
+		_textDoc=New TextDocument
+		
+		_textDoc.TextChanged=Lambda()
+			Dirty=True
+		End
+		
+		_textDoc.TextHighlighter=Mx2TextHighlighter
+		
+		_textDoc.LinesModified=Lambda( first:Int,removed:Int,inserted:Int )
+			Local put:=0
+			For Local get:=0 Until _errors.Length
+				Local err:=_errors[get]
+				If err.line>=first
+					If err.line<first+removed 
+						err.removed=True
+						Continue
+					Endif
+					err.line+=(inserted-removed)
+				Endif
+				_errors[put]=err
+				put+=1
+			Next
+			_errors.Resize( put )
+		End
+
+	
+		_textView=New Mx2TextView( Self )
+		
+	End
+	
+	Property TextDocument:TextDocument()
+	
+		Return _textDoc
+	End
+	
+	Property DebugLine:Int()
+	
+		Return _debugLine
+	
+	Setter( debugLine:Int )
+		If debugLine=_debugLine Return
+		
+		_debugLine=debugLine
+		If _debugLine=-1 Return
+
+		Local scroller:=Cast<ScrollView>( _textView.Container )
+		If Not scroller Return
+		
+		Local h:=_textView.LineHeight
+		Local y:=_debugLine*h
+		
+		scroller.EnsureVisible( New Recti( 0,y,1,y+scroller.ContentClipRect.Height/3 ) )
+	End
+	
+	Property Errors:Stack<Mx2Error>()
+	
+		Return _errors
+	End
+	
+	Private
+
+	Field _textDoc:TextDocument
+	Field _errors:=New Stack<Mx2Error>
+	Field _debugLine:Int=-1
+
+	Field _textView:TextView
+	
+	Method OnLoad:Bool() Override
+	
+		Local text:=stringio.LoadString( Path )
+		
+		_textDoc.Text=text
+		
+		Return True
+	End
+	
+	Method OnSave:Bool() Override
+	
+		Local text:=_textDoc.Text
+		
+		Return stringio.SaveString( text,Path )
+	
+	End
+	
+	Method OnCreateView:View() Override
+	
+		Return _textView
+	End
+	
+End
+

+ 147 - 0
src/ted2/mx2highlighter.monkey2

@@ -0,0 +1,147 @@
+
+Namespace ted2
+
+Using std.chartype
+
+Const COLOR_NONE:=0
+Const COLOR_IDENT:=1
+Const COLOR_KEYWORD:=2
+Const COLOR_STRING:=3
+Const COLOR_NUMBER:=4
+Const COLOR_COMMENT:=5
+Const COLOR_PREPROC:=6
+Const COLOR_OTHER:=7
+
+Function Mx2TextHighlighter:Int( text:String,colors:Byte[],sol:Int,eol:Int,state:Int )
+
+	Local i0:=sol
+	
+	Local icolor:=0
+	Local istart:=sol
+	Local preproc:=False
+	
+	If state>-1 icolor=COLOR_COMMENT
+	
+	While i0<eol
+	
+		Local start:=i0
+		Local chr:=text[i0]
+		i0+=1
+		If IsSpace( chr ) Continue
+		
+		If chr=35 And istart=sol
+			preproc=True
+			If state=-1 icolor=COLOR_PREPROC
+			Continue
+		Endif
+		
+		If preproc And (IsAlpha( chr ) Or chr=95)
+
+			While i0<eol And (IsAlpha( text[i0] ) Or IsDigit( text[i0] )  Or text[i0]=95)
+				i0+=1
+			Wend
+			
+			Local id:=text.Slice( start,i0 )
+			
+			Select id.ToLower()
+			Case "rem"
+				state+=1
+				icolor=COLOR_COMMENT
+			Case "end"
+				If state>-1 
+					state-=1
+					icolor=COLOR_COMMENT
+				Endif
+			End
+			
+			Exit
+		
+		Endif
+		
+		If state>-1 Or preproc Exit
+		
+		Local color:=icolor
+		
+		If chr=39
+		
+			i0=eol
+			color=COLOR_COMMENT
+			
+		Else If chr=34
+		
+			While i0<eol And text[i0]<>34
+				i0+=1
+			Wend
+			If i0<eol i0+=1
+			
+			color=COLOR_STRING
+			
+		Else If IsAlpha( chr ) Or chr=95
+
+			While i0<eol And (IsAlpha( text[i0] ) Or IsDigit( text[i0] )  Or text[i0]=95)
+				i0+=1
+			Wend
+			
+			Local id:=text.Slice( start,i0 )
+			
+			If preproc And istart=sol
+			
+				Select id.ToLower()
+				Case "rem"				
+					state+=1
+				Case "end"
+					state=Max( state-1,-1 )
+				End
+				
+				icolor=COLOR_COMMENT
+				
+				Exit
+			Else
+			
+				color=COLOR_IDENT
+				
+				If Mx2Keywords.Contains( id.ToLower() ) color=COLOR_KEYWORD
+			
+			Endif
+			
+		Else If IsDigit( chr )
+		
+			While i0<eol And IsDigit( text[i0] )
+				i0+=1
+			Wend
+			
+			color=COLOR_NUMBER
+			
+		Else If chr=36 And i0<eol And IsHexDigit( text[i0] )
+		
+			i0+=1
+			While i0<eol And IsHexDigit( text[i0] )
+				i0+=1
+			Wend
+			
+			color=COLOR_NUMBER
+			
+		Else
+			
+			color=COLOR_NONE
+			
+		Endif
+		
+		If color=icolor Continue
+		
+		For Local i:=istart Until start
+			colors[i]=icolor
+		Next
+		
+		icolor=color
+		istart=start
+	
+	Wend
+	
+	For Local i:=istart Until eol
+		colors[i]=icolor
+	Next
+	
+	Return state
+
+End

+ 91 - 0
src/ted2/projectview.monkey2

@@ -0,0 +1,91 @@
+
+Namespace ted2
+
+Class ProjectView Extends ScrollView
+
+	Method New()
+	
+		_docker=New DockingView
+		
+		ContentView=_docker
+		
+		_docker.ContentView=New TreeView
+		
+	End
+
+	Method OpenProject:Bool( dir:String )
+	
+		dir=StripSlashes( dir )
+		
+		If _projects[dir] Return False
+		
+		If GetFileType( dir )<>FileType.Directory Return False
+	
+		Local browser:=New FileBrowser( dir )
+		
+		browser.FileClicked=Lambda( path:String,event:MouseEvent )
+		
+			Select event.Button
+			Case MouseButton.Left
+			
+				If GetFileType( path )=FileType.File
+				
+					New Fiber( Lambda()
+					
+						MainWindow.OpenDocument( path,True )
+						MainWindow.SaveState()
+						
+					End )
+					
+				Endif
+			
+			Case MouseButton.Right
+			
+				#rem Laters...!
+				Select GetFileType( path )
+				Case FileType.Directory
+				
+					Local menu:=New Menu( path )
+					menu.AddAction( "New file" ).Triggered=Lambda()
+						Local file:=MainWindow.RequestFile( "New file","",True,path )
+						Print "File="+file
+					End
+					
+					menu.Open( event.Location,browser,Null )
+				
+				End
+				#end
+				
+			End
+		End
+		
+		browser.RootNode.Label=StripDir( dir )+" ("+dir+")"
+		
+		_docker.AddView( browser,"top" )
+		
+		_projects[dir]=browser
+		
+		Return True
+		
+	End
+	
+	Method CloseProject( dir:String )
+
+		dir=StripSlashes( dir )
+		
+		Local view:=_projects[dir]
+		If Not view Return
+		
+		_docker.RemoveView( view )
+		
+		_projects.Remove( dir )
+	End
+	
+	Private
+	
+	Field _docker:=New DockingView
+	
+	Field _projects:=New StringMap<FileBrowser>
+End
+
+

+ 48 - 0
src/ted2/ted2.monkey2

@@ -0,0 +1,48 @@
+
+#Import "mojox/mojox"
+
+#Import "mainwindow"
+#Import "projectview"
+#Import "debugview"
+#Import "helpview"
+#Import "finddialog"
+
+#Import "ted2document"
+#Import "txtdocument"
+#Import "mx2document"
+#Import "mx2highlighter"
+#Import "imgdocument"
+
+Namespace ted2
+
+Using std..
+Using mojo..
+Using mojox..
+
+Function Main()
+
+'	Print "Hello World from Ted2"
+
+	ChangeDir( AppDir() )
+		
+	While GetFileType( "bin" )<>FileType.Directory Or GetFileType( "modules" )<>FileType.Directory
+
+'		Print "CurrentDir="+CurrentDir()
+			
+		If IsRootDir( CurrentDir() )
+			Print "Error initializing Ted2 - can't find working dir!"
+			libc.exit_( -1 )
+		Endif
+		
+		ChangeDir( ExtractDir( CurrentDir() ) )
+		
+	Wend
+	
+	New AppInstance
+	
+	Theme.Load()
+	
+	New MainWindowInstance( "Ted2",New Recti( 16,16,960,800 ),WindowFlags.Resizable|WindowFlags.Center )
+	
+	App.Run()
+End

+ 97 - 0
src/ted2/ted2document.monkey2

@@ -0,0 +1,97 @@
+
+Namespace ted2
+
+Class Ted2Document
+
+	Field DirtyChanged:Void()
+
+	Method New( path:String )
+	
+		_path=path
+	End
+	
+	Property Path:String()
+
+		Return _path
+	End
+	
+	Property View:View()
+	
+		If Not _view _view=OnCreateView()
+		
+		Return _view
+	End
+	
+	Property Dirty:Bool()
+	
+		Return _dirty
+	
+	Setter( dirty:Bool)
+
+		If dirty=_dirty Return
+		
+		_dirty=dirty
+		
+		DirtyChanged()
+	End
+	
+	Method Load:Bool()
+	
+		If Not OnLoad() Return False
+
+		Dirty=False
+		
+		Return True
+	End
+	
+	Method Save:Bool()
+	
+		If Not _dirty Return True
+		
+		If Not OnSave() Return False
+		
+		Dirty=False
+
+		Return True
+	End
+	
+	Method Rename( path:String )
+	
+		_path=path
+		
+		Dirty=True
+	End
+	
+	Method Close()
+	
+		OnClose()
+	
+	End
+	
+	Protected
+
+	Method OnLoad:Bool() Virtual
+	
+		Return False
+	End
+	
+	Method OnSave:Bool() Virtual
+	
+		Return False
+	End
+	
+	Method OnClose() Virtual
+	End
+	
+	Method OnCreateView:View() Virtual
+	
+		Return Null
+	End
+	
+	Private
+
+	Field _dirty:Bool
+	Field _path:String
+	Field _view:View
+	
+End

+ 45 - 0
src/ted2/test.monkey2

@@ -0,0 +1,45 @@
+
+Namespace test
+
+#Import "<std>"
+#Import "<mojo>"
+
+#Import "../mojox/mojox"
+
+Using std..
+Using mojo..
+Using mojox..
+
+Class MyWindow Extends Window
+
+	Method New()
+	
+		Local dialog:=New Dialog
+	
+		Local docker:=New DockingView
+		docker.AddView( New Label( "Find..." ),"top" )
+		docker.AddView( New Label( "Replace..." ),"top" )
+		docker.AddView( New Label( "Case sensitive" ),"top" )
+
+		dialog.Title="Find"
+		dialog.ContentView=docker
+		dialog.AddAction( "Find Next" )
+		dialog.AddAction( "Close" )
+		
+'		dialog.MinSize=New Vec2i( 320,240 )
+		
+		dialog.Open()
+	End
+
+End
+
+Function Main()
+
+	New AppInstance
+
+	New Theme
+		
+	New MyWindow
+	
+	App.Run()
+End

+ 104 - 0
src/ted2/txtdocument.monkey2

@@ -0,0 +1,104 @@
+
+Namespace ted2
+
+Class TxtTextView Extends TextView
+
+	Method New( doc:TxtDocument )
+
+		_doc=doc
+		
+		Document=_doc.TextDocument
+		
+		GutterWidth=64
+		CursorColor=New Color( 0,.5,1 )
+		SelectionColor=New Color( .4,.4,.4 )
+	End
+	
+	Protected
+	
+	Method OnRender( canvas:Canvas ) Override
+	
+		Super.OnRender( canvas )
+
+		'OK, VERY ugly! Draw gutter...
+		
+		Local clip:Recti
+		clip.min.x=-Frame.min.x
+		clip.min.y=-Frame.min.y
+		clip.max.x=clip.min.x+GutterWidth
+		clip.max.y=clip.min.y+ClipRect.Height
+		
+		Local viewport:=clip
+		viewport.min+=RenderStyle.Bounds.min
+		canvas.Viewport=viewport
+		canvas.Color=RenderStyle.BackgroundColor
+		canvas.DrawRect( 0,0,viewport.Width,viewport.Height )
+		
+		canvas.Viewport=Rect
+		
+		Local line0:=clip.Top/LineHeight
+		Local line1:=(clip.Bottom-1)/LineHeight+1
+		
+		canvas.Color=Color.Grey
+
+		For Local i:=line0 Until line1
+			canvas.DrawText( String( i+1 ),clip.X+GutterWidth-8,i*LineHeight,1,0 )
+		Next
+		
+	End
+	
+	Private
+	
+	Field _doc:TxtDocument
+	
+End
+
+Class TxtDocument Extends Ted2Document
+
+	Method New( path:String )
+		Super.New( path )
+
+		_textDoc=New TextDocument
+		
+		_textDoc.TextChanged=Lambda()
+			Dirty=True
+		End
+		
+		_textView=New TxtTextView( Self )
+	End
+	
+	Property TextDocument:TextDocument()
+	
+		Return _textDoc
+	End
+	
+	Protected
+	
+	Method OnLoad:Bool() Override
+	
+		Local text:=stringio.LoadString( Path )
+		
+		_textDoc.Text=text
+		
+		Return True
+	End
+	
+	Method OnSave:Bool() Override
+	
+		Local text:=_textDoc.Text
+		
+		Return stringio.SaveString( text,Path )
+	End
+	
+	Method OnCreateView:View() Override
+	
+		Return _textView
+	End
+	
+	Private
+	
+	Field _textDoc:TextDocument
+	Field _textView:TxtTextView
+	
+End
+