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
;
47 // ////////////////////////////////////////////////////////////////////////// //
48 __gshared AmpMainWindow ampMain
;
49 __gshared AmpPListWindow ampPList
;
52 // ////////////////////////////////////////////////////////////////////////// //
54 static bool isGoodImageExtension (ConString fname
) {
55 auto epos
= fname
.lastIndexOf('.');
56 if (epos
< 0) return false;
57 fname
= fname
[epos
..$];
59 fname
.strEquCI(".bmp") ||
60 fname
.strEquCI(".png") ||
61 fname
.strEquCI(".jpg") ||
62 fname
.strEquCI(".jpeg") ||
66 void loadImage (EImage
* img
, string fname
) {
67 auto epos
= fname
.lastIndexOf('.');
68 if (epos
< 0) throw new Exception("invalid skin image '"~fname
~"'");
69 auto ext
= fname
[epos
..$];
71 auto fl
= VFile(fname
);
73 if (sz
< 1 || sz
> 1024*1024*8) throw new Exception("invalid skin image '"~fname
~"'");
75 auto data
= new ubyte[](cast(uint)sz
);
76 scope(exit
) delete data
;
77 fl
.rawReadExact(data
);
80 scope(exit
) delete ximg
;
82 if (ext
.strEquCI(".bmp")) ximg
= readBmp(data
);
83 else if (ext
.strEquCI(".png")) ximg
= imageFromPng(readPng(data
));
84 else if (ext
.strEquCI(".jpg") || ext
.strEquCI(".jpeg")) ximg
= readJpegFromMemory(data
);
85 else throw new Exception("unknown image format for '"~fname
~"'");
86 assert(ximg
!is null);
88 *img
= new EImage(ximg
);
91 AmpPListWindow
.SavedData pldata
;
92 if (ampPList
!is null) pldata
= ampPList
.saveData();
93 scope(exit
) if (ampPList
!is null) ampPList
.restoreData(pldata
);
100 static struct ImageInfo
{
104 ImageInfo
[$] imglist
= [
105 ImageInfo("main", &skinImgMain
),
106 ImageInfo("cbuttons", &skinImgCButtons
),
107 ImageInfo("shufrep", &skinImgShufRep
),
108 ImageInfo("numbers", &numsex
),
109 ImageInfo("nums_ex", &skinImgNums
),
110 ImageInfo("pledit", &skinImgPlaylist
),
113 foreach (ref ii
; imglist
) { *(ii
.img
) = null; }
115 auto vid
= vfsAddPak(skinfile
, "skin:");
116 scope(exit
) vfsRemovePak(vid
);
117 foreach (auto de; vfsFileList
) {
118 if (!isGoodImageExtension(de.name
)) continue;
120 auto fname
= de.name
;
121 auto xpos
= fname
.lastIndexOf('/');
122 if (xpos
>= 0) fname
= fname
[xpos
+1..$];
123 else if (fname
.startsWith("skin:")) fname
= fname
[5..$];
124 xpos
= fname
.lastIndexOf('.');
125 if (xpos
<= 0) continue;
126 auto iname
= fname
[0..xpos
];
127 //conwriteln("[", de.name, "]:[", fname, "]:[", iname, "]");
129 foreach (ref ii
; imglist
) {
130 if (*(ii
.img
) is null && iname
.strEquCI(ii
.name
)) {
131 //conwriteln(de.name);
132 loadImage(ii
.img
, de.name
);
138 if (skinImgNums
is null) {
139 foreach (ref ii
; imglist
) if (ii
.name
== "numbers" && *(ii
.img
) !is null) skinImgNums
= *(ii
.img
);
142 foreach (ref ii
; imglist
) {
143 if (ii
.name
== "numbers" && *(ii
.img
) is null) *(ii
.img
) = skinImgNums
;
144 if (ii
.name
== "nums_ex" && *(ii
.img
) is null) *(ii
.img
) = numsex
;
145 if (*(ii
.img
) is null) throw new Exception("invalid skin (missing '"~ii
.name
~"' image)");
148 ampMain
= new AmpMainWindow();
149 ampPList
= new AmpPListWindow();
153 // ////////////////////////////////////////////////////////////////////////// //
159 this (MemoryImage img
) {
160 if (img
is null) throw new Exception("can't create eimage from nothing");
163 data
.length
= width
*height
;
164 { import core
.memory
: GC
; GC
.setAttr(data
.ptr
, GC
.BlkAttr
.NO_INTERIOR|GC
.BlkAttr
.NO_SCAN
); }
165 foreach (immutable int dy
; 0..img
.height
) {
166 foreach (immutable int dx
; 0..img
.width
) {
167 auto cc
= img
.getPixel(dx
, dy
);
168 data
[dy
*width
+dx
] = gxrgb(cc
.r
, cc
.g
, cc
.b
);
173 final void blitAt (int x
, int y
) nothrow @trusted @nogc {
175 if (x < 0 || y < 0 || x+width > VBufWidth || y+height > VBufHeight) return;
176 const(uint)* src = data.ptr;
177 uint* dst = vglTexBuf+y*VBufWidth+x;
178 immutable int w = width;
179 foreach (immutable dy; 0..height) {
180 dst[0..w] = src[0..w];
185 blitRect(x
, y
, GxRect(0, 0, width
, height
));
188 final void blitRect (int destx
, int desty
, GxRect srect
) nothrow @trusted @nogc {
189 srect
.intersect(GxRect(0, 0, width
, height
));
190 if (srect
.empty
) return;
191 assert(srect
.x0
>= 0 && srect
.x0
< width
);
192 assert(srect
.y0
>= 0 && srect
.y0
< height
);
193 assert(srect
.x1
>= 0 && srect
.x1
< width
);
194 assert(srect
.y1
>= 0 && srect
.y1
< height
);
197 int dw = srect
.width
;
198 int dh
= srect
.height
;
199 int skipLeft
, skipTop
;
200 if (!gxClipRect
.clipHVStripes(dx
, dy
, dw, dh
, &skipLeft
, &skipTop
)) return;
201 if (!GxRect(0, 0, VBufWidth
, VBufHeight
).clipHVStripes(dx
, dy
, dw, dh
, &skipLeft
, &skipTop
)) return;
202 assert(dw > 0 && dw <= width
);
203 assert(dh
> 0 && dh
<= height
);
204 assert(skipLeft
+srect
.x0
+dw <= width
);
205 assert(skipTop
+srect
.y0
+dh
<= height
);
206 const(uint)* src
= data
.ptr
+(skipTop
+srect
.y0
)*width
+(skipLeft
+srect
.x0
);
207 uint* dst
= vglTexBuf
+dy
*VBufWidth
+dx
;
209 assert(src
+dw <= data
.ptr
+width
*height
);
210 assert(dst
+dw <= vglTexBuf
+VBufWidth
*VBufHeight
);
211 dst
[0..dw] = src
[0..dw];
219 // ////////////////////////////////////////////////////////////////////////// //
224 int mActiveWidget
= -1;
227 if (aimg
is null) throw new Exception("no image for window");
229 imgrc
= GxRect(0, 0, img
.width
, img
.height
);
232 AmpWidget
addWidget (AmpWidget w
) {
233 if (w
is null) return null;
234 if (w
.parent
!is null) throw new Exception("widget already owned");
240 final @property AmpWidget
activeWidget () pure nothrow @safe @nogc { pragma(inline
, true); return (mActiveWidget
>= 0 && mActiveWidget
< widgets
.length ? widgets
[mActiveWidget
] : null); }
242 final @property void activeWidget (AmpWidget w
) pure nothrow @safe @nogc {
243 if (w
is null || w
.parent
!is this) { mActiveWidget
= -1; return; }
244 foreach (immutable idx
, AmpWidget ww
; widgets
) if (ww
is w
) { mActiveWidget
= cast(int)idx
; return; }
248 final AmpWidget
widgetAt (int x
, int y
) pure nothrow @safe @nogc {
249 foreach_reverse (AmpWidget w
; widgets
) {
250 if (w
.rc
.inside(x
, y
)) return w
;
257 foreach (AmpWidget w
; widgets
) w
.onPaint();
260 bool onKeyPre (KeyEvent event
) { return false; }
262 bool onKeyPost (KeyEvent event
) {
263 if (event
== "C-Q") { if (event
.pressed
) postQuitEvent(); return true; }
267 bool onKey (KeyEvent event
) {
268 if (onKeyPre(event
)) return true;
269 if (auto aw
= activeWidget
) {
270 if (aw
.onKey(event
)) return true;
272 if (onKeyPost(event
)) return true;
276 bool onMousePre (MouseEvent event
) { return false; }
277 bool onMousePost (MouseEvent event
) { return false; }
279 bool onMouse (MouseEvent event
) {
280 bool wasAwOrAct
= false;
281 if (onMousePre(event
)) return true;
282 auto aw
= activeWidget
;
284 if (aw
.onMouse(event
)) return true;
287 if (auto ww
= widgetAt(event
.x
, event
.y
)) {
289 if (ww
.onMouse(event
)) return true;
293 if (onMousePost(event
)) return true;
294 if (wasAwOrAct
&& event
.type
!= MouseEventType
.motion
) return true;
300 // ////////////////////////////////////////////////////////////////////////// //
308 void delegate () onAction
;
312 this (EImage aimg
, int x
, int y
, GxRect aimgrc
, string acmd
=null) {
313 if (aimg
is null) throw new Exception("no image for window");
316 rc
= GxRect(x
, y
, aimgrc
.width
, aimgrc
.height
);
321 bool active
= (parent
!is null && parent
.activeWidget
is this);
323 if (active
) irc
.moveBy(0, imgrc
.height
);
324 img
.blitRect(rc
.x0
, rc
.y0
, irc
);
327 bool onKey (KeyEvent event
) {
331 bool onMouse (MouseEvent event
) {
332 if (event
.type
== MouseEventType
.buttonPressed
&& event
.button
== MouseButton
.left
) {
333 parent
.activeWidget
= this;
336 if (event
.type
== MouseEventType
.buttonReleased
&& event
.button
== MouseButton
.left
) {
337 if (parent
.activeWidget
is this) {
338 parent
.activeWidget
= null;
339 if (rc
.inside(event
.x
, event
.y
)) {
341 if (onAction
!is null) onAction();
351 // ////////////////////////////////////////////////////////////////////////// //
352 class AmpWidgetToggle
: AmpWidget
{
354 private bool checkedvar
;
356 this (bool* cvp
, EImage aimg
, int x
, int y
, GxRect aimgrc
, string acmd
=null) {
357 super(aimg
, x
, y
, aimgrc
, acmd
);
358 if (cvp
is null) cvp
= &checkedvar
;
362 override void onPaint () {
363 bool active
= (parent
!is null && parent
.activeWidget
is this);
365 if (*checked
) irc
.moveBy(0, imgrc
.height
*2);
366 if (active
) irc
.moveBy(0, imgrc
.height
);
367 img
.blitRect(rc
.x0
, rc
.y0
, irc
);
372 // ////////////////////////////////////////////////////////////////////////// //
373 class AmpWidgetSongTitle
: AmpWidget
{
375 int titleofsmovedir
= 1;
376 int titlemovepause
= 8;
378 bool dragging
= false;
380 this(T
:const(char)[]) (GxRect arc
, T atitle
) {
381 static if (is(T
== typeof(null))) title
= null;
382 else static if (is(T
== string
)) title
= atitle
;
383 else title
= atitle
.idup
;
388 override void onPaint () {
391 gxDrawTextUtf(rc
.x0
-titleofs
, rc
.y0
-2, title
, gxRGB
!(0, 255, 0));
395 final void scrollTitle () {
396 if (parent
.activeWidget
!is this) dragging
= false;
397 if (dragging
) return;
398 if (titlemovepause
-- >= 0) return;
400 if (title
.length
== 0) { titleofs
= 0; titleofsmovedir
= 1; titlemovepause
= 8; return; }
401 auto tw
= gxTextWidthUtf(title
);
402 titleofs
+= titleofsmovedir
;
403 if (titleofs
<= 0) { titleofs
= 0; titleofsmovedir
= 1; titlemovepause
= 8; }
404 else if (titleofs
>= tw
-rc
.width
) { titleofs
= tw
-rc
.width
; titleofsmovedir
= -1; titlemovepause
= 8; }
407 override bool onKey (KeyEvent event
) {
411 override bool onMouse (MouseEvent event
) {
412 if (event
.type
== MouseEventType
.buttonPressed
&& event
.button
== MouseButton
.left
) {
413 parent
.activeWidget
= this;
417 if (event
.type
== MouseEventType
.buttonReleased
&& event
.button
== MouseButton
.left
) {
418 if (parent
.activeWidget
is this) {
420 if (title
.length
!= 0) {
422 auto tw
= gxTextWidthUtf(title
);
423 if (titleofs
<= 0) { titleofs
= 0; titleofsmovedir
= 1; }
424 else if (titleofs
>= tw
-rc
.width
) { titleofs
= tw
-rc
.width
; titleofsmovedir
= -1; }
426 parent
.activeWidget
= null;
430 if (dragging
&& event
.type
== MouseEventType
.motion
&& event
.modifierState
&ModifierState
.leftButtonDown
) {
431 if (title
.length
!= 0 && event
.dx
!= 0) {
432 titleofs
-= event
.dx
;
433 //conwriteln("dx=", event.dx, "; titleofs=", titleofs);
434 titleofsmovedir
= (event
.dx
< 0 ?
1 : -1);
435 auto tw
= gxTextWidthUtf(title
);
436 if (titleofs
<= 0) titleofs
= 0;
437 else if (titleofs
>= tw
-rc
.width
) titleofs
= tw
-rc
.width
;
445 // ////////////////////////////////////////////////////////////////////////// //
446 class AmpMainWindow
: AmpWindow
{
447 AmpWidgetSongTitle wtitle
;
451 addWidget(new AmpWidget(skinImgCButtons
, 16, 88, GxRect(0, 0, 23, 18), "song_prev"));
452 addWidget(new AmpWidget(skinImgCButtons
, 39, 88, GxRect(23, 0, 23, 18), "song_play"));
453 addWidget(new AmpWidget(skinImgCButtons
, 62, 88, GxRect(46, 0, 23, 18), "song_pause"));
454 addWidget(new AmpWidget(skinImgCButtons
, 85, 88, GxRect(69, 0, 23, 18), "song_stop"));
455 addWidget(new AmpWidget(skinImgCButtons
, 108, 88, GxRect(92, 0, 22, 18), "song_next"));
456 addWidget(new AmpWidget(skinImgCButtons
, 136, 88, GxRect(114, 0, 22, 16), "song_eject"));
457 addWidget(new AmpWidgetToggle(&modeShuffle
, skinImgShufRep
, 164, 89, GxRect(28, 0, 46, 15), "mode_shuffle toggle"));
458 addWidget(new AmpWidgetToggle(&modeRepeat
, skinImgShufRep
, 210, 89, GxRect(0, 0, 28, 15), "mode_repeat toggle"));
459 wtitle
= cast(AmpWidgetSongTitle
)addWidget(new AmpWidgetSongTitle(GxRect(109, 24, 157, 12), "Testing text... жопф дохуя текста"));
462 final void scrollTitle () {
463 if (wtitle
!is null) wtitle
.scrollTitle();
468 // ////////////////////////////////////////////////////////////////////////// //
472 int duration
; // in seconds
476 class AmpPListWindow
: AmpWindow
{
477 static struct SavedData
{
492 img
= skinImgPlaylist
;
498 res
.totaldur
= totaldur
;
499 res
.topitem
= topitem
;
500 res
.curitem
= curitem
;
504 void restoreData (ref SavedData sd
) {
506 totaldur
= sd
.totaldur
;
507 topitem
= sd
.topitem
;
508 curitem
= sd
.curitem
;
511 void appendListItem (PListItem li
) {
512 if (li
.duration
> 0) totaldur
+= li
.duration
;
513 auto optr
= items
.ptr
;
515 // check if runtime allocated a new memory block due to array growth
516 if (items
.ptr
!is optr
) {
517 import core
.memory
: GC
;
518 // check if our array really *IS* the head
519 if (items
.ptr
is GC
.addrOf(items
.ptr
)) {
520 // set the magic flag!
521 GC
.setAttr(items
.ptr
, GC
.BlkAttr
.NO_INTERIOR
);
526 final @property int visibleFullItems () const nothrow @trusted {
527 int hgt
= imgrc
.height
-20-38;
528 int res
= hgt
/gxTextHeightUtf
;
529 if (res
< 1) res
= 1;
533 final @property int visibleItems () const nothrow @trusted {
534 int hgt
= imgrc
.height
-20-38;
535 int res
= hgt
/gxTextHeightUtf
;
536 if (hgt
%gxTextHeightUtf
) ++res
;
537 if (res
< 1) res
= 1;
541 void normTop () nothrow @trusted {
544 int el
= topitem
+visibleItems
-1;
545 if (el
>= items
.length
) {
546 tl
= cast(int)items
.length
-visibleItems
;
552 final @property int knobYOfs () nothrow @trusted {
554 if (items
.length
< 2) return 0;
555 int hgt
= imgrc
.height
-37-19-18;
556 if (hgt
< 1) return 0;
557 return cast(int)(hgt
*topitem
/(items
.length
-1));
558 // normal knob: 52,53 8x18 at width-15,[19..height-37)
561 final void paintScrollKnob () {
562 int xofs
= imgrc
.width
-15;
563 int yofs
= knobYOfs
+19;
564 img
.blitRect(xofs
, yofs
, GxRect(52, 53, 8, 18));
567 final void paintList () {
568 char[1024] buf
= void;
569 gxClipRect
= GxRect(9, 20, imgrc
.width
-9-20, imgrc
.height
-20-38);
571 if (items
.length
< 1) return;
575 while (idx
< items
.length
&& ty
< imgrc
.height
-38) {
576 import core
.stdc
.stdio
: snprintf
;
577 auto len
= snprintf(buf
.ptr
, buf
.length
, "%d. %.*s", idx
+1, cast(uint)items
.ptr
[idx
].title
.length
, items
.ptr
[idx
].title
.ptr
);
578 if (curitem
== idx
) gxFillRect(9, ty
, gxClipRect
.width
, gxTextHeightUtf
, gxRGB
!(0, 0, 120));
579 gxDrawTextUtf(9, ty
, buf
[0..len
], gxRGB
!(0, 255, 0));
580 ty
+= gxTextHeightUtf
;
585 override void onPaint () {
587 gxFillRect(imgrc
, gxRGB
!(0, 0, 0));
588 bool active
= true; //(parent !is null && parent.activeWidget is this);
589 auto irc
= GxRect(0, 0, 25, 20);
590 if (!active
) irc
.moveBy(0, 21);
595 while (tx
+irc
.width
<= imgrc
.width
-irc
.width
) {
596 img
.blitRect(tx
, 0, irc
);
603 img
.blitRect((imgrc
.width
-irtitle
.width
)/2, 0, irtitle
);
606 img
.blitRect(0, 0, irc
);
609 img
.blitRect(imgrc
.width
-irc
.width
, 0, irc
);
610 // left and right bars
615 while (ty
+irc
.height
<= imgrc
.height
-irc
.height
) {
618 img
.blitRect(0, ty
, irc
);
621 img
.blitRect(imgrc
.width
-irc
.width
, ty
, irc
);
625 // left part: 0,72 125x38
626 // right part: 126,72 150x38
629 ty
= imgrc
.height
-irc
.height
;
635 while (tx
+irc
.width
<= imgrc
.width
-125-150) {
636 img
.blitRect(tx
, ty
, irc
);
639 // bottom left corner
643 img
.blitRect(0, ty
, irc
);
644 // bottom right corner
647 img
.blitRect(imgrc
.width
-irc
.width
, ty
, irc
);
653 foreach (AmpWidget w
; widgets
) w
.onPaint();
654 // close pressed: 52,42 9x9 at width-10
655 // normal knob: 52,53 8x18 at width-15,[19..height-37)
656 // pressed knob: 61,53 8x18 at width-15,[19..height-37)
657 // resize knob at top-right, size is 20x20