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 	x11.X,
12 	x11.Xlib,
13 	x11.extensions.render,
14 	x11.extensions.Xrender,
15 	ws.draw,
16 	ws.wm,
17 	ws.bindings.xft,
18 	ws.gui.point,
19 	ws.x.font;
20 
21 
22 class Color {
23 
24 	ulong pix;
25 	XftColor rgb;
26 	long[4] rgba;
27 
28 	this(Display* dpy, int screen, long[4] values){
29 		Colormap cmap = DefaultColormap(dpy, screen);
30 		Visual* vis = DefaultVisual(dpy, screen);
31 		rgba = values;
32 		auto name = "#%02x%02x%02x".format(values[0], values[1], values[2]);
33 		if(!XftColorAllocName(dpy, vis, cmap, name.toStringz, &rgb))
34 			throw new Exception("Cannot allocate color " ~ name);
35 		pix = rgb.pixel;
36 	}
37 
38 }
39 
40 class Cur {
41 	Cursor cursor;
42 	this(Display* dpy, int shape){
43 		cursor = XCreateFontCursor(dpy, shape);
44 	}
45 	void destroy(Display* dpy){
46 		XFreeCursor(dpy, cursor);
47 	}
48 }
49 
50 class Icon {
51 	Picture picture;
52 	int[2] size;
53 	void destroy(Display* dpy){
54 		XRenderFreePicture(dpy, picture);
55 	}
56 }
57 
58 class XDraw: DrawEmpty {
59 
60 	int[2] size;
61 	Display* dpy;
62 	int screen;
63 	x11.X.Window window;
64 	Visual* visual;
65 	Drawable drawable;
66 	XftDraw* xft;
67 	GC gc;
68 
69 	Color color;
70 	Color[long[4]] colors;
71 
72 	ws.x.font.Font font;
73 	ws.x.font.Font[string] fonts;
74 
75 	XRectangle[] clipStack;
76 
77 	Picture frontBuffer;
78 
79 	this(ws.wm.Window window){
80 		this(wm.displayHandle, window.windowHandle);
81 	}
82 
83 	this(Display* dpy, x11.X.Window window){
84 		XWindowAttributes wa;
85 		XGetWindowAttributes(dpy, window, &wa);
86 		this.dpy = dpy;
87 		screen = DefaultScreen(dpy);
88 		this.window = window;
89 		this.size = [wa.width, wa.height];
90 		drawable = XCreatePixmap(dpy, window, size.w, size.h, wa.depth);
91 		gc = XCreateGC(dpy, window, 0, null);
92 		XSetLineAttributes(dpy, gc, 1, LineSolid, CapButt, JoinMiter);
93 		xft = XftDrawCreate(dpy, drawable, wa.visual, wa.colormap);
94 		visual = wa.visual;
95 		auto format = XRenderFindVisualFormat(dpy, wa.visual);
96 		XRenderPictureAttributes pa;
97 		pa.subwindow_mode = IncludeInferiors;
98 		frontBuffer = XRenderCreatePicture(dpy, drawable, format, CPSubwindowMode, &pa);
99 	}
100 
101 	this(x11.X.Window root, Drawable drawable, Picture frontBuffer){
102 		dpy = wm.displayHandle;
103 		screen = DefaultScreen(dpy);
104 		XWindowAttributes wa;
105 		XGetWindowAttributes(dpy, root, &wa);
106 		this.drawable = drawable;
107 		this.frontBuffer = frontBuffer;
108 		xft = XftDrawCreate(dpy, drawable, wa.visual, wa.colormap);
109 	}
110 
111 	~this(){
112 		if(drawable)
113 			XFreePixmap(dpy, drawable);
114 		if(frontBuffer)
115 			XRenderFreePicture(dpy, frontBuffer);
116 	}
117 
118 	override int width(string text){
119 		return font.width(text);
120 	}
121 
122 	override void resize(int[2] size){
123 		if(this.size == size)
124 			return;
125 		this.size = size;
126 		if(drawable)
127 			XFreePixmap(dpy, drawable);
128 		drawable = XCreatePixmap(dpy, window, size.w, size.h, DefaultDepth(dpy, screen));
129 		auto format = XRenderFindVisualFormat(dpy, visual);
130 		XRenderPictureAttributes pa;
131 		pa.subwindow_mode = IncludeInferiors;
132 		if(frontBuffer)
133 			XRenderFreePicture(dpy, frontBuffer);
134 		frontBuffer = XRenderCreatePicture(dpy, drawable, format, CPSubwindowMode, &pa);
135 		XftDrawChange(xft, drawable);
136 	}
137 
138 	override void destroy(){
139 		foreach(font; fonts)
140 			font.destroy;
141 		XFreePixmap(dpy, drawable);
142 		XRenderFreePicture(dpy, frontBuffer);
143 		drawable = None;
144 		frontBuffer = None;
145 		XftDrawDestroy(xft);
146 		XFreeGC(dpy, gc);
147 	}
148 
149 	override void setFont(string font, int size){
150 		font ~= ":size=%d".format(size);
151 		if(font !in fonts)
152 			fonts[font] = new ws.x.font.Font(dpy, screen, font);
153 		this.font = fonts[font];
154 	}
155 
156 	override int fontHeight(){
157 		return font.h;
158 	}
159 
160 	override void setColor(float[3] color){
161 		setColor([color[0], color[1], color[2], 1]);
162 	}
163 
164 	override void setColor(float[4] color){
165 		long[4] values = [
166 			(color[0]*255).lround.max(0).min(255),
167 			(color[1]*255).lround.max(0).min(255),
168 			(color[2]*255).lround.max(0).min(255),
169 			(color[3]*255).lround.max(0).min(255)
170 		];
171 		if(values !in colors)
172 			colors[values] = new Color(dpy, screen, values);
173 		this.color = colors[values];
174 	}
175 
176 	override void clip(int[2] pos, int[2] size){
177 		pos[1] = this.size[1] - pos[1] - size[1];
178 		if(clipStack.length){
179 			auto rect = clipStack[$-1];
180 			auto maxx = rect.x + rect.width;
181 			auto maxy = rect.y + rect.height;
182 			pos = [
183 					pos.x.max(rect.x).min(maxx),
184 					pos.y.max(rect.y).min(maxy)
185 			];
186 			size = [
187 					(pos.x+size.w.min(rect.width)).min(maxx)-pos.x,
188 					(pos.y+size.h.min(rect.height)).min(maxy)-pos.y
189 			];
190 		}
191 		auto rect = XRectangle(cast(short)pos[0], cast(short)pos[1], cast(short)size[0], cast(short)size[1]);
192 		XftDrawSetClipRectangles(xft, 0, 0, &rect, 1);
193 		if(gc)
194 			XSetClipRectangles(dpy, gc, 0, 0, &rect, 1, Unsorted);
195 		clipStack ~= rect;
196 	}
197 
198 	override void noclip(){
199 		clipStack = clipStack[0..$-1];
200 		if(clipStack.length){
201 			auto rect = clipStack[$-1];
202 			XftDrawSetClipRectangles(xft, 0, 0, &rect, 1);
203 			if(gc)
204 				XSetClipRectangles(dpy, gc, 0, 0, &rect, 1, Unsorted);
205 		}else{
206 			if(gc)
207 				XSetClipMask(dpy, gc, None);
208 			XftDrawSetClip(xft, null);
209 		}
210 	}
211 
212 	override void rect(int[2] pos, int[2] size){
213 		auto a = this.color.rgba[3]/255.0;
214 		XRenderColor color = {
215 			(this.color.rgba[0]*255*a).to!ushort,
216 			(this.color.rgba[1]*255*a).to!ushort,
217 			(this.color.rgba[2]*255*a).to!ushort,
218 			(this.color.rgba[3]*255).to!ushort
219 		};
220 		XRenderFillRectangle(dpy, PictOpOver, frontBuffer, &color, pos.x, this.size.h-size.h-pos.y, size.w, size.h);
221 	}
222 
223 	override void rectOutline(int[2] pos, int[2] size){
224 		XSetForeground(dpy, gc, color.pix);
225 		XDrawRectangle(dpy, drawable, gc, pos.x, this.size.h-pos.y-size.h, size.w, size.h);
226 	}
227 
228 	override void line(int[2] start, int[2] end){
229 		XSetForeground(dpy, gc, color.pix);
230 		XDrawLine(dpy, drawable, gc, start.x, start.y, end.x, end.y);
231 	}
232 
233 	override int text(int[2] pos, string text, double offset=-0.2){
234 		if(text.length){
235 			auto width = width(text);
236 			auto fontHeight = font.h;
237 			auto offsetRight = max(0.0,-offset)*fontHeight;
238 			auto offsetLeft = max(0.0,offset-1)*fontHeight;
239 			auto x = pos.x - min(1,max(0,offset))*width + offsetRight - offsetLeft;
240 			auto y = this.size.h - pos.y - 2;
241 			XftDrawStringUtf8(xft, &color.rgb, font.xfont, cast(int)x.lround, cast(int)y.lround, text.toStringz, cast(int)text.length);
242 			return this.width(text);
243 		}
244 		return 0;
245 	}
246 	
247 	override int text(int[2] pos, int h, string text, double offset=-0.2){
248 		pos.y += ((h-font.h)/2.0).lround;
249 		return this.text(pos, text, offset);
250 	}
251 
252 	Icon icon(ubyte[] data, int[2] size){
253 		assert(data.length == size.w*size.h*4, "%s != %s*%s*4".format(data.length, size.w, size.h));
254 		auto res = new Icon;
255 
256 		auto img = XCreateImage(
257 				dpy,
258 				null,
259 				32,
260 				ZPixmap,
261 				0,
262 				cast(char*)data.ptr,
263 				cast(uint)size.w,
264 				cast(uint)size.h,
265 				32,
266 				0
267 		);
268 
269 		auto pixmap = XCreatePixmap(dpy, drawable, size.w, size.h, 32);
270 
271      	XRenderPictureAttributes attributes;
272 		auto gc = XCreateGC(dpy, pixmap, 0, null);
273 	    XPutImage(dpy, pixmap, gc, img, 0, 0, 0, 0, size.w, size.h);
274      	auto pictformat = XRenderFindStandardFormat(dpy, PictStandardARGB32);
275      	res.picture = XRenderCreatePicture(dpy, pixmap, pictformat, 0, &attributes);
276 		XRenderSetPictureFilter(dpy, res.picture, "best", null, 0);
277 		XFreePixmap(dpy, pixmap);
278 
279 		res.size = size;
280 		return res;
281 		/+
282 		res.pixmap = XCreatePixmap(wm.displayHandle, window, DisplayWidth(wm.displayHandle, 0), DisplayHeight(wm.displayHandle, 0), DefaultDepth(wm.displayHandle, 0));
283 		res.picture = XRenderCreatePicture(wm.displayHandle, pixmap, format, 0, null);
284 		XFreePixmap(wm.displayHandle, res.pixmap);
285 		+/
286 		
287 	}
288 
289 	void icon(Icon icon, int x, int y, double scale, Picture alpha=None){
290 		XTransform xform = {[
291 			[XDoubleToFixed( 1 ), XDoubleToFixed( 0 ), XDoubleToFixed( 0 )],
292 			[XDoubleToFixed( 0 ), XDoubleToFixed( 1 ), XDoubleToFixed( 0 )],
293 			[XDoubleToFixed( 0 ), XDoubleToFixed( 0 ), XDoubleToFixed( scale )]
294 		]};
295 		XRenderSetPictureTransform(dpy, icon.picture, &xform);
296 		XRenderComposite(dpy, PictOpOver, icon.picture, alpha, frontBuffer, 0, 0, 0, 0, x, y, (icon.size.w*scale).to!int, (icon.size.h*scale).to!int);
297 	}
298 
299 	override void finishFrame(){
300 		XCopyArea(dpy, drawable, window, gc, 0, 0, size.w, size.h, 0, 0);
301 		XSync(dpy, False);
302 	}
303 
304 }
305