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
~"'");
39 loadFont(textFont
, "text", textFontName
);
40 loadFont(textiFont
, "texti", textiFontName
);
41 loadFont(textbFont
, "textb", textbFontName
);
42 loadFont(textzFont
, "textz", textzFontName
);
44 loadFont(monoFont
, "mono", monoFontName
);
45 loadFont(monoiFont
, "monoi", monoiFontName
);
46 loadFont(monobFont
, "monob", monobFontName
);
47 loadFont(monozFont
, "monoz", monozFontName
);
49 loadFont(uiFont
, "ui", uiFontName
);
51 loadFont(galmapFont
, "galmap", galmapFontName
);
57 // ////////////////////////////////////////////////////////////////////////// //
58 // cursor (hi, Death Track!)
59 private enum curWidth
= 17;
60 private enum curHeight
= 23;
61 private static immutable ubyte[curWidth
*curHeight
] curImg
= [
62 0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
63 0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
64 1,0,3,2,2,0,0,0,0,0,0,0,0,0,0,0,0,
65 1,1,3,3,2,2,0,0,0,0,0,0,0,0,0,0,0,
66 1,1,3,3,4,2,2,0,0,0,0,0,0,0,0,0,0,
67 1,1,3,3,4,4,2,2,0,0,0,0,0,0,0,0,0,
68 1,1,3,3,4,4,4,2,2,0,0,0,0,0,0,0,0,
69 1,1,3,3,4,4,4,4,2,2,0,0,0,0,0,0,0,
70 1,1,3,3,4,4,4,5,6,2,2,0,0,0,0,0,0,
71 1,1,3,3,4,4,5,6,7,5,2,2,0,0,0,0,0,
72 1,1,3,3,4,5,6,7,5,4,5,2,2,0,0,0,0,
73 1,1,3,3,5,6,7,5,4,5,6,7,2,2,0,0,0,
74 1,1,3,3,6,7,5,4,5,6,7,7,7,2,2,0,0,
75 1,1,3,3,7,5,4,5,6,7,7,7,7,7,2,2,0,
76 1,1,3,3,5,4,5,6,8,8,8,8,8,8,8,8,2,
77 1,1,3,3,4,5,6,3,8,8,8,8,8,8,8,8,8,
78 1,1,3,3,5,6,3,3,1,1,1,1,1,1,1,0,0,
79 1,1,3,3,6,3,3,1,1,1,1,1,1,1,1,0,0,
80 1,1,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,
81 1,1,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,
82 1,1,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,
83 1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
84 1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
86 struct CC
{ ubyte r
, g
, b
, a
; }
87 private static immutable CC
[9] curPal
= [
100 NVGImage
createCursorImage (NVGContext vg
, bool white
=false) {
101 auto img
= new ubyte[](curWidth
*curHeight
*4);
102 scope(exit
) img
.destroy
;
103 const(ubyte)* s
= curImg
.ptr
;
105 foreach (immutable _
; 0..curWidth
*curHeight
) {
106 CC c
= curPal
.ptr
[*s
++];
109 //c = CC(255, 127, 0, 255);
110 auto hsl
= nvgRGB(c
.r
, c
.g
, c
.b
).asHSL
;
111 //hsl.l *= 1.25; if (hsl.l > 0.8) hsl.l = 0.8;
112 //hsl.l *= 1.25; if (hsl.l > 1) hsl.l = 1;
113 //hsl.h *= 2.25; if (hsl.h > 1) hsl.h = 1;
115 auto cc
= hsl
.asColor
.asUint
;
116 *d
++ = cc
&0xff; cc
>>= 8;
117 *d
++ = cc
&0xff; cc
>>= 8;
118 *d
++ = cc
&0xff; cc
>>= 8;
119 *d
++ = cc
&0xff; cc
>>= 8;
133 return vg
.createImageRGBA(curWidth
, curHeight
, img
[]);
137 // ////////////////////////////////////////////////////////////////////////// //
138 final class PopupMenu
{
143 static struct SecInfo
{
148 void buildKoi () nothrow {
150 foreach (dchar dc
; s
) skoi
~= uni2koi(dc
);
153 private static bool xmatch (const(char)[] s
, const(char)[] pat
) nothrow @trusted @nogc {
154 if (s
.length
== 0) return (pat
.length
== 0);
155 while (s
.length
&& pat
.length
) {
156 // is pattern char space?
158 if (s
[0] > ' ') return false;
163 // pattern char is not space
164 if (koi8lower(s
[0]) != koi8lower(pat
[0])) return false;
168 return (pat
.length
== 0);
171 bool filterMatch (const(char)[] flt
) const nothrow @nogc {
172 if (flt
.length
== 0) return true;
173 //if (skoi.length < flt) return false;
174 foreach (immutable dpos
; 0..skoi
.length
-1) {
175 if (xmatch(skoi
[dpos
..$], flt
)) return true;
181 static struct VisItem
{
182 int idx
; // item index in `items`
187 private SecInfo
[] items
;
188 private VisItem
[] visitems
;
189 private int curItemIdx
= 0;
190 private int topItemIdx
= 0;
191 private int hoverItem
= -1;
193 int maxW
; // maximum text width
198 private char[] filter
; // WARNING! it is unsafe to slice this!
200 bool allowFiltering
= false;
202 void textSize (dstring s
, out int tw
, out int th
) {
203 auto w
= vg
.bndLabelWidth(-1, s
)+4;
204 if (w
> GWidth
-Padding
*4) w
= GWidth
-Padding
*4;
205 auto h
= vg
.bndLabelHeight(-1, s
, w
);
206 if (h
< BND_WIDGET_HEIGHT
) h
= BND_WIDGET_HEIGHT
;
211 this (NVGContext avg
, dstring atitle
, scope dstring
[] delegate () buildSections
) {
212 assert(avg
!is null);
214 vg
.fontFaceId(uiFont
);
215 vg
.textAlign(NVGTextAlign
.H
.Left
, NVGTextAlign
.V
.Baseline
);
216 vg
.fontSize(fsizeUI
);
218 if (atitle
!is null) {
222 textSize(atitle
, items
[0].w
, items
[0].h
);
225 if (buildSections
!is null) {
226 auto list
= buildSections();
227 auto cpos
= items
.length
;
228 items
.length
+= list
.length
;
229 foreach (dstring s
; list
) {
231 items
[cpos
].buildKoi();
232 textSize(s
, items
[cpos
].w
, items
[cpos
].h
);
233 if (maxW
< items
[cpos
].w
) maxW
= items
[cpos
].w
;
238 menuTitle
= items
[0];
241 leftX
= (GWidth
-maxW
-Padding
*4)/2;
244 //topY = (GHeight-(ey-topY))/2;
250 private ref SecInfo
itAt (int idx
) nothrow @nogc {
251 if (idx
< 0 || idx
>= visitems
.length
) assert(0, "ui internal error");
252 return items
[visitems
[idx
].idx
];
255 @property bool isCurValid () const pure nothrow @safe @nogc { pragma(inline
, true); return (curItemIdx
>= 0 && curItemIdx
< visitems
.length
); }
257 @property int itemIndex () const pure nothrow @safe @nogc { pragma(inline
, true); return (curItemIdx
>= 0 && curItemIdx
< visitems
.length ? visitems
[curItemIdx
].idx
: -1); }
259 @property void itemIndex (int v
) nothrow @trusted @nogc {
260 if (items
.length
== 0) curItemIdx
= 0;
261 else if (v
< 0) v
= 0;
262 else if (v
>= items
.length
) v
= cast(int)(items
.length
-1);
263 foreach (immutable vidx
, const ref vi
; visitems
) {
264 if (vi
.idx
== v
) { curItemIdx
= cast(int)vidx
; return; }
271 visitems
.assumeSafeAppend
;
272 foreach (immutable iidx
, const ref it
; items
) {
273 if (it
.filterMatch(filter
)) {
274 visitems
~= VisItem(cast(int)iidx
);
279 void removeItem (int idx
) {
280 if (idx
< 0 || idx
>= items
.length
) return;
281 int oidx
= itemIndex
;
282 foreach (immutable c
; idx
+1..items
.length
) items
[c
-1] = items
[c
];
284 items
.assumeSafeAppend
;
285 if (oidx
>= items
.length
) oidx
= cast(int)(items
.length
-1);
286 if (oidx
< 0) oidx
= 0;
291 int lastFullVisible () {
292 if (visitems
.length
== 0) return 0;
293 int res
= topItemIdx
;
294 int y
= topY
+Padding
+menuTitle
.h
;
295 int idx
= topItemIdx
;
296 if (idx
< 0) idx
= 0;
297 int ey
= calcBotY
-Padding
;
298 while (idx
< visitems
.length
) {
300 if (y
> ey
) return res
;
304 return cast(int)visitems
.length
-1;
308 int y
= topY
+Padding
*2+menuTitle
.h
;
309 foreach (const ref sc
; items
) {
311 if (ny
> GHeight
-Padding
*2) return y
;
318 if (curItemIdx
< 0) curItemIdx
= 0;
319 if (curItemIdx
>= visitems
.length
) curItemIdx
= cast(int)visitems
.length
-1;
320 if (visitems
.length
) {
321 // make current item visible
322 if (curItemIdx
>= 0 && curItemIdx
< visitems
.length
) {
323 if (curItemIdx
< topItemIdx
) {
324 topItemIdx
= curItemIdx
;
326 //FIXME: make this faster!
327 //conwriteln("top=", topItemIdx, "; cur=", curItemIdx, "; lv=", lastFullVisible);
328 while (lastFullVisible
< curItemIdx
) {
329 if (topItemIdx
>= visitems
.length
-1) break;
335 while (topItemIdx
> 0) {
336 int lv
= lastFullVisible
;
338 int lv1
= lastFullVisible
;
339 if (lv1
!= lv
) { ++topItemIdx
; break; }
345 scope(exit
) vg
.restore();
346 if (moving
) vg
.globalAlpha(0.8);
347 vg
.fontFaceId(uiFont
);
348 vg
.textAlign(NVGTextAlign
.H
.Left
, NVGTextAlign
.V
.Baseline
);
349 vg
.fontSize(fsizeUI
);
352 vg
.bndMenuBackground(leftX
, topY
, maxW
+Padding
*2, ey
-topY
, BND_CORNER_NONE
);
353 int y
= topY
+Padding
;
354 vg
.scissor(leftX
+Padding
, y
, maxW
, ey
-Padding
);
355 if (hasTitle
&& filter
.length
== 0) {
356 vg
.bndMenuLabel(leftX
+Padding
, y
, maxW
, menuTitle
.h
, -1, menuTitle
.s
);
359 if (filter
.length
!= 0 && hasTitle
) { //FIXME
360 char[256] buf
= void;
363 foreach (char fch
; filter
) {
364 auto len
= utf8Encode(ub
[], koi2uni(fch
));
365 if (len
< 1) continue;
366 if (bufpos
+len
> buf
.length
) break;
367 buf
[bufpos
..bufpos
+len
] = ub
[0..len
];
370 vg
.bndMenuLabel(leftX
+Padding
, y
, maxW
, menuTitle
.h
, -1, buf
[0..bufpos
]);
373 int idx
= topItemIdx
;
374 if (idx
< 0) idx
= 0;
376 while (idx
< visitems
.length
) {
377 vg
.bndMenuItem(leftX
+Padding
, y
, maxW
, itAt(idx
).h
,
378 (curItemIdx
== idx ? BND_ACTIVE
: hoverItem
== idx ? BND_HOVER
: BND_DEFAULT
),
387 enum int Close
= -666;
389 enum int NotMine
= -1;
391 int onKey (KeyEvent event
) {
392 if (!event
.pressed
) return NotMine
;
397 if (curItemIdx
> 0) {
403 if (curItemIdx
+1 < visitems
.length
) {
409 if (visitems
.length
) {
410 if (curItemIdx
== topItemIdx
) {
411 while (topItemIdx
> 0 && lastFullVisible
!= curItemIdx
) --topItemIdx
;
414 curItemIdx
= topItemIdx
;
418 if (visitems
.length
) {
419 if (curItemIdx
== lastFullVisible
) {
420 while (lastFullVisible
< visitems
.length
&& topItemIdx
!= curItemIdx
) ++topItemIdx
;
423 curItemIdx
= lastFullVisible
;
431 if (visitems
.length
) {
432 curItemIdx
= cast(int)visitems
.length
-1;
437 return (isCurValid ? itemIndex
: Close
);
441 if (allowFiltering
&& event
== "C-Y") {
444 filter
.assumeSafeAppend
;
445 auto cidx
= itemIndex
;
452 if (allowFiltering
&& filter
.length
&& event
== "Backspace") {
454 filter
.assumeSafeAppend
;
455 auto cidx
= itemIndex
;
464 int onChar (dchar dch
) {
465 if (!allowFiltering || dch
< ' ') return NotMine
;
466 char ch
= uni2koi(dch
);
467 if (ch
< ' ' || ch
== 127) return NotMine
;
469 auto cidx
= itemIndex
;
476 int onMouse (MouseEvent event
) {
479 if (event
.x
>= leftX
+Padding
&& event
.x
< leftX
+Padding
+maxW
&& event
.y
>= topY
+Padding
) {
481 int ey
= calcBotY
-Padding
;
484 int y
= topY
+Padding
;
485 if (hasTitle
) y
+= menuTitle
.h
;
486 int idx
= topItemIdx
;
487 if (idx
< 0) idx
= 0;
488 while (idx
< visitems
.length
) {
489 if (event
.y
>= y
&& event
.y
< y
+itAt(idx
).h
) { atItem
= idx
; break; }
496 if (hoverItem
!= atItem
) {
500 switch (event
.type
) {
501 case MouseEventType
.motion
:
508 case MouseEventType
.buttonPressed
:
509 if (event
.button
== MouseButton
.right
) return Close
;
510 if (event
.button
== MouseButton
.left
) {
511 //if (hoverItem >= 0 && hoverItem < visitems.length) return hoverItem;
512 if (hoverItem
>= 0 && hoverItem
< visitems
.length
) {
513 curItemIdx
= hoverItem
;
521 if (event
.button
== MouseButton
.wheelUp
) {
522 //if (topItemIdx > 0) { --topItemIdx; needRedraw = true; }
523 if (curItemIdx
> 0) { --curItemIdx
; needRedraw
= true; }
525 if (event
.button
== MouseButton
.wheelDown
) {
526 //if (topItemIdx < visitems.length) { ++topItemIdx; needRedraw = true; }
527 if (curItemIdx
< visitems
.length
) { ++curItemIdx
; needRedraw
= true; }
530 case MouseEventType
.buttonReleased
:
531 if (event
.button
== MouseButton
.left
) {
532 //if (curItemIdx >= 0 && curItemIdx < visitems.length) return curItemIdx;
533 if (curItemIdx
== hoverItem
&& curItemIdx
>= 0 && curItemIdx
< visitems
.length
&& isCurValid
) return itemIndex
;