1 /**************************************************************************
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 3
5 * of the License, or (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 **************************************************************************/
12 class UIControl : Object;
21 int hotCharOfs = -1; // in chars
24 override void Destroy () {
25 if (owner) owner.remove(self);
31 final int buildColor (int clr) {
32 return (clr&0xff_ff_ff)|(Video.color&0xff_00_00_00);
36 void setTextColor (int clr) {
39 alpha = int(GetTickCount()*200)%255;
40 alpha = clamp(alpha > 127 ? 255-(alpha-128)*2 : alpha*2, 0, 255);
41 alpha = clamp(alpha-alpha/4, 0, 255);
43 Video.color = (clr&0xff_ff_ff)|(alpha<<24);
47 void drawWithOfs (int xofs, int yofs, int scale) {
62 bool onEvent (ref event_t evt) {
67 // ////////////////////////////////////////////////////////////////////////// //
68 class UILabel : UIControl;
71 static final UILabel Create (UIPane aowner, string acaption) {
72 UILabel res = SpawnObject(UILabel);
73 res.caption = acaption;
74 if (aowner) aowner.append(res);
79 override void drawWithOfs (int xofs, int yofs, int scale) {
80 setTextColor(0x00_ff_ff);
81 owner.sprStore.renderText(xofs+12*scale, yofs, caption, scale);
90 // ////////////////////////////////////////////////////////////////////////// //
91 class UIMenuItem : UIControl;
93 void delegate (UIMenuItem me) onSelected;
94 class!Object tagClass;
97 static final UIMenuItem Create (UIPane aowner, string acaption, string ahelp, optional void delegate (UIMenuItem me) seldg) {
98 UIMenuItem res = SpawnObject(UIMenuItem);
99 res.caption = acaption;
101 res.onSelected = seldg;
102 if (aowner) aowner.append(res);
107 override void drawWithOfs (int xofs, int yofs, int scale) {
108 setTextColor(active ? 0xff_ff_00 : 0xff_ff_ff);
109 //owner.sprStore.renderText(xofs, yofs, caption, scale);
110 owner.sprStore.renderTextHiChar(xofs, yofs, caption, scale, hotCharOfs, buildColor(0xff_7f_00));
115 override bool onEvent (ref event_t evt) {
116 if (evt.type == ev_keydown) {
117 switch (evt.keycode) {
118 case K_ENTER: case K_SPACE:
119 if (onSelected) onSelected(self);
132 // ////////////////////////////////////////////////////////////////////////// //
133 class UIKeyBinding : UIControl;
137 int maxCaptionLength; // not scaled!
139 void delegate (int bindidx, int newval) onValueChanged;
142 static final UIKeyBinding Create (UIPane aowner, int *abind0, int *abind1, string acaption, string ahelp) {
143 UIKeyBinding res = SpawnObject(UIKeyBinding);
144 res.caption = acaption;
150 res.fixCaptionWidth();
156 override void blur () {
162 private transient int tmpMaxCapWidth;
164 final void fixCaptionWidth () {
167 owner.forEachControl(delegate bool (UIControl c) {
168 auto kb = UIKeyBinding(c);
170 tmpMaxCapWidth = max(tmpMaxCapWidth, owner.sprStore.getTextWidth(kb.caption));
172 return false; // continue
174 owner.forEachControl(delegate bool (UIControl c) {
175 auto kb = UIKeyBinding(c);
176 if (kb) kb.maxCaptionLength = tmpMaxCapWidth;
177 return false; // continue
182 final void fixCurrBindIndex () {
184 owner.forEachControl(delegate bool (UIControl c) {
186 auto kb = UIKeyBinding(c);
187 if (kb) kb.currBindIdx = currBindIdx;
189 return false; // continue
194 override void drawWithOfs (int xofs, int yofs, int scale) {
195 setTextColor(active ? 0xff_ff_00 : 0x9f_9f_9f);
196 //owner.sprStore.renderText(xofs, yofs, caption, scale);
197 owner.sprStore.renderTextHiChar(xofs, yofs, caption, scale, hotCharOfs, buildColor(0xff_7f_00));
198 xofs += (maxCaptionLength+12)*scale;
199 setTextColor(0xff_ff_ff);
201 string bname = (bind0 && *bind0 ? GetInputKeyStrName(*bind0) : "---");
202 if (active) setTextColor(currBindIdx == 0 ? 0x00_ff_00 : 0xff_ff_ff); else setTextColor(0xff_ff_00);
203 if (waitingKey && currBindIdx == 0) { bname = "???"; setTextColor(0xff_00_00); }
204 owner.sprStore.renderText(xofs, yofs, bname, scale);
205 xofs += owner.sprStore.getTextWidth("WWWWWWWWWW")*scale;
207 bname = (bind1 && *bind1 ? GetInputKeyStrName(*bind1) : "---");
208 if (active) setTextColor(currBindIdx == 1 ? 0x00_ff_00 : 0xff_ff_ff); else setTextColor(0xff_ff_00);
209 if (waitingKey && currBindIdx == 1) { bname = "???"; setTextColor(0xff_00_00); }
210 owner.sprStore.renderText(xofs, yofs, bname, scale);
214 private transient int tmpKeyCode;
217 override bool onEvent (ref event_t evt) {
219 if (evt.type == ev_keyup) return true;
220 if (evt.type == ev_keydown) {
221 if (evt.keycode >= K_F1 && evt.keycode <= K_F12) return true;
222 switch (evt.keycode) {
223 case K_ESCAPE: waitingKey = false; return true;
225 // remove keycode from other bindings
226 tmpKeyCode = evt.keycode;
227 owner.forEachControl(delegate bool (UIControl c) {
228 auto kb = UIKeyBinding(c);
230 if (kb.bind0 && *kb.bind0 == tmpKeyCode) {
232 if (kb.onValueChanged) kb.onValueChanged(0, 0);
234 if (kb.bind1 && *kb.bind1 == tmpKeyCode) {
236 if (kb.onValueChanged) kb.onValueChanged(1, 0);
239 return false; // continue
241 if (currBindIdx == 0) *bind0 = evt.keycode; else *bind1 = evt.keycode;
243 if (onValueChanged) onValueChanged(currBindIdx, evt.keycode);
248 if (evt.type == ev_keydown) {
249 switch (evt.keycode) {
251 if (currBindIdx == 0 && !bind0) return true;
252 if (currBindIdx == 1 && !bind1) return true;
255 case K_DELETE: case K_PADDOT:
257 if (currBindIdx == 0 && bind0 && *bind0) {
259 if (onValueChanged) onValueChanged(0, 0);
261 if (currBindIdx == 1 && bind1 && *bind1) {
263 if (onValueChanged) onValueChanged(1, 0);
266 case K_LEFTARROW: case K_PAD4:
272 case K_RIGHTARROW: case K_PAD6:
289 // ////////////////////////////////////////////////////////////////////////// //
290 class UICheckBox : UIControl;
293 void delegate (int newval) onValueChanged;
296 static final UICheckBox Create (UIPane aowner, int *avalue, string acaption, string ahelp) {
297 UICheckBox res = SpawnObject(UICheckBox);
298 res.caption = acaption;
301 if (aowner) aowner.append(res);
306 override void drawWithOfs (int xofs, int yofs, int scale) {
307 setTextColor(active ? 0xff_ff_00 : 0xff_ff_ff);
308 owner.sprStore.renderText(xofs, yofs, "[ ]", scale);
310 setTextColor(0xff_00_00);
311 owner.sprStore.renderText(xofs+8*scale, yofs, "x", scale);
312 setTextColor(active ? 0xff_ff_00 : 0xff_ff_ff);
314 owner.sprStore.renderTextHiChar(xofs+owner.sprStore.getTextWidth("[ ]", scale)+4*scale, yofs, caption, scale, hotCharOfs, buildColor(0xff_7f_00));
319 override bool onEvent (ref event_t evt) {
320 if (evt.type == ev_keydown) {
321 switch (evt.keycode) {
322 case K_ENTER: case K_SPACE:
324 if (onValueChanged) onValueChanged(*value);
337 // ////////////////////////////////////////////////////////////////////////// //
338 class UIIntEnum : UIControl;
345 string delegate (int val) getNameCB;
346 void delegate (int newval) onValueChanged;
349 static final UIIntEnum Create (UIPane aowner, int *avalue, int amin, int amax, string acaption, string ahelp) {
350 UIIntEnum res = SpawnObject(UIIntEnum);
351 res.caption = acaption;
356 if (aowner) aowner.append(res);
361 override void Destroy () {
367 override void drawWithOfs (int xofs, int yofs, int scale) {
368 //owner.sprStore.renderText(xofs, yofs, "[ ]", scale);
369 int ofs = owner.sprStore.getTextWidth("[ ]", scale);
370 setTextColor(0x00_ff_00);
371 owner.sprStore.renderText(xofs+ofs/2-owner.sprStore.getTextWidth("<>", scale)/2, yofs, "<>", scale);
372 setTextColor(active ? 0xff_ff_00 : 0xff_ff_ff);
374 owner.sprStore.renderTextHiChar(xofs, yofs, caption, scale, hotCharOfs, buildColor(0xff_7f_00));
375 xofs += owner.sprStore.getTextWidth(caption, scale);
376 setTextColor(0xff_00_00);
378 if (getNameCB) val = getNameCB(*value);
379 if (!val && names.length) {
381 if (n >= 0 && n < names.length) val = names[n];
383 if (!val) val = va("%d", *value);
384 owner.sprStore.renderText(xofs, yofs, val, scale);
389 override bool onEvent (ref event_t evt) {
390 if (evt.type == ev_keydown) {
392 switch (evt.keycode) {
393 case K_LEFTARROW: case K_PAD4:
394 *value = max(vmin, *value-step);
395 if (*value != oval && onValueChanged) onValueChanged(*value);
397 case K_RIGHTARROW: case K_PAD6:
398 *value = min(vmax, *value+step);
399 if (*value != oval && onValueChanged) onValueChanged(*value);
412 // ////////////////////////////////////////////////////////////////////////// //
413 class UIPane : Object;
416 SpriteStore sprStore;
420 name fontName = 'sFontSmall';
424 array!UIControl controls;
434 override void Destroy () {
435 foreach (ref auto ctl; controls) {
436 ctl.owner = none; // so it won't try to remove itself
444 final void saveState (out SaveInfo nfo) {
450 final void restoreState (const ref SaveInfo nfo) {
451 setTopCurr(nfo.top, nfo.curr);
455 final void setupHotkeys () {
456 foreach (UIControl c; controls) {
457 if (!c.selectable) continue;
458 if (c.hotChar) continue;
461 foreach (auto sidx; 0..c.caption.length-1) {
462 auto ch = c.caption[sidx];
464 ch = c.caption[sidx+1];
465 if (ch >= "A" && ch <= "Z") ch += 32; // tolower
466 if ((ch >= "0" && ch <= "9") || (ch >= "a" && ch <= "z")) {
469 c.caption[sidx..sidx+1] = ""; // remove "~"
474 if (c.hotCharOfs >= 0) continue;
476 foreach (auto sidx; 0..c.caption.length) {
477 auto ch = c.caption[sidx];
478 if (ch >= "A" && ch <= "Z") ch += 32; // tolower
479 if ((ch >= "0" && ch <= "9") || (ch >= "a" && ch <= "z")) {
480 // check if it doesn't conflict
481 bool conflictFound = false;
482 foreach (UIControl cc; controls) {
483 if (!cc.selectable) continue;
484 if (cc.hotChar == ch) {
486 conflictFound = true;
490 if (!conflictFound) {
501 final void setTopCurr (int newtop, int newcurr) {
502 if (newcurr < 0 || newcurr >= controls.length) return;
503 if (newcurr != currItem) {
504 if (currItem >= 0 && currItem <= controls.length) controls[currItem].blur();
506 controls[currItem].focus();
508 topItem = clamp(newtop, 0, controls.length-visLines+1);
509 makeCurrentItemVisible();
513 void append (UIControl ctl) {
515 if (ctl.owner == self) return;
516 if (ctl.owner) FatalError("UIControl already owned by another pane");
519 if (ctl.selectable && currItem >= 0 && currItem < controls.length-1 && !controls[currItem].selectable) {
520 controls[currItem].blur();
521 currItem = controls.length-1;
522 controls[currItem].focus();
524 if (currItem >= 0 && currItem <= controls.length) controls[currItem].focus();
528 // return `true` from delegate to stop
529 final UIControl forEachControl (bool delegate (UIControl c) dg) {
530 if (!dg) return none;
531 foreach (UIControl c; controls) if (dg(c)) return c;
536 final int getLineHeight () {
537 sprStore.loadFont(fontName);
538 auto fh = sprStore.getFontHeight(scale);
544 final int visLines () {
545 return max(1, height/getLineHeight()-7);
549 final void makeCurrentItemVisible () {
550 auto clen = controls.length;
551 if (clen == 0 || currItem < 0 || currItem >= clen) return;
552 if (currItem < topItem) { topItem = currItem; return; }
553 auto vls = visLines();
554 if (currItem > topItem+vls-1) topItem = max(0, currItem-(vls-1));
559 for (int n = currItem-1; n >= 0; --n) {
560 if (controls[n].selectable) {
561 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
563 controls[currItem].focus();
569 while (n < controls.length && !controls[n].selectable) ++n;
570 if (n >= controls.length) return;
571 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
573 if (currItem >= 0 && currItem < controls.length) controls[currItem].focus();
578 for (int n = currItem+1; n < controls.length; ++n) {
579 if (controls[n].selectable) {
580 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
582 controls[currItem].focus();
589 void drawWithOfs (int xofs, int yofs) {
590 makeCurrentItemVisible();
591 sprStore.loadFont(fontName);
595 Video.color = 0x3f_ff_ff_00;
596 auto spr = sprStore['sPageUp'];
597 spr.frames[0].tex.blitAt(xofs-9*scale, yofs, scale);
600 if (topItem+visLines < controls.length) {
601 Video.color = 0x3f_ff_ff_00;
602 auto spr = sprStore['sPageDown'];
603 spr.frames[0].tex.blitAt(xofs-9*scale, yofs+getLineHeight()*(visLines-1)-scale, scale);
607 foreach (int n; topItem..topItem+visLines) {
609 if (n >= controls.length) break;
610 controls[n].drawWithOfs(xofs, yofs, scale);
612 yofs += getLineHeight();
616 if (currItem >= 0 && currItem < controls.length) {
617 string help = controls[currItem].help;
618 yofs += getLineHeight();
620 Video.color = 0xff_ff_00;
621 sprStore.renderTextWrapped(xofs, yofs, 36*8*scale, help, scale);
628 bool onEvent (ref event_t evt) {
629 if (currItem >= 0 && currItem < controls.length) {
630 if (controls[currItem].onEvent(evt)) return true;
632 if (evt.type == ev_keydown) {
633 switch (evt.keycode) {
634 case K_UPARROW: case K_PAD8: goItemUp(); return true;
635 case K_DOWNARROW: case K_PAD2: goItemDown(); return true;
636 case K_HOME: case K_PAD7:
637 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
639 if (currItem >= 0 && currItem < controls.length) controls[currItem].focus();
641 case K_END: case K_PAD1:
642 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
643 if (controls.length) currItem = controls.length-1;
644 if (currItem >= 0 && currItem < controls.length) controls[currItem].focus();
646 case K_PAGEUP: case K_PAD9:
647 if (controls.length < visLines) return true;
648 auto oldItemIndex = currItem;
649 makeCurrentItemVisible();
650 controls[currItem].blur();
651 if (currItem != topItem) {
654 currItem = max(0, currItem-(visLines-1));
656 while (currItem >= 0 && currItem != oldItemIndex && !controls[currItem].selectable) --currItem;
657 if (currItem < 0) { topItem = 0; currItem = oldItemIndex; }
658 if (currItem >= 0 && currItem < controls.length) controls[currItem].focus();
660 case K_PAGEDOWN: case K_PAD3:
661 if (controls.length < visLines) return true;
662 oldItemIndex = currItem;
663 makeCurrentItemVisible();
664 controls[currItem].blur();
665 if (currItem != topItem+visLines-1) {
666 currItem = min(controls.length-1, topItem+visLines-1);
668 currItem = min(controls.length-1, currItem+(visLines-1));
670 while (currItem < controls.length && currItem != oldItemIndex && !controls[currItem].selectable) ++currItem;
671 if (currItem >= controls.length) currItem = oldItemIndex;
672 if (currItem >= 0 && currItem < controls.length) controls[currItem].focus();
676 if ((evt.keycode >= K_N0 && evt.keycode <= K_N9) || (evt.keycode >= K_a && evt.keycode <= K_z)) {
678 foreach (auto cidx, UIControl c; controls) {
679 if (!c.selectable) continue;
680 if (c.hotChar == evt.keycode) {
686 if (newidx >= 0 && newidx < controls.length && newidx != currItem) {
687 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
689 if (currItem >= 0 && currItem < controls.length) controls[currItem].focus();
691 return (newidx >= 0);