Browse Source

Asset Manager - Create New Assets

This code as the ability to create new assets to the asset manager. It adjusts the way cursors work a little since they were not appearing when system dialogs were opening.
Peter Robinson 4 years ago
parent
commit
ec7212195b
33 changed files with 1898 additions and 41 deletions
  1. 7 1
      editor/AssetAdmin/AssetAdmin.cs
  2. 54 15
      editor/AssetAdmin/AssetDictionary.cs
  3. 0 1
      editor/AssetAdmin/AssetDictionaryButton.cs
  4. 13 0
      editor/AssetAdmin/AssetInspector.cs
  5. 234 0
      editor/AssetAdmin/NewAnimationAssetDialog.cs
  6. 91 0
      editor/AssetAdmin/NewAssetButton.cs
  7. 180 0
      editor/AssetAdmin/NewAudioAssetDialog.cs
  8. 180 0
      editor/AssetAdmin/NewFontAssetDialog.cs
  9. 187 0
      editor/AssetAdmin/NewImageAssetDialog.cs
  10. 279 0
      editor/AssetAdmin/NewParticleAssetDialog.cs
  11. 51 0
      editor/EditorCore/EditorCore.cs
  12. 71 0
      editor/EditorCore/EditorDialog.cs
  13. 170 0
      editor/EditorCore/EditorForm.cs
  14. 252 3
      editor/EditorCore/Themes/BaseTheme/BaseTheme.cs
  15. 12 3
      editor/ProjectManager/ProjectManager.cs
  16. 2 4
      editor/main.cs
  17. 2 0
      engine/compilers/VisualStudio 2017/main.cs
  18. 2 0
      engine/compilers/VisualStudio 2019/main.cs
  19. 3 0
      engine/source/gui/containers/guiGridCtrl.cc
  20. 18 0
      engine/source/gui/editor/guiInspector.cc
  21. 3 3
      engine/source/gui/editor/guiInspectorTypes.cc
  22. 13 1
      engine/source/gui/guiCanvas.cc
  23. 1 1
      engine/source/gui/guiCanvas.h
  24. 9 2
      engine/source/io/fileSystem_ScriptBinding.cc
  25. 1 1
      engine/source/module/moduleDefinition.h
  26. 1 1
      engine/source/module/moduleManager.cc
  27. 37 0
      engine/source/platform/nativeDialogs/fileDialog.cc
  28. 3 0
      engine/source/platform/nativeDialogs/fileDialog.h
  29. 5 1
      engine/source/platform/nativeDialogs/fileDialog_ScriptBinding.h
  30. 4 1
      engine/source/platform/platformFileIO.cc
  31. 1 1
      engine/source/platform/platformInput.h
  32. 8 1
      engine/source/platformWin32/winInput.cc
  33. 4 1
      engine/source/platformWin32/winWindow.cc

+ 7 - 1
editor/AssetAdmin/AssetAdmin.cs

@@ -27,6 +27,12 @@ function AssetAdmin::create(%this)
 	exec("./AssetDictionaryButton.cs");
 	exec("./AssetInspector.cs");
 	exec("./AssetAudioPlayButton.cs");
+	exec("./NewAssetButton.cs");
+	exec("./NewImageAssetDialog.cs");
+	exec("./NewAnimationAssetDialog.cs");
+	exec("./NewParticleAssetDialog.cs");
+	exec("./NewFontAssetDialog.cs");
+	exec("./NewAudioAssetDialog.cs");
 
 	%this.guiPage = EditorCore.RegisterEditor("Asset Manager", %this);
 	%this.guiPage.add(%this.buildAssetWindow());
@@ -158,6 +164,7 @@ function AssetAdmin::buildAudioPlayButton(%this)
 		extent = "700 444";
 		HorizSizing="width";
 		VertSizing="height";
+		Visible="0";
 	};
 	ThemeManager.setProfile(%this.audioPlayButtonContainer, "emptyProfile");
 
@@ -167,7 +174,6 @@ function AssetAdmin::buildAudioPlayButton(%this)
 		HorizSizing="center";
 		VertSizing="center";
 		Extent="100 48";
-		Visible="0";
 		Text = "Play";
 	};
 	ThemeManager.setProfile(%this.audioPlayButton, "buttonProfile");

+ 54 - 15
editor/AssetAdmin/AssetDictionary.cs

@@ -22,9 +22,19 @@
 
 function AssetDictionary::onAdd(%this)
 {
+	%this.newButton = new GuiButtonCtrl()
+	{
+		class = "NewAssetButton";
+		Position = "5 27";
+		Extent = "300 30";
+		Text = "New";
+	};
+	ThemeManager.setProfile(%this.newButton, "buttonProfile");
+	%this.add(%this.newButton);
+
 	%this.grid = new GuiGridCtrl()
 	{
-		Position="0 22";
+		Position="0 62";
 		Extent = "310 50";
 		CellSizeX = 60;
 		CellSizeY = 60;
@@ -49,23 +59,52 @@ function AssetDictionary::load(%this)
 
 		if(!AssetDatabase.isAssetInternal(%assetID))
 		{
-			%button = new GuiButtonCtrl()
-			{
-				Class = AssetDictionaryButton;
-				HorizSizing="center";
-				VertSizing="center";
-				Extent = "100 100";
-				Tooltip = AssetDatabase.getAssetName(%assetID);
-				Text = "";
-				AssetID = %assetID;
-				Type = %this.Type;
-			};
-			ThemeManager.setProfile(%button, "itemSelectProfile");
-			ThemeManager.setProfile(%button, "tipProfile", "TooltipProfile");
-			%this.grid.add(%button);
+			%this.addButton(%assetID);
 		}
 	}
 	%query.delete();
+
+	%this.newButton.text = "New" SPC %this.type;
+	%this.newButton.type = %this.type;
+}
+
+function AssetDictionary::addButton(%this, %assetID)
+{
+	%button = new GuiButtonCtrl()
+	{
+		Class = AssetDictionaryButton;
+		HorizSizing="center";
+		VertSizing="center";
+		Extent = "100 100";
+		Tooltip = AssetDatabase.getAssetName(%assetID);
+		Text = "";
+		AssetID = %assetID;
+		Type = %this.Type;
+	};
+	ThemeManager.setProfile(%button, "itemSelectProfile");
+	ThemeManager.setProfile(%button, "tipProfile", "TooltipProfile");
+	%this.grid.add(%button);
+
+	if(%this.getExpanded())
+	{
+		%this.setExpanded(false);
+		%this.setExpanded(true);
+	}
+
+	return %button;
+}
+
+function AssetDictionary::getButton(%this, %assetID)
+{
+	for(%i = 0; %i < %this.grid.getCount(); %i++)
+	{
+		%button = %this.grid.getObject(%i);
+		if(%button.AssetID $= %assetID)
+		{
+			return %button;
+		}
+	}
+	return 0;
 }
 
 function AssetDictionary::unload(%this)

+ 0 - 1
editor/AssetAdmin/AssetDictionaryButton.cs

@@ -23,7 +23,6 @@
 function AssetDictionaryButton::onAdd(%this)
 {
 	%this.call("load" @ %this.type, %this.assetID);
-	%this.assetID = "";
 }
 
 function AssetDictionaryButton::loadImageAsset(%this, %assetID)

+ 13 - 0
editor/AssetAdmin/AssetInspector.cs

@@ -92,7 +92,10 @@ function AssetInspector::loadImageAsset(%this, %imageAsset, %assetID)
 	%this.inspector.addHiddenField("hidden");
 	%this.inspector.addHiddenField("locked");
 	%this.inspector.addHiddenField("AssetInternal");
+	%this.inspector.addHiddenField("AssetPrivate");
+	%this.inspector.addHiddenField("ExplicitMode");
 	%this.inspector.inspect(%imageAsset);
+	%this.inspector.openGroupByIndex(0);
 }
 
 function AssetInspector::loadAnimationAsset(%this, %animationAsset, %assetID)
@@ -102,7 +105,9 @@ function AssetInspector::loadAnimationAsset(%this, %animationAsset, %assetID)
 	%this.inspector.addHiddenField("hidden");
 	%this.inspector.addHiddenField("locked");
 	%this.inspector.addHiddenField("AssetInternal");
+	%this.inspector.addHiddenField("AssetPrivate");
 	%this.inspector.inspect(%animationAsset);
+	%this.inspector.openGroupByIndex(0);
 }
 
 function AssetInspector::loadParticleAsset(%this, %particleAsset, %assetID)
@@ -112,7 +117,9 @@ function AssetInspector::loadParticleAsset(%this, %particleAsset, %assetID)
 	%this.inspector.addHiddenField("hidden");
 	%this.inspector.addHiddenField("locked");
 	%this.inspector.addHiddenField("AssetInternal");
+	%this.inspector.addHiddenField("AssetPrivate");
 	%this.inspector.inspect(%particleAsset);
+	%this.inspector.openGroupByIndex(0);
 }
 
 function AssetInspector::loadFontAsset(%this, %fontAsset, %assetID)
@@ -122,7 +129,9 @@ function AssetInspector::loadFontAsset(%this, %fontAsset, %assetID)
 	%this.inspector.addHiddenField("hidden");
 	%this.inspector.addHiddenField("locked");
 	%this.inspector.addHiddenField("AssetInternal");
+	%this.inspector.addHiddenField("AssetPrivate");
 	%this.inspector.inspect(%fontAsset);
+	%this.inspector.openGroupByIndex(0);
 }
 
 function AssetInspector::loadAudioAsset(%this, %audioAsset, %assetID)
@@ -132,7 +141,9 @@ function AssetInspector::loadAudioAsset(%this, %audioAsset, %assetID)
 	%this.inspector.addHiddenField("hidden");
 	%this.inspector.addHiddenField("locked");
 	%this.inspector.addHiddenField("AssetInternal");
+	%this.inspector.addHiddenField("AssetPrivate");
 	%this.inspector.inspect(%audioAsset);
+	%this.inspector.openGroupByIndex(0);
 }
 
 function AssetInspector::loadSpineAsset(%this, %spineAsset, %assetID)
@@ -142,5 +153,7 @@ function AssetInspector::loadSpineAsset(%this, %spineAsset, %assetID)
 	%this.inspector.addHiddenField("hidden");
 	%this.inspector.addHiddenField("locked");
 	%this.inspector.addHiddenField("AssetInternal");
+	%this.inspector.addHiddenField("AssetPrivate");
 	%this.inspector.inspect(%spineAsset);
+	%this.inspector.openGroupByIndex(0);
 }

