Browse Source

Search Bar Functionality - Issue #195

- Implemented the search function in the Hierarchy Frame, Project Frame and Resource Selector Modal
- Created a search algorithm in SearchBarFiltering.ts
raheelx 9 years ago
parent
commit
f4120cbb97

+ 4 - 0
Resources/EditorData/AtomicEditor/editor/ui/hierarchyframe.tb.txt

@@ -14,4 +14,8 @@ TBLayout: distribution: gravity, axis: y, id: hierarchyframe
 			gravity left right
 			placeholder @search
 			type search	
+		TBButton
+			@include definitions>menubutton
+			text X
+			id cancel search
 	TBLayout: distribution: gravity, id: hierarchycontainer, gravity: all

+ 4 - 0
Resources/EditorData/AtomicEditor/editor/ui/projectframe.tb.txt

@@ -14,6 +14,10 @@ TBLayout: distribution: gravity, axis: y, id: projectframe
 			gravity left right
 			placeholder @search
 			type search	
+		TBButton
+			@include definitions>menubutton
+			text X
+			id cancel search
 	TBWidget: gravity: all
 		TBLayout: distribution: gravity, id: foldercontainer, gravity: all
 	TBScrollContainer: scroll-mode: auto, id: contentcontainerscroll, gravity: all

+ 3 - 0
Resources/EditorData/AtomicEditor/editor/ui/resourceselection.tb.txt

@@ -10,6 +10,9 @@ TBLayout: distribution: gravity, axis: y, id: resourceselectionframe
 			gravity left right
 			placeholder @search
 			type search	
+		TBButton 
+			id cancel search
+			text X
 	TBWidget: gravity: all
 		TBLayout: distribution: gravity, id: resourcecontainer, gravity: all
 	TBLayout: 

+ 61 - 0
Script/AtomicEditor/resources/SearchBarFiltering.ts

@@ -0,0 +1,61 @@
+//
+// Copyright (c) 2014-2016 THUNDERBEAST GAMES 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.
+//
+
+export class UISearchBar {
+    searchPopulate(searchText: string, compareText: string): boolean {
+
+        var equal = false;
+
+        searchText = searchText.toUpperCase();
+        compareText = compareText.toUpperCase();
+
+        for (var i = 0; i < compareText.length; i++) {
+
+            if (compareText[i] == searchText[0]) {
+                var j = 0;
+                var k = i;
+
+                while (k < compareText.length && j < searchText.length) {
+
+                    if (compareText[k] == searchText[j]) {
+                        equal = true;
+                    } else {
+                        equal = false;
+                        break;
+                    }
+                    j++;
+                    k++;
+                }
+
+                if (j != searchText.length) {
+                    equal = false;
+                }
+
+                if (equal) {
+                    break;
+                }
+            }
+        }
+
+        return equal;
+    }
+}

+ 67 - 2
Script/AtomicEditor/ui/frames/HierarchyFrame.ts

@@ -24,6 +24,7 @@ import HierarchyFrameMenu = require("./menus/HierarchyFrameMenu");
 import MenuItemSources = require("./menus/MenuItemSources");
 import EditorEvents = require("editor/EditorEvents");
 import EditorUI = require("ui/EditorUI");
+import SearchBarFiltering = require("resources/SearchBarFiltering");
 
 var IconTemporary = "ComponentBitmap";
 
