1 module window;
2 
3 import std.functional : memoize;
4 
5 import win32;
6 import x11window;
7 
8 import dg2d;
9 
10 interface IWindow
11 {
12     void setContent(Widget widget);
13 
14     void create(int x, int y, int w, int h, string title);
15 
16     void repaint();
17     void repaint(int x0, int y0, int x1, int y1);
18 }
19 
20 version (Windows)
21 {
22     enum WindowingBackend
23     {
24         win32,
25     }
26 }
27 else version (linux)
28 {
29     enum WindowingBackend
30     {
31         x11,
32         // wayland,
33     }
34 }
35 
36 WindowingBackend redetermineWindowingBackend()
37 {
38     version (Windows)
39         return WindowingBackend.win32;
40     else version (linux)
41     {
42         import std.process : environment;
43 
44         switch (environment.get("XDG_SESSION_TYPE"))
45         {
46             case("x11"): return WindowingBackend.x11;
47             case("wayland"): assert(false, "wayland not supported yet");
48             default: break; // check below
49         }
50 
51         if (environment.get("DISPLAY").length)
52             return WindowingBackend.x11;
53         else if (environment.get("WAYLAND_DISPLAY").length)
54             assert(false, "wayland not supported yet");
55         else
56             throw new Exception("No supported windowing system detected, please start again in X11 or wayland");
57     }
58     else
59         static assert(false, "No windowing backend for this platform");
60 }
61 
62 alias determineWindowingBackend = memoize!redetermineWindowingBackend;
63 
64 IWindow createPlatformWindow()
65 {
66   
67     final switch (determineWindowingBackend)
68     {
69         version (Windows) case(WindowingBackend.win32): return new Win32Window();
70         version (linux) case(WindowingBackend.x11): return new X11Window();
71     }
72 }
73 
74 void loadPlatformWindow()
75 {
76     final switch (determineWindowingBackend)
77     {
78         version (Windows) case(WindowingBackend.win32): RegisterWindowClass();
79         version (linux) case(WindowingBackend.x11): ConnectX11();
80     }
81 }
82 
83 void runMainLoop()
84 {
85     final switch (determineWindowingBackend)
86     {
87         version (Windows) case(WindowingBackend.win32): WindowsMessageLoop(); 
88         version (linux) case(WindowingBackend.x11): X11EventLoop();
89     }
90 }
91 
92 // mouse stuff
93 
94 enum MouseEvent
95 {
96     LeftDown, MiddleDown, RightDown,
97     LeftUp, MiddleUp, RightUp,
98     LeftDblCk, MiddleDblCk, RightDblCk,
99     Move, Enter, Exit, EndFocus,
100     LeftDrag, RightDrag,
101     Wheel
102 }
103 
104 struct MouseMsg
105 {
106     MouseEvent  event;
107     int         x,y;
108     /// wheel offset, down is positive, up is negative
109     int         w;
110     bool        focused;
111     bool        left;
112     bool        middle;
113     bool        right;
114     bool        shift;
115     bool        ctrl;
116     bool        alt;
117     bool        super_;
118 }
119 
120 // Widget
121 
122 class Widget
123 {
124     this(int x, int y, int width, int height)
125     {
126         m_x = x;
127         m_y = y;
128         m_width = width;
129         m_height = height;
130     }
131 
132     void addChild(Widget widget)
133     {
134         if (widget.m_parent !is null) widget.m_parent.removeChild(widget);
135         if (widget.m_window !is null) widget.m_window.setContent(this);
136         m_widgets ~= widget;
137         widget.m_parent = this;
138     }
139 
140     void removeChild(Widget widget)
141     {
142         foreach(i, child; m_widgets)
143         {
144             if (child is widget)
145             {
146                 m_widgets[i..$-1] = m_widgets[i+1..$];
147                 m_widgets.length = m_widgets.length-1;
148                 widget.m_parent = null;
149                 return;
150             }
151         }   
152     }
153 
154     void repaint()
155     {
156         repaint(0,0,m_width,m_height);
157     }
158 
159     void repaint(int x0, int y0, int x1, int y1)
160     {
161         if (m_parent !is null)
162         {
163             m_parent.repaint(m_x+x0,m_y+y0,m_x+x1,m_y+y1);
164         }
165         else if (m_window !is null)
166         {
167             m_window.repaint(m_x+x0,m_y+y0,m_x+x1,m_y+y1);
168         }
169     }
170 
171     void onPaint(Canvas canvas)
172     {
173     }
174 
175     void onMouse(MouseMsg msg)
176     {
177     }
178 
179     void onTimer()
180     {
181     }
182 
183     int right()
184     {
185         return m_x+m_width;
186     }
187     
188     int bottom()
189     {
190         return m_y+m_height;
191     }
192 
193     bool contains(int x, int y)
194     {
195         return ((x >= m_x) && (x < m_x+m_width)
196             && (y >= m_y) && (y < m_y+m_height));
197     }
198 
199     void internalPaint(Canvas canvas)
200     {
201         onPaint(canvas);
202         auto state = canvas.getViewState();
203  
204         foreach(widget; m_widgets)
205         {
206             canvas.setView(state, widget.m_x, widget.m_y, widget.right, widget.bottom); 
207             if (!canvas.isClipEmpty) widget.internalPaint(canvas);
208         }
209 
210         canvas.resetState(state);
211     }
212    
213     // returns the widget that got the message
214 
215     Widget internalMouse(MouseMsg msg)
216     {
217         foreach_reverse(widget; m_widgets)
218         {
219             if (widget.contains(msg.x,msg.y))
220             {
221                 msg.x -= widget.m_x;
222                 msg.y -= widget.m_y;
223                 return widget.internalMouse(msg);
224             }
225         }
226         onMouse(msg);
227         return this;
228     }
229 
230     void internalTimer()
231     {
232         foreach_reverse(widget; m_widgets)
233         {
234             widget.onTimer();
235         }
236         onTimer();
237     }
238 
239     Widget m_parent;
240     IWindow m_window;
241     Widget[] m_widgets;
242 
243     int m_x,m_y,m_width,m_height;
244 }
245 
246 /*
247   Button class
248 */
249 
250 alias ButtonClick = void delegate();
251 
252 class Button : Widget
253 {
254     this(int x,int y, int w, int h, string text, Font f)
255     {
256         super(x,y,w,h);
257         m_text = text;
258         m_font = f;
259     }
260 
261     void setOnClick(ButtonClick onclick)
262     {
263         m_onclick = onclick;
264     }
265 
266     bool hitTest(int x, int y)
267     {
268         return ((x >= m_x) && (x < m_x+m_width)
269             && (y >= m_y) && (y < m_y+m_height));
270     }
271 
272     override void onPaint(Canvas c)
273     {
274         c.draw(RoundRect(0,0,m_width,m_height,10).asPath, 0x80a0c0ff, WindingRule.NonZero);
275         c.draw(RoundRect(2,2,m_width-2,m_height-2,10).asPath, 0xFF000000, WindingRule.NonZero);
276 
277         int tx = 20;
278         int ty = m_height - cast(int) (m_height - m_font.height) / 2;
279         c.drawText(tx,ty,m_text,m_font,0xFFffffff);
280     }
281 
282     override void onMouse(MouseMsg msg)
283     {
284         if ((msg.event == MouseEvent.LeftDown) ||
285             (msg.event == MouseEvent.LeftDblCk))
286         {
287             if (m_onclick !is null) m_onclick();
288         }
289     }
290 
291     void setText(string txt)
292     {
293         m_text = txt;
294     }
295 
296 private:
297 
298     string m_text;
299     Font m_font;
300     ButtonClick m_onclick;
301 }
302 
303 class Label : Widget
304 {
305     this(int x,int y, int w, int h, string text, Font f)
306     {
307         super(x,y,w,h);
308         m_text = text;
309         m_font = f;
310     }
311 
312     override void onPaint(Canvas c)
313     {
314         c.draw(RoundRect(0,0,m_width,m_height,4).asPath, 0x80000000, WindingRule.NonZero);
315         c.draw(RoundRect(1,1,m_width-1,m_height-1,4).asPath, 0xFFFFFFFF, WindingRule.NonZero);
316         int tx = cast(int) (m_width - m_font.getStrWidth(m_text)) / 2;
317         int ty = m_height - cast(int) (m_height - m_font.height) / 2;
318         c.drawText(tx,ty,m_text,m_font,0xFF000000);
319     }
320 
321     void setText(string text)
322     {
323         m_text = text;
324     }
325 
326 private:
327 
328     string m_text;
329     Font m_font;
330 }