Browse Source

Merge pull request #1611 from AtomicGameEngine/JME-TB-DOCKING

Turbobadger TabContainer Docking (continued)
JoshEngebretson 8 years ago
parent
commit
4bd4e9351e

BIN
Resources/EditorData/AtomicEditor/resources/default_skin/window_redock.png


BIN
Resources/EditorData/AtomicEditor/resources/default_skin/window_redock_pressed.png


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

@@ -585,4 +585,14 @@ elements
 		bitmap star.png
 	FolderUp
 		bitmap arrow_up.png
+# dockwindow support
+	TBWindow.redock
+		bitmap window_redock.png
+		type Image
+		children
+			element TBWindow.redock.pressed
+				state pressed
+	TBWindow.redock.pressed
+		bitmap window_redock_pressed.png
+		type Image
 

BIN
Resources/EditorData/AtomicEditor/resources/default_skin_light/window_redock.png


BIN
Resources/EditorData/AtomicEditor/resources/default_skin_light/window_redock_pressed.png


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

@@ -494,4 +494,14 @@ elements
 		bitmap star.png
 	FolderUp
 		bitmap arrow_up.png
+# dockwindow support
+	TBWindow.redock
+		bitmap window_redock.png
+		type Image
+		children
+			element TBWindow.redock.pressed
+				state pressed
+	TBWindow.redock.pressed
+		bitmap window_redock_pressed.png
+		type Image
 

BIN
Resources/PlayerData/DefaultUI/skin/window_redock.png


BIN
Resources/PlayerData/DefaultUI/skin/window_redock_pressed.png


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

@@ -10,7 +10,7 @@
 								"UISkinImage", "UITabContainer", "UISceneView", "UIPreferredSize", "UIDragObject",
 								"UIContainer", "UISection", "UIInlineSelect", "UITextureWidget", "UIColorWidget", "UIColorWheel",
 								"UIScrollContainer", "UISeparator", "UIDimmer", "UISelectDropdown", "UISlider", "UIBargraph",
-								"UIPromptWindow", "UIFinderWindow", "UIPulldownMenu", "UIRadioButton", "UIScrollBar"],
+								"UIPromptWindow", "UIFinderWindow", "UIPulldownMenu", "UIRadioButton", "UIScrollBar", "UIDockWindow"],
 	"overloads" : {
 	},
 	"typescript_decl" : {
@@ -23,8 +23,12 @@
 		],
 		"UIWidget": [
 		    "getWidget<T extends UIWidget>(id: string): T;",
+		    "findWidget<T extends UIWidget>(id: string): T;",
 		    "onEvent: (eventData:UIWidgetEvent) => void;",
-		    "onChanged: () => void;"
+		    "onChanged: () => void;",
+		    "searchWidgetClass(name:string):UIWidget[];",
+		    "searchWidgetId(name:string):UIWidget[];",
+		    "searchWidgetText(name:string):UIWidget[];"
 		]
 	}
 

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

@@ -95,6 +95,7 @@ using namespace tb;
 #include "UIComponent.h"
 #include "UIRadioButton.h"
 #include "UIScrollBar.h"
+#include "UIDockWindow.h"
 
 #include "SystemUI/SystemUI.h"
 #include "SystemUI/SystemUIEvents.h"

+ 124 - 0
Source/Atomic/UI/UIDockWindow.cpp

