Browse Source

Merge remote-tracking branch 'MonkeyFirst/PaintSelection-and-Origins'

Lasse Öörni 9 years ago
parent
commit
356ad88bae

+ 7 - 0
Docs/GettingStarted.dox

@@ -846,6 +846,13 @@ Middle mouse       - Hold down pans the camera
 Shift+Middle mouse - Hold down orbits the camera around selected objects
 Shift+Middle mouse - Hold down orbits the camera around selected objects
 \endverbatim
 \endverbatim
 
 
+The PaintSelect-tool usage:
+
+\verbatim
+Key C              - Enable/Disable PaintSelection-tool (circle) for selecting origins in viewport, Hold Ctrl during painting with circle to enable deselection operation
+Key Space          - Show/Hide name for nearly placed orinigs in viewport while ShowOrigins are enabled in toolbar
+\endverbatim
+
 \section EditorInstructions_Controls2 Controls (Blender style hotkeys)
 \section EditorInstructions_Controls2 Controls (Blender style hotkeys)
 
 
 Hotkeys list show only differences with standard style
 Hotkeys list show only differences with standard style

+ 5 - 1
bin/Data/Scripts/Editor.as

@@ -22,6 +22,8 @@
 #include "Scripts/Editor/EditorColorWheel.as"
 #include "Scripts/Editor/EditorColorWheel.as"
 #include "Scripts/Editor/EditorEventsHandlers.as"
 #include "Scripts/Editor/EditorEventsHandlers.as"
 #include "Scripts/Editor/EditorViewDebugIcons.as"
 #include "Scripts/Editor/EditorViewDebugIcons.as"
+#include "Scripts/Editor/EditorViewSelectableOrigins.as"
+#include "Scripts/Editor/EditorViewPaintSelection.as"
 
 
 String configFileName;
 String configFileName;
 
 
@@ -127,7 +129,9 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
     UpdateGizmo();
     UpdateGizmo();
     UpdateDirtyUI();
     UpdateDirtyUI();
     UpdateViewDebugIcons();
     UpdateViewDebugIcons();
-
+    UpdateOrigins();
+    UpdatePaintSelection();
+    
     // Handle Particle Editor looping.
     // Handle Particle Editor looping.
     if (particleEffectWindow !is null and particleEffectWindow.visible)
     if (particleEffectWindow !is null and particleEffectWindow.visible)
     {
     {

+ 65 - 1
bin/Data/Scripts/Editor/EditorEventsHandlers.as

@@ -1,4 +1,7 @@
 // Editor main handlers (add your local handler in proper main handler to prevent losing events)
 // Editor main handlers (add your local handler in proper main handler to prevent losing events)
+const String EDITOR_EVENT_SCENE_LOADED("EditorEventSceneLoaded");
+const String EDITOR_EVENT_ORIGIN_START_HOVER("EditorEventOriginStartHover");
+const String EDITOR_EVENT_ORIGIN_END_HOVER("EditorEventOriginEndHover");
 
 
 void EditorSubscribeToEvents()
 void EditorSubscribeToEvents()
 {
 {
@@ -19,6 +22,19 @@ void EditorSubscribeToEvents()
     SubscribeToEvent("EndViewUpdate", "EditorMainHandleEndViewUpdate");
     SubscribeToEvent("EndViewUpdate", "EditorMainHandleEndViewUpdate");
     SubscribeToEvent("BeginViewRender", "EditorMainHandleBeginViewRender");
     SubscribeToEvent("BeginViewRender", "EditorMainHandleBeginViewRender");
     SubscribeToEvent("EndViewRender", "EditorMainHandleEndViewRender");
     SubscribeToEvent("EndViewRender", "EditorMainHandleEndViewRender");
+    
+    SubscribeToEvent(EDITOR_EVENT_SCENE_LOADED, "EditorMainHandleSceneLoaded");
+    
+    SubscribeToEvent("HoverBegin", "EditorMainHandleHoverBegin");
+    SubscribeToEvent("HoverEnd", "EditorMainHandleHoverEnd");
+    
+    SubscribeToEvent(EDITOR_EVENT_ORIGIN_START_HOVER, "EditorMainHandleOriginStartHover");
+    SubscribeToEvent(EDITOR_EVENT_ORIGIN_END_HOVER, "EditorMainHandleOriginEndHover");
+    
+    SubscribeToEvent("NodeAdded", "EditorMainHandleNodeAdded");
+    SubscribeToEvent("NodeRemoved", "EditorMainHandleNodeRemoved");
+    
+    SubscribeToEvent("NodeNameChanged", "EditorMainHandleNodeNameChanged");
 }
 }
 
 
 void EditorMainHandleKeyDown(StringHash eventType, VariantMap& eventData)
 void EditorMainHandleKeyDown(StringHash eventType, VariantMap& eventData)
@@ -46,6 +62,9 @@ void EditorMainHandleMouseMove(StringHash eventType, VariantMap& eventData)
 
 
     // EditorLayer.as handler
     // EditorLayer.as handler
     HandleHideLayerEditor(eventType, eventData);
     HandleHideLayerEditor(eventType, eventData);
+    
+    // PaintSelectionMouseMove
+    HandlePaintSelectionMouseMove(eventType, eventData);
 }
 }
 
 
 void EditorMainHandleMouseWheel(StringHash eventType, VariantMap& eventData)
 void EditorMainHandleMouseWheel(StringHash eventType, VariantMap& eventData)
