//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "console/consoleTypes.h" #include "console/console.h" #include "dgl/dgl.h" #include "gui/core/guiCanvas.h" #include "gui/containers/guiWindowCtrl.h" #include "gui/core/guiDefaultControlRender.h" IMPLEMENT_CONOBJECT(GuiWindowCtrl); GuiWindowCtrl::GuiWindowCtrl(void) { mResizeWidth = true; mResizeHeight = true; mCanMove = true; mCanClose = true; mCanMinimize = true; mCanMaximize = true; mTitleHeight = 20; mResizeRightWidth = 10; mResizeBottomHeight = 10; mCloseCommand = StringTable->insert(""); mMinimized = false; mMaximized = false; mMouseMovingWin = false; mMouseResizeWidth = false; mMouseResizeHeight = false; mBounds.extent.set(100, 200); mMinSize.set(50, 50); mMinimizeIndex = -1; mTabIndex = -1; RectI closeRect(80, 2, 16, 16); mCloseButton = closeRect; closeRect.point.x -= 18; mMaximizeButton = closeRect; closeRect.point.x -= 18; mMinimizeButton = closeRect; //other defaults mActive = true; mPressClose = false; mPressMaximize = false; mPressMinimize = false; mDefaultCursor = NULL; mNWSECursor = NULL; mNESWCursor = NULL; mUpDownCursor = NULL; mLeftRightCursor = NULL; } void GuiWindowCtrl::initPersistFields() { Parent::initPersistFields(); addField("resizeWidth", TypeBool, Offset(mResizeWidth, GuiWindowCtrl)); addField("resizeHeight", TypeBool, Offset(mResizeHeight, GuiWindowCtrl)); addField("canMove", TypeBool, Offset(mCanMove, GuiWindowCtrl)); addField("canClose", TypeBool, Offset(mCanClose, GuiWindowCtrl)); addField("canMinimize", TypeBool, Offset(mCanMinimize, GuiWindowCtrl)); addField("canMaximize", TypeBool, Offset(mCanMaximize, GuiWindowCtrl)); addField("minSize", TypePoint2I, Offset(mMinSize, GuiWindowCtrl)); addField("closeCommand", TypeString, Offset(mCloseCommand, GuiWindowCtrl)); } bool GuiWindowCtrl::isMinimized(S32 &index) { index = mMinimizeIndex; return mMinimized && mVisible; } // helper fn so button positioning shares code... void GuiWindowCtrl::PositionButtons(void) { 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. int closeLeft = mainOff.x, closeTop = mainOff.y, closeOff = buttonWidth + 2; if ( mProfile->mAlignment == GuiControlProfile::LeftJustify ) { closeOff = -closeOff; closeLeft = mBounds.extent.x - 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; } } bool GuiWindowCtrl::onWake() { if (! Parent::onWake()) return false; //get the texture for the close, minimize, and maximize buttons mTextureHandle = mProfile->mTextureHandle; bool result = mProfile->constructBitmapArray() >= NumBitmaps; AssertFatal(result, "Failed to create the bitmap array"); if(!result) return false; mBitmapBounds = mProfile->mBitmapArrayRects.address(); S32 buttonWidth = mBitmapBounds[BmpStates * BmpClose].extent.x; S32 buttonHeight = mBitmapBounds[BmpStates * BmpClose].extent.y; mTitleHeight = buttonHeight + 4; mResizeRightWidth = mTitleHeight / 2; mResizeBottomHeight = mTitleHeight / 2; //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() { mTextureHandle = NULL; Parent::onSleep(); } GuiControl* GuiWindowCtrl::findHitControl(const Point2I &pt, S32 initialLayer) { if (! mMinimized) return Parent::findHitControl(pt, initialLayer); else return this; } void GuiWindowCtrl::resize(const Point2I &newPosition, const Point2I &newExtent) { Parent::resize(newPosition, newExtent); //set the button coords PositionButtons(); } void GuiWindowCtrl::onMouseDown(const GuiEvent &event) { setUpdate(); mOrigBounds = mBounds; mMouseDownPosition = event.mousePoint; Point2I localPoint = globalToLocalCoord(event.mousePoint); //select this window - move it to the front, and set the first responder selectWindow(); //if we clicked within the title bar if (localPoint.y < mTitleHeight) { //if we clicked on the close button if (mCanClose && mCloseButton.pointInRect(localPoint)) { mPressClose = mCanClose; } else if (mCanMaximize && mMaximizeButton.pointInRect(localPoint)) { mPressMaximize = mCanMaximize; } else if (mCanMinimize && mMinimizeButton.pointInRect(localPoint)) { mPressMinimize = mCanMinimize; } //else we clicked within the title else { mMouseMovingWin = mCanMove; mMouseResizeWidth = false; mMouseResizeHeight = false; } } else { mMouseMovingWin = false; //see if we clicked on the right edge if (mResizeWidth && (localPoint.x > mBounds.extent.x - mResizeRightWidth)) { mMouseResizeWidth = true; } //see if we clicked on the bottom edge (as well) if (mResizeHeight && (localPoint.y > mBounds.extent.y - mResizeBottomHeight)) { mMouseResizeHeight = true; } } if (mMouseMovingWin || mMouseResizeWidth || mMouseResizeHeight || mPressClose || mPressMaximize || mPressMinimize) { 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; Point2I deltaMousePosition = event.mousePoint - mMouseDownPosition; Point2I newPosition = mBounds.point; Point2I newExtent = mBounds.extent; bool update = false; if (mMouseMovingWin && parent) { newPosition.x = getMax(0, getMin(parent->mBounds.extent.x - mBounds.extent.x, mOrigBounds.point.x + deltaMousePosition.x)); newPosition.y = getMax(0, getMin(parent->mBounds.extent.y - mBounds.extent.y, mOrigBounds.point.y + deltaMousePosition.y)); update = true; } else if(mPressClose || mPressMaximize || mPressMinimize) { setUpdate(); } else { if (mMouseResizeWidth && parent) { newExtent.x = getMax(0, getMax(mMinSize.x, getMin(parent->mBounds.extent.x, mOrigBounds.extent.x + deltaMousePosition.x))); update = true; } if (mMouseResizeHeight && parent) { newExtent.y = getMax(0, getMax(mMinSize.y, getMin(parent->mBounds.extent.y, mOrigBounds.extent.y + deltaMousePosition.y))); update = true; } } if (update) { Point2I pos = parent->localToGlobalCoord(mBounds.point); root->addUpdateRegion(pos, mBounds.extent); resize(newPosition, newExtent); } } void GuiWindowCtrl::onMouseUp(const GuiEvent &event) { bool closing = mPressClose; bool maximizing = mPressMaximize; bool minimizing = mPressMinimize; mPressClose = false; mPressMaximize = false; mPressMinimize = false; event; mouseUnlock(); mMouseMovingWin = false; mMouseResizeWidth = false; mMouseResizeHeight = false; GuiControl *parent = getParent(); if (! parent) return; //see if we take an action Point2I localPoint = globalToLocalCoord(event.mousePoint); if (closing && mCloseButton.pointInRect(localPoint)) { Con::evaluate(mCloseCommand); } 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->mBounds.extent.x - mStandardBounds.extent.x, mStandardBounds.point.x)), getMax(0, getMin(parent->mBounds.extent.y - mStandardBounds.extent.y, mStandardBounds.point.y))), mStandardBounds.extent); //set the flag mMaximized = false; } else { //only save the position if we're not minimized if (! mMinimized) { mStandardBounds = mBounds; } else { mMinimized = false; } //resize to fit the parent resize(Point2I(0, 0), parent->mBounds.extent); //set the flag mMaximized = true; } } 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->mBounds.extent.x - mStandardBounds.extent.x, mStandardBounds.point.x)), getMax(0, getMin(parent->mBounds.extent.y - mStandardBounds.extent.y, mStandardBounds.point.y))), mStandardBounds.extent); //set the flag mMinimized = false; } else { if (parent->mBounds.extent.x < 100 || parent->mBounds.extent.y < mTitleHeight + 3) return; //only save the position if we're not maximized if (! mMaximized) { mStandardBounds = mBounds; } 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->mBounds.extent.x / newExtent.x + 2)); //find the new "mini position" Point2I newPosition; newPosition.x = (count % numAcross) * (newExtent.x + 2) + 2; newPosition.y = parent->mBounds.extent.y - (((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; } } } 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; } 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_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::selectWindow(void) { //first make sure this window is the front most of its siblings GuiControl *parent = getParent(); if (parent) { parent->pushObjectToBack(this); } //also set the first responder to be the one within this window setFirstResponder(mFirstResponder); } void GuiWindowCtrl::drawWinRect(const RectI &myRect) { Point2I bl = myRect.point; Point2I tr; tr.x = myRect.point.x + myRect.extent.x - 1; tr.y = myRect.point.y + myRect.extent.y - 1; dglDrawRectFill(myRect, mProfile->mFillColor); dglDrawLine(Point2I(bl.x + 1, tr.y), Point2I(bl.x + 1, bl.y), ColorI(255, 255, 255)); dglDrawLine(Point2I(bl.x, tr.y + 1), Point2I(tr.x, tr.y + 1), ColorI(255, 255, 255)); //dglDrawRect(myRect, ColorI(0, 0, 0)); // Taken out, this is controled via mProfile->mBorder } void GuiWindowCtrl::onRender(Point2I offset, const RectI &updateRect) { //draw the outline RectI winRect; winRect.point = offset; winRect.extent = mBounds.extent; 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; dglDrawRectFill(winRect, mProfile->mFillColor); dglClearBitmapModulation(); dglDrawBitmapSR(mTextureHandle, offset, mBitmapBounds[topBase]); dglDrawBitmapSR(mTextureHandle, Point2I(offset.x + mBounds.extent.x - 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 = mBounds.extent.x - 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); dglDrawBitmapStretchSR(mTextureHandle, 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 = mBounds.extent.y - mBitmapBounds[topBase].extent.y - mBitmapBounds[BorderBottomLeft].extent.y; stretchRect = mBitmapBounds[BorderLeft]; stretchRect.inset(0,1); dglDrawBitmapStretchSR(mTextureHandle, destRect, stretchRect); destRect.point.x = offset.x + mBounds.extent.x - mBitmapBounds[BorderRight].extent.x; destRect.extent.x = mBitmapBounds[BorderRight].extent.x; destRect.point.y = offset.y + mBitmapBounds[topBase + 1].extent.y; destRect.extent.y = mBounds.extent.y - mBitmapBounds[topBase + 1].extent.y - mBitmapBounds[BorderBottomRight].extent.y; stretchRect = mBitmapBounds[BorderRight]; stretchRect.inset(0,1); dglDrawBitmapStretchSR(mTextureHandle, destRect, stretchRect); dglDrawBitmapSR(mTextureHandle, offset + Point2I(0, mBounds.extent.y - mBitmapBounds[BorderBottomLeft].extent.y), mBitmapBounds[BorderBottomLeft]); dglDrawBitmapSR(mTextureHandle, offset + mBounds.extent - mBitmapBounds[BorderBottomRight].extent, mBitmapBounds[BorderBottomRight]); destRect.point.x = offset.x + mBitmapBounds[BorderBottomLeft].extent.x; destRect.extent.x = mBounds.extent.x - mBitmapBounds[BorderBottomLeft].extent.x - mBitmapBounds[BorderBottomRight].extent.x; destRect.point.y = offset.y + mBounds.extent.y - mBitmapBounds[BorderBottom].extent.y; destRect.extent.y = mBitmapBounds[BorderBottom].extent.y; stretchRect = mBitmapBounds[BorderBottom]; stretchRect.inset(1,0); dglDrawBitmapStretchSR(mTextureHandle, 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. dglSetBitmapModulation(mProfile->mFontColor); S32 fontHeight = mFont->getHeight(); 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; dglDrawText(mFont, start + offset + mProfile->mTextOffset, mText); // deal with rendering the titlebar controls AssertFatal(root, "Unable to get the root Canvas."); Point2I localPoint = globalToLocalCoord(root->getCursorPos()); //draw the close button Point2I tempUL; Point2I tempLR; S32 bmp = BmpStates * BmpClose; if( mCanClose ) { if( mCloseButton.pointInRect( localPoint ) && mPressClose ) bmp += BmpHilite; dglClearBitmapModulation(); dglDrawBitmapSR(mTextureHandle, offset + mCloseButton.point, mBitmapBounds[bmp]); } //draw the maximize button if( mMaximized ) bmp = BmpStates * BmpNormal; else bmp = BmpStates * BmpMaximize; if( mCanMaximize ) { if( mMaximizeButton.pointInRect( localPoint ) && mPressMaximize ) bmp += BmpHilite; dglClearBitmapModulation(); dglDrawBitmapSR( mTextureHandle, offset + mMaximizeButton.point, mBitmapBounds[bmp] ); } //draw the minimize button if( mMinimized ) bmp = BmpStates * BmpNormal; else bmp = BmpStates * BmpMinimize; if( mCanMinimize ) { if( mMinimizeButton.pointInRect( localPoint ) && mPressMinimize ) bmp += BmpHilite; dglClearBitmapModulation(); dglDrawBitmapSR( mTextureHandle, offset + mMinimizeButton.point, mBitmapBounds[bmp] ); } if( !mMinimized ) { //render the children renderChildControls( offset, updateRect ); } } bool GuiWindowCtrl::initCursors() { if ( mUpDownCursor == NULL || mLeftRightCursor == NULL || mDefaultCursor == NULL || mNWSECursor == NULL || mNESWCursor == NULL ) { SimObject *obj; obj = Sim::findObject("UpDownCursor"); mUpDownCursor = dynamic_cast(obj); obj = Sim::findObject("LeftRightCursor"); mLeftRightCursor = dynamic_cast(obj); obj = Sim::findObject("DefaultCursor"); mDefaultCursor = dynamic_cast(obj); obj = Sim::findObject("NESWCursor"); mNESWCursor = dynamic_cast(obj); obj = Sim::findObject("NWSECursor"); mNWSECursor = dynamic_cast(obj); return( mUpDownCursor != NULL && mLeftRightCursor != NULL && mDefaultCursor != NULL && mNWSECursor != NULL && mNESWCursor != NULL ); } else return(true); } void GuiWindowCtrl::getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent) { showCursor = true; if( initCursors() ) { Point2I mousePos = lastGuiEvent.mousePoint; RectI winRect = mBounds; RectI rightRect = RectI( ( ( winRect.extent.x + winRect.point.x ) - mResizeRightWidth), winRect.point.y, mResizeRightWidth, winRect.extent.y ); RectI bottomRect = RectI( winRect.point.x, ( ( winRect.point.y + winRect.extent.y ) - mResizeBottomHeight), winRect.extent.x , mResizeBottomHeight ); bool resizeRight = rightRect.pointInRect( mousePos ); bool resizeBottom = bottomRect.pointInRect( mousePos ); if ( resizeRight && resizeBottom && mResizeHeight && mResizeWidth ) cursor = mNWSECursor; else if ( resizeBottom && mResizeHeight ) cursor = mUpDownCursor; else if ( resizeRight && mResizeWidth ) cursor = mLeftRightCursor; else cursor = NULL; } }