@@ -0,0 +1,124 @@
+//
+// 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 <TurboBadger/tb_window.h>
+
+#include "../IO/Log.h"
+
+#include "UI.h"
+#include "UIEvents.h"
+#include "UIDockWindow.h"
+
+using namespace tb;
+
+namespace Atomic
+{
+
+/// UIDockWindow is the host for UI content which has been transferred from the main window.
+UIDockWindow::UIDockWindow(Context* context, bool createWidget, const String& title, UIWidget *contentptr, int minwidth, int minheight ) : UIWindow(context, false)
+{
+    if (createWidget)
+    {
+        tb:TBWidget *contents = contentptr->GetInternalWidget();
+        widget_ = new TBDockWindow( title.CString(), contents, minwidth, minheight );
+        widget_->SetDelegate(this);
+        GetSubsystem<UI>()->WrapWidget(this, widget_);
+    }
+}
+
+UIDockWindow::~UIDockWindow()
+{
+}
+
+/// ID of the redock widget. If specified, pressing the dock button, will move the content there.
+void UIDockWindow::SetDockOrigin( String dockid )
+{
+    if (!widget_)
+        return;
+
+   ((TBDockWindow*)widget_)->SetDockOrigin(TBIDC(dockid.CString()));
+
+}
+
+/// This returns a pointer to the docked content.
+UIWidget *UIDockWindow::GetDockContent()
+{
+    if (!widget_)
+        return NULL;
+
+    TBWidget* content = ((TBDockWindow*)widget_)->GetDockContent();
+
+    if (!content)
+        return 0;
+
+    UI* ui = GetSubsystem<UI>();
+
+    return ui->WrapWidget(content);
+
+}
+
+/// Returns if the UIDockWindow contains docked content.
+bool UIDockWindow::HasDockContent()
+{
+    if (!widget_)
+        return false;
+
+    return ((TBDockWindow*)widget_)->HasDockContent();
+}
+
+/// Transfers the dock content to the target widget
+void UIDockWindow::Dock( UIWidget *target )
+{
+    if (!widget_)
+        return;
+
+    if (!target)
+        return;
+
+    ((TBDockWindow*)widget_)->Dock( target->GetInternalWidget() );
+}
+
+/// Show the UIDockWindow, and optional position
+void UIDockWindow::Show( UIWidget *host, int xpos, int ypos )
+{
+    if (!widget_)
+        return;
+
+    if (!host)
+        return;
+
+    ((TBDockWindow*)widget_)->Show( host->GetInternalWidget(), xpos, ypos );
+
+}
+
+bool UIDockWindow::OnEvent(const tb::TBWidgetEvent &ev)
+{
+    return UIWidget::OnEvent(ev);
+}
+
+
+
+
+
+}

+ 62 - 0
Source/Atomic/UI/UIDockWindow.h

@@ -0,0 +1,62 @@
+//
+// 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 "UIWidget.h"
+#include "UIWindow.h"
+
+namespace Atomic
+{
+
+class ATOMIC_API UIDockWindow : public UIWindow
+{
+    ATOMIC_OBJECT(UIDockWindow, UIWindow)
+
+    public:
+    /// UIDockWindow is the host for UI content which has been transferred from the main window.
+    /// Context is a required argument, this is the application context.
+    /// createWidget is if the UIDockWindow should be created, should be set to true.
+    /// title is the string in the titlebar of the UIDockWindow
+    /// contentptr is the pointer to the widget which the UIDockWindow will display
+    /// minwidth is the minimum width for the UIDockWindow
+    /// minheight is the minimum height for the UIDockWindow
+    UIDockWindow(Context* context, bool createWidget = true, const String& title = String::EMPTY, UIWidget *contentptr = NULL, int minwidth = 800, int minheight=400 );
+    virtual ~UIDockWindow();
+
+    void SetDockOrigin( String dockid );  /// ID of the redock widget. If specified, pressing the dock button, will move the content there.
+    UIWidget *GetDockContent();  /// This returns a pointer to the docked content.
+    bool HasDockContent();  /// Returns if the UIDockWindow contains docked content.
+    void Dock( UIWidget *target );  /// Transfers the dock content to the target widget
+    void Show( UIWidget *host, int xpos = 50, int ypos = 50 ); /// Show the UIDockWindow, and optional position
+
+protected:
+
+    virtual bool OnEvent(const tb::TBWidgetEvent &ev);
+
+private:
+
+};
+
+}

+ 32 - 2
Source/Atomic/UI/UITabContainer.cpp

@@ -156,7 +156,7 @@ bool UITabContainer::DeletePage( int page )
 }
 
 /// adds a tab + page from layout file
