2016-05-16 18:28:30 -05:00
// 16 may 2016
# include "uipriv_windows.hpp"
2016-05-17 19:46:28 -05:00
// TODO should the d2dscratch programs capture mouse?
2016-05-16 18:28:30 -05:00
struct colorDialog {
HWND hwnd ;
HWND svChooser ;
HWND hSlider ;
2016-05-17 12:44:14 -05:00
HWND preview ;
2016-05-16 18:28:30 -05:00
HWND opacitySlider ;
HWND editH ;
HWND editS ;
HWND editV ;
HWND editRDouble , editRInt ;
HWND editGDouble , editGInt ;
HWND editBDouble , editBInt ;
HWND editADouble , editAInt ;
HWND editHex ;
2016-05-17 12:52:56 -05:00
double h ;
double s ;
double v ;
2016-05-16 18:28:30 -05:00
double a ;
struct colorDialogRGBA * out ;
2016-05-17 12:44:14 -05:00
BOOL updating ;
2016-05-16 18:28:30 -05:00
} ;
2016-05-17 11:18:36 -05:00
// both of these are from the wikipedia page on HSV
2016-05-17 16:00:00 -05:00
// TODO what to do about negative h?
2016-05-16 22:54:28 -05:00
static void rgb2HSV ( double r , double g , double b , double * h , double * s , double * v )
2016-05-16 18:28:30 -05:00
{
2016-05-16 22:54:28 -05:00
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 ) ;
2016-05-17 16:00:00 -05:00
* h = fmod ( * h , 6 ) ;
2016-05-16 22:54:28 -05:00
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 ;
2016-05-16 18:28:30 -05:00
}
2016-05-17 16:41:38 -05:00
// TODO negative R values?
2016-05-16 22:54:28 -05:00
static void hsv2RGB ( double h , double s , double v , double * r , double * g , double * b )
2016-05-16 18:28:30 -05:00
{
2016-05-16 22:54:28 -05:00
double c ;
2016-05-17 12:44:14 -05:00
double hPrime ;
int h60 ;
2016-05-16 22:54:28 -05:00
double x ;
double m ;
double c1 , c2 ;
c = v * s ;
2016-05-17 12:44:14 -05:00
hPrime = h * 6 ;
h60 = ( int ) hPrime ; // equivalent to splitting into 60° chunks
x = c * ( 1.0 - fabs ( fmod ( hPrime , 2 ) - 1.0 ) ) ;
2016-05-16 22:54:28 -05:00
m = v - c ;
2016-05-17 12:44:14 -05:00
switch ( h60 ) {
2016-05-16 22:54:28 -05:00
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 ;
2016-05-16 18:28:30 -05:00
}
2016-05-16 22:54:28 -05:00
// TODO
}
2016-05-16 18:28:30 -05:00
2016-05-17 16:41:38 -05:00
# 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 ' ;
}
2016-05-17 12:44:14 -05:00
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 )
{
2016-05-17 12:52:56 -05:00
double r , g , b ;
2016-05-17 12:44:14 -05:00
uint8_t rb , gb , bb , ab ;
WCHAR * str ;
2016-05-17 16:41:38 -05:00
WCHAR hexbuf [ 16 ] ; // more than enough
2016-05-17 12:44:14 -05:00
c - > updating = TRUE ;
2016-05-17 12:52:56 -05:00
updateDouble ( c - > editH , c - > h , whichChanged ) ;
updateDouble ( c - > editS , c - > s , whichChanged ) ;
updateDouble ( c - > editV , c - > v , whichChanged ) ;
2016-05-17 12:44:14 -05:00
2016-05-17 12:52:56 -05:00
hsv2RGB ( c - > h , c - > s , c - > v , & r , & g , & b ) ;
2016-05-17 12:44:14 -05:00
2016-05-17 12:52:56 -05:00
updateDouble ( c - > editRDouble , r , whichChanged ) ;
updateDouble ( c - > editGDouble , g , whichChanged ) ;
updateDouble ( c - > editBDouble , b , whichChanged ) ;
2016-05-17 12:44:14 -05:00
updateDouble ( c - > editADouble , c - > a , whichChanged ) ;
2016-05-17 12:52:56 -05:00
rb = ( uint8_t ) ( r * 255 ) ;
gb = ( uint8_t ) ( g * 255 ) ;
bb = ( uint8_t ) ( b * 255 ) ;
2016-05-17 12:44:14 -05:00
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 ) {
2016-05-17 16:41:38 -05:00
rgba2Hex ( rb , gb , bb , ab , hexbuf ) ;
setWindowText ( c - > editHex , hexbuf ) ;
2016-05-17 12:44:14 -05:00
}
// TODO TRUE?
invalidateRect ( c - > svChooser , NULL , TRUE ) ;
2016-05-17 19:46:28 -05:00
invalidateRect ( c - > hSlider , NULL , TRUE ) ;
2016-05-17 23:20:41 -05:00
invalidateRect ( c - > preview , NULL , TRUE ) ;
2016-05-17 12:44:14 -05:00
//TODO invalidateRect(c->opacitySlider, NULL, TRUE);
c - > updating = FALSE ;
}
2016-05-17 21:55:35 -05:00
// 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 ;
pformat = rt - > GetPixelFormat ( ) ;
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 ( ) ;
}
2016-05-17 11:18:36 -05:00
// this interesting approach comes from http://blogs.msdn.com/b/wpfsdk/archive/2006/10/26/uncommon-dialogs--font-chooser-and-color-picker-dialogs.aspx
2016-05-16 22:54:28 -05:00
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 ;
2016-05-17 11:18:36 -05:00
ID2D1LinearGradientBrush * opacity ;
ID2D1Layer * layer ;
D2D1_LAYER_PARAMETERS layerparams ;
D2D1_ELLIPSE mparam ;
D2D1_COLOR_F mcolor ;
ID2D1SolidColorBrush * markerBrush ;
2016-05-16 22:54:28 -05:00
HRESULT hr ;
size = rt - > GetSize ( ) ;
rect . left = 0 ;
rect . top = 0 ;
rect . right = size . width ;
rect . bottom = size . height ;
2016-05-17 21:55:35 -05:00
drawGrid ( rt , & rect ) ;
2016-05-16 22:54:28 -05:00
// 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
2016-05-17 12:52:56 -05:00
hsv2RGB ( c - > h , 1.0 , 1.0 , & rTop , & gTop , & bTop ) ;
2016-05-16 22:54:28 -05:00
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 ;
2016-05-17 21:55:35 -05:00
// TODO decide what to do about the duplication of this
2016-05-16 22:54:28 -05:00
ZeroMemory ( & bprop , sizeof ( D2D1_BRUSH_PROPERTIES ) ) ;
2016-05-17 15:46:47 -05:00
bprop . opacity = c - > a ; // note this part; we also use it below for the layer
2016-05-16 22:54:28 -05:00
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 ( ) ;
2016-05-17 11:18:36 -05:00
// 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 ;
2016-05-17 15:46:47 -05:00
layerparams . opacity = c - > a ; // here's the other use of c->a to note
2016-05-17 11:18:36 -05:00
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 ) ) ;
2016-05-17 12:52:56 -05:00
mparam . point . x = c - > s * size . width ;
mparam . point . y = ( 1 - c - > v ) * size . height ;
2016-05-17 11:18:36 -05:00
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 ;
2016-05-17 15:46:47 -05:00
bprop . opacity = 1.0 ; // the marker should always be opaque
2016-05-17 11:18:36 -05:00
hr = rt - > CreateSolidColorBrush ( & mcolor , & bprop , & markerBrush ) ;
if ( hr ! = S_OK )
2016-05-17 19:46:28 -05:00
logHRESULT ( L " error creating brush for SV chooser marker " , hr ) ;
2016-05-17 11:18:36 -05:00
rt - > DrawEllipse ( & mparam , markerBrush , 2 , NULL ) ;
markerBrush - > Release ( ) ;
2016-05-16 22:54:28 -05:00
}
static LRESULT CALLBACK svChooserSubProc ( HWND hwnd , UINT uMsg , WPARAM wParam , LPARAM lParam , UINT_PTR uIdSubclass , DWORD_PTR dwRefData )
{
ID2D1RenderTarget * rt ;
struct colorDialog * c ;
2016-05-17 13:44:57 -05:00
D2D1_POINT_2F * pos ;
D2D1_SIZE_F * size ;
2016-05-16 22:54:28 -05:00
2016-05-17 13:44:57 -05:00
c = ( struct colorDialog * ) dwRefData ;
2016-05-16 22:54:28 -05:00
switch ( uMsg ) {
case msgD2DScratchPaint :
rt = ( ID2D1RenderTarget * ) lParam ;
drawSVChooser ( c , rt ) ;
return 0 ;
2016-05-17 13:44:57 -05:00
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 ;
2016-05-16 22:54:28 -05:00
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 ) ;
}
2016-05-17 19:46:28 -05:00
// 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 , leg ;
D2D1_POINT_2F center ;
D2D1_MATRIX_3X2_F oldtf , rotate ;
D2D1_COLOR_F mcolor ;
ID2D1SolidColorBrush * markerBrush ;
HRESULT hr ;
size = rt - > GetSize ( ) ;
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
// to avoid needing a geometry, this will just be a rotated square
center . x = 0 ;
center . y = c - > h * size . height ;
// compute the length of each side; the diagonal of the square is 2 * offset to gradient
hypot = rect . left ;
// a^2 + a^2 = c^2 -> 2a^2 = c^2
// a = sqrt(c^2/2)
hypot * = hypot ;
hypot / = 2 ;
leg = sqrt ( hypot ) ;
rect . left = - leg ;
rect . top = center . y - leg ;
rect . right = 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
mcolor . r = 0.0 ;
mcolor . g = 0.0 ;
mcolor . b = 0.0 ;
mcolor . a = 1.0 ;
hr = rt - > CreateSolidColorBrush ( & mcolor , & bprop , & markerBrush ) ;
if ( hr ! = S_OK )
logHRESULT ( L " error creating brush for H slider marker " , hr ) ;
rt - > FillRectangle ( & rect , markerBrush ) ;
markerBrush - > Release ( ) ;
// clean up
rt - > SetTransform ( & oldtf ) ;
}
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 ) ;
}
2016-05-17 23:20:41 -05:00
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 = rt - > GetSize ( ) ;
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 ) ;
}
2016-05-16 22:54:28 -05:00
// 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
2016-05-16 18:28:30 -05:00
}
// 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 ) ;
// TODO this uses the message font, not the dialog font
uiWindowsGetSizing ( c - > hwnd , & sizing ) ;
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 ;
2016-05-17 12:52:56 -05:00
// load initial values now
rgb2HSV ( c - > out - > r , c - > out - > g , c - > out - > b , & ( c - > h ) , & ( c - > s ) , & ( c - > v ) ) ;
2016-05-16 18:28:30 -05:00
c - > a = c - > out - > a ;
// TODO set up d2dscratches
2016-05-16 22:54:28 -05:00
// TODO prefix all these with rcColor instead of just rc
2016-05-16 18:28:30 -05:00
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 ) ;
2016-05-16 22:54:28 -05:00
c - > svChooser = replaceWithD2DScratch ( c - > hwnd , rcColorSVChooser , svChooserSubProc , c ) ;
2016-05-17 19:46:28 -05:00
c - > hSlider = replaceWithD2DScratch ( c - > hwnd , rcColorHSlider , hSliderSubProc , c ) ;
2016-05-17 23:20:41 -05:00
c - > preview = replaceWithD2DScratch ( c - > hwnd , rcPreview , previewSubProc , c ) ;
2016-05-16 22:54:28 -05:00
2016-05-16 18:28:30 -05:00
fixupControlPositions ( c ) ;
2016-05-17 12:44:14 -05:00
// and get the ball rolling
updateDialog ( c , NULL ) ;
2016-05-16 18:28:30 -05:00
return c ;
}
2016-05-17 15:46:47 -05:00
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 ) ;
}
2016-05-17 12:44:14 -05:00
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 )
{
2016-05-17 12:52:56 -05:00
double h ;
2016-05-17 12:44:14 -05:00
h = editDouble ( c - > editH ) ;
if ( h < 0 | | h > = 1.0 ) // note the >=
return ;
2016-05-17 12:52:56 -05:00
c - > h = h ;
2016-05-17 12:44:14 -05:00
updateDialog ( c , c - > editH ) ;
}
static void sChanged ( struct colorDialog * c )
{
2016-05-17 12:52:56 -05:00
double s ;
2016-05-17 12:44:14 -05:00
s = editDouble ( c - > editS ) ;
if ( s < 0 | | s > 1 )
return ;
2016-05-17 12:52:56 -05:00
c - > s = s ;
2016-05-17 12:44:14 -05:00
updateDialog ( c , c - > editS ) ;
}
static void vChanged ( struct colorDialog * c )
{
2016-05-17 12:52:56 -05:00
double v ;
2016-05-17 12:44:14 -05:00
v = editDouble ( c - > editV ) ;
if ( v < 0 | | v > 1 )
return ;
2016-05-17 12:52:56 -05:00
c - > v = v ;
2016-05-17 12:44:14 -05:00
updateDialog ( c , c - > editV ) ;
}
2016-05-17 15:46:47 -05:00
static void rDoubleChanged ( struct colorDialog * c )
2016-05-16 22:54:28 -05:00
{
2016-05-17 15:46:47 -05:00
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 ) ;
2016-05-16 22:54:28 -05:00
}
2016-05-17 15:46:47 -05:00
static void gDoubleChanged ( struct colorDialog * c )
2016-05-16 22:54:28 -05:00
{
2016-05-17 15:46:47 -05:00
double r , g , b ;
hsv2RGB ( c - > h , c - > s , c - > v , & r , & g , & b ) ;
g = editDouble ( c - > editGDouble ) ;
if ( g < 0 | | g > 1 )
2016-05-17 12:44:14 -05:00
return ;
2016-05-17 15:46:47 -05:00
rgb2HSV ( r , g , b , & ( c - > h ) , & ( c - > s ) , & ( c - > v ) ) ;
updateDialog ( c , c - > editGDouble ) ;
}
2016-05-16 22:54:28 -05:00
2016-05-17 15:46:47 -05:00
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 ) ;
2016-05-16 22:54:28 -05:00
}
2016-05-17 16:00:00 -05:00
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 ) ;
}
2016-05-17 16:41:38 -05:00
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 ) ;
}
2016-05-17 12:44:14 -05:00
// 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 } ,
2016-05-17 15:46:47 -05:00
{ rcRDouble , rDoubleChanged } ,
{ rcGDouble , gDoubleChanged } ,
{ rcBDouble , bDoubleChanged } ,
{ rcADouble , aDoubleChanged } ,
2016-05-17 16:00:00 -05:00
{ rcRInt , rIntChanged } ,
{ rcGInt , gIntChanged } ,
{ rcBInt , bIntChanged } ,
{ rcAInt , aIntChanged } ,
2016-05-17 16:41:38 -05:00
{ rcHex , hexChanged } ,
2016-05-17 12:44:14 -05:00
} ;
2016-05-16 18:28:30 -05:00
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 ;
2016-05-17 12:44:14 -05:00
tryFinishDialog ( c , wParam ) ;
return TRUE ;
case rcH :
case rcS :
case rcV :
2016-05-17 15:46:47 -05:00
case rcRDouble :
case rcGDouble :
case rcBDouble :
case rcADouble :
2016-05-17 16:00:00 -05:00
case rcRInt :
case rcGInt :
case rcBInt :
case rcAInt :
2016-05-17 16:41:38 -05:00
case rcHex :
2016-05-17 12:44:14 -05:00
if ( HIWORD ( wParam ) ! = EN_CHANGE )
return FALSE ;
if ( c - > updating ) // prevent infinite recursion during an update
return FALSE ;
( * ( changed [ LOWORD ( wParam ) ] ) ) ( c ) ;
return TRUE ;
2016-05-16 18:28:30 -05:00
}
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 ;
}