two new contact commands: "/always" and "/normal" (and cosmetic fixes)
[bioacid.git] / popups.d
blob26affe92a40a10b432a0884b586e729e2835b07a
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("#00c");
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("#008");
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|WindowFlags.dontAutoShow);
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);
304 show();
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();