@@ -34,6 +35,10 @@ class HierarchyFrame extends Atomic.UIWidget {
     hierList: Atomic.UIListView;
     menu: HierarchyFrameMenu;
     nodeIDToItemID = {};
+    uiSearchBar: SearchBarFiltering.UISearchBar = new SearchBarFiltering.UISearchBar();
+    search: boolean = false;
+    searchEdit: Atomic.UIEditField;
+    selectedNode: Atomic.Node;
 
     constructor(parent: Atomic.UIWidget) {
 
@@ -45,6 +50,8 @@ class HierarchyFrame extends Atomic.UIWidget {
 
         this.gravity = Atomic.UI_GRAVITY_TOP_BOTTOM;
 
+        this.searchEdit = <Atomic.UIEditField>this.getWidget("filter");
+
         var hierarchycontainer = parent.getWidget("hierarchycontainer");
         hierarchycontainer.addChild(this);
 
@@ -138,6 +145,21 @@ class HierarchyFrame extends Atomic.UIWidget {
 
         });
 
+        // Activates search while user is typing in search widget
+        this.searchEdit.subscribeToEvent(this.searchEdit, "WidgetEvent", (data) => {
+            if (!ToolCore.toolSystem.project) return;
+
+            if (data.type == Atomic.UI_EVENT_TYPE_KEY_UP) {
+                this.search = true;
+                this.populate();
+
+                if (this.searchEdit.text == "" && this.selectedNode) {
+                        this.hierList.selectItemByID(this.selectedNode.id.toString(), true);    //maintains selected item after search is cancelled
+                }
+
+            }
+        });
+
     }
 
     handleSceneEditNodeAdded(ev: Editor.SceneEditNodeAddedEvent) {
@@ -260,6 +282,25 @@ class HierarchyFrame extends Atomic.UIWidget {
 
     }
 
+    // Searches folders within folders recursively
+    searchHierarchyFolder(asset: Atomic.Node, parentID: number, searchText: string) {
+
+        for (var i = 0; i < asset.getNumChildren(false); i++) {
+
+            var childAsset = asset.getChildAtIndex(i);
+
+            if (this.uiSearchBar.searchPopulate(searchText, childAsset.name)) {
+                this.recursiveAddNode(parentID, childAsset);
+            }
+
+            if (childAsset.getNumChildren(false) > 0) {
+                this.searchHierarchyFolder(childAsset, parentID, searchText);
+            }
+
+        }
+    }
+
+    // Populates frame when the search bar is used
     populate() {
 
         this.nodeIDToItemID = {};
@@ -272,10 +313,15 @@ class HierarchyFrame extends Atomic.UIWidget {
 
         this.nodeIDToItemID[this.scene.id] = parentID;
 
-        for (var i = 0; i < this.scene.getNumChildren(false); i++) {
+        var asset = this.scene;
 
-            this.recursiveAddNode(parentID, this.scene.getChildAtIndex(i));
+        if (this.searchEdit.text == "" || !this.search) {
+            for (var i = 0; i < this.scene.getNumChildren(false); i++) {
+                this.recursiveAddNode(parentID, this.scene.getChildAtIndex(i));
+            }
 
+        } else if (this.search) {
+            this.searchHierarchyFolder(asset, parentID, this.searchEdit.text);
         }
 
         this.hierList.rootList.value = -1;
@@ -312,6 +358,7 @@ class HierarchyFrame extends Atomic.UIWidget {
 
     handleSceneNodeSelected(ev: Editor.SceneNodeSelectedEvent) {
 
+        this.selectedNode = ev.node;    //Stores selection for when the search is cancelled
         this.hierList.selectItemByID(ev.node.id.toString(), ev.selected);
 
     }
@@ -387,6 +434,24 @@ class HierarchyFrame extends Atomic.UIWidget {
 
             }
 
+            // cancel search
+            if (id == "cancel search") {
+
+                if (!this.scene) {
+                    this.searchEdit.text = "";
+                    return;
+                }
+
+                if (!ToolCore.toolSystem.project) return;
+                this.searchEdit.text = "";
+                this.populate();
+                this.search = false;
+
+                if (this.selectedNode) {
+                    this.hierList.selectItemByID(this.selectedNode.id.toString(), true);    //maintains selected item after search is cancelled
+                }
+            }
+
 
         } else if (data.type == Atomic.UI_EVENT_TYPE_RIGHT_POINTER_UP) {
 

+ 56 - 7
Script/AtomicEditor/ui/frames/ProjectFrame.ts

@@ -25,6 +25,7 @@ import Editor = require("editor/Editor");
 import EditorEvents = require("editor/EditorEvents");
 import ProjectFrameMenu = require("./menus/ProjectFrameMenu");
 import MenuItemSources = require("./menus/MenuItemSources");
+import SearchBarFiltering = require("resources/SearchBarFiltering");
 
 class ProjectFrame extends ScriptWidget {
 
@@ -38,6 +39,9 @@ class ProjectFrame extends ScriptWidget {
     currentReferencedButton: Atomic.UIButton = null;
     containerScrollToHeight: number;
     containerScrollToHeightCounter: number;
+    uiSearchBar: SearchBarFiltering.UISearchBar = new SearchBarFiltering.UISearchBar();
+    search: boolean = false;
+    searchEdit: Atomic.UIEditField;
 
     constructor(parent: Atomic.UIWidget) {
 
@@ -49,6 +53,8 @@ class ProjectFrame extends ScriptWidget {
 
         this.gravity = Atomic.UI_GRAVITY_TOP_BOTTOM;
 
+        this.searchEdit = <Atomic.UIEditField>this.getWidget("filter");
+
         var projectviewcontainer = parent.getWidget("projectviewcontainer");
 
         projectviewcontainer.addChild(this);
@@ -82,6 +88,17 @@ class ProjectFrame extends ScriptWidget {
 
         });
 
+        // Activates search while user is typing in search widget
+        this.searchEdit.subscribeToEvent(this.searchEdit, "WidgetEvent", (data) => {
+
+            if (!ToolCore.toolSystem.project) return;
+
+            if (data.type == Atomic.UI_EVENT_TYPE_KEY_UP) {
+                this.search = true;
+                this.refreshContent(this.currentFolder);
+            }
+        });
+
     }
 
     handleAssetRenamed(ev: ToolCore.AssetRenamedEvent) {
@@ -182,6 +199,12 @@ class ProjectFrame extends ScriptWidget {
 
             var id = data.target.id;
 
+            // cancel search - goes back to the last selected folder
+            if (id == "cancel search") {
+                if (!ToolCore.toolSystem.project) return;
+                this.searchEdit.text = "";
+                this.refreshContent(this.currentFolder);
+            }
 
             if (this.menu.handlePopupMenu(data.target, data.refid))
                 return true;
@@ -418,6 +441,26 @@ class ProjectFrame extends ScriptWidget {
         this.folderList.scrollToSelectedItem();
     }
 
+    // Searches folders within folders recursively
+    searchProjectFolder(folderPath: string, container: Atomic.UILayout, searchText: string, db: ToolCore.AssetDatabase) {
+
+        if (folderPath == "")
+            return;
+
+        var assets = db.getFolderAssets(folderPath);
+
+        for (var i in assets) {
+
+            var childAsset = assets[i];
+
+            if (childAsset.isFolder()) {
+                this.searchProjectFolder(childAsset.path, container, searchText, db);
+            } else if (this.uiSearchBar.searchPopulate(searchText, childAsset.name + childAsset.extension)) {
+                container.addChild(this.createButtonLayout(childAsset));
+            }
+        }
+    }
+
     private refreshContent(folder: ToolCore.Asset) {
 
         if (this.currentFolder != folder) {
@@ -433,20 +476,26 @@ class ProjectFrame extends ScriptWidget {
         var container: Atomic.UILayout = <Atomic.UILayout>this.getWidget("contentcontainer");
         container.deleteAllChildren();
 
-        var assets = db.getFolderAssets(folder.path);
+        if (this.currentFolder != null) {
+            var assets = db.getFolderAssets(folder.path);
 
-        this.containerScrollToHeightCounter = 0;
+            this.containerScrollToHeightCounter = 0;
 
-        for (var i in assets) {
+            if (this.searchEdit.text == "" || !this.search) {
 
-            var asset = assets[i];
-            container.addChild(this.createButtonLayout(asset));
-            this.containerScrollToHeightCounter++;
+                for (var i in assets) {
+                    var asset = assets[i];
+                    container.addChild(this.createButtonLayout(asset));
+                    this.containerScrollToHeightCounter++;
+                }
+            } else if (this.search) {
+                this.searchProjectFolder(this.resourceFolder.path, container, this.searchEdit.text, db);
+            }
         }
 
         var containerScroll: Atomic.UIScrollContainer = <Atomic.UIScrollContainer>this.getWidget("contentcontainerscroll");
         containerScroll.scrollTo(0, this.containerScrollToHeight);
-
+        this.search = false;
     }
 
     private createButtonLayout(asset: ToolCore.Asset): Atomic.UILayout {

+ 59 - 36
Script/AtomicEditor/ui/modal/ResourceSelection.ts

@@ -23,19 +23,60 @@
 import EditorUI = require("../EditorUI");
 import EditorEvents = require("../../editor/EditorEvents");
 import ModalWindow = require("./ModalWindow");
+import SearchBarFiltering = require("resources/SearchBarFiltering");
 
 class ResourceSelection extends ModalWindow {
 
+    uiSearchBar: SearchBarFiltering.UISearchBar = new SearchBarFiltering.UISearchBar();
     folderList: Atomic.UIListView;
     callback: (returnObject: any, args: any) => void;
     args: any;
     resourceType: string;
+    importerType: string;
+    searchEdit: Atomic.UIEditField;
 
-    populate(importerType: string, resourceType: string) {
+
+    constructor(windowText: string, importerType: string, resourceType: string, callback: (asset: ToolCore.Asset, args: any) => void, args: any) {
+
+        super();
+
+        this.importerType = importerType;
+        this.resourceType = resourceType;
+        this.callback = callback;
+        this.args = args;
+        this.load("AtomicEditor/editor/ui/resourceselection.tb.txt");
+        this.searchEdit = <Atomic.UIEditField>this.getWidget("filter");
+        this.populate(importerType, resourceType, false);
+
+        this.text = windowText;
+        this.setSize(800, 600);
+        this.center();
+
+        //Activates the search as the user types
+        this.searchEdit.subscribeToEvent(this.searchEdit, "WidgetEvent", (data) => {
+
+            if (data.type == Atomic.UI_EVENT_TYPE_KEY_UP) {
+                this.populate(this.importerType, this.resourceType, true);
+            }
+        });
+
+    }
+
+    //adjusted to delete current folderlist and replace with search list if search is activated
+    populate(importerType: string, resourceType: string, search: boolean) {
 
         var db = ToolCore.assetDatabase;
         var assets = db.getAssetsByImporterType(importerType, resourceType);
 
+        if (search)
+            this.folderList.remove();
+
+        //Constructor Stuff
+        var foldercontainer = this.getWidget("resourcecontainer");
+        var folderList = this.folderList = new Atomic.UIListView();
+        folderList.rootList.id = "resourceList_";
+        foldercontainer.addChild(folderList);
+
         this.folderList.addRootItem("None", "", "");
 
         for (var i in assets) {
@@ -57,49 +98,28 @@ class ResourceSelection extends ModalWindow {
 
                     for (var i in animations) {
 
-                        this.folderList.addRootItem(animations[i].animationName + " : " + asset.name, "", animations[i].name);
-
+                        if (!search || this.searchEdit.text == "")
+                            this.folderList.addRootItem(animations[i].animationName + " : " + asset.name, "", animations[i].name);
+                        else if (search) {
+                            if (this.uiSearchBar.searchPopulate(this.searchEdit.text, animations[i].animationName + " : " + asset.name))
+                                this.folderList.addRootItem(animations[i].animationName + " : " + asset.name, "", animations[i].name);
+                        }
                     }
-
-
                 }
 
             } else {
 
-                this.folderList.addRootItem(asset.relativePath, "", asset.guid);
-
+                if (!search || this.searchEdit.text == "")
+                    this.folderList.addRootItem(asset.relativePath, "", asset.guid);
+                else if (search) {
+                    if (this.uiSearchBar.searchPopulate(this.searchEdit.text, asset.relativePath))
+                        this.folderList.addRootItem(asset.relativePath, "", asset.guid);
+                }
             }
-
         }
 
     }
 
-    constructor(windowText: string, importerType: string, resourceType: string, callback: (asset: ToolCore.Asset, args: any) => void, args: any) {
-
-        super();
-
-        this.resourceType = resourceType;
-        this.callback = callback;
-        this.args = args;
-
-        this.load("AtomicEditor/editor/ui/resourceselection.tb.txt");
-
-        var foldercontainer = this.getWidget("resourcecontainer");
-
-        var folderList = this.folderList = new Atomic.UIListView();
-
-        folderList.rootList.id = "resourceList_";
-
-        foldercontainer.addChild(folderList);
-
-        this.populate(importerType, resourceType);
-
-        this.text = windowText;
-        this.setSize(800, 600);
-        this.center();
-
-    }
-
     handleWidgetEvent(ev: Atomic.UIWidgetEvent) {
 
         if (ev.count >= 2) {
@@ -119,12 +139,15 @@ class ResourceSelection extends ModalWindow {
             }
 
             if (id == "cancel") {
-
                 this.hide();
-
                 return true;
             }
 
+            if (id == "cancel search") {
+                this.searchEdit.text = "";
+                this.populate(this.importerType, this.resourceType, true);
+            }
+
         }
 
     }

+ 1 - 0
Script/tsconfig.json

@@ -27,6 +27,7 @@
         "./AtomicEditor/main.ts",
         "./AtomicEditor/resources/ProjectTemplates.ts",
         "./AtomicEditor/resources/ResourceOps.ts",
+        "./AtomicEditor/resources/SearchBarFiltering.ts",
         "./AtomicEditor/ui/EditorStrings.ts",
         "./AtomicEditor/ui/EditorUI.ts",
         "./AtomicEditor/ui/frames/HierarchyFrame.ts",