//----------------------------------------------------------------------------- // 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" 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), (0, 0, 0, 0), "Called when the window is resized in a regular manner by mouse manipulation."); //----------------------------------------------------------------------------- GuiWindowCtrl::GuiWindowCtrl() : mResizeWidth(true), mResizeEdge(edgeNone), mResizeHeight(true), mCanMove(true), mResizeMargin(2.f), mCanClose(true), mCanMinimize(true), mCanMaximize(true), mCanCollapse(false), mCanDock(false), mEdgeSnap(true), mCollapseGroup(-1), mCollapseGroupNum(-1), mIsCollapsed(false), mIsMouseResizing(false) { // 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; setExtent(100, 200); 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; mRepositionWindow = false; mResizeWindow = false; mSnapSignal = false; mPreCollapsedYExtent = 200; mPreCollapsedYMinExtent = 176; mText = "New Window"; } //----------------------------------------------------------------------------- void GuiWindowCtrl::initPersistFields() { 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." ); 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." ); 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); parent->mCollapseGroupVec.erase(groupVec); } } // 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 { groupVec = hitWindow->mCollapseGroup; 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; CollapseGroupNumVec collapseGroupNumVec; // 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; CollapseGroupNumVec collapseGroupNumVec; 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(*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(*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(); newChildPosition.y += moveChildYBy; (*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; CollapseGroupNumVec collapseGroupNumVec; 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 &windowOutVector, bool canCollapse ) { GuiControl *parent = getParent(); if( !parent ) return; S32 parentSize = parent->size(); for( S32 i = 0; i < parentSize; i++ ) { GuiWindowCtrl *childWindow = dynamic_cast(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; } mTextureObject = mProfile->getBitmapResource(); mBitmapBounds = mProfile->mBitmapArrayRects.address(); S32 buttonHeight = mBitmapBounds[BmpStates * BmpClose].extent.y; 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(*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) { #ifdef TORQUE_TOOLS GuiMenuBar* menuBar = dynamic_cast(guiCanvas->getMenuBar()); if (menuBar) { menuBarHeight = menuBar->getHeight(); } #endif } // 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) ) continue; // Build edges for snap detection EdgeRectI snapRect(windowBounds, mResizeMargin ); 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 ) { // 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); } //----------------------------------------------------------------------------- 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(*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 snapList; Vector 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 && (snapType == -1 || (hitWindow && snapType >= 0 && mCollapseGroup != hitWindow->mCollapseGroup))) 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(*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(); 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), 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); 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); 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); drawUtil->drawBitmapSR(mTextureObject, offset + Point2I(0, getHeight() - mBitmapBounds[BorderBottomLeft].extent.y), mBitmapBounds[BorderBottomLeft]); drawUtil->drawBitmapSR(mTextureObject, offset + getExtent() - mBitmapBounds[BorderBottomRight].extent, mBitmapBounds[BorderBottomRight]); 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); // 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); 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 ); // Deal with rendering the titlebar controls AssertFatal(root, "Unable to get the root GuiCanvas."); // Draw the close button Point2I tempUL; Point2I tempLR; S32 bmp = BmpStates * BmpClose; if( mCanClose ) { if( mCloseButton.pointInRect( mMousePosition ) ) { if( mCloseButtonPressed ) bmp += BmpDown; else bmp += BmpHilite; } drawUtil->clearBitmapModulation(); drawUtil->drawBitmapSR(mTextureObject, offset + mCloseButton.point, mBitmapBounds[bmp]); } // Draw the maximize button if( mMaximized ) bmp = BmpStates * BmpNormal; else bmp = BmpStates * BmpMaximize; if( mCanMaximize ) { if( mMaximizeButton.pointInRect( mMousePosition ) ) { if( mMaximizeButtonPressed ) bmp += BmpDown; else bmp += BmpHilite; } drawUtil->clearBitmapModulation(); drawUtil->drawBitmapSR( mTextureObject, offset + mMaximizeButton.point, mBitmapBounds[bmp] ); } // Draw the minimize button if( mMinimized ) bmp = BmpStates * BmpNormal; else bmp = BmpStates * BmpMinimize; if( mCanMinimize ) { if( mMinimizeButton.pointInRect( mMousePosition ) ) { if( mMinimizeButtonPressed ) bmp += BmpDown; else bmp += BmpHilite; } drawUtil->clearBitmapModulation(); drawUtil->drawBitmapSR( mTextureObject, offset + mMinimizeButton.point, mBitmapBounds[bmp] ); } 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( mSizingOptions.mPadding.point.x, mSizingOptions.mPadding.point.y ); return winRect; } //----------------------------------------------------------------------------- bool GuiWindowCtrl::isMinimized(S32 &index) { index = mMinimizeIndex; return mMinimized && mVisible; } //----------------------------------------------------------------------------- void GuiWindowCtrl::positionButtons(void) { if( !mBitmapBounds || !mAwake ) return; S32 buttonWidth = mBitmapBounds[BmpStates * BmpClose].extent.x; S32 buttonHeight = mBitmapBounds[BmpStates * BmpClose].extent.y; 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; 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(); } //----------------------------------------------------------------------------- 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(); onResize_callback(newPosition.x, newPosition.y, newExtent.x, newExtent.y); 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(*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(*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(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 ) { 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( (*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 ); }