hide old log dividers when adding a new one (i should really remove 'em, and do relay...
[bioacid.git] / popups.d
blobd333aadb4149d6e5cd1e5927180af9e0d975542c
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.sdpyutil;
34 import iv.unarray;
35 import iv.utfutil;
36 import iv.vfs.io;
38 import fonts;
41 // ////////////////////////////////////////////////////////////////////////// //
42 public class PopupCheckerEvent {} ///
43 __gshared PopupCheckerEvent evPopupChecker;
44 shared static this () { evPopupChecker = new PopupCheckerEvent(); }
47 void postPopupCheckerEvent () {
48 if (glconCtlWindow !is null && !glconCtlWindow.eventQueued!PopupCheckerEvent) {
49 glconCtlWindow.postTimeout(evPopupChecker, 500);
54 // ////////////////////////////////////////////////////////////////////////// //
55 class PopupWindow : SimpleWindow {
56 public:
57 enum Width = 300;
58 enum MinHeight = 64;
59 enum MaxHeight = 200;
61 enum Kind {
62 Info,
63 Incoming,
64 Status,
65 Error,
68 public:
69 static GxRect getWorkArea () nothrow @trusted {
70 GxRect rc;
71 try {
72 getWorkAreaRect(rc.x0, rc.y0, rc.width, rc.height);
73 } catch (Exception e) {
74 rc = GxRect(0, 0, 800, 600);
76 return rc;
79 private:
80 NVGContext vg;
81 LayTextClass lay;
82 int x0 = -6000, y0 = -6000;
83 SysTime dieTime;
84 char[128] titlestr;
85 int titlelen;
86 Kind kind;
88 private:
89 void cbOnClosing () {
90 if (lay !is null) delete lay; // this will free malloced memory
91 if (vg !is null) {
92 this.setAsCurrentOpenGlContext();
93 scope (exit) { flushGui(); this.releaseCurrentOpenGlContext(); }
94 vg.kill();
96 popupRemoved(this);
99 void cbWindowResized (int wdt, int hgt) {
100 if (this.closed) return;
101 if (!this.visible) return;
102 this.redrawOpenGlSceneNow();
105 void cbInit () {
106 this.setAsCurrentOpenGlContext(); // make this window active
107 scope(exit) this.releaseCurrentOpenGlContext();
108 this.vsync = false;
109 vg = nvgCreateContext();
110 if (vg is null) assert(0, "cannot initialize NanoVG");
111 //vg.createFont("ui", uiFontNames[0]);
112 vg.fonsContext.addFontsFrom(fstash);
113 //this.redrawOpenGlSceneNow();
116 // titlebar background color
117 NVGColor colorTitleBG () {
118 final switch (kind) {
119 case Kind.Info: return NVGColor("#006");
120 case Kind.Incoming: return NVGColor("#444");
121 case Kind.Status: return NVGColor("#333");
122 case Kind.Error: return NVGColor("#600");
124 assert(0);
127 // titlebar background color
128 NVGColor colorTitleFG () {
129 final switch (kind) {
130 case Kind.Info: return NVGColor("#fff");
131 case Kind.Incoming: return NVGColor("#fff");
132 case Kind.Status: return NVGColor("#aaa");
133 case Kind.Error: return NVGColor("#ff0");
135 assert(0);
138 // background color
139 NVGColor colorBG () {
140 final switch (kind) {
141 case Kind.Info: return NVGColor("#00c");
142 case Kind.Incoming: return NVGColor("#222");
143 case Kind.Status: return NVGColor("#222");
144 case Kind.Error: return NVGColor("#c00");
146 assert(0);
149 // foreground color
150 NVGColor colorFG () {
151 final switch (kind) {
152 case Kind.Info: return NVGColor("#ccc");
153 case Kind.Incoming: return NVGColor("#bbb");
154 case Kind.Status: return NVGColor("#888");
155 case Kind.Error: return NVGColor("#fff");
157 assert(0);
160 void cbOnPaint () {
161 if (this.closed) return;
162 if (vg !is null) {
163 this.setAsCurrentOpenGlContext(); // make this window active
164 scope(exit) this.releaseCurrentOpenGlContext();
166 glClearColor(0, 0, 0, 0);
167 glClear(glNVGClearFlags/*|GL_COLOR_BUFFER_BIT*/);
169 vg.beginFrame(this.width, this.height);
170 scope(exit) vg.endFrame();
171 vg.shapeAntiAlias = false;
173 // title height
174 int th = 0;
175 if (titlelen > 0) {
176 vg.fontFace = "uib";
177 vg.fontSize = 20;
178 th = cast(int)vg.textFontHeight;
181 // draw background and title
183 vg.save();
184 vg.newPath();
185 vg.roundedRect(0.5, 0.5, vg.width-1, vg.height-1, 6);
186 // draw titlebar
187 if (th > 0) {
188 vg.save();
189 vg.intersectScissor(0, 0, vg.width, th+2);
190 vg.fillColor(colorTitleBG);
191 vg.fill();
192 vg.restore();
193 vg.intersectScissor(0, th+1.5, vg.width, vg.height); // .5: slightly visible transition line
195 vg.fillColor(colorBG);
196 vg.fill();
197 vg.restore();
198 vg.strokeWidth = 1;
199 vg.strokeColor(NVGColor.white);
200 vg.stroke();
203 int hgt = vg.height-3*2;
205 // draw title text
206 if (th > 0) {
207 vg.intersectScissor(3, 3, vg.width-3*2, hgt);
208 vg.textAlign = NVGTextAlign.H.Center;
209 vg.textAlign = NVGTextAlign.V.Baseline;
210 vg.fillColor(colorTitleFG);
211 vg.text(vg.width/2, 3+cast(int)vg.textFontAscender, titlestr[0..titlelen]);
212 hgt -= th;
213 vg.intersectScissor(3, 3+th, vg.width-3*2, hgt);
214 } else {
215 vg.intersectScissor(3, 3, vg.width-3*2, hgt);
218 int y0 = th+(lay.textHeight < hgt ? (hgt-lay.textHeight)/2 : 0);
219 vg.drawLayouter(lay, 0, 3, y0, hgt);
221 //flushGui();
224 protected:
225 void cbOnMouse (MouseEvent event) {
226 if (this.closed) return;
227 if (event == "LMB-DOWN") { this.close(); return; }
230 public:
231 this(T) (Kind akind, const(char)[] atitle, T[] atext) if (isAnyCharType!(T, true)) {
232 import std.algorithm : min, max;
234 kind = akind;
235 if (atitle.length > titlestr.length) atitle = atitle[0..titlestr.length];
236 titlelen = cast(int)atitle.length;
237 if (titlelen) titlestr[0..titlelen] = atitle[0..titlelen];
240 import std.functional : toDelegate;
242 auto oldFixFontDG = laf.fixFontDG;
243 scope(exit) laf.fixFontDG = oldFixFontDG;
244 laf.fixFontDG = toDelegate(&fixFontForStyleUI);
246 lay = new LayTextClass(laf, Width-6);
248 lay.fontStyle.fontsize = 20;
249 lay.fontStyle.color = NVGColor("#aaa").asUint;
250 lay.fontStyle.bgcolor = NVGColor("#222").asUint;
251 lay.fontStyle.bold = true;
252 //lay.lineStyle.setCenter;
253 lay.putExpander();
254 lay.put("popup window");
255 lay.putExpander();
256 lay.endPara();
258 lay.fontStyle.color = colorFG.asUint;
259 lay.fontStyle.bgcolor = NVGColor.transparent.asUint;
260 lay.fontStyle.fontsize = 18;
261 lay.fontStyle.bold = false;
262 lay.lineStyle.setCenter;
263 lay.put(atext);
264 lay.finalize();
266 //conwriteln("wdt=", lay.width, "; text width=", lay.textWidth);
268 int wdt = Width;
269 int hgt = min(max(MinHeight, lay.textHeight), MaxHeight);
271 sdpyWindowClass = "BIOACID_POPUP";
272 super(wdt, hgt, "PopupWindow", OpenGlOptions.yes, Resizability.fixedSize, WindowTypes.undecorated, WindowFlags.skipTaskbar|WindowFlags.alwaysOnTop|WindowFlags.cannotBeActivated);
273 //XSetWindowBackground(impl.display, impl.window, gxRGB!(0, 0, 0));
275 this.onClosing = &cbOnClosing;
276 this.windowResized = &cbWindowResized;
277 this.visibleForTheFirstTime = &cbInit;
278 this.redrawOpenGlScene = &cbOnPaint;
279 this.handleMouseEvent = &cbOnMouse;
281 // sorry for this hack
282 setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_DOCK", true)(impl.display));
283 //setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_TOOLTIP", true)(impl.display));
285 Atom[4] atoms;
286 atoms[0] = GetAtom!("_NET_WM_STATE_STICKY", true)(impl.display);
287 atoms[1] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(impl.display);
288 atoms[2] = GetAtom!("_NET_WM_STATE_SKIP_PAGER", true)(impl.display);
289 atoms[3] = GetAtom!("_NET_WM_STATE_ABOVE", true)(impl.display);
290 XChangeProperty(
291 impl.display,
292 impl.window,
293 GetAtom!("_NET_WM_STATE", true)(impl.display),
294 XA_ATOM,
295 32 /* bits */,
296 0 /*PropModeReplace*/,
297 atoms.ptr,
298 cast(int)atoms.length);
300 if (hidden) show();
301 //auto wrc = getWorkArea();
302 //this.moveResize(wrc.x0+x, wrc.y0+y, wdt, hgt);
303 this.moveResize(-6000, -6000, wdt, hgt);
305 import core.time;
306 dieTime = Clock.currTime+5.seconds;
307 //flushGui();
312 // ////////////////////////////////////////////////////////////////////////// //
313 __gshared PopupWindow[] popups;
316 void popupKillAll () {
317 if (popups.length) {
318 auto pwlist = popups;
319 scope(exit) delete pwlist;
320 popups = null;
321 foreach (ref PopupWindow pw; pwlist) if (pw !is null) { pw.close(); pw = null; }
322 flushGui();
327 void popupArrange () {
328 import std.algorithm : min, max;
329 auto wrc = PopupWindow.getWorkArea();
330 int px0 = 0, py0 = wrc.y1;
331 int rowWdt = 0;
332 foreach (immutable pwidx, PopupWindow pw; popups) {
333 if (pw is null || pw.closed || pw.hidden) continue;
334 // new row?
335 if (rowWdt > 0 && py0-pw.height < wrc.y0) {
336 py0 = wrc.y1;
337 px0 += rowWdt;
338 if (px0 >= wrc.x1) break;
340 rowWdt = max(rowWdt, pw.width);
341 int wx = px0;
342 int wy = py0-pw.height;
343 if (wx != pw.x0 || wy != pw.y0) {
344 /*if (pw.x0 < 0)*/ pw.move(wx, wy);
345 pw.x0 = wx;
346 pw.y0 = wy;
347 } else {
348 //conwriteln("idx=", pwidx, ": SAME!");
350 py0 -= pw.height;
352 flushGui();
356 void popupRemoved (PopupWindow win) {
357 if (win is null) return;
358 foreach (immutable idx, PopupWindow pw; popups) {
359 if (pw is win) {
360 foreach (immutable c; idx+1..popups.length) popups[c-1] = popups[c];
361 popups.length -= 1;
362 popups.assumeSafeAppend;
363 popupArrange();
364 return;
370 void popupCheckExpirations () {
371 if (popups.length == 0) return;
372 auto tm = Clock.currTime;
373 bool smthChanged = false;
374 usize idx = 0;
375 while (idx < popups.length) {
376 PopupWindow pw = popups[idx];
377 if (pw !is null && !pw.closed && tm >= pw.dieTime) {
378 //conwriteln("tm=", tm, "; dt=", pw.dieTime, "; ", pw.dieTime >= tm);
379 // first, remove from list, so `popupRemoved()` will turn to noop
380 foreach (immutable c; idx+1..popups.length) popups[c-1] = popups[c];
381 popups.length -= 1;
382 popups.assumeSafeAppend;
383 // now close
384 pw.close();
385 smthChanged = true;
386 } else {
387 ++idx;
390 if (smthChanged) popupArrange();
391 if (popups.length) postPopupCheckerEvent();
395 void showPopup(T) (PopupWindow.Kind akind, const(char)[] atitle, T[] atext) if (isAnyCharType!(T, true)) {
396 while (atext.length > 0 && atext[0] <= ' ') atext = atext[1..$];
397 while (atext.length > 0 && atext[$-1] <= ' ') atext = atext[0..$-1];
398 if (atext.length == 0) return;
399 popups ~= new PopupWindow(akind, atitle, atext);
400 popupArrange();
401 postPopupCheckerEvent();