浏览代码

Prompt and Finder Widgets, others

JimMarlowe 8 年之前
父节点
当前提交
446741ee80
共有 36 个文件被更改,包括 1427 次插入1 次删除
  1. 二进制
      Resources/EditorData/AtomicEditor/resources/default_skin/[email protected]
  2. 二进制
      Resources/EditorData/AtomicEditor/resources/default_skin/folder.png
  3. 二进制
      Resources/EditorData/AtomicEditor/resources/default_skin/[email protected]
  4. 二进制
      Resources/EditorData/AtomicEditor/resources/default_skin/folder_create.png
  5. 二进制
      Resources/EditorData/AtomicEditor/resources/default_skin/[email protected]
  6. 10 0
      Resources/EditorData/AtomicEditor/resources/default_skin/skin.tb.txt
  7. 二进制
      Resources/EditorData/AtomicEditor/resources/default_skin/star.png
  8. 二进制
      Resources/EditorData/AtomicEditor/resources/default_skin/[email protected]
  9. 二进制
      Resources/EditorData/AtomicEditor/resources/default_skin_light/[email protected]
  10. 二进制
      Resources/EditorData/AtomicEditor/resources/default_skin_light/folder.png
  11. 二进制
      Resources/EditorData/AtomicEditor/resources/default_skin_light/[email protected]
  12. 二进制
      Resources/EditorData/AtomicEditor/resources/default_skin_light/folder_create.png
  13. 二进制
      Resources/EditorData/AtomicEditor/resources/default_skin_light/[email protected]
  14. 10 0
      Resources/EditorData/AtomicEditor/resources/default_skin_light/skin.tb.txt
  15. 二进制
      Resources/EditorData/AtomicEditor/resources/default_skin_light/star.png
  16. 二进制
      Resources/EditorData/AtomicEditor/resources/default_skin_light/[email protected]
  17. 二进制
      Resources/PlayerData/DefaultUI/skin/[email protected]
  18. 二进制
      Resources/PlayerData/DefaultUI/skin/folder.png
  19. 二进制
      Resources/PlayerData/DefaultUI/skin/[email protected]
  20. 二进制
      Resources/PlayerData/DefaultUI/skin/folder_create.png
  21. 二进制
      Resources/PlayerData/DefaultUI/skin/[email protected]
  22. 10 0
      Resources/PlayerData/DefaultUI/skin/skin.tb.txt
  23. 二进制
      Resources/PlayerData/DefaultUI/skin/star.png
  24. 二进制
      Resources/PlayerData/DefaultUI/skin/[email protected]
  25. 2 1
      Script/Packages/Atomic/UI.json
  26. 18 0
      Source/Atomic/UI/UI.cpp
  27. 29 0
      Source/Atomic/UI/UIEditField.cpp
  28. 16 0
      Source/Atomic/UI/UIEvents.h
  29. 618 0
      Source/Atomic/UI/UIFinderWindow.cpp
  30. 86 0
      Source/Atomic/UI/UIFinderWindow.h
  31. 110 0
      Source/Atomic/UI/UIPromptWindow.cpp
  32. 55 0
      Source/Atomic/UI/UIPromptWindow.h
  33. 46 0
      Source/Atomic/UI/UISelectList.cpp
  34. 8 0
      Source/Atomic/UI/UISelectList.h
  35. 353 0
      Source/ThirdParty/TurboBadger/tb_atomic_widgets.cpp
  36. 56 0
      Source/ThirdParty/TurboBadger/tb_atomic_widgets.h

二进制
Resources/EditorData/AtomicEditor/resources/default_skin/[email protected]


二进制
Resources/EditorData/AtomicEditor/resources/default_skin/folder.png


二进制
Resources/EditorData/AtomicEditor/resources/default_skin/[email protected]


二进制
Resources/EditorData/AtomicEditor/resources/default_skin/folder_create.png


二进制
Resources/EditorData/AtomicEditor/resources/default_skin/[email protected]


+ 10 - 0
Resources/EditorData/AtomicEditor/resources/default_skin/skin.tb.txt

@@ -574,3 +574,13 @@ elements
 		content-ofs-x 1
 		content-ofs-y 1
 
+	# == FileFinder support 
+	FolderIcon
+		bitmap folder.png
+	FolderAdd
+		bitmap folder_create.png
+	BookmarkIcon
+		bitmap star.png
+	FolderUp
+		bitmap arrow_up.png
+

二进制
Resources/EditorData/AtomicEditor/resources/default_skin/star.png


二进制
Resources/EditorData/AtomicEditor/resources/default_skin/[email protected]


二进制
Resources/EditorData/AtomicEditor/resources/default_skin_light/[email protected]


二进制
Resources/EditorData/AtomicEditor/resources/default_skin_light/folder.png


二进制
Resources/EditorData/AtomicEditor/resources/default_skin_light/[email protected]


二进制
Resources/EditorData/AtomicEditor/resources/default_skin_light/folder_create.png


二进制
Resources/EditorData/AtomicEditor/resources/default_skin_light/[email protected]


+ 10 - 0
Resources/EditorData/AtomicEditor/resources/default_skin_light/skin.tb.txt

@@ -576,3 +576,13 @@ elements
 		content-ofs-x 1
 		content-ofs-y 1
 
+	# == FileFinder support 
+	FolderIcon
+		bitmap folder.png
+	FolderAdd
+		bitmap folder_create.png
+	BookmarkIcon
+		bitmap star.png
+	FolderUp
+		bitmap arrow_up.png
+

二进制
Resources/EditorData/AtomicEditor/resources/default_skin_light/star.png


二进制
Resources/EditorData/AtomicEditor/resources/default_skin_light/[email protected]


二进制
Resources/PlayerData/DefaultUI/skin/[email protected]


二进制
Resources/PlayerData/DefaultUI/skin/folder.png


二进制
Resources/PlayerData/DefaultUI/skin/[email protected]


二进制
Resources/PlayerData/DefaultUI/skin/folder_create.png


二进制
Resources/PlayerData/DefaultUI/skin/[email protected]


+ 10 - 0
Resources/PlayerData/DefaultUI/skin/skin.tb.txt

@@ -485,3 +485,13 @@ elements
 		content-ofs-x 1
 		content-ofs-y 1
 
+	# == FileFinder support 
+	FolderIcon
+		bitmap folder.png
+	FolderAdd
+		bitmap folder_create.png
+	BookmarkIcon
+		bitmap star.png
+	FolderUp
+		bitmap arrow_up.png
+

二进制
Resources/PlayerData/DefaultUI/skin/star.png


二进制
Resources/PlayerData/DefaultUI/skin/[email protected]


+ 2 - 1
Script/Packages/Atomic/UI.json

@@ -9,7 +9,8 @@
 								"UISelectList", "UIListView", "UIMessageWindow", "UILayoutParams", "UIFontDescription",
 								"UISkinImage", "UITabContainer", "UISceneView", "UIPreferredSize", "UIDragObject",
 								"UIContainer", "UISection", "UIInlineSelect", "UITextureWidget", "UIColorWidget", "UIColorWheel",
-								"UIScrollContainer", "UISeparator", "UIDimmer", "UISelectDropdown", "UISlider", "UIBargraph"],
+								"UIScrollContainer", "UISeparator", "UIDimmer", "UISelectDropdown", "UISlider", "UIBargraph",
+								"UIPromptWindow", "UIFinderWindow"],
 	"overloads" : {
 	},
 	"typescript_decl" : {

+ 18 - 0
Source/Atomic/UI/UI.cpp

@@ -88,6 +88,8 @@ using namespace tb;
 #include "UIColorWidget.h"
 #include "UIColorWheel.h"
 #include "UIBargraph.h"
+#include "UIPromptWindow.h"
+#include "UIFinderWindow.h"
 
 #include "SystemUI/SystemUI.h"
 #include "SystemUI/SystemUIEvents.h"
@@ -821,6 +823,22 @@ UIWidget* UI::WrapWidget(tb::TBWidget* widget)
         WrapWidget(nwidget, widget);
         return nwidget;
     }
