1 // Written in the D programming language. 2 3 /** 4 DLANGUI library. 5 6 This module contains menu widgets implementation. 7 8 9 10 Synopsis: 11 12 ---- 13 import dlangui.widgets.popup; 14 15 ---- 16 17 Copyright: Vadim Lopatin, 2014 18 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 19 Authors: $(WEB coolreader.org, Vadim Lopatin) 20 */ 21 module dlangui.widgets.menu; 22 23 import dlangui.core.events; 24 import dlangui.widgets.controls; 25 import dlangui.widgets.layouts; 26 import dlangui.widgets.lists; 27 import dlangui.widgets.popup; 28 29 /// menu item properties 30 class MenuItem { 31 protected bool _checkable; 32 protected bool _checked; 33 protected bool _enabled; 34 protected Action _action; 35 protected MenuItem[] _subitems; 36 /// item action id, 0 if no action 37 @property int id() { return _action is null ? 0 : _action.id; } 38 /// returns count of submenu items 39 @property int subitemCount() { 40 return cast(int)_subitems.length; 41 } 42 /// returns submenu item by index 43 MenuItem subitem(int index) { 44 return _subitems[index]; 45 } 46 /// adds submenu item 47 MenuItem add(MenuItem subitem) { 48 _subitems ~= subitem; 49 return this; 50 } 51 /// adds submenu item from action 52 MenuItem add(Action subitemAction) { 53 _subitems ~= new MenuItem(subitemAction); 54 return this; 55 } 56 /// returns true if item is submenu (contains subitems) 57 @property bool isSubmenu() { 58 return _subitems.length > 0; 59 } 60 /// returns item label 61 @property UIString label() { 62 return _action.labelValue; 63 } 64 /// returns item action 65 @property const(Action) action() const { return _action; } 66 /// sets item action 67 @property MenuItem action(Action a) { _action = a; return this; } 68 this() { 69 _enabled = true; 70 } 71 this(Action action) { 72 _action = action; 73 _enabled = true; 74 } 75 ~this() { 76 // TODO 77 } 78 } 79 80 /// widget to draw menu item 81 class MenuItemWidget : HorizontalLayout { 82 protected MenuItem _item; 83 protected TextWidget _label; 84 @property MenuItem item() { return _item; } 85 this(MenuItem item) { 86 id="menuitem"; 87 _item = item; 88 styleId = "MENU_ITEM"; 89 _label = new TextWidget("MENU_LABEL"); 90 _label.text = _item.label; 91 addChild(_label); 92 trackHover = true; 93 clickable = true; 94 } 95 } 96 97 /// base class for menus 98 class MenuWidgetBase : ListWidget { 99 protected MenuWidgetBase _parentMenu; 100 protected MenuItem _item; 101 protected PopupMenu _openedMenu; 102 protected PopupWidget _openedPopup; 103 protected bool delegate(MenuItem item) _onMenuItemClickListener; 104 /// menu item click listener 105 @property bool delegate(MenuItem item) onMenuItemListener() { return _onMenuItemClickListener; } 106 /// menu item click listener 107 @property MenuWidgetBase onMenuItemListener(bool delegate(MenuItem item) listener) { _onMenuItemClickListener = listener; return this; } 108 109 this(MenuWidgetBase parentMenu, MenuItem item, Orientation orientation) { 110 _parentMenu = parentMenu; 111 _item = item; 112 this.orientation = orientation; 113 id = "popup_menu"; 114 styleId = "POPUP_MENU"; 115 WidgetListAdapter adapter = new WidgetListAdapter(); 116 for (int i=0; i < _item.subitemCount; i++) { 117 MenuItem subitem = _item.subitem(i); 118 MenuItemWidget widget = new MenuItemWidget(subitem); 119 if (orientation == Orientation.Horizontal) 120 widget.styleId = "MAIN_MENU_ITEM"; 121 adapter.widgets.add(widget); 122 } 123 ownAdapter = adapter; 124 } 125 126 protected void onPopupClosed(PopupWidget p) { 127 _openedPopup = null; 128 _openedMenu = null; 129 selectItem(-1); 130 setHoverItem(-1); 131 window.setFocus(this); 132 } 133 134 protected void openSubmenu(MenuItemWidget itemWidget, bool selectFirstItem) { 135 if (_openedPopup !is null) { 136 _openedPopup.close(); 137 } 138 PopupMenu popupMenu = new PopupMenu(itemWidget.item, this); 139 PopupWidget popup = window.showPopup(popupMenu, itemWidget, orientation == Orientation.Horizontal ? PopupAlign.Below : PopupAlign.Right); 140 popup.onPopupCloseListener = &onPopupClosed; 141 popup.flags = PopupFlags.CloseOnClickOutside; 142 _openedPopup = popup; 143 _openedMenu = popupMenu; 144 window.setFocus(popupMenu); 145 if (selectFirstItem) 146 _openedMenu.selectItem(0); 147 } 148 149 /// override to handle change of selection 150 override protected void selectionChanged(int index, int previouslySelectedItem = -1) { 151 MenuItemWidget itemWidget = index >= 0 ? cast(MenuItemWidget)_adapter.itemWidget(index) : null; 152 MenuItemWidget prevWidget = previouslySelectedItem >= 0 ? cast(MenuItemWidget)_adapter.itemWidget(previouslySelectedItem) : null; 153 if (prevWidget !is null) { 154 if (_openedPopup !is null) 155 _openedPopup.close(); 156 } 157 if (itemWidget !is null) { 158 if (itemWidget.item.isSubmenu()) { 159 if (_selectOnHover) { 160 openSubmenu(itemWidget, false); 161 } 162 } else { 163 // normal item 164 } 165 } 166 } 167 168 protected void onMenuItem(MenuItem item) { 169 if (_openedPopup !is null) { 170 _openedPopup.close(); 171 _openedPopup = null; 172 } 173 if (_parentMenu !is null) 174 _parentMenu.onMenuItem(item); 175 else { 176 // top level handling 177 Log.d("onMenuItem ", item.id); 178 selectItem(-1); 179 setHoverItem(-1); 180 selectOnHover = false; 181 bool delegate(MenuItem item) listener = _onMenuItemClickListener; 182 PopupWidget popup = cast(PopupWidget)parent; 183 if (popup) 184 popup.close(); 185 // this pointer now can be invalid - if popup removed 186 if (listener !is null) 187 listener(item); 188 } 189 } 190 191 @property MenuItemWidget selectedMenuItemWidget() { 192 return _selectedItemIndex >= 0 ? cast(MenuItemWidget)_adapter.itemWidget(_selectedItemIndex) : null; 193 } 194 195 /// override to handle mouse up on item 196 override protected void itemClicked(int index) { 197 MenuItemWidget itemWidget = index >= 0 ? cast(MenuItemWidget)_adapter.itemWidget(index) : null; 198 if (itemWidget !is null) { 199 Log.d("Menu Item clicked ", itemWidget.item.action.id); 200 if (itemWidget.item.isSubmenu()) { 201 // submenu clicked 202 if (_clickOnButtonDown && _openedPopup !is null && _openedMenu._item is itemWidget.item) { 203 // second click on main menu opened item 204 _openedPopup.close(); 205 _openedPopup = null; 206 selectItem(-1); 207 selectOnHover = false; 208 } else { 209 openSubmenu(itemWidget, false); 210 selectOnHover = true; 211 } 212 } else { 213 // normal item 214 onMenuItem(itemWidget.item); 215 } 216 } 217 } 218 219 /// returns popup this menu is located in 220 @property PopupWidget thisPopup() { 221 return cast(PopupWidget)parent; 222 } 223 224 /// list navigation using keys 225 override bool onKeyEvent(KeyEvent event) { 226 // TODO: 227 if (orientation == Orientation.Horizontal) { 228 if (event.action == KeyAction.KeyDown) { 229 } 230 } else { 231 if (event.action == KeyAction.KeyDown) { 232 if (event.keyCode == KeyCode.LEFT) { 233 if (_parentMenu !is null) { 234 if (_parentMenu.orientation == Orientation.Vertical) { 235 if (thisPopup !is null) { 236 // back to parent menu on Left key 237 thisPopup.close(); 238 return true; 239 } 240 } else { 241 // parent is main menu 242 _parentMenu.moveSelection(-1); 243 return true; 244 } 245 } 246 return true; 247 } else if (event.keyCode == KeyCode.RIGHT) { 248 MenuItemWidget thisItem = selectedMenuItemWidget(); 249 if (thisItem !is null && thisItem.item.isSubmenu) { 250 openSubmenu(thisItem, true); 251 return true; 252 } else if (_parentMenu !is null && _parentMenu.orientation == Orientation.Horizontal) { 253 _parentMenu.moveSelection(1); 254 return true; 255 } 256 return true; 257 } 258 } else if (event.action == KeyAction.KeyUp) { 259 if (event.keyCode == KeyCode.LEFT || event.keyCode == KeyCode.RIGHT) { 260 return true; 261 } 262 } 263 } 264 return super.onKeyEvent(event); 265 } 266 267 } 268 269 /// main menu (horizontal) 270 class MainMenu : MenuWidgetBase { 271 272 this(MenuItem item) { 273 super(null, item, Orientation.Horizontal); 274 id = "MAIN_MENU"; 275 styleId = "MAIN_MENU"; 276 _clickOnButtonDown = true; 277 } 278 } 279 280 /// popup menu widget (vertical layout of items) 281 class PopupMenu : MenuWidgetBase { 282 283 this(MenuItem item, MenuWidgetBase parentMenu = null) { 284 super(parentMenu, item, Orientation.Vertical); 285 id = "POPUP_MENU"; 286 styleId = "POPUP_MENU"; 287 selectOnHover = true; 288 } 289 }