Selaa lähdekoodia

Navigating tree view with the arrow keys

Marko Pintera 12 vuotta sitten
vanhempi
sitoutus
051bbe0c52
3 muutettua tiedostoa jossa 133 lisäystä ja 27 poistoa
  1. 3 1
      CamelotClient/Include/BsGUISceneTreeView.h
  2. 114 15
      CamelotClient/Source/BsGUISceneTreeView.cpp
  3. 16 11
      TreeView.txt

+ 3 - 1
CamelotClient/Include/BsGUISceneTreeView.h

@@ -37,6 +37,7 @@ namespace BansheeEditor
 			{ }
 
 			bool isTreeElement() const { return index % 2 == 1; }
+			TreeElement* getTreeElement() const;
 
 			TreeElement* parent;
 			CM::UINT32 index;
@@ -124,7 +125,8 @@ namespace BansheeEditor
 			BS::GUIElementStyle* dragHighlightStyle, BS::GUIElementStyle* dragSepHighlightStyle, const BS::GUILayoutOptions& layoutOptions);
 
 		const GUISceneTreeView::InteractableElement* findElementUnderCoord(const CM::Vector2I& coord) const;
-		GUISceneTreeView::TreeElement* GUISceneTreeView::interactableToRealElement(const GUISceneTreeView::InteractableElement& element);
+		TreeElement* getTopMostSelectedElement() const;
+		TreeElement* getBottomMostSelectedElement() const;
 
 		void enableEdit(TreeElement* element);
 		void disableEdit(bool acceptChanges);

+ 114 - 15
CamelotClient/Source/BsGUISceneTreeView.cpp

@@ -353,7 +353,7 @@ namespace BansheeEditor
 
 			if(element != nullptr && element->isTreeElement())
 			{
-				treeElement = interactableToRealElement(*element);
+				treeElement = element->getTreeElement();
 			}
 
 			if(treeElement != nullptr && event.getPosition().x >= treeElement->mElement->getBounds().x)
@@ -379,7 +379,7 @@ namespace BansheeEditor
 							if(!iterStartFind->isTreeElement())
 								continue;
 
-							TreeElement* curElem = interactableToRealElement(*iterStartFind);
+							TreeElement* curElem = iterStartFind->getTreeElement();
 							if(curElem == selectionRoot)
 							{
 								foundStart = true;
@@ -400,7 +400,7 @@ namespace BansheeEditor
 								for(;iterStartFind != (iterEndFind + 1); ++iterStartFind)
 								{
 									if(iterStartFind->isTreeElement())
-										selectElement(interactableToRealElement(*iterStartFind));
+										selectElement(iterStartFind->getTreeElement());
 								}
 							}
 							else if(iterEndFind < iterStartFind)
@@ -408,7 +408,7 @@ namespace BansheeEditor
 								for(;iterEndFind != (iterStartFind + 1); ++iterEndFind)
 								{
 									if(iterEndFind->isTreeElement())
-										selectElement(interactableToRealElement(*iterEndFind));
+										selectElement(iterEndFind->getTreeElement());
 								}
 							}
 							else
@@ -452,14 +452,14 @@ namespace BansheeEditor
 					if(element != nullptr && element->isTreeElement())
 					{
 						// If element we are trying to drag isn't selected, select it
-						TreeElement* treeElement = interactableToRealElement(*element);
+						TreeElement* treeElement = element->getTreeElement();
 						auto iterFind = std::find_if(mSelectedElements.begin(), mSelectedElements.end(), 
 							[&] (const SelectedElement& x) { return x.element == treeElement; });
 
 						if(iterFind == mSelectedElements.end())
 						{
 							unselectAll();
-							selectElement(interactableToRealElement(*element));
+							selectElement(element->getTreeElement());
 						}						
 					}
 
@@ -503,7 +503,7 @@ namespace BansheeEditor
 				if(element != nullptr)
 				{
 					if(element->isTreeElement())
-						treeElement = interactableToRealElement(*element);
+						treeElement = element->getTreeElement();
 					else
 						treeElement = element->parent;
 				}
