Torque3D/Engine/source/gui/containers/guiWindowCtrl.cpp

1975 lines
67 KiB
C++
Raw Normal View History

2012-09-19 15:15:01 +00:00
//-----------------------------------------------------------------------------
// 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/guiWindowCtrl.h"
#include "console/consoleTypes.h"
#include "console/console.h"
#include "console/engineAPI.h"
#include "gui/core/guiCanvas.h"
#include "gui/core/guiDefaultControlRender.h"
#include "gfx/gfxDevice.h"
#include "gfx/gfxDrawUtil.h"
#include "gui/containers/guiRolloutCtrl.h"
#include "gui/editor/guiMenuBar.h"
2012-09-19 15:15:01 +00:00
IMPLEMENT_CONOBJECT( GuiWindowCtrl );
ConsoleDocClass( GuiWindowCtrl,
"@brief A window with a title bar and an optional set of buttons.\n\n"
"The GuiWindowCtrl class implements windows that can be freely placed within the render window. Additionally, "
"the windows can be resized and maximized/minimized.\n\n"
"@tsexample\n"
"new GuiWindowCtrl( MyWindow )\n"
"{\n"
" text = \"My Window\"; // The text that is displayed on the title bar.\n"
" resizeWidth = true; // Allow horizontal resizing by user via mouse.\n"
" resizeHeight = true; // Allow vertical resizing by user via mouse.\n"
" canClose = true; // Display a close button in the title bar.\n"
" canMinimize = true; // Display a minimize button in the title bar.\n"
" canMaximize = true; // Display a maximize button in the title bar.\n"
"};\n"
"@endtsexample\n\n"
"@ingroup GuiContainers"
);
IMPLEMENT_CALLBACK( GuiWindowCtrl, onClose, void, (), (),
"Called when the close button has been pressed." );
IMPLEMENT_CALLBACK( GuiWindowCtrl, onMinimize, void, (), (),
"Called when the window has been minimized." );
IMPLEMENT_CALLBACK( GuiWindowCtrl, onMaximize, void, (), (),
"Called when the window has been maximized." );
IMPLEMENT_CALLBACK( GuiWindowCtrl, onCollapse, void, (), (),
"Called when the window is collapsed by clicking its title bar." );
IMPLEMENT_CALLBACK( GuiWindowCtrl, onRestore, void, (), (),
"Called when the window is restored from minimized, maximized, or collapsed state." );
IMPLEMENT_CALLBACK(GuiWindowCtrl, onResize, void, (S32 posX, S32 posY, S32 width, S32 height), (posX, posY, width, height),
Adjusted handling for the bitmap and bitmapAsset fields for guiBitmapButtonCtrl to forcefully update the button states when changed, ensuring that the bitmaps refresh when changed via the field Added callback for onResize to guiWindowCtrl so controls - such as the EditorTree - can be properly resized in certain circumstances when the window is changed Added getIncrement() and getRange() to GuiGameSettingsCtrl to better facilitate options manipulation on the script side Corrected some of the console method documentation strings in GuiGameSettingsCtrl Removed some unneeded, extraneous files and their asset definitions that came from odd import conversions. Where applicable, created cleaned up versions to make naming conventions and references stable Fixed canvas mode update text typo: FSAA -> FXAA Added logic to DOF, Light Rays, SSAO and Vignette postFX's to check both the preset setting AND the user preference before enabling. Shifted initialization order so PostFX's are loaded before we configure the canvas, to ensure stuff like the FXAAPostFX exists and can be toggled on on load Fixed multiple issues with options menu: When using gamepad, unable to navigate from categories to options. Fixed so can now traverse as normal Input limitations on gamepad necessitated changing of how setting applying happens, is now done as a 'apply or discard' prompt when leaving the options menu Added proper handling for adjusting settings with gamepad with left/right inputs Fixed issue where the unapplied change for an option was sometimes being processed as an object name rather than an implicit string. Now made to be explicit strings to avoid issue. Made the menu button input for "Select" to go from categories to options gamepad only, and hidden when in the options list Fixed issue where changing window mode didn't correctly affect resolution option. Now set up so changing this field correctly refreshes the resolution option. Specifically, when on borderless, the resolution field does not show, preventing confusion as it is always full resolution Generally have the options list refresh when changes happen to allow any and all fields to be able to dynamically respond to other options having changed improving flexibility. Cleaned up old, unused, commented out functions Added ability on OKCancel message boxes to override the button text if needed Fixed issue with AssetBrowser where the shrink/grow icons next to the preview size slider were not anchored correctly. Adjusted callback logic so if preview slider is clicked on, rather than dragged, it will correctly update the zoom values Added sorting to Modules List dropdown for the AssetBrowser Improved standardization of double-clicking in AssetBrowser. Now defaults to editing action if regularly browsing and selecting if in select mode. Still allows regular per-type overrides as normal Moved definition of GuiDisabledTextEditProfile to gui profiles.ed.tscript file, removed duplicates to stop error spam Adjusted default settings value for double-click action in AB to be edit to prevent unstable behavior Removed old file refs from Load Recent list in the default settings
2022-03-27 03:36:37 +00:00
"Called when the window is resized in a regular manner by mouse manipulation.");
IMPLEMENT_CALLBACK(GuiWindowCtrl, onMouseDragged, void, (), (),
"Called when the height has changed.");
Adjusted handling for the bitmap and bitmapAsset fields for guiBitmapButtonCtrl to forcefully update the button states when changed, ensuring that the bitmaps refresh when changed via the field Added callback for onResize to guiWindowCtrl so controls - such as the EditorTree - can be properly resized in certain circumstances when the window is changed Added getIncrement() and getRange() to GuiGameSettingsCtrl to better facilitate options manipulation on the script side Corrected some of the console method documentation strings in GuiGameSettingsCtrl Removed some unneeded, extraneous files and their asset definitions that came from odd import conversions. Where applicable, created cleaned up versions to make naming conventions and references stable Fixed canvas mode update text typo: FSAA -> FXAA Added logic to DOF, Light Rays, SSAO and Vignette postFX's to check both the preset setting AND the user preference before enabling. Shifted initialization order so PostFX's are loaded before we configure the canvas, to ensure stuff like the FXAAPostFX exists and can be toggled on on load Fixed multiple issues with options menu: When using gamepad, unable to navigate from categories to options. Fixed so can now traverse as normal Input limitations on gamepad necessitated changing of how setting applying happens, is now done as a 'apply or discard' prompt when leaving the options menu Added proper handling for adjusting settings with gamepad with left/right inputs Fixed issue where the unapplied change for an option was sometimes being processed as an object name rather than an implicit string. Now made to be explicit strings to avoid issue. Made the menu button input for "Select" to go from categories to options gamepad only, and hidden when in the options list Fixed issue where changing window mode didn't correctly affect resolution option. Now set up so changing this field correctly refreshes the resolution option. Specifically, when on borderless, the resolution field does not show, preventing confusion as it is always full resolution Generally have the options list refresh when changes happen to allow any and all fields to be able to dynamically respond to other options having changed improving flexibility. Cleaned up old, unused, commented out functions Added ability on OKCancel message boxes to override the button text if needed Fixed issue with AssetBrowser where the shrink/grow icons next to the preview size slider were not anchored correctly. Adjusted callback logic so if preview slider is clicked on, rather than dragged, it will correctly update the zoom values Added sorting to Modules List dropdown for the AssetBrowser Improved standardization of double-clicking in AssetBrowser. Now defaults to editing action if regularly browsing and selecting if in select mode. Still allows regular per-type overrides as normal Moved definition of GuiDisabledTextEditProfile to gui profiles.ed.tscript file, removed duplicates to stop error spam Adjusted default settings value for double-click action in AB to be edit to prevent unstable behavior Removed old file refs from Load Recent list in the default settings
2022-03-27 03:36:37 +00:00
2012-09-19 15:15:01 +00:00
//-----------------------------------------------------------------------------
GuiWindowCtrl::GuiWindowCtrl()
: mResizeWidth(true),
mResizeEdge(edgeNone),
2012-09-19 15:15:01 +00:00
mResizeHeight(true),
mCanMove(true),
mResizeMargin(5.f),
2012-09-19 15:15:01 +00:00
mCanClose(true),
mCanMinimize(true),
mCanMaximize(true),
mCanCollapse(false),
mCanDock(false),
2012-09-19 15:15:01 +00:00
mEdgeSnap(true),
mCollapseGroup(-1),
mCollapseGroupNum(-1),
mIsCollapsed(false),
mIsMouseResizing(false),
mButtonOffset(0, 3)
2012-09-19 15:15:01 +00:00
{
// mTitleHeight will change in instanciation most likely...
mTitleHeight = 24;
mIsContainer = true;
mCloseCommand = StringTable->EmptyString();
mMinimized = false;
mMaximized = false;
mMouseMovingWin = false;
mMouseResizeWidth = false;
mMouseResizeHeight = false;
mMinimizeIndex = -1;
mTabIndex = -1;
mBitmapBounds = NULL;
2014-11-07 15:25:10 +00:00
setExtent(100, 200);
2012-09-19 15:15:01 +00:00
RectI closeRect(80, 2, 16, 16);
mCloseButton = closeRect;
closeRect.point.x -= 18;
mMaximizeButton = closeRect;
closeRect.point.x -= 18;
mMinimizeButton = closeRect;
// Other defaults
mActive = true;
mCloseButtonPressed = false;
mMaximizeButtonPressed = false;
mMinimizeButtonPressed = false;
2020-05-11 20:03:27 +00:00
mRepositionWindow = false;
mResizeWindow = false;
mSnapSignal = false;
mPreCollapsedYExtent = 200;
mPreCollapsedYMinExtent = 176;
2012-09-19 15:15:01 +00:00
mText = "New Window";
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::initPersistFields()
{
docsURL;
2012-09-19 15:15:01 +00:00
addGroup( "Window" );
addField( "text", TypeRealString, Offset( mText, GuiWindowCtrl ),
"Text label to display in titlebar." );
addField( "resizeWidth", TypeBool, Offset( mResizeWidth, GuiWindowCtrl ),
"Whether the window can be resized horizontally." );
addField( "resizeHeight", TypeBool, Offset( mResizeHeight, GuiWindowCtrl ),
"Whether the window can be resized vertically." );
2025-03-09 16:53:23 +00:00
addFieldV("resizeMargin", TypeRangedF32, Offset(mResizeMargin, GuiWindowCtrl), &CommonValidators::PositiveFloat,
"Margin along the window edge to allow grabbing.");
2012-09-19 15:15:01 +00:00
addField( "canMove", TypeBool, Offset( mCanMove, GuiWindowCtrl ),
"Whether the window can be moved by dragging its titlebar." );
addField( "canClose", TypeBool, Offset( mCanClose, GuiWindowCtrl ),
"Whether the window has a close button." );
addField( "canMinimize", TypeBool, Offset( mCanMinimize, GuiWindowCtrl ),
"Whether the window has a minimize button." );
addField( "canMaximize", TypeBool, Offset( mCanMaximize, GuiWindowCtrl ),
"Whether the window has a maximize button." );
addField( "canCollapse", TypeBool, Offset( mCanCollapse, GuiWindowCtrl ),
"Whether the window can be collapsed by clicking its title bar." );
addField( "closeCommand", TypeString, Offset( mCloseCommand, GuiWindowCtrl ),
"Script code to execute when the window is closed." );
addField( "edgeSnap", TypeBool, Offset( mEdgeSnap,GuiWindowCtrl ),
"If true, the window will snap to the edges of other windows when moved close to them." );
addField( "buttonOffset", TypePoint2I, Offset (mButtonOffset, GuiWindowCtrl),
"Margin between window edge and the button(s).");
2012-09-19 15:15:01 +00:00
endGroup( "Window" );
Parent::initPersistFields();
}
//=============================================================================
// Collapsing.
//=============================================================================
// MARK: ---- Collapsing ----
//-----------------------------------------------------------------------------
void GuiWindowCtrl::moveFromCollapseGroup()
{
GuiControl *parent = getParent();
if( !parent )
return;
S32 groupVec = mCollapseGroup;
S32 vecPos = mCollapseGroupNum;
S32 groupVecCount = parent->mCollapseGroupVec[groupVec].size() - 1;
CollapseGroupNumVec collapseGroupNumVec;
if( groupVecCount > vecPos )
{
if (vecPos == 1)
{
CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[groupVec].begin();
for(; iter != parent->mCollapseGroupVec[groupVec].end(); iter++ )
{
if((*iter)->mCollapseGroupNum >= vecPos)
collapseGroupNumVec.push_back((*iter));
}
parent->mCollapseGroupVec[groupVec].first()->mCollapseGroup = -1;
parent->mCollapseGroupVec[groupVec].first()->mCollapseGroupNum = -1;
parent->mCollapseGroupVec[groupVec].erase(U32(0));
parent->mCollapseGroupVec[groupVec].setSize(groupVecCount - 1);
parent->mCollapseGroupVec.erase(groupVec);
if(groupVec > 0)
parent->mCollapseGroupVec.setSize(groupVec);
parent->mCollapseGroupVec.push_back( collapseGroupNumVec );
}
else
{
// Iterate through the group i was once in, gather myself and the controls below me and store them in an array
CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[groupVec].begin();
for(; iter != parent->mCollapseGroupVec[groupVec].end(); iter++ )
{
if((*iter)->mCollapseGroupNum >= vecPos)
collapseGroupNumVec.push_back((*iter));
}
// Iterate through the newly created array; delete my references in the old group, create a new group and organize me accord.
S32 assignWindowNumber = 0;
CollapseGroupNumVec::iterator iter2 = collapseGroupNumVec.begin();
for(; iter2 != collapseGroupNumVec.end(); iter2++ )
{
parent->mCollapseGroupVec[groupVec].pop_back();
parent->mCollapseGroupVec[groupVec].setSize(groupVecCount);
(*iter2)->mCollapseGroup = (parent->mCollapseGroupVec.size());
(*iter2)->mCollapseGroupNum = assignWindowNumber;
assignWindowNumber++;
groupVecCount--;
}
parent->mCollapseGroupVec.push_back( collapseGroupNumVec );
}
}
else
{
parent->mCollapseGroupVec[groupVec].erase(mCollapseGroupNum);
parent->mCollapseGroupVec[groupVec].setSize(groupVecCount);
mCollapseGroup = -1;
mCollapseGroupNum = -1;
if(groupVecCount <= 1)
{
parent->mCollapseGroupVec[groupVec].first()->mCollapseGroup = -1;
parent->mCollapseGroupVec[groupVec].first()->mCollapseGroupNum = -1;
parent->mCollapseGroupVec[groupVec].erase(U32(0));
parent->mCollapseGroupVec[groupVec].setSize(groupVecCount - 1);
2017-01-12 04:34:46 +00:00
parent->mCollapseGroupVec.erase(groupVec);
2012-09-19 15:15:01 +00:00
}
}
// Re id collapse groups
refreshCollapseGroups();
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::moveToCollapseGroup(GuiWindowCtrl* hitWindow, bool orientation )
{
// Orientation 0 - window in question is being connected to top of another window
// Orientation 1 - window in question is being connected to bottom of another window
GuiControl *parent = getParent();
if( !parent )
return;
S32 groupVec = mCollapseGroup;
S32 attatchedGroupVec = hitWindow->mCollapseGroup;
S32 vecPos = mCollapseGroupNum;
if(mCollapseGroup == attatchedGroupVec && vecPos != -1)
return;
CollapseGroupNumVec collapseGroupNumVec;
// Window colliding with is not in a collapse group
if(hitWindow->mCollapseGroup < 0)
{
// We(the collider) are in a group of windows
if(mCollapseGroup >= 0)
{
S32 groupVecCount = parent->mCollapseGroupVec[groupVec].size() - 1;
// Copy pointer window data in my array, and store in a temp array
CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[groupVec].begin();
for(; iter != parent->mCollapseGroupVec[groupVec].end(); iter++ )
{
if((*iter)->mCollapseGroupNum >= vecPos)
{
collapseGroupNumVec.push_back((*iter));
groupVecCount--;
}
}
// Kill my old array group and erase its footprints
if( vecPos <= 1 || groupVecCount == 0 )
{
// Isn't covered in the renumbering of groups, so we it here
parent->mCollapseGroupVec[groupVec].first()->mCollapseGroup = -1;
parent->mCollapseGroupVec[groupVec].first()->mCollapseGroupNum = -1;
parent->mCollapseGroupVec[groupVec].clear();
parent->mCollapseGroupVec[groupVec].setSize(U32(0));
parent->mCollapseGroupVec.erase(groupVec);
if(groupVec > 0)
parent->mCollapseGroupVec.setSize(groupVec);
}
// Push the collided window
if(orientation == 0)
collapseGroupNumVec.push_back(hitWindow);
else
collapseGroupNumVec.push_front(hitWindow);
// Push the temp array in the guiControl array holder
parent->mCollapseGroupVec.push_back( collapseGroupNumVec );
}
else
{
if(orientation == 0)
{
collapseGroupNumVec.push_front(hitWindow);
collapseGroupNumVec.push_front(this);
}
else
{
collapseGroupNumVec.push_front(this);
collapseGroupNumVec.push_front(hitWindow);
}
parent->mCollapseGroupVec.push_back( collapseGroupNumVec );
}
}
else // Window colliding with *IS* in a collapse group
{
if(mCollapseGroup >= 0)
{
S32 groupVecCount = parent->mCollapseGroupVec[groupVec].size() - 1;
CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[groupVec].begin();
for(; iter != parent->mCollapseGroupVec[groupVec].end(); iter++ )
{
if((*iter)->mCollapseGroupNum >= vecPos)
{
// Push back the pointer window controls to the collided array
parent->mCollapseGroupVec[attatchedGroupVec].push_back((*iter));
groupVecCount--;
}
}
if( vecPos <= 1 || groupVecCount == 0 )
{
// Isn't covered in the renumbering of groups, so we it here
parent->mCollapseGroupVec[groupVec].first()->mCollapseGroup = -1;
parent->mCollapseGroupVec[groupVec].first()->mCollapseGroupNum = -1;
parent->mCollapseGroupVec[groupVec].clear();
parent->mCollapseGroupVec[groupVec].setSize(U32(0));
parent->mCollapseGroupVec.erase(groupVec);
if(groupVec > 0)
parent->mCollapseGroupVec.setSize(groupVec);
}
// Since we killed my old array group, run in case the guiControl array moved me down a notch
if(attatchedGroupVec > groupVec )
attatchedGroupVec--;
}
else
{
2018-03-15 00:07:03 +00:00
groupVec = hitWindow->mCollapseGroup;
2012-09-19 15:15:01 +00:00
if(orientation == 0)
parent->mCollapseGroupVec[groupVec].push_front(this);
else
parent->mCollapseGroupVec[groupVec].push_back(this);
}
}
// Re id collapse groups
refreshCollapseGroups();
// Select the current window and its collapse group
selectWindow();
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::refreshCollapseGroups()
{
GuiControl *parent = getParent();
if( !parent )
return;
2017-01-12 04:34:46 +00:00
CollapseGroupNumVec collapseGroupNumVec;
2012-09-19 15:15:01 +00:00
// iterate through the collided array, renumbering the windows pointers
S32 assignGroupNum = 0;
CollapseGroupVec::iterator iter = parent->mCollapseGroupVec.begin();
for(; iter != parent->mCollapseGroupVec.end(); iter++ )
{
S32 assignWindowNumber = 0;
CollapseGroupNumVec::iterator iter2 = parent->mCollapseGroupVec[assignGroupNum].begin();
for(; iter2 != parent->mCollapseGroupVec[assignGroupNum].end(); iter2++ )
{
(*iter2)->mCollapseGroup = assignGroupNum;
(*iter2)->mCollapseGroupNum = assignWindowNumber;
assignWindowNumber++;
}
assignGroupNum++;
}
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::moveWithCollapseGroup(Point2I windowPosition)
{
GuiControl *parent = getParent();
if( !parent )
return;
Point2I newChildPosition(0, 0);
S32 addedPosition = getExtent().y;
// Iterate through windows below this. Move them according to my position.
CollapseGroupNumVec collapseGroupNumVec;
CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[mCollapseGroup].begin();
for(; iter != parent->mCollapseGroupVec[mCollapseGroup].end(); iter++ )
{
if((*iter)->mCollapseGroupNum > mCollapseGroupNum)
{
newChildPosition.x = windowPosition.x;
newChildPosition.y = windowPosition.y + addedPosition;
(*iter)->resize(newChildPosition, (*iter)->getExtent());
addedPosition += (*iter)->getExtent().y;
}
}
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::setCollapseGroup(bool state)
{
GuiControl *parent = getParent();
if( !parent )
return;
if( mIsCollapsed != state )
{
mIsCollapsed = state;
handleCollapseGroup();
}
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::toggleCollapseGroup()
{
GuiControl *parent = getParent();
if( !parent )
return;
mIsCollapsed = !mIsCollapsed;
handleCollapseGroup();
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::handleCollapseGroup()
{
GuiControl *parent = getParent();
if( !parent )
return;
2017-01-12 04:34:46 +00:00
CollapseGroupNumVec collapseGroupNumVec;
2012-09-19 15:15:01 +00:00
if( mIsCollapsed ) // minimize window up to its header bar
{
//save settings
mPreCollapsedYExtent = getExtent().y;
mPreCollapsedYMinExtent = getMinExtent().y;
//create settings for collapsed window to abide by
mResizeHeight = false;
setMinExtent( Point2I( getMinExtent().x, 24 ) );
iterator i;
for(i = begin(); i != end(); i++)
{
GuiControl *ctrl = static_cast<GuiControl *>(*i);
ctrl->setVisible(false);
ctrl->mCanResize = false;
}
resize( getPosition(), Point2I( getExtent().x, 24 ) );
if(mCollapseGroup >= 0)
{
S32 moveChildYBy = (mPreCollapsedYExtent - 24);
CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[mCollapseGroup].begin();
for(; iter != parent->mCollapseGroupVec[mCollapseGroup].end(); iter++ )
{
if((*iter)->mCollapseGroupNum > mCollapseGroupNum)
{
Point2I newChildPosition = (*iter)->getPosition();
newChildPosition.y -= moveChildYBy;
(*iter)->resize(newChildPosition, (*iter)->getExtent());
}
}
}
onCollapse_callback();
}
else // maximize the window to its previous position
{
//create and load settings
mResizeHeight = true;
setMinExtent( Point2I( getMinExtent().x, mPreCollapsedYMinExtent ) );
resize( getPosition(), Point2I( getExtent().x, mPreCollapsedYExtent ) );
iterator i;
for(i = begin(); i != end(); i++)
{
GuiControl *ctrl = static_cast<GuiControl *>(*i);
ctrl->setVisible(true);
ctrl->mCanResize = true;
}
if(mCollapseGroup >= 0)
{
S32 moveChildYBy = (mPreCollapsedYExtent - 24);
CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[mCollapseGroup].begin();
for(; iter != parent->mCollapseGroupVec[mCollapseGroup].end(); iter++ )
{
if((*iter)->mCollapseGroupNum > mCollapseGroupNum)
{
Point2I newChildPosition = (*iter)->getPosition();
2017-01-12 04:34:46 +00:00
newChildPosition.y += moveChildYBy;
2012-09-19 15:15:01 +00:00
(*iter)->resize(newChildPosition, (*iter)->getExtent());
}
}
}
onRestore_callback();
}
}
//-----------------------------------------------------------------------------
bool GuiWindowCtrl::resizeCollapseGroup(bool resizeX, bool resizeY, Point2I resizePos, Point2I resizeExtent)
{
GuiControl *parent = getParent();
if( !parent )
return false;
2017-01-12 04:34:46 +00:00
CollapseGroupNumVec collapseGroupNumVec;
2012-09-19 15:15:01 +00:00
bool canResize = true;
CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[mCollapseGroup].begin();
for(; iter != parent->mCollapseGroupVec[mCollapseGroup].end(); iter++ )
{
if((*iter) == this)
continue;
Point2I newChildPosition = (*iter)->getPosition();
Point2I newChildExtent = (*iter)->getExtent();
if( resizeX == true )
{
newChildPosition.x -= resizePos.x;
newChildExtent.x -= resizeExtent.x;
}
if( resizeY == true )
{
if((*iter)->mCollapseGroupNum > mCollapseGroupNum)
{
newChildPosition.y -= resizeExtent.y;
newChildPosition.y -= resizePos.y;
}
else if((*iter)->mCollapseGroupNum == mCollapseGroupNum - 1)
newChildExtent.y -= resizePos.y;
}
// check is done for normal extent of windows. if false, check again in case its just giving false
// due to being a collapsed window. if your truly being forced passed your extent, return false
if( !(*iter)->mIsCollapsed && newChildExtent.y >= (*iter)->getMinExtent().y )
(*iter)->resize( newChildPosition, newChildExtent);
else
{
if( (*iter)->mIsCollapsed )
(*iter)->resize( newChildPosition, newChildExtent);
else
canResize = false;
}
}
return canResize;
}
//-----------------------------------------------------------------------------
// Mouse Methods
//-----------------------------------------------------------------------------
S32 GuiWindowCtrl::findHitEdges( const Point2I &globalPoint )
{
// No Edges
S32 edgeMask = edgeNone;
GuiControl *parent = getParent();
if( !parent )
return edgeMask;
RectI bounds( getGlobalBounds() );
// Create an EdgeRectI structure that has four edges
// Left/Right/Top/Bottom
// Each Edge structure has a hit operation that will take
// another edge and test for a hit on the edge with a margin
// specified by the .margin scalar
EdgeRectI edges = EdgeRectI(bounds, mResizeMargin);
// Get Cursor Edges
Edge cursorVertEdge = Edge( globalPoint, Point2F( 1.f, 0.f ) );
Edge cursorHorzEdge = Edge( globalPoint, Point2F( 0.f, 1.f ) );
if( edges.left.hit( cursorVertEdge ) )
edgeMask |= edgeLeft;
else if( edges.right.hit( cursorVertEdge ) )
edgeMask |= edgeRight;
if( edges.bottom.hit( cursorHorzEdge ) )
edgeMask |= edgeBottom;
else if( edges.top.hit( cursorHorzEdge ) )
{
// Only the top window in a collapse group can be extended from the top
if( mCanCollapse && mCollapseGroup >= 0 )
{
if( parent->mCollapseGroupVec[mCollapseGroup].first() != this )
return edgeMask;
}
edgeMask |= edgeTop;
}
// Return the resulting mask
return edgeMask;
}
void GuiWindowCtrl::getSnappableWindows( Vector<GuiWindowCtrl*> &windowOutVector, bool canCollapse )
{
GuiControl *parent = getParent();
if( !parent )
return;
S32 parentSize = parent->size();
for( S32 i = 0; i < parentSize; i++ )
{
GuiWindowCtrl *childWindow = dynamic_cast<GuiWindowCtrl*>(parent->at(i));
if( !childWindow || !childWindow->isVisible() || childWindow == this || childWindow->mEdgeSnap == false)
continue;
if( canCollapse && !childWindow->mCanCollapse )
continue;
windowOutVector.push_back(childWindow);
}
}
//=============================================================================
// Events.
//=============================================================================
// MARK: ---- Events ----
//-----------------------------------------------------------------------------
bool GuiWindowCtrl::onWake()
{
if (! Parent::onWake())
return false;
//get the texture for the close, minimize, and maximize buttons
bool result = mProfile->constructBitmapArray() >= NumBitmaps;
if( !result )
{
Con::errorf( "GuiWindowCtrl::onWake - failed to create bitmap array from profile bitmap." );
return false;
}
2024-12-26 13:11:29 +00:00
mTextureObject = mProfile->getBitmap();
2012-09-19 15:15:01 +00:00
mBitmapBounds = mProfile->mBitmapArrayRects.address();
S32 buttonHeight = mBitmapBounds[(U32)BmpStates * (U32)BmpClose].extent.y;
2012-09-19 15:15:01 +00:00
mTitleHeight = buttonHeight + 4;
//set the button coords
positionButtons();
//set the tab index
mTabIndex = -1;
GuiControl *parent = getParent();
if (parent && mFirstResponder)
{
mTabIndex = 0;
//count the number of windows preceeding this one
iterator i;
for (i = parent->begin(); i != parent->end(); i++)
{
GuiWindowCtrl *ctrl = dynamic_cast<GuiWindowCtrl *>(*i);
if (ctrl)
{
if (ctrl == this) break;
else if (ctrl->mFirstResponder) mTabIndex++;
}
}
}
return true;
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::onSleep()
{
mTextureObject = NULL;
mMousePosition = Point2I(0,0);
Parent::onSleep();
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::onMouseDown(const GuiEvent &event)
{
setUpdate();
mOrigBounds = getBounds();
mMouseDownPosition = event.mousePoint;
Point2I localPoint = globalToLocalCoord(event.mousePoint);
// Select this window - move it to the front, and set the first responder
selectWindow();
mMouseMovingWin = false;
S32 hitEdges = findHitEdges( event.mousePoint );
mResizeEdge = edgeNone;
// Set flag by default so we only clear it
// if we don't match either edge
mMouseResizeHeight = true;
// Check Bottom/Top edges (Mutually Exclusive)
if( mResizeHeight && hitEdges & edgeBottom )
mResizeEdge |= edgeBottom;
else if( mResizeHeight && hitEdges & edgeTop )
mResizeEdge |= edgeTop;
else
mMouseResizeHeight = false;
// Set flag by default so we only clear it
// if we don't match either edge
mMouseResizeWidth = true;
// Check Left/Right edges (Mutually Exclusive)
if( mResizeWidth && hitEdges & edgeLeft )
mResizeEdge |= edgeLeft;
else if( mResizeWidth && hitEdges & edgeRight )
mResizeEdge |= edgeRight;
else
mMouseResizeWidth = false;
// If we clicked within the title bar
if ( !(mResizeEdge & ( edgeTop | edgeLeft | edgeRight ) ) && localPoint.y < mTitleHeight)
{
if (mCanClose && mCloseButton.pointInRect(localPoint))
{
mCloseButtonPressed = mCanClose;
}
else if (mCanMaximize && mMaximizeButton.pointInRect(localPoint))
{
mMaximizeButtonPressed = mCanMaximize;
}
else if (mCanMinimize && mMinimizeButton.pointInRect(localPoint))
{
mMinimizeButtonPressed = mCanMinimize;
}
else // We clicked anywhere else within the title
{
S32 docking = getDocking();
if( docking == Docking::dockInvalid || docking == Docking::dockNone )
mMouseMovingWin = mCanMove;
mMouseResizeWidth = false;
mMouseResizeHeight = false;
}
}
if (mMouseMovingWin || mResizeEdge != edgeNone ||
mCloseButtonPressed || mMaximizeButtonPressed || mMinimizeButtonPressed)
{
mouseLock();
}
else
{
GuiControl *ctrl = findHitControl(localPoint);
if (ctrl && ctrl != this)
ctrl->onMouseDown(event);
}
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::onMouseDragged(const GuiEvent &event)
{
GuiControl *parent = getParent();
GuiCanvas *root = getRoot();
if ( !root )
return;
mMousePosition = globalToLocalCoord(event.mousePoint);
Point2I deltaMousePosition = event.mousePoint - mMouseDownPosition;
Point2I newPosition = getPosition();
Point2I newExtent = getExtent();
bool resizeX = false;
bool resizeY = false;
mResizeWindow = false;
mRepositionWindow = false;
if (mMouseMovingWin && parent)
{
if( parent != root )
{
newPosition.x = mOrigBounds.point.x + deltaMousePosition.x;
newPosition.y = getMax(0, mOrigBounds.point.y + deltaMousePosition.y );
mRepositionWindow = true;
}
else
{
newPosition.x = getMax(0, getMin(parent->getWidth() - getWidth(), mOrigBounds.point.x + deltaMousePosition.x));
newPosition.y = getMax(0, getMin(parent->getHeight() - getHeight(), mOrigBounds.point.y + deltaMousePosition.y));
}
// Check snapping to other windows
if( mEdgeSnap )
{
RectI bounds = getGlobalBounds();
bounds.point = mOrigBounds.point + deltaMousePosition;
EdgeRectI edges = EdgeRectI( bounds, mResizeMargin );
// Create a global-space rectangle that covers the snapping
// zone of this window. Double the space in which snapping occurs
// for top and bottom.
RectI snapZone = bounds;
snapZone.point.x -= SnapDistance;
snapZone.point.y -= SnapDistance;
snapZone.extent.x += SnapDistance + SnapDistance;
snapZone.extent.y += SnapDistance + SnapDistance;
//check if we need to offset because of the menubar
U32 menuBarHeight = 0;
GuiCanvas* guiCanvas = getRoot();
if (guiCanvas)
{
2022-06-13 17:38:08 +00:00
#ifdef TORQUE_TOOLS
GuiMenuBar* menuBar = dynamic_cast<GuiMenuBar*>(guiCanvas->getMenuBar());
if (menuBar)
{
menuBarHeight = menuBar->getHeight();
}
2022-06-13 17:38:08 +00:00
#endif
}
2012-09-19 15:15:01 +00:00
// Build valid snap and window vectors to compare against
Vector< GuiWindowCtrl* > windowList;
getSnappableWindows( windowList );
for( S32 i =0; i < windowList.size(); i++ )
{
// Make sure the window is both horizontally and vertically
// within the snap zone for this window.
RectI windowBounds = windowList[i]->getGlobalBounds();
//offset position by menubar height
windowBounds.point.y -= menuBarHeight;
if( !snapZone.overlaps(windowBounds) )
2012-09-19 15:15:01 +00:00
continue;
// Build edges for snap detection
EdgeRectI snapRect(windowBounds, mResizeMargin );
2012-09-19 15:15:01 +00:00
if( snapRect.right.position.x <= edges.left.position.x + SnapDistance &&
snapRect.right.position.x >= edges.left.position.x - SnapDistance )
{
newPosition.x = snapRect.right.position.x;
}
else if( snapRect.left.position.x <= edges.right.position.x + SnapDistance &&
snapRect.left.position.x >= edges.right.position.x - SnapDistance )
{
newPosition.x = snapRect.left.position.x - bounds.extent.x;
}
else if( snapRect.top.position.y <= edges.bottom.position.y + SnapDistance + SnapDistance &&
snapRect.top.position.y >= edges.bottom.position.y - SnapDistance - SnapDistance )
{
// Ensure that we're not snapping to the middle of collapse groups
if( (windowList[i]->mCanCollapse && windowList[i]->mCollapseGroup >= 0) ||
(mCanCollapse && mCollapseGroup >= 0) )
continue;
newPosition.x = snapRect.left.position.x;
newPosition.y = snapRect.top.position.y - bounds.extent.y;
}
else if( snapRect.bottom.position.y <= edges.top.position.y + SnapDistance + SnapDistance &&
snapRect.bottom.position.y >= edges.top.position.y - SnapDistance - SnapDistance)
{
// Ensure that we're not snapping to the middle of collapse groups
// We are not in a group, or we are not in the same group
if( mCollapseGroup == -1 || ( mCollapseGroup >= 0 && mCollapseGroup != windowList[i]->mCollapseGroup ) )
{
// If the window checked is in a group, if its anything but the last, its n/a
if( windowList[i]->mCollapseGroup >= 0 &&
windowList[i] != parent->mCollapseGroupVec[windowList[i]->mCollapseGroup].last() )
continue;
}
else // We are in the same group, we can't obviously be [0]
{
// If we are [-1/0] we have a serious problem. Also, we only allow connection to the window directly above us
if( mCollapseGroupNum <= 0 ||
windowList[i] != parent->mCollapseGroupVec[mCollapseGroup][mCollapseGroupNum-1] )
continue;
}
newPosition.x = snapRect.left.position.x;
newPosition.y = snapRect.bottom.position.y;
}
}
}
}
else if(mCloseButtonPressed || mMaximizeButtonPressed || mMinimizeButtonPressed)
{
setUpdate();
return;
}
else
{
if( ( !mMouseResizeHeight && !mMouseResizeWidth ) || !parent )
return;
mResizeWindow = true;
if( mResizeEdge & edgeBottom )
{
newExtent.y = getMin(parent->getHeight(), mOrigBounds.extent.y + deltaMousePosition.y);
resizeY = true;
}
else if ( mResizeEdge & edgeTop )
{
newExtent.y = getMin(parent->getHeight(), mOrigBounds.extent.y - deltaMousePosition.y);
if ( newExtent.y >= mMinExtent.y )
{
// Standard reposition as we're not travelling into the min extent range
newPosition.y = mOrigBounds.point.y + deltaMousePosition.y;
}
else
{
// We're into the min extent, so adjust the position up to the min extent
// so the window doesn't appear to jump
newPosition.y = mOrigBounds.point.y + (mOrigBounds.extent.y - mMinExtent.y);
}
resizeY = true;
}
if( mResizeEdge & edgeRight )
{
newExtent.x = getMin(parent->getWidth(), mOrigBounds.extent.x + deltaMousePosition.x);
resizeX = true;
}
else if( mResizeEdge & edgeLeft )
{
newExtent.x = getMin(parent->getWidth(), mOrigBounds.extent.x - deltaMousePosition.x);
if ( newExtent.x >= mMinExtent.x )
{
// Standard reposition as we're not travelling into the min extent range
newPosition.x = mOrigBounds.point.x + deltaMousePosition.x;
}
else
{
// We're into the min extent, so adjust the position up to the min extent
// so the window doesn't appear to jump
newPosition.x = mOrigBounds.point.x + (mOrigBounds.extent.x - mMinExtent.x);
}
resizeX = true;
}
}
// Resize this
Point2I pos = parent->localToGlobalCoord(getPosition());
root->addUpdateRegion(pos, getExtent());
if(mCanCollapse && mCollapseGroup >= 0 && mRepositionWindow == true)
moveWithCollapseGroup(newPosition);
if(mCanCollapse && mCollapseGroup >= 0 && mResizeWindow == true )
2017-01-12 04:34:46 +00:00
{
2012-09-19 15:15:01 +00:00
// Resize the window if allowed
if( newExtent.y >= getMinExtent().y && newExtent.x >= getMinExtent().x)
{
mIsMouseResizing = true;
if( resizeCollapseGroup( resizeX, resizeY, (getPosition() - newPosition), (getExtent() - newExtent) ) )
resize(newPosition, newExtent);
}
}
else // Normal window sizing functionality
resize(newPosition, newExtent);
// Add a callback for the GUI scripts
onMouseDragged_callback();
2012-09-19 15:15:01 +00:00
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::onMouseUp(const GuiEvent &event)
{
bool closing = mCloseButtonPressed;
bool maximizing = mMaximizeButtonPressed;
bool minimizing = mMinimizeButtonPressed;
mCloseButtonPressed = false;
mMaximizeButtonPressed = false;
mMinimizeButtonPressed = false;
TORQUE_UNUSED(event);
mouseUnlock();
mMouseMovingWin = false;
mMouseResizeWidth = false;
mMouseResizeHeight = false;
GuiControl *parent = getParent();
if (! parent)
return;
if( mIsMouseResizing )
{
mIsMouseResizing = false;
return;
}
Point2I localPoint = globalToLocalCoord(event.mousePoint);
if (closing && mCloseButton.pointInRect(localPoint))
{
// Here is where were going to put our other if statement
// if the window closes, and there were only 2 windows in the array, then just delete the array and default there params
// if not, delete the window from the array, default its params, and renumber the windows
if( engineAPI::gUseConsoleInterop )
evaluate( mCloseCommand );
onClose_callback();
}
else if (maximizing && mMaximizeButton.pointInRect(localPoint))
{
if (mMaximized)
{
// Resize to the previous position and extent, bounded by the parent
resize(Point2I(getMax(0, getMin(parent->getWidth() - mStandardBounds.extent.x, mStandardBounds.point.x)),
getMax(0, getMin(parent->getHeight() - mStandardBounds.extent.y, mStandardBounds.point.y))),
mStandardBounds.extent);
// Set the flag
mMaximized = false;
onRestore_callback();
}
else
{
// Only save the position if we're not minimized
if (! mMinimized)
mStandardBounds = getBounds();
else
mMinimized = false;
// Resize to fit the parent
resize(Point2I(0, 0), parent->getExtent());
// Set the flag
mMaximized = true;
onMaximize_callback();
}
}
else if (minimizing && mMinimizeButton.pointInRect(localPoint))
{
if (mMinimized)
{
// Resize to the previous position and extent, bounded by the parent
resize(Point2I(getMax(0, getMin(parent->getWidth() - mStandardBounds.extent.x, mStandardBounds.point.x)),
getMax(0, getMin(parent->getHeight() - mStandardBounds.extent.y, mStandardBounds.point.y))),
mStandardBounds.extent);
// Set the flag
mMinimized = false;
onRestore_callback();
}
else
{
if (parent->getWidth() < 100 || parent->getHeight() < mTitleHeight + 3)
return;
// Only save the position if we're not maximized
if (! mMaximized)
{
mStandardBounds = getBounds();
}
else
{
mMaximized = false;
}
// First find the lowest unused minimized index up to 32 minimized windows
U32 indexMask = 0;
iterator i;
S32 count = 0;
for (i = parent->begin(); i != parent->end() && count < 32; i++)
{
count++;
S32 index;
GuiWindowCtrl *ctrl = dynamic_cast<GuiWindowCtrl *>(*i);
if (ctrl && ctrl->isMinimized(index))
{
indexMask |= (1 << index);
}
}
// Now find the first unused bit
for (count = 0; count < 32; count++)
{
if (! (indexMask & (1 << count))) break;
}
// If we have more than 32 minimized windows, use the first position
count = getMax(0, count);
// This algorithm assumes all window have the same title height, and will minimize to 98 pix
Point2I newExtent(98, mTitleHeight);
// First, how many can fit across
S32 numAcross = getMax(1, (parent->getWidth() / newExtent.x + 2));
// Find the new "mini position"
Point2I newPosition;
newPosition.x = (count % numAcross) * (newExtent.x + 2) + 2;
newPosition.y = parent->getHeight() - (((count / numAcross) + 1) * (newExtent.y + 2)) - 2;
// Find the minimized position and extent
resize(newPosition, newExtent);
// Set the index so other windows will not try to minimize to the same location
mMinimizeIndex = count;
// Set the flag
mMinimized = true;
onMinimize_callback();
}
}
else if ( !(mResizeEdge & edgeTop) && localPoint.y < mTitleHeight && event.mousePoint == mMouseDownPosition)
{
if (mCanClose && mCloseButton.pointInRect(localPoint))
return;
else if (mCanMaximize && mMaximizeButton.pointInRect(localPoint))
return;
else if (mCanMinimize && mMinimizeButton.pointInRect(localPoint))
return;
else if( mCanCollapse ) // If we clicked anywhere else on the title bar
toggleCollapseGroup();
}
else if( mEdgeSnap && mCanCollapse )
{
// Create storage pointer
GuiWindowCtrl* hitWindow = NULL;
S32 snapType = -1;
Point2I deltaMousePosition = event.mousePoint - mMouseDownPosition;
Point2I newExtent = getExtent();
RectI bounds = getGlobalBounds();
bounds.point = mOrigBounds.point + deltaMousePosition;
EdgeRectI edges = EdgeRectI( bounds, mResizeMargin );
RectI snapZone = bounds;
snapZone.point.x -= SnapDistance;
snapZone.point.y -= SnapDistance;
snapZone.extent.x += SnapDistance + SnapDistance;
snapZone.extent.y += SnapDistance + SnapDistance;
Vector<EdgeRectI> snapList;
Vector<GuiWindowCtrl*> windowList;
getSnappableWindows( windowList, true );
for( S32 i =0; i < windowList.size(); i++ )
{
if( !snapZone.overlaps( windowList[i]->getGlobalBounds() ) )
continue;
// Build edges for snap detection
EdgeRectI snapRect( windowList[i]->getGlobalBounds(), mResizeMargin );
if( windowList[i]->mCollapseGroupNum == -1 )
{
// BECOMES "PARENT"
if( snapRect.top.position.y <= edges.bottom.position.y + SnapDistance + SnapDistance &&
snapRect.top.position.y >= edges.bottom.position.y - SnapDistance - SnapDistance )
{
hitWindow = windowList[i];
snapType = 0;
newExtent.x = snapRect.right.position.x - snapRect.left.position.x;
break;
}
}
if( (windowList[i]->mCollapseGroupNum == -1) || (windowList[i]->mCollapseGroupNum == mCollapseGroupNum - 1) ||
(!parent->mCollapseGroupVec.empty() && parent->mCollapseGroupVec[windowList[i]->mCollapseGroup].last() == windowList[i]) )
{
// BECOMES "CHILD"
if( snapRect.bottom.position.y <= edges.top.position.y + SnapDistance + SnapDistance &&
snapRect.bottom.position.y >= edges.top.position.y - SnapDistance - SnapDistance)
{
hitWindow = windowList[i];
snapType = 1;
newExtent.x = snapRect.right.position.x - snapRect.left.position.x;
break;
}
}
}
// We're either moving out of a collapse group or moving to another one
// Not valid for windows not previously in a group
if( mCollapseGroup >= 0 &&
2017-01-12 04:34:46 +00:00
(snapType == -1 || (hitWindow && snapType >= 0 && mCollapseGroup != hitWindow->mCollapseGroup)))
2012-09-19 15:15:01 +00:00
moveFromCollapseGroup();
// No window to connect to
if( !hitWindow )
return;
moveToCollapseGroup( hitWindow, snapType );
resize( getPosition(), newExtent );
}
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::onMouseMove(const GuiEvent &event)
{
mMousePosition = globalToLocalCoord(event.mousePoint);
}
bool GuiWindowCtrl::onKeyDown(const GuiEvent &event)
{
// If this control is a dead end, kill the event
if ((! mVisible) || (! mActive) || (! mAwake)) return true;
if ((event.keyCode == KEY_TAB) && (event.modifier & SI_PRIMARY_CTRL))
{
// Find the next sibling window, and select it
GuiControl *parent = getParent();
if (parent)
{
GuiWindowCtrl *firstWindow = NULL;
iterator i;
for (i = parent->begin(); i != parent->end(); i++)
{
GuiWindowCtrl *ctrl = dynamic_cast<GuiWindowCtrl *>(*i);
if (ctrl && ctrl->getTabIndex() == mTabIndex + 1)
{
ctrl->selectWindow();
return true;
}
else if (ctrl && ctrl->getTabIndex() == 0)
{
firstWindow = ctrl;
}
}
// Recycle from the beginning
if (firstWindow != this)
{
firstWindow->selectWindow();
return true;
}
}
}
return Parent::onKeyDown(event);
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::onRender(Point2I offset, const RectI &updateRect)
{
if( !mProfile || mProfile->mFont == NULL || mProfile->mBitmapArrayRects.size() < NumBitmaps )
return Parent::onRender( offset, updateRect );
// Draw the outline
RectI winRect;
winRect.point = offset;
winRect.extent = getExtent();
GuiCanvas *root = getRoot();
GuiControl *firstResponder = root ? root->getFirstResponder() : NULL;
bool isKey = (!firstResponder || controlIsChild(firstResponder));
U32 topBase = isKey ? BorderTopLeftKey : BorderTopLeftNoKey;
winRect.point.x += mBitmapBounds[BorderLeft].extent.x;
winRect.point.y += mBitmapBounds[topBase + 2].extent.y;
winRect.extent.x -= mBitmapBounds[BorderLeft].extent.x + mBitmapBounds[BorderRight].extent.x;
winRect.extent.y -= mBitmapBounds[topBase + 2].extent.y + mBitmapBounds[BorderBottom].extent.y;
winRect.extent.x += 1;
GFXDrawUtil* drawUtil = GFX->getDrawUtil();
2012-09-19 15:15:01 +00:00
drawUtil->drawRectFill(winRect, mProfile->mFillColor);
drawUtil->clearBitmapModulation();
drawUtil->drawBitmapSR(mTextureObject, offset, mBitmapBounds[topBase]);
drawUtil->drawBitmapSR(mTextureObject, Point2I(offset.x + getWidth() - mBitmapBounds[topBase+1].extent.x, offset.y),
2012-09-19 15:15:01 +00:00
mBitmapBounds[topBase + 1]);
RectI destRect;
destRect.point.x = offset.x + mBitmapBounds[topBase].extent.x;
destRect.point.y = offset.y;
destRect.extent.x = getWidth() - mBitmapBounds[topBase].extent.x - mBitmapBounds[topBase + 1].extent.x;
destRect.extent.y = mBitmapBounds[topBase + 2].extent.y;
RectI stretchRect = mBitmapBounds[topBase + 2];
stretchRect.inset(1,0);
drawUtil->drawBitmapStretchSR(mTextureObject, destRect, stretchRect);
2012-09-19 15:15:01 +00:00
destRect.point.x = offset.x;
destRect.point.y = offset.y + mBitmapBounds[topBase].extent.y;
destRect.extent.x = mBitmapBounds[BorderLeft].extent.x;
destRect.extent.y = getHeight() - mBitmapBounds[topBase].extent.y - mBitmapBounds[BorderBottomLeft].extent.y;
stretchRect = mBitmapBounds[BorderLeft];
stretchRect.inset(0,1);
drawUtil->drawBitmapStretchSR(mTextureObject, destRect, stretchRect);
2012-09-19 15:15:01 +00:00
destRect.point.x = offset.x + getWidth() - mBitmapBounds[BorderRight].extent.x;
destRect.extent.x = mBitmapBounds[BorderRight].extent.x;
destRect.point.y = offset.y + mBitmapBounds[topBase + 1].extent.y;
destRect.extent.y = getHeight() - mBitmapBounds[topBase + 1].extent.y - mBitmapBounds[BorderBottomRight].extent.y;
stretchRect = mBitmapBounds[BorderRight];
stretchRect.inset(0,1);
drawUtil->drawBitmapStretchSR(mTextureObject, destRect, stretchRect);
2012-09-19 15:15:01 +00:00
drawUtil->drawBitmapSR(mTextureObject, offset + Point2I(0, getHeight() - mBitmapBounds[BorderBottomLeft].extent.y), mBitmapBounds[BorderBottomLeft]);
drawUtil->drawBitmapSR(mTextureObject, offset + getExtent() - mBitmapBounds[BorderBottomRight].extent, mBitmapBounds[BorderBottomRight]);
2012-09-19 15:15:01 +00:00
destRect.point.x = offset.x + mBitmapBounds[BorderBottomLeft].extent.x;
destRect.extent.x = getWidth() - mBitmapBounds[BorderBottomLeft].extent.x - mBitmapBounds[BorderBottomRight].extent.x;
destRect.point.y = offset.y + getHeight() - mBitmapBounds[BorderBottom].extent.y;
destRect.extent.y = mBitmapBounds[BorderBottom].extent.y;
stretchRect = mBitmapBounds[BorderBottom];
stretchRect.inset(1,0);
drawUtil->drawBitmapStretchSR(mTextureObject, destRect, stretchRect);
2012-09-19 15:15:01 +00:00
// Draw the title
// dhc addition: copied/modded from renderJustifiedText, since we enforce a
// different color usage here. NOTE: it currently CAN overdraw the controls
// if mis-positioned or 'scrunched' in a small width.
drawUtil->setBitmapModulation(mProfile->mFontColor);
2012-09-19 15:15:01 +00:00
S32 textWidth = mProfile->mFont->getStrWidth((const UTF8 *)mText);
Point2I start(0,0);
// Align the horizontal
if ( mProfile->mAlignment == GuiControlProfile::RightJustify )
start.set( winRect.extent.x - textWidth, 0 );
else if ( mProfile->mAlignment == GuiControlProfile::CenterJustify )
start.set( ( winRect.extent.x - textWidth) / 2, 0 );
else // GuiControlProfile::LeftJustify or garbage... ;)
start.set( 0, 0 );
// If the text is longer then the box size, (it'll get clipped) so force Left Justify
if( textWidth > winRect.extent.x ) start.set( 0, 0 );
// center the vertical
// start.y = ( winRect.extent.y - ( font->getHeight() - 2 ) ) / 2;
drawUtil->drawText( mProfile->mFont, start + offset + mProfile->mTextOffset, mText );
2012-09-19 15:15:01 +00:00
// Deal with rendering the titlebar controls
AssertFatal(root, "Unable to get the root GuiCanvas.");
// Draw the close button
Point2I tempUL;
Point2I tempLR;
S32 bmp = (U32)BmpStates * (U32)BmpClose;
2012-09-19 15:15:01 +00:00
if( mCanClose ) {
if( mCloseButton.pointInRect( mMousePosition ) )
{
if( mCloseButtonPressed )
bmp += BmpDown;
else
bmp += BmpHilite;
}
drawUtil->clearBitmapModulation();
drawUtil->drawBitmapSR(mTextureObject, mButtonOffset + offset + mCloseButton.point, mBitmapBounds[bmp]);
2012-09-19 15:15:01 +00:00
}
// Draw the maximize button
if( mMaximized )
bmp = (U32)BmpStates * (U32)BmpNormal;
2012-09-19 15:15:01 +00:00
else
bmp = (U32)BmpStates * (U32)BmpMaximize;
2012-09-19 15:15:01 +00:00
if( mCanMaximize ) {
if( mMaximizeButton.pointInRect( mMousePosition ) )
{
if( mMaximizeButtonPressed )
bmp += BmpDown;
else
bmp += BmpHilite;
}
drawUtil->clearBitmapModulation();
drawUtil->drawBitmapSR( mTextureObject, mButtonOffset + offset + mMaximizeButton.point, mBitmapBounds[bmp] );
2012-09-19 15:15:01 +00:00
}
// Draw the minimize button
if( mMinimized )
bmp = (U32)BmpStates * (U32)BmpNormal;
2012-09-19 15:15:01 +00:00
else
bmp = (U32)BmpStates * (U32)BmpMinimize;
2012-09-19 15:15:01 +00:00
if( mCanMinimize ) {
if( mMinimizeButton.pointInRect( mMousePosition ) )
{
if( mMinimizeButtonPressed )
bmp += BmpDown;
else
bmp += BmpHilite;
}
drawUtil->clearBitmapModulation();
drawUtil->drawBitmapSR( mTextureObject, mButtonOffset + offset + mMinimizeButton.point, mBitmapBounds[bmp] );
2012-09-19 15:15:01 +00:00
}
if( !mMinimized )
{
// Render the children
renderChildControls( offset, updateRect );
}
}
//=============================================================================
// Misc.
//=============================================================================
// MARK: ---- Misc ----
//-----------------------------------------------------------------------------
const RectI GuiWindowCtrl::getClientRect()
{
if( !mProfile || mProfile->mBitmapArrayRects.size() < NumBitmaps )
return Parent::getClientRect();
if( !mBitmapBounds )
mBitmapBounds = mProfile->mBitmapArrayRects.address();
RectI winRect;
winRect.point.x = mBitmapBounds[BorderLeft].extent.x;
winRect.point.y = mBitmapBounds[BorderTopKey].extent.y;
winRect.extent.y = getHeight() - ( winRect.point.y + mBitmapBounds[BorderBottom].extent.y );
winRect.extent.x = getWidth() - ( winRect.point.x + mBitmapBounds[BorderRight].extent.x );
// Finally, inset it by padding
// Inset by padding. margin is specified for all t/b/l/r but
// uses only pointx pointy uniformly on both ends. This should be fixed. - JDD
winRect.inset(mResizeMargin, mResizeMargin);
2012-09-19 15:15:01 +00:00
return winRect;
}
//-----------------------------------------------------------------------------
bool GuiWindowCtrl::isMinimized(S32 &index)
{
index = mMinimizeIndex;
return mMinimized && mVisible;
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::positionButtons(void)
{
if( !mBitmapBounds || !mAwake )
return;
S32 buttonWidth = mBitmapBounds[(U32)BmpStates * (U32)BmpClose].extent.x;
S32 buttonHeight = mBitmapBounds[(U32)BmpStates * (U32)BmpClose].extent.y;
2012-09-19 15:15:01 +00:00
Point2I mainOff = mProfile->mTextOffset;
// Until a pref, if alignment is LEFT, put buttons RIGHT justified.
// ELSE, put buttons LEFT justified.
S32 closeLeft = mainOff.x, closeTop = mainOff.y, closeOff = buttonWidth + 2;
2012-09-19 15:15:01 +00:00
if ( mProfile->mAlignment == GuiControlProfile::LeftJustify )
{
closeOff = -closeOff;
closeLeft = getWidth() - buttonWidth - mainOff.x;
}
RectI closeRect(closeLeft, closeTop, buttonHeight, buttonWidth);
mCloseButton = closeRect;
// Always put Minimize on left side of Maximize.
closeRect.point.x += closeOff;
if (closeOff>0)
{
mMinimizeButton = closeRect;
closeRect.point.x += closeOff;
mMaximizeButton = closeRect;
}
else
{
mMaximizeButton = closeRect;
closeRect.point.x += closeOff;
mMinimizeButton = closeRect;
}
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::setCloseCommand(const char *newCmd)
{
if (newCmd)
mCloseCommand = StringTable->insert(newCmd);
else
mCloseCommand = StringTable->EmptyString();
2012-09-19 15:15:01 +00:00
}
//-----------------------------------------------------------------------------
GuiControl* GuiWindowCtrl::findHitControl(const Point2I &pt, S32 initialLayer)
{
if (! mMinimized)
return Parent::findHitControl(pt, initialLayer);
else
return this;
}
//-----------------------------------------------------------------------------
bool GuiWindowCtrl::resize(const Point2I &newPosition, const Point2I &newExtent)
{
if( !Parent::resize(newPosition, newExtent) )
return false;
// Set the button coords
positionButtons();
Adjusted handling for the bitmap and bitmapAsset fields for guiBitmapButtonCtrl to forcefully update the button states when changed, ensuring that the bitmaps refresh when changed via the field Added callback for onResize to guiWindowCtrl so controls - such as the EditorTree - can be properly resized in certain circumstances when the window is changed Added getIncrement() and getRange() to GuiGameSettingsCtrl to better facilitate options manipulation on the script side Corrected some of the console method documentation strings in GuiGameSettingsCtrl Removed some unneeded, extraneous files and their asset definitions that came from odd import conversions. Where applicable, created cleaned up versions to make naming conventions and references stable Fixed canvas mode update text typo: FSAA -> FXAA Added logic to DOF, Light Rays, SSAO and Vignette postFX's to check both the preset setting AND the user preference before enabling. Shifted initialization order so PostFX's are loaded before we configure the canvas, to ensure stuff like the FXAAPostFX exists and can be toggled on on load Fixed multiple issues with options menu: When using gamepad, unable to navigate from categories to options. Fixed so can now traverse as normal Input limitations on gamepad necessitated changing of how setting applying happens, is now done as a 'apply or discard' prompt when leaving the options menu Added proper handling for adjusting settings with gamepad with left/right inputs Fixed issue where the unapplied change for an option was sometimes being processed as an object name rather than an implicit string. Now made to be explicit strings to avoid issue. Made the menu button input for "Select" to go from categories to options gamepad only, and hidden when in the options list Fixed issue where changing window mode didn't correctly affect resolution option. Now set up so changing this field correctly refreshes the resolution option. Specifically, when on borderless, the resolution field does not show, preventing confusion as it is always full resolution Generally have the options list refresh when changes happen to allow any and all fields to be able to dynamically respond to other options having changed improving flexibility. Cleaned up old, unused, commented out functions Added ability on OKCancel message boxes to override the button text if needed Fixed issue with AssetBrowser where the shrink/grow icons next to the preview size slider were not anchored correctly. Adjusted callback logic so if preview slider is clicked on, rather than dragged, it will correctly update the zoom values Added sorting to Modules List dropdown for the AssetBrowser Improved standardization of double-clicking in AssetBrowser. Now defaults to editing action if regularly browsing and selecting if in select mode. Still allows regular per-type overrides as normal Moved definition of GuiDisabledTextEditProfile to gui profiles.ed.tscript file, removed duplicates to stop error spam Adjusted default settings value for double-click action in AB to be edit to prevent unstable behavior Removed old file refs from Load Recent list in the default settings
2022-03-27 03:36:37 +00:00
onResize_callback(newPosition.x, newPosition.y, newExtent.x, newExtent.y);
2012-09-19 15:15:01 +00:00
return true;
}
//-----------------------------------------------------------------------------
GuiControl *GuiWindowCtrl::findNextTabable(GuiControl *curResponder, bool firstCall)
{
// Set the global if this is the first call (directly from the canvas)
if (firstCall)
{
GuiControl::smCurResponder = NULL;
}
// If the window does not already contain the first responder, return false
// ie. Can't tab into or out of a window
if (! controlIsChild(curResponder))
{
return NULL;
}
// Loop through, checking each child to see if it is the one that follows the firstResponder
GuiControl *tabCtrl = NULL;
iterator i;
for (i = begin(); i != end(); i++)
{
GuiControl *ctrl = static_cast<GuiControl *>(*i);
tabCtrl = ctrl->findNextTabable(curResponder, false);
if (tabCtrl) break;
}
// To ensure the tab cycles within the current window...
if (! tabCtrl)
{
tabCtrl = findFirstTabable();
}
mFirstResponder = tabCtrl;
return tabCtrl;
}
//-----------------------------------------------------------------------------
GuiControl *GuiWindowCtrl::findPrevTabable(GuiControl *curResponder, bool firstCall)
{
if (firstCall)
{
GuiControl::smPrevResponder = NULL;
}
// If the window does not already contain the first responder, return false
// ie. Can't tab into or out of a window
if (! controlIsChild(curResponder))
{
return NULL;
}
// Loop through, checking each child to see if it is the one that follows the firstResponder
GuiControl *tabCtrl = NULL;
iterator i;
for (i = begin(); i != end(); i++)
{
GuiControl *ctrl = static_cast<GuiControl *>(*i);
tabCtrl = ctrl->findPrevTabable(curResponder, false);
if (tabCtrl) break;
}
// To ensure the tab cycles within the current window...
if (! tabCtrl)
{
tabCtrl = findLastTabable();
}
mFirstResponder = tabCtrl;
return tabCtrl;
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::selectWindow(void)
{
// First make sure this window is the front most of its siblings
GuiControl *parent = getParent();
if (parent && *parent->end() != this )
{
// Valid collapse groups have to be selected together
if( mCanCollapse && mCollapseGroup >= 0 )
{
CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[mCollapseGroup].begin();
for(; iter != parent->mCollapseGroupVec[mCollapseGroup].end(); iter++ )
{
parent->pushObjectToBack( (*iter) );
}
}
else
{
parent->pushObjectToBack( this );
}
}
// Also set the first responder to be the one within this window
setFirstResponder(mFirstResponder);
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent)
{
GuiCanvas *pRoot = getRoot();
if( !pRoot )
return;
PlatformWindow *pWindow = static_cast<GuiCanvas*>(getRoot())->getPlatformWindow();
AssertFatal(pWindow != NULL,"GuiControl without owning platform window! This should not be possible.");
PlatformCursorController *pController = pWindow->getCursorController();
AssertFatal(pController != NULL,"PlatformWindow without an owned CursorController!");
S32 desiredCursor = PlatformCursorController::curArrow;
S32 hitEdges = findHitEdges( lastGuiEvent.mousePoint );
if( hitEdges & edgeBottom && hitEdges & edgeLeft && mResizeHeight )
desiredCursor = PlatformCursorController::curResizeNESW;
else if( hitEdges & edgeBottom && hitEdges & edgeRight && mResizeHeight )
desiredCursor = PlatformCursorController::curResizeNWSE;
else if( hitEdges & edgeBottom && mResizeHeight )
desiredCursor = PlatformCursorController::curResizeHorz;
else if( hitEdges & edgeTop && hitEdges & edgeLeft && mResizeHeight )
desiredCursor = PlatformCursorController::curResizeNWSE;
else if( hitEdges & edgeTop && hitEdges & edgeRight && mResizeHeight )
desiredCursor = PlatformCursorController::curResizeNESW;
else if( hitEdges & edgeTop && mResizeHeight )
desiredCursor = PlatformCursorController::curResizeHorz;
else if ( hitEdges & edgeLeft && mResizeWidth )
desiredCursor = PlatformCursorController::curResizeVert;
else if( hitEdges & edgeRight && mResizeWidth )
desiredCursor = PlatformCursorController::curResizeVert;
else
desiredCursor = PlatformCursorController::curArrow;
// Bail if we're already at the desired cursor
if(pRoot->mCursorChanged == desiredCursor )
return;
// Now change the cursor shape
pController->popCursor();
pController->pushCursor(desiredCursor);
pRoot->mCursorChanged = desiredCursor;
}
//-----------------------------------------------------------------------------
void GuiWindowCtrl::parentResized(const RectI &oldParentRect, const RectI &newParentRect)
{
if(!mCanResize)
return;
GuiControl *parent = getParent();
if( !parent )
return;
// Bail if were not sized both by windowrelative
if( mHorizSizing != horizResizeWindowRelative || mHorizSizing != vertResizeWindowRelative )
return Parent::parentResized( oldParentRect, newParentRect );
Point2I newPosition = getPosition();
Point2I newExtent = getExtent();
bool doCollapse = false;
S32 deltaX = newParentRect.extent.x - oldParentRect.extent.x;
S32 deltaY = newParentRect.extent.y - oldParentRect.extent.y;// + mProfile->mYPositionOffset;
if( newPosition.x > ( oldParentRect.extent.x / 2 ) )
newPosition.x = newPosition.x + deltaX;
if (oldParentRect.extent.y != 0)
{
// Only if were apart of a group
if ( mCanCollapse && mCollapseGroup >= 0 )
{
// Setup parsing mechanisms
CollapseGroupNumVec collapseGroupNumVec;
// Lets grab the information we need (this should probably be already stored on each individual window object)
S32 groupNum = mCollapseGroup;
S32 groupMax = parent->mCollapseGroupVec[ groupNum ].size() - 1;
// Set up vars that we're going to be using
S32 groupPos = 0;
S32 groupExtent = 0;
S32 tempGroupExtent = 0;
// Set up the vector/iterator combo we need
collapseGroupNumVec = parent->mCollapseGroupVec[ groupNum ];
CollapseGroupNumVec::iterator iter = collapseGroupNumVec.begin();
// Grab some more information we need later ( this info should also be located on each ind. window object )
for( ; iter != collapseGroupNumVec.end(); iter++ )
{
if((*iter)->getCollapseGroupNum() == 0)
{
groupPos = (*iter)->getPosition().y;
}
groupExtent += (*iter)->getExtent().y;
}
// Use the information we just gatherered; only enter this block if we need to
tempGroupExtent = groupPos + groupExtent;
if( tempGroupExtent > ( newParentRect.extent.y / 2 ) )
{
// Lets size the collapse group
S32 windowParser = groupMax;
bool secondLoop = false;
while( tempGroupExtent >= newParentRect.extent.y )
{
if( windowParser == -1)
{
if( !secondLoop )
{
secondLoop = true;
windowParser = groupMax;
}
else
break;
}
GuiWindowCtrl *tempWindow = collapseGroupNumVec[windowParser];
if(tempWindow->mIsCollapsed)
{
windowParser--;
continue;
}
// Resizing block for the loop... if we can get away with just resizing the bottom window do it before
// resizing the whole block. else, go through the group and start setting min extents. if that doesnt work
// on the second go around, start collpsing the windows.
if( tempGroupExtent - ( tempWindow->getExtent().y - tempWindow->getMinExtent().y ) <= newParentRect.extent.y )
{
if( this == tempWindow )
newExtent.y = newExtent.y - ( tempGroupExtent - newParentRect.extent.y );
tempGroupExtent = tempGroupExtent - newParentRect.extent.y;
}
else
{
if( secondLoop )
{
tempGroupExtent = tempGroupExtent - (tempWindow->getExtent().y + 32);
if( this == tempWindow )
doCollapse = true;
}
else
{
tempGroupExtent = tempGroupExtent - (tempWindow->getExtent().y - tempWindow->getMinExtent().y);
if( this == tempWindow )
newExtent.y = tempWindow->getMinExtent().y;
}
}
windowParser--;
}
}
}
else if( newPosition.y > ( oldParentRect.extent.y / 2 ) )
{
newPosition.y = newPosition.y + deltaY - 1;
}
}
if( newExtent.x >= getMinExtent().x && newExtent.y >= getMinExtent().y )
{
// If we are already outside the reach of the main window, lets not place ourselves
// further out; but if were trying to improve visibility, go for it
// note: small tolerance (4) added to keep guis that are very slightly outside
// the main window (like all of the editor windows!) from appearing to 'undock'
// when the resolution is changed.
if( (newPosition.x + newExtent.x) > newParentRect.extent.x + 4 )
{
if( (newPosition.x + newExtent.x) > (getPosition().x + getExtent().x) )
{
newPosition.x = getPosition().x;
newExtent.x = getExtent().x;
}
}
if( (newPosition.y + newExtent.y) > newParentRect.extent.y + 4)
{
if( (newPosition.y + newExtent.y) > (getPosition().y + getExtent().y) )
{
newPosition.y = getPosition().y;
newExtent.y = getExtent().y;
}
}
// Only for collpasing groups, if were not, then do it like normal windows
if( mCanCollapse && mCollapseGroup >= 0 )
2017-01-12 04:34:46 +00:00
{
2012-09-19 15:15:01 +00:00
bool resizeMe = false;
// Only the group window should control positioning
if( mCollapseGroupNum == 0 )
{
resizeMe = resizeCollapseGroup( true, true, (getPosition() - newPosition), (getExtent() - newExtent) );
if(resizeMe == true)
resize(newPosition, newExtent);
}
else if( getExtent() != newExtent)
{
resizeMe = resizeCollapseGroup( false, true, (getPosition() - newPosition), (getExtent() - newExtent) );
if(resizeMe == true)
resize(getPosition(), newExtent);
}
}
else
{
resize(newPosition, newExtent);
}
}
if( mCanCollapse && !mIsCollapsed && doCollapse )
toggleCollapseGroup();
// If docking is invalid on this control, then bail out here
if( getDocking() & Docking::dockInvalid || getDocking() & Docking::dockNone)
return;
// 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 );
}
}
//=============================================================================
// Console Methods.
//=============================================================================
// MARK: ---- Console Methods ----
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiWindowCtrl, selectWindow, void, (),,
"Bring the window to the front." )
{
object->selectWindow();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiWindowCtrl, setCollapseGroup, void, ( bool state ),,
"Set the window's collapsing state." )
{
object->setCollapseGroup(state);
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiWindowCtrl, toggleCollapseGroup, void, (),,
"Toggle the window collapsing." )
{
object->toggleCollapseGroup();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiWindowCtrl, attachTo, void, ( GuiWindowCtrl* window ),,
"" )
{
object->moveToCollapseGroup( window, 1 );
}
//-----------------------------------------------------------------------------
DefineEngineStaticMethod( GuiWindowCtrl, attach, void, ( GuiWindowCtrl* bottomWindow, GuiWindowCtrl* topWindow ),,
"Attach @a bottomWindow to @topWindow so that @a bottomWindow moves along with @a topWindow when it is dragged.\n\n"
"@param bottomWindow \n"
"@param topWindow " )
{
if(bottomWindow == NULL || topWindow == NULL)
{
Con::warnf("Warning: AttachWindows - could not find windows");
return;
}
bottomWindow->moveToCollapseGroup( topWindow, 1 );
}