1 // Hyperbolic Rogue -- dialogs
2 // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file dialogs.cpp
5  *  \brief Implementation of various generic dialogs and elements of dialog windows
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 EX const char* COLORBAR = "###";
12 
13 EX namespace dialog {
14 
15 #if HDR
16   #define IFM(x) (mousing?"":x)
17 
18   static const int DONT_SHOW = 16;
19 
20   enum tDialogItem {diTitle, diItem, diBreak, diHelp, diInfo, diIntSlider, diSlider, diBigItem, diKeyboard};
21 
22   struct item {
23     tDialogItem type;
24     string body;
25     string value;
26     int key;
27     color_t color, colorv, colork, colors, colorc;
28     int scale;
29     double param;
30     int p1, p2, p3;
31     int position;
32     };
33 
34   struct scaler {
35     ld (*direct) (ld);
36     ld (*inverse) (ld);
37     bool positive;
38     };
39 
identity_f(ld x)40   static inline ld identity_f(ld x) { return x; }
41 
42   const static scaler identity = {identity_f, identity_f, false};
43   const static scaler logarithmic = {log, exp, true};
44   const static scaler asinhic = {asinh, sinh, false};
__anon1ab684400102() 45   const static scaler asinhic100 = {[] (ld x) { return asinh(x*100); }, [] (ld x) { return sinh(x)/100; }, false};
46 
47   struct numberEditor {
48     ld *editwhat;
49     string s;
50     ld vmin, vmax, step, dft;
51     string title, help;
52     scaler sc;
53     int *intval; ld intbuf;
54     bool animatable;
55     };
56 
57   extern numberEditor ne;
58 
scaleLog()59   inline void scaleLog() { ne.sc = logarithmic; }
scaleSinh()60   inline void scaleSinh() { ne.sc = asinhic; }
scaleSinh100()61   inline void scaleSinh100() { ne.sc = asinhic100; }
62 #endif
63 
64   EX color_t dialogcolor = 0xC0C0C0;
65 
addBack()66   EX void addBack() {
67     addItem(XLAT("go back"),
68       (cmode & sm::NUMBER) ? SDLK_RETURN :
69       ISWEB ? SDLK_BACKSPACE :
70       SDLK_ESCAPE);
71     }
72 
addHelp()73   EX void addHelp() {
74     addItem(XLAT("help"), SDLK_F1);
75     }
76 
77   EX namespace zoom {
78     int zoomf = 1, shiftx, shifty;
79     bool zoomoff = false;
80 
nozoom()81     void nozoom() {
82       zoomf = 1; shiftx = 0; shifty = 0; zoomoff = false;
83       }
84 
initzoom()85     void initzoom() {
86       zoomf = 3;
87       shiftx = -2*mousex;
88       if(mousex < vid.xres / 6) shiftx = 0;
89       if(mousex > vid.xres * 5 / 6) shiftx = -2 * vid.xres;
90       shifty = -2*mousey;
91       if(mousey < vid.yres / 6) shifty = 0;
92       if(mousey > vid.yres * 5 / 6) shifty = -2 * vid.yres;
93       }
94 
stopzoom()95     void stopzoom() { zoomoff = true; }
96 
displayfr(int x,int y,int b,int size,const string & s,color_t color,int align)97     EX bool displayfr(int x, int y, int b, int size, const string &s, color_t color, int align) {
98       return hr::displayfr(x * zoomf + shiftx, y * zoomf + shifty, b, size * zoomf, s, color, align);
99       }
100 
101     EX bool displayfr_highlight(int x, int y, int b, int size, const string &s, color_t color, int align, int hicolor IS(0xFFFF00)) {
102       bool clicked = hr::displayfr(x * zoomf + shiftx, y * zoomf + shifty, b, size * zoomf, s, color, align);
103       if(clicked) hr::displayfr(x * zoomf + shiftx, y * zoomf + shifty, b, size * zoomf, s, hicolor, align);
104       return clicked;
105       }
106     EX }
107 
108 #if CAP_MENUSCALING && CAP_SDL
handleZooming(SDL_Event & ev)109   EX void handleZooming(SDL_Event &ev) {
110     using namespace zoom;
111     if(zoomoff || !(cmode & sm::ZOOMABLE)) {
112       nozoom(); return;
113       }
114     if(ev.type == SDL_MOUSEBUTTONDOWN) initzoom();
115     if(ev.type == SDL_MOUSEBUTTONUP && zoomf > 1) stopzoom();
116     }
117 #endif
118 #if !(CAP_MENUSCALING && CAP_SDL)
handleZooming(SDL_Event & ev)119   EX void handleZooming(SDL_Event &ev) {}
120 #endif
121 
122   EX vector<item> items;
123 
lastItem()124   EX item& lastItem() { return items[items.size() - 1]; }
125 
titleItem()126   EX item& titleItem() { return items[0]; }
127 
128   EX map<int, reaction_t> key_actions;
129 
add_key_action(int key,const reaction_t & action)130   EX void add_key_action(int key, const reaction_t& action) {
131     key_actions[key] = action;
132     }
133 
add_key_action_adjust(int & key,const reaction_t & action)134   EX void add_key_action_adjust(int& key, const reaction_t& action) {
135     while(key_actions.count(key)) key++;
136     add_key_action(key, action);
137     }
138 
extend()139   EX void extend() {
140     items.back().key = items[isize(items)-2].key;
141     }
142 
add_action(const reaction_t & action)143   EX void add_action(const reaction_t& action) {
144     add_key_action_adjust(lastItem().key, action);
145     }
146 
add_action_push(const reaction_t & action)147   EX void add_action_push(const reaction_t& action) { add_action([action] { pushScreen(action); }); }
148 
add_action_push_clear(const reaction_t & action)149   EX void add_action_push_clear(const reaction_t& action) { add_action([action] { clearMessages(); pushScreen(action); }); }
150 
handler(int sym,int uni)151   EX void handler(int sym, int uni) {
152     dialog::handleNavigation(sym, uni);
153     if(doexiton(sym, uni)) popScreen();
154     }
155 
init()156   EX void init() {
157     items.clear();
158     key_actions.clear();
159     keyhandler = dialog::handler;
160     }
161 
keyname(int k)162   EX string keyname(int k) {
163     if(k == 0) return "";
164     if(k == SDLK_ESCAPE) return "Esc";
165     if(k == SDLK_F5) return "F5";
166     if(k == SDLK_F10) return "F10";
167     if(k == SDLK_F9) return "F9";
168     if(k == SDLK_F1) return "F1";
169     if(k == SDLK_HOME) return "Home";
170     if(k == SDLK_BACKSPACE) return "Backspace";
171     if(k == SDLK_RETURN) return "Enter";
172     if(k == 32) return "space";
173     if(k >= 1 && k <= 26) { string s = "Ctrl+"; s += (k+64); return s; }
174     if(k < 128) { string s; s += k; return s; }
175     if(k == 508) return "Alt+8";
176     return "?";
177     }
178 
addSlider(double d1,double d2,double d3,int key)179   EX void addSlider(double d1, double d2, double d3, int key) {
180     item it;
181     it.type = diSlider;
182     it.color = dialogcolor;
183     it.scale = 100;
184     it.key = key;
185     it.param = (d2-d1) / (d3-d1);
186     items.push_back(it);
187     }
188 
addIntSlider(int d1,int d2,int d3,int key)189   EX void addIntSlider(int d1, int d2, int d3, int key) {
190     item it;
191     it.type = diIntSlider;
192     it.color = dialogcolor;
193     it.scale = 100;
194     it.key = key;
195     it.p1 = (d2-d1);
196     it.p2 = (d3-d1);
197     items.push_back(it);
198     }
199 
addSelItem(string body,string value,int key)200   EX void addSelItem(string body, string value, int key) {
201     item it;
202     it.type = diItem;
203     it.body = body;
204     it.value = value;
205     it.key = key;
206     it.color = dialogcolor;
207     it.colork = 0x808080;
208     it.colorv = 0x80A040;
209     it.colorc = 0xFFD500;
210     it.colors = 0xFF8000;
211     if(value == ONOFF(true)) it.colorv = 0x40FF40;
212     if(value == ONOFF(false)) it.colorv = 0xC04040;
213     it.scale = 100;
214     items.push_back(it);
215     }
216 
addBoolItem(string body,bool value,int key)217   EX void addBoolItem(string body, bool value, int key) {
218     addSelItem(body, ONOFF(value), key);
219     }
220 
displaycolor(color_t col)221   EX int displaycolor(color_t col) {
222     int c = col >> 8;
223     if(!c) return 0x181818;
224     return c;
225     }
226 
addKeyboardItem(string keys)227   EX void addKeyboardItem(string keys) {
228     item it;
229     it.type = diKeyboard;
230     it.body = keys;
231     it.color = dialogcolor;
232     it.colors = 0xFF8000;
233     it.scale = 150;
234     items.push_back(it);
235     }
236 
addColorItem(string body,int value,int key)237   EX void addColorItem(string body, int value, int key) {
238     item it;
239     it.type = diItem;
240     it.body = body;
241     it.value = COLORBAR;
242     it.key = key;
243     it.color = it.colorv = displaycolor(value);
244     it.colors = it.color ^ 0x404040;
245     it.colorc = it.color ^ 0x808080;
246     it.colork = 0x808080;
247     it.scale = 100;
248     items.push_back(it);
249     }
250 
addHelp(string body)251   EX void addHelp(string body) {
252     item it;
253     it.type = diHelp;
254     it.body = body;
255     it.scale = 100;
256 
257     if(isize(body) >= 500) it.scale = 70;
258 
259     items.push_back(it);
260     }
261 
addInfo(string body,color_t color IS (dialogcolor))262   EX void addInfo(string body, color_t color IS(dialogcolor)) {
263     item it;
264     it.type = diInfo;
265     it.body = body;
266     it.color = color;
267     it.scale = 100;
268     items.push_back(it);
269     }
270 
addItem(string body,int key)271   EX void addItem(string body, int key) {
272     item it;
273     it.type = diItem;
274     it.body = body;
275     it.key = key;
276     it.color = dialogcolor;
277     it.colork = 0x808080;
278     it.colors = 0xFFD500;
279     it.colorc = 0xFF8000;
280     it.scale = 100;
281     items.push_back(it);
282     }
283 
addBigItem(string body,int key)284   EX void addBigItem(string body, int key) {
285     item it;
286     it.type = diBigItem;
287     it.body = body;
288     it.key = key;
289     it.color = 0xC06000;
290     it.colors = 0xFFD500;
291     it.colorc = 0xFF8000;
292     it.scale = 150;
293     items.push_back(it);
294     }
295 
addBreak(int val)296   EX int addBreak(int val) {
297     item it;
298     it.type = diBreak;
299     it.scale = val;
300     items.push_back(it);
301     return items.size()-1;
302     }
303 
addTitle(string body,color_t color,int scale)304   EX void addTitle(string body, color_t color, int scale) {
305     item it;
306     it.type = diTitle;
307     it.body = body;
308     it.color = color;
309     it.scale = scale;
310     items.push_back(it);
311     }
312 
313   EX void init(string title, color_t color IS(0xE8E8E8), int scale IS(150), int brk IS(60)) {
314     init();
315     addTitle(title, color, scale);
316     addBreak(brk);
317     }
318 
319   EX int dcenter, dwidth;
320 
321   EX int dialogflags;
322 
displayLong(string str,int siz,int y,bool measure)323   EX int displayLong(string str, int siz, int y, bool measure) {
324 
325     int last = 0;
326     int lastspace = 0;
327 
328     int xs, xo;
329     if(current_display->sidescreen)
330       xs = dwidth - vid.fsize*2, xo = vid.yres + vid.fsize;
331     else
332       xs = vid.xres * 618/1000, xo = vid.xres * 186/1000;
333 
334     for(int i=0; i<=isize(str); i++) {
335       int ls = 0;
336       int prev = last;
337       if(str[i] == ' ') lastspace = i;
338       if(textwidth(siz, str.substr(last, i-last)) > xs) {
339         if(lastspace == last) ls = i-1, last = i-1;
340         else ls = lastspace, last = ls+1;
341         }
342       if(str[i] == 10 || i == isize(str)) ls = i, last = i+1;
343       if(ls) {
344         if(!measure) displayfr(xo, y, 2, siz, str.substr(prev, ls-prev), dialogcolor, 0);
345         if(ls == prev) y += siz/2;
346         else y += siz;
347         lastspace = last;
348         }
349 
350       }
351 
352     y += siz/2;
353     return y;
354     }
355 
356   EX int tothei, dialogwidth, dfsize, dfspace, leftwidth, rightwidth, innerwidth, itemx, keyx, valuex;
357 
358   EX string highlight_text;
359 
measure()360   EX void measure() {
361     tothei = 0;
362     dialogwidth = 0;
363     innerwidth = 0;
364     int N = items.size();
365     for(int i=0; i<N; i++) {
366       if(items[i].type == diHelp)
367         tothei += displayLong(items[i].body, dfsize * items[i].scale / 100, 0, true);
368       else {
369         tothei += dfspace * items[i].scale / 100;
370         if(items[i].type == diItem)
371           innerwidth = max(innerwidth, textwidth(dfsize * items[i].scale / 100, items[i].body));
372         if(items[i].type == diTitle || items[i].type == diInfo || items[i].type == diBigItem)
373           dialogwidth = max(dialogwidth, textwidth(dfsize * items[i].scale / 100, items[i].body) * 10/9);
374         }
375       }
376 
377     leftwidth = ISMOBILE ? 0 : textwidth(dfsize, "MMMMM") + dfsize/2;
378     rightwidth = textwidth(dfsize, "MMMMMMMM") + dfsize/2;
379 
380     int fwidth = innerwidth + leftwidth + rightwidth;
381     dialogwidth = max(dialogwidth, fwidth);
382     itemx  = dcenter - fwidth / 2 + leftwidth;
383     keyx   = dcenter - fwidth / 2 + leftwidth - dfsize/2;
384     valuex = dcenter - fwidth / 2 + leftwidth + innerwidth + dfsize/2;
385     }
386 
387   EX purehookset hooks_display_dialog;
388 
389   EX vector<int> key_queue;
390 
queue_key(int key)391   EX void queue_key(int key) { key_queue.push_back(key); }
392 
display()393   EX void display() {
394 
395     callhooks(hooks_display_dialog);
396     int N = items.size();
397     dfsize = vid.fsize;
398     #if ISMOBILE || ISPANDORA
399     dfsize *= 3;
400     #endif
401     dfspace = dfsize * 5/4;
402 
403     dcenter = vid.xres/2;
404     dwidth = vid.xres;
405 
406     if(current_display->sidescreen) {
407       dwidth = vid.xres - vid.yres;
408       dcenter = vid.xres - dwidth / 2;
409       }
410 
411     measure();
412 
413     while(tothei > vid.yres - 5 * vid.fsize) {
414       int adfsize = int(dfsize * sqrt((vid.yres - 5. * vid.fsize) / tothei));
415       if(adfsize < dfsize-1) dfsize = adfsize + 1;
416       else dfsize--;
417       dfspace = dfsize * 5/4;
418       measure();
419       }
420     while(dialogwidth > dwidth) {
421       int adfsize = int(dfsize * sqrt(vid.xres * 1. / dialogwidth));
422       if(adfsize < dfsize-1) dfsize = adfsize + 1;
423       else dfsize--; // keep dfspace
424       measure();
425       }
426 
427     tothei = (vid.yres - tothei) / 2;
428 
429     for(int i=0; i<N; i++) {
430       item& I = items[i];
431 
432       if(I.type == diHelp) {
433         tothei = displayLong(items[i].body, dfsize * items[i].scale / 100, tothei, false);
434         continue;
435         }
436 
437       int top = tothei;
438       tothei += dfspace * I.scale / 100;
439       int mid = (top + tothei) / 2;
440       I.position = mid;
441       if(I.type == diTitle || I.type == diInfo) {
442         bool xthis = (mousey >= top && mousey < tothei && I.key);
443         if(cmode & sm::DIALOG_STRICT_X)
444           xthis = xthis && (mousex >= dcenter - dialogwidth/2 && mousex <= dcenter + dialogwidth/2);
445         displayfr(dcenter, mid, 2, dfsize * I.scale/100, I.body, I.color, 8);
446         if(xthis) getcstat = I.key;
447         }
448       else if(I.type == diItem || I.type == diBigItem) {
449         bool xthis = (mousey >= top && mousey < tothei);
450         if(cmode & sm::DIALOG_STRICT_X)
451           xthis = xthis && (mousex >= dcenter - dialogwidth/2 && mousex <= dcenter + dialogwidth/2);
452 #if ISMOBILE
453         if(xthis && mousepressed)
454           I.color = I.colorc;
455 #else
456         if(xthis && mousemoved) {
457           highlight_text = I.body;
458           mousemoved = false;
459           }
460         if(highlight_text == I.body) {
461           I.color = (xthis&&mousepressed&&actonrelease) ? I.colorc : I.colors;
462           }
463 #endif
464 
465         if(I.type == diBigItem) {
466           displayfr(dcenter, mid, 2, dfsize * I.scale/100, I.body, I.color, 8);
467           }
468         else {
469           if(!mousing)
470             displayfr(keyx, mid, 2, dfsize * I.scale/100, keyname(I.key), I.colork, 16);
471           displayfr(itemx, mid, 2, dfsize * I.scale/100, I.body, I.color, 0);
472           int siz = dfsize * I.scale/100;
473           while(siz > 6 && textwidth(siz, I.value) >= vid.xres - valuex) siz--;
474           displayfr(valuex, mid, 2, siz, I.value, I.colorv, 0);
475           }
476         if(xthis) getcstat = I.key;
477         }
478       else if(among(I.type, diSlider, diIntSlider)) {
479         bool xthis = (mousey >= top && mousey < tothei);
480         int sl, sr;
481         if(current_display->sidescreen)
482           sl = vid.yres + vid.fsize*2, sr = vid.xres - vid.fsize*2;
483         else
484           sl = vid.xres/4, sr = vid.xres*3/4;
485         int sw = sr-sl;
486         if(I.type == diSlider) {
487           displayfr(sl, mid, 2, dfsize * I.scale/100, "(", I.color, 16);
488           displayfr(sl + double(sw * I.param), mid, 2, dfsize * I.scale/100, "#", I.color, 8);
489           displayfr(sr, mid, 2, dfsize * I.scale/100, ")", I.color, 0);
490           }
491         else {
492           displayfr(sl, mid, 2, dfsize * I.scale/100, "{", I.color, 16);
493           if(I.p2 < sw / 4) for(int a=0; a<=I.p2; a++) if(a != I.p1)
494             displayfr(sl + double(sw * a / I.p2), mid, 2, dfsize * I.scale/100, a == I.p1 ? "#" : ".", I.color, 8);
495           displayfr(sl + double(sw * I.p1 / I.p2), mid, 2, dfsize * I.scale/100, "#", I.color, 8);
496           displayfr(sr, mid, 2, dfsize * I.scale/100, "}", I.color, 0);
497           }
498         if(xthis) getcstat = I.key, inslider = true, slider_x = mousex;
499         }
500       else if(I.type == diKeyboard) {
501         int len = 0;
502         for(char c: I.body)
503           if(c == ' ' || c == '\t') len += 3;
504           else len++;
505         int sl, sr;
506         if(current_display->sidescreen)
507           sl = vid.yres + vid.fsize*2, sr = vid.xres - vid.fsize*2;
508         else
509           sl = vid.xres/4, sr = vid.xres*3/4;
510         int pos = 0;
511         for(char c: I.body) {
512           string s = "";
513           s += c;
514           int nlen = 1;
515           if(c == ' ') s = "SPACE", nlen = 3;
516           if(c == '\b') s = "⌫", nlen = 1;
517           if(c == '\r' || c == '\n') s = "⏎", nlen = 1;
518           if(c == 1) s = "←", nlen = 1;
519           if(c == 2) s = "→", nlen = 1;
520           if(c == 3) s = "π";
521           if(c == '\t') s = "CLEAR", nlen = 3;
522           int left = sl + (sr-sl) * pos / len;
523           pos += nlen;
524           int right = sl + (sr-sl) * pos / len;
525           bool in = (mousex >= left && mousex <= right && mousey >= top && mousey < tothei);
526           int xpos = (left + right) / 2;
527           if(in) {
528             if(c == 1) getcstat = SDLK_LEFT;
529             else if(c == 2) getcstat = SDLK_RIGHT;
530             else getcstat = c;
531             }
532           displayfr(xpos, mid, 2, dfsize * I.scale/100, s, in ? I.colors : I.color, 8);
533           }
534         }
535       }
536     }
537 
isitem(item & it)538   bool isitem(item& it) {
539     return it.type == diItem || it.type == diBigItem;
540     }
541 
handle_actions(int & sym,int & uni)542   EX void handle_actions(int &sym, int &uni) {
543     if(key_actions.count(uni)) {
544       key_actions[uni]();
545       sym = uni = 0;
546       return;
547       }
548     if(key_actions.count(sym)) {
549       key_actions[sym]();
550       sym = uni = 0;
551       return;
552       }
553     }
554 
handleNavigation(int & sym,int & uni)555   EX void handleNavigation(int &sym, int &uni) {
556     if(uni == '\n' || uni == '\r' || DIRECTIONKEY == SDLK_KP5) {
557       for(int i=0; i<isize(items); i++)
558         if(isitem(items[i]))
559           if(items[i].body == highlight_text) {
560             uni = sym = items[i].key;
561             handle_actions(sym, uni);
562             return;
563             }
564       }
565     if(DKEY == SDLK_PAGEDOWN) {
566       uni = sym = 0;
567       for(int i=0; i<isize(items); i++)
568         if(isitem(items[i]))
569           highlight_text = items[i].body;
570       }
571     if(DKEY == SDLK_PAGEUP) {
572       uni = sym = 0;
573       for(int i=0; i<isize(items); i++)
574         if(isitem(items[i])) {
575           highlight_text = items[i].body;
576           break;
577           }
578       }
579     if(DKEY == SDLK_UP) {
580       uni = sym = 0;
581       string last = "";
582       for(int i=0; i<isize(items); i++)
583         if(isitem(items[i]))
584           last = items[i].body;
585       for(int i=0; i<isize(items); i++)
586         if(isitem(items[i])) {
587           if(items[i].body == highlight_text) {
588             highlight_text = last; return;
589             }
590           else last = items[i].body;
591           }
592       highlight_text = last;
593       }
594     if(DKEY == SDLK_DOWN) {
595       uni = sym = 0;
596       int state = 0;
597       for(int i=0; i<isize(items); i++)
598         if(isitem(items[i])) {
599           if(state) { highlight_text = items[i].body; return; }
600           else if(items[i].body == highlight_text) state = 1;
601           }
602       for(int i=0; i<isize(items); i++)
603         if(isitem(items[i])) {
604           highlight_text = items[i].body;
605           break;
606           }
607       }
608     handle_actions(sym, uni);
609     }
610 
611   color_t colorhistory[10] = {
612     0x202020FF, 0x800000FF, 0x008000FF, 0x000080FF,
613     0x404040FF, 0xC0C0C0FF, 0x804000FF, 0xC0C000FF,
614     0x408040FF, 0xFFD500FF
615     }, lch;
616 
617   EX color_t *palette;
618 
619   int colorp = 0;
620 
621   color_t *colorPointer;
622 
handleKeyColor(int sym,int uni)623   EX void handleKeyColor(int sym, int uni) {
624     unsigned& color = *colorPointer;
625     int shift = colorAlpha ? 0 : 8;
626 
627     if(uni >= 'A' && uni <= 'D') {
628       int x = (slider_x - (dcenter-dwidth/4)) * 510 / dwidth;
629       if(x < 0) x = 0;
630       if(x > 255) x = 255;
631       part(color, uni - 'A') = x;
632       }
633     else if(uni == ' ' || uni == '\n' || uni == '\r') {
634       bool inHistory = false;
635       for(int i=0; i<10; i++) if(colorhistory[i] == (color << shift))
636         inHistory = true;
637       if(!inHistory) { colorhistory[lch] = (color << shift); lch++; lch %= 10; }
638       popScreen();
639       if(reaction) reaction();
640       if(reaction_final) reaction_final();
641       }
642     else if(uni >= '0' && uni <= '9') {
643       color = colorhistory[uni - '0'] >> shift;
644       }
645     else if(palette && uni >= 'a' && uni < 'a'+(int) palette[0]) {
646       color = palette[1 + uni - 'a'] >> shift;
647       }
648     else if(DKEY == SDLK_DOWN) {
649       colorp = (colorp-1) & 3;
650       }
651     else if(DKEY == SDLK_UP) {
652       colorp = (colorp+1) & 3;
653       }
654     else if(DKEY == SDLK_LEFT) {
655       unsigned char* pts = (unsigned char*) &color;
656       pts[colorp] -= abs(shiftmul) < .6 ? 1 : 17;
657       }
658     else if(DKEY == SDLK_RIGHT) {
659       unsigned char* pts = (unsigned char*) &color;
660       pts[colorp] += abs(shiftmul) < .6 ? 1 : 17;
661       }
662     else if(doexiton(sym, uni)) {
663       popScreen();
664       if(reaction_final) reaction_final();
665       }
666     }
667 
668   EX bool colorAlpha;
669 
drawColorDialog()670   EX void drawColorDialog() {
671     cmode = sm::NUMBER | dialogflags;
672     if(cmode & sm::SIDE) gamescreen(0);
673     else emptyscreen();
674 
675     dcenter = vid.xres/2;
676     dwidth = vid.xres;
677 
678     if(current_display->sidescreen) {
679       dwidth = vid.xres - vid.yres;
680       dcenter = vid.xres - dwidth / 2;
681       }
682 
683     color_t color = *colorPointer;
684 
685     int ash = 8;
686 
687     for(int j=0; j<10; j++) {
688       int x = dcenter + vid.fsize * 2 * (j-5);
689       int y = vid.yres / 2- 5 * vid.fsize;
690 
691       string s0 = ""; s0 += ('0'+j);
692 
693       vid.fsize *= 2;
694       displayColorButton(x, y, s0, '0'+j, 0, 0, colorhistory[j] >> ash);
695       vid.fsize /= 2;
696       }
697 
698     if(palette) {
699       int q = palette[0];
700       for(int i=0; i<q; i++) {
701         int x = dcenter + vid.fsize * (2 * i-q);
702         int y = vid.yres / 2- 7 * vid.fsize;
703         string s0 = ""; s0 += ('a'+i);
704         vid.fsize *= 2;
705         displayColorButton(x, y, s0, 'a'+i, 0, 0, palette[i+1] >> ash);
706         vid.fsize /= 2;
707         }
708       }
709 
710     for(int i=0; i<4; i++) {
711       int y = vid.yres / 2 + (2-i) * vid.fsize * 2;
712       if(i == 3 && !colorAlpha) continue;
713 
714       color_t col = ((i==colorp) && !mousing) ? 0xFFD500 : dialogcolor;
715 
716       displayColorButton(dcenter - dwidth/4, y, "(", 0, 16, 0, col);
717       string rgt = ") "; rgt += "ABGR" [i+(colorAlpha?0:1)];
718       displayColorButton(dcenter + dwidth/4, y, rgt, 0, 0, 0, col);
719       displayColorButton(dcenter - dwidth/4 + dwidth * part(color, i) / 510, y, "#", 0, 8, 0, col);
720 
721       if(mousey >= y - vid.fsize && mousey < y + vid.fsize)
722         getcstat = 'A' + i, inslider = true, slider_x = mousex;
723       }
724 
725     displayColorButton(dcenter, vid.yres/2+vid.fsize * 6, XLAT("select this color") + " : " + format(colorAlpha ? "%08X" : "%06X", color), ' ', 8, 0, color >> (colorAlpha ? ash : 0));
726 
727     if(extra_options) extra_options();
728 
729     keyhandler = handleKeyColor;
730     }
731 
openColorDialog(unsigned int & col,unsigned int * pal IS (palette))732   EX void openColorDialog(unsigned int& col, unsigned int *pal IS(palette)) {
733     colorPointer = &col; palette = pal;
734     colorAlpha = true;
735     dialogflags = 0;
736     pushScreen(drawColorDialog);
737     reaction = reaction_t();
738     extra_options = reaction_t();
739     }
740 
741   EX numberEditor ne;
742 
editingDetail()743   EX bool editingDetail() {
744     return ne.editwhat == &vid.highdetail || ne.editwhat == &vid.middetail;
745     }
746 
ldtoint(ld x)747   int ldtoint(ld x) {
748     if(x > 0) return int(x+.5);
749     else return int(x-.5);
750     }
751 
disp(ld x)752   EX string disp(ld x) {
753     if(dialogflags & sm::HEXEDIT) return "0x" + itsh((unsigned long long)(x));
754     else if(ne.intval) return its(ldtoint(x));
755     else return fts(x); }
756 
757   EX reaction_t reaction;
758   EX reaction_t reaction_final;
759 
760   EX reaction_t extra_options;
761 
apply_slider()762   EX void apply_slider() {
763     if(ne.intval) *ne.intval = ldtoint(*ne.editwhat);
764     if(reaction) reaction();
765     if(ne.intval) *ne.editwhat = *ne.intval;
766     ne.s = disp(*ne.editwhat);
767     #if CAP_ANIMATIONS
768     anims::deanimate(*ne.editwhat);
769     #endif
770     }
771 
use_hexeditor()772   EX void use_hexeditor() {
773     dialogflags |= sm::HEXEDIT;
774     ne.s = disp(*ne.editwhat);
775     }
776 
apply_edit()777   EX void apply_edit() {
778     try {
779       exp_parser ep;
780       ep.s = ne.s;
781       ld x = ep.rparse();
782       if(ne.sc.positive && x <= 0) return;
783       *ne.editwhat = x;
784       if(ne.intval) *ne.intval = ldtoint(*ne.editwhat);
785       #if CAP_ANIMATIONS
786       if(ne.animatable) anims::animate_parameter(*ne.editwhat, ne.s, reaction ? reaction : reaction_final);
787       #endif
788       if(reaction) reaction();
789       }
790     catch(const hr_parse_exception&) {
791       }
792     }
793 
bound_low(ld val)794   EX void bound_low(ld val) {
795     auto r = reaction;
796     reaction = [r, val] () {
797       if(*ne.editwhat < val) {
798         *ne.editwhat = val;
799         if(ne.intval) *ne.intval = ldtoint(*ne.editwhat);
800         }
801       if(r) r();
802       };
803     }
804 
bound_up(ld val)805   EX void bound_up(ld val) {
806     auto r = reaction;
807     reaction = [r, val] () {
808       if(*ne.editwhat > val) {
809         *ne.editwhat = val;
810         if(ne.intval) *ne.intval = ldtoint(*ne.editwhat);
811         }
812       if(r) r();
813       };
814     }
815 
816   EX int numberdark;
817 
formula_keyboard(bool lr)818   EX void formula_keyboard(bool lr) {
819     addKeyboardItem("1234567890");
820     addKeyboardItem("=+-*/^()\x3");
821     addKeyboardItem("qwertyuiop");
822     addKeyboardItem("asdfghjkl");
823     addKeyboardItem("zxcvbnm,.\b");
824     addKeyboardItem(lr ? " \t\x1\x2" : " \t");
825     }
826 
827   EX bool onscreen_keyboard = ISMOBILE;
828 
number_dialog_help()829   EX void number_dialog_help() {
830     init("number dialog help");
831     dialog::addBreak(100);
832     dialog::addHelp(XLAT("You can enter formulas in this dialog."));
833     dialog::addBreak(100);
834     dialog::addHelp(XLAT("Functions available:"));
835     addHelp(available_functions());
836     dialog::addBreak(100);
837     dialog::addHelp(XLAT("Constants and variables available:"));
838     addHelp(available_constants());
839     if(ne.animatable) {
840       dialog::addBreak(100);
841       dialog::addHelp(XLAT("Animations:"));
842       dialog::addHelp(XLAT("a..b -- animate linearly from a to b"));
843       dialog::addHelp(XLAT("a..b..|c..d -- animate from a to b, then from c to d"));
844       dialog::addHelp(XLAT("a../x..b../y -- change smoothly, x and y are derivatives"));
845       }
846 
847     /* "Most parameters can be animated simply by using '..' in their editing dialog. "
848       "For example, the value of a parameter set to 0..1 will grow linearly from 0 to 1. "
849       "You can also use functions (e.g. cos(0..2*pi)) and refer to other parameters."
850       )); */
851 
852     #if CAP_ANIMATIONS
853     dialog::addBreak(50);
854     auto f = find_edit(ne.intval ? (void*) ne.intval : (void*) ne.editwhat);
855     if(f)
856       dialog::addHelp(XLAT("Parameter names, e.g. '%1'", f->parameter_name));
857     else
858       dialog::addHelp(XLAT("Parameter names"));
859     dialog::addBreak(50);
860     for(auto& ap: anims::aps) {
861       string what = "?";
862       for(auto& p: params) if(p.second->affects(ap.value)) what = p.first;
863       dialog::addInfo(what + " = " + ap.formula);
864       }
865     #endif
866     dialog::addBreak(50);
867     dialog::addHelp(XLAT("These can be combined, e.g. %1", "projection*sin(0..2*pi)"));
868     display();
869     }
870 
parser_help()871   EX void parser_help() {
872     ne.editwhat = nullptr;
873     ne.intval = nullptr;
874     addItem("help", SDLK_F1);
875     add_action_push(number_dialog_help);
876     }
877 
drawNumberDialog()878   EX void drawNumberDialog() {
879     cmode = sm::NUMBER | dialogflags;
880     if(numberdark < DONT_SHOW)
881     gamescreen(numberdark);
882     else emptyscreen();
883     init(ne.title);
884     addInfo(ne.s);
885     if(ne.intval && ne.sc.direct == &identity_f)
886       addIntSlider(int(ne.vmin), int(*ne.editwhat), int(ne.vmax), 500);
887     else
888       addSlider(ne.sc.direct(ne.vmin), ne.sc.direct(*ne.editwhat), ne.sc.direct(ne.vmax), 500);
889     addBreak(100);
890 #if !ISMOBILE
891     addHelp(XLAT("You can scroll with arrow keys -- Ctrl to fine-tune"));
892     addBreak(100);
893 #endif
894 
895     dialog::addBack();
896     addSelItem(XLAT("default value"), disp(ne.dft), SDLK_HOME);
897     add_edit(onscreen_keyboard);
898     addItem("help", SDLK_F1);
899     add_action_push(number_dialog_help);
900 
901     addBreak(100);
902 
903     if(ne.help != "") {
904       addHelp(ne.help);
905       // bool scal = !ISMOBILE && !ISPANDORA && isize(ne.help) > 160;
906       // if(scal) lastItem().scale = 30;
907       }
908 
909     if(extra_options) extra_options();
910 
911     if(onscreen_keyboard) {
912       addBreak(100);
913       formula_keyboard(false);
914       }
915 
916     display();
917 
918     keyhandler = [] (int sym, int uni) {
919       handleNavigation(sym, uni);
920       if((uni >= '0' && uni <= '9') || among(uni, '.', '+', '-', '*', '/', '^', '(', ')', ',', '|', 3) || (uni >= 'a' && uni <= 'z')) {
921         if(uni == 3) ne.s += "pi";
922         else ne.s += uni;
923         apply_edit();
924         }
925       else if(uni == '\b' || uni == '\t') {
926         ne.s = ne.s. substr(0, isize(ne.s)-1);
927         sscanf(ne.s.c_str(), LDF, ne.editwhat);
928         apply_edit();
929         }
930   #if !ISMOBILE
931       else if(DKEY == SDLK_RIGHT) {
932         if(ne.intval && abs(shiftmul) < .6)
933           (*ne.editwhat)++;
934         else
935           *ne.editwhat = ne.sc.inverse(ne.sc.direct(*ne.editwhat) + shiftmul * ne.step);
936         if(abs(*ne.editwhat) < ne.step * 1e-6 && !ne.intval) *ne.editwhat = 0;
937         apply_slider();
938         }
939       else if(DKEY == SDLK_LEFT) {
940         if(ne.intval && abs(shiftmul) < .6)
941           (*ne.editwhat)--;
942         else
943           *ne.editwhat = ne.sc.inverse(ne.sc.direct(*ne.editwhat) - shiftmul * ne.step);
944         if(abs(*ne.editwhat) < ne.step * 1e-6 && !ne.intval) *ne.editwhat = 0;
945         apply_slider();
946         }
947   #endif
948       else if(sym == SDLK_HOME) {
949         *ne.editwhat = ne.dft;
950         apply_slider();
951         }
952       else if(uni == 500) {
953         int sl, sr;
954         if(current_display->sidescreen)
955           sl = vid.yres + vid.fsize*2, sr = vid.xres - vid.fsize*2;
956         else
957           sl = vid.xres/4, sr = vid.xres*3/4;
958         ld d = (slider_x - sl + .0) / (sr-sl);
959         ld val = ne.sc.inverse(d * (ne.sc.direct(ne.vmax) - ne.sc.direct(ne.vmin)) + ne.sc.direct(ne.vmin));
960         ld nextval = ne.sc.inverse((mousex + 1. - sl) / (sr - sl) * (ne.sc.direct(ne.vmax) - ne.sc.direct(ne.vmin)) + ne.sc.direct(ne.vmin));
961         ld dif = abs(val - nextval);
962         if(dif > 1e-6) {
963           ld mul = 1;
964           while(dif < 10) dif *= 10, mul *= 10;
965           val *= mul;
966           val = floor(val + 0.5);
967           val /= mul;
968           }
969         *ne.editwhat = val;
970 
971         apply_slider();
972         }
973       else if(doexiton(sym, uni)) { popScreen(); if(reaction_final) reaction_final(); }
974       };
975     }
976 
977   int nlpage = 1;
978   int wheelshift = 0;
979 
handlePage(int & nl,int & nlm,int perpage)980   EX int handlePage(int& nl, int& nlm, int perpage) {
981     nlm = nl;
982     int onl = nl;
983     int ret = 0;
984     if(nlpage) {
985       nl = nlm = perpage;
986       if(nlpage == 2) ret = nlm;
987       int w = wheelshift;
988       int realw = 0;
989       while(w<0 && ret) {
990         ret--; w++; realw--;
991         }
992       while(w>0 && ret+perpage < onl) {
993         ret++; w--; realw++;
994         }
995       wheelshift = realw;
996       if(ret+nl > onl) nl = onl-ret;
997       }
998     return ret;
999     }
1000 
displayPageButtons(int i,bool pages)1001   EX void displayPageButtons(int i, bool pages) {
1002     int i0 = vid.yres - vid.fsize;
1003     int xr = vid.xres / 80;
1004     if(pages) if(displayfrZH(xr*8, i0, 1, vid.fsize, IFM("1 - ") + XLAT("page") + " 1", nlpage == 1 ? 0xD8D8C0 : dialogcolor, 8))
1005       getcstat = '1';
1006     if(pages) if(displayfrZH(xr*24, i0, 1, vid.fsize, IFM("2 - ") + XLAT("page") + " 2", nlpage == 1 ? 0xD8D8C0 : dialogcolor, 8))
1007       getcstat = '2';
1008     if(pages) if(displayfrZH(xr*40, i0, 1, vid.fsize, IFM("3 - ") + XLAT("all"), nlpage == 1 ? 0xD8D8C0 : dialogcolor, 8))
1009       getcstat = '3';
1010     if(i&1) if(displayfrZH(xr*56, i0, 1, vid.fsize, IFM(keyname(SDLK_ESCAPE) + " - ") + XLAT("go back"), dialogcolor, 8))
1011       getcstat = '0';
1012     if(i&2) if(displayfrZH(xr*72, i0, 1, vid.fsize, IFM("F1 - ") + XLAT("help"), dialogcolor, 8))
1013       getcstat = SDLK_F1;
1014     if(i&4) if(displayfrZH(xr*8, i0, 1, vid.fsize, IFM("1 - ") + XLAT("plain"), dialogcolor, 8))
1015       getcstat = '1';
1016     }
1017 
handlePageButtons(int uni)1018   EX bool handlePageButtons(int uni) {
1019     if(uni == '1') nlpage = 1, wheelshift = 0;
1020     else if(uni == '2') nlpage = 2, wheelshift = 0;
1021     else if(uni == '3') nlpage = 0, wheelshift = 0;
1022     else if(uni == PSEUDOKEY_WHEELUP) wheelshift--;
1023     else if(uni == PSEUDOKEY_WHEELDOWN) wheelshift++;
1024     else return false;
1025     return true;
1026     }
1027 
editNumber(ld & x,ld vmin,ld vmax,ld step,ld dft,string title,string help)1028   EX void editNumber(ld& x, ld vmin, ld vmax, ld step, ld dft, string title, string help) {
1029     ne.editwhat = &x;
1030     ne.s = disp(x);
1031     ne.vmin = vmin;
1032     ne.vmax = vmax;
1033     ne.step = step;
1034     ne.dft = dft;
1035     ne.title = title;
1036     ne.help = help;
1037     ne.sc = identity;
1038     ne.intval = NULL;
1039     dialogflags = 0;
1040     if(cmode & sm::SIDE) dialogflags |= sm::MAYDARK | sm::SIDE;
1041     cmode |= sm::NUMBER;
1042     pushScreen(drawNumberDialog);
1043     reaction = reaction_t();
1044     reaction_final = reaction_t();
1045     extra_options = reaction_t();
1046     numberdark = 0;
1047     ne.animatable = true;
1048     #if CAP_ANIMATIONS
1049     anims::get_parameter_animation(x, ne.s);
1050     #endif
1051     }
1052 
editNumber(int & x,int vmin,int vmax,ld step,int dft,string title,string help)1053   EX void editNumber(int& x, int vmin, int vmax, ld step, int dft, string title, string help) {
1054     editNumber(ne.intbuf, vmin, vmax, step, dft, title, help);
1055     ne.intbuf = x; ne.intval = &x; ne.s = its(x);
1056     ne.animatable = false;
1057     }
1058 
helpToEdit(int & x,int vmin,int vmax,int step,int dft)1059   EX void helpToEdit(int& x, int vmin, int vmax, int step, int dft) {
1060     popScreen();
1061     string title = "help";
1062     if(help[0] == '@') {
1063       int iv = help.find("\t");
1064       int id = help.find("\n");
1065       title = help.substr(iv+1, id-iv-1);
1066       help = help.substr(id+1);
1067       }
1068     editNumber(x, vmin, vmax, step, dft, title, help);
1069     }
1070 
1071   //-- choose file dialog--
1072 
1073   #define CDIR dialogcolor
1074   #define CFILE forecolor
1075 
filecmp(const pair<string,color_t> & f1,const pair<string,color_t> & f2)1076   bool filecmp(const pair<string,color_t> &f1, const pair<string,color_t> &f2) {
1077     if(f1.first == "../") return true;
1078     if(f2.first == "../") return false;
1079     if(f1.second != f2.second)
1080       return f1.second == CDIR;
1081     return f1.first < f2.first;
1082     }
1083 
1084   string filecaption, cfileext;
1085   string *cfileptr;
1086   bool editext = false;
1087 
1088   bool_reaction_t file_action;
1089 
1090   void handleKeyFile(int sym, int uni);
1091 
drawFileDialog()1092   EX void drawFileDialog() {
1093     displayfr(vid.xres/2, 30 + vid.fsize, 2, vid.fsize,
1094       filecaption, forecolor, 8);
1095 
1096     string& cfile = *cfileptr;
1097 
1098     displayfr(vid.xres/2, 34 + vid.fsize * 2, 2, vid.fsize,
1099       cfile, 0xFFFF00, 8);
1100 
1101     displayColorButton(vid.xres*1/4, 38+vid.fsize * 3,
1102       XLAT("F4 = extension"), SDLK_F4, 8, 0, editext ? 0xFF00FF : 0xFFFF00, editext ? 0x800080 : 0x808000);
1103     displayButton(vid.xres*2/4, 38+vid.fsize * 3,
1104       XLAT("Enter = choose"), SDLK_RETURN, 8);
1105     displayButton(vid.xres*3/4, 38+vid.fsize * 3,
1106       XLAT("Esc = cancel"), SDLK_ESCAPE, 8);
1107 
1108     v.clear();
1109 
1110     DIR           *d;
1111     struct dirent *dir;
1112 
1113     string where = ".";
1114     for(int i=0; i<isize(cfile); i++)
1115       if(cfile[i] == '/' || cfile[i] == '\\')
1116         where = cfile.substr(0, i+1);
1117 
1118     d = opendir(where.c_str());
1119     if (d) {
1120       while ((dir = readdir(d)) != NULL) {
1121         string s = dir->d_name;
1122         if(s != ".." && s[0] == '.') ;
1123         else if(isize(s) > 4 && s.substr(isize(s)-4) == cfileext)
1124           v.push_back(make_pair(s, CFILE));
1125         else if(dir->d_type & DT_DIR)
1126           v.push_back(make_pair(s+"/", CDIR));
1127         }
1128       closedir(d);
1129       }
1130     sort(v.begin(), v.end(), filecmp);
1131 
1132     int q = v.size();
1133     int percolumn = (vid.yres-38) / (vid.fsize+5) - 4;
1134     int columns = 1 + (q-1) / percolumn;
1135 
1136     for(int i=0; i<q; i++) {
1137       int x = 16 + (vid.xres * (i/percolumn)) / columns;
1138       int y = 42 + vid.fsize * 4 + (vid.fsize+5) * (i % percolumn);
1139 
1140       displayColorButton(x, y, v[i].first, 1000 + i, 0, 0, v[i].second, 0xFFFF00);
1141       }
1142 
1143     keyhandler = handleKeyFile;
1144     }
1145 
handleKeyFile(int sym,int uni)1146   EX void handleKeyFile(int sym, int uni) {
1147     string& s(*cfileptr);
1148     int i = isize(s) - (editext?0:4);
1149 
1150     if(sym == SDLK_ESCAPE) {
1151       popScreen();
1152       }
1153     else if(sym == SDLK_RETURN || sym == SDLK_KP_ENTER) {
1154       // we pop and re-push, in case if action opens something
1155       popScreen();
1156       if(!file_action()) pushScreen(drawFileDialog);
1157       }
1158     else if(sym == SDLK_F4) {
1159       editext = !editext;
1160       }
1161     else if(sym == SDLK_BACKSPACE && i) {
1162       s.erase(i-1, 1);
1163       }
1164     else if(uni >= 32 && uni < 127) {
1165       s.insert(i, s0 + char(uni));
1166       }
1167     else if(uni >= 1000 && uni <= 1000+isize(v)) {
1168       string where = "", what = s, whereparent = "../";
1169       for(int i=0; i<isize(s); i++)
1170         if(s[i] == '/') {
1171           if(i >= 2 && s.substr(i-2,3) == "../")
1172             whereparent = s.substr(0, i+1) + "../";
1173           else
1174             whereparent = where;
1175           where = s.substr(0, i+1), what = s.substr(i+1);
1176           }
1177       int i = uni - 1000;
1178       if(v[i].first == "../") {
1179         s = whereparent + what;
1180         }
1181       else if(v[i].second == CDIR)
1182         s = where + v[i].first + what;
1183       else
1184         s = where + v[i].first;
1185       }
1186     return;
1187     }
1188 
openFileDialog(string & filename,string fcap,string ext,bool_reaction_t action)1189   EX void openFileDialog(string& filename, string fcap, string ext, bool_reaction_t action) {
1190     cfileptr = &filename;
1191     filecaption = fcap;
1192     cfileext = ext;
1193     file_action = action;
1194     pushScreen(dialog::drawFileDialog);
1195     }
1196 
1197   // infix/v/vpush
1198 
1199   EX string infix;
1200 
1201   string foreign_letters = "ÁÄÇÈÉÍÎÖÚÜßàáâãäçèéêìíîïòóôõöøùúüýąćČčĎďĘęĚěğİıŁłńňŘřŚśŞşŠšŤťůŹźŻżŽž";
1202   string latin_letters   = "AACEEIIOUUsAAAAACEEEIIIIOOOOOOUUUYACCCDDEEEEGIILLNNRRSSSSSSTTUZZZZZZ";
1203 
hasInfix(const string & s)1204   EX bool hasInfix(const string &s) {
1205     if(infix == "") return true;
1206     string t = "";
1207     for(int i=0; i<isize(s); i++) {
1208       char c = s[i];
1209       char tt = 0;
1210       if(c >= 'a' && c <= 'z') tt += c - 32;
1211       else if(c >= 'A' && c <= 'Z') tt += c;
1212       else if(c == '@') tt += c;
1213 
1214       if(tt == 0) for(int k=0; k<isize(latin_letters); k++) {
1215         if(s[i] == foreign_letters[2*k] && s[i+1] == foreign_letters[2*k+1]) {
1216           if(latin_letters[k] == 's') t += "SS";
1217           else tt += latin_letters[k];
1218           }
1219         }
1220 
1221       if(tt) t += tt;
1222       }
1223     return t.find(infix) != string::npos;
1224     }
1225 
editInfix(int uni)1226   EX bool editInfix(int uni) {
1227     if(uni >= 'A' && uni <= 'Z') infix += uni;
1228     else if(uni >= 'a' && uni <= 'z') infix += uni-32;
1229     else if(infix != "" && uni == 8) infix = infix.substr(0, isize(infix)-1);
1230     else if(infix != "" && uni != 0) infix = "";
1231     else return false;
1232     return true;
1233     }
1234 
1235   EX vector<pair<string, color_t> > v;
1236 
vpush(color_t color,const char * name)1237   EX void vpush(color_t color, const char *name) {
1238     string s = XLATN(name);
1239     if(!hasInfix(s)) return;
1240     dialog::v.push_back(make_pair(s, color));
1241     }
1242 
1243   int editpos = 0;
1244   EX string *edited_string;
1245 
view_edited_string()1246   EX string view_edited_string() {
1247     string cs = *edited_string;
1248     if(editpos < 0) editpos = 0;
1249     if(editpos > isize(cs)) editpos = isize(cs);
1250     cs.insert(editpos, "°");
1251     return cs;
1252     }
1253 
start_editing(string & s)1254   EX void start_editing(string& s) {
1255     edited_string = &s;
1256     editpos = isize(s);
1257     }
1258 
editchecker(int sym,int uni)1259   EX string editchecker(int sym, int uni) {
1260     if(uni >= 32 && uni < 127) return string("") + char(uni);
1261     return "";
1262     }
1263 
handle_edit_string(int sym,int uni,function<string (int,int)> checker IS (editchecker))1264   EX bool handle_edit_string(int sym, int uni, function<string(int, int)> checker IS(editchecker)) {
1265     auto& es = *edited_string;
1266     string u2;
1267     if(DKEY == SDLK_LEFT) editpos--;
1268     else if(DKEY == SDLK_RIGHT) editpos++;
1269     else if(uni == 8) {
1270       if(editpos == 0) return true;
1271       es.replace(editpos-1, 1, "");
1272       editpos--;
1273       }
1274     else if((u2 = checker(sym, uni)) != "") {
1275       for(char c: u2) {
1276         es.insert(editpos, 1, c);
1277         editpos ++;
1278         }
1279       }
1280     else return false;
1281     return true;
1282     }
1283 
string_edit_dialog()1284   EX void string_edit_dialog() {
1285     cmode = sm::NUMBER | dialogflags;
1286     if(numberdark < DONT_SHOW)
1287     gamescreen(numberdark);
1288     else emptyscreen();
1289     init(ne.title);
1290     addInfo(view_edited_string());
1291     addBreak(100);
1292     formula_keyboard(true);
1293     addBreak(100);
1294     dialog::addBack();
1295     addBreak(100);
1296 
1297     if(ne.help != "") {
1298       addHelp(ne.help);
1299       }
1300 
1301     if(extra_options) extra_options();
1302 
1303     display();
1304 
1305     keyhandler = [] (int sym, int uni) {
1306       handleNavigation(sym, uni);
1307       if(handle_edit_string(sym, uni)) ;
1308       else if(doexiton(sym, uni)) {
1309         popScreen();
1310         if(reaction_final) reaction_final();
1311         }
1312       };
1313     }
1314 
edit_string(string & s,string title,string help)1315   EX void edit_string(string& s, string title, string help) {
1316     start_editing(s);
1317     ne.title = title;
1318     ne.help = help;
1319     dialogflags = 0;
1320     if(cmode & sm::SIDE) dialogflags |= sm::MAYDARK | sm::SIDE;
1321     cmode |= sm::NUMBER;
1322     pushScreen(string_edit_dialog);
1323     reaction = reaction_t();
1324     extra_options = reaction_t();
1325     numberdark = 0;
1326     }
1327 
confirm_dialog(const string & text,const reaction_t & act)1328   EX void confirm_dialog(const string& text, const reaction_t& act) {
1329     gamescreen(1);
1330     dialog::addBreak(250);
1331     dialog::init(XLAT("WARNING"), 0xFF0000, 150, 100);
1332     dialog::addHelp(text);
1333     dialog::addItem(XLAT("YES"), 'y');
1334     auto yes = [act] () { popScreen(); act(); };
1335     dialog::add_action(yes);
1336     dialog::add_key_action(SDLK_RETURN, yes);
1337     dialog::addItem(XLAT("NO"), 'n');
1338     dialog::add_action([] () { popScreen(); });
1339     dialog::display();
1340     }
1341 
addBoolItem_action(const string & s,bool & b,char c)1342   EX void addBoolItem_action(const string& s, bool& b, char c) {
1343     dialog::addBoolItem(s, b, c);
1344     dialog::add_action([&b] { b = !b; });
1345     }
1346 
addBoolItem_action_neg(const string & s,bool & b,char c)1347   EX void addBoolItem_action_neg(const string& s, bool& b, char c) {
1348     dialog::addBoolItem(s, !b, c);
1349     dialog::add_action([&b] { b = !b; });
1350     }
1351 
cheat_forbidden()1352   EX bool cheat_forbidden() {
1353     if(tactic::on && !cheater) {
1354       addMessage(XLAT("Not available in the pure tactics mode!"));
1355       return true;
1356       }
1357     if(daily::on) {
1358       addMessage(XLAT("Not available in the daily challenge!"));
1359       return true;
1360       }
1361     return false;
1362     }
1363 
add_action_confirmed(const reaction_t & act)1364   EX void add_action_confirmed(const reaction_t& act) {
1365     dialog::add_action(dialog::add_confirmation(act));
1366     }
1367 
1368   #if HDR
1369 
addBoolItem_choice(const string & s,T & b,T val,char c)1370   template<class T> void addBoolItem_choice(const string&  s, T& b, T val, char c) {
1371     addBoolItem(s, b == val, c);
1372     add_action([&b, val] { b = val; });
1373     }
1374 
cheat_if_confirmed(const reaction_t & act)1375   inline void cheat_if_confirmed(const reaction_t& act) {
1376     if(cheat_forbidden())
1377       return;
1378     if(needConfirmationEvenIfSaved()) pushScreen([act] () { confirm_dialog(XLAT("This will enable the cheat mode, making this game ineligible for scoring. Are you sure?"), act); });
1379     else act();
1380     }
1381 
do_if_confirmed(const reaction_t & act)1382   inline void do_if_confirmed(const reaction_t& act) {
1383     if(needConfirmationEvenIfSaved()) pushScreen([act] () { confirm_dialog(XLAT("This will end your current game and start a new one. Are you sure?"), act); });
1384     else act();
1385     }
1386 
push_confirm_dialog(const reaction_t & act,const string & s)1387   inline void push_confirm_dialog(const reaction_t& act, const string& s) {
1388     pushScreen([act, s] () { confirm_dialog(s, act); });
1389     }
1390 
add_confirmation(const reaction_t & act)1391   inline reaction_t add_confirmation(const reaction_t& act) {
1392     return [act] { do_if_confirmed(act); };
1393     }
1394   #endif
1395 
1396   }
1397 
1398 }
1399