@@ -548,6 +548,51 @@ namespace BansheeEditor
 
 			return true;
 		}
+		
+		if(ev.getType() == GUICommandEventType::CursorMoveUp || ev.getType() == GUICommandEventType::SelectUp)
+		{
+			TreeElement* topMostElement = getTopMostSelectedElement();
+			auto topMostIter = std::find_if(mVisibleElements.begin(), mVisibleElements.end(), 
+				[&] (const InteractableElement& x) { return x.getTreeElement() == topMostElement; });
+
+			if(topMostIter != mVisibleElements.end() && topMostIter != mVisibleElements.begin())
+			{
+				do 
+				{
+					topMostIter--;
+				} while (!topMostIter->isTreeElement() && topMostIter != mVisibleElements.begin());
+				
+				if(topMostIter->isTreeElement())
+				{
+					if(ev.getType() == GUICommandEventType::CursorMoveUp)
+						unselectAll();
+
+					selectElement(topMostIter->getTreeElement());
+				}
+			}
+		}
+		else if(ev.getType() == GUICommandEventType::CursorMoveDown || ev.getType() == GUICommandEventType::SelectDown)
+		{
+			TreeElement* bottoMostElement = getBottomMostSelectedElement();
+			auto bottomMostIter = std::find_if(mVisibleElements.begin(), mVisibleElements.end(), 
+				[&] (const InteractableElement& x) { return x.getTreeElement() == bottoMostElement; });
+
+			if(bottomMostIter != mVisibleElements.end())
+			{
+				do 
+				{
+					bottomMostIter++;
+				} while (bottomMostIter != mVisibleElements.end() && !bottomMostIter->isTreeElement());
+
+				if(bottomMostIter != mVisibleElements.end() && bottomMostIter->isTreeElement())
+				{
+					if(ev.getType() == GUICommandEventType::CursorMoveDown)
+						unselectAll();
+
+					selectElement(bottomMostIter->getTreeElement());
+				}
+			}
+		}
 
 		return false;
 	}
@@ -961,20 +1006,58 @@ namespace BansheeEditor
 		return nullptr;
 	}
 
-	GUISceneTreeView::TreeElement* GUISceneTreeView::interactableToRealElement(const GUISceneTreeView::InteractableElement& element)
+	GUISceneTreeView::TreeElement* GUISceneTreeView::getTopMostSelectedElement() const
 	{
-		if(!element.isTreeElement())
+		auto topMostElement = mVisibleElements.end();
+
+		for(auto& selectedElement : mSelectedElements)
+		{
+			auto iterFind = std::find_if(mVisibleElements.begin(), mVisibleElements.end(), 
+				[&] (const InteractableElement& x) { return x.getTreeElement() == selectedElement.element; });
+
+			if(iterFind != mVisibleElements.end())
+			{
+				if(topMostElement == mVisibleElements.end())
+					topMostElement = iterFind;
+				else
+				{
+					if(iterFind->bounds.y < topMostElement->bounds.y)
+						topMostElement = iterFind;
+				}
+			}
+		}
+
+		if(topMostElement != mVisibleElements.end())
+			return topMostElement->getTreeElement();
+		else
 			return nullptr;
+	}
 
-		UINT32 sortedIdx = (element.index - 1) / 2;
+	GUISceneTreeView::TreeElement* GUISceneTreeView::getBottomMostSelectedElement() const
+	{
+		auto& botMostElement = mVisibleElements.end();
 
-		auto findIter = std::find_if(element.parent->mChildren.begin(), element.parent->mChildren.end(),
-			[&](const TreeElement* x) { return x->mSortedIdx == sortedIdx; });
+		for(auto& selectedElement : mSelectedElements)
+		{
+			auto iterFind = std::find_if(mVisibleElements.begin(), mVisibleElements.end(), 
+				[&] (const InteractableElement& x) { return x.getTreeElement() == selectedElement.element; });
 
-		if(findIter != element.parent->mChildren.end())
-			return *findIter;
+			if(iterFind != mVisibleElements.end())
+			{
+				if(botMostElement == mVisibleElements.end())
+					botMostElement = iterFind;
+				else
+				{
+					if((iterFind->bounds.y + iterFind->bounds.height) > (botMostElement->bounds.y + botMostElement->bounds.height))
+						botMostElement = iterFind;
+				}
+			}
+		}
 
-		return nullptr;
+		if(botMostElement != mVisibleElements.end())
+			return botMostElement->getTreeElement();
+		else
+			return nullptr;
 	}
 
 	const String& GUISceneTreeView::getGUITypeName()
