Ver Fonte

Reordering GuiControls

This update allows the reordering of controls using the control tree.
Peter Robinson há 2 anos atrás
pai
commit
70cb299753

+ 52 - 8
editor/GuiEditor/scripts/GuiEditorBrain.cs

@@ -49,12 +49,18 @@ function GuiEditorBrain::finishControlDropped(%this, %payload, %x, %y)
    %payload.setPositionGlobal(%x, %y);
 }
 
-function GuiEditorBrain::onInspect(%this, %ctrl)
+function GuiEditorBrain::startRadioSilence(%this)
 {
-    %this.clearSelection();
-	%this.select(%ctrl);
+    %this.removeAllListeners();
+}
+
+function GuiEditorBrain::endRadioSilence(%this)
+{
+    %this.addListener(GuiEditor.explorerWindow);
+    %this.addListener(GuiEditor.inspectorWindow);
 }
 
+//Source callbacks - Events that happened with this control and need to be relayed to other controls.
 function GuiEditorBrain::onSelect(%this, %ctrl)
 {
 	%this.clearSelection();
@@ -62,13 +68,19 @@ function GuiEditorBrain::onSelect(%this, %ctrl)
     %this.postEvent("Inspect", %ctrl);
 }
 
+function GuiEditorBrain::onRemoveSelected(%this,%ctrl)
+{
+    %this.postEvent("ClearInspect", %ctrl);
+}
+
 function GuiEditorBrain::onClearSelected(%this)
 {
     %this.postEvent("ClearInspectAll");
 }
 
-function GuiEditorBrain::onSelectionParentChange(%this)
+function GuiEditorBrain::onAddSelected(%this, %ctrl)
 {
+    %this.postEvent("AlsoInspect", %ctrl);
 }
 
 function GuiEditorBrain::onDelete(%this)
@@ -76,12 +88,44 @@ function GuiEditorBrain::onDelete(%this)
 	%this.postEvent("ObjectRemoved", %ctrl);
 }
 
-function GuiEditorBrain::onAddSelected(%this,%ctrl)
+function GuiEditorBrain::onSelectionParentChange(%this, %parent)
 {
-    %this.postEvent("AlsoInspect", %ctrl);
+    %this.postEvent("ParentChange", %parent);
 }
 
-function GuiEditorBrain::onRemoveSelected(%this,%ctrl)
+//Receiving Callbacks - Events that happened at other controls and need to be reflected with this control.
+function GuiEditorBrain::onInspect(%this, %ctrl)
 {
-    %this.postEvent("ClearInspect", %ctrl);
+    %this.startRadioSilence();
+    %this.clearSelection();
+	%this.select(%ctrl);
+    %this.endRadioSilence();
+}
+
+function GuiEditorBrain::onAlsoInspect(%this, %ctrl)
+{
+    %this.startRadioSilence();
+	%this.addSelection(%ctrl);
+    %this.endRadioSilence();
+}
+
+function GuiEditorBrain::onClearInspect(%this, %ctrl)
+{
+    %this.startRadioSilence();
+	%this.removeSelection(%ctrl);
+    %this.endRadioSilence();
+}
+
+function GuiEditorBrain::onClearInspectAll(%this)
+{
+    %this.startRadioSilence();
+	%this.clearSelection();
+    %this.endRadioSilence();
+}
+
+function GuiEditorBrain::onObjectRemoved(%this, %ctrl)
+{
+    %this.startRadioSilence();
+	%this.deleteSelection();
+    %this.endRadioSilence();
 }

+ 34 - 2
editor/GuiEditor/scripts/GuiEditorExplorerTree.cs

@@ -1,11 +1,43 @@
 
 function GuiEditorExplorerTree::onAdd(%this)
+{
+    %this.endRadioSilence();
+}
+
+function GuiEditorExplorerTree::startRadioSilence(%this)
+{
+    %this.removeAllListeners();
+}
+
+function GuiEditorExplorerTree::endRadioSilence(%this)
 {
     %this.addListener(GuiEditor.brain);
     %this.addListener(GuiEditor.inspectorWindow);
 }
 