@@ -54,7 +73,9 @@ void EditorMainHandleMouseWheel(StringHash eventType, VariantMap& eventData)
     HandleColorWheelMouseWheel(eventType, eventData);
     HandleColorWheelMouseWheel(eventType, eventData);
 
 
     // EditorLayer.as handler
     // EditorLayer.as handler
-    HandleMaskTypeScroll(eventType, eventData);    
+    HandleMaskTypeScroll(eventType, eventData);
+    // PaintSelection handler
+    HandlePaintSelectionWheel(eventType, eventData);
 }
 }
 
 
 void EditorMainHandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
 void EditorMainHandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
@@ -83,6 +104,7 @@ void EditorMainHandleUIMouseClick(StringHash eventType, VariantMap& eventData)
 {
 {
     // EditorView.as handler
     // EditorView.as handler
     ViewMouseClick();
     ViewMouseClick();
+    HandleOriginToggled(eventType, eventData);
 }
 }
 
 
 void EditorMainHandleUIMouseClickEnd(StringHash eventType, VariantMap& eventData)
 void EditorMainHandleUIMouseClickEnd(StringHash eventType, VariantMap& eventData)
@@ -111,4 +133,46 @@ void EditorMainHandleBeginViewRender(StringHash eventType, VariantMap& eventData
 void EditorMainHandleEndViewRender(StringHash eventType, VariantMap& eventData)
 void EditorMainHandleEndViewRender(StringHash eventType, VariantMap& eventData)
 {
 {
     HandleEndViewRender(eventType, eventData);
     HandleEndViewRender(eventType, eventData);
+}
+
+void EditorMainHandleSceneLoaded(StringHash eventType, VariantMap& eventData)
+{
+    HandleSceneLoadedForOrigins();
+}
+
+void EditorMainHandleHoverBegin(StringHash eventType, VariantMap& eventData)
+{
+    HandleOriginsHoverBegin(eventType, eventData);
+}
+
+void EditorMainHandleHoverEnd(StringHash eventType, VariantMap& eventData)
+{
+    HandleOriginsHoverEnd(eventType, eventData);
+}
+
+void EditorMainHandleNodeAdded(StringHash eventType, VariantMap& eventData)
+{
+    HandleNodeAdded(eventType, eventData);
+    rebuildSceneOrigins = true;
+}
+
+void EditorMainHandleNodeRemoved(StringHash eventType, VariantMap& eventData)
+{
+    HandleNodeRemoved(eventType, eventData);
+    rebuildSceneOrigins = true;
+}
+
+void EditorMainHandleNodeNameChanged(StringHash eventType, VariantMap& eventData)
+{
+    HandleNodeNameChanged(eventType, eventData);
+}
+
+void EditorMainHandleOriginStartHover(StringHash eventType, VariantMap& eventData)
+{
+
+}
+
+void EditorMainHandleOriginEndHover(StringHash eventType, VariantMap& eventData)
+{
+
 }
 }

+ 17 - 3
bin/Data/Scripts/Editor/EditorHierarchyWindow.as

@@ -85,11 +85,8 @@ void CreateHierarchyWindow()
     SubscribeToEvent(hierarchyList, "ItemClicked", "HandleHierarchyItemClick");
     SubscribeToEvent(hierarchyList, "ItemClicked", "HandleHierarchyItemClick");
     SubscribeToEvent("DragDropTest", "HandleDragDropTest");
     SubscribeToEvent("DragDropTest", "HandleDragDropTest");
     SubscribeToEvent("DragDropFinish", "HandleDragDropFinish");
     SubscribeToEvent("DragDropFinish", "HandleDragDropFinish");
-    SubscribeToEvent(editorScene, "NodeAdded", "HandleNodeAdded");
-    SubscribeToEvent(editorScene, "NodeRemoved", "HandleNodeRemoved");
     SubscribeToEvent(editorScene, "ComponentAdded", "HandleComponentAdded");
     SubscribeToEvent(editorScene, "ComponentAdded", "HandleComponentAdded");
     SubscribeToEvent(editorScene, "ComponentRemoved", "HandleComponentRemoved");
     SubscribeToEvent(editorScene, "ComponentRemoved", "HandleComponentRemoved");
-    SubscribeToEvent(editorScene, "NodeNameChanged", "HandleNodeNameChanged");
     SubscribeToEvent(editorScene, "NodeEnabledChanged", "HandleNodeEnabledChanged");
     SubscribeToEvent(editorScene, "NodeEnabledChanged", "HandleNodeEnabledChanged");
     SubscribeToEvent(editorScene, "ComponentEnabledChanged", "HandleComponentEnabledChanged");
     SubscribeToEvent(editorScene, "ComponentEnabledChanged", "HandleComponentEnabledChanged");
     SubscribeToEvent("TemporaryChanged", "HandleTemporaryChanged");
     SubscribeToEvent("TemporaryChanged", "HandleTemporaryChanged");
@@ -541,6 +538,23 @@ void SelectNode(Node@ node, bool multiselect)
         hierarchyList.ClearSelection();
         hierarchyList.ClearSelection();
 }
 }
 
 