@@ -982,4 +1065,20 @@ namespace BansheeEditor
 		static String typeName = "SceneTreeView";
 		return typeName;
 	}
+
+	GUISceneTreeView::TreeElement* GUISceneTreeView::InteractableElement::getTreeElement() const
+	{
+		if(!isTreeElement())
+			return nullptr;
+
+		UINT32 sortedIdx = (index - 1) / 2;
+
+		auto findIter = std::find_if(parent->mChildren.begin(), parent->mChildren.end(),
+			[&](const TreeElement* x) { return x->mSortedIdx == sortedIdx; });
+
+		if(findIter != parent->mChildren.end())
+			return *findIter;
+
+		return nullptr;
+	}
 }

+ 16 - 11
TreeView.txt

@@ -1,31 +1,36 @@
 TODO:
  - Callback on tree item select
- - Clicking on an already selectecd element starts rename 
-    - Will likely need some kind of check to ignore double-clicks?
+ - When dragging in tree view automatically expand mouse over elements
+ - Ability to move selection via arrow keys
  - Context menu with rename/copy/paste/duplicate
  - Delete with Undo/Redo support
  - Auto scroll
     - Sliding over top or bottom of the tree view while dragging an element will search GUIElement parents to find a ScrollArea. If it finds one it will attempt to scroll up or down.
  - Support for icons next to tree elements
- - Ability to move selection via arrow keys
+ - Clicking on an already selectecd element starts rename 
+   - Will likely need some kind of check to ignore double-clicks?
 
 MenuBar problems:
  - Clicking the top menu item closes and immediately opens the menu again (it should only close it)
    - Something similar probably also happens with ListBox
 
  Other:
- - When dragging in tree view automatically expand mouse over elements
  - "Ping" effect
  - Copy/Paste/Duplicate
    - This is more of a problem with actual copying of SceneObjects (It's not implemented). I should be able to serialize, and then de-serialize with a new parent as a form of copy.
 
-
 --------------------------------------------------
 LOW PRIORITY:
 
-Shoutcuts ideas:
-Remove Cut/Copy/Paste/Undo/Redo/Rename/SelectAll and similar shortcuts from InputCommands. Instead send generic shortcut commands whenever user presses some key (with shift/ctrl/alt state).
- - I will need some kind of generic shortcuts at some point so there is no point that Rename is hardcoded to F2
- - I should also add a GUIShortcutManager, as a centralized place for dealing with shortcuts (so that I my edit them at a later date)
-  - TODO - Figure out how would this work. How would the centralized system know all of the UI shortcuts? Probably register them somehow on program start?
- - This shortcut system would be separate from the input manager, and would not be meant for in-game use
+Shortcut plan:
+Replace all GUICommandEventType with two things:
+ - Events like CursorMoveUp should just be received a keys + modifiers (shift/ctrl/alt) and the individual element can then decide what to do with them and how to interpret them.
+ - Events like Rename, Undo, Redo, Copy, Paste, etc. should be globally set shortcut keys
+    - Shortcut keys are globally registered on program start in ShortcutManager
+    - Objects can then register callbacks for certain shortcut using mechanism like: addCallback("Undo", onUndo).
+     - They should also be able to unregister those callbacks.
+    - GUIElement can choose to listen to certain shortcut keys
+        - getUsedShortcut is a overridable method that returns a list of shortcuts and callbacks
+        - When element comes into focus or loses focus GUIManager will register/unregister those shortcuts with ShortcutManager
+          - I should probably prevent one shortcut from possibly triggering multiple callbacks
+    - Whenever some key combination or a single key is pressed all elements in focus get send the shortcut key (if that shortcut exists)