+    
+    if (widget->IsOfType<TBPromptWindow>())
+    {
+        UIPromptWindow* nwidget = new UIPromptWindow(context_, NULL, "", false);
+        nwidget->SetWidget(widget);
+        WrapWidget(nwidget, widget);
+        return nwidget;
+    }
+
+    if (widget->IsOfType<TBFinderWindow>())
+    {
+        UIFinderWindow* nwidget = new UIFinderWindow(context_, NULL, "", false);
+        nwidget->SetWidget(widget);
+        WrapWidget(nwidget, widget);
+        return nwidget;
+    }
 
     if (widget->IsOfType<TBTabContainer>())
     {

+ 29 - 0
Source/Atomic/UI/UIEditField.cpp

@@ -28,6 +28,8 @@
 #include "UIEvents.h"
 #include "UIEditField.h"
 
+#include "../Atomic/Input/Input.h"
+
 using namespace tb;
 
 namespace Atomic
@@ -213,6 +215,17 @@ void UIEditField::OnFocusChanged(bool focused)
             if (!w->GetMultiline())
                 styleEdit->selection.SelectAll();
             firstFocusFlag_ = true;
+
+#if defined(ANDROID) || defined( __ANDROID__) || defined(IOS)
+
+            // click on field to gain focus and bring up the onscreen keyboard to edit
+            if ( !(w->GetReadOnly() || w->GetState(WIDGET_STATE_DISABLED)) )
+            { 
+                Input* input = GetSubsystem<Input>();
+                input->SetScreenKeyboardVisible(true); 
+            }
+#endif  
+
         }
         else
         {
@@ -247,6 +260,22 @@ bool UIEditField::OnEvent(const tb::TBWidgetEvent &ev)
                 styleEdit->selection.SelectAll();
             }
         }
+
+#if defined(ANDROID) || defined( __ANDROID__) || defined(IOS)
+
+        // triple click to get the onscreen keyboard, in case it is auto-focused
+        else if ( ev.count == 3 ) 
+        {
+            TBEditField* w = (TBEditField*) widget_;
+            if ( !(w->GetReadOnly() || w->GetState(WIDGET_STATE_DISABLED)) )
+            { 
+                Input* input = GetSubsystem<Input>();
+                input->SetScreenKeyboardVisible(true); 
+            }
+        }
+
+#endif
+
     }
 
     return UIWidget::OnEvent(ev);

+ 16 - 0
Source/Atomic/UI/UIEvents.h

@@ -144,4 +144,20 @@ ATOMIC_EVENT(E_UILISTVIEWSELECTIONCHANGED, UIListViewSelectionChanged)
     ATOMIC_PARAM(P_SELECTED, Selected);        // bool
 }
 
+/// event for PromptWindow
+ATOMIC_EVENT(E_UIPROMPTCOMPLETE, UIPromptComplete)
+{
+    ATOMIC_PARAM(P_TITLE, Title);  // string
+    ATOMIC_PARAM(P_REASON, Reason);  // string
+    ATOMIC_PARAM(P_SELECTED, Selected);  // string
+}
+
+/// event for FinderWindow
+ATOMIC_EVENT(E_UIFINDERCOMPLETE, UIFinderComplete)
+{
+    ATOMIC_PARAM(P_TITLE, Title);  // string
+    ATOMIC_PARAM(P_REASON, Reason);  // string
+    ATOMIC_PARAM(P_SELECTED, Selected);  // string
+}
+
 }

+ 618 - 0
Source/Atomic/UI/UIFinderWindow.cpp