-void UITabContainer::AddTabPageFile ( const String &tabname, const String &layoutFile )
+void UITabContainer::AddTabPageFile ( const String &tabname, const String &layoutFile, bool selecttab )
 {
     UIButton* button = new UIButton(context_);
     button->SetText(tabname);
@@ -176,10 +176,12 @@ void UITabContainer::AddTabPageFile ( const String &tabname, const String &layou
 
     Invalidate();
 
+    if (selecttab) 
+        SetCurrentPage( GetNumPages() -1 );
 }
 
 /// adds a tab + page widget(s)
-void UITabContainer::AddTabPageWidget ( const String &tabname, UIWidget *widget ) 
+void UITabContainer::AddTabPageWidget ( const String &tabname, UIWidget *widget, bool selecttab ) 
 {
     UIButton* button = new UIButton(context_);
     button->SetText(tabname);
@@ -199,7 +201,35 @@ void UITabContainer::AddTabPageWidget ( const String &tabname, UIWidget *widget
 
     Invalidate();
 
+    if (selecttab) 
+        SetCurrentPage( GetNumPages() -1 );
 }
 
+/// undocks the page into a window with the tab name, and removes the tab
+void UITabContainer::UndockPage ( int page )
+{
+    if (!widget_)
+        return;
+
+    ((TBTabContainer*)widget_)->UndockPage(page);
+
+    // tab container "feature", can not set it to the page number that was removed.
+    int num = 0;
+    if ( page - 1 > 0 ) num = page - 1;
+    SetCurrentPage(num);
+
+}
+
+/// docks content from a UIDockWindow with specified title
+bool UITabContainer::DockWindow ( String windowTitle )
+{
+    if (!widget_)
+        return false;
+    bool done = ((TBTabContainer*)widget_)->DockFromWindow(windowTitle.CString());
+    if (done) 
+        SetCurrentPage( GetNumPages() -1 );
+
+   return done;
+}
 
 }

+ 5 - 2
Source/Atomic/UI/UITabContainer.h

@@ -47,8 +47,11 @@ public:
     
     int GetCurrentPage(); /// returns the current page number
     bool DeletePage( int page ); /// deletes a tab + page, returns true if successful
-    void AddTabPageFile ( const String &tabname, const String &layoutFile ); /// adds a tab + page from file
-    void AddTabPageWidget ( const String &tabname, UIWidget *widget ); /// adds a tab + page widget(s)
+    void AddTabPageFile ( const String &tabname, const String &layoutFile, bool selecttab = true ); /// adds a tab + page from file
+    void AddTabPageWidget ( const String &tabname, UIWidget *widget, bool selecttab = true ); /// adds a tab + page widget(s)
+
+    void UndockPage ( int page ); /// undocks the page into a window with the tab name, and removes the tab
+    bool DockWindow ( String windowTitle ); /// docks content from a UIDockWindow with specified title
 
 protected:
 

+ 91 - 0
Source/Atomic/UI/UIWidget.cpp

@@ -29,6 +29,7 @@
 #include "UILayout.h"
 #include "UIFontDescription.h"
 #include "UIView.h"
+#include "UISelectItem.h"
 
 using namespace tb;
 
@@ -348,6 +349,96 @@ void UIWidget::Invalidate()
     widget_->Invalidate();
 }
 
