some cosmetix (force opengl viewport)
[bioacid.git] / popups.d
blob866dc1dbecbc93db236023c5922db38782d1aff8
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 popups is aliced;
18 import std.datetime;
20 import arsd.color;
21 import arsd.image;
22 import arsd.simpledisplay;
24 import iv.cmdcon;
25 import iv.cmdcon.gl;
26 import iv.gxx;
27 import iv.meta;
28 import iv.nanovega;
29 import iv.nanovega.textlayouter;
30 import iv.strex;
31 import iv.tox;
32 import iv.sdpyutil;
33 import iv.unarray;
34 import iv.utfutil;
35 import iv.vfs.io;
37 import fonts;
40 // ////////////////////////////////////////////////////////////////////////// //
41 public class PopupCheckerEvent {} ///
42 __gshared PopupCheckerEvent evPopupChecker;
43 shared static this () { evPopupChecker = new PopupCheckerEvent(); }
46 void postPopupCheckerEvent () {
47 if (glconCtlWindow !is null && !glconCtlWindow.eventQueued!PopupCheckerEvent) {
48 glconCtlWindow.postTimeout(evPopupChecker, 500);
53 // ////////////////////////////////////////////////////////////////////////// //
54 class PopupWindow : SimpleWindow {
55 public:
56 enum Width = 300;
57 enum MinHeight = 64;
58 enum MaxHeight = 200;
60 enum Kind {
61 Info,
62 Incoming,
63 Status,
64 Error,
67 public:
68 static GxRect getWorkArea () nothrow @trusted {
69 GxRect rc;
70 try {
71 getWorkAreaRect(rc.x0, rc.y0, rc.width, rc.height);
72 } catch (Exception e) {
73 rc = GxRect(0, 0, 800, 600);
75 return rc;
78 private:
79 NVGContext vg;
80 LayTextClass lay;
81 int x0 = -6000, y0 = -6000;
82 SysTime dieTime;
83 char[128] titlestr;
84 int titlelen;
85 Kind kind;
87 private:
88 void cbOnClosing () {
89 if (lay !is null) delete lay; // this will free malloced memory
90 if (vg !is null) {
91 this.setAsCurrentOpenGlContext();
92 scope (exit) { flushGui(); this.releaseCurrentOpenGlContext(); }
93 vg.kill();
95 popupRemoved(this);
98 void cbWindowResized (int wdt, int hgt) {
99 if (this.closed) return;
100 if (!this.visible) return;
101 this.redrawOpenGlSceneNow();
104 void cbInit () {
105 this.setAsCurrentOpenGlContext(); // make this window active
106 scope(exit) this.releaseCurrentOpenGlContext();
107 this.vsync = false;
108 vg = nvgCreateContext();
109 if (vg is null) assert(0, "cannot initialize NanoVG");
110 //vg.createFont("ui", uiFontNames[0]);
111 vg.fonsContext.addFontsFrom(fstash);
112 //this.redrawOpenGlSceneNow();
115 // titlebar background color
116 NVGColor colorTitleBG () {
117 final switch (kind) {
118 case Kind.Info: return NVGColor("#006");
119 case Kind.Incoming: return NVGColor("#444");
120 case Kind.Status: return NVGColor("#00c");
121 case Kind.Error: return NVGColor("#600");
123 assert(0);
126 // titlebar background color
127 NVGColor colorTitleFG () {
128 final switch (kind) {
129 case Kind.Info: return NVGColor("#fff");
130 case Kind.Incoming: return NVGColor("#fff");
131 case Kind.Status: return NVGColor("#aaa");
132 case Kind.Error: return NVGColor("#ff0");
134 assert(0);
137 // background color
138 NVGColor colorBG () {
139 final switch (kind) {
140 case Kind.Info: return NVGColor("#00c");
141 case Kind.Incoming: return NVGColor("#222");
142 case Kind.Status: return NVGColor("#008");
143 case Kind.Error: return NVGColor("#c00");
145 assert(0);
148 // foreground color
149 NVGColor colorFG () {
150 final switch (kind) {
151 case Kind.Info: return NVGColor("#ccc");
152 case Kind.Incoming: return NVGColor("#bbb");
153 case Kind.Status: return NVGColor("#888");
154 case Kind.Error: return NVGColor("#fff");
156 assert(0);
159 void cbOnPaint () {
160 if (this.closed) return;
161 if (vg !is null) {
162 this.setAsCurrentOpenGlContext(); // make this window active
163 scope(exit) this.releaseCurrentOpenGlContext();
165 glViewport(0, 0, this.width, this.height);
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);
272 auto oldWClass = sdpyWindowClass;
273 scope(exit) sdpyWindowClass = oldWClass;
274 sdpyWindowClass = "BIOACID_POPUP";
275 super(wdt, hgt, "PopupWindow", OpenGlOptions.yes, Resizability.fixedSize, WindowTypes.undecorated, WindowFlags.skipTaskbar|WindowFlags.alwaysOnTop|WindowFlags.cannotBeActivated|WindowFlags.dontAutoShow);
277 //XSetWindowBackground(impl.display, impl.window, gxRGB!(0, 0, 0));
279 this.onClosing = &cbOnClosing;
280 this.windowResized = &cbWindowResized;
281 this.visibleForTheFirstTime = &cbInit;
282 this.redrawOpenGlScene = &cbOnPaint;
283 this.handleMouseEvent = &cbOnMouse;
285 // sorry for this hack
286 setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_DOCK", true)(impl.display));
287 //setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_TOOLTIP", true)(impl.display));
289 Atom[4] atoms;
290 atoms[0] = GetAtom!("_NET_WM_STATE_STICKY", true)(impl.display);
291 atoms[1] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(impl.display);
292 atoms[2] = GetAtom!("_NET_WM_STATE_SKIP_PAGER", true)(impl.display);
293 atoms[3] = GetAtom!("_NET_WM_STATE_ABOVE", true)(impl.display);
294 XChangeProperty(
295 impl.display,
296 impl.window,
297 GetAtom!("_NET_WM_STATE", true)(impl.display),
298 XA_ATOM,
299 32 /* bits */,
300 0 /*PropModeReplace*/,
301 atoms.ptr,
302 cast(int)atoms.length);
304 //if (hidden) show();
305 //auto wrc = getWorkArea();
306 //this.moveResize(wrc.x0+x, wrc.y0+y, wdt, hgt);
307 this.moveResize(6000, 6000, wdt, hgt);
308 show();
310 import core.time;
311 dieTime = Clock.currTime+5.seconds;
312 //flushGui();
317 // ////////////////////////////////////////////////////////////////////////// //
318 __gshared PopupWindow[] popups;
321 void popupKillAll () {
322 if (popups.length) {
323 auto pwlist = popups;
324 scope(exit) delete pwlist;
325 popups = null;
326 foreach (ref PopupWindow pw; pwlist) if (pw !is null) { pw.close(); pw = null; }
327 flushGui();
332 void popupArrange () {
333 import std.algorithm : min, max;
334 auto wrc = PopupWindow.getWorkArea();
335 int px0 = 0, py0 = wrc.y1;
336 int rowWdt = 0;
337 foreach (immutable pwidx, PopupWindow pw; popups) {
338 if (pw is null || pw.closed || pw.hidden) continue;
339 // new row?
340 if (rowWdt > 0 && py0-pw.height < wrc.y0) {
341 py0 = wrc.y1;
342 px0 += rowWdt;
343 if (px0 >= wrc.x1) break;
345 rowWdt = max(rowWdt, pw.width);
346 int wx = px0;
347 int wy = py0-pw.height;
348 if (wx != pw.x0 || wy != pw.y0) {
349 /*if (pw.x0 < 0)*/ pw.move(wx, wy);
350 pw.x0 = wx;
351 pw.y0 = wy;
352 } else {
353 //conwriteln("idx=", pwidx, ": SAME!");
355 py0 -= pw.height;
357 flushGui();
361 void popupRemoved (PopupWindow win) {
362 if (win is null) return;
363 foreach (immutable idx, PopupWindow pw; popups) {
364 if (pw is win) {
365 foreach (immutable c; idx+1..popups.length) popups[c-1] = popups[c];
366 popups.length -= 1;
367 popups.assumeSafeAppend;
368 popupArrange();
369 return;
375 void popupCheckExpirations () {
376 if (popups.length == 0) return;
377 auto tm = Clock.currTime;
378 bool smthChanged = false;
379 usize idx = 0;
380 while (idx < popups.length) {
381 PopupWindow pw = popups[idx];
382 if (pw !is null && !pw.closed && tm >= pw.dieTime) {
383 //conwriteln("tm=", tm, "; dt=", pw.dieTime, "; ", pw.dieTime >= tm);
384 // first, remove from list, so `popupRemoved()` will turn to noop
385 foreach (immutable c; idx+1..popups.length) popups[c-1] = popups[c];
386 popups.length -= 1;
387 popups.assumeSafeAppend;
388 // now close
389 pw.close();
390 smthChanged = true;
391 } else {
392 ++idx;
395 if (smthChanged) popupArrange();
396 if (popups.length) postPopupCheckerEvent();
400 void showPopup(T) (PopupWindow.Kind akind, const(char)[] atitle, T[] atext) if (isAnyCharType!(T, true)) {
401 while (atext.length > 0 && atext[0] <= ' ') atext = atext[1..$];
402 while (atext.length > 0 && atext[$-1] <= ' ') atext = atext[0..$-1];
403 if (atext.length == 0) return;
404 popups ~= new PopupWindow(akind, atitle, atext);
405 popupArrange();
406 postPopupCheckerEvent();