-function GuiEditorExplorerTree::onClick(%this, %target)
+function GuiEditorExplorerTree::onSelect(%this, %index, %text, %item)
+{
+    if(%this.getSelCount() == 1)
+    {
+        %this.postEvent("Inspect", %item);
+    }
+    else 
+    {
+        %this.postEvent("AlsoInspect", %item);
+    }
+}
+
+function GuiEditorExplorerTree::onUnselect(%this, %index, %text, %item)
+{
+    %this.postEvent("ClearInspect", %item);
+}
+
+function GuiEditorExplorerTree::onUnselectAll(%this)
+{
+    %this.postEvent("ClearInspectAll");
+}
+
+function GuiEditorExplorerTree::onDeleteKey(%this, %index, %text, %item)
 {
-    %this.postEvent("Inspect", %target);
+	%this.postEvent("ObjectRemoved", %item);
 }

+ 23 - 1
editor/GuiEditor/scripts/GuiEditorExplorerWindow.cs

@@ -38,33 +38,55 @@ function GuiEditorExplorerWindow::open(%this, %object)
 
 function GuiEditorExplorerWindow::onInspect(%this, %ctrl)
 {
+	%this.tree.startRadioSilence();
 	%index = %this.tree.findItemID(%ctrl.getID());
 	%this.tree.clearSelection();
 	%this.tree.setSelected(%index, true);
+	%this.tree.endRadioSilence();
 }
 
 function GuiEditorExplorerWindow::onAlsoInspect(%this, %ctrl)
 {
-	%this.tree.setSelected(%ctrl, true);
+	%this.tree.startRadioSilence();
+	%index = %this.tree.findItemID(%ctrl.getID());
+	%this.tree.setSelected(%index, true);
+	%this.tree.endRadioSilence();
 }
 
 function GuiEditorExplorerWindow::onClearInspect(%this, %ctrl)
 {
+	%this.tree.startRadioSilence();
 	%this.tree.setSelected(%ctrl, false);
+	%this.tree.endRadioSilence();
 }
 
 function GuiEditorExplorerWindow::onClearInspectAll(%this)
 {
+	%this.tree.startRadioSilence();
 	%this.tree.clearSelection();
+	%this.tree.endRadioSilence();
 }
 
 function GuiEditorExplorerWindow::onObjectRemoved(%this, %ctrl)
 {
+	%this.tree.startRadioSilence();
 	%index = %this.tree.findItemID(%ctrl.getID());
 	%this.tree.deleteItem(%index);
+	%this.tree.endRadioSilence();
 }
 
 function GuiEditorExplorerWindow::onAddControl(%this, %ctrl)
 {
 	%this.tree.refresh();
+}
+
+function GuiEditorExplorerWindow::onParentChange(%this, %parent)
+{
+	%index = %this.tree.findItemID(%parent);
+	while(%index != -1)
+	{
+		%this.tree.setItemOpen(%index, true);
+		%index = %this.tree.getItemParent(%index);
+	} 
+	%this.tree.refresh();
 }

+ 7 - 1
engine/source/collection/vector.h

@@ -573,13 +573,19 @@ template<class T> inline void Vector<T>::pop_back()
 
 template<class T> inline T& Vector<T>::operator[](U32 index)
 {
-   AssertFatal(index < mElementCount, "Vector<T>::operator[] - out of bounds array access!");
+	if(index >= mElementCount)
+	{
+	AssertFatal(index < mElementCount, "Vector<T>::operator[] - out of bounds array access!");
+   }
    return mArray[index];
 }
 
 template<class T> inline const T& Vector<T>::operator[](U32 index) const
 {
+	if(index >= mElementCount)
+	{
    AssertFatal(index < mElementCount, "Vector<T>::operator[] - out of bounds array access!");
+   }
    return mArray[index];
 }
 

+ 1 - 1
engine/source/gui/editor/guiEditCtrl.cc

@@ -1105,7 +1105,7 @@ void GuiEditCtrl::moveSelectionToCtrl(GuiControl *newParent)
       ctrl->mBounds.set(newpos, ctrl->mBounds.extent);
    }
 
-   Con::executef(this, 1, "onSelectionParentChange");
+   Con::executef(this, 2, "onSelectionParentChange", Con::getIntArg(newParent->getId()));
 }
 
 static Point2I snapPoint(Point2I point, Point2I delta, Point2I gridSnap)

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