+void DeselectNode(Node@ node)
+{
+    if (node is null)
+    {
+        hierarchyList.ClearSelection();
+        return;
+    }
+
+    uint index = GetListIndex(node);
+    uint numItems = hierarchyList.numItems;
+
+    if (index < numItems)
+    {
+        hierarchyList.ToggleSelection(index);
+    }
+}
+
 void SelectComponent(Component@ component, bool multiselect)
 void SelectComponent(Component@ component, bool multiselect)
 {
 {
     if (component is null && !multiselect)
     if (component is null && !multiselect)

+ 20 - 0
bin/Data/Scripts/Editor/EditorToolBar.as

@@ -67,6 +67,12 @@ void CreateToolBar()
     fillModeGroup.AddChild(CreateToolBarToggle("FillSolid"));
     fillModeGroup.AddChild(CreateToolBarToggle("FillSolid"));
     FinalizeGroupHorizontal(fillModeGroup, "ToolBarToggle");
     FinalizeGroupHorizontal(fillModeGroup, "ToolBarToggle");
     toolBar.AddChild(fillModeGroup);
     toolBar.AddChild(fillModeGroup);
+    
+    toolBar.AddChild(CreateToolBarSpacer(4));
+    UIElement@ originGroup = CreateGroup("OriginGroup", LM_HORIZONTAL);
+    originGroup.AddChild(CreateToolBarToggle("ShowOrigin"));
+    FinalizeGroupHorizontal(originGroup, "ToolBarToggle");
+    toolBar.AddChild(originGroup);
 
 
     toolBar.AddChild(CreateToolBarSpacer(4));
     toolBar.AddChild(CreateToolBarSpacer(4));
     DropDownList@ viewportModeList = DropDownList();
     DropDownList@ viewportModeList = DropDownList();
@@ -381,6 +387,15 @@ void ToolBarSetViewportMode(StringHash eventType, VariantMap& eventData)
     SetViewportMode(mode);
     SetViewportMode(mode);
 }
 }
 
 
