1 module amperskin
is aliced
;
6 import arsd
.simpledisplay
;
16 // ////////////////////////////////////////////////////////////////////////// //
20 void postQuitEvent () {
21 if (glconMainWindow
is null) return;
22 if (!glconMainWindow
.eventQueued
!QuitEvent
) glconMainWindow
.postEvent(new QuitEvent());
26 // ////////////////////////////////////////////////////////////////////////// //
27 //__gshared string skinfile = "skins/WastedAMP1-0.zip";
28 __gshared string skinfile
= "skins/winamp2.zip";
29 __gshared
bool modeShuffle
= false;
30 __gshared
bool modeRepeat
= false;
32 shared static this () {
33 conRegVar
!modeShuffle("mode_shuffle", "shuffle playlist");
34 conRegVar
!modeRepeat("mode_repeat", "repeat playlist");
38 // ////////////////////////////////////////////////////////////////////////// //
39 // width: 275; height: shaded: 14, full: 116
40 __gshared EImage skinImgMain
;
41 __gshared EImage skinImgCButtons
;
42 __gshared EImage skinImgShufRep
;
43 __gshared EImage skinImgNums
;
44 __gshared EImage skinImgPlaylist
;
45 __gshared EImage skinImgTitle
;
46 __gshared
uint skinPListColNormal
= gxRGB
!(0, 255, 0);
47 __gshared
uint skinPListColCurrent
= gxRGB
!(255, 255, 255);
48 __gshared
uint skinPListColNormalBG
= gxRGB
!(0, 0, 0);
49 __gshared
uint skinPListColCurrentBG
= gxRGB
!(0, 0, 255);
52 // ////////////////////////////////////////////////////////////////////////// //
53 __gshared AmpMainWindow ampMain
;
54 __gshared AmpPListWindow ampPList
;
57 // ////////////////////////////////////////////////////////////////////////// //
59 static struct KeyValue
{
62 nothrow @trusted @nogc:
66 while (pos
< s
.length
&& s
.ptr
[pos
] != '=' && s
.ptr
[pos
] != ';') ++pos
;
67 key
= s
[0..pos
].xstrip
;
68 if (pos
< s
.length
&& s
.ptr
[pos
] == '=') {
70 if (pos
< s
.length
&& s
.ptr
[pos
] <= ' ') ++pos
;
72 if (pos
< s
.length
&& s
.ptr
[pos
] != ';') ++pos
;
73 value
= s
[stpos
..pos
].xstrip
;
77 uint asColor () const {
78 auto val
= value
.xstrip
;
79 if (val
.length
!= 4 && val
.length
!= 7) return gxTransparent
;
81 if (val
.length
== 4) {
82 foreach (immutable idx
, char ch
; val
[1..$]) {
83 int dg
= digitInBase(ch
, 16);
84 if (dg
< 0) return gxTransparent
;
89 foreach (immutable idx
; 0..3) {
90 int d0
= digitInBase(val
[1+idx
*2], 16);
91 int d1
= digitInBase(val
[2+idx
*2], 16);
92 if (d0
< 0 || d1
< 0) return gxTransparent
;
96 return gxrgb(rgb
[0], rgb
[1], rgb
[2]);
100 static void parsePlaylistConfig (VFile fl
) {
101 bool inTextSection
= false;
102 foreach (auto line
; fl
.byLine
) {
104 if (line
.length
== 0 || line
.ptr
[0] == ';') continue;
105 if (line
[0] == '[') {
106 inTextSection
= line
.strEquCI("[Text]");
110 auto kv
= KeyValue(line
);
111 auto clr
= kv
.asColor
;
112 if (clr
.gxIsTransparent
) continue;
113 if (kv
.key
.strEquCI("Normal")) skinPListColNormal
= clr
;
114 else if (kv
.key
.strEquCI("Current")) skinPListColCurrent
= clr
;
115 else if (kv
.key
.strEquCI("NormalBG")) skinPListColNormalBG
= clr
;
116 else if (kv
.key
.strEquCI("CurrentBG")) skinPListColCurrentBG
= clr
;
121 static bool isGoodImageExtension (ConString fname
) {
122 auto epos
= fname
.lastIndexOf('.');
123 if (epos
< 0) return false;
124 fname
= fname
[epos
..$];
126 fname
.strEquCI(".bmp") ||
127 fname
.strEquCI(".png") ||
128 fname
.strEquCI(".jpg") ||
129 fname
.strEquCI(".jpeg") ||
133 void loadImage (EImage
* img
, string fname
) {
134 auto epos
= fname
.lastIndexOf('.');
135 if (epos
< 0) throw new Exception("invalid skin image '"~fname
~"'");
136 auto ext
= fname
[epos
..$];
138 auto fl
= VFile(fname
);
140 if (sz
< 1 || sz
> 1024*1024*8) throw new Exception("invalid skin image '"~fname
~"'");
142 auto data
= new ubyte[](cast(uint)sz
);
143 scope(exit
) delete data
;
144 fl
.rawReadExact(data
);
147 scope(exit
) delete ximg
;
149 if (ext
.strEquCI(".bmp")) ximg
= readBmp(data
);
150 else if (ext
.strEquCI(".png")) ximg
= imageFromPng(readPng(data
));
151 else if (ext
.strEquCI(".jpg") || ext
.strEquCI(".jpeg")) ximg
= readJpegFromMemory(data
);
152 else throw new Exception("unknown image format for '"~fname
~"'");
153 assert(ximg
!is null);
155 *img
= new EImage(ximg
);
158 AmpPListWindow
.SavedData pldata
;
159 if (ampPList
!is null) pldata
= ampPList
.saveData();
160 scope(exit
) if (ampPList
!is null) ampPList
.restoreData(pldata
);
165 skinPListColNormal
= gxRGB
!(0, 255, 0);
166 skinPListColCurrent
= gxRGB
!(255, 255, 255);
167 skinPListColNormalBG
= gxRGB
!(0, 0, 0);
168 skinPListColCurrentBG
= gxRGB
!(0, 0, 255);
172 static struct ImageInfo
{
177 ImageInfo
[$] imglist
= [
178 ImageInfo("main", &skinImgMain
),
179 ImageInfo("cbuttons", &skinImgCButtons
),
180 ImageInfo("shufrep", &skinImgShufRep
),
181 ImageInfo("numbers", &numsex
),
182 ImageInfo("nums_ex", &skinImgNums
),
183 ImageInfo("pledit", &skinImgPlaylist
),
184 ImageInfo("titlebar", &skinImgTitle
, true),
187 foreach (ref ii
; imglist
) { *(ii
.img
) = null; }
189 auto vid
= vfsAddPak(skinfile
, "skin:");
190 scope(exit
) vfsRemovePak(vid
);
191 foreach (auto de; vfsFileList
) {
192 if (!isGoodImageExtension(de.name
)) continue;
194 auto fname
= de.name
;
195 auto xpos
= fname
.lastIndexOf('/');
196 if (xpos
>= 0) fname
= fname
[xpos
+1..$];
197 else if (fname
.startsWith("skin:")) fname
= fname
[5..$];
198 xpos
= fname
.lastIndexOf('.');
199 if (xpos
<= 0) continue;
200 auto iname
= fname
[0..xpos
];
201 //conwriteln("[", de.name, "]:[", fname, "]:[", iname, "]");
203 if (fname
.strEquCI("pledit.txt")) {
204 parsePlaylistConfig(VFile(de.name
));
208 foreach (ref ii
; imglist
) {
209 if (*(ii
.img
) is null && iname
.strEquCI(ii
.name
)) {
210 //conwriteln(de.name);
211 loadImage(ii
.img
, de.name
);
217 if (skinImgNums
is null) {
218 foreach (ref ii
; imglist
) if (ii
.name
== "numbers" && *(ii
.img
) !is null) skinImgNums
= *(ii
.img
);
221 foreach (ref ii
; imglist
) {
222 if (ii
.name
== "numbers" && *(ii
.img
) is null) *(ii
.img
) = skinImgNums
;
223 if (ii
.name
== "nums_ex" && *(ii
.img
) is null) *(ii
.img
) = numsex
;
224 if (*(ii
.img
) is null && !ii
.optional
) throw new Exception("invalid skin (missing '"~ii
.name
~"' image)");
227 ampMain
= new AmpMainWindow();
228 ampPList
= new AmpPListWindow();
232 // ////////////////////////////////////////////////////////////////////////// //
238 this (MemoryImage img
) {
239 if (img
is null) throw new Exception("can't create eimage from nothing");
242 data
.length
= width
*height
;
243 { import core
.memory
: GC
; GC
.setAttr(data
.ptr
, GC
.BlkAttr
.NO_INTERIOR|GC
.BlkAttr
.NO_SCAN
); }
244 foreach (immutable int dy
; 0..img
.height
) {
245 foreach (immutable int dx
; 0..img
.width
) {
246 auto cc
= img
.getPixel(dx
, dy
);
247 data
[dy
*width
+dx
] = gxrgb(cc
.r
, cc
.g
, cc
.b
);
252 final void blitAt (int x
, int y
) nothrow @trusted @nogc {
254 if (x < 0 || y < 0 || x+width > VBufWidth || y+height > VBufHeight) return;
255 const(uint)* src = data.ptr;
256 uint* dst = vglTexBuf+y*VBufWidth+x;
257 immutable int w = width;
258 foreach (immutable dy; 0..height) {
259 dst[0..w] = src[0..w];
264 blitRect(x
, y
, GxRect(0, 0, width
, height
));
267 final void blitRect (int destx
, int desty
, GxRect srect
) nothrow @trusted @nogc {
268 srect
.intersect(GxRect(0, 0, width
, height
));
269 if (srect
.empty
) return;
270 assert(srect
.x0
>= 0 && srect
.x0
< width
);
271 assert(srect
.y0
>= 0 && srect
.y0
< height
);
272 assert(srect
.x1
>= 0 && srect
.x1
< width
);
273 assert(srect
.y1
>= 0 && srect
.y1
< height
);
276 int dw = srect
.width
;
277 int dh
= srect
.height
;
278 int skipLeft
, skipTop
;
279 if (!gxClipRect
.clipHVStripes(dx
, dy
, dw, dh
, &skipLeft
, &skipTop
)) return;
280 if (!GxRect(0, 0, VBufWidth
, VBufHeight
).clipHVStripes(dx
, dy
, dw, dh
, &skipLeft
, &skipTop
)) return;
281 assert(dw > 0 && dw <= width
);
282 assert(dh
> 0 && dh
<= height
);
283 assert(skipLeft
+srect
.x0
+dw <= width
);
284 assert(skipTop
+srect
.y0
+dh
<= height
);
285 const(uint)* src
= data
.ptr
+(skipTop
+srect
.y0
)*width
+(skipLeft
+srect
.x0
);
286 uint* dst
= vglTexBuf
+dy
*VBufWidth
+dx
;
288 assert(src
+dw <= data
.ptr
+width
*height
);
289 assert(dst
+dw <= vglTexBuf
+VBufWidth
*VBufHeight
);
290 dst
[0..dw] = src
[0..dw];
298 // ////////////////////////////////////////////////////////////////////////// //
304 int mActiveWidget
= -1;
307 if (aimg
is null) throw new Exception("no image for window");
309 imgrc
= GxRect(0, 0, img
.width
, img
.height
);
312 AmpWidget
addWidget (AmpWidget w
) {
313 if (w
is null) return null;
314 if (w
.parent
!is null) throw new Exception("widget already owned");
324 glconPostScreenRebuild();
331 glconPostScreenRebuild();
335 final void focusChanged (bool focused
) {
336 if (focused
) this.focused(); else this.defocused();
339 final @property AmpWidget
activeWidget () pure nothrow @safe @nogc { pragma(inline
, true); return (mActiveWidget
>= 0 && mActiveWidget
< widgets
.length ? widgets
[mActiveWidget
] : null); }
341 final @property void activeWidget (AmpWidget w
) pure nothrow @safe @nogc {
342 if (w
is null || w
.parent
!is this) { mActiveWidget
= -1; return; }
343 foreach (immutable idx
, AmpWidget ww
; widgets
) if (ww
is w
) { mActiveWidget
= cast(int)idx
; return; }
347 final AmpWidget
widgetAt (int x
, int y
) pure nothrow @safe @nogc {
348 foreach_reverse (AmpWidget w
; widgets
) {
349 if (w
.rc
.inside(x
, y
)) return w
;
354 protected void paintWidgets () {
355 foreach (AmpWidget w
; widgets
) {
361 protected void paintBackground () {
366 protected void paintFinished () {
375 bool onKeyPre (KeyEvent event
) { return false; }
377 bool onKeyPost (KeyEvent event
) { return false; }
379 bool onKey (KeyEvent event
) {
380 if (onKeyPre(event
)) return true;
381 if (auto aw
= activeWidget
) {
382 if (aw
.onKey(event
)) return true;
384 if (onKeyPost(event
)) return true;
388 bool onMousePre (MouseEvent event
) { return false; }
389 bool onMousePost (MouseEvent event
) { return false; }
391 bool onMouse (MouseEvent event
) {
392 bool wasAwOrAct
= false;
393 if (onMousePre(event
)) return true;
394 auto aw
= activeWidget
;
396 if (aw
.onMouse(event
)) return true;
399 if (auto ww
= widgetAt(event
.x
, event
.y
)) {
401 if (ww
.onMouse(event
)) return true;
405 if (onMousePost(event
)) return true;
406 if (wasAwOrAct
&& event
.type
!= MouseEventType
.motion
) return true;
412 // ////////////////////////////////////////////////////////////////////////// //
420 void delegate () onAction
;
424 this (EImage aimg
, int x
, int y
, GxRect aimgrc
, string acmd
=null) {
425 if (aimg
is null) throw new Exception("no image for window");
428 rc
= GxRect(x
, y
, aimgrc
.width
, aimgrc
.height
);
433 bool active
= (parent
!is null && parent
.activeWidget
is this);
435 if (active
) irc
.moveBy(0, imgrc
.height
);
436 img
.blitRect(rc
.x0
, rc
.y0
, irc
);
439 bool onKey (KeyEvent event
) {
443 bool onMouse (MouseEvent event
) {
444 if (event
.type
== MouseEventType
.buttonPressed
&& event
.button
== MouseButton
.left
) {
445 parent
.activeWidget
= this;
448 if (event
.type
== MouseEventType
.buttonReleased
&& event
.button
== MouseButton
.left
) {
449 if (parent
.activeWidget
is this) {
450 parent
.activeWidget
= null;
451 if (rc
.inside(event
.x
, event
.y
)) {
453 if (onAction
!is null) onAction();
463 // ////////////////////////////////////////////////////////////////////////// //
464 class AmpWidgetToggle
: AmpWidget
{
466 private bool checkedvar
;
468 this (bool* cvp
, EImage aimg
, int x
, int y
, GxRect aimgrc
, string acmd
=null) {
469 super(aimg
, x
, y
, aimgrc
, acmd
);
470 if (cvp
is null) cvp
= &checkedvar
;
474 override void onPaint () {
475 bool active
= (parent
!is null && parent
.activeWidget
is this);
477 if (*checked
) irc
.moveBy(0, imgrc
.height
*2);
478 if (active
) irc
.moveBy(0, imgrc
.height
);
479 img
.blitRect(rc
.x0
, rc
.y0
, irc
);
484 // ////////////////////////////////////////////////////////////////////////// //
485 class AmpWidgetSongTitle
: AmpWidget
{
487 int titleofsmovedir
= 1;
488 int titlemovepause
= 8;
490 bool dragging
= false;
492 this(T
:const(char)[]) (GxRect arc
, T atitle
) {
493 static if (is(T
== typeof(null))) title
= null;
494 else static if (is(T
== string
)) title
= atitle
;
495 else title
= atitle
.idup
;
500 override void onPaint () {
503 gxDrawTextUtf(rc
.x0
-titleofs
, rc
.y0
-2, title
, gxRGB
!(0, 255, 0));
507 final void scrollTitle () {
508 if (parent
.activeWidget
!is this) dragging
= false;
509 if (dragging
) return;
510 if (titlemovepause
-- >= 0) return;
512 if (title
.length
== 0) { titleofs
= 0; titleofsmovedir
= 1; titlemovepause
= 8; return; }
513 auto tw
= gxTextWidthUtf(title
);
514 titleofs
+= titleofsmovedir
;
515 if (titleofs
<= 0) { titleofs
= 0; titleofsmovedir
= 1; titlemovepause
= 8; }
516 else if (titleofs
>= tw
-rc
.width
) { titleofs
= tw
-rc
.width
; titleofsmovedir
= -1; titlemovepause
= 8; }
519 override bool onKey (KeyEvent event
) {
523 override bool onMouse (MouseEvent event
) {
524 if (event
.type
== MouseEventType
.buttonPressed
&& event
.button
== MouseButton
.left
) {
525 parent
.activeWidget
= this;
529 if (event
.type
== MouseEventType
.buttonReleased
&& event
.button
== MouseButton
.left
) {
530 if (parent
.activeWidget
is this) {
532 if (title
.length
!= 0) {
534 auto tw
= gxTextWidthUtf(title
);
535 if (titleofs
<= 0) { titleofs
= 0; titleofsmovedir
= 1; }
536 else if (titleofs
>= tw
-rc
.width
) { titleofs
= tw
-rc
.width
; titleofsmovedir
= -1; }
538 parent
.activeWidget
= null;
542 if (dragging
&& event
.type
== MouseEventType
.motion
&& event
.modifierState
&ModifierState
.leftButtonDown
) {
543 if (title
.length
!= 0 && event
.dx
!= 0) {
544 titleofs
-= event
.dx
;
545 //conwriteln("dx=", event.dx, "; titleofs=", titleofs);
546 titleofsmovedir
= (event
.dx
< 0 ?
1 : -1);
547 auto tw
= gxTextWidthUtf(title
);
548 if (titleofs
<= 0) titleofs
= 0;
549 else if (titleofs
>= tw
-rc
.width
) titleofs
= tw
-rc
.width
;
557 // ////////////////////////////////////////////////////////////////////////// //
558 class AmpMainWindow
: AmpWindow
{
559 AmpWidgetSongTitle wtitle
;
563 addWidget(new AmpWidget(skinImgCButtons
, 16, 88, GxRect(0, 0, 23, 18), "song_prev"));
564 addWidget(new AmpWidget(skinImgCButtons
, 39, 88, GxRect(23, 0, 23, 18), "song_play"));
565 addWidget(new AmpWidget(skinImgCButtons
, 62, 88, GxRect(46, 0, 23, 18), "song_pause"));
566 addWidget(new AmpWidget(skinImgCButtons
, 85, 88, GxRect(69, 0, 23, 18), "song_stop"));
567 addWidget(new AmpWidget(skinImgCButtons
, 108, 88, GxRect(92, 0, 22, 18), "song_next"));
568 addWidget(new AmpWidget(skinImgCButtons
, 136, 88, GxRect(114, 0, 22, 16), "song_eject"));
569 addWidget(new AmpWidgetToggle(&modeShuffle
, skinImgShufRep
, 164, 89, GxRect(28, 0, 46, 15), "mode_shuffle toggle"));
570 addWidget(new AmpWidgetToggle(&modeRepeat
, skinImgShufRep
, 210, 89, GxRect(0, 0, 28, 15), "mode_repeat toggle"));
571 wtitle
= cast(AmpWidgetSongTitle
)addWidget(new AmpWidgetSongTitle(GxRect(109, 24, 157, 12), "Testing text... жопф дохуя текста"));
574 final void scrollTitle () {
575 if (wtitle
!is null) wtitle
.scrollTitle();
578 protected override void paintBackground () {
581 auto irc
= GxRect(27, 0, 275, 14);
582 if (!active
) irc
.moveBy(0, 15);
583 skinImgTitle
.blitRect(0, 0, irc
);
586 override bool onKeyPost (KeyEvent event
) {
587 if (event
== "C-Q") { if (event
.pressed
) postQuitEvent(); return true; }
593 // ////////////////////////////////////////////////////////////////////////// //
597 int duration
; // in seconds
601 class AmpPListWindow
: AmpWindow
{
602 static struct SavedData
{
617 img
= skinImgPlaylist
;
623 res
.totaldur
= totaldur
;
624 res
.topitem
= topitem
;
625 res
.curitem
= curitem
;
629 void restoreData (ref SavedData sd
) {
631 totaldur
= sd
.totaldur
;
632 topitem
= sd
.topitem
;
633 curitem
= sd
.curitem
;
636 void appendListItem (PListItem li
) {
637 if (li
.duration
> 0) totaldur
+= li
.duration
;
638 auto optr
= items
.ptr
;
640 // check if runtime allocated a new memory block due to array growth
641 if (items
.ptr
!is optr
) {
642 import core
.memory
: GC
;
643 // check if our array really *IS* the head
644 if (items
.ptr
is GC
.addrOf(items
.ptr
)) {
645 // set the magic flag!
646 GC
.setAttr(items
.ptr
, GC
.BlkAttr
.NO_INTERIOR
);
651 final @property int visibleFullItems () const nothrow @trusted {
652 int hgt
= imgrc
.height
-20-38;
653 int res
= hgt
/gxTextHeightUtf
;
654 if (res
< 1) res
= 1;
658 final @property int visibleItems () const nothrow @trusted {
659 int hgt
= imgrc
.height
-20-38;
660 int res
= hgt
/gxTextHeightUtf
;
661 if (hgt
%gxTextHeightUtf
) ++res
;
662 if (res
< 1) res
= 1;
666 void normTop () nothrow @trusted {
667 if (curitem
< 0) curitem
= 0;
668 if (items
.length
== 0) { topitem
= 0; return; }
669 if (curitem
>= items
.length
) curitem
= cast(int)items
.length
-1;
672 if (tl
< curitem
) tl
= curitem
;
673 if (tl
+visibleFullItems
>= curitem
) tl
= curitem
-visibleFullItems
+1;
674 int el
= topitem
+visibleItems
-1;
675 if (el
>= items
.length
) {
676 tl
= cast(int)items
.length
-visibleItems
;
682 final @property int knobYOfs () nothrow @trusted {
684 if (items
.length
< visibleItems
+1) return 0;
685 int hgt
= imgrc
.height
-37-19-18;
686 if (hgt
< 1) return 0;
687 return cast(int)(hgt
*topitem
/(items
.length
-visibleItems
));
688 // normal knob: 52,53 8x18 at width-15,[19..height-37)
691 final void paintScrollKnob () {
692 int xofs
= imgrc
.width
-15;
693 int yofs
= knobYOfs
+19;
694 img
.blitRect(xofs
, yofs
, GxRect(52, 53, 8, 18));
697 final void setListClip () {
698 gxClipRect
= GxRect(11, 20, imgrc
.width
-11-20+1, imgrc
.height
-20-38);
701 final void paintList () {
702 char[1024] buf
= void;
705 if (items
.length
< 1) return;
709 while (idx
< items
.length
&& ty
< imgrc
.height
-38) {
710 import core
.stdc
.stdio
: snprintf
;
711 auto len
= snprintf(buf
.ptr
, buf
.length
, "%d. %.*s", idx
+1, cast(uint)items
.ptr
[idx
].title
.length
, items
.ptr
[idx
].title
.ptr
);
712 if (curitem
== idx
) gxFillRect(gxClipRect
.x0
, ty
, gxClipRect
.width
, gxTextHeightUtf
, skinPListColCurrentBG
);
713 gxDrawTextUtf(gxClipRect
.x0
, ty
, buf
[0..len
], (curitem
!= idx ? skinPListColNormal
: skinPListColCurrent
));
714 ty
+= gxTextHeightUtf
;
719 protected override void paintBackground () {
721 gxFillRect(imgrc
, skinPListColNormalBG
);
722 auto irc
= GxRect(0, 0, 25, 20);
723 if (!active
) irc
.moveBy(0, 21);
728 while (tx
< imgrc
.width
-irc
.width
) {
729 img
.blitRect(tx
, 0, irc
);
736 img
.blitRect((imgrc
.width
-irtitle
.width
)/2, 0, irtitle
);
739 img
.blitRect(0, 0, irc
);
742 img
.blitRect(imgrc
.width
-irc
.width
, 0, irc
);
743 // left and right bars
748 while (ty
< imgrc
.height
-irc
.height
) {
751 img
.blitRect(0, ty
, irc
);
754 img
.blitRect(imgrc
.width
-irc
.width
, ty
, irc
);
758 // left part: 0,72 125x38
759 // right part: 126,72 150x38
762 ty
= imgrc
.height
-irc
.height
;
768 while (tx
< imgrc
.width
-150) {
769 img
.blitRect(tx
, ty
, irc
);
772 // bottom left corner
776 img
.blitRect(0, ty
, irc
);
777 // bottom right corner
780 img
.blitRect(imgrc
.width
-irc
.width
, ty
, irc
);
785 protected override void paintFinished () {
788 // close pressed: 52,42 9x9 at width-10
789 // normal knob: 52,53 8x18 at width-15,[19..height-37)
790 // pressed knob: 61,53 8x18 at width-15,[19..height-37)
791 // resize knob at top-right, size is 20x20
794 override bool onKeyPost (KeyEvent event
) {
795 if (event
== "C-Q") { if (event
.pressed
) postQuitEvent(); return true; }
799 override bool onMousePost (MouseEvent event
) {
801 if (gxClipRect
.inside(event
.x
, event
.y
)) {
803 scope(exit
) normTop();
804 if (event
.type
== MouseEventType
.buttonPressed
&& event
.button
== MouseButton
.wheelUp
) {
805 if (curitem
> 0) --curitem
;
808 if (event
.type
== MouseEventType
.buttonPressed
&& event
.button
== MouseButton
.wheelDown
) {