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 auto sbox = owner.sprStore[*value ? 'sBoxChecked' : 'sBox'];
309 auto frm = sbox.frames[0];
310 frm.tex.blitAt(xofs, yofs, scale);
311 owner.sprStore.renderTextHiChar(xofs+(frm.width+2)*scale, yofs, caption, scale, hotCharOfs, buildColor(0xff_7f_00));
313 owner.sprStore.renderText(xofs, yofs, "[ ]", scale);
315 setTextColor(0xff_00_00);
316 owner.sprStore.renderText(xofs+8*scale, yofs, "x", scale);
317 setTextColor(active ? 0xff_ff_00 : 0xff_ff_ff);
319 owner.sprStore.renderTextHiChar(xofs+owner.sprStore.getTextWidth("[ ]", scale)+4*scale, yofs, caption, scale, hotCharOfs, buildColor(0xff_7f_00));
325 override bool onEvent (ref event_t evt) {
326 if (evt.type == ev_keydown) {
327 switch (evt.keycode) {
328 case K_ENTER: case K_SPACE:
330 if (onValueChanged) onValueChanged(*value);
343 // ////////////////////////////////////////////////////////////////////////// //
344 class UIIntEnum : UIControl;
351 string delegate (int val) getNameCB;
352 void delegate (int newval) onValueChanged;
355 static final UIIntEnum Create (UIPane aowner, int *avalue, int amin, int amax, string acaption, string ahelp) {
356 UIIntEnum res = SpawnObject(UIIntEnum);
357 res.caption = acaption;
362 if (aowner) aowner.append(res);
367 override void Destroy () {
373 override void drawWithOfs (int xofs, int yofs, int scale) {
374 //owner.sprStore.renderText(xofs, yofs, "[ ]", scale);
375 auto sbox = owner.sprStore['sBox'];
376 auto frm = sbox.frames[0];
377 int ofs = (frm.width+2)*scale;
378 setTextColor(0x00_ff_00);
379 owner.sprStore.renderText(xofs, yofs, "<", scale);
380 owner.sprStore.renderText(xofs+ofs-owner.sprStore.getTextWidth(">", scale), yofs, ">", scale);
381 setTextColor(active ? 0xff_ff_00 : 0xff_ff_ff);
383 owner.sprStore.renderTextHiChar(xofs, yofs, caption, scale, hotCharOfs, buildColor(0xff_7f_00));
384 xofs += owner.sprStore.getTextWidth(caption, scale);
385 setTextColor(0xff_00_00);
387 if (getNameCB) val = getNameCB(*value);
388 if (!val && names.length) {
390 if (n >= 0 && n < names.length) val = names[n];
392 if (!val) val = va("%d", *value);
393 owner.sprStore.renderText(xofs, yofs, val, scale);
398 override bool onEvent (ref event_t evt) {
399 if (evt.type == ev_keydown) {
401 switch (evt.keycode) {
402 case K_LEFTARROW: case K_PAD4:
403 *value = max(vmin, *value-step);
404 if (*value != oval && onValueChanged) onValueChanged(*value);
406 case K_RIGHTARROW: case K_PAD6:
407 *value = min(vmax, *value+step);
408 if (*value != oval && onValueChanged) onValueChanged(*value);
421 // ////////////////////////////////////////////////////////////////////////// //
422 class UIPane : Object;
425 SpriteStore sprStore;
429 name fontName = 'sFontSmall';
433 array!UIControl controls;
443 override void Destroy () {
444 foreach (ref auto ctl; controls) {
445 ctl.owner = none; // so it won't try to remove itself
453 final void saveState (out SaveInfo nfo) {
459 final void restoreState (const ref SaveInfo nfo) {
460 setTopCurr(nfo.top, nfo.curr);
464 final void setupHotkeys () {
465 foreach (UIControl c; controls) {
466 if (!c.selectable) continue;
467 if (c.hotChar) continue;
470 foreach (auto sidx; 0..c.caption.length-1) {
471 auto ch = c.caption[sidx];
473 ch = c.caption[sidx+1];
474 if (ch >= "A" && ch <= "Z") ch += 32; // tolower
475 if ((ch >= "0" && ch <= "9") || (ch >= "a" && ch <= "z")) {
478 c.caption[sidx..sidx+1] = ""; // remove "~"
483 if (c.hotCharOfs >= 0) continue;
485 foreach (auto sidx; 0..c.caption.length) {
486 auto ch = c.caption[sidx];
487 if (ch >= "A" && ch <= "Z") ch += 32; // tolower
488 if ((ch >= "0" && ch <= "9") || (ch >= "a" && ch <= "z")) {
489 // check if it doesn't conflict
490 bool conflictFound = false;
491 foreach (UIControl cc; controls) {
492 if (!cc.selectable) continue;
493 if (cc.hotChar == ch) {
495 conflictFound = true;
499 if (!conflictFound) {
510 final void setTopCurr (int newtop, int newcurr) {
511 if (newcurr < 0 || newcurr >= controls.length) return;
512 if (newcurr != currItem) {
513 if (currItem >= 0 && currItem <= controls.length) controls[currItem].blur();
515 controls[currItem].focus();
517 topItem = clamp(newtop, 0, controls.length-visLines+1);
518 makeCurrentItemVisible();
522 void append (UIControl ctl) {
524 if (ctl.owner == self) return;
525 if (ctl.owner) FatalError("UIControl already owned by another pane");
528 if (ctl.selectable && currItem >= 0 && currItem < controls.length-1 && !controls[currItem].selectable) {
529 controls[currItem].blur();
530 currItem = controls.length-1;
531 controls[currItem].focus();
533 if (currItem >= 0 && currItem <= controls.length) controls[currItem].focus();
537 // return `true` from delegate to stop
538 final UIControl forEachControl (bool delegate (UIControl c) dg) {
539 if (!dg) return none;
540 foreach (UIControl c; controls) if (dg(c)) return c;
545 final int getLineHeight () {
546 sprStore.loadFont(fontName);
547 auto fh = sprStore.getFontHeight(scale);
553 final int visLines () {
554 return max(1, height/getLineHeight()-7);
558 final void makeCurrentItemVisible () {
559 auto clen = controls.length;
560 if (clen == 0 || currItem < 0 || currItem >= clen) return;
561 if (currItem < topItem) { topItem = currItem; return; }
562 auto vls = visLines();
563 if (currItem > topItem+vls-1) topItem = max(0, currItem-(vls-1));
568 for (int n = currItem-1; n >= 0; --n) {
569 if (controls[n].selectable) {
570 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
572 controls[currItem].focus();
578 while (n < controls.length && !controls[n].selectable) ++n;
579 if (n >= controls.length) return;
580 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
582 if (currItem >= 0 && currItem < controls.length) controls[currItem].focus();
587 for (int n = currItem+1; n < controls.length; ++n) {
588 if (controls[n].selectable) {
589 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
591 controls[currItem].focus();
598 void drawWithOfs (int xofs, int yofs) {
599 makeCurrentItemVisible();
600 sprStore.loadFont(fontName);
604 Video.color = 0x3f_ff_ff_00;
605 auto spr = sprStore['sPageUp'];
606 spr.frames[0].tex.blitAt(xofs-9*scale, yofs, scale);
609 if (topItem+visLines < controls.length) {
610 Video.color = 0x3f_ff_ff_00;
611 auto spr = sprStore['sPageDown'];
612 spr.frames[0].tex.blitAt(xofs-9*scale, yofs+getLineHeight()*(visLines-1)-scale, scale);
616 foreach (int n; topItem..topItem+visLines) {
618 if (n >= controls.length) break;
619 controls[n].drawWithOfs(xofs, yofs, scale);
621 yofs += getLineHeight();
625 if (currItem >= 0 && currItem < controls.length) {
626 string help = controls[currItem].help;
627 yofs += getLineHeight();
629 Video.color = 0xff_ff_00;
630 sprStore.renderTextWrapped(xofs, yofs, 36*8*scale, help, scale);
637 bool onEvent (ref event_t evt) {
638 if (currItem >= 0 && currItem < controls.length) {
639 if (controls[currItem].onEvent(evt)) return true;
641 if (evt.type == ev_keydown) {
642 switch (evt.keycode) {
643 case K_UPARROW: case K_PAD8: goItemUp(); return true;
644 case K_DOWNARROW: case K_PAD2: goItemDown(); return true;
645 case K_HOME: case K_PAD7:
646 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
648 if (currItem >= 0 && currItem < controls.length) controls[currItem].focus();
650 case K_END: case K_PAD1:
651 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
652 if (controls.length) currItem = controls.length-1;
653 if (currItem >= 0 && currItem < controls.length) controls[currItem].focus();
655 case K_PAGEUP: case K_PAD9:
656 if (controls.length < visLines) return true;
657 auto oldItemIndex = currItem;
658 makeCurrentItemVisible();
659 controls[currItem].blur();
660 if (currItem != topItem) {
663 currItem = max(0, currItem-(visLines-1));
665 while (currItem >= 0 && currItem != oldItemIndex && !controls[currItem].selectable) --currItem;
666 if (currItem < 0) { topItem = 0; currItem = oldItemIndex; }
667 if (currItem >= 0 && currItem < controls.length) controls[currItem].focus();
669 case K_PAGEDOWN: case K_PAD3:
670 if (controls.length < visLines) return true;
671 oldItemIndex = currItem;
672 makeCurrentItemVisible();
673 controls[currItem].blur();
674 if (currItem != topItem+visLines-1) {
675 currItem = min(controls.length-1, topItem+visLines-1);
677 currItem = min(controls.length-1, currItem+(visLines-1));
679 while (currItem < controls.length && currItem != oldItemIndex && !controls[currItem].selectable) ++currItem;
680 if (currItem >= controls.length) currItem = oldItemIndex;
681 if (currItem >= 0 && currItem < controls.length) controls[currItem].focus();
685 if ((evt.keycode >= K_N0 && evt.keycode <= K_N9) || (evt.keycode >= K_a && evt.keycode <= K_z)) {
687 foreach (auto cidx, UIControl c; controls) {
688 if (!c.selectable) continue;
689 if (c.hotChar == evt.keycode) {
695 if (newidx >= 0 && newidx < controls.length && newidx != currItem) {
696 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
698 if (currItem >= 0 && currItem < controls.length) controls[currItem].focus();
700 return (newidx >= 0);