feat: added eyedropper

This commit is contained in:
2025-12-17 10:21:33 +03:00
parent e6bac8e220
commit 2813a8bd05
7 changed files with 278 additions and 4 deletions

View File

@@ -1,13 +1,12 @@
#ifndef CLRSYNC_CORE_VERSION_HPP
#define CLRSYNC_CORE_VERSION_HPP
#include <cstdint>
#include <string>
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

View File

@@ -1,7 +1,6 @@
#ifndef CLRSYNC_CORE_VERSION_HPP
#define CLRSYNC_CORE_VERSION_HPP
#include <cstdint>
#include <string>
namespace clrsync::core

View File

@@ -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

View File

@@ -1,4 +1,5 @@
#include "color_table_renderer.hpp"
#include "screen_eyedropper.hpp"
#include "imgui.h"
#include <vector>
@@ -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,10 +54,76 @@ 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);
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();
}
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);
@@ -58,6 +134,9 @@ void color_table_renderer::render_color_row(const std::string &name,
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<const char *> &keys) {
ImGui::TextUnformatted(title);

View File

@@ -4,6 +4,7 @@
#include "core/palette/palette.hpp"
#include "palette_controller.hpp"
#include <functional>
#include <string>
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

View File

@@ -0,0 +1,158 @@
#include "screen_eyedropper.hpp"
#include <cstdint>
#if defined(__APPLE__)
#include <ApplicationServices/ApplicationServices.h>
#endif
#if defined(__linux__)
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#endif
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#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<uint8_t>(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

View File

@@ -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