+ 234 - 0
editor/AssetAdmin/NewAnimationAssetDialog.cs

@@ -0,0 +1,234 @@
+
+function NewAnimationAssetDialog::init(%this, %width, %height)
+{
+	//Get the dialog contents
+	%window = %this.getObject(0);
+	%content = %window.getObject(0);
+
+	//Create the file text box
+	%form = new GuiGridCtrl()
+	{
+		class = "EditorForm";
+		extent = %width SPC %height;
+		cellSizeX = %width;
+		cellSizeY = 50;
+	};
+	%form.addListener(%this);
+
+	%item = %form.addFormItem("Image Asset", %width SPC 30);
+	%this.imageDropDown = %form.createDropDownItem(%item);
+	%this.populateImageDropDown();
+
+	%item = %form.addFormItem("Target Folder", %width SPC 30);
+	%this.folderBox = %form.createFolderOpenItem(%item, "Select Target Folder");
+	%this.folderBox.Command = %this.getId() @ ".Validate();";
+
+	%item = %form.addFormItem("Asset Name", %width SPC 30);
+	%this.assetNameBox = %form.createTextEditItem(%item);
+	%this.assetNameBox.Command = %this.getId() @ ".Validate();";
+
+	%item = %form.addFormItem("Target Module", %width SPC 30);
+	%this.moduleNameBox = %form.createTextEditItem(%item);
+	%this.moduleNameBox.setActive(false);
+
+	%content.add(%form);
+
+	%this.feedback = new GuiControl()
+	{
+		HorizSizing = "right";
+		VertSizing = "bottom";
+		Position = "12 220";
+		Extent = (%width - 24) SPC 80;
+		text = "Select an Image Asset to get started!";
+	};
+	ThemeManager.setProfile(%this.feedback, "infoProfile");
+
+	%this.cancelButton = new GuiButtonCtrl()
+	{
+		HorizSizing = "right";
+		VertSizing = "bottom";
+		Position = "478 320";
+		Extent = "100 30";
+		Text = "Cancel";
+		Command = %this.getID() @ ".onClose();";
+	};
+	ThemeManager.setProfile(%this.cancelButton, "buttonProfile");
+
+	%this.createButton = new GuiButtonCtrl()
+	{
+		HorizSizing = "right";
+		VertSizing = "bottom";
+		Position = "588 318";
+		Extent = "100 34";
+		Text = "Create";
+		Command = %this.getID() @ ".onCreate();";
+	};
+	ThemeManager.setProfile(%this.createButton, "primaryButtonProfile");
+
+	%content.add(%this.feedback);
+	%content.add(%this.cancelButton);
+	%content.add(%this.createButton);
+
+	%this.prevFolder = "";
+}
+
+function NewAnimationAssetDialog::Validate(%this)
+{
+	%this.createButton.active = false;
+
+	%assetID = %this.imageDropDown.getText();
+	if(%assetID $= "")
+	{
+		%this.feedback.setText("Select an Image Asset to get started!");
+		return false;
+	}
+	%imageAssetFilePath = AssetDatabase.getAssetFilePath(%assetID);
+
+	if(%this.folderBox.getText() $= "")
+	{
+		%this.folderBox.setText(makeRelativePath(filePath(%imageAssetFilePath), getMainDotCsDir()));
+	}
+
+	if(%this.assetNameBox.getText() $= "")
+	{
+		%this.assetNameBox.setText(getUnit(%assetID, 1, ":") @ "_AN");
+	}
+
+	%folderPath = %this.getFolderPath();
+	%assetName = %this.assetNameBox.getText();
+
+	if(%folderPath !$= %this.prevFolder)
+	{
+		%modSig = EditorCore.findModuleOfPath(%folderPath @ "a.txt");
+		%this.moduleNameBox.setText(%modSig);
+		%this.prevFolder = %folderPath;
+	}
+	%assetPath = %folderPath @ %assetName @ ".asset.taml";
+	%moduleName = getUnit(%this.moduleNameBox.getText(), 0, "_");
+	%moduleVersion = getUnit(%this.moduleNameBox.getText(), 1, "_");
+	%assetID = %moduleName @ ":" @ %assetName;
+
+	if(%folderPath $= "")
+	{
+		%this.feedback.setText("Please select a target folder.");
+		return false;
+	}
+
+	if(%assetName $= "")
+	{
+		%this.feedback.setText("An animation asset must have an Asset Name.");
+		return false;
+	}
+
+	if(%moduleName $= "")
+	{
+		%this.feedback.setText("You can only create an animation asset inside of a module.");
+		return false;
+	}
+
+	%button = AssetAdmin.Dictionary["AnimationAsset"].getButton(%assetID);
+	if(isObject(%button))
+	{
+		%this.feedback.setText("An asset by this name already exists in this module. Try choosing a different name.");
+		return false;
+	}
+
+	%this.createButton.active = true;
+	%this.feedback.setText("Press the Create button to open the new asset for editing.");
+	return true;
+}
+
+function NewAnimationAssetDialog::onClose(%this)
+{
+	Canvas.popDialog(%this);
+}
+
+function NewAnimationAssetDialog::onCreate(%this)
+{
+	if(%this.validate())
+	{
+		%folderPath = %this.getFolderPath();
+		%assetName = %this.assetNameBox.getText();
+		%assetPath = %folderPath @ %assetName @ ".asset.taml";
+		%moduleName = getUnit(%this.moduleNameBox.getText(), 0, "_");
+		%moduleVersion = getUnit(%this.moduleNameBox.getText(), 1, "_");
+		%assetID = %moduleName @ ":" @ %assetName;
+
+		//Time to create a new file
+		%newAsset = new AnimationAsset()
+		{
+			assetName = %assetName;
+			Image = %this.imageDropDown.getText();
+			AnimationFrames = "0 1";
+			AnimationTime = "1";
+			AnimationCycle = "1";
+		};
+
+		%assetImportSuccessful = TAMLWrite(%newAsset, %assetPath);
+
+		%moduleDef = ModuleDatabase.findModule(%moduleName, %moduleVersion);
+		AssetDatabase.addDeclaredAsset(%moduleDef, %assetPath);
+
+		//Do we already have this button?
+		%button = AssetAdmin.Dictionary["AnimationAsset"].getButton(%assetID);
+
+		if(!isObject(%button))
+		{
+			//Load it into the image dictionary
+			%button = AssetAdmin.Dictionary["AnimationAsset"].addButton(%assetID);
+		}
+		%button.onClick();
+		%this.onClose();
+	}
+}
+
+function NewAnimationAssetDialog::onFolderOpened(%this, %textBox)
+{
+	%this.Validate();
+}
+
+function NewAnimationAssetDialog::getFolderPath(%this)
+{
+	%folderPath = stripTrailingSpaces(makeFullPath(%this.folderBox.getText()));
+	%length = strlen(%folderPath);
+	%lastChar = getSubStr(%folderPath, %length - 1, 1);
+	if(%lastChar $= "/")
+	{
+		return %folderPath;
+	}
+	return %folderPath @ "/";
+}
+
+function NewAnimationAssetDialog::onDropDownClosed(%this, %dropDown)
+{
+	%this.validate();
+}
+
+function NewAnimationAssetDialog::populateImageDropDown(%this)
+{
+	%this.imageDropDown.clearItems();
+
+	%query = new AssetQuery();
+	AssetDatabase.findAllAssets(%query);
+	AssetDatabase.findAssetType(%query, "ImageAsset", true);
+
+	for(%i = 0; %i < %query.getCount(); %i++)
+	{
+		%assetID = %query.getAsset(%i);
+
+		if(!AssetDatabase.isAssetInternal(%assetID))
+		{
+			%imageAsset = AssetDatabase.acquireAsset(%assetID);
+
+			if(%imageAsset.getFrameCount() > 1)
+			{
+				%this.imageDropDown.addItem(%assetID);
+			}
+		}
+	}
+	%query.delete();
+
+	%this.imageDropDown.sortByText();
+	%this.imageDropDown.insertItem(0, "");
+	%this.imageDropDown.setSelected(0);
+}

+ 91 - 0
editor/AssetAdmin/NewAssetButton.cs

@@ -0,0 +1,91 @@
+//NewAssetButton.cs
+
+function NewAssetButton::onClick(%this)
+{
+	%this.call("onNew" @ %this.type);
+}
+
+function NewAssetButton::onNewImageAsset(%this)
+{
+	%width = 700;
+	%height = 340;
+	%dialog = new GuiControl()
+	{
+		class = "NewImageAssetDialog";
+		superclass = "EditorDialog";
+		dialogSize = (%width + 8) SPC (%height + 8);
+		dialogCanClose = true;
+		dialogText = "New Image Asset";
+	};
+	%dialog.init(%width, %height);
+
+	Canvas.pushDialog(%dialog);
+}
+
+function NewAssetButton::onNewAnimationAsset(%this)
+{
+	%width = 700;
+	%height = 390;
+	%dialog = new GuiControl()
+	{
+		class = "NewAnimationAssetDialog";
+		superclass = "EditorDialog";
+		dialogSize = (%width + 8) SPC (%height + 8);
+		dialogCanClose = true;
+		dialogText = "New Animation Asset";
+	};
+	%dialog.init(%width, %height);
+
+	Canvas.pushDialog(%dialog);
+}
+
+function NewAssetButton::onNewParticleAsset(%this)
+{
+	%width = 700;
+	%height = 440;
+	%dialog = new GuiControl()
+	{
+		class = "NewParticleAssetDialog";
+		superclass = "EditorDialog";
+		dialogSize = (%width + 8) SPC (%height + 8);
+		dialogCanClose = true;
+		dialogText = "New Particle Asset";
+	};
+	%dialog.init(%width, %height);
+
+	Canvas.pushDialog(%dialog);
+}
+
+function NewAssetButton::onNewFontAsset(%this)
+{
+	%width = 700;
+	%height = 340;
+	%dialog = new GuiControl()
+	{
+		class = "NewFontAssetDialog";
+		superclass = "EditorDialog";
+		dialogSize = (%width + 8) SPC (%height + 8);
+		dialogCanClose = true;
+		dialogText = "New Bitmap Font Asset";
+	};
+	%dialog.init(%width, %height);
+
+	Canvas.pushDialog(%dialog);
+}
+
+function NewAssetButton::onNewAudioAsset(%this)
+{
+	%width = 700;
+	%height = 340;
+	%dialog = new GuiControl()
+	{
+		class = "NewAudioAssetDialog";
+		superclass = "EditorDialog";
+		dialogSize = (%width + 8) SPC (%height + 8);
+		dialogCanClose = true;
+		dialogText = "New Audio Asset";
+	};
+	%dialog.init(%width, %height);
+
+	Canvas.pushDialog(%dialog);
+}

