Add color palette and pipette to editor

Add color palette with up to 8 colors to editor toolbar. The palette colors work like regular color picker buttons, so they open a color picker popup on click and the value can be copied and pasted with Ctrl+Right click and Ctrl+Left click respectively. Less palette colors are shown when not enough space is available (with 5:4 resolutions).

Add color pipette which allows selecting any color displayed on the screen. Selecting a color with the pipette adds the new color to the palette and shifts the other colors to the right. The selected color is also copied to the clipboard immediately. The hotkey Ctrl+Shift+C is added to toggle the color pipette, which allows using the color pipette in popups and dialogs.

The implement this, the function `IGraphics::ReadPixel` and the command `SCommand_TrySwapAndReadPixel` are added to read a specified pixel's color from the backbuffer. Like the screenshot command, this command also requires a swap operation to be performed before the correct pixel color can be read with the Vulkan backend. The `ReadPixel` function therefore accepts a pointer to a `ColorRGBA` that will be filled after the next swap operation.

Closes #7430.
This commit is contained in:
Robert Müller 2023-11-11 18:10:52 +01:00
parent 9c8a081692
commit f5e7fa24d6
9 changed files with 294 additions and 53 deletions

View file

@ -313,8 +313,9 @@ bool CCommandProcessorFragment_OpenGL::InitOpenGL(const SCommand_Init *pCommand)
{
m_IsOpenGLES = pCommand->m_RequestedBackend == BACKEND_TYPE_OPENGL_ES;
TGLBackendReadPresentedImageData &ReadPresentedImgDataFunc = *pCommand->m_pReadPresentedImageDataFunc;
ReadPresentedImgDataFunc = [this](uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector<uint8_t> &vDstData) { return GetPresentedImageData(Width, Height, Format, vDstData); };
*pCommand->m_pReadPresentedImageDataFunc = [this](uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector<uint8_t> &vDstData) {
return GetPresentedImageData(Width, Height, Format, vDstData);
};
const char *pVendorString = (const char *)glGetString(GL_VENDOR);
dbg_msg("opengl", "Vendor string: %s", pVendorString);
@ -983,10 +984,27 @@ void CCommandProcessorFragment_OpenGL::Cmd_Render(const CCommandBuffer::SCommand
#endif
}
void CCommandProcessorFragment_OpenGL::Cmd_ReadPixel(const CCommandBuffer::SCommand_TrySwapAndReadPixel *pCommand)
{
// get size of viewport
GLint aViewport[4] = {0, 0, 0, 0};
glGetIntegerv(GL_VIEWPORT, aViewport);
const int h = aViewport[3];
// fetch the pixel
uint8_t aPixelData[3];
GLint Alignment;
glGetIntegerv(GL_PACK_ALIGNMENT, &Alignment);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(pCommand->m_Position.x, h - 1 - pCommand->m_Position.y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, aPixelData);
glPixelStorei(GL_PACK_ALIGNMENT, Alignment);
// fill in the information
*pCommand->m_pColor = ColorRGBA(aPixelData[0] / 255.0f, aPixelData[1] / 255.0f, aPixelData[2] / 255.0f, 1.0f);
}
void CCommandProcessorFragment_OpenGL::Cmd_Screenshot(const CCommandBuffer::SCommand_TrySwapAndScreenshot *pCommand)
{
*pCommand->m_pSwapped = false;
// fetch image data
GLint aViewport[4] = {0, 0, 0, 0};
glGetIntegerv(GL_VIEWPORT, aViewport);
@ -1068,6 +1086,9 @@ ERunCommandReturnTypes CCommandProcessorFragment_OpenGL::RunCommand(const CComma
case CCommandBuffer::CMD_RENDER_TEX3D:
Cmd_RenderTex3D(static_cast<const CCommandBuffer::SCommand_RenderTex3D *>(pBaseCommand));
break;
case CCommandBuffer::CMD_TRY_SWAP_AND_READ_PIXEL:
Cmd_ReadPixel(static_cast<const CCommandBuffer::SCommand_TrySwapAndReadPixel *>(pBaseCommand));
break;
case CCommandBuffer::CMD_TRY_SWAP_AND_SCREENSHOT:
Cmd_Screenshot(static_cast<const CCommandBuffer::SCommand_TrySwapAndScreenshot *>(pBaseCommand));
break;

View file

@ -99,6 +99,7 @@ protected:
virtual void Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand);
virtual void Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand);
virtual void Cmd_RenderTex3D(const CCommandBuffer::SCommand_RenderTex3D *pCommand) { dbg_assert(false, "Call of unsupported Cmd_RenderTex3D"); }
virtual void Cmd_ReadPixel(const CCommandBuffer::SCommand_TrySwapAndReadPixel *pCommand);
virtual void Cmd_Screenshot(const CCommandBuffer::SCommand_TrySwapAndScreenshot *pCommand);
virtual void Cmd_Update_Viewport(const CCommandBuffer::SCommand_Update_Viewport *pCommand);

View file

@ -15,26 +15,22 @@
#include <base/math.h>
#include <base/system.h>
#include <array>
#include <map>
#include <set>
#include <vector>
#include <algorithm>
#include <array>
#include <condition_variable>
#include <cstddef>
#include <cstdlib>
#include <functional>
#include <limits>
#include <map>
#include <memory>
#include <string>
#include <condition_variable>
#include <mutex>
#include <optional>
#include <set>
#include <string>
#include <thread>
#include <cstdlib>
#include <unordered_map>
#include <vector>
#include <SDL_video.h>
#include <SDL_vulkan.h>
@ -904,6 +900,7 @@ class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
uint32_t m_MinUniformAlign;
std::vector<uint8_t> m_vReadPixelHelper;
std::vector<uint8_t> m_vScreenshotHelper;
SDeviceMemoryBlock m_GetPresentedImgDataHelperMem;
@ -1278,6 +1275,7 @@ protected:
m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_VSYNC)] = {false, [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_VSync(static_cast<const CCommandBuffer::SCommand_VSync *>(pBaseCommand)); }};
m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_MULTISAMPLING)] = {false, [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_MultiSampling(static_cast<const CCommandBuffer::SCommand_MultiSampling *>(pBaseCommand)); }};
m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_TRY_SWAP_AND_READ_PIXEL)] = {false, [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_ReadPixel(static_cast<const CCommandBuffer::SCommand_TrySwapAndReadPixel *>(pBaseCommand)); }};
m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_TRY_SWAP_AND_SCREENSHOT)] = {false, [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_Screenshot(static_cast<const CCommandBuffer::SCommand_TrySwapAndScreenshot *>(pBaseCommand)); }};
m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_UPDATE_VIEWPORT)] = {false, [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) { Cmd_Update_Viewport_FillExecuteBuffer(ExecBuffer, static_cast<const CCommandBuffer::SCommand_Update_Viewport *>(pBaseCommand)); }, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_Update_Viewport(static_cast<const CCommandBuffer::SCommand_Update_Viewport *>(pBaseCommand)); }};
@ -1378,15 +1376,29 @@ protected:
}
}
[[nodiscard]] bool GetPresentedImageDataImpl(uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector<uint8_t> &vDstData, bool ResetAlpha)
[[nodiscard]] bool GetPresentedImageDataImpl(uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector<uint8_t> &vDstData, bool ResetAlpha, std::optional<ivec2> PixelOffset)
{
bool IsB8G8R8A8 = m_VKSurfFormat.format == VK_FORMAT_B8G8R8A8_UNORM;
bool UsesRGBALikeFormat = m_VKSurfFormat.format == VK_FORMAT_R8G8B8A8_UNORM || IsB8G8R8A8;
if(UsesRGBALikeFormat && m_LastPresentedSwapChainImageIndex != std::numeric_limits<decltype(m_LastPresentedSwapChainImageIndex)>::max())
{
auto Viewport = m_VKSwapImgAndViewportExtent.GetPresentedImageViewport();
Width = Viewport.width;
Height = Viewport.height;
VkOffset3D SrcOffset;
if(PixelOffset.has_value())
{
SrcOffset.x = PixelOffset.value().x;
SrcOffset.y = PixelOffset.value().y;
Width = 1;
Height = 1;
}
else
{
SrcOffset.x = 0;
SrcOffset.y = 0;
Width = Viewport.width;
Height = Viewport.height;
}
SrcOffset.z = 0;
Format = CImageInfo::FORMAT_RGBA;
const size_t ImageTotalSize = (size_t)Width * Height * CImageInfo::PixelSize(Format);
@ -1414,9 +1426,11 @@ protected:
BlitSize.x = Width;
BlitSize.y = Height;
BlitSize.z = 1;
VkImageBlit ImageBlitRegion{};
ImageBlitRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
ImageBlitRegion.srcSubresource.layerCount = 1;
ImageBlitRegion.srcOffsets[0] = SrcOffset;
ImageBlitRegion.srcOffsets[1] = BlitSize;
ImageBlitRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
ImageBlitRegion.dstSubresource.layerCount = 1;
@ -1436,6 +1450,7 @@ protected:
VkImageCopy ImageCopyRegion{};
ImageCopyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
ImageCopyRegion.srcSubresource.layerCount = 1;
ImageCopyRegion.srcOffset = SrcOffset;
ImageCopyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
ImageCopyRegion.dstSubresource.layerCount = 1;
ImageCopyRegion.extent.width = Width;
@ -1458,7 +1473,6 @@ protected:
VkSubmitInfo SubmitInfo{};
SubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
SubmitInfo.commandBufferCount = 1;
SubmitInfo.pCommandBuffers = &CommandBuffer;
@ -1525,7 +1539,7 @@ protected:
[[nodiscard]] bool GetPresentedImageData(uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector<uint8_t> &vDstData) override
{
return GetPresentedImageDataImpl(Width, Height, Format, vDstData, false);
return GetPresentedImageDataImpl(Width, Height, Format, vDstData, false, {});
}
/************************
@ -6523,8 +6537,9 @@ public:
m_MultiSamplingCount = (g_Config.m_GfxFsaaSamples & 0xFFFFFFFE); // ignore the uneven bit, only even multi sampling works
TGLBackendReadPresentedImageData &ReadPresentedImgDataFunc = *pCommand->m_pReadPresentedImageDataFunc;
ReadPresentedImgDataFunc = [this](uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector<uint8_t> &vDstData) { return GetPresentedImageData(Width, Height, Format, vDstData); };
*pCommand->m_pReadPresentedImageDataFunc = [this](uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector<uint8_t> &vDstData) {
return GetPresentedImageData(Width, Height, Format, vDstData);
};
m_pWindow = pCommand->m_pWindow;
@ -6748,18 +6763,39 @@ public:
return RenderStandard<CCommandBuffer::SVertex, false>(ExecBuffer, pCommand->m_State, pCommand->m_PrimType, pCommand->m_pVertices, pCommand->m_PrimCount);
}
[[nodiscard]] bool Cmd_Screenshot(const CCommandBuffer::SCommand_TrySwapAndScreenshot *pCommand)
[[nodiscard]] bool Cmd_ReadPixel(const CCommandBuffer::SCommand_TrySwapAndReadPixel *pCommand)
{
if(!NextFrame())
if(!*pCommand->m_pSwapped && !NextFrame())
return false;
*pCommand->m_pSwapped = true;
uint32_t Width;
uint32_t Height;
CImageInfo::EImageFormat Format;
if(GetPresentedImageDataImpl(Width, Height, Format, m_vScreenshotHelper, true))
if(GetPresentedImageDataImpl(Width, Height, Format, m_vReadPixelHelper, false, pCommand->m_Position))
{
size_t ImgSize = (size_t)Width * (size_t)Height * (size_t)4;
*pCommand->m_pColor = ColorRGBA(m_vReadPixelHelper[0] / 255.0f, m_vReadPixelHelper[1] / 255.0f, m_vReadPixelHelper[2] / 255.0f, 1.0f);
}
else
{
*pCommand->m_pColor = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
}
return true;
}
[[nodiscard]] bool Cmd_Screenshot(const CCommandBuffer::SCommand_TrySwapAndScreenshot *pCommand)
{
if(!*pCommand->m_pSwapped && !NextFrame())
return false;
*pCommand->m_pSwapped = true;
uint32_t Width;
uint32_t Height;
CImageInfo::EImageFormat Format;
if(GetPresentedImageDataImpl(Width, Height, Format, m_vScreenshotHelper, true, {}))
{
const size_t ImgSize = (size_t)Width * (size_t)Height * CImageInfo::PixelSize(Format);
pCommand->m_pImage->m_pData = malloc(ImgSize);
mem_copy(pCommand->m_pImage->m_pData, m_vScreenshotHelper.data(), ImgSize);
}

View file

@ -784,19 +784,21 @@ public:
}
};
bool CGraphics_Threaded::ScreenshotDirect()
void CGraphics_Threaded::ScreenshotDirect(bool *pSwapped)
{
// add swap command
CImageInfo Image;
if(!m_DoScreenshot)
return;
m_DoScreenshot = false;
if(!WindowActive())
return;
bool DidSwap = false;
CImageInfo Image;
CCommandBuffer::SCommand_TrySwapAndScreenshot Cmd;
Cmd.m_pImage = &Image;
Cmd.m_pSwapped = &DidSwap;
Cmd.m_pSwapped = pSwapped;
AddCmd(Cmd);
// kick the buffer and wait for the result
KickCommandBuffer();
WaitForIdle();
@ -804,8 +806,6 @@ bool CGraphics_Threaded::ScreenshotDirect()
{
m_pEngine->AddJob(std::make_shared<CScreenshotSaveJob>(m_pStorage, m_pConsole, m_aScreenshotName, Image.m_Width, Image.m_Height, Image.m_pData));
}
return DidSwap;
}
void CGraphics_Threaded::TextureSet(CTextureHandle TextureID)
@ -2755,6 +2755,32 @@ void CGraphics_Threaded::NotifyWindow()
return m_pBackend->NotifyWindow();
}
void CGraphics_Threaded::ReadPixel(ivec2 Position, ColorRGBA *pColor)
{
dbg_assert(Position.x >= 0 && Position.x < ScreenWidth(), "ReadPixel position x out of range");
dbg_assert(Position.y >= 0 && Position.y < ScreenHeight(), "ReadPixel position y out of range");
m_ReadPixelPosition = Position;
m_pReadPixelColor = pColor;
}
void CGraphics_Threaded::ReadPixelDirect(bool *pSwapped)
{
if(m_pReadPixelColor == nullptr)
return;
CCommandBuffer::SCommand_TrySwapAndReadPixel Cmd;
Cmd.m_Position = m_ReadPixelPosition;
Cmd.m_pColor = m_pReadPixelColor;
Cmd.m_pSwapped = pSwapped;
AddCmd(Cmd);
KickCommandBuffer();
WaitForIdle();
m_pReadPixelColor = nullptr;
}
void CGraphics_Threaded::TakeScreenshot(const char *pFilename)
{
// TODO: screenshot support
@ -2781,23 +2807,16 @@ void CGraphics_Threaded::Swap()
}
}
bool TookScreenshotAndSwapped = false;
bool Swapped = false;
ScreenshotDirect(&Swapped);
ReadPixelDirect(&Swapped);
if(m_DoScreenshot)
if(!Swapped)
{
if(WindowActive())
TookScreenshotAndSwapped = ScreenshotDirect();
m_DoScreenshot = false;
}
if(!TookScreenshotAndSwapped)
{
// add swap command
CCommandBuffer::SCommand_Swap Cmd;
AddCmd(Cmd);
}
// kick the command buffer
KickCommandBuffer();
// TODO: Remove when https://github.com/libsdl-org/SDL/issues/5203 is fixed
#ifdef CONF_PLATFORM_MACOS

View file

@ -130,6 +130,7 @@ public:
// misc
CMD_MULTISAMPLING,
CMD_VSYNC,
CMD_TRY_SWAP_AND_READ_PIXEL,
CMD_TRY_SWAP_AND_SCREENSHOT,
CMD_UPDATE_VIEWPORT,
@ -462,12 +463,21 @@ public:
void *m_pOffset;
};
struct SCommand_TrySwapAndReadPixel : public SCommand
{
SCommand_TrySwapAndReadPixel() :
SCommand(CMD_TRY_SWAP_AND_READ_PIXEL) {}
ivec2 m_Position;
SColorf *m_pColor; // processor will fill this out
bool *m_pSwapped; // processor may set this to true, must be initialized to false by the caller
};
struct SCommand_TrySwapAndScreenshot : public SCommand
{
SCommand_TrySwapAndScreenshot() :
SCommand(CMD_TRY_SWAP_AND_SCREENSHOT) {}
CImageInfo *m_pImage; // processor will fill this out, the one who adds this command must free the data as well
bool *m_pSwapped;
bool *m_pSwapped; // processor may set this to true, must be initialized to false by the caller
};
struct SCommand_Swap : public SCommand
@ -917,6 +927,11 @@ class CGraphics_Threaded : public IEngineGraphics
void AdjustViewport(bool SendViewportChangeToBackend);
ivec2 m_ReadPixelPosition = ivec2(0, 0);
ColorRGBA *m_pReadPixelColor = nullptr;
void ReadPixelDirect(bool *pSwapped);
void ScreenshotDirect(bool *pSwapped);
int IssueInit();
int InitWindow();
@ -975,8 +990,6 @@ public:
void CopyTextureBufferSub(uint8_t *pDestBuffer, uint8_t *pSourceBuffer, size_t FullWidth, size_t FullHeight, size_t PixelSize, size_t SubOffsetX, size_t SubOffsetY, size_t SubCopyWidth, size_t SubCopyHeight) override;
void CopyTextureFromTextureBufferSub(uint8_t *pDestBuffer, size_t DestWidth, size_t DestHeight, uint8_t *pSourceBuffer, size_t SrcWidth, size_t SrcHeight, size_t PixelSize, size_t SrcSubOffsetX, size_t SrcSubOffsetY, size_t SrcSubCopyWidth, size_t SrcSubCopyHeight) override;
bool ScreenshotDirect();
void TextureSet(CTextureHandle TextureID) override;
void Clear(float r, float g, float b, bool ForceClearNow = false) override;
@ -1243,6 +1256,7 @@ public:
int Init() override;
void Shutdown() override;
void ReadPixel(ivec2 Position, ColorRGBA *pColor) override;
void TakeScreenshot(const char *pFilename) override;
void TakeCustomScreenshot(const char *pFilename) override;
void Swap() override;

View file

@ -513,6 +513,15 @@ public:
virtual void ChangeColorOfCurrentQuadVertices(float r, float g, float b, float a) = 0;
virtual void ChangeColorOfQuadVertices(size_t QuadOffset, unsigned char r, unsigned char g, unsigned char b, unsigned char a) = 0;
/**
* Reads the color at the specified position from the backbuffer once,
* after the next swap operation.
*
* @param Position The pixel position to read.
* @param pColor Pointer that will receive the read pixel color.
* The pointer must be valid until the next swap operation.
*/
virtual void ReadPixel(ivec2 Position, ColorRGBA *pColor) = 0;
virtual void TakeScreenshot(const char *pFilename) = 0;
virtual void TakeCustomScreenshot(const char *pFilename) = 0;
virtual int GetVideoModes(CVideoMode *pModes, int MaxModes, int Screen) = 0;

View file

@ -132,6 +132,7 @@ MAYBE_UNUSED static const char *FONT_ICON_CIRCLE_PLAY = "\xEF\x85\x84";
MAYBE_UNUSED static const char *FONT_ICON_BORDER_ALL = "\xEF\xA1\x8C";
MAYBE_UNUSED static const char *FONT_ICON_EYE = "\xEF\x81\xAE";
MAYBE_UNUSED static const char *FONT_ICON_EYE_SLASH = "\xEF\x81\xB0";
MAYBE_UNUSED static const char *FONT_ICON_EYE_DROPPER = "\xEF\x87\xBB";
MAYBE_UNUSED static const char *FONT_ICON_DICE_ONE = "\xEF\x94\xA5";
MAYBE_UNUSED static const char *FONT_ICON_DICE_TWO = "\xEF\x94\xA8";

View file

@ -1248,6 +1248,38 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar)
pLayer->BrushRotate(s_RotationAmount / 360.0f * pi * 2);
}
}
// Color pipette and palette
{
const float PipetteButtonWidth = 30.0f;
const float ColorPickerButtonWidth = 20.0f;
const float Spacing = 2.0f;
const size_t NumColorsShown = clamp<int>(round_to_int((TB_Top.w - PipetteButtonWidth - 40.0f) / (ColorPickerButtonWidth + Spacing)), 1, std::size(m_aSavedColors));
CUIRect ColorPalette;
TB_Top.VSplitRight(NumColorsShown * (ColorPickerButtonWidth + Spacing) + PipetteButtonWidth, &TB_Top, &ColorPalette);
// Pipette button
static char s_PipetteButton;
ColorPalette.VSplitLeft(PipetteButtonWidth, &Button, &ColorPalette);
ColorPalette.VSplitLeft(Spacing, nullptr, &ColorPalette);
if(DoButton_FontIcon(&s_PipetteButton, FONT_ICON_EYE_DROPPER, m_ColorPipetteActive ? 1 : 0, &Button, 0, "[Ctrl+Shift+C] Color pipette. Pick a color from the screen by clicking on it.", IGraphics::CORNER_ALL) ||
(CLineInput::GetActiveInput() == nullptr && ModPressed && ShiftPressed && Input()->KeyPress(KEY_C)))
{
m_ColorPipetteActive = !m_ColorPipetteActive;
}
// Palette color pickers
for(size_t i = 0; i < NumColorsShown; ++i)
{
ColorPalette.VSplitLeft(ColorPickerButtonWidth, &Button, &ColorPalette);
ColorPalette.VSplitLeft(Spacing, nullptr, &ColorPalette);
const auto &&SetColor = [&](ColorRGBA NewColor) {
m_aSavedColors[i] = NewColor;
};
DoColorPickerButton(&m_aSavedColors[i], &Button, m_aSavedColors[i], SetColor);
}
}
}
// Bottom line buttons
@ -8202,9 +8234,17 @@ void CEditor::Render()
MapView()->UpdateZoom();
// Cancel color pipette with escape before closing popup menus with escape
if(m_ColorPipetteActive && UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
{
m_ColorPipetteActive = false;
}
UI()->RenderPopupMenus();
FreeDynamicPopupMenus();
UpdateColorPipette();
if(m_Dialog == DIALOG_NONE && !m_PopupEventActivated && UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
{
OnClose();
@ -8274,22 +8314,105 @@ void CEditor::FreeDynamicPopupMenus()
}
}
void CEditor::UpdateColorPipette()
{
if(!m_ColorPipetteActive)
return;
static char s_PipetteScreenButton;
if(UI()->HotItem() == &s_PipetteScreenButton)
{
// Read color one pixel to the top and left as we would otherwise not read the correct
// color due to the cursor sprite being rendered over the current mouse position.
const int PixelX = clamp<int>(round_to_int((UI()->MouseX() - 1.0f) / UI()->Screen()->w * Graphics()->ScreenWidth()), 0, Graphics()->ScreenWidth() - 1);
const int PixelY = clamp<int>(round_to_int((UI()->MouseY() - 1.0f) / UI()->Screen()->h * Graphics()->ScreenHeight()), 0, Graphics()->ScreenHeight() - 1);
Graphics()->ReadPixel(ivec2(PixelX, PixelY), &m_PipetteColor);
}
// Simulate button overlaying the entire screen to intercept all clicks for color pipette.
const int ButtonResult = DoButton_Editor_Common(&s_PipetteScreenButton, "", 0, UI()->Screen(), 0, "Left click to pick a color from the screen. Right click to cancel pipette mode.");
// Don't handle clicks if we are panning, so the pipette stays active while panning.
// Checking m_pContainerPanned alone is not enough, as this variable is reset when
// panning ends before this function is called.
if(m_pContainerPanned == nullptr && m_pContainerPannedLast == nullptr)
{
if(ButtonResult == 1)
{
char aClipboard[9];
str_format(aClipboard, sizeof(aClipboard), "%08X", m_PipetteColor.PackAlphaLast());
Input()->SetClipboardText(aClipboard);
// Check if any of the saved colors is equal to the picked color and
// bring it to the front of the list instead of adding a duplicate.
int ShiftEnd = (int)std::size(m_aSavedColors) - 1;
for(int i = 0; i < (int)std::size(m_aSavedColors); ++i)
{
if(m_aSavedColors[i].Pack() == m_PipetteColor.Pack())
{
ShiftEnd = i;
break;
}
}
for(int i = ShiftEnd; i > 0; --i)
{
m_aSavedColors[i] = m_aSavedColors[i - 1];
}
m_aSavedColors[0] = m_PipetteColor;
}
if(ButtonResult > 0)
{
m_ColorPipetteActive = false;
}
}
}
void CEditor::RenderMousePointer()
{
if(!m_ShowMousePointer)
return;
constexpr float CursorSize = 16.0f;
// Cursor
Graphics()->WrapClamp();
Graphics()->TextureSet(m_aCursorTextures[m_CursorType]);
Graphics()->QuadsBegin();
if(m_CursorType == CURSOR_RESIZE_V)
Graphics()->QuadsSetRotation(pi / 2);
{
Graphics()->QuadsSetRotation(pi / 2.0f);
}
if(ms_pUiGotContext == UI()->HotItem())
Graphics()->SetColor(1, 0, 0, 1);
IGraphics::CQuadItem QuadItem(UI()->MouseX(), UI()->MouseY(), 16.0f, 16.0f);
{
Graphics()->SetColor(1.0f, 0.0f, 0.0f, 1.0f);
}
IGraphics::CQuadItem QuadItem(UI()->MouseX(), UI()->MouseY(), CursorSize, CursorSize);
Graphics()->QuadsDrawTL(&QuadItem, 1);
Graphics()->QuadsEnd();
Graphics()->WrapNormal();
// Pipette color
if(m_ColorPipetteActive)
{
CUIRect PipetteRect = {UI()->MouseX() + CursorSize, UI()->MouseY() + CursorSize, 80.0f, 20.0f};
if(PipetteRect.x + PipetteRect.w + 2.0f > UI()->Screen()->w)
{
PipetteRect.x = UI()->MouseX() - PipetteRect.w - CursorSize / 2.0f;
}
if(PipetteRect.y + PipetteRect.h + 2.0f > UI()->Screen()->h)
{
PipetteRect.y = UI()->MouseY() - PipetteRect.h - CursorSize / 2.0f;
}
PipetteRect.Draw(ColorRGBA(0.2f, 0.2f, 0.2f, 0.7f), IGraphics::CORNER_ALL, 3.0f);
CUIRect Pipette, Label;
PipetteRect.VSplitLeft(PipetteRect.h, &Pipette, &Label);
Pipette.Margin(2.0f, &Pipette);
Pipette.Draw(m_PipetteColor, IGraphics::CORNER_ALL, 3.0f);
char aLabel[8];
str_format(aLabel, sizeof(aLabel), "#%06X", m_PipetteColor.PackAlphaLast(false));
UI()->DoLabel(&Label, aLabel, 10.0f, TEXTALIGN_MC);
}
}
void CEditor::Reset(bool CreateDefault)
@ -8320,6 +8443,7 @@ void CEditor::Reset(bool CreateDefault)
m_MouseDeltaWx = 0;
m_MouseDeltaWy = 0;
m_pContainerPanned = nullptr;
m_pContainerPannedLast = nullptr;
m_Map.m_Modified = false;
m_Map.m_ModifiedAuto = false;
@ -8658,6 +8782,8 @@ void CEditor::OnUpdate()
Reset();
}
m_pContainerPannedLast = m_pContainerPanned;
// handle key presses
for(size_t i = 0; i < Input()->NumEvents(); i++)
{
@ -8735,6 +8861,8 @@ void CEditor::OnWindowResize()
void CEditor::OnClose()
{
m_ColorPipetteActive = false;
if(m_ToolbarPreviewSound >= 0 && Sound()->IsPlaying(m_ToolbarPreviewSound))
Sound()->Pause(m_ToolbarPreviewSound);
if(m_FilePreviewSound >= 0 && Sound()->IsPlaying(m_FilePreviewSound))

View file

@ -396,6 +396,11 @@ public:
m_QuadKnifeCount = 0;
mem_zero(m_aQuadKnifePoints, sizeof(m_aQuadKnifePoints));
for(size_t i = 0; i < std::size(m_aSavedColors); ++i)
{
m_aSavedColors[i] = color_cast<ColorRGBA>(ColorHSLA(i / (float)std::size(m_aSavedColors), 1.0f, 0.5f));
}
m_CheckerTexture.Invalidate();
m_BackgroundTexture.Invalidate();
for(int i = 0; i < NUM_CURSORS; i++)
@ -467,6 +472,7 @@ public:
void RenderPressedKeys(CUIRect View);
void RenderSavingIndicator(CUIRect View);
void FreeDynamicPopupMenus();
void UpdateColorPipette();
void RenderMousePointer();
std::vector<CQuad *> GetSelectedQuads();
@ -686,7 +692,8 @@ public:
float m_MouseDeltaY;
float m_MouseDeltaWx;
float m_MouseDeltaWy;
void *m_pContainerPanned;
const void *m_pContainerPanned;
const void *m_pContainerPannedLast;
enum EShowTile
{
@ -745,6 +752,11 @@ public:
int m_QuadKnifeCount;
vec2 m_aQuadKnifePoints[4];
// Color palette and pipette
ColorRGBA m_aSavedColors[8];
ColorRGBA m_PipetteColor = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
bool m_ColorPipetteActive = false;
IGraphics::CTextureHandle m_CheckerTexture;
IGraphics::CTextureHandle m_BackgroundTexture;