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 }