Merge pull request #2118 from Areloch/MenuAndPopups

Refactors the Popup menus and GuiMenuBars
This commit is contained in:
Areloch 2018-01-27 18:41:00 -06:00 committed by GitHub
commit c23c99dbea
30 changed files with 995 additions and 3116 deletions

View file

@ -276,8 +276,6 @@ bool GuiCanvas::onAdd()
// Define the menu bar for this canvas (if any)
Con::executef(this, "onCreateMenu");
Sim::findObject("PlatformGenericMenubar", mMenuBarCtrl);
return parentRet;
}
@ -302,21 +300,39 @@ void GuiCanvas::setMenuBar(SimObject *obj)
Parent::removeObject( oldMenuBar );
// set new menubar
if( mMenuBarCtrl )
Parent::addObject(mMenuBarCtrl);
if (mMenuBarCtrl)
{
//Add a wrapper control so that the menubar sizes correctly
GuiControlProfile* profile;
Sim::findObject("GuiModelessDialogProfile", profile);
if (!profile)
{
Con::errorf("GuiCanvas::setMenuBar: Unable to find the GuiModelessDialogProfile profile!");
return;
}
GuiControl* menuBackground = new GuiControl();
menuBackground->registerObject();
menuBackground->setControlProfile(profile);
menuBackground->addObject(mMenuBarCtrl);
Parent::addObject(menuBackground);
}
// update window accelerator keys
if( oldMenuBar != mMenuBarCtrl )
{
StringTableEntry ste = StringTable->insert("menubar");
GuiMenuBar* menu = NULL;
menu = !oldMenuBar ? NULL : dynamic_cast<GuiMenuBar*>(oldMenuBar->findObjectByInternalName( ste, true));
if( menu )
menu->removeWindowAcceleratorMap( *getPlatformWindow()->getInputGenerator() );
GuiMenuBar* oldMenu = dynamic_cast<GuiMenuBar*>(oldMenuBar);
GuiMenuBar* newMenu = dynamic_cast<GuiMenuBar*>(mMenuBarCtrl);
menu = !mMenuBarCtrl ? NULL : dynamic_cast<GuiMenuBar*>(mMenuBarCtrl->findObjectByInternalName( ste, true));
if( menu )
menu->buildWindowAcceleratorMap( *getPlatformWindow()->getInputGenerator() );
if(oldMenu)
oldMenu->removeWindowAcceleratorMap(*getPlatformWindow()->getInputGenerator());
if(newMenu)
newMenu->buildWindowAcceleratorMap(*getPlatformWindow()->getInputGenerator());
}
}
@ -1633,27 +1649,26 @@ void GuiCanvas::maintainSizing()
Point2I newPos = screenRect.point;
// if menubar is active displace content gui control
if( mMenuBarCtrl && (ctrl == getContentControl()) )
{
const SimObject *menu = mMenuBarCtrl->findObjectByInternalName( StringTable->insert("menubar"), true);
if (mMenuBarCtrl && (ctrl == getContentControl()))
{
/*const SimObject *menu = mMenuBarCtrl->findObjectByInternalName( StringTable->insert("menubar"), true);
if( !menu )
continue;
if( !menu )
continue;
AssertFatal( dynamic_cast<const GuiControl*>(menu), "");
AssertFatal( dynamic_cast<const GuiControl*>(menu), "");*/
const U32 yOffset = static_cast<const GuiControl*>(menu)->getExtent().y;
newPos.y += yOffset;
newExt.y -= yOffset;
const U32 yOffset = static_cast<const GuiMenuBar*>(mMenuBarCtrl)->mMenubarHeight;
newPos.y += yOffset;
newExt.y -= yOffset;
}
if(pos != newPos || ext != newExt)
if (pos != newPos || ext != newExt)
{
ctrl->resize(newPos, newExt);
resetUpdateRegions();
}
}
}
void GuiCanvas::setupFences()

View file

@ -210,6 +210,7 @@ public:
virtual void onRemove();
void setMenuBar(SimObject *obj);
SimObject* getMenuBar() { return mMenuBarCtrl; }
static void initPersistFields();

File diff suppressed because it is too large Load diff

View file