+ 180 - 0
editor/AssetAdmin/NewAudioAssetDialog.cs

@@ -0,0 +1,180 @@
+
+function NewAudioAssetDialog::init(%this, %width, %height)
+{
+	//Get the dialog contents
+	%window = %this.getObject(0);
+	%content = %window.getObject(0);
+
+	//Create the file text box
+	%form = new GuiGridCtrl()
+	{
+		class = "EditorForm";
+		extent = %width SPC %height;
+		cellSizeX = %width;
+		cellSizeY = 50;
+	};
+	%form.addListener(%this);
+
+	%item = %form.addFormItem("Audio File", %width SPC 30);
+	%this.imageFileBox = %form.createFileOpenItem(%item, "Waveform Audio (*.WAV;*.WAVE)|*.WAV;*.WAVE|OGG (*.OGG;*.OGV;*.OGA;*.OGX;*.OGM;*.SPX;*.OPUS)|*.OGG;*.OGV;*.OGA;*.OGX;*.OGM;*.SPX;*.OPUS", "Open Audio File");
+	%this.imageFileBox.Command = %this.getId() @ ".Validate();";
+
+	%item = %form.addFormItem("Asset Name", %width SPC 30);
+	%this.assetNameBox = %form.createTextEditItem(%item);
+	%this.assetNameBox.Command = %this.getId() @ ".Validate();";
+
+	%item = %form.addFormItem("Target Module", %width SPC 30);
+	%this.moduleNameBox = %form.createTextEditItem(%item);
+	%this.moduleNameBox.setActive(false);
+
+	%content.add(%form);
+
+	%this.feedback = new GuiControl()
+	{
+		HorizSizing = "right";
+		VertSizing = "bottom";
+		Position = "12 170";
+		Extent = (%width - 24) SPC 80;
+		text = "Select an Audio File to get started!";
+	};
+	ThemeManager.setProfile(%this.feedback, "infoProfile");
+
+	%this.cancelButton = new GuiButtonCtrl()
+	{
+		HorizSizing = "right";
+		VertSizing = "bottom";
+		Position = "478 270";
+		Extent = "100 30";
+		Text = "Cancel";
+		Command = %this.getID() @ ".onClose();";
+	};
+	ThemeManager.setProfile(%this.cancelButton, "buttonProfile");
+
+	%this.createButton = new GuiButtonCtrl()
+	{
+		HorizSizing = "right";
+		VertSizing = "bottom";
+		Position = "588 268";
+		Extent = "100 34";
+		Text = "Create";
+		Command = %this.getID() @ ".onCreate();";
+	};
+	ThemeManager.setProfile(%this.createButton, "primaryButtonProfile");
+
+	%content.add(%this.feedback);
+	%content.add(%this.cancelButton);
+	%content.add(%this.createButton);
+
+	%this.prevFile = "";
+}
+
+function NewAudioAssetDialog::Validate(%this)
+{
+	%this.createButton.active = false;
+
+	%file = %this.imageFileBox.getText();
+	%assetName = %this.assetNameBox.getText();
+
+	if(%file !$= %this.prevFile)
+	{
+		//remove the extention
+		%fileNameSansExt = fileBase(fileName(%file));
+		if(%fileNameSansExt !$= "" && %assetName $= "")
+		{
+			%assetName = %fileNameSansExt;
+			%this.assetNameBox.setText(%fileNameSansExt);
+		}
+		%modSig = EditorCore.findModuleOfPath(%file);
+		%this.moduleNameBox.setText(%modSig);
+		%this.prevFile = %file;
+	}
+	%assetPath = filePath(%file) @ "/" @ %assetName @ ".asset.taml";
+	%moduleName = getUnit(%this.moduleNameBox.getText(), 0, "_");
+	%moduleVersion = getUnit(%this.moduleNameBox.getText(), 1, "_");
+	%assetID = %moduleName @ ":" @ %assetName;
+
+	if(!isFile(%file))
+	{
+		//We need a real image file!
+		%this.feedback.setText("An existing audio file must be used to create an audio asset!");
+		return false;
+	}
+
+	if(%file $= "")
+	{
+		%this.feedback.setText("Select an Audio File to get started!");
+		return false;
+	}
+
+	if(%assetName $= "")
+	{
+		%this.feedback.setText("An audio asset must have an Asset Name.");
+		return false;
+	}
+
+	if(%moduleName $= "")
+	{
+		%this.feedback.setText("You can only create an audio asset inside of a module.");
+		return false;
+	}
+
+	%button = AssetAdmin.Dictionary["AudioAsset"].getButton(%assetID);
+	if(isObject(%button))
+	{
+		%this.feedback.setText("An asset by this name already exists in this module. Try choosing a different name.");
+		return false;
+	}
+
+	%this.createButton.active = true;
+	%this.feedback.setText("Press the Create button to open the new asset for editing.");
+	return true;
+}
+
+function NewAudioAssetDialog::onClose(%this)
+{
+	Canvas.popDialog(%this);
+}
+
+function NewAudioAssetDialog::onCreate(%this)
+{
+	if(%this.validate())
+	{
+		%file = makeFullPath(%this.imageFileBox.getText());
+		%assetName = %this.assetNameBox.getText();
+		%assetPath = filePath(%file) @ "/" @ %assetName @ ".asset.taml";
+		%moduleName = getUnit(%this.moduleNameBox.getText(), 0, "_");
+		%moduleVersion = getUnit(%this.moduleNameBox.getText(), 1, "_");
+		%assetID = %moduleName @ ":" @ %assetName;
+
+		//Time to create a new file
+		%newAsset = new AudioAsset()
+		{
+			assetName = %assetName;
+			audioFile = %file;
+		};
+
+		%assetImportSuccessful = TAMLWrite(%newAsset, %assetPath);
+
+		%moduleDef = ModuleDatabase.findModule(%moduleName, %moduleVersion);
+		AssetDatabase.addDeclaredAsset(%moduleDef, %assetPath);
+
+		//Refresh the asset so that the loose file will be a path relative to the asset file.
+		AssetDatabase.refreshAsset(%assetID);
+
+		//Do we already have this button?
+		%button = AssetAdmin.Dictionary["AudioAsset"].getButton(%assetID);
+
+		if(!isObject(%button))
+		{
+			//Load it into the image dictionary
+			%button = AssetAdmin.Dictionary["AudioAsset"].addButton(%assetID);
+		}
+		%button.onClick();
+		%this.onClose();
+	}
+}
+
+function NewAudioAssetDialog::onFileOpened(%this, %textBox)
+{
+	%this.Validate();
+}

+ 180 - 0
editor/AssetAdmin/NewFontAssetDialog.cs

@@ -0,0 +1,180 @@
+
+function NewFontAssetDialog::init(%this, %width, %height)
+{
+	//Get the dialog contents
+	%window = %this.getObject(0);
+	%content = %window.getObject(0);
+
+	//Create the file text box
+	%form = new GuiGridCtrl()
+	{
+		class = "EditorForm";
+		extent = %width SPC %height;
+		cellSizeX = %width;
+		cellSizeY = 50;
+	};
+	%form.addListener(%this);
+
+	%item = %form.addFormItem("Font File", %width SPC 30);
+	%this.imageFileBox = %form.createFileOpenItem(%item, "Bitmap Font (*.FNT)|*.FNT", "Open Bitmap Font File");
+	%this.imageFileBox.Command = %this.getId() @ ".Validate();";
+
+	%item = %form.addFormItem("Asset Name", %width SPC 30);
+	%this.assetNameBox = %form.createTextEditItem(%item);
+	%this.assetNameBox.Command = %this.getId() @ ".Validate();";
+
+	%item = %form.addFormItem("Target Module", %width SPC 30);
+	%this.moduleNameBox = %form.createTextEditItem(%item);
+	%this.moduleNameBox.setActive(false);
+
+	%content.add(%form);
+
+	%this.feedback = new GuiControl()
+	{
+		HorizSizing = "right";
+		VertSizing = "bottom";
+		Position = "12 170";
+		Extent = (%width - 24) SPC 80;
+		text = "Select a Bimap Font File to get started!";
+	};
+	ThemeManager.setProfile(%this.feedback, "infoProfile");
+
+	%this.cancelButton = new GuiButtonCtrl()
+	{
+		HorizSizing = "right";
+		VertSizing = "bottom";
+		Position = "478 270";
+		Extent = "100 30";
+		Text = "Cancel";
+		Command = %this.getID() @ ".onClose();";
+	};
+	ThemeManager.setProfile(%this.cancelButton, "buttonProfile");
+
+	%this.createButton = new GuiButtonCtrl()
+	{
+		HorizSizing = "right";
+		VertSizing = "bottom";
+		Position = "588 268";
+		Extent = "100 34";
+		Text = "Create";
+		Command = %this.getID() @ ".onCreate();";
+	};
+	ThemeManager.setProfile(%this.createButton, "primaryButtonProfile");
+
+	%content.add(%this.feedback);
+	%content.add(%this.cancelButton);
+	%content.add(%this.createButton);
+
+	%this.prevFile = "";
+}
+
+function NewFontAssetDialog::Validate(%this)
+{
+	%this.createButton.active = false;
+
+	%file = %this.imageFileBox.getText();
+	%assetName = %this.assetNameBox.getText();
+
+	if(%file !$= %this.prevFile)
+	{
+		//remove the extention
+		%fileNameSansExt = fileBase(fileName(%file));
+		if(%fileNameSansExt !$= "" && %assetName $= "")
+		{
+			%assetName = %fileNameSansExt;
+			%this.assetNameBox.setText(%fileNameSansExt);
+		}
+		%modSig = EditorCore.findModuleOfPath(%file);
+		%this.moduleNameBox.setText(%modSig);
+		%this.prevFile = %file;
+	}
+	%assetPath = filePath(%file) @ "/" @ %assetName @ ".asset.taml";
+	%moduleName = getUnit(%this.moduleNameBox.getText(), 0, "_");
+	%moduleVersion = getUnit(%this.moduleNameBox.getText(), 1, "_");
+	%assetID = %moduleName @ ":" @ %assetName;
+
+	if(!isFile(%file))
+	{
+		//We need a real image file!
+		%this.feedback.setText("An existing font file must be used to create a font asset!");
+		return false;
+	}
+
+	if(%file $= "")
+	{
+		%this.feedback.setText("Select a Bitmap Font File to get started!");
+		return false;
+	}
+
+	if(%assetName $= "")
+	{
+		%this.feedback.setText("A font asset must have an Asset Name.");
+		return false;
+	}
+
+	if(%moduleName $= "")
+	{
+		%this.feedback.setText("You can only create a font asset inside of a module.");
+		return false;
+	}
+
+	%button = AssetAdmin.Dictionary["FontAsset"].getButton(%assetID);
+	if(isObject(%button))
+	{
+		%this.feedback.setText("An asset by this name already exists in this module. Try choosing a different name.");
+		return false;
+	}
+
+	%this.createButton.active = true;
+	%this.feedback.setText("Press the Create button to open the new asset for editing.");
+	return true;
+}
+
+function NewFontAssetDialog::onClose(%this)
+{
+	Canvas.popDialog(%this);
+}
+
+function NewFontAssetDialog::onCreate(%this)
+{
+	if(%this.validate())
+	{
+		%file = makeFullPath(%this.imageFileBox.getText());
+		%assetName = %this.assetNameBox.getText();
+		%assetPath = filePath(%file) @ "/" @ %assetName @ ".asset.taml";
+		%moduleName = getUnit(%this.moduleNameBox.getText(), 0, "_");
+		%moduleVersion = getUnit(%this.moduleNameBox.getText(), 1, "_");
+		%assetID = %moduleName @ ":" @ %assetName;
+
+		//Time to create a new file
+		%newAsset = new FontAsset()
+		{
+			assetName = %assetName;
+			fontFile = %file;
+		};
+
+		%assetImportSuccessful = TAMLWrite(%newAsset, %assetPath);
+
+		%moduleDef = ModuleDatabase.findModule(%moduleName, %moduleVersion);
+		AssetDatabase.addDeclaredAsset(%moduleDef, %assetPath);
+
+		//Refresh the asset so that the loose file will be a path relative to the asset file.
+		AssetDatabase.refreshAsset(%assetID);
+
+		//Do we already have this button?
+		%button = AssetAdmin.Dictionary["FontAsset"].getButton(%assetID);
+
+		if(!isObject(%button))
+		{
+			//Load it into the image dictionary
+			%button = AssetAdmin.Dictionary["FontAsset"].addButton(%assetID);
+		}
+		%button.onClick();
+		%this.onClose();
+	}
+}
+
+function NewFontAssetDialog::onFileOpened(%this, %textBox)
+{
+	%this.Validate();
+}

