1 module modernui.ui.direct2d;
2 
3 import modernui.ui.core;
4 import modernui.ui.render;
5 
6 import std.string;
7 
8 version(Windows) 
9 {
10 
11 import core.sys.windows.windows;
12 import directx.d2d1;
13 import directx.d2d1helper;
14 import directx.dwrite_2;
15 
16 pragma(lib, "User32");
17 pragma(lib, "gdi32");
18 pragma(lib, "D2d1");
19 pragma(lib, "Dwrite");
20 
21 version(Unicode)
22 {
23 	import std.utf : toUTF16z;
24 	private alias toTStringz = toUTF16z;
25 } 
26 else
27 {
28 	import std.string : toStringz;
29 	private alias toTStringz = toStringz;
30 }
31 
32 private extern(Windows) HRESULT D2D1CreateFactory(D2D1_FACTORY_TYPE factoryType, REFIID riid, void* factoryOptions, IUnknown* ppIFactory);
33 
34 private extern(Windows) HRESULT DWriteCreateFactory(DWRITE_FACTORY_TYPE FactoryType, REFIID IID, IUnknown* ppFactory);
35 
36 private HRESULT D2D1CreateFactory(Factory : ID2D1Factory)(D2D1_FACTORY_TYPE factoryType, /*out*/ Factory* factory)
37 {
38 	return D2D1CreateFactory(factoryType, mixin("&IID_"~Factory.stringof), null, cast(IUnknown*)factory);
39 }
40 
41 private HRESULT DWriteCreateFactory(Factory : IDWriteFactory)(DWRITE_FACTORY_TYPE factoryType, /*out*/ Factory* factory)
42 {
43 	return DWriteCreateFactory(factoryType, mixin("&IID_"~Factory.stringof), cast(IUnknown*)factory);
44 }
45 
46 class Direct2DRenderContext_TextFormat : TextFormat
47 {
48 	private IDWriteTextFormat myField;
49 
50 	this(IDWriteFactory directWriteFactory, string fontFamily, float size, FontStyle style=FontStyle.normal, int weight=400, FontStretch stretch=FontStretch.normal) 
51 	{
52 		int d2dStyle;
53 		switch(style) 
54 		{
55 			case FontStyle.normal: d2dStyle = DWRITE_FONT_STYLE_NORMAL; break;
56 			case FontStyle.italic: d2dStyle = DWRITE_FONT_STYLE_ITALIC; break;
57 			case FontStyle.oblique: d2dStyle = DWRITE_FONT_STYLE_OBLIQUE; break;
58 			default: throw new Error("Invalid style");
59 		}
60 
61 		int d2dFontStretch;
62 		switch(stretch) {
63 			case FontStretch.ultraCondensed: d2dFontStretch = DWRITE_FONT_STRETCH_ULTRA_CONDENSED; break;
64 			case FontStretch.extraCondensed: d2dFontStretch = DWRITE_FONT_STRETCH_EXTRA_CONDENSED; break;
65 			case FontStretch.condensed: d2dFontStretch = DWRITE_FONT_STRETCH_CONDENSED; break;
66 			case FontStretch.semiCondensed: d2dFontStretch = DWRITE_FONT_STRETCH_SEMI_CONDENSED; break;
67 			case FontStretch.normal: d2dFontStretch = DWRITE_FONT_STRETCH_NORMAL; break;
68 			case FontStretch.semiExpanded: d2dFontStretch = DWRITE_FONT_STRETCH_SEMI_EXPANDED; break;
69 			case FontStretch.expanded: d2dFontStretch = DWRITE_FONT_STRETCH_EXPANDED; break;
70 			case FontStretch.extraExpanded: d2dFontStretch = DWRITE_FONT_STRETCH_EXTRA_EXPANDED; break;
71 			case FontStretch.ultraExpanded: d2dFontStretch = DWRITE_FONT_STRETCH_ULTRA_EXPANDED; break;
72 			default: throw new Error("Invalid stretch");
73 		}
74 
75 		auto hr = directWriteFactory.CreateTextFormat(toTStringz(fontFamily), null, weight, d2dStyle, d2dFontStretch, size, "en-us", &myField);
76 		if(hr != S_OK) throw new Error("DirectWrite unable to create text format");
77 	}
78 
79 	~this() { myField.Release(); }
80 }
81 
82 class Direct2DRenderContext_TextLayout : TextLayout
83 {
84 	private IDWriteTextLayout myField;
85 	private Size myLayoutBox;
86 	private Size myLayoutSize;
87 	private bool loadedMetrics;
88 	private string myText;
89 
90 	this(IDWriteFactory1 factory, Direct2DRenderContext_TextFormat format, string text="", Size box=Size(0.0, 0.0))
91 	{
92 		loadedMetrics = false;
93 		myText = text;
94 		myLayoutBox = box;
95 		auto hr = factory.CreateTextLayout(toTStringz(text), cast(int)text.length, format.myField, cast(float)box.width, cast(float)box.height, &myField); 
96 		if(hr != S_OK) throw new Error("DirectWrite unable to create text layout");
97 	}
98 
99 	~this() { myField.Release(); }
100 
101 	private void ensureMetrics()
102 	{
103 		if(loadedMetrics) return;
104 		DWRITE_TEXT_METRICS metrics;
105 		auto hr = myField.GetMetrics(&metrics);
106 		if(hr != S_OK) throw new Error("DirectWrite unable to get metrics for text layout");
107 		myLayoutSize.width = metrics.width;
108 		myLayoutSize.height = metrics.height;
109 		loadedMetrics = true;
110 	}
111 
112 	override @property string text() { return myText; }
113 	override @property Size layoutBox() { return myLayoutBox; }
114 	override @property Size layoutSize() { ensureMetrics(); return myLayoutSize; }
115 }
116 
117 class Direct2DRenderContext : RenderContext
118 {
119 	private HWND myHwnd;
120 	private ID2D1HwndRenderTarget myRenderTarget;
121 	private IDWriteFactory1 myDirectWriteFactory;
122 	private ID2D1SolidColorBrush myBlackBrush; // TODO: temporary
123 
124 	this(HWND hwnd)
125 	{
126 		myHwnd = hwnd;
127 
128 		ID2D1Factory d2dFactory;
129 		scope(exit) d2dFactory.Release();
130 		auto hr = D2D1CreateFactory!ID2D1Factory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &d2dFactory);
131 		if(hr != S_OK) throw new Error("Direct2D unable to start");
132 
133 		auto d2drtProperties = D2D1.RenderTargetProperties();
134 		d2drtProperties.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
135 
136 		auto hwndrtProperties = D2D1_HWND_RENDER_TARGET_PROPERTIES.init;
137 		hwndrtProperties.hwnd = myHwnd;
138 
139 		RECT rc;
140 		GetClientRect(myHwnd, &rc);
141 		hwndrtProperties.pixelSize = D2D_SIZE_U(rc.right - rc.left, rc.bottom - rc.top);
142 
143 		hr = d2dFactory.CreateHwndRenderTarget(&d2drtProperties, &hwndrtProperties, &myRenderTarget);
144 		if(hr != S_OK) throw new Error("Direct2D unable to create render target");
145 
146 		hr = DWriteCreateFactory!IDWriteFactory1(DWRITE_FACTORY_TYPE_ISOLATED, &myDirectWriteFactory);
147 		if(hr != S_OK) throw new Error("Direct2D unable to create render target");
148 
149 		auto col = D2D1.ColorF(D2D1.ColorF.Black).color;
150 		hr = myRenderTarget.CreateSolidColorBrush(&col, null, &myBlackBrush);
151 		if(hr != S_OK) throw new Error("Direct2D unable to create a solid brush");
152 	}
153 
154 	~this()
155 	{
156 		if(myRenderTarget !is null) myRenderTarget.Release();
157 		if(myDirectWriteFactory !is null) myDirectWriteFactory.Release();
158 	}
159 
160 	override void resize()
161 	{
162 		RECT rc;
163 		GetClientRect(myHwnd, &rc);
164 		auto newSize = D2D1_SIZE_U(rc.right - rc.left, rc.bottom - rc.top);
165 		auto hr = myRenderTarget.Resize(&newSize);
166 		if(hr != S_OK) throw new Error("Direct2D unable to resize render target");
167 	}
168 
169 	override void begin()
170 	{
171 		myRenderTarget.BeginDraw();
172 	}
173 
174 	override void end()
175 	{
176 		myRenderTarget.EndDraw();
177 	}
178 
179 	override void clear(Color color)
180 	{
181 		auto col = D2D1.ColorF(color.r, color.g, color.b);
182 		myRenderTarget.Clear(&col.color);
183 	}
184 
185 	override TextFormat createTextFormat(string fontFamily, float size, FontStyle style=FontStyle.normal, int weight=400, FontStretch stretch=FontStretch.normal)
186 	{
187 		auto tf = new Direct2DRenderContext_TextFormat(myDirectWriteFactory, fontFamily, size, style, weight, stretch);
188 		return tf;
189 	}
190 
191 	override TextLayout createTextLayout(TextFormat format, string text="", Size box=Size(0.0, 0.0))
192 	{
193 		auto tl = new Direct2DRenderContext_TextLayout(myDirectWriteFactory, cast(Direct2DRenderContext_TextFormat) format, text, box);
194 		return tl;
195 	}
196 
197 	override ImageBrush createImageBrush(Image image)
198 	{
199 		// TODO
200 		return null;
201 	}
202 
203 	override void drawText(double x, double y, TextLayout textLayout)
204 	{
205 		auto tl = cast(Direct2DRenderContext_TextLayout) textLayout;
206 		myRenderTarget.DrawTextLayout(D2D1.Point2F(x, y), tl.myField, myBlackBrush);
207 	}
208 }
209 
210 }