| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) RedWolf Design |
| 5 | * Copyright (c) 2001, Sven2 |
| 6 | * Copyright (c) 2017-2020, The LegacyClonk Team and contributors |
| 7 | * |
| 8 | * Distributed under the terms of the ISC license; see accompanying file |
| 9 | * "COPYING" for details. |
| 10 | * |
| 11 | * "Clonk" is a registered trademark of Matthes Bender, used with permission. |
| 12 | * See accompanying file "TRADEMARK" for details. |
| 13 | * |
| 14 | * To redistribute this file separately, substitute the full license texts |
| 15 | * for the above references. |
| 16 | */ |
| 17 | |
| 18 | // generic user interface |
| 19 | // context menu |
| 20 | |
| 21 | #include "C4GuiResource.h" |
| 22 | #include <C4Include.h> |
| 23 | #include <C4Gui.h> |
| 24 | #include <C4FacetEx.h> |
| 25 | #include <C4Wrappers.h> |
| 26 | #include <C4MouseControl.h> |
| 27 | |
| 28 | #include <StdWindow.h> |
| 29 | |
| 30 | namespace C4GUI |
| 31 | { |
| 32 | |
| 33 | int32_t ContextMenu:: = 0; |
| 34 | |
| 35 | // ContextMenu::Entry |
| 36 | |
| 37 | void ContextMenu::Entry::(C4FacetEx &cgo) |
| 38 | { |
| 39 | // icon |
| 40 | if (icoIcon > Ico_None) |
| 41 | { |
| 42 | // get icon counts |
| 43 | int32_t iXMax, iYMax; |
| 44 | GetRes()->fctIcons.GetPhaseNum(rX&: iXMax, rY&: iYMax); |
| 45 | if (!iXMax) |
| 46 | iXMax = 6; |
| 47 | // load icon |
| 48 | const C4Facet &rfctIcon = GetRes()->fctIcons.GetPhase(iPhaseX: icoIcon % iXMax, iPhaseY: icoIcon / iXMax); |
| 49 | rfctIcon.DrawX(sfcTarget: cgo.Surface, iX: rcBounds.x + cgo.TargetX, iY: rcBounds.y + cgo.TargetY, iWdt: rcBounds.Hgt, iHgt: rcBounds.Hgt); |
| 50 | } |
| 51 | // print out label |
| 52 | if (!!sText) |
| 53 | lpDDraw->TextOut(szText: sText.getData(), rFont&: GetRes()->TextFont, fZoom: 1.0f, sfcDest: cgo.Surface, iTx: cgo.TargetX + rcBounds.x + GetIconIndent(), iTy: rcBounds.y + cgo.TargetY, C4GUI_ContextFontClr, byForm: ALeft); |
| 54 | // submenu arrow |
| 55 | if (pSubmenuHandler) |
| 56 | { |
| 57 | C4Facet &rSubFct = GetRes()->fctSubmenu; |
| 58 | rSubFct.Draw(sfcTarget: cgo.Surface, iX: cgo.TargetX + rcBounds.x + rcBounds.Wdt - rSubFct.Wdt, iY: cgo.TargetY + rcBounds.y + (rcBounds.Hgt - rSubFct.Hgt) / 2); |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | ContextMenu::Entry::Entry(const char *szText, Icons icoIcon, MenuHandler *pMenuHandler, ContextHandler *pSubmenuHandler) |
| 63 | : Element(), cHotkey(0), icoIcon(icoIcon), pMenuHandler(pMenuHandler), pSubmenuHandler(pSubmenuHandler) |
| 64 | { |
| 65 | // set text with hotkey |
| 66 | if (szText) |
| 67 | { |
| 68 | sText.Copy(pnData: szText); |
| 69 | ExpandHotkeyMarkup(sText, rcHotkey&: cHotkey); |
| 70 | // adjust size |
| 71 | GetRes()->TextFont.GetTextExtent(szText: sText.getData(), rsx&: rcBounds.Wdt, rsy&: rcBounds.Hgt, fCheckMarkup: true); |
| 72 | } |
| 73 | else |
| 74 | { |
| 75 | rcBounds.Wdt = 40; |
| 76 | rcBounds.Hgt = GetRes()->TextFont.GetLineHeight(); |
| 77 | } |
| 78 | // regard icon |
| 79 | rcBounds.Wdt += GetIconIndent(); |
| 80 | // submenu arrow |
| 81 | if (pSubmenuHandler) rcBounds.Wdt += GetRes()->fctSubmenu.Wdt + 2; |
| 82 | } |
| 83 | |
| 84 | // ContextMenu |
| 85 | |
| 86 | ContextMenu::() : Window(), pTarget(nullptr), pSelectedItem(nullptr), pSubmenu(nullptr) |
| 87 | { |
| 88 | iMenuIndex = ++iGlobalMenuIndex; |
| 89 | // set min size |
| 90 | rcBounds.Wdt = 40; rcBounds.Hgt = 7; |
| 91 | // key bindings |
| 92 | C4CustomKey::CodeList Keys; |
| 93 | Keys.push_back(x: C4KeyCodeEx(K_UP)); |
| 94 | if (Config.Controls.GamepadGuiControl) |
| 95 | { |
| 96 | Keys.push_back(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: 0, idButton: KEY_JOY_Up))); |
| 97 | } |
| 98 | pKeySelUp = new C4KeyBinding(Keys, "GUIContextSelUp" , KEYSCOPE_Gui, |
| 99 | new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeySelUp), C4CustomKey::PRIO_Context); |
| 100 | |
| 101 | Keys.clear(); |
| 102 | Keys.push_back(x: C4KeyCodeEx(K_DOWN)); |
| 103 | if (Config.Controls.GamepadGuiControl) |
| 104 | { |
| 105 | Keys.push_back(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: 0, idButton: KEY_JOY_Down))); |
| 106 | } |
| 107 | pKeySelDown = new C4KeyBinding(Keys, "GUIContextSelDown" , KEYSCOPE_Gui, |
| 108 | new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeySelDown), C4CustomKey::PRIO_Context); |
| 109 | |
| 110 | Keys.clear(); |
| 111 | Keys.push_back(x: C4KeyCodeEx(K_RIGHT)); |
| 112 | if (Config.Controls.GamepadGuiControl) |
| 113 | { |
| 114 | Keys.push_back(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: 0, idButton: KEY_JOY_Right))); |
| 115 | } |
| 116 | pKeySubmenu = new C4KeyBinding(Keys, "GUIContextSubmenu" , KEYSCOPE_Gui, |
| 117 | new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeySubmenu), C4CustomKey::PRIO_Context); |
| 118 | |
| 119 | Keys.clear(); |
| 120 | Keys.push_back(x: C4KeyCodeEx(K_LEFT)); |
| 121 | if (Config.Controls.GamepadGuiControl) |
| 122 | { |
| 123 | Keys.push_back(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: 0, idButton: KEY_JOY_Left))); |
| 124 | } |
| 125 | pKeyBack = new C4KeyBinding(Keys, "GUIContextBack" , KEYSCOPE_Gui, |
| 126 | new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeyBack), C4CustomKey::PRIO_Context); |
| 127 | |
| 128 | Keys.clear(); |
| 129 | Keys.push_back(x: C4KeyCodeEx(K_ESCAPE)); |
| 130 | if (Config.Controls.GamepadGuiControl) |
| 131 | { |
| 132 | Keys.push_back(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: 0, idButton: KEY_JOY_AnyHighButton))); |
| 133 | } |
| 134 | pKeyAbort = new C4KeyBinding(Keys, "GUIContextAbort" , KEYSCOPE_Gui, |
| 135 | new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeyAbort), C4CustomKey::PRIO_Context); |
| 136 | |
| 137 | Keys.clear(); |
| 138 | Keys.push_back(x: C4KeyCodeEx(K_RETURN)); |
| 139 | if (Config.Controls.GamepadGuiControl) |
| 140 | { |
| 141 | Keys.push_back(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: 0, idButton: KEY_JOY_AnyLowButton))); |
| 142 | } |
| 143 | pKeyConfirm = new C4KeyBinding(Keys, "GUIContextConfirm" , KEYSCOPE_Gui, |
| 144 | new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeyConfirm), C4CustomKey::PRIO_Context); |
| 145 | |
| 146 | pKeyHotkey = new C4KeyBinding(C4KeyCodeEx(KEY_Any), "GUIContextHotkey" , KEYSCOPE_Gui, |
| 147 | new C4KeyCBPassKey<ContextMenu>(*this, &ContextMenu::KeyHotkey), C4CustomKey::PRIO_Context); |
| 148 | } |
| 149 | |
| 150 | ContextMenu::() |
| 151 | { |
| 152 | // del any submenu |
| 153 | delete pSubmenu; pSubmenu = nullptr; |
| 154 | // forward RemoveElement to screen |
| 155 | Screen *pScreen = GetScreen(); |
| 156 | if (pScreen) pScreen->RemoveElement(pChild: this); |
| 157 | // clear key bindings |
| 158 | delete pKeySelUp; |
| 159 | delete pKeySelDown; |
| 160 | delete pKeySubmenu; |
| 161 | delete pKeyBack; |
| 162 | delete pKeyAbort; |
| 163 | delete pKeyConfirm; |
| 164 | delete pKeyHotkey; |
| 165 | // clear children to get appropriate callbacks |
| 166 | Clear(); |
| 167 | } |
| 168 | |
| 169 | void ContextMenu::(bool fByUser) |
| 170 | { |
| 171 | // effect |
| 172 | if (fByUser) GUISound(szSound: "DoorClose" ); |
| 173 | // simply del menu: dtor will remove itself |
| 174 | delete this; |
| 175 | } |
| 176 | |
| 177 | void ContextMenu::(C4FacetEx &cgo) |
| 178 | { |
| 179 | // draw context menu bg |
| 180 | lpDDraw->DrawBoxDw(sfcDest: cgo.Surface, iX1: rcBounds.x + cgo.TargetX, iY1: rcBounds.y + cgo.TargetY, |
| 181 | iX2: rcBounds.x + rcBounds.Wdt + cgo.TargetX - 1, iY2: rcBounds.y + rcBounds.Hgt + cgo.TargetY - 1, |
| 182 | C4GUI_ContextBGColor); |
| 183 | // context bg: mark selected item |
| 184 | if (pSelectedItem) |
| 185 | { |
| 186 | // get marked item bounds |
| 187 | C4Rect rcSelArea = pSelectedItem->GetBounds(); |
| 188 | // do indent |
| 189 | rcSelArea.x += GetClientRect().x; |
| 190 | rcSelArea.y += GetClientRect().y; |
| 191 | // draw |
| 192 | lpDDraw->DrawBoxDw(sfcDest: cgo.Surface, iX1: rcSelArea.x + cgo.TargetX, iY1: rcSelArea.y + cgo.TargetY, |
| 193 | iX2: rcSelArea.x + rcSelArea.Wdt + cgo.TargetX - 1, iY2: rcSelArea.y + rcSelArea.Hgt + cgo.TargetY - 1, |
| 194 | C4GUI_ContextSelColor); |
| 195 | } |
| 196 | // draw frame |
| 197 | Draw3DFrame(cgo); |
| 198 | } |
| 199 | |
| 200 | void ContextMenu::(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, uint32_t dwKeyParam) |
| 201 | { |
| 202 | // inherited |
| 203 | Window::MouseInput(rMouse, iButton, iX, iY, dwKeyParam); |
| 204 | // mouse is in client area? |
| 205 | if (GetClientRect().Contains(iX: iX + rcBounds.x, iY: iY + rcBounds.y)) |
| 206 | { |
| 207 | // reset selection |
| 208 | Element *pPrevSelectedItem = pSelectedItem; |
| 209 | pSelectedItem = nullptr; |
| 210 | // get client component the mouse is over |
| 211 | iX -= GetMarginLeft(); iY -= GetMarginTop(); |
| 212 | for (Element *pCurr = GetFirst(); pCurr; pCurr = pCurr->GetNext()) |
| 213 | if (pCurr->GetBounds().Contains(iX, iY)) |
| 214 | pSelectedItem = pCurr; |
| 215 | // selection change sound |
| 216 | if (pSelectedItem != pPrevSelectedItem) |
| 217 | { |
| 218 | SelectionChanged(fByUser: true); |
| 219 | // selection by mouse: Check whether submenu can be opened |
| 220 | CheckOpenSubmenu(); |
| 221 | } |
| 222 | // check mouse click |
| 223 | if (iButton == C4MC_Button_LeftDown) |
| 224 | { |
| 225 | DoOK(); return; |
| 226 | } |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | void ContextMenu::(CMouse &rMouse, Entry *pOldEntry) |
| 231 | { |
| 232 | // no submenu open? then deselect any selected item |
| 233 | if (pOldEntry == pSelectedItem && !pSubmenu) |
| 234 | { |
| 235 | pSelectedItem = nullptr; |
| 236 | SelectionChanged(fByUser: true); |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | bool ContextMenu::() |
| 241 | { |
| 242 | // not if focus is in submenu |
| 243 | if (pSubmenu) return false; |
| 244 | Element *pPrevSelectedItem = pSelectedItem; |
| 245 | // select prev |
| 246 | if (pSelectedItem) pSelectedItem = pSelectedItem->GetPrev(); |
| 247 | // nothing selected or beginning reached: cycle |
| 248 | if (!pSelectedItem) pSelectedItem = GetLastContained(); |
| 249 | // selection might have changed |
| 250 | if (pSelectedItem != pPrevSelectedItem) SelectionChanged(fByUser: true); |
| 251 | return true; |
| 252 | } |
| 253 | |
| 254 | bool ContextMenu::() |
| 255 | { |
| 256 | // not if focus is in submenu |
| 257 | if (pSubmenu) return false; |
| 258 | Element *pPrevSelectedItem = pSelectedItem; |
| 259 | // select next |
| 260 | if (pSelectedItem) pSelectedItem = pSelectedItem->GetNext(); |
| 261 | // nothing selected or end reached: cycle |
| 262 | if (!pSelectedItem) pSelectedItem = GetFirstContained(); |
| 263 | // selection might have changed |
| 264 | if (pSelectedItem != pPrevSelectedItem) SelectionChanged(fByUser: true); |
| 265 | return true; |
| 266 | } |
| 267 | |
| 268 | bool ContextMenu::() |
| 269 | { |
| 270 | // not if focus is in submenu |
| 271 | if (pSubmenu) return false; |
| 272 | CheckOpenSubmenu(); |
| 273 | return true; |
| 274 | } |
| 275 | |
| 276 | bool ContextMenu::() |
| 277 | { |
| 278 | // not if focus is in submenu |
| 279 | if (pSubmenu) return false; |
| 280 | // close submenu on keyboard input |
| 281 | if (IsSubmenu()) { Abort(fByUser: true); return true; } |
| 282 | return false; |
| 283 | } |
| 284 | |
| 285 | bool ContextMenu::() |
| 286 | { |
| 287 | // not if focus is in submenu |
| 288 | if (pSubmenu) return false; |
| 289 | Abort(fByUser: true); |
| 290 | return true; |
| 291 | } |
| 292 | |
| 293 | bool ContextMenu::() |
| 294 | { |
| 295 | // not if focus is in submenu |
| 296 | if (pSubmenu) return false; |
| 297 | CheckOpenSubmenu(); |
| 298 | DoOK(); |
| 299 | return true; |
| 300 | } |
| 301 | |
| 302 | bool ContextMenu::(C4KeyCodeEx key) |
| 303 | { |
| 304 | // not if focus is in submenu |
| 305 | if (pSubmenu) return false; |
| 306 | Element *pPrevSelectedItem = pSelectedItem; |
| 307 | C4KeyCode wKey = TOUPPERIFX11(key.Key); |
| 308 | if (Inside<C4KeyCode>(ival: wKey, lbound: 'A', rbound: 'Z') || Inside<C4KeyCode>(ival: wKey, lbound: '0', rbound: '9')) |
| 309 | { |
| 310 | // process hotkeys |
| 311 | char ch = char(wKey); |
| 312 | for (Element *pCurr = GetFirst(); pCurr; pCurr = pCurr->GetNext()) |
| 313 | if (pCurr->OnHotkey(cHotkey: ch)) |
| 314 | { |
| 315 | pSelectedItem = pCurr; |
| 316 | if (pSelectedItem != pPrevSelectedItem) SelectionChanged(fByUser: true); |
| 317 | CheckOpenSubmenu(); |
| 318 | DoOK(); |
| 319 | return true; |
| 320 | } |
| 321 | return false; |
| 322 | } |
| 323 | // unrecognized hotkey |
| 324 | return false; |
| 325 | } |
| 326 | |
| 327 | void ContextMenu::() |
| 328 | { |
| 329 | // first item at zero offset |
| 330 | Element *pCurr = GetFirst(); |
| 331 | if (!pCurr) return; |
| 332 | pCurr->GetBounds().y = 0; |
| 333 | int32_t iMinWdt = std::max<int32_t>(a: 20, b: pCurr->GetBounds().Wdt); |
| 334 | int32_t iOverallHgt = pCurr->GetBounds().Hgt; |
| 335 | // others stacked under it |
| 336 | while (pCurr = pCurr->GetNext()) |
| 337 | { |
| 338 | iMinWdt = (std::max)(a: iMinWdt, b: pCurr->GetBounds().Wdt); |
| 339 | int32_t iYSpace = pCurr->GetListItemTopSpacing(); |
| 340 | int32_t iNewY = iOverallHgt + iYSpace; |
| 341 | iOverallHgt += pCurr->GetBounds().Hgt + iYSpace; |
| 342 | if (iNewY != pCurr->GetBounds().y) |
| 343 | { |
| 344 | pCurr->GetBounds().y = iNewY; |
| 345 | pCurr->UpdateOwnPos(); |
| 346 | } |
| 347 | } |
| 348 | // don't make smaller |
| 349 | iMinWdt = (std::max)(a: iMinWdt, b: rcBounds.Wdt - GetMarginLeft() - GetMarginRight()); |
| 350 | // all entries same size |
| 351 | for (pCurr = GetFirst(); pCurr; pCurr = pCurr->GetNext()) |
| 352 | if (pCurr->GetBounds().Wdt != iMinWdt) |
| 353 | { |
| 354 | pCurr->GetBounds().Wdt = iMinWdt; |
| 355 | pCurr->UpdateOwnPos(); |
| 356 | } |
| 357 | // update own size |
| 358 | rcBounds.Wdt = iMinWdt + GetMarginLeft() + GetMarginRight(); |
| 359 | rcBounds.Hgt = std::max<int32_t>(a: iOverallHgt, b: 8) + GetMarginTop() + GetMarginBottom(); |
| 360 | UpdateSize(); |
| 361 | } |
| 362 | |
| 363 | void ContextMenu::(Element *pChild) |
| 364 | { |
| 365 | // inherited |
| 366 | Window::RemoveElement(pChild); |
| 367 | // target lost? |
| 368 | if (pChild == pTarget) { Abort(fByUser: false); return; } |
| 369 | // submenu? |
| 370 | if (pChild == pSubmenu) pSubmenu = nullptr; |
| 371 | // clear selection var |
| 372 | if (pChild == pSelectedItem) |
| 373 | { |
| 374 | pSelectedItem = nullptr; |
| 375 | SelectionChanged(fByUser: false); |
| 376 | } |
| 377 | // forward to any submenu |
| 378 | if (pSubmenu) pSubmenu->RemoveElement(pChild); |
| 379 | // forward to mouse |
| 380 | if (GetScreen()) |
| 381 | GetScreen()->Mouse.RemoveElement(pChild); |
| 382 | // update positions |
| 383 | UpdateElementPositions(); |
| 384 | } |
| 385 | |
| 386 | bool ContextMenu::(Element *pChild) |
| 387 | { |
| 388 | // add it |
| 389 | Window::AddElement(pChild); |
| 390 | // update own size and positions |
| 391 | UpdateElementPositions(); |
| 392 | // success |
| 393 | return true; |
| 394 | } |
| 395 | |
| 396 | void ContextMenu::(Element *pOfElement) |
| 397 | { |
| 398 | // inherited |
| 399 | Window::ElementSizeChanged(pOfElement); |
| 400 | // update positions of all list items |
| 401 | UpdateElementPositions(); |
| 402 | } |
| 403 | |
| 404 | void ContextMenu::(Element *pOfElement) |
| 405 | { |
| 406 | // inherited |
| 407 | Window::ElementSizeChanged(pOfElement); |
| 408 | // update positions of all list items |
| 409 | UpdateElementPositions(); |
| 410 | } |
| 411 | |
| 412 | void ContextMenu::(bool fByUser) |
| 413 | { |
| 414 | // any selection? |
| 415 | if (pSelectedItem) |
| 416 | { |
| 417 | // effect |
| 418 | if (fByUser) GUISound(szSound: "Command" ); |
| 419 | } |
| 420 | // close any submenu from prev selection |
| 421 | if (pSubmenu) pSubmenu->Abort(fByUser: true); |
| 422 | } |
| 423 | |
| 424 | Screen *ContextMenu::() |
| 425 | { |
| 426 | // context menus don't have a parent; get screen by static var |
| 427 | return Screen::GetScreenS(); |
| 428 | } |
| 429 | |
| 430 | bool ContextMenu::(CMouse &rMouse, int32_t iButton, int32_t iScreenX, int32_t iScreenY, uint32_t dwKeyParam) |
| 431 | { |
| 432 | // check submenu |
| 433 | if (pSubmenu) |
| 434 | if (pSubmenu->CtxMouseInput(rMouse, iButton, iScreenX, iScreenY, dwKeyParam)) return true; |
| 435 | // check bounds |
| 436 | if (!rcBounds.Contains(iX: iScreenX, iY: iScreenY)) return false; |
| 437 | // inside menu: do input in local coordinates |
| 438 | MouseInput(rMouse, iButton, iX: iScreenX - rcBounds.x, iY: iScreenY - rcBounds.y, dwKeyParam); |
| 439 | return true; |
| 440 | } |
| 441 | |
| 442 | bool ContextMenu::(const char *c) |
| 443 | { |
| 444 | // forward to submenu |
| 445 | if (pSubmenu) return pSubmenu->CharIn(c); |
| 446 | return false; |
| 447 | } |
| 448 | |
| 449 | void ContextMenu::(C4FacetEx &cgo) |
| 450 | { |
| 451 | // draw self |
| 452 | Window::Draw(cgo); |
| 453 | // draw submenus on top |
| 454 | if (pSubmenu) pSubmenu->Draw(cgo); |
| 455 | } |
| 456 | |
| 457 | void ContextMenu::(Element *pTarget, int32_t iScreenX, int32_t iScreenY) |
| 458 | { |
| 459 | // set pos |
| 460 | rcBounds.x = iScreenX; rcBounds.y = iScreenY; |
| 461 | UpdatePos(); |
| 462 | // set target |
| 463 | this->pTarget = pTarget; |
| 464 | // effect :) |
| 465 | GUISound(szSound: "DoorOpen" ); |
| 466 | // done |
| 467 | } |
| 468 | |
| 469 | void ContextMenu::() |
| 470 | { |
| 471 | // safety |
| 472 | if (!GetScreen()) return; |
| 473 | // anything selected? |
| 474 | if (!pSelectedItem) return; |
| 475 | // get as entry |
| 476 | Entry *pSelEntry = static_cast<Entry *>(pSelectedItem); |
| 477 | // has submenu handler? |
| 478 | ContextHandler *pSubmenuHandler = pSelEntry->pSubmenuHandler; |
| 479 | if (!pSubmenuHandler) return; |
| 480 | // create submenu then |
| 481 | if (pSubmenu) pSubmenu->Abort(fByUser: false); |
| 482 | pSubmenu = pSubmenuHandler->OnSubcontext(pOnElement: pTarget); |
| 483 | // get open pos |
| 484 | int32_t iX = GetClientRect().x + pSelEntry->GetBounds().x + pSelEntry->GetBounds().Wdt; |
| 485 | int32_t iY = GetClientRect().y + pSelEntry->GetBounds().y + pSelEntry->GetBounds().Hgt / 2; |
| 486 | int32_t iScreenWdt = GetScreen()->GetBounds().Wdt, iScreenHgt = GetScreen()->GetBounds().Hgt; |
| 487 | if (iY + pSubmenu->GetBounds().Hgt >= iScreenHgt) |
| 488 | { |
| 489 | // bottom too narrow: open to top, if height is sufficient |
| 490 | // otherwise, open to top from bottom screen pos |
| 491 | if (iY < pSubmenu->GetBounds().Hgt) iY = iScreenHgt; |
| 492 | iY -= pSubmenu->GetBounds().Hgt; |
| 493 | } |
| 494 | if (iX + pSubmenu->GetBounds().Wdt >= iScreenWdt) |
| 495 | { |
| 496 | // right too narrow: try opening left of this menu |
| 497 | // otherwise, open to left from right screen border |
| 498 | if (GetClientRect().x < pSubmenu->GetBounds().Wdt) |
| 499 | iX = iScreenWdt; |
| 500 | else |
| 501 | iX = GetClientRect().x; |
| 502 | iX -= pSubmenu->GetBounds().Wdt; |
| 503 | } |
| 504 | // open it |
| 505 | pSubmenu->Open(pTarget, iScreenX: iX, iScreenY: iY); |
| 506 | } |
| 507 | |
| 508 | bool ContextMenu::() |
| 509 | { |
| 510 | return GetScreen() && GetScreen()->pContext != this; |
| 511 | } |
| 512 | |
| 513 | void ContextMenu::() |
| 514 | { |
| 515 | // safety |
| 516 | if (!GetScreen()) return; |
| 517 | // anything selected? |
| 518 | if (!pSelectedItem) return; |
| 519 | // get as entry |
| 520 | Entry *pSelEntry = static_cast<Entry *>(pSelectedItem); |
| 521 | // get CB; take over pointer |
| 522 | MenuHandler *pCallback = pSelEntry->GetAndZeroCallback(); |
| 523 | Element *pTarget = this->pTarget; |
| 524 | if (!pCallback) return; |
| 525 | // close all menus (deletes this class!) w/o sound |
| 526 | GetScreen()->AbortContext(fByUser: false); |
| 527 | // sound |
| 528 | GUISound(szSound: "Click" ); |
| 529 | // do CB |
| 530 | pCallback->OnOK(pTarget); |
| 531 | // free CB class |
| 532 | delete pCallback; |
| 533 | } |
| 534 | |
| 535 | } // end of namespace |
| 536 | |