@@ -0,0 +1,618 @@
+//
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// 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.
+//
+
+#include <TurboBadger/tb_widgets.h>
+#include <TurboBadger/tb_widgets_common.h>
+
+#include <Atomic/IO/FileSystem.h>
+#include <Atomic/IO/Log.h>
+#include <Atomic/IO/File.h>
+
+#include "UI.h"
+#include "UIEvents.h"
+#include "UIWindow.h"
+#include "UIEditField.h"
+#include "UISelectList.h"
+#include "UIPromptWindow.h"
+#include "UIFinderWindow.h"
+#include "UISelectItem.h"
+#include "UIMenuWindow.h"
+
+using namespace tb;
+
+namespace Atomic
+{
+
+/// finder window
+UIFinderWindow::UIFinderWindow(Context* context, UIWidget* target, const String& id, bool createWidget) : 
+    UIWindow(context, false),
+    finderMode(0),
+    currentPath(),
+    resultPath(),
+    bookmarks(),
+    bookmarkPaths(),
+    newBookmarkPtr(),
+    newFolderPtr(),
+    bookmarksDirty(0)
+{
+    if (createWidget)
+    {
+        widget_ = new TBFinderWindow(target ? target->GetInternalWidget() : 0, TBIDC(id.CString()));
+        widget_->SetDelegate(this);
+        GetSubsystem<UI>()->WrapWidget(this, widget_);
+    }
+}
+
+UIFinderWindow::~UIFinderWindow()
+{
+    SaveBookmarks();
+    
+    if ( !newFolderPtr.Expired())
+        newFolderPtr->UnsubscribeFromAllEvents();
+    if ( !newBookmarkPtr.Expired() )
+       newBookmarkPtr->UnsubscribeFromAllEvents();
+
+}
+
+void UIFinderWindow::FindFile(const String& title, const String& preset, int dimmer, int width, int height)
+{
+    if (!widget_)
+        return;
+
+    if ( ((TBFinderWindow*)widget_)->Show(title.CString(), preset.CString(), dimmer, width, height) )
+    {
+        CreateBookmarks();
+        PresetCurrentPath(preset);
+        UpdateUiPath();
+        UpdateUiList();
+        UpdateUiResult();
+    }
+}
+
+void UIFinderWindow::FindPath(const String& title, const String& preset, int dimmer, int width, int height)
+{
+    if (!widget_)
+        return;
+
+    finderMode = 1;
+    
+    if ( ((TBFinderWindow*)widget_)->Show(title.CString(), preset.CString(), dimmer, width, height) )
+    {
+        UIWidget *reswid = GetResultWidget();
+        if (reswid) reswid->SetVisibility( UI_WIDGET_VISIBILITY_INVISIBLE );
+        CreateBookmarks();
+        PresetCurrentPath(preset);
+        UpdateUiPath();
+        UpdateUiList();
+        UpdateUiResult();
+    }
+}
+
+bool UIFinderWindow::OnEvent(const tb::TBWidgetEvent &ev)
+{
+
+    if ( ev.type == EVENT_TYPE_CHANGED && ev.target && ((uint32)ev.target->GetID()) == 1 ) 
+    {
+        FileSystem* filesystem = GetSubsystem<FileSystem>();
+        UIWidget *pathwidget = GetPathWidget();  // paste or type in currentpath widget
+        if(pathwidget)
+        {
+            if ( filesystem->DirExists (pathwidget->GetText())) 
+            {
+                if ( pathwidget->GetText() != currentPath ) 
+                {
+                    resultPath = "";
+                    SetCurrentPath (pathwidget->GetText());
+                    UpdateUiList();
+                    UpdateUiResult();
+                }
+            }
+        }
+        return true;
+    }
+
+   if ( ev.type == EVENT_TYPE_POINTER_UP && ev.target && ((uint32)ev.target->GetID()) == 2 ) // go up
+    {
+        GoFolderUp();
+        return true;
+    }
+
+    if ( ev.type == EVENT_TYPE_POINTER_UP && ev.target && ((uint32)ev.target->GetID()) == 3 ) // add bookmark request
+    {
+        // this check is necessary because you can kill the bookmark window with the "X" and
+        // this kills the UI but not the newBookmarkPtr to it, and it doesnt get cleaned up properly.
+        if ( newBookmarkPtr.NotNull() )
+        {
+            newBookmarkPtr->UnsubscribeFromAllEvents();
+            newBookmarkPtr.Reset();
+        }
+        newBookmarkPtr = new UIPromptWindow(context_, this, "createbookmark",  true);
+        SubscribeToEvent(newBookmarkPtr, E_UIPROMPTCOMPLETE, ATOMIC_HANDLER(UIFinderWindow, HandleCreateBookmark ));
+
+        String prospect = "";
+        char delim = '/';
+        Vector <String> tokens = currentPath.Split(delim, false);
+        prospect = tokens[ tokens.Size()-1 ]; // get the last folder name as preset
+    
+        newBookmarkPtr->Show("Create New Bookmark", "Enter a name for the new bookmark", prospect );
+        return true;
+    }
+
+    if ( ev.type == EVENT_TYPE_POINTER_UP && ev.target && ((uint32)ev.target->GetID()) == 4 ) // add folder request
+    {
+        if (newFolderPtr.NotNull())
+        {
+            newFolderPtr->UnsubscribeFromAllEvents();
+            newFolderPtr.Reset();
+        }
+        newFolderPtr = new UIPromptWindow(context_, this, "createfolder",  true);
+        SubscribeToEvent(newFolderPtr, E_UIPROMPTCOMPLETE, ATOMIC_HANDLER(UIFinderWindow, HandleCreateFolder));
+        newFolderPtr->Show("Create new folder", "Enter a name for the new folder", "" );
+        return true;
+    }
+
+    if ( ev.type == EVENT_TYPE_CLICK && ev.target && ((uint32)ev.target->GetID()) == 5 ) // clicked in bookmarks
+    {
+        UISelectList *bklist = static_cast<UISelectList *>(GetBookmarksWidget());
+        int selected = bklist->GetValue();
+        if ( selected >= 0 )
+        {
+            resultPath = "";  // were going back, give up file.
+            SetCurrentPath ( bookmarkPaths[selected]);
+            UpdateUiPath();
+            UpdateUiList();
+            UpdateUiResult();
+            bklist->SetValue(-1); // clear the select visuals
+        }
+        return true;
+    }
+    
+    if ( ev.type == EVENT_TYPE_CUSTOM && ev.target && ((uint32)ev.target->GetID()) == 5 ) // bookmarks TB context menu result
+    {
+        UI* ui = GetSubsystem<UI>();
+        if ( ev.special_key == tb::TB_KEY_DELETE ) // we wanna delete something
+        {
+            String myid;
+            ui->GetTBIDString(ev.target?((uint32)ev.target->GetID()) : 0, myid);
+            UISelectList *bklist = static_cast<UISelectList *>(GetBookmarksWidget());
+            if (bklist)
+            {
+                int myindex = bklist->FindId ( (uint32)ev.ref_id );
+                if ( myindex >= 0 )
+                {
+                    DeleteBookmark(myindex);
+                }
+            }
+        }
+        return true;
+    }
+
+    if ( ev.type == EVENT_TYPE_CLICK && ev.target && ((uint32)ev.target->GetID()) == 6 ) // clicked dirfiles list
+    {
+        UISelectList *filelist = static_cast<UISelectList *>(GetFilesWidget());
+        ComposePath( filelist->GetSelectedItemString() );
+        return true;
+    }
+
+    if ( ev.type == EVENT_TYPE_CLICK && (ev.ref_id == 8 || ev.ref_id == 9) ) // clicked 8 = ok button, 9 = cancel button
+    {
+        UI* ui = GetSubsystem<UI>();
+        VariantMap eventData;
+
+        String title = "FinderWindow";
+        TBStr tbtext;
+        if(  widget_ && (TBWindow*)widget_->GetText(tbtext) )
+            title = tbtext.CStr();
+
+        eventData[UIFinderComplete::P_TITLE] = title;
+        eventData[UIFinderComplete::P_SELECTED] = "";
+        eventData[UIFinderComplete::P_REASON] = "CANCEL";
+
+        if (ev.ref_id == 8) // ok button was pressed, otherwise it was cancel button
+        {
+            eventData[UIFinderComplete::P_REASON] = "OK";
+            if ( finderMode == 0 ) // finding a file
+            {   // get from widget, in case the user had been typing.
+                UIWidget *ewidget = GetResultWidget(); 
+                if( ewidget) eventData[UIFinderComplete::P_SELECTED] = ewidget->GetText();
+            }
+            else  // finding a folder
+            {
+                UIWidget *cwidget = GetPathWidget();
+                if( cwidget) eventData[UIFinderComplete::P_SELECTED] = cwidget->GetText();
+            }
+        }
+
+        ConvertEvent(this, ui->WrapWidget(ev.target), ev, eventData);
+        SendEvent(E_UIFINDERCOMPLETE, eventData);
+
+        if (eventData[WidgetEvent::P_HANDLED].GetBool())
+           return true;
+    }
+
+    return UIWindow::OnEvent(ev);
+}
+
+void UIFinderWindow::HandleCreateBookmark(StringHash eventType, VariantMap& eventData)
+{
+    String Title = eventData["Title"].GetString();
+    String Reason = eventData["Reason"].GetString();
+    String Selected = eventData["Selected"].GetString();
+    if( Reason == "OK" )
+        CreateBookmark( Selected, currentPath );
+    if (newBookmarkPtr)
+    {
+        newBookmarkPtr->UnsubscribeFromAllEvents();
+        newBookmarkPtr.Reset();
+    }
+}
+
+void UIFinderWindow::HandleCreateFolder(StringHash eventType, VariantMap& eventData)
+{
+    String Title = eventData["Title"].GetString();
+    String Reason = eventData["Reason"].GetString();
+    String Selected = eventData["Selected"].GetString();
+    if( Reason == "OK" )
+        CreateFolder( Selected); 
+    if (newFolderPtr)
+    {
+         newFolderPtr->UnsubscribeFromAllEvents();
+         newFolderPtr.Reset();
+    }
+}
+
+UIWidget* UIFinderWindow::GetWindowWidget()
+{
+    if (!widget_)
+        return 0;
+    UI* ui = GetSubsystem<UI>();
+    return ui->WrapWidget(widget_);
+}
+
+UIWidget* UIFinderWindow::GetPathWidget()
+{
+    if (!widget_)
+        return 0;
+    TBWidget* child = (TBWidget*) widget_->GetWidgetByIDAndType<TBEditField>(1);
+    if (!child)
+        return 0;
+    UI* ui = GetSubsystem<UI>();
+    return ui->WrapWidget(child);
+}
+
+UIWidget* UIFinderWindow::GetResultWidget()
+{
+    if (!widget_)
+        return 0;
+    TBWidget* child = (TBWidget*)widget_->GetWidgetByIDAndType<TBEditField>(7);
+    if (!child)
+        return 0;
+    UI* ui = GetSubsystem<UI>();
+    return ui->WrapWidget(child);
+}
+
+UIWidget* UIFinderWindow::GetBookmarksWidget()
+{
+    if (!widget_)
+        return 0;
+    TBWidget* child = (TBWidget*)widget_->GetWidgetByIDAndType<TBSelectList>(5);
+    if (!child)
+        return 0;
+    UI* ui = GetSubsystem<UI>();
+    return ui->WrapWidget(child);
+}
+
+UIWidget* UIFinderWindow::GetFilesWidget()
+{
+    if (!widget_)
+        return 0;
+    TBWidget* child = (TBWidget*)widget_->GetWidgetByIDAndType<TBSelectList>(6);
+    if (!child)
+        return 0;
+    UI* ui = GetSubsystem<UI>();
+    return ui->WrapWidget(child);
+}
+
+// where the finder starts
+void UIFinderWindow::PresetCurrentPath( const String& preset ) 
+{
+    FileSystem* filesystem = GetSubsystem<FileSystem>();
+    if ( !preset.Empty() && filesystem->DirExists (preset) )
+        SetCurrentPath (preset);
+    else
+        SetCurrentPath ( filesystem->GetUserDocumentsDir() );
+}
+
+// set the current path value
+void UIFinderWindow::SetCurrentPath( const String& string ) 
+{ 
+    currentPath = string;
+}
+
+//using the list, jam things together, we'll either get another path or a file.
+void UIFinderWindow::ComposePath (const String& string )
+{
+    String prospect = currentPath + string;
+
+    FileSystem* filesystem = GetSubsystem<FileSystem>();
+ 
+    if ( !filesystem->FileExists ( prospect ) )  // its a dir, feel the joy
+    {
+        SetCurrentPath( prospect + "/" );  // add the trailing slash, OR ELSE
+        UpdateUiPath();
+        UpdateUiList();
+        UpdateUiResult();
+    }
+    else  // its a file
+    {
+        resultPath = prospect;
+        UpdateUiResult();
+    }
+}
+
+// create the list of bookmarks ... can be different per platform
+void UIFinderWindow::CreateBookmarks()
+{
+    FileSystem* filesystem = GetSubsystem<FileSystem>();
+    String basepath = filesystem->GetUserDocumentsDir();
+    UISelectList *bklist = static_cast<UISelectList *>(GetBookmarksWidget());
+    
+#if defined(ATOMIC_PLATFORM_LINUX) 
+    if ( filesystem->DirExists ( basepath )) CreateBookmark ( "Home", basepath );
+    if ( filesystem->DirExists ( basepath + "Documents")) CreateBookmark ( "Documents", basepath + "Documents/" );
+    if ( filesystem->DirExists ( basepath + "Music")) CreateBookmark ( "Music", basepath + "Music/" );
+    if ( filesystem->DirExists ( basepath + "Pictures" )) CreateBookmark ( "Pictures", basepath + "Pictures/" );
+    if ( filesystem->DirExists ( basepath + "Videos" )) CreateBookmark ( "Videos", basepath + "Videos/" );
+    if ( filesystem->DirExists ( basepath + "Downloads")) CreateBookmark ( "Downloads", basepath + "Downloads/" );
+#elif defined(ATOMIC_PLATFORM_WINDOWS)
+    if ( filesystem->DirExists ( basepath )) CreateBookmark ( "Home", basepath );
+    if ( filesystem->DirExists ( basepath + "Desktop")) CreateBookmark ( "Desktop", basepath + "Desktop/" );
+    if ( filesystem->DirExists ( basepath + "Documents")) CreateBookmark ( "Documents", basepath + "Documents/" );
+    if ( filesystem->DirExists ( basepath + "Downloads")) CreateBookmark ( "Downloads", basepath + "Downloads/" );
+    if ( filesystem->DirExists ( basepath + "Music")) CreateBookmark ( "Music", basepath + "Music/" );
+    if ( filesystem->DirExists ( basepath + "Pictures" )) CreateBookmark ( "Pictures", basepath + "Pictures/" );
+    if ( filesystem->DirExists ( basepath + "Videos" )) CreateBookmark ( "Videos", basepath + "Videos/" );
+#elif defined(ATOMIC_PLATFORM_OSX)
+    if ( filesystem->DirExists ( basepath )) CreateBookmark ( "Home", basepath );
+    if ( filesystem->DirExists ( basepath + "Documents")) CreateBookmark ( "Documents", basepath + "Documents/" );
+    if ( filesystem->DirExists ( basepath + "Downloads")) CreateBookmark ( "Downloads", basepath + "Downloads/" );
+    if ( filesystem->DirExists ( basepath + "Public")) CreateBookmark ( "Public", basepath + "Public/" );
+#else  // android, ios, web?
+    if ( filesystem->DirExists ( basepath )) CreateBookmark ( "Home", basepath );
+#endif
+    CreateBookmark ( "-", basepath );  // create separator!
+    LoadBookmarks();
+    bklist->SetValue(-1); // fix the selection and scrolling
+
+}
+
+// go up tree 1 folder
+void UIFinderWindow::GoFolderUp()
+{
+    String prospect = "";
+    char delim = '/';
+    Vector <String> tokens = currentPath.Split(delim, false);
+ 
+    if ( tokens.Size() == 0 ) // were at the top
+        prospect = "/";
+    else
+    {
+        int nn = 0;
+        for ( nn=0; nn<tokens.Size()-1; nn++ )
+        {
+            prospect += delim;
+            prospect += tokens[nn];
+        }
+        prospect += delim;
+    }
+
+    if ( prospect != currentPath ) 
+    {
+        resultPath = "";
+        SetCurrentPath (prospect);
+        UpdateUiPath();
+        UpdateUiList();
+        UpdateUiResult();
+    }
+}
+
+// move current path to widget
+void UIFinderWindow::UpdateUiPath ()
+{
+    UIWidget *pathwidget = GetPathWidget();
+    if(pathwidget)
+    {
+        if ( pathwidget->GetText() != currentPath )
+            pathwidget->SetText(currentPath);
+    }
+}
+
+// move result path to widget
+void UIFinderWindow::UpdateUiResult ()
+{
+    UIWidget *resultwidget = GetResultWidget();
+    if( resultwidget)
+    {
+        if ( resultwidget->GetText() != resultPath )
+            resultwidget->SetText(resultPath);
+    }
+}
+
+#include "Container/Sort.h"
+
+// a very local compare function
+bool CompareStrs(const String& a, const String &b)  
+{
+    return a < b;
+}
+
+// move folder contents into list
+void UIFinderWindow::UpdateUiList()
+{
+    FileSystem* filesystem = GetSubsystem<FileSystem>();
+    UISelectList *filelist = static_cast<UISelectList *>(GetFilesWidget());
+    UISelectItemSource *fileSource = new UISelectItemSource(context_);
+    
+    if ( filesystem->DirExists (currentPath ) )
+    { 
+        Vector <String> mydirs;
+        int nn = 0;
+        filesystem->ScanDir (mydirs,currentPath, "*", SCAN_DIRS, false );
+        Sort(mydirs.Begin(), mydirs.End(), CompareStrs); // sort them
+        for ( nn=0; nn<mydirs.Size(); nn++ ) 
+        {
+            if ( mydirs[nn] == "." ) continue;  
+            if ( mydirs[nn] == ".." ) continue;
+            String idz = "DIR"+ String(nn);
+            fileSource->AddItem( new UISelectItem(context_, mydirs[nn], idz, "FolderIcon" ));
+        }
+        Vector <String> myfiles;
+        filesystem->ScanDir (myfiles, currentPath, "*", SCAN_FILES, false );
+        Sort(myfiles.Begin(), myfiles.End(), CompareStrs);
+        for ( nn=0; nn<myfiles.Size(); nn++ ) 
+        {
+            String idz = "FIL"+ String(nn);
+            fileSource->AddItem(new UISelectItem( context_, myfiles[nn], idz));
+        }
+    }
+    filelist->SetValue(-1);
+    filelist->SetSource(fileSource);
+}
+
+// utility to add a folder in current path
+void UIFinderWindow::CreateFolder( const String& string )
+{
+    FileSystem* filesystem = GetSubsystem<FileSystem>();
+    if ( filesystem->CreateDir( currentPath + string ) )
+    {
+        UpdateUiList();
+    }
+}
+
+// utility to add a bookmark from the current path
+void UIFinderWindow::CreateBookmark ( const String& bkname, const String&  bkpath ) 
+{
+    UISelectList *bklist = static_cast<UISelectList *>(GetBookmarksWidget());
+    if (bklist ) 
+    {
+        int inspos = bklist->GetNumItems();
+        if ( inspos < 0 ) inspos = 0;
+        String idz = "BKM" + String(inspos);
+        if ( bklist->AddItem ( inspos, bkname, idz ) )
+        {
+            bookmarks.Push(bkname);
+            bookmarkPaths.Push (bkpath);
+            bookmarksDirty = 1;
+        }
+    }
+}
+
+// removes a bookmark based upon its index in the bookmark list
+void UIFinderWindow::DeleteBookmark ( int bkindex )
+{
+    UISelectList *bklist = static_cast<UISelectList *>(GetBookmarksWidget());
+    if (bklist && bkindex >= 0) 
+    {
+        bklist->DeleteItem(bkindex);
+        bookmarks.Erase(bkindex,1);
+        bookmarkPaths.Erase(bkindex,1);
+        resultPath = "";
+        SetCurrentPath (bookmarkPaths[0]);
+        UpdateUiPath();
+        UpdateUiList();
+        UpdateUiResult();
+        bklist->SetValue(-1);
+        bookmarksDirty = 1;
+    }
+}
+
+void UIFinderWindow::LoadBookmarks()
+{
+    FileSystem* filesystem = GetSubsystem<FileSystem>();
+    String bkdata = "";
+    String bkpath = "";
+    
+#if defined(ANDROID) || defined( __ANDROID__) || defined(IOS)
+    bkpath = filesystem->GetUserDocumentsDir(); // somewhere writable on mobile
+#else
+    bkpath = filesystem->GetAppPreferencesDir("AtomicGameEngine", "Bookmarks"); // desktop systems
+#endif
+    
+    bkpath += "/Bookmarks.txt";
+    if ( filesystem->FileExists ( bkpath ) )
+    {
+        File *fp = new File (context_, bkpath, FILE_READ);
+        if(fp->IsOpen())
+        {
+            fp->ReadText(bkdata);
+            fp->Close();
+        }
+    }
+    char delim = '\n';
+    Vector <String> tokens = bkdata.Split(delim, false);
+    int nn=0;
+    for ( nn=0; nn< tokens.Size(); nn+=2)
+    {
+        CreateBookmark ( tokens[nn], tokens[nn+1] );
+    }
+    bookmarksDirty = 0;
+
+}
+
+void UIFinderWindow::SaveBookmarks()
+{
+    if ( bookmarksDirty > 0 )
+    {
+        FileSystem* filesystem = GetSubsystem<FileSystem>();
+        String bkpath = "";
+
+#if defined(ANDROID) || defined( __ANDROID__) || defined(IOS)
+        bkpath = filesystem->GetUserDocumentsDir();
+#else
+        bkpath = filesystem->GetAppPreferencesDir("AtomicGameEngine", "Bookmarks");
+#endif
+
+        bkpath += "/Bookmarks.txt";
+        String bkdata = "";
+        int nn=0, sep=-1;
+        for ( nn = 0; nn<bookmarks.Size(); nn++)
+        {
+            if ( sep > 0 )
+            {
+                bkdata += bookmarks[nn];
+                bkdata += "\n";
+                bkdata += bookmarkPaths[nn];
+                bkdata += "\n";
+            }
+            if ( bookmarks[nn] == "-" ) 
+                sep = nn;
+        }
+        File *fp = new File (context_, bkpath, FILE_WRITE);
+        if(fp->IsOpen())
+        {
+            fp->Write ((const void *)bkdata.CString(), bkdata.Length() );
+            fp->Close();
+        }
+    }
+}
+
+}

