//----------------------------------------------------------------------------- // 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 "gui/core/guiCanvas.h" #include "gui/controls/guiPopUpCtrl.h" #include "console/consoleTypes.h" #include "console/engineAPI.h" #include "gui/core/guiDefaultControlRender.h" #include "gfx/primBuilder.h" #include "gfx/gfxDrawUtil.h" #include "console/engineAPI.h" static ColorI colorWhite(255,255,255); // Added // Function to return the number of columns in 'string' given delimeters in 'set' static U32 getColumnCount(const char *string, const char *set) { U32 count = 0; U8 last = 0; while(*string) { last = *string++; for(U32 i =0; set[i]; i++) { if(last == set[i]) { count++; last = 0; break; } } } if(last) count++; return count; } // Function to return the 'index' column from 'string' given delimeters in 'set' static const char *getColumn(const char *string, char* returnbuff, U32 index, const char *set) { U32 sz; while(index--) { if(!*string) return ""; sz = dStrcspn(string, set); if (string[sz] == 0) return ""; string += (sz + 1); } sz = dStrcspn(string, set); if (sz == 0) return ""; char *ret = returnbuff; dStrncpy(ret, string, sz); ret[sz] = '\0'; return ret; } GuiPopUpBackgroundCtrl::GuiPopUpBackgroundCtrl(GuiPopUpMenuCtrl *ctrl, GuiPopupTextListCtrl *textList) { mPopUpCtrl = ctrl; mTextList = textList; } void GuiPopUpBackgroundCtrl::onMouseDown(const GuiEvent &event) { mPopUpCtrl->mBackgroundCancel = true; // Set that the user didn't click within the text list. Replaces the line above. mPopUpCtrl->closePopUp(); } //------------------------------------------------------------------------------ GuiPopupTextListCtrl::GuiPopupTextListCtrl() { mPopUpCtrl = NULL; } //------------------------------------------------------------------------------ GuiPopupTextListCtrl::GuiPopupTextListCtrl(GuiPopUpMenuCtrl *ctrl) { mPopUpCtrl = ctrl; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ //void GuiPopUpTextListCtrl::onCellSelected( Point2I /*cell*/ ) //{ // // Do nothing, the parent control will take care of everything... //} void GuiPopupTextListCtrl::onCellSelected( Point2I cell ) { // The old function is above. This new one will only call the the select // functions if we were not cancelled by a background click. // Check if we were cancelled by the user clicking on the Background ie: anywhere // other than within the text list. if(mPopUpCtrl->mBackgroundCancel) return; if( isMethod( "onSelect" ) ) Con::executef(this, "onSelect", Con::getFloatArg(cell.x), Con::getFloatArg(cell.y)); //call the console function execConsoleCallback(); //if (mConsoleCommand[0]) // Con::evaluate(mConsoleCommand, false); } //------------------------------------------------------------------------------ bool GuiPopupTextListCtrl::onKeyDown(const GuiEvent &event) { //if the control is a dead end, don't process the input: if ( !mVisible || !mActive || !mAwake ) return false; //see if the key down is a or not if ( event.modifier == 0 ) { if ( event.keyCode == KEY_RETURN ) { mPopUpCtrl->closePopUp(); return true; } else if ( event.keyCode == KEY_ESCAPE ) { mSelectedCell.set( -1, -1 ); mPopUpCtrl->closePopUp(); return true; } } //otherwise, pass the event to it's parent return Parent::onKeyDown(event); } void GuiPopupTextListCtrl::onMouseUp(const GuiEvent &event) { Parent::onMouseUp( event ); mPopUpCtrl->closePopUp(); } //------------------------------------------------------------------------------ void GuiPopupTextListCtrl::onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver) { Point2I size; getCellSize( size ); // Render a background color for the cell if ( mouseOver ) { RectI cellR( offset.x, offset.y, size.x, size.y ); GFX->getDrawUtil()->drawRectFill( cellR, mProfile->mFillColorHL ); } else if ( selected ) { RectI cellR( offset.x, offset.y, size.x, size.y ); GFX->getDrawUtil()->drawRectFill( cellR, mProfile->mFillColorSEL ); } // Define the default x offset for the text U32 textXOffset = offset.x + mProfile->mTextOffset.x; // Do we also draw a colored box beside the text? ColorI boxColor; bool drawbox = mPopUpCtrl->getColoredBox( boxColor, mList[cell.y].id); if(drawbox) { Point2I coloredboxsize(15,10); RectI boxBounds(offset.x + mProfile->mTextOffset.x, offset.y+2, coloredboxsize.x, coloredboxsize.y); GFX->getDrawUtil()->drawRectFill(boxBounds, boxColor); GFX->getDrawUtil()->drawRect(boxBounds, ColorI(0,0,0)); textXOffset += coloredboxsize.x + mProfile->mTextOffset.x; } ColorI fontColor; mPopUpCtrl->getFontColor( fontColor, mList[cell.y].id, selected, mouseOver ); GFX->getDrawUtil()->setBitmapModulation( fontColor ); //GFX->drawText( mFont, Point2I( offset.x + 4, offset.y ), mList[cell.y].text ); // Get the number of columns in the cell S32 colcount = getColumnCount(mList[cell.y].text, "\t"); // Are there two or more columns? if(colcount >= 2) { char buff[256]; // Draw the first column getColumn(mList[cell.y].text, buff, 0, "\t"); GFX->getDrawUtil()->drawText( mFont, Point2I( textXOffset, offset.y ), buff ); // Used mTextOffset as a margin for the text list rather than the hard coded value of '4'. // Draw the second column to the right getColumn(mList[cell.y].text, buff, 1, "\t"); S32 txt_w = mFont->getStrWidth(buff); GFX->getDrawUtil()->drawText( mFont, Point2I( offset.x+size.x-mProfile->mTextOffset.x-txt_w, offset.y ), buff ); // Used mTextOffset as a margin for the text list rather than the hard coded value of '4'. } else { GFX->getDrawUtil()->drawText( mFont, Point2I( textXOffset, offset.y ), mList[cell.y].text ); // Used mTextOffset as a margin for the text list rather than the hard coded value of '4'. } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ IMPLEMENT_CONOBJECT(GuiPopUpMenuCtrl); ConsoleDocClass( GuiPopUpMenuCtrl, "@brief A control that allows to select a value from a drop-down list.\n\n" "For a nearly identical GUI with additional features, use GuiPopUpMenuCtrlEx.\n\n" "@tsexample\n" "new GuiPopUpMenuCtrl()\n" "{\n" " maxPopupHeight = \"200\";\n" " sbUsesNAColor = \"0\";\n" " reverseTextList = \"0\";\n" " bitmapBounds = \"16 16\";\n" " maxLength = \"1024\";\n" " position = \"56 31\";\n" " extent = \"64 64\";\n" " minExtent = \"8 2\";\n" " profile = \"GuiPopUpMenuProfile\";\n" " tooltipProfile = \"GuiToolTipProfile\";\n" "};\n" "@endtsexample\n\n" "@note This is definitely going to be deprecated soon.\n\n" "@see GuiPopUpMenuCtrlEx for more features and better explanations.\n" "@ingroup GuiControls\n"); GuiPopUpMenuCtrl::GuiPopUpMenuCtrl(void) { VECTOR_SET_ASSOCIATION(mEntries); VECTOR_SET_ASSOCIATION(mSchemes); mSelIndex = -1; mActive = true; mMaxPopupHeight = 200; mScrollDir = GuiScrollCtrl::None; mScrollCount = 0; mLastYvalue = 0; mIncValue = 0; mRevNum = 0; mInAction = false; mMouseOver = false; // Added mRenderScrollInNA = false; // Added mBackgroundCancel = false; // Added mReverseTextList = false; // Added - Don't reverse text list if displaying up INIT_IMAGEASSET_ARRAY(Bitmap, GFXDefaultGUIProfile, 0); INIT_IMAGEASSET_ARRAY(Bitmap, GFXDefaultGUIProfile, 1); mBitmapBounds.set(16, 16); // Added mIdMax = -1; mBackground = NULL; mTl = NULL; mSc = NULL; mReplaceText = false; } //------------------------------------------------------------------------------ GuiPopUpMenuCtrl::~GuiPopUpMenuCtrl() { } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::initPersistFields(void) { addField("maxPopupHeight", TypeS32, Offset(mMaxPopupHeight, GuiPopUpMenuCtrl)); addField("sbUsesNAColor", TypeBool, Offset(mRenderScrollInNA, GuiPopUpMenuCtrl)); addField("reverseTextList", TypeBool, Offset(mReverseTextList, GuiPopUpMenuCtrl)); addProtectedField("bitmap", TypeImageFilename, Offset(mBitmapName, GuiPopUpMenuCtrl), _setBitmaps, defaultProtectedGetFn, ""); addProtectedField("bitmapAsset", TypeImageAssetId, Offset(mBitmapAssetId, GuiPopUpMenuCtrl), _setBitmaps, defaultProtectedGetFn, ""); addField("bitmapBounds", TypePoint2I, Offset(mBitmapBounds, GuiPopUpMenuCtrl)); Parent::initPersistFields(); } bool GuiPopUpMenuCtrl::_setBitmaps(void* obj, const char* index, const char* data) { GuiPopUpMenuCtrl* object = static_cast(obj); object->setBitmap(data); return true; } //------------------------------------------------------------------------------ DefineEngineMethod( GuiPopUpMenuCtrl, add, void, (const char * name, S32 idNum, U32 scheme), ("", -1, 0), "(string name, int idNum, int scheme=0)") { object->addEntry(name, idNum, scheme); } DefineEngineMethod( GuiPopUpMenuCtrl, addScheme, void, (U32 id, ColorI fontColor, ColorI fontColorHL, ColorI fontColorSEL), , "(int id, ColorI fontColor, ColorI fontColorHL, ColorI fontColorSEL)") { object->addScheme( id, fontColor, fontColorHL, fontColorSEL ); } DefineEngineMethod( GuiPopUpMenuCtrl, getText, const char*, (), , "") { return object->getText(); } DefineEngineMethod( GuiPopUpMenuCtrl, clear, void, (), , "Clear the popup list.") { object->clear(); } //FIXME: clashes with SimSet.sort DefineEngineMethod(GuiPopUpMenuCtrl, sort, void, (), , "Sort the list alphabetically.") { object->sort(); } // Added to sort the entries by ID DefineEngineMethod(GuiPopUpMenuCtrl, sortID, void, (), , "Sort the list by ID.") { object->sortID(); } DefineEngineMethod( GuiPopUpMenuCtrl, forceOnAction, void, (), , "") { object->onAction(); } DefineEngineMethod( GuiPopUpMenuCtrl, forceClose, void, (), , "") { object->closePopUp(); } DefineEngineMethod( GuiPopUpMenuCtrl, getSelected, S32, (), , "Gets the selected index") { return object->getSelected(); } DefineEngineMethod( GuiPopUpMenuCtrl, setSelected, void, (S32 id, bool scriptCallback), (true), "(int id, [scriptCallback=true])") { object->setSelected( id, scriptCallback ); } DefineEngineMethod( GuiPopUpMenuCtrl, setFirstSelected, void, (bool scriptCallback), (true), "([scriptCallback=true])") { object->setFirstSelected( scriptCallback ); } DefineEngineMethod( GuiPopUpMenuCtrl, setNoneSelected, void, (), , "") { object->setNoneSelected(); } DefineEngineMethod( GuiPopUpMenuCtrl, getTextById, const char*, (S32 id), , "(int id)") { return(object->getTextById(id)); } DefineEngineMethod( GuiPopUpMenuCtrl, changeTextById, void, ( S32 id, const char * text ), , "( int id, string text )" ) { object->setEntryText( id, text ); } DefineEngineMethod( GuiPopUpMenuCtrl, setEnumContent, void, (const char * className, const char * enumName), , "(string class, string enum)" "This fills the popup with a classrep's field enumeration type info.\n\n" "More of a helper function than anything. If console access to the field list is added, " "at least for the enumerated types, then this should go away..") { AbstractClassRep * classRep = AbstractClassRep::getClassList(); // walk the class list to get our class while(classRep) { if(!dStricmp(classRep->getClassName(), className)) break; classRep = classRep->getNextClass(); } // get it? if(!classRep) { Con::warnf(ConsoleLogEntry::General, "failed to locate class rep for '%s'", className); return; } // walk the fields to check for this one (findField checks StringTableEntry ptrs...) U32 i; for(i = 0; i < classRep->mFieldList.size(); i++) if(!dStricmp(classRep->mFieldList[i].pFieldname, enumName)) break; // found it? if(i == classRep->mFieldList.size()) { Con::warnf(ConsoleLogEntry::General, "failed to locate field '%s' for class '%s'", enumName, className); return; } const AbstractClassRep::Field & field = classRep->mFieldList[i]; ConsoleBaseType* conType = ConsoleBaseType::getType( field.type ); // check the type if( !conType->getEnumTable() ) { Con::warnf(ConsoleLogEntry::General, "field '%s' is not an enumeration for class '%s'", enumName, className); return; } // fill it const EngineEnumTable& table = *( conType->getEnumTable() ); const U32 numValues = table.getNumValues(); for(i = 0; i < numValues; i++) object->addEntry( table[i].getName(), table[i] ); } //------------------------------------------------------------------------------ DefineEngineMethod( GuiPopUpMenuCtrl, findText, S32, (const char * text), , "(string text)" "Returns the position of the first entry containing the specified text or -1 if not found.") { return( object->findText( text ) ); } //------------------------------------------------------------------------------ DefineEngineMethod( GuiPopUpMenuCtrl, size, S32, (), , "Get the size of the menu - the number of entries in it.") { return( object->getNumEntries() ); } //------------------------------------------------------------------------------ DefineEngineMethod( GuiPopUpMenuCtrl, replaceText, void, (bool doReplaceText), , "(bool doReplaceText)") { object->replaceText(S32(doReplaceText)); } //------------------------------------------------------------------------------ // Added bool GuiPopUpMenuCtrl::onWake() { if ( !Parent::onWake() ) return false; // Set the bitmap for the popup. setBitmap(getBitmap(Normal)); // Now update the Form Control's bitmap array, and possibly the child's too mProfile->constructBitmapArray(); if ( mProfile->getChildrenProfile() ) mProfile->getChildrenProfile()->constructBitmapArray(); return true; } //------------------------------------------------------------------------------ bool GuiPopUpMenuCtrl::onAdd() { if ( !Parent::onAdd() ) return false; mSelIndex = -1; mReplaceText = true; return true; } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::onSleep() { Parent::onSleep(); closePopUp(); // Tests in function. } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::clear() { mEntries.setSize(0); setText(""); mSelIndex = -1; mRevNum = 0; mIdMax = -1; } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::clearEntry( S32 entry ) { if( entry == -1 ) return; U32 i = 0; for ( ; i < mEntries.size(); i++ ) { if ( mEntries[i].id == entry ) break; } mEntries.erase( i ); if( mEntries.size() <= 0 ) { mEntries.setSize(0); setText(""); mSelIndex = -1; mRevNum = 0; } else { if (entry < mSelIndex) { mSelIndex--; } else if( entry == mSelIndex ) { setText(""); mSelIndex = -1; } } } //------------------------------------------------------------------------------ DefineEngineMethod( GuiPopUpMenuCtrl, clearEntry, void, (S32 entry), , "(S32 entry)") { object->clearEntry(entry); } //------------------------------------------------------------------------------ static S32 QSORT_CALLBACK textCompare(const void *a,const void *b) { GuiPopUpMenuCtrl::Entry *ea = (GuiPopUpMenuCtrl::Entry *) (a); GuiPopUpMenuCtrl::Entry *eb = (GuiPopUpMenuCtrl::Entry *) (b); return (dStrnatcasecmp(ea->buf, eb->buf)); } // Added to sort by entry ID //------------------------------------------------------------------------------ static S32 QSORT_CALLBACK idCompare(const void *a,const void *b) { GuiPopUpMenuCtrl::Entry *ea = (GuiPopUpMenuCtrl::Entry *) (a); GuiPopUpMenuCtrl::Entry *eb = (GuiPopUpMenuCtrl::Entry *) (b); return ( (ea->id < eb->id) ? -1 : ((ea->id > eb->id) ? 1 : 0) ); } //------------------------------------------------------------------------------ // Added void GuiPopUpMenuCtrl::setBitmap( const char *name ) { StringTableEntry bitmapName = StringTable->insert(name); if ( bitmapName != StringTable->EmptyString() ) { char buffer[1024]; char *p; dStrcpy(buffer, bitmapName, 1024); p = buffer + dStrlen(buffer); S32 pLen = 1024 - dStrlen(buffer); dStrcpy(p, "_n", pLen); _setBitmap((StringTableEntry)buffer, Normal); dStrcpy(p, "_d", pLen); _setBitmap((StringTableEntry)buffer, Depressed); if ( !mBitmap[Depressed] ) mBitmap[Depressed] = mBitmap[Normal]; } else { _setBitmap(StringTable->EmptyString(), Normal); _setBitmap(StringTable->EmptyString(), Depressed); } setUpdate(); } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::sort() { S32 selId = getSelected(); S32 size = mEntries.size(); if( size > 0 ) dQsort( mEntries.address(), size, sizeof(Entry), textCompare); if( selId != -1 ) setSelected( selId, false ); } // Added to sort by entry ID //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::sortID() { S32 selId = getSelected(); S32 size = mEntries.size(); if( size > 0 ) dQsort( mEntries.address(), size, sizeof(Entry), idCompare); if( selId != -1 ) setSelected( selId, false ); } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::addEntry( const char *buf, S32 id, U32 scheme ) { if( !buf ) { //Con::printf( "GuiPopupMenuCtrlEx::addEntry - Invalid buffer!" ); return; } // Ensure that there are no other entries with exactly the same name for ( U32 i = 0; i < mEntries.size(); i++ ) { if ( String::compare( mEntries[i].buf, buf ) == 0 ) return; } // If we don't give an id, create one from mIdMax if( id == -1 ) id = mIdMax + 1; // Increase mIdMax when an id is greater than it if( id > mIdMax ) mIdMax = id; Entry e; dStrcpy( e.buf, buf, 256 ); e.id = id; e.scheme = scheme; // see if there is a shortcut key char * cp = dStrchr( e.buf, '~' ); e.ascii = cp ? cp[1] : 0; // See if there is a colour box defined with the text char *cb = dStrchr( e.buf, '|' ); if ( cb ) { e.usesColorBox = true; cb[0] = '\0'; char* red = &cb[1]; cb = dStrchr(red, '|'); cb[0] = '\0'; char* green = &cb[1]; cb = dStrchr(green, '|'); cb[0] = '\0'; char* blue = &cb[1]; U32 r = dAtoi(red); U32 g = dAtoi(green); U32 b = dAtoi(blue); e.colorbox = ColorI(r,g,b); } else { e.usesColorBox = false; } mEntries.push_back(e); if ( mInAction && mTl ) { // Add the new entry: mTl->addEntry( e.id, e.buf ); repositionPopup(); } } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::addScheme( U32 id, ColorI fontColor, ColorI fontColorHL, ColorI fontColorSEL ) { if ( !id ) return; Scheme newScheme; newScheme.id = id; newScheme.fontColor = fontColor; newScheme.fontColorHL = fontColorHL; newScheme.fontColorSEL = fontColorSEL; mSchemes.push_back( newScheme ); } //------------------------------------------------------------------------------ S32 GuiPopUpMenuCtrl::getSelected() { if (mSelIndex == -1) return 0; return mEntries[mSelIndex].id; } //------------------------------------------------------------------------------ bool GuiPopUpMenuCtrl::setEntryText( S32 id, const char* buf ) { const U32 numEntries = getNumEntries(); for( U32 i = 0; i < numEntries; i++ ) { if( mEntries[ i ].id == id ) { Entry& entry = mEntries[ i ]; dStrncpy( entry.buf, buf, sizeof( entry.buf ) ); entry.buf[ sizeof( entry.buf ) - 1 ] = '\0'; return true; } } return false; } //------------------------------------------------------------------------------ const char* GuiPopUpMenuCtrl::getTextById(S32 id) { for ( U32 i = 0; i < mEntries.size(); i++ ) { if ( mEntries[i].id == id ) return( mEntries[i].buf ); } return( "" ); } //------------------------------------------------------------------------------ S32 GuiPopUpMenuCtrl::findText( const char* text ) { for ( U32 i = 0; i < mEntries.size(); i++ ) { if ( String::compare( text, mEntries[i].buf ) == 0 ) return( mEntries[i].id ); } return( -1 ); } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::setSelected(S32 id, bool bNotifyScript ) { for( S32 i = 0; i < mEntries.size(); i++ ) { if( id == mEntries[i].id ) { i = ( mRevNum > i ) ? mRevNum - i : i; mSelIndex = i; if( mReplaceText ) // Only change the displayed text if appropriate. setText( mEntries[ i ].buf ); // Now perform the popup action: if( bNotifyScript ) { if( isMethod( "onSelect" ) ) Con::executef( this, "onSelect", Con::getIntArg( mEntries[ mSelIndex ].id ), mEntries[mSelIndex].buf ); execConsoleCallback(); } return; } } if( mReplaceText ) // Only change the displayed text if appropriate. { setText(""); } mSelIndex = -1; if( bNotifyScript && isMethod( "onCancel" ) ) Con::executef( this, "onCancel" ); if( id == -1 ) return; // Execute the popup console command: if( bNotifyScript ) execConsoleCallback(); } //------------------------------------------------------------------------------ // Added to set the first item as selected. void GuiPopUpMenuCtrl::setFirstSelected( bool bNotifyScript ) { if( mEntries.size() > 0 ) { mSelIndex = 0; if ( mReplaceText ) // Only change the displayed text if appropriate. { setText( mEntries[0].buf ); } // Execute the popup console command: if( bNotifyScript ) { if ( isMethod( "onSelect" ) ) Con::executef( this, "onSelect", Con::getIntArg( mEntries[ mSelIndex ].id ), mEntries[mSelIndex].buf ); execConsoleCallback(); } } else { if ( mReplaceText ) // Only change the displayed text if appropriate. setText(""); mSelIndex = -1; if( bNotifyScript ) { Con::executef( this, "onCancel" ); execConsoleCallback(); } } } //------------------------------------------------------------------------------ // Added to set no items as selected. void GuiPopUpMenuCtrl::setNoneSelected() { if ( mReplaceText ) // Only change the displayed text if appropriate. { setText(""); } mSelIndex = -1; } //------------------------------------------------------------------------------ const char *GuiPopUpMenuCtrl::getScriptValue() { return getText(); } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::onRender( Point2I offset, const RectI &updateRect ) { TORQUE_UNUSED(updateRect); Point2I localStart; if ( mScrollDir != GuiScrollCtrl::None ) autoScroll(); GFXDrawUtil* drawUtil = GFX->getDrawUtil(); RectI baseRect( offset, getExtent() ); if ( mInAction ) { S32 left = baseRect.point.x, right = baseRect.point.x + baseRect.extent.x - 1; S32 top = baseRect.point.y, bottom = baseRect.point.y + baseRect.extent.y - 1; // Do we render a bitmap border or lines? if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) { if (mProfile->mBitmapArrayRects[0].extent.y < baseRect.extent.y) { //if our bitmap is smaller than the height of our ctrl, we'll nudge it towards the center U32 nudge = (baseRect.extent.y - mProfile->mBitmapArrayRects[0].extent.y) / 2; baseRect.point.y += nudge; } // Render the fixed, filled in border renderFixedBitmapBordersFilled(baseRect, 3, mProfile ); } else { //renderSlightlyLoweredBox(r, mProfile); drawUtil->drawRectFill(baseRect, mProfile->mFillColor ); } // Draw a bitmap over the background? if ( mBitmap[Depressed] ) { RectI rect(offset, mBitmapBounds); drawUtil->clearBitmapModulation(); drawUtil->drawBitmapStretch( mBitmap[Depressed], rect ); } else if ( mBitmap[Normal] ) { RectI rect(offset, mBitmapBounds); drawUtil->clearBitmapModulation(); drawUtil->drawBitmapStretch( mBitmap[Normal], rect ); } // Do we render a bitmap border or lines? if ( !( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) ) { drawUtil->drawLine(left, top, left, bottom, colorWhite ); drawUtil->drawLine(left, top, right, top, colorWhite ); drawUtil->drawLine(left + 1, bottom, right, bottom, mProfile->mBorderColor ); drawUtil->drawLine(right, top + 1, right, bottom - 1, mProfile->mBorderColor ); } } else // TODO: Implement // TODO: Add onMouseEnter() and onMouseLeave() and a definition of mMouseOver (see guiButtonBaseCtrl) for this to work. if ( mMouseOver ) { S32 left = baseRect.point.x, right = baseRect.point.x + baseRect.extent.x - 1; S32 top = baseRect.point.y, bottom = baseRect.point.y + baseRect.extent.y - 1; // Do we render a bitmap border or lines? if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) { if (mProfile->mBitmapArrayRects[0].extent.y < baseRect.extent.y) { //if our bitmap is smaller than the height of our ctrl, we'll nudge it towards the center U32 nudge = (baseRect.extent.y - mProfile->mBitmapArrayRects[0].extent.y) / 2; baseRect.point.y += nudge; } // Render the fixed, filled in border renderFixedBitmapBordersFilled(baseRect, 2, mProfile ); } else { drawUtil->drawRectFill(baseRect, mProfile->mFillColorHL ); } // Draw a bitmap over the background? if ( mBitmap[Normal] ) { RectI rect( offset, mBitmapBounds ); drawUtil->clearBitmapModulation(); drawUtil->drawBitmapStretch( mBitmap[Normal], rect ); } // Do we render a bitmap border or lines? if ( !( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) ) { drawUtil->drawLine(left, top, left, bottom, colorWhite); drawUtil->drawLine(left, top, right, top, colorWhite); drawUtil->drawLine(left + 1, bottom, right, bottom, mProfile->mBorderColor); drawUtil->drawLine(right, top + 1, right, bottom - 1, mProfile->mBorderColor); } } else { // Do we render a bitmap border or lines? if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) { if (mProfile->mBitmapArrayRects[0].extent.y < baseRect.extent.y) { //if our bitmap is smaller than the height of our ctrl, we'll nudge it towards the center U32 nudge = (baseRect.extent.y - mProfile->mBitmapArrayRects[0].extent.y) / 2; baseRect.point.y += nudge; } // Render the fixed, filled in border renderFixedBitmapBordersFilled(baseRect, 1, mProfile ); } else { drawUtil->drawRectFill(baseRect, mProfile->mFillColorNA ); } // Draw a bitmap over the background? if ( mBitmap[Normal] ) { RectI rect(offset, mBitmapBounds); drawUtil->clearBitmapModulation(); drawUtil->drawBitmapStretch( mBitmap[Normal], rect ); } // Do we render a bitmap border or lines? if ( !( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) ) { if (mProfile->mBitmapArrayRects[0].extent.y < baseRect.extent.y) { //if our bitmap is smaller than the height of our ctrl, we'll nudge it towards the center U32 nudge = (baseRect.extent.y - mProfile->mBitmapArrayRects[0].extent.y) / 2; baseRect.point.y += nudge; } drawUtil->drawRect( baseRect, mProfile->mBorderColorNA ); } } // renderSlightlyRaisedBox(r, mProfile); // Used to be the only 'else' condition to mInAction above. S32 txt_w = mProfile->mFont->getStrWidth(mText); localStart.x = 0; localStart.y = (getHeight() - (mProfile->mFont->getHeight())) / 2; // align the horizontal switch (mProfile->mAlignment) { case GuiControlProfile::RightJustify: if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) { // We're making use of a bitmap border, so take into account the // right cap of the border. RectI* bitmapBounds = mProfile->mBitmapArrayRects.address(); localStart.x = getWidth() - bitmapBounds[2].extent.x - txt_w; } else { localStart.x = getWidth() - txt_w; } break; case GuiControlProfile::CenterJustify: if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) { // We're making use of a bitmap border, so take into account the // right cap of the border. RectI* bitmapBounds = mProfile->mBitmapArrayRects.address(); localStart.x = (getWidth() - bitmapBounds[2].extent.x - txt_w) / 2; } else { localStart.x = (getWidth() - txt_w) / 2; } break; default: // GuiControlProfile::LeftJustify if ( txt_w > getWidth() ) { // The width of the text is greater than the width of the control. // In this case we will right justify the text and leave some space // for the down arrow. if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) { // We're making use of a bitmap border, so take into account the // right cap of the border. RectI* bitmapBounds = mProfile->mBitmapArrayRects.address(); localStart.x = getWidth() - bitmapBounds[2].extent.x - txt_w; } else { localStart.x = getWidth() - txt_w - 12; } } else { localStart.x = mProfile->mTextOffset.x; // Use mProfile->mTextOffset as a controlable margin for the control's text. } break; } // Do we first draw a coloured box beside the text? ColorI boxColor; bool drawbox = getColoredBox( boxColor, mSelIndex); if ( drawbox ) { Point2I coloredboxsize( 15, 10 ); RectI boxBounds( offset.x + mProfile->mTextOffset.x, offset.y + ( (getHeight() - coloredboxsize.y ) / 2 ), coloredboxsize.x, coloredboxsize.y ); drawUtil->drawRectFill(boxBounds, boxColor); drawUtil->drawRect(boxBounds, ColorI(0,0,0)); localStart.x += coloredboxsize.x + mProfile->mTextOffset.x; } // Draw the text Point2I globalStart = localToGlobalCoord( localStart ); ColorI fontColor = mActive ? ( mInAction ? mProfile->mFontColor : mProfile->mFontColorNA ) : mProfile->mFontColorNA; drawUtil->setBitmapModulation( fontColor ); // was: (mProfile->mFontColor); // Get the number of columns in the text S32 colcount = getColumnCount( mText, "\t" ); // Are there two or more columns? if ( colcount >= 2 ) { char buff[256]; // Draw the first column getColumn( mText, buff, 0, "\t" ); drawUtil->drawText( mProfile->mFont, globalStart, buff, mProfile->mFontColors ); // Draw the second column to the right getColumn( mText, buff, 1, "\t" ); S32 colTxt_w = mProfile->mFont->getStrWidth( buff ); if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) { // We're making use of a bitmap border, so take into account the // right cap of the border. RectI* bitmapBounds = mProfile->mBitmapArrayRects.address(); Point2I textpos = localToGlobalCoord( Point2I( getWidth() - colTxt_w - bitmapBounds[2].extent.x, localStart.y ) ); drawUtil->drawText( mProfile->mFont, textpos, buff, mProfile->mFontColors ); } else { Point2I textpos = localToGlobalCoord( Point2I( getWidth() - colTxt_w - 12, localStart.y ) ); drawUtil->drawText( mProfile->mFont, textpos, buff, mProfile->mFontColors ); } } else { drawUtil->drawText( mProfile->mFont, globalStart, mText, mProfile->mFontColors ); } // If we're rendering a bitmap border, then it will take care of the arrow. if ( !(mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size()) ) { if (mProfile->mBitmapArrayRects[0].extent.y < baseRect.extent.y) { //if our bitmap is smaller than the height of our ctrl, we'll nudge it towards the center U32 nudge = (baseRect.extent.y - mProfile->mBitmapArrayRects[0].extent.y) / 2; baseRect.point.y += nudge; } // Draw a triangle (down arrow) S32 left = baseRect.point.x + baseRect.extent.x - 12; S32 right = left + 8; S32 middle = left + 4; S32 top = baseRect.extent.y / 2 + baseRect.point.y - 4; S32 bottom = top + 8; PrimBuild::color( mProfile->mFontColor ); PrimBuild::begin( GFXTriangleList, 3 ); PrimBuild::vertex2fv( Point3F( (F32)left, (F32)top, 0.0f ) ); PrimBuild::vertex2fv( Point3F( (F32)right, (F32)top, 0.0f ) ); PrimBuild::vertex2fv( Point3F( (F32)middle, (F32)bottom, 0.0f ) ); PrimBuild::end(); } } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::closePopUp() { if ( !mInAction ) return; // Get the selection from the text list: if( !mBackgroundCancel ) { mSelIndex = mTl->getSelectedCell().y; mSelIndex = ( mRevNum >= mSelIndex && mSelIndex != -1 ) ? mRevNum - mSelIndex : mSelIndex; if ( mSelIndex != -1 ) { if ( mReplaceText ) setText( mEntries[mSelIndex].buf ); setIntVariable( mEntries[mSelIndex].id ); } } // Release the mouse: mInAction = false; mTl->mouseUnlock(); // Now perform the popup action: if( mSelIndex != -1 && !mBackgroundCancel ) { if ( isMethod( "onSelect" ) ) Con::executef( this, "onSelect", Con::getIntArg( mEntries[ mSelIndex ].id ), mEntries[mSelIndex].buf ); // Execute the popup console command: execConsoleCallback(); } else if ( isMethod( "onCancel" ) ) Con::executef( this, "onCancel" ); // Pop the background: GuiCanvas *root = getRoot(); if ( root ) root->popDialogControl(mBackground); // Kill the popup: mBackground->removeObject( mSc ); mTl->deleteObject(); mSc->deleteObject(); mBackground->deleteObject(); mBackground = NULL; mTl = NULL; mSc = NULL; // Set this as the first responder: setFirstResponder(); } //------------------------------------------------------------------------------ bool GuiPopUpMenuCtrl::onKeyDown(const GuiEvent &event) { //if the control is a dead end, don't process the input: if ( !mVisible || !mActive || !mAwake ) return false; //see if the key down is a or not if ( event.keyCode == KEY_RETURN && event.modifier == 0 ) { onAction(); return true; } //otherwise, pass the event to its parent return Parent::onKeyDown( event ); } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::onAction() { GuiControl *canCtrl = getParent(); addChildren(); GuiCanvas *root = getRoot(); Point2I windowExt = root->getExtent(); mBackground->resize( Point2I(0,0), root->getExtent() ); S32 textWidth = 0, width = getWidth(); const S32 textSpace = 2; bool setScroll = false; for ( U32 i = 0; i < mEntries.size(); ++i ) if ( S32(mProfile->mFont->getStrWidth( mEntries[i].buf )) > textWidth ) textWidth = mProfile->mFont->getStrWidth( mEntries[i].buf ); S32 sbWidth = mSc->getControlProfile()->mBorderThickness * 2 + mSc->scrollBarThickness(); // Calculate the scroll bar width if ( textWidth > ( getWidth() - sbWidth-mProfile->mTextOffset.x - mSc->getChildMargin().x * 2 ) ) // The text draw area to test against is the width of the drop-down minus the scroll bar width, the text margin and the scroll bar child margins. { textWidth +=sbWidth + mProfile->mTextOffset.x + mSc->getChildMargin().x * 2; // The new width is the width of the text plus the scroll bar width plus the text margin size plus the scroll bar child margins. width = textWidth; // If a child margin is not defined for the scroll control, let's add // some space between the text and scroll control for readability if(mSc->getChildMargin().x == 0) width += textSpace; } mTl->setCellSize(Point2I(width, mProfile->mFont->getHeight() + textSpace)); for ( U32 j = 0; j < mEntries.size(); ++j ) mTl->addEntry( mEntries[j].id, mEntries[j].buf ); if ( mSelIndex >= 0 ) mTl->setSelectedCell( Point2I( 0, mSelIndex ) ); Point2I pointInGC = canCtrl->localToGlobalCoord( getPosition() ); Point2I scrollPoint( pointInGC.x, pointInGC.y + getHeight() ); //Calc max Y distance, so Scroll Ctrl will fit on window S32 sbBorder = mSc->getControlProfile()->mBorderThickness * 2 + mSc->getChildMargin().y * 2; S32 maxYdis = windowExt.y - pointInGC.y - getHeight() - sbBorder; //If scroll bars need to be added mRevNum = 0; if ( maxYdis < mTl->getHeight() + sbBorder ) { //Should we pop menu list above the button if ( maxYdis < pointInGC.y ) { if(mReverseTextList) reverseTextList(); maxYdis = pointInGC.y; //Does the menu need a scroll bar if ( maxYdis < mTl->getHeight() + sbBorder ) { setScroll = true; } //No scroll bar needed else { maxYdis = mTl->getHeight() + sbBorder; } // Added the next two lines scrollPoint.set(pointInGC.x, pointInGC.y - maxYdis); // Used to have the following on the end: '-1);' } //Scroll bar needed but Don't pop above button else { setScroll = true; } } //No scroll bar needed else { maxYdis = mTl->getHeight() + sbBorder; } RectI newBounds = mSc->getBounds(); //offset it from the background so it lines up properly newBounds.point = mBackground->globalToLocalCoord( scrollPoint ); if ( newBounds.point.x + width > mBackground->getWidth() ) if ( width - getWidth() > 0 ) newBounds.point.x -= width - getWidth(); newBounds.extent.set( width, maxYdis ); mSc->setBounds( newBounds ); mSc->registerObject(); mTl->registerObject(); mBackground->registerObject(); mSc->addObject( mTl ); mBackground->addObject( mSc ); mBackgroundCancel = false; // Setup check if user clicked on the background instead of the text list (ie: didn't want to change their current selection). root->pushDialogControl( mBackground, 99 ); if ( setScroll ) { // Resize the text list Point2I cellSize; mTl->getCellSize( cellSize ); cellSize.x = width - mSc->scrollBarThickness() - sbBorder; mTl->setCellSize( cellSize ); mTl->setWidth( cellSize.x ); if ( mSelIndex ) mTl->scrollCellVisible( Point2I( 0, mSelIndex ) ); else mTl->scrollCellVisible( Point2I( 0, 0 ) ); } mTl->setFirstResponder(); mInAction = true; } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::addChildren() { // Create Text List. mTl = new GuiPopupTextListCtrl( this ); AssertFatal( mTl, "Failed to create the GuiPopUpTextListCtrl for the PopUpMenu" ); // Use the children's profile rather than the parent's profile, if it exists. mTl->setControlProfile( mProfile->getChildrenProfile() ? mProfile->getChildrenProfile() : mProfile ); mTl->setField("noDuplicates", "false"); mSc = new GuiScrollCtrl; AssertFatal( mSc, "Failed to create the GuiScrollCtrl for the PopUpMenu" ); GuiControlProfile *prof; if ( Sim::findObject( "GuiScrollProfile", prof ) ) { mSc->setControlProfile( prof ); } else { // Use the children's profile rather than the parent's profile, if it exists. mSc->setControlProfile( mProfile->getChildrenProfile() ? mProfile->getChildrenProfile() : mProfile ); } mSc->setField( "hScrollBar", "AlwaysOff" ); mSc->setField( "vScrollBar", "dynamic" ); //if(mRenderScrollInNA) // Force the scroll control to render using fillColorNA rather than fillColor // mSc->mUseNABackground = true; mBackground = new GuiPopUpBackgroundCtrl( this, mTl ); AssertFatal( mBackground, "Failed to create the GuiBackgroundCtrl for the PopUpMenu" ); } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::repositionPopup() { if ( !mInAction || !mSc || !mTl ) return; // I'm not concerned with this right now... } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::reverseTextList() { mTl->clear(); for ( S32 i = mEntries.size()-1; i >= 0; --i ) mTl->addEntry( mEntries[i].id, mEntries[i].buf ); // Don't lose the selected cell: if ( mSelIndex >= 0 ) mTl->setSelectedCell( Point2I( 0, mEntries.size() - mSelIndex - 1 ) ); mRevNum = mEntries.size() - 1; } //------------------------------------------------------------------------------ bool GuiPopUpMenuCtrl::getFontColor( ColorI &fontColor, S32 id, bool selected, bool mouseOver ) { U32 i; Entry* entry = NULL; for ( i = 0; i < mEntries.size(); i++ ) { if ( mEntries[i].id == id ) { entry = &mEntries[i]; break; } } if ( !entry ) return( false ); if ( entry->scheme != 0 ) { // Find the entry's color scheme: for ( i = 0; i < mSchemes.size(); i++ ) { if ( mSchemes[i].id == entry->scheme ) { fontColor = selected ? mSchemes[i].fontColorSEL : mouseOver ? mSchemes[i].fontColorHL : mSchemes[i].fontColor; return( true ); } } } // Default color scheme... fontColor = selected ? mProfile->mFontColorSEL : mouseOver ? mProfile->mFontColorHL : mProfile->mFontColorNA; // Modified the final color choice from mProfile->mFontColor to mProfile->mFontColorNA return( true ); } //------------------------------------------------------------------------------ // Added bool GuiPopUpMenuCtrl::getColoredBox( ColorI &fontColor, S32 id ) { U32 i; Entry* entry = NULL; for ( i = 0; i < mEntries.size(); i++ ) { if ( mEntries[i].id == id ) { entry = &mEntries[i]; break; } } if ( !entry ) return false; if ( entry->usesColorBox == false ) return false; fontColor = entry->colorbox; return true; } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::onMouseDown( const GuiEvent &event ) { TORQUE_UNUSED(event); if( !mVisible || !mActive || !mAwake ) return; onAction(); } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::onMouseUp( const GuiEvent &event ) { TORQUE_UNUSED(event); } //------------------------------------------------------------------------------ // Added void GuiPopUpMenuCtrl::onMouseEnter( const GuiEvent &event ) { mMouseOver = true; } //------------------------------------------------------------------------------ // Added void GuiPopUpMenuCtrl::onMouseLeave( const GuiEvent &event ) { mMouseOver = false; } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::setupAutoScroll( const GuiEvent &event ) { GuiControl *parent = getParent(); if ( !parent ) return; Point2I mousePt = mSc->globalToLocalCoord( event.mousePoint ); mEventSave = event; if ( mLastYvalue != mousePt.y ) { mScrollDir = GuiScrollCtrl::None; if ( mousePt.y > mSc->getHeight() || mousePt.y < 0 ) { S32 topOrBottom = ( mousePt.y > mSc->getHeight() ) ? 1 : 0; mSc->scrollTo( 0, topOrBottom ); return; } F32 percent = (F32)mousePt.y / (F32)mSc->getHeight(); if ( percent > 0.7f && mousePt.y > mLastYvalue ) { mIncValue = percent - 0.5f; mScrollDir = GuiScrollCtrl::DownArrow; } else if ( percent < 0.3f && mousePt.y < mLastYvalue ) { mIncValue = 0.5f - percent; mScrollDir = GuiScrollCtrl::UpArrow; } mLastYvalue = mousePt.y; } } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::autoScroll() { mScrollCount += mIncValue; while ( mScrollCount > 1 ) { mSc->autoScroll( mScrollDir ); mScrollCount -= 1; } mTl->onMouseMove( mEventSave ); } //------------------------------------------------------------------------------ void GuiPopUpMenuCtrl::replaceText(S32 boolVal) { mReplaceText = boolVal; }