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
;
23 import arsd
.simpledisplay
;
30 import iv
.nanovega
.textlayouter
;
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
{
69 static GxRect
getWorkArea () nothrow @trusted {
72 getWorkAreaRect(rc
.x0
, rc
.y0
, rc
.width
, rc
.height
);
73 } catch (Exception e
) {
74 rc
= GxRect(0, 0, 800, 600);
82 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();
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");
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");
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");
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");
161 if (this.closed
) return;
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;
178 th
= cast(int)vg
.textFontHeight
;
181 // draw background and title
185 vg
.roundedRect(0.5, 0.5, vg
.width
-1, vg
.height
-1, 6);
189 vg
.intersectScissor(0, 0, vg
.width
, th
+2);
190 vg
.fillColor(colorTitleBG
);
193 vg
.intersectScissor(0, th
+1.5, vg
.width
, vg
.height
); // .5: slightly visible transition line
195 vg
.fillColor(colorBG
);
199 vg
.strokeColor(NVGColor
.white
);
203 int hgt
= vg
.height
-3*2;
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
]);
213 vg
.intersectScissor(3, 3+th
, vg
.width
-3*2, hgt
);
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
);
225 void cbOnMouse (MouseEvent event
) {
226 if (this.closed
) return;
227 if (event
== "LMB-DOWN") { this.close(); return; }
231 this(T
) (Kind akind
, const(char)[] atitle
, T
[] atext
) if (isAnyCharType
!(T
, true)) {
232 import std
.algorithm
: min
, max
;
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;
254 lay.put("popup window");
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
;
266 //conwriteln("wdt=", lay.width, "; text width=", lay.textWidth);
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));
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
);
293 GetAtom
!("_NET_WM_STATE", true)(impl
.display
),
296 0 /*PropModeReplace*/,
298 cast(int)atoms
.length
);
301 //auto wrc = getWorkArea();
302 //this.moveResize(wrc.x0+x, wrc.y0+y, wdt, hgt);
303 this.moveResize(-6000, -6000, wdt
, hgt
);
306 dieTime
= Clock
.currTime
+5.seconds
;
312 // ////////////////////////////////////////////////////////////////////////// //
313 __gshared PopupWindow
[] popups
;
316 void popupKillAll () {
318 auto pwlist
= popups
;
319 scope(exit
) delete pwlist
;
321 foreach (ref PopupWindow pw
; pwlist
) if (pw
!is null) { pw
.close(); pw
= null; }
327 void popupArrange () {
328 import std
.algorithm
: min
, max
;
329 auto wrc
= PopupWindow
.getWorkArea();
330 int px0
= 0, py0
= wrc
.y1
;
332 foreach (immutable pwidx
, PopupWindow pw
; popups
) {
333 if (pw
is null || pw
.closed || pw
.hidden
) continue;
335 if (rowWdt
> 0 && py0
-pw
.height
< wrc
.y0
) {
338 if (px0
>= wrc
.x1
) break;
340 rowWdt
= max(rowWdt
, pw
.width
);
342 int wy
= py0
-pw
.height
;
343 if (wx
!= pw
.x0 || wy
!= pw
.y0
) {
344 /*if (pw.x0 < 0)*/ pw
.move(wx
, wy
);
348 //conwriteln("idx=", pwidx, ": SAME!");
356 void popupRemoved (PopupWindow win
) {
357 if (win
is null) return;
358 foreach (immutable idx
, PopupWindow pw
; popups
) {
360 foreach (immutable c
; idx
+1..popups
.length
) popups
[c
-1] = popups
[c
];
362 popups
.assumeSafeAppend
;
370 void popupCheckExpirations () {
371 if (popups
.length
== 0) return;
372 auto tm
= Clock
.currTime
;
373 bool smthChanged
= false;
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
];
382 popups
.assumeSafeAppend
;
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
);
401 postPopupCheckerEvent();