Merge pull request #7744 from Robyt3/Editor-Color-Palette-Pipette

Add color palette and pipette to editor
This commit is contained in:
Dennis Felsing 2024-01-13 22:55:16 +00:00 committed by GitHub
commit 8dee975d66
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
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

@ -809,19 +809,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();
@ -829,8 +831,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)
@ -2780,6 +2780,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
@ -2806,23 +2832,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();
@ -976,8 +991,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;
@ -1244,6 +1257,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

@ -514,6 +514,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;