#pragma once #include "BsEditorPrerequisites.h" #include "BsGUIElementContainer.h" #include "BsVirtualInput.h" #include "BsEvent.h" namespace BansheeEngine { /** * @brief GUI element that displays some contents in a tree-view where elements * are placed vertically above/beneath each other, and different elements * may be nested within other elements. * * Elements may be selected, renamed, dragged and re-parented. * * This class is abstract and meant to be extended by an implementation * specific to some content type (e.g. scene object hierarchy). */ class BS_ED_EXPORT GUITreeView : public GUIElementContainer { protected: /** * @brief Types of possible states when the tree view is auto-scrolling. */ enum class ScrollState { None, Up, Down, TransitioningUp, TransitioningDown }; /** * @brief Contains data about a single piece of content * and all its children. This element may be visible and represented * by a GUI element, but might not (e.g. its parent is collapsed). */ struct TreeElement { TreeElement(); virtual ~TreeElement(); TreeElement* mParent; Vector mChildren; GUIToggle* mFoldoutBtn; GUILabel* mElement; String mName; UINT32 mSortedIdx; bool mIsExpanded; bool mIsSelected; bool mIsHighlighted; bool mIsVisible; bool mIsGrayedOut; Color mTint; bool isParentRec(TreeElement* element) const; }; /** * @brief Contains data about all visible elements in the tree view. * This might be a TreeElement entry, or a separator (empty space) * between two TreeElement%s. */ struct InteractableElement { InteractableElement(TreeElement* parent, UINT32 index, const Rect2I& bounds) :parent(parent), index(index), bounds(bounds) { } bool isTreeElement() const { return index % 2 == 1; } TreeElement* getTreeElement() const; TreeElement* parent; UINT32 index; Rect2I bounds; }; /** * @brief Contains data about one of the currently selected tree elements. */ struct SelectedElement { SelectedElement() :element(nullptr), background(nullptr) { } SelectedElement(TreeElement* elem, GUITexture* back) :element(elem), background(back) { } TreeElement* element; GUITexture* background; }; public: /** * Returns type name of the GUI element used for finding GUI element styles. */ static const String& getGUITypeName(); /** * @brief Updates tree view if dirty, among other operations. * Must be called every frame. * * @note Internal method. */ void _update(); protected: GUITreeView(const String& backgroundStyle, const String& elementBtnStyle, const String& foldoutBtnStyle, const String& highlightBackgroundStyle, const String& selectionBackgroundStyle, const String& editBoxStyle, const String& dragHighlightStyle, const String& dragSepHighlightStyle, const GUIDimensions& dimensions); virtual ~GUITreeView(); /** * @copydoc GUIElement::_getOptimalSize */ Vector2I _getOptimalSize() const override; /** * @copydoc GUIElement::updateClippedBounds */ void updateClippedBounds() override; /** * @copydoc GUIElement::_updateLayoutInternal */ virtual void _updateLayoutInternal(const GUILayoutData& data) override; /** * @copydoc GUIElement::_mouseEvent */ virtual bool _mouseEvent(const GUIMouseEvent& ev) override; /** * @copydoc GUIElement::_commandEvent */ virtual bool _commandEvent(const GUICommandEvent& ev) override; /** * @copydoc GUIElement::_virtualButtonEvent */ virtual bool _virtualButtonEvent(const GUIVirtualButtonEvent& ev) override; /** * @brief Attempts to find an interactable element under the specified coordinates. * Returns null if one cannot be found. * * @param coord Coordinates relative to parent GUI widget. */ const GUITreeView::InteractableElement* findElementUnderCoord(const Vector2I& coord) const; /** * @brief Returns the top-most selected tree element if selection is active, * null otherwise. */ TreeElement* getTopMostSelectedElement() const; /** * @brief Returns the bottom-most selected tree element if selection is active, * null otherwise. */ TreeElement* getBottomMostSelectedElement() const; /** * @brief Starts rename operation on the specified tree element, spawning an input box. */ void enableEdit(TreeElement* element); /** * @brief Ends rename operation if one is currently active. * * @param acceptChanges Should the new name be accepted or discarded. */ void disableEdit(bool acceptChanges); /** * @brief Triggered when the Foldout button for a tree element was been toggled. * (i.e. something was expanded or collapsed). */ void elementToggled(TreeElement* element, bool toggled); /** * @brief Returns the top level TreeElement. */ virtual TreeElement& getRootElement() = 0; /** * @brief Returns the top level TreeElement that cannot be modified. */ virtual const TreeElement& getRootElementConst() const = 0; /** * @brief Checks if the hierarchy needs any updates and performs those * updates if needed. */ virtual void updateTreeElementHierarchy() = 0; /** * @brief Changes the name of the content associated with the provided tree element. */ virtual void renameTreeElement(TreeElement* element, const WString& name) = 0; /** * @brief Deletes the content associated with the provided tree element. */ virtual void deleteTreeElement(TreeElement* element) = 0; /** * @brief Checks whether the tree view can accept the currently active drag and drop * operation. */ virtual bool acceptDragAndDrop() const = 0; /** * @brief Triggered when the user drags a tree element and starts a drag and drop operation. */ virtual void dragAndDropStart() = 0; /** * @brief Triggered when the user ends a drag and drop operation over the tree view. * * @param overTreeElement TreeElement the drag operation ended over, if any. */ virtual void dragAndDropEnded(TreeElement* overTreeElement) = 0; /** * @brief Triggered whenever a TreeElement gets selected or deselected. */ virtual void selectionChanged() { } /** * @brief Are any tree elements currently selected. */ bool isSelectionActive() const; /** * @brief Expands the selection to the provided TreeElement. Doesn't clear * previous selection. */ void selectElement(TreeElement* element); /** * @brief Unselects the provided TreeElement. */ void unselectElement(TreeElement* element); /** * @brief Unselects all selected TreeElement%s. * * @param sendEvent Determines should the external world be notified of this change. */ void unselectAll(bool sendEvent = true); /** * @brief Starts rename operation on the currently selected element. */ void renameSelected(); /** * @brief Deletes all currently selected elements. */ void deleteSelection(); /** * @brief Expands all parents of the provided TreeElement making it interactable. */ void expandToElement(TreeElement* element); /** * @brief Expands the provided TreeElement making its children interactable. */ void expandElement(TreeElement* element); /** * @brief Collapses the provided TreeElement making its children hidden and not interactable. */ void collapseElement(TreeElement* element); /** * @brief Duplicates the currently selected entries. */ virtual void duplicateSelection() { } /** * @brief Marks the current selection for copying. */ virtual void copySelection() { } /** * @brief Marks the current selection for cutting. */ virtual void cutSelection() { } /** * @brief Pastes a set of entries previously marked for cut or copy. */ virtual void paste() { } /** * @brief Rebuilds the needed GUI elements for the provided TreeElement. */ void updateElementGUI(TreeElement* element); /** * @brief Close any elements that were temporarily expanded due to a drag operation * hovering over them. */ void closeTemporarilyExpandedElements(); /** * @brief Temporarily expand the provided element. Temporarily expanded elements can be * closed by calling ::closeTemporarilyExpandedElements. */ void temporarilyExpandElement(const GUITreeView::InteractableElement* mouseOverElement); /** * @brief Scrolls the parent GUIScrollArea (if any) so that the specified TreeElement is visible. * * @param element Element to scroll to. * @param center If true the element will be centered in the scroll view, * otherwise it will be at the top. */ void scrollToElement(TreeElement* element, bool center); /** * @brief Attempts to find the parent GUIScrollArea that the tree view is a child of. */ GUIScrollArea* findParentScrollArea() const; /** * @brief Scrolls the tree view to the specified element and highlights it. */ void ping(TreeElement* element); /** * @brief Clears the ping highlight on the currently highlighted element. */ void clearPing(); /** * @brief Triggered when the user accepts the changes during a rename operation. */ void onEditAccepted(); /** * @brief Triggered when the user rejects the changes during a rename operation. */ void onEditCanceled(); /** * @brief Triggered when the user clicks outside of the edit box during a rename operation. */ void onEditFocusLost(); String mBackgroundStyle; String mElementBtnStyle; String mFoldoutBtnStyle; String mSelectionBackgroundStyle; String mHighlightBackgroundStyle; String mEditBoxStyle; String mDragHighlightStyle; String mDragSepHighlightStyle; GUITexture* mBackgroundImage; Vector mVisibleElements; bool mIsElementSelected; Vector mSelectedElements; bool mIsElementHighlighted; SelectedElement mHighlightedElement; TreeElement* mEditElement; GUITreeViewEditBox* mNameEditBox; Vector2I mDragStartPosition; Vector2I mDragPosition; bool mDragInProgress; GUITexture* mDragHighlight; GUITexture* mDragSepHighlight; Rect2I mTopScrollBounds; Rect2I mBottomScrollBounds; ScrollState mScrollState; float mLastScrollTime; Stack mAutoExpandedElements; TreeElement* mMouseOverDragElement; float mMouseOverDragElementTime; static VirtualButton mRenameVB; static VirtualButton mDeleteVB; static VirtualButton mDuplicateVB; static VirtualButton mCutVB; static VirtualButton mCopyVB; static VirtualButton mPasteVB; static const UINT32 ELEMENT_EXTRA_SPACING; static const UINT32 INDENT_SIZE; static const UINT32 INITIAL_INDENT_OFFSET; static const UINT32 DRAG_MIN_DISTANCE; static const float AUTO_EXPAND_DELAY_SEC; static const float SCROLL_AREA_HEIGHT_PCT; static const UINT32 SCROLL_SPEED_PX_PER_SEC; static const Color GRAYED_OUT_COLOR; }; }