+ 86 - 0
Source/Atomic/UI/UIFinderWindow.h

@@ -0,0 +1,86 @@
+//
+// Copyright (c) 2014-2017, THUNDERBEAST GAMES LLC All rights reserved
+//
+// 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.
+//
+
+#pragma once
+
+#include <TurboBadger/tb_atomic_widgets.h>
+
+#include "UIWindow.h"
+
+namespace Atomic
+{
+
+class UIPromptWindow;
+
+class UIFinderWindow : public UIWindow
+{
+    ATOMIC_OBJECT(UIFinderWindow, UIWindow)
+
+public:
+
+    UIFinderWindow(Context* context, UIWidget* target, const String& id, bool createWidget = true);
+    virtual ~UIFinderWindow();
+
+    void FindFile(const String& title, const String& preset, int dimmer=0, int width=0, int height=0);
+    void FindPath(const String& title, const String& preset, int dimmer=0, int width=0, int height=0);
+
+protected:
+
+    void HandleCreateBookmark(StringHash eventType, VariantMap& eventData); /// event handler for bookmark creation
+    void HandleCreateFolder(StringHash eventType, VariantMap& eventData); /// event handler for folder creation
+    virtual bool OnEvent(const tb::TBWidgetEvent &ev); /// general event handler
+    UIWidget* GetWindowWidget(); /// get internal widgets
+    UIWidget* GetPathWidget();
+    UIWidget* GetResultWidget();
+    UIWidget* GetBookmarksWidget();
+    UIWidget* GetFilesWidget();
+   
+private:
+
+    void PresetCurrentPath( const String& preset );  /// location where the finder starts
+    void SetCurrentPath( const String& string );  /// set the current path value
+    void ComposePath (const String& string );  /// using the list, jam things together, we'll either get another path or a file.
+    void CreateBookmarks(); /// create the list of bookmarks, per platform
+    void GoFolderUp(); /// go up one folder
+    void UpdateUiPath ();  /// move current path to widget
+    void UpdateUiResult (); /// move result path to widget
+    void UpdateUiList(); /// move folder contents into list
+    void CreateFolder( const String& string ); /// utility to add a folder in current path
+    void CreateBookmark ( const String& bkname, const String&  bkpath ); /// utility to add a bookmark from the current path
+    void DeleteBookmark ( int bkindex ); /// removes a bookmark based upon its index in the bookmark list
+    void LoadBookmarks();  /// load user created bookmarks from file
+    void SaveBookmarks();  /// save user created bookmarks to file
+
+    int finderMode;  /// finder mode, 0 = find file, 1 = find folder
+    String currentPath; /// current path for the finder
+    String resultPath;  /// result file for finder
+    Vector <String> bookmarks;  /// array of names of bookmarks
+    Vector <String> bookmarkPaths; /// array of bookmark paths
+    WeakPtr<UIPromptWindow> newBookmarkPtr;  /// pointer for bookmark prompt window
+    WeakPtr<UIPromptWindow> newFolderPtr; /// pointer for new folder prompt window
+    int bookmarksDirty;  /// flag to track if the bookmark array has changed
+
+};
+
+
+
+}