+/// searches for specified widget ID from the top of the widget tree, returns the 1st one found.
+UIWidget *UIWidget::FindWidget ( const String& searchid )
+{
+    if (!widget_)
+        return NULL;
+
+    TBWidget* child = widget_->FindWidget(TBID(searchid.CString()));
+
+    if (!child)
+        return 0;
+
+    UI* ui = GetSubsystem<UI>();
+    return ui->WrapWidget(child);
+}
+
+void UIWidget::PrintPrettyTree()
+{
+    if (!widget_)
+        return;
+
+    widget_->PrintPretty("", true);
+}
+
+/// return all of the widgets of the specified classname
+void UIWidget::SearchWidgetClass ( const String& className, PODVector<UIWidget*> &results ) 
+{
+    results.Clear();
+
+    if (!widget_)
+        return;
+
+    tb::TBValue tbval(TBValue::TYPE_ARRAY); // TB array of values
+    tbval.SetArray(new tb::TBValueArray(), TBValue::SET_AS_STATIC); // dont delete pointers on destruction
+    widget_->SearchWidgetClass(className.CString(), tbval ); // visit all children for search
+
+    UI* ui = GetSubsystem<UI>();
+    int nn=0;
+    for ( nn=0; nn<tbval.GetArrayLength(); nn++ ) // copy tbwidget ptr to uiwidget ptr
+    {
+        tb::TBWidget *tbw = (tb::TBWidget *)tbval.GetArray()->GetValue(nn)->GetObject();
+        UIWidget *wrp = ui->WrapWidget(tbw);
+        results.Push( wrp );
+    }
+}
+
+///  return all of the widgets of the specified id
+void UIWidget::SearchWidgetId ( const String& searchid, PODVector<UIWidget*> &results )
+{
+
+    results.Clear();
+
+    if (!widget_)
+        return;
+
+    tb::TBValue tbval(TBValue::TYPE_ARRAY);
+    tbval.SetArray(new tb::TBValueArray(), TBValue::SET_AS_STATIC);
+    widget_->SearchWidgetId(TBID(searchid.CString()), tbval );
+
+    UI* ui = GetSubsystem<UI>();
+    int nn=0;
+    for ( nn=0; nn<tbval.GetArrayLength(); nn++ )
+    {
+        tb::TBWidget *tbw = (tb::TBWidget *)tbval.GetArray()->GetValue(nn)->GetObject();
+        UIWidget *wrp = ui->WrapWidget(tbw);
+        results.Push( wrp );
+    }
+}
+
+/// return all of the widgets with the specified text
+void UIWidget::SearchWidgetText ( const String& searchText, PODVector<UIWidget*> &results )
+{
+    results.Clear();
+
+    if (!widget_)
+        return;
+
+    tb::TBValue tbval(TBValue::TYPE_ARRAY);
+    tbval.SetArray(new tb::TBValueArray(), TBValue::SET_AS_STATIC);
+    widget_->SearchWidgetText(searchText.CString(), tbval );
+
+    UI* ui = GetSubsystem<UI>();
+    int nn=0;
+    for ( nn=0; nn<tbval.GetArrayLength(); nn++ )
+    {
+        tb::TBWidget *tbw = (tb::TBWidget *)tbval.GetArray()->GetValue(nn)->GetObject();
+        UIWidget *wrp = ui->WrapWidget(tbw);
+        results.Push( wrp );
+    }
+}
+
 void UIWidget::Center()
 {
     if (!widget_)

+ 12 - 0
Source/Atomic/UI/UIWidget.h

@@ -166,6 +166,7 @@ enum UI_AXIS {
 class UIView;
 class UILayoutParams;
 class UIFontDescription;
+class UISelectItemSource;
 
 /// Wraps a TurboBadger widget in our Object model
 class ATOMIC_API UIWidget : public Object, public tb::TBWidgetDelegate
@@ -203,6 +204,17 @@ class ATOMIC_API UIWidget : public Object, public tb::TBWidgetDelegate
 
     void DeleteAllChildren();
 
+    /// searches for specified widget ID from the top of the widget tree, returns the 1st one found.
+    virtual UIWidget *FindWidget ( const String& searchid );
+    /// return all of the widgets of the specified classname and id that is not 0 from the current widget
+    virtual void SearchWidgetClass ( const String& className, PODVector<UIWidget*> &results );
+    ///  return all of the widgets of the specified id and id that is not 0 from the current widget
+    virtual void SearchWidgetId ( const String& searchid, PODVector<UIWidget*> &results );
+    /// return all of the widgets with the specified text and id that is not 0 from the current widget
+    virtual void SearchWidgetText ( const String& searchText, PODVector<UIWidget*> &results );
+    /// print out the widget tree to stdout from the current widget
+    virtual void PrintPrettyTree();
+
     // String ID
     virtual void SetId(const String& id);
 

+ 72 - 0
Source/AtomicJS/Javascript/JSUIAPI.cpp

@@ -127,6 +127,70 @@ int UI_DebugGetWrappedWidgetCount(duk_context* ctx)
     return 1;
 }
 
+static int UIWidget_SearchWidgetClass(duk_context* ctx)
+{
+    const char* clssName = duk_require_string(ctx, 0);
+    duk_push_this(ctx);
+
+    UIWidget* uiwidget = js_to_class_instance<UIWidget>(ctx, -1, 0);
+
+    PODVector<UIWidget*> dest;
+    uiwidget->SearchWidgetClass( clssName, dest );
+
+    duk_push_array(ctx);
+
+    for (unsigned ii = 0; ii < dest.Size(); ii++)
+    {
+        js_push_class_object_instance(ctx, dest[ii], "UIWidget");
+        duk_put_prop_index(ctx, -2, ii);
+    }
+
+    return 1;
+}
+
+static int UIWidget_SearchWidgetId(duk_context* ctx)
+{
+    const char* idName = duk_require_string(ctx, 0);
+    duk_push_this(ctx);
+ 
+    UIWidget* uiwidget = js_to_class_instance<UIWidget>(ctx, -1, 0);
+
+    PODVector<UIWidget*> dest;
+    uiwidget->SearchWidgetId( idName, dest );
+
+    duk_push_array(ctx);
+
+    for (unsigned i = 0; i < dest.Size(); i++)
+    {
+        js_push_class_object_instance(ctx, dest[i], "UIWidget");
+        duk_put_prop_index(ctx, -2, i);
+    }
+
+    return 1;
+}
+
+static int UIWidget_SearchWidgetText(duk_context* ctx)
+{
+    const char* textName = duk_require_string(ctx, 0);
+    duk_push_this(ctx);
+
+    UIWidget* uiwidget = js_to_class_instance<UIWidget>(ctx, -1, 0);
+
+    PODVector<UIWidget*> dest;
+    uiwidget->SearchWidgetText( textName, dest );
+
+    duk_push_array(ctx);
+
+    for (unsigned i = 0; i < dest.Size(); i++)
+    {
+        js_push_class_object_instance(ctx, dest[i], "UIWidget");
+        duk_put_prop_index(ctx, -2, i);
+    }
+
+    return 1;
+}
+
+
 void jsapi_init_ui(JSVM* vm)
 {
     duk_context* ctx = vm->GetJSContext();
@@ -150,6 +214,14 @@ void jsapi_init_ui(JSVM* vm)
     duk_put_prop_string(ctx, -2, "getResizeToFitContentRect");
     duk_pop(ctx);
 
+    js_class_get_prototype(ctx, "Atomic", "UIWidget");
+    duk_push_c_function(ctx, UIWidget_SearchWidgetClass, 1);
+    duk_put_prop_string(ctx, -2, "searchWidgetClass");
+    duk_push_c_function(ctx, UIWidget_SearchWidgetId, 1);
+    duk_put_prop_string(ctx, -2, "searchWidgetId");
+    duk_push_c_function(ctx, UIWidget_SearchWidgetText, 1);
+    duk_put_prop_string(ctx, -2, "searchWidgetText");
+    duk_pop(ctx);
 
 }
 

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

@@ -32,6 +32,7 @@
 #include "tb_editfield.h"
 #include "tb_menu_window.h"
 #include "tb_select.h"
+#include "tb_tab_container.h"
 
 #include <math.h>
 
@@ -759,5 +760,145 @@ void TBPulldownMenu::OnInflate(const INFLATE_INFO &info)
     TBWidget::OnInflate(info);
 }
 