@@ -194,7 +194,7 @@ GuiControl* GuiInspectorTypeGuiProfile::constructEditControl(S32 width)
    for(SimGroup::iterator i = grp->begin(); i != grp->end(); i++)
    {
       GuiControlProfile * profile = dynamic_cast<GuiControlProfile *>(*i);
-      if(profile)
+      if(profile && profile->getName())
       {
          entries.push_back(profile->getName());
       }
@@ -202,8 +202,12 @@ GuiControl* GuiInspectorTypeGuiProfile::constructEditControl(S32 width)
 
    retCtrl->getList()->sortByText();
    for(U32 j = 0; j < (U32)entries.size(); j++)
-	   retCtrl->getList()->addItem(entries[j]);
-
+   {
+		if(entries[j] != NULL)
+		{
+			retCtrl->getList()->addItem(entries[j]);
+		}
+   }
    retCtrl->setField("text", getData());
 
    return retCtrl;

+ 24 - 13
engine/source/gui/guiListBoxCtrl.cc

@@ -94,6 +94,11 @@ void GuiListBoxCtrl::clearSelection()
 	  (*i)->isSelected = false;
 
    mSelectedItems.clear();
+
+   if (caller->isMethod("onUnselectAll"))
+   {
+	   Con::executef(caller, 1, "onUnselectAll");
+   }
 }
 
 void GuiListBoxCtrl::removeSelection( S32 index )
@@ -121,7 +126,10 @@ void GuiListBoxCtrl::removeSelection( LBItem *item, S32 index )
 	  {
 		 mSelectedItems.erase( &mSelectedItems[i] );
 		 item->isSelected = false;
-		 Con::executef(caller, 3, "onUnSelect", Con::getIntArg( index ), item->itemText, item->ID);
+		 if (caller->isMethod("onUnselect"))
+		 {
+			Con::executef(caller, 4, "onUnselect", Con::getIntArg( index ), item->itemText, Con::getIntArg(item->ID));
+		 }
 		 return;
 	  }
    }
@@ -169,7 +177,7 @@ void GuiListBoxCtrl::addSelection( LBItem *item, S32 index )
    ScrollToIndex(index);
 
    if(caller->isMethod("onSelect"))
-		Con::executef(caller, 3, "onSelect", Con::getIntArg( index ), item->itemText, item->ID);
+		Con::executef(caller, 4, "onSelect", Con::getIntArg( index ), item->itemText, Con::getIntArg(item->ID));
 
 }
 
@@ -251,13 +259,16 @@ S32 GuiListBoxCtrl::findItemText( StringTableEntry text, bool caseSensitive )
 
 void GuiListBoxCtrl::setSelectionInternal(StringTableEntry text)
 {
-	S32 index = findItemText(text);
-	if (index != -1)
+	if(text != StringTable->EmptyString)
 	{
-		mSelectedItems.clear();
-		LBItem *item = mItems[index];
-		item->isSelected = true;
-		mSelectedItems.push_front(item);
+		S32 index = findItemText(text);
+		if (index != -1)
+		{
+			mSelectedItems.clear();
+			LBItem *item = mItems[index];
+			item->isSelected = true;
+			mSelectedItems.push_front(item);
+		}
 	}
 }
 
