fixed segfault when clicking on empty space
[bioacid.git] / popups.d
blobfe7112dc4e027e22ad6572a92f881e6fee1e1125
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 popups is aliced;
19 import std.datetime;
21 import arsd.color;
22 import arsd.image;
23 import arsd.simpledisplay;
25 import iv.cmdcon;
26 import iv.cmdcon.gl;
27 import iv.gxx;
28 import iv.meta;
29 import iv.nanovega;
30 import iv.nanovega.textlayouter;
31 import iv.strex;
32 import iv.tox;
33 import iv.txtser;
34 import iv.sdpyutil;
35 import iv.unarray;
36 import iv.utfutil;
37 import iv.vfs.io;
39 import fonts;
42 // ////////////////////////////////////////////////////////////////////////// //
43 public class PopupCheckerEvent {} ///
44 __gshared PopupCheckerEvent evPopupChecker;
45 shared static this () { evPopupChecker = new PopupCheckerEvent(); }
48 void postPopupCheckerEvent () {
49 if (glconCtlWindow !is null && !glconCtlWindow.eventQueued!PopupCheckerEvent) {
50 glconCtlWindow.postTimeout(evPopupChecker, 500);
55 // ////////////////////////////////////////////////////////////////////////// //
56 class PopupWindow : SimpleWindow {
57 public:
58 enum Width = 300;
59 enum MinHeight = 64;
60 enum MaxHeight = 200;
62 enum Kind {
63 Info,
64 Incoming,
65 Status,
66 Error,
69 public:
70 static GxRect getWorkArea () nothrow @trusted {
71 GxRect rc;
72 try {
73 getWorkAreaRect(rc.x0, rc.y0, rc.width, rc.height);
74 } catch (Exception e) {
75 rc = GxRect(0, 0, 800, 600);
77 return rc;
80 private:
81 NVGContext vg;
82 LayTextClass lay;
83 int x0 = -6000, y0 = -6000;
84 SysTime dieTime;
85 char[128] titlestr;
86 int titlelen;
87 Kind kind;
89 private:
90 void cbOnClosing () {
91 if (lay !is null) delete lay; // this will free malloced memory
92 if (vg !is null) {
93 this.setAsCurrentOpenGlContext();
94 scope (exit) { flushGui(); this.releaseCurrentOpenGlContext(); }
95 vg.kill();
97 popupRemoved(this);
100 void cbWindowResized (int wdt, int hgt) {
101 if (this.closed) return;
102 if (!this.visible) return;
103 this.redrawOpenGlSceneNow();
106 void cbInit () {
107 this.setAsCurrentOpenGlContext(); // make this window active
108 scope(exit) this.releaseCurrentOpenGlContext();
109 this.vsync = false;
110 vg = nvgCreateContext();
111 if (vg is null) assert(0, "cannot initialize NanoVG");
112 //vg.createFont("ui", uiFontNames[0]);
113 vg.fonsContext.fonsAddStashFonts(fstash);
114 //this.redrawOpenGlSceneNow();
117 // titlebar background color
118 NVGColor colorTitleBG () {
119 final switch (kind) {
120 case Kind.Info: return NVGColor("#006");
121 case Kind.Incoming: return NVGColor("#444");
122 case Kind.Status: return NVGColor("#333");
123 case Kind.Error: return NVGColor("#600");
125 assert(0);
128 // titlebar background color
129 NVGColor colorTitleFG () {
130 final switch (kind) {
131 case Kind.Info: return NVGColor("#fff");
132 case Kind.Incoming: return NVGColor("#fff");
133 case Kind.Status: return NVGColor("#aaa");
134 case Kind.Error: return NVGColor("#ff0");
136 assert(0);
139 // background color
140 NVGColor colorBG () {
141 final switch (kind) {
142 case Kind.Info: return NVGColor("#00c");
143 case Kind.Incoming: return NVGColor("#222");
144 case Kind.Status: return NVGColor("#222");
145 case Kind.Error: return NVGColor("#c00");
147 assert(0);
150 // foreground color
151 NVGColor colorFG () {
152 final switch (kind) {
153 case Kind.Info: return NVGColor("#ccc");
154 case Kind.Incoming: return NVGColor("#bbb");
155 case Kind.Status: return NVGColor("#888");
156 case Kind.Error: return NVGColor("#fff");
158 assert(0);
161 void cbOnPaint () {
162 if (this.closed) return;
163 if (vg !is null) {
164 this.setAsCurrentOpenGlContext(); // make this window active
165 scope(exit) this.releaseCurrentOpenGlContext();
167 glClearColor(0, 0, 0, 0);
168 glClear(glNVGClearFlags/*|GL_COLOR_BUFFER_BIT*/);
170 vg.beginFrame(this.width, this.height);
171 scope(exit) vg.endFrame();
172 vg.shapeAntiAlias = false;
174 // title height
175 int th = 0;
176 if (titlelen > 0) {
177 vg.fontFace = "uib";
178 vg.fontSize = 20;
179 th = cast(int)vg.textFontHeight;
182 // draw background and title
184 vg.save();
185 vg.newPath();
186 vg.roundedRect(0.5, 0.5, vg.width-1, vg.height-1, 6);
187 // draw titlebar
188 if (th > 0) {
189 vg.save();
190 vg.intersectScissor(0, 0, vg.width, th+2);
191 vg.fillColor(colorTitleBG);
192 vg.fill();
193 vg.restore();
194 vg.intersectScissor(0, th+1.5, vg.width, vg.height); // .5: slightly visible transition line
196 vg.fillColor(colorBG);
197 vg.fill();
198 vg.restore();
199 vg.strokeWidth = 1;
200 vg.strokeColor(NVGColor.white);
201 vg.stroke();
204 int hgt = vg.height-3*2;
206 // draw title text
207 if (th > 0) {
208 vg.intersectScissor(3, 3, vg.width-3*2, hgt);
209 vg.textAlign = NVGTextAlign.H.Center;
210 vg.textAlign = NVGTextAlign.V.Baseline;
211 vg.fillColor(colorTitleFG);
212 vg.text(vg.width/2, 3+cast(int)vg.textFontAscender, titlestr[0..titlelen]);
213 hgt -= th;
214 vg.intersectScissor(3, 3+th, vg.width-3*2, hgt);
215 } else {
216 vg.intersectScissor(3, 3, vg.width-3*2, hgt);
219 int y0 = th+(lay.textHeight < hgt ? (hgt-lay.textHeight)/2 : 0);
220 vg.drawLayouter(lay, 0, 3, y0, hgt);
222 //flushGui();
225 protected:
226 void cbOnMouse (MouseEvent event) {
227 if (this.closed) return;
228 if (event == "LMB-DOWN") { this.close(); return; }
231 public:
232 this(T) (Kind akind, const(char)[] atitle, T[] atext) if (isAnyCharType!(T, true)) {
233 import std.algorithm : min, max;
235 kind = akind;
236 if (atitle.length > titlestr.length) atitle = atitle[0..titlestr.length];
237 titlelen = cast(int)atitle.length;
238 if (titlelen) titlestr[0..titlelen] = atitle[0..titlelen];
241 import std.functional : toDelegate;
243 auto oldFixFontDG = laf.fixFontDG;
244 scope(exit) laf.fixFontDG = oldFixFontDG;
245 laf.fixFontDG = toDelegate(&fixFontForStyleUI);
247 lay = new LayTextClass(laf, Width-6);
249 lay.fontStyle.fontsize = 20;
250 lay.fontStyle.color = NVGColor("#aaa").asUint;
251 lay.fontStyle.bgcolor = NVGColor("#222").asUint;
252 lay.fontStyle.bold = true;
253 //lay.lineStyle.setCenter;
254 lay.putExpander();
255 lay.put("popup window");
256 lay.putExpander();
257 lay.endPara();
259 lay.fontStyle.color = colorFG.asUint;
260 lay.fontStyle.bgcolor = NVGColor.transparent.asUint;
261 lay.fontStyle.fontsize = 18;
262 lay.fontStyle.bold = false;
263 lay.lineStyle.setCenter;
264 lay.put(atext);
265 lay.finalize();
267 //conwriteln("wdt=", lay.width, "; text width=", lay.textWidth);
269 int wdt = Width;
270 int hgt = min(max(MinHeight, lay.textHeight), MaxHeight);
272 sdpyWindowClass = "BIOACID_POPUP";
273 super(wdt, hgt, "PopupWindow", OpenGlOptions.yes, Resizability.fixedSize, WindowTypes.undecorated, WindowFlags.skipTaskbar|WindowFlags.alwaysOnTop|WindowFlags.cannotBeActivated);
274 //XSetWindowBackground(impl.display, impl.window, gxRGB!(0, 0, 0));
276 this.onClosing = &cbOnClosing;
277 this.windowResized = &cbWindowResized;
278 this.visibleForTheFirstTime = &cbInit;
279 this.redrawOpenGlScene = &cbOnPaint;
280 this.handleMouseEvent = &cbOnMouse;
282 // sorry for this hack
283 setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_DOCK", true)(impl.display));
284 //setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_TOOLTIP", true)(impl.display));
286 Atom[4] atoms;
287 atoms[0] = GetAtom!("_NET_WM_STATE_STICKY", true)(impl.display);
288 atoms[1] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(impl.display);
289 atoms[2] = GetAtom!("_NET_WM_STATE_SKIP_PAGER", true)(impl.display);
290 atoms[3] = GetAtom!("_NET_WM_STATE_ABOVE", true)(impl.display);
291 XChangeProperty(
292 impl.display,
293 impl.window,
294 GetAtom!("_NET_WM_STATE", true)(impl.display),
295 XA_ATOM,
296 32 /* bits */,
297 0 /*PropModeReplace*/,
298 atoms.ptr,
299 cast(int)atoms.length);
301 if (hidden) show();
302 //auto wrc = getWorkArea();
303 //this.moveResize(wrc.x0+x, wrc.y0+y, wdt, hgt);
304 this.moveResize(-6000, -6000, wdt, hgt);
306 import core.time;
307 dieTime = Clock.currTime+5.seconds;
308 //flushGui();
313 // ////////////////////////////////////////////////////////////////////////// //
314 __gshared PopupWindow[] popups;
317 void popupKillAll () {
318 if (popups.length) {
319 auto pwlist = popups;
320 scope(exit) delete pwlist;
321 popups = null;
322 foreach (ref PopupWindow pw; pwlist) if (pw !is null) { pw.close(); pw = null; }
323 flushGui();
328 void popupArrange () {
329 import std.algorithm : min, max;
330 auto wrc = PopupWindow.getWorkArea();
331 int px0 = 0, py0 = wrc.y1;
332 int rowWdt = 0;
333 foreach (immutable pwidx, PopupWindow pw; popups) {
334 if (pw is null || pw.closed || pw.hidden) continue;
335 // new row?
336 if (rowWdt > 0 && py0-pw.height < wrc.y0) {
337 py0 = wrc.y1;
338 px0 += rowWdt;
339 if (px0 >= wrc.x1) break;
341 rowWdt = max(rowWdt, pw.width);
342 int wx = px0;
343 int wy = py0-pw.height;
344 if (wx != pw.x0 || wy != pw.y0) {
345 /*if (pw.x0 < 0)*/ pw.move(wx, wy);
346 pw.x0 = wx;
347 pw.y0 = wy;
348 } else {
349 //conwriteln("idx=", pwidx, ": SAME!");
351 py0 -= pw.height;
353 flushGui();
357 void popupRemoved (PopupWindow win) {
358 if (win is null) return;
359 foreach (immutable idx, PopupWindow pw; popups) {
360 if (pw is win) {
361 foreach (immutable c; idx+1..popups.length) popups[c-1] = popups[c];
362 popups.length -= 1;
363 popups.assumeSafeAppend;
364 popupArrange();
365 return;
371 void popupCheckExpirations () {
372 if (popups.length == 0) return;
373 auto tm = Clock.currTime;
374 bool smthChanged = false;
375 usize idx = 0;
376 while (idx < popups.length) {
377 PopupWindow pw = popups[idx];
378 if (pw !is null && !pw.closed && tm >= pw.dieTime) {
379 //conwriteln("tm=", tm, "; dt=", pw.dieTime, "; ", pw.dieTime >= tm);
380 // first, remove from list, so `popupRemoved()` will turn to noop
381 foreach (immutable c; idx+1..popups.length) popups[c-1] = popups[c];
382 popups.length -= 1;
383 popups.assumeSafeAppend;
384 // now close
385 pw.close();
386 smthChanged = true;
387 } else {
388 ++idx;
391 if (smthChanged) popupArrange();
392 if (popups.length) postPopupCheckerEvent();
396 void showPopup(T) (PopupWindow.Kind akind, const(char)[] atitle, T[] atext) if (isAnyCharType!(T, true)) {
397 while (atext.length > 0 && atext[0] <= ' ') atext = atext[1..$];
398 while (atext.length > 0 && atext[$-1] <= ' ') atext = atext[0..$-1];
399 if (atext.length == 0) return;
400 popups ~= new PopupWindow(akind, atitle, atext);
401 popupArrange();
402 postPopupCheckerEvent();