+// == TBDockWindow ==========================================
+
+TBDockWindow::TBDockWindow( TBStr title, TBWidget *contentptr, int minwidth, int minheight ) : TBWindow()
+{
+    SetID(TBID(title));
+    m_mover.AddChild(&m_redock_button);
+    m_redock_button.SetSkinBg(TBIDC("TBWindow.redock"));
+    m_redock_button.SetIsFocusable(false);
+    m_redock_button.SetID(TBIDC("TBWindow.redock"));
+    m_redock_button.SetVisibilility(WIDGET_VISIBILITY_INVISIBLE);
+
+    SetText(title);
+
+    TBStr uilayout; // undock host ui layout
+    uilayout.SetFormatted ( "TBLayout: axis: y, distribution: gravity\n\tlp: min-width: %ddp, min-height: %ddp\n\tTBLayout: id: \"undocklayout\", distribution: gravity\n", minwidth, minheight );
+    g_widgets_reader->LoadData(this, uilayout );
+    TBLayout *lo1 = GetWidgetByIDAndType<TBLayout>(TBID("undocklayout"));
+    lo1->AddChild(contentptr); // jam it into the window before the pointer gets stale
+    ResizeToFitContent();
+} 
+
+TBDockWindow::~TBDockWindow()
+{
+    if (m_resizer.GetParent()) RemoveChild(&m_resizer);
+    if (m_mover.GetParent()) RemoveChild(&m_mover);
+    if (m_close_button.GetParent()) m_mover.RemoveChild(&m_close_button);
+    if (m_redock_button.GetParent()) m_mover.RemoveChild(&m_redock_button);
+    m_mover.RemoveChild(&m_textfield);
+}
+
+void TBDockWindow::SetDockOrigin( TBID dockid )
+{
+     m_dockid = dockid;
+    if ( m_dockid > 0 ) //enable/show the (re)dock button is a return id is specified
+    {
+        m_redock_button.SetVisibilility(WIDGET_VISIBILITY_VISIBLE);
+    }
+}
+
+
+void TBDockWindow::Show( TBWidget *host, int xpos, int ypos )
+{
+    if ( host )
+    {
+        host->AddChild(this);
+        SetPosition( TBPoint( xpos, ypos) );
+    }
+}
+
+TBWidget *TBDockWindow::GetDockContent()
+{
+    TBLayout *lo1 = GetWidgetByIDAndType<TBLayout>(TBID("undocklayout"));
+    if ( lo1 )
+        return lo1->GetChildFromIndex(0);
+}
+
+bool TBDockWindow::HasDockContent()
+{
+    return ( GetDockContent() != NULL );
+}
+
+void TBDockWindow::Redock ()
+{
+    if ( m_dockid > 0 ) // see if there is a dock point id specified  
+    {
+        TBWidget *mytarget = GetParentRoot(true)->GetWidgetByID(m_dockid);
+        if (mytarget)
+            Dock (mytarget);
+    }
+}
+
+void TBDockWindow::Dock ( TBWidget *target )
+{
+    if ( !HasDockContent() ) return; 
+
+    if ( target ) // what kind of widget is it?
+    {
+        TBStr mystr;
+        TBWidget *mypage = NULL;
+        TBLayout *lo1 = NULL;
+        TBTabContainer *tc1 = NULL;
+        if ( tc1 = TBSafeCast<TBTabContainer>( target ) ) // handle TBTabContainer
+        {
+            mystr = GetText();
+            mypage = GetDockContent();
+            if (mypage)
+            {
+                TBButton *tbb = new TBButton();
+                tbb->SetID (TBID(mystr));
+                tbb->SetText(mystr);
+                mypage->GetParent()->RemoveChild(mypage);
+                tc1->GetContentRoot()->AddChild(mypage);
+                tc1->GetTabLayout()->AddChild(tbb);
+                tc1->Invalidate();
+                tc1->SetCurrentPage(tc1->GetNumPages()-1);
+                Close();
+            }
+        }
+    }   
+}
+
+bool TBDockWindow::OnEvent(const TBWidgetEvent &ev)
+{
+   if (ev.target == &m_close_button)
+    {
+        if (ev.type == EVENT_TYPE_CLICK)
+            Close();
+        return true;
+    }
+   if (ev.target == &m_redock_button)
+    {
+        if (ev.type == EVENT_TYPE_CLICK)
+            Redock();
+        return true;
+    }
+    return TBWidget::OnEvent(ev);
+}
+
+void TBDockWindow::OnResized(int old_w, int old_h)
+{
+    // Apply gravity on children
+    TBWidget::OnResized(old_w, old_h);
+    // Manually move our own decoration children
+    // FIX: Put a layout in the TBMover so we can add things there nicely.
+    int title_height = GetTitleHeight();
+    m_mover.SetRect(TBRect(0, 0, GetRect().w, title_height));
+    PreferredSize ps = m_resizer.GetPreferredSize();
+    m_resizer.SetRect(TBRect(GetRect().w - ps.pref_w, GetRect().h - ps.pref_h, ps.pref_w, ps.pref_h));
+    TBRect mover_rect = m_mover.GetPaddingRect();
+    int button_size = mover_rect.h;
+    m_close_button.SetRect(TBRect(mover_rect.x + mover_rect.w - button_size, mover_rect.y, button_size, button_size));
+    if (m_settings & WINDOW_SETTINGS_CLOSE_BUTTON)
+        mover_rect.w -= button_size;
+    // add redock button to header
+    m_redock_button.SetRect(TBRect(mover_rect.x + mover_rect.w - button_size, mover_rect.y, button_size, button_size));
+    if (m_settings & WINDOW_SETTINGS_CLOSE_BUTTON)
+        mover_rect.w -= button_size;
+    m_textfield.SetRect(mover_rect);
+}
+
 
 }; // namespace tb

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

