mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 04:34:48 +00:00
440 lines
14 KiB
C++
440 lines
14 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Copyright (c) 2012 GarageGames, 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.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "platform/platform.h"
|
|
#include "gui/containers/guiContainer.h"
|
|
|
|
#include "gui/containers/guiPanel.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "console/engineAPI.h"
|
|
|
|
|
|
IMPLEMENT_CONOBJECT( GuiContainer );
|
|
|
|
ConsoleDocClass( GuiContainer,
|
|
"@brief Brief Desc.\n\n"
|
|
|
|
"@tsexample\n"
|
|
"// Comment:\n"
|
|
"%okButton = new ClassObject()\n"
|
|
"instantiation\n"
|
|
"@endtsexample\n\n"
|
|
|
|
"@ingroup GuiContainers"
|
|
);
|
|
|
|
ImplementEnumType( GuiDockingType,
|
|
"\n\n"
|
|
"@ingroup GuiContainers" )
|
|
{ Docking::dockNone, "None" },
|
|
{ Docking::dockClient, "Client" },
|
|
{ Docking::dockTop, "Top" },
|
|
{ Docking::dockBottom, "Bottom" },
|
|
{ Docking::dockLeft, "Left" },
|
|
{ Docking::dockRight, "Right" }
|
|
EndImplementEnumType;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
GuiContainer::GuiContainer()
|
|
{
|
|
mUpdateLayout = false;
|
|
mValidDockingMask = Docking::dockNone | Docking::dockBottom |
|
|
Docking::dockTop | Docking::dockClient |
|
|
Docking::dockLeft | Docking::dockRight;
|
|
mIsContainer = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
GuiContainer::~GuiContainer()
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiContainer::initPersistFields()
|
|
{
|
|
Con::setIntVariable("$DOCKING_NONE", Docking::dockNone);
|
|
Con::setIntVariable("$DOCKING_CLIENT", Docking::dockClient);
|
|
Con::setIntVariable("$DOCKING_TOP", Docking::dockTop);
|
|
Con::setIntVariable("$DOCKING_BOTTOM", Docking::dockBottom);
|
|
Con::setIntVariable("$DOCKING_LEFT", Docking::dockLeft);
|
|
Con::setIntVariable("$DOCKING_RIGHT", Docking::dockRight);
|
|
|
|
addGroup( "Layout" );
|
|
|
|
addProtectedField("docking", TYPEID< Docking::DockingType >(), Offset(mSizingOptions.mDocking, GuiContainer), &setDockingField, &defaultProtectedGetFn, "" );
|
|
addField("margin", TypeRectSpacingI, Offset(mSizingOptions.mPadding, GuiContainer));
|
|
addField("padding", TypeRectSpacingI, Offset(mSizingOptions.mInternalPadding, GuiContainer));
|
|
addField("anchorTop", TypeBool, Offset(mSizingOptions.mAnchorTop, GuiContainer));
|
|
addField("anchorBottom", TypeBool, Offset(mSizingOptions.mAnchorBottom, GuiContainer));
|
|
addField("anchorLeft", TypeBool, Offset(mSizingOptions.mAnchorLeft, GuiContainer));
|
|
addField("anchorRight", TypeBool, Offset(mSizingOptions.mAnchorRight, GuiContainer));
|
|
|
|
endGroup( "Layout" );
|
|
|
|
Parent::initPersistFields();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiContainer::onChildAdded(GuiControl* control)
|
|
{
|
|
Parent::onChildAdded( control );
|
|
setUpdateLayout();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiContainer::onChildRemoved(GuiControl* control)
|
|
{
|
|
Parent::onChildRemoved( control );
|
|
setUpdateLayout();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool GuiContainer::reOrder(SimObject* obj, SimObject* target)
|
|
{
|
|
if ( !Parent::reOrder(obj, target) )
|
|
return false;
|
|
|
|
setUpdateLayout();
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool GuiContainer::resize( const Point2I &newPosition, const Point2I &newExtent )
|
|
{
|
|
RectI oldBounds = getBounds();
|
|
Point2I minExtent = getMinExtent();
|
|
|
|
if( !Parent::resize( newPosition, newExtent ) )
|
|
return false;
|
|
|
|
RectI clientRect = getClientRect();
|
|
layoutControls( clientRect );
|
|
|
|
GuiControl *parent = getParent();
|
|
S32 docking = getDocking();
|
|
if( parent && docking != Docking::dockNone && docking != Docking::dockInvalid )
|
|
setUpdateLayout( updateParent );
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiContainer::addObject(SimObject *obj)
|
|
{
|
|
Parent::addObject(obj);
|
|
|
|
setUpdateLayout();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiContainer::removeObject(SimObject *obj)
|
|
{
|
|
Parent::removeObject(obj);
|
|
|
|
setUpdateLayout();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiContainer::parentResized(const RectI &oldParentRect, const RectI &newParentRect)
|
|
{
|
|
//if(!mCanResize)
|
|
// return;
|
|
|
|
// If it's a control that specifies invalid docking, we'll just treat it as an old GuiControl
|
|
if( getDocking() & Docking::dockInvalid || getDocking() & Docking::dockNone)
|
|
return Parent::parentResized( oldParentRect, newParentRect );
|
|
|
|
S32 deltaX = newParentRect.extent.x - oldParentRect.extent.x;
|
|
S32 deltaY = newParentRect.extent.y - oldParentRect.extent.y;
|
|
|
|
// Update Self
|
|
RectI oldThisRect = getBounds();
|
|
anchorControl( this, Point2I( deltaX, deltaY ) );
|
|
RectI newThisRect = getBounds();
|
|
|
|
// Update Deltas to pass on to children
|
|
deltaX = newThisRect.extent.x - oldThisRect.extent.x;
|
|
deltaY = newThisRect.extent.y - oldThisRect.extent.y;
|
|
|
|
// Iterate over all children and update their anchors
|
|
iterator nI = begin();
|
|
for( ; nI != end(); nI++ )
|
|
{
|
|
// Sanity
|
|
GuiControl *control = dynamic_cast<GuiControl*>( (*nI) );
|
|
if( control )
|
|
control->parentResized( oldThisRect, newThisRect );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiContainer::childResized(GuiControl *child)
|
|
{
|
|
Parent::childResized( child );
|
|
setUpdateLayout();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool GuiContainer::layoutControls( RectI &clientRect )
|
|
{
|
|
// This variable is set to the first 'Client' docking
|
|
// control that is found. We defer client docking until
|
|
// after all other docks have been made since it will consume
|
|
// the remaining client area available.
|
|
GuiContainer *clientDocking = NULL;
|
|
|
|
// Iterate over all children and perform docking
|
|
iterator nI = begin();
|
|
for( ; nI != end(); nI++ )
|
|
{
|
|
// Layout Content with proper docking (Client Default)
|
|
GuiControl *control = static_cast<GuiControl*>(*nI);
|
|
|
|
// If we're invisible we don't get counted in docking
|
|
if( control == NULL || !control->isVisible() )
|
|
continue;
|
|
|
|
S32 dockingMode = Docking::dockNone;
|
|
GuiContainer *container = dynamic_cast<GuiContainer*>(control);
|
|
if( container != NULL )
|
|
dockingMode = container->getDocking();
|
|
else
|
|
continue;
|
|
|
|
// See above note about clientDocking pointer
|
|
if( dockingMode & Docking::dockClient && clientDocking == NULL )
|
|
clientDocking = container;
|
|
|
|
// Dock Appropriately
|
|
if( !(dockingMode & Docking::dockClient) )
|
|
dockControl( container, dockingMode, clientRect );
|
|
}
|
|
|
|
// Do client dock
|
|
if( clientDocking != NULL )
|
|
dockControl( clientDocking, Docking::dockClient, clientRect );
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool GuiContainer::dockControl( GuiContainer *control, S32 dockingMode, RectI &clientRect )
|
|
{
|
|
if( !control )
|
|
return false;
|
|
|
|
// Make sure this class support docking of this type
|
|
if( !(dockingMode & getValidDockingMask()))
|
|
return false;
|
|
|
|
// If our client rect has run out of room, we can't dock any more
|
|
if( !clientRect.isValidRect() )
|
|
return false;
|
|
|
|
// Dock Appropriately
|
|
RectI dockRect;
|
|
RectSpacingI rectShrinker;
|
|
ControlSizing sizingOptions = control->getSizingOptions();
|
|
switch( dockingMode )
|
|
{
|
|
case Docking::dockClient:
|
|
|
|
// Inset by padding
|
|
sizingOptions.mPadding.insetRect(clientRect);
|
|
|
|
// Dock to entirety of client rectangle
|
|
control->resize( clientRect.point, clientRect.extent );
|
|
|
|
// Remove Client Rect, can only have one client dock
|
|
clientRect.set(0,0,0,0);
|
|
break;
|
|
case Docking::dockTop:
|
|
|
|
dockRect = clientRect;
|
|
dockRect.extent.y = getMin( control->getHeight() + sizingOptions.mPadding.top + sizingOptions.mPadding.bottom , clientRect.extent.y );
|
|
|
|
// Subtract our rect
|
|
clientRect.point.y += dockRect.extent.y;
|
|
clientRect.extent.y -= dockRect.extent.y;
|
|
|
|
// Inset by padding
|
|
sizingOptions.mPadding.insetRect(dockRect);
|
|
|
|
// Resize
|
|
control->resize( dockRect.point, dockRect.extent );
|
|
|
|
break;
|
|
case Docking::dockBottom:
|
|
|
|
dockRect = clientRect;
|
|
dockRect.extent.y = getMin( control->getHeight() + sizingOptions.mPadding.top + sizingOptions.mPadding.bottom, clientRect.extent.y );
|
|
dockRect.point.y += clientRect.extent.y - dockRect.extent.y;
|
|
|
|
// Subtract our rect
|
|
clientRect.extent.y -= dockRect.extent.y;
|
|
|
|
// Inset by padding
|
|
sizingOptions.mPadding.insetRect(dockRect);
|
|
|
|
// Resize
|
|
control->resize( dockRect.point, dockRect.extent );
|
|
|
|
break;
|
|
case Docking::dockLeft:
|
|
|
|
dockRect = clientRect;
|
|
dockRect.extent.x = getMin( control->getWidth() + sizingOptions.mPadding.left + sizingOptions.mPadding.right, clientRect.extent.x );
|
|
|
|
// Subtract our rect
|
|
clientRect.point.x += dockRect.extent.x;
|
|
clientRect.extent.x -= dockRect.extent.x;
|
|
|
|
// Inset by padding
|
|
sizingOptions.mPadding.insetRect(dockRect);
|
|
|
|
// Resize
|
|
control->resize( dockRect.point, dockRect.extent );
|
|
|
|
break;
|
|
case Docking::dockRight:
|
|
|
|
dockRect = clientRect;
|
|
dockRect.extent.x = getMin( control->getWidth() + sizingOptions.mPadding.left + sizingOptions.mPadding.right, clientRect.extent.x );
|
|
dockRect.point.x += clientRect.extent.x - dockRect.extent.x;
|
|
|
|
// Subtract our rect
|
|
clientRect.extent.x -= dockRect.extent.x;
|
|
|
|
// Inset by padding
|
|
sizingOptions.mPadding.insetRect(dockRect);
|
|
|
|
// Resize
|
|
control->resize( dockRect.point, dockRect.extent );
|
|
|
|
break;
|
|
case Docking::dockNone:
|
|
control->setUpdateLayout();
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool GuiContainer::anchorControl( GuiControl *control, const Point2I &deltaParentExtent )
|
|
{
|
|
GuiContainer *container = dynamic_cast<GuiContainer*>( control );
|
|
if( !control || !container )
|
|
return false;
|
|
|
|
// If we're docked, we don't anchor to anything
|
|
if( (container->getDocking() & Docking::dockAny) || !(container->getDocking() & Docking::dockInvalid) )
|
|
return false;
|
|
|
|
if( deltaParentExtent.isZero() )
|
|
return false;
|
|
|
|
RectI oldRect = control->getBounds();
|
|
RectI newRect = control->getBounds();
|
|
|
|
F32 deltaBottom = mSizingOptions.mAnchorBottom ? (F32)deltaParentExtent.y : 0.0f;
|
|
F32 deltaRight = mSizingOptions.mAnchorRight ? (F32)deltaParentExtent.x : 0.0f;
|
|
F32 deltaLeft = mSizingOptions.mAnchorLeft ? 0.0f : (F32)deltaParentExtent.x;
|
|
F32 deltaTop = mSizingOptions.mAnchorTop ? 0.0f : (F32)deltaParentExtent.y;
|
|
|
|
// Apply Delta's to newRect
|
|
newRect.point.x += (S32)deltaLeft;
|
|
newRect.extent.x += (S32)(deltaRight - deltaLeft);
|
|
newRect.point.y += (S32)deltaTop;
|
|
newRect.extent.y += (S32)(deltaBottom - deltaTop);
|
|
|
|
Point2I minExtent = control->getMinExtent();
|
|
// Only resize if our minExtent is satisfied with it.
|
|
if( !( newRect.extent.x >= control->getMinExtent().x && newRect.extent.y >= control->getMinExtent().y ) )
|
|
return false;
|
|
|
|
if( newRect.point == oldRect.point && newRect.extent == oldRect.extent )
|
|
return false;
|
|
|
|
// Finally Size the control
|
|
control->resize( newRect.point, newRect.extent );
|
|
|
|
// We made changes
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiContainer::onPreRender()
|
|
{
|
|
if( mUpdateLayout == updateNone )
|
|
return;
|
|
|
|
RectI clientRect = getClientRect();
|
|
if( mUpdateLayout & updateSelf )
|
|
layoutControls( clientRect );
|
|
|
|
GuiContainer *parent = dynamic_cast<GuiContainer*>( getParent() );
|
|
if( parent && ( mUpdateLayout & updateParent ) )
|
|
parent->setUpdateLayout();
|
|
|
|
// Always set AFTER layoutControls call to prevent recursive calling of layoutControls - JDD
|
|
mUpdateLayout = updateNone;
|
|
|
|
Parent::onPreRender();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const RectI GuiContainer::getClientRect()
|
|
{
|
|
RectI resRect = RectI( Point2I(0,0), getExtent() );
|
|
|
|
// Inset by padding
|
|
mSizingOptions.mInternalPadding.insetRect( resRect );
|
|
|
|
return resRect;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void GuiContainer::setDocking( S32 docking )
|
|
{
|
|
mSizingOptions.mDocking = docking;
|
|
setUpdateLayout( updateParent );
|
|
}
|