+ 187 - 0
editor/AssetAdmin/NewImageAssetDialog.cs

@@ -0,0 +1,187 @@
+
+function NewImageAssetDialog::init(%this, %width, %height)
+{
+	//Get the dialog contents
+	%window = %this.getObject(0);
+	%content = %window.getObject(0);
+
+	//Create the file text box
+	%form = new GuiGridCtrl()
+	{
+		class = "EditorForm";
+		extent = %width SPC %height;
+		cellSizeX = %width;
+		cellSizeY = 50;
+	};
+	%form.addListener(%this);
+
+	%item = %form.addFormItem("Image File", %width SPC 30);
+	%this.imageFileBox = %form.createFileOpenItem(%item, "PNG (*.PNG)|*.PNG|JPEG (*.JPG;*.JPEG;*.JPE)|*.JPG;*.JPEG;*.JPE", "Open Image File");
+	%this.imageFileBox.Command = %this.getId() @ ".Validate();";
+
+	%item = %form.addFormItem("Asset Name", %width SPC 30);
+	%this.assetNameBox = %form.createTextEditItem(%item);
+	%this.assetNameBox.Command = %this.getId() @ ".Validate();";
+
+	%item = %form.addFormItem("Target Module", %width SPC 30);
+	%this.moduleNameBox = %form.createTextEditItem(%item);
+	%this.moduleNameBox.setActive(false);
+
+	%content.add(%form);
+
+	%this.feedback = new GuiControl()
+	{
+		HorizSizing = "right";
+		VertSizing = "bottom";
+		Position = "12 170";
+		Extent = (%width - 24) SPC 80;
+		text = "Select an Image File to get started!";
+	};
+	ThemeManager.setProfile(%this.feedback, "infoProfile");
+
+	%this.cancelButton = new GuiButtonCtrl()
+	{
+		HorizSizing = "right";
+		VertSizing = "bottom";
+		Position = "478 270";
+		Extent = "100 30";
+		Text = "Cancel";
+		Command = %this.getID() @ ".onClose();";
+	};
+	ThemeManager.setProfile(%this.cancelButton, "buttonProfile");
+
+	%this.createButton = new GuiButtonCtrl()
+	{
+		HorizSizing = "right";
+		VertSizing = "bottom";
+		Position = "588 268";
+		Extent = "100 34";
+		Text = "Create";
+		Command = %this.getID() @ ".onCreate();";
+	};
+	ThemeManager.setProfile(%this.createButton, "primaryButtonProfile");
+
+	%content.add(%this.feedback);
+	%content.add(%this.cancelButton);
+	%content.add(%this.createButton);
+
+	%this.prevFile = "";
+}
+
+function NewImageAssetDialog::Validate(%this)
+{
+	%this.createButton.active = false;
+
+	%file = %this.imageFileBox.getText();
+	%assetName = %this.assetNameBox.getText();
+
+	if(%file !$= %this.prevFile)
+	{
+		//remove the extention
+		%fileNameSansExt = fileBase(fileName(%file));
+		if(%fileNameSansExt !$= "" && %assetName $= "")
+		{
+			%assetName = %fileNameSansExt;
+			%this.assetNameBox.setText(%fileNameSansExt);
+		}
+		%modSig = EditorCore.findModuleOfPath(%file);
+		%this.moduleNameBox.setText(%modSig);
+		%this.prevFile = %file;
+	}
+	%assetPath = filePath(%file) @ "/" @ %assetName @ ".asset.taml";
+	%moduleName = getUnit(%this.moduleNameBox.getText(), 0, "_");
+	%moduleVersion = getUnit(%this.moduleNameBox.getText(), 1, "_");
+	%assetID = %moduleName @ ":" @ %assetName;
+
+	if(!isFile(%file))
+	{
+		//We need a real image file!
+		%this.feedback.setText("An existing image file must be used to create an image asset!");
+		return false;
+	}
+
+	if(!isValidImageFile(%file))
+	{
+		//We need a real image file!
+		%this.feedback.setText("There is a problem with the image file!");
+		return false;
+	}
+
+	if(%file $= "")
+	{
+		%this.feedback.setText("Select an Image File to get started!");
+		return false;
+	}
+
+	if(%assetName $= "")
+	{
+		%this.feedback.setText("An image asset must have an Asset Name.");
+		return false;
+	}
+
+	if(%moduleName $= "")
+	{
+		%this.feedback.setText("You can only create an image asset inside of a module.");
+		return false;
+	}
+
+	%button = AssetAdmin.Dictionary["ImageAsset"].getButton(%assetID);
+	if(isObject(%button))
+	{
+		%this.feedback.setText("An asset by this name already exists in this module. Try choosing a different name.");
+		return false;
+	}
+
+	%this.createButton.active = true;
+	%this.feedback.setText("Press the Create button to open the new asset for editing.");
+	return true;
+}
+
+function NewImageAssetDialog::onClose(%this)
+{
+	Canvas.popDialog(%this);
+}
+
+function NewImageAssetDialog::onCreate(%this)
+{
+	if(%this.validate())
+	{
+		%file = makeFullPath(%this.imageFileBox.getText());
+		%assetName = %this.assetNameBox.getText();
+		%assetPath = filePath(%file) @ "/" @ %assetName @ ".asset.taml";
+		%moduleName = getUnit(%this.moduleNameBox.getText(), 0, "_");
+		%moduleVersion = getUnit(%this.moduleNameBox.getText(), 1, "_");
+		%assetID = %moduleName @ ":" @ %assetName;
+
+		//Time to create a new file
+		%newAsset = new ImageAsset()
+		{
+			assetName = %assetName;
+			imageFile = %file;
+		};
+
+		%assetImportSuccessful = TAMLWrite(%newAsset, %assetPath);
+
+		%moduleDef = ModuleDatabase.findModule(%moduleName, %moduleVersion);
+		AssetDatabase.addDeclaredAsset(%moduleDef, %assetPath);
+
+		//Refresh the asset so that the loose file will be a path relative to the asset file.
+		AssetDatabase.refreshAsset(%assetID);
+
+		//Do we already have this button?
+		%button = AssetAdmin.Dictionary["ImageAsset"].getButton(%assetID);
+
+		if(!isObject(%button))
+		{
+			//Load it into the image dictionary
+			%button = AssetAdmin.Dictionary["ImageAsset"].addButton(%assetID);
+		}
+		%button.onClick();
+		%this.onClose();
+	}
+}
+
+function NewImageAssetDialog::onFileOpened(%this, %textBox)
+{
+	%this.Validate();
+}

+ 279 - 0
editor/AssetAdmin/NewParticleAssetDialog.cs

