Page 1 of 1

GUI Modification: Leftward Cascading SubMenu

Posted: Wed May 27, 2009 3:45 pm
by Narcan
One of my projects uses sub menus near the right edge of the screen. Since the default Irrlicht sub menu only cascades to the right (off the screen in this case), I added the option of cascading to the left.

This version of the addItem() function is overloaded to accept a bool that determines if the menu expands normally or is reversed.

Normal:

Code: Select all

submenu->addItem(L"About", 500, true, true, false);
Image

Reversed:

Code: Select all

submenu->addItem(L"About", 500, true, true, false, true);
Image

Diff:

Code: Select all

CGUIContextMenu.cpp:

71a72
> 	s.ReverseCascade = false;
85a87,109
> u32 CGUIContextMenu::addItem(const wchar_t* text, s32 id, bool enabled, bool hasSubMenu, bool checked, bool cascadeLeft)
> {
> 	SItem s;
> 	s.Enabled = enabled;
> 	s.Checked = checked;
> 	s.Text = text;
> 	s.IsSeparator = (text == 0);
> 	s.SubMenu = 0;
> 	s.CommandId = id;
> 	s.ReverseCascade = cascadeLeft;
> 
> 	if (hasSubMenu)
> 	{
> 		s.SubMenu = new CGUIContextMenu(Environment, this, id,
> 			core::rect<s32>(0,0,100,100), false, false);
> 		s.SubMenu->setVisible(false);
> 	}
> 
> 	Items.push_back(s);
> 
> 	recalculateSize();
> 	return Items.size() - 1;
> }
502a527,529
> 
> 				if (!Items[i].ReverseCascade)
> 				{
511a539,550
> 				else
> 				{
> 					r.LowerRightCorner.X = r.UpperLeftCorner.X - 15;
> 
> 					sprites->draw2DSprite(skin->getIcon(EGDI_CURSOR_LEFT),
> 						r.getCenter(), clip, skin->getColor(c),
> 						(i == HighLighted) ? ChangeTime : 0,
> 						(i == HighLighted) ? os::Timer::getTime() : 0,
> 						(i == HighLighted), true);
> 				}
> 			}
> 
583a623,624
> 			if (!Items[i].ReverseCascade)
> 			{
586a628,635
> 			}
> 
> 			else
> 			{
> 				Items[i].SubMenu->setRelativePosition(
> 					core::rect<s32>(0 - w, Items[i].PosY,
> 						0, Items[i].PosY+h));
> 			}

CGUIContextMenu.h:

40a41,43
> 		virtual u32 addItem(const wchar_t* text, s32 commandid,
> 				bool enabled, bool hasSubMenu, bool checked, bool cascadeLeft);
> 
109a113
> 			bool ReverseCascade;

IGUIContextMenu.h:

38,41c38,60
< 		virtual u32 addItem(const wchar_t* text, s32 commandId=-1, bool enabled=true,
< 			bool hasSubMenu=false,
< 			bool checked=false
< 			) = 0;
---
> 
> 		virtual u32 addItem(const wchar_t* text, s32 commandid,
> 				bool enabled, bool hasSubMenu, bool checked)
> 		{
> 			commandid=-1;
> 			enabled=true;
> 			hasSubMenu=false;
> 			checked=false;
> 
> 			return 0;
> 		};
> 
> 		virtual u32 addItem(const wchar_t* text, s32 commandid,
> 				bool enabled, bool hasSubMenu, bool checked, bool cascadeLeft)
> 		{
> 			commandid=-1;
> 			enabled=true;
> 			hasSubMenu=false;
> 			checked=false;
> 			cascadeLeft=false;
> 
> 			return 0;
> 		};


Posted: Wed May 27, 2009 5:22 pm
by Sylence
Nice!
But imho it would be better to let the code decide if it should cascade left or right by checking if the right menu would hang off the screen.

Posted: Wed May 27, 2009 5:58 pm
by Narcan
I considered doing it that way, since it would probably be useful to more people. For my purpose, though, it's best to outright dictate which way the menus go.

