irrlicht/source/Irrlicht/CGUIContextMenu.cpp
cutealien 17c7a1bd6e CGUIContextMenu no longer marks EMIE_MOUSE_MOVED as handled
This got in the way of allowing to move a camera with right-mouse-button while having a context menu.
Hard to tell which way is "more" correct as this break of behavior probably can also mess up some situations.
There also would be 3rd option of only catching the event when highlighting happens.
Anyway - usually if this should be caught it should be caught for all ui elements and people will check focused or hovered.

git-svn-id: svn://svn.code.sf.net/p/irrlicht/code/trunk@6412 dfc29bdd-3216-0410-991c-e03cc46cb475
2022-06-23 13:25:55 +00:00

881 lines
20 KiB
C++

// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "CGUIContextMenu.h"
#ifdef _IRR_COMPILE_WITH_GUI_
#include "IGUISkin.h"
#include "IGUIEnvironment.h"
#include "IVideoDriver.h"
#include "IGUIFont.h"
#include "IGUISpriteBank.h"
#include "os.h"
namespace irr
{
namespace gui
{
//! constructor
CGUIContextMenu::CGUIContextMenu(IGUIEnvironment* environment,
IGUIElement* parent, s32 id,
core::rect<s32> rectangle, bool getFocus, bool allowFocus)
: IGUIContextMenu(environment, parent, id, rectangle), EventParent(0), LastFont(0),
CloseHandling(ECMC_REMOVE), HighLighted(-1), ChangeTime(0), AllowFocus(allowFocus)
{
#ifdef _DEBUG
setDebugName("CGUIContextMenu");
#endif
Pos = rectangle.UpperLeftCorner;
recalculateSize();
if (getFocus)
Environment->setFocus(this);
setNotClipped(true);
}
//! destructor
CGUIContextMenu::~CGUIContextMenu()
{
for (u32 i=0; i<Items.size(); ++i)
if (Items[i].SubMenu)
Items[i].SubMenu->drop();
if (LastFont)
LastFont->drop();
}
//! set behavior when menus are closed
void CGUIContextMenu::setCloseHandling(ECONTEXT_MENU_CLOSE onClose)
{
CloseHandling = onClose;
}
//! get current behavior when the menue will be closed
ECONTEXT_MENU_CLOSE CGUIContextMenu::getCloseHandling() const
{
return CloseHandling;
}
//! Returns amount of menu items
u32 CGUIContextMenu::getItemCount() const
{
return Items.size();
}
//! Adds a menu item.
u32 CGUIContextMenu::addItem(const wchar_t* text, s32 commandId, bool enabled, bool hasSubMenu, bool checked, bool autoChecking)
{
return insertItem(Items.size(), text, commandId, enabled, hasSubMenu, checked, autoChecking);
}
//! Insert a menu item at specified position.
u32 CGUIContextMenu::insertItem(u32 idx, const wchar_t* text, s32 commandId, bool enabled,
bool hasSubMenu, bool checked, bool autoChecking)
{
SItem s;
s.Enabled = enabled;
s.Checked = checked;
s.AutoChecking = autoChecking;
s.Text = text;
s.IsSeparator = (text == 0);
s.SubMenu = 0;
s.CommandId = commandId;
s.PosY = 0;
if (hasSubMenu)
{
s.SubMenu = new CGUIContextMenu(Environment, this, commandId,
core::rect<s32>(0,0,100,100), false, false);
s.SubMenu->setVisible(false);
}
u32 result = idx;
if ( idx < Items.size() )
{
Items.insert(s, idx);
}
else
{
Items.push_back(s);
result = Items.size() - 1;
}
recalculateSize();
return result;
}
s32 CGUIContextMenu::findItemWithCommandId(s32 commandId, u32 idxStartSearch) const
{
for ( u32 i=idxStartSearch; i<Items.size(); ++i )
{
if ( Items[i].CommandId == commandId )
{
return (s32)i;
}
}
return -1;
}
//! Adds a sub menu from an element that already exists.
void CGUIContextMenu::setSubMenu(u32 index, CGUIContextMenu* menu)
{
if (index >= Items.size())
return;
if (menu)
menu->grab();
if (Items[index].SubMenu)
Items[index].SubMenu->drop();
Items[index].SubMenu = menu;
if (menu)
{
menu->setVisible(false);
menu->AllowFocus = false;
if ( Environment->getFocus() == menu )
{
Environment->setFocus( this );
}
}
recalculateSize();
}
//! Adds a separator item to the menu
void CGUIContextMenu::addSeparator()
{
addItem(0, -1, true, false, false, false);
}
//! Returns text of the menu item.
const wchar_t* CGUIContextMenu::getItemText(u32 idx) const
{
if (idx >= Items.size())
return 0;
return Items[idx].Text.c_str();
}
//! Sets text of the menu item.
void CGUIContextMenu::setItemText(u32 idx, const wchar_t* text)
{
if (idx >= Items.size())
return;
Items[idx].Text = text;
recalculateSize();
}
//! should the element change the checked status on clicking
void CGUIContextMenu::setItemAutoChecking(u32 idx, bool autoChecking)
{
if ( idx >= Items.size())
return;
Items[idx].AutoChecking = autoChecking;
}
//! does the element change the checked status on clicking
bool CGUIContextMenu::getItemAutoChecking(u32 idx) const
{
if (idx >= Items.size())
return false;
return Items[idx].AutoChecking;
}
//! Returns if a menu item is enabled
bool CGUIContextMenu::isItemEnabled(u32 idx) const
{
if (idx >= Items.size())
{
return false;
}
return Items[idx].Enabled;
}
//! Returns if a menu item is checked
bool CGUIContextMenu::isItemChecked(u32 idx) const
{
if (idx >= Items.size())
{
return false;
}
return Items[idx].Checked;
}
//! Sets if the menu item should be enabled.
void CGUIContextMenu::setItemEnabled(u32 idx, bool enabled)
{
if (idx >= Items.size())
return;
Items[idx].Enabled = enabled;
}
//! Sets if the menu item should be checked.
void CGUIContextMenu::setItemChecked(u32 idx, bool checked )
{
if (idx >= Items.size())
return;
Items[idx].Checked = checked;
}
//! Removes a menu item
void CGUIContextMenu::removeItem(u32 idx)
{
if (idx >= Items.size())
return;
if (Items[idx].SubMenu)
{
Items[idx].SubMenu->drop();
Items[idx].SubMenu = 0;
}
Items.erase(idx);
recalculateSize();
}
//! Removes all menu items
void CGUIContextMenu::removeAllItems()
{
for (u32 i=0; i<Items.size(); ++i)
if (Items[i].SubMenu)
Items[i].SubMenu->drop();
Items.clear();
recalculateSize();
}
//! called if an event happened.
bool CGUIContextMenu::OnEvent(const SEvent& event)
{
if (isEnabled())
{
switch(event.EventType)
{
case EET_GUI_EVENT:
switch(event.GUIEvent.EventType)
{
case EGET_ELEMENT_FOCUS_LOST:
if (event.GUIEvent.Caller == this && !isMyChild(event.GUIEvent.Element) && AllowFocus)
{
// set event parent of submenus
IGUIElement * p = EventParent ? EventParent : Parent;
if ( p ) // can be 0 when element got removed already
{
setEventParent(p);
SEvent eventClose;
eventClose.EventType = EET_GUI_EVENT;
eventClose.GUIEvent.Caller = this;
eventClose.GUIEvent.Element = 0;
eventClose.GUIEvent.EventType = EGET_ELEMENT_CLOSED;
if ( !p->OnEvent(eventClose) )
{
if ( CloseHandling & ECMC_HIDE )
{
setVisible(false);
}
if ( CloseHandling & ECMC_REMOVE )
{
remove();
}
}
}
return false;
}
break;
case EGET_ELEMENT_FOCUSED:
if (event.GUIEvent.Caller == this && !AllowFocus)
{
return true;
}
break;
default:
break;
}
break;
case EET_MOUSE_INPUT_EVENT:
switch(event.MouseInput.Event)
{
case EMIE_LMOUSE_LEFT_UP:
{
// menu might be removed if it loses focus in sendClick, so grab a reference
grab();
const u32 t = sendClick(core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y));
if ((t==0 || t==1) && Environment->hasFocus(this))
Environment->removeFocus(this);
drop();
}
return true;
case EMIE_LMOUSE_PRESSED_DOWN:
return true;
case EMIE_MOUSE_MOVED:
if (Environment->hasFocus(this))
highlight(core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y), true);
break;
default:
break;
}
break;
default:
break;
}
}
return IGUIElement::OnEvent(event);
}
//! Sets the visible state of this element.
void CGUIContextMenu::setVisible(bool visible)
{
HighLighted = -1;
ChangeTime = os::Timer::getTime();
for (u32 j=0; j<Items.size(); ++j)
if (Items[j].SubMenu)
Items[j].SubMenu->setVisible(false);
IGUIElement::setVisible(visible);
}
//! sends a click Returns:
//! 0 if click went outside of the element,
//! 1 if a valid button was clicked,
//! 2 if a nonclickable element was clicked
u32 CGUIContextMenu::sendClick(const core::position2d<s32>& p)
{
u32 t = 0;
// get number of open submenu
s32 openmenu = -1;
s32 j;
for (j=0; j<(s32)Items.size(); ++j)
if (Items[j].SubMenu && Items[j].SubMenu->isVisible())
{
openmenu = j;
break;
}
// delegate click operation to submenu
if (openmenu != -1)
{
t = Items[j].SubMenu->sendClick(p);
if (t != 0)
return t; // clicked something
}
// check click on myself
if (isPointInside(p) &&
(u32)HighLighted < Items.size())
{
if (!Items[HighLighted].Enabled ||
Items[HighLighted].IsSeparator ||
Items[HighLighted].SubMenu)
return 2;
if ( Items[HighLighted].AutoChecking )
{
Items[HighLighted].Checked = Items[HighLighted].Checked ? false : true;
}
SEvent event;
event.EventType = EET_GUI_EVENT;
event.GUIEvent.Caller = this;
event.GUIEvent.Element = 0;
event.GUIEvent.EventType = EGET_MENU_ITEM_SELECTED;
if (EventParent)
EventParent->OnEvent(event);
else if (Parent)
Parent->OnEvent(event);
return 1;
}
return 0;
}
//! returns true, if an element was highlighted
bool CGUIContextMenu::highlight(const core::position2d<s32>& p, bool canOpenSubMenu)
{
if (!isEnabled())
{
return false;
}
// get number of open submenu
s32 openmenu = -1;
s32 i;
for (i=0; i<(s32)Items.size(); ++i)
if (Items[i].Enabled && Items[i].SubMenu && Items[i].SubMenu->isVisible())
{
openmenu = i;
break;
}
// delegate highlight operation to submenu
if (openmenu != -1)
{
if (Items[openmenu].Enabled && Items[openmenu].SubMenu->highlight(p, canOpenSubMenu))
{
HighLighted = openmenu;
ChangeTime = os::Timer::getTime();
return true;
}
}
// highlight myself
for (i=0; i<(s32)Items.size(); ++i)
{
if (Items[i].Enabled && getHRect(Items[i], AbsoluteRect).isPointInside(p))
{
HighLighted = i;
ChangeTime = os::Timer::getTime();
// make submenus visible/invisible
for (s32 j=0; j<(s32)Items.size(); ++j)
if (Items[j].SubMenu)
{
if ( j == i && canOpenSubMenu && Items[j].Enabled )
Items[j].SubMenu->setVisible(true);
else if ( j != i )
Items[j].SubMenu->setVisible(false);
}
return true;
}
}
HighLighted = openmenu;
return false;
}
//! returns the item highlight-area
core::rect<s32> CGUIContextMenu::getHRect(const SItem& i, const core::rect<s32>& absolute) const
{
core::rect<s32> r = absolute;
r.UpperLeftCorner.Y += i.PosY;
r.LowerRightCorner.Y = r.UpperLeftCorner.Y + i.Dim.Height;
return r;
}
//! Gets drawing rect of Item
core::rect<s32> CGUIContextMenu::getRect(const SItem& i, const core::rect<s32>& absolute) const
{
core::rect<s32> r = absolute;
r.UpperLeftCorner.Y += i.PosY;
r.LowerRightCorner.Y = r.UpperLeftCorner.Y + i.Dim.Height;
r.UpperLeftCorner.X += 20;
return r;
}
//! draws the element and its children
void CGUIContextMenu::draw()
{
if (!IsVisible)
return;
IGUISkin* skin = Environment->getSkin();
if (!skin)
return;
IGUIFont* font = skin->getFont(EGDF_MENU);
if (font != LastFont)
{
if (LastFont)
LastFont->drop();
LastFont = font;
if (LastFont)
LastFont->grab();
recalculateSize();
}
IGUISpriteBank* sprites = skin->getSpriteBank();
core::rect<s32> rect = AbsoluteRect;
core::rect<s32>* clip = 0;
// draw frame
skin->draw3DMenuPane(this, AbsoluteRect, clip);
// loop through all menu items
rect = AbsoluteRect;
s32 y = AbsoluteRect.UpperLeftCorner.Y;
for (s32 i=0; i<(s32)Items.size(); ++i)
{
if (Items[i].IsSeparator)
{
// draw separator
rect = AbsoluteRect;
rect.UpperLeftCorner.Y += Items[i].PosY + 3;
rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 1;
rect.UpperLeftCorner.X += 5;
rect.LowerRightCorner.X -= 5;
skin->draw2DRectangle(this, skin->getColor(EGDC_3D_SHADOW), rect, clip);
rect.LowerRightCorner.Y += 1;
rect.UpperLeftCorner.Y += 1;
skin->draw2DRectangle(this, skin->getColor(EGDC_3D_HIGH_LIGHT), rect, clip);
y += 10;
}
else
{
rect = getRect(Items[i], AbsoluteRect);
// draw highlighted
if (i == HighLighted && Items[i].Enabled)
{
core::rect<s32> r = AbsoluteRect;
r.LowerRightCorner.Y = rect.LowerRightCorner.Y;
r.UpperLeftCorner.Y = rect.UpperLeftCorner.Y;
r.LowerRightCorner.X -= 5;
r.UpperLeftCorner.X += 5;
skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), r, clip);
}
// draw text
EGUI_DEFAULT_COLOR c = EGDC_BUTTON_TEXT;
if (i == HighLighted)
c = EGDC_HIGH_LIGHT_TEXT;
if (!Items[i].Enabled)
c = EGDC_GRAY_TEXT;
if (font)
font->draw(Items[i].Text.c_str(), rect,
skin->getColor(c), false, true, clip);
// draw submenu symbol
if (Items[i].SubMenu && sprites)
{
core::rect<s32> r = rect;
r.UpperLeftCorner.X = r.LowerRightCorner.X - 15;
sprites->draw2DSprite(skin->getIcon(EGDI_CURSOR_RIGHT),
r.getCenter(), clip, skin->getColor(c),
(i == HighLighted) ? ChangeTime : 0,
(i == HighLighted) ? os::Timer::getTime() : 0,
(i == HighLighted), true);
}
// draw checked symbol
if (Items[i].Checked && sprites)
{
core::rect<s32> r = rect;
r.LowerRightCorner.X = r.UpperLeftCorner.X - 15;
r.UpperLeftCorner.X = r.LowerRightCorner.X + 15;
sprites->draw2DSprite(skin->getIcon(EGDI_CHECK_BOX_CHECKED),
r.getCenter(), clip, skin->getColor(c),
(i == HighLighted) ? ChangeTime : 0,
(i == HighLighted) ? os::Timer::getTime() : 0,
(i == HighLighted), true);
}
}
}
IGUIElement::draw();
}
void CGUIContextMenu::recalculateSize()
{
IGUIFont* font = Environment->getSkin()->getFont(EGDF_MENU);
if (!font)
return;
core::rect<s32> rect;
rect.UpperLeftCorner = RelativeRect.UpperLeftCorner;
u32 width = 100;
u32 height = 3;
u32 i;
for (i=0; i<Items.size(); ++i)
{
if (Items[i].IsSeparator)
{
Items[i].Dim.Width = 100;
Items[i].Dim.Height = 10;
}
else
{
Items[i].Dim = font->getDimension(Items[i].Text.c_str());
Items[i].Dim.Width += 40;
if (Items[i].Dim.Width > width)
width = Items[i].Dim.Width;
}
Items[i].PosY = height;
height += Items[i].Dim.Height;
}
height += 5;
if (height < 10)
height = 10;
rect.LowerRightCorner.X = RelativeRect.UpperLeftCorner.X + width;
rect.LowerRightCorner.Y = RelativeRect.UpperLeftCorner.Y + height;
setRelativePosition(rect);
// recalculate submenus
for (i=0; i<Items.size(); ++i)
{
if (Items[i].SubMenu)
{
// move submenu
const s32 w = Items[i].SubMenu->getAbsolutePosition().getWidth();
const s32 h = Items[i].SubMenu->getAbsolutePosition().getHeight();
core::rect<s32> subRect(width-5, Items[i].PosY, width+w-5, Items[i].PosY+h);
gui::IGUIElement * root = Environment->getRootGUIElement();
if ( root )
{
core::rect<s32> rectRoot( root->getAbsolutePosition() );
// if it would be drawn beyond the right border, then add it to the left side
if ( getAbsolutePosition().UpperLeftCorner.X+subRect.LowerRightCorner.X > rectRoot.LowerRightCorner.X )
{
subRect.UpperLeftCorner.X = -w;
subRect.LowerRightCorner.X = 0;
}
// if it would be drawn below bottom border, move it up, but not further than to top.
irr::s32 belowBottom = getAbsolutePosition().UpperLeftCorner.Y+subRect.LowerRightCorner.Y - rectRoot.LowerRightCorner.Y;
if ( belowBottom > 0 )
{
irr::s32 belowTop = getAbsolutePosition().UpperLeftCorner.Y+subRect.UpperLeftCorner.Y;
irr::s32 moveUp = belowBottom < belowTop ? belowBottom : belowTop;
subRect.UpperLeftCorner.Y -= moveUp;
subRect.LowerRightCorner.Y -= moveUp;
}
}
Items[i].SubMenu->setRelativePosition(subRect);
}
}
}
//! Returns the selected item in the menu
s32 CGUIContextMenu::getSelectedItem() const
{
return HighLighted;
}
//! \return Returns a pointer to the submenu of an item.
IGUIContextMenu* CGUIContextMenu::getSubMenu(u32 idx) const
{
if (idx >= Items.size())
return 0;
return Items[idx].SubMenu;
}
//! Returns command id of a menu item
s32 CGUIContextMenu::getItemCommandId(u32 idx) const
{
if (idx >= Items.size())
return -1;
return Items[idx].CommandId;
}
//! Sets the command id of a menu item
void CGUIContextMenu::setItemCommandId(u32 idx, s32 id)
{
if (idx >= Items.size())
return;
Items[idx].CommandId = id;
}
//! Writes attributes of the element.
void CGUIContextMenu::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const
{
IGUIElement::serializeAttributes(out,options);
out->addPosition2d("Position", Pos);
if (Parent->getType() == EGUIET_CONTEXT_MENU || Parent->getType() == EGUIET_MENU )
{
const IGUIContextMenu* const ptr = (const IGUIContextMenu*)Parent;
// find the position of this item in its parent's list
u32 i;
// VC6 needs the cast for this
for (i=0; (i<ptr->getItemCount()) && (ptr->getSubMenu(i) != (const IGUIContextMenu*)this); ++i)
; // do nothing
out->addInt("ParentItem", i);
}
out->addInt("CloseHandling", (s32)CloseHandling);
// write out the item list
out->addInt("ItemCount", Items.size());
core::stringc tmp;
for (u32 i=0; i < Items.size(); ++i)
{
tmp = "IsSeparator"; tmp += i;
out->addBool(tmp.c_str(), Items[i].IsSeparator);
if (!Items[i].IsSeparator)
{
tmp = "Text"; tmp += i;
out->addString(tmp.c_str(), Items[i].Text.c_str());
tmp = "CommandID"; tmp += i;
out->addInt(tmp.c_str(), Items[i].CommandId);
tmp = "Enabled"; tmp += i;
out->addBool(tmp.c_str(), Items[i].Enabled);
tmp = "Checked"; tmp += i;
out->addBool(tmp.c_str(), Items[i].Checked);
tmp = "AutoChecking"; tmp += i;
out->addBool(tmp.c_str(), Items[i].AutoChecking);
}
}
}
//! Reads attributes of the element
void CGUIContextMenu::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0)
{
IGUIElement::deserializeAttributes(in,options);
Pos = in->getAttributeAsPosition2d("Position");
// link to this item's parent
if (Parent && ( Parent->getType() == EGUIET_CONTEXT_MENU || Parent->getType() == EGUIET_MENU ) )
((CGUIContextMenu*)Parent)->setSubMenu(in->getAttributeAsInt("ParentItem"),this);
CloseHandling = (ECONTEXT_MENU_CLOSE)in->getAttributeAsInt("CloseHandling");
removeAllItems();
// read the item list
const s32 count = in->getAttributeAsInt("ItemCount");
for (s32 i=0; i<count; ++i)
{
core::stringc tmp;
core::stringw txt;
s32 commandid=-1;
bool enabled=true;
bool checked=false;
bool autochecking=false;
tmp = "IsSeparator"; tmp += i;
if ( in->existsAttribute(tmp.c_str()) && in->getAttributeAsBool(tmp.c_str()) )
addSeparator();
else
{
tmp = "Text"; tmp += i;
if ( in->existsAttribute(tmp.c_str()) )
txt = in->getAttributeAsStringW(tmp.c_str());
tmp = "CommandID"; tmp += i;
if ( in->existsAttribute(tmp.c_str()) )
commandid = in->getAttributeAsInt(tmp.c_str());
tmp = "Enabled"; tmp += i;
if ( in->existsAttribute(tmp.c_str()) )
enabled = in->getAttributeAsBool(tmp.c_str());
tmp = "Checked"; tmp += i;
if ( in->existsAttribute(tmp.c_str()) )
checked = in->getAttributeAsBool(tmp.c_str());
tmp = "AutoChecking"; tmp += i;
if ( in->existsAttribute(tmp.c_str()) )
autochecking = in->getAttributeAsBool(tmp.c_str());
addItem(core::stringw(txt.c_str()).c_str(), commandid, enabled, false, checked, autochecking);
}
}
recalculateSize();
}
// because sometimes the element has no parent at click time
void CGUIContextMenu::setEventParent(IGUIElement *parent)
{
EventParent = parent;
for (u32 i=0; i<Items.size(); ++i)
if (Items[i].SubMenu)
Items[i].SubMenu->setEventParent(parent);
}
bool CGUIContextMenu::hasOpenSubMenu() const
{
for (u32 i=0; i<Items.size(); ++i)
if (Items[i].SubMenu && Items[i].SubMenu->isVisible())
return true;
return false;
}
void CGUIContextMenu::closeAllSubMenus()
{
for (u32 i=0; i<Items.size(); ++i)
if (Items[i].SubMenu)
Items[i].SubMenu->setVisible(false);
//HighLighted = -1;
}
} // end namespace
} // end namespace
#endif // _IRR_COMPILE_WITH_GUI_