switched to GPLv3 ONLY, because i don't trust FSF anymore
[amper.git] / egfx / backx.d
blobe17133a4d18ab443288e78dbd517e87655cadc96
1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 module egfx.backx;
17 private:
19 import arsd.simpledisplay;
21 import iv.cmdcon;
22 import iv.cmdcongl;
23 import iv.sdpyutil;
25 import egfx.base : GxRect, c2img;
26 import egfx.text;
27 import egfx.util;
30 // ////////////////////////////////////////////////////////////////////////// //
31 public struct XlibTCImage {
32 XImage* handle;
34 @disable this (this);
36 this (MemoryImage img) {
37 if (img is null || img.width < 1 || img.height < 1) throw new Exception("can't create xlib image from empty MemoryImage");
38 create(img.width, img.height, img);
41 this (int wdt, int hgt) {
42 if (wdt < 1 || hgt < 1) throw new Exception("invalid xlib image");
43 create(wdt, hgt, null);
46 ~this () { dispose(); }
48 @property bool valid () const pure nothrow @trusted @nogc { pragma(inline, true); return (handle !is null); }
50 @property int width () const pure nothrow @trusted @nogc { pragma(inline, true); return (handle !is null ? handle.width : 0); }
51 @property int height () const pure nothrow @trusted @nogc { pragma(inline, true); return (handle !is null ? handle.height : 0); }
53 void setup (MemoryImage aimg) {
54 dispose();
55 if (aimg is null || aimg.width < 1 || aimg.height < 1) throw new Exception("can't create xlib image from empty MemoryImage");
56 create(aimg.width, aimg.height, aimg);
59 private void create (int width, int height, MemoryImage ximg) {
60 import core.stdc.stdlib : malloc, free;
61 if (glconCtlWindow is null || glconCtlWindow.closed) assert(0, "wtf?!");
62 auto dpy = glconCtlWindow.impl.display;
63 assert(dpy !is null);
64 auto screen = DefaultScreen(dpy);
65 // this actually needs to be malloc to avoid a double free error when XDestroyImage is called
66 auto rawData = cast(uint*)malloc(width*height*4);
67 scope(failure) free(rawData);
68 if (ximg is null || ximg.width < width || ximg.height < height) rawData[0..width*height] = 0;
69 if (ximg && ximg.width > 0 && ximg.height > 0) {
70 foreach (immutable int y; 0..height) {
71 foreach (immutable int x; 0..width) {
72 rawData[y*width+x] = c2img(ximg.getPixel(x, y));
76 handle = XCreateImage(dpy, DefaultVisual(dpy, screen), 24/*bpp*/, ImageFormat.ZPixmap, 0/*offset*/, cast(ubyte*)rawData, width, height, 8/*FIXME*/, 4*width); // padding, bytes per line
79 void dispose () {
80 // note: this calls free() for us
81 if (handle !is null) {
82 XDestroyImage(handle);
83 handle = null;
87 // blit to window buffer
88 final void blitAt (SimpleWindow w, int destx, int desty) {
89 blitRect(w, destx, desty, GxRect(0, 0, width, height));
92 // blit to window buffer
93 final void blitRect (SimpleWindow w, int destx, int desty, GxRect srect) {
94 if (w is null || handle is null || w.closed) return;
95 XPutImage(w.impl.display, cast(Drawable)w.impl.buffer, w.impl.gc, handle, srect.x0, srect.y0, destx, desty, srect.width, srect.height);
98 // blit to window
99 final void blitAtWin (SimpleWindow w, int destx, int desty) {
100 blitRectWin(w, destx, desty, GxRect(0, 0, width, height));
103 // blit to window
104 final void blitRectWin (SimpleWindow w, int destx, int desty, GxRect srect) {
105 if (w is null || handle is null || w.closed) return;
106 XPutImage(w.impl.display, cast(Drawable)w.impl.window, w.impl.gc, handle, srect.x0, srect.y0, destx, desty, srect.width, srect.height);
111 // ////////////////////////////////////////////////////////////////////////// //
112 public struct EPixmapImpl {
113 Pixmap xpm;
114 private int mWidth, mHeight;
116 this (int wdt, int hgt) {
117 //if (width < 1 || height < 1) throw new Exception("invalid pixmap dimensions");
118 if (wdt < 1) wdt = 1;
119 if (hgt < 1) hgt = 1;
120 if (wdt > 1024) wdt = 1024;
121 if (hgt > 1024) hgt = 1024;
122 xpm = XCreatePixmap(glconCtlWindow.impl.display, cast(Drawable)glconCtlWindow.impl.window, wdt, hgt, 24);
123 mWidth = wdt;
124 mHeight = hgt;
127 this (ref XlibTCImage xtc) {
128 if (!xtc.valid) throw new Exception("can't create pixmap from empty xlib image");
129 int wdt = xtc.width;
130 int hgt = xtc.height;
131 if (wdt < 1) wdt = 1;
132 if (hgt < 1) hgt = 1;
133 if (wdt > 1024) wdt = 1024;
134 if (hgt > 1024) hgt = 1024;
135 xpm = XCreatePixmap(glconCtlWindow.impl.display, cast(Drawable)glconCtlWindow.impl.window, wdt, hgt, 24);
136 // source x, source y
137 XPutImage(glconCtlWindow.impl.display, cast(Drawable)xpm, glconCtlWindow.impl.gc, xtc.handle, 0, 0, 0, 0, wdt, hgt);
138 mWidth = wdt;
139 mHeight = hgt;
142 @disable this (this);
144 ~this () {
145 if (glconCtlWindow is null || glconCtlWindow.closed) { xpm = 0; return; }
146 if (xpm) {
147 XFreePixmap(glconCtlWindow.impl.display, xpm);
148 xpm = 0;
152 @property bool valid () const pure nothrow @trusted @nogc { pragma(inline, true); return (xpm != 0); }
154 @property int width () const pure nothrow @trusted @nogc { pragma(inline, true); return mWidth; }
155 @property int height () const pure nothrow @trusted @nogc { pragma(inline, true); return mHeight; }
157 // blit to window buffer
158 final void blitAt (SimpleWindow w, int x, int y) {
159 blitRect(w, x, y, GxRect(0, 0, width, height));
162 // blit to window buffer
163 final void blitRect (SimpleWindow w, int destx, int desty, GxRect srect) {
164 if (w is null || !xpm || w.closed) return;
165 XCopyArea(w.impl.display, cast(Drawable)xpm, cast(Drawable)w.impl.buffer, w.impl.gc, srect.x0, srect.y0, srect.width, srect.height, destx, desty);
170 public alias EPixmap = KRC!EPixmapImpl;
173 // ////////////////////////////////////////////////////////////////////////// //
174 shared static this () {
175 gxCreatePixmap1bpp = delegate (int wdt, int hgt, scope uint delegate (int x, int y) getPixel) {
176 if (glconCtlWindow !is null && !glconCtlWindow.closed) {
177 auto dpy = glconCtlWindow.impl.display;
178 auto drw = cast(Drawable)cast(Drawable)glconCtlWindow.impl.window;
179 Pixmap px = XCreatePixmap(dpy, drw, wdt, hgt, 24);
180 // alas, painter can set clip mask, and we have no way to save and restore it, so...
181 GC gc = XCreateGC(dpy, drw, 0, null);
182 scope(exit) XFreeGC(dpy, gc);
183 XCopyGC(dpy, DefaultGC(dpy, DefaultScreen(dpy)), 0xffffffff, gc);
184 XSetClipMask(dpy, gc, None);
186 foreach (immutable int dy; 0..hgt) {
187 foreach (immutable int dx; 0..wdt) {
188 auto c = (getPixel(dx, dy) ? ~0 : 0);
189 XSetForeground(dpy, gc, c);
190 XDrawPoint(dpy, cast(Drawable)px, gc, dx, dy);
194 import core.stdc.stdlib : malloc;
195 ubyte* rawData = cast(ubyte*)malloc(wdt*hgt*4);
196 rawData[0..wdt*hgt*4] = 0;
197 uint* rd = cast(uint*)rawData;
198 foreach (immutable int y; 0..hgt) {
199 foreach (immutable int x; 0..wdt) {
200 auto c = (getPixel(x, y) ? ~0 : 0);
201 rd[y*wdt+x] = c;
204 import arsd.simpledisplay : ImageFormat;
205 auto handle = XCreateImage(dpy, DefaultVisual(dpy, DefaultScreen(dpy)), 24/*bpp*/, ImageFormat.ZPixmap, 0/*offset*/, rawData, wdt, hgt, 8/*FIXME*/, 4*wdt); // padding, bytes per line
206 scope(exit) XDestroyImage(handle);
207 XPutImage(dpy, cast(Drawable)px, gc, handle, 0, 0, 0, 0, wdt, hgt);
208 return px;
209 } else {
210 return 0;
213 gxDeletePixmap = delegate (Pixmap px) {
214 if (glconCtlWindow is null || glconCtlWindow.closed) return;
215 XFreePixmap(glconCtlWindow.impl.display, px);
220 // ////////////////////////////////////////////////////////////////////////// //
221 public class EWindow {
222 protected:
223 SimpleWindow swin;
224 int mWidth, mHeight;
225 bool mActive;
226 EWidget[] widgets;
227 int mActiveWidget = -1;
229 public:
230 this (int awidth, int aheight) {
231 if (awidth < 1) awidth = 1;
232 if (aheight < 1) aheight = 1;
233 mWidth = awidth;
234 mHeight = aheight;
237 final @property int width () const pure nothrow @safe @nogc { pragma(inline, true); return mWidth; }
238 final @property int height () const pure nothrow @safe @nogc { pragma(inline, true); return mHeight; }
240 void setSize (int awdt, int ahgt) {
241 if (awdt < 1) awdt = 1;
242 if (ahgt < 1) ahgt = 1;
243 mWidth = awdt;
244 mHeight = ahgt;
247 EWidget addWidget (EWidget w) {
248 if (w is null) return null;
249 if (w.parent !is null) throw new Exception("widget already owned");
250 widgets ~= w;
251 w.parent = this;
252 return w;
255 void defocused () {
256 if (mActive) {
257 activeWidget = null;
258 mActive = false;
259 glconPostScreenRepaint();
263 void focused () {
264 if (!active) {
265 mActive = true;
266 glconPostScreenRepaint();
270 final void focusChanged (bool focused) { if (focused) this.focused(); else this.defocused(); }
272 final @property bool active () const pure nothrow @safe @nogc { pragma(inline, true); return mActive; }
273 final @property void active (bool v) { if (mActive == v) return; if (v) focused(); else defocused(); }
275 final @property EWidget activeWidget () pure nothrow @safe @nogc { pragma(inline, true); return (mActiveWidget >= 0 && mActiveWidget < widgets.length ? widgets[mActiveWidget] : null); }
277 final @property void activeWidget (EWidget w) {
278 EWidget oaw = (mActiveWidget >= 0 && mActiveWidget < widgets.length ? widgets[mActiveWidget] : null);
279 if (w is null || w.parent !is this) {
280 mActiveWidget = -1;
281 if (oaw !is null) oaw.onDeactivate();
282 return;
284 foreach (immutable idx, EWidget ww; widgets) {
285 if (ww is w) {
286 if (mActiveWidget == idx) return;
287 mActiveWidget = cast(int)idx;
288 if (oaw !is null) oaw.onDeactivate();
289 ww.onActivate();
290 return;
293 mActiveWidget = -1;
294 if (oaw !is null) oaw.onDeactivate();
297 final EWidget widgetAt (int x, int y) pure nothrow @safe @nogc {
298 foreach_reverse (EWidget w; widgets) {
299 if (w.rc.inside(x, y)) return w;
301 return null;
304 protected void paintWidgets () {
305 foreach (EWidget w; widgets) w.onPaint();
308 protected void paintBackground () {}
309 protected void paintFinished () {}
311 void onPaint () {
312 paintBackground();
313 paintWidgets();
314 paintFinished();
317 bool onKeyPre (KeyEvent event) { return false; }
318 bool onKeyPost (KeyEvent event) { return false; }
320 bool onKey (KeyEvent event) {
321 if (onKeyPre(event)) return true;
322 if (auto aw = activeWidget) {
323 if (aw.onKey(event)) return true;
325 if (onKeyPost(event)) return true;
326 return false;
329 bool onCharPre (dchar ch) { return false; }
330 bool onCharPost (dchar ch) { return false; }
332 bool onChar (dchar ch) {
333 if (onCharPre(ch)) return true;
334 if (auto aw = activeWidget) {
335 if (aw.onChar(ch)) return true;
337 if (onCharPost(ch)) return true;
338 return false;
341 bool onMousePre (MouseEvent event) { return false; }
342 bool onMousePost (MouseEvent event) { return false; }
344 bool onMouse (MouseEvent event) {
345 bool wasAwOrAct = false;
346 if (onMousePre(event)) return true;
347 auto aw = activeWidget;
348 if (aw !is null) {
349 if (aw.onMouse(event)) return true;
350 wasAwOrAct = true;
352 if (auto ww = widgetAt(event.x, event.y)) {
353 if (ww !is aw) {
354 if (ww.onMouse(event)) return true;
355 wasAwOrAct = true;
358 if (onMousePost(event)) return true;
359 if (wasAwOrAct && event.type != MouseEventType.motion) return true;
360 return false;
363 final void drawFillRect (int x0, int y0, int wdt, int hgt, uint clr) { drawFillRect(GxRect(x0, y0, wdt, hgt), clr); }
365 final void drawFillRect() (in auto ref GxRect rc, uint clr) {
366 if (rc.empty) return;
367 XSetForeground(swin.impl.display, swin.impl.gc, clr);
368 XFillRectangle(swin.impl.display, cast(Drawable)swin.impl.buffer, swin.impl.gc, rc.x0, rc.y0, rc.width+1, rc.height+1);
371 final void setClipRect (int x0, int y0, int wdt, int hgt) {
372 if (wdt < 0) wdt = 0; else ++wdt;
373 if (hgt < 0) hgt = 0; else ++hgt;
374 auto cr = XRectangle(cast(short)x0, cast(short)y0, cast(short)wdt, cast(short)hgt);
375 XSetClipRectangles(swin.impl.display, swin.impl.gc, 0, 0, &cr, 1, 3/*YXBanded*/);
378 final void setClipRect() (in auto ref GxRect rc) { setClipRect(rc.x0, rc.y0, rc.width, rc.height); }
380 final void resetClip () { XSetClipMask(swin.impl.display, swin.impl.gc, 0/*None*/); }
384 // ////////////////////////////////////////////////////////////////////////// //
385 public class EWidget {
386 EWindow parent;
387 GxRect rc;
389 this (GxRect arc) {
390 rc = arc;
393 final @property SimpleWindow swin () { pragma(inline, true); return parent.swin; }
395 final @property bool active () nothrow @trusted @nogc { pragma(inline, true); return (parent !is null && parent.activeWidget is this); }
396 final @property void active (bool v) { pragma(inline, true); if (parent !is null) parent.activeWidget = (v ? this : null); }
398 void onPaint () {}
400 void onActivate () {} // parent.activeWidget is this
401 void onDeactivate () {} // parent.activeWidget already changed
403 bool onKey (KeyEvent event) { return false; }
404 bool onChar (dchar ch) { return false; }
405 bool onMouse (MouseEvent event) { return false; }
407 final void drawFillRect (int x0, int y0, int wdt, int hgt, uint clr) { parent.drawFillRect(x0, y0, wdt, hgt, clr); }
408 final void drawFillRect() (in auto ref GxRect rc, uint clr) { parent.drawFillRect(rc, clr); }
409 final void setClipRect (int x0, int y0, int wdt, int hgt) { parent.setClipRect(x0, y0, wdt, hgt); }
410 final void setClipRect() (in auto ref GxRect rc) { parent.setClipRect(rc); }
411 final void resetClip () { parent.resetClip(); }
415 // ////////////////////////////////////////////////////////////////////////// //
417 shared static this () {
418 import core.stdc.stdlib : malloc;
419 vglTexBuf = cast(uint*)malloc((VBufWidth*VBufHeight+4)*4);
420 if (vglTexBuf is null) assert(0, "out of memory!");
421 vglTexBuf[0..VBufWidth*VBufHeight] = 0;
426 // ////////////////////////////////////////////////////////////////////////// //
427 __gshared EgfxWindow mainwin;
429 public void egfxSetMainWindow (EgfxWindow win) {
431 if (win !is null) {
432 if (mainwin is win) return;
433 static if (is(typeof(&mainwin.closeQuery))) {
434 if (mainwin !is null) mainwin.closeQuery = null;
436 mainwin = win;
437 static if (is(typeof(&mainwin.closeQuery))) {
438 mainwin.closeQuery = delegate () { concmd("quit"); glconPostDoConCommands(); };
440 glconBackBuffer = win.backbuf;
441 } else {
442 glconBackBuffer = null;
448 // ////////////////////////////////////////////////////////////////////////// //
449 public class EgfxWindow : SimpleWindow {
450 EWindow ampw;
452 // minsize will be taken from aampw
453 // if resizestep is zero, size on that dimension is fixed
454 this (EWindow aampw, string winclass, string title, int resizeXStep=0, int resizeYStep=0) {
455 if (aampw is null) assert(0, "wtf?! no EWindow!");
456 if (winclass.length) sdpyWindowClass = winclass;
457 ampw = aampw;
458 ampw.swin = this;
459 int minw = aampw.width;
460 int maxw = aampw.width;
461 int minh = aampw.height;
462 int maxh = aampw.height;
463 setupHandlers();
464 super(minw, minh, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.undecorated, WindowFlags.normal|WindowFlags.dontAutoShow);
465 XSetWindowBackground(impl.display, impl.window, 0);
467 int acount = 0;
468 Atom[16] atoms;
469 atoms[acount++] = GetAtom!("_NET_WM_ACTION_MOVE", true)(impl.display);
470 if (resizeXStep > 0 || resizeYStep > 0) atoms[acount++] = GetAtom!("_NET_WM_ACTION_RESIZE", true)(impl.display);
471 atoms[acount++] = GetAtom!("_NET_WM_ACTION_CLOSE", true)(impl.display);
472 atoms[acount++] = GetAtom!("_NET_WM_ACTION_CHANGE_DESKTOP", true)(impl.display);
473 atoms[acount++] = GetAtom!("_NET_WM_ACTION_ABOVE", true)(impl.display);
474 atoms[acount++] = GetAtom!("_NET_WM_ACTION_BELOW", true)(impl.display);
475 atoms[acount++] = GetAtom!("_NET_WM_ACTION_STICK")(impl.display);
476 XChangeProperty(
477 impl.display,
478 impl.window,
479 GetAtom!("_NET_WM_ALLOWED_ACTIONS", true)(impl.display),
480 XA_ATOM,
481 32 /* bits */,
482 0 /*PropModeReplace*/,
483 atoms.ptr,
484 acount);
487 if (resizeXStep > 0) maxw = 4096;
488 if (resizeYStep > 0) maxh = 4096;
489 setMinSize(minw, minh);
490 setMaxSize(maxw, maxh);
491 if (resizeXStep > 0 || resizeYStep > 0) {
492 if (resizeXStep <= 0) resizeXStep = 1;
493 if (resizeYStep <= 0) resizeYStep = 1;
494 setResizeGranularity(resizeXStep, resizeYStep);
497 //show();
500 void delegate () onDismiss;
501 void delegate () onSetup;
503 override void close () {
504 if (!closed && !hidden && onDismiss !is null) onDismiss();
505 super.close();
508 override void hide () {
509 if (closed || hidden) return;
510 if (onDismiss !is null) onDismiss();
511 super.hide();
514 final void hideInternal () {
515 auto ood = onDismiss;
516 onDismiss = null;
517 scope(exit) onDismiss = ood;
518 hide();
521 override void show () {
522 if (closed || !hidden) return;
523 super.show();
524 if (onSetup !is null) onSetup();
527 void redraw () {
528 if (closed) return;
529 ampw.onPaint();
530 if (mainwin is this) glconDraw();
532 if (backbuf.usingXshm) {
533 XShmPutImage(impl.display, cast(Drawable)impl.window, impl.gc, backbuf.handle, 0, 0, 0, 0, backbuf.width, backbuf.height, false);
534 } else {
535 XPutImage(impl.display, cast(Drawable)impl.window, impl.gc, backbuf.handle, 0, 0, 0, 0, backbuf.width, backbuf.height);
538 XCopyArea(impl.display, cast(Drawable)impl.buffer, cast(Drawable)impl.window, impl.gc, 0, 0, width, height, 0, 0);
541 protected void setupHandlers () {
542 handleKeyEvent = delegate (KeyEvent event) {
543 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
544 if (mainwin is this && glconKeyEvent(event)) { glconPostScreenRepaint(); return; }
545 if (isQuitRequested) { close(); return; }
546 if ((event.modifierState&ModifierState.numLock) == 0) {
547 switch (event.key) {
548 case Key.Pad0: event.key = Key.Insert; break;
549 case Key.Pad1: event.key = Key.End; break;
550 case Key.Pad2: event.key = Key.Down; break;
551 case Key.Pad3: event.key = Key.PageDown; break;
552 case Key.Pad4: event.key = Key.Left; break;
553 //case Key.Pad5: event.key = Key.Insert; break;
554 case Key.Pad6: event.key = Key.Right; break;
555 case Key.Pad7: event.key = Key.Home; break;
556 case Key.Pad8: event.key = Key.Up; break;
557 case Key.Pad9: event.key = Key.PageUp; break;
558 case Key.PadEnter: event.key = Key.Enter; break;
559 case Key.PadDot: event.key = Key.Delete; break;
560 default: break;
562 } else {
563 if (event.key == Key.PadEnter) event.key = Key.Enter;
565 ampw.onKey(event);
566 glconPostScreenRepaint();
569 handleMouseEvent = delegate (MouseEvent event) {
570 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
571 if (isQuitRequested) { close(); return; }
572 ampw.onMouse(event);
573 glconPostScreenRepaint();
576 handleCharEvent = delegate (dchar ch) {
577 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
578 if (mainwin is this && glconCharEvent(ch)) { glconPostScreenRepaint(); return; }
579 if (isQuitRequested) { close(); return; }
580 ampw.onChar(ch);
581 glconPostScreenRepaint();
584 windowResized = delegate (int wdt, int hgt) {
585 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
586 if (isQuitRequested) { close(); return; }
587 if (wdt < 1) wdt = 1;
588 if (hgt < 1) hgt = 1;
589 if (mainwin is this) glconResize(wdt, hgt);
590 ampw.mWidth = wdt;
591 ampw.mHeight = hgt;
592 redraw();
593 //glconPostScreenRepaint();
596 onFocusChange = delegate (bool focused) {
597 ampw.active = focused;
600 handleExpose = delegate (int x, int y, int wdt, int hgt, int eventsLeft) {
601 if (eventsLeft == 0) redraw();
602 return true; // so sdpy will not draw backbuffer