slightly better URL detection
[bioacid.git] / popups.d
blobd363b4f7b51d44752d3d58b90c22a7292028ad27
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;
86 bool errTooMany;
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 // frame color
117 NVGColor colorFrame () {
118 final switch (kind) {
119 case Kind.Info: return NVGColor("#aaa");
120 case Kind.Incoming: return NVGColor("#ccc");
121 case Kind.Status: return NVGColor("#0cf");
122 case Kind.Error: return NVGColor("#f33");
124 assert(0);
127 // titlebar background color
128 NVGColor colorTitleBG () {
129 final switch (kind) {
130 case Kind.Info: return NVGColor("#006");
131 case Kind.Incoming: return NVGColor("#666");
132 case Kind.Status: return NVGColor("#00c");
133 case Kind.Error: return NVGColor("#600");
135 assert(0);
138 // titlebar text color
139 NVGColor colorTitleFG () {
140 final switch (kind) {
141 case Kind.Info: return NVGColor("#fff");
142 case Kind.Incoming: return NVGColor("#fff");
143 case Kind.Status: return NVGColor("#aaa");
144 case Kind.Error: return NVGColor("#ff0");
146 assert(0);
149 // background color
150 NVGColor colorBG () {
151 final switch (kind) {
152 case Kind.Info: return NVGColor("#00c");
153 case Kind.Incoming: return NVGColor("#333");
154 case Kind.Status: return NVGColor("#008");
155 case Kind.Error: return NVGColor("#c00");
157 assert(0);
160 // text color
161 NVGColor colorFG () {
162 final switch (kind) {
163 case Kind.Info: return NVGColor("#ccc");
164 case Kind.Incoming: return NVGColor("#bbb");
165 case Kind.Status: return NVGColor("#888");
166 case Kind.Error: return NVGColor("#fff");
168 assert(0);
171 void cbOnPaint () {
172 if (this.closed) return;
173 if (vg !is null) {
174 this.setAsCurrentOpenGlContext(); // make this window active
175 scope(exit) this.releaseCurrentOpenGlContext();
177 glViewport(0, 0, this.width, this.height);
178 glClearColor(0, 0, 0, 0);
179 glClear(glNVGClearFlags/*|GL_COLOR_BUFFER_BIT*/);
181 vg.beginFrame(this.width, this.height);
182 scope(exit) vg.endFrame();
183 vg.shapeAntiAlias = false;
185 // title height
186 int th = 0;
187 if (titlelen > 0) {
188 vg.fontFace = "uib";
189 vg.fontSize = 20;
190 th = cast(int)vg.textFontHeight;
193 // draw background and title
195 vg.save();
196 vg.newPath();
197 vg.roundedRect(0.5, 0.5, vg.width-1, vg.height-1, 6);
198 // draw titlebar
199 if (th > 0) {
200 vg.save();
201 vg.intersectScissor(0, 0, vg.width, th+2);
202 vg.fillColor(colorTitleBG);
203 vg.fill();
204 vg.restore();
205 vg.intersectScissor(0, th+1.5, vg.width, vg.height); // .5: slightly visible transition line
207 vg.fillColor(colorBG);
208 vg.fill();
209 vg.restore();
210 vg.strokeWidth = 1;
211 vg.strokeColor(colorFrame);
212 vg.stroke();
215 int hgt = vg.height-3*2;
217 // draw title text
218 if (th > 0) {
219 vg.intersectScissor(3, 3, vg.width-3*2, hgt);
220 vg.textAlign = NVGTextAlign.H.Center;
221 vg.textAlign = NVGTextAlign.V.Baseline;
222 vg.fillColor(colorTitleFG);
223 vg.text(vg.width/2, 3+cast(int)vg.textFontAscender, titlestr[0..titlelen]);
224 hgt -= th;
225 vg.intersectScissor(3, 3+th, vg.width-3*2, hgt);
226 } else {
227 vg.intersectScissor(3, 3, vg.width-3*2, hgt);
230 int y0 = th+(lay.textHeight < hgt ? (hgt-lay.textHeight)/2 : 0);
231 vg.drawLayouter(lay, 0, 3, y0, hgt);
233 //flushGui();
236 protected:
237 void cbOnMouse (MouseEvent event) {
238 if (this.closed) return;
239 if (event == "LMB-DOWN") { this.close(); return; }
242 public:
243 this(T) (Kind akind, const(char)[] atitle, T[] atext) if (isAnyCharType!(T, true)) {
244 import std.algorithm : min, max;
246 kind = akind;
247 if (atitle.length > titlestr.length) atitle = atitle[0..titlestr.length];
248 titlelen = cast(int)atitle.length;
249 if (titlelen) titlestr[0..titlelen] = atitle[0..titlelen];
252 import std.functional : toDelegate;
254 auto oldFixFontDG = laf.fixFontDG;
255 scope(exit) laf.fixFontDG = oldFixFontDG;
256 laf.fixFontDG = toDelegate(&fixFontForStyleUI);
258 lay = new LayTextClass(laf, Width-6);
260 lay.fontStyle.fontsize = 20;
261 lay.fontStyle.color = NVGColor("#aaa").asUint;
262 lay.fontStyle.bgcolor = NVGColor("#222").asUint;
263 lay.fontStyle.bold = true;
264 //lay.lineStyle.setCenter;
265 lay.putExpander();
266 lay.put("popup window");
267 lay.putExpander();
268 lay.endPara();
270 lay.fontStyle.color = colorFG.asUint;
271 lay.fontStyle.bgcolor = NVGColor.transparent.asUint;
272 lay.fontStyle.fontsize = 18;
273 lay.fontStyle.bold = false;
274 lay.lineStyle.setCenter;
275 lay.put(atext);
276 lay.finalize();
278 //conwriteln("wdt=", lay.width, "; text width=", lay.textWidth);
280 int wdt = Width;
281 int hgt = min(max(MinHeight, lay.textHeight), MaxHeight);
284 auto oldWClass = sdpyWindowClass;
285 scope(exit) sdpyWindowClass = oldWClass;
286 sdpyWindowClass = "BIOACID_POPUP";
287 super(wdt, hgt, "PopupWindow", OpenGlOptions.yes, Resizability.fixedSize, WindowTypes.undecorated, WindowFlags.skipTaskbar|WindowFlags.alwaysOnTop|WindowFlags.cannotBeActivated|WindowFlags.dontAutoShow);
289 //XSetWindowBackground(impl.display, impl.window, gxRGB!(0, 0, 0));
291 this.onClosing = &cbOnClosing;
292 this.windowResized = &cbWindowResized;
293 this.visibleForTheFirstTime = &cbInit;
294 this.redrawOpenGlScene = &cbOnPaint;
295 this.handleMouseEvent = &cbOnMouse;
297 // sorry for this hack
298 setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_DOCK", true)(impl.display));
299 //setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_TOOLTIP", true)(impl.display));
301 Atom[4] atoms;
302 atoms[0] = GetAtom!("_NET_WM_STATE_STICKY", true)(impl.display);
303 atoms[1] = GetAtom!("_NET_WM_STATE_SKIP_TASKBAR", true)(impl.display);
304 atoms[2] = GetAtom!("_NET_WM_STATE_SKIP_PAGER", true)(impl.display);
305 atoms[3] = GetAtom!("_NET_WM_STATE_ABOVE", true)(impl.display);
306 XChangeProperty(
307 impl.display,
308 impl.window,
309 GetAtom!("_NET_WM_STATE", true)(impl.display),
310 XA_ATOM,
311 32 /* bits */,
312 0 /*PropModeReplace*/,
313 atoms.ptr,
314 cast(int)atoms.length);
316 //if (hidden) show();
317 //auto wrc = getWorkArea();
318 //this.moveResize(wrc.x0+x, wrc.y0+y, wdt, hgt);
319 this.moveResize(6000, 6000, wdt, hgt);
320 show();
322 import core.time;
323 dieTime = Clock.currTime+5.seconds;
324 //flushGui();
329 // ////////////////////////////////////////////////////////////////////////// //
330 __gshared PopupWindow[] popups;
333 void popupKillAll () {
334 if (popups.length) {
335 auto pwlist = popups;
336 scope(exit) delete pwlist;
337 popups = null;
338 foreach (ref PopupWindow pw; pwlist) if (pw !is null) { pw.close(); pw = null; }
339 flushGui();
344 void popupArrange () {
345 import std.algorithm : min, max;
346 auto wrc = PopupWindow.getWorkArea();
347 int px0 = 0, py0 = wrc.y1;
348 int rowWdt = 0;
349 foreach (immutable pwidx, PopupWindow pw; popups) {
350 if (pw is null || pw.closed || pw.hidden) continue;
351 // new row?
352 if (rowWdt > 0 && py0-pw.height < wrc.y0) {
353 py0 = wrc.y1;
354 px0 += rowWdt;
355 if (px0 >= wrc.x1) break;
357 rowWdt = max(rowWdt, pw.width);
358 int wx = px0;
359 int wy = py0-pw.height;
360 if (wx != pw.x0 || wy != pw.y0) {
361 /*if (pw.x0 < 0)*/ pw.move(wx, wy);
362 pw.x0 = wx;
363 pw.y0 = wy;
364 } else {
365 //conwriteln("idx=", pwidx, ": SAME!");
367 py0 -= pw.height;
369 flushGui();
373 void popupRemoved (PopupWindow win) {
374 if (win is null) return;
375 foreach (immutable idx, PopupWindow pw; popups) {
376 if (pw is win) {
377 foreach (immutable c; idx+1..popups.length) popups[c-1] = popups[c];
378 popups.length -= 1;
379 popups.assumeSafeAppend;
380 popupArrange();
381 return;
387 void popupCheckExpirations () {
388 if (popups.length == 0) return;
389 auto tm = Clock.currTime;
390 bool smthChanged = false;
391 usize idx = 0;
392 while (idx < popups.length) {
393 PopupWindow pw = popups[idx];
394 if (pw !is null && !pw.closed && tm >= pw.dieTime) {
395 //conwriteln("tm=", tm, "; dt=", pw.dieTime, "; ", pw.dieTime >= tm);
396 // first, remove from list, so `popupRemoved()` will turn to noop
397 foreach (immutable c; idx+1..popups.length) popups[c-1] = popups[c];
398 popups.length -= 1;
399 popups.assumeSafeAppend;
400 // now close
401 pw.close();
402 smthChanged = true;
403 } else {
404 ++idx;
407 if (smthChanged) popupArrange();
408 if (popups.length) postPopupCheckerEvent();
412 void showPopup(T) (PopupWindow.Kind akind, const(char)[] atitle, T[] atext) if (isAnyCharType!(T, true)) {
413 if (popups.length >= 32) {
414 foreach (PopupWindow pw; popups) if (pw.errTooMany) return;
415 popups ~= new PopupWindow(PopupWindow.Kind.Error, "POPUP FLOOD", "too many popup windows!");
416 popups[$-1].errTooMany = true;
417 } else {
418 while (atext.length > 0 && atext[0] <= ' ') atext = atext[1..$];
419 while (atext.length > 0 && atext[$-1] <= ' ') atext = atext[0..$-1];
420 if (atext.length == 0) return;
421 popups ~= new PopupWindow(akind, atitle, atext);
423 popupArrange();
424 postPopupCheckerEvent();