openclonk/src/gui/C4GuiComboBox.cpp

251 lines
7.6 KiB
C++

/*
* 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<ComboBox, ComboBox::ComboMenuCBStruct> 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<ComboBox>(*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<ComboBox>(*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