1265 lines
31 KiB
C++
1265 lines
31 KiB
C++
// 16 may 2016
|
|
#include "uipriv_windows.hpp"
|
|
|
|
// TODO should the d2dscratch programs capture mouse?
|
|
|
|
struct colorDialog {
|
|
HWND hwnd;
|
|
|
|
HWND svChooser;
|
|
HWND hSlider;
|
|
HWND preview;
|
|
HWND opacitySlider;
|
|
HWND editH;
|
|
HWND editS;
|
|
HWND editV;
|
|
HWND editRDouble, editRInt;
|
|
HWND editGDouble, editGInt;
|
|
HWND editBDouble, editBInt;
|
|
HWND editADouble, editAInt;
|
|
HWND editHex;
|
|
|
|
double h;
|
|
double s;
|
|
double v;
|
|
double a;
|
|
struct colorDialogRGBA *out;
|
|
|
|
BOOL updating;
|
|
};
|
|
|
|
// both of these are from the wikipedia page on HSV
|
|
// TODO what to do about negative h?
|
|
static void rgb2HSV(double r, double g, double b, double *h, double *s, double *v)
|
|
{
|
|
double M, m;
|
|
int whichmax;
|
|
double c;
|
|
|
|
M = r;
|
|
whichmax = 0;
|
|
if (M < g) {
|
|
M = g;
|
|
whichmax = 1;
|
|
}
|
|
if (M < b) {
|
|
M = b;
|
|
whichmax = 2;
|
|
}
|
|
m = r;
|
|
if (m > g)
|
|
m = g;
|
|
if (m > b)
|
|
m = b;
|
|
c = M - m;
|
|
|
|
if (c == 0)
|
|
*h = 0;
|
|
else {
|
|
switch (whichmax) {
|
|
case 0:
|
|
*h = ((g - b) / c);
|
|
*h = fmod(*h, 6);
|
|
break;
|
|
case 1:
|
|
*h = ((b - r) / c) + 2;
|
|
break;
|
|
case 2:
|
|
*h = ((r - g) / c) + 4;
|
|
break;
|
|
}
|
|
*h /= 6; // put in range [0,1)
|
|
}
|
|
|
|
*v = M;
|
|
|
|
if (c == 0)
|
|
*s = 0;
|
|
else
|
|
*s = c / *v;
|
|
}
|
|
|
|
// TODO negative R values?
|
|
static void hsv2RGB(double h, double s, double v, double *r, double *g, double *b)
|
|
{
|
|
double c;
|
|
double hPrime;
|
|
int h60;
|
|
double x;
|
|
double m;
|
|
double c1, c2;
|
|
|
|
c = v * s;
|
|
hPrime = h * 6;
|
|
h60 = (int) hPrime; // equivalent to splitting into 60° chunks
|
|
x = c * (1.0 - fabs(fmod(hPrime, 2) - 1.0));
|
|
m = v - c;
|
|
switch (h60) {
|
|
case 0:
|
|
*r = c + m;
|
|
*g = x + m;
|
|
*b = m;
|
|
return;
|
|
case 1:
|
|
*r = x + m;
|
|
*g = c + m;
|
|
*b = m;
|
|
return;
|
|
case 2:
|
|
*r = m;
|
|
*g = c + m;
|
|
*b = x + m;
|
|
return;
|
|
case 3:
|
|
*r = m;
|
|
*g = x + m;
|
|
*b = c + m;
|
|
return;
|
|
case 4:
|
|
*r = x + m;
|
|
*g = m;
|
|
*b = c + m;
|
|
return;
|
|
case 5:
|
|
*r = c + m;
|
|
*g = m;
|
|
*b = x + m;
|
|
return;
|
|
}
|
|
// TODO
|
|
}
|
|
|
|
#define hexd L"0123456789ABCDEF"
|
|
|
|
static void rgba2Hex(uint8_t r, uint8_t g, uint8_t b, uint8_t a, WCHAR *buf)
|
|
{
|
|
buf[0] = L'#';
|
|
buf[1] = hexd[(a >> 4) & 0xF];
|
|
buf[2] = hexd[a & 0xF];
|
|
buf[3] = hexd[(r >> 4) & 0xF];
|
|
buf[4] = hexd[r & 0xF];
|
|
buf[5] = hexd[(g >> 4) & 0xF];
|
|
buf[6] = hexd[g & 0xF];
|
|
buf[7] = hexd[(b >> 4) & 0xF];
|
|
buf[8] = hexd[b & 0xF];
|
|
buf[9] = L'\0';
|
|
}
|
|
|
|
static int convHexDigit(WCHAR c)
|
|
{
|
|
if (c >= L'0' && c <= L'9')
|
|
return c - L'0';
|
|
if (c >= L'A' && c <= L'F')
|
|
return c - L'A' + 0xA;
|
|
if (c >= L'a' && c <= L'f')
|
|
return c - L'a' + 0xA;
|
|
return -1;
|
|
}
|
|
|
|
// TODO allow #NNN shorthand
|
|
static BOOL hex2RGBA(WCHAR *buf, double *r, double *g, double *b, double *a)
|
|
{
|
|
uint8_t component;
|
|
int i;
|
|
|
|
if (*buf == L'#')
|
|
buf++;
|
|
|
|
component = 0;
|
|
i = convHexDigit(*buf++);
|
|
if (i < 0)
|
|
return FALSE;
|
|
component |= ((uint8_t) i) << 4;
|
|
i = convHexDigit(*buf++);
|
|
if (i < 0)
|
|
return FALSE;
|
|
component |= ((uint8_t) i);
|
|
*a = ((double) component) / 255;
|
|
|
|
component = 0;
|
|
i = convHexDigit(*buf++);
|
|
if (i < 0)
|
|
return FALSE;
|
|
component |= ((uint8_t) i) << 4;
|
|
i = convHexDigit(*buf++);
|
|
if (i < 0)
|
|
return FALSE;
|
|
component |= ((uint8_t) i);
|
|
*r = ((double) component) / 255;
|
|
|
|
component = 0;
|
|
i = convHexDigit(*buf++);
|
|
if (i < 0)
|
|
return FALSE;
|
|
component |= ((uint8_t) i) << 4;
|
|
i = convHexDigit(*buf++);
|
|
if (i < 0)
|
|
return FALSE;
|
|
component |= ((uint8_t) i);
|
|
*g = ((double) component) / 255;
|
|
|
|
if (*buf == L'\0') { // #NNNNNN syntax
|
|
*b = *g;
|
|
*g = *r;
|
|
*r = *a;
|
|
*a = 1;
|
|
return TRUE;
|
|
}
|
|
|
|
component = 0;
|
|
i = convHexDigit(*buf++);
|
|
if (i < 0)
|
|
return FALSE;
|
|
component |= ((uint8_t) i) << 4;
|
|
i = convHexDigit(*buf++);
|
|
if (i < 0)
|
|
return FALSE;
|
|
component |= ((uint8_t) i);
|
|
*b = ((double) component) / 255;
|
|
|
|
return *buf == L'\0';
|
|
}
|
|
|
|
static void updateDouble(HWND hwnd, double d, HWND whichChanged)
|
|
{
|
|
WCHAR *str;
|
|
|
|
if (whichChanged == hwnd)
|
|
return;
|
|
str = ftoutf16(d);
|
|
setWindowText(hwnd, str);
|
|
uiFree(str);
|
|
}
|
|
|
|
static void updateDialog(struct colorDialog *c, HWND whichChanged)
|
|
{
|
|
double r, g, b;
|
|
uint8_t rb, gb, bb, ab;
|
|
WCHAR *str;
|
|
WCHAR hexbuf[16]; // more than enough
|
|
|
|
c->updating = TRUE;
|
|
|
|
updateDouble(c->editH, c->h, whichChanged);
|
|
updateDouble(c->editS, c->s, whichChanged);
|
|
updateDouble(c->editV, c->v, whichChanged);
|
|
|
|
hsv2RGB(c->h, c->s, c->v, &r, &g, &b);
|
|
|
|
updateDouble(c->editRDouble, r, whichChanged);
|
|
updateDouble(c->editGDouble, g, whichChanged);
|
|
updateDouble(c->editBDouble, b, whichChanged);
|
|
updateDouble(c->editADouble, c->a, whichChanged);
|
|
|
|
rb = (uint8_t) (r * 255);
|
|
gb = (uint8_t) (g * 255);
|
|
bb = (uint8_t) (b * 255);
|
|
ab = (uint8_t) (c->a * 255);
|
|
|
|
if (whichChanged != c->editRInt) {
|
|
str = itoutf16(rb);
|
|
setWindowText(c->editRInt, str);
|
|
uiFree(str);
|
|
}
|
|
if (whichChanged != c->editGInt) {
|
|
str = itoutf16(gb);
|
|
setWindowText(c->editGInt, str);
|
|
uiFree(str);
|
|
}
|
|
if (whichChanged != c->editBInt) {
|
|
str = itoutf16(bb);
|
|
setWindowText(c->editBInt, str);
|
|
uiFree(str);
|
|
}
|
|
if (whichChanged != c->editAInt) {
|
|
str = itoutf16(ab);
|
|
setWindowText(c->editAInt, str);
|
|
uiFree(str);
|
|
}
|
|
|
|
if (whichChanged != c->editHex) {
|
|
rgba2Hex(rb, gb, bb, ab, hexbuf);
|
|
setWindowText(c->editHex, hexbuf);
|
|
}
|
|
|
|
// TODO TRUE?
|
|
invalidateRect(c->svChooser, NULL, TRUE);
|
|
invalidateRect(c->hSlider, NULL, TRUE);
|
|
invalidateRect(c->preview, NULL, TRUE);
|
|
invalidateRect(c->opacitySlider, NULL, TRUE);
|
|
|
|
c->updating = FALSE;
|
|
}
|
|
|
|
// this imitates http://blogs.msdn.com/b/wpfsdk/archive/2006/10/26/uncommon-dialogs--font-chooser-and-color-picker-dialogs.aspx
|
|
static void drawGrid(ID2D1RenderTarget *rt, D2D1_RECT_F *fillRect)
|
|
{
|
|
D2D1_SIZE_F size;
|
|
D2D1_PIXEL_FORMAT pformat;
|
|
ID2D1BitmapRenderTarget *brt;
|
|
D2D1_COLOR_F color;
|
|
D2D1_BRUSH_PROPERTIES bprop;
|
|
ID2D1SolidColorBrush *brush;
|
|
D2D1_RECT_F rect;
|
|
ID2D1Bitmap *bitmap;
|
|
D2D1_BITMAP_BRUSH_PROPERTIES bbp;
|
|
ID2D1BitmapBrush *bb;
|
|
HRESULT hr;
|
|
|
|
// mind the divisions; they represent the fact the original uses a viewport
|
|
size.width = 100 / 10;
|
|
size.height = 100 / 10;
|
|
// yay more ABI bugs
|
|
#ifdef _MSC_VER
|
|
pformat = rt->GetPixelFormat();
|
|
#else
|
|
{
|
|
typedef D2D1_PIXEL_FORMAT *(__stdcall ID2D1RenderTarget::* GetPixelFormatF)(D2D1_PIXEL_FORMAT *);
|
|
GetPixelFormatF gpf;
|
|
|
|
gpf = (GetPixelFormatF) (&(rt->GetPixelFormat));
|
|
(rt->*gpf)(&pformat);
|
|
}
|
|
#endif
|
|
hr = rt->CreateCompatibleRenderTarget(&size, NULL,
|
|
&pformat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE,
|
|
&brt);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error creating render target for grid", hr);
|
|
|
|
brt->BeginDraw();
|
|
|
|
color.r = 1.0;
|
|
color.g = 1.0;
|
|
color.b = 1.0;
|
|
color.a = 1.0;
|
|
brt->Clear(&color);
|
|
|
|
color = D2D1::ColorF(D2D1::ColorF::LightGray, 1.0);
|
|
ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));
|
|
bprop.opacity = 1.0;
|
|
bprop.transform._11 = 1;
|
|
bprop.transform._22 = 1;
|
|
hr = brt->CreateSolidColorBrush(&color, &bprop, &brush);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error creating brush for grid", hr);
|
|
rect.left = 0;
|
|
rect.top = 0;
|
|
rect.right = 50 / 10;
|
|
rect.bottom = 50 / 10;
|
|
brt->FillRectangle(&rect, brush);
|
|
rect.left = 50 / 10;
|
|
rect.top = 50 / 10;
|
|
rect.right = 100 / 10;
|
|
rect.bottom = 100 / 10;
|
|
brt->FillRectangle(&rect, brush);
|
|
brush->Release();
|
|
|
|
hr = brt->EndDraw(NULL, NULL);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error finalizing render target for grid", hr);
|
|
hr = brt->GetBitmap(&bitmap);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error getting bitmap for grid", hr);
|
|
brt->Release();
|
|
|
|
ZeroMemory(&bbp, sizeof (D2D1_BITMAP_BRUSH_PROPERTIES));
|
|
bbp.extendModeX = D2D1_EXTEND_MODE_WRAP;
|
|
bbp.extendModeY = D2D1_EXTEND_MODE_WRAP;
|
|
bbp.interpolationMode = D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR;
|
|
hr = rt->CreateBitmapBrush(bitmap, &bbp, &bprop, &bb);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error creating bitmap brush for grid", hr);
|
|
rt->FillRectangle(fillRect, bb);
|
|
bb->Release();
|
|
bitmap->Release();
|
|
}
|
|
|
|
// this interesting approach comes from http://blogs.msdn.com/b/wpfsdk/archive/2006/10/26/uncommon-dialogs--font-chooser-and-color-picker-dialogs.aspx
|
|
static void drawSVChooser(struct colorDialog *c, ID2D1RenderTarget *rt)
|
|
{
|
|
D2D1_SIZE_F size;
|
|
D2D1_RECT_F rect;
|
|
double rTop, gTop, bTop;
|
|
D2D1_GRADIENT_STOP stops[2];
|
|
ID2D1GradientStopCollection *collection;
|
|
D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lprop;
|
|
D2D1_BRUSH_PROPERTIES bprop;
|
|
ID2D1LinearGradientBrush *brush;
|
|
ID2D1LinearGradientBrush *opacity;
|
|
ID2D1Layer *layer;
|
|
D2D1_LAYER_PARAMETERS layerparams;
|
|
D2D1_ELLIPSE mparam;
|
|
D2D1_COLOR_F mcolor;
|
|
ID2D1SolidColorBrush *markerBrush;
|
|
HRESULT hr;
|
|
|
|
size = realGetSize(rt);
|
|
rect.left = 0;
|
|
rect.top = 0;
|
|
rect.right = size.width;
|
|
rect.bottom = size.height;
|
|
|
|
drawGrid(rt, &rect);
|
|
|
|
// first, draw a vertical gradient from the current hue at max S/V to black
|
|
// the source example draws it upside down; let's do so too just to be safe
|
|
hsv2RGB(c->h, 1.0, 1.0, &rTop, &gTop, &bTop);
|
|
stops[0].position = 0;
|
|
stops[0].color.r = 0.0;
|
|
stops[0].color.g = 0.0;
|
|
stops[0].color.b = 0.0;
|
|
stops[0].color.a = 1.0;
|
|
stops[1].position = 1;
|
|
stops[1].color.r = rTop;
|
|
stops[1].color.g = gTop;
|
|
stops[1].color.b = bTop;
|
|
stops[1].color.a = 1.0;
|
|
hr = rt->CreateGradientStopCollection(stops, 2,
|
|
D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP,
|
|
&collection);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error making gradient stop collection for first gradient in SV chooser", hr);
|
|
ZeroMemory(&lprop, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES));
|
|
lprop.startPoint.x = size.width / 2;
|
|
lprop.startPoint.y = size.height;
|
|
lprop.endPoint.x = size.width / 2;
|
|
lprop.endPoint.y = 0;
|
|
// TODO decide what to do about the duplication of this
|
|
ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));
|
|
bprop.opacity = c->a; // note this part; we also use it below for the layer
|
|
bprop.transform._11 = 1;
|
|
bprop.transform._22 = 1;
|
|
hr = rt->CreateLinearGradientBrush(&lprop, &bprop,
|
|
collection, &brush);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error making gradient brush for first gradient in SV chooser", hr);
|
|
rt->FillRectangle(&rect, brush);
|
|
brush->Release();
|
|
collection->Release();
|
|
|
|
// second, create an opacity mask for the third step: a horizontal gradientthat goes from opaque to translucent
|
|
stops[0].position = 0;
|
|
stops[0].color.r = 0.0;
|
|
stops[0].color.g = 0.0;
|
|
stops[0].color.b = 0.0;
|
|
stops[0].color.a = 1.0;
|
|
stops[1].position = 1;
|
|
stops[1].color.r = 0.0;
|
|
stops[1].color.g = 0.0;
|
|
stops[1].color.b = 0.0;
|
|
stops[1].color.a = 0.0;
|
|
hr = rt->CreateGradientStopCollection(stops, 2,
|
|
D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP,
|
|
&collection);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error making gradient stop collection for opacity mask gradient in SV chooser", hr);
|
|
ZeroMemory(&lprop, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES));
|
|
lprop.startPoint.x = 0;
|
|
lprop.startPoint.y = size.height / 2;
|
|
lprop.endPoint.x = size.width;
|
|
lprop.endPoint.y = size.height / 2;
|
|
ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));
|
|
bprop.opacity = 1.0;
|
|
bprop.transform._11 = 1;
|
|
bprop.transform._22 = 1;
|
|
hr = rt->CreateLinearGradientBrush(&lprop, &bprop,
|
|
collection, &opacity);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error making gradient brush for opacity mask gradient in SV chooser", hr);
|
|
collection->Release();
|
|
|
|
// finally, make a vertical gradient from white at the top to black at the bottom (right side up this time) and with the previous opacity mask
|
|
stops[0].position = 0;
|
|
stops[0].color.r = 1.0;
|
|
stops[0].color.g = 1.0;
|
|
stops[0].color.b = 1.0;
|
|
stops[0].color.a = 1.0;
|
|
stops[1].position = 1;
|
|
stops[1].color.r = 0.0;
|
|
stops[1].color.g = 0.0;
|
|
stops[1].color.b = 0.0;
|
|
stops[1].color.a = 1.0;
|
|
hr = rt->CreateGradientStopCollection(stops, 2,
|
|
D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP,
|
|
&collection);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error making gradient stop collection for second gradient in SV chooser", hr);
|
|
ZeroMemory(&lprop, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES));
|
|
lprop.startPoint.x = size.width / 2;
|
|
lprop.startPoint.y = 0;
|
|
lprop.endPoint.x = size.width / 2;
|
|
lprop.endPoint.y = size.height;
|
|
ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));
|
|
bprop.opacity = 1.0;
|
|
bprop.transform._11 = 1;
|
|
bprop.transform._22 = 1;
|
|
hr = rt->CreateLinearGradientBrush(&lprop, &bprop,
|
|
collection, &brush);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error making gradient brush for second gradient in SV chooser", hr);
|
|
// oh but wait we can't use FillRectangle() with an opacity mask
|
|
// and we can't use FillGeometry() with both an opacity mask and a non-bitmap
|
|
// layers it is!
|
|
hr = rt->CreateLayer(&size, &layer);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error making layer for second gradient in SV chooser", hr);
|
|
ZeroMemory(&layerparams, sizeof (D2D1_LAYER_PARAMETERS));
|
|
layerparams.contentBounds = rect;
|
|
// TODO make sure these are right
|
|
layerparams.geometricMask = NULL;
|
|
layerparams.maskAntialiasMode = D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
|
|
layerparams.maskTransform._11 = 1;
|
|
layerparams.maskTransform._22 = 1;
|
|
layerparams.opacity = c->a; // here's the other use of c->a to note
|
|
layerparams.opacityBrush = opacity;
|
|
layerparams.layerOptions = D2D1_LAYER_OPTIONS_NONE;
|
|
rt->PushLayer(&layerparams, layer);
|
|
rt->FillRectangle(&rect, brush);
|
|
rt->PopLayer();
|
|
layer->Release();
|
|
brush->Release();
|
|
collection->Release();
|
|
opacity->Release();
|
|
|
|
// and now we just draw the marker
|
|
ZeroMemory(&mparam, sizeof (D2D1_ELLIPSE));
|
|
mparam.point.x = c->s * size.width;
|
|
mparam.point.y = (1 - c->v) * size.height;
|
|
mparam.radiusX = 7;
|
|
mparam.radiusY = 7;
|
|
// TODO make the color contrast?
|
|
mcolor.r = 1.0;
|
|
mcolor.g = 1.0;
|
|
mcolor.b = 1.0;
|
|
mcolor.a = 1.0;
|
|
bprop.opacity = 1.0; // the marker should always be opaque
|
|
hr = rt->CreateSolidColorBrush(&mcolor, &bprop, &markerBrush);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error creating brush for SV chooser marker", hr);
|
|
rt->DrawEllipse(&mparam, markerBrush, 2, NULL);
|
|
markerBrush->Release();
|
|
}
|
|
|
|
static LRESULT CALLBACK svChooserSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
ID2D1RenderTarget *rt;
|
|
struct colorDialog *c;
|
|
D2D1_POINT_2F *pos;
|
|
D2D1_SIZE_F *size;
|
|
|
|
c = (struct colorDialog *) dwRefData;
|
|
switch (uMsg) {
|
|
case msgD2DScratchPaint:
|
|
rt = (ID2D1RenderTarget *) lParam;
|
|
drawSVChooser(c, rt);
|
|
return 0;
|
|
case msgD2DScratchLButtonDown:
|
|
pos = (D2D1_POINT_2F *) wParam;
|
|
size = (D2D1_SIZE_F *) lParam;
|
|
c->s = pos->x / size->width;
|
|
c->v = 1 - (pos->y / size->height);
|
|
updateDialog(c, NULL);
|
|
return 0;
|
|
case WM_NCDESTROY:
|
|
if (RemoveWindowSubclass(hwnd, svChooserSubProc, uIdSubclass) == FALSE)
|
|
logLastError(L"error removing color dialog SV chooser subclass");
|
|
break;
|
|
}
|
|
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
static void drawArrow(ID2D1RenderTarget *rt, D2D1_POINT_2F center, double hypot)
|
|
{
|
|
double leg;
|
|
D2D1_RECT_F rect;
|
|
D2D1_MATRIX_3X2_F oldtf, rotate;
|
|
D2D1_COLOR_F color;
|
|
D2D1_BRUSH_PROPERTIES bprop;
|
|
ID2D1SolidColorBrush *brush;
|
|
HRESULT hr;
|
|
|
|
// to avoid needing a geometry, this will just be a rotated square
|
|
// compute the length of each side; the diagonal of the square is 2 * offset to gradient
|
|
// a^2 + a^2 = c^2 -> 2a^2 = c^2
|
|
// a = sqrt(c^2/2)
|
|
hypot *= hypot;
|
|
hypot /= 2;
|
|
leg = sqrt(hypot);
|
|
rect.left = center.x - leg;
|
|
rect.top = center.y - leg;
|
|
rect.right = center.x + leg;
|
|
rect.bottom = center.y + leg;
|
|
|
|
// now we need to rotate the render target 45° (either way works) about the center point
|
|
rt->GetTransform(&oldtf);
|
|
rotate = oldtf * D2D1::Matrix3x2F::Rotation(45, center);
|
|
rt->SetTransform(&rotate);
|
|
|
|
// and draw
|
|
color.r = 0.0;
|
|
color.g = 0.0;
|
|
color.b = 0.0;
|
|
color.a = 1.0;
|
|
ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));
|
|
bprop.opacity = 1.0;
|
|
bprop.transform._11 = 1;
|
|
bprop.transform._22 = 1;
|
|
hr = rt->CreateSolidColorBrush(&color, &bprop, &brush);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error creating brush for arrow", hr);
|
|
rt->FillRectangle(&rect, brush);
|
|
brush->Release();
|
|
|
|
// clean up
|
|
rt->SetTransform(&oldtf);
|
|
}
|
|
|
|
// the gradient stuff also comes from http://blogs.msdn.com/b/wpfsdk/archive/2006/10/26/uncommon-dialogs--font-chooser-and-color-picker-dialogs.aspx
|
|
#define nStops (30)
|
|
#define degPerStop (360 / nStops)
|
|
#define stopIncr (1.0 / ((double) nStops))
|
|
|
|
static void drawHSlider(struct colorDialog *c, ID2D1RenderTarget *rt)
|
|
{
|
|
D2D1_SIZE_F size;
|
|
D2D1_RECT_F rect;
|
|
D2D1_GRADIENT_STOP stops[nStops];
|
|
double r, g, b;
|
|
int i;
|
|
double h;
|
|
ID2D1GradientStopCollection *collection;
|
|
D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lprop;
|
|
D2D1_BRUSH_PROPERTIES bprop;
|
|
ID2D1LinearGradientBrush *brush;
|
|
double hypot;
|
|
D2D1_POINT_2F center;
|
|
HRESULT hr;
|
|
|
|
size = realGetSize(rt);
|
|
rect.left = size.width / 6; // leftmost sixth for arrow
|
|
rect.top = 0;
|
|
rect.right = size.width;
|
|
rect.bottom = size.height;
|
|
|
|
for (i = 0; i < nStops; i++) {
|
|
h = ((double) (i * degPerStop)) / 360.0;
|
|
if (i == (nStops - 1))
|
|
h = 0;
|
|
hsv2RGB(h, 1.0, 1.0, &r, &g, &b);
|
|
stops[i].position = ((double) i) * stopIncr;
|
|
stops[i].color.r = r;
|
|
stops[i].color.g = g;
|
|
stops[i].color.b = b;
|
|
stops[i].color.a = 1.0;
|
|
}
|
|
// and pin the last one
|
|
stops[i - 1].position = 1.0;
|
|
|
|
hr = rt->CreateGradientStopCollection(stops, nStops,
|
|
// note that in this case this gamma is explicitly specified by the original
|
|
D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP,
|
|
&collection);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error creating stop collection for H slider gradient", hr);
|
|
ZeroMemory(&lprop, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES));
|
|
lprop.startPoint.x = (rect.right - rect.left) / 2;
|
|
lprop.startPoint.y = 0;
|
|
lprop.endPoint.x = (rect.right - rect.left) / 2;
|
|
lprop.endPoint.y = size.height;
|
|
ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));
|
|
bprop.opacity = 1.0;
|
|
bprop.transform._11 = 1;
|
|
bprop.transform._22 = 1;
|
|
hr = rt->CreateLinearGradientBrush(&lprop, &bprop,
|
|
collection, &brush);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error creating gradient brush for H slider", hr);
|
|
rt->FillRectangle(&rect, brush);
|
|
brush->Release();
|
|
collection->Release();
|
|
|
|
// now draw a black arrow
|
|
center.x = 0;
|
|
center.y = c->h * size.height;
|
|
hypot = rect.left;
|
|
drawArrow(rt, center, hypot);
|
|
}
|
|
|
|
static LRESULT CALLBACK hSliderSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
ID2D1RenderTarget *rt;
|
|
struct colorDialog *c;
|
|
D2D1_POINT_2F *pos;
|
|
D2D1_SIZE_F *size;
|
|
|
|
c = (struct colorDialog *) dwRefData;
|
|
switch (uMsg) {
|
|
case msgD2DScratchPaint:
|
|
rt = (ID2D1RenderTarget *) lParam;
|
|
drawHSlider(c, rt);
|
|
return 0;
|
|
case msgD2DScratchLButtonDown:
|
|
pos = (D2D1_POINT_2F *) wParam;
|
|
size = (D2D1_SIZE_F *) lParam;
|
|
c->h = pos->y / size->height;
|
|
updateDialog(c, NULL);
|
|
return 0;
|
|
case WM_NCDESTROY:
|
|
if (RemoveWindowSubclass(hwnd, hSliderSubProc, uIdSubclass) == FALSE)
|
|
logLastError(L"error removing color dialog H slider subclass");
|
|
break;
|
|
}
|
|
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
static void drawPreview(struct colorDialog *c, ID2D1RenderTarget *rt)
|
|
{
|
|
D2D1_SIZE_F size;
|
|
D2D1_RECT_F rect;
|
|
double r, g, b;
|
|
D2D1_COLOR_F color;
|
|
D2D1_BRUSH_PROPERTIES bprop;
|
|
ID2D1SolidColorBrush *brush;
|
|
HRESULT hr;
|
|
|
|
size = realGetSize(rt);
|
|
rect.left = 0;
|
|
rect.top = 0;
|
|
rect.right = size.width;
|
|
rect.bottom = size.height;
|
|
|
|
drawGrid(rt, &rect);
|
|
|
|
hsv2RGB(c->h, c->s, c->v, &r, &g, &b);
|
|
color.r = r;
|
|
color.g = g;
|
|
color.b = b;
|
|
color.a = c->a;
|
|
ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));
|
|
bprop.opacity = 1.0;
|
|
bprop.transform._11 = 1;
|
|
bprop.transform._22 = 1;
|
|
hr = rt->CreateSolidColorBrush(&color, &bprop, &brush);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error creating brush for preview", hr);
|
|
rt->FillRectangle(&rect, brush);
|
|
brush->Release();
|
|
}
|
|
|
|
static LRESULT CALLBACK previewSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
ID2D1RenderTarget *rt;
|
|
struct colorDialog *c;
|
|
|
|
c = (struct colorDialog *) dwRefData;
|
|
switch (uMsg) {
|
|
case msgD2DScratchPaint:
|
|
rt = (ID2D1RenderTarget *) lParam;
|
|
drawPreview(c, rt);
|
|
return 0;
|
|
case WM_NCDESTROY:
|
|
if (RemoveWindowSubclass(hwnd, previewSubProc, uIdSubclass) == FALSE)
|
|
logLastError(L"error removing color dialog previewer subclass");
|
|
break;
|
|
}
|
|
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
// once again, this is based on the Microsoft sample above
|
|
static void drawOpacitySlider(struct colorDialog *c, ID2D1RenderTarget *rt)
|
|
{
|
|
D2D1_SIZE_F size;
|
|
D2D1_RECT_F rect;
|
|
D2D1_GRADIENT_STOP stops[2];
|
|
ID2D1GradientStopCollection *collection;
|
|
D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lprop;
|
|
D2D1_BRUSH_PROPERTIES bprop;
|
|
ID2D1LinearGradientBrush *brush;
|
|
double hypot;
|
|
D2D1_POINT_2F center;
|
|
HRESULT hr;
|
|
|
|
size = realGetSize(rt);
|
|
rect.left = 0;
|
|
rect.top = 0;
|
|
rect.right = size.width;
|
|
rect.bottom = size.height * (5.0 / 6.0); // bottommost sixth for arrow
|
|
|
|
drawGrid(rt, &rect);
|
|
|
|
stops[0].position = 0.0;
|
|
stops[0].color.r = 0.0;
|
|
stops[0].color.g = 0.0;
|
|
stops[0].color.b = 0.0;
|
|
stops[0].color.a = 1.0;
|
|
stops[1].position = 1.0;
|
|
stops[1].color.r = 1.0; // this is the XAML color Transparent, as in the source
|
|
stops[1].color.g = 1.0;
|
|
stops[1].color.b = 1.0;
|
|
stops[1].color.a = 0.0;
|
|
hr = rt->CreateGradientStopCollection(stops, 2,
|
|
// note that in this case this gamma is explicitly specified by the original
|
|
D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP,
|
|
&collection);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error creating stop collection for opacity slider gradient", hr);
|
|
ZeroMemory(&lprop, sizeof (D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES));
|
|
lprop.startPoint.x = 0;
|
|
lprop.startPoint.y = (rect.bottom - rect.top) / 2;
|
|
lprop.endPoint.x = size.width;
|
|
lprop.endPoint.y = (rect.bottom - rect.top) / 2;
|
|
ZeroMemory(&bprop, sizeof (D2D1_BRUSH_PROPERTIES));
|
|
bprop.opacity = 1.0;
|
|
bprop.transform._11 = 1;
|
|
bprop.transform._22 = 1;
|
|
hr = rt->CreateLinearGradientBrush(&lprop, &bprop,
|
|
collection, &brush);
|
|
if (hr != S_OK)
|
|
logHRESULT(L"error creating gradient brush for opacity slider", hr);
|
|
rt->FillRectangle(&rect, brush);
|
|
brush->Release();
|
|
collection->Release();
|
|
|
|
// now draw a black arrow
|
|
center.x = (1 - c->a) * size.width;
|
|
center.y = size.height;
|
|
hypot = size.height - rect.bottom;
|
|
drawArrow(rt, center, hypot);
|
|
}
|
|
|
|
static LRESULT CALLBACK opacitySliderSubProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
ID2D1RenderTarget *rt;
|
|
struct colorDialog *c;
|
|
D2D1_POINT_2F *pos;
|
|
D2D1_SIZE_F *size;
|
|
|
|
c = (struct colorDialog *) dwRefData;
|
|
switch (uMsg) {
|
|
case msgD2DScratchPaint:
|
|
rt = (ID2D1RenderTarget *) lParam;
|
|
drawOpacitySlider(c, rt);
|
|
return 0;
|
|
case msgD2DScratchLButtonDown:
|
|
pos = (D2D1_POINT_2F *) wParam;
|
|
size = (D2D1_SIZE_F *) lParam;
|
|
c->a = 1 - (pos->x / size->width);
|
|
updateDialog(c, NULL);
|
|
return 0;
|
|
case WM_NCDESTROY:
|
|
if (RemoveWindowSubclass(hwnd, opacitySliderSubProc, uIdSubclass) == FALSE)
|
|
logLastError(L"error removing color dialog opacity slider subclass");
|
|
break;
|
|
}
|
|
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
// TODO extract into d2dscratch.cpp, use in font dialog
|
|
HWND replaceWithD2DScratch(HWND parent, int id, SUBCLASSPROC subproc, void *data)
|
|
{
|
|
HWND replace;
|
|
RECT r;
|
|
|
|
replace = getDlgItem(parent, id);
|
|
uiWindowsEnsureGetWindowRect(replace, &r);
|
|
mapWindowRect(NULL, parent, &r);
|
|
uiWindowsEnsureDestroyWindow(replace);
|
|
return newD2DScratch(parent, &r, (HMENU) id, subproc, (DWORD_PTR) data);
|
|
// TODO preserve Z-order
|
|
}
|
|
|
|
// a few issues:
|
|
// - some controls are positioned wrong; see http://stackoverflow.com/questions/37263267/why-are-some-of-my-controls-positioned-slightly-off-in-a-dialog-template-in-a-re
|
|
// - labels are too low; need to adjust them by the font's internal leading
|
|
// fixupControlPositions() and the following helper routines fix that for us
|
|
|
|
static LONG offsetTo(HWND a, HWND b)
|
|
{
|
|
RECT ra, rb;
|
|
|
|
uiWindowsEnsureGetWindowRect(a, &ra);
|
|
uiWindowsEnsureGetWindowRect(b, &rb);
|
|
return rb.top - ra.bottom;
|
|
}
|
|
|
|
static void moveWindowsUp(struct colorDialog *c, LONG by, ...)
|
|
{
|
|
va_list ap;
|
|
HWND cur;
|
|
RECT r;
|
|
|
|
va_start(ap, by);
|
|
for (;;) {
|
|
cur = va_arg(ap, HWND);
|
|
if (cur == NULL)
|
|
break;
|
|
uiWindowsEnsureGetWindowRect(cur, &r);
|
|
mapWindowRect(NULL, c->hwnd, &r);
|
|
r.top -= by;
|
|
r.bottom -= by;
|
|
// TODO this isn't technically during a resize
|
|
uiWindowsEnsureMoveWindowDuringResize(cur,
|
|
r.left, r.top,
|
|
r.right - r.left, r.bottom - r.top);
|
|
}
|
|
va_end(ap);
|
|
}
|
|
|
|
static void fixupControlPositions(struct colorDialog *c)
|
|
{
|
|
HWND labelH;
|
|
HWND labelS;
|
|
HWND labelV;
|
|
HWND labelR;
|
|
HWND labelG;
|
|
HWND labelB;
|
|
HWND labelA;
|
|
HWND labelHex;
|
|
LONG offset;
|
|
uiWindowsSizing sizing;
|
|
|
|
labelH = getDlgItem(c->hwnd, rcHLabel);
|
|
labelS = getDlgItem(c->hwnd, rcSLabel);
|
|
labelV = getDlgItem(c->hwnd, rcVLabel);
|
|
labelR = getDlgItem(c->hwnd, rcRLabel);
|
|
labelG = getDlgItem(c->hwnd, rcGLabel);
|
|
labelB = getDlgItem(c->hwnd, rcBLabel);
|
|
labelA = getDlgItem(c->hwnd, rcALabel);
|
|
labelHex = getDlgItem(c->hwnd, rcHexLabel);
|
|
|
|
offset = offsetTo(c->editH, c->editS);
|
|
moveWindowsUp(c, offset,
|
|
labelS, c->editS,
|
|
labelG, c->editGDouble, c->editGInt,
|
|
NULL);
|
|
offset = offsetTo(c->editS, c->editV);
|
|
moveWindowsUp(c, offset,
|
|
labelV, c->editV,
|
|
labelB, c->editBDouble, c->editBInt,
|
|
NULL);
|
|
offset = offsetTo(c->editBDouble, c->editADouble);
|
|
moveWindowsUp(c, offset,
|
|
labelA, c->editADouble, c->editAInt,
|
|
NULL);
|
|
|
|
getSizing(c->hwnd, &sizing, (HFONT) SendMessageW(labelH, WM_GETFONT, 0, 0));
|
|
offset = sizing.InternalLeading;
|
|
moveWindowsUp(c, offset,
|
|
labelH, labelS, labelV,
|
|
labelR, labelG, labelB, labelA,
|
|
labelHex,
|
|
NULL);
|
|
}
|
|
|
|
static struct colorDialog *beginColorDialog(HWND hwnd, LPARAM lParam)
|
|
{
|
|
struct colorDialog *c;
|
|
|
|
c = uiNew(struct colorDialog);
|
|
c->hwnd = hwnd;
|
|
c->out = (struct colorDialogRGBA *) lParam;
|
|
// load initial values now
|
|
rgb2HSV(c->out->r, c->out->g, c->out->b, &(c->h), &(c->s), &(c->v));
|
|
c->a = c->out->a;
|
|
|
|
// TODO set up d2dscratches
|
|
|
|
// TODO prefix all these with rcColor instead of just rc
|
|
c->editH = getDlgItem(c->hwnd, rcH);
|
|
c->editS = getDlgItem(c->hwnd, rcS);
|
|
c->editV = getDlgItem(c->hwnd, rcV);
|
|
c->editRDouble = getDlgItem(c->hwnd, rcRDouble);
|
|
c->editRInt = getDlgItem(c->hwnd, rcRInt);
|
|
c->editGDouble = getDlgItem(c->hwnd, rcGDouble);
|
|
c->editGInt = getDlgItem(c->hwnd, rcGInt);
|
|
c->editBDouble = getDlgItem(c->hwnd, rcBDouble);
|
|
c->editBInt = getDlgItem(c->hwnd, rcBInt);
|
|
c->editADouble = getDlgItem(c->hwnd, rcADouble);
|
|
c->editAInt = getDlgItem(c->hwnd, rcAInt);
|
|
c->editHex = getDlgItem(c->hwnd, rcHex);
|
|
|
|
c->svChooser = replaceWithD2DScratch(c->hwnd, rcColorSVChooser, svChooserSubProc, c);
|
|
c->hSlider = replaceWithD2DScratch(c->hwnd, rcColorHSlider, hSliderSubProc, c);
|
|
c->preview = replaceWithD2DScratch(c->hwnd, rcPreview, previewSubProc, c);
|
|
c->opacitySlider = replaceWithD2DScratch(c->hwnd, rcOpacitySlider, opacitySliderSubProc, c);
|
|
|
|
fixupControlPositions(c);
|
|
|
|
// and get the ball rolling
|
|
updateDialog(c, NULL);
|
|
return c;
|
|
}
|
|
|
|
static void endColorDialog(struct colorDialog *c, INT_PTR code)
|
|
{
|
|
if (EndDialog(c->hwnd, code) == 0)
|
|
logLastError(L"error ending color dialog");
|
|
uiFree(c);
|
|
}
|
|
|
|
// TODO make this void on the font dialog too
|
|
static void tryFinishDialog(struct colorDialog *c, WPARAM wParam)
|
|
{
|
|
// cancelling
|
|
if (LOWORD(wParam) != IDOK) {
|
|
endColorDialog(c, 1);
|
|
return;
|
|
}
|
|
|
|
// OK
|
|
hsv2RGB(c->h, c->s, c->v, &(c->out->r), &(c->out->g), &(c->out->b));
|
|
c->out->a = c->a;
|
|
endColorDialog(c, 2);
|
|
}
|
|
|
|
static double editDouble(HWND hwnd)
|
|
{
|
|
WCHAR *s;
|
|
double d;
|
|
|
|
s = windowText(hwnd);
|
|
d = _wtof(s);
|
|
uiFree(s);
|
|
return d;
|
|
}
|
|
|
|
static void hChanged(struct colorDialog *c)
|
|
{
|
|
double h;
|
|
|
|
h = editDouble(c->editH);
|
|
if (h < 0 || h >= 1.0) // note the >=
|
|
return;
|
|
c->h = h;
|
|
updateDialog(c, c->editH);
|
|
}
|
|
|
|
static void sChanged(struct colorDialog *c)
|
|
{
|
|
double s;
|
|
|
|
s = editDouble(c->editS);
|
|
if (s < 0 || s > 1)
|
|
return;
|
|
c->s = s;
|
|
updateDialog(c, c->editS);
|
|
}
|
|
|
|
static void vChanged(struct colorDialog *c)
|
|
{
|
|
double v;
|
|
|
|
v = editDouble(c->editV);
|
|
if (v < 0 || v > 1)
|
|
return;
|
|
c->v = v;
|
|
updateDialog(c, c->editV);
|
|
}
|
|
|
|
static void rDoubleChanged(struct colorDialog *c)
|
|
{
|
|
double r, g, b;
|
|
|
|
hsv2RGB(c->h, c->s, c->v, &r, &g, &b);
|
|
r = editDouble(c->editRDouble);
|
|
if (r < 0 || r > 1)
|
|
return;
|
|
rgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v));
|
|
updateDialog(c, c->editRDouble);
|
|
}
|
|
|
|
static void gDoubleChanged(struct colorDialog *c)
|
|
{
|
|
double r, g, b;
|
|
|
|
hsv2RGB(c->h, c->s, c->v, &r, &g, &b);
|
|
g = editDouble(c->editGDouble);
|
|
if (g < 0 || g > 1)
|
|
return;
|
|
rgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v));
|
|
updateDialog(c, c->editGDouble);
|
|
}
|
|
|
|
static void bDoubleChanged(struct colorDialog *c)
|
|
{
|
|
double r, g, b;
|
|
|
|
hsv2RGB(c->h, c->s, c->v, &r, &g, &b);
|
|
b = editDouble(c->editBDouble);
|
|
if (b < 0 || b > 1)
|
|
return;
|
|
rgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v));
|
|
updateDialog(c, c->editBDouble);
|
|
}
|
|
|
|
static void aDoubleChanged(struct colorDialog *c)
|
|
{
|
|
double a;
|
|
|
|
a = editDouble(c->editADouble);
|
|
if (a < 0 || a > 1)
|
|
return;
|
|
c->a = a;
|
|
updateDialog(c, c->editADouble);
|
|
}
|
|
|
|
static int editInt(HWND hwnd)
|
|
{
|
|
WCHAR *s;
|
|
int i;
|
|
|
|
s = windowText(hwnd);
|
|
i = _wtoi(s);
|
|
uiFree(s);
|
|
return i;
|
|
}
|
|
|
|
static void rIntChanged(struct colorDialog *c)
|
|
{
|
|
double r, g, b;
|
|
int i;
|
|
|
|
hsv2RGB(c->h, c->s, c->v, &r, &g, &b);
|
|
i = editInt(c->editRInt);
|
|
if (i < 0 || i > 255)
|
|
return;
|
|
r = ((double) i) / 255.0;
|
|
rgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v));
|
|
updateDialog(c, c->editRInt);
|
|
}
|
|
|
|
static void gIntChanged(struct colorDialog *c)
|
|
{
|
|
double r, g, b;
|
|
int i;
|
|
|
|
hsv2RGB(c->h, c->s, c->v, &r, &g, &b);
|
|
i = editInt(c->editGInt);
|
|
if (i < 0 || i > 255)
|
|
return;
|
|
g = ((double) i) / 255.0;
|
|
rgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v));
|
|
updateDialog(c, c->editGInt);
|
|
}
|
|
|
|
static void bIntChanged(struct colorDialog *c)
|
|
{
|
|
double r, g, b;
|
|
int i;
|
|
|
|
hsv2RGB(c->h, c->s, c->v, &r, &g, &b);
|
|
i = editInt(c->editBInt);
|
|
if (i < 0 || i > 255)
|
|
return;
|
|
b = ((double) i) / 255.0;
|
|
rgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v));
|
|
updateDialog(c, c->editBInt);
|
|
}
|
|
|
|
static void aIntChanged(struct colorDialog *c)
|
|
{
|
|
int a;
|
|
|
|
a = editInt(c->editAInt);
|
|
if (a < 0 || a > 255)
|
|
return;
|
|
c->a = ((double) a) / 255;
|
|
updateDialog(c, c->editAInt);
|
|
}
|
|
|
|
static void hexChanged(struct colorDialog *c)
|
|
{
|
|
WCHAR *buf;
|
|
double r, g, b, a;
|
|
BOOL is;
|
|
|
|
buf = windowText(c->editHex);
|
|
is = hex2RGBA(buf, &r, &g, &b, &a);
|
|
uiFree(buf);
|
|
if (!is)
|
|
return;
|
|
rgb2HSV(r, g, b, &(c->h), &(c->s), &(c->v));
|
|
c->a = a;
|
|
updateDialog(c, c->editHex);
|
|
}
|
|
|
|
// TODO change fontdialog to use this
|
|
// note that if we make this const, we get lots of weird compiler errors
|
|
static std::map<int, void (*)(struct colorDialog *)> changed = {
|
|
{ rcH, hChanged },
|
|
{ rcS, sChanged },
|
|
{ rcV, vChanged },
|
|
{ rcRDouble, rDoubleChanged },
|
|
{ rcGDouble, gDoubleChanged },
|
|
{ rcBDouble, bDoubleChanged },
|
|
{ rcADouble, aDoubleChanged },
|
|
{ rcRInt, rIntChanged },
|
|
{ rcGInt, gIntChanged },
|
|
{ rcBInt, bIntChanged },
|
|
{ rcAInt, aIntChanged },
|
|
{ rcHex, hexChanged },
|
|
};
|
|
|
|
static INT_PTR CALLBACK colorDialogDlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
struct colorDialog *c;
|
|
|
|
c = (struct colorDialog *) GetWindowLongPtrW(hwnd, DWLP_USER);
|
|
if (c == NULL) {
|
|
if (uMsg == WM_INITDIALOG) {
|
|
c = beginColorDialog(hwnd, lParam);
|
|
SetWindowLongPtrW(hwnd, DWLP_USER, (LONG_PTR) c);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
switch (uMsg) {
|
|
case WM_COMMAND:
|
|
SetWindowLongPtrW(c->hwnd, DWLP_MSGRESULT, 0); // just in case
|
|
switch (LOWORD(wParam)) {
|
|
case IDOK:
|
|
case IDCANCEL:
|
|
if (HIWORD(wParam) != BN_CLICKED)
|
|
return FALSE;
|
|
tryFinishDialog(c, wParam);
|
|
return TRUE;
|
|
case rcH:
|
|
case rcS:
|
|
case rcV:
|
|
case rcRDouble:
|
|
case rcGDouble:
|
|
case rcBDouble:
|
|
case rcADouble:
|
|
case rcRInt:
|
|
case rcGInt:
|
|
case rcBInt:
|
|
case rcAInt:
|
|
case rcHex:
|
|
if (HIWORD(wParam) != EN_CHANGE)
|
|
return FALSE;
|
|
if (c->updating) // prevent infinite recursion during an update
|
|
return FALSE;
|
|
(*(changed[LOWORD(wParam)]))(c);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL showColorDialog(HWND parent, struct colorDialogRGBA *c)
|
|
{
|
|
switch (DialogBoxParamW(hInstance, MAKEINTRESOURCE(rcColorDialog), parent, colorDialogDlgProc, (LPARAM) c)) {
|
|
case 1: // cancel
|
|
return FALSE;
|
|
case 2: // ok
|
|
// make the compiler happy by putting the return after the switch
|
|
break;
|
|
default:
|
|
logLastError(L"error running color dialog");
|
|
}
|
|
return TRUE;
|
|
}
|