Some of my menus are popping out of a vertical column along the right edge of the screen. I know ahead of time that all those menus should only go left, so there's no reason to calculate it on the fly. (I also wanted to avoid the circumstance where they start to expand right, cross the column of buttons, hit the edge and zigzag back. Which meant precalculating the width of every submenu or just using a bool.)

I may write another version that does it your way if I get bored or there's enough interest. :)

Thanks for the feedback.

Posted: Thu May 28, 2009 9:53 pm
by MasterGod
Cool.

By the way, I think it would be better posting the patch instead of the diff.

Posted: Fri May 29, 2009 12:01 am
by CuteAlien
Nice one, thanks for posting.

Posted: Fri Jun 05, 2009 6:59 pm
by Narcan
I just switched from 1.5 to SVN. Here's the patch for revision 2410 if anyone is interested:

Code: Select all

Index: include/IGUIContextMenu.h
===================================================================
--- include/IGUIContextMenu.h	(revision 2410)
+++ include/IGUIContextMenu.h	(working copy)
@@ -35,9 +35,30 @@
 		at this item. You can acess this submenu via getSubMenu().
 		\param checked: Specifies if the menu item should be initially checked.
 		\return Returns the index of the new item */
-		virtual u32 addItem(const wchar_t* text, s32 commandId=-1, bool enabled=true,
-			bool hasSubMenu=false, bool checked=false) = 0;
 
+		virtual u32 addItem(const wchar_t* text, s32 commandid,
+			bool enabled, bool hasSubMenu, bool checked)
+			{
+				commandid=-1;
+				enabled=true;
+				hasSubMenu=false;
+				checked=false;
+
+				return 0;
+			}; 
+
+		virtual u32 addItem(const wchar_t* text, s32 commandid,
+			bool enabled, bool hasSubMenu, bool checked, bool cascadeLeft)
+			{
+				commandid=-1;
+				enabled=true;
+				hasSubMenu=false;
+				checked=false;
+				cascadeLeft=false;
+
+				return 0;
+			}; 
+
 		//! Adds a separator item to the menu
 		virtual void addSeparator() = 0;
 
Index: source/Irrlicht/CGUIContextMenu.cpp
===================================================================
--- source/Irrlicht/CGUIContextMenu.cpp	(revision 2410)
+++ source/Irrlicht/CGUIContextMenu.cpp	(working copy)
@@ -69,6 +69,7 @@
 	s.IsSeparator = (text == 0);
 	s.SubMenu = 0;
 	s.CommandId = id;
+	s.ReverseCascade = false;
 
 	if (hasSubMenu)
 	{
@@ -83,7 +84,30 @@
 	return Items.size() - 1;
 }
 
+u32 CGUIContextMenu::addItem(const wchar_t* text, s32 id, bool enabled, bool hasSubMenu, bool checked, bool cascadeLeft)
+{
+	SItem s;
+	s.Enabled = enabled;
+	s.Checked = checked;
+	s.Text = text;
+	s.IsSeparator = (text == 0);
+	s.SubMenu = 0;
+	s.CommandId = id;
+	s.ReverseCascade = cascadeLeft;
 
+	if (hasSubMenu)
+	{
+		s.SubMenu = new CGUIContextMenu(Environment, this, id,
+			core::rect<s32>(0,0,100,100), false, false);
+		s.SubMenu->setVisible(false);
+	}
+
+	Items.push_back(s);
+
+	recalculateSize();
+	return Items.size() - 1;
+}
+
 //! Adds a sub menu from an element that already exists.
 void CGUIContextMenu::setSubMenu(u32 index, CGUIContextMenu* menu)
 {
@@ -500,13 +524,28 @@
 			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);
+				if (!Items[i].ReverseCascade)
+				{
+					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);
+				}
+
+				else
+				{
+					r.LowerRightCorner.X = r.UpperLeftCorner.X - 15;
+
+					sprites->draw2DSprite(skin->getIcon(EGDI_CURSOR_LEFT),
+						r.getCenter(), clip, skin->getColor(c),
+						(i == HighLighted) ? ChangeTime : 0,
+						(i == HighLighted) ? os::Timer::getTime() : 0,
+						(i == HighLighted), true);
+				}
 			}
 
 			// draw checked symbol
