From bb5e203f3a96db25812e1b64a2ca00fd7f84f624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 20 Jun 2023 20:33:20 +0200 Subject: [PATCH 1/5] Remove individual RGBA sliders from editor color picker Remove the individual RGBA sliders for editor color pickers and only show one button that opens the color picker popup instead. Decrease size of layer and point popups that previously had color properties which need less space now. --- src/game/editor/editor.cpp | 76 +++++++++----------------------------- 1 file changed, 18 insertions(+), 58 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 37678c624..47cda3216 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -1688,7 +1688,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); } @@ -3133,53 +3133,19 @@ 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.")) + if(DoButton_Editor_Common(&pIDs[i], nullptr, 0, &Shifter, 0, "Click to show the color picker.")) { s_ColorPickerPopupContext.m_HsvaColor = color_cast(ColorPick); s_ColorPickerPopupContext.m_Alpha = true; @@ -3188,18 +3154,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) @@ -3709,7 +3669,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; From 0aa55e224cab2e2ea3f25004a4f479e296714950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 20 Jun 2023 23:01:55 +0200 Subject: [PATCH 2/5] Add `str_isallnum_hex` To check if string contains only `[0-9a-zA-Z]`. --- src/base/system.cpp | 11 +++++++++++ src/base/system.h | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/src/base/system.cpp b/src/base/system.cpp index d08cb7d02..406c8e36f 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -3430,6 +3430,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 74ed2fe63..6535d66dd 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -2186,7 +2186,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 From f30682be04db895e5f62fe2dcc4844b455311b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 20 Jun 2023 23:04:52 +0200 Subject: [PATCH 3/5] Add `color4_base::PackAlphaLast` and `UnpackAlphaLast` So pack and unpack colors as 4 byte unsigned values with the alpha component last. Existing functions assume that alpha is the first component. --- src/base/color.h | 28 ++++++++++++++++++++++++++++ src/game/client/ui.cpp | 21 ++------------------- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/base/color.h b/src/base/color.h index e767a0637..e3e9bccc0 100644 --- a/src/base/color.h +++ b/src/base/color.h @@ -114,12 +114,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 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; } From 2db569374ddaf3f15af728b5a2d4ee21ab95e53a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 20 Jun 2023 23:07:44 +0200 Subject: [PATCH 4/5] Add `color_parse` function to parse strings as colors Parses RGB, RGBA, RRGGBB and RRGGBBAA hex color formats into any `color4_base`. Reuse code from color parsing in console. --- CMakeLists.txt | 1 + src/base/color.cpp | 48 +++++++++++++++++++++++++++++++++++ src/base/color.h | 5 ++++ src/engine/shared/console.cpp | 39 +--------------------------- 4 files changed, 55 insertions(+), 38 deletions(-) create mode 100644 src/base/color.cpp 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 e3e9bccc0..0bf867901 100644 --- a/src/base/color.h +++ b/src/base/color.h @@ -5,6 +5,8 @@ #include #include +#include + /* Title: Color handling */ @@ -290,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/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); From 7b9823688f8c4a5f774fae541527c7c7031e36f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 20 Jun 2023 23:09:22 +0200 Subject: [PATCH 5/5] Add shift+rightclick/leftclick to copy/paste color in editor Support shift-rightclicking color picker buttons to copy the color to the clipboard in RRGGBBAA hex format. Support shift-leftclicking color picker buttons to paste a color from the clipboard in RGB, RGBA, RRGGBB or RRGGBBAA format with optional leading `#` or `$`. --- src/game/editor/editor.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 47cda3216..27a045595 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -3145,7 +3145,32 @@ int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int * ColorRect.Draw(ColorPick, IGraphics::CORNER_ALL, ColorRect.h / 2.0f); static CUI::SColorPickerPopupContext s_ColorPickerPopupContext; - if(DoButton_Editor_Common(&pIDs[i], nullptr, 0, &Shifter, 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;