1 /*
2   Copyright Chris Jones 2020.
3   Distributed under the Boost Software License, Version 1.0.
4   See accompanying file Licence.txt or copy at...
5   https://www.boost.org/LICENSE_1_0.txt
6 */
7 
8 module win32;
9 
10 version(Windows):
11 
12 import window;
13 
14 import std.stdio;
15 import core.sys.windows.windows;
16 import std..string;
17 import std.conv;
18 import dg2d;
19 
20 static import gdi = core.sys.windows.wingdi;
21 
22 pragma(lib, "gdi32");
23 pragma(lib, "user32");
24 
25 void RegisterWindowClass()
26 {
27     Win32Window.hinstance = HINSTANCE(GetModuleHandleA(NULL));
28 
29     WNDCLASSEXA wcx;
30     wcx.cbSize          = wcx.sizeof;
31     wcx.style           = CS_DBLCLKS;
32     wcx.lpfnWndProc     = &WindowProc;
33     wcx.cbClsExtra      = 0;
34     wcx.cbWndExtra      = (void*).sizeof;
35     wcx.hInstance       = Win32Window.hinstance;
36     wcx.hIcon           = NULL;
37     wcx.hCursor         = LoadCursor(NULL, IDC_ARROW);
38     wcx.hbrBackground   = NULL;
39     wcx.lpszMenuName    = NULL;
40     wcx.lpszClassName   = Win32Window.wndclass.ptr;
41     wcx.hIconSm         = NULL;
42 
43     RegisterClassExA(&wcx);
44 }
45 
46 /*
47   Windows Message Loop
48 */
49 
50 int WindowsMessageLoop()
51 {    
52     MSG  msg;
53     while (GetMessageA(&msg, null, 0, 0))
54     {
55         TranslateMessage(&msg);
56         DispatchMessageA(&msg);
57     }
58     return cast(int) msg.wParam;
59 }
60 
61 /*
62   Window Proc, not sure what to do about catching Errors/Exceptions, seems to
63   just hang no matter what, tried assert(0), ExitProcess etc.. just doesnt close
64 */
65 
66 extern(Windows)
67 LRESULT WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) nothrow
68 {
69     try
70     {
71         auto window = cast(Win32Window) (cast(void*) GetWindowLongPtr(hwnd, GWLP_USERDATA));
72 
73         if (window is null)
74             return DefWindowProcA(hwnd, msg, wparam, lparam);
75         else
76             return window.windowProc(hwnd, msg, wparam, lparam);
77     }
78     catch (Exception e)
79     {
80         try { writeln(e.toString()); }
81         catch(Exception what) {}
82         PostQuitMessage(0);
83         return 0;
84     }
85 }
86 
87 /*
88   Window class, bare bones WinAPI wrapper
89 */
90 
91 class Win32Window : IWindow
92 {
93 private:
94 
95     HWND        m_handle;
96     DWORD       m_style;
97     DWORD       m_exstyle;
98     string      m_title;
99     int         m_width;
100     int         m_height;
101     Canvas      m_canvas;
102 
103     Widget      m_client;
104 
105     static immutable char[] wndclass  = "GFXWindow";
106     static HINSTANCE hinstance;
107 
108     UINT_PTR m_timer;
109 
110 public:
111 
112     void create(int x, int y, int w, int h, string title)
113     {
114         if (m_handle) destroyWindow();
115 
116         m_width = w;
117         m_height = h;
118 
119         m_style = WS_OVERLAPPEDWINDOW;
120         m_exstyle = WS_EX_APPWINDOW;
121 
122         RECT rect;
123         rect.left = x;
124         rect.top = y;
125         rect.right = x+w;
126         rect.bottom = y+h;
127 
128         AdjustWindowRectEx(&rect, m_style, false, m_exstyle);
129 
130         m_handle = CreateWindowExA(
131             WS_EX_APPWINDOW, wndclass.ptr,  toStringz(title), WS_OVERLAPPEDWINDOW,
132             rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top,
133             null, null, hinstance, NULL
134             );
135 
136         if (m_handle)
137         {
138             SetWindowLongPtrA(m_handle, GWLP_USERDATA, cast(LONG_PTR)( cast(void*)this ));
139             ShowWindow(m_handle, SW_SHOW);
140 
141             m_timer = SetTimer(m_handle, 0, 1000/20, NULL);
142         }
143         else
144         {
145             writeln("oops... coud not create window");
146             PostQuitMessage(0);
147         }
148     }
149 
150     void destroyWindow()
151     {
152         if (m_handle)
153         {
154             DestroyWindow(m_handle);
155             m_handle = null;
156         }
157     }
158 
159     this()
160     {
161     }
162 
163     ~this()
164     {
165         destroyWindow();
166     }
167 
168     void setBounds(int x, int y, int w, int h)
169     {
170         assert((w >= 0) && (h >= 0));
171 
172         m_width = w;
173         m_height = h;
174 
175         if (m_handle)
176         {
177             RECT r = RECT(x, y, x+w, y+h);
178             AdjustWindowRectEx(&r, m_style, false, m_exstyle);
179             MoveWindow(m_handle, r.left, r.top, r.right-r.left,
180                 r.bottom-r.top, true);
181         }
182     }
183 
184     bool isVisible()
185     {
186         if (m_handle) return (IsWindowVisible(m_handle) != 0);
187         return false;
188     }
189 
190     void repaint()
191     {
192         InvalidateRect(m_handle,null,0);
193     }
194 
195     void repaint(int x0, int y0, int x1, int y1)
196     {
197         RECT rect;
198         rect.left = x0;
199         rect.top = y0;
200         rect.right = x1;
201         rect.bottom = y1;
202         InvalidateRect(m_handle,&rect,0);
203     }
204 
205     // Window proc handler
206 
207     LRESULT windowProc(HWND hwnd, UINT msg, WPARAM _wparam, LPARAM _lparam)
208     {
209         WPARAM wparam = _wparam;
210         LPARAM lparam = _lparam;
211 
212         switch (msg)
213         {
214             case(WM_PAINT):              wm_Paint(); break;
215             case(WM_CLOSE):              wm_Close(); break;
216             case(WM_DESTROY):            wm_Destroy(); break;
217             case(WM_LBUTTONDOWN):        wm_Mouse(MouseEvent.LeftDown, cast(uint)wparam, cast(uint)lparam); break;
218             case(WM_LBUTTONUP):          wm_Mouse(MouseEvent.LeftUp, cast(uint)wparam, cast(uint)lparam); break;
219             case(WM_RBUTTONDOWN):        wm_Mouse(MouseEvent.RightDown, cast(uint)wparam, cast(uint)lparam); break;
220             case(WM_RBUTTONUP):          wm_Mouse(MouseEvent.RightUp, cast(uint)wparam, cast(uint)lparam); break;
221             case(WM_MOUSEMOVE):          wm_Mouse(MouseEvent.Move, cast(uint)wparam, cast(uint)lparam); break;
222             case(WM_TIMER):              wm_Timer(); break;
223 
224             default: return DefWindowProc(hwnd, msg, wparam, lparam); // can't take m_handle there because of WM_CREATE
225         }
226         return 0;
227     }
228 
229     void wm_Paint()
230     { 
231         PAINTSTRUCT ps;
232         BeginPaint(m_handle, &ps);
233         int l = ps.rcPaint.left;
234         int t = ps.rcPaint.top;
235         int r = ps.rcPaint.right;
236         int b = ps.rcPaint.bottom;
237 
238         if (m_canvas is null) m_canvas = new Canvas(r, b);
239         
240         if ((m_canvas.width < r) || (m_canvas.height < b))
241         {
242             m_canvas.resize(r, b);
243         }
244 
245         m_canvas.resetView();
246         m_canvas.setClip(l,t,r,b);
247 
248         onPaint(m_canvas);
249         if (m_client !is null) m_client.internalPaint(m_canvas);
250 
251         BITMAPINFO info;
252         info.bmiHeader.biSize          = info.sizeof;
253         info.bmiHeader.biWidth         = m_canvas.stride;
254         info.bmiHeader.biHeight        = -m_canvas.height;
255         info.bmiHeader.biPlanes        = 1;
256         info.bmiHeader.biBitCount      = 32;
257         info.bmiHeader.biCompression   = BI_RGB;
258         info.bmiHeader.biSizeImage     = m_canvas.stride*m_canvas.height*4;
259         info.bmiHeader.biXPelsPerMeter = 0;
260         info.bmiHeader.biYPelsPerMeter = 0;
261         info.bmiHeader.biClrUsed       = 0;
262         info.bmiHeader.biClrImportant  = 0;
263 
264         SetDIBitsToDevice(
265             ps.hdc, 0, 0, m_canvas.stride, m_canvas.height,0, 0, 0,
266             m_canvas.height, m_canvas.pixels, &info, DIB_RGB_COLORS);
267 
268         EndPaint(m_handle, &ps);
269     }
270 
271     void wm_Mouse(MouseEvent evt, uint wparam, uint lparam)
272     {
273         if (m_client !is null)
274         {
275             MouseMsg msg;
276             msg.event = evt;
277             msg.left = ((wparam & MK_LBUTTON) != 0);
278             msg.middle = ((wparam & MK_MBUTTON) != 0);
279             msg.right = ((wparam & MK_RBUTTON) != 0);
280             msg.x = cast(short)(lparam); // couldnt find GET_X_PARAM etc...
281             msg.y = cast(short)(lparam>>16);
282             m_client.internalMouse(msg);
283         }
284     }
285 
286     void wm_Close()
287     {
288         PostQuitMessage(0);        
289     }
290 
291     void wm_Destroy()
292     {
293         m_handle = null;
294     }
295 
296     void wm_Timer()
297     {
298         if (m_client !is null) m_client.internalTimer();
299     }
300 
301     void onPaint(Canvas canvas)
302     {
303     }
304 
305     void setContent(Widget widget)
306     {
307         if (widget.m_parent !is null) widget.m_parent.removeChild(widget);
308         widget.m_window = this;
309         m_client = widget;
310     }
311 }