@@ -0,0 +1,279 @@
+
+function NewParticleAssetDialog::init(%this, %width, %height)
+{
+	//Get the dialog contents
+	%window = %this.getObject(0);
+	%content = %window.getObject(0);
+
+	//Create the file text box
+	%form = new GuiGridCtrl()
+	{
+		class = "EditorForm";
+		extent = %width SPC %height;
+		cellSizeX = %width;
+		cellSizeY = 50;
+	};
+	%form.addListener(%this);
+
+	%item = %form.addFormItem("Image Asset", %width SPC 30);
+	%this.imageDropDown = %form.createDropDownItem(%item);
+	%this.populateImageDropDown();
+
+	%item = %form.addFormItem("Animation Asset", %width SPC 30);
+	%this.animationDropDown = %form.createDropDownItem(%item);
+	%this.populateAnimationDropDown();
+
+	%item = %form.addFormItem("Target Folder", %width SPC 30);
+	%this.folderBox = %form.createFolderOpenItem(%item, "Select Target Folder");
+	%this.folderBox.Command = %this.getId() @ ".Validate();";
+
+	%item = %form.addFormItem("Asset Name", %width SPC 30);
+	%this.assetNameBox = %form.createTextEditItem(%item);
+	%this.assetNameBox.Command = %this.getId() @ ".Validate();";
+
+	%item = %form.addFormItem("Target Module", %width SPC 30);
+	%this.moduleNameBox = %form.createTextEditItem(%item);
+	%this.moduleNameBox.setActive(false);
+
+	%content.add(%form);
+
+	%this.feedback = new GuiControl()
+	{
+		HorizSizing = "right";
+		VertSizing = "bottom";
+		Position = "12 270";
+		Extent = (%width - 24) SPC 80;
+		text = "Select an Image Asset or Animation Assset to get started!";
+	};
+	ThemeManager.setProfile(%this.feedback, "infoProfile");
+
+	%this.cancelButton = new GuiButtonCtrl()
+	{
+		HorizSizing = "right";
+		VertSizing = "bottom";
+		Position = "478 370";
+		Extent = "100 30";
+		Text = "Cancel";
+		Command = %this.getID() @ ".onClose();";
+	};
+	ThemeManager.setProfile(%this.cancelButton, "buttonProfile");
+
+	%this.createButton = new GuiButtonCtrl()
+	{
+		HorizSizing = "right";
+		VertSizing = "bottom";
+		Position = "588 368";
+		Extent = "100 34";
+		Text = "Create";
+		Command = %this.getID() @ ".onCreate();";
+	};
+	ThemeManager.setProfile(%this.createButton, "primaryButtonProfile");
+
+	%content.add(%this.feedback);
+	%content.add(%this.cancelButton);
+	%content.add(%this.createButton);
+
+	%this.prevFolder = "";
+}
+
+function NewParticleAssetDialog::Validate(%this)
+{
+	%this.createButton.active = false;
+
+	%imageAssetID = %this.imageDropDown.getText();
+	%animationAssetID = %this.animationDropDown.getText();
+	if(%imageAssetID $= "" && %animationAssetID $= "")
+	{
+		%this.feedback.setText("Select an Image Asset or Animation Assset to get started!");
+		return false;
+	}
+	%assetID = %imageAssetID;
+	if(%imageAssetID $= "")
+	{
+		%assetID = %animationAssetID;
+	}
+
+	%assetFilePath = AssetDatabase.getAssetFilePath(%assetID);
+
+	if(%this.folderBox.getText() $= "")
+	{
+		%this.folderBox.setText(makeRelativePath(filePath(%assetFilePath), getMainDotCsDir()));
+	}
+
+	if(%this.assetNameBox.getText() $= "")
+	{
+		%this.assetNameBox.setText(getUnit(%assetID, 1, ":") @ "_PT");
+	}
+
+	%folderPath = %this.getFolderPath();
+	%assetName = %this.assetNameBox.getText();
+
+	if(%folderPath !$= %this.prevFolder)
+	{
+		%modSig = EditorCore.findModuleOfPath(%folderPath @ "a.txt");
+		%this.moduleNameBox.setText(%modSig);
+		%this.prevFolder = %folderPath;
+	}
+	%assetPath = %folderPath @ %assetName @ ".asset.taml";
+	%moduleName = getUnit(%this.moduleNameBox.getText(), 0, "_");
+	%moduleVersion = getUnit(%this.moduleNameBox.getText(), 1, "_");
+	%assetID = %moduleName @ ":" @ %assetName;
+
+	if(%folderPath $= "")
+	{
+		%this.feedback.setText("Please select a target folder.");
+		return false;
+	}
+
+	if(%assetName $= "")
+	{
+		%this.feedback.setText("A particle asset must have an Asset Name.");
+		return false;
+	}
+
+	if(%moduleName $= "")
+	{
+		%this.feedback.setText("You can only create a particle asset inside of a module.");
+		return false;
+	}
+
+	%button = AssetAdmin.Dictionary["ParticleAsset"].getButton(%assetID);
+	if(isObject(%button))
+	{
+		%this.feedback.setText("An asset by this name already exists in this module. Try choosing a different name.");
+		return false;
+	}
+
+	%this.createButton.active = true;
+	%this.feedback.setText("Press the Create button to open the new asset for editing.");
+	return true;
+}
+
+function NewParticleAssetDialog::onClose(%this)
+{
+	Canvas.popDialog(%this);
+}
+
+function NewParticleAssetDialog::onCreate(%this)
+{
+	if(%this.validate())
+	{
+		%folderPath = %this.getFolderPath();
+		%assetName = %this.assetNameBox.getText();
+		%assetPath = %folderPath @ %assetName @ ".asset.taml";
+		%moduleName = getUnit(%this.moduleNameBox.getText(), 0, "_");
+		%moduleVersion = getUnit(%this.moduleNameBox.getText(), 1, "_");
+		%assetID = %moduleName @ ":" @ %assetName;
+
+		//Time to create a new file
+		%newAsset = new ParticleAsset()
+		{
+			assetName = %assetName;
+		};
+		%newEmitter = %newAsset.createEmitter();
+		%newEmitter.setEmitterName("DefaultEmitter");
+		if(%this.imageDropDown.getText() !$= "")
+		{
+			%newEmitter.setImage(%this.imageDropDown.getText());
+		}
+		else
+		{
+			%newEmitter.setAnimation(%this.animationDropDown.getText());
+		}
+
+		%assetImportSuccessful = TAMLWrite(%newAsset, %assetPath);
+
+		%moduleDef = ModuleDatabase.findModule(%moduleName, %moduleVersion);
+		AssetDatabase.addDeclaredAsset(%moduleDef, %assetPath);
+
+		//Do we already have this button?
+		%button = AssetAdmin.Dictionary["ParticleAsset"].getButton(%assetID);
+
+		if(!isObject(%button))
+		{
+			//Load it into the image dictionary
+			%button = AssetAdmin.Dictionary["ParticleAsset"].addButton(%assetID);
+		}
+		%button.onClick();
+		%this.onClose();
+	}
+}
+
+function NewParticleAssetDialog::onFolderOpened(%this, %textBox)
+{
+	%this.Validate();
+}
+
+function NewParticleAssetDialog::getFolderPath(%this)
+{
+	%folderPath = stripTrailingSpaces(makeFullPath(%this.folderBox.getText()));
+	%length = strlen(%folderPath);
+	%lastChar = getSubStr(%folderPath, %length - 1, 1);
+	if(%lastChar $= "/")
+	{
+		return %folderPath;
+	}
+	return %folderPath @ "/";
+}
+
+function NewParticleAssetDialog::onDropDownClosed(%this, %dropDown)
+{
+	if(%dropDown == %this.imageDropDown && %dropDown.getText() !$= "")
+	{
+		%this.animationDropDown.setSelected(0);
+	}
+	else if(%dropDown == %this.animationDropDown && %dropDown.getText() !$= "")
+	{
+		%this.imageDropDown.setSelected(0);
+	}
+
+	%this.validate();
+}
+
+function NewParticleAssetDialog::populateImageDropDown(%this)
+{
+	%this.imageDropDown.clearItems();
+
+	%query = new AssetQuery();
+	AssetDatabase.findAllAssets(%query);
+	AssetDatabase.findAssetType(%query, "ImageAsset", true);
+
+	for(%i = 0; %i < %query.getCount(); %i++)
+	{
+		%assetID = %query.getAsset(%i);
+
+		if(!AssetDatabase.isAssetInternal(%assetID))
+		{
+			%this.imageDropDown.addItem(%assetID);
+		}
+	}
+	%query.delete();
+
+	%this.imageDropDown.sortByText();
+	%this.imageDropDown.insertItem(0, "");
+	%this.imageDropDown.setSelected(0);
+}
+
+function NewParticleAssetDialog::populateAnimationDropDown(%this)
+{
+	%this.animationDropDown.clearItems();
+
+	%query = new AssetQuery();
+	AssetDatabase.findAllAssets(%query);
+	AssetDatabase.findAssetType(%query, "AnimationAsset", true);
+
+	for(%i = 0; %i < %query.getCount(); %i++)
+	{
+		%assetID = %query.getAsset(%i);
+
+		if(!AssetDatabase.isAssetInternal(%assetID))
+		{
+			%this.animationDropDown.addItem(%assetID);
+		}
+	}
+	%query.delete();
+
+	%this.animationDropDown.sortByText();
+	%this.animationDropDown.insertItem(0, "");
+	%this.animationDropDown.setSelected(0);
+}

+ 51 - 0
editor/EditorCore/EditorCore.cs

@@ -23,6 +23,8 @@
 function EditorCore::create( %this )
 {
 	exec("./Themes/ThemeManager.cs");
+	exec("./EditorDialog.cs");
+	exec("./EditorForm.cs");
 
 	new ScriptObject(ThemeManager);
 
@@ -180,3 +182,52 @@ function EditorCoreTabBook::onTabSelected(%this, %tabText)
 	%this.Core.page[%tabText].Editor.open();
 	%this.openEditor = %this.Core.page[%tabText].Editor;
 }
+
+function EditorCore::findModuleOfPath(%this, %pathWithFile)
+{
+	%pathWithFile = makeFullPath(%pathWithFile);
+	%loadedModules = ModuleDatabase.findModules(false);
+
+	%chosenModSig = "";
+	for(%i = 0; %i < getWordCount(%loadedModules); %i++)
+	{
+		%mod = getWord(%loadedModules, %i);
+		%modPath = %this.flattenPath(%mod.ModulePath);
+
+		//Now compare the two paths to see if they go together
+		if(strPos(%pathWithFile, %modPath) != -1)
+		{
+			%chosenModSig = %mod.Signature;
+			break;
+		}
+	}
+
+	return %chosenModSig;
+}
+
+function EditorCore::flattenPath(%this, %path)
+{
+	%pen = 0;
+	for(%i = 0; %i < getUnitCount(%path, "/"); %i++)
+	{
+		%token = getUnit(%path, %i, "/");
+
+		if(%token $= "..")
+		{
+			%pen = mGetMax(%pen - 1, 0);
+		}
+		else
+		{
+			%token[%pen] = %token;
+			%pen++;
+		}
+	}
+
+	%result = %token[0];
+	for(%i = 1; %i <= %pen; %i++)
+	{
+		%result = %result @ "/" @ %token[%i];
+	}
+
+	return %result;
+}

+ 71 - 0
editor/EditorCore/EditorDialog.cs

