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 skinMainImg
;
41 __gshared EImage skinMainCButtons
;
42 __gshared EImage skinMainShufRep
;
45 // ////////////////////////////////////////////////////////////////////////// //
46 __gshared AmpMainWindow ampMain
;
49 // ////////////////////////////////////////////////////////////////////////// //
51 static bool isGoodImageExtension (ConString fname
) {
52 auto epos
= fname
.lastIndexOf('.');
53 if (epos
< 0) return false;
54 fname
= fname
[epos
..$];
56 fname
.strEquCI(".bmp") ||
57 fname
.strEquCI(".png") ||
58 fname
.strEquCI(".jpg") ||
59 fname
.strEquCI(".jpeg") ||
63 void loadImage (EImage
* img
, string fname
) {
64 auto epos
= fname
.lastIndexOf('.');
65 if (epos
< 0) throw new Exception("invalid skin image '"~fname
~"'");
66 auto ext
= fname
[epos
..$];
68 auto fl
= VFile(fname
);
70 if (sz
< 1 || sz
> 1024*1024*8) throw new Exception("invalid skin image '"~fname
~"'");
72 auto data
= new ubyte[](cast(uint)sz
);
73 scope(exit
) delete data
;
74 fl
.rawReadExact(data
);
77 scope(exit
) delete ximg
;
79 if (ext
.strEquCI(".bmp")) ximg
= readBmp(data
);
80 else if (ext
.strEquCI(".png")) ximg
= imageFromPng(readPng(data
));
81 else if (ext
.strEquCI(".jpg") || ext
.strEquCI(".jpeg")) ximg
= readJpegFromMemory(data
);
82 else throw new Exception("unknown image format for '"~fname
~"'");
83 assert(ximg
!is null);
85 *img
= new EImage(ximg
);
90 static struct ImageInfo
{
94 ImageInfo
[$] imglist
= [
95 ImageInfo("main", &skinMainImg
),
96 ImageInfo("cbuttons", &skinMainCButtons
),
97 ImageInfo("shufrep", &skinMainShufRep
),
100 foreach (ref ii
; imglist
) { *(ii
.img
) = null; }
102 auto vid
= vfsAddPak(skinfile
, "skin:");
103 scope(exit
) vfsRemovePak(vid
);
104 foreach (auto de; vfsFileList
) {
105 if (!isGoodImageExtension(de.name
)) continue;
107 auto fname
= de.name
;
108 auto xpos
= fname
.lastIndexOf('/');
109 if (xpos
>= 0) fname
= fname
[xpos
+1..$];
110 else if (fname
.startsWith("skin:")) fname
= fname
[5..$];
111 xpos
= fname
.lastIndexOf('.');
112 if (xpos
<= 0) continue;
113 auto iname
= fname
[0..xpos
];
114 //conwriteln("[", de.name, "]:[", fname, "]:[", iname, "]");
116 foreach (ref ii
; imglist
) {
117 if (*(ii
.img
) is null && iname
.strEquCI(ii
.name
)) {
119 loadImage(ii
.img
, de.name
);
125 foreach (ref ii
; imglist
) if (*(ii
.img
) is null) throw new Exception("invalid skin (missing '"~ii
.name
~"' image)");
127 ampMain
= new AmpMainWindow();
131 // ////////////////////////////////////////////////////////////////////////// //
137 this (MemoryImage img
) {
138 if (img
is null) throw new Exception("can't create eimage from nothing");
141 data
.length
= width
*height
;
142 { import core
.memory
: GC
; GC
.setAttr(data
.ptr
, GC
.BlkAttr
.NO_INTERIOR|GC
.BlkAttr
.NO_SCAN
); }
143 foreach (immutable int dy
; 0..img
.height
) {
144 foreach (immutable int dx
; 0..img
.width
) {
145 auto cc
= img
.getPixel(dx
, dy
);
146 data
[dy
*width
+dx
] = gxrgb(cc
.r
, cc
.g
, cc
.b
);
151 final void blitAt (int x
, int y
) nothrow @trusted @nogc {
153 if (x < 0 || y < 0 || x+width > VBufWidth || y+height > VBufHeight) return;
154 const(uint)* src = data.ptr;
155 uint* dst = vglTexBuf+y*VBufWidth+x;
156 immutable int w = width;
157 foreach (immutable dy; 0..height) {
158 dst[0..w] = src[0..w];
163 blitRect(x
, y
, GxRect(0, 0, width
, height
));
166 final void blitRect (int destx
, int desty
, GxRect srect
) nothrow @trusted @nogc {
167 srect
.intersect(GxRect(0, 0, width
, height
));
168 if (srect
.empty
) return;
169 assert(srect
.x0
>= 0 && srect
.x0
< width
);
170 assert(srect
.y0
>= 0 && srect
.y0
< height
);
171 assert(srect
.x1
>= 0 && srect
.x1
< width
);
172 assert(srect
.y1
>= 0 && srect
.y1
< height
);
175 int dw = srect
.width
;
176 int dh
= srect
.height
;
177 int skipLeft
, skipTop
;
178 if (!gxClipRect
.clipHVStripes(dx
, dy
, dw, dh
, &skipLeft
, &skipTop
)) return;
179 if (!GxRect(0, 0, VBufWidth
, VBufHeight
).clipHVStripes(dx
, dy
, dw, dh
, &skipLeft
, &skipTop
)) return;
180 assert(dw > 0 && dw <= width
);
181 assert(dh
> 0 && dh
<= height
);
182 assert(skipLeft
+srect
.x0
+dw <= width
);
183 assert(skipTop
+srect
.y0
+dh
<= height
);
184 const(uint)* src
= data
.ptr
+(skipTop
+srect
.y0
)*width
+(skipLeft
+srect
.x0
);
185 uint* dst
= vglTexBuf
+dy
*VBufWidth
+dx
;
187 assert(src
+dw <= data
.ptr
+width
*height
);
188 assert(dst
+dw <= vglTexBuf
+VBufWidth
*VBufHeight
);
189 dst
[0..dw] = src
[0..dw];
197 // ////////////////////////////////////////////////////////////////////////// //
202 int mActiveWidget
= -1;
205 if (aimg
is null) throw new Exception("no image for window");
207 imgrc
= GxRect(0, 0, img
.width
, img
.height
);
210 AmpWidget
addWidget (AmpWidget w
) {
211 if (w
is null) return null;
212 if (w
.parent
!is null) throw new Exception("widget already owned");
218 final @property AmpWidget
activeWidget () pure nothrow @safe @nogc { pragma(inline
, true); return (mActiveWidget
>= 0 && mActiveWidget
< widgets
.length ? widgets
[mActiveWidget
] : null); }
220 final @property void activeWidget (AmpWidget w
) pure nothrow @safe @nogc {
221 if (w
is null || w
.parent
!is this) { mActiveWidget
= -1; return; }
222 foreach (immutable idx
, AmpWidget ww
; widgets
) if (ww
is w
) { mActiveWidget
= cast(int)idx
; return; }
226 final AmpWidget
widgetAt (int x
, int y
) pure nothrow @safe @nogc {
227 foreach_reverse (AmpWidget w
; widgets
) {
228 if (w
.rc
.inside(x
, y
)) return w
;
235 foreach (AmpWidget w
; widgets
) w
.onPaint();
238 bool onKeyPre (KeyEvent event
) { return false; }
240 bool onKeyPost (KeyEvent event
) {
241 if (event
== "C-Q") { if (event
.pressed
) postQuitEvent(); return true; }
245 bool onKey (KeyEvent event
) {
246 if (onKeyPre(event
)) return true;
247 if (auto aw
= activeWidget
) {
248 if (aw
.onKey(event
)) return true;
250 if (onKeyPost(event
)) return true;
254 bool onMousePre (MouseEvent event
) { return false; }
255 bool onMousePost (MouseEvent event
) { return false; }
257 bool onMouse (MouseEvent event
) {
258 bool wasAwOrAct
= false;
259 if (onMousePre(event
)) return true;
260 auto aw
= activeWidget
;
262 if (aw
.onMouse(event
)) return true;
265 if (auto ww
= widgetAt(event
.x
, event
.y
)) {
267 if (ww
.onMouse(event
)) return true;
271 if (onMousePost(event
)) return true;
272 if (wasAwOrAct
&& event
.type
!= MouseEventType
.motion
) return true;
278 // ////////////////////////////////////////////////////////////////////////// //
286 void delegate () onAction
;
290 this (EImage aimg
, int x
, int y
, GxRect aimgrc
, string acmd
=null) {
291 if (aimg
is null) throw new Exception("no image for window");
294 rc
= GxRect(x
, y
, aimgrc
.width
, aimgrc
.height
);
299 bool active
= (parent
!is null && parent
.activeWidget
is this);
301 if (active
) irc
.moveBy(0, imgrc
.height
);
302 img
.blitRect(rc
.x0
, rc
.y0
, irc
);
305 bool onKey (KeyEvent event
) {
309 bool onMouse (MouseEvent event
) {
310 if (event
.type
== MouseEventType
.buttonPressed
&& event
.button
== MouseButton
.left
) {
311 parent
.activeWidget
= this;
314 if (event
.type
== MouseEventType
.buttonReleased
&& event
.button
== MouseButton
.left
) {
315 if (parent
.activeWidget
is this) {
316 parent
.activeWidget
= null;
317 if (rc
.inside(event
.x
, event
.y
)) {
319 if (onAction
!is null) onAction();
329 // ////////////////////////////////////////////////////////////////////////// //
330 class AmpWidgetToggle
: AmpWidget
{
332 private bool checkedvar
;
334 this (bool* cvp
, EImage aimg
, int x
, int y
, GxRect aimgrc
, string acmd
=null) {
335 super(aimg
, x
, y
, aimgrc
, acmd
);
336 if (cvp
is null) cvp
= &checkedvar
;
340 override void onPaint () {
341 bool active
= (parent
!is null && parent
.activeWidget
is this);
343 if (*checked
) irc
.moveBy(0, imgrc
.height
*2);
344 if (active
) irc
.moveBy(0, imgrc
.height
);
345 img
.blitRect(rc
.x0
, rc
.y0
, irc
);
350 // ////////////////////////////////////////////////////////////////////////// //
351 class AmpWidgetSongTitle
: AmpWidget
{
353 int titleofsmovedir
= 1;
354 int titlemovepause
= 8;
356 bool dragging
= false;
358 this(T
:const(char)[]) (GxRect arc
, T atitle
) {
359 static if (is(T
== typeof(null))) title
= null;
360 else static if (is(T
== string
)) title
= atitle
;
361 else title
= atitle
.idup
;
366 override void onPaint () {
369 gxDrawTextUtf(rc
.x0
-titleofs
, rc
.y0
-2, title
, gxRGB
!(0, 255, 0));
373 final void scrollTitle () {
374 if (parent
.activeWidget
!is this) dragging
= false;
375 if (dragging
) return;
376 if (titlemovepause
-- >= 0) return;
378 if (title
.length
== 0) { titleofs
= 0; titleofsmovedir
= 1; titlemovepause
= 8; return; }
379 auto tw
= gxTextWidthUtf(title
);
380 titleofs
+= titleofsmovedir
;
381 if (titleofs
<= 0) { titleofs
= 0; titleofsmovedir
= 1; titlemovepause
= 8; }
382 else if (titleofs
>= tw
-rc
.width
) { titleofs
= tw
-rc
.width
; titleofsmovedir
= -1; titlemovepause
= 8; }
385 override bool onKey (KeyEvent event
) {
389 override bool onMouse (MouseEvent event
) {
390 if (event
.type
== MouseEventType
.buttonPressed
&& event
.button
== MouseButton
.left
) {
391 parent
.activeWidget
= this;
395 if (event
.type
== MouseEventType
.buttonReleased
&& event
.button
== MouseButton
.left
) {
396 if (parent
.activeWidget
is this) {
398 if (title
.length
!= 0) {
400 auto tw
= gxTextWidthUtf(title
);
401 if (titleofs
<= 0) { titleofs
= 0; titleofsmovedir
= 1; }
402 else if (titleofs
>= tw
-rc
.width
) { titleofs
= tw
-rc
.width
; titleofsmovedir
= -1; }
404 parent
.activeWidget
= null;
408 if (dragging
&& event
.type
== MouseEventType
.motion
&& event
.modifierState
&ModifierState
.leftButtonDown
) {
409 if (title
.length
!= 0 && event
.dx
!= 0) {
410 titleofs
-= event
.dx
;
411 //conwriteln("dx=", event.dx, "; titleofs=", titleofs);
412 titleofsmovedir
= (event
.dx
< 0 ?
1 : -1);
413 auto tw
= gxTextWidthUtf(title
);
414 if (titleofs
<= 0) titleofs
= 0;
415 else if (titleofs
>= tw
-rc
.width
) titleofs
= tw
-rc
.width
;
423 // ////////////////////////////////////////////////////////////////////////// //
424 class AmpMainWindow
: AmpWindow
{
425 AmpWidgetSongTitle wtitle
;
429 addWidget(new AmpWidget(skinMainCButtons
, 16, 88, GxRect(0, 0, 23, 18), "song_prev"));
430 addWidget(new AmpWidget(skinMainCButtons
, 39, 88, GxRect(23, 0, 23, 18), "song_play"));
431 addWidget(new AmpWidget(skinMainCButtons
, 62, 88, GxRect(46, 0, 23, 18), "song_pause"));
432 addWidget(new AmpWidget(skinMainCButtons
, 85, 88, GxRect(69, 0, 23, 18), "song_stop"));
433 addWidget(new AmpWidget(skinMainCButtons
, 108, 88, GxRect(92, 0, 22, 18), "song_next"));
434 addWidget(new AmpWidget(skinMainCButtons
, 136, 88, GxRect(114, 0, 22, 16), "song_eject"));
435 addWidget(new AmpWidgetToggle(&modeShuffle
, skinMainShufRep
, 164, 89, GxRect(28, 0, 46, 15), "mode_shuffle toggle"));
436 addWidget(new AmpWidgetToggle(&modeRepeat
, skinMainShufRep
, 210, 89, GxRect(0, 0, 28, 15), "mode_repeat toggle"));
437 wtitle
= cast(AmpWidgetSongTitle
)addWidget(new AmpWidgetSongTitle(GxRect(109, 24, 157, 12), "Testing text... жопф дохуя текста"));
440 final void scrollTitle () {
441 if (wtitle
!is null) wtitle
.scrollTitle();