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 }