@@ -264,6 +264,34 @@ protected:
 };
 
 
+/** TBDockWindow is a window for undocking (and redocking) content */
+
+class TBDockWindow : public TBWindow
+{
+public:
+    // For safe typecasting
+    TBOBJECT_SUBCLASS(TBDockWindow, TBWindow);
+
+    TBDockWindow( TBStr title, TBWidget *contentptr, int minwidth = 800, int minheight = 400 );
+    virtual ~TBDockWindow();
+
+    void SetDockOrigin( TBID dockid );
+    TBWidget *GetDockContent();
+    bool HasDockContent();
+    void Dock( TBWidget *target );
+    void Show(TBWidget *host, int xpos = 50, int ypos = 50 );
+
+    virtual bool OnEvent(const TBWidgetEvent &ev);
+    virtual void OnResized(int old_w, int old_h);
+
+protected:
+
+    void Redock();
+
+    TBWidget m_redock_button;  // return content to origin
+    TBID m_dockid; /// the TBID of the origin container
+
+};
 
 
 }; // namespace tb

+ 49 - 0
Source/ThirdParty/TurboBadger/tb_tab_container.cpp

@@ -6,6 +6,12 @@
 #include "tb_tab_container.h"
 #include <assert.h>
 
+// ATOMIC BEGIN
+#include "tb_node_tree.h"
+#include "tb_widgets_reader.h"
+#include "tb_atomic_widgets.h"
+// ATOMIC END
+
 namespace tb {
 
 // == TBTabLayout =======================================================================
@@ -182,4 +188,47 @@ void TBTabContainer::OnProcess()
     }
 }
 
