cosmetic fix
[amper.git] / egfx / backx.d
blobfc2889287f4d9f66f63b5522e910d258ba5aa111
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, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module egfx.backx;
18 private:
20 import arsd.simpledisplay;
22 import iv.cmdcon;
23 import iv.cmdcongl;
24 import iv.sdpyutil;
26 import egfx.base : GxRect, c2img;
27 import egfx.text;
28 import egfx.util;
31 // ////////////////////////////////////////////////////////////////////////// //
32 public struct XlibTCImage {
33 XImage* handle;
35 @disable this (this);
37 this (MemoryImage img) {
38 if (img is null || img.width < 1 || img.height < 1) throw new Exception("can't create xlib image from empty MemoryImage");
39 create(img.width, img.height, img);
42 this (int wdt, int hgt) {
43 if (wdt < 1 || hgt < 1) throw new Exception("invalid xlib image");
44 create(wdt, hgt, null);
47 ~this () { dispose(); }
49 @property bool valid () const pure nothrow @trusted @nogc { pragma(inline, true); return (handle !is null); }
51 @property int width () const pure nothrow @trusted @nogc { pragma(inline, true); return (handle !is null ? handle.width : 0); }
52 @property int height () const pure nothrow @trusted @nogc { pragma(inline, true); return (handle !is null ? handle.height : 0); }
54 void setup (MemoryImage aimg) {
55 dispose();
56 if (aimg is null || aimg.width < 1 || aimg.height < 1) throw new Exception("can't create xlib image from empty MemoryImage");
57 create(aimg.width, aimg.height, aimg);
60 private void create (int width, int height, MemoryImage ximg) {
61 import core.stdc.stdlib : malloc, free;
62 if (glconCtlWindow is null || glconCtlWindow.closed) assert(0, "wtf?!");
63 auto dpy = glconCtlWindow.impl.display;
64 assert(dpy !is null);
65 auto screen = DefaultScreen(dpy);
66 // this actually needs to be malloc to avoid a double free error when XDestroyImage is called
67 auto rawData = cast(uint*)malloc(width*height*4);
68 scope(failure) free(rawData);
69 if (ximg is null || ximg.width < width || ximg.height < height) rawData[0..width*height] = 0;
70 if (ximg && ximg.width > 0 && ximg.height > 0) {
71 foreach (immutable int y; 0..height) {
72 foreach (immutable int x; 0..width) {
73 rawData[y*width+x] = c2img(ximg.getPixel(x, y));
77 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
80 void dispose () {
81 // note: this calls free() for us
82 if (handle !is null) {
83 XDestroyImage(handle);
84 handle = null;
88 // blit to window buffer
89 final void blitAt (SimpleWindow w, int destx, int desty) {
90 blitRect(w, destx, desty, GxRect(0, 0, width, height));
93 // blit to window buffer
94 final void blitRect (SimpleWindow w, int destx, int desty, GxRect srect) {
95 if (w is null || handle is null || w.closed) return;
96 XPutImage(w.impl.display, cast(Drawable)w.impl.buffer, w.impl.gc, handle, srect.x0, srect.y0, destx, desty, srect.width, srect.height);
99 // blit to window
100 final void blitAtWin (SimpleWindow w, int destx, int desty) {
101 blitRectWin(w, destx, desty, GxRect(0, 0, width, height));
104 // blit to window
105 final void blitRectWin (SimpleWindow w, int destx, int desty, GxRect srect) {
106 if (w is null || handle is null || w.closed) return;
107 XPutImage(w.impl.display, cast(Drawable)w.impl.window, w.impl.gc, handle, srect.x0, srect.y0, destx, desty, srect.width, srect.height);
112 // ////////////////////////////////////////////////////////////////////////// //
113 public struct EPixmapImpl {
114 Pixmap xpm;
115 private int mWidth, mHeight;
117 this (int wdt, int hgt) {
118 //if (width < 1 || height < 1) throw new Exception("invalid pixmap dimensions");
119 if (wdt < 1) wdt = 1;
120 if (hgt < 1) hgt = 1;
121 if (wdt > 1024) wdt = 1024;
122 if (hgt > 1024) hgt = 1024;
123 xpm = XCreatePixmap(glconCtlWindow.impl.display, cast(Drawable)glconCtlWindow.impl.window, wdt, hgt, 24);
124 mWidth = wdt;
125 mHeight = hgt;
128 this (ref XlibTCImage xtc) {
129 if (!xtc.valid) throw new Exception("can't create pixmap from empty xlib image");
130 int wdt = xtc.width;
131 int hgt = xtc.height;
132 if (wdt < 1) wdt = 1;
133 if (hgt < 1) hgt = 1;
134 if (wdt > 1024) wdt = 1024;
135 if (hgt > 1024) hgt = 1024;
136 xpm = XCreatePixmap(glconCtlWindow.impl.display, cast(Drawable)glconCtlWindow.impl.window, wdt, hgt, 24);
137 // source x, source y
138 XPutImage(glconCtlWindow.impl.display, cast(Drawable)xpm, glconCtlWindow.impl.gc, xtc.handle, 0, 0, 0, 0, wdt, hgt);
139 mWidth = wdt;
140 mHeight = hgt;
143 @disable this (this);
145 ~this () {
146 if (glconCtlWindow is null || glconCtlWindow.closed) { xpm = 0; return; }
147 if (xpm) {
148 XFreePixmap(glconCtlWindow.impl.display, xpm);
149 xpm = 0;
153 @property bool valid () const pure nothrow @trusted @nogc { pragma(inline, true); return (xpm != 0); }
155 @property int width () const pure nothrow @trusted @nogc { pragma(inline, true); return mWidth; }
156 @property int height () const pure nothrow @trusted @nogc { pragma(inline, true); return mHeight; }
158 // blit to window buffer
159 final void blitAt (SimpleWindow w, int x, int y) {
160 blitRect(w, x, y, GxRect(0, 0, width, height));
163 // blit to window buffer
164 final void blitRect (SimpleWindow w, int destx, int desty, GxRect srect) {
165 if (w is null || !xpm || w.closed) return;
166 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);
171 public alias EPixmap = KRC!EPixmapImpl;
174 // ////////////////////////////////////////////////////////////////////////// //
175 shared static this () {
176 gxCreatePixmap1bpp = delegate (int wdt, int hgt, scope uint delegate (int x, int y) getPixel) {
177 if (glconCtlWindow !is null && !glconCtlWindow.closed) {
178 auto dpy = glconCtlWindow.impl.display;
179 auto drw = cast(Drawable)cast(Drawable)glconCtlWindow.impl.window;
180 Pixmap px = XCreatePixmap(dpy, drw, wdt, hgt, 24);
181 // alas, painter can set clip mask, and we have no way to save and restore it, so...
182 GC gc = XCreateGC(dpy, drw, 0, null);
183 scope(exit) XFreeGC(dpy, gc);
184 XCopyGC(dpy, DefaultGC(dpy, DefaultScreen(dpy)), 0xffffffff, gc);
185 XSetClipMask(dpy, gc, None);
187 foreach (immutable int dy; 0..hgt) {
188 foreach (immutable int dx; 0..wdt) {
189 auto c = (getPixel(dx, dy) ? ~0 : 0);
190 XSetForeground(dpy, gc, c);
191 XDrawPoint(dpy, cast(Drawable)px, gc, dx, dy);
195 import core.stdc.stdlib : malloc;
196 ubyte* rawData = cast(ubyte*)malloc(wdt*hgt*4);
197 rawData[0..wdt*hgt*4] = 0;
198 uint* rd = cast(uint*)rawData;
199 foreach (immutable int y; 0..hgt) {
200 foreach (immutable int x; 0..wdt) {
201 auto c = (getPixel(x, y) ? ~0 : 0);
202 rd[y*wdt+x] = c;
205 import arsd.simpledisplay : ImageFormat;
206 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
207 scope(exit) XDestroyImage(handle);
208 XPutImage(dpy, cast(Drawable)px, gc, handle, 0, 0, 0, 0, wdt, hgt);
209 return px;
210 } else {
211 return 0;
214 gxDeletePixmap = delegate (Pixmap px) {
215 if (glconCtlWindow is null || glconCtlWindow.closed) return;
216 XFreePixmap(glconCtlWindow.impl.display, px);
221 // ////////////////////////////////////////////////////////////////////////// //
222 public class EWindow {
223 protected:
224 SimpleWindow swin;
225 int mWidth, mHeight;
226 bool mActive;
227 EWidget[] widgets;
228 int mActiveWidget = -1;
230 public:
231 this (int awidth, int aheight) {
232 if (awidth < 1) awidth = 1;
233 if (aheight < 1) aheight = 1;
234 mWidth = awidth;
235 mHeight = aheight;
238 final @property int width () const pure nothrow @safe @nogc { pragma(inline, true); return mWidth; }
239 final @property int height () const pure nothrow @safe @nogc { pragma(inline, true); return mHeight; }
241 void setSize (int awdt, int ahgt) {
242 if (awdt < 1) awdt = 1;
243 if (ahgt < 1) ahgt = 1;
244 mWidth = awdt;
245 mHeight = ahgt;
248 EWidget addWidget (EWidget w) {
249 if (w is null) return null;
250 if (w.parent !is null) throw new Exception("widget already owned");
251 widgets ~= w;
252 w.parent = this;
253 return w;
256 void defocused () {
257 if (mActive) {
258 activeWidget = null;
259 mActive = false;
260 glconPostScreenRepaint();
264 void focused () {
265 if (!active) {
266 mActive = true;
267 glconPostScreenRepaint();
271 final void focusChanged (bool focused) { if (focused) this.focused(); else this.defocused(); }
273 final @property bool active () const pure nothrow @safe @nogc { pragma(inline, true); return mActive; }
274 final @property void active (bool v) { if (mActive == v) return; if (v) focused(); else defocused(); }
276 final @property EWidget activeWidget () pure nothrow @safe @nogc { pragma(inline, true); return (mActiveWidget >= 0 && mActiveWidget < widgets.length ? widgets[mActiveWidget] : null); }
278 final @property void activeWidget (EWidget w) {
279 EWidget oaw = (mActiveWidget >= 0 && mActiveWidget < widgets.length ? widgets[mActiveWidget] : null);
280 if (w is null || w.parent !is this) {
281 mActiveWidget = -1;
282 if (oaw !is null) oaw.onDeactivate();
283 return;
285 foreach (immutable idx, EWidget ww; widgets) {
286 if (ww is w) {
287 if (mActiveWidget == idx) return;
288 mActiveWidget = cast(int)idx;
289 if (oaw !is null) oaw.onDeactivate();
290 ww.onActivate();
291 return;
294 mActiveWidget = -1;
295 if (oaw !is null) oaw.onDeactivate();
298 final EWidget widgetAt (int x, int y) pure nothrow @safe @nogc {
299 foreach_reverse (EWidget w; widgets) {
300 if (w.rc.inside(x, y)) return w;
302 return null;
305 protected void paintWidgets () {
306 foreach (EWidget w; widgets) w.onPaint();
309 protected void paintBackground () {}
310 protected void paintFinished () {}
312 void onPaint () {
313 paintBackground();
314 paintWidgets();
315 paintFinished();
318 bool onKeyPre (KeyEvent event) { return false; }
319 bool onKeyPost (KeyEvent event) { return false; }
321 bool onKey (KeyEvent event) {
322 if (onKeyPre(event)) return true;
323 if (auto aw = activeWidget) {
324 if (aw.onKey(event)) return true;
326 if (onKeyPost(event)) return true;
327 return false;
330 bool onCharPre (dchar ch) { return false; }
331 bool onCharPost (dchar ch) { return false; }
333 bool onChar (dchar ch) {
334 if (onCharPre(ch)) return true;
335 if (auto aw = activeWidget) {
336 if (aw.onChar(ch)) return true;
338 if (onCharPost(ch)) return true;
339 return false;
342 bool onMousePre (MouseEvent event) { return false; }
343 bool onMousePost (MouseEvent event) { return false; }
345 bool onMouse (MouseEvent event) {
346 bool wasAwOrAct = false;
347 if (onMousePre(event)) return true;
348 auto aw = activeWidget;
349 if (aw !is null) {
350 if (aw.onMouse(event)) return true;
351 wasAwOrAct = true;
353 if (auto ww = widgetAt(event.x, event.y)) {
354 if (ww !is aw) {
355 if (ww.onMouse(event)) return true;
356 wasAwOrAct = true;
359 if (onMousePost(event)) return true;
360 if (wasAwOrAct && event.type != MouseEventType.motion) return true;
361 return false;
364 final void drawFillRect (int x0, int y0, int wdt, int hgt, uint clr) { drawFillRect(GxRect(x0, y0, wdt, hgt), clr); }
366 final void drawFillRect() (in auto ref GxRect rc, uint clr) {
367 if (rc.empty) return;
368 XSetForeground(swin.impl.display, swin.impl.gc, clr);
369 XFillRectangle(swin.impl.display, cast(Drawable)swin.impl.buffer, swin.impl.gc, rc.x0, rc.y0, rc.width+1, rc.height+1);
372 final void setClipRect (int x0, int y0, int wdt, int hgt) {
373 if (wdt < 0) wdt = 0; else ++wdt;
374 if (hgt < 0) hgt = 0; else ++hgt;
375 auto cr = XRectangle(cast(short)x0, cast(short)y0, cast(short)wdt, cast(short)hgt);
376 XSetClipRectangles(swin.impl.display, swin.impl.gc, 0, 0, &cr, 1, 3/*YXBanded*/);
379 final void setClipRect() (in auto ref GxRect rc) { setClipRect(rc.x0, rc.y0, rc.width, rc.height); }
381 final void resetClip () { XSetClipMask(swin.impl.display, swin.impl.gc, 0/*None*/); }
385 // ////////////////////////////////////////////////////////////////////////// //
386 public class EWidget {
387 EWindow parent;
388 GxRect rc;
390 this (GxRect arc) {
391 rc = arc;
394 final @property SimpleWindow swin () { pragma(inline, true); return parent.swin; }
396 final @property bool active () nothrow @trusted @nogc { pragma(inline, true); return (parent !is null && parent.activeWidget is this); }
397 final @property void active (bool v) { pragma(inline, true); if (parent !is null) parent.activeWidget = (v ? this : null); }
399 void onPaint () {}
401 void onActivate () {} // parent.activeWidget is this
402 void onDeactivate () {} // parent.activeWidget already changed
404 bool onKey (KeyEvent event) { return false; }
405 bool onChar (dchar ch) { return false; }
406 bool onMouse (MouseEvent event) { return false; }
408 final void drawFillRect (int x0, int y0, int wdt, int hgt, uint clr) { parent.drawFillRect(x0, y0, wdt, hgt, clr); }
409 final void drawFillRect() (in auto ref GxRect rc, uint clr) { parent.drawFillRect(rc, clr); }
410 final void setClipRect (int x0, int y0, int wdt, int hgt) { parent.setClipRect(x0, y0, wdt, hgt); }
411 final void setClipRect() (in auto ref GxRect rc) { parent.setClipRect(rc); }
412 final void resetClip () { parent.resetClip(); }
416 // ////////////////////////////////////////////////////////////////////////// //
418 shared static this () {
419 import core.stdc.stdlib : malloc;
420 vglTexBuf = cast(uint*)malloc((VBufWidth*VBufHeight+4)*4);
421 if (vglTexBuf is null) assert(0, "out of memory!");
422 vglTexBuf[0..VBufWidth*VBufHeight] = 0;
427 // ////////////////////////////////////////////////////////////////////////// //
428 __gshared EgfxWindow mainwin;
430 public void egfxSetMainWindow (EgfxWindow win) {
432 if (win !is null) {
433 if (mainwin is win) return;
434 static if (is(typeof(&mainwin.closeQuery))) {
435 if (mainwin !is null) mainwin.closeQuery = null;
437 mainwin = win;
438 static if (is(typeof(&mainwin.closeQuery))) {
439 mainwin.closeQuery = delegate () { concmd("quit"); glconPostDoConCommands(); };
441 glconBackBuffer = win.backbuf;
442 } else {
443 glconBackBuffer = null;
449 // ////////////////////////////////////////////////////////////////////////// //
450 public class EgfxWindow : SimpleWindow {
451 EWindow ampw;
453 // minsize will be taken from aampw
454 // if resizestep is zero, size on that dimension is fixed
455 this (EWindow aampw, string winclass, string title, int resizeXStep=0, int resizeYStep=0) {
456 if (aampw is null) assert(0, "wtf?! no EWindow!");
457 if (winclass.length) sdpyWindowClass = winclass;
458 ampw = aampw;
459 ampw.swin = this;
460 int minw = aampw.width;
461 int maxw = aampw.width;
462 int minh = aampw.height;
463 int maxh = aampw.height;
464 setupHandlers();
465 super(minw, minh, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.undecorated, WindowFlags.normal|WindowFlags.dontAutoShow);
466 XSetWindowBackground(impl.display, impl.window, 0);
468 int acount = 0;
469 Atom[16] atoms;
470 atoms[acount++] = GetAtom!("_NET_WM_ACTION_MOVE", true)(impl.display);
471 if (resizeXStep > 0 || resizeYStep > 0) atoms[acount++] = GetAtom!("_NET_WM_ACTION_RESIZE", true)(impl.display);
472 atoms[acount++] = GetAtom!("_NET_WM_ACTION_CLOSE", true)(impl.display);
473 atoms[acount++] = GetAtom!("_NET_WM_ACTION_CHANGE_DESKTOP", true)(impl.display);
474 atoms[acount++] = GetAtom!("_NET_WM_ACTION_ABOVE", true)(impl.display);
475 atoms[acount++] = GetAtom!("_NET_WM_ACTION_BELOW", true)(impl.display);
476 atoms[acount++] = GetAtom!("_NET_WM_ACTION_STICK")(impl.display);
477 XChangeProperty(
478 impl.display,
479 impl.window,
480 GetAtom!("_NET_WM_ALLOWED_ACTIONS", true)(impl.display),
481 XA_ATOM,
482 32 /* bits */,
483 0 /*PropModeReplace*/,
484 atoms.ptr,
485 acount);
488 if (resizeXStep > 0) maxw = 4096;
489 if (resizeYStep > 0) maxh = 4096;
490 setMinSize(minw, minh);
491 setMaxSize(maxw, maxh);
492 if (resizeXStep > 0 || resizeYStep > 0) {
493 if (resizeXStep <= 0) resizeXStep = 1;
494 if (resizeYStep <= 0) resizeYStep = 1;
495 setResizeGranularity(resizeXStep, resizeYStep);
498 //show();
501 void delegate () onDismiss;
502 void delegate () onSetup;
504 override void close () {
505 if (!closed && !hidden && onDismiss !is null) onDismiss();
506 super.close();
509 override void hide () {
510 if (closed || hidden) return;
511 if (onDismiss !is null) onDismiss();
512 super.hide();
515 final void hideInternal () {
516 auto ood = onDismiss;
517 onDismiss = null;
518 scope(exit) onDismiss = ood;
519 hide();
522 override void show () {
523 if (closed || !hidden) return;
524 super.show();
525 if (onSetup !is null) onSetup();
528 void redraw () {
529 if (closed) return;
530 ampw.onPaint();
531 if (mainwin is this) glconDraw();
533 if (backbuf.usingXshm) {
534 XShmPutImage(impl.display, cast(Drawable)impl.window, impl.gc, backbuf.handle, 0, 0, 0, 0, backbuf.width, backbuf.height, false);
535 } else {
536 XPutImage(impl.display, cast(Drawable)impl.window, impl.gc, backbuf.handle, 0, 0, 0, 0, backbuf.width, backbuf.height);
539 XCopyArea(impl.display, cast(Drawable)impl.buffer, cast(Drawable)impl.window, impl.gc, 0, 0, width, height, 0, 0);
542 protected void setupHandlers () {
543 handleKeyEvent = delegate (KeyEvent event) {
544 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
545 if (mainwin is this && glconKeyEvent(event)) { glconPostScreenRepaint(); return; }
546 if (isQuitRequested) { close(); return; }
547 if ((event.modifierState&ModifierState.numLock) == 0) {
548 switch (event.key) {
549 case Key.Pad0: event.key = Key.Insert; break;
550 case Key.Pad1: event.key = Key.End; break;
551 case Key.Pad2: event.key = Key.Down; break;
552 case Key.Pad3: event.key = Key.PageDown; break;
553 case Key.Pad4: event.key = Key.Left; break;
554 //case Key.Pad5: event.key = Key.Insert; break;
555 case Key.Pad6: event.key = Key.Right; break;
556 case Key.Pad7: event.key = Key.Home; break;
557 case Key.Pad8: event.key = Key.Up; break;
558 case Key.Pad9: event.key = Key.PageUp; break;
559 case Key.PadEnter: event.key = Key.Enter; break;
560 case Key.PadDot: event.key = Key.Delete; break;
561 default: break;
563 } else {
564 if (event.key == Key.PadEnter) event.key = Key.Enter;
566 ampw.onKey(event);
567 glconPostScreenRepaint();
570 handleMouseEvent = delegate (MouseEvent event) {
571 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
572 if (isQuitRequested) { close(); return; }
573 ampw.onMouse(event);
574 glconPostScreenRepaint();
577 handleCharEvent = delegate (dchar ch) {
578 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
579 if (mainwin is this && glconCharEvent(ch)) { glconPostScreenRepaint(); return; }
580 if (isQuitRequested) { close(); return; }
581 ampw.onChar(ch);
582 glconPostScreenRepaint();
585 windowResized = delegate (int wdt, int hgt) {
586 scope(exit) if (!conQueueEmpty()) glconPostDoConCommands();
587 if (isQuitRequested) { close(); return; }
588 if (wdt < 1) wdt = 1;
589 if (hgt < 1) hgt = 1;
590 if (mainwin is this) glconResize(wdt, hgt);
591 ampw.mWidth = wdt;
592 ampw.mHeight = hgt;
593 redraw();
594 //glconPostScreenRepaint();
597 onFocusChange = delegate (bool focused) {
598 ampw.active = focused;
601 handleExpose = delegate (int x, int y, int wdt, int hgt, int eventsLeft) {
602 if (eventsLeft == 0) redraw();
603 return true; // so sdpy will not draw backbuffer