diff --git a/CMakeLists.txt b/CMakeLists.txt index 980e147be..b629ef40b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1864,6 +1864,7 @@ add_custom_command(OUTPUT "src/game/generated/wordlist.h" set_src(BASE GLOB_RECURSE src/base bezier.cpp bezier.h + color.cpp color.h detect.h dynamic.h diff --git a/src/base/color.cpp b/src/base/color.cpp new file mode 100644 index 000000000..81edd0c61 --- /dev/null +++ b/src/base/color.cpp @@ -0,0 +1,48 @@ +#include "color.h" +#include "system.h" + +template +std::optional color_parse(const char *pStr) +{ + if(!str_isallnum_hex(pStr)) + return {}; + + const unsigned Num = str_toulong_base(pStr, 16); + + T Color; + switch(str_length(pStr)) + { + case 3: + Color.x = (((Num >> 8) & 0x0F) + ((Num >> 4) & 0xF0)) / 255.0f; + Color.y = (((Num >> 4) & 0x0F) + ((Num >> 0) & 0xF0)) / 255.0f; + Color.z = (((Num >> 0) & 0x0F) + ((Num << 4) & 0xF0)) / 255.0f; + Color.a = 1.0f; + return Color; + + case 4: + Color.x = (((Num >> 12) & 0x0F) + ((Num >> 8) & 0xF0)) / 255.0f; + Color.y = (((Num >> 8) & 0x0F) + ((Num >> 4) & 0xF0)) / 255.0f; + Color.z = (((Num >> 4) & 0x0F) + ((Num >> 0) & 0xF0)) / 255.0f; + Color.a = (((Num >> 0) & 0x0F) + ((Num << 4) & 0xF0)) / 255.0f; + return Color; + + case 6: + Color.x = ((Num >> 16) & 0xFF) / 255.0f; + Color.y = ((Num >> 8) & 0xFF) / 255.0f; + Color.z = ((Num >> 0) & 0xFF) / 255.0f; + Color.a = 1.0f; + return Color; + + case 8: + Color.x = ((Num >> 24) & 0xFF) / 255.0f; + Color.y = ((Num >> 16) & 0xFF) / 255.0f; + Color.z = ((Num >> 8) & 0xFF) / 255.0f; + Color.a = ((Num >> 0) & 0xFF) / 255.0f; + return Color; + + default: + return {}; + } +} + +template std::optional color_parse(const char *); diff --git a/src/base/color.h b/src/base/color.h index e767a0637..0bf867901 100644 --- a/src/base/color.h +++ b/src/base/color.h @@ -5,6 +5,8 @@ #include #include +#include + /* Title: Color handling */ @@ -114,12 +116,40 @@ public: return (Alpha ? ((unsigned)round_to_int(a * 255.0f) << 24) : 0) + ((unsigned)round_to_int(x * 255.0f) << 16) + ((unsigned)round_to_int(y * 255.0f) << 8) + (unsigned)round_to_int(z * 255.0f); } + unsigned PackAlphaLast(bool Alpha = true) const + { + if(Alpha) + return ((unsigned)round_to_int(x * 255.0f) << 24) + ((unsigned)round_to_int(y * 255.0f) << 16) + ((unsigned)round_to_int(z * 255.0f) << 8) + (unsigned)round_to_int(a * 255.0f); + return ((unsigned)round_to_int(x * 255.0f) << 16) + ((unsigned)round_to_int(y * 255.0f) << 8) + (unsigned)round_to_int(z * 255.0f); + } + DerivedT WithAlpha(float alpha) const { DerivedT col(static_cast(*this)); col.a = alpha; return col; } + + template + static UnpackT UnpackAlphaLast(unsigned Color, bool Alpha = true) + { + UnpackT Result; + if(Alpha) + { + Result.x = ((Color >> 24) & 0xFF) / 255.0f; + Result.y = ((Color >> 16) & 0xFF) / 255.0f; + Result.z = ((Color >> 8) & 0xFF) / 255.0f; + Result.a = ((Color >> 0) & 0xFF) / 255.0f; + } + else + { + Result.x = ((Color >> 16) & 0xFF) / 255.0f; + Result.y = ((Color >> 8) & 0xFF) / 255.0f; + Result.z = ((Color >> 0) & 0xFF) / 255.0f; + Result.a = 1.0f; + } + return Result; + } }; class ColorHSLA : public color4_base @@ -262,4 +292,7 @@ T color_invert(const T &col) return T(1.0f - col.x, 1.0f - col.y, 1.0f - col.z, 1.0f - col.a); } +template +std::optional color_parse(const char *pStr); + #endif diff --git a/src/base/system.cpp b/src/base/system.cpp index a3d9f9277..711ccceca 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -3463,6 +3463,17 @@ int str_isallnum(const char *str) return 1; } +int str_isallnum_hex(const char *str) +{ + while(*str) + { + if(!(*str >= '0' && *str <= '9') && !(*str >= 'a' && *str <= 'f') && !(*str >= 'A' && *str <= 'F')) + return 0; + str++; + } + return 1; +} + int str_toint(const char *str) { return str_toint_base(str, 10); diff --git a/src/base/system.h b/src/base/system.h index 1085e90ec..9bd3ab54e 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -2219,7 +2219,11 @@ float str_tofloat(const char *str); int str_isspace(char c); char str_uppercase(char c); + int str_isallnum(const char *str); + +int str_isallnum_hex(const char *str); + unsigned str_quickhash(const char *str); enum diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp index bcc303031..d59902f6d 100644 --- a/src/engine/shared/console.cpp +++ b/src/engine/shared/console.cpp @@ -55,44 +55,7 @@ ColorHSLA CConsole::CResult::GetColor(unsigned Index, bool Light) const } else if(*pStr == '$') // Hex RGB/RGBA { - ColorRGBA Rgba = ColorRGBA(0, 0, 0, 1); - const int Len = str_length(pStr); - if(Len == 4) - { - const unsigned Num = str_toulong_base(pStr + 1, 16); - Rgba.r = (((Num >> 8) & 0x0F) + ((Num >> 4) & 0xF0)) / 255.0f; - Rgba.g = (((Num >> 4) & 0x0F) + ((Num >> 0) & 0xF0)) / 255.0f; - Rgba.b = (((Num >> 0) & 0x0F) + ((Num << 4) & 0xF0)) / 255.0f; - } - else if(Len == 5) - { - const unsigned Num = str_toulong_base(pStr + 1, 16); - Rgba.r = (((Num >> 12) & 0x0F) + ((Num >> 8) & 0xF0)) / 255.0f; - Rgba.g = (((Num >> 8) & 0x0F) + ((Num >> 4) & 0xF0)) / 255.0f; - Rgba.b = (((Num >> 4) & 0x0F) + ((Num >> 0) & 0xF0)) / 255.0f; - Rgba.a = (((Num >> 0) & 0x0F) + ((Num << 4) & 0xF0)) / 255.0f; - } - else if(Len == 7) - { - const unsigned Num = str_toulong_base(pStr + 1, 16); - Rgba.r = ((Num >> 16) & 0xFF) / 255.0f; - Rgba.g = ((Num >> 8) & 0xFF) / 255.0f; - Rgba.b = ((Num >> 0) & 0xFF) / 255.0f; - } - else if(Len == 9) - { - const unsigned Num = str_toulong_base(pStr + 1, 16); - Rgba.r = ((Num >> 24) & 0xFF) / 255.0f; - Rgba.g = ((Num >> 16) & 0xFF) / 255.0f; - Rgba.b = ((Num >> 8) & 0xFF) / 255.0f; - Rgba.a = ((Num >> 0) & 0xFF) / 255.0f; - } - else - { - return ColorHSLA(0, 0, 0); - } - - return color_cast(Rgba); + return color_cast(color_parse(pStr + 1).value_or(ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f))); } else if(!str_comp_nocase(pStr, "red")) return ColorHSLA(0.0f / 6.0f, 1, .5f); diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index df1626ccd..23cb4a01d 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -1711,32 +1711,15 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View PickerColorHSV = ColorHSVA(H / 255.0f, S / 255.0f, V / 255.0f, A / 255.0f); - const auto RotateByteLeft = [pColorPicker](unsigned Num) { - if(pColorPicker->m_Alpha) - { - // ARGB -> RGBA (internal -> displayed) - return ((Num & 0xFF000000u) >> 24) | (Num << 8); - } - return Num; - }; - const auto RotateByteRight = [pColorPicker](unsigned Num) { - if(pColorPicker->m_Alpha) - { - // RGBA -> ARGB (displayed -> internal) - return ((Num & 0xFFu) << 24) | (Num >> 8); - } - return Num; - }; - SValueSelectorProperties Props; Props.m_UseScroll = false; Props.m_IsHex = true; Props.m_HexPrefix = pColorPicker->m_Alpha ? 8 : 6; - const unsigned Hex = RotateByteLeft(color_cast(PickerColorHSV).Pack(pColorPicker->m_Alpha)); + 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) { - PickerColorHSV = color_cast(ColorRGBA(RotateByteRight(NewHex), pColorPicker->m_Alpha)); + PickerColorHSV = color_cast(ColorRGBA::UnpackAlphaLast(NewHex, pColorPicker->m_Alpha)); if(!pColorPicker->m_Alpha) PickerColorHSV.a = A / 255.0f; } diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index d51bbb0b7..563906c4b 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -1738,7 +1738,7 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) m_SelectedQuadIndex = FindSelectedQuadIndex(QuadIndex); static SPopupMenuId s_PopupPointId; - UI()->DoPopupMenu(&s_PopupPointId, UI()->MouseX(), UI()->MouseY(), 120, 150, this, PopupPoint); + UI()->DoPopupMenu(&s_PopupPointId, UI()->MouseX(), UI()->MouseY(), 120, 75, this, PopupPoint); } UI()->SetActiveItem(nullptr); } @@ -3183,53 +3183,44 @@ int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int * } else if(pProps[i].m_Type == PROPTYPE_COLOR) { - static const char *s_apTexts[4] = {"R", "G", "B", "A"}; - static const int s_aShift[] = {24, 16, 8, 0}; - int NewColor = 0; + const ColorRGBA ColorPick = ColorRGBA( + ((pProps[i].m_Value >> 24) & 0xff) / 255.0f, + ((pProps[i].m_Value >> 16) & 0xff) / 255.0f, + ((pProps[i].m_Value >> 8) & 0xff) / 255.0f, + (pProps[i].m_Value & 0xff) / 255.0f); - // extra space - CUIRect ColorBox, ColorSlots; - - pToolBox->HSplitTop(3.0f * 13.0f, &Slot, pToolBox); - Slot.VSplitMid(&ColorBox, &ColorSlots); - ColorBox.HSplitTop(8.0f, nullptr, &ColorBox); - ColorBox.VMargin(12.0f, &ColorBox); - ColorBox.HMargin((ColorBox.h - ColorBox.w) / 2.0f, &ColorBox); - - for(int c = 0; c < 4; c++) - { - int v = (pProps[i].m_Value >> s_aShift[c]) & 0xff; - NewColor |= UiDoValueSelector(((char *)&pIDs[i]) + c, &Shifter, s_apTexts[c], v, 0, 255, 1, 1.0f, "Use left mouse button to drag and change the color value. Hold shift to be more precise. Rightclick to edit as text.") << s_aShift[c]; - - if(c != 3) - { - ColorSlots.HSplitTop(13.0f, &Shifter, &ColorSlots); - Shifter.HMargin(1.0f, &Shifter); - } - } - - // hex - pToolBox->HSplitTop(13.0f, &Slot, pToolBox); - Slot.VSplitMid(nullptr, &Shifter); - Shifter.HMargin(1.0f, &Shifter); - - int NewColorHex = pProps[i].m_Value & 0xff; - NewColorHex |= UiDoValueSelector(((char *)&pIDs[i] - 1), &Shifter, "", (pProps[i].m_Value >> 8) & 0xFFFFFF, 0, 0xFFFFFF, 1, 1.0f, "Use left mouse button to drag and change the color value. Hold shift to be more precise. Rightclick to edit as text.", false, true) << 8; - - // color picker - ColorRGBA ColorPick = ColorRGBA( - ((pProps[i].m_Value >> s_aShift[0]) & 0xff) / 255.0f, - ((pProps[i].m_Value >> s_aShift[1]) & 0xff) / 255.0f, - ((pProps[i].m_Value >> s_aShift[2]) & 0xff) / 255.0f, - 1.0f); - - static int s_ColorPicker; - if(UI()->HotItem() == &s_ColorPicker) - ColorBox.Margin(-2.0f, &ColorBox); - ColorBox.Draw(ColorPick, IGraphics::CORNER_ALL, 3.0f); + CUIRect ColorRect; + Shifter.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f * UI()->ButtonColorMul(&pIDs[i])), IGraphics::CORNER_ALL, 5.0f); + Shifter.Margin(1.0f, &ColorRect); + ColorRect.Draw(ColorPick, IGraphics::CORNER_ALL, ColorRect.h / 2.0f); static CUI::SColorPickerPopupContext s_ColorPickerPopupContext; - if(DoButton_Editor_Common(&s_ColorPicker, nullptr, 0, &ColorBox, 0, "Click to show the color picker.")) + const int ButtonResult = DoButton_Editor_Common(&pIDs[i], nullptr, 0, &Shifter, 0, "Click to show the color picker. Shift+rightclick to copy color to clipboard. Shift+leftclick to paste color from clipboard."); + if(Input()->ShiftIsPressed()) + { + if(ButtonResult == 1) + { + const char *pClipboard = Input()->GetClipboardText(); + if(*pClipboard == '#' || *pClipboard == '$') // ignore leading # (web color format) and $ (console color format) + ++pClipboard; + if(str_isallnum_hex(pClipboard)) + { + std::optional ParsedColor = color_parse(pClipboard); + if(ParsedColor) + { + *pNewVal = ParsedColor.value().PackAlphaLast(); + Change = i; + } + } + } + else if(ButtonResult == 2) + { + char aClipboard[9]; + str_format(aClipboard, sizeof(aClipboard), "%08X", ColorPick.PackAlphaLast()); + Input()->SetClipboardText(aClipboard); + } + } + else if(ButtonResult > 0) { s_ColorPickerPopupContext.m_HsvaColor = color_cast(ColorPick); s_ColorPickerPopupContext.m_Alpha = true; @@ -3238,18 +3229,12 @@ int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int * else if(UI()->IsPopupOpen(&s_ColorPickerPopupContext)) { ColorRGBA c = color_cast(s_ColorPickerPopupContext.m_HsvaColor); - NewColor = ((int)(c.r * 255.0f) & 0xff) << 24 | ((int)(c.g * 255.0f) & 0xff) << 16 | ((int)(c.b * 255.0f) & 0xff) << 8 | (pProps[i].m_Value & 0xff); - } - - if(NewColor != pProps[i].m_Value) - { - *pNewVal = NewColor; - Change = i; - } - else if(NewColorHex != pProps[i].m_Value) - { - *pNewVal = NewColorHex; - Change = i; + const int NewColor = ((int)(c.r * 255.0f) & 0xff) << 24 | ((int)(c.g * 255.0f) & 0xff) << 16 | ((int)(c.b * 255.0f) & 0xff) << 8 | ((int)(c.a * 255.0f) & 0xff); + if(NewColor != pProps[i].m_Value) + { + *pNewVal = NewColor; + Change = i; + } } } else if(pProps[i].m_Type == PROPTYPE_IMAGE) @@ -3759,7 +3744,7 @@ void CEditor::RenderLayers(CUIRect LayersBox) else s_LayerPopupContext.m_vpLayers.clear(); - UI()->DoPopupMenu(&s_LayerPopupContext, UI()->MouseX(), UI()->MouseY(), 120, 320, &s_LayerPopupContext, PopupLayer); + UI()->DoPopupMenu(&s_LayerPopupContext, UI()->MouseX(), UI()->MouseY(), 120, 230, &s_LayerPopupContext, PopupLayer); } s_Operation = OP_NONE;