From 2813a8bd05472f8d8f37f88d89f771a328468d23 Mon Sep 17 00:00:00 2001 From: Daniel Dada Date: Wed, 17 Dec 2025 10:21:33 +0300 Subject: [PATCH] feat: added eyedropper --- src/core/version.hpp | 3 +- src/core/version.hpp.in | 1 - src/gui/CMakeLists.txt | 11 +++ src/gui/color_table_renderer.cpp | 88 ++++++++++++++++- src/gui/color_table_renderer.hpp | 12 +++ src/gui/screen_eyedropper.cpp | 158 +++++++++++++++++++++++++++++++ src/gui/screen_eyedropper.hpp | 9 ++ 7 files changed, 278 insertions(+), 4 deletions(-) create mode 100644 src/gui/screen_eyedropper.cpp create mode 100644 src/gui/screen_eyedropper.hpp diff --git a/src/core/version.hpp b/src/core/version.hpp index f0e15fb..74f79e2 100644 --- a/src/core/version.hpp +++ b/src/core/version.hpp @@ -1,13 +1,12 @@ #ifndef CLRSYNC_CORE_VERSION_HPP #define CLRSYNC_CORE_VERSION_HPP -#include #include namespace clrsync::core { -const std::string GIT_SEMVER = "0.1.4+git.g89888ad"; +const std::string GIT_SEMVER = "0.1.4+git.ge6bac8e"; const std::string version_string(); } // namespace clrsync::core diff --git a/src/core/version.hpp.in b/src/core/version.hpp.in index 7caf9ed..f2d54e5 100644 --- a/src/core/version.hpp.in +++ b/src/core/version.hpp.in @@ -1,7 +1,6 @@ #ifndef CLRSYNC_CORE_VERSION_HPP #define CLRSYNC_CORE_VERSION_HPP -#include #include namespace clrsync::core diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 27737be..c3ab368 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -2,6 +2,7 @@ set(GUI_SOURCES main.cpp color_scheme_editor.cpp color_table_renderer.cpp + screen_eyedropper.cpp preview_renderer.cpp theme_applier.cpp template_editor.cpp @@ -27,8 +28,18 @@ if(WIN32) clrsync_core glfw imgui + user32 + gdi32 OpenGL::GL ) +elseif(APPLE) + target_link_libraries(clrsync_gui PRIVATE + clrsync_core + glfw + imgui + OpenGL::GL + "-framework ApplicationServices" + ) else() target_link_libraries(clrsync_gui PRIVATE clrsync_core diff --git a/src/gui/color_table_renderer.cpp b/src/gui/color_table_renderer.cpp index 0ea6a16..40c1d96 100644 --- a/src/gui/color_table_renderer.cpp +++ b/src/gui/color_table_renderer.cpp @@ -1,4 +1,5 @@ #include "color_table_renderer.hpp" +#include "screen_eyedropper.hpp" #include "imgui.h" #include @@ -9,10 +10,19 @@ void color_table_renderer::render_color_row(const std::string &name, { const clrsync::core::color &col = current.get_color(name); + const bool is_picking = m_screen_pick.active && m_screen_pick.key == name; + ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted(name.c_str()); + const float key_col_width = ImGui::GetContentRegionAvail().x; + const bool copied = ImGui::Selectable(name.c_str(), false, 0, ImVec2(key_col_width, 0.0f)); + if (ImGui::IsItemHovered()) + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + if (copied) + { + ImGui::SetClipboardText(name.c_str()); + } ImGui::TableSetColumnIndex(1); { @@ -44,11 +54,35 @@ void color_table_renderer::render_color_row(const std::string &name, float c[4] = {((col.hex() >> 24) & 0xFF) / 255.0f, ((col.hex() >> 16) & 0xFF) / 255.0f, ((col.hex() >> 8) & 0xFF) / 255.0f, (col.hex() & 0xFF) / 255.0f}; + if (is_picking) + { + float sampled_rgb[3]; + if (clrsync::gui::sample_screen_rgb(sampled_rgb)) + { + m_screen_pick.rgba[0] = sampled_rgb[0]; + m_screen_pick.rgba[1] = sampled_rgb[1]; + m_screen_pick.rgba[2] = sampled_rgb[2]; + m_screen_pick.rgba[3] = m_screen_pick.alpha; + m_screen_pick.has_sample = true; + } + + if (m_screen_pick.has_sample) + { + c[0] = m_screen_pick.rgba[0]; + c[1] = m_screen_pick.rgba[1]; + c[2] = m_screen_pick.rgba[2]; + c[3] = m_screen_pick.rgba[3]; + } + } + ImGui::SetNextItemWidth(-FLT_MIN); if (ImGui::ColorEdit4(("##color_" + name).c_str(), c, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar)) { + if (is_picking) + m_screen_pick.active = false; + uint32_t r = (uint32_t)(c[0] * 255.0f); uint32_t g = (uint32_t)(c[1] * 255.0f); uint32_t b = (uint32_t)(c[2] * 255.0f); @@ -60,6 +94,51 @@ void color_table_renderer::render_color_row(const std::string &name, on_changed(); } + ImGui::SameLine(); + if (!is_picking) + { + if (ImGui::Button("Pick")) + { + m_screen_pick.active = true; + m_screen_pick.key = name; + m_screen_pick.alpha = c[3]; + m_screen_pick.has_sample = false; + } + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) + ImGui::SetTooltip("Pick a color from anywhere on the screen"); + } + else + { + if (ImGui::Button("Cancel")) + { + m_screen_pick.active = false; + } + + const bool confirm = + ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter) || + ImGui::IsKeyPressed(ImGuiKey_Space); + const bool cancel = ImGui::IsKeyPressed(ImGuiKey_Escape); + + if (cancel) + { + m_screen_pick.active = false; + } + else if (confirm && m_screen_pick.has_sample) + { + uint32_t r = (uint32_t)(c[0] * 255.0f); + uint32_t g = (uint32_t)(c[1] * 255.0f); + uint32_t b = (uint32_t)(c[2] * 255.0f); + uint32_t a = (uint32_t)(c[3] * 255.0f); + uint32_t hex = (r << 24) | (g << 16) | (b << 8) | a; + + controller.set_color(name, clrsync::core::color(hex)); + if (on_changed) + on_changed(); + + m_screen_pick.active = false; + } + } + ImGui::PopID(); } @@ -76,6 +155,13 @@ void color_table_renderer::render(const clrsync::core::palette& current, ImGui::Text("Color Variables"); ImGui::Separator(); + if (m_screen_pick.active) + { + ImGui::TextUnformatted( + "Screen picker active: move cursor anywhere, press Enter/Space to pick, Esc to cancel"); + ImGui::Spacing(); + } + auto draw_table = [&](const char *title, const std::vector &keys) { ImGui::TextUnformatted(title); diff --git a/src/gui/color_table_renderer.hpp b/src/gui/color_table_renderer.hpp index 7823362..377176b 100644 --- a/src/gui/color_table_renderer.hpp +++ b/src/gui/color_table_renderer.hpp @@ -4,6 +4,7 @@ #include "core/palette/palette.hpp" #include "palette_controller.hpp" #include +#include class color_table_renderer { @@ -19,6 +20,17 @@ private: const clrsync::core::palette& palette, palette_controller& controller, const OnColorChangedCallback& on_changed); + + struct screen_pick_state + { + bool active{false}; + std::string key; + float rgba[4]{0.0f, 0.0f, 0.0f, 1.0f}; + float alpha{1.0f}; + bool has_sample{false}; + }; + + screen_pick_state m_screen_pick; }; #endif // CLRSYNC_GUI_COLOR_TABLE_RENDERER_HPP diff --git a/src/gui/screen_eyedropper.cpp b/src/gui/screen_eyedropper.cpp new file mode 100644 index 0000000..bc4d8d6 --- /dev/null +++ b/src/gui/screen_eyedropper.cpp @@ -0,0 +1,158 @@ +#include "screen_eyedropper.hpp" + +#include + +#if defined(__APPLE__) +#include +#endif + +#if defined(__linux__) +#include +#include +#endif + +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif + +namespace clrsync::gui +{ +bool sample_screen_rgb(float out_rgb[3]) +{ +#ifdef _WIN32 + POINT pt; + if (!GetCursorPos(&pt)) + return false; + + HDC hdc = GetDC(nullptr); + if (!hdc) + return false; + + const COLORREF color = GetPixel(hdc, pt.x, pt.y); + ReleaseDC(nullptr, hdc); + + if (color == CLR_INVALID) + return false; + + const uint8_t r = GetRValue(color); + const uint8_t g = GetGValue(color); + const uint8_t b = GetBValue(color); + + out_rgb[0] = r / 255.0f; + out_rgb[1] = g / 255.0f; + out_rgb[2] = b / 255.0f; + return true; +#elif defined(__APPLE__) + CGEventRef event = CGEventCreate(nullptr); + if (!event) + return false; + const CGPoint pt = CGEventGetLocation(event); + CFRelease(event); + + const CGRect rect = CGRectMake(pt.x, pt.y, 1, 1); + CGImageRef image = + CGWindowListCreateImage(rect, kCGWindowListOptionOnScreenOnly, kCGNullWindowID, + kCGWindowImageDefault); + if (!image) + return false; + + uint8_t pixel[4] = {0, 0, 0, 255}; + CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); + if (!color_space) + { + CGImageRelease(image); + return false; + } + + CGContextRef ctx = CGBitmapContextCreate(pixel, 1, 1, 8, 4, color_space, + kCGImageAlphaPremultipliedLast | + kCGBitmapByteOrder32Big); + CGColorSpaceRelease(color_space); + if (!ctx) + { + CGImageRelease(image); + return false; + } + + CGContextDrawImage(ctx, CGRectMake(0, 0, 1, 1), image); + CGContextRelease(ctx); + CGImageRelease(image); + + out_rgb[0] = pixel[0] / 255.0f; + out_rgb[1] = pixel[1] / 255.0f; + out_rgb[2] = pixel[2] / 255.0f; + return true; +#elif defined(__linux__) + Display *display = XOpenDisplay(nullptr); + if (!display) + return false; + + const Window root = DefaultRootWindow(display); + Window root_ret, child_ret; + int root_x = 0, root_y = 0; + int win_x = 0, win_y = 0; + unsigned int mask = 0; + + if (!XQueryPointer(display, root, &root_ret, &child_ret, &root_x, &root_y, &win_x, &win_y, + &mask)) + { + XCloseDisplay(display); + return false; + } + + XImage *img = XGetImage(display, root, root_x, root_y, 1, 1, AllPlanes, ZPixmap); + if (!img) + { + XCloseDisplay(display); + return false; + } + + const unsigned long pixel = XGetPixel(img, 0, 0); + + auto channel_from_mask = [](unsigned long px, unsigned long mask_val) -> uint8_t { + if (mask_val == 0) + return 0; + + unsigned int shift = 0; + while ((mask_val & 1UL) == 0) + { + mask_val >>= 1; + shift++; + } + + unsigned int bits = 0; + while (mask_val & 1UL) + { + mask_val >>= 1; + bits++; + } + + if (bits == 0) + return 0; + + const unsigned long max_val = (bits >= 32) ? 0xFFFFFFFFUL : ((1UL << bits) - 1UL); + const unsigned long val = (px >> shift) & max_val; + const unsigned long scaled = (val * 255UL) / max_val; + return static_cast(scaled & 0xFF); + }; + + const uint8_t r = channel_from_mask(pixel, img->red_mask); + const uint8_t g = channel_from_mask(pixel, img->green_mask); + const uint8_t b = channel_from_mask(pixel, img->blue_mask); + + XDestroyImage(img); + XCloseDisplay(display); + + out_rgb[0] = r / 255.0f; + out_rgb[1] = g / 255.0f; + out_rgb[2] = b / 255.0f; + return true; +#else + (void)out_rgb; + return false; +#endif +} +} // namespace clrsync::gui diff --git a/src/gui/screen_eyedropper.hpp b/src/gui/screen_eyedropper.hpp new file mode 100644 index 0000000..e510980 --- /dev/null +++ b/src/gui/screen_eyedropper.hpp @@ -0,0 +1,9 @@ +#ifndef CLRSYNC_GUI_SCREEN_EYEDROPPER_HPP +#define CLRSYNC_GUI_SCREEN_EYEDROPPER_HPP + +namespace clrsync::gui +{ +bool sample_screen_rgb(float out_rgb[3]); +} + +#endif // CLRSYNC_GUI_SCREEN_EYEDROPPER_HPP