//----------------------------------------------------------------------------- // 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 "console/engineAPI.h" #include "platform/platform.h" #include "gui/containers/guiFormCtrl.h" #include "gui/core/guiDefaultControlRender.h" #include "gfx/gfxDrawUtil.h" #ifdef TORQUE_TOOLS IMPLEMENT_CONOBJECT(GuiFormCtrl); ConsoleDocClass( GuiFormCtrl, "@brief A generic form control.\n\n" "Currently editor use only.\n\n " "@internal" ); IMPLEMENT_CALLBACK( GuiFormCtrl, onResize, void, (), (), "Called when the control is resized." ); GuiFormCtrl::GuiFormCtrl() { setMinExtent(Point2I(200,100)); mActive = true; mMouseOver = false; mDepressed = false; mCanMove = false; mCaption = "[none]"; mUseSmallCaption = false; mContentLibrary = StringTable->EmptyString(); mContent = StringTable->EmptyString(); mCanSaveFieldDictionary = true; mIsContainer = true; // The attached menu bar mHasMenu = false; mMenuBar = NULL; mMouseMovingWin = false; } GuiFormCtrl::~GuiFormCtrl() { // If we still have a menu bar, delete it. if( mMenuBar ) mMenuBar->deleteObject(); } bool GuiFormCtrl::_setHasMenu( void *object, const char *index, const char *data ) { GuiFormCtrl* ctrl = reinterpret_cast< GuiFormCtrl* >( object ); ctrl->setHasMenu( dAtob( data ) ); return false; } void GuiFormCtrl::initPersistFields() { addField("caption", TypeRealString, Offset(mCaption, GuiFormCtrl)); addField("contentLibrary",TypeString, Offset(mContentLibrary, GuiFormCtrl)); addField("content", TypeString, Offset(mContent, GuiFormCtrl)); addField("movable", TypeBool, Offset(mCanMove, GuiFormCtrl)); addProtectedField( "hasMenu", TypeBool, Offset(mHasMenu, GuiFormCtrl), &_setHasMenu, &defaultProtectedGetFn, "" ); Parent::initPersistFields(); } void GuiFormCtrl::setHasMenu( bool value ) { if( mHasMenu == value ) return; if( !value ) { mMenuBar->deleteObject(); mMenuBar = NULL; } else { if( !mMenuBar ) { mMenuBar = new GuiMenuBar(); mMenuBar->setField( "profile", "GuiFormMenuBarProfile" ); mMenuBar->setField( "horizSizing", "right" ); mMenuBar->setField( "vertSizing", "bottom" ); mMenuBar->setField( "extent", "16 16" ); mMenuBar->setField( "minExtent", "16 16" ); mMenuBar->setField( "position", "0 0" ); mMenuBar->setField( "class", "FormMenuBarClass "); // Give a generic class to the menu bar so that one set of functions may be used for all of them. mMenuBar->registerObject(); mMenuBar->setProcessTicks(true); // Activate the processing of ticks to track if the mouse pointer has been hovering within the menu } addObject( mMenuBar ); // Add the menu bar to the form } mHasMenu = value; } bool GuiFormCtrl::onWake() { if ( !Parent::onWake() ) return false; mFont = mProfile->mFont; AssertFatal(mFont, "GuiFormCtrl::onWake: invalid font in profile" ); mProfile->constructBitmapArray(); if(mProfile->mUseBitmapArray && mProfile->mBitmapArrayRects.size()) { mThumbSize.set( mProfile->mBitmapArrayRects[0].extent.x, mProfile->mBitmapArrayRects[0].extent.y ); mThumbSize.setMax( mProfile->mBitmapArrayRects[1].extent ); if(mFont->getHeight() > mThumbSize.y) mThumbSize.y = mFont->getHeight(); } else { mThumbSize.set(20, 20); } return true; } void GuiFormCtrl::addObject(SimObject *newObj ) { if( ( mHasMenu && size() > 1) || (!mHasMenu && size() > 0 ) ) { Con::warnf("GuiFormCtrl::addObject - Forms may only have one *direct* child - Placing on Parent!"); GuiControl* parent = getParent(); if ( parent ) parent->addObject( newObj ); return; } GuiControl *newCtrl = dynamic_cast( newObj ); GuiFormCtrl*formCtrl = dynamic_cast( newObj ); if( newCtrl && formCtrl ) newCtrl->setCanSave( true ); else if ( newCtrl ) newCtrl->setCanSave( false ); Parent::addObject( newObj ); } void GuiFormCtrl::removeObject( SimObject* object ) { if( object == mMenuBar ) { mHasMenu = false; mMenuBar = NULL; } Parent::removeObject( object ); } bool GuiFormCtrl::acceptsAsChild( SimObject* object ) const { return Parent::acceptsAsChild( object ) && ( ( mHasMenu && size() == 1 ) || ( !mHasMenu && !size() ) ); // Only accept a single child. } void GuiFormCtrl::onSleep() { Parent::onSleep(); mFont = NULL; } bool GuiFormCtrl::resize(const Point2I &newPosition, const Point2I &newExtent) { if( !Parent::resize(newPosition, newExtent) ) return false; if( !mAwake || !mProfile->mBitmapArrayRects.size() ) return false; // Should the caption be modified because the title bar is too small? S32 textWidth = mProfile->mFont->getStrWidth(mCaption); S32 newTextArea = getWidth() - mThumbSize.x - mProfile->mBitmapArrayRects[4].extent.x; if(newTextArea < textWidth) { static char buf[256]; mUseSmallCaption = true; mSmallCaption = StringTable->EmptyString(); S32 strlen = dStrlen((const char*)mCaption); for(S32 i=strlen; i>=0; --i) { dStrcpy(buf, "", i); dStrcat(buf, (const char*)mCaption, i); dStrcat(buf, "...", i); textWidth = mProfile->mFont->getStrWidth(buf); if(textWidth < newTextArea) { mSmallCaption = StringTable->insert(buf, true); break; } } } else { mUseSmallCaption = false; } onResize_callback(); return true; } void GuiFormCtrl::onRender(Point2I offset, const RectI &updateRect) { // Fill in the control's child area RectI boundsRect(offset, getExtent()); boundsRect.point.y += mThumbSize.y; boundsRect.extent.y -= mThumbSize.y; // draw the border of the form if specified if (mProfile->mOpaque) GFX->getDrawUtil()->drawRectFill(boundsRect, mProfile->mFillColor); if (mProfile->mBorder) renderBorder(boundsRect, mProfile); // If we don't have a child, put some text in the child area if( empty() ) { GFX->getDrawUtil()->setBitmapModulation(ColorI(0,0,0)); renderJustifiedText(boundsRect.point, boundsRect.extent, "[none]"); } S32 textWidth = 0; // Draw our little bar, too if (mProfile->mBitmapArrayRects.size() >= 5) { GFX->getDrawUtil()->clearBitmapModulation(); S32 barStart = offset.x + textWidth; S32 barTop = mThumbSize.y / 2 + offset.y - mProfile->mBitmapArrayRects[3].extent.y / 2; Point2I barOffset(barStart, barTop); // Draw the start of the bar... GFX->getDrawUtil()->drawBitmapStretchSR(mProfile->getBitmapResource(),RectI(barOffset, mProfile->mBitmapArrayRects[2].extent), mProfile->mBitmapArrayRects[2] ); // Now draw the middle... barOffset.x += mProfile->mBitmapArrayRects[2].extent.x; S32 barMiddleSize = (getExtent().x - (barOffset.x - offset.x)) - mProfile->mBitmapArrayRects[4].extent.x + 1; if (barMiddleSize > 0) { // We have to do this inset to prevent nasty stretching artifacts RectI foo = mProfile->mBitmapArrayRects[3]; foo.inset(1,0); GFX->getDrawUtil()->drawBitmapStretchSR( mProfile->getBitmapResource(), RectI(barOffset, Point2I(barMiddleSize, mProfile->mBitmapArrayRects[3].extent.y)), foo ); } // And the end barOffset.x += barMiddleSize; GFX->getDrawUtil()->drawBitmapStretchSR( mProfile->getBitmapResource(), RectI(barOffset, mProfile->mBitmapArrayRects[4].extent), mProfile->mBitmapArrayRects[4]); GFX->getDrawUtil()->setBitmapModulation((mMouseOver ? mProfile->mFontColorHL : mProfile->mFontColor)); renderJustifiedText(Point2I(mThumbSize.x, 0) + offset, Point2I(getWidth() - mThumbSize.x - mProfile->mBitmapArrayRects[4].extent.x, mThumbSize.y), (mUseSmallCaption ? mSmallCaption : mCaption) ); } // Render the children renderChildControls(offset, updateRect); } void GuiFormCtrl::onMouseMove(const GuiEvent &event) { Point2I localMove = globalToLocalCoord(event.mousePoint); // If we're clicking in the header then resize mMouseOver = (localMove.y < mThumbSize.y); if(isMouseLocked()) mDepressed = mMouseOver; } void GuiFormCtrl::onMouseEnter(const GuiEvent &event) { setUpdate(); if(isMouseLocked()) { mDepressed = true; mMouseOver = true; } else { mMouseOver = true; } } void GuiFormCtrl::onMouseLeave(const GuiEvent &event) { setUpdate(); if(isMouseLocked()) mDepressed = false; mMouseOver = false; } void GuiFormCtrl::onMouseDown(const GuiEvent &event) { Point2I localClick = globalToLocalCoord(event.mousePoint); // If we're clicking in the header then resize if(localClick.y < mThumbSize.y) { mouseLock(); mDepressed = true; mMouseMovingWin = mCanMove; //update setUpdate(); } mOrigBounds = getBounds(); mMouseDownPosition = event.mousePoint; if (mMouseMovingWin ) { mouseLock(); } else { GuiControl *ctrl = findHitControl(localClick); if (ctrl && ctrl != this) ctrl->onMouseDown(event); } } void GuiFormCtrl::onMouseUp(const GuiEvent &event) { // Make sure we only get events we ought to be getting... if (! mActive) return; mouseUnlock(); setUpdate(); // If we're clicking in the header then resize //if(localClick.y < mThumbSize.y && mDepressed) // setCollapsed(!mCollapsed); } DefineEngineMethod( GuiFormCtrl, getMenuID, S32, (),, "Get the ID of this form's menu.\n\n" "@return The ID of the form menu\n" ) { return object->getMenuBarID(); } U32 GuiFormCtrl::getMenuBarID() { return mMenuBar ? mMenuBar->getId() : 0; } DefineEngineMethod( GuiFormCtrl, setCaption, void, ( const char* caption ),, "Sets the title of the form.\n\n" "@param caption Form caption\n" ) { object->setCaption( caption ); } #endif