initial commit; PoC
[amper.git] / amperskin.d
blobcc6740e3a2e63b10ec9ef227558d1db3133a643a
1 module amperskin is aliced;
3 import arsd.bmp;
4 import arsd.color;
5 import arsd.image;
6 import arsd.simpledisplay;
8 import iv.cmdcon;
9 import iv.cmdcongl;
10 import iv.strex;
11 import iv.vfs;
13 import egfx;
16 // ////////////////////////////////////////////////////////////////////////// //
17 class QuitEvent {}
20 void postQuitEvent () {
21 if (glconMainWindow is null) return;
22 if (!glconMainWindow.eventQueued!QuitEvent) glconMainWindow.postEvent(new QuitEvent());
26 // ////////////////////////////////////////////////////////////////////////// //
27 //__gshared string skinfile = "WastedAMP1-0.zip";
28 __gshared string skinfile = "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 AmpWindow ampMain;
49 // ////////////////////////////////////////////////////////////////////////// //
50 AmpWindow createAmpMain () {
51 AmpWindow w = new AmpWindow(skinMainImg);
52 w.addWidget(new AmpWidget(skinMainCButtons, 16, 88, GxRect(0, 0, 23, 18), "song_prev"));
53 w.addWidget(new AmpWidget(skinMainCButtons, 39, 88, GxRect(23, 0, 23, 18), "song_play"));
54 w.addWidget(new AmpWidget(skinMainCButtons, 62, 88, GxRect(46, 0, 23, 18), "song_pause"));
55 w.addWidget(new AmpWidget(skinMainCButtons, 85, 88, GxRect(69, 0, 23, 18), "song_stop"));
56 w.addWidget(new AmpWidget(skinMainCButtons, 108, 88, GxRect(92, 0, 22, 18), "song_next"));
57 w.addWidget(new AmpWidget(skinMainCButtons, 136, 88, GxRect(114, 0, 22, 16), "song_eject"));
58 w.addWidget(new AmpWidgetToggle(&modeShuffle, skinMainShufRep, 164, 89, GxRect(28, 0, 46, 15), "mode_shuffle toggle"));
59 w.addWidget(new AmpWidgetToggle(&modeRepeat, skinMainShufRep, 210, 89, GxRect(0, 0, 28, 15), "mode_repeat toggle"));
60 return w;
64 // ////////////////////////////////////////////////////////////////////////// //
65 void loadSkin () {
66 static bool isGoodImageExtension (ConString fname) {
67 auto epos = fname.lastIndexOf('.');
68 if (epos < 0) return false;
69 fname = fname[epos..$];
70 return
71 fname.strEquCI(".bmp") ||
72 fname.strEquCI(".png") ||
73 fname.strEquCI(".jpg") ||
74 fname.strEquCI(".jpeg") ||
75 false;
78 void loadImage (EImage* img, string fname) {
79 auto epos = fname.lastIndexOf('.');
80 if (epos < 0) throw new Exception("invalid skin image '"~fname~"'");
81 auto ext = fname[epos..$];
83 auto fl = VFile(fname);
84 auto sz = fl.size;
85 if (sz < 1 || sz > 1024*1024*8) throw new Exception("invalid skin image '"~fname~"'");
87 auto data = new ubyte[](cast(uint)sz);
88 scope(exit) delete data;
89 fl.rawReadExact(data);
91 MemoryImage ximg;
92 scope(exit) delete ximg;
94 if (ext.strEquCI(".bmp")) ximg = readBmp(data);
95 else if (ext.strEquCI(".png")) ximg = imageFromPng(readPng(data));
96 else if (ext.strEquCI(".jpg") || ext.strEquCI(".jpeg")) ximg = readJpegFromMemory(data);
97 else throw new Exception("unknown image format for '"~fname~"'");
98 assert(ximg !is null);
100 *img = new EImage(ximg);
103 ampMain = null;
105 static struct ImageInfo {
106 string name;
107 EImage* img;
109 ImageInfo[$] imglist = [
110 ImageInfo("main", &skinMainImg),
111 ImageInfo("cbuttons", &skinMainCButtons),
112 ImageInfo("shufrep", &skinMainShufRep),
115 foreach (ref ii; imglist) { *(ii.img) = null; }
117 auto vid = vfsAddPak(skinfile, "skin:");
118 scope(exit) vfsRemovePak(vid);
119 foreach (auto de; vfsFileList) {
120 if (!isGoodImageExtension(de.name)) continue;
122 auto fname = de.name;
123 auto xpos = fname.lastIndexOf('/');
124 if (xpos >= 0) fname = fname[xpos+1..$];
125 else if (fname.startsWith("skin:")) fname = fname[5..$];
126 xpos = fname.lastIndexOf('.');
127 if (xpos <= 0) continue;
128 auto iname = fname[0..xpos];
129 //conwriteln("[", de.name, "]:[", fname, "]:[", iname, "]");
131 foreach (ref ii; imglist) {
132 if (*(ii.img) is null && iname.strEquCI(ii.name)) {
133 conwriteln(de.name);
134 loadImage(ii.img, de.name);
135 break;
140 foreach (ref ii; imglist) if (*(ii.img) is null) throw new Exception("invalid skin (missing '"~ii.name~"' image)");
142 ampMain = createAmpMain();
146 // ////////////////////////////////////////////////////////////////////////// //
147 final class EImage {
148 int width;
149 int height;
150 uint[] data;
152 this (MemoryImage img) {
153 if (img is null) throw new Exception("can't create eimage from nothing");
154 width = img.width;
155 height = img.height;
156 data.length = width*height;
157 { import core.memory : GC; GC.setAttr(data.ptr, GC.BlkAttr.NO_INTERIOR|GC.BlkAttr.NO_SCAN); }
158 foreach (immutable int dy; 0..img.height) {
159 foreach (immutable int dx; 0..img.width) {
160 auto cc = img.getPixel(dx, dy);
161 data[dy*width+dx] = gxrgb(cc.r, cc.g, cc.b);
166 final void blitAt (int x, int y) nothrow @trusted @nogc {
168 if (x < 0 || y < 0 || x+width > VBufWidth || y+height > VBufHeight) return;
169 const(uint)* src = data.ptr;
170 uint* dst = vglTexBuf+y*VBufWidth+x;
171 immutable int w = width;
172 foreach (immutable dy; 0..height) {
173 dst[0..w] = src[0..w];
174 src += width;
175 dst += VBufWidth;
178 blitRect(x, y, GxRect(0, 0, width, height));
181 final void blitRect (int destx, int desty, GxRect srect) nothrow @trusted @nogc {
182 srect.intersect(GxRect(0, 0, width, height));
183 if (srect.empty) return;
184 assert(srect.x0 >= 0 && srect.x0 < width);
185 assert(srect.y0 >= 0 && srect.y0 < height);
186 assert(srect.x1 >= 0 && srect.x1 < width);
187 assert(srect.y1 >= 0 && srect.y1 < height);
188 int dx = destx;
189 int dy = desty;
190 int dw = srect.width;
191 int dh = srect.height;
192 int skipLeft, skipTop;
193 if (!gxClipRect.clipHVStripes(dx, dy, dw, dh, &skipLeft, &skipTop)) return;
194 if (!GxRect(0, 0, VBufWidth, VBufHeight).clipHVStripes(dx, dy, dw, dh, &skipLeft, &skipTop)) return;
195 assert(dw > 0 && dw <= width);
196 assert(dh > 0 && dh <= height);
197 assert(skipLeft+srect.x0+dw <= width);
198 assert(skipTop+srect.y0+dh <= height);
199 const(uint)* src = data.ptr+(skipTop+srect.y0)*width+(skipLeft+srect.x0);
200 uint* dst = vglTexBuf+dy*VBufWidth+dx;
201 while (dh-- > 0) {
202 assert(src+dw <= data.ptr+width*height);
203 assert(dst+dw <= vglTexBuf+VBufWidth*VBufHeight);
204 dst[0..dw] = src[0..dw];
205 src += width;
206 dst += VBufWidth;
212 // ////////////////////////////////////////////////////////////////////////// //
213 class AmpWindow {
214 EImage img;
215 GxRect imgrc;
216 AmpWidget[] widgets;
217 int mActiveWidget = -1;
219 this (EImage aimg) {
220 if (aimg is null) throw new Exception("no image for window");
221 img = aimg;
222 imgrc = GxRect(0, 0, img.width, img.height);
225 AmpWidget addWidget (AmpWidget w) {
226 if (w is null) return null;
227 if (w.parent !is null) throw new Exception("widget already owned");
228 widgets ~= w;
229 w.parent = this;
230 return w;
233 final @property AmpWidget activeWidget () pure nothrow @safe @nogc { pragma(inline, true); return (mActiveWidget >= 0 && mActiveWidget < widgets.length ? widgets[mActiveWidget] : null); }
235 final @property void activeWidget (AmpWidget w) pure nothrow @safe @nogc {
236 if (w is null || w.parent !is this) { mActiveWidget = -1; return; }
237 foreach (immutable idx, AmpWidget ww; widgets) if (ww is w) { mActiveWidget = cast(int)idx; return; }
238 mActiveWidget = -1;
241 final AmpWidget widgetAt (int x, int y) pure nothrow @safe @nogc {
242 foreach_reverse (AmpWidget w; widgets) {
243 if (w.rc.inside(x, y)) return w;
245 return null;
248 void onPaint () {
249 img.blitAt(0, 0);
250 foreach (AmpWidget w; widgets) w.onPaint();
251 gxClipRect = GxRect(109, 24, 157, 12);
252 gxDrawTextUtf(109, 24-2, "Testing text... жопф", gxRGB!(0, 255, 0));
255 bool onKeyPre (KeyEvent event) { return false; }
257 bool onKeyPost (KeyEvent event) {
258 if (event == "C-Q") { if (event.pressed) postQuitEvent(); return true; }
259 return false;
262 bool onKey (KeyEvent event) {
263 if (onKeyPre(event)) return true;
264 if (auto aw = activeWidget) {
265 if (aw.onKey(event)) return true;
267 if (onKeyPost(event)) return true;
268 return false;
271 bool onMousePre (MouseEvent event) { return false; }
272 bool onMousePost (MouseEvent event) { return false; }
274 bool onMouse (MouseEvent event) {
275 bool wasAwOrAct = false;
276 if (onMousePre(event)) return true;
277 if (auto aw = activeWidget) {
278 if (aw.onMouse(event)) return true;
279 wasAwOrAct = true;
281 if (auto ww = widgetAt(event.x, event.y)) {
282 if (ww.onMouse(event)) return true;
283 wasAwOrAct = true;
285 if (onMousePost(event)) return true;
286 if (wasAwOrAct && event.type != MouseEventType.motion) return true;
287 return false;
292 // ////////////////////////////////////////////////////////////////////////// //
293 class AmpWidget {
294 AmpWindow parent;
295 EImage img;
296 GxRect rc;
297 GxRect imgrc;
298 string cmd;
300 void delegate () onAction;
302 protected this () {}
304 this (EImage aimg, int x, int y, GxRect aimgrc, string acmd=null) {
305 if (aimg is null) throw new Exception("no image for window");
306 img = aimg;
307 imgrc = aimgrc;
308 rc = GxRect(x, y, aimgrc.width, aimgrc.height);
309 cmd = acmd;
312 void onPaint () {
313 bool active = (parent !is null && parent.activeWidget is this);
314 auto irc = imgrc;
315 if (active) irc.moveBy(0, imgrc.height);
316 img.blitRect(rc.x0, rc.y0, irc);
319 bool onKey (KeyEvent event) {
320 return false;
323 bool onMouse (MouseEvent event) {
324 if (event.type == MouseEventType.buttonPressed && event.button == MouseButton.left) {
325 parent.activeWidget = this;
326 return true;
328 if (event.type == MouseEventType.buttonReleased && event.button == MouseButton.left) {
329 if (parent.activeWidget is this) {
330 parent.activeWidget = null;
331 if (rc.inside(event.x, event.y)) {
332 concmd(cmd);
333 if (onAction !is null) onAction();
335 return true;
338 return false;
343 // ////////////////////////////////////////////////////////////////////////// //
344 class AmpWidgetToggle : AmpWidget {
345 bool* checked;
346 private bool checkedvar;
348 this (bool* cvp, EImage aimg, int x, int y, GxRect aimgrc, string acmd=null) {
349 super(aimg, x, y, aimgrc, acmd);
350 if (cvp is null) cvp = &checkedvar;
351 checked = cvp;
354 override void onPaint () {
355 bool active = (parent !is null && parent.activeWidget is this);
356 auto irc = imgrc;
357 if (*checked) irc.moveBy(0, imgrc.height*2);
358 if (active) irc.moveBy(0, imgrc.height);
359 img.blitRect(rc.x0, rc.y0, irc);