ddnet/src/game/client/ui.cpp
Robert Müller 4fb44fc0e8 Improve dropdown menu button label alignment
Reduce the space reserved for the label by the space for the dropdown icon instead of rendering the label behind the icon.

This is more noticeable with smaller dropdown menu buttons, which will be used in the future.
2023-09-10 13:42:38 +02:00

1843 lines
56 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include "ui.h"
#include "ui_scrollregion.h"
#include <base/math.h>
#include <base/system.h>
#include <engine/client.h>
#include <engine/graphics.h>
#include <engine/input.h>
#include <engine/keys.h>
#include <engine/shared/config.h>
#include <game/localization.h>
#include <limits>
using namespace FontIcons;
void CUIElement::Init(CUI *pUI, int RequestedRectCount)
{
m_pUI = pUI;
pUI->AddUIElement(this);
if(RequestedRectCount > 0)
InitRects(RequestedRectCount);
}
void CUIElement::InitRects(int RequestedRectCount)
{
dbg_assert(m_vUIRects.empty(), "UI rects can only be initialized once, create another ui element instead.");
m_vUIRects.resize(RequestedRectCount);
for(auto &Rect : m_vUIRects)
Rect.m_pParent = this;
}
CUIElement::SUIElementRect::SUIElementRect() { Reset(); }
void CUIElement::SUIElementRect::Reset()
{
m_UIRectQuadContainer = -1;
m_UITextContainer.Reset();
m_X = -1;
m_Y = -1;
m_Width = -1;
m_Height = -1;
m_Rounding = -1.0f;
m_Corners = -1;
m_Text.clear();
mem_zero(&m_Cursor, sizeof(m_Cursor));
m_TextColor = ColorRGBA(-1, -1, -1, -1);
m_TextOutlineColor = ColorRGBA(-1, -1, -1, -1);
m_QuadColor = ColorRGBA(-1, -1, -1, -1);
}
void CUIElement::SUIElementRect::Draw(const CUIRect *pRect, ColorRGBA Color, int Corners, float Rounding)
{
bool NeedsRecreate = false;
if(m_UIRectQuadContainer == -1 || m_Width != pRect->w || m_Height != pRect->h || mem_comp(&m_QuadColor, &Color, sizeof(Color)) != 0)
{
m_pParent->UI()->Graphics()->DeleteQuadContainer(m_UIRectQuadContainer);
NeedsRecreate = true;
}
m_X = pRect->x;
m_Y = pRect->y;
if(NeedsRecreate)
{
m_Width = pRect->w;
m_Height = pRect->h;
m_QuadColor = Color;
m_pParent->UI()->Graphics()->SetColor(Color);
m_UIRectQuadContainer = m_pParent->UI()->Graphics()->CreateRectQuadContainer(0, 0, pRect->w, pRect->h, Rounding, Corners);
m_pParent->UI()->Graphics()->SetColor(1, 1, 1, 1);
}
m_pParent->UI()->Graphics()->TextureClear();
m_pParent->UI()->Graphics()->RenderQuadContainerEx(m_UIRectQuadContainer,
0, -1, m_X, m_Y, 1, 1);
}
/********************************************************
UI
*********************************************************/
const CLinearScrollbarScale CUI::ms_LinearScrollbarScale;
const CLogarithmicScrollbarScale CUI::ms_LogarithmicScrollbarScale(25);
const CDarkButtonColorFunction CUI::ms_DarkButtonColorFunction;
const CLightButtonColorFunction CUI::ms_LightButtonColorFunction;
const CScrollBarColorFunction CUI::ms_ScrollBarColorFunction;
const float CUI::ms_FontmodHeight = 0.8f;
CUI *CUIElementBase::s_pUI = nullptr;
IClient *CUIElementBase::Client() const { return s_pUI->Client(); }
IGraphics *CUIElementBase::Graphics() const { return s_pUI->Graphics(); }
IInput *CUIElementBase::Input() const { return s_pUI->Input(); }
ITextRender *CUIElementBase::TextRender() const { return s_pUI->TextRender(); }
void CUI::Init(IKernel *pKernel)
{
m_pClient = pKernel->RequestInterface<IClient>();
m_pGraphics = pKernel->RequestInterface<IGraphics>();
m_pInput = pKernel->RequestInterface<IInput>();
m_pTextRender = pKernel->RequestInterface<ITextRender>();
CUIRect::Init(m_pGraphics);
CLineInput::Init(m_pClient, m_pGraphics, m_pInput, m_pTextRender);
CUIElementBase::Init(this);
}
CUI::CUI()
{
m_Enabled = true;
m_pHotItem = nullptr;
m_pActiveItem = nullptr;
m_pLastActiveItem = nullptr;
m_pBecomingHotItem = nullptr;
m_MouseX = 0;
m_MouseY = 0;
m_MouseWorldX = 0;
m_MouseWorldY = 0;
m_MouseButtons = 0;
m_LastMouseButtons = 0;
m_Screen.x = 0.0f;
m_Screen.y = 0.0f;
}
CUI::~CUI()
{
for(CUIElement *&pEl : m_vpOwnUIElements)
{
delete pEl;
}
m_vpOwnUIElements.clear();
}
CUIElement *CUI::GetNewUIElement(int RequestedRectCount)
{
CUIElement *pNewEl = new CUIElement(this, RequestedRectCount);
m_vpOwnUIElements.push_back(pNewEl);
return pNewEl;
}
void CUI::AddUIElement(CUIElement *pElement)
{
m_vpUIElements.push_back(pElement);
}
void CUI::ResetUIElement(CUIElement &UIElement)
{
for(CUIElement::SUIElementRect &Rect : UIElement.m_vUIRects)
{
Graphics()->DeleteQuadContainer(Rect.m_UIRectQuadContainer);
TextRender()->DeleteTextContainer(Rect.m_UITextContainer);
Rect.Reset();
}
}
void CUI::OnElementsReset()
{
for(CUIElement *pEl : m_vpUIElements)
{
ResetUIElement(*pEl);
}
}
void CUI::OnWindowResize()
{
OnElementsReset();
}
void CUI::OnCursorMove(float X, float Y)
{
if(!CheckMouseLock())
{
m_UpdatedMousePos.x = clamp(m_UpdatedMousePos.x + X, 0.0f, (float)Graphics()->WindowWidth());
m_UpdatedMousePos.y = clamp(m_UpdatedMousePos.y + Y, 0.0f, (float)Graphics()->WindowHeight());
}
m_UpdatedMouseDelta += vec2(X, Y);
}
void CUI::Update()
{
const CUIRect *pScreen = Screen();
const float MouseX = (m_UpdatedMousePos.x / (float)Graphics()->WindowWidth()) * pScreen->w;
const float MouseY = (m_UpdatedMousePos.y / (float)Graphics()->WindowHeight()) * pScreen->h;
Update(MouseX, MouseY, m_UpdatedMouseDelta.x, m_UpdatedMouseDelta.y, MouseX * 3.0f, MouseY * 3.0f);
m_UpdatedMouseDelta = vec2(0.0f, 0.0f);
}
void CUI::Update(float MouseX, float MouseY, float MouseDeltaX, float MouseDeltaY, float MouseWorldX, float MouseWorldY)
{
unsigned MouseButtons = 0;
if(Enabled())
{
if(Input()->KeyIsPressed(KEY_MOUSE_1))
MouseButtons |= 1;
if(Input()->KeyIsPressed(KEY_MOUSE_2))
MouseButtons |= 2;
if(Input()->KeyIsPressed(KEY_MOUSE_3))
MouseButtons |= 4;
}
m_MouseDeltaX = MouseDeltaX;
m_MouseDeltaY = MouseDeltaY;
m_MouseX = MouseX;
m_MouseY = MouseY;
m_MouseWorldX = MouseWorldX;
m_MouseWorldY = MouseWorldY;
m_LastMouseButtons = m_MouseButtons;
m_MouseButtons = MouseButtons;
m_pHotItem = m_pBecomingHotItem;
if(m_pActiveItem)
m_pHotItem = m_pActiveItem;
m_pBecomingHotItem = nullptr;
if(Enabled())
{
CLineInput *pActiveInput = CLineInput::GetActiveInput();
if(pActiveInput && m_pLastActiveItem && pActiveInput != m_pLastActiveItem)
pActiveInput->Deactivate();
}
else
{
m_pHotItem = nullptr;
m_pActiveItem = nullptr;
}
}
void CUI::DebugRender()
{
MapScreen();
char aBuf[128];
str_format(aBuf, sizeof(aBuf), "hot=%p nexthot=%p active=%p lastactive=%p", HotItem(), NextHotItem(), ActiveItem(), m_pLastActiveItem);
TextRender()->Text(2.0f, Screen()->h - 12.0f, 10.0f, aBuf);
}
bool CUI::MouseInside(const CUIRect *pRect) const
{
return pRect->Inside(m_MouseX, m_MouseY);
}
void CUI::ConvertMouseMove(float *pX, float *pY, IInput::ECursorType CursorType) const
{
float Factor = 1.0f;
switch(CursorType)
{
case IInput::CURSOR_MOUSE:
Factor = g_Config.m_UiMousesens / 100.0f;
break;
case IInput::CURSOR_JOYSTICK:
Factor = g_Config.m_UiControllerSens / 100.0f;
break;
default:
dbg_msg("assert", "CUI::ConvertMouseMove CursorType %d", (int)CursorType);
dbg_break();
break;
}
if(m_MouseSlow)
Factor *= 0.05f;
*pX *= Factor;
*pY *= Factor;
}
bool CUI::ConsumeHotkey(EHotkey Hotkey)
{
const bool Pressed = m_HotkeysPressed & Hotkey;
m_HotkeysPressed &= ~Hotkey;
return Pressed;
}
bool CUI::OnInput(const IInput::CEvent &Event)
{
if(!Enabled())
return false;
CLineInput *pActiveInput = CLineInput::GetActiveInput();
if(pActiveInput && pActiveInput->ProcessInput(Event))
return true;
if(Event.m_Flags & IInput::FLAG_PRESS)
{
unsigned LastHotkeysPressed = m_HotkeysPressed;
if(Event.m_Key == KEY_RETURN || Event.m_Key == KEY_KP_ENTER)
m_HotkeysPressed |= HOTKEY_ENTER;
else if(Event.m_Key == KEY_ESCAPE)
m_HotkeysPressed |= HOTKEY_ESCAPE;
else if(Event.m_Key == KEY_TAB && !Input()->AltIsPressed())
m_HotkeysPressed |= HOTKEY_TAB;
else if(Event.m_Key == KEY_DELETE)
m_HotkeysPressed |= HOTKEY_DELETE;
else if(Event.m_Key == KEY_UP)
m_HotkeysPressed |= HOTKEY_UP;
else if(Event.m_Key == KEY_DOWN)
m_HotkeysPressed |= HOTKEY_DOWN;
else if(Event.m_Key == KEY_MOUSE_WHEEL_UP)
m_HotkeysPressed |= HOTKEY_SCROLL_UP;
else if(Event.m_Key == KEY_MOUSE_WHEEL_DOWN)
m_HotkeysPressed |= HOTKEY_SCROLL_DOWN;
else if(Event.m_Key == KEY_PAGEUP)
m_HotkeysPressed |= HOTKEY_PAGE_UP;
else if(Event.m_Key == KEY_PAGEDOWN)
m_HotkeysPressed |= HOTKEY_PAGE_DOWN;
else if(Event.m_Key == KEY_HOME)
m_HotkeysPressed |= HOTKEY_HOME;
else if(Event.m_Key == KEY_END)
m_HotkeysPressed |= HOTKEY_END;
return LastHotkeysPressed != m_HotkeysPressed;
}
return false;
}
float CUI::ButtonColorMul(const void *pID)
{
if(CheckActiveItem(pID))
return ButtonColorMulActive();
else if(HotItem() == pID)
return ButtonColorMulHot();
return ButtonColorMulDefault();
}
const CUIRect *CUI::Screen()
{
m_Screen.h = 600.0f;
m_Screen.w = Graphics()->ScreenAspect() * m_Screen.h;
return &m_Screen;
}
void CUI::MapScreen()
{
const CUIRect *pScreen = Screen();
Graphics()->MapScreen(pScreen->x, pScreen->y, pScreen->w, pScreen->h);
}
float CUI::PixelSize()
{
return Screen()->w / Graphics()->ScreenWidth();
}
void CUI::ClipEnable(const CUIRect *pRect)
{
if(IsClipped())
{
const CUIRect *pOldRect = ClipArea();
CUIRect Intersection;
Intersection.x = std::max(pRect->x, pOldRect->x);
Intersection.y = std::max(pRect->y, pOldRect->y);
Intersection.w = std::min(pRect->x + pRect->w, pOldRect->x + pOldRect->w) - pRect->x;
Intersection.h = std::min(pRect->y + pRect->h, pOldRect->y + pOldRect->h) - pRect->y;
m_vClips.push_back(Intersection);
}
else
{
m_vClips.push_back(*pRect);
}
UpdateClipping();
}
void CUI::ClipDisable()
{
dbg_assert(IsClipped(), "no clip region");
m_vClips.pop_back();
UpdateClipping();
}
const CUIRect *CUI::ClipArea() const
{
dbg_assert(IsClipped(), "no clip region");
return &m_vClips.back();
}
void CUI::UpdateClipping()
{
if(IsClipped())
{
const CUIRect *pRect = ClipArea();
const float XScale = Graphics()->ScreenWidth() / Screen()->w;
const float YScale = Graphics()->ScreenHeight() / Screen()->h;
Graphics()->ClipEnable((int)(pRect->x * XScale), (int)(pRect->y * YScale), (int)(pRect->w * XScale), (int)(pRect->h * YScale));
}
else
{
Graphics()->ClipDisable();
}
}
int CUI::DoButtonLogic(const void *pID, int Checked, const CUIRect *pRect)
{
// logic
int ReturnValue = 0;
const bool Inside = MouseHovered(pRect);
static int s_ButtonUsed = -1;
if(CheckActiveItem(pID))
{
if(s_ButtonUsed >= 0 && !MouseButton(s_ButtonUsed))
{
if(Inside && Checked >= 0)
ReturnValue = 1 + s_ButtonUsed;
SetActiveItem(nullptr);
s_ButtonUsed = -1;
}
}
else if(HotItem() == pID)
{
for(int i = 0; i < 3; ++i)
{
if(MouseButton(i))
{
SetActiveItem(pID);
s_ButtonUsed = i;
}
}
}
if(Inside && !MouseButton(0) && !MouseButton(1) && !MouseButton(2))
SetHotItem(pID);
return ReturnValue;
}
int CUI::DoDraggableButtonLogic(const void *pID, int Checked, const CUIRect *pRect, bool *pClicked, bool *pAbrupted)
{
// logic
int ReturnValue = 0;
const bool Inside = MouseHovered(pRect);
static int s_ButtonUsed = -1;
if(pClicked != nullptr)
*pClicked = false;
if(pAbrupted != nullptr)
*pAbrupted = false;
if(CheckActiveItem(pID))
{
if(s_ButtonUsed == 0)
{
if(Checked >= 0)
ReturnValue = 1 + s_ButtonUsed;
if(!MouseButton(s_ButtonUsed))
{
if(pClicked != nullptr)
*pClicked = true;
SetActiveItem(nullptr);
s_ButtonUsed = -1;
}
if(MouseButton(1))
{
if(pAbrupted != nullptr)
*pAbrupted = true;
SetActiveItem(nullptr);
s_ButtonUsed = -1;
}
}
else if(s_ButtonUsed > 0 && !MouseButton(s_ButtonUsed))
{
if(Inside && Checked >= 0)
ReturnValue = 1 + s_ButtonUsed;
if(pClicked != nullptr)
*pClicked = true;
SetActiveItem(nullptr);
s_ButtonUsed = -1;
}
}
else if(HotItem() == pID)
{
for(int i = 0; i < 3; ++i)
{
if(MouseButton(i))
{
SetActiveItem(pID);
s_ButtonUsed = i;
}
}
}
if(Inside && !MouseButton(0) && !MouseButton(1) && !MouseButton(2))
SetHotItem(pID);
return ReturnValue;
}
int CUI::DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY)
{
if(MouseHovered(pRect))
SetHotItem(pID);
if(HotItem() == pID && MouseButtonClicked(0))
SetActiveItem(pID);
if(CheckActiveItem(pID) && !MouseButton(0))
SetActiveItem(nullptr);
if(!CheckActiveItem(pID))
return 0;
if(Input()->ShiftIsPressed())
m_MouseSlow = true;
if(pX)
*pX = clamp(m_MouseX - pRect->x, 0.0f, pRect->w);
if(pY)
*pY = clamp(m_MouseY - pRect->y, 0.0f, pRect->h);
return 1;
}
void CUI::DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp, float ScrollSpeed)
{
// reset scrolling if it's not necessary anymore
if(TotalSize < ViewPortSize)
{
*pScrollOffsetChange = -*pScrollOffset;
}
// instant scrolling if distance too long
if(absolute(*pScrollOffsetChange) > 2.0f * ViewPortSize)
{
*pScrollOffset += *pScrollOffsetChange;
*pScrollOffsetChange = 0.0f;
}
// smooth scrolling
if(*pScrollOffsetChange)
{
const float Delta = *pScrollOffsetChange * clamp(Client()->RenderFrameTime() * ScrollSpeed, 0.0f, 1.0f);
*pScrollOffset += Delta;
*pScrollOffsetChange -= Delta;
}
// clamp to first item
if(*pScrollOffset < 0.0f)
{
if(SmoothClamp && *pScrollOffset < -0.1f)
{
*pScrollOffsetChange = -*pScrollOffset;
}
else
{
*pScrollOffset = 0.0f;
*pScrollOffsetChange = 0.0f;
}
}
// clamp to last item
if(TotalSize > ViewPortSize && *pScrollOffset > TotalSize - ViewPortSize)
{
if(SmoothClamp && *pScrollOffset - (TotalSize - ViewPortSize) > 0.1f)
{
*pScrollOffsetChange = (TotalSize - ViewPortSize) - *pScrollOffset;
}
else
{
*pScrollOffset = TotalSize - ViewPortSize;
*pScrollOffsetChange = 0.0f;
}
}
}
struct SCursorAndBoundingBox
{
vec2 m_TextSize;
float m_BiggestCharacterHeight;
int m_LineCount;
};
static SCursorAndBoundingBox CalcFontSizeCursorHeightAndBoundingBox(ITextRender *pTextRender, const char *pText, int Flags, float &Size, float MaxWidth, const SLabelProperties &LabelProps)
{
const float MinFontSize = 5.0f;
const float MaxTextWidth = LabelProps.m_MaxWidth != -1.0f ? LabelProps.m_MaxWidth : MaxWidth;
const int FlagsWithoutStop = Flags & ~(TEXTFLAG_STOP_AT_END | TEXTFLAG_ELLIPSIS_AT_END);
const float MaxTextWidthWithoutStop = Flags == FlagsWithoutStop ? LabelProps.m_MaxWidth : -1.0f;
float TextBoundingHeight = 0.0f;
float TextHeight = 0.0f;
int LineCount = 0;
STextSizeProperties TextSizeProps{};
TextSizeProps.m_pHeight = &TextHeight;
TextSizeProps.m_pMaxCharacterHeightInLine = &TextBoundingHeight;
TextSizeProps.m_pLineCount = &LineCount;
float TextWidth;
do
{
Size = maximum(Size, MinFontSize);
// Only consider stop-at-end and ellipsis-at-end when minimum font size reached or font scaling disabled
if((Size == MinFontSize || !LabelProps.m_EnableWidthCheck) && Flags != FlagsWithoutStop)
TextWidth = pTextRender->TextWidth(Size, pText, -1, LabelProps.m_MaxWidth, Flags, TextSizeProps);
else
TextWidth = pTextRender->TextWidth(Size, pText, -1, MaxTextWidthWithoutStop, FlagsWithoutStop, TextSizeProps);
if(TextWidth <= MaxTextWidth + 0.001f || !LabelProps.m_EnableWidthCheck || Size == MinFontSize)
break;
Size--;
} while(true);
SCursorAndBoundingBox Res{};
Res.m_TextSize = vec2(TextWidth, TextHeight);
Res.m_BiggestCharacterHeight = TextBoundingHeight;
Res.m_LineCount = LineCount;
return Res;
}
static int GetFlagsForLabelProperties(const SLabelProperties &LabelProps, const CTextCursor *pReadCursor)
{
if(pReadCursor != nullptr)
return pReadCursor->m_Flags & ~TEXTFLAG_RENDER;
int Flags = 0;
Flags |= LabelProps.m_StopAtEnd ? TEXTFLAG_STOP_AT_END : 0;
Flags |= LabelProps.m_EllipsisAtEnd ? TEXTFLAG_ELLIPSIS_AT_END : 0;
return Flags;
}
vec2 CUI::CalcAlignedCursorPos(const CUIRect *pRect, vec2 TextSize, int Align, const float *pBiggestCharHeight)
{
vec2 Cursor(pRect->x, pRect->y);
const int HorizontalAlign = Align & TEXTALIGN_MASK_HORIZONTAL;
if(HorizontalAlign == TEXTALIGN_CENTER)
{
Cursor.x += (pRect->w - TextSize.x) / 2.0f;
}
else if(HorizontalAlign == TEXTALIGN_RIGHT)
{
Cursor.x += pRect->w - TextSize.x;
}
const int VerticalAlign = Align & TEXTALIGN_MASK_VERTICAL;
if(VerticalAlign == TEXTALIGN_MIDDLE)
{
Cursor.y += pBiggestCharHeight != nullptr ? ((pRect->h - *pBiggestCharHeight) / 2.0f - (TextSize.y - *pBiggestCharHeight)) : (pRect->h - TextSize.y) / 2.0f;
}
else if(VerticalAlign == TEXTALIGN_BOTTOM)
{
Cursor.y += pRect->h - TextSize.y;
}
return Cursor;
}
void CUI::DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps)
{
const int Flags = GetFlagsForLabelProperties(LabelProps, nullptr);
const SCursorAndBoundingBox TextBounds = CalcFontSizeCursorHeightAndBoundingBox(TextRender(), pText, Flags, Size, pRect->w, LabelProps);
const vec2 CursorPos = CalcAlignedCursorPos(pRect, TextBounds.m_TextSize, Align, TextBounds.m_LineCount == 1 ? &TextBounds.m_BiggestCharacterHeight : nullptr);
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, CursorPos.x, CursorPos.y, Size, TEXTFLAG_RENDER | Flags);
Cursor.m_LineWidth = (float)LabelProps.m_MaxWidth;
TextRender()->TextEx(&Cursor, pText, -1);
}
void CUI::DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps, int StrLen, const CTextCursor *pReadCursor)
{
const int Flags = GetFlagsForLabelProperties(LabelProps, pReadCursor);
const SCursorAndBoundingBox TextBounds = CalcFontSizeCursorHeightAndBoundingBox(TextRender(), pText, Flags, Size, pRect->w, LabelProps);
CTextCursor Cursor;
if(pReadCursor)
{
Cursor = *pReadCursor;
}
else
{
const vec2 CursorPos = CalcAlignedCursorPos(pRect, TextBounds.m_TextSize, Align);
TextRender()->SetCursor(&Cursor, CursorPos.x, CursorPos.y, Size, TEXTFLAG_RENDER | Flags);
}
Cursor.m_LineWidth = LabelProps.m_MaxWidth;
RectEl.m_TextColor = TextRender()->GetTextColor();
RectEl.m_TextOutlineColor = TextRender()->GetTextOutlineColor();
TextRender()->TextColor(TextRender()->DefaultTextColor());
TextRender()->TextOutlineColor(TextRender()->DefaultTextOutlineColor());
TextRender()->CreateTextContainer(RectEl.m_UITextContainer, &Cursor, pText, StrLen);
TextRender()->TextColor(RectEl.m_TextColor);
TextRender()->TextOutlineColor(RectEl.m_TextOutlineColor);
RectEl.m_Cursor = Cursor;
}
void CUI::DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps, int StrLen, const CTextCursor *pReadCursor)
{
bool NeedsRecreate = false;
bool ColorChanged = RectEl.m_TextColor != TextRender()->GetTextColor() || RectEl.m_TextOutlineColor != TextRender()->GetTextOutlineColor();
if(!RectEl.m_UITextContainer.Valid() || RectEl.m_Width != pRect->w || RectEl.m_Height != pRect->h || ColorChanged)
{
NeedsRecreate = true;
}
else
{
if(StrLen <= -1)
{
if(str_comp(RectEl.m_Text.c_str(), pText) != 0)
NeedsRecreate = true;
}
else
{
if(StrLen != (int)RectEl.m_Text.size() || str_comp_num(RectEl.m_Text.c_str(), pText, StrLen) != 0)
NeedsRecreate = true;
}
}
RectEl.m_X = pRect->x;
RectEl.m_Y = pRect->y;
if(NeedsRecreate)
{
TextRender()->DeleteTextContainer(RectEl.m_UITextContainer);
RectEl.m_Width = pRect->w;
RectEl.m_Height = pRect->h;
if(StrLen > 0)
RectEl.m_Text = std::string(pText, StrLen);
else if(StrLen < 0)
RectEl.m_Text = pText;
else
RectEl.m_Text.clear();
CUIRect TmpRect;
TmpRect.x = 0;
TmpRect.y = 0;
TmpRect.w = pRect->w;
TmpRect.h = pRect->h;
DoLabel(RectEl, &TmpRect, pText, Size, TEXTALIGN_TL, LabelProps, StrLen, pReadCursor);
}
ColorRGBA ColorText(RectEl.m_TextColor);
ColorRGBA ColorTextOutline(RectEl.m_TextOutlineColor);
if(RectEl.m_UITextContainer.Valid())
{
const vec2 CursorPos = CalcAlignedCursorPos(pRect, vec2(RectEl.m_Cursor.m_LongestLineWidth, RectEl.m_Cursor.Height()), Align);
TextRender()->RenderTextContainer(RectEl.m_UITextContainer, ColorText, ColorTextOutline, CursorPos.x, CursorPos.y);
}
}
bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners)
{
const bool Inside = MouseHovered(pRect);
const bool Active = m_pLastActiveItem == pLineInput;
const bool Changed = pLineInput->WasChanged();
const float VSpacing = 2.0f;
CUIRect Textbox;
pRect->VMargin(VSpacing, &Textbox);
bool JustGotActive = false;
if(CheckActiveItem(pLineInput))
{
if(MouseButton(0))
{
if(pLineInput->IsActive() && (Input()->HasComposition() || Input()->GetCandidateCount()))
{
// Clear IME composition/candidates on mouse press
Input()->StopTextInput();
Input()->StartTextInput();
}
}
else
{
SetActiveItem(nullptr);
}
}
else if(HotItem() == pLineInput)
{
if(MouseButton(0))
{
if(!Active)
JustGotActive = true;
SetActiveItem(pLineInput);
}
}
if(Inside)
SetHotItem(pLineInput);
if(Enabled() && Active && !JustGotActive)
pLineInput->Activate(EInputPriority::UI);
else
pLineInput->Deactivate();
float ScrollOffset = pLineInput->GetScrollOffset();
float ScrollOffsetChange = pLineInput->GetScrollOffsetChange();
// Update mouse selection information
CLineInput::SMouseSelection *pMouseSelection = pLineInput->GetMouseSelection();
if(Inside)
{
if(!pMouseSelection->m_Selecting && MouseButtonClicked(0))
{
pMouseSelection->m_Selecting = true;
pMouseSelection->m_PressMouse = MousePos();
pMouseSelection->m_Offset.x = ScrollOffset;
}
}
if(pMouseSelection->m_Selecting)
{
pMouseSelection->m_ReleaseMouse = MousePos();
if(MouseButtonReleased(0))
{
pMouseSelection->m_Selecting = false;
}
}
if(ScrollOffset != pMouseSelection->m_Offset.x)
{
// When the scroll offset is changed, update the position that the mouse was pressed at,
// so the existing text selection still stays mostly the same.
// TODO: The selection may change by one character temporarily, due to different character widths.
// Needs text render adjustment: keep selection start based on character.
pMouseSelection->m_PressMouse.x -= ScrollOffset - pMouseSelection->m_Offset.x;
pMouseSelection->m_Offset.x = ScrollOffset;
}
// Render
pRect->Draw(ms_LightButtonColorFunction.GetColor(Active, HotItem() == pLineInput), Corners, 3.0f);
ClipEnable(pRect);
Textbox.x -= ScrollOffset;
const STextBoundingBox BoundingBox = pLineInput->Render(&Textbox, FontSize, TEXTALIGN_ML, Changed, -1.0f);
ClipDisable();
// Scroll left or right if necessary
if(Active && !JustGotActive && (Changed || Input()->HasComposition()))
{
const float CaretPositionX = pLineInput->GetCaretPosition().x - Textbox.x - ScrollOffset - ScrollOffsetChange;
if(CaretPositionX > Textbox.w)
ScrollOffsetChange += CaretPositionX - Textbox.w;
else if(CaretPositionX < 0.0f)
ScrollOffsetChange += CaretPositionX;
}
DoSmoothScrollLogic(&ScrollOffset, &ScrollOffsetChange, Textbox.w, BoundingBox.m_W, true);
pLineInput->SetScrollOffset(ScrollOffset);
pLineInput->SetScrollOffsetChange(ScrollOffsetChange);
return Changed;
}
bool CUI::DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners)
{
CUIRect EditBox, ClearButton;
pRect->VSplitRight(pRect->h, &EditBox, &ClearButton);
bool ReturnValue = DoEditBox(pLineInput, &EditBox, FontSize, Corners & ~IGraphics::CORNER_R);
ClearButton.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.33f * ButtonColorMul(pLineInput->GetClearButtonId())), Corners & ~IGraphics::CORNER_L, 3.0f);
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
DoLabel(&ClearButton, "×", ClearButton.h * CUI::ms_FontmodHeight * 0.8f, TEXTALIGN_MC);
TextRender()->SetRenderFlags(0);
if(DoButtonLogic(pLineInput->GetClearButtonId(), 0, &ClearButton))
{
pLineInput->Clear();
SetActiveItem(pLineInput);
ReturnValue = true;
}
return ReturnValue;
}
int CUI::DoButton_Menu(CUIElement &UIElement, const CButtonContainer *pID, const std::function<const char *()> &GetTextLambda, const CUIRect *pRect, const SMenuButtonProperties &Props)
{
CUIRect Text = *pRect, DropDownIcon;
Text.HMargin(pRect->h >= 20.0f ? 2.0f : 1.0f, &Text);
Text.HMargin((Text.h * Props.m_FontFactor) / 2.0f, &Text);
if(Props.m_ShowDropDownIcon)
{
Text.VSplitRight(pRect->h * 0.25f, &Text, nullptr);
Text.VSplitRight(pRect->h * 0.75f, &Text, &DropDownIcon);
}
if(!UIElement.AreRectsInit() || Props.m_HintRequiresStringCheck || Props.m_HintCanChangePositionOrSize || !UIElement.Rect(0)->m_UITextContainer.Valid())
{
bool NeedsRecalc = !UIElement.AreRectsInit() || !UIElement.Rect(0)->m_UITextContainer.Valid();
if(Props.m_HintCanChangePositionOrSize)
{
if(UIElement.AreRectsInit())
{
if(UIElement.Rect(0)->m_X != pRect->x || UIElement.Rect(0)->m_Y != pRect->y || UIElement.Rect(0)->m_Width != pRect->w || UIElement.Rect(0)->m_Height != pRect->h || UIElement.Rect(0)->m_Rounding != Props.m_Rounding || UIElement.Rect(0)->m_Corners != Props.m_Corners)
{
NeedsRecalc = true;
}
}
}
const char *pText = nullptr;
if(Props.m_HintRequiresStringCheck)
{
if(UIElement.AreRectsInit())
{
pText = GetTextLambda();
if(str_comp(UIElement.Rect(0)->m_Text.c_str(), pText) != 0)
{
NeedsRecalc = true;
}
}
}
if(NeedsRecalc)
{
if(!UIElement.AreRectsInit())
{
UIElement.InitRects(3);
}
ResetUIElement(UIElement);
for(int i = 0; i < 3; ++i)
{
ColorRGBA Color = Props.m_Color;
if(i == 0)
Color.a *= ButtonColorMulActive();
else if(i == 1)
Color.a *= ButtonColorMulHot();
else if(i == 2)
Color.a *= ButtonColorMulDefault();
Graphics()->SetColor(Color);
CUIElement::SUIElementRect &NewRect = *UIElement.Rect(i);
NewRect.m_UIRectQuadContainer = Graphics()->CreateRectQuadContainer(pRect->x, pRect->y, pRect->w, pRect->h, Props.m_Rounding, Props.m_Corners);
NewRect.m_X = pRect->x;
NewRect.m_Y = pRect->y;
NewRect.m_Width = pRect->w;
NewRect.m_Height = pRect->h;
NewRect.m_Rounding = Props.m_Rounding;
NewRect.m_Corners = Props.m_Corners;
if(i == 0)
{
if(pText == nullptr)
pText = GetTextLambda();
NewRect.m_Text = pText;
if(Props.m_UseIconFont)
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
DoLabel(NewRect, &Text, pText, Text.h * CUI::ms_FontmodHeight, TEXTALIGN_MC);
if(Props.m_UseIconFont)
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
}
}
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
}
}
// render
size_t Index = 2;
if(CheckActiveItem(pID))
Index = 0;
else if(HotItem() == pID)
Index = 1;
Graphics()->TextureClear();
Graphics()->RenderQuadContainer(UIElement.Rect(Index)->m_UIRectQuadContainer, -1);
if(Props.m_ShowDropDownIcon)
{
TextRender()->SetFontPreset(EFontPreset::ICON_FONT);
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
DoLabel(&DropDownIcon, FONT_ICON_CIRCLE_CHEVRON_DOWN, DropDownIcon.h * CUI::ms_FontmodHeight, TEXTALIGN_MR);
TextRender()->SetRenderFlags(0);
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
}
ColorRGBA ColorText(TextRender()->DefaultTextColor());
ColorRGBA ColorTextOutline(TextRender()->DefaultTextOutlineColor());
if(UIElement.Rect(0)->m_UITextContainer.Valid())
TextRender()->RenderTextContainer(UIElement.Rect(0)->m_UITextContainer, ColorText, ColorTextOutline);
return DoButtonLogic(pID, Props.m_Checked, pRect);
}
int CUI::DoButton_PopupMenu(CButtonContainer *pButtonContainer, const char *pText, const CUIRect *pRect, float Size, int Align, float Padding, bool TransparentInactive)
{
if(!TransparentInactive || CheckActiveItem(pButtonContainer) || HotItem() == pButtonContainer)
pRect->Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f * ButtonColorMul(pButtonContainer)), IGraphics::CORNER_ALL, 3.0f);
CUIRect Label;
pRect->Margin(Padding, &Label);
DoLabel(&Label, pText, Size, Align);
return DoButtonLogic(pButtonContainer, 0, pRect);
}
int64_t CUI::DoValueSelector(const void *pID, const CUIRect *pRect, const char *pLabel, int64_t Current, int64_t Min, int64_t Max, const SValueSelectorProperties &Props)
{
// logic
static float s_Value;
static CLineInputNumber s_NumberInput;
static const void *s_pLastTextID = pID;
const bool Inside = MouseInside(pRect);
if(Inside)
SetHotItem(pID);
const int Base = Props.m_IsHex ? 16 : 10;
if(MouseButtonReleased(1) && HotItem() == pID)
{
s_pLastTextID = pID;
m_ValueSelectorTextMode = true;
s_NumberInput.SetInteger64(Current, Base, Props.m_HexPrefix);
s_NumberInput.SelectAll();
}
if(CheckActiveItem(pID))
{
if(!MouseButton(0))
{
DisableMouseLock();
SetActiveItem(nullptr);
m_ValueSelectorTextMode = false;
}
}
if(m_ValueSelectorTextMode && s_pLastTextID == pID)
{
DoEditBox(&s_NumberInput, pRect, 10.0f);
SetActiveItem(&s_NumberInput);
if(ConsumeHotkey(HOTKEY_ENTER) || ((MouseButtonClicked(1) || MouseButtonClicked(0)) && !Inside))
{
Current = clamp(s_NumberInput.GetInteger64(Base), Min, Max);
DisableMouseLock();
SetActiveItem(nullptr);
m_ValueSelectorTextMode = false;
}
if(ConsumeHotkey(HOTKEY_ESCAPE))
{
DisableMouseLock();
SetActiveItem(nullptr);
m_ValueSelectorTextMode = false;
}
}
else
{
if(CheckActiveItem(pID))
{
if(Props.m_UseScroll)
{
if(MouseButton(0))
{
s_Value += MouseDeltaX() * (Input()->ShiftIsPressed() ? 0.05f : 1.0f);
if(absolute(s_Value) > Props.m_Scale)
{
const int64_t Count = (int64_t)(s_Value / Props.m_Scale);
s_Value = std::fmod(s_Value, Props.m_Scale);
Current += Props.m_Step * Count;
Current = clamp(Current, Min, Max);
// Constrain to discrete steps
if(Count > 0)
Current = Current / Props.m_Step * Props.m_Step;
else
Current = std::ceil(Current / (float)Props.m_Step) * Props.m_Step;
}
}
}
}
else if(HotItem() == pID)
{
if(MouseButtonClicked(0))
{
s_Value = 0;
SetActiveItem(pID);
if(Props.m_UseScroll)
EnableMouseLock(pID);
}
}
// render
char aBuf[128];
if(pLabel[0] != '\0')
{
if(Props.m_IsHex)
str_format(aBuf, sizeof(aBuf), "%s #%0*" PRIX64, pLabel, Props.m_HexPrefix, Current);
else
str_format(aBuf, sizeof(aBuf), "%s %" PRId64, pLabel, Current);
}
else
{
if(Props.m_IsHex)
str_format(aBuf, sizeof(aBuf), "#%0*" PRIX64, Props.m_HexPrefix, Current);
else
str_format(aBuf, sizeof(aBuf), "%" PRId64, Current);
}
pRect->Draw(Props.m_Color, IGraphics::CORNER_ALL, 3.0f);
DoLabel(pRect, aBuf, 10.0f, TEXTALIGN_MC);
}
if(!m_ValueSelectorTextMode)
s_NumberInput.Clear();
return Current;
}
float CUI::DoScrollbarV(const void *pID, const CUIRect *pRect, float Current)
{
Current = clamp(Current, 0.0f, 1.0f);
// layout
CUIRect Rail;
pRect->Margin(5.0f, &Rail);
CUIRect Handle;
Rail.HSplitTop(clamp(33.0f, Rail.w, Rail.h / 3.0f), &Handle, 0);
Handle.y = Rail.y + (Rail.h - Handle.h) * Current;
// logic
static float s_OffsetY;
const bool InsideRail = MouseHovered(&Rail);
const bool InsideHandle = MouseHovered(&Handle);
bool Grabbed = false; // whether to apply the offset
if(CheckActiveItem(pID))
{
if(MouseButton(0))
{
Grabbed = true;
if(Input()->ShiftIsPressed())
m_MouseSlow = true;
}
else
{
SetActiveItem(nullptr);
}
}
else if(HotItem() == pID)
{
if(MouseButton(0))
{
SetActiveItem(pID);
s_OffsetY = MouseY() - Handle.y;
Grabbed = true;
}
}
else if(MouseButtonClicked(0) && !InsideHandle && InsideRail)
{
SetActiveItem(pID);
s_OffsetY = Handle.h / 2.0f;
Grabbed = true;
}
if(InsideHandle)
{
SetHotItem(pID);
}
float ReturnValue = Current;
if(Grabbed)
{
const float Min = Rail.y;
const float Max = Rail.h - Handle.h;
const float Cur = MouseY() - s_OffsetY;
ReturnValue = clamp((Cur - Min) / Max, 0.0f, 1.0f);
}
// render
Rail.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, Rail.w / 2.0f);
Handle.Draw(ms_ScrollBarColorFunction.GetColor(CheckActiveItem(pID), HotItem() == pID), IGraphics::CORNER_ALL, Handle.w / 2.0f);
return ReturnValue;
}
float CUI::DoScrollbarH(const void *pID, const CUIRect *pRect, float Current, const ColorRGBA *pColorInner)
{
Current = clamp(Current, 0.0f, 1.0f);
// layout
CUIRect Rail;
if(pColorInner)
Rail = *pRect;
else
pRect->HMargin(5.0f, &Rail);
CUIRect Handle;
Rail.VSplitLeft(pColorInner ? 8.0f : clamp(33.0f, Rail.h, Rail.w / 3.0f), &Handle, 0);
Handle.x += (Rail.w - Handle.w) * Current;
// logic
static float s_OffsetX;
const bool InsideRail = MouseHovered(&Rail);
const bool InsideHandle = MouseHovered(&Handle);
bool Grabbed = false; // whether to apply the offset
if(CheckActiveItem(pID))
{
if(MouseButton(0))
{
Grabbed = true;
if(Input()->ShiftIsPressed())
m_MouseSlow = true;
}
else
{
SetActiveItem(nullptr);
}
}
else if(HotItem() == pID)
{
if(MouseButton(0))
{
SetActiveItem(pID);
s_OffsetX = MouseX() - Handle.x;
Grabbed = true;
}
}
else if(MouseButtonClicked(0) && !InsideHandle && InsideRail)
{
SetActiveItem(pID);
s_OffsetX = Handle.w / 2.0f;
Grabbed = true;
}
if(InsideHandle)
{
SetHotItem(pID);
}
float ReturnValue = Current;
if(Grabbed)
{
const float Min = Rail.x;
const float Max = Rail.w - Handle.w;
const float Cur = MouseX() - s_OffsetX;
ReturnValue = clamp((Cur - Min) / Max, 0.0f, 1.0f);
}
// render
if(pColorInner)
{
CUIRect Slider;
Handle.VMargin(-2.0f, &Slider);
Slider.HMargin(-3.0f, &Slider);
Slider.Draw(ColorRGBA(0.15f, 0.15f, 0.15f, 1.0f), IGraphics::CORNER_ALL, 5.0f);
Slider.Margin(2.0f, &Slider);
Slider.Draw(*pColorInner, IGraphics::CORNER_ALL, 3.0f);
}
else
{
Rail.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, Rail.h / 2.0f);
Handle.Draw(ms_ScrollBarColorFunction.GetColor(CheckActiveItem(pID), HotItem() == pID), IGraphics::CORNER_ALL, Handle.h / 2.0f);
}
return ReturnValue;
}
void CUI::DoScrollbarOption(const void *pID, int *pOption, const CUIRect *pRect, const char *pStr, int Min, int Max, const IScrollbarScale *pScale, unsigned Flags, const char *pSuffix)
{
const bool Infinite = Flags & CUI::SCROLLBAR_OPTION_INFINITE;
const bool NoClampValue = Flags & CUI::SCROLLBAR_OPTION_NOCLAMPVALUE;
const bool MultiLine = Flags & CUI::SCROLLBAR_OPTION_MULTILINE;
int Value = *pOption;
if(Infinite)
{
Max += 1;
if(Value == 0)
Value = Max;
}
char aBuf[256];
if(!Infinite || Value != Max)
str_format(aBuf, sizeof(aBuf), "%s: %i%s", pStr, Value, pSuffix);
else
str_format(aBuf, sizeof(aBuf), "%s: ∞", pStr);
if(NoClampValue)
{
// clamp the value internally for the scrollbar
Value = clamp(Value, Min, Max);
}
CUIRect Label, ScrollBar;
if(MultiLine)
pRect->HSplitMid(&Label, &ScrollBar);
else
pRect->VSplitMid(&Label, &ScrollBar, minimum(10.0f, pRect->w * 0.05f));
const float FontSize = Label.h * CUI::ms_FontmodHeight * 0.8f;
DoLabel(&Label, aBuf, FontSize, TEXTALIGN_ML);
Value = pScale->ToAbsolute(DoScrollbarH(pID, &ScrollBar, pScale->ToRelative(Value, Min, Max)), Min, Max);
if(NoClampValue && ((Value == Min && *pOption < Min) || (Value == Max && *pOption > Max)))
{
Value = *pOption; // use previous out of range value instead if the scrollbar is at the edge
}
else if(Infinite)
{
if(Value == Max)
Value = 0;
}
*pOption = Value;
}
void CUI::RenderProgressSpinner(vec2 Center, float OuterRadius, const SProgressSpinnerProperties &Props)
{
static float s_SpinnerOffset = 0.0f;
static float s_LastRender = Client()->LocalTime();
s_SpinnerOffset += (Client()->LocalTime() - s_LastRender) * 1.5f;
s_SpinnerOffset = std::fmod(s_SpinnerOffset, 1.0f);
Graphics()->TextureClear();
Graphics()->QuadsBegin();
// The filled and unfilled segments need to begin at the same angle offset
// or the differences in pixel alignment will make the filled segments flicker.
const float SegmentsAngle = 2.0f * pi / Props.m_Segments;
const float InnerRadius = OuterRadius * 0.75f;
const float AngleOffset = -0.5f * pi;
Graphics()->SetColor(Props.m_Color.WithMultipliedAlpha(0.5f));
for(int i = 0; i < Props.m_Segments; ++i)
{
const float Angle1 = AngleOffset + i * SegmentsAngle;
const float Angle2 = AngleOffset + (i + 1) * SegmentsAngle;
IGraphics::CFreeformItem Item = IGraphics::CFreeformItem(
Center.x + std::cos(Angle1) * InnerRadius, Center.y + std::sin(Angle1) * InnerRadius,
Center.x + std::cos(Angle2) * InnerRadius, Center.y + std::sin(Angle2) * InnerRadius,
Center.x + std::cos(Angle1) * OuterRadius, Center.y + std::sin(Angle1) * OuterRadius,
Center.x + std::cos(Angle2) * OuterRadius, Center.y + std::sin(Angle2) * OuterRadius);
Graphics()->QuadsDrawFreeform(&Item, 1);
}
const float FilledRatio = Props.m_Progress < 0.0f ? 0.333f : Props.m_Progress;
const int FilledSegmentOffset = Props.m_Progress < 0.0f ? round_to_int(s_SpinnerOffset * Props.m_Segments) : 0;
const int FilledNumSegments = minimum<int>(Props.m_Segments * FilledRatio + (Props.m_Progress < 0.0f ? 0 : 1), Props.m_Segments);
Graphics()->SetColor(Props.m_Color);
for(int i = 0; i < FilledNumSegments; ++i)
{
const float Angle1 = AngleOffset + (i + FilledSegmentOffset) * SegmentsAngle;
const float Angle2 = AngleOffset + ((i + 1 == FilledNumSegments && Props.m_Progress >= 0.0f) ? (2.0f * pi * Props.m_Progress) : ((i + FilledSegmentOffset + 1) * SegmentsAngle));
IGraphics::CFreeformItem Item = IGraphics::CFreeformItem(
Center.x + std::cos(Angle1) * InnerRadius, Center.y + std::sin(Angle1) * InnerRadius,
Center.x + std::cos(Angle2) * InnerRadius, Center.y + std::sin(Angle2) * InnerRadius,
Center.x + std::cos(Angle1) * OuterRadius, Center.y + std::sin(Angle1) * OuterRadius,
Center.x + std::cos(Angle2) * OuterRadius, Center.y + std::sin(Angle2) * OuterRadius);
Graphics()->QuadsDrawFreeform(&Item, 1);
}
Graphics()->QuadsEnd();
s_LastRender = Client()->LocalTime();
}
void CUI::DoPopupMenu(const SPopupMenuId *pID, int X, int Y, int Width, int Height, void *pContext, FPopupMenuFunction pfnFunc, const SPopupMenuProperties &Props)
{
constexpr float Margin = SPopupMenu::POPUP_BORDER + SPopupMenu::POPUP_MARGIN;
if(X + Width > Screen()->w - Margin)
X = maximum<float>(X - Width, Margin);
if(Y + Height > Screen()->h - Margin)
Y = maximum<float>(Y - Height, Margin);
m_vPopupMenus.emplace_back();
SPopupMenu *pNewMenu = &m_vPopupMenus.back();
pNewMenu->m_pID = pID;
pNewMenu->m_Props = Props;
pNewMenu->m_Rect.x = X;
pNewMenu->m_Rect.y = Y;
pNewMenu->m_Rect.w = Width;
pNewMenu->m_Rect.h = Height;
pNewMenu->m_pContext = pContext;
pNewMenu->m_pfnFunc = pfnFunc;
}
void CUI::RenderPopupMenus()
{
for(size_t i = 0; i < m_vPopupMenus.size(); ++i)
{
const SPopupMenu &PopupMenu = m_vPopupMenus[i];
const bool Inside = MouseInside(&PopupMenu.m_Rect);
const bool Active = i == m_vPopupMenus.size() - 1;
if(Active)
SetHotItem(PopupMenu.m_pID);
if(CheckActiveItem(PopupMenu.m_pID))
{
if(!MouseButton(0))
{
if(!Inside)
{
ClosePopupMenu(PopupMenu.m_pID);
}
SetActiveItem(nullptr);
}
}
else if(HotItem() == PopupMenu.m_pID)
{
if(MouseButton(0))
SetActiveItem(PopupMenu.m_pID);
}
CUIRect PopupRect = PopupMenu.m_Rect;
PopupRect.Draw(PopupMenu.m_Props.m_BorderColor, PopupMenu.m_Props.m_Corners, 3.0f);
PopupRect.Margin(SPopupMenu::POPUP_BORDER, &PopupRect);
PopupRect.Draw(PopupMenu.m_Props.m_BackgroundColor, PopupMenu.m_Props.m_Corners, 3.0f);
PopupRect.Margin(SPopupMenu::POPUP_MARGIN, &PopupRect);
EPopupMenuFunctionResult Result = PopupMenu.m_pfnFunc(PopupMenu.m_pContext, PopupRect, Active);
if(Result != POPUP_KEEP_OPEN || (Active && ConsumeHotkey(HOTKEY_ESCAPE)))
ClosePopupMenu(PopupMenu.m_pID, Result == POPUP_CLOSE_CURRENT_AND_DESCENDANTS);
}
}
void CUI::ClosePopupMenu(const SPopupMenuId *pID, bool IncludeDescendants)
{
auto PopupMenuToClose = std::find_if(m_vPopupMenus.begin(), m_vPopupMenus.end(), [pID](const SPopupMenu PopupMenu) { return PopupMenu.m_pID == pID; });
if(PopupMenuToClose != m_vPopupMenus.end())
{
if(IncludeDescendants)
m_vPopupMenus.erase(PopupMenuToClose, m_vPopupMenus.end());
else
m_vPopupMenus.erase(PopupMenuToClose);
SetActiveItem(nullptr);
if(m_pfnPopupMenuClosedCallback)
m_pfnPopupMenuClosedCallback();
}
}
void CUI::ClosePopupMenus()
{
if(m_vPopupMenus.empty())
return;
m_vPopupMenus.clear();
SetActiveItem(nullptr);
if(m_pfnPopupMenuClosedCallback)
m_pfnPopupMenuClosedCallback();
}
bool CUI::IsPopupOpen() const
{
return !m_vPopupMenus.empty();
}
bool CUI::IsPopupOpen(const SPopupMenuId *pID) const
{
return std::any_of(m_vPopupMenus.begin(), m_vPopupMenus.end(), [pID](const SPopupMenu PopupMenu) { return PopupMenu.m_pID == pID; });
}
bool CUI::IsPopupHovered() const
{
return std::any_of(m_vPopupMenus.begin(), m_vPopupMenus.end(), [this](const SPopupMenu PopupMenu) { return MouseHovered(&PopupMenu.m_Rect); });
}
void CUI::SetPopupMenuClosedCallback(FPopupMenuClosedCallback pfnCallback)
{
m_pfnPopupMenuClosedCallback = std::move(pfnCallback);
}
void CUI::SMessagePopupContext::DefaultColor(ITextRender *pTextRender)
{
m_TextColor = pTextRender->DefaultTextColor();
}
void CUI::SMessagePopupContext::ErrorColor()
{
m_TextColor = ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f);
}
CUI::EPopupMenuFunctionResult CUI::PopupMessage(void *pContext, CUIRect View, bool Active)
{
SMessagePopupContext *pMessagePopup = static_cast<SMessagePopupContext *>(pContext);
CUI *pUI = pMessagePopup->m_pUI;
pUI->TextRender()->TextColor(pMessagePopup->m_TextColor);
pUI->TextRender()->Text(View.x, View.y, SMessagePopupContext::POPUP_FONT_SIZE, pMessagePopup->m_aMessage, View.w);
pUI->TextRender()->TextColor(pUI->TextRender()->DefaultTextColor());
return (Active && pUI->ConsumeHotkey(HOTKEY_ENTER)) ? CUI::POPUP_CLOSE_CURRENT : CUI::POPUP_KEEP_OPEN;
}
void CUI::ShowPopupMessage(float X, float Y, SMessagePopupContext *pContext)
{
const float TextWidth = minimum(std::ceil(TextRender()->TextWidth(SMessagePopupContext::POPUP_FONT_SIZE, pContext->m_aMessage, -1, -1.0f) + 0.5f), SMessagePopupContext::POPUP_MAX_WIDTH);
float TextHeight = 0.0f;
STextSizeProperties TextSizeProps{};
TextSizeProps.m_pHeight = &TextHeight;
TextRender()->TextWidth(SMessagePopupContext::POPUP_FONT_SIZE, pContext->m_aMessage, -1, TextWidth, 0, TextSizeProps);
pContext->m_pUI = this;
DoPopupMenu(pContext, X, Y, TextWidth + 10.0f, TextHeight + 10.0f, pContext, PopupMessage);
}
CUI::SConfirmPopupContext::SConfirmPopupContext()
{
Reset();
}
void CUI::SConfirmPopupContext::Reset()
{
m_Result = SConfirmPopupContext::UNSET;
}
void CUI::SConfirmPopupContext::YesNoButtons()
{
str_copy(m_aPositiveButtonLabel, Localize("Yes"));
str_copy(m_aNegativeButtonLabel, Localize("No"));
}
void CUI::ShowPopupConfirm(float X, float Y, SConfirmPopupContext *pContext)
{
const float TextWidth = minimum(std::ceil(TextRender()->TextWidth(SConfirmPopupContext::POPUP_FONT_SIZE, pContext->m_aMessage, -1, -1.0f) + 0.5f), SConfirmPopupContext::POPUP_MAX_WIDTH);
float TextHeight = 0.0f;
STextSizeProperties TextSizeProps{};
TextSizeProps.m_pHeight = &TextHeight;
TextRender()->TextWidth(SConfirmPopupContext::POPUP_FONT_SIZE, pContext->m_aMessage, -1, TextWidth, 0, TextSizeProps);
const float PopupHeight = TextHeight + SConfirmPopupContext::POPUP_BUTTON_HEIGHT + SConfirmPopupContext::POPUP_BUTTON_SPACING + 10.0f;
pContext->m_pUI = this;
pContext->m_Result = SConfirmPopupContext::UNSET;
DoPopupMenu(pContext, X, Y, TextWidth + 10.0f, PopupHeight, pContext, PopupConfirm);
}
CUI::EPopupMenuFunctionResult CUI::PopupConfirm(void *pContext, CUIRect View, bool Active)
{
SConfirmPopupContext *pConfirmPopup = static_cast<SConfirmPopupContext *>(pContext);
CUI *pUI = pConfirmPopup->m_pUI;
CUIRect Label, ButtonBar, CancelButton, ConfirmButton;
View.HSplitBottom(SConfirmPopupContext::POPUP_BUTTON_HEIGHT, &Label, &ButtonBar);
ButtonBar.VSplitMid(&CancelButton, &ConfirmButton, SConfirmPopupContext::POPUP_BUTTON_SPACING);
pUI->TextRender()->Text(Label.x, Label.y, SConfirmPopupContext::POPUP_FONT_SIZE, pConfirmPopup->m_aMessage, Label.w);
if(pUI->DoButton_PopupMenu(&pConfirmPopup->m_CancelButton, pConfirmPopup->m_aNegativeButtonLabel, &CancelButton, SConfirmPopupContext::POPUP_FONT_SIZE, TEXTALIGN_MC))
{
pConfirmPopup->m_Result = SConfirmPopupContext::CANCELED;
return CUI::POPUP_CLOSE_CURRENT;
}
if(pUI->DoButton_PopupMenu(&pConfirmPopup->m_ConfirmButton, pConfirmPopup->m_aPositiveButtonLabel, &ConfirmButton, SConfirmPopupContext::POPUP_FONT_SIZE, TEXTALIGN_MC) || (Active && pUI->ConsumeHotkey(HOTKEY_ENTER)))
{
pConfirmPopup->m_Result = SConfirmPopupContext::CONFIRMED;
return CUI::POPUP_CLOSE_CURRENT;
}
return CUI::POPUP_KEEP_OPEN;
}
CUI::SSelectionPopupContext::SSelectionPopupContext()
{
Reset();
}
void CUI::SSelectionPopupContext::Reset()
{
m_Props = SPopupMenuProperties();
m_aMessage[0] = '\0';
m_pSelection = nullptr;
m_SelectionIndex = -1;
m_vEntries.clear();
m_vButtonContainers.clear();
m_EntryHeight = 12.0f;
m_EntryPadding = 0.0f;
m_EntrySpacing = 5.0f;
m_FontSize = 10.0f;
m_Width = 300.0f + (SPopupMenu::POPUP_BORDER + SPopupMenu::POPUP_MARGIN) * 2;
m_AlignmentHeight = -1.0f;
m_TransparentButtons = false;
}
CUI::EPopupMenuFunctionResult CUI::PopupSelection(void *pContext, CUIRect View, bool Active)
{
SSelectionPopupContext *pSelectionPopup = static_cast<SSelectionPopupContext *>(pContext);
CUI *pUI = pSelectionPopup->m_pUI;
CScrollRegion *pScrollRegion = pSelectionPopup->m_pScrollRegion;
vec2 ScrollOffset(0.0f, 0.0f);
CScrollRegionParams ScrollParams;
ScrollParams.m_ScrollbarWidth = 10.0f;
ScrollParams.m_ScrollbarMargin = SPopupMenu::POPUP_MARGIN;
ScrollParams.m_ScrollbarNoMarginRight = true;
ScrollParams.m_ScrollUnit = 3 * (pSelectionPopup->m_EntryHeight + pSelectionPopup->m_EntrySpacing);
pScrollRegion->Begin(&View, &ScrollOffset, &ScrollParams);
View.y += ScrollOffset.y;
CUIRect Slot;
if(pSelectionPopup->m_aMessage[0] != '\0')
{
const STextBoundingBox TextBoundingBox = pUI->TextRender()->TextBoundingBox(pSelectionPopup->m_FontSize, pSelectionPopup->m_aMessage, -1, pSelectionPopup->m_Width);
View.HSplitTop(TextBoundingBox.m_H, &Slot, &View);
if(pScrollRegion->AddRect(Slot))
{
pUI->TextRender()->Text(Slot.x, Slot.y, pSelectionPopup->m_FontSize, pSelectionPopup->m_aMessage, Slot.w);
}
}
pSelectionPopup->m_vButtonContainers.resize(pSelectionPopup->m_vEntries.size());
size_t Index = 0;
for(const auto &Entry : pSelectionPopup->m_vEntries)
{
if(pSelectionPopup->m_aMessage[0] != '\0' || Index != 0)
View.HSplitTop(pSelectionPopup->m_EntrySpacing, nullptr, &View);
View.HSplitTop(pSelectionPopup->m_EntryHeight, &Slot, &View);
if(pScrollRegion->AddRect(Slot))
{
if(pUI->DoButton_PopupMenu(&pSelectionPopup->m_vButtonContainers[Index], Entry.c_str(), &Slot, pSelectionPopup->m_FontSize, TEXTALIGN_ML, pSelectionPopup->m_EntryPadding, pSelectionPopup->m_TransparentButtons))
{
pSelectionPopup->m_pSelection = &Entry;
pSelectionPopup->m_SelectionIndex = Index;
}
}
++Index;
}
pScrollRegion->End();
return pSelectionPopup->m_pSelection == nullptr ? CUI::POPUP_KEEP_OPEN : CUI::POPUP_CLOSE_CURRENT;
}
void CUI::ShowPopupSelection(float X, float Y, SSelectionPopupContext *pContext)
{
const STextBoundingBox TextBoundingBox = TextRender()->TextBoundingBox(pContext->m_FontSize, pContext->m_aMessage, -1, pContext->m_Width);
const float PopupHeight = minimum((pContext->m_aMessage[0] == '\0' ? -pContext->m_EntrySpacing : TextBoundingBox.m_H) + pContext->m_vEntries.size() * (pContext->m_EntryHeight + pContext->m_EntrySpacing) + (SPopupMenu::POPUP_BORDER + SPopupMenu::POPUP_MARGIN) * 2 + CScrollRegion::HEIGHT_MAGIC_FIX, Screen()->h * 0.4f);
pContext->m_pUI = this;
pContext->m_pSelection = nullptr;
pContext->m_SelectionIndex = -1;
pContext->m_Props.m_Corners = IGraphics::CORNER_ALL;
if(pContext->m_AlignmentHeight >= 0.0f)
{
constexpr float Margin = SPopupMenu::POPUP_BORDER + SPopupMenu::POPUP_MARGIN;
if(X + pContext->m_Width > Screen()->w - Margin)
{
X = maximum<float>(X - pContext->m_Width, Margin);
}
if(Y + pContext->m_AlignmentHeight + PopupHeight > Screen()->h - Margin)
{
Y -= PopupHeight;
pContext->m_Props.m_Corners = IGraphics::CORNER_T;
}
else
{
Y += pContext->m_AlignmentHeight;
pContext->m_Props.m_Corners = IGraphics::CORNER_B;
}
}
DoPopupMenu(pContext, X, Y, pContext->m_Width, PopupHeight, pContext, PopupSelection, pContext->m_Props);
}
int CUI::DoDropDown(CUIRect *pRect, int CurSelection, const char **pStrs, int Num, SDropDownState &State)
{
if(!State.m_Init)
{
State.m_UiElement.Init(this, -1);
State.m_Init = true;
}
const auto LabelFunc = [CurSelection, pStrs]() {
return CurSelection > -1 ? pStrs[CurSelection] : "";
};
SMenuButtonProperties Props;
Props.m_HintRequiresStringCheck = true;
Props.m_HintCanChangePositionOrSize = true;
Props.m_ShowDropDownIcon = true;
if(IsPopupOpen(&State.m_SelectionPopupContext))
Props.m_Corners = IGraphics::CORNER_ALL & (~State.m_SelectionPopupContext.m_Props.m_Corners);
if(DoButton_Menu(State.m_UiElement, &State.m_ButtonContainer, LabelFunc, pRect, Props))
{
State.m_SelectionPopupContext.Reset();
State.m_SelectionPopupContext.m_Props.m_BorderColor = ColorRGBA(0.7f, 0.7f, 0.7f, 0.9f);
State.m_SelectionPopupContext.m_Props.m_BackgroundColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f);
for(int i = 0; i < Num; ++i)
State.m_SelectionPopupContext.m_vEntries.emplace_back(pStrs[i]);
State.m_SelectionPopupContext.m_EntryHeight = pRect->h;
State.m_SelectionPopupContext.m_EntryPadding = pRect->h >= 20.0f ? 2.0f : 1.0f;
State.m_SelectionPopupContext.m_FontSize = (State.m_SelectionPopupContext.m_EntryHeight - 2 * State.m_SelectionPopupContext.m_EntryPadding) * CUI::ms_FontmodHeight;
State.m_SelectionPopupContext.m_Width = pRect->w;
State.m_SelectionPopupContext.m_AlignmentHeight = pRect->h;
State.m_SelectionPopupContext.m_TransparentButtons = true;
ShowPopupSelection(pRect->x, pRect->y, &State.m_SelectionPopupContext);
}
if(State.m_SelectionPopupContext.m_SelectionIndex >= 0)
{
const int NewSelection = State.m_SelectionPopupContext.m_SelectionIndex;
State.m_SelectionPopupContext.Reset();
return NewSelection;
}
return CurSelection;
}
CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View, bool Active)
{
SColorPickerPopupContext *pColorPicker = static_cast<SColorPickerPopupContext *>(pContext);
CUI *pUI = pColorPicker->m_pUI;
CUIRect ColorsArea = View, HueArea, BottomArea, HueRect, SatRect, ValueRect, HexRect, AlphaRect;
ColorsArea.HSplitBottom(View.h - 140.0f, &ColorsArea, &BottomArea);
ColorsArea.VSplitRight(20.0f, &ColorsArea, &HueArea);
BottomArea.HSplitTop(3.0f, nullptr, &BottomArea);
HueArea.VSplitLeft(3.0f, nullptr, &HueArea);
BottomArea.HSplitTop(20.0f, &HueRect, &BottomArea);
BottomArea.HSplitTop(3.0f, nullptr, &BottomArea);
constexpr float ValuePadding = 5.0f;
const float HsvValueWidth = (HueRect.w - ValuePadding * 2) / 3.0f;
const float HexValueWidth = HsvValueWidth * 2 + ValuePadding;
HueRect.VSplitLeft(HsvValueWidth, &HueRect, &SatRect);
SatRect.VSplitLeft(ValuePadding, nullptr, &SatRect);
SatRect.VSplitLeft(HsvValueWidth, &SatRect, &ValueRect);
ValueRect.VSplitLeft(ValuePadding, nullptr, &ValueRect);
BottomArea.HSplitTop(20.0f, &HexRect, &BottomArea);
HexRect.VSplitLeft(HexValueWidth, &HexRect, &AlphaRect);
AlphaRect.VSplitLeft(ValuePadding, nullptr, &AlphaRect);
const ColorRGBA BlackColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f);
HueArea.Draw(BlackColor, IGraphics::CORNER_NONE, 0.0f);
HueArea.Margin(1.0f, &HueArea);
ColorsArea.Draw(BlackColor, IGraphics::CORNER_NONE, 0.0f);
ColorsArea.Margin(1.0f, &ColorsArea);
ColorHSVA PickerColorHSV = pColorPicker->m_HsvaColor;
unsigned H = (unsigned)(PickerColorHSV.x * 255.0f);
unsigned S = (unsigned)(PickerColorHSV.y * 255.0f);
unsigned V = (unsigned)(PickerColorHSV.z * 255.0f);
unsigned A = (unsigned)(PickerColorHSV.a * 255.0f);
// Color Area
ColorRGBA TL, TR, BL, BR;
TL = BL = color_cast<ColorRGBA>(ColorHSVA(PickerColorHSV.x, 0.0f, 1.0f));
TR = BR = color_cast<ColorRGBA>(ColorHSVA(PickerColorHSV.x, 1.0f, 1.0f));
ColorsArea.Draw4(TL, TR, BL, BR, IGraphics::CORNER_NONE, 0.0f);
TL = TR = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
BL = BR = ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f);
ColorsArea.Draw4(TL, TR, BL, BR, IGraphics::CORNER_NONE, 0.0f);
// Hue Area
static const float s_aaColorIndices[7][3] = {
{1.0f, 0.0f, 0.0f}, // red
{1.0f, 0.0f, 1.0f}, // magenta
{0.0f, 0.0f, 1.0f}, // blue
{0.0f, 1.0f, 1.0f}, // cyan
{0.0f, 1.0f, 0.0f}, // green
{1.0f, 1.0f, 0.0f}, // yellow
{1.0f, 0.0f, 0.0f}, // red
};
const float HuePickerOffset = HueArea.h / 6.0f;
CUIRect HuePartialArea = HueArea;
HuePartialArea.h = HuePickerOffset;
for(size_t j = 0; j < std::size(s_aaColorIndices) - 1; j++)
{
TL = ColorRGBA(s_aaColorIndices[j][0], s_aaColorIndices[j][1], s_aaColorIndices[j][2], 1.0f);
BL = ColorRGBA(s_aaColorIndices[j + 1][0], s_aaColorIndices[j + 1][1], s_aaColorIndices[j + 1][2], 1.0f);
HuePartialArea.y = HueArea.y + HuePickerOffset * j;
HuePartialArea.Draw4(TL, TL, BL, BL, IGraphics::CORNER_NONE, 0.0f);
}
// Editboxes Area
H = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", H, 0, 255);
S = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", S, 0, 255);
V = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "V:", V, 0, 255);
if(pColorPicker->m_Alpha)
{
A = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[3], &AlphaRect, "A:", A, 0, 255);
}
else
{
char aBuf[8];
str_format(aBuf, sizeof(aBuf), "A: %d", A);
pUI->DoLabel(&AlphaRect, aBuf, 10.0f, TEXTALIGN_MC);
AlphaRect.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.65f), IGraphics::CORNER_ALL, 3.0f);
}
PickerColorHSV = ColorHSVA(H / 255.0f, S / 255.0f, V / 255.0f, A / 255.0f);
SValueSelectorProperties Props;
Props.m_UseScroll = false;
Props.m_IsHex = true;
Props.m_HexPrefix = pColorPicker->m_Alpha ? 8 : 6;
const unsigned Hex = color_cast<ColorRGBA>(PickerColorHSV).PackAlphaLast(pColorPicker->m_Alpha);
const unsigned NewHex = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[4], &HexRect, "Hex:", Hex, 0, pColorPicker->m_Alpha ? 0xFFFFFFFFll : 0xFFFFFFll, Props);
if(Hex != NewHex)
{
PickerColorHSV = color_cast<ColorHSVA>(ColorRGBA::UnpackAlphaLast<ColorRGBA>(NewHex, pColorPicker->m_Alpha));
if(!pColorPicker->m_Alpha)
PickerColorHSV.a = A / 255.0f;
}
// Logic
float PickerX, PickerY;
if(pUI->DoPickerLogic(&pColorPicker->m_ColorPickerId, &ColorsArea, &PickerX, &PickerY))
{
PickerColorHSV.y = PickerX / ColorsArea.w;
PickerColorHSV.z = 1.0f - PickerY / ColorsArea.h;
}
if(pUI->DoPickerLogic(&pColorPicker->m_HuePickerId, &HueArea, &PickerX, &PickerY))
PickerColorHSV.x = 1.0f - PickerY / HueArea.h;
// Marker Color Area
const float MarkerX = ColorsArea.x + ColorsArea.w * PickerColorHSV.y;
const float MarkerY = ColorsArea.y + ColorsArea.h * (1.0f - PickerColorHSV.z);
const float MarkerOutlineInd = PickerColorHSV.z > 0.5f ? 0.0f : 1.0f;
const ColorRGBA MarkerOutline = ColorRGBA(MarkerOutlineInd, MarkerOutlineInd, MarkerOutlineInd, 1.0f);
pUI->Graphics()->TextureClear();
pUI->Graphics()->QuadsBegin();
pUI->Graphics()->SetColor(MarkerOutline);
pUI->Graphics()->DrawCircle(MarkerX, MarkerY, 4.5f, 32);
pUI->Graphics()->SetColor(color_cast<ColorRGBA>(PickerColorHSV));
pUI->Graphics()->DrawCircle(MarkerX, MarkerY, 3.5f, 32);
pUI->Graphics()->QuadsEnd();
// Marker Hue Area
CUIRect HueMarker;
HueArea.Margin(-2.5f, &HueMarker);
HueMarker.h = 6.5f;
HueMarker.y = (HueArea.y + HueArea.h * (1.0f - PickerColorHSV.x)) - HueMarker.h / 2.0f;
const ColorRGBA HueMarkerColor = color_cast<ColorRGBA>(ColorHSVA(PickerColorHSV.x, 1.0f, 1.0f, 1.0f));
const float HueMarkerOutlineColor = PickerColorHSV.x > 0.75f ? 1.0f : 0.0f;
const ColorRGBA HueMarkerOutline = ColorRGBA(HueMarkerOutlineColor, HueMarkerOutlineColor, HueMarkerOutlineColor, 1.0f);
HueMarker.Draw(HueMarkerOutline, IGraphics::CORNER_ALL, 1.2f);
HueMarker.Margin(1.2f, &HueMarker);
HueMarker.Draw(HueMarkerColor, IGraphics::CORNER_ALL, 1.2f);
pColorPicker->m_HsvaColor = PickerColorHSV;
if(pColorPicker->m_pHslaColor != nullptr)
*pColorPicker->m_pHslaColor = color_cast<ColorHSLA>(PickerColorHSV).Pack(pColorPicker->m_Alpha);
return CUI::POPUP_KEEP_OPEN;
}
void CUI::ShowPopupColorPicker(float X, float Y, SColorPickerPopupContext *pContext)
{
pContext->m_pUI = this;
DoPopupMenu(pContext, X, Y, 160.0f + 10.0f, 186.0f + 10.0f, pContext, PopupColorPicker);
}