@ -23,119 +23,43 @@
#ifndef _GUIMENUBAR_H_
#define _GUIMENUBAR_H_
#ifndef _GUITEXTLISTCTRL_H_
#include "gui/controls/guiTextListCtrl.h"
#endif
#ifndef _GUITICKCTRL_H_
#include "gui/shiny/guiTickCtrl.h"
#endif
#ifndef _POPUPMENU_H_
#include "gui/editor/popupMenu.h"
#endif
class GuiMenuBar;
class GuiMenuTextListCtrl;
class WindowInputGenerator;
class GuiMenuBackgroundCtrl : public GuiControl
{
typedef GuiControl Parent;
protected:
GuiMenuBar *mMenuBarCtrl;
GuiMenuTextListCtrl *mTextList;
public:
GuiMenuBackgroundCtrl(GuiMenuBar *ctrl, GuiMenuTextListCtrl* textList);
void onMouseDown(const GuiEvent &event);
void onMouseMove(const GuiEvent &event);
void onMouseDragged(const GuiEvent &event);
};
class GuiSubmenuBackgroundCtrl : public GuiMenuBackgroundCtrl
{
typedef GuiMenuBackgroundCtrl Parent;
public:
GuiSubmenuBackgroundCtrl(GuiMenuBar *ctrl, GuiMenuTextListCtrl* textList);
bool pointInControl(const Point2I & parentCoordPoint);
void onMouseDown(const GuiEvent &event);
};
//------------------------------------------------------------------------------
class GuiMenuTextListCtrl : public GuiTextListCtrl
{
private:
typedef GuiTextListCtrl Parent;
protected:
GuiMenuBar *mMenuBarCtrl;
public:
bool isSubMenu; // Indicates that this text list is in a submenu
GuiMenuTextListCtrl(); // for inheritance
GuiMenuTextListCtrl(GuiMenuBar *ctrl);
// GuiControl overloads:
bool onKeyDown(const GuiEvent &event);
void onMouseDown(const GuiEvent &event);
void onMouseUp(const GuiEvent &event);
void onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver);
virtual void onCellHighlighted(Point2I cell); // Added
};
//------------------------------------------------------------------------------
class GuiMenuBar : public GuiTickCtrl // Was: GuiControl
{
typedef GuiTickCtrl Parent; // Was: GuiControl Parent;
public:
struct Menu;
struct MenuEntry
{
U32 pos;
RectI bounds;
struct MenuItem // an individual item in a pull-down menu
{
char *text; // the text of the menu item
U32 id; // a script-assigned identifier
char *accelerator; // the keyboard accelerator shortcut for the menu item
U32 acceleratorIndex; // index of this accelerator
bool enabled; // true if the menu item is selectable
bool visible; // true if the menu item is visible
S32 bitmapIndex; // index of the bitmap in the bitmap array
S32 checkGroup; // the group index of the item visa vi check marks -
// only one item in the group can be checked.
MenuItem *nextMenuItem; // next menu item in the linked list
bool isSubmenu; // This menu item has a submenu that will be displayed
Menu* submenuParentMenu; // For a submenu, this is the parent menu
Menu* submenu;
String cmd;
};
struct Menu
{
char *text;
U32 id;
RectI bounds;
bool visible;
S32 bitmapIndex; // Index of the bitmap in the bitmap array (-1 = no bitmap)
bool drawBitmapOnly; // Draw only the bitmap and not the text
bool drawBorder; // Should a border be drawn around this menu (usually if we only have a bitmap, we don't want a border)
S32 bitmapIndex;
bool drawBitmapOnly;
Menu *nextMenu;
MenuItem *firstMenuItem;
};
GuiMenuBackgroundCtrl *mBackground;
GuiMenuTextListCtrl *mTextList;
GuiSubmenuBackgroundCtrl *mSubmenuBackground; // Background for a submenu
GuiMenuTextListCtrl *mSubmenuTextList; // Text list for a submenu
bool drawBorder;
Vector<Menu*> mMenuList;
Menu *mouseDownMenu;
Menu *mouseOverMenu;
StringTableEntry text;
PopupMenu* popupMenu;
};
Vector<MenuEntry> mMenuList;
MenuEntry *mouseDownMenu;
MenuEntry *mouseOverMenu;
MenuItem* mouseDownSubmenu; // Stores the menu item that is a submenu that has been selected
MenuItem* mouseOverSubmenu; // Stores the menu item that is a submenu that has been highlighted
@ -151,59 +75,26 @@ public:
S32 mVerticalMargin; // Top and bottom margin around the text of each menu
S32 mBitmapMargin; // Margin between a menu's bitmap and text
// Used to keep track of the amount of ticks that the mouse is hovering
// over a menu.
S32 mMouseOverCounter;
bool mCountMouseOver;
S32 mMouseHoverAmount;
U32 mMenubarHeight;
bool mMouseInMenu;
GuiMenuBar();
void onRemove();
bool onWake();
void onSleep();
// internal menu handling functions
// these are used by the script manipulation functions to add/remove/change menu items
static Menu* sCreateMenu(const char *menuText, U32 menuId);
void addMenu(Menu *menu, S32 pos = -1);
void addMenu(const char *menuText, U32 menuId);
Menu *findMenu(const char *menu); // takes either a menu text or a string id
static MenuItem *findMenuItem(Menu *menu, const char *menuItem); // takes either a menu text or a string id
void removeMenu(Menu *menu);
static void removeMenuItem(Menu *menu, MenuItem *menuItem);
static MenuItem* addMenuItem(Menu *menu, const char *text, U32 id, const char *accelerator, S32 checkGroup, const char *cmd);
static MenuItem* addMenuItem(Menu *menu, MenuItem *menuItem);
static void clearMenuItems(Menu *menu);
void clearMenus();
virtual void addObject(SimObject* object);
void attachToMenuBar(Menu* menu, S32 pos = -1);
void removeFromMenuBar(Menu* menu);
// Methods to deal with submenus
static MenuItem* findSubmenuItem(Menu *menu, const char *menuItem, const char *submenuItem);
static MenuItem* findSubmenuItem(MenuItem *menuItem, const char *submenuItem);
static void addSubmenuItem(Menu *menu, MenuItem *submenu, const char *text, U32 id, const char *accelerator, S32 checkGroup);
static void addSubmenuItem(Menu *menu, MenuItem *submenu, MenuItem *newMenuItem );
static void removeSubmenuItem(MenuItem *menuItem, MenuItem *submenuItem);
static void clearSubmenuItems(MenuItem *menuitem);
void onSubmenuAction(S32 selectionIndex, const RectI& bounds, Point2I cellSize);
void closeSubmenu();
void checkSubmenuMouseMove(const GuiEvent &event);
MenuItem *findHitMenuItem(Point2I mousePoint);
void highlightedMenuItem(S32 selectionIndex, const RectI& bounds, Point2I cellSize); // Called whenever a menu item is highlighted by the mouse
// display/mouse functions
Menu *findHitMenu(Point2I mousePoint);
// Called when the GUI theme changes and a bitmap arrary may need updating
// void onThemeChange();
MenuEntry *findHitMenu(Point2I mousePoint);
void onPreRender();
void onRender(Point2I offset, const RectI &updateRect);
void checkMenuMouseMove(const GuiEvent &event);
void onMouseMove(const GuiEvent &event);
void onMouseEnter(const GuiEvent &event);
void onMouseLeave(const GuiEvent &event);
void onMouseDown(const GuiEvent &event);
void onMouseDragged(const GuiEvent &event);
@ -215,18 +106,21 @@ public:
void removeWindowAcceleratorMap( WindowInputGenerator &inputGenerator );
void acceleratorKeyPress(U32 index);
virtual void menuItemSelected(Menu *menu, MenuItem *item);
// Added to support 'ticks'
void processTick();
void insert(SimObject* pObject, S32 pos);
static void initPersistFields();
U32 getMenuListCount() { return mMenuList.size(); }
PopupMenu* getMenu(U32 index);
DECLARE_CONOBJECT(GuiMenuBar);
DECLARE_CALLBACK( void, onMouseInMenu, ( bool hasLeftMenu ));
DECLARE_CALLBACK( void, onMenuSelect, ( S32 menuId, const char* menuText ));
DECLARE_CALLBACK( void, onMenuItemSelect, ( S32 menuId, const char* menuText, S32 menuItemId, const char* menuItemText ));
DECLARE_CALLBACK( void, onSubmenuSelect, ( S32 submenuId, const char* submenuText ));
};
#endif

