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/>.
19 import arsd
.simpledisplay
;
25 import egfx
.base
: GxRect
, c2img
;
30 // ////////////////////////////////////////////////////////////////////////// //
31 public struct XlibTCImage
{
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
) {
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
;
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
80 // note: this calls free() for us
81 if (handle
!is null) {
82 XDestroyImage(handle
);
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
);
99 final void blitAtWin (SimpleWindow w
, int destx
, int desty
) {
100 blitRectWin(w
, destx
, desty
, GxRect(0, 0, width
, height
));
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
{
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);
127 this (ref XlibTCImage xtc
) {
128 if (!xtc
.valid
) throw new Exception("can't create pixmap from empty xlib image");
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
);
142 @disable this (this);
145 if (glconCtlWindow
is null || glconCtlWindow
.closed
) { xpm
= 0; return; }
147 XFreePixmap(glconCtlWindow
.impl
.display
, xpm
);
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);
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
);
213 gxDeletePixmap
= delegate (Pixmap px
) {
214 if (glconCtlWindow
is null || glconCtlWindow
.closed
) return;
215 XFreePixmap(glconCtlWindow
.impl
.display
, px
);
220 // ////////////////////////////////////////////////////////////////////////// //
221 public class EWindow
{
227 int mActiveWidget
= -1;
230 this (int awidth
, int aheight
) {
231 if (awidth
< 1) awidth
= 1;
232 if (aheight
< 1) aheight
= 1;
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;
247 EWidget
addWidget (EWidget w
) {
248 if (w
is null) return null;
249 if (w
.parent
!is null) throw new Exception("widget already owned");
259 glconPostScreenRepaint();
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) {
281 if (oaw
!is null) oaw
.onDeactivate();
284 foreach (immutable idx
, EWidget ww
; widgets
) {
286 if (mActiveWidget
== idx
) return;
287 mActiveWidget
= cast(int)idx
;
288 if (oaw
!is null) oaw
.onDeactivate();
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
;
304 protected void paintWidgets () {
305 foreach (EWidget w
; widgets
) w
.onPaint();
308 protected void paintBackground () {}
309 protected void 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;
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;
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
;
349 if (aw
.onMouse(event
)) return true;
352 if (auto ww
= widgetAt(event
.x
, event
.y
)) {
354 if (ww
.onMouse(event
)) return true;
358 if (onMousePost(event
)) return true;
359 if (wasAwOrAct
&& event
.type
!= MouseEventType
.motion
) return true;
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
{
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); }
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
) {
432 if (mainwin is win) return;
433 static if (is(typeof(&mainwin.closeQuery))) {
434 if (mainwin !is null) mainwin.closeQuery = null;
437 static if (is(typeof(&mainwin.closeQuery))) {
438 mainwin.closeQuery = delegate () { concmd("quit"); glconPostDoConCommands(); };
440 glconBackBuffer = win.backbuf;
442 glconBackBuffer = null;
448 // ////////////////////////////////////////////////////////////////////////// //
449 public class EgfxWindow
: SimpleWindow
{
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
;
459 int minw
= aampw
.width
;
460 int maxw
= aampw
.width
;
461 int minh
= aampw
.height
;
462 int maxh
= aampw
.height
;
464 super(minw
, minh
, title
, OpenGlOptions
.no
, Resizability
.allowResizing
, WindowTypes
.undecorated
, WindowFlags
.normal|WindowFlags
.dontAutoShow
);
465 XSetWindowBackground(impl
.display
, impl
.window
, 0);
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
);
479 GetAtom
!("_NET_WM_ALLOWED_ACTIONS", true)(impl
.display
),
482 0 /*PropModeReplace*/,
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
);
500 void delegate () onDismiss
;
501 void delegate () onSetup
;
503 override void close () {
504 if (!closed
&& !hidden
&& onDismiss
!is null) onDismiss();
508 override void hide () {
509 if (closed || hidden
) return;
510 if (onDismiss
!is null) onDismiss();
514 final void hideInternal () {
515 auto ood
= onDismiss
;
517 scope(exit
) onDismiss
= ood
;
521 override void show () {
522 if (closed ||
!hidden
) return;
524 if (onSetup
!is null) onSetup();
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);
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) {
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;
563 if (event
.key
== Key
.PadEnter
) event
.key
= Key
.Enter
;
566 glconPostScreenRepaint();
569 handleMouseEvent
= delegate (MouseEvent event
) {
570 scope(exit
) if (!conQueueEmpty()) glconPostDoConCommands();
571 if (isQuitRequested
) { close(); return; }
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; }
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
);
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