@@ -580,9 +619,19 @@
 			const s32 w = Items[i].SubMenu->getAbsolutePosition().getWidth();
 			const s32 h = Items[i].SubMenu->getAbsolutePosition().getHeight();
 
-			Items[i].SubMenu->setRelativePosition(
-				core::rect<s32>(width-5, Items[i].PosY,
-					width+w-5, Items[i].PosY+h));
+			if (!Items[i].ReverseCascade)
+			{
+				Items[i].SubMenu->setRelativePosition(
+					core::rect<s32>(width-5, Items[i].PosY,
+						width+w-5, Items[i].PosY+h));
+			}
+
+			else
+			{
+				Items[i].SubMenu->setRelativePosition(
+					core::rect<s32>(0 - w, Items[i].PosY,
+						0, Items[i].PosY+h));
+			}
 		}
 	}
 }
Index: source/Irrlicht/CGUIContextMenu.h
===================================================================
--- source/Irrlicht/CGUIContextMenu.h	(revision 2410)
+++ source/Irrlicht/CGUIContextMenu.h	(working copy)
@@ -38,6 +38,9 @@
 		virtual u32 addItem(const wchar_t* text, s32 commandid,
 				bool enabled, bool hasSubMenu, bool checked);
 
+		virtual u32 addItem(const wchar_t* text, s32 commandid,
+				bool enabled, bool hasSubMenu, bool checked, bool cascadeLeft);
+
 		//! Adds a separator item to the menu
 		virtual void addSeparator();
 
@@ -111,6 +114,7 @@
 			s32 PosY;
 			CGUIContextMenu* SubMenu;
 			s32 CommandId;
+			bool ReverseCascade;
 		};
 
 		virtual void recalculateSize();

Posted: Thu Aug 20, 2009 6:24 pm
by bitplane
I consider this a bug, added to the tracker. This patch will need a rewrite before inclusion though, as it should be automatic.

Posted: Thu Aug 20, 2009 7:10 pm
by CuteAlien
Hm, I think I have an automatic patch at least for the sub-menues. I just didn't include it so far because I have another 3 menu patches on my list for which I haven't found time yet.

My patch is rather simple - all I do is before positioning the submenues to check if they fit into the root element rectangle and when they don't I move them to the left.

Code: Select all

diff -r 22573a580586 lib/irrlicht/source/Irrlicht/CGUIContextMenu.cpp
--- a/lib/irrlicht/source/Irrlicht/CGUIContextMenu.cpp	Sat Jul 04 01:26:03 2009 +0200
+++ b/lib/irrlicht/source/Irrlicht/CGUIContextMenu.cpp	Thu Jul 09 09:51:01 2009 +0200
@@ -671,9 +671,21 @@
 			const s32 w = Items[i].SubMenu->getAbsolutePosition().getWidth();
 			const s32 h = Items[i].SubMenu->getAbsolutePosition().getHeight();
 
-			Items[i].SubMenu->setRelativePosition(
-				core::rect<s32>(width-5, Items[i].PosY,
-					width+w-5, Items[i].PosY+h));
+            core::rect<s32> subRect(width-5, Items[i].PosY, width+w-5, Items[i].PosY+h);
+
+            // if it would be drawn beyond the right border, then add it to the left side
+            gui::IGUIElement * root = Environment->getRootGUIElement();
+            if ( root )
+            {
+                core::rect<s32> rectRoot( root->getAbsolutePosition() );
+                if ( getAbsolutePosition().UpperLeftCorner.X+subRect.LowerRightCorner.X > rectRoot.LowerRightCorner.X )
+                {
+                    subRect.UpperLeftCorner.X = -w;
+                    subRect.LowerRightCorner.X = 0;
+                }
+            }
+
+			Items[i].SubMenu->setRelativePosition(subRect);
 		}
 	}
 }
Would that sound ok?