View file

@ -25,20 +25,32 @@
#include "gfx/primBuilder.h"
#include "gui/core/guiCanvas.h"
GuiPopupMenuBackgroundCtrl::GuiPopupMenuBackgroundCtrl(GuiPopupMenuTextListCtrl *textList)
GuiPopupMenuBackgroundCtrl::GuiPopupMenuBackgroundCtrl()
{
mTextList = textList;
mTextList->mBackground = this;
mMenuBarCtrl = nullptr;
}
void GuiPopupMenuBackgroundCtrl::onMouseDown(const GuiEvent &event)
{
mTextList->setSelectedCell(Point2I(-1, -1));
}
void GuiPopupMenuBackgroundCtrl::onMouseUp(const GuiEvent &event)
{
clearPopups();
//Pass along the event just in case we clicked over a menu item. We don't want to eat the input for it.
if (mMenuBarCtrl)
mMenuBarCtrl->onMouseUp(event);
close();
}
void GuiPopupMenuBackgroundCtrl::onMouseMove(const GuiEvent &event)
{
//It's possible we're trying to pan through a menubar while a popup is displayed. Pass along our event to the menubar for good measure
if (mMenuBarCtrl)
mMenuBarCtrl->onMouseMove(event);
}
void GuiPopupMenuBackgroundCtrl::onMouseDragged(const GuiEvent &event)
@ -48,26 +60,64 @@ void GuiPopupMenuBackgroundCtrl::onMouseDragged(const GuiEvent &event)
void GuiPopupMenuBackgroundCtrl::close()
{
getRoot()->removeObject(this);
mMenuBarCtrl = nullptr;
}
S32 GuiPopupMenuBackgroundCtrl::findPopupMenu(PopupMenu* menu)
{
S32 menuId = -1;
for (U32 i = 0; i < mPopups.size(); i++)
{
if (mPopups[i]->getId() == menu->getId())
return i;
}
return menuId;
}
void GuiPopupMenuBackgroundCtrl::clearPopups()
{
for (U32 i = 0; i < mPopups.size(); i++)
{
mPopups[i]->mTextList->setSelectedCell(Point2I(-1, -1));
mPopups[i]->mTextList->mPopup->hidePopup();
}
}
GuiPopupMenuTextListCtrl::GuiPopupMenuTextListCtrl()
{
isSubMenu = false; // Added
mMenu = NULL;
mMenuBar = NULL;
mPopup = NULL;
mMenuBar = nullptr;
mPopup = nullptr;
mLastHighlightedMenuIdx = -1;
}
void GuiPopupMenuTextListCtrl::onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver)
{
if (dStrcmp(mList[cell.y].text + 3, "-\t")) // Was: dStrcmp(mList[cell.y].text + 2, "-\t")) but has been changed to take into account the submenu flag
Parent::onRenderCell(offset, cell, selected, mouseOver);
else
//check if we're a real entry, or if it's a divider
if (mPopup->mMenuItems[cell.y].isSpacer)
{
S32 yp = offset.y + mCellSize.y / 2;
GFX->getDrawUtil()->drawLine(offset.x, yp, offset.x + mCellSize.x, yp, ColorI(128, 128, 128));
GFX->getDrawUtil()->drawLine(offset.x, yp + 1, offset.x + mCellSize.x, yp + 1, ColorI(255, 255, 255));
GFX->getDrawUtil()->drawLine(offset.x + 5, yp, offset.x + mCellSize.x - 5, yp, ColorI(128, 128, 128));
}
else
{
if (dStrcmp(mList[cell.y].text + 3, "-\t")) // Was: dStrcmp(mList[cell.y].text + 2, "-\t")) but has been changed to take into account the submenu flag
{
Parent::onRenderCell(offset, cell, selected, mouseOver);
}
else
{
S32 yp = offset.y + mCellSize.y / 2;
GFX->getDrawUtil()->drawLine(offset.x, yp, offset.x + mCellSize.x, yp, ColorI(128, 128, 128));
GFX->getDrawUtil()->drawLine(offset.x, yp + 1, offset.x + mCellSize.x, yp + 1, ColorI(255, 255, 255));
}
}
// now see if there's a bitmap...
U8 idx = mList[cell.y].text[0];
if (idx != 1)
@ -153,17 +203,12 @@ void GuiPopupMenuTextListCtrl::onMouseUp(const GuiEvent &event)
if (selectionIndex != -1)
{
GuiMenuBar::MenuItem *list = mMenu->firstMenuItem;
MenuItem *item = &mPopup->mMenuItems[selectionIndex];
while (selectionIndex && list)
if (item)
{
list = list->nextMenuItem;
selectionIndex--;
}
if (list)
{
if (list->enabled)
dAtob(Con::executef(mPopup, "onSelectItem", Con::getIntArg(getSelectedCell().y), list->text ? list->text : ""));
if (item->enabled)
dAtob(Con::executef(mPopup, "onSelectItem", Con::getIntArg(getSelectedCell().y), item->text.isNotEmpty() ? item->text : ""));
}
}
@ -181,4 +226,23 @@ void GuiPopupMenuTextListCtrl::onCellHighlighted(Point2I cell)
Point2I globalpoint = localToGlobalCoord(globalbounds.point);
globalbounds.point = globalpoint;
}
S32 selectionIndex = cell.y;
if (selectionIndex != -1 && mLastHighlightedMenuIdx != selectionIndex)
{
mLastHighlightedMenuIdx = selectionIndex;
mPopup->hidePopupSubmenus();
}
if (selectionIndex != -1)
{
MenuItem *list = &mPopup->mMenuItems[selectionIndex];
if (list->isSubmenu && list->subMenu != nullptr)
{
list->subMenu->showPopup(getRoot(), getPosition().x + mCellSize.x, getPosition().y + (selectionIndex * mCellSize.y));
}
}
}