+// ATOMIC BEGIN
+
+/// takes the contents of a DockWindow into a tab in a tabcontainer 
+bool TBTabContainer::DockFromWindow ( TBStr windowTitle )
+{
+    int nn = 0;
+    TBLinkListOf<TBWidget>::Iterator mx = GetParentRoot(true)->GetIteratorForward();
+    while (TBWidget *mxw = mx.GetAndStep())
+    {
+        if ( mxw->GetText().Equals(windowTitle) )
+        {
+            TBDockWindow *dw1 = TBSafeCast<TBDockWindow>( mxw );
+            if (dw1) 
+                dw1->Dock ( this );
+            return true;
+        }
+        nn++;
+    }
+    return false;
+}
+
+/// undocks the page into a window with the tab name, and removes the tab
+void TBTabContainer::UndockPage ( int page )
+{
+    TBWidget *mytab = GetTabLayout()->GetChildFromIndex(page);  // find the offending tab 
+    TBWidget *mypage = GetContentRoot()->GetChildFromIndex(page);   // find the offending page (layout)
+
+    if (mytab == NULL || mypage == NULL ) return; // nobody home
+
+    TBStr tabstr = mytab->GetText(); //get the name/text of the tab
+    GetTabLayout()->RemoveChild(mytab); // remove, delete the tab[page] button
+    mytab->Die();  // and get rid of it
+    GetContentRoot()->RemoveChild(mypage);  // remove the pagewidget[page] from the content root (without deleting it, hopefully)
+    TBDockWindow *mywindow = new TBDockWindow(tabstr, mypage); // create an undock window.
+    mywindow->SetDockOrigin( GetID() );  //tell it to redock here
+    mywindow->Show(GetParentRoot(true)); 
+
+    Invalidate(); // and tell everyone the party is over.
+
+}
+
+// ATOMIC END
+
 }; // namespace tb

+ 6 - 0
Source/ThirdParty/TurboBadger/tb_tab_container.h

@@ -71,6 +71,12 @@ public:
 
     virtual TBWidget *GetContentRoot() { return &m_content_root; }
     TBLayout *GetTabLayout() { return &m_tab_layout; }
+    
+// ATOMIC BEGIN
+    virtual void UndockPage ( int page ); /// undocks the page into a window with the tab name, and removes the tab
+    virtual bool DockFromWindow ( TBStr windowTitle ); /// takes the contents of a DockWindow into a tab in a tabcontainer 
+// ATOMIC END
+
 protected:
     TBLayout m_root_layout;
     TBTabLayout m_tab_layout;

+ 101 - 0
Source/ThirdParty/TurboBadger/tb_widgets.cpp

@@ -17,6 +17,8 @@
 #include "tb_editfield.h"
 #endif // TB_ALWAYS_SHOW_EDIT_FOCUS
 
+#include <stdio.h>  // for printfs in PrintPretty
+
 namespace tb {
 
 //static data
@@ -773,6 +775,105 @@ TBWidget *TBWidget::GetWidgetAt(int x, int y, bool include_children) const
     return last_match;
 }
 
