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 UIMenuItem : UIControl;
70 void delegate (UIMenuItem me) onSelected;
71 class!Object tagClass;
74 static final UIMenuItem Create (UIPane aowner, string acaption, string ahelp, optional void delegate (UIMenuItem me) seldg) {
75 UIMenuItem res = SpawnObject(UIMenuItem);
76 res.caption = acaption;
78 res.onSelected = seldg;
79 if (aowner) aowner.append(res);
84 override void drawWithOfs (int xofs, int yofs, int scale) {
85 setTextColor(active ? 0xff_ff_00 : 0xff_ff_ff);
86 //owner.sprStore.renderText(xofs, yofs, caption, scale);
87 owner.sprStore.renderTextHiChar(xofs, yofs, caption, scale, hotCharOfs, buildColor(0xff_7f_00));
92 override bool onEvent (ref event_t evt) {
93 if (evt.type == ev_keydown) {
94 switch (evt.keycode) {
95 case K_ENTER: case K_SPACE:
96 if (onSelected) onSelected(self);
109 // ////////////////////////////////////////////////////////////////////////// //
110 class UIKeyBinding : UIControl;
114 int maxCaptionLength; // not scaled!
116 void delegate (int bindidx, int newval) onValueChanged;
119 static final UIKeyBinding Create (UIPane aowner, int *abind0, int *abind1, string acaption, string ahelp) {
120 UIKeyBinding res = SpawnObject(UIKeyBinding);
121 res.caption = acaption;
127 res.fixCaptionWidth();
133 override void blur () {
139 private transient int tmpMaxCapWidth;
141 final void fixCaptionWidth () {
144 owner.forEachControl(delegate bool (UIControl c) {
145 auto kb = UIKeyBinding(c);
147 tmpMaxCapWidth = max(tmpMaxCapWidth, owner.sprStore.getTextWidth(kb.caption));
149 return false; // continue
151 owner.forEachControl(delegate bool (UIControl c) {
152 auto kb = UIKeyBinding(c);
153 if (kb) kb.maxCaptionLength = tmpMaxCapWidth;
154 return false; // continue
159 final void fixCurrBindIndex () {
161 owner.forEachControl(delegate bool (UIControl c) {
163 auto kb = UIKeyBinding(c);
164 if (kb) kb.currBindIdx = currBindIdx;
166 return false; // continue
171 override void drawWithOfs (int xofs, int yofs, int scale) {
172 setTextColor(active ? 0xff_ff_00 : 0x9f_9f_9f);
173 //owner.sprStore.renderText(xofs, yofs, caption, scale);
174 owner.sprStore.renderTextHiChar(xofs, yofs, caption, scale, hotCharOfs, buildColor(0xff_7f_00));
175 xofs += (maxCaptionLength+12)*scale;
176 setTextColor(0xff_ff_ff);
178 string bname = (bind0 && *bind0 ? GetInputKeyStr(*bind0) : "---");
179 if (active) setTextColor(currBindIdx == 0 ? 0x00_ff_00 : 0xff_ff_ff); else setTextColor(0xff_ff_00);
180 if (waitingKey && currBindIdx == 0) { bname = "???"; setTextColor(0xff_00_00); }
181 owner.sprStore.renderText(xofs, yofs, bname, scale);
182 xofs += owner.sprStore.getTextWidth("WWWWWWWWWW")*scale;
184 bname = (bind1 && *bind1 ? GetInputKeyStr(*bind1) : "---");
185 if (active) setTextColor(currBindIdx == 1 ? 0x00_ff_00 : 0xff_ff_ff); else setTextColor(0xff_ff_00);
186 if (waitingKey && currBindIdx == 1) { bname = "???"; setTextColor(0xff_00_00); }
187 owner.sprStore.renderText(xofs, yofs, bname, scale);
191 private transient int tmpKeyCode;
194 override bool onEvent (ref event_t evt) {
196 if (evt.type == ev_keyup) return true;
197 if (evt.type == ev_keydown) {
198 if (evt.keycode >= K_F1 && evt.keycode <= K_F12) return true;
199 switch (evt.keycode) {
200 case K_ESCAPE: waitingKey = false; return true;
202 // remove keycode from other bindings
203 tmpKeyCode = evt.keycode;
204 owner.forEachControl(delegate bool (UIControl c) {
205 auto kb = UIKeyBinding(c);
207 if (kb.bind0 && *kb.bind0 == tmpKeyCode) {
209 if (kb.onValueChanged) kb.onValueChanged(0, 0);
211 if (kb.bind1 && *kb.bind1 == tmpKeyCode) {
213 if (kb.onValueChanged) kb.onValueChanged(1, 0);
216 return false; // continue
218 if (currBindIdx == 0) *bind0 = evt.keycode; else *bind1 = evt.keycode;
220 if (onValueChanged) onValueChanged(currBindIdx, evt.keycode);
225 if (evt.type == ev_keydown) {
226 switch (evt.keycode) {
228 if (currBindIdx == 0 && !bind0) return true;
229 if (currBindIdx == 1 && !bind1) return true;
232 case K_DELETE: case K_PADDOT:
234 if (currBindIdx == 0 && bind0 && *bind0) {
236 if (onValueChanged) onValueChanged(0, 0);
238 if (currBindIdx == 1 && bind1 && *bind1) {
240 if (onValueChanged) onValueChanged(1, 0);
243 case K_LEFTARROW: case K_PAD4:
249 case K_RIGHTARROW: case K_PAD6:
266 // ////////////////////////////////////////////////////////////////////////// //
267 class UICheckBox : UIControl;
270 void delegate (int newval) onValueChanged;
273 static final UICheckBox Create (UIPane aowner, int *avalue, string acaption, string ahelp) {
274 UICheckBox res = SpawnObject(UICheckBox);
275 res.caption = acaption;
278 if (aowner) aowner.append(res);
283 override void drawWithOfs (int xofs, int yofs, int scale) {
284 setTextColor(active ? 0xff_ff_00 : 0xff_ff_ff);
285 owner.sprStore.renderText(xofs, yofs, "[ ]", scale);
287 setTextColor(0xff_00_00);
288 owner.sprStore.renderText(xofs+8*scale, yofs, "x", scale);
289 setTextColor(active ? 0xff_ff_00 : 0xff_ff_ff);
291 owner.sprStore.renderTextHiChar(xofs+owner.sprStore.getTextWidth("[ ]", scale)+4*scale, yofs, caption, scale, hotCharOfs, buildColor(0xff_7f_00));
296 override bool onEvent (ref event_t evt) {
297 if (evt.type == ev_keydown) {
298 switch (evt.keycode) {
299 case K_ENTER: case K_SPACE:
301 if (onValueChanged) onValueChanged(*value);
314 // ////////////////////////////////////////////////////////////////////////// //
315 class UIIntEnum : UIControl;
322 string delegate (int val) getNameCB;
323 void delegate (int newval) onValueChanged;
326 static final UIIntEnum Create (UIPane aowner, int *avalue, int amin, int amax, string acaption, string ahelp) {
327 UIIntEnum res = SpawnObject(UIIntEnum);
328 res.caption = acaption;
333 if (aowner) aowner.append(res);
338 override void Destroy () {
344 override void drawWithOfs (int xofs, int yofs, int scale) {
345 //owner.sprStore.renderText(xofs, yofs, "[ ]", scale);
346 int ofs = owner.sprStore.getTextWidth("[ ]", scale);
347 setTextColor(0x00_ff_00);
348 owner.sprStore.renderText(xofs+ofs/2-owner.sprStore.getTextWidth("<>", scale)/2, yofs, "<>", scale);
349 setTextColor(active ? 0xff_ff_00 : 0xff_ff_ff);
351 owner.sprStore.renderTextHiChar(xofs, yofs, caption, scale, hotCharOfs, buildColor(0xff_7f_00));
352 xofs += owner.sprStore.getTextWidth(caption, scale);
353 setTextColor(0xff_00_00);
355 if (getNameCB) val = getNameCB(*value);
356 if (!val && names.length) {
358 if (n >= 0 && n < names.length) val = names[n];
360 if (!val) val = va("%d", *value);
361 owner.sprStore.renderText(xofs, yofs, val, scale);
366 override bool onEvent (ref event_t evt) {
367 if (evt.type == ev_keydown) {
369 switch (evt.keycode) {
370 case K_LEFTARROW: case K_PAD4:
371 *value = max(vmin, *value-step);
372 if (*value != oval && onValueChanged) onValueChanged(*value);
374 case K_RIGHTARROW: case K_PAD6:
375 *value = min(vmax, *value+step);
376 if (*value != oval && onValueChanged) onValueChanged(*value);
389 // ////////////////////////////////////////////////////////////////////////// //
390 class UIPane : Object;
393 SpriteStore sprStore;
397 name fontName = 'sFontSmall';
401 array!UIControl controls;
411 override void Destroy () {
412 foreach (ref auto ctl; controls) {
413 ctl.owner = none; // so it won't try to remove itself
421 final void saveState (out SaveInfo nfo) {
427 final void restoreState (const ref SaveInfo nfo) {
428 setTopCurr(nfo.top, nfo.curr);
432 final void setupHotkeys () {
433 foreach (UIControl c; controls) {
434 if (!c.selectable) continue;
435 if (c.hotChar) continue;
438 foreach (auto sidx; 0..c.caption.length-1) {
439 auto ch = c.caption[sidx];
441 ch = c.caption[sidx+1];
442 if (ch >= "A" && ch <= "Z") ch += 32; // tolower
443 if ((ch >= "0" && ch <= "9") || (ch >= "a" && ch <= "z")) {
446 c.caption[sidx..sidx+1] = ""; // remove "~"
451 if (c.hotCharOfs >= 0) continue;
453 foreach (auto sidx; 0..c.caption.length) {
454 auto ch = c.caption[sidx];
455 if (ch >= "A" && ch <= "Z") ch += 32; // tolower
456 if ((ch >= "0" && ch <= "9") || (ch >= "a" && ch <= "z")) {
457 // check if it doesn't conflict
458 bool conflictFound = false;
459 foreach (UIControl cc; controls) {
460 if (!cc.selectable) continue;
461 if (cc.hotChar == ch) {
463 conflictFound = true;
467 if (!conflictFound) {
478 final void setTopCurr (int newtop, int newcurr) {
479 if (newcurr < 0 || newcurr >= controls.length) return;
480 if (newcurr != currItem) {
481 if (currItem >= 0 && currItem <= controls.length) controls[currItem].blur();
483 controls[currItem].focus();
485 topItem = clamp(newtop, 0, controls.length-visLines+1);
486 makeCurrentItemVisible();
490 void append (UIControl ctl) {
492 if (ctl.owner == self) return;
493 if (ctl.owner) FatalError("UIControl already owned by another pane");
496 if (ctl.selectable && currItem >= 0 && currItem < controls.length-1 && !controls[currItem].selectable) {
497 controls[currItem].blur();
498 currItem = controls.length-1;
499 controls[currItem].focus();
501 if (currItem >= 0 && currItem <= controls.length) controls[currItem].focus();
505 // return `true` from delegate to stop
506 final UIControl forEachControl (bool delegate (UIControl c) dg) {
507 if (!dg) return none;
508 foreach (UIControl c; controls) if (dg(c)) return c;
513 final int getLineHeight () {
514 sprStore.loadFont(fontName);
515 auto fh = sprStore.getFontHeight(scale);
521 final int visLines () {
522 return max(1, height/getLineHeight()-7);
526 final void makeCurrentItemVisible () {
527 auto clen = controls.length;
528 if (clen == 0 || currItem < 0 || currItem >= clen) return;
529 if (currItem < topItem) { topItem = currItem; return; }
530 auto vls = visLines();
531 if (currItem > topItem+vls-1) topItem = max(0, currItem-(vls-1));
536 for (int n = currItem-1; n >= 0; --n) {
537 if (controls[n].selectable) {
538 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
540 controls[currItem].focus();
548 for (int n = currItem+1; n < controls.length; ++n) {
549 if (controls[n].selectable) {
550 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
552 controls[currItem].focus();
559 void drawWithOfs (int xofs, int yofs) {
560 makeCurrentItemVisible();
561 sprStore.loadFont(fontName);
565 Video.color = 0x3f_ff_ff_00;
566 auto spr = sprStore['sPageUp'];
567 spr.frames[0].tex.blitAt(xofs-9*scale, yofs, scale);
570 if (topItem+visLines < controls.length) {
571 Video.color = 0x3f_ff_ff_00;
572 auto spr = sprStore['sPageDown'];
573 spr.frames[0].tex.blitAt(xofs-9*scale, yofs+getLineHeight()*(visLines-1)-scale, scale);
577 foreach (int n; topItem..topItem+visLines) {
579 if (n >= controls.length) break;
580 controls[n].drawWithOfs(xofs, yofs, scale);
582 yofs += getLineHeight();
586 if (currItem >= 0 && currItem < controls.length) {
587 string help = controls[currItem].help;
588 yofs += getLineHeight();
590 Video.color = 0xff_ff_00;
591 sprStore.renderTextWrapped(xofs, yofs, 36*8*scale, help, scale);
598 bool onEvent (ref event_t evt) {
599 if (currItem >= 0 && currItem < controls.length) {
600 if (controls[currItem].onEvent(evt)) return true;
602 if (evt.type == ev_keydown) {
603 switch (evt.keycode) {
604 case K_UPARROW: case K_PAD8: goItemUp(); return true;
605 case K_DOWNARROW: case K_PAD2: goItemDown(); return true;
606 case K_HOME: case K_PAD7:
607 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
609 if (currItem >= 0 && currItem < controls.length) controls[currItem].focus();
611 case K_END: case K_PAD1:
612 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
613 if (controls.length) currItem = controls.length-1;
614 if (currItem >= 0 && currItem < controls.length) controls[currItem].focus();
616 case K_PAGEUP: case K_PAD9:
617 if (controls.length < visLines) return true;
618 makeCurrentItemVisible();
619 controls[currItem].blur();
620 if (currItem != topItem) {
623 currItem = max(0, currItem-(visLines-1));
625 controls[currItem].focus();
627 case K_PAGEDOWN: case K_PAD3:
628 if (controls.length < visLines) return true;
629 makeCurrentItemVisible();
630 controls[currItem].blur();
631 if (currItem != topItem+visLines-1) {
632 currItem = min(controls.length-1, topItem+visLines-1);
634 currItem = min(controls.length-1, currItem+(visLines-1));
636 controls[currItem].focus();
640 if ((evt.keycode >= K_N0 && evt.keycode <= K_N9) || (evt.keycode >= K_a && evt.keycode <= K_z)) {
642 foreach (auto cidx, UIControl c; controls) {
643 if (!c.selectable) continue;
644 if (c.hotChar == evt.keycode) {
650 if (newidx >= 0 && newidx < controls.length && newidx != currItem) {
651 if (currItem >= 0 && currItem < controls.length) controls[currItem].blur();
653 if (currItem >= 0 && currItem < controls.length) controls[currItem].focus();
655 return (newidx >= 0);