1 module ws.x.draw; 2 3 version(Posix): 4 5 6 import 7 std.string, 8 std.algorithm, 9 std.math, 10 std.conv, 11 std.range, 12 x11.X, 13 x11.Xlib, 14 x11.extensions.render, 15 x11.extensions.Xrender, 16 x11.extensions.Xfixes, 17 ws.draw, 18 ws.wm, 19 ws.bindings.xft, 20 ws.gui.point, 21 ws.x.backbuffer, 22 ws.x.font; 23 24 25 class Color { 26 27 ulong pix; 28 XftColor rgb; 29 long[4] rgba; 30 31 this(Display* dpy, int screen, long[4] values){ 32 Colormap cmap = DefaultColormap(dpy, screen); 33 Visual* vis = DefaultVisual(dpy, screen); 34 rgba = values; 35 auto name = "#%02x%02x%02x".format(values[0], values[1], values[2]); 36 if(!XftColorAllocName(dpy, vis, cmap, name.toStringz, &rgb)) 37 throw new Exception("Cannot allocate color " ~ name); 38 pix = rgb.pixel; 39 } 40 41 } 42 43 class Cur { 44 Cursor cursor; 45 this(Display* dpy, int shape){ 46 cursor = XCreateFontCursor(dpy, shape); 47 } 48 void destroy(Display* dpy){ 49 XFreeCursor(dpy, cursor); 50 } 51 } 52 53 class Icon { 54 Picture picture; 55 int[2] size; 56 ~this(){ 57 // TODO: fix crash if X connection closes before this is called 58 XRenderFreePicture(wm.displayHandle, picture); 59 } 60 } 61 62 63 class ManagedPicture { 64 Display* dpy; 65 Picture picture; 66 alias picture this; 67 this(Display* dpy, Drawable drawable, XRenderPictFormat* format){ 68 this.dpy = dpy; 69 XRenderPictureAttributes pa; 70 pa.subwindow_mode = IncludeInferiors; 71 picture = XRenderCreatePicture(dpy, drawable, format, CPSubwindowMode, &pa); 72 } 73 ~this(){ 74 XRenderFreePicture(dpy, picture); 75 } 76 } 77 78 79 struct ClipStack { 80 XserverRegion[] stack; 81 82 void push(XserverRegion region){ 83 XserverRegion newregion = XFixesCreateRegion(wm.displayHandle, null, 0); 84 XFixesCopyRegion(wm.displayHandle, newregion, region); 85 if(stack.length) 86 XFixesIntersectRegion(wm.displayHandle, newregion, stack[$-1], newregion); 87 stack ~= newregion; 88 } 89 90 void push(int[2] pos, int[2] size){ 91 XRectangle r = { 92 pos.x.to!short, 93 pos.y.to!short, 94 size.w.max(0).to!ushort, 95 size.h.max(0).to!ushort 96 }; 97 XserverRegion region = XFixesCreateRegion(wm.displayHandle, &r, 1); 98 if(stack.length) 99 XFixesIntersectRegion(wm.displayHandle, region, stack[$-1], region); 100 stack ~= region; 101 } 102 103 void clip(XftDraw* xft, GC gc, Picture picture){ 104 if(stack.length){ 105 int count; 106 auto area = XFixesFetchRegion(wm.displayHandle, stack[$-1], &count); 107 XftDrawSetClipRectangles(xft, 0, 0, area, count); 108 XFree(area); 109 XFixesSetPictureClipRegion(wm.displayHandle, picture, 0, 0, stack[$-1]); 110 if(gc) 111 XFixesSetGCClipRegion(wm.displayHandle, gc, 0, 0, stack[$-1]); 112 }else{ 113 if(gc) 114 XFixesSetGCClipRegion(wm.displayHandle, gc, 0, 0, None); 115 XFixesSetPictureClipRegion(wm.displayHandle, picture, 0, 0, None); 116 XftDrawSetClip(xft, null); 117 } 118 } 119 120 void pop(){ 121 XFixesDestroyRegion(wm.displayHandle, stack[$-1]); 122 stack.length -= 1; 123 } 124 125 XserverRegion all(){ 126 return stack[$-1]; 127 } 128 } 129 130 131 class PixmapDoubleBuffer { 132 133 Pixmap pixmap; 134 WindowHandle window; 135 GC gc; 136 int[2] size; 137 Display* dpy; 138 139 alias pixmap this; 140 141 this(Display* dpy, WindowHandle window, GC gc, XWindowAttributes* wa){ 142 this.dpy = dpy; 143 this.gc = gc; 144 this.window = window; 145 pixmap = XCreatePixmap(dpy, window, wa.width, wa.height, wa.depth); 146 this.size = [wa.width, wa.height]; 147 } 148 149 void swap(){ 150 XCopyArea(dpy, pixmap, window, gc, 0, 0, size.w, size.h, 0, 0); 151 } 152 153 ~this(){ 154 XFreePixmap(dpy, pixmap); 155 } 156 157 } 158 159 160 class PictureDoubleBuffer { 161 162 Pixmap backPixmap; 163 ManagedPicture back; 164 ManagedPicture front; 165 WindowHandle window; 166 Display* dpy; 167 int[2] size; 168 169 alias backPixmap this; 170 171 this(Display* dpy, WindowHandle window, XWindowAttributes* wa){ 172 this.dpy = dpy; 173 XRenderPictFormat* format = XRenderFindVisualFormat(wm.displayHandle, wa.visual); 174 front = new ManagedPicture(dpy, window, format); 175 backPixmap = XCreatePixmap(dpy, window, wa.width, wa.height, wa.depth); 176 back = new ManagedPicture(dpy, backPixmap, format); 177 this.window = window; 178 this.size = [wa.width, wa.height]; 179 } 180 181 void swap(){ 182 XRenderComposite( 183 dpy, 184 PictOpSrc, 185 back, 186 None, 187 front, 188 0, 189 0, 190 0, 191 0, 192 0, 193 0, 194 size.w, 195 size.h 196 ); 197 } 198 199 ~this(){ 200 XFreePixmap(wm.displayHandle, backPixmap); 201 } 202 203 } 204 205 206 class XDraw: DrawEmpty { 207 208 int[2] size; 209 Display* dpy; 210 int screen; 211 x11.X.Window window; 212 Visual* visual; 213 XftDraw* xft; 214 GC gc; 215 216 Color color; 217 Color[long[4]] colors; 218 219 ws.x.font.Font font; 220 ws.x.font.Font[string] fonts; 221 222 ClipStack clipStack; 223 224 version(Xdbe){ 225 Xdbe.BackBuffer drawable; 226 }else{ 227 PixmapDoubleBuffer drawable; // TODO: flatman splits/floating title bars break with this 228 } 229 230 ManagedPicture picture; 231 232 this(ws.wm.Window window){ 233 this(wm.displayHandle, window.windowHandle); 234 } 235 236 this(Display* dpy, x11.X.Window window){ 237 XWindowAttributes wa; 238 XGetWindowAttributes(dpy, window, &wa); 239 this.dpy = dpy; 240 screen = DefaultScreen(dpy); 241 this.window = window; 242 this.size = [wa.width, wa.height]; 243 this.gc = XCreateGC(dpy, window, 0, null); 244 XSetLineAttributes(dpy, gc, 1, LineSolid, CapButt, JoinMiter); 245 version(Xdbe){ 246 drawable = new Xdbe.BackBuffer(dpy, window); 247 }else{ 248 drawable = new PixmapDoubleBuffer(dpy, window, gc, &wa); 249 } 250 xft = XftDrawCreate(dpy, drawable, wa.visual, wa.colormap); 251 visual = wa.visual; 252 auto format = XRenderFindVisualFormat(dpy, wa.visual); 253 picture = new ManagedPicture(dpy, drawable, format); 254 } 255 256 override int width(string text){ 257 debug { 258 assert(font, "No font active"); 259 } 260 return font.width(text); 261 } 262 263 override void resize(int[2] size){ 264 this.size = size; 265 XWindowAttributes wa; 266 XGetWindowAttributes(dpy, window, &wa); 267 .destroy(picture); 268 version(Xdbe){} 269 else{ 270 .destroy(drawable); 271 drawable = new PixmapDoubleBuffer(dpy, window, gc, &wa); 272 } 273 auto format = XRenderFindVisualFormat(dpy, wa.visual); 274 picture = new ManagedPicture(dpy, drawable, format); 275 XftDrawChange(xft, drawable); 276 } 277 278 override void destroy(){ 279 foreach(font; fonts) 280 font.destroy; 281 .destroy(drawable); 282 .destroy(picture); 283 XftDrawDestroy(xft); 284 XFreeGC(dpy, gc); 285 } 286 287 override void setFont(string font, int size){ 288 font ~= ":size=%d".format(size); 289 if(font !in fonts) 290 fonts[font] = new ws.x.font.Font(dpy, screen, font); 291 this.font = fonts[font]; 292 } 293 294 override int fontHeight(){ 295 return font.h; 296 } 297 298 override void setColor(float[3] color){ 299 setColor([color[0], color[1], color[2], 1]); 300 } 301 302 override void setColor(float[4] color){ 303 long[4] values = [ 304 (color[0]*255).lround.max(0).min(255), 305 (color[1]*255).lround.max(0).min(255), 306 (color[2]*255).lround.max(0).min(255), 307 (color[3]*255).lround.max(0).min(255) 308 ]; 309 if(values !in colors) 310 colors[values] = new Color(dpy, screen, values); 311 this.color = colors[values]; 312 } 313 314 override void clip(int[2] pos, int[2] size){ 315 clipStack.push([pos.x, this.size.h-size.h-pos.y], size); 316 //clipStack.push(pos, size); 317 clipStack.clip(xft, gc, picture); 318 } 319 320 void clip(XserverRegion region){ 321 clipStack.push(region); 322 clipStack.clip(xft, gc, picture); 323 } 324 325 override void noclip(){ 326 clipStack.pop; 327 clipStack.clip(xft, gc, picture); 328 } 329 330 override void rect(int[2] pos, int[2] size){ 331 auto a = this.color.rgba[3]/255.0; 332 XRenderColor color = { 333 (this.color.rgba[0]*255*a).to!ushort, 334 (this.color.rgba[1]*255*a).to!ushort, 335 (this.color.rgba[2]*255*a).to!ushort, 336 (this.color.rgba[3]*255).to!ushort 337 }; 338 XRenderFillRectangle(dpy, PictOpOver, picture, &color, pos.x, this.size.h-size.h-pos.y, size.w, size.h); 339 } 340 341 override void rectOutline(int[2] pos, int[2] size){ 342 clip(pos.a + [1,1], size.a - [2,2]); 343 rect(pos, size); 344 noclip; 345 } 346 347 override void line(int[2] start, int[2] end){ 348 XSetForeground(dpy, gc, color.pix); 349 XDrawLine(dpy, drawable, gc, start.x, start.y, end.x, end.y); 350 } 351 352 override int text(int[2] pos, string text, double offset=-0.2){ 353 if(text.length){ 354 text = text.replace("\t", " "); 355 auto width = width(text); 356 auto fontHeight = font.h; 357 auto offsetRight = max(0.0,-offset)*fontHeight; 358 auto offsetLeft = max(0.0,offset-1)*fontHeight; 359 auto x = pos.x - min(1,max(0,offset))*width + offsetRight - offsetLeft; 360 auto y = this.size.h - pos.y - 2; 361 XftDrawStringUtf8(xft, &color.rgb, font.xfont, cast(int)x.lround, cast(int)y.lround, text.toStringz, cast(int)text.length); 362 return this.width(text); 363 } 364 return 0; 365 } 366 367 override int text(int[2] pos, int h, string text, double offset=-0.2){ 368 pos.y += ((h-font.h)/2.0).lround; 369 return this.text(pos, text, offset); 370 } 371 372 Icon icon(ubyte[] data, int[2] size){ 373 assert(data.length == size.w*size.h*4, "%s != %s*%s*4".format(data.length, size.w, size.h)); 374 auto res = new Icon; 375 376 auto img = XCreateImage( 377 dpy, 378 null, 379 32, 380 ZPixmap, 381 0, 382 cast(char*)data.ptr, 383 cast(uint)size.w, 384 cast(uint)size.h, 385 32, 386 0 387 ); 388 389 auto pixmap = XCreatePixmap(dpy, drawable, size.w, size.h, 32); 390 391 XRenderPictureAttributes attributes; 392 auto gc = XCreateGC(dpy, pixmap, 0, null); 393 XPutImage(dpy, pixmap, gc, img, 0, 0, 0, 0, size.w, size.h); 394 auto pictformat = XRenderFindStandardFormat(dpy, PictStandardARGB32); 395 res.picture = XRenderCreatePicture(dpy, pixmap, pictformat, 0, &attributes); 396 XRenderSetPictureFilter(dpy, res.picture, "best", null, 0); 397 XFreePixmap(dpy, pixmap); 398 XFreeGC(dpy, gc); 399 400 res.size = size; 401 return res; 402 /+ 403 res.pixmap = XCreatePixmap(wm.displayHandle, window, DisplayWidth(wm.displayHandle, 0), DisplayHeight(wm.displayHandle, 0), DefaultDepth(wm.displayHandle, 0)); 404 res.picture = XRenderCreatePicture(wm.displayHandle, pixmap, format, 0, null); 405 XFreePixmap(wm.displayHandle, res.pixmap); 406 +/ 407 408 } 409 410 void icon(Icon icon, int x, int y, double scale, Picture alpha=None){ 411 XTransform xform = {[ 412 [XDoubleToFixed( 1 ), XDoubleToFixed( 0 ), XDoubleToFixed( 0 )], 413 [XDoubleToFixed( 0 ), XDoubleToFixed( 1 ), XDoubleToFixed( 0 )], 414 [XDoubleToFixed( 0 ), XDoubleToFixed( 0 ), XDoubleToFixed( scale )] 415 ]}; 416 XRenderSetPictureTransform(dpy, icon.picture, &xform); 417 XRenderComposite(dpy, PictOpOver, icon.picture, alpha, picture, 0, 0, 0, 0, x, y, (icon.size.w*scale).to!int, (icon.size.h*scale).to!int); 418 } 419 420 override void clear(){ 421 XRenderColor color = {0, 0, 0, 0}; 422 XRenderFillRectangle(dpy, PictOpSrc, picture, &color, 0, 0, size.w, size.h); 423 } 424 425 override void finishFrame(){ 426 //XCopyArea(dpy, drawable, window, gc, 0, 0, size.w, size.h, 0, 0); 427 //XRenderComposite(dpy, PictOpSrc, picture, None, frontBuffer, 0, 0, 0, 0, 0, 0, size.w, size.h); 428 drawable.swap; 429 } 430 431 }