@@ -0,0 +1,71 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+function EditorDialog::onAdd(%this)
+{
+	ThemeManager.setProfile(%this, "overlayProfile");
+
+	%this.window = new GuiWindowCtrl()
+	{
+		class = "EditorDialogWindow";
+		HorizSizing = "center";
+		VertSizing = "center";
+		Position = "0 0";
+		Extent = %this.dialogSize;
+		Text = %this.dialogText;
+		canClose = %this.dialogCanClose;
+		canMove = true;
+		CanMinimize = false;
+		CanMaximize = false;
+		titleHeight = 30;
+		dialog = %this;
+	};
+	ThemeManager.setProfile(%this.window, "windowProfile");
+	ThemeManager.setProfile(%this.window, "windowContentProfile", "contentProfile");
+	ThemeManager.setProfile(%this.window, "windowButtonProfile", "MinButtonProfile");
+	ThemeManager.setProfile(%this.window, "windowButtonProfile", "MaxButtonProfile");
+	ThemeManager.setProfile(%this.window, "windowButtonProfile", "CloseButtonProfile");
+	%this.add(%this.window);
+
+	%this.content = new GuiScrollCtrl()
+	{
+		HorizSizing = "width";
+		VertSizing = "height";
+		Position = "0 0";
+		Extent = (getWord(%this.dialogSize, 0) - 8) SPC (getWord(%this.dialogSize, 1) - 34);//we assume a 4 pixel border
+		hScrollBar = "dynamic";
+		vScrollBar = "dynamic";
+		constantThumbHeight="0";
+		showArrowButtons = 0;
+		scrollBarThickness = 16;
+	};
+	ThemeManager.setProfile(%this.content, "scrollingPanelProfile");
+	ThemeManager.setProfile(%this.content, "scrollingPanelThumbProfile", ThumbProfile);
+	ThemeManager.setProfile(%this.content, "scrollingPanelTrackProfile", TrackProfile);
+	ThemeManager.setProfile(%this.content, "scrollingPanelArrowProfile", ArrowProfile);
+	%this.window.add(%this.content);
+}
+
+function EditorDialogWindow::onClose(%this)
+{
+	%this.dialog.call("onClose");
+}

+ 170 - 0
editor/EditorCore/EditorForm.cs

@@ -0,0 +1,170 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+function EditorForm::onAdd(%this)
+{
+	ThemeManager.setProfile(%this, "emptyProfile");
+
+	%this.setCellSpacing("0 0");
+	%this.setCellModeX(Variable);
+	%this.setCellModeY(Variable);
+	%this.setMaxColCount(0);
+}
+
+function EditorForm::addFormItem(%this, %text, %size)
+{
+	%label = new GuiControl()
+	{
+		Extent = %size;
+		Text = %text;
+	};
+	ThemeManager.setProfile(%label, "labelProfile");
+	%this.add(%label);
+	return %label;
+}
+
+function EditorForm::createTextEditItem(%this, %label)
+{
+	%textEdit = new GuiTextEditCtrl()
+	{
+		Position = "10 16";
+		Extent = (getWord(%label.extent, 0) - 24) SPC 30;
+	};
+	ThemeManager.setProfile(%textEdit, "textEditProfile");
+	%label.add(%textEdit);
+	return %textEdit;
+}
+
+function EditorForm::createFileOpenItem(%this, %label, %filters, %dialogTitle)
+{
+	%textEdit = %this.createTextEditItem(%label);
+	%teWidth = getWord(%label.extent, 0) - 110;
+	%textEdit.setExtent(%teWidth, 30);
+
+	%button = new GuiButtonCtrl()
+	{
+		Position = (%teWidth + 20) SPC 16;
+		Extent = "76 30";
+		Text = "Find";
+		Command = %this.getID() @ ".getFilePath(\"" @ %filters @ "\", \"" @ %dialogTitle @ "\", " @ %textEdit.getID() @ ");";
+	};
+	ThemeManager.setProfile(%button, "buttonProfile");
+	%label.add(%button);
+
+	return %textEdit;
+}
+
+function EditorForm::getFilePath(%this, %filter, %title, %textEdit)
+{
+	%dialog = new OpenFileDialog()
+	{
+		Filters = %filter;
+		ChangePath = false;
+		MultipleFiles = false;
+		DefaultFile = "";
+		defaultPath = "./";
+		title = %title;
+	};
+	%result = %dialog.execute();
+
+	if ( %result )
+	{
+		%selectedFile = makeRelativePath(%dialog.fileName, getMainDotCsDir());
+		%textEdit.setText(%selectedFile);
+		%textEdit.setCursorPos(999999);//move it to the far right.
+	}
+	// Cleanup
+	%dialog.delete();
+
+	%this.postEvent("FileOpened", %textEdit);
+}
+
+function EditorForm::createFolderOpenItem(%this, %label, %dialogTitle)
+{
+	%textEdit = %this.createTextEditItem(%label);
+	%teWidth = getWord(%label.extent, 0) - 110;
+	%textEdit.setExtent(%teWidth, 30);
+
+	%button = new GuiButtonCtrl()
+	{
+		Position = (%teWidth + 20) SPC 16;
+		Extent = "76 30";
+		Text = "Select";
+		Command = %this.getID() @ ".getFolderPath(\"" @ %dialogTitle @ "\", " @ %textEdit.getID() @ ");";
+	};
+	ThemeManager.setProfile(%button, "buttonProfile");
+	%label.add(%button);
+
+	return %textEdit;
+}
+
+function EditorForm::getFolderPath(%this, %title, %textEdit)
+{
+	%dialog = new OpenFolderDialog()
+	{
+		Filters = "All Files|*.*";
+		ChangePath = false;
+		DefaultFile = "";
+		defaultPath = "./";
+		title = %title;
+	};
+	%result = %dialog.execute();
+
+	if ( %result )
+	{
+		%selectedFile = makeRelativePath(%dialog.fileName, getMainDotCsDir());
+		%textEdit.setText(%selectedFile);
+		%textEdit.setCursorPos(999999);//move it to the far right.
+	}
+	// Cleanup
+	%dialog.delete();
+
+	%this.postEvent("FolderOpened", %textEdit);
+}
+
+function EditorForm::createDropDownItem(%this, %label)
+{
+	%dropDown = new GuiDropDownCtrl()
+	{
+		class = "EditorFormDropDown";
+		Position = "10 17";
+		Extent = (getWord(%label.extent, 0) - 24) SPC 28;
+		ConstantThumbHeight=false;
+		ScrollBarThickness=12;
+		ShowArrowButtons=true;
+		Form = %this;
+	};
+	ThemeManager.setProfile(%dropDown, "dropDownProfile");
+	ThemeManager.setProfile(%dropDown, "dropDownItemProfile", "listBoxProfile");
+	ThemeManager.setProfile(%dropDown, "emptyProfile", "backgroundProfile");
+	ThemeManager.setProfile(%dropDown, "scrollingPanelProfile", "ScrollProfile");
+	ThemeManager.setProfile(%dropDown, "scrollingPanelThumbProfile", "ThumbProfile");
+	ThemeManager.setProfile(%dropDown, "scrollingPanelTrackProfile", "TrackProfile");
+	ThemeManager.setProfile(%dropDown, "scrollingPanelArrowProfile", "ArrowProfile");
+	%label.add(%dropDown);
+	return %dropDown;
+}
+
+function EditorFormDropDown::onClose(%this)
+{
+	%this.form.postEvent("DropDownClosed", %this);
+}

+ 252 - 3
editor/EditorCore/Themes/BaseTheme/BaseTheme.cs

@@ -50,6 +50,7 @@ function BaseTheme::onAdd(%this)
 	%this.makePanelProfile();
 	%this.makeItemSelectProfile();
 	%this.makeButtonProfile();
+	%this.makePrimaryButtonProfile();
 	%this.makeCheckboxProfile();
 	%this.makeTabProfile();
 	%this.makeTextEditProfile();
@@ -57,6 +58,7 @@ function BaseTheme::onAdd(%this)
 	%this.makeConsoleProfile();
 	%this.makeMenuProfile();
 	%this.makeDropDownProfile();
+	%this.makeWindowProfile();
 }
 
 function BaseTheme::init(%this)
@@ -106,7 +108,7 @@ function BaseTheme::makeGeneralProfiles(%this)
 	//Useful as a background
 	%this.overlayProfile = new GuiControlProfile()
 	{
-		fillColor = %this.setAlpha(%this.color1, 150);
+		fillColor = %this.setAlpha(%this.color1, 180);
 
 		fontType = %this.font[1];
 		fontDirectory = %this.fontDirectory;
@@ -156,6 +158,27 @@ function BaseTheme::makeGeneralProfiles(%this)
 		borderDefault = %simpleBorder2;
 	};
 
+	%infoBorder = new GuiBorderProfile()
+	{
+		padding = 10;
+		border = %this.borderSize;
+		borderColor = %this.setAlpha(%this.color4, 30);
+	};
+	//A simple, well-rounded profile for text or containers
+	%this.infoProfile = new GuiControlProfile()
+	{
+		fillColor = %this.color1;
+
+		fontType = %this.font[3];
+		fontDirectory = %this.fontDirectory;
+		fontSize = %this.fontSize;
+		fontColor = %this.color5;
+		align = left;
+		vAlign = top;
+
+		borderDefault = %infoBorder;
+	};
+
 	%this.spriteProfile = new GuiControlProfile()
 	{
 		fillColor = "0 0 0 0";
@@ -450,6 +473,105 @@ function BaseTheme::makeButtonProfile(%this)
 	};
 }
 
