Menu.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. namespace EE{
  4. /******************************************************************************
  5. Menu is always added to the Desktop, and not the parent, this is so that other elements don't occlude menus.
  6. Setting just higher 'GuiObj.baseLevel' would not be enough, because that's only within the parent.
  7. For example, 2 Comboboxes with their own menu added on the desktop:
  8. -Desktop
  9. -ComboBox
  10. -ComboBox.Menu
  11. -ComboBox
  12. -ComboBox.Menu
  13. Menu from first ComboBox would be occluded by second ComboBox.
  14. Instead Menu's are added to the root
  15. -Desktop
  16. -ComboBox
  17. -ComboBox
  18. -ComboBox.Menu
  19. -ComboBox.Menu
  20. baseLevel is still used, to put them always on top of other objects (including windows)
  21. Menu's are sorted in the Desktop children array, by their number of Parents/Owners,
  22. so, a Menu belonging to Window, will be before a Menu belonging to Desktop.
  23. -Desktop
  24. -WindowMenu
  25. -DesktopMenu
  26. This is so that Window Menu's can be processed before Desktop, to check for keyboard shortcuts first.
  27. However current implementation is not perfect, because:
  28. Menu's are sorted in Desktop children array at the moment when they're added to the Desktop.
  29. However at this point, parents of Menu Owners may not be fully set yet.
  30. And when Owners are assigned to other Parents, then the Menu's that belong to them, are not re-sorted.
  31. TODO: solve somehow the problem of processing keyboard shortcuts for leaf Menu's first, because currently Menu's are not always sorted correctly.
  32. /******************************************************************************/
  33. GuiPC::GuiPC(C GuiPC &old, Menu &menu)
  34. {
  35. T=old;
  36. visible &=menu.visible();
  37. enabled &=menu.enabled(); if(menu._owner)enabled&=menu._owner->enabledFull();
  38. client_rect=menu._crect;
  39. clip =client_rect; // set instead of intersection because child Menu's would be clipped fully
  40. offset =client_rect.lu();
  41. }
  42. /******************************************************************************/
  43. // MANAGE
  44. /******************************************************************************/
  45. void MenuElm::zero()
  46. {
  47. on =0;
  48. disabled =0;
  49. _flag =0;
  50. _func_immediate=false;
  51. _func_user =null;
  52. _func =null;
  53. _func2 =null;
  54. _menu =null;
  55. }
  56. MenuElm::MenuElm() {zero();}
  57. MenuElm& MenuElm::del()
  58. {
  59. Delete(_menu);
  60. zero(); return T;
  61. }
  62. MenuElm& MenuElm::create(C Str &name, void (*func)(), Bool immediate)
  63. {
  64. del();
  65. T.name=T.display_name=name;
  66. T.func(func, immediate);
  67. return T;
  68. }
  69. MenuElm& MenuElm::create(C Str &name, void (*func)(Ptr), Ptr user, Bool immediate)
  70. {
  71. del();
  72. T.name=T.display_name=name;
  73. T.func(func, user, immediate);
  74. return T;
  75. }
  76. MenuElm& MenuElm::create(C MenuElm &src, Menu *parent)
  77. {
  78. if(this!=&src)
  79. {
  80. del();
  81. T.name=src.name;
  82. T.display_name=src.display_name;
  83. T.on=src.on;
  84. T.disabled=src.disabled;
  85. if(src._menu)New(T._menu)->create(*src._menu)._parent=parent;
  86. T._flag =src._flag;
  87. T._func_immediate=src._func_immediate;
  88. T._func_user =src._func_user;
  89. T._func =src._func;
  90. T._func2 =src._func2;
  91. T._desc =src._desc;
  92. T._kbsc =src._kbsc;
  93. T._kbsc2 =src._kbsc2;
  94. }
  95. return T;
  96. }
  97. /******************************************************************************/
  98. MenuElm& MenuElm::flag ( Byte flag) {T._flag =flag; return T;}
  99. MenuElm& MenuElm::kbsc (C KbSc &kbsc) {T._kbsc =kbsc; return T;}
  100. MenuElm& MenuElm::kbsc2 (C KbSc &kbsc) {T._kbsc2=kbsc; return T;}
  101. MenuElm& MenuElm::desc (C Str &desc) {T._desc =desc; return T;}
  102. MenuElm& MenuElm::setOn ( Bool on ) {T. on =on ; return T;}
  103. MenuElm& MenuElm::display(C Str &name) {if(name.is())display_name=name; return T;}
  104. MenuElm& MenuElm::func (void (*func)( ), Bool immediate) {T._func=func; T._func2=null; T._func_user=null; T._func_immediate=immediate; return T;} // clear the other function as only one function type should be available at a time
  105. MenuElm& MenuElm::func (void (*func)(Ptr user), Ptr user, Bool immediate) {T._func=null; T._func2=func; T._func_user=user; T._func_immediate=immediate; return T;} // clear the other function as only one function type should be available at a time
  106. void MenuElm::call ()
  107. {
  108. if(_func )if(_func_immediate)_func ( );else Gui.addFuncCall(_func );
  109. if(_func2)if(_func_immediate)_func2(_func_user);else Gui.addFuncCall(_func2, _func_user);
  110. }
  111. /******************************************************************************/
  112. Bool MenuElm::pushable()
  113. {
  114. return (flag()&MENU_TOGGLABLE) || _func || _func2;
  115. }
  116. void MenuElm::push()
  117. {
  118. if(flag()&MENU_TOGGLABLE)on^=1;
  119. call();
  120. }
  121. void Menu::push(C Str &elm)
  122. {
  123. Str path=elm;
  124. for(GuiObj *go=this; go->is(GO_MENU); go=go->parent())if(go->asMenu()._func)
  125. {
  126. GuiObj *menu =this,
  127. *parent=menu->parent();
  128. for(; parent->is(GO_MENU); )
  129. {
  130. Menu &parent_menu=parent->asMenu();
  131. FREP( parent_menu.elms())if(parent_menu.elm(i).menu()==menu)
  132. {
  133. path=parent_menu.elm(i).name+'\\'+path;
  134. goto found;
  135. }
  136. return;
  137. found:;
  138. menu =parent;
  139. parent=parent->parent();
  140. }
  141. go->asMenu()._func(path, go->asMenu()._func_user);
  142. break;
  143. }
  144. }
  145. /******************************************************************************/
  146. void Menu::zero()
  147. {
  148. _crect.zero();
  149. _no_child_draw=false;
  150. _selectable_offset=-1;
  151. _kb =null;
  152. _owner =null;
  153. _func_user =null;
  154. _func =null;
  155. _base_level=GBL_MENU;
  156. }
  157. Menu::Menu() {zero();}
  158. Menu& Menu::del()
  159. {
  160. _skin.clear();
  161. list.del();
  162. _elms.del();
  163. super::del(); zero(); return T;
  164. }
  165. Menu& Menu::create()
  166. {
  167. del();
  168. _type =GO_MENU;
  169. _visible=false; // keep hidden at creation stage
  170. _kb =&list;
  171. list.create(); list._parent=this;
  172. list.cur_mode=LCM_MOUSE;
  173. list.flag |= LIST_ROLLABLE;
  174. list.flag &=~LIST_SEARCHABLE;
  175. setSize();
  176. return T;
  177. }
  178. Menu& Menu::create(C Menu &src)
  179. {
  180. if(this!=&src)
  181. {
  182. if(!src.is())del();else
  183. {
  184. copyParams(src);
  185. _type=GO_MENU;
  186. _kb=_owner=null;
  187. _no_child_draw =src._no_child_draw;
  188. _selectable_offset=src._selectable_offset;
  189. _func_user =src._func_user;
  190. _func =src._func;
  191. _crect =src._crect;
  192. _skin =src._skin;
  193. list.create(src.list)._parent=this;
  194. _elms.setNum(src.elms()); FREPAO(_elms).create(src._elms[i], this);
  195. }
  196. }
  197. return T;
  198. }
  199. /******************************************************************************/
  200. // GET
  201. /******************************************************************************/
  202. GuiObj* Menu::owner()C {return Owner();}
  203. void Menu::operator()(C Str &command, Bool on, SET_MODE mode)
  204. {
  205. CChar *start=_GetStart(command);
  206. if( Is(start))REPA(_elms)
  207. {
  208. MenuElm &elm=_elms[i];
  209. if(Equal(elm.name, start))
  210. {
  211. start=_GetStartNot(command);
  212. if(!Is(start))if(elm.on!=on)
  213. {
  214. elm.on^=true;
  215. if(mode!=QUIET)elm.call();
  216. }
  217. if(Menu *menu=elm.menu())(*menu)(start, on, mode);
  218. break;
  219. }
  220. }
  221. }
  222. Bool Menu::operator()(C Str &command)C
  223. {
  224. CChar *start=_GetStart(command);
  225. if( Is(start))REPA(_elms)
  226. {
  227. C MenuElm &elm=_elms[i];
  228. if(Equal(elm.name, start))
  229. {
  230. start=_GetStartNot(command);
  231. if(!Is(start) )return elm.on;
  232. if(Menu *menu=elm.menu())return (*menu)(start);
  233. return false;
  234. }
  235. }
  236. return false;
  237. }
  238. Bool Menu::exists(C Str &command)C
  239. {
  240. CChar *start=_GetStart(command);
  241. if( Is(start))REPA(_elms)
  242. {
  243. C MenuElm &elm=_elms[i];
  244. if(Equal(elm.name, start))
  245. {
  246. start=_GetStartNot(command);
  247. if(!Is(start) )return true;
  248. if(Menu *menu=elm.menu())return menu->exists(start);
  249. return false;
  250. }
  251. }
  252. return false;
  253. }
  254. /******************************************************************************/
  255. // SET
  256. /******************************************************************************/
  257. static MemberDesc MenuElmFlag(MEMBER(MenuElm, _flag));
  258. Menu& Menu::setData(CChar8 *data[], Int elms, C MemPtr<Bool> &visible, Bool keep_cur)
  259. {
  260. ListColumn columns[]=
  261. {
  262. ListColumn(),
  263. };
  264. columns[0].width =LCW_MAX_DATA_PARENT;
  265. columns[0].md.type=DATA_CHAR8_PTR;
  266. setColumns(columns, Elms(columns), true);
  267. setData<CChar8*>(data, elms, visible, keep_cur); // call not self but the template
  268. return T;
  269. }
  270. Menu& Menu::setData(CChar *data[], Int elms, C MemPtr<Bool> &visible, Bool keep_cur)
  271. {
  272. ListColumn columns[]=
  273. {
  274. ListColumn(),
  275. };
  276. columns[0].width =LCW_MAX_DATA_PARENT;
  277. columns[0].md.type=DATA_CHAR_PTR;
  278. setColumns(columns, Elms(columns), true);
  279. setData<CChar*>(data, elms, visible, keep_cur); // call not self but the template
  280. return T;
  281. }
  282. Menu& Menu::setData(C Node<MenuElm> &node)
  283. {
  284. Bool children =false, togglable=false;
  285. Flt width_name =0,
  286. width_kbsc =0,
  287. width_kbsc2=0;
  288. GuiSkin *skin =list.getSkin();
  289. TextStyle *text_style=(skin ? skin->list.text_style() : null); TextStyleParams ts(text_style, false); ts.size=list.textSizeActual();
  290. #if DEFAULT_FONT_FROM_CUSTOM_SKIN
  291. if(!ts.font() && skin)ts.font(skin->font()); // adjust font in case it's empty and the custom skin has a different font than the 'Gui.skin'
  292. #endif
  293. _elms.clear().setNum(node.children.elms()); Memt<Bool> visible; visible.setNum(_elms.elms());
  294. FREPA(_elms)
  295. {
  296. C Node<MenuElm> &child= node.children[i];
  297. MenuElm &elm =_elms [i];
  298. elm.create(child, this);
  299. elm.disabled|=!elm.name.is();
  300. visible[i] =!FlagTest(elm.flag(), MENU_HIDDEN);
  301. if(child.children.elms() && !elm._menu) // if menu was already created, then ignore the following code
  302. {
  303. Menu &menu=New(elm._menu)->create(); menu._parent=this;
  304. menu.skin ( T.skin(), false);
  305. menu.list.skin(list.skin());
  306. menu.setData(child); // call 'setData' after setting the skin
  307. }
  308. children|=(elm._menu!=null);
  309. if(visible[i])
  310. {
  311. togglable|=FlagTest(elm.flag(), MENU_TOGGLABLE);
  312. MAX(width_name , ts.textWidth(elm.display_name ));
  313. if(elm.kbsc ().is())MAX(width_kbsc , ts.textWidth(elm.kbsc ().asText()));
  314. if(elm.kbsc2().is())MAX(width_kbsc2, ts.textWidth(elm.kbsc2().asText()));
  315. }
  316. }
  317. ListColumn columns[]=
  318. {
  319. ListColumn(MEMBER(MenuElm, on ), 0.05f, "on" ), // 0
  320. ListColumn(MEMBER(MenuElm, display_name), 0.00f, "name" ), // 1
  321. ListColumn(MEMBER(MenuElm, _kbsc ), 0.00f, "kbsc" ), // 2
  322. ListColumn(MEMBER(MenuElm, _kbsc2 ), 0.00f, "kbsc2"), // 3
  323. ListColumn(MEMBER(MenuElm, _menu ), 0.05f, "menu" ), // 4
  324. }; ASSERT(MENU_COLUMN_CHECK==0 && MENU_COLUMN_NAME==1 && MENU_COLUMN_KBSC==2 && MENU_COLUMN_KBSC2==3 && MENU_COLUMN_SUB==4 && MENU_COLUMN_NUM==5);
  325. columns[MENU_COLUMN_CHECK].md.type=DATA_CHECK;
  326. columns[MENU_COLUMN_CHECK].sort=&MenuElmFlag; // use 'sort' to point to '_flag' so we can access 'MENU_TOGGLABLE' in 'DataGuiImage'
  327. columns[MENU_COLUMN_NAME ].width =width_name +ts.size.x*(0.5f+((width_kbsc || width_kbsc2) ? 0.5f : 0));
  328. columns[MENU_COLUMN_KBSC ].width =width_kbsc +ts.size.x* 0.5f;
  329. columns[MENU_COLUMN_KBSC2].width =width_kbsc2+ts.size.x*(0.5f+( width_kbsc ? 0.5f : 0)); // keyboard shortcuts are right-aligned, so we need to add space to kbsc2 (on the right) if kbsc (on the left) exists
  330. columns[MENU_COLUMN_CHECK].visible(togglable );
  331. columns[MENU_COLUMN_KBSC ].visible(width_kbsc !=0);
  332. columns[MENU_COLUMN_KBSC2].visible(width_kbsc2!=0);
  333. columns[MENU_COLUMN_SUB ].visible(children !=0);
  334. disabled(node.disabled);
  335. list.setColumns(columns, Elms(columns), true).setData(_elms, visible).setElmDesc(MEMBER(MenuElm, _desc));
  336. return T;
  337. }
  338. Menu& Menu::setColumns(ListColumn *column, Int columns, Bool columns_hidden)
  339. {
  340. _elms.del();
  341. list.setColumns(column, columns, columns_hidden);
  342. return T;
  343. }
  344. /******************************************************************************/
  345. Menu& Menu::skin(C GuiSkinPtr &skin, Bool sub_objects)
  346. {
  347. T._skin=skin;
  348. if(sub_objects)
  349. {
  350. list.skin(skin);
  351. REPA(_elms)if(Menu *menu=_elms[i].menu())menu->skin(skin, true);
  352. }
  353. return T;
  354. }
  355. /******************************************************************************/
  356. Menu& Menu::setSize(Bool touch)
  357. {
  358. GuiSkin *skin=getSkin();
  359. Flt h=(skin ? skin->menu.list_elm_height : 0.043f);
  360. list.elmHeight(h*(touch ? 2 : 1)).textSize(h, 0);
  361. REPA(_elms)if(Menu *menu=_elms[i].menu())menu->setSize(touch);
  362. return T;
  363. }
  364. /******************************************************************************/
  365. Menu& Menu::rect(C Rect &rect)
  366. {
  367. if(T.rect()!=rect)
  368. {
  369. _crect=rect;
  370. if(GuiSkin *skin=getSkin())
  371. {
  372. if(C PanelPtr &panel=skin->menu.normal)
  373. {
  374. Rect padd; panel->innerPadding(T.rect(), padd);
  375. _crect.min+=padd.min;
  376. _crect.max-=padd.max;
  377. }
  378. _crect.min+=skin->menu.padding;
  379. _crect.max-=skin->menu.padding;
  380. if(!_crect.validX())_crect.setX(_crect.centerX());
  381. if(!_crect.validY())_crect.setY(_crect.centerY());
  382. }
  383. super::rect(rect); // call this last
  384. }
  385. return T;
  386. }
  387. Menu& Menu::move(C Vec2 &delta)
  388. {
  389. //if(delta.any()) looks fast so skip this check
  390. {
  391. super::move(delta);
  392. _crect+=delta;
  393. }
  394. return T;
  395. }
  396. Menu& Menu::moveClamp(C Vec2 &delta)
  397. {
  398. Vec2 d=delta;
  399. if(rect().max.x+d.x> D.w())d.x= D.w()-rect().max.x;
  400. if(rect().min.y+d.y<-D.h())d.y=-D.h()-rect().min.y;
  401. if(rect().min.x+d.x<-D.w())d.x=-D.w()-rect().min.x;
  402. if(rect().max.y+d.y> D.h())d.y= D.h()-rect().max.y;
  403. return move(d);
  404. }
  405. Menu& Menu::pos (C Vec2 &pos) {return moveClamp(pos-T.pos ());}
  406. Menu& Menu::posRU(C Vec2 &pos) {return moveClamp(pos-T.posRU());}
  407. Menu& Menu::posLD(C Vec2 &pos) {return moveClamp(pos-T.posLD());}
  408. Menu& Menu::posL (C Vec2 &pos) {return moveClamp(pos-T.posL ());}
  409. Menu& Menu::posR (C Vec2 &pos) {return moveClamp(pos-T.posR ());}
  410. Menu& Menu::posD (C Vec2 &pos) {return moveClamp(pos-T.posD ());}
  411. Menu& Menu::posU (C Vec2 &pos) {return moveClamp(pos-T.posU ());}
  412. Menu& Menu::posC (C Vec2 &pos) {return moveClamp(pos-T.posC ());}
  413. Menu& Menu::posAround(C Rect &rect, Flt align)
  414. {
  415. Vec2 size=T.size(),
  416. pos =rect.ld(); pos.x+=Lerp(rect.w()-size.x+paddingR(), -paddingL(), LerpR(-1.0f, 1.0f, align));
  417. Rect screen_rect(0, -D.h(), 0, D.h()); // only Y are needed
  418. Flt h_below=(Rect_LU(0, pos.y , 0, size.y)&screen_rect).h(), // visible height when below the 'rect'
  419. h_above=(Rect_LD(0, pos.y+rect.h(), 0, size.y)&screen_rect).h(); // visible height when above the 'rect'
  420. return T.pos((h_above>h_below+EPS) ? pos+Vec2(0, rect.h()+size.y) : pos); // select position according to which visible height is bigger, use EPS to more often place the Menu below
  421. }
  422. void Menu::clientSize(C Vec2 &size)
  423. {
  424. Vec2 s=size;
  425. if(GuiSkin *skin=getSkin())
  426. {
  427. if(Panel *panel=skin->menu.normal())
  428. {
  429. Rect padd; panel->innerPadding(T.rect(), padd);
  430. s+=padd.min;
  431. s+=padd.max;
  432. }
  433. s+=skin->menu.padding*2;
  434. }
  435. T.size(s);
  436. }
  437. Menu& Menu::show()
  438. {
  439. if(hidden())
  440. {
  441. list.cur=-1;
  442. super::show();
  443. }
  444. return T;
  445. }
  446. /******************************************************************************/
  447. Menu& Menu::clearElmSelectable( ) {_selectable_offset=-1 ; return T;}
  448. Menu& Menu:: setElmSelectable(Bool &selectable) {_selectable_offset=UInt(UIntPtr(&selectable)); return T;}
  449. /******************************************************************************/
  450. Menu& Menu::func(void (*func)(C Str &path, Ptr user), Ptr user)
  451. {
  452. T._func =func;
  453. T._func_user=user;
  454. return T;
  455. }
  456. /******************************************************************************/
  457. ListColumn* Menu::listColumn()
  458. {
  459. if(elms() && InRange(1, list.columns()))return &list.column(1);
  460. if( InRange(0, list.columns()))return &list.column(0);
  461. return null;
  462. }
  463. /******************************************************************************/
  464. // MAIN
  465. /******************************************************************************/
  466. GuiObj* Menu::test(C GuiPC &gpc, C Vec2 &pos, GuiObj* &mouse_wheel)
  467. {
  468. if(visible() && gpc.visible)
  469. {
  470. GuiPC gpc2(gpc, T);
  471. FREPA(_elms)if(Menu *menu=_elms[i].menu())if(GuiObj *go=menu->test(gpc2, pos, mouse_wheel))return go;
  472. if(Cuts(pos, rect()))
  473. {
  474. mouse_wheel=this;
  475. if(GuiObj *go=list.test(gpc2, pos, mouse_wheel))return go;
  476. return this;
  477. }
  478. }
  479. return null;
  480. }
  481. /******************************************************************************/
  482. void Menu::hideAll() {if(GuiObj *root=last(GO_MENU))root->deactivate();}
  483. /******************************************************************************/
  484. void Menu::checkKeyboardShortcuts()
  485. {
  486. if(enabled())REPA(_elms)
  487. {
  488. MenuElm &e=_elms[i]; if(!e.disabled)
  489. {
  490. if(e._kbsc.pd())
  491. {
  492. e._kbsc.eat();
  493. e.push();
  494. push(e.name);
  495. }else
  496. if(e._kbsc2.pd())
  497. {
  498. e._kbsc2.eat();
  499. e.push();
  500. push(e.name);
  501. }
  502. if(Menu *menu=e.menu())menu->checkKeyboardShortcuts();
  503. }
  504. }
  505. }
  506. static inline Flt ScrollSpeed(Menu &menu) {return menu.list.elmHeight()*14*Time.ad();} // speed of 14 elements per second
  507. void Menu::update(C GuiPC &gpc)
  508. {
  509. GuiPC gpc2(gpc, T);
  510. if( gpc2.enabled)
  511. {
  512. // mouse wheel
  513. if(Ms.wheel() && Gui.wheel()==this)
  514. {
  515. Vec2 d(0, -list._height_ez*Ms.wheel());
  516. if(Ms.test(_crect+d))
  517. {
  518. move(d);
  519. gpc2=GuiPC(gpc, T); // reset GuiPC because move can affect position
  520. }
  521. }
  522. // list
  523. list.update(gpc2);
  524. if(gpc2.visible)
  525. {
  526. if(_elms.elms())
  527. {
  528. if(Ms._action)_no_child_draw=false;
  529. if(Gui.menu()==this)
  530. {
  531. if(Kb.k.any())_no_child_draw=true;
  532. }else
  533. {
  534. if(Ms._action && list.contains(Gui.ms())){ activate(); }else // activate self on mouse action
  535. if(Kb.k(KB_LEFT) && Gui.menu()==_kb ){Int c=list.cur; activate(); list.cur=c; Kb.eatKey(); _no_child_draw=true;} // activate self on keyboard action
  536. }
  537. }
  538. // handle input
  539. if(Gui.menu()==this)
  540. {
  541. // scroll (priority: 1 - touch hold, 2 - mouse hover with previous mouse action, 3 - current element)
  542. C Vec2 *pos=null;
  543. REPA(Touches)if(Touches[i].on() && Gui.menu()->contains(Touches[i].guiObj()) ){pos=&Touches[i].pos(); break;}
  544. if(!pos && Gui.menu()->contains(Gui .ms ()) && !list._kb_action){pos=&Ms .pos(); }
  545. if(pos) // use detection basing on the 'pos' screen position
  546. {
  547. Flt margin=Max(0.0f, D.h()-paddingT()-list.elmHeight()*1.0f); // use max 0 to don't go to lower half of the screen
  548. if(pos->y>=margin) // top of the screen
  549. {
  550. Flt d=D.h()-rect().max.y; if(d<0)T.move(Vec2(0, Max(d, -ScrollSpeed(T))));
  551. }else
  552. if(pos->y<=-margin) // bottom of the screen
  553. {
  554. Flt d=-D.h()-rect().min.y; if(d>0)T.move(Vec2(0, Min(d, ScrollSpeed(T))));
  555. }
  556. }else // use detection basing on the 'list.cur'
  557. if(InRange(list.cur, list.visibleElms()))
  558. {
  559. Flt y=list.visToLocalY(list.cur)+gpc2.offset.y,
  560. d=D.h()-(y+paddingT());
  561. if( d<0) // top of the screen (check this before bottom of the screen)
  562. {
  563. T.move(Vec2(0, Max(d, -ScrollSpeed(T))));
  564. }else
  565. {
  566. d=-D.h()-(y-list.elmHeight()-paddingB());
  567. if(d>0) // bottom of the screen
  568. {
  569. T.move(Vec2(0, Min(d, ScrollSpeed(T))));
  570. }
  571. }
  572. }
  573. // input
  574. Bool enter_only=Kb.k(KB_RIGHT), by_touch=false;
  575. C Vec2 *rs_pos =null; if(Ms.br(0) && Gui.menu()->contains(Gui.ms()))rs_pos=&Ms.pos(); REPA(Touches)if(Touches[i].rs() && Gui.menu()->contains(Touches[i].guiObj())){rs_pos=&Touches[i].pos(); by_touch=true;} // release pos
  576. if(rs_pos || enter_only || ((Kb.k(KB_ENTER) || Kb.k(KB_NPENTER)) && Kb.k.first()))
  577. {
  578. Bool entered=false;
  579. if(InRange(list.cur, list.visibleElms()))
  580. {
  581. if(_elms.elms())
  582. {
  583. if(MenuElm *e=(MenuElm*)list.visToData(list.cur))if(!e->disabled)
  584. {
  585. Menu *menu=e->menu();
  586. if((e->flag()&MENU_NOT_SELECTABLE) // can't be selected (so enter only)
  587. || enter_only // or we actually want to enter only
  588. || (menu && !e->pushable()) // has child menu and can't be pushed
  589. || (menu && by_touch ) // has child menu and we're entering by touch
  590. )
  591. {
  592. if(menu) // has child menu
  593. {
  594. if(0 && D.smallSize())menu->posC(0);
  595. else menu->pos(Vec2(rs_pos ? by_touch ? Avg(rs_pos->x-menu->rect().w()*0.5f, menu->rect().min.x) // move half way if by touch, to avoid finger covering menu
  596. : rs_pos->x-menu->rect().w()*0.5f
  597. : _crect.max.x,
  598. rect().max.y+list.visToLocalY(list.cur))); // move the sub menu under the mouse cursor
  599. menu->activate();
  600. menu->list.cur=((menu->list.elms() && !by_touch) ? 0 : -1);
  601. if(enter_only)menu->_no_child_draw=true ; // when moving right using keyboard, don't display children of selected element
  602. _no_child_draw=false;
  603. entered=true;
  604. }
  605. }else
  606. {
  607. e->push();
  608. push(e->name);
  609. if(!(e->flag()&(MENU_TOGGLABLE|MENU_NO_CLOSE)) && Gui.menu()==this)hideAll();
  610. }
  611. }
  612. }else
  613. {
  614. if(Ptr data=list.visToData(list.cur))
  615. {
  616. if(_selectable_offset>=0 && !*(Bool*)((Byte*)data+_selectable_offset))goto skip;
  617. if(ListColumn *lc=listColumn())push(lc->md.asText(data, lc->precision));
  618. }
  619. hideAll();
  620. skip:;
  621. }
  622. }
  623. if(enter_only && !entered)if(GuiObj *go=first(GO_MENU_BAR)) // activate next MenuBar element
  624. {
  625. MenuBar &g=go->asMenuBar();
  626. FREPA(g)if(!g.elm((g._lit+1+i)%g.elms()).menu.disabled()){g._push=(g._lit+1+i)%g.elms(); break;}
  627. }
  628. Ms.eat(0); Kb.eatKey();
  629. }else
  630. if(Kb.k(KB_LEFT))
  631. {
  632. if(GuiObj *go=first(GO_MENU_BAR)) // activate previous 'MenuBar' element
  633. {
  634. MenuBar &g=go->asMenuBar();
  635. REPA(g)if(!g.elm((g._lit+i)%g.elms()).menu.disabled()){g._push=(g._lit+i)%g.elms(); Kb.eatKey(); break;}
  636. }
  637. }else
  638. if(contains(Gui.ms()) && Ms.bp(2) || Kb.kf(KB_ESC)) // close all menu's (check mouse focus too in case we've just activated the Menu in this frame with the use of Ms.bp(2) in such case the Menu would get immediately closed without showing up at all)
  639. {
  640. hideAll();
  641. Ms.eat(2); Kb.eatKey();
  642. }else
  643. if(contains(Gui.ms()) && Ms.bp(1) || Kb.kf(KB_NAV_BACK)) // activate parent menu (check mouse focus too in case we've just activated the Menu in this frame with the use of Ms.bp(1) in such case the Menu would get immediately closed without showing up at all)
  644. {
  645. if(parent())
  646. {
  647. parent()->activate();
  648. if(parent()->type()==GO_MENU)
  649. {
  650. Menu &menu=parent()->asMenu();
  651. menu.list.lit=menu.list.cur=-1; // immediatelly clear lit/cur to avoid 1 frame delay
  652. }
  653. }
  654. Ms.eat(1); Kb.eatKey();
  655. }
  656. }
  657. // set child visibility
  658. REPA(_elms)
  659. {
  660. MenuElm &elm=_elms[i];
  661. if(Menu *menu=elm.menu())if(!elm.disabled)
  662. {
  663. if(Gui.menu()==this && Ms._action && menu->visible() && menu->contains(Gui.ms()))menu->activate(); // activate child on mouse action
  664. if(menu->contains(Gui.menu()))list.cur=list.absToVis(i);
  665. Bool visible=(list.visToAbs(list.cur)==i && !_no_child_draw);
  666. if(menu->visible()!=visible)
  667. {
  668. if(visible)
  669. {
  670. Flt w=menu->rect().w(), r=D.w()-_crect.max.x+EPS, l=_crect.min.x-(-D.w()); // visible space on the right/left
  671. Vec2 pos((r>=w || r>=l) // put on right side if there's enough space to fit all, or right side has more visible space
  672. ? _crect.max.x : _crect.min.x-w, rect().max.y+list.visToLocalY(list.absToVis(i)));
  673. menu->pos (pos); // first use 'pos' to use clamping
  674. pos.x-=menu->rect().min.x; pos.y=0;
  675. menu->move(pos); // then use 'move' to skip clamping to force x
  676. }
  677. menu->visible(visible);
  678. }
  679. }
  680. }
  681. }
  682. // check shortcuts
  683. if(Kb.k.any()) // shortcuts are detected based on char or key press, so check them only if there's something pressed
  684. if(!parent()->is(GO_MENU)) // don't check Menu's that are children of other Menu's, check only root Menu's, and if it's accessible, then it will check its children
  685. if(GuiObj *owner=Owner())
  686. {
  687. GuiObj *owner_window=owner->first(GO_WINDOW); // get owner Window (first Window that contains the owner)
  688. if(!owner_window // if there's no Window (owner is assigned to Desktop)
  689. || owner_window->contains(Gui.window())) // if owner Window contains focused Window (focused Window is a child of owner Window)
  690. checkKeyboardShortcuts();
  691. }
  692. // children
  693. REPA(_elms)if(Menu *menu=_elms[i].menu())menu->update(gpc2);
  694. }
  695. }
  696. /******************************************************************************/
  697. void Menu::draw(C GuiPC &gpc)
  698. {
  699. if(visible() && gpc.visible)
  700. {
  701. if(GuiSkin *skin=getSkin())
  702. {
  703. D.clip();
  704. if(skin->menu.normal )skin->menu.normal->draw(skin->menu.normal_color, rect());else
  705. if(skin->menu.normal_color.a) rect().draw(skin->menu.normal_color);
  706. }
  707. GuiPC gpc2(gpc, T); list.draw(gpc2); REPA(_elms)if(Menu *menu=_elms[i].menu())menu->draw(gpc2);
  708. }
  709. }
  710. /******************************************************************************/
  711. }
  712. /******************************************************************************/