@@ -471,7 +482,7 @@ S32 GuiListBoxCtrl::insertItem( S32 index, StringTableEntry text, void *itemData
    // Sanity checking
    if( !text )
    {
-	  Con::warnf("GuiListBoxCtrl::insertItem - cannot add NULL string" );
+ 	  Con::warnf("GuiListBoxCtrl::insertItem - cannot add NULL string" );
 	  return -1;
    }
 
@@ -737,7 +748,7 @@ void GuiListBoxCtrl::onTouchDragged(const GuiEvent &event)
 
 	if(caller->isMethod("onTouchDragged"))
 	{
-		Con::executef(caller, 3, "onTouchDragged", Con::getIntArg(hitIndex), hitItem->itemText, hitItem->ID);
+		Con::executef(caller, 4, "onTouchDragged", Con::getIntArg(hitIndex), hitItem->itemText, Con::getIntArg(hitItem->ID));
 	}
 	else
 	{
@@ -845,11 +856,11 @@ void GuiListBoxCtrl::handleItemClick_ClickCallbacks(LBItem* hitItem, S32 hitInde
 	if (hitItem == mLastClickItem && event.mouseClickCount == 2)
 	{
 		if (caller->isMethod("onDoubleClick"))
-			Con::executef(caller, 4, "onDoubleClick", Con::getIntArg(hitIndex), hitItem->itemText, hitItem->ID);
+			Con::executef(caller, 4, "onDoubleClick", Con::getIntArg(hitIndex), hitItem->itemText, Con::getIntArg(hitItem->ID));
 	}
 	else if (caller->isMethod("onClick"))
 	{
-		Con::executef(caller, 4, "onClick", Con::getIntArg(hitIndex), hitItem->itemText, hitItem->ID);
+		Con::executef(caller, 4, "onClick", Con::getIntArg(hitIndex), hitItem->itemText, Con::getIntArg(hitItem->ID));
 	}
 }
 
@@ -898,7 +909,7 @@ bool GuiListBoxCtrl::onKeyDown(const GuiEvent &event)
 		break;
 	case KEY_DELETE:
 		if (index != -1 && isMethod("onDeleteKey"))
-			Con::executef(caller, 3, "onDeleteKey", Con::getIntArg(index), getItemText(index), getItemID(index));
+			Con::executef(caller, 4, "onDeleteKey", Con::getIntArg(index), getItemText(index), Con::getIntArg(getItemID(index)));
 		break;
 	default:
 		return(Parent::onKeyDown(event));

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

@@ -62,7 +62,7 @@ public:
       StringTableEntry  itemText;
       bool              isSelected;
 	  bool				isActive;
-	  int				ID;
+	  S32				ID;
       void*             itemData;
       ColorF            color;
       bool              hasColor;

+ 2 - 2
engine/source/gui/guiListBoxCtrl_ScriptBinding.h

@@ -91,7 +91,7 @@ ConsoleMethodWithDocs(GuiListBoxCtrl, setItemID, ConsoleVoid, 4, 4, "(int index,
 
 /*! Returns an item's ID using an index.
 	@param index The zero-based index of the item with the needed ID.
-	@return No return value.
+	@return The ID of the item.
 */
 ConsoleMethodWithDocs(GuiListBoxCtrl, getItemID, ConsoleInt, 3, 3, "(int index)")
 {
@@ -155,7 +155,7 @@ ConsoleMethodWithDocs(GuiListBoxCtrl, setItemInactive, ConsoleVoid, 3, 3, "(int
 
 /*! Returns if an item is active or not using an index.
 	@param index The zero-based index of the item.
-	@return No return value.
+	@return True if the item is active and false otherwise.
 */
 ConsoleMethodWithDocs(GuiListBoxCtrl, getItemActive, ConsoleBool, 3, 3, "(int index)")
 {

+ 240 - 2
engine/source/gui/guiTreeViewCtrl.cc

@@ -34,6 +34,11 @@ GuiTreeViewCtrl::GuiTreeViewCtrl()
 	mActive = true;
 	mIsContainer = false;
 	mIndentSize = 10;
+	mMultipleSelections = true;
+	mTouchPoint = Point2I::Zero;
+	mDragActive = false;
+	mDragIndex = 0;
+	mIsDragLegal = false;
 }
 
 GuiTreeViewCtrl::~GuiTreeViewCtrl()
@@ -58,6 +63,84 @@ void GuiTreeViewCtrl::initPersistFields()
 	Parent::initPersistFields();
 }
 
+void GuiTreeViewCtrl::onTouchDown(const GuiEvent& event)
+{
+	mTouchPoint == event.mousePoint;
+	Parent::onTouchDown(event);
+}
+
+void GuiTreeViewCtrl::onTouchDragged(const GuiEvent& event)
+{
+	mDragActive = false;
+	if (!mActive || !mVisible)
+		return;
+
+	S32 hitIndex = getHitIndex(event);
+	if (hitIndex >= mItems.size() || hitIndex == -1)
+		return;
+
+	LBItem* hitItem = mItems[hitIndex];
+	if (hitItem == NULL || !hitItem->isActive)
+		return;
+
+	if (mAbs(mTouchPoint.x - event.mousePoint.x) > 2 || mAbs(mTouchPoint.y - event.mousePoint.y) > 2)
+	{
+		mDragActive = true;
+	}
+}
+void GuiTreeViewCtrl::onTouchUp(const GuiEvent& event)
+{
+	if (mDragActive && mIsDragLegal)
+	{
+		TreeItem* dragItem = grabItemPtr(mDragIndex);
+		dragItem->isOpen = true;
+		SimGroup* target = static_cast<SimGroup*>(mReorderMethod == ReorderMethod::Insert ? dragItem->itemData : dragItem->trunk->itemData);
+		
+		if (!target)
+		{
+			Con::warnf("GuiTreeViewCtrl::onTouchUp - attempted to drag selection into an object that is not a SimGroup");
+			return;
+		}
+		vector<SimObject*> objectAboveTargetList = vector<SimObject*>();
+		if (mReorderMethod != ReorderMethod::Insert)
+		{
+			S32 index = mReorderMethod == ReorderMethod::Below ? mDragIndex : mDragIndex - 1;
+			TreeItem* checkItem = grabItemPtr(index);
+			while (checkItem->level == dragItem->level)
+			{
+				if(!checkItem->isSelected)
+				{
+					SimObject* obj = static_cast<SimObject*>(checkItem->itemData);
+					objectAboveTargetList.push_back(obj);
+				}
+				index = index - 1;
+				checkItem = grabItemPtr(index);
+			}
+		}
+		for (S32 i = mItems.size() - 1; i >= 0; i--)
+		{
+			TreeItem* treeItem = dynamic_cast<TreeItem*>(mItems[i]);
+			if(treeItem && treeItem->isSelected)
+			{
+				SimObject* obj = static_cast<SimObject*>(treeItem->itemData);
+				if(obj)
+				{
+					target->addObject(obj);
+					target->bringObjectToFront(obj);
+				}
+			}
+		}
+		for (auto obj : objectAboveTargetList)
+		{
+			SimGroup* group = obj->getGroup();
+			group->bringObjectToFront(obj);
+		}
+		refreshTree();
+	}
+	mDragActive = false;
+	Parent::onTouchUp(event);
+}
+
 void GuiTreeViewCtrl::onPreRender()
 {
 	
@@ -72,6 +155,7 @@ void GuiTreeViewCtrl::onRender(Point2I offset, const RectI& updateRect)
 		mItemSize.x = clip.extent.x;
 	}
 
+	RectI dragRect;
 	for (S32 i = 0, j = 0; i < mItems.size(); i++)
 	{
 		// Only render visible items
@@ -90,9 +174,17 @@ void GuiTreeViewCtrl::onRender(Point2I offset, const RectI& updateRect)
 		{
 			// Render our item
 			onRenderItem(itemRect, mItems[i]);
+			if (mDragActive && j == mDragIndex)
+			{
+				dragRect = RectI(itemRect);
+			}
 			j++;
 		}
 	}
+	if(mDragActive && mIsDragLegal)
+	{
+		onRenderDragLine(dragRect);
+	}
 }
 
 void GuiTreeViewCtrl::onRenderItem(RectI& itemRect, LBItem* item)
@@ -139,7 +231,7 @@ void GuiTreeViewCtrl::onRenderItem(RectI& itemRect, LBItem* item)
 	// Render open/close triangle
 	if(obj)
 	{
-		SimSet* setObj = dynamic_cast<SimSet*>(obj);
+		SimGroup* setObj = dynamic_cast<SimGroup*>(obj);
 		GuiControl* guiCtrl = dynamic_cast<GuiControl*>(obj);
 		bool showTriangle = (guiCtrl && guiCtrl->mIsContainer) || (!guiCtrl && setObj && setObj->size() > 0);
 		if (showTriangle)
@@ -156,6 +248,35 @@ void GuiTreeViewCtrl::onRenderItem(RectI& itemRect, LBItem* item)
 	renderText(contentRect.point, contentRect.extent, item->itemText, mProfile);
 }
 
+void GuiTreeViewCtrl::onRenderDragLine(RectI& itemRect)
+{
+	ColorI colorW = ColorI(255,255,255,150);
+	ColorI colorB = ColorI(0, 0, 0, 150);
+	RectI rect = RectI(itemRect);
+	rect.inset(4, 2);
+	if (mReorderMethod == ReorderMethod::Insert)
+	{
+		dglDrawRect(itemRect, colorW);
+		itemRect.inset(-1, -1);
+		dglDrawRect(itemRect, colorB);
+	}
+	else if (mReorderMethod == ReorderMethod::Above)
+	{
+		itemRect.extent.y = 4;
+		dglDrawRect(itemRect, colorW);
+		itemRect.inset(-1, -1);
+		dglDrawRect(itemRect, colorB);
+	}
+	else if(mReorderMethod == ReorderMethod::Below)
+	{
+		itemRect.point.y += mItemSize.y;
+		itemRect.extent.y = 4;
+		dglDrawRect(itemRect, colorW);
+		itemRect.inset(-1, -1);
+		dglDrawRect(itemRect, colorB);
+	}
+}
+
 S32 GuiTreeViewCtrl::getHitIndex(const GuiEvent& event)
 {
 	Point2I localPoint = globalToLocalCoord(event.mousePoint);
@@ -171,11 +292,44 @@ S32 GuiTreeViewCtrl::getHitIndex(const GuiEvent& event)
 		{
 			if (j == slot)
 			{
+				S32 roundSlot = (S32)mRound((F32)localPoint.y / (F32)mItemSize.y);
+				mReorderMethod = roundSlot == slot ? ReorderMethod::Above : ReorderMethod::Below;
+
+				SimObject* obj = static_cast<SimObject*>(treeItem->itemData);
+				bool showTriangle = false;
+				if(obj)
+				{
+					SimGroup* setObj = dynamic_cast<SimGroup*>(obj);
+					GuiControl* guiCtrl = dynamic_cast<GuiControl*>(obj);
+					showTriangle = (guiCtrl && guiCtrl->mIsContainer) || (!guiCtrl && setObj && setObj->size() > 0);
+					
+					if (((slot * mItemSize.y) + 5) < localPoint.y && mReorderMethod == ReorderMethod::Above && showTriangle)
+					{
+						mReorderMethod = ReorderMethod::Insert;
+					}
+				}
+				if (j == 0)
+				{
+					mReorderMethod = ReorderMethod::Insert; 
+				}
+
+				mIsDragLegal = true;
+				for(TreeItem* trunk = treeItem; trunk != nullptr; trunk = trunk->trunk)
+				{
+					if (trunk->isSelected)
+					{
+						mIsDragLegal = false;
+						break;
+					}
+				}
+
+				mDragIndex = j;
 				return i;
 			}
 			j++;
 		}
 	}
+	return -1;
 }
 
 void GuiTreeViewCtrl::handleItemClick(LBItem* hitItem, S32 hitIndex, const GuiEvent& event)
@@ -229,7 +383,7 @@ void GuiTreeViewCtrl::inspectObject(SimObject* obj)
 
 void GuiTreeViewCtrl::addBranches(TreeItem* treeItem, SimObject* obj, U16 level)
 {
-	SimSet* setObj = dynamic_cast<SimSet*>(obj);
+	SimGroup* setObj = dynamic_cast<SimGroup*>(obj);
 	if(setObj)
 	{
 		for(auto sub : *setObj)
@@ -260,7 +414,54 @@ void GuiTreeViewCtrl::refreshTree()
 {
 	if (mRootObject)
 	{
+		Vector<U32> selectedList;
+		Vector<U32> openList;
+		for (auto item : mItems)
+		{
+			TreeItem* treeItem = dynamic_cast<TreeItem*>(item);
+			if (item->isSelected)
+			{
+				selectedList.push_back(treeItem->ID);
+			}
+			if (treeItem && treeItem->isOpen)
+			{
+				openList.push_back(treeItem->ID);
+			}
+		}
+
+		Point2I pos = Point2I::Zero;
+		auto scroller = dynamic_cast<GuiScrollCtrl*>(getParent());
+		if (scroller)
+		{
+			pos = scroller->mScrollOffset;
+		}
+		
 		inspectObject(mRootObject);
+
+
+		for (auto item : mItems)
+		{
+			TreeItem* treeItem = dynamic_cast<TreeItem*>(item);
+			if (selectedList.contains(treeItem->ID))
+			{
+				item->isSelected = true;
+				mSelectedItems.push_front(item);
+			}
+			if (treeItem && openList.contains(treeItem->ID))
+			{
+				treeItem->isOpen = true;
+			}
+			else if(treeItem)
+			{
+				treeItem->isOpen = false;
+				setBranchesVisible(treeItem, treeItem->isOpen);
+			}
+		}
+
+		if (scroller)
+		{
+			scroller->scrollTo(pos.x, pos.y);
+		}
 	}
 }
 
@@ -311,4 +512,41 @@ void GuiTreeViewCtrl::setBranchesVisible(TreeItem* treeItem, bool isVisible)
 		}
 		branch->isVisible = isVisible;
 	}
+}
+
+void GuiTreeViewCtrl::setItemOpen(S32 index, bool isOpen)
+{
+	if ((index >= mItems.size()) || index < 0)
+	{
+		Con::warnf("GuiTreeViewCtrl::setItemOpen - invalid index");
+		return;
+	}
+
+	TreeItem* item = dynamic_cast<TreeItem*>(mItems[index]);
+	item->isOpen = isOpen;
+}
+
+bool GuiTreeViewCtrl::getItemOpen(S32 index)
+{
+	if ((index >= mItems.size()) || index < 0)
+	{
+		Con::warnf("GuiTreeViewCtrl::getItemOpen - invalid index");
+		return true;
+	}
+
+	TreeItem* item = dynamic_cast<TreeItem*>(mItems[index]);
+	return item->isOpen;
+}
+
+S32 GuiTreeViewCtrl::getItemTrunk(S32 index)
+{
+	if ((index >= mItems.size()) || index < 0)
+	{
+		Con::warnf(" GuiTreeViewCtrl::getItemTrunk - invalid index");
+		return -1;
+	}
+
+	TreeItem* item = dynamic_cast<TreeItem*>(mItems[index]);
+	
+	return item->level == 0 ? -1 : getItemIndex(item->trunk);
 }

+ 16 - 4
engine/source/gui/guiTreeViewCtrl.h

@@ -32,10 +32,18 @@ class GuiTreeViewCtrl : public GuiListBoxCtrl
 {
 private:
 	typedef GuiListBoxCtrl Parent;
+	class TreeItem;
+
+	enum class ReorderMethod { Above, Below, Insert };
 
 protected:
 	SimObjectPtr<SimObject> mRootObject;
 	S32 mIndentSize;
+	Point2I mTouchPoint;
+	bool mDragActive;
+	S32 mDragIndex;
+	bool mIsDragLegal;
+	ReorderMethod mReorderMethod;
 
 public:
 	GuiTreeViewCtrl();
@@ -45,7 +53,7 @@ public:
 	class TreeItem : public GuiListBoxCtrl::LBItem
 	{
 	public:
-		TreeItem() : isOpen(1), level(0), triangleArea(RectI()), isVisible(1) { }
+		TreeItem() : isOpen(1), level(0), triangleArea(RectI()), isVisible(1), branchList(vector<TreeItem*>()), trunk(nullptr) { }
 		virtual ~TreeItem() { }
 
 		bool				isOpen;
@@ -65,14 +73,14 @@ public:
 	//void onSleep();
 	//void onPreRender();
 	//bool onKeyDown(const GuiEvent& event);
-	//void onTouchDown(const GuiEvent& event);
+	void onTouchDown(const GuiEvent& event);
 	//void onMiddleMouseDown(const GuiEvent& event);
 	//void onTouchMove(const GuiEvent& event);
 	//void onTouchEnter(const GuiEvent& event);
 	//void onTouchLeave(const GuiEvent& event);
 	//void onRightMouseDown(const GuiEvent& event);
-	//void onTouchDragged(const GuiEvent& event);
-	//void onTouchUp(const GuiEvent& event);
+	void onTouchDragged(const GuiEvent& event);
+	void onTouchUp(const GuiEvent& event);
 
 	//bool onAdd();
 	void onPreRender();
@@ -80,6 +88,7 @@ public:
 	//void setControlProfile(GuiControlProfile* prof);
 	//void resize(const Point2I& newPosition, const Point2I& newExtent);
 	virtual void onRenderItem(RectI& itemRect, LBItem* item);
+	virtual void onRenderDragLine(RectI& itemRect);
 
 	S32 getHitIndex(const GuiEvent& event);
 	virtual void handleItemClick(LBItem* hitItem, S32 hitIndex, const GuiEvent& event);
@@ -92,6 +101,9 @@ public:
 	void calculateHeaderExtent();
 	virtual GuiListBoxCtrl::LBItem* createItem();
 	void setBranchesVisible(TreeItem* treeItem, bool isVisible);
+	void setItemOpen(S32 index, bool isOpen);
+	bool getItemOpen(S32 index);
+	S32 getItemTrunk(S32 index);
 
 	DECLARE_CONOBJECT(GuiTreeViewCtrl);
 };

+ 50 - 5
engine/source/gui/guiTreeViewCtrl_ScriptBinding.h

@@ -23,21 +23,21 @@
 ConsoleMethodGroupBeginWithDocs(GuiTreeViewCtrl, GuiListBoxCtrl)
 
 /*! Sets the root object to view.
-	@param obj A SimSet that will be viewed as a tree.
+	@param obj A SimGroup that will be viewed as a tree.
 	@return No return value.
 */
-ConsoleMethodWithDocs( GuiTreeViewCtrl, open, ConsoleVoid, 3, 3, (SimSet obj))
+ConsoleMethodWithDocs( GuiTreeViewCtrl, open, ConsoleVoid, 3, 3, (SimGroup obj))
 {
-   SimSet *treeRoot = NULL;
+   SimGroup* treeRoot = NULL;
    SimObject* target = Sim::findObject(argv[2]);
 
    if (target)
-      treeRoot = dynamic_cast<SimSet*>(target);
+      treeRoot = dynamic_cast<SimGroup*>(target);
 
    object->inspectObject(treeRoot);
 }
 
-/*! Informs the tree that the contents of the tree have changed and should be refreshed.
+/*! Informs the tree that the contents of the tree have changed and should be refreshed but preserves selection if possible.
 	@return No return value.
 */
 ConsoleMethodWithDocs(GuiTreeViewCtrl, refresh, ConsoleVoid, 2, 2, ())
@@ -45,4 +45,49 @@ ConsoleMethodWithDocs(GuiTreeViewCtrl, refresh, ConsoleVoid, 2, 2, ())
 	object->refreshTree();
 }
 
+/*! Sets if an item is open by the index.
+	@param index The zero-based index of the item.
+	@param isOpen True if the item should be open and false otherwise.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(GuiTreeViewCtrl, setItemOpen, ConsoleVoid, 4, 4, "(int index, bool isOpen)")
+{
+	if (argc != 4)
+	{
+		Con::warnf("GuiTreeViewCtrl::setItemOpen() - Invalid number of parameters! Should be (index, isOpen).");
+	}
+	else
+	{
+		object->setItemOpen(dAtoi(argv[2]), dAtob(argv[3]));
+	}
+}
+
+/*! Returns if an item is open or not using an index.
+	@param index The zero-based index of the item.
+	@return True if the item is open and false otherwise.
+*/
+ConsoleMethodWithDocs(GuiTreeViewCtrl, getItemOpen, ConsoleBool, 3, 3, "(int index)")
+{
+	if (argc != 3)
+	{
+		Con::warnf("GuiTreeViewCtrl::getItemOpen() - Invalid number of parameters! Should be (index).");
+		return true;
+	}
+	return object->getItemOpen(dAtoi(argv[2]));
+}
+
+/*! Returns the index of the parent of the item.
+	@param index The zero-based index of the child item.
+	@return The index of the parent or -1 if the parent is the root.
+*/
+ConsoleMethodWithDocs(GuiTreeViewCtrl, getItemParent, ConsoleInt, 3, 3, "(int index)")
+{
+	if (argc != 3)
+	{
+		Con::warnf("GuiTreeViewCtrl::getItemOpen() - Invalid number of parameters! Should be (index).");
+		return -1;
+	}
+	return object->getItemTrunk(dAtoi(argv[2]));
+}
+
 ConsoleMethodGroupEndWithDocs(GuiTreeViewCtrl)