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
;
42 // ////////////////////////////////////////////////////////////////////////// //
43 public class PopupCheckerEvent
{} ///
44 __gshared PopupCheckerEvent evPopupChecker
;
45 shared static this () { evPopupChecker
= new PopupCheckerEvent(); }
48 void postPopupCheckerEvent () {
49 if (glconCtlWindow
!is null && !glconCtlWindow
.eventQueued
!PopupCheckerEvent
) {
50 glconCtlWindow
.postTimeout(evPopupChecker
, 500);
55 // ////////////////////////////////////////////////////////////////////////// //
56 class PopupWindow
: SimpleWindow
{
70 static GxRect
getWorkArea () nothrow @trusted {
73 getWorkAreaRect(rc
.x0
, rc
.y0
, rc
.width
, rc
.height
);
74 } catch (Exception e
) {
75 rc
= GxRect(0, 0, 800, 600);
83 int x0
= -6000, y0
= -6000;
91 if (lay
!is null) delete lay
; // this will free malloced memory
93 this.setAsCurrentOpenGlContext();
94 scope (exit
) { flushGui(); this.releaseCurrentOpenGlContext(); }
100 void cbWindowResized (int wdt
, int hgt
) {
101 if (this.closed
) return;
102 if (!this.visible
) return;
103 this.redrawOpenGlSceneNow();
107 this.setAsCurrentOpenGlContext(); // make this window active
108 scope(exit
) this.releaseCurrentOpenGlContext();
110 vg
= nvgCreateContext();
111 if (vg
is null) assert(0, "cannot initialize NanoVG");
112 //vg.createFont("ui", uiFontNames[0]);
113 vg
.fonsContext
.fonsAddStashFonts(fstash
);
114 //this.redrawOpenGlSceneNow();
117 // titlebar background color
118 NVGColor
colorTitleBG () {
119 final switch (kind
) {
120 case Kind
.Info
: return NVGColor("#006");
121 case Kind
.Incoming
: return NVGColor("#444");
122 case Kind
.Status
: return NVGColor("#333");
123 case Kind
.Error
: return NVGColor("#600");
128 // titlebar background color
129 NVGColor
colorTitleFG () {
130 final switch (kind
) {
131 case Kind
.Info
: return NVGColor("#fff");
132 case Kind
.Incoming
: return NVGColor("#fff");
133 case Kind
.Status
: return NVGColor("#aaa");
134 case Kind
.Error
: return NVGColor("#ff0");
140 NVGColor
colorBG () {
141 final switch (kind
) {
142 case Kind
.Info
: return NVGColor("#00c");
143 case Kind
.Incoming
: return NVGColor("#222");
144 case Kind
.Status
: return NVGColor("#222");
145 case Kind
.Error
: return NVGColor("#c00");
151 NVGColor
colorFG () {
152 final switch (kind
) {
153 case Kind
.Info
: return NVGColor("#ccc");
154 case Kind
.Incoming
: return NVGColor("#bbb");
155 case Kind
.Status
: return NVGColor("#888");
156 case Kind
.Error
: return NVGColor("#fff");
162 if (this.closed
) return;
164 this.setAsCurrentOpenGlContext(); // make this window active
165 scope(exit
) this.releaseCurrentOpenGlContext();
167 glClearColor(0, 0, 0, 0);
168 glClear(glNVGClearFlags
/*|GL_COLOR_BUFFER_BIT*/);
170 vg
.beginFrame(this.width
, this.height
);
171 scope(exit
) vg
.endFrame();
172 vg
.shapeAntiAlias
= false;
179 th
= cast(int)vg
.textFontHeight
;
182 // draw background and title
186 vg
.roundedRect(0.5, 0.5, vg
.width
-1, vg
.height
-1, 6);
190 vg
.intersectScissor(0, 0, vg
.width
, th
+2);
191 vg
.fillColor(colorTitleBG
);
194 vg
.intersectScissor(0, th
+1.5, vg
.width
, vg
.height
); // .5: slightly visible transition line
196 vg
.fillColor(colorBG
);
200 vg
.strokeColor(NVGColor
.white
);
204 int hgt
= vg
.height
-3*2;
208 vg
.intersectScissor(3, 3, vg
.width
-3*2, hgt
);
209 vg
.textAlign
= NVGTextAlign
.H
.Center
;
210 vg
.textAlign
= NVGTextAlign
.V
.Baseline
;
211 vg
.fillColor(colorTitleFG
);
212 vg
.text(vg
.width
/2, 3+cast(int)vg
.textFontAscender
, titlestr
[0..titlelen
]);
214 vg
.intersectScissor(3, 3+th
, vg
.width
-3*2, hgt
);
216 vg
.intersectScissor(3, 3, vg
.width
-3*2, hgt
);
219 int y0
= th
+(lay
.textHeight
< hgt ?
(hgt
-lay
.textHeight
)/2 : 0);
220 vg
.drawLayouter(lay
, 0, 3, y0
, hgt
);
226 void cbOnMouse (MouseEvent event
) {
227 if (this.closed
) return;
228 if (event
== "LMB-DOWN") { this.close(); return; }
232 this(T
) (Kind akind
, const(char)[] atitle
, T
[] atext
) if (isAnyCharType
!(T
, true)) {
233 import std
.algorithm
: min
, max
;
236 if (atitle
.length
> titlestr
.length
) atitle
= atitle
[0..titlestr
.length
];
237 titlelen
= cast(int)atitle
.length
;
238 if (titlelen
) titlestr
[0..titlelen
] = atitle
[0..titlelen
];
241 import std
.functional
: toDelegate
;
243 auto oldFixFontDG
= laf
.fixFontDG
;
244 scope(exit
) laf
.fixFontDG
= oldFixFontDG
;
245 laf
.fixFontDG
= toDelegate(&fixFontForStyleUI
);
247 lay
= new LayTextClass(laf
, Width
-6);
249 lay.fontStyle.fontsize = 20;
250 lay.fontStyle.color = NVGColor("#aaa").asUint;
251 lay.fontStyle.bgcolor = NVGColor("#222").asUint;
252 lay.fontStyle.bold = true;
253 //lay.lineStyle.setCenter;
255 lay.put("popup window");
259 lay
.fontStyle
.color
= colorFG
.asUint
;
260 lay
.fontStyle
.bgcolor
= NVGColor
.transparent
.asUint
;
261 lay
.fontStyle
.fontsize
= 18;
262 lay
.fontStyle
.bold
= false;
263 lay
.lineStyle
.setCenter
;
267 //conwriteln("wdt=", lay.width, "; text width=", lay.textWidth);
270 int hgt
= min(max(MinHeight
, lay
.textHeight
), MaxHeight
);
272 sdpyWindowClass
= "BIOACID_POPUP";
273 super(wdt
, hgt
, "PopupWindow", OpenGlOptions
.yes
, Resizability
.fixedSize
, WindowTypes
.undecorated
, WindowFlags
.skipTaskbar|WindowFlags
.alwaysOnTop|WindowFlags
.cannotBeActivated
);
274 //XSetWindowBackground(impl.display, impl.window, gxRGB!(0, 0, 0));
276 this.onClosing
= &cbOnClosing
;
277 this.windowResized
= &cbWindowResized
;
278 this.visibleForTheFirstTime
= &cbInit
;
279 this.redrawOpenGlScene
= &cbOnPaint
;
280 this.handleMouseEvent
= &cbOnMouse
;
282 // sorry for this hack
283 setNetWMWindowType(GetAtom
!("_NET_WM_WINDOW_TYPE_DOCK", true)(impl
.display
));
284 //setNetWMWindowType(GetAtom!("_NET_WM_WINDOW_TYPE_TOOLTIP", true)(impl.display));
287 atoms
[0] = GetAtom
!("_NET_WM_STATE_STICKY", true)(impl
.display
);
288 atoms
[1] = GetAtom
!("_NET_WM_STATE_SKIP_TASKBAR", true)(impl
.display
);
289 atoms
[2] = GetAtom
!("_NET_WM_STATE_SKIP_PAGER", true)(impl
.display
);
290 atoms
[3] = GetAtom
!("_NET_WM_STATE_ABOVE", true)(impl
.display
);
294 GetAtom
!("_NET_WM_STATE", true)(impl
.display
),
297 0 /*PropModeReplace*/,
299 cast(int)atoms
.length
);
302 //auto wrc = getWorkArea();
303 //this.moveResize(wrc.x0+x, wrc.y0+y, wdt, hgt);
304 this.moveResize(-6000, -6000, wdt
, hgt
);
307 dieTime
= Clock
.currTime
+5.seconds
;
313 // ////////////////////////////////////////////////////////////////////////// //
314 __gshared PopupWindow
[] popups
;
317 void popupKillAll () {
319 auto pwlist
= popups
;
320 scope(exit
) delete pwlist
;
322 foreach (ref PopupWindow pw
; pwlist
) if (pw
!is null) { pw
.close(); pw
= null; }
328 void popupArrange () {
329 import std
.algorithm
: min
, max
;
330 auto wrc
= PopupWindow
.getWorkArea();
331 int px0
= 0, py0
= wrc
.y1
;
333 foreach (immutable pwidx
, PopupWindow pw
; popups
) {
334 if (pw
is null || pw
.closed || pw
.hidden
) continue;
336 if (rowWdt
> 0 && py0
-pw
.height
< wrc
.y0
) {
339 if (px0
>= wrc
.x1
) break;
341 rowWdt
= max(rowWdt
, pw
.width
);
343 int wy
= py0
-pw
.height
;
344 if (wx
!= pw
.x0 || wy
!= pw
.y0
) {
345 /*if (pw.x0 < 0)*/ pw
.move(wx
, wy
);
349 //conwriteln("idx=", pwidx, ": SAME!");
357 void popupRemoved (PopupWindow win
) {
358 if (win
is null) return;
359 foreach (immutable idx
, PopupWindow pw
; popups
) {
361 foreach (immutable c
; idx
+1..popups
.length
) popups
[c
-1] = popups
[c
];
363 popups
.assumeSafeAppend
;
371 void popupCheckExpirations () {
372 if (popups
.length
== 0) return;
373 auto tm
= Clock
.currTime
;
374 bool smthChanged
= false;
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
];
383 popups
.assumeSafeAppend
;
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
);
402 postPopupCheckerEvent();