Torque3D/Engine/source/gui/controls/guiPopUpCtrl.cpp
Areloch 683c438b09 Improves handling of rendering guiPopupCtrls where if the height extent is taller than the bitmap array height, it'll adjust the height to recenter the displayed bitmap elements.
Streamlined the toolbar for the gui and world editors to utilize a stack, making the behavior and manipulation of toolbar elements significantly more consistent.
Added Settings and Asset Browser buttons to both gui and world editor toolbars for easier access.
Moved all tool toolbars over to work with the stack system to make them more consistent and better formatting
Added saving of asset browser's last position and extent so it remembers it on load.
Added editor setting to close the asset browser after completing a drag-n-drop action.
Added keybind to editor keybind list, making space toggle the asset browser
2021-09-01 01:12:16 -05:00

1549 lines
47 KiB
C++

//-----------------------------------------------------------------------------
// 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 <return> 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, 0);
INIT_IMAGEASSET_ARRAY(Bitmap, 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)
{
bool ret = false;
GuiPopUpMenuCtrl* object = static_cast<GuiPopUpMenuCtrl*>(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 <return> 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;
}