1 /* 2 * OpenClonk, http://www.openclonk.org 3 * 4 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ 5 * Copyright (c) 2009-2016, The OpenClonk Team and contributors 6 * 7 * Distributed under the terms of the ISC license; see accompanying file 8 * "COPYING" for details. 9 * 10 * "Clonk" is a registered trademark of Matthes Bender, used with permission. 11 * See accompanying file "TRADEMARK" for details. 12 * 13 * To redistribute this file separately, substitute the full license texts 14 * for the above references. 15 */ 16 // generic user interface 17 // context menu 18 19 #include "C4Include.h" 20 #include "gui/C4Gui.h" 21 22 #include "graphics/C4Draw.h" 23 #include "graphics/C4FacetEx.h" 24 #include "graphics/C4GraphicsResource.h" 25 #include "gui/C4MouseControl.h" 26 27 namespace C4GUI 28 { 29 30 int32_t ContextMenu::iGlobalMenuIndex = 0; 31 32 33 // ---------------------------------------------------- 34 // ContextMenu::Entry 35 DrawElement(C4TargetFacet & cgo)36 void ContextMenu::Entry::DrawElement(C4TargetFacet &cgo) 37 { 38 // icon 39 if (icoIcon > Ico_None) 40 { 41 // get icon counts 42 int32_t iXMax, iYMax; 43 ::GraphicsResource.fctIcons.GetPhaseNum(iXMax, iYMax); 44 if (!iXMax) 45 iXMax = 6; 46 // load icon 47 const C4Facet &rfctIcon = ::GraphicsResource.fctIcons.GetPhase(icoIcon % iXMax, icoIcon / iXMax); 48 rfctIcon.DrawX(cgo.Surface, rcBounds.x + cgo.TargetX, rcBounds.y + cgo.TargetY, rcBounds.Hgt, rcBounds.Hgt); 49 } 50 // print out label 51 if (!!sText) 52 pDraw->TextOut(sText.getData(), ::GraphicsResource.TextFont, 1.0f, cgo.Surface, cgo.TargetX+rcBounds.x+GetIconIndent(), rcBounds.y + cgo.TargetY, C4GUI_ContextFontClr, ALeft); 53 // submenu arrow 54 if (pSubmenuHandler) 55 { 56 C4Facet &rSubFct = ::GraphicsResource.fctSubmenu; 57 rSubFct.Draw(cgo.Surface, cgo.TargetX+rcBounds.x+rcBounds.Wdt - rSubFct.Wdt, cgo.TargetY+rcBounds.y+(rcBounds.Hgt - rSubFct.Hgt)/2); 58 } 59 } 60 Entry(const char * szText,Icons icoIcon,MenuHandler * pMenuHandler,ContextHandler * pSubmenuHandler)61 ContextMenu::Entry::Entry(const char *szText, Icons icoIcon, MenuHandler *pMenuHandler, ContextHandler *pSubmenuHandler) 62 : Element(), cHotkey(0), icoIcon(icoIcon), pMenuHandler(pMenuHandler), pSubmenuHandler(pSubmenuHandler) 63 { 64 // set text with hotkey 65 if (szText) 66 { 67 sText.Copy(szText); 68 ExpandHotkeyMarkup(sText, cHotkey); 69 // adjust size 70 ::GraphicsResource.TextFont.GetTextExtent(sText.getData(), rcBounds.Wdt, rcBounds.Hgt, true); 71 } 72 else 73 { 74 rcBounds.Wdt = 40; 75 rcBounds.Hgt = ::GraphicsResource.TextFont.GetLineHeight(); 76 } 77 // regard icon 78 rcBounds.Wdt += GetIconIndent(); 79 // submenu arrow 80 if (pSubmenuHandler) rcBounds.Wdt += ::GraphicsResource.fctSubmenu.Wdt+2; 81 } 82 83 // ---------------------------------------------------- 84 // ContextMenu 85 ContextMenu()86 ContextMenu::ContextMenu() : Window() 87 { 88 iMenuIndex = ++iGlobalMenuIndex; 89 // set min size 90 rcBounds.Wdt=40; rcBounds.Hgt=7; 91 // key bindings 92 C4CustomKey::CodeList Keys; 93 Keys.emplace_back(K_UP); 94 if (Config.Controls.GamepadGuiControl) 95 { 96 ControllerKeys::Up(Keys); 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.emplace_back(K_DOWN); 103 if (Config.Controls.GamepadGuiControl) 104 { 105 ControllerKeys::Down(Keys); 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.emplace_back(K_RIGHT); 112 if (Config.Controls.GamepadGuiControl) 113 { 114 ControllerKeys::Right(Keys); 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.emplace_back(K_LEFT); 121 if (Config.Controls.GamepadGuiControl) 122 { 123 ControllerKeys::Left(Keys); 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.emplace_back(K_ESCAPE); 130 if (Config.Controls.GamepadGuiControl) 131 { 132 ControllerKeys::Cancel(Keys); 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.emplace_back(K_RETURN); 139 if (Config.Controls.GamepadGuiControl) 140 { 141 ControllerKeys::Ok(Keys); 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 ~ContextMenu()150 ContextMenu::~ContextMenu() 151 { 152 // del any submenu 153 if (pSubmenu) { delete pSubmenu; pSubmenu=nullptr; } 154 // forward RemoveElement to screen 155 Screen *pScreen = GetScreen(); 156 if (pScreen) pScreen->RemoveElement(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 Abort(bool fByUser)169 void ContextMenu::Abort(bool fByUser) 170 { 171 // effect 172 if (fByUser) GUISound("UI::Close"); 173 // simply del menu: dtor will remove itself 174 delete this; 175 } 176 DrawElement(C4TargetFacet & cgo)177 void ContextMenu::DrawElement(C4TargetFacet &cgo) 178 { 179 // draw context menu bg 180 pDraw->DrawBoxDw(cgo.Surface, rcBounds.x+cgo.TargetX, rcBounds.y+cgo.TargetY, 181 rcBounds.x+rcBounds.Wdt+cgo.TargetX-1, 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 pDraw->DrawBoxDw(cgo.Surface, rcSelArea.x+cgo.TargetX, rcSelArea.y+cgo.TargetY, 193 rcSelArea.x+rcSelArea.Wdt+cgo.TargetX-1, rcSelArea.y+rcSelArea.Hgt+cgo.TargetY-1, 194 C4GUI_ContextSelColor); 195 } 196 // draw frame 197 Draw3DFrame(cgo); 198 } 199 MouseInput(CMouse & rMouse,int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyParam)200 void ContextMenu::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam) 201 { 202 // inherited 203 Window::MouseInput(rMouse, iButton, iX, iY, dwKeyParam); 204 // mouse is in client area? 205 if (GetClientRect().Contains(iX+rcBounds.x, 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(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 { DoOK(); return; } 225 } 226 } 227 MouseLeaveEntry(CMouse & rMouse,Entry * pOldEntry)228 void ContextMenu::MouseLeaveEntry(CMouse &rMouse, Entry *pOldEntry) 229 { 230 // no submenu open? then deselect any selected item 231 if (pOldEntry==pSelectedItem && !pSubmenu) 232 { 233 pSelectedItem = nullptr; 234 SelectionChanged(true); 235 } 236 } 237 KeySelUp()238 bool ContextMenu::KeySelUp() 239 { 240 // not if focus is in submenu 241 if (pSubmenu) return false; 242 Element *pPrevSelectedItem = pSelectedItem; 243 // select prev 244 if (pSelectedItem) pSelectedItem = pSelectedItem->GetPrev(); 245 // nothing selected or beginning reached: cycle 246 if (!pSelectedItem) pSelectedItem = GetLastContained(); 247 // selection might have changed 248 if (pSelectedItem != pPrevSelectedItem) SelectionChanged(true); 249 return true; 250 } 251 KeySelDown()252 bool ContextMenu::KeySelDown() 253 { 254 // not if focus is in submenu 255 if (pSubmenu) return false; 256 Element *pPrevSelectedItem = pSelectedItem; 257 // select next 258 if (pSelectedItem) pSelectedItem = pSelectedItem->GetNext(); 259 // nothing selected or end reached: cycle 260 if (!pSelectedItem) pSelectedItem = GetFirstContained(); 261 // selection might have changed 262 if (pSelectedItem != pPrevSelectedItem) SelectionChanged(true); 263 return true; 264 } 265 KeySubmenu()266 bool ContextMenu::KeySubmenu() 267 { 268 // not if focus is in submenu 269 if (pSubmenu) return false; 270 CheckOpenSubmenu(); 271 return true; 272 } 273 KeyBack()274 bool ContextMenu::KeyBack() 275 { 276 // not if focus is in submenu 277 if (pSubmenu) return false; 278 // close submenu on keyboard input 279 if (IsSubmenu()) { Abort(true); return true; } 280 return false; 281 } 282 KeyAbort()283 bool ContextMenu::KeyAbort() 284 { 285 // not if focus is in submenu 286 if (pSubmenu) return false; 287 Abort(true); 288 return true; 289 } 290 KeyConfirm()291 bool ContextMenu::KeyConfirm() 292 { 293 // not if focus is in submenu 294 if (pSubmenu) return false; 295 CheckOpenSubmenu(); 296 DoOK(); 297 return true; 298 } 299 KeyHotkey(const C4KeyCodeEx & key)300 bool ContextMenu::KeyHotkey(const C4KeyCodeEx &key) 301 { 302 // not if focus is in submenu 303 if (pSubmenu) return false; 304 Element *pPrevSelectedItem = pSelectedItem; 305 StdStrBuf sKey = C4KeyCodeEx::KeyCode2String(key.Key, true, true); 306 // do hotkey procs for standard alphanumerics only 307 if (sKey.getLength() != 1) return false; 308 WORD wKey = WORD(*sKey.getData()); 309 if (Inside<C4KeyCode, C4KeyCode, C4KeyCode>(wKey, 'A', 'Z') || Inside<C4KeyCode, C4KeyCode, C4KeyCode>(wKey, '0', '9')) 310 { 311 // process hotkeys 312 uint32_t ch = wKey; 313 for (Element *pCurr = GetFirst(); pCurr; pCurr = pCurr->GetNext()) 314 if (pCurr->OnHotkey(ch)) 315 { 316 pSelectedItem = pCurr; 317 if (pSelectedItem != pPrevSelectedItem) SelectionChanged(true); 318 CheckOpenSubmenu(); 319 DoOK(); 320 return true; 321 } 322 return false; 323 } 324 // unrecognized hotkey 325 return false; 326 } 327 UpdateElementPositions()328 void ContextMenu::UpdateElementPositions() 329 { 330 // first item at zero offset 331 Element *pCurr = GetFirst(); 332 if (!pCurr) return; 333 pCurr->GetBounds().y = 0; 334 int32_t iMinWdt = std::max<int32_t>(20, pCurr->GetBounds().Wdt);; 335 int32_t iOverallHgt = pCurr->GetBounds().Hgt; 336 // others stacked under it 337 while ((pCurr = pCurr->GetNext())) 338 { 339 iMinWdt = std::max(iMinWdt, pCurr->GetBounds().Wdt); 340 int32_t iYSpace = pCurr->GetListItemTopSpacing(); 341 int32_t iNewY = iOverallHgt + iYSpace; 342 iOverallHgt += pCurr->GetBounds().Hgt + iYSpace; 343 if (iNewY != pCurr->GetBounds().y) 344 { 345 pCurr->GetBounds().y = iNewY; 346 pCurr->UpdateOwnPos(); 347 } 348 } 349 // don't make smaller 350 iMinWdt = std::max(iMinWdt, rcBounds.Wdt - GetMarginLeft() - GetMarginRight()); 351 // all entries same size 352 for (pCurr = GetFirst(); pCurr; pCurr = pCurr->GetNext()) 353 if (pCurr->GetBounds().Wdt != iMinWdt) 354 { 355 pCurr->GetBounds().Wdt = iMinWdt; 356 pCurr->UpdateOwnPos(); 357 } 358 // update own size 359 rcBounds.Wdt = iMinWdt + GetMarginLeft() + GetMarginRight(); 360 rcBounds.Hgt = std::max<int32_t>(iOverallHgt, 8) + GetMarginTop() + GetMarginBottom(); 361 UpdateSize(); 362 } 363 RemoveElement(Element * pChild)364 void ContextMenu::RemoveElement(Element *pChild) 365 { 366 // inherited 367 Window::RemoveElement(pChild); 368 // target lost? 369 if (pChild == pTarget) { Abort(false); return; } 370 // submenu? 371 if (pChild == pSubmenu) pSubmenu = nullptr; 372 // clear selection var 373 if (pChild == pSelectedItem) 374 { 375 pSelectedItem = nullptr; 376 SelectionChanged(false); 377 } 378 // forward to any submenu 379 if (pSubmenu) pSubmenu->RemoveElement(pChild); 380 // forward to mouse 381 if (GetScreen()) 382 GetScreen()->Mouse.RemoveElement(pChild); 383 // update positions 384 UpdateElementPositions(); 385 } 386 AddElement(Element * pChild)387 bool ContextMenu::AddElement(Element *pChild) 388 { 389 // add it 390 Window::AddElement(pChild); 391 // update own size and positions 392 UpdateElementPositions(); 393 // success 394 return true; 395 } 396 InsertElement(Element * pChild,Element * pInsertBefore)397 bool ContextMenu::InsertElement(Element *pChild, Element *pInsertBefore) 398 { 399 // insert it 400 Window::InsertElement(pChild, pInsertBefore); 401 // update own size and positions 402 UpdateElementPositions(); 403 // success 404 return true; 405 } 406 ElementSizeChanged(Element * pOfElement)407 void ContextMenu::ElementSizeChanged(Element *pOfElement) 408 { 409 // inherited 410 Window::ElementSizeChanged(pOfElement); 411 // update positions of all list items 412 UpdateElementPositions(); 413 } 414 ElementPosChanged(Element * pOfElement)415 void ContextMenu::ElementPosChanged(Element *pOfElement) 416 { 417 // inherited 418 Window::ElementSizeChanged(pOfElement); 419 // update positions of all list items 420 UpdateElementPositions(); 421 } 422 SelectionChanged(bool fByUser)423 void ContextMenu::SelectionChanged(bool fByUser) 424 { 425 // any selection? 426 if (pSelectedItem) 427 { 428 // effect 429 if (fByUser) GUISound("UI::Select"); 430 } 431 // close any submenu from prev selection 432 if (pSubmenu) pSubmenu->Abort(true); 433 } 434 GetScreen()435 Screen *ContextMenu::GetScreen() 436 { 437 // context menus don't have a parent; get screen by static var 438 return Screen::GetScreenS(); 439 } 440 CtxMouseInput(CMouse & rMouse,int32_t iButton,int32_t iScreenX,int32_t iScreenY,DWORD dwKeyParam)441 bool ContextMenu::CtxMouseInput(CMouse &rMouse, int32_t iButton, int32_t iScreenX, int32_t iScreenY, DWORD dwKeyParam) 442 { 443 // check submenu 444 if (pSubmenu) 445 if (pSubmenu->CtxMouseInput(rMouse, iButton, iScreenX, iScreenY, dwKeyParam)) return true; 446 // check bounds 447 if (!rcBounds.Contains(iScreenX, iScreenY)) return false; 448 // inside menu: do input in local coordinates 449 MouseInput(rMouse, iButton, iScreenX - rcBounds.x, iScreenY - rcBounds.y, dwKeyParam); 450 return true; 451 } 452 CharIn(const char * c)453 bool ContextMenu::CharIn(const char * c) 454 { 455 // forward to submenu 456 if (pSubmenu) return pSubmenu->CharIn(c); 457 return false; 458 } 459 Draw(C4TargetFacet & cgo)460 void ContextMenu::Draw(C4TargetFacet &cgo) 461 { 462 // In editor mode, the surface is not assigned 463 // The menu is drawn directly by the dialogue, so just exit here. 464 if (!cgo.Surface) return; 465 // draw self 466 Window::Draw(cgo); 467 // draw submenus on top 468 if (pSubmenu) pSubmenu->Draw(cgo); 469 } 470 Open(Element * pTarget,int32_t iScreenX,int32_t iScreenY)471 void ContextMenu::Open(Element *pTarget, int32_t iScreenX, int32_t iScreenY) 472 { 473 // set pos 474 rcBounds.x = iScreenX; rcBounds.y = iScreenY; 475 UpdatePos(); 476 // set target 477 this->pTarget = pTarget; 478 // effect :) 479 GUISound("UI::Open"); 480 // done 481 } 482 CheckOpenSubmenu()483 void ContextMenu::CheckOpenSubmenu() 484 { 485 // safety 486 if (!GetScreen()) return; 487 // anything selected? 488 if (!pSelectedItem) return; 489 // get as entry 490 Entry *pSelEntry = (Entry *) pSelectedItem; 491 // has submenu handler? 492 ContextHandler *pSubmenuHandler = pSelEntry->pSubmenuHandler; 493 if (!pSubmenuHandler) return; 494 // create submenu then 495 if (pSubmenu) pSubmenu->Abort(false); 496 pSubmenu = pSubmenuHandler->OnSubcontext(pTarget); 497 // get open pos 498 int32_t iX = GetClientRect().x + pSelEntry->GetBounds().x + pSelEntry->GetBounds().Wdt; 499 int32_t iY = GetClientRect().y + pSelEntry->GetBounds().y + pSelEntry->GetBounds().Hgt/2; 500 int32_t iScreenWdt = GetScreen()->GetBounds().Wdt, iScreenHgt = GetScreen()->GetBounds().Hgt; 501 if (iY + pSubmenu->GetBounds().Hgt >= iScreenHgt) 502 { 503 // bottom too narrow: open to top, if height is sufficient 504 // otherwise, open to top from bottom screen pos 505 if (iY < pSubmenu->GetBounds().Hgt) iY = iScreenHgt; 506 iY -= pSubmenu->GetBounds().Hgt; 507 } 508 if (iX + pSubmenu->GetBounds().Wdt >= iScreenWdt) 509 { 510 // right too narrow: try opening left of this menu 511 // otherwise, open to left from right screen border 512 if (GetClientRect().x < pSubmenu->GetBounds().Wdt) 513 iX = iScreenWdt; 514 else 515 iX = GetClientRect().x; 516 iX -= pSubmenu->GetBounds().Wdt; 517 } 518 // open it 519 pSubmenu->Open(pTarget, iX, iY); 520 } 521 IsSubmenu()522 bool ContextMenu::IsSubmenu() 523 { 524 return GetScreen() && GetScreen()->pContext!=this; 525 } 526 DoOK()527 void ContextMenu::DoOK() 528 { 529 // safety 530 if (!GetScreen()) return; 531 // anything selected? 532 if (!pSelectedItem) return; 533 // get as entry 534 Entry *pSelEntry = (Entry *) pSelectedItem; 535 // get CB; take over pointer 536 MenuHandler *pCallback = pSelEntry->GetAndZeroCallback(); 537 Element *pTarget = this->pTarget; 538 if (!pCallback) return; 539 // close all menus (deletes this class!) w/o sound 540 GetScreen()->AbortContext(false); 541 // sound 542 GUISound("UI::Click"); 543 // do CB 544 pCallback->OnOK(pTarget); 545 // free CB class 546 delete pCallback; 547 } 548 SelectItem(int32_t iIndex)549 void ContextMenu::SelectItem(int32_t iIndex) 550 { 551 // get item to be selected (may be nullptr on purpose!) 552 Element *pNewSelElement = GetElementByIndex(iIndex); 553 if (pNewSelElement != pSelectedItem) return; 554 // set new 555 pSelectedItem = pNewSelElement; 556 SelectionChanged(false); 557 } 558 559 560 // ---------------------------------------------------- 561 // ContextButton 562 ContextButton(C4Rect & rtBounds)563 ContextButton::ContextButton(C4Rect &rtBounds) : Control(rtBounds), iOpenMenu(0), fMouseOver(false) 564 { 565 RegisterContextKey(); 566 } 567 ContextButton(Element * pForEl,bool fAdd,int32_t iHIndent,int32_t iVIndent)568 ContextButton::ContextButton(Element *pForEl, bool fAdd, int32_t iHIndent, int32_t iVIndent) 569 : Control(C4Rect(0,0,0,0)), iOpenMenu(0), fMouseOver(false) 570 { 571 SetBounds(pForEl->GetToprightCornerRect(16, 16, iHIndent, iVIndent)); 572 // copy context handler 573 SetContextHandler(pForEl->GetContextHandler()); 574 // add if desired 575 Container *pCont; 576 if (fAdd) if ((pCont = pForEl->GetContainer())) 577 pCont->AddElement(this); 578 RegisterContextKey(); 579 } 580 ~ContextButton()581 ContextButton::~ContextButton() 582 { 583 delete pKeyContext; 584 } 585 RegisterContextKey()586 void ContextButton::RegisterContextKey() 587 { 588 // reg keys for pressing the context button 589 C4CustomKey::CodeList ContextKeys; 590 ContextKeys.emplace_back(K_RIGHT); 591 ContextKeys.emplace_back(K_DOWN); 592 ContextKeys.emplace_back(K_SPACE); 593 ContextKeys.emplace_back(K_RIGHT, KEYS_Alt); 594 ContextKeys.emplace_back(K_DOWN, KEYS_Alt); 595 ContextKeys.emplace_back(K_SPACE, KEYS_Alt); 596 pKeyContext = new C4KeyBinding(ContextKeys, "GUIContextButtonPress", KEYSCOPE_Gui, 597 new ControlKeyCB<ContextButton>(*this, &ContextButton::KeyContext), C4CustomKey::PRIO_Ctrl); 598 } 599 DoContext(int32_t iX,int32_t iY)600 bool ContextButton::DoContext(int32_t iX, int32_t iY) 601 { 602 // get context pos 603 if (iX<0) 604 { 605 iX = rcBounds.Wdt/2; 606 iY = rcBounds.Hgt/2; 607 } 608 // do context 609 ContextHandler *pCtx = GetContextHandler(); 610 if (!pCtx) return false; 611 if (!pCtx->OnContext(this, iX, iY)) return false; 612 // store menu 613 Screen *pScr = GetScreen(); 614 if (!pScr) return false; 615 iOpenMenu = pScr->GetContextMenuIndex(); 616 // return whether all was successful 617 return !!iOpenMenu; 618 } 619 DrawElement(C4TargetFacet & cgo)620 void ContextButton::DrawElement(C4TargetFacet &cgo) 621 { 622 // recheck open menu 623 Screen *pScr = GetScreen(); 624 if (!pScr || (iOpenMenu != pScr->GetContextMenuIndex())) iOpenMenu = 0; 625 // calc drawing bounds 626 int32_t x0 = cgo.TargetX + rcBounds.x, y0 = cgo.TargetY + rcBounds.y; 627 // draw button; down (phase 1) if a menu is open 628 ::GraphicsResource.fctContext.Draw(cgo.Surface, x0, y0, iOpenMenu ? 1 : 0); 629 // draw selection highlight 630 if (HasDrawFocus() || (fMouseOver && IsInActiveDlg(false)) || iOpenMenu) 631 { 632 pDraw->SetBlitMode(C4GFXBLIT_ADDITIVE); 633 ::GraphicsResource.fctButtonHighlight.DrawX(cgo.Surface, x0, y0, rcBounds.Wdt, rcBounds.Hgt); 634 pDraw->ResetBlitMode(); 635 } 636 } 637 MouseInput(CMouse & rMouse,int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyParam)638 void ContextButton::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam) 639 { 640 // left-click activates menu 641 if ((iButton == C4MC_Button_LeftDown) || (iButton == C4MC_Button_RightDown)) 642 if (DoContext()) return; 643 // inherited 644 Control::MouseInput(rMouse, iButton, iX, iY, dwKeyParam); 645 } 646 MouseEnter(CMouse & rMouse)647 void ContextButton::MouseEnter(CMouse &rMouse) 648 { 649 Control::MouseEnter(rMouse); 650 // remember mouse state for button highlight 651 fMouseOver = true; 652 } 653 MouseLeave(CMouse & rMouse)654 void ContextButton::MouseLeave(CMouse &rMouse) 655 { 656 Control::MouseLeave(rMouse); 657 // mouse left 658 fMouseOver = false; 659 } 660 661 662 663 } // end of namespace 664 665