+ 110 - 0
Source/Atomic/UI/UIPromptWindow.cpp

@@ -0,0 +1,110 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+//
+// 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.
+//
+
+#include <TurboBadger/tb_widgets.h>
+#include <TurboBadger/tb_widgets_common.h>
+
+#include <Atomic/IO/FileSystem.h>
+#include <Atomic/IO/Log.h>
+
+#include "UI.h"
+#include "UIEvents.h"
+#include "UIWindow.h"
+#include "UIEditField.h"
+#include "UISelectList.h"
+#include "UIPromptWindow.h"
+
+using namespace tb;
+
+namespace Atomic
+{
+
+UIPromptWindow::UIPromptWindow(Context* context, UIWidget* target, const String& id, bool createWidget) : UIWindow(context, false)
+{
+    if (createWidget)
+    {
+        widget_ = new TBPromptWindow(target ? target->GetInternalWidget() : 0, TBIDC(id.CString()));
+        widget_->SetDelegate(this);
+        GetSubsystem<UI>()->WrapWidget(this, widget_);
+    }
+}
+
+UIPromptWindow::~UIPromptWindow()
+{
+}
+
+void UIPromptWindow::Show(const String &title, const String &message, const String &preset, int dimmer, int width, int height)
+{
+    if (!widget_)
+        return;
+
+    ((TBPromptWindow*)widget_)->Show(title.CString(), message.CString(), preset.CString(), dimmer, width, height);
+
+}
+
+bool UIPromptWindow::OnEvent(const tb::TBWidgetEvent &ev)
+{
+ 
+    if ( ev.type == EVENT_TYPE_CLICK 
+        && ( ev.ref_id == TBID("TBPromptWindow.ok") || ev.ref_id == TBID("TBPromptWindow.cancel") ))
+    {
+        UI* ui = GetSubsystem<UI>();
+        VariantMap eventData;
+
+        String title = "PromptWindow";
+        TBStr tbtext;
+        if(  widget_ && (TBWindow*)widget_->GetText(tbtext) )
+            title = tbtext.CStr();
+
+        eventData[UIPromptComplete::P_TITLE] = title;
+        eventData[UIPromptComplete::P_SELECTED] = "";
+        eventData[UIPromptComplete::P_REASON] = "CANCEL";
+
+        if ( ev.ref_id == TBID("TBPromptWindow.ok") )
+        {
+            eventData[UIPromptComplete::P_REASON] = "OK";
+            UIWidget *ewidget = GetEditWidget();
+            if( ewidget) 
+                eventData[UIPromptComplete::P_SELECTED] = ewidget->GetText();
+        }
+        ConvertEvent(this, ui->WrapWidget(ev.target), ev, eventData);
+        SendEvent(E_UIPROMPTCOMPLETE, eventData);
+
+        if (eventData[WidgetEvent::P_HANDLED].GetBool())
+           return true;
+    }
+
+    return UIWindow::OnEvent(ev);
+}
+
+UIWidget* UIPromptWindow::GetEditWidget()
+{
+    if (!widget_)
+        return 0;
+    TBWidget* child = (TBWidget*)widget_->GetWidgetByIDAndType<TBEditField>(2);
+    if (!child)
+        return 0;
+    UI* ui = GetSubsystem<UI>();
+    return ui->WrapWidget(child);
+}
+
+}

