/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ // generic user interface // dropdown box // implemented via context menu #include "C4Include.h" #include "gui/C4Gui.h" #include "graphics/C4Draw.h" #include "graphics/C4FacetEx.h" #include "graphics/C4GraphicsResource.h" #include "gui/C4MouseControl.h" namespace C4GUI { // ---------------------------------------------------- // ComboBox_FillCB void ComboBox_FillCB::AddEntry(const char *szText, int32_t id) { if (!szText) szText = ""; typedef C4GUI::CBMenuHandlerEx Handler; Handler *pHandler = new Handler(pCombo, &ComboBox::OnCtxComboSelect); pHandler->SetExtra(ComboBox::ComboMenuCBStruct(szText, id)); pDrop->AddItem(szText, FormatString(LoadResStr("IDS_MSG_SELECT"), szText).getData(), Ico_Empty, pHandler); } bool ComboBox_FillCB::FindEntry(const char *szText) { // check for entry with same name ContextMenu::Entry *pEntry; int32_t idx=0; while ((pEntry = pDrop->GetIndexedEntry(idx++))) if (SEqual(pEntry->GetText(), szText)) return true; return false; } void ComboBox_FillCB::SelectEntry(int32_t iEntry) { pDrop->SelectItem(iEntry); } void ComboBox_FillCB::ClearEntries() { pDrop->Clear(); } // ---------------------------------------------------- // ComboBox ComboBox::ComboBox(const C4Rect &rtBounds) : Control(rtBounds), iOpenMenu(0), pFillCallback(nullptr), fReadOnly(false), fSimple(false), fMouseOver(false), pUseFont(nullptr), dwFontClr(C4GUI_ComboFontClr), dwBGClr(C4GUI_StandardBGColor), dwBorderClr(0), pFctSideArrow(nullptr) { *Text=0; // key callbacks - lots of possibilities to get the dropdown C4CustomKey::CodeList cbKeys; cbKeys.emplace_back(K_DOWN); cbKeys.emplace_back(K_SPACE); cbKeys.emplace_back(K_DOWN, KEYS_Alt); cbKeys.emplace_back(K_SPACE, KEYS_Alt); if (Config.Controls.GamepadGuiControl) { ControllerKeys::Ok(cbKeys); ControllerKeys::Down(cbKeys); } pKeyOpenCombo = new C4KeyBinding(cbKeys, "GUIComboOpen", KEYSCOPE_Gui, new ControlKeyCB(*this, &ComboBox::KeyDropDown), C4CustomKey::PRIO_Ctrl); cbKeys.clear(); cbKeys.emplace_back(K_ESCAPE); if (Config.Controls.GamepadGuiControl) { ControllerKeys::Cancel(cbKeys); } pKeyCloseCombo = new C4KeyBinding(cbKeys, "GUIComboClose", KEYSCOPE_Gui, new ControlKeyCB(*this, &ComboBox::KeyAbortDropDown), C4CustomKey::PRIO_Ctrl); } ComboBox::~ComboBox() { delete pKeyCloseCombo; delete pKeyOpenCombo; if (pFillCallback) delete pFillCallback; } void ComboBox::SetComboCB(ComboBox_FillCB *pNewFillCallback) { if (pFillCallback) delete pFillCallback; pFillCallback = pNewFillCallback; } bool ComboBox::DoDropdown() { // not if readonly if (fReadOnly) return false; // get dropdown pos int32_t iX = 0; int32_t iY = rcBounds.Hgt; // do dropdown Screen *pScreen = GetScreen(); if (!pScreen) return false; // item list as context menu if (!pFillCallback) return false; ContextMenu *pNewMenu = new C4GUI::ContextMenu(); // init with minimum size pNewMenu->GetBounds().Wdt = std::max(rcBounds.Wdt, pNewMenu->GetBounds().Wdt); // fill with items pFillCallback->FillDropDown(this, pNewMenu); // open it on screen pScreen->DoContext(pNewMenu, this, iX, iY); // store menu iOpenMenu = pNewMenu->GetMenuIndex(); // done, success return true; } bool ComboBox::AbortDropdown(bool fByUser) { // recheck open menu Screen *pScr = GetScreen(); if (!pScr || (iOpenMenu != pScr->GetLastContextMenuIndex())) iOpenMenu = 0; if (!iOpenMenu) return false; // abort it pScr->AbortContext(fByUser); return true; } void ComboBox::DrawElement(C4TargetFacet &cgo) { CStdFont *pUseFont = this->pUseFont ? this->pUseFont : &(::GraphicsResource.TextFont); // recheck open menu Screen *pScr = GetScreen(); if (!pScr || (iOpenMenu != pScr->GetContextMenuIndex())) iOpenMenu = 0; // calc drawing bounds int32_t x0 = cgo.TargetX + rcBounds.x, y0 = cgo.TargetY + rcBounds.y; int32_t iRightTextEnd = x0 + rcBounds.Wdt - ::GraphicsResource.fctContext.Wdt - 1; if (!fReadOnly && !fSimple) { // draw background pDraw->DrawBoxDw(cgo.Surface, x0,y0,x0+rcBounds.Wdt-1,y0+rcBounds.Hgt-1,dwBGClr); // draw frame if (dwBorderClr) { int32_t x1=cgo.TargetX+rcBounds.x,y1=cgo.TargetY+rcBounds.y,x2=x1+rcBounds.Wdt,y2=y1+rcBounds.Hgt; pDraw->DrawFrameDw(cgo.Surface, x1, y1, x2, y2-1, dwBorderClr); pDraw->DrawFrameDw(cgo.Surface, x1+1, y1+1, x2-1, y2-2, dwBorderClr); } else // default frame color Draw3DFrame(cgo); // draw button; down (phase 1) if combo is down (pFctSideArrow ? pFctSideArrow : &(::GraphicsResource.fctContext))->Draw(cgo.Surface, iRightTextEnd, y0 + (rcBounds.Hgt-::GraphicsResource.fctContext.Hgt)/2, iOpenMenu ? 1 : 0); } else if (!fReadOnly) { // draw button in simple mode: Left of text (pFctSideArrow ? pFctSideArrow : &(::GraphicsResource.fctContext))->Draw(cgo.Surface, x0, y0 + (rcBounds.Hgt-::GraphicsResource.fctContext.Hgt)/2, iOpenMenu ? 1 : 0); } // draw text if (*Text) { pDraw->StorePrimaryClipper(); pDraw->SubPrimaryClipper(x0,y0,iRightTextEnd-1,y0+rcBounds.Hgt-1); pDraw->TextOut(Text, *pUseFont, 1.0f, cgo.Surface, x0 + ::GraphicsResource.fctContext.Wdt + 2, y0 + (rcBounds.Hgt-pUseFont->GetLineHeight())/2, dwFontClr, ALeft); pDraw->RestorePrimaryClipper(); } // draw selection highlight if ((HasDrawFocus() || iOpenMenu || fMouseOver) && !fReadOnly) { pDraw->SetBlitMode(C4GFXBLIT_ADDITIVE); ::GraphicsResource.fctButtonHighlightRound.DrawX(cgo.Surface, x0, y0, rcBounds.Wdt, rcBounds.Hgt); pDraw->ResetBlitMode(); } } void ComboBox::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam) { // left-click activates menu if (!fReadOnly) if (iButton == C4MC_Button_LeftDown) { // recheck open menu Screen *pScr = GetScreen(); if (!pScr || (iOpenMenu != pScr->GetLastContextMenuIndex())) iOpenMenu = 0; if (iOpenMenu) // left-click with combo down: abort has been done by screen; ignore return; else // otherwise, open it if (DoDropdown()) return; } // inherited Control::MouseInput(rMouse, iButton, iX, iY, dwKeyParam); } void ComboBox::MouseEnter(CMouse &rMouse) { fMouseOver = true; Control::MouseEnter(rMouse); } void ComboBox::MouseLeave(CMouse &rMouse) { fMouseOver = false; Control::MouseLeave(rMouse); } int32_t ComboBox::GetDefaultHeight() { return ::GraphicsResource.TextFont.GetLineHeight() + 4; } void ComboBox::SetText(const char *szToText) { // set text without accelerator keys if (szToText) { StdStrBuf sTxt(szToText); sTxt.Replace("&", ""); SCopy(sTxt.getData(), Text, C4MaxTitle); } else *Text=0; } void ComboBox::OnCtxComboSelect(C4GUI::Element *pListItem, const ComboMenuCBStruct &rNewSel) { // ignore in readonly if (fReadOnly) return; // do callback if (!pFillCallback || !pFillCallback->OnComboSelChange(this, rNewSel.id)) // callback didn't process: default behaviour SetText(rNewSel.sText.getData()); // don't do anything else, because this might be deleted } } // namespace C4GUI