+function BaseTheme::makePrimaryButtonProfile(%this)
+{
+	%borderLightH = new GuiBorderProfile()
+	{
+		padding = 4;
+		paddingHL = 4;
+		paddingSL = 4;
+		paddingNA = 4;
+
+		border = %this.borderSize;
+		borderHL = %this.borderSize;
+		borderSL = %this.borderSize;
+		borderNA = %this.borderSize;
+
+		borderColor = "255 255 255 80";
+		borderColorHL = "255 255 255 80";
+		borderColorSL = "0 0 0 80";
+		borderColorNA = "255 255 255 80";
+
+		underfill = true;
+	};
+
+	%borderLightV = new GuiBorderProfile()
+	{
+		border = %this.borderSize;
+		borderHL = %this.borderSize;
+		borderSL = %this.borderSize;
+		borderNA = %this.borderSize;
+
+		borderColor = "255 255 255 80";
+		borderColorHL = "255 255 255 80";
+		borderColorSL = "0 0 0 80";
+		borderColorNA = "255 255 255 80";
+
+		underfill = true;
+	};
+
+	%borderDarkH = new GuiBorderProfile()
+	{
+		padding = 4;
+		paddingHL = 4;
+		paddingSL = 4;
+		paddingNA = 4;
+
+		border = %this.borderSize;
+		borderHL = %this.borderSize;
+		borderSL = %this.borderSize;
+		borderNA = %this.borderSize;
+
+		borderColor = "0 0 0 80";
+		borderColorHL = "0 0 0 80";
+		borderColorSL = "255 255 255 80";
+		borderColorNA = "0 0 0 80";
+
+		underfill = true;
+	};
+
+	%borderDarkV = new GuiBorderProfile()
+	{
+		border = %this.borderSize;
+		borderHL = %this.borderSize;
+		borderSL = %this.borderSize;
+		borderNA = %this.borderSize;
+
+		borderColor = "0 0 0 80";
+		borderColorHL = "0 0 0 80";
+		borderColorSL = "255 255 255 80";
+		borderColorNA = "0 0 0 80";
+
+		underfill = true;
+	};
+
+	%this.primaryButtonProfile = new GuiControlProfile()
+	{
+		fillColor = %this.color5;
+		fillColorHL = %this.adjustValue(%this.color5, 10);
+		fillColorSL = %this.adjustValue(%this.color5, 15);
+		fillColorNA = %this.setAlpha(%this.color5, 80);
+
+		fontType = %this.font[2];
+		fontDirectory = %this.fontDirectory;
+		fontSize = %this.fontSize + 1;
+		fontColor = %this.color1;
+		fontColorHL = %this.adjustValue(%this.color1, 10);
+		fontColorSL = %this.color1;
+		fontColorNA = %this.setAlpha(%this.color1, 100);
+		align = center;
+		vAlign = middle;
+
+		borderLeft = %borderLightH;
+		borderRight = %borderDarkH;
+		borderTop = %borderLightV;
+		borderBottom = %borderDarkV;
+
+		canKeyFocus = true;
+		tab = true;
+	};
+}
+
 function BaseTheme::makeCheckboxProfile(%this)
 {
 	%borderLight = new GuiBorderProfile()
@@ -1193,7 +1315,7 @@ function BaseTheme::makeDropDownProfile(%this)
 		fillColorSL = %this.color5;
 		fillColorNA = %this.setAlpha(%this.color4, 80);
 
-		fontType = %this.font[1];
+		fontType = %this.font[3];
 		fontDirectory = %this.fontDirectory;
 		fontSize = %this.fontSize;
 		fontColor = %this.color1;
@@ -1244,7 +1366,7 @@ function BaseTheme::makeDropDownProfile(%this)
 	    fillColorSL = %this.color5;
 	    fillColorNA = SetColorAlpha(%this.color1, 100);
 
-		fontType = %this.font[1];
+		fontType = %this.font[3];
 		fontDirectory = %this.fontDirectory;
 		fontSize = %this.fontSize;
 		fontColor = %this.color4;
@@ -1261,6 +1383,133 @@ function BaseTheme::makeDropDownProfile(%this)
 	};
 }
 