View file

@ -42,6 +42,7 @@ class GuiPopupMenuBackgroundCtrl;
class GuiPopupMenuTextListCtrl : public GuiTextListCtrl
{
friend class GuiPopupMenuBackgroundCtrl;
friend class PopupMenu;
private:
typedef GuiTextListCtrl Parent;
@ -51,10 +52,12 @@ private:
public:
bool isSubMenu; // Indicates that this text list is in a submenu
Point2I maxBitmapSize;
GuiMenuBar::Menu* mMenu;
GuiMenuBar* mMenuBar;
PopupMenu* mPopup;
S32 mLastHighlightedMenuIdx;
GuiPopupMenuTextListCtrl();
// GuiControl overloads:
@ -70,16 +73,21 @@ class GuiPopupMenuBackgroundCtrl : public GuiControl
{
typedef GuiControl Parent;
protected:
GuiPopupMenuTextListCtrl *mTextList;
public:
GuiPopupMenuBackgroundCtrl(GuiPopupMenuTextListCtrl* textList);
GuiPopupMenuBackgroundCtrl();
void onMouseDown(const GuiEvent &event);
void onMouseUp(const GuiEvent &event);
void onMouseMove(const GuiEvent &event);
void onMouseDragged(const GuiEvent &event);
void close();
void clearPopups();
S32 findPopupMenu(PopupMenu* menu);
Vector<PopupMenu*> mPopups;
GuiMenuBar* mMenuBarCtrl;
};
#endif

View file

@ -0,0 +1,506 @@
//-----------------------------------------------------------------------------
// 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/editor/popupMenu.h"
#include "console/consoleTypes.h"
#include "console/engineAPI.h"
#include "gui/core/guiCanvas.h"
#include "core/util/safeDelete.h"
#include "gui/editor/guiPopupMenuCtrl.h"
#include "gui/editor/guiMenuBar.h"
static U32 sMaxPopupGUID = 0;
PopupMenuEvent PopupMenu::smPopupMenuEvent;
bool PopupMenu::smSelectionEventHandled = false;
/// Event class used to remove popup menus from the event notification in a safe way
class PopUpNotifyRemoveEvent : public SimEvent
{
public:
void process(SimObject *object)
{
PopupMenu::smPopupMenuEvent.remove((PopupMenu *)object, &PopupMenu::handleSelectEvent);
}
};
//-----------------------------------------------------------------------------
// Constructor/Destructor
//-----------------------------------------------------------------------------
PopupMenu::PopupMenu()
{
bitmapIndex = -1;
barTitle = StringTable->EmptyString();
mMenuBarCtrl = nullptr;
mTextList = nullptr;
isSubmenu = false;
}
PopupMenu::~PopupMenu()
{
PopupMenu::smPopupMenuEvent.remove(this, &PopupMenu::handleSelectEvent);
}
IMPLEMENT_CONOBJECT(PopupMenu);
ConsoleDocClass( PopupMenu,
"@brief PopupMenu represents a system menu.\n\n"
"You can add menu items to the menu, but there is no torque object associated "
"with these menu items, they exist only in a platform specific manner.\n\n"
"@note Internal use only\n\n"
"@internal"
);
//-----------------------------------------------------------------------------
void PopupMenu::initPersistFields()
{
Parent::initPersistFields();
addField("barTitle", TypeCaseString, Offset(barTitle, PopupMenu), "");
}
//-----------------------------------------------------------------------------
bool PopupMenu::onAdd()
{
if(! Parent::onAdd())
return false;
Con::executef(this, "onAdd");
return true;
}
void PopupMenu::onRemove()
{
Con::executef(this, "onRemove");
Parent::onRemove();
}
//-----------------------------------------------------------------------------
void PopupMenu::onMenuSelect()
{
Con::executef(this, "onMenuSelect");
}
//-----------------------------------------------------------------------------
void PopupMenu::handleSelectEvent(U32 popID, U32 command)
{
}
//-----------------------------------------------------------------------------
bool PopupMenu::onMessageReceived(StringTableEntry queue, const char* event, const char* data)
{
return Con::executef(this, "onMessageReceived", queue, event, data);
}
bool PopupMenu::onMessageObjectReceived(StringTableEntry queue, Message *msg )
{
return Con::executef(this, "onMessageReceived", queue, Con::getIntArg(msg->getId()));
}
//////////////////////////////////////////////////////////////////////////
// Platform Menu Data
//////////////////////////////////////////////////////////////////////////
GuiMenuBar* PopupMenu::getMenuBarCtrl()
{
return mMenuBarCtrl;
}
//////////////////////////////////////////////////////////////////////////
// Public Methods
//////////////////////////////////////////////////////////////////////////
S32 PopupMenu::insertItem(S32 pos, const char *title, const char* accelerator, const char* cmd)
{
String titleString = title;
MenuItem newItem;
newItem.id = pos;
newItem.text = titleString;
newItem.cmd = cmd;
if (titleString.isEmpty() || titleString == String("-"))
newItem.isSpacer = true;
else
newItem.isSpacer = false;
if (accelerator[0])
newItem.accelerator = dStrdup(accelerator);
else
newItem.accelerator = NULL;
newItem.visible = true;
newItem.isChecked = false;
newItem.acceleratorIndex = 0;
newItem.enabled = !newItem.isSpacer;
newItem.isSubmenu = false;
newItem.subMenu = nullptr;
newItem.subMenuParentMenu = nullptr;
mMenuItems.push_back(newItem);
return pos;
}
S32 PopupMenu::insertSubMenu(S32 pos, const char *title, PopupMenu *submenu)
{
S32 itemPos = insertItem(pos, title, "", "");
mMenuItems[itemPos].isSubmenu = true;
mMenuItems[itemPos].subMenu = submenu;
mMenuItems[itemPos].subMenuParentMenu = this;
submenu->isSubmenu = true;
return itemPos;
}
bool PopupMenu::setItem(S32 pos, const char *title, const char* accelerator, const char* cmd)
{
String titleString = title;
for (U32 i = 0; i < mMenuItems.size(); i++)
{
if (mMenuItems[i].text == titleString)
{
mMenuItems[i].id = pos;
mMenuItems[i].cmd = cmd;
if (accelerator && accelerator[0])
mMenuItems[i].accelerator = dStrdup(accelerator);
else
mMenuItems[i].accelerator = NULL;
return true;
}
}
return false;
}
void PopupMenu::removeItem(S32 itemPos)
{
if (mMenuItems.size() < itemPos || itemPos < 0)
return;
mMenuItems.erase(itemPos);
}
//////////////////////////////////////////////////////////////////////////
void PopupMenu::enableItem(S32 pos, bool enable)
{
if (mMenuItems.size() < pos || pos < 0)
return;
mMenuItems[pos].enabled = enable;
}
void PopupMenu::checkItem(S32 pos, bool checked)
{
if (mMenuItems.size() < pos || pos < 0)
return;
if (checked && mMenuItems[pos].checkGroup != -1)
{
// first, uncheck everything in the group:
for (U32 i = 0; i < mMenuItems.size(); i++)
if (mMenuItems[i].checkGroup == mMenuItems[pos].checkGroup && mMenuItems[i].isChecked)
mMenuItems[i].isChecked = false;
}
mMenuItems[pos].isChecked;
}
void PopupMenu::checkRadioItem(S32 firstPos, S32 lastPos, S32 checkPos)
{
for (U32 i = 0; i < mMenuItems.size(); i++)
{
if (mMenuItems[i].id >= firstPos && mMenuItems[i].id <= lastPos)
{
mMenuItems[i].isChecked = false;
}
}
}
bool PopupMenu::isItemChecked(S32 pos)
{
if (mMenuItems.size() < pos || pos < 0)
return false;
return mMenuItems[pos].isChecked;
}
U32 PopupMenu::getItemCount()
{
return mMenuItems.size();
}
//////////////////////////////////////////////////////////////////////////
bool PopupMenu::canHandleID(U32 id)
{
return true;
}
bool PopupMenu::handleSelect(U32 command, const char *text /* = NULL */)
{
return dAtob(Con::executef(this, "onSelectItem", Con::getIntArg(command), text ? text : ""));
}
//////////////////////////////////////////////////////////////////////////
void PopupMenu::showPopup(GuiCanvas *owner, S32 x /* = -1 */, S32 y /* = -1 */)
{
if (owner == NULL)
return;
GuiControl* editorGui;
Sim::findObject("EditorGui", editorGui);
if (editorGui)
{
GuiPopupMenuBackgroundCtrl* backgroundCtrl;
Sim::findObject("PopUpMenuControl", backgroundCtrl);
GuiControlProfile* profile;
Sim::findObject("GuiMenubarProfile", profile);
if (!profile)
return;
if (mTextList == nullptr)
{
mTextList = new GuiPopupMenuTextListCtrl();
mTextList->registerObject();
mTextList->setControlProfile(profile);
mTextList->mPopup = this;
mTextList->mMenuBar = getMenuBarCtrl();
}
if (!backgroundCtrl)
{
backgroundCtrl = new GuiPopupMenuBackgroundCtrl();
backgroundCtrl->registerObject("PopUpMenuControl");
}
if (!backgroundCtrl || !mTextList)
return;
if (!isSubmenu)
{
//if we're a 'parent' menu, then tell the background to clear out all existing other popups
backgroundCtrl->clearPopups();
}
//find out if we're doing a first-time add
S32 popupIndex = backgroundCtrl->findPopupMenu(this);
if (popupIndex == -1)
{
backgroundCtrl->addObject(mTextList);
backgroundCtrl->mPopups.push_back(this);
}
mTextList->mBackground = backgroundCtrl;
owner->pushDialogControl(backgroundCtrl, 10);
//Set the background control's menubar, if any, and if it's not already set
if(backgroundCtrl->mMenuBarCtrl == nullptr)
backgroundCtrl->mMenuBarCtrl = getMenuBarCtrl();
backgroundCtrl->setExtent(editorGui->getExtent());
mTextList->clear();
S32 textWidth = 0, width = 0;
S32 acceleratorWidth = 0;
GFont *font = profile->mFont;
Point2I maxBitmapSize = Point2I(0, 0);
S32 numBitmaps = profile->mBitmapArrayRects.size();
if (numBitmaps)
{
RectI *bitmapBounds = profile->mBitmapArrayRects.address();
for (S32 i = 0; i < numBitmaps; i++)
{
if (bitmapBounds[i].extent.x > maxBitmapSize.x)
maxBitmapSize.x = bitmapBounds[i].extent.x;
if (bitmapBounds[i].extent.y > maxBitmapSize.y)
maxBitmapSize.y = bitmapBounds[i].extent.y;
}
}
for (U32 i = 0; i < mMenuItems.size(); i++)
{
if (!mMenuItems[i].visible)
continue;
S32 iTextWidth = font->getStrWidth(mMenuItems[i].text.c_str());
S32 iAcceleratorWidth = mMenuItems[i].accelerator ? font->getStrWidth(mMenuItems[i].accelerator) : 0;
if (iTextWidth > textWidth)
textWidth = iTextWidth;
if (iAcceleratorWidth > acceleratorWidth)
acceleratorWidth = iAcceleratorWidth;
}
width = textWidth + acceleratorWidth + maxBitmapSize.x * 2 + 2 + 4;
mTextList->setCellSize(Point2I(width, font->getHeight() + 2));
mTextList->clearColumnOffsets();
mTextList->addColumnOffset(-1); // add an empty column in for the bitmap index.
mTextList->addColumnOffset(maxBitmapSize.x + 1);
mTextList->addColumnOffset(maxBitmapSize.x + 1 + textWidth + 4);
U32 entryCount = 0;
for (U32 i = 0; i < mMenuItems.size(); i++)
{
if (!mMenuItems[i].visible)
continue;
char buf[512];
// If this menu item is a submenu, then set the isSubmenu to 2 to indicate
// an arrow should be drawn. Otherwise set the isSubmenu normally.
char isSubmenu = 1;
if (mMenuItems[i].isSubmenu)
isSubmenu = 2;
char bitmapIndex = 1;
if (mMenuItems[i].bitmapIndex >= 0 && (mMenuItems[i].bitmapIndex * 3 <= profile->mBitmapArrayRects.size()))
bitmapIndex = mMenuItems[i].bitmapIndex + 2;
dSprintf(buf, sizeof(buf), "%c%c\t%s\t%s", bitmapIndex, isSubmenu, mMenuItems[i].text.c_str(), mMenuItems[i].accelerator ? mMenuItems[i].accelerator : "");
mTextList->addEntry(entryCount, buf);
if (!mMenuItems[i].enabled)
mTextList->setEntryActive(entryCount, false);
entryCount++;
}
Point2I pos = Point2I::Zero;
if (x == -1 && y == -1)
pos = owner->getCursorPos();
else
pos = Point2I(x, y);
mTextList->setPosition(pos);
//nudge in if we'd overshoot the screen
S32 widthDiff = (mTextList->getPosition().x + mTextList->getExtent().x) - backgroundCtrl->getWidth();
if (widthDiff > 0)
{
Point2I popupPos = mTextList->getPosition();
mTextList->setPosition(popupPos.x - widthDiff, popupPos.y);
}
mTextList->setHidden(false);
}
}
void PopupMenu::hidePopup()
{
if (mTextList)
{
mTextList->setHidden(true);
}
hidePopupSubmenus();
}
void PopupMenu::hidePopupSubmenus()
{
for (U32 i = 0; i < mMenuItems.size(); i++)
{
if (mMenuItems[i].subMenu != nullptr)
mMenuItems[i].subMenu->hidePopup();
}
}
//-----------------------------------------------------------------------------
// Console Methods
//-----------------------------------------------------------------------------
DefineConsoleMethod(PopupMenu, insertItem, S32, (S32 pos, const char * title, const char * accelerator, const char* cmd), ("", "", ""), "(pos[, title][, accelerator][, cmd])")
{
return object->insertItem(pos, title, accelerator, cmd);
}
DefineConsoleMethod(PopupMenu, removeItem, void, (S32 pos), , "(pos)")
{
object->removeItem(pos);
}
DefineConsoleMethod(PopupMenu, insertSubMenu, S32, (S32 pos, String title, String subMenu), , "(pos, title, subMenu)")
{
PopupMenu *mnu = dynamic_cast<PopupMenu *>(Sim::findObject(subMenu));
if(mnu == NULL)
{
Con::errorf("PopupMenu::insertSubMenu - Invalid PopupMenu object specified for submenu");
return -1;
}
return object->insertSubMenu(pos, title, mnu);
}
DefineConsoleMethod(PopupMenu, setItem, bool, (S32 pos, const char * title, const char * accelerator, const char *cmd), (""), "(pos, title[, accelerator][, cmd])")
{
return object->setItem(pos, title, accelerator, cmd);
}
//-----------------------------------------------------------------------------
DefineConsoleMethod(PopupMenu, enableItem, void, (S32 pos, bool enabled), , "(pos, enabled)")
{
object->enableItem(pos, enabled);
}
DefineConsoleMethod(PopupMenu, checkItem, void, (S32 pos, bool checked), , "(pos, checked)")
{
object->checkItem(pos, checked);
}
DefineConsoleMethod(PopupMenu, checkRadioItem, void, (S32 firstPos, S32 lastPos, S32 checkPos), , "(firstPos, lastPos, checkPos)")
{
object->checkRadioItem(firstPos, lastPos, checkPos);
}
DefineConsoleMethod(PopupMenu, isItemChecked, bool, (S32 pos), , "(pos)")
{
return object->isItemChecked(pos);
}
DefineConsoleMethod(PopupMenu, getItemCount, S32, (), , "()")
{
return object->getItemCount();
}
//-----------------------------------------------------------------------------
DefineConsoleMethod(PopupMenu, showPopup, void, (const char * canvasName, S32 x, S32 y), ( -1, -1), "(Canvas,[x, y])")
{
GuiCanvas *pCanvas = dynamic_cast<GuiCanvas*>(Sim::findObject(canvasName));
object->showPopup(pCanvas, x, y);
}

View file

@ -0,0 +1,184 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _POPUPMENU_H_
#define _POPUPMENU_H_
#include "console/simBase.h"
#include "core/util/tVector.h"
#include "util/messaging/dispatcher.h"
#include "gui/core/guiCanvas.h"
class PopupMenu;
class GuiMenuBar;
class GuiPopupMenuTextListCtrl;
class GuiPopupMenuBackgroundCtrl;
struct MenuItem // an individual item in a pull-down menu
{
String text; // the text of the menu item
U32 id; // a script-assigned identifier
char *accelerator; // the keyboard accelerator shortcut for the menu item
U32 acceleratorIndex; // index of this accelerator
bool enabled; // true if the menu item is selectable
bool visible; // true if the menu item is visible
S32 bitmapIndex; // index of the bitmap in the bitmap array
S32 checkGroup; // the group index of the item visa vi check marks -
// only one item in the group can be checked.
bool isSubmenu; // This menu item has a submenu that will be displayed
bool isChecked;
bool isSpacer;
bool isMenubarEntry;
PopupMenu* subMenuParentMenu; // For a submenu, this is the parent menu
PopupMenu* subMenu;
String cmd;
};
// PopupMenu represents a menu.
// You can add menu items to the menu, but there is no torque object associated
// with these menu items, they exist only in a platform specific manner.
class PopupMenu : public SimObject, public virtual Dispatcher::IMessageListener
{
typedef SimObject Parent;
friend class GuiMenuBar;
friend class GuiPopupMenuTextListCtrl;
friend class GuiPopupMenuBackgroundCtrl;
protected:
Vector<MenuItem> mMenuItems;
GuiMenuBar* mMenuBarCtrl;
StringTableEntry barTitle;
RectI bounds;
bool visible;
S32 bitmapIndex; // Index of the bitmap in the bitmap array (-1 = no bitmap)
bool drawBitmapOnly; // Draw only the bitmap and not the text
bool drawBorder; // Should a border be drawn around this menu (usually if we only have a bitmap, we don't want a border)
bool isSubmenu;
//This is the gui control that renders our popup
GuiPopupMenuTextListCtrl *mTextList;
public:
PopupMenu();
virtual ~PopupMenu();
DECLARE_CONOBJECT(PopupMenu);
static void initPersistFields();
virtual bool onAdd();
virtual void onRemove();
static PopupMenuEvent smPopupMenuEvent;
static bool smSelectionEventHandled; /// Set to true if any menu or submenu handles a selection event
/// pass NULL for @p title to insert a separator
/// returns the menu item's ID, or -1 on failure.
/// implementd on a per-platform basis.
/// TODO: factor out common code
S32 insertItem(S32 pos, const char *title, const char* accelerator, const char* cmd);
/// Sets the name title and accelerator for
/// an existing item.
bool setItem(S32 pos, const char *title, const char* accelerator, const char* cmd);
/// pass NULL for @p title to insert a separator
/// returns the menu item's ID, or -1 on failure.
/// adds the submenu to the mSubmenus vector.
/// implemented on a per-platform basis.
/// TODO: factor out common code
S32 insertSubMenu(S32 pos, const char *title, PopupMenu *submenu);
/// remove the menu item at @p itemPos
/// if the item has a submenu, it is removed from the mSubmenus list.
/// implemented on a per-platform basis.
/// TODO: factor out common code
void removeItem(S32 itemPos);
/// implemented on a per-platform basis.
void enableItem(S32 pos, bool enable);
/// implemented on a per-platform basis.
void checkItem(S32 pos, bool checked);
/// All items at positions firstPos through lastPos are unchecked, and the
/// item at checkPos is checked.
/// implemented on a per-platform basis.
void checkRadioItem(S32 firstPos, S32 lastPos, S32 checkPos);
bool isItemChecked(S32 pos);
/// Returns the number of items in the menu.
U32 getItemCount();
//-----------------------------------------------------------------------------
/// Displays this menu as a popup menu and blocks until the user has selected
/// an item.
/// @param canvas the owner to show this popup associated with
/// @param x window local x coordinate at which to display the popup menu
/// @param y window local y coordinate at which to display the popup menu
/// implemented on a per-platform basis.
void showPopup(GuiCanvas *owner, S32 x = -1, S32 y = -1);
void hidePopup();
void hidePopupSubmenus();
/// Returns true iff this menu contains an item that matches @p iD.
/// implemented on a per-platform basis.
/// TODO: factor out common code
bool canHandleID(U32 iD);
/// A menu item in this menu has been selected by id.
/// Submenus are given a chance to respond to the command first.
/// If no submenu can handle the command id, this menu handles it.
/// The script callback this::onSelectItem( position, text) is called.
/// If @p text is null, then the text arg passed to script is the text of
/// the selected menu item.
/// implemented on a per-platform basis.
/// TODO: factor out common code
bool handleSelect(U32 command, const char *text = NULL);
void onMenuSelect();
/// Helper function to allow menu selections from signal events.
/// Wraps canHandleID() and handleSelect() in one function
/// without changing their internal functionality, so
/// it should work regardless of platform.
void handleSelectEvent(U32 popID, U32 command);
virtual bool onMessageReceived(StringTableEntry queue, const char* event, const char* data );
virtual bool onMessageObjectReceived(StringTableEntry queue, Message *msg );
bool isVisible() { return visible; }
void setVisible(bool isVis) { visible = isVis; }
GuiMenuBar* getMenuBarCtrl();
};
#endif // _POPUPMENU_H_