2015-09-16 22:15:42 -05:00
// 7 september 2015
2015-10-09 10:17:58 -05:00
# include "uipriv_windows.h"
2015-09-16 22:15:42 -05:00
2015-12-22 00:02:06 -06:00
// TODO
2015-12-22 00:06:18 -06:00
// - write a test for transform followed by clip and clip followed by transform to make sure they work the same as on gtk+ and cocoa
2015-12-22 00:02:06 -06:00
2015-09-16 22:15:42 -05:00
static ID2D1Factory * d2dfactory = NULL ;
HRESULT initDraw ( void )
{
D2D1_FACTORY_OPTIONS opts ;
ZeroMemory ( & opts , sizeof ( D2D1_FACTORY_OPTIONS ) ) ;
// TODO make this an option
opts . debugLevel = D2D1_DEBUG_LEVEL_NONE ;
return D2D1CreateFactory ( D2D1_FACTORY_TYPE_SINGLE_THREADED ,
& IID_ID2D1Factory ,
& opts ,
2015-09-17 09:31:22 -05:00
( void * * ) ( & d2dfactory ) ) ;
2015-09-16 22:15:42 -05:00
}
void uninitDraw ( void )
{
ID2D1Factory_Release ( d2dfactory ) ;
}
2015-09-16 22:42:24 -05:00
ID2D1HwndRenderTarget * makeHWNDRenderTarget ( HWND hwnd )
{
D2D1_RENDER_TARGET_PROPERTIES props ;
D2D1_HWND_RENDER_TARGET_PROPERTIES hprops ;
HDC dc ;
RECT r ;
ID2D1HwndRenderTarget * rt ;
2015-09-17 09:31:22 -05:00
HRESULT hr ;
2015-09-16 22:42:24 -05:00
// we need a DC for the DPI
// we *could* just use the screen DPI but why when we have a window handle and its DC has a DPI
dc = GetDC ( hwnd ) ;
if ( dc = = NULL )
logLastError ( " error getting DC to find DPI in makeHWNDRenderTarget() " ) ;
ZeroMemory ( & props , sizeof ( D2D1_RENDER_TARGET_PROPERTIES ) ) ;
props . type = D2D1_RENDER_TARGET_TYPE_DEFAULT ;
props . pixelFormat . format = DXGI_FORMAT_UNKNOWN ;
props . pixelFormat . alphaMode = D2D1_ALPHA_MODE_UNKNOWN ;
props . dpiX = GetDeviceCaps ( dc , LOGPIXELSX ) ;
props . dpiY = GetDeviceCaps ( dc , LOGPIXELSY ) ;
props . usage = D2D1_RENDER_TARGET_USAGE_NONE ;
props . minLevel = D2D1_FEATURE_LEVEL_DEFAULT ;
if ( ReleaseDC ( hwnd , dc ) = = 0 )
logLastError ( " error releasing DC for finding DPI in makeHWNDRenderTarget() " ) ;
if ( GetClientRect ( hwnd , & r ) = = 0 )
logLastError ( " error getting current size of window in makeHWNDRenderTarget() " ) ;
ZeroMemory ( & hprops , sizeof ( D2D1_HWND_RENDER_TARGET_PROPERTIES ) ) ;
hprops . hwnd = hwnd ;
hprops . pixelSize . width = r . right - r . left ;
hprops . pixelSize . height = r . bottom - r . top ;
2015-12-04 19:45:31 -06:00
// according to Rick Brewster, some drivers will misbehave if we don't specify this (see http://stackoverflow.com/a/33222983/3408572)
hprops . presentOptions = D2D1_PRESENT_OPTIONS_RETAIN_CONTENTS ;
2015-09-16 22:42:24 -05:00
2015-09-17 09:31:22 -05:00
hr = ID2D1Factory_CreateHwndRenderTarget ( d2dfactory ,
2015-09-16 22:42:24 -05:00
& props ,
& hprops ,
& rt ) ;
if ( hr ! = S_OK )
logHRESULT ( " error creating area HWND render target in makeHWNDRenderTarget() " , hr ) ;
return rt ;
}
2015-10-06 18:56:38 -05:00
struct uiDrawPath {
ID2D1PathGeometry * path ;
ID2D1GeometrySink * sink ;
BOOL inFigure ;
} ;
uiDrawPath * uiDrawNewPath ( uiDrawFillMode fillmode )
{
uiDrawPath * p ;
HRESULT hr ;
2015-10-09 10:49:57 -05:00
p = uiNew ( uiDrawPath ) ;
2015-10-06 18:56:38 -05:00
hr = ID2D1Factory_CreatePathGeometry ( d2dfactory ,
& ( p - > path ) ) ;
if ( hr ! = S_OK )
logHRESULT ( " error creating path in uiDrawNewPath() " , hr ) ;
hr = ID2D1PathGeometry_Open ( p - > path ,
& ( p - > sink ) ) ;
if ( hr ! = S_OK )
logHRESULT ( " error opening path in uiDrawNewPath() " , hr ) ;
switch ( fillmode ) {
2015-10-07 10:12:18 -05:00
case uiDrawFillModeWinding :
ID2D1GeometrySink_SetFillMode ( p - > sink ,
2015-10-06 18:56:38 -05:00
D2D1_FILL_MODE_WINDING ) ;
break ;
case uiDrawFillModeAlternate :
2015-10-07 10:12:18 -05:00
ID2D1GeometrySink_SetFillMode ( p - > sink ,
2015-10-06 18:56:38 -05:00
D2D1_FILL_MODE_ALTERNATE ) ;
break ;
}
return p ;
}
void uiDrawFreePath ( uiDrawPath * p )
{
if ( p - > inFigure )
ID2D1GeometrySink_EndFigure ( p - > sink ,
D2D1_FIGURE_END_OPEN ) ;
if ( p - > sink ! = NULL )
// TODO close sink first?
ID2D1GeometrySink_Release ( p - > sink ) ;
ID2D1PathGeometry_Release ( p - > path ) ;
2015-10-09 10:49:57 -05:00
uiFree ( p ) ;
2015-10-06 18:56:38 -05:00
}
void uiDrawPathNewFigure ( uiDrawPath * p , double x , double y )
2015-09-16 22:15:42 -05:00
{
2015-10-06 18:56:38 -05:00
D2D1_POINT_2F pt ;
2015-09-17 12:10:38 -05:00
2015-10-06 18:56:38 -05:00
if ( p - > inFigure )
2015-10-07 10:12:18 -05:00
ID2D1GeometrySink_EndFigure ( p - > sink ,
2015-09-17 12:10:38 -05:00
D2D1_FIGURE_END_OPEN ) ;
2015-10-07 10:12:18 -05:00
pt . x = x ;
pt . y = y ;
2015-10-06 18:56:38 -05:00
ID2D1GeometrySink_BeginFigure ( p - > sink ,
pt ,
2015-09-17 12:10:38 -05:00
D2D1_FIGURE_BEGIN_FILLED ) ;
2015-10-06 18:56:38 -05:00
p - > inFigure = TRUE ;
2015-09-16 22:15:42 -05:00
}
2015-10-10 09:48:10 -05:00
// Direct2D arcs require a little explanation.
// An arc in Direct2D is defined by the chord between the endpoints.
// There are four possible arcs with the same two endpoints that you can draw this way.
// See https://www.youtube.com/watch?v=ATS0ANW1UxQ for a demonstration.
// There is a property rotationAngle which deals with the rotation /of the entire ellipse that forms an ellpitical arc/ - it's effectively a transformation on the arc.
// That is to say, it's NOT THE SWEEP.
// The sweep is defined by the start and end points and whether the arc is "large".
// As a result, this design does not allow for full circles or ellipses with a single arc; they have to be simulated with two.
struct arc {
double xCenter ;
double yCenter ;
double radius ;
double startAngle ;
double sweep ;
2015-10-11 21:14:27 -05:00
int negative ;
2015-10-10 09:48:10 -05:00
} ;
2015-10-11 06:51:14 -05:00
// this is used for the comparison below
// if it falls apart it can be changed later
# define aerMax 6 * DBL_EPSILON
2015-10-10 09:48:10 -05:00
static void drawArc ( uiDrawPath * p , struct arc * a , void ( * startFunction ) ( uiDrawPath * , double , double ) )
2015-10-07 07:07:07 -05:00
{
2015-10-10 09:48:10 -05:00
double sinx , cosx ;
double startX , startY ;
double endX , endY ;
D2D1_ARC_SEGMENT as ;
2015-10-11 06:51:14 -05:00
BOOL fullCircle ;
2015-10-11 21:14:27 -05:00
double absSweep ;
2015-10-07 07:07:07 -05:00
2015-10-10 09:48:10 -05:00
// as above, we can't do a full circle with one arc
// simulate it with two half-circles
2015-10-11 06:51:14 -05:00
// of course, we have a dragon: equality on floating-point values!
// I've chosen to do the AlmostEqualRelative() technique in https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
fullCircle = FALSE ;
2015-10-11 21:14:27 -05:00
// use the absolute value to tackle both ≥2π and ≤-2π at the same time
absSweep = fabs ( a - > sweep ) ;
if ( absSweep > ( 2 * M_PI ) ) // this part is easy
2015-10-11 06:51:14 -05:00
fullCircle = TRUE ;
else {
double aerDiff ;
2015-10-11 21:14:27 -05:00
aerDiff = fabs ( absSweep - ( 2 * M_PI ) ) ;
2015-10-11 06:51:14 -05:00
// if we got here then we know a->sweep is larger (or the same!)
2015-10-11 21:14:27 -05:00
fullCircle = aerDiff < = absSweep * aerMax ;
2015-10-11 06:51:14 -05:00
}
2015-10-11 21:14:27 -05:00
// TODO make sure this works right for the negative direction
2015-10-11 06:51:14 -05:00
if ( fullCircle ) {
2015-10-10 09:48:10 -05:00
a - > sweep = M_PI ;
drawArc ( p , a , startFunction ) ;
a - > startAngle + = M_PI ;
drawArc ( p , a , NULL ) ;
return ;
}
// first, figure out the arc's endpoints
2015-10-07 10:12:18 -05:00
// unfortunately D2D1SinCos() is only defined on Windows 8 and newer
// the MSDN page doesn't say this, but says it requires d2d1_1.h, which is listed as only supported on Windows 8 and newer elsewhere on MSDN
// so we must use sin() and cos() and hope it's right...
2015-10-10 09:48:10 -05:00
sinx = sin ( a - > startAngle ) ;
cosx = cos ( a - > startAngle ) ;
startX = a - > xCenter + a - > radius * cosx ;
2015-10-11 19:24:06 -05:00
startY = a - > yCenter + a - > radius * sinx ;
2015-10-10 09:48:10 -05:00
sinx = sin ( a - > startAngle + a - > sweep ) ;
cosx = cos ( a - > startAngle + a - > sweep ) ;
endX = a - > xCenter + a - > radius * cosx ;
2015-10-11 19:24:06 -05:00
endY = a - > yCenter + a - > radius * sinx ;
2015-10-10 09:48:10 -05:00
// now do the initial step to get the current point to be the start point
// this is either creating a new figure, drawing a line, or (in the case of our full circle code above) doing nothing
if ( startFunction ! = NULL )
( * startFunction ) ( p , startX , startY ) ;
// now we can draw the arc
2015-10-07 07:07:07 -05:00
as . point . x = endX ;
as . point . y = endY ;
2015-10-10 09:48:10 -05:00
as . size . width = a - > radius ;
as . size . height = a - > radius ;
as . rotationAngle = 0 ; // as above, not relevant for circles
2015-10-11 21:14:27 -05:00
if ( a - > negative )
as . sweepDirection = D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE ;
2015-10-07 11:19:20 -05:00
else
2015-10-11 21:14:27 -05:00
as . sweepDirection = D2D1_SWEEP_DIRECTION_CLOCKWISE ;
// TODO explain the outer if
if ( ! a - > negative )
if ( a - > sweep > M_PI )
as . arcSize = D2D1_ARC_SIZE_LARGE ;
else
as . arcSize = D2D1_ARC_SIZE_SMALL ;
else
// TODO especially this part
if ( a - > sweep > M_PI )
as . arcSize = D2D1_ARC_SIZE_SMALL ;
else
as . arcSize = D2D1_ARC_SIZE_LARGE ;
2015-10-10 09:48:10 -05:00
ID2D1GeometrySink_AddArc ( p - > sink , & as ) ;
2015-10-07 07:07:07 -05:00
}
2015-10-11 21:14:27 -05:00
void uiDrawPathNewFigureWithArc ( uiDrawPath * p , double xCenter , double yCenter , double radius , double startAngle , double sweep , int negative )
2015-10-07 07:07:07 -05:00
{
2015-10-10 09:48:10 -05:00
struct arc a ;
a . xCenter = xCenter ;
a . yCenter = yCenter ;
a . radius = radius ;
a . startAngle = startAngle ;
a . sweep = sweep ;
2015-10-11 21:14:27 -05:00
a . negative = negative ;
2015-10-10 09:48:10 -05:00
drawArc ( p , & a , uiDrawPathNewFigure ) ;
2015-10-07 07:07:07 -05:00
}
2015-10-06 18:56:38 -05:00
void uiDrawPathLineTo ( uiDrawPath * p , double x , double y )
2015-09-16 22:15:42 -05:00
{
2015-10-06 18:56:38 -05:00
D2D1_POINT_2F pt ;
2015-09-17 12:10:38 -05:00
2015-10-06 18:56:38 -05:00
pt . x = x ;
pt . y = y ;
ID2D1GeometrySink_AddLine ( p - > sink , pt ) ;
2015-09-16 22:15:42 -05:00
}
2015-10-11 21:14:27 -05:00
void uiDrawPathArcTo ( uiDrawPath * p , double xCenter , double yCenter , double radius , double startAngle , double sweep , int negative )
2015-09-16 22:15:42 -05:00
{
2015-10-10 09:48:10 -05:00
struct arc a ;
a . xCenter = xCenter ;
a . yCenter = yCenter ;
a . radius = radius ;
a . startAngle = startAngle ;
a . sweep = sweep ;
2015-10-11 21:14:27 -05:00
a . negative = negative ;
2015-10-10 09:48:10 -05:00
drawArc ( p , & a , uiDrawPathLineTo ) ;
2015-09-16 22:15:42 -05:00
}
2015-10-06 18:56:38 -05:00
void uiDrawPathBezierTo ( uiDrawPath * p , double c1x , double c1y , double c2x , double c2y , double endX , double endY )
2015-09-16 22:15:42 -05:00
{
2015-10-06 18:56:38 -05:00
D2D1_BEZIER_SEGMENT s ;
s . point1 . x = c1x ;
s . point1 . y = c1y ;
s . point2 . x = c2x ;
s . point2 . y = c2y ;
s . point3 . x = endX ;
s . point3 . y = endY ;
ID2D1GeometrySink_AddBezier ( p - > sink , & s ) ;
2015-09-16 22:15:42 -05:00
}
2015-10-06 18:56:38 -05:00
void uiDrawPathCloseFigure ( uiDrawPath * p )
2015-09-16 22:15:42 -05:00
{
2015-10-06 18:56:38 -05:00
ID2D1GeometrySink_EndFigure ( p - > sink ,
2015-09-17 12:10:38 -05:00
D2D1_FIGURE_END_CLOSED ) ;
2015-10-06 18:56:38 -05:00
p - > inFigure = FALSE ;
}
2015-10-07 11:34:53 -05:00
void uiDrawPathAddRectangle ( uiDrawPath * p , double x , double y , double width , double height )
2015-10-06 18:56:38 -05:00
{
2015-10-07 11:34:53 -05:00
// this is the same algorithm used by cairo and Core Graphics, according to their documentations
uiDrawPathNewFigure ( p , x , y ) ;
uiDrawPathLineTo ( p , x + width , y ) ;
uiDrawPathLineTo ( p , x + width , y + height ) ;
uiDrawPathLineTo ( p , x , y + height ) ;
uiDrawPathCloseFigure ( p ) ;
2015-10-06 18:56:38 -05:00
}
void uiDrawPathEnd ( uiDrawPath * p )
{
HRESULT hr ;
2015-10-07 10:12:18 -05:00
if ( p - > inFigure ) {
ID2D1GeometrySink_EndFigure ( p - > sink ,
2015-10-06 18:56:38 -05:00
D2D1_FIGURE_END_OPEN ) ;
2015-10-07 10:12:18 -05:00
// needed for uiDrawFreePath()
p - > inFigure = FALSE ;
}
2015-10-06 18:56:38 -05:00
hr = ID2D1GeometrySink_Close ( p - > sink ) ;
if ( hr ! = S_OK )
logHRESULT ( " error closing path in uiDrawPathEnd() " , hr ) ;
ID2D1GeometrySink_Release ( p - > sink ) ;
p - > sink = NULL ;
2015-09-16 22:15:42 -05:00
}
2015-10-07 09:47:38 -05:00
struct uiDrawContext {
ID2D1RenderTarget * rt ;
2015-11-27 19:38:21 -06:00
struct ptrArray * states ;
2015-10-13 12:20:25 -05:00
ID2D1PathGeometry * currentClip ;
2015-10-07 09:47:38 -05:00
} ;
2015-10-13 09:47:54 -05:00
// declared below
static void initStates ( uiDrawContext * ) ;
static void uninitStates ( uiDrawContext * ) ;
2015-10-11 11:36:48 -05:00
static void resetTarget ( ID2D1RenderTarget * rt )
{
D2D1_MATRIX_3X2_F dm ;
// transformations persist
// reset to the identity matrix
ZeroMemory ( & dm , sizeof ( D2D1_MATRIX_3X2_F ) ) ;
dm . _11 = 1 ;
dm . _22 = 1 ;
ID2D1RenderTarget_SetTransform ( rt , & dm ) ;
}
2015-10-07 09:47:38 -05:00
uiDrawContext * newContext ( ID2D1RenderTarget * rt )
{
uiDrawContext * c ;
2015-10-09 10:49:57 -05:00
c = uiNew ( uiDrawContext ) ;
2015-10-07 09:47:38 -05:00
c - > rt = rt ;
2015-10-13 09:47:54 -05:00
initStates ( c ) ;
2015-10-11 11:36:48 -05:00
resetTarget ( c - > rt ) ;
2015-10-07 09:47:38 -05:00
return c ;
}
2015-10-09 11:41:01 -05:00
void freeContext ( uiDrawContext * c )
{
2015-10-13 12:20:25 -05:00
if ( c - > currentClip ! = NULL )
ID2D1PathGeometry_Release ( c - > currentClip ) ;
2015-10-13 09:47:54 -05:00
uninitStates ( c ) ;
2015-10-09 11:41:01 -05:00
uiFree ( c ) ;
}
2015-10-07 09:47:38 -05:00
static ID2D1Brush * makeSolidBrush ( uiDrawBrush * b , ID2D1RenderTarget * rt , D2D1_BRUSH_PROPERTIES * props )
2015-09-16 22:15:42 -05:00
{
2015-09-17 12:10:38 -05:00
D2D1_COLOR_F color ;
ID2D1SolidColorBrush * brush ;
HRESULT hr ;
2015-09-16 22:15:42 -05:00
2015-10-07 09:47:38 -05:00
color . r = b - > R ;
color . g = b - > G ;
color . b = b - > B ;
color . a = b - > A ;
hr = ID2D1RenderTarget_CreateSolidColorBrush ( rt ,
& color ,
2015-10-07 10:12:18 -05:00
props ,
2015-10-07 09:47:38 -05:00
& brush ) ;
if ( hr ! = S_OK )
logHRESULT ( " error creating solid brush in makeSolidBrush() " , hr ) ;
return ( ID2D1Brush * ) brush ;
}
2015-10-07 15:54:56 -05:00
static ID2D1GradientStopCollection * mkstops ( uiDrawBrush * b , ID2D1RenderTarget * rt )
{
ID2D1GradientStopCollection * s ;
D2D1_GRADIENT_STOP * stops ;
size_t i ;
HRESULT hr ;
2015-10-09 10:49:57 -05:00
stops = uiAlloc ( b - > NumStops * sizeof ( D2D1_GRADIENT_STOP ) , " D2D1_GRADIENT_STOP[] " ) ;
2015-10-07 15:54:56 -05:00
for ( i = 0 ; i < b - > NumStops ; i + + ) {
stops [ i ] . position = b - > Stops [ i ] . Pos ;
stops [ i ] . color . r = b - > Stops [ i ] . R ;
stops [ i ] . color . g = b - > Stops [ i ] . G ;
stops [ i ] . color . b = b - > Stops [ i ] . B ;
stops [ i ] . color . a = b - > Stops [ i ] . A ;
}
// TODO BUG IN MINGW
// the Microsoft headers give this all 6 parameters
// the MinGW headers use the 4-parameter version
hr = ( * ( rt - > lpVtbl - > CreateGradientStopCollection ) ) ( rt ,
stops ,
b - > NumStops ,
D2D1_GAMMA_2_2 , // this is the default for the C++-only overload of ID2D1RenderTarget::GradientStopCollection()
D2D1_EXTEND_MODE_CLAMP ,
& s ) ;
if ( hr ! = S_OK )
logHRESULT ( " error creating stop collection in mkstops() " , hr ) ;
2015-10-09 10:49:57 -05:00
uiFree ( stops ) ;
2015-10-07 15:54:56 -05:00
return s ;
}
2015-10-07 09:47:38 -05:00
static ID2D1Brush * makeLinearBrush ( uiDrawBrush * b , ID2D1RenderTarget * rt , D2D1_BRUSH_PROPERTIES * props )
{
2015-10-07 15:54:56 -05:00
ID2D1LinearGradientBrush * brush ;
D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES gprops ;
ID2D1GradientStopCollection * stops ;
HRESULT hr ;
ZeroMemory ( & gprops , sizeof ( D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES ) ) ;
gprops . startPoint . x = b - > X0 ;
gprops . startPoint . y = b - > Y0 ;
gprops . endPoint . x = b - > X1 ;
gprops . endPoint . y = b - > Y1 ;
stops = mkstops ( b , rt ) ;
hr = ID2D1RenderTarget_CreateLinearGradientBrush ( rt ,
& gprops ,
props ,
stops ,
& brush ) ;
if ( hr ! = S_OK )
logHRESULT ( " error creating gradient brush in makeLinearBrush() " , hr ) ;
// the example at https://msdn.microsoft.com/en-us/library/windows/desktop/dd756682%28v=vs.85%29.aspx says this is safe to do now
ID2D1GradientStopCollection_Release ( stops ) ;
return ( ID2D1Brush * ) brush ;
2015-10-07 09:47:38 -05:00
}
static ID2D1Brush * makeRadialBrush ( uiDrawBrush * b , ID2D1RenderTarget * rt , D2D1_BRUSH_PROPERTIES * props )
{
2015-10-07 17:32:55 -05:00
ID2D1RadialGradientBrush * brush ;
D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES gprops ;
ID2D1GradientStopCollection * stops ;
HRESULT hr ;
ZeroMemory ( & gprops , sizeof ( D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES ) ) ;
gprops . gradientOriginOffset . x = b - > X0 - b - > X1 ;
gprops . gradientOriginOffset . y = b - > Y0 - b - > Y1 ;
gprops . center . x = b - > X1 ;
gprops . center . y = b - > Y1 ;
gprops . radiusX = b - > OuterRadius ;
gprops . radiusY = b - > OuterRadius ;
stops = mkstops ( b , rt ) ;
hr = ID2D1RenderTarget_CreateRadialGradientBrush ( rt ,
& gprops ,
props ,
stops ,
& brush ) ;
if ( hr ! = S_OK )
logHRESULT ( " error creating gradient brush in makeRadialBrush() " , hr ) ;
ID2D1GradientStopCollection_Release ( stops ) ;
return ( ID2D1Brush * ) brush ;
2015-10-07 09:47:38 -05:00
}
static ID2D1Brush * makeBrush ( uiDrawBrush * b , ID2D1RenderTarget * rt )
{
D2D1_BRUSH_PROPERTIES props ;
2015-09-16 22:15:42 -05:00
2015-09-17 12:10:38 -05:00
ZeroMemory ( & props , sizeof ( D2D1_BRUSH_PROPERTIES ) ) ;
props . opacity = 1.0 ;
// identity matrix
props . transform . _11 = 1 ;
props . transform . _22 = 1 ;
2015-09-16 22:15:42 -05:00
2015-10-07 09:47:38 -05:00
switch ( b - > Type ) {
case uiDrawBrushTypeSolid :
return makeSolidBrush ( b , rt , & props ) ;
2015-10-07 15:54:56 -05:00
case uiDrawBrushTypeLinearGradient :
return makeLinearBrush ( b , rt , & props ) ;
2015-10-07 17:32:55 -05:00
case uiDrawBrushTypeRadialGradient :
return makeRadialBrush ( b , rt , & props ) ;
2015-10-07 09:47:38 -05:00
// case uiDrawBrushTypeImage:
// TODO
}
2015-10-09 10:49:57 -05:00
complain ( " invalid brush type %d in makeBrush() " , b - > Type ) ;
2015-10-07 09:47:38 -05:00
return NULL ; // make compiler happy
2015-09-16 22:15:42 -05:00
}
2015-10-13 12:20:25 -05:00
// how clipping works:
// every fill and stroke is done on a temporary layer with the clip geometry applied to it
// this is really the only way to clip in Direct2D that doesn't involve opacity images
// reference counting:
// - initially the clip is NULL, which means do not use a layer
// - the first time uiDrawClip() is called, we take a reference on the path passed in (this is also why uiPathEnd() is needed)
// - every successive time, we create a new PathGeometry and merge the current clip with the new path, releasing the reference we took earlier and taking a reference to the new one
// - in Save, we take another reference; in Restore we drop the refernece to the existing path geometry and transfer that saved ref to the new path geometry over to the context
// uiDrawFreePath() doesn't destroy the path geometry, it just drops the reference count, so a clip can exist independent of its path
static ID2D1Layer * applyClip ( uiDrawContext * c )
{
ID2D1Layer * layer ;
D2D1_LAYER_PARAMETERS params ;
HRESULT hr ;
// if no clip, don't do anything
if ( c - > currentClip = = NULL )
return NULL ;
// create a layer for clipping
// we have to explicitly make the layer because we're still targeting Windows 7
// TODO MINGW BUG
// this macro is supposed to take three parameters
hr = ( * ( c - > rt - > lpVtbl - > CreateLayer ) ) ( c - > rt ,
NULL ,
& layer ) ;
if ( hr ! = S_OK )
logHRESULT ( " error creating clip layer in applyClip() " , hr ) ;
// apply it as the clip
ZeroMemory ( & params , sizeof ( D2D1_LAYER_PARAMETERS ) ) ;
// this is the equivalent of InfiniteRect() in d2d1helper.h
params . contentBounds . left = - FLT_MAX ;
params . contentBounds . top = - FLT_MAX ;
params . contentBounds . right = FLT_MAX ;
params . contentBounds . bottom = FLT_MAX ;
params . geometricMask = ( ID2D1Geometry * ) ( c - > currentClip ) ;
// TODO is this correct?
params . maskAntialiasMode = ID2D1RenderTarget_GetAntialiasMode ( c - > rt ) ;
// identity matrix
params . maskTransform . _11 = 1 ;
params . maskTransform . _22 = 1 ;
params . opacity = 1.0 ;
params . opacityBrush = NULL ;
params . layerOptions = D2D1_LAYER_OPTIONS_NONE ;
// TODO is this correct?
if ( ID2D1RenderTarget_GetTextAntialiasMode ( c - > rt ) = = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE )
params . layerOptions = D2D1_LAYER_OPTIONS_INITIALIZE_FOR_CLEARTYPE ;
ID2D1RenderTarget_PushLayer ( c - > rt ,
& params ,
layer ) ;
// return the layer so it can be freed later
return layer ;
}
static void unapplyClip ( uiDrawContext * c , ID2D1Layer * layer )
{
if ( layer = = NULL )
return ;
ID2D1RenderTarget_PopLayer ( c - > rt ) ;
ID2D1Layer_Release ( layer ) ;
}
2015-10-07 09:47:38 -05:00
void uiDrawStroke ( uiDrawContext * c , uiDrawPath * p , uiDrawBrush * b , uiDrawStrokeParams * sp )
2015-09-16 22:15:42 -05:00
{
2015-10-07 09:47:38 -05:00
ID2D1Brush * brush ;
2015-10-07 12:05:48 -05:00
ID2D1StrokeStyle * style ;
D2D1_STROKE_STYLE_PROPERTIES dsp ;
2015-10-16 09:46:26 -05:00
FLOAT * dashes ;
size_t i ;
2015-10-13 12:20:25 -05:00
ID2D1Layer * cliplayer ;
2015-10-07 12:05:48 -05:00
HRESULT hr ;
2015-09-16 22:15:42 -05:00
2015-10-07 09:47:38 -05:00
brush = makeBrush ( b , c - > rt ) ;
2015-10-07 12:05:48 -05:00
ZeroMemory ( & dsp , sizeof ( D2D1_STROKE_STYLE_PROPERTIES ) ) ;
switch ( sp - > Cap ) {
case uiDrawLineCapFlat :
dsp . startCap = D2D1_CAP_STYLE_FLAT ;
dsp . endCap = D2D1_CAP_STYLE_FLAT ;
dsp . dashCap = D2D1_CAP_STYLE_FLAT ;
break ;
case uiDrawLineCapRound :
dsp . startCap = D2D1_CAP_STYLE_ROUND ;
dsp . endCap = D2D1_CAP_STYLE_ROUND ;
dsp . dashCap = D2D1_CAP_STYLE_ROUND ;
break ;
case uiDrawLineCapSquare :
dsp . startCap = D2D1_CAP_STYLE_SQUARE ;
dsp . endCap = D2D1_CAP_STYLE_SQUARE ;
dsp . dashCap = D2D1_CAP_STYLE_SQUARE ;
break ;
}
switch ( sp - > Join ) {
case uiDrawLineJoinMiter :
dsp . lineJoin = D2D1_LINE_JOIN_MITER_OR_BEVEL ;
dsp . miterLimit = sp - > MiterLimit ;
break ;
case uiDrawLineJoinRound :
dsp . lineJoin = D2D1_LINE_JOIN_ROUND ;
break ;
case uiDrawLineJoinBevel :
dsp . lineJoin = D2D1_LINE_JOIN_BEVEL ;
break ;
}
dsp . dashStyle = D2D1_DASH_STYLE_SOLID ;
2015-10-16 09:46:26 -05:00
dashes = NULL ;
// note that dash widths and the dash phase are scaled up by the thickness by Direct2D
// TODO be sure to formally document this
if ( sp - > NumDashes ! = 0 ) {
dsp . dashStyle = D2D1_DASH_STYLE_CUSTOM ;
dashes = ( FLOAT * ) uiAlloc ( sp - > NumDashes * sizeof ( FLOAT ) , " FLOAT[] " ) ;
for ( i = 0 ; i < sp - > NumDashes ; i + + )
dashes [ i ] = sp - > Dashes [ i ] / sp - > Thickness ;
}
dsp . dashOffset = sp - > DashPhase / sp - > Thickness ;
2015-10-07 12:05:48 -05:00
hr = ID2D1Factory_CreateStrokeStyle ( d2dfactory ,
& dsp ,
2015-10-16 09:46:26 -05:00
dashes ,
sp - > NumDashes ,
2015-10-07 12:05:48 -05:00
& style ) ;
if ( hr ! = S_OK )
logHRESULT ( " error creating stroke style in uiDrawStroke() " , hr ) ;
2015-10-16 10:05:08 -05:00
if ( sp - > NumDashes ! = 0 )
2015-10-16 09:46:26 -05:00
uiFree ( dashes ) ;
2015-10-07 12:05:48 -05:00
2015-10-13 12:20:25 -05:00
cliplayer = applyClip ( c ) ;
2015-09-17 12:10:38 -05:00
ID2D1RenderTarget_DrawGeometry ( c - > rt ,
2015-10-07 09:47:38 -05:00
( ID2D1Geometry * ) ( p - > path ) ,
brush ,
sp - > Thickness ,
2015-10-07 12:05:48 -05:00
style ) ;
2015-10-13 12:20:25 -05:00
unapplyClip ( c , cliplayer ) ;
2015-10-07 12:05:48 -05:00
ID2D1StrokeStyle_Release ( style ) ;
2015-10-07 09:47:38 -05:00
ID2D1Brush_Release ( brush ) ;
2015-09-16 22:15:42 -05:00
}
2015-10-07 09:47:38 -05:00
void uiDrawFill ( uiDrawContext * c , uiDrawPath * p , uiDrawBrush * b )
2015-09-16 22:15:42 -05:00
{
2015-10-07 09:47:38 -05:00
ID2D1Brush * brush ;
2015-10-13 12:20:25 -05:00
ID2D1Layer * cliplayer ;
2015-09-17 12:10:38 -05:00
2015-10-07 09:47:38 -05:00
brush = makeBrush ( b , c - > rt ) ;
2015-10-13 12:20:25 -05:00
cliplayer = applyClip ( c ) ;
2015-09-17 12:10:38 -05:00
ID2D1RenderTarget_FillGeometry ( c - > rt ,
2015-10-07 09:47:38 -05:00
( ID2D1Geometry * ) ( p - > path ) ,
brush ,
2015-09-17 12:10:38 -05:00
NULL ) ;
2015-10-13 12:20:25 -05:00
unapplyClip ( c , cliplayer ) ;
2015-10-07 09:47:38 -05:00
ID2D1Brush_Release ( brush ) ;
2015-09-16 22:15:42 -05:00
}
2015-10-11 11:36:48 -05:00
static void m2d ( uiDrawMatrix * m , D2D1_MATRIX_3X2_F * d )
{
d - > _11 = m - > M11 ;
d - > _12 = m - > M12 ;
d - > _21 = m - > M21 ;
d - > _22 = m - > M22 ;
d - > _31 = m - > M31 ;
d - > _32 = m - > M32 ;
}
static void d2m ( D2D1_MATRIX_3X2_F * d , uiDrawMatrix * m )
{
m - > M11 = d - > _11 ;
m - > M12 = d - > _12 ;
m - > M21 = d - > _21 ;
m - > M22 = d - > _22 ;
m - > M31 = d - > _31 ;
m - > M32 = d - > _32 ;
}
void uiDrawMatrixSetIdentity ( uiDrawMatrix * m )
{
setIdentity ( m ) ;
}
// frustratingly all of the operations on a matrix except rotation and skeweing are provided by the C++-only d2d1helper.h
// we'll have to recreate their functionalities here
// the implementations are all in the main matrix.c file
void uiDrawMatrixTranslate ( uiDrawMatrix * m , double x , double y )
{
fallbackTranslate ( m , x , y ) ;
}
2015-10-12 00:43:12 -05:00
void uiDrawMatrixScale ( uiDrawMatrix * m , double xCenter , double yCenter , double x , double y )
2015-10-11 11:36:48 -05:00
{
2015-10-12 00:43:12 -05:00
fallbackScale ( m , xCenter , yCenter , x , y ) ;
2015-10-11 11:36:48 -05:00
}
void uiDrawMatrixRotate ( uiDrawMatrix * m , double x , double y , double amount )
{
D2D1_POINT_2F center ;
D2D1_MATRIX_3X2_F dm ;
uiDrawMatrix rm ;
amount * = 180 / M_PI ; // must be in degrees
center . x = x ;
center . y = y ;
2015-10-12 09:30:36 -05:00
D2D1MakeRotateMatrix ( amount , center , & dm ) ;
2015-10-11 11:36:48 -05:00
d2m ( & dm , & rm ) ;
uiDrawMatrixMultiply ( m , & rm ) ;
}
void uiDrawMatrixSkew ( uiDrawMatrix * m , double x , double y , double xamount , double yamount )
{
D2D1_POINT_2F center ;
D2D1_MATRIX_3X2_F dm ;
uiDrawMatrix sm ;
xamount * = 180 / M_PI ; // must be in degrees
yamount * = 180 / M_PI ; // must be in degrees
center . x = x ;
center . y = y ;
D2D1MakeSkewMatrix ( xamount , yamount , center , & dm ) ;
d2m ( & dm , & sm ) ;
uiDrawMatrixMultiply ( m , & sm ) ;
}
void uiDrawMatrixMultiply ( uiDrawMatrix * dest , uiDrawMatrix * src )
{
fallbackMultiply ( dest , src ) ;
}
int uiDrawMatrixInvertible ( uiDrawMatrix * m )
{
D2D1_MATRIX_3X2_F d ;
m2d ( m , & d ) ;
return D2D1IsMatrixInvertible ( & d ) ! = FALSE ;
}
int uiDrawMatrixInvert ( uiDrawMatrix * m )
{
D2D1_MATRIX_3X2_F d ;
m2d ( m , & d ) ;
if ( D2D1InvertMatrix ( & d ) = = FALSE )
return 0 ;
d2m ( & d , m ) ;
return 1 ;
}
void uiDrawMatrixTransformPoint ( uiDrawMatrix * m , double * x , double * y )
{
fallbackTransformPoint ( m , x , y ) ;
}
void uiDrawMatrixTransformSize ( uiDrawMatrix * m , double * x , double * y )
{
fallbackTransformSize ( m , x , y ) ;
}
2015-10-12 20:11:42 -05:00
void uiDrawTransform ( uiDrawContext * c , uiDrawMatrix * m )
{
D2D1_MATRIX_3X2_F dm ;
uiDrawMatrix already ;
uiDrawMatrix temp ;
ID2D1RenderTarget_GetTransform ( c - > rt , & dm ) ;
d2m ( & dm , & already ) ;
temp = * m ; // don't modify m
// you would think we have to do already * m, right?
// WRONG! we have to do m * already
// why? a few reasons
// a) this lovely comment in cairo's source - http://cgit.freedesktop.org/cairo/tree/src/cairo-matrix.c?id=0537479bd1d4c5a3bc0f6f41dec4deb98481f34a#n330
// Direct2D uses column vectors and I don't know if this is even documented
// b) that's what Core Graphics does
// TODO see if Microsoft says to do this
uiDrawMatrixMultiply ( & temp , & already ) ;
m2d ( & temp , & dm ) ;
ID2D1RenderTarget_SetTransform ( c - > rt , & dm ) ;
}
2015-10-13 12:20:25 -05:00
// TODO for this, stroke, and fill, make sure that an open path causes error state
void uiDrawClip ( uiDrawContext * c , uiDrawPath * path )
{
ID2D1PathGeometry * newPath ;
ID2D1GeometrySink * newSink ;
HRESULT hr ;
// if there's no current clip, borrow the path
if ( c - > currentClip = = NULL ) {
c - > currentClip = path - > path ;
// we have to take our own reference to that clip
ID2D1PathGeometry_AddRef ( c - > currentClip ) ;
return ;
}
// otherwise we have to intersect the current path with the new one
// we do that into a new path, and then replace c->currentClip with that new path
hr = ID2D1Factory_CreatePathGeometry ( d2dfactory ,
& newPath ) ;
if ( hr ! = S_OK )
logHRESULT ( " error creating new path in uiDrawClip() " , hr ) ;
hr = ID2D1PathGeometry_Open ( newPath ,
& newSink ) ;
if ( hr ! = S_OK )
logHRESULT ( " error opening new path in uiDrawClip() " , hr ) ;
// TODO BUG IN MINGW
// the macro is supposed to take 6 parameters
hr = ( * ( c - > currentClip - > lpVtbl - > Base . CombineWithGeometry ) ) (
( ID2D1Geometry * ) ( c - > currentClip ) ,
( ID2D1Geometry * ) ( path - > path ) ,
D2D1_COMBINE_MODE_INTERSECT ,
NULL ,
// TODO is this correct or can this be set per target?
D2D1_DEFAULT_FLATTENING_TOLERANCE ,
( ID2D1SimplifiedGeometrySink * ) newSink ) ;
if ( hr ! = S_OK )
logHRESULT ( " error intersecting old path with new path in uiDrawClip() " , hr ) ;
hr = ID2D1GeometrySink_Close ( newSink ) ;
if ( hr ! = S_OK )
logHRESULT ( " error closing new path in uiDrawClip() " , hr ) ;
ID2D1GeometrySink_Release ( newSink ) ;
// okay we have the new clip; we just need to replace the old one with it
ID2D1PathGeometry_Release ( c - > currentClip ) ;
c - > currentClip = newPath ;
// we have a reference already; no need for another
}
2015-10-13 09:47:54 -05:00
struct state {
ID2D1DrawingStateBlock * dsb ;
2015-10-13 12:20:25 -05:00
ID2D1PathGeometry * clip ;
2015-10-13 09:47:54 -05:00
} ;
static void initStates ( uiDrawContext * c )
{
2015-11-27 19:38:21 -06:00
c - > states = newPtrArray ( ) ;
2015-10-13 09:47:54 -05:00
}
static void uninitStates ( uiDrawContext * c )
{
2015-11-27 19:38:21 -06:00
// have a helpful diagnostic here
if ( c - > states - > len > 0 )
2015-10-13 09:47:54 -05:00
complain ( " imbalanced save/restore count in uiDrawContext in uninitStates(); did you save without restoring? " ) ;
2015-11-27 19:38:21 -06:00
ptrArrayDestroy ( c - > states ) ;
2015-10-13 09:47:54 -05:00
}
2015-10-11 11:36:48 -05:00
void uiDrawSave ( uiDrawContext * c )
{
2015-11-27 19:38:21 -06:00
struct state * state ;
2015-10-12 09:30:36 -05:00
ID2D1DrawingStateBlock * dsb ;
HRESULT hr ;
hr = ID2D1Factory_CreateDrawingStateBlock ( d2dfactory ,
// TODO verify that these are correct
NULL ,
NULL ,
& dsb ) ;
if ( hr ! = S_OK )
logHRESULT ( " error creating drawing state block in uiDrawSave() " , hr ) ;
ID2D1RenderTarget_SaveDrawingState ( c - > rt , dsb ) ;
2015-10-13 09:47:54 -05:00
2015-10-13 12:20:25 -05:00
// if we have a clip, we need to hold another reference to it
if ( c - > currentClip ! = NULL )
ID2D1PathGeometry_AddRef ( c - > currentClip ) ;
2015-11-27 19:38:21 -06:00
state = uiNew ( struct state ) ;
state - > dsb = dsb ;
state - > clip = c - > currentClip ;
ptrArrayAppend ( c - > states , state ) ;
2015-10-11 11:36:48 -05:00
}
void uiDrawRestore ( uiDrawContext * c )
{
2015-10-13 09:47:54 -05:00
struct state * state ;
2015-11-27 19:38:21 -06:00
state = ptrArrayIndex ( c - > states , struct state * , c - > states - > len - 1 ) ;
2015-10-13 09:47:54 -05:00
ID2D1RenderTarget_RestoreDrawingState ( c - > rt , state - > dsb ) ;
ID2D1DrawingStateBlock_Release ( state - > dsb ) ;
2015-10-12 09:30:36 -05:00
2015-10-13 12:20:25 -05:00
// if we have a current clip, we need to drop it
if ( c - > currentClip ! = NULL )
ID2D1PathGeometry_Release ( c - > currentClip ) ;
// no need to explicitly addref or release; just transfer the ref
c - > currentClip = state - > clip ;
2015-11-27 19:38:21 -06:00
uiFree ( state ) ;
ptrArrayDelete ( c - > states , c - > states - > len - 1 ) ;
2015-10-11 11:36:48 -05:00
}
2016-01-09 00:07:48 -06:00
// TODO document that fully opaque black is the default text color; figure out whether this is upheld in various scenarios on other platforms
2016-01-09 01:23:01 -06:00
void uiDrawText ( uiDrawContext * c , double x , double y , uiDrawTextLayout * layout )
2016-01-09 00:07:48 -06:00
{
uiDrawBrush brush ;
ID2D1Brush * black ;
ZeroMemory ( & brush , sizeof ( uiDrawBrush ) ) ;
brush . Type = uiDrawBrushTypeSolid ;
brush . R = 0.0 ;
brush . G = 0.0 ;
brush . B = 0.0 ;
brush . A = 1.0 ;
black = makeBrush ( & brush , c - > rt ) ;
doDrawText ( c - > rt , black , x , y , layout ) ;
ID2D1Brush_Release ( black ) ;
}