1 /* Written 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/>.
19 import arsd
.simpledisplay
;
23 import iv
.nanovega
.blendish
;
32 // ////////////////////////////////////////////////////////////////////////// //
33 void loadFonts (NVGContext vg
) {
34 void loadFont (ref int fid
, string name
, string path
) {
35 fid
= vg
.createFont(name
, path
);
36 if (fid
< 0) throw new Exception("can't load font '"~name
~"' from '"~path
~"'");
37 //writeln("id=", fid, "; name=<", name, ">; path=", path);
40 loadFont(textFont
, "text", textFontName
);
41 loadFont(textiFont
, "texti", textiFontName
);
42 loadFont(textbFont
, "textb", textbFontName
);
43 loadFont(textzFont
, "textz", textzFontName
);
45 loadFont(monoFont
, "mono", monoFontName
);
46 loadFont(monoiFont
, "monoi", monoiFontName
);
47 loadFont(monobFont
, "monob", monobFontName
);
48 loadFont(monozFont
, "monoz", monozFontName
);
50 loadFont(uiFont
, "ui", uiFontName
);
52 loadFont(galmapFont
, "galmap", galmapFontName
);
58 // ////////////////////////////////////////////////////////////////////////// //
59 // cursor (hi, Death Track!)
60 private enum curWidth
= 17;
61 private enum curHeight
= 23;
62 private static immutable ubyte[curWidth
*curHeight
] curImg
= [
63 0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
64 0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
65 1,0,3,2,2,0,0,0,0,0,0,0,0,0,0,0,0,
66 1,1,3,3,2,2,0,0,0,0,0,0,0,0,0,0,0,
67 1,1,3,3,4,2,2,0,0,0,0,0,0,0,0,0,0,
68 1,1,3,3,4,4,2,2,0,0,0,0,0,0,0,0,0,
69 1,1,3,3,4,4,4,2,2,0,0,0,0,0,0,0,0,
70 1,1,3,3,4,4,4,4,2,2,0,0,0,0,0,0,0,
71 1,1,3,3,4,4,4,5,6,2,2,0,0,0,0,0,0,
72 1,1,3,3,4,4,5,6,7,5,2,2,0,0,0,0,0,
73 1,1,3,3,4,5,6,7,5,4,5,2,2,0,0,0,0,
74 1,1,3,3,5,6,7,5,4,5,6,7,2,2,0,0,0,
75 1,1,3,3,6,7,5,4,5,6,7,7,7,2,2,0,0,
76 1,1,3,3,7,5,4,5,6,7,7,7,7,7,2,2,0,
77 1,1,3,3,5,4,5,6,8,8,8,8,8,8,8,8,2,
78 1,1,3,3,4,5,6,3,8,8,8,8,8,8,8,8,8,
79 1,1,3,3,5,6,3,3,1,1,1,1,1,1,1,0,0,
80 1,1,3,3,6,3,3,1,1,1,1,1,1,1,1,0,0,
81 1,1,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,
82 1,1,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,
83 1,1,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,
84 1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
85 1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
87 struct CC
{ ubyte r
, g
, b
, a
; }
88 private static immutable CC
[9] curPal
= [
101 NVGImage
createCursorImage (NVGContext vg
, bool white
=false) {
102 auto img
= new ubyte[](curWidth
*curHeight
*4);
103 scope(exit
) img
.destroy
;
104 const(ubyte)* s
= curImg
.ptr
;
106 foreach (immutable _
; 0..curWidth
*curHeight
) {
107 CC c
= curPal
.ptr
[*s
++];
110 //c = CC(255, 127, 0, 255);
111 auto hsl
= nvgRGB(c
.r
, c
.g
, c
.b
).asHSL
;
112 //hsl.l *= 1.25; if (hsl.l > 0.8) hsl.l = 0.8;
113 //hsl.l *= 1.25; if (hsl.l > 1) hsl.l = 1;
114 //hsl.h *= 2.25; if (hsl.h > 1) hsl.h = 1;
116 auto cc
= hsl
.asColor
.asUint
;
117 *d
++ = cc
&0xff; cc
>>= 8;
118 *d
++ = cc
&0xff; cc
>>= 8;
119 *d
++ = cc
&0xff; cc
>>= 8;
120 *d
++ = cc
&0xff; cc
>>= 8;
134 return vg
.createImageRGBA(curWidth
, curHeight
, img
[]);
138 // ////////////////////////////////////////////////////////////////////////// //
139 final class PopupMenu
{
144 static struct SecInfo
{
149 void buildKoi () nothrow {
151 foreach (dchar dc
; s
) skoi
~= uni2koi(dc
);
154 private static bool xmatch (const(char)[] s
, const(char)[] pat
) nothrow @trusted @nogc {
155 if (s
.length
== 0) return (pat
.length
== 0);
156 while (s
.length
&& pat
.length
) {
157 // is pattern char space?
159 if (s
[0] > ' ') return false;
164 // pattern char is not space
165 if (koi8lower(s
[0]) != koi8lower(pat
[0])) return false;
169 return (pat
.length
== 0);
172 bool filterMatch (const(char)[] flt
) const nothrow @nogc {
173 if (flt
.length
== 0) return true;
174 //if (skoi.length < flt) return false;
175 foreach (immutable dpos
; 0..skoi
.length
-1) {
176 if (xmatch(skoi
[dpos
..$], flt
)) return true;
182 static struct VisItem
{
183 int idx
; // item index in `items`
188 private SecInfo
[] items
;
189 private VisItem
[] visitems
;
190 private int curItemIdx
= 0;
191 private int topItemIdx
= 0;
192 private int hoverItem
= -1;
194 int maxW
; // maximum text width
199 private char[] filter
; // WARNING! it is unsafe to slice this!
201 bool allowFiltering
= false;
203 void textSize (dstring s
, out int tw
, out int th
) {
204 auto w
= vg
.bndLabelWidth(-1, s
)+4;
205 //{ import core.stdc.stdio : printf; printf("* 03: w=%g\n", cast(double)w); }
206 if (w
> GWidth
-Padding
*4) w
= GWidth
-Padding
*4;
207 //{ import core.stdc.stdio : printf; printf("* 04: w=%g\n", cast(double)w); }
208 auto h
= vg
.bndLabelHeight(-1, s
, w
);
209 if (h
< BND_WIDGET_HEIGHT
) h
= BND_WIDGET_HEIGHT
;
214 this (NVGContext avg
, dstring atitle
, scope dstring
[] delegate () buildSections
) {
215 assert(avg
!is null);
216 //writeln("fw=", avg.width, "; pr=", avg.devicePixelRatio);
217 bool doEndFrame
= !avg
.inFrame
;
218 if (doEndFrame
) avg
.beginFrame(1024, 768);
219 scope(exit
) if (doEndFrame
) avg
.cancelFrame();
221 vg
.fontFaceId
= uiFont
;
222 vg
.textAlign(NVGTextAlign
.H
.Left
, NVGTextAlign
.V
.Baseline
);
223 vg
.fontSize
= fsizeUI
;
225 if (atitle
!is null) {
229 textSize(atitle
, items
[0].w
, items
[0].h
);
232 if (buildSections
!is null) {
233 auto list
= buildSections();
234 auto cpos
= items
.length
;
235 items
.length
+= list
.length
;
236 foreach (dstring s
; list
) {
238 items
[cpos
].buildKoi();
239 textSize(s
, items
[cpos
].w
, items
[cpos
].h
);
240 if (maxW
< items
[cpos
].w
) maxW
= items
[cpos
].w
;
245 menuTitle
= items
[0];
248 //writeln("maxW=", maxW);
249 leftX
= (GWidth
-maxW
-Padding
*4)/2;
252 //topY = (GHeight-(ey-topY))/2;
258 private ref SecInfo
itAt (int idx
) nothrow @nogc {
259 if (idx
< 0 || idx
>= visitems
.length
) assert(0, "ui internal error");
260 return items
[visitems
[idx
].idx
];
263 @property bool isCurValid () const pure nothrow @safe @nogc { pragma(inline
, true); return (curItemIdx
>= 0 && curItemIdx
< visitems
.length
); }
265 @property int itemIndex () const pure nothrow @safe @nogc { pragma(inline
, true); return (curItemIdx
>= 0 && curItemIdx
< visitems
.length ? visitems
[curItemIdx
].idx
: -1); }
267 @property void itemIndex (int v
) nothrow @trusted @nogc {
268 if (items
.length
== 0) curItemIdx
= 0;
269 else if (v
< 0) v
= 0;
270 else if (v
>= items
.length
) v
= cast(int)(items
.length
-1);
271 foreach (immutable vidx
, const ref vi
; visitems
) {
272 if (vi
.idx
== v
) { curItemIdx
= cast(int)vidx
; return; }
279 visitems
.assumeSafeAppend
;
280 foreach (immutable iidx
, const ref it
; items
) {
281 if (it
.filterMatch(filter
)) {
282 visitems
~= VisItem(cast(int)iidx
);
287 void removeItem (int idx
) {
288 if (idx
< 0 || idx
>= items
.length
) return;
289 int oidx
= itemIndex
;
290 foreach (immutable c
; idx
+1..items
.length
) items
[c
-1] = items
[c
];
292 items
.assumeSafeAppend
;
293 if (oidx
>= items
.length
) oidx
= cast(int)(items
.length
-1);
294 if (oidx
< 0) oidx
= 0;
299 int lastFullVisible () {
300 if (visitems
.length
== 0) return 0;
301 int res
= topItemIdx
;
302 int y
= topY
+Padding
+menuTitle
.h
;
303 int idx
= topItemIdx
;
304 if (idx
< 0) idx
= 0;
305 int ey
= calcBotY
-Padding
;
306 while (idx
< visitems
.length
) {
308 if (y
> ey
) return res
;
312 return cast(int)visitems
.length
-1;
316 int y
= topY
+Padding
*2+menuTitle
.h
;
317 foreach (const ref sc
; items
) {
319 if (ny
> GHeight
-Padding
*2) return y
;
326 if (curItemIdx
< 0) curItemIdx
= 0;
327 if (curItemIdx
>= visitems
.length
) curItemIdx
= cast(int)visitems
.length
-1;
328 if (visitems
.length
) {
329 // make current item visible
330 if (curItemIdx
>= 0 && curItemIdx
< visitems
.length
) {
331 if (curItemIdx
< topItemIdx
) {
332 topItemIdx
= curItemIdx
;
334 //FIXME: make this faster!
335 //conwriteln("top=", topItemIdx, "; cur=", curItemIdx, "; lv=", lastFullVisible);
336 while (lastFullVisible
< curItemIdx
) {
337 if (topItemIdx
>= visitems
.length
-1) break;
343 while (topItemIdx
> 0) {
344 int lv
= lastFullVisible
;
346 int lv1
= lastFullVisible
;
347 if (lv1
!= lv
) { ++topItemIdx
; break; }
353 scope(exit
) vg
.restore();
354 if (moving
) vg
.globalAlpha(0.8);
355 vg
.fontFaceId(uiFont
);
356 vg
.textAlign(NVGTextAlign
.H
.Left
, NVGTextAlign
.V
.Baseline
);
357 vg
.fontSize(fsizeUI
);
360 //writeln("leftX=", leftX, "; topY=", topY, "; w=", maxW+Padding*2, "; h=", ey-topY);
361 vg
.bndMenuBackground(leftX
, topY
, maxW
+Padding
*2, ey
-topY
, BND_CORNER_NONE
);
362 int y
= topY
+Padding
;
363 vg
.scissor(leftX
+Padding
, y
, maxW
, ey
-Padding
);
364 if (hasTitle
&& filter
.length
== 0) {
365 vg
.bndMenuLabel(leftX
+Padding
, y
, maxW
, menuTitle
.h
, -1, menuTitle
.s
);
368 if (filter
.length
!= 0 && hasTitle
) { //FIXME
369 char[256] buf
= void;
372 foreach (char fch
; filter
) {
373 auto len
= utf8Encode(ub
[], koi2uni(fch
));
374 if (len
< 1) continue;
375 if (bufpos
+len
> buf
.length
) break;
376 buf
[bufpos
..bufpos
+len
] = ub
[0..len
];
379 vg
.bndMenuLabel(leftX
+Padding
, y
, maxW
, menuTitle
.h
, -1, buf
[0..bufpos
]);
382 int idx
= topItemIdx
;
383 if (idx
< 0) idx
= 0;
385 while (idx
< visitems
.length
) {
386 vg
.bndMenuItem(leftX
+Padding
, y
, maxW
, itAt(idx
).h
,
387 (curItemIdx
== idx ? BND_ACTIVE
: hoverItem
== idx ? BND_HOVER
: BND_DEFAULT
),
396 enum int Close
= -666;
398 enum int NotMine
= -1;
400 int onKey (KeyEvent event
) {
401 if (!event
.pressed
) return NotMine
;
406 if (curItemIdx
> 0) {
412 if (curItemIdx
+1 < visitems
.length
) {
418 if (visitems
.length
) {
419 if (curItemIdx
== topItemIdx
) {
420 while (topItemIdx
> 0 && lastFullVisible
!= curItemIdx
) --topItemIdx
;
423 curItemIdx
= topItemIdx
;
427 if (visitems
.length
) {
428 if (curItemIdx
== lastFullVisible
) {
429 while (lastFullVisible
< visitems
.length
&& topItemIdx
!= curItemIdx
) ++topItemIdx
;
432 curItemIdx
= lastFullVisible
;
440 if (visitems
.length
) {
441 curItemIdx
= cast(int)visitems
.length
-1;
446 return (isCurValid ? itemIndex
: Close
);
450 if (allowFiltering
&& event
== "C-Y") {
453 filter
.assumeSafeAppend
;
454 auto cidx
= itemIndex
;
461 if (allowFiltering
&& filter
.length
&& event
== "Backspace") {
463 filter
.assumeSafeAppend
;
464 auto cidx
= itemIndex
;
473 int onChar (dchar dch
) {
474 if (!allowFiltering || dch
< ' ') return NotMine
;
475 char ch
= uni2koi(dch
);
476 if (ch
< ' ' || ch
== 127) return NotMine
;
478 auto cidx
= itemIndex
;
485 int onMouse (MouseEvent event
) {
488 if (event
.x
>= leftX
+Padding
&& event
.x
< leftX
+Padding
+maxW
&& event
.y
>= topY
+Padding
) {
490 int ey
= calcBotY
-Padding
;
493 int y
= topY
+Padding
;
494 if (hasTitle
) y
+= menuTitle
.h
;
495 int idx
= topItemIdx
;
496 if (idx
< 0) idx
= 0;
497 while (idx
< visitems
.length
) {
498 if (event
.y
>= y
&& event
.y
< y
+itAt(idx
).h
) { atItem
= idx
; break; }
505 if (hoverItem
!= atItem
) {
509 switch (event
.type
) {
510 case MouseEventType
.motion
:
517 case MouseEventType
.buttonPressed
:
518 if (event
.button
== MouseButton
.right
) return Close
;
519 if (event
.button
== MouseButton
.left
) {
520 //if (hoverItem >= 0 && hoverItem < visitems.length) return hoverItem;
521 if (hoverItem
>= 0 && hoverItem
< visitems
.length
) {
522 curItemIdx
= hoverItem
;
530 if (event
.button
== MouseButton
.wheelUp
) {
531 //if (topItemIdx > 0) { --topItemIdx; needRedraw = true; }
532 if (curItemIdx
> 0) { --curItemIdx
; needRedraw
= true; }
534 if (event
.button
== MouseButton
.wheelDown
) {
535 //if (topItemIdx < visitems.length) { ++topItemIdx; needRedraw = true; }
536 if (curItemIdx
< visitems
.length
) { ++curItemIdx
; needRedraw
= true; }
539 case MouseEventType
.buttonReleased
:
540 if (event
.button
== MouseButton
.left
) {
541 //if (curItemIdx >= 0 && curItemIdx < visitems.length) return curItemIdx;
542 if (curItemIdx
== hoverItem
&& curItemIdx
>= 0 && curItemIdx
< visitems
.length
&& isCurValid
) return itemIndex
;