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
;
22 import arsd
.simpledisplay
;
29 import iv
.nanovega
.textlayouter
;
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
{
68 static GxRect
getWorkArea () nothrow @trusted {
71 getWorkAreaRect(rc
.x0
, rc
.y0
, rc
.width
, rc
.height
);
72 } catch (Exception e
) {
73 rc
= GxRect(0, 0, 800, 600);
81 int x0
= -6000, y0
= -6000;
90 if (lay
!is null) delete lay
; // this will free malloced memory
92 this.setAsCurrentOpenGlContext();
93 scope (exit
) { flushGui(); this.releaseCurrentOpenGlContext(); }
99 void cbWindowResized (int wdt
, int hgt
) {
100 if (this.closed
) return;
101 if (!this.visible
) return;
102 this.redrawOpenGlSceneNow();
106 this.setAsCurrentOpenGlContext(); // make this window active
107 scope(exit
) this.releaseCurrentOpenGlContext();
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();
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");
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");
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");
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");
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");
172 if (this.closed
) return;
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;
190 th
= cast(int)vg
.textFontHeight
;
193 // draw background and title
197 vg
.roundedRect(0.5, 0.5, vg
.width
-1, vg
.height
-1, 6);
201 vg
.intersectScissor(0, 0, vg
.width
, th
+2);
202 vg
.fillColor(colorTitleBG
);
205 vg
.intersectScissor(0, th
+1.5, vg
.width
, vg
.height
); // .5: slightly visible transition line
207 vg
.fillColor(colorBG
);
211 vg
.strokeColor(colorFrame
);
215 int hgt
= vg
.height
-3*2;
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
]);
225 vg
.intersectScissor(3, 3+th
, vg
.width
-3*2, hgt
);
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
);
237 void cbOnMouse (MouseEvent event
) {
238 if (this.closed
) return;
239 if (event
== "LMB-DOWN") { this.close(); return; }
243 this(T
) (Kind akind
, const(char)[] atitle
, T
[] atext
) if (isAnyCharType
!(T
, true)) {
244 import std
.algorithm
: min
, max
;
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;
266 lay.put("popup window");
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
;
278 //conwriteln("wdt=", lay.width, "; text width=", lay.textWidth);
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));
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
);
309 GetAtom
!("_NET_WM_STATE", true)(impl
.display
),
312 0 /*PropModeReplace*/,
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
);
323 dieTime
= Clock
.currTime
+5.seconds
;
329 // ////////////////////////////////////////////////////////////////////////// //
330 __gshared PopupWindow
[] popups
;
333 void popupKillAll () {
335 auto pwlist
= popups
;
336 scope(exit
) delete pwlist
;
338 foreach (ref PopupWindow pw
; pwlist
) if (pw
!is null) { pw
.close(); pw
= null; }
344 void popupArrange () {
345 import std
.algorithm
: min
, max
;
346 auto wrc
= PopupWindow
.getWorkArea();
347 int px0
= 0, py0
= wrc
.y1
;
349 foreach (immutable pwidx
, PopupWindow pw
; popups
) {
350 if (pw
is null || pw
.closed || pw
.hidden
) continue;
352 if (rowWdt
> 0 && py0
-pw
.height
< wrc
.y0
) {
355 if (px0
>= wrc
.x1
) break;
357 rowWdt
= max(rowWdt
, pw
.width
);
359 int wy
= py0
-pw
.height
;
360 if (wx
!= pw
.x0 || wy
!= pw
.y0
) {
361 /*if (pw.x0 < 0)*/ pw
.move(wx
, wy
);
365 //conwriteln("idx=", pwidx, ": SAME!");
373 void popupRemoved (PopupWindow win
) {
374 if (win
is null) return;
375 foreach (immutable idx
, PopupWindow pw
; popups
) {
377 foreach (immutable c
; idx
+1..popups
.length
) popups
[c
-1] = popups
[c
];
379 popups
.assumeSafeAppend
;
387 void popupCheckExpirations () {
388 if (popups
.length
== 0) return;
389 auto tm
= Clock
.currTime
;
390 bool smthChanged
= false;
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
];
399 popups
.assumeSafeAppend
;
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;
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
);
424 postPopupCheckerEvent();