From 78217c44d7ed1d553247a553ff8efb9e95e2b890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 9 Jul 2023 10:37:49 +0200 Subject: [PATCH] Add mode selection to color picker popups (RGBA, HSVA, HSLA) Add buttons for selecting the color mode (RGBA, HSVA, HSLA) in color picker popups in menu and editor. Previously, the color picker popups always showed the color components in HSVA. Now the mode can be switched so the components are shown in either RGBA or HSLA. The hex color code is always RGBA, same as before. In the editor, the RGBA mode is used per default, to prevent any loss of information due to color conversions, as the map format stores RGBA values. In the menus, the HSVA mode is the default, same as before, although the config actually stores HSLA values. The HSVA color picker controls (1D slider for hue, 2D picker for saturation and value) are still shown for all modes. As many color conversions as possible are avoided by only converting between color formats when necessary. Switching between color modes should not change the selected color. Changing the RGBA hex value in the editor will set the exact value also when another color mode is selected. However, changing the color with the other controls in HSVA/HSLA mode will cause a conversion and possible loss of information. The conversion directly from RGBA to HSVA and vice versa is also explicitly avoided, as this internally converts to HSLA first, which is done separately already. Closes #7409. --- src/game/client/components/menus.cpp | 2 + src/game/client/ui.cpp | 136 ++++++++++++++++++++++----- src/game/client/ui.h | 13 +++ src/game/editor/editor.cpp | 9 +- 4 files changed, 132 insertions(+), 28 deletions(-) diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index 81aedce8c..87ff08c28 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -411,7 +411,9 @@ ColorHSLA CMenus::DoButton_ColorPicker(const CUIRect *pRect, unsigned int *pHsla if(UI()->DoButtonLogic(pHslaColor, 0, pRect)) { s_ColorPickerPopupContext.m_pHslaColor = pHslaColor; + s_ColorPickerPopupContext.m_HslaColor = HslaColor; s_ColorPickerPopupContext.m_HsvaColor = color_cast(HslaColor); + s_ColorPickerPopupContext.m_RgbaColor = color_cast(s_ColorPickerPopupContext.m_HsvaColor); s_ColorPickerPopupContext.m_Alpha = Alpha; UI()->ShowPopupColorPicker(UI()->MouseX(), UI()->MouseY(), &s_ColorPickerPopupContext); } diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index e156f16f9..a02b4d238 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -1687,9 +1687,9 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View SColorPickerPopupContext *pColorPicker = static_cast(pContext); CUI *pUI = pColorPicker->m_pUI; - CUIRect ColorsArea = View, HueArea, BottomArea, HueRect, SatRect, ValueRect, HexRect, AlphaRect; + CUIRect ColorsArea, HueArea, BottomArea, ModeButtonArea, HueRect, SatRect, ValueRect, HexRect, AlphaRect; - ColorsArea.HSplitBottom(View.h - 140.0f, &ColorsArea, &BottomArea); + View.HSplitTop(140.0f, &ColorsArea, &BottomArea); ColorsArea.VSplitRight(20.0f, &ColorsArea, &HueArea); BottomArea.HSplitTop(3.0f, nullptr, &BottomArea); @@ -1708,8 +1708,10 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View ValueRect.VSplitLeft(ValuePadding, nullptr, &ValueRect); BottomArea.HSplitTop(20.0f, &HexRect, &BottomArea); + BottomArea.HSplitTop(3.0f, nullptr, &BottomArea); HexRect.VSplitLeft(HexValueWidth, &HexRect, &AlphaRect); AlphaRect.VSplitLeft(ValuePadding, nullptr, &AlphaRect); + BottomArea.HSplitTop(20.0f, &ModeButtonArea, &BottomArea); const ColorRGBA BlackColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f); @@ -1720,10 +1722,8 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View 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); + ColorRGBA PickerColorRGB = pColorPicker->m_RgbaColor; + ColorHSLA PickerColorHSL = pColorPicker->m_HslaColor; // Color Area ColorRGBA TL, TR, BL, BR; @@ -1759,35 +1759,98 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View HuePartialArea.Draw4(TL, TL, BL, BL, IGraphics::CORNER_NONE, 0.0f); } + const auto &&RenderAlphaSelector = [&](unsigned OldA) -> unsigned { + if(pColorPicker->m_Alpha) + { + return pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[3], &AlphaRect, "A:", OldA, 0, 255); + } + else + { + char aBuf[8]; + str_format(aBuf, sizeof(aBuf), "A: %d", OldA); + pUI->DoLabel(&AlphaRect, aBuf, 10.0f, TEXTALIGN_MC); + AlphaRect.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.65f), IGraphics::CORNER_ALL, 3.0f); + return OldA; + } + }; + // 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) + if(pColorPicker->m_ColorMode == SColorPickerPopupContext::MODE_HSVA) { - A = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[3], &AlphaRect, "A:", A, 0, 255); + const unsigned OldH = (unsigned)(PickerColorHSV.h * 255.0f); + const unsigned OldS = (unsigned)(PickerColorHSV.s * 255.0f); + const unsigned OldV = (unsigned)(PickerColorHSV.v * 255.0f); + const unsigned OldA = (unsigned)(PickerColorHSV.a * 255.0f); + + const unsigned H = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", OldH, 0, 255); + const unsigned S = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", OldS, 0, 255); + const unsigned V = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "V:", OldV, 0, 255); + const unsigned A = RenderAlphaSelector(OldA); + + if(OldH != H || OldS != S || OldV != V || OldA != A) + { + PickerColorHSV = ColorHSVA(H / 255.0f, S / 255.0f, V / 255.0f, A / 255.0f); + PickerColorHSL = color_cast(PickerColorHSV); + PickerColorRGB = color_cast(PickerColorHSL); + } + } + else if(pColorPicker->m_ColorMode == SColorPickerPopupContext::MODE_RGBA) + { + const unsigned OldR = (unsigned)(PickerColorRGB.r * 255.0f); + const unsigned OldG = (unsigned)(PickerColorRGB.g * 255.0f); + const unsigned OldB = (unsigned)(PickerColorRGB.b * 255.0f); + const unsigned OldA = (unsigned)(PickerColorRGB.a * 255.0f); + + const unsigned R = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "R:", OldR, 0, 255); + const unsigned G = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "G:", OldG, 0, 255); + const unsigned B = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "B:", OldB, 0, 255); + const unsigned A = RenderAlphaSelector(OldA); + + if(OldR != R || OldG != G || OldB != B || OldA != A) + { + PickerColorRGB = ColorRGBA(R / 255.0f, G / 255.0f, B / 255.0f, A / 255.0f); + PickerColorHSL = color_cast(PickerColorRGB); + PickerColorHSV = color_cast(PickerColorHSL); + } + } + else if(pColorPicker->m_ColorMode == SColorPickerPopupContext::MODE_HSLA) + { + const unsigned OldH = (unsigned)(PickerColorHSL.h * 255.0f); + const unsigned OldS = (unsigned)(PickerColorHSL.s * 255.0f); + const unsigned OldL = (unsigned)(PickerColorHSL.l * 255.0f); + const unsigned OldA = (unsigned)(PickerColorHSL.a * 255.0f); + + const unsigned H = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[0], &HueRect, "H:", OldH, 0, 255); + const unsigned S = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[1], &SatRect, "S:", OldS, 0, 255); + const unsigned L = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[2], &ValueRect, "L:", OldL, 0, 255); + const unsigned A = RenderAlphaSelector(OldA); + + if(OldH != H || OldS != S || OldL != L || OldA != A) + { + PickerColorHSL = ColorHSLA(H / 255.0f, S / 255.0f, L / 255.0f, A / 255.0f); + PickerColorHSV = color_cast(PickerColorHSL); + PickerColorRGB = color_cast(PickerColorHSL); + } } 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); + dbg_assert(false, "Color picker mode invalid"); } - 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(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) + const unsigned OldHex = PickerColorRGB.PackAlphaLast(pColorPicker->m_Alpha); + const unsigned Hex = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[4], &HexRect, "Hex:", OldHex, 0, pColorPicker->m_Alpha ? 0xFFFFFFFFll : 0xFFFFFFll, Props); + if(OldHex != Hex) { - PickerColorHSV = color_cast(ColorRGBA::UnpackAlphaLast(NewHex, pColorPicker->m_Alpha)); + const float OldAlpha = PickerColorRGB.a; + PickerColorRGB = ColorRGBA::UnpackAlphaLast(Hex, pColorPicker->m_Alpha); if(!pColorPicker->m_Alpha) - PickerColorHSV.a = A / 255.0f; + PickerColorRGB.a = OldAlpha; + PickerColorHSL = color_cast(PickerColorRGB); + PickerColorHSV = color_cast(PickerColorHSL); } // Logic @@ -1796,10 +1859,16 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View { PickerColorHSV.y = PickerX / ColorsArea.w; PickerColorHSV.z = 1.0f - PickerY / ColorsArea.h; + PickerColorHSL = color_cast(PickerColorHSV); + PickerColorRGB = color_cast(PickerColorHSL); } if(pUI->DoPickerLogic(&pColorPicker->m_HuePickerId, &HueArea, &PickerX, &PickerY)) + { PickerColorHSV.x = 1.0f - PickerY / HueArea.h; + PickerColorHSL = color_cast(PickerColorHSV); + PickerColorRGB = color_cast(PickerColorHSL); + } // Marker Color Area const float MarkerX = ColorsArea.x + ColorsArea.w * PickerColorHSV.y; @@ -1812,7 +1881,7 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View pUI->Graphics()->QuadsBegin(); pUI->Graphics()->SetColor(MarkerOutline); pUI->Graphics()->DrawCircle(MarkerX, MarkerY, 4.5f, 32); - pUI->Graphics()->SetColor(color_cast(PickerColorHSV)); + pUI->Graphics()->SetColor(PickerColorRGB); pUI->Graphics()->DrawCircle(MarkerX, MarkerY, 3.5f, 32); pUI->Graphics()->QuadsEnd(); @@ -1831,8 +1900,23 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View HueMarker.Draw(HueMarkerColor, IGraphics::CORNER_ALL, 1.2f); pColorPicker->m_HsvaColor = PickerColorHSV; + pColorPicker->m_RgbaColor = PickerColorRGB; + pColorPicker->m_HslaColor = PickerColorHSL; if(pColorPicker->m_pHslaColor != nullptr) - *pColorPicker->m_pHslaColor = color_cast(PickerColorHSV).Pack(pColorPicker->m_Alpha); + *pColorPicker->m_pHslaColor = PickerColorHSL.Pack(pColorPicker->m_Alpha); + + static const SColorPickerPopupContext::EColorPickerMode s_aModes[] = {SColorPickerPopupContext::MODE_HSVA, SColorPickerPopupContext::MODE_RGBA, SColorPickerPopupContext::MODE_HSLA}; + static const char *s_apModeLabels[std::size(s_aModes)] = {"HSVA", "RGBA", "HSLA"}; + for(SColorPickerPopupContext::EColorPickerMode Mode : s_aModes) + { + CUIRect ModeButton; + ModeButtonArea.VSplitLeft(HsvValueWidth, &ModeButton, &ModeButtonArea); + ModeButtonArea.VSplitLeft(ValuePadding, nullptr, &ModeButtonArea); + if(pUI->DoButton_PopupMenu(&pColorPicker->m_aModeButtons[(int)Mode], s_apModeLabels[Mode], &ModeButton, 10.0f, TEXTALIGN_MC, 2.0f, false, pColorPicker->m_ColorMode != Mode)) + { + pColorPicker->m_ColorMode = Mode; + } + } return CUI::POPUP_KEEP_OPEN; } @@ -1840,5 +1924,7 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View 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); + if(pContext->m_ColorMode == SColorPickerPopupContext::MODE_UNSET) + pContext->m_ColorMode = SColorPickerPopupContext::MODE_HSVA; + DoPopupMenu(pContext, X, Y, 160.0f + 10.0f, 209.0f + 10.0f, pContext, PopupColorPicker); } diff --git a/src/game/client/ui.h b/src/game/client/ui.h index 95836ef01..80a7e26df 100644 --- a/src/game/client/ui.h +++ b/src/game/client/ui.h @@ -600,13 +600,26 @@ public: struct SColorPickerPopupContext : public SPopupMenuId { + enum EColorPickerMode + { + MODE_UNSET = -1, + MODE_HSVA, + MODE_RGBA, + MODE_HSLA, + }; + CUI *m_pUI; // set by CUI when popup is shown + EColorPickerMode m_ColorMode = MODE_UNSET; bool m_Alpha = false; unsigned int *m_pHslaColor = nullptr; // may be nullptr ColorHSVA m_HsvaColor; + ColorRGBA m_RgbaColor; + ColorHSLA m_HslaColor; + // UI element IDs const char m_HuePickerId = 0; const char m_ColorPickerId = 0; const char m_aValueSelectorIds[5] = {0}; + CButtonContainer m_aModeButtons[(int)MODE_HSLA + 1]; }; void ShowPopupColorPicker(float X, float Y, SColorPickerPopupContext *pContext); diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index adbf16d25..811b0b6b5 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -3133,14 +3133,17 @@ int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int * } else if(ButtonResult > 0) { - s_ColorPickerPopupContext.m_HsvaColor = color_cast(ColorPick); + if(s_ColorPickerPopupContext.m_ColorMode == CUI::SColorPickerPopupContext::MODE_UNSET) + s_ColorPickerPopupContext.m_ColorMode = CUI::SColorPickerPopupContext::MODE_RGBA; + s_ColorPickerPopupContext.m_RgbaColor = ColorPick; + s_ColorPickerPopupContext.m_HslaColor = color_cast(ColorPick); + s_ColorPickerPopupContext.m_HsvaColor = color_cast(s_ColorPickerPopupContext.m_HslaColor); s_ColorPickerPopupContext.m_Alpha = true; UI()->ShowPopupColorPicker(UI()->MouseX(), UI()->MouseY(), &s_ColorPickerPopupContext); } else if(UI()->IsPopupOpen(&s_ColorPickerPopupContext)) { - ColorRGBA c = color_cast(s_ColorPickerPopupContext.m_HsvaColor); - const int NewColor = c.PackAlphaLast(s_ColorPickerPopupContext.m_Alpha); + const int NewColor = s_ColorPickerPopupContext.m_RgbaColor.PackAlphaLast(s_ColorPickerPopupContext.m_Alpha); if(NewColor != pProps[i].m_Value) { *pNewVal = NewColor;