+void ToolBarShowOrigin(StringHash eventType, VariantMap& eventData)
+{
+    CheckBox@ edit = eventData["Element"].GetPtr();
+    
+    ShowOrigins (edit.checked);
+        
+    toolBarDirty = true;
+}
+
 void UpdateDirtyToolBar()
 void UpdateDirtyToolBar()
 {
 {
     if (toolBar is null || !toolBarDirty)
     if (toolBar is null || !toolBarDirty)
@@ -473,6 +488,10 @@ void UpdateDirtyToolBar()
     CheckBox@ fillSolidToggle = toolBar.GetChild("FillSolid", true);
     CheckBox@ fillSolidToggle = toolBar.GetChild("FillSolid", true);
     if (fillSolidToggle.checked != (fillMode == FILL_SOLID))
     if (fillSolidToggle.checked != (fillMode == FILL_SOLID))
         fillSolidToggle.checked = fillMode == FILL_SOLID;
         fillSolidToggle.checked = fillMode == FILL_SOLID;
+        
+    CheckBox@ showOriginToggle = toolBar.GetChild("ShowOrigin", true);
+    if (showOriginToggle.checked != (EditorOriginShow == true))
+        showOriginToggle.checked = EditorOriginShow == true;
 
 
     if (!subscribedToEditorToolBar)
     if (!subscribedToEditorToolBar)
     {
     {
@@ -498,6 +517,7 @@ void UpdateDirtyToolBar()
         SubscribeToEvent(fillPointToggle, "Toggled", "ToolBarFillModePoint");
         SubscribeToEvent(fillPointToggle, "Toggled", "ToolBarFillModePoint");
         SubscribeToEvent(fillWireFrameToggle, "Toggled", "ToolBarFillModeWireFrame");
         SubscribeToEvent(fillWireFrameToggle, "Toggled", "ToolBarFillModeWireFrame");
         SubscribeToEvent(fillSolidToggle, "Toggled", "ToolBarFillModeSolid");
         SubscribeToEvent(fillSolidToggle, "Toggled", "ToolBarFillModeSolid");
+        SubscribeToEvent(showOriginToggle, "Toggled", "ToolBarShowOrigin");
         subscribedToEditorToolBar = true;
         subscribedToEditorToolBar = true;
     }
     }
 
 

+ 1 - 0
bin/Data/Scripts/Editor/EditorUI.as

@@ -1171,6 +1171,7 @@ void HandleOpenSceneFile(StringHash eventType, VariantMap& eventData)
 {
 {
     CloseFileSelector(uiSceneFilter, uiScenePath);
     CloseFileSelector(uiSceneFilter, uiScenePath);
     LoadScene(ExtractFileName(eventData));
     LoadScene(ExtractFileName(eventData));
+    SendEvent(EDITOR_EVENT_SCENE_LOADED);
 }
 }
 
 
 void HandleSaveSceneFile(StringHash eventType, VariantMap& eventData)
 void HandleSaveSceneFile(StringHash eventType, VariantMap& eventData)

+ 194 - 0
bin/Data/Scripts/Editor/EditorViewPaintSelection.as

@@ -0,0 +1,194 @@
+const int PAINT_STEP_UPDATE = 16;
+const int PAINT_SELECTION_KEY = KEY_C;
+
+bool EditorPaintSelectionShow = false;
+int EditorPaintSelectionUITimeToUpdate = 0;
+
+UIElement@ EditorPaintSelectionUIContainer = null;
+BorderImage@ paintSelectionImage = null;
+
+IntVector2 paintSelectionBrushDefaultSize(96,96);
+IntVector2 paintSelectionBrushCurrentSize = paintSelectionBrushDefaultSize;
+IntVector2 paintSelectionBrushMinSize(64,64);
+IntVector2 paintSelectionBrushMaxSize(512,512);
+IntVector2 paintSelectionBrushStepSizeChange(16,16);
+
+void CreatePaintSelectionContainer()
+{
+    if (editorScene is null) return;
+    EditorPaintSelectionUIContainer = UIElement();
+    EditorPaintSelectionUIContainer.position = IntVector2(0,0);
+    EditorPaintSelectionUIContainer.size = IntVector2(graphics.width,graphics.height);
+    EditorPaintSelectionUIContainer.priority = -5;
+    EditorPaintSelectionUIContainer.focusMode = FM_NOTFOCUSABLE;
+    EditorPaintSelectionUIContainer.bringToBack = true;
+    EditorPaintSelectionUIContainer.name ="DebugPaintSelectionContainer";
+    EditorPaintSelectionUIContainer.temporary = true;
+    ui.root.AddChild(EditorPaintSelectionUIContainer);
+}
+
+void CreatePaintSelectionTool()
+{
+    paintSelectionImage = BorderImage("Icon");
+    paintSelectionImage.temporary = true;
+    paintSelectionImage.SetFixedSize(paintSelectionBrushDefaultSize.x,paintSelectionBrushDefaultSize.y);
+    paintSelectionImage.texture = cache.GetResource("Texture2D", "Textures/Editor/SelectionCircle.png");
+    paintSelectionImage.imageRect = IntRect(0,0,512,512);
+    paintSelectionImage.priority = -5;
+    paintSelectionImage.color = Color(1,1,1);
+    paintSelectionImage.bringToBack = true;
+    paintSelectionImage.enabled = false;
+    paintSelectionImage.selected = false;
+    paintSelectionImage.visible = true;
+    EditorPaintSelectionUIContainer.AddChild(paintSelectionImage);
+}
+
+void UpdatePaintSelection()
+{
+    PaintSelectionCheckKeyboard();
+
+    // Early out if disabled
+    if (!EditorPaintSelectionShow) return;
+
+    if (editorScene is null || EditorPaintSelectionUITimeToUpdate > time.systemTime) return;
+
+    EditorPaintSelectionUIContainer = ui.root.GetChild("DebugPaintSelectionContainer");
+
+    if (EditorPaintSelectionUIContainer is null)
+    {
+        CreatePaintSelectionContainer();
+        CreatePaintSelectionTool();
+    }
+
+    if (EditorPaintSelectionUIContainer !is null)
+    {
+        // Set visibility for all origins
+        EditorPaintSelectionUIContainer.visible = EditorPaintSelectionShow;
+
+        if (viewportMode!=VIEWPORT_SINGLE) 
+            EditorPaintSelectionUIContainer.visible = false;
+
+        if (EditorPaintSelectionShow)
+        {
+            IntVector2 mp = input.mousePosition;
+            paintSelectionImage.position = IntVector2(mp.x - (paintSelectionBrushCurrentSize.x * 0.5f), mp.y - (paintSelectionBrushCurrentSize.y * 0.5f));
+        }
+    }
+
+    EditorPaintSelectionUITimeToUpdate = time.systemTime + PAINT_STEP_UPDATE;
+}
+
+void PaintSelectionCheckKeyboard()
+{
+    bool key = input.keyPress[PAINT_SELECTION_KEY];
+
+    if (key && ui.focusElement is null)
+    {
+        EditorPaintSelectionShow = !EditorPaintSelectionShow;
+        if (EditorPaintSelectionUIContainer !is null)
+            EditorPaintSelectionUIContainer.visible = EditorPaintSelectionShow;
+
+        if (EditorPaintSelectionShow)
+        {
+            // When we start paint selection we change editmode to select
+            editMode = EDIT_SELECT;
+            //selectedNodes.Clear();
+            // and also we show origins for proper origins update
+            ShowOrigins(true);
+            toolBarDirty = true;
+        }
+    }
+    else if (EditorPaintSelectionShow && ui.focusElement is null)
+    {
+        if (editMode != EDIT_SELECT)
+        {
+            EditorPaintSelectionShow = false;
+            EditorPaintSelectionUIContainer.visible = false;
+        }
+    }
+
+    if (input.mouseButtonDown[MOUSEB_RIGHT])
+    {
+        EditorPaintSelectionShow = false;
+        if (EditorPaintSelectionUIContainer !is null)
+            EditorPaintSelectionUIContainer.visible = false;
+    }
+}
+
+void SelectOriginsByPaintSelection(IntVector2 curPos, float brushRadius, bool isOperationAddToSelection = true)
+{
+    if (!EditorPaintSelectionShow || EditorPaintSelectionUIContainer is null) return;
+
+    for (int i=0; i < originsNodes.length; i++)
+    {
+        Vector3 v1(originsIcons[i].position.x, originsIcons[i].position.y, 0);
+        Vector3 v2(curPos.x - ORIGINOFFSETICON.x, curPos.y - ORIGINOFFSETICON.y, 0);
+
+        float distance = (v1 - v2).length;
+        bool isThisOriginInCircle = distance < brushRadius ? true : false;
+        int nodeID = originsIcons[i].vars[ORIGIN_NODEID_VAR].GetInt();
+
+        if (isThisOriginInCircle)
+        {
+            WeakHandle handle = editorScene.GetNode(nodeID);
+            if (handle.Get() !is null)
+            {
+                Node@ node = handle.Get();
+                if (isOperationAddToSelection)
+                {
+                    if (node !is null && isThisNodeOneOfSelected(node) == false)
+                        SelectNode(node, true);
+                }
+                else // Deselect origins operation
+                {
+                    if (node !is null && isThisNodeOneOfSelected(node) == true)
+                        DeselectNode(node);
+                }
+            }
+        }
+    }
+}
+
+void HandlePaintSelectionMouseMove(StringHash eventType, VariantMap& eventData)
+{
+    if (!EditorPaintSelectionShow || EditorPaintSelectionUIContainer is null) return;
+
+    int x = eventData["X"].GetInt();
+    int y = eventData["Y"].GetInt();
+    float r = (paintSelectionBrushCurrentSize.x * 0.5);
+
+    IntVector2 mousePos(x,y);
+
+    // Select by mouse
+    if (input.mouseButtonDown[MOUSEB_LEFT] && input.qualifierDown[QUAL_CTRL] != true)
+    {
+        SelectOriginsByPaintSelection(mousePos, r, true);
+    }
+    // Deselect by mouse
+    else if (input.mouseButtonDown[MOUSEB_LEFT] && input.qualifierDown[QUAL_CTRL] == true)
+    {
+        SelectOriginsByPaintSelection(mousePos, r, false);
+    }
+}
+
+void HandlePaintSelectionWheel(StringHash eventType, VariantMap& eventData)
+{
+    if (!EditorPaintSelectionShow || EditorPaintSelectionUIContainer is null) return;
+
+    int wheelValue = eventData["Wheel"].GetInt();
+
+    if (wheelValue != 0)
+    {
+        if (wheelValue > 0)
+        {
+            paintSelectionBrushCurrentSize = paintSelectionBrushCurrentSize - paintSelectionBrushStepSizeChange;
+            paintSelectionBrushCurrentSize = IntVector2(Max(paintSelectionBrushCurrentSize.x, paintSelectionBrushMinSize.x), Max(paintSelectionBrushCurrentSize.y, paintSelectionBrushMinSize.y));
+        }
+        else if (wheelValue < 0)
+        {
+            paintSelectionBrushCurrentSize = paintSelectionBrushCurrentSize + paintSelectionBrushStepSizeChange;
+            paintSelectionBrushCurrentSize = IntVector2(Min(paintSelectionBrushCurrentSize.x, paintSelectionBrushMaxSize.x), Min(paintSelectionBrushCurrentSize.y, paintSelectionBrushMaxSize.y));
+        }
+        paintSelectionImage.SetFixedSize(paintSelectionBrushCurrentSize.x, paintSelectionBrushCurrentSize.y);
+    }
+}

+ 435 - 0
bin/Data/Scripts/Editor/EditorViewSelectableOrigins.as

@@ -0,0 +1,435 @@
+const bool DEFAULT_SHOW_NAMES_FOR_ALL = false;
+const int ORIGIN_STEP_UPDATE = 10;
+const int NAMES_SIZE = 11;
+const StringHash ORIGIN_NODEID_VAR("OriginNodeID");
+const Color ORIGIN_COLOR(1.0f,1.0f,1.0f,1.0f);
+const Color ORIGIN_COLOR_SELECTED(0.0f,1.0f,1.0f,1.0f);
+const Color ORIGIN_COLOR_DISABLED(1.0f,0.0f,0.0f,1.0f);
+const Color ORIGIN_COLOR_TEXT(1.0f,1.0f,1.0f,0.3f);
+const Color ORIGIN_COLOR_SELECTED_TEXT(1.0f,1.0f,1.0f,1.0f);
+const IntVector2 ORIGIN_ICON_SIZE(14,14);
+const IntVector2 ORIGIN_ICON_SIZE_SELECTED(18,18);
+const float ORIGINS_VISIBLITY_RANGE = 32.0f;
+const IntVector2 ORIGINOFFSETICON(8,8);
+const IntVector2 ORIGINOFFSETICONSELECTED(10,8);
+
+bool showNamesForAll = DEFAULT_SHOW_NAMES_FOR_ALL;
+bool EditorOriginShow = false;
+bool rebuildSceneOrigins = true;
+bool isOriginsHovered = false;
+
+int EditorOriginUITimeToUpdate = 0;
+int EditorOriginUITimeToSceneNodeRead = 0;
+int prevSelectedID;
+int selectedNodeInfoState = 0;
+int originHoveredIndex = -1;
+
+UIElement@ EditorOriginUIContainer = null;
+Text@ selectedNodeName = null;
+BorderImage@ selectedNodeOrigin = null;
+
+Array<BorderImage@> selectedNodeOriginChilds;
+Array<Text@> selectedNodeNameChilds;
+Array<Node@> originsNodes;
+Array<BorderImage@> originsIcons;
+Array<Text@> originsNames;
+
+void CreateOriginsContainer()
+{
+    if (editorScene is null) return;
+    EditorOriginUIContainer = UIElement();
+    EditorOriginUIContainer.position = IntVector2(0,0);
+    EditorOriginUIContainer.size = IntVector2(graphics.width,graphics.height);
+    EditorOriginUIContainer.priority = -1000;
+    EditorOriginUIContainer.focusMode = FM_NOTFOCUSABLE;
+    EditorOriginUIContainer.bringToBack = true;
+    EditorOriginUIContainer.name ="DebugOriginsContainer";
+    EditorOriginUIContainer.temporary = true;
+    ui.root.AddChild(EditorOriginUIContainer);
+}
+
+void HandleOriginToggled(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ origin = eventData["Element"].GetPtr();
+    if (origin is null) return;
+
+    if (EditorPaintSelectionShow) return;
+
+    if (IsSceneOrigin(origin))
+    {
+        int nodeID = origin.vars[ORIGIN_NODEID_VAR].GetInt();
+        if (editorScene !is null) 
+        {
+            bool goBackAndSelectNodeParent = input.qualifierDown[QUAL_CTRL];
+            bool multiSelect = input.qualifierDown[QUAL_SHIFT];
+
+            WeakHandle handle = editorScene.GetNode(nodeID);
+            if (handle.Get() !is null) {
+                Node@ selectedNodeByOrigin = handle.Get();
+                if (selectedNodeByOrigin !is null)
+                {
+                    if (goBackAndSelectNodeParent)
+                        SelectNode(selectedNodeByOrigin.parent, false);
+                    else
+                        SelectNode(selectedNodeByOrigin, multiSelect);
+                }
+            }
+        }
+    }
+}
+
+void ShowOrigins(bool isVisible = true)
+{
+    EditorOriginShow = isVisible;
+
+    if (EditorOriginUIContainer is null)
+      CreateOriginsContainer();
+      
+    EditorOriginUIContainer.visible = isVisible;
+}
+
+void UpdateOrigins()
+{
+    // Early out if Origins are disabled
+    if (!EditorOriginShow) return; 
+
+    CheckKeyboardQualifers();
+
+    if (editorScene is null || EditorOriginUITimeToUpdate > time.systemTime) return;
+
+    EditorOriginUIContainer = ui.root.GetChild("DebugOriginsContainer");
+
+    // Since editor not clear UIs then do loading new scenes, this creation called once per Editor's starting event
+    // for other scenes we use the same container
+    if (EditorOriginUIContainer is null)
+    {
+        CreateOriginsContainer();
+    }
+
+    if (EditorOriginUIContainer !is null)
+    {
+        // Set visibility for all origins
+        EditorOriginUIContainer.visible = EditorOriginShow;
+
+        if (viewportMode!=VIEWPORT_SINGLE) 
+            EditorOriginUIContainer.visible = false;
+        
+        // Forced read nodes for some reason:
+        if ((originsNodes.length < 1) || rebuildSceneOrigins)
+        {
+            originsNodes = editorScene.GetChildren(true);
+            // If we are hot have free origins icons in arrays, resize x 2
+            if (originsIcons.length < originsNodes.length)
+            {
+                EditorOriginUIContainer.RemoveAllChildren();
+                originsIcons.Clear();
+                originsNames.Clear();
+
+                originsIcons.Resize(originsNodes.length * 2);
+                originsNames.Resize(originsNodes.length * 2);
+
+                if (originsIcons.length > 0)
+                {
+                    for (int i=0; i < originsIcons.length; i++)
+                    {
+                        CreateOrigin(i, false);
+                    }
+                }
+            }
+            // If this rebuild pass after new scene loading or add/delete node - reset flag to default
+            if (rebuildSceneOrigins)
+                rebuildSceneOrigins = false;
+        }
+
+        if (originsNodes.length > 0)
+        {
+            // Get selected node for feeding proper arrray's UIElements with slyte colorig and additional info on ALT
+            Node@ selectedNode = null;
+            if (selectedNodes.length > 0)
+            {
+                selectedNode = selectedNodes[0];
+            }
+            else if (selectedComponents.length > 0)
+            {
+                selectedNode = selectedComponents[0].node;
+            }
+
+            // Update existed origins (every 10 ms)
+            if (originsNodes.length > 0 )
+            {
+                for (int i=0; i < originsNodes.length; i++)
+                {
+                    Vector3 eyeDir = originsNodes[i].worldPosition - cameraNode.worldPosition;
+                    float distance = (eyeDir).length;
+                    eyeDir.Normalize();
+                    Vector3 cameraDir = (cameraNode.worldRotation * Vector3(0.0f, 0.0f, 1.0f)).Normalized();
+                    float angleCameraDirVsDirToNode = eyeDir.DotProduct(cameraDir);
+
+                    // if node in range and in camera view (clip back sibe)
+                    if (distance < ORIGINS_VISIBLITY_RANGE && angleCameraDirVsDirToNode > 0.7f)
+                    {
+                        // turn on origin and move
+                        MoveOrigin(i, true);
+
+                        if (isThisNodeOneOfSelected(originsNodes[i]))
+                        {
+                            ShowSelectedNodeOrigin(originsNodes[i], i);
+                            originsNames[i].visible = true;
+                        }
+                        else
+                        { 
+                            if (showNamesForAll || (isOriginsHovered && originHoveredIndex == i))
+                                originsNames[i].text = NodeInfo(originsNodes[i], selectedNodeInfoState);
+                        }
+                    }
+                    else
+                    {
+                        // turn-off origin
+                        VisibilityOrigin(i, false);
+                    }
+                }
+
+                // Hide non used origins
+                for (int j=originsNodes.length; j < originsIcons.length; j++)
+                {
+                    VisibilityOrigin(j, false);
+                }
+            }  
+        }
+    }
+
+    EditorOriginUITimeToUpdate = time.systemTime + ORIGIN_STEP_UPDATE;
+}
+
+bool isThisNodeOneOfSelected(Node@ node)
+{
+    if (selectedNodes.length < 1) return false;
+
+    for (int i = 0; i < selectedNodes.length; i++)
+    {
+        if (node is selectedNodes[i])
+            return true;
+    }
+
+    return false;
+}
+
+void ShowSelectedNodeOrigin(Node@ node, int index)
+{
+    if (node !is null)
+    {
+        // just keep node's text and node's origin icon position in actual view
+        Viewport@ vp = activeViewport.viewport;
+        Vector2 sp = activeViewport.camera.WorldToScreenPoint(node.worldPosition);
+        //originsIcons[index].position = IntVector2(10+int(vp.rect.left + sp.x * vp.rect.right), -5 + int(vp.rect.top + sp.y* vp.rect.bottom));
+        originsIcons[index].position = IntVector2(int(vp.rect.left + sp.x * vp.rect.right) - ORIGINOFFSETICONSELECTED.x, int(vp.rect.top + sp.y* vp.rect.bottom) - ORIGINOFFSETICONSELECTED.y);
+        originsNames[index].color = ORIGIN_COLOR_SELECTED_TEXT;
+
+        if (originsNodes[index].enabled)
+            originsIcons[index].color = ORIGIN_COLOR_SELECTED;
+        else
+            originsIcons[index].color = ORIGIN_COLOR_DISABLED;
+        
+        originsIcons[index].SetFixedSize(ORIGIN_ICON_SIZE_SELECTED.x,ORIGIN_ICON_SIZE_SELECTED.y);
+
+        // if selected node chaged, reset some vars
+        if (prevSelectedID != node.id)
+        {
+            prevSelectedID = node.id;
+            selectedNodeInfoState = 0;
+            originsIcons[index].vars[ORIGIN_NODEID_VAR] = node.id;
+        }
+
+        // We always update to keep and feed alt-info with actual info about node components
+        Array<Component@> components = node.GetComponents();
+        Array<String> componentsShortInfo;
+        Array<String> componentsDetailInfo;
+        componentsShortInfo.Resize(components.length);
+        componentsDetailInfo.Resize(components.length);
+        // Add std info node name + tags
+        originsNames[index].text = NodeInfo(node, selectedNodeInfoState) + "\n";
+    }
+}
+
+void CreateOrigin(int index, bool isVisible = false) 
+{
+    if (originsIcons.length < index) return;
+
+    originsIcons[index] = BorderImage("Icon");
+    originsIcons[index].temporary = true;
+    originsIcons[index].SetFixedSize(ORIGIN_ICON_SIZE.x,ORIGIN_ICON_SIZE.y);
+    originsIcons[index].texture = cache.GetResource("Texture2D", "Textures/Editor/EditorIcons.png");
+    originsIcons[index].imageRect = IntRect(0,0,14,14);
+    originsIcons[index].priority = -1000;
+    originsIcons[index].color = ORIGIN_COLOR;
+    originsIcons[index].bringToBack = true;
+    originsIcons[index].enabled = true;
+    originsIcons[index].selected = true;
+    originsIcons[index].visible = isVisible;
+    EditorOriginUIContainer.AddChild(originsIcons[index]);
+
+    originsNames[index] = Text();
+    originsNames[index].visible = false;
+    originsNames[index].SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), NAMES_SIZE);
+    originsNames[index].color = ORIGIN_COLOR_TEXT;
+    //originsNames[index].textEffect = TE_STROKE;
+    originsNames[index].temporary = true;
+    originsNames[index].bringToBack = true;
+    originsNames[index].priority = -1000;
+    originsNames[index].enabled = false;
+
+    EditorOriginUIContainer.AddChild(originsNames[index]);
+}
+
+void MoveOrigin(int index, bool isVisible = false)
+{
+    if (originsIcons.length < index) return;
+    if (originsIcons[index] is null) return;
+    if (originsNodes[index].temporary)
+    {
+        originsIcons[index].visible = false;
+        originsNames[index].visible = false;
+        return;
+    }
+
+    Viewport@ vp = activeViewport.viewport;
+    Vector2 sp = activeViewport.camera.WorldToScreenPoint(originsNodes[index].worldPosition);
+
+    originsIcons[index].SetFixedSize(ORIGIN_ICON_SIZE.x,ORIGIN_ICON_SIZE.y);
+
+    if (originsNodes[index].enabled)
+        originsIcons[index].color = ORIGIN_COLOR;
+    else
+        originsIcons[index].color = ORIGIN_COLOR_DISABLED;
+
+    originsIcons[index].position = IntVector2(int(vp.rect.left + sp.x * vp.rect.right) - ORIGINOFFSETICON.x, int(vp.rect.top + sp.y* vp.rect.bottom) - ORIGINOFFSETICON.y);
+    originsIcons[index].visible = isVisible;
+    originsIcons[index].vars[ORIGIN_NODEID_VAR] = originsNodes[index].id;
+
+    originsNames[index].position = IntVector2(10+int(vp.rect.left + sp.x * vp.rect.right), -5 + int(vp.rect.top + sp.y* vp.rect.bottom));
+
+    if (isOriginsHovered && originHoveredIndex == index)
+    {
+        originsNames[index].visible = true;
+        originsNames[index].color = ORIGIN_COLOR_SELECTED_TEXT;
+    }
+    else
+    {
+        originsNames[index].visible = showNamesForAll ? isVisible : false;
+        originsNames[index].color = ORIGIN_COLOR_TEXT;
+    }
+}
+
+void VisibilityOrigin(int index, bool isVisible = false)
+{
+    originsIcons[index].visible = isVisible;
+    originsNames[index].visible = isVisible;
+}
+
+bool IsSceneOrigin(UIElement@ element)
+{
+    if (originsIcons.length < 1) return false;
+
+    for (int i=0; i < originsIcons.length; i++)
+    {
+        if (element is originsIcons[i])
+        {
+            originHoveredIndex = i;
+            return true;
+        }
+    }
+
+    originHoveredIndex = -1;
+    return false;
+}
+
+void CheckKeyboardQualifers()
+{
+    // if pressed alt we inc state for info
+    bool showAltInfo = input.keyPress[KEY_ALT];
+    if (showAltInfo)
+        if (selectedNodeInfoState < 3) selectedNodeInfoState += 1;
+
+    // if pressed ctrl we reset info state
+    bool hideAltInfo = input.qualifierDown[QUAL_CTRL];
+    if (hideAltInfo)
+        selectedNodeInfoState = 0;
+
+    bool showNameForOther = false;
+
+    // In-B.mode Key_Space are busy by quick menu, so we use other key for B.mode
+    if (hotKeyMode == HOTKEYS_MODE_BLENDER)
+      showNameForOther = (input.keyPress[KEY_TAB] && ui.focusElement is null);
+    else
+      showNameForOther = (input.keyPress[KEY_SPACE] && ui.focusElement is null);
+
+    if (showNameForOther)
+        showNamesForAll =!showNamesForAll;
+
+}
+
+String NodeInfo(Node& node, int st)
+{
+    String result = "";
+    if (node !is editorScene)
+    {
+        if (node.name.empty)
+            result = "Node";
+        else
+            result = node.name;
+
+        // Add node's tags if wey are exist
+        if (st > 0 && node.tags.length > 0)
+        {
+            result = result + "\n[";
+            for (int i=0;i<node.tags.length; i++)
+            {
+                result = result + " " + node.tags[i];
+            }
+            result = result + " ] ";
+        }
+    }
+    else
+        result = "Scene Origin";
+
+    return result;
+}
+
+void HandleSceneLoadedForOrigins()
+{
+    rebuildSceneOrigins = true;
+}
+
+void HandleOriginsHoverBegin(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ origin = eventData["Element"].GetPtr();
+    if (origin is null)
+        return;
+
+    if (IsSceneOrigin(origin))
+    {
+        VariantMap data;
+        data["Element"] = originsIcons[originHoveredIndex];
+        data["Id"] = originHoveredIndex;
+        data["NodeId"] = originsIcons[originHoveredIndex].vars[ORIGIN_NODEID_VAR].GetInt();
+        SendEvent(EDITOR_EVENT_ORIGIN_START_HOVER, data);
+        isOriginsHovered = true;
+    }
+}
+
+void HandleOriginsHoverEnd(StringHash eventType, VariantMap& eventData)
+{
+    UIElement@ origin = eventData["Element"].GetPtr();
+    if (origin is null)
+        return;
+
+    if (IsSceneOrigin(origin))
+    {
+        VariantMap data;
+        data["Element"] = originsIcons[originHoveredIndex];
+        data["Id"] = originHoveredIndex;
+        data["NodeId"] = originsIcons[originHoveredIndex].vars[ORIGIN_NODEID_VAR].GetInt();
+        SendEvent(EDITOR_EVENT_ORIGIN_END_HOVER, data);
+        isOriginsHovered = false;
+    }
+}

BIN
bin/Data/Textures/Editor/EditorIcons.png


BIN
bin/Data/Textures/Editor/SelectionCircle.png


+ 4 - 0
bin/Data/UI/EditorIcons.xml

@@ -435,4 +435,8 @@
         <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
         <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
         <attribute name="Image Rect" value="176 16 190 30" />
         <attribute name="Image Rect" value="176 16 190 30" />
     </element>
     </element>
+    <element type="ShowOrigin">
+        <attribute name="Texture" value="Texture2D;Textures/Editor/EditorIcons.png" />
+        <attribute name="Image Rect" value="192 160 222 190" />
+    </element>
 </elements>
 </elements>