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 }