+// ATOMIC BEGIN
+
+/** Returns the number of children this widget contains. */
+int TBWidget::numChildren()
+{
+    int nn = 0;
+    TBLinkListOf<TBWidget>::Iterator mx = GetIteratorForward();
+    while (TBWidget *mxw = mx.GetAndStep())
+    {
+        nn++;
+    }
+    return nn;
+}
+
+/** print out the widget tree */
+void TBWidget::PrintPretty( TBStr indent, bool last)
+{
+    printf("%s", indent.CStr());
+    if (last)
+    {
+       printf("\\-");
+       indent.Append("  ");
+    }
+    else
+    {
+       printf("|-");
+       indent.Append("| ");
+    }
+    TBStr shorty(GetText(), 32); 
+    printf("%s:%u `%s`\n", GetClassName(), (uint32)GetID(), shorty.CStr() );
+    int num = numChildren();
+    for (int ii = 0; ii < num; ii++)
+    {
+        TBWidget *child = GetChildFromIndex(ii);
+        child->PrintPretty(indent, ii == num - 1);
+    }
+}
+
+/// searches for specified widget ID from the top of the widget tree, returns the 1st one found.
+TBWidget *TBWidget::FindWidget ( TBID searchid )
+{
+    TBWidget *rootwidget = GetParentRoot(true);
+    if(rootwidget)
+        return rootwidget->GetWidgetByID( searchid );
+    return NULL;
+}
+
+/// return all of the widgets of the specified classname
+void TBWidget::SearchWidgetClass ( TBStr className, TBValue &results )
+{
+    if ( className.Equals(GetClassName()) )
+    {
+        TBValue *new_val = results.GetArray()->AddValue();
+        new_val->SetObject(this); 
+    }
+    int num = numChildren();
+    for (int ii = 0; ii < num; ii++)
+    {
+        TBWidget *child = GetChildFromIndex(ii);
+        child->SearchWidgetClass( className, results);
+    }
+}
+
+///  return all of the widgets of the specified id
+void TBWidget::SearchWidgetId ( TBID searchId, TBValue &results )
+{
+    if ( searchId == GetID() )
+    {
+        TBValue *new_val = results.GetArray()->AddValue();
+        new_val->SetObject(this); 
+    }
+    int num = numChildren();
+    for (int ii = 0; ii < num; ii++)
+    {
+        TBWidget *child = GetChildFromIndex(ii);
+        child->SearchWidgetId( searchId, results);
+    }
+}
+
+/// return all of the widgets with the specified text
+void TBWidget::SearchWidgetText ( TBStr searchText, TBValue &results )
+{
+    if ( searchText.Equals(GetText()) )
+    {
+        TBValue *new_val = results.GetArray()->AddValue();
+        new_val->SetObject(this); 
+    }
+    int num = numChildren();
+    for (int ii = 0; ii < num; ii++)
+    {
+        TBWidget *child = GetChildFromIndex(ii);
+        child->SearchWidgetText( searchText, results);
+    }
+}
+
+
+// ATOMIC END
+
+
 TBWidget *TBWidget::GetChildFromIndex(int index) const
 {
     int i = 0;

+ 18 - 0
Source/ThirdParty/TurboBadger/tb_widgets.h

@@ -619,6 +619,24 @@ public:
     TBLinkListOf<TBWidget>::Iterator GetIteratorForward() { return m_children.IterateForward(); }
     TBLinkListOf<TBWidget>::Iterator GetIteratorBackward() { return m_children.IterateBackward(); }
 
+// ATOMIC BEGIN
+
+    /** Returns the number of children this widget contains. */
+    int numChildren();
+    /// searches for specified widget ID from the top of the widget tree, returns the 1st one found.
+    TBWidget *FindWidget ( TBID searchid );
+
+    /// print out the widget tree to stdout
+    void PrintPretty( TBStr indent, bool last);
+    /// return all of the widgets of the specified classname
+    void SearchWidgetClass ( TBStr className, TBValue &results );  
+    ///  return all of the widgets of the specified id
+    void SearchWidgetId ( TBID searchid, TBValue &results );
+    /// return all of the widgets with the specified text
+    void SearchWidgetText ( TBStr searchText, TBValue &results );
+
+// ATOMIC END
+
     /** Return true if this widget is the same or a ancestor of other_widget. */
     bool IsAncestorOf(TBWidget *other_widget) const;