+function BaseTheme::makeWindowProfile(%this)
+{
+	%windowBorderL = new GuiBorderProfile()
+	{
+		padding = 10;
+		paddingHL = 10;
+		paddingSL = 10;
+		paddingNA = 4;
+
+		border = 1;
+		borderHL = 1;
+		borderSL = 1;
+		borderNA = 1;
+
+		borderColor = %this.adjustValue(%this.color3, -50);
+		borderColorHL = %this.adjustValue(%this.color3, -60);
+		borderColorSL = %this.adjustValue(%this.color3, -60);
+		borderColorNA = %this.adjustValue(%this.color3, -50);
+	};
+
+	%windowBorderR = new GuiBorderProfile()
+	{
+		border = 1;
+		borderHL = 1;
+		borderSL = 1;
+		borderNA = 1;
+
+		borderColor = %this.adjustValue(%this.color3, -50);
+		borderColorHL = %this.adjustValue(%this.color3, -60);
+		borderColorSL = %this.adjustValue(%this.color3, -60);
+		borderColorNA = %this.adjustValue(%this.color3, -50);
+	};
+
+	%windowBorderV = new GuiBorderProfile()
+	{
+		border = 1;
+		borderHL = 1;
+		borderSL = 1;
+		borderNA = 1;
+
+		borderColor = %this.adjustValue(%this.color3, -50);
+		borderColorHL = %this.adjustValue(%this.color3, -60);
+		borderColorSL = %this.adjustValue(%this.color3, -60);
+		borderColorNA = %this.adjustValue(%this.color3, -50);
+	};
+
+	%this.windowProfile = new GuiControlProfile()
+	{
+		fillColor = %this.color2;
+		fillColorHL = %this.adjustValue(%this.color2, 1);
+		fillColorSL = %this.adjustValue(%this.color2, 5);
+		fillColorNA = %this.color2;
+
+		fontType = %this.font[2];
+		fontDirectory = %this.fontDirectory;
+		fontSize = %this.fontSize - 1;
+		fontColor = %this.color5;
+		fontColorHL = %this.adjustValue(%this.color5, 2);
+		fontColorSL = %this.adjustValue(%this.color5, 4);
+		fontColorNA = %this.color5;
+		align = left;
+		vAlign = middle;
+
+		borderLeft = %windowBorderL;
+		borderRight = %windowBorderR;
+		borderTop = %windowBorderV;
+		borderBottom = %windowBorderV;
+	};
+
+	%windowContentBorder = new GuiBorderProfile()
+	{
+		border = 3;
+		borderHL = 3;
+		borderSL = 3;
+		borderNA = 3;
+
+		borderColor = %this.adjustValue(%this.color3, -50);
+		borderColorHL = %this.adjustValue(%this.color3, -60);
+		borderColorSL = %this.adjustValue(%this.color3, -60);
+		borderColorNA = %this.adjustValue(%this.color3, -50);
+
+		padding = 1;
+		paddingHL = 1;
+		paddingSL = 1;
+		paddingNA = 1;
+	};
+
+	%this.windowContentProfile = new GuiControlProfile()
+	{
+		fillColor = %this.color1;
+		fillColorSL = %this.adjustValue(%this.color1, 5);
+
+		borderLeft = %windowContentBorder;
+		borderRight = %windowContentBorder;
+		borderTop = %this.emptyBorder;
+		borderBottom = %windowContentBorder;
+	};
+
+	%windowButtonBorder = new GuiBorderProfile()
+	{
+		margin = 3;
+		marginHL = 3;
+		marginSL = 3;
+		marginNA = 3;
+
+		padding = 3;
+		paddingHL = 3;
+		paddingSL = 3;
+		paddingNA = 3;
+	};
+
+	%this.windowButtonProfile = new GuiControlProfile()
+	{
+		fillColor = %this.color3;
+		fillColorHL = %this.adjustValue(%this.color3, 10);
+		fillColorSL = %this.color5;
+		fillColorNA = %this.color3;
+
+		fontColor = %this.color1;
+		fontColorHL = %this.color1;
+		fontColorSL = %this.color1;
+		fontColorNA = %this.color1;
+
+		borderDefault = %windowButtonBorder;
+	};
+}
+
 //Positive values are brighter, negative are darker
 function BaseTheme::adjustValue(%this, %color, %percent)
 {

+ 12 - 3
editor/ProjectManager/ProjectManager.cs

@@ -24,22 +24,31 @@ function ProjectManager::create(%this)
 {
 	%this.guiPage = EditorCore.RegisterEditor("Project Manager", %this);
 
-	%this.comingSoon = new GuiControl()
+	%this.comingSoon = new GuiWindowCtrl()
 	{
+		class = "comingSoonTest";
 		HorizSizing="center";
 		VertSizing="center";
 		Position="412 324";
 		Extent="200 120";
-		minExtent="8 8";
 		Visible="1";
 		Text = "Coming Soon!";
+		canClose = true;
+		canMove = true;
+		Profile = "GuiWindowProfile";
+		CloseButtonProfile = "GuiWindowCloseButtonProfile";
 	};
-	ThemeManager.setProfile(%this.comingSoon, "simpleProfile");
+	//ThemeManager.setProfile(%this.comingSoon, "simpleProfile");
 	%this.guiPage.add(%this.comingSoon);
 
 	EditorCore.FinishRegistration(%this.guiPage);
 }
 
+function comingSoonTest::onClose(%this)
+{
+	echo("comingSoonTest::onClose fired successfully!");
+}
+
 function ProjectManager::destroy(%this)
 {
 

+ 2 - 4
editor/main.cs

@@ -22,12 +22,10 @@
 
 new ModuleManager(EditorManager);
 EditorManager.addListener(AssetDatabase);
+EditorManager.EchoInfo = false;
 
 // Scans for the modules that make up the editors.
-EditorManager.scanModules( "." );
-
-// Load the central module
-EditorManager.LoadGroup( "EditorGroup" );
+EditorManager.scanModules( "./" );
 
 // Load various editors
 EditorManager.LoadExplicit("EditorConsole");

+ 2 - 0
engine/compilers/VisualStudio 2017/main.cs

@@ -22,4 +22,6 @@
 
 // This file simply points to the real main.cs file in the root of the working directory.
 // This is needed if the project is run in debug mode from within VisualStudio.
+setMainDotCsDir(makeFullPath("../../../"));
+setCurrentDirectory(makeFullPath("./"));
 exec("../../../main.cs");

+ 2 - 0
engine/compilers/VisualStudio 2019/main.cs

@@ -22,4 +22,6 @@
 
 // This file simply points to the real main.cs file in the root of the working directory.
 // This is needed if the project is run in debug mode from within VisualStudio.
+setMainDotCsDir(makeFullPath("../../../"));
+setCurrentDirectory(makeFullPath("./"));
 exec("../../../main.cs");

+ 3 - 0
engine/source/gui/containers/guiGridCtrl.cc

@@ -171,6 +171,9 @@ void GuiGridCtrl::resize(const Point2I &newPosition, const Point2I &newExtent)
 
 Point2I GuiGridCtrl::getCellPosition(const U16 cellNumber, const Point2I &innerExtent, GuiControl *ctrl)
 {
+	if(mCalcChainLength == 0)
+		return Point2I(0, 0);
+
 	Point2I result(0,0);
 	U16 y = (U16)mFloor(cellNumber / mCalcChainLength);
 	U16 x = (U16)(cellNumber % mCalcChainLength);

+ 18 - 0
engine/source/gui/editor/guiInspector.cc

@@ -406,6 +406,24 @@ ConsoleMethod(GuiInspector, addHiddenField, void, 3, 3, "() Adds a new field to
 {
 	object->addHiddenField(argv[2]);
 }
+
+ConsoleMethod(GuiInspector, openGroupByIndex, void, 3, 3, "(Index) Opens the group that matches the given zero-based index\n"
+	"@return No return value.")
+{
+	if (argc < 1)
+	{
+		return;
+	}
+
+	S32 index = dAtoi(argv[2]);
+
+	if (index >= object->mGroups.size())
+	{
+		return;
+	}
+
+	object->mGroups[index]->setExpanded(true);
+}
 #pragma endregion
 
 #pragma region GuiInspectorField

+ 3 - 3
engine/source/gui/editor/guiInspectorTypes.cc

@@ -52,7 +52,7 @@ GuiControl* GuiInspectorTypeEnum::constructEditControl(S32 width)
    char szBuffer[512];
    dSprintf( szBuffer, 512, "%d.%s = %d.getText();",mTarget->getId(), mField->pFieldname, retCtrl->getId() );
    retCtrl->setField("Command", szBuffer );
-   retCtrl->mBounds.set(mGroup->mInspector->mControlOffset, Point2I(width - mGroup->mInspector->mControlOffset.x, 24));
+   retCtrl->mBounds.set(mGroup->mInspector->mControlOffset, Point2I(width - mGroup->mInspector->mControlOffset.x, 28));
 
    //now add the entries
    GuiListBoxCtrl* list = retCtrl->getList();
@@ -186,7 +186,7 @@ GuiControl* GuiInspectorTypeGuiProfile::constructEditControl(S32 width)
    char szBuffer[512];
    dSprintf( szBuffer, 512, "%d.apply(%d.getText());",getId(),retCtrl->getId() );
    retCtrl->setField("Command", szBuffer );
-   retCtrl->mBounds.set(mGroup->mInspector->mControlOffset, Point2I(width - mGroup->mInspector->mControlOffset.x, 24));
+   retCtrl->mBounds.set(mGroup->mInspector->mControlOffset, Point2I(width - mGroup->mInspector->mControlOffset.x, 28));
 
    Vector<StringTableEntry> entries;
 
@@ -247,7 +247,7 @@ GuiControl* GuiInspectorTypeGuiBorderProfile::constructEditControl(S32 width)
    char szBuffer[512];
    dSprintf(szBuffer, 512, "%d.apply(%d.getText());", getId(), retCtrl->getId());
    retCtrl->setField("Command", szBuffer);
-   retCtrl->mBounds.set(mGroup->mInspector->mControlOffset, Point2I(width - mGroup->mInspector->mControlOffset.x, 24));
+   retCtrl->mBounds.set(mGroup->mInspector->mControlOffset, Point2I(width - mGroup->mInspector->mControlOffset.x, 28));
 
    Vector<StringTableEntry> entries;
 

+ 13 - 1
engine/source/gui/guiCanvas.cc

@@ -130,6 +130,7 @@ void GuiCanvas::setCursor(GuiCursor *curs)
 
    if(mShowCursor)
    {
+		mUseNativeCursor = false;
        Input::setCursorState(false);
    }
 }
@@ -148,7 +149,18 @@ bool GuiCanvas::getUseNativeCursor(void)
 
 void GuiCanvas::useNativeCursor(bool useNative)
 {
-   mUseNativeCursor = useNative;
+	if(!mUseNativeCursor && useNative)
+	{
+		//We are turning on the native cursor
+		Input::setCursorState(true);
+	}
+	else if(mUseNativeCursor && !useNative)
+	{
+		//We are turning off the native cursor
+		Input::setCursorState(false);
+	}
+
+	mUseNativeCursor = useNative;
 }
 
 void GuiCanvas::setCursorPos(const Point2I &pt)   

+ 1 - 1
engine/source/gui/guiCanvas.h

@@ -294,7 +294,7 @@ public:
 
    /// Enable/disable rendering of the cursor.
    /// @param   state    True if we should render cursor
-   virtual void showCursor(bool state)            { mShowCursor = state; Input::setCursorState(state); }
+   virtual void showCursor(bool state)            { mShowCursor = state; }
 
    /// Returns true if the cursor is being rendered.
    virtual bool isCursorShown()                   { return(mShowCursor); }

+ 9 - 2
engine/source/io/fileSystem_ScriptBinding.cc

@@ -601,11 +601,18 @@ ConsoleFunctionWithDocs(getExecutableName, ConsoleString, 1, 1, ())
    return Platform::getExecutableName();
 }
 
-/*! 
+/*!
 */
 ConsoleFunctionWithDocs(getMainDotCsDir, ConsoleString, 1, 1, ())
 {
-   return Platform::getMainDotCsDir();
+	return Platform::getMainDotCsDir();
+}
+
+/*!
+*/
+ConsoleFunctionWithDocs(setMainDotCsDir, ConsoleVoid, 2, 2, (path))
+{
+	Platform::setMainDotCsDir(argv[1]);
 }
 
 /*! 

+ 1 - 1
engine/source/module/moduleDefinition.h

@@ -90,7 +90,7 @@ private:
     bool                            mDeprecated;
     bool                            mCriticalMerge;
     StringTableEntry                mModuleDescription;
-    StringTableEntry                mAuthor;;
+    StringTableEntry                mAuthor;
     StringTableEntry                mModuleGroup;
     StringTableEntry                mModuleType;
     typeModuleDependencyVector      mDependencies;

+ 1 - 1
engine/source/module/moduleManager.cc

@@ -1888,7 +1888,7 @@ bool ModuleManager::registerModule( const char* pModulePath, const char* pModule
 
     // Make the module path a full-path.
     char fullPathBuffer[1024];
-    Platform::makeFullPathName( pModulePath, fullPathBuffer, sizeof(fullPathBuffer) );
+    Platform::makeFullPathName( Platform::makeRelativePathName(pModulePath, Platform::getMainDotCsDir()), fullPathBuffer, sizeof(fullPathBuffer) );
     pModulePath = fullPathBuffer;
 
 

+ 37 - 0
engine/source/platform/nativeDialogs/fileDialog.cc

@@ -22,6 +22,7 @@
 #include "platform/nativeDialogs/fileDialog.h"
 #include "console/consoleTypes.h"
 #include "fileDialog_ScriptBinding.h"
+#include "gui/guiCanvas.h"
 
 IMPLEMENT_CONOBJECT(FileDialog);
 IMPLEMENT_CONOBJECT(OpenFileDialog);
@@ -122,6 +123,42 @@ bool FileDialog::setFile(void* obj, const char* data)
    return false;
 }
 
+void FileDialog::preExecute()
+{
+	mPrevNativeCursorState = true;
+
+	SimObject *obj = Sim::findObject("Canvas");
+	GuiCanvas* canvas = NULL;
+	if (obj)
+	{
+		canvas = dynamic_cast<GuiCanvas*>(obj);
+
+		if (canvas && !canvas->getUseNativeCursor())
+		{
+			mPrevNativeCursorState = false;
+			canvas->useNativeCursor(true);
+		}
+	}
+}
+
+void FileDialog::postExecute()
+{
+	if (!mPrevNativeCursorState)
+	{
+		SimObject *obj = Sim::findObject("Canvas");
+		GuiCanvas* canvas = NULL;
+		if (obj)
+		{
+			canvas = dynamic_cast<GuiCanvas*>(obj);
+
+			if (canvas)
+			{
+				canvas->useNativeCursor(mPrevNativeCursorState);
+			}
+		}
+	}
+}
+
 //-----------------------------------------------------------------------------
 // OpenFileDialog Constructor
 OpenFileDialog::OpenFileDialog()

+ 3 - 0
engine/source/platform/nativeDialogs/fileDialog.h

@@ -106,6 +106,7 @@ protected:
    FileDialogData mData; ///< Stores platform agnostic information about the dialogs properties
    bool mChangePath; ///< Exposed ChangePath Property
    bool mBoolTranslator; ///< Internally used to translate boolean values into their respective bits of dialog style
+   bool mPrevNativeCursorState; ///< Was the native cursor active before the dialog was opened?
 public:
 
    FileDialog();
@@ -114,7 +115,9 @@ public:
 
    static void initPersistFields();
 
+   virtual void preExecute();
    virtual bool Execute();
+   virtual void postExecute();
 
    FileDialogData &getData() { return mData; };
 protected:

+ 5 - 1
engine/source/platform/nativeDialogs/fileDialog_ScriptBinding.h

@@ -29,7 +29,11 @@ ConsoleMethodGroupBeginWithDocs(FileDialog, SimObject)
 */
 ConsoleMethodWithDocs( FileDialog, Execute, ConsoleBool, 2, 2, ())
 {
-   return object->Execute();
+	object->preExecute();
+	bool result = object->Execute();
+	object->postExecute();
+
+	return result;
 }
 
 ConsoleMethodGroupEndWithDocs(FileDialog)

+ 4 - 1
engine/source/platform/platformFileIO.cc

@@ -244,7 +244,7 @@ char * Platform::makeFullPathName(const char *path, char *buffer, U32 size, cons
    }
 
    if(cwd == NULL)
-      cwd = Platform::getCurrentDirectory();
+      cwd = Platform::getMainDotCsDir();
 
    dStrncpy(buffer, cwd, size);
    buffer[size-1] = 0;
@@ -266,6 +266,9 @@ char * Platform::makeFullPathName(const char *path, char *buffer, U32 size, cons
          {
             // Parent
             endptr = dStrrchr(buffer, '/');
+
+			if (endptr)
+				*endptr-- = 0;
          }
          else if(dStrcmp(ptr, ".") == 0)
          {

+ 1 - 1
engine/source/platform/platformInput.h

@@ -179,7 +179,7 @@ public:
 
     static void process();
     static void setCursorPos(S32 x, S32 y);
-    static void setCursorState(bool on); ///< If True, turn on the platform's cursor
+    static void setCursorState(bool on); ///< If True, turns off the platform cursor
 
     static void setCursorShape(U32 cursorID);
 

+ 8 - 1
engine/source/platformWin32/winInput.cc

@@ -509,7 +509,14 @@ void Input::setCursorPos(S32 x, S32 y)
 // Set the cursor to draw (true) or not (false)
 void Input::setCursorState(bool on)
 {
-   ShowCursor(on);
+	if (on)
+	{
+		while (ShowCursor(TRUE) < 0);
+	}
+	else
+	{
+		while (ShowCursor(FALSE) >= 0);
+	}
 }
 
 //------------------------------------------------------------------------------

+ 4 - 1
engine/source/platformWin32/winWindow.cc

@@ -954,7 +954,10 @@ case WM_NCMOUSEMOVE:
 
 case WM_MOUSEMOVE:
    // keep trying until we actually show it
-   while (ShowCursor(FALSE) >= 0);
+   if(!Canvas->getUseNativeCursor())
+   {
+		while (ShowCursor(FALSE) >= 0);
+   }
    Input::refreshCursor();
 
    if ( !windowLocked )