+ 55 - 0
Source/Atomic/UI/UIPromptWindow.h

@@ -0,0 +1,55 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+//
+// 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.
+//
+
+#pragma once
+
+
+#include <TurboBadger/tb_atomic_widgets.h>
+
+#include "UIWindow.h"
+
+namespace Atomic
+{
+
+class UIPromptWindow : public UIWindow
+{
+    ATOMIC_OBJECT(UIPromptWindow, UIWindow)
+
+public:
+
+    UIPromptWindow(Context* context, UIWidget* target, const String& id, bool createWidget = true);
+    virtual ~UIPromptWindow();
+
+    void Show(const String& title, const String& message, const String& preset, int dimmer=0, int width=0, int height=0);
+
+protected:
+
+    UIWidget* GetEditWidget();
+
+    virtual bool OnEvent(const tb::TBWidgetEvent &ev);
+
+private:
+
+};
+
+
+}

+ 46 - 0
Source/Atomic/UI/UISelectList.cpp

@@ -173,6 +173,52 @@ String UISelectList::GetItemString(int index)
     return String::EMPTY;
 }
 
+/// Add a new item at the given index.
+bool UISelectList::AddItem(int index, const String& str, const String& id )
+{
+    if ( index < 0 ) return false; // dont let the UI crash.
+    TBSelectItemSourceList<TBGenericStringItem> *tbsource = (TBSelectItemSourceList<TBGenericStringItem> *)((TBSelectList*)widget_)->GetSource();
+    if ( tbsource )
+    {
+      return tbsource->AddItem ( new TBGenericStringItem(str.CString(), TBID(id.CString())), index);
+    }
+    return false;
+}
+
+/// Delete the item at the given index.
+void UISelectList::DeleteItem(int index)
+{
+    TBSelectItemSourceList<TBGenericStringItem> *tbsource = (TBSelectItemSourceList<TBGenericStringItem> *)((TBSelectList*)widget_)->GetSource();
+    if ( tbsource && index >= 0 && index < tbsource->GetNumItems() )
+    {
+       tbsource->DeleteItem(index);
+    }
+}
+
+/// Delete all items. 
+void UISelectList::DeleteAllItems()
+{
+    TBSelectItemSourceList<TBGenericStringItem> *tbsource = (TBSelectItemSourceList<TBGenericStringItem> *)((TBSelectList*)widget_)->GetSource();
+    if ( tbsource  )
+    {
+       tbsource->DeleteAllItems();
+    }
+}
+
+/// Searches the items for the id as a number, returns index, -1 if not found 
+int UISelectList::FindId ( int idnum )
+{
+    uint32 myid = (uint32)idnum;
+    TBSelectItemSource *tbsource = (TBSelectItemSource*)((TBSelectList*)widget_)->GetSource();
+    int nn = 0;
+    for( nn=0; nn < tbsource->GetNumItems(); nn++ )
+    {
+       if ( tbsource->GetItemID(nn) == myid ) 
+            return nn;
+    }
+    return -1;
+}
+
 String UISelectList::GetHoverItemID()
 {
     if (!widget_)

+ 8 - 0
Source/Atomic/UI/UISelectList.h

@@ -74,6 +74,14 @@ public:
     String GetItemString(int index);
     /// Returns the string of the selected item
     String GetSelectedItemString();
+    /// Add a new item at the given index
+    bool AddItem(int index, const String& str, const String& id = String::EMPTY );
+    /// Delete the item at the given index.
+    void DeleteItem(int index);
+    /// Delete all items.
+    void DeleteAllItems();
+    /// Searches the items for the id as a number, returns index, -1 if not found 
+    int FindId ( int idnum );
 
 protected:
 

+ 353 - 0
Source/ThirdParty/TurboBadger/tb_atomic_widgets.cpp

@@ -29,6 +29,8 @@
 #include "tb_node_tree.h"
 #include "tb_system.h"
 #include "tb_atomic_widgets.h"
+#include "tb_editfield.h"
+#include "tb_menu_window.h"
 #include <math.h>
 
 namespace tb {
@@ -283,4 +285,355 @@ void TBBarGraph::SetAxis(AXIS axis)
 
 TB_WIDGET_FACTORY(TBBarGraph, TBValue::TYPE_FLOAT, WIDGET_Z_TOP) {}
 
+
+// == TBPromptWindow =======================================
+
+
+TBPromptWindow::TBPromptWindow(TBWidget *target, TBID id)
+    : m_target(target)
+{
+    TBWidgetListener::AddGlobalListener(this);
+    SetID(id);
+}
+
+TBPromptWindow::~TBPromptWindow()
+{
+    TBWidgetListener::RemoveGlobalListener(this);
+    if (TBWidget *dimmer = m_dimmer.Get())
+    {
+        dimmer->GetParent()->RemoveChild(dimmer);
+        delete dimmer;
+    }
+}
+
+bool TBPromptWindow::Show(const char *title, const char *message,
+    const char *preset, int dimmer,
+    int width, int height)
+{
+    TBWidget *target = m_target.Get();
+    if (!target)
+        return false;
+
+    TBWidget *root = target->GetParentRoot();
+
+    const char *source = "TBLayout: axis: y, distribution: gravity, position: left\n"
+    "	TBLayout: axis: y, distribution: gravity, distribution-position: left\n"
+    "		TBTextField: id: 1, gravity: left right\n"
+    "			font: size: 14dp\n"
+    "		TBEditField: id: 2, multiline: 0, styling: 0, adapt-to-content: 0, gravity: left right\n"
+     "			font: size: 14dp\n"
+   "	TBSeparator: gravity: left right\n"
+    "	TBLayout: distribution: gravity\n"
+    "		TBButton: text: \"  OK  \", id: \"TBPromptWindow.ok\"\n"
+    "			font: size: 14dp\n"
+    "		TBButton: text: \"Cancel\", id: \"TBPromptWindow.cancel\"\n"
+    "			font: size: 14dp\n";
+
+    if (!g_widgets_reader->LoadData(GetContentRoot(), source))
+        return false;
+
+    SetText(title);
+
+    TBTextField *editfield = GetWidgetByIDAndType<TBTextField>(1);
+    editfield->SetText(message);
+    editfield->SetSkinBg("");
+
+    TBEditField *stringfield = GetWidgetByIDAndType<TBEditField>(2);
+    if (preset) 
+        stringfield->SetText(preset);
+
+    TBRect rect;
+
+    // Size to fit content. This will use the default size of the textfield.
+    if (width == 0 || height == 0)
+    {
+        ResizeToFitContent();
+        rect = GetRect();
+    }
+    else
+    {
+        SetSize(width, height);
+        rect = GetRect();
+    }
+
+    // Create background dimmer
+    if (dimmer != 0)
+    {
+        if (TBDimmer *dimmer = new TBDimmer)
+        {
+            root->AddChild(dimmer);
+            m_dimmer.Set(dimmer);
+        }
+    }
+
+    // Center and size to the new height
+    TBRect bounds(0, 0, root->GetRect().w,  root->GetRect().h);
+    SetRect(rect.CenterIn(bounds).MoveIn(bounds).Clip(bounds));
+    root->AddChild(this);
+    return true;
+}
+
+bool TBPromptWindow::OnEvent(const TBWidgetEvent &ev)
+{
+    if (ev.type == EVENT_TYPE_CLICK && ev.target->IsOfType<TBButton>())
+    {
+        TBWidgetSafePointer this_widget(this);
+
+        // Invoke the click on the target
+        TBWidgetEvent target_ev(EVENT_TYPE_CLICK);
+        target_ev.ref_id = ev.target->GetID();
+        InvokeEvent(target_ev);
+
+        // If target got deleted, close
+        if (this_widget.Get())
+            Close();
+
+        return true;
+    }
+    else if (ev.type == EVENT_TYPE_KEY_DOWN && ev.special_key == TB_KEY_ESC)
+    {
+        TBWidgetEvent click_ev(EVENT_TYPE_CLICK);
+        m_close_button.InvokeEvent(click_ev);
+        return true;
+    }
+    return TBWindow::OnEvent(ev);
+}
+
+void TBPromptWindow::OnDie()
+{
+    if (TBWidget *dimmer = m_dimmer.Get())
+        dimmer->Die();
+}
+
+void TBPromptWindow::OnWidgetDelete(TBWidget *widget)
+{
+    // If the target widget is deleted, close!
+    if (!m_target.Get())
+        Close();
+}
+
+bool TBPromptWindow::OnWidgetDying(TBWidget *widget)
+{
+    // If the target widget or an ancestor of it is dying, close!
+    if (widget == m_target.Get() || widget->IsAncestorOf(m_target.Get()))
+        Close();
+    return false;
+}
+
+
+// == TBFinderWindow =======================================
+
+
+TBFinderWindow::TBFinderWindow(TBWidget *target, TBID id)
+    : m_target(target),
+    rightMenuParent(NULL),
+    rightMenuChild(NULL)
+{
+    TBWidgetListener::AddGlobalListener(this);
+    SetID(id);
+}
+
+TBFinderWindow::~TBFinderWindow()
+{
+    TBWidgetListener::RemoveGlobalListener(this);
+    if (TBWidget *dimmer = m_dimmer.Get())
+    {
+        dimmer->GetParent()->RemoveChild(dimmer);
+        delete dimmer;
+    }
+    rightMenuParent=NULL;
+    rightMenuChild=NULL;
+}
+
+
+bool TBFinderWindow::Show(const char *title,
+    const char *preset, int dimmer,
+    int width, int height)
+{
+    TBWidget *target = m_target.Get();
+    if (!target)
+        return false;
+
+    TBWidget *root = target->GetParentRoot();
+
+    const char *source = 
+    "TBLayout: axis: y, size: available, position: gravity, distribution: gravity\n"
+    "	lp: min-width: 512dp, min-height: 500dp\n"
+    "	TBLayout: distribution: gravity, distribution-position: left\n"
+    "		TBEditField: id: 1, multiline: 0, styling: 0, adapt-to-content: 0, gravity: left right\n"
+    "			tooltip current folder location\n"
+    "			font: size: 14dp\n"
+    "		TBButton\n"
+    "			lp: height: 28dp, width: 28dp\n"
+    "			skin TBButton.uniformflat\n"
+    "			TBSkinImage: skin: FolderUp, id: up_image\n"
+    "			id 2\n"
+    "			tooltip Go up one folder\n"
+    "		TBWidget\n"
+    "			lp: height: 28dp, width: 28dp\n"
+    "		TBButton\n"
+    "			lp: height: 28dp, width: 28dp\n"
+    "			skin TBButton.uniformflat\n"
+    "			TBSkinImage: skin: BookmarkIcon, id: book_image\n"
+    "			id 3\n"
+    "			tooltip Bookmark current folder\n"
+    "		TBButton\n"
+    "			lp: height: 28dp, width: 28dp\n"
+    "			skin TBButton.uniformflat\n"
+    "			TBSkinImage: skin: FolderAdd, id: create_image\n"
+    "			id 4\n"
+    "			tooltip Create a new folder \n"
+    "	TBLayout: distribution: gravity, distribution-position: left\n"
+    "		TBLayout: axis: x, distribution: gravity\n"
+    "			TBSelectList: id: 5, gravity: all\n"
+    "				lp: max-width: 160dp, min-width: 160dp\n"
+    "				font: size: 14dp\n"
+    "			TBSelectList: id: 6, gravity: all\n"
+    "				font: size: 14dp\n"
+    "	TBLayout: distribution: gravity\n"
+    "		TBEditField: id: 7, multiline: 0, styling: 0, adapt-to-content: 0, gravity: left right\n"
+    "			tooltip name of the wanted file\n"
+    "			font: size: 14dp\n"
+    "	TBLayout: distribution: gravity\n"
+    "		TBButton: text: \"  OK  \", id: 8\n"
+    "			font: size: 14dp\n"
+    "		TBButton: text: \"Cancel\", id: 9\n"
+    "			font: size: 14dp\n";
+
+    if (!g_widgets_reader->LoadData(GetContentRoot(), source))
+        return false;
+
+    SetText(title);
+    TBRect rect;
+
+    // Size to fit content. This will use the default size of the textfield.
+    if (width == 0 || height == 0)
+    {
+        ResizeToFitContent();
+        rect = GetRect();
+    }
+    else
+    {
+        SetSize(width, height);
+        rect = GetRect();
+    }
+
+    // Create background dimmer
+    if (dimmer != 0)
+    {
+        if (TBDimmer *dimmer = new TBDimmer)
+        {
+            root->AddChild(dimmer);
+            m_dimmer.Set(dimmer);
+        }
+    }
+
+    // Center and size to the new height
+    TBRect bounds(0, 0, root->GetRect().w,  root->GetRect().h);
+    SetRect(rect.CenterIn(bounds).MoveIn(bounds).Clip(bounds));
+    root->AddChild(this);
+    return true;
+}
+
+bool TBFinderWindow::OnEvent(const TBWidgetEvent &ev)
+{
+
+    if (ev.type == EVENT_TYPE_CLICK )
+    {
+        if (  ev.target->IsOfType<TBButton>())
+        {
+            TBWidgetSafePointer this_widget(this);
+
+            // Invoke the click on the target
+            TBWidgetEvent target_ev(EVENT_TYPE_CLICK);
+            target_ev.ref_id = ev.target->GetID();
+            InvokeEvent(target_ev);
+
+            // these are internal buttons that do not close the finder window!
+            bool isbuttons = (ev.target->GetID() == 2 || ev.target->GetID() == 3 || ev.target->GetID() == 4 );
+            // If target got deleted, close
+            if (this_widget.Get() && !isbuttons )
+                Close();
+            return true;
+        }
+        else if ( ev.target->GetID() == TBIDC("popupmenu"))
+        {
+            if (ev.ref_id == TBIDC("delete"))
+            {
+                // send EVENT_TYPE_CUSTOM, were gonna used cached information to send info how we got here
+                if(rightMenuParent)
+                {
+                    TBWidgetEvent custom_ev(EVENT_TYPE_CUSTOM);
+                    custom_ev.target = rightMenuParent;  // were want to operate on this list
+                    custom_ev.ref_id = rightMenuChild?rightMenuChild->GetID():ev.ref_id; // on this entry 
+                    custom_ev.special_key = TB_KEY_DELETE;  // and what we wanna do to it
+                    rightMenuParent->InvokeEvent(custom_ev); // forward to delegate
+                    rightMenuChild = NULL; // clear the cached values
+                    rightMenuParent = NULL;
+                }
+            }
+            else
+                return false;
+            return true;
+        }
+    }
+    else if (ev.type == EVENT_TYPE_KEY_DOWN && ev.special_key == TB_KEY_ESC)
+    {
+        TBWidgetEvent click_ev(EVENT_TYPE_CLICK);
+        m_close_button.InvokeEvent(click_ev);
+        return true;
+    }
+    else if (ev.type == EVENT_TYPE_CONTEXT_MENU || ev.type == EVENT_TYPE_RIGHT_POINTER_UP ) // want to embed popup menu on TBSelectList id: 5
+    {
+        rightMenuChild = ev.target; // save for later, this is where we started
+        rightMenuParent = FindParentList(ev.target);  // save for later, omg why is this so hard!
+        if ( rightMenuParent && rightMenuParent->GetID() == 5 ) // if we clicked in bookmark list take action!
+        {
+            TBPoint pos_in_root(ev.target_x, ev.target_y);
+            if (TBMenuWindow *menu = new TBMenuWindow(rightMenuParent, TBIDC("popupmenu")))
+            {
+                TBGenericStringItemSource *source = menu->GetList()->GetDefaultSource();
+                source->AddItem(new TBGenericStringItem("delete", TBIDC("delete")));
+                menu->Show(source, TBPopupAlignment(pos_in_root), -1);
+            }
+            return true;
+        }
+    }
+
+    return TBWindow::OnEvent(ev);
+}
+
+void TBFinderWindow::OnDie()
+{
+    if (TBWidget *dimmer = m_dimmer.Get())
+        dimmer->Die();
+}
+
+void TBFinderWindow::OnWidgetDelete(TBWidget *widget)
+{
+    // If the target widget is deleted, close!
+    if (!m_target.Get())
+        Close();
+}
+
+bool TBFinderWindow::OnWidgetDying(TBWidget *widget)
+{
+    // If the target widget or an ancestor of it is dying, close!
+    if (widget == m_target.Get() || widget->IsAncestorOf(m_target.Get()))
+        Close();
+    return false;
+}
+
+TBWidget *TBFinderWindow::FindParentList( TBWidget *widget) // utility for dealing with menus.
+{
+    TBWidget *tmp = widget;
+    while (tmp)
+    {
+        if ( tmp->IsOfType<TBSelectList>() ) return tmp;
+        tmp = tmp->GetParent();
+    }
+    return NULL;
+}
+
+
 }; // namespace tb

+ 56 - 0
Source/ThirdParty/TurboBadger/tb_atomic_widgets.h

@@ -31,6 +31,7 @@
 #include "tb_msg.h"
 #include "tb_widgets_listener.h"
 #include "tb_widgets_common.h"
+#include "tb_window.h"
 
 namespace tb {
 
@@ -138,6 +139,61 @@ private:
 };
 
 
+/** TBPromptWindow is a window for requesting a string. */
+class TBPromptWindow : public TBWindow, private TBWidgetListener
+{
+public:
+    // For safe typecasting
+    TBOBJECT_SUBCLASS(TBPromptWindow, TBWindow);
+    TBPromptWindow(TBWidget *target, TBID id);
+    virtual ~TBPromptWindow();
+    bool Show(const char *title, const char *message,
+          const char *preset = nullptr, int dimmer = 0,
+          int width = 0, int height = 0);
+    virtual TBWidget *GetEventDestination() { return m_target.Get(); }
+    virtual bool OnEvent(const TBWidgetEvent &ev);
+    virtual void OnDie();
+
+private:
+    // TBWidgetListener
+    virtual void OnWidgetDelete(TBWidget *widget);
+    virtual bool OnWidgetDying(TBWidget *widget);
+    TBWidgetSafePointer m_dimmer;
+    TBWidgetSafePointer m_target;
+};
+
+// file, path finder
+class TBFinderWindow : public TBWindow, private TBWidgetListener
+{
+public:
+    // For safe typecasting
+    TBOBJECT_SUBCLASS(TBFinderWindow, TBWindow);
+    TBFinderWindow(TBWidget *target, TBID id);
+    virtual ~TBFinderWindow();
+    bool Show(const char *title,
+          const char *preset = nullptr, int dimmer = 0,
+          int width = 0, int height = 0);
+    virtual TBWidget *GetEventDestination() { return m_target.Get(); }
+    virtual bool OnEvent(const TBWidgetEvent &ev);
+    virtual void OnDie();
+
+private:
+    // TBWidgetListener
+    virtual void OnWidgetDelete(TBWidget *widget);
+    virtual bool OnWidgetDying(TBWidget *widget);
+    TBWidget *FindParentList( TBWidget *widget); // utility for dealing with menus.
+    TBWidgetSafePointer m_dimmer;
+    TBWidgetSafePointer m_target;
+    TBWidget *rightMenuParent;  // information for context menus
+    TBWidget *rightMenuChild;
+
+};
+
+
+
+
+
+
 }; // namespace tb
 
 #endif // TB_ATOMIC_WIDGETS_H