// Hyperbolic Rogue -- dialogs // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details /** \file dialogs.cpp * \brief Implementation of various generic dialogs and elements of dialog windows */ #include "hyper.h" namespace hr { EX const char* COLORBAR = "###"; EX namespace dialog { #if HDR #define IFM(x) (mousing?"":x) static const int DONT_SHOW = 16; enum tDialogItem {diTitle, diItem, diBreak, diHelp, diInfo, diIntSlider, diSlider, diBigItem, diKeyboard}; struct item { tDialogItem type; string body; string value; int key; color_t color, colorv, colork, colors, colorc; int scale; double param; int p1, p2, p3; int position; }; struct scaler { ld (*direct) (ld); ld (*inverse) (ld); bool positive; }; static inline ld identity_f(ld x) { return x; } const static scaler identity = {identity_f, identity_f, false}; const static scaler logarithmic = {log, exp, true}; const static scaler asinhic = {asinh, sinh, false}; const static scaler asinhic100 = {[] (ld x) { return asinh(x*100); }, [] (ld x) { return sinh(x)/100; }, false}; struct numberEditor { ld *editwhat; string s; ld vmin, vmax, step, dft; string title, help; scaler sc; int *intval; ld intbuf; bool animatable; }; extern numberEditor ne; inline void scaleLog() { ne.sc = logarithmic; } inline void scaleSinh() { ne.sc = asinhic; } inline void scaleSinh100() { ne.sc = asinhic100; } #endif EX color_t dialogcolor = 0xC0C0C0; EX void addBack() { addItem(XLAT("go back"), (cmode & sm::NUMBER) ? SDLK_RETURN : ISWEB ? SDLK_BACKSPACE : SDLK_ESCAPE); } EX void addHelp() { addItem(XLAT("help"), SDLK_F1); } EX namespace zoom { int zoomf = 1, shiftx, shifty; bool zoomoff = false; void nozoom() { zoomf = 1; shiftx = 0; shifty = 0; zoomoff = false; } void initzoom() { zoomf = 3; shiftx = -2*mousex; if(mousex < vid.xres / 6) shiftx = 0; if(mousex > vid.xres * 5 / 6) shiftx = -2 * vid.xres; shifty = -2*mousey; if(mousey < vid.yres / 6) shifty = 0; if(mousey > vid.yres * 5 / 6) shifty = -2 * vid.yres; } void stopzoom() { zoomoff = true; } EX bool displayfr(int x, int y, int b, int size, const string &s, color_t color, int align) { return hr::displayfr(x * zoomf + shiftx, y * zoomf + shifty, b, size * zoomf, s, color, align); } EX bool displayfr_highlight(int x, int y, int b, int size, const string &s, color_t color, int align, int hicolor IS(0xFFFF00)) { bool clicked = hr::displayfr(x * zoomf + shiftx, y * zoomf + shifty, b, size * zoomf, s, color, align); if(clicked) hr::displayfr(x * zoomf + shiftx, y * zoomf + shifty, b, size * zoomf, s, hicolor, align); return clicked; } EX } #if CAP_MENUSCALING && CAP_SDL EX void handleZooming(SDL_Event &ev) { using namespace zoom; if(zoomoff || !(cmode & sm::ZOOMABLE)) { nozoom(); return; } if(ev.type == SDL_MOUSEBUTTONDOWN) initzoom(); if(ev.type == SDL_MOUSEBUTTONUP && zoomf > 1) stopzoom(); } #endif #if !(CAP_MENUSCALING && CAP_SDL) EX void handleZooming(SDL_Event &ev) {} #endif EX vector items; EX item& lastItem() { return items[items.size() - 1]; } EX item& titleItem() { return items[0]; } EX map key_actions; EX void add_key_action(int key, const reaction_t& action) { key_actions[key] = action; } EX void add_key_action_adjust(int& key, const reaction_t& action) { while(key_actions.count(key)) key++; add_key_action(key, action); } EX void extend() { items.back().key = items[isize(items)-2].key; } EX void add_action(const reaction_t& action) { add_key_action_adjust(lastItem().key, action); } EX void add_action_push(const reaction_t& action) { add_action([action] { pushScreen(action); }); } EX void handler(int sym, int uni) { dialog::handleNavigation(sym, uni); if(doexiton(sym, uni)) popScreen(); } EX void init() { items.clear(); key_actions.clear(); keyhandler = dialog::handler; } EX string keyname(int k) { if(k == 0) return ""; if(k == SDLK_ESCAPE) return "Esc"; if(k == SDLK_F5) return "F5"; if(k == SDLK_F10) return "F10"; if(k == SDLK_F1) return "F1"; if(k == SDLK_HOME) return "Home"; if(k == SDLK_BACKSPACE) return "Backspace"; if(k == SDLK_RETURN) return "Enter"; if(k == 32) return "space"; if(k >= 1 && k <= 26) { string s = "Ctrl+"; s += (k+64); return s; } if(k < 128) { string s; s += k; return s; } if(k == 508) return "Alt+8"; return "?"; } EX void addSlider(double d1, double d2, double d3, int key) { item it; it.type = diSlider; it.color = dialogcolor; it.scale = 100; it.key = key; it.param = (d2-d1) / (d3-d1); items.push_back(it); } EX void addIntSlider(int d1, int d2, int d3, int key) { item it; it.type = diIntSlider; it.color = dialogcolor; it.scale = 100; it.key = key; it.p1 = (d2-d1); it.p2 = (d3-d1); items.push_back(it); } EX void addSelItem(string body, string value, int key) { item it; it.type = diItem; it.body = body; it.value = value; it.key = key; it.color = dialogcolor; it.colork = 0x808080; it.colorv = 0x80A040; it.colorc = 0xFFD500; it.colors = 0xFF8000; if(value == ONOFF(true)) it.colorv = 0x40FF40; if(value == ONOFF(false)) it.colorv = 0xC04040; it.scale = 100; items.push_back(it); } EX void addBoolItem(string body, bool value, int key) { addSelItem(body, ONOFF(value), key); } EX int displaycolor(color_t col) { int c = col >> 8; if(!c) return 0x181818; return c; } EX void addKeyboardItem(string keys) { item it; it.type = diKeyboard; it.body = keys; it.color = dialogcolor; it.colors = 0xFF8000; it.scale = 150; items.push_back(it); } EX void addColorItem(string body, int value, int key) { item it; it.type = diItem; it.body = body; it.value = COLORBAR; it.key = key; it.color = it.colorv = displaycolor(value); it.colors = it.color ^ 0x404040; it.colorc = it.color ^ 0x808080; it.colork = 0x808080; it.scale = 100; items.push_back(it); } EX void addHelp(string body) { item it; it.type = diHelp; it.body = body; it.scale = 100; if(isize(body) >= 500) it.scale = 70; items.push_back(it); } EX void addInfo(string body, color_t color IS(dialogcolor)) { item it; it.type = diInfo; it.body = body; it.color = color; it.scale = 100; items.push_back(it); } EX void addItem(string body, int key) { item it; it.type = diItem; it.body = body; it.key = key; it.color = dialogcolor; it.colork = 0x808080; it.colors = 0xFFD500; it.colorc = 0xFF8000; it.scale = 100; items.push_back(it); } EX void addBigItem(string body, int key) { item it; it.type = diBigItem; it.body = body; it.key = key; it.color = 0xC06000; it.colors = 0xFFD500; it.colorc = 0xFF8000; it.scale = 150; items.push_back(it); } EX int addBreak(int val) { item it; it.type = diBreak; it.scale = val; items.push_back(it); return items.size()-1; } EX void addTitle(string body, color_t color, int scale) { item it; it.type = diTitle; it.body = body; it.color = color; it.scale = scale; items.push_back(it); } EX void init(string title, color_t color IS(0xE8E8E8), int scale IS(150), int brk IS(60)) { init(); addTitle(title, color, scale); addBreak(brk); } EX int dcenter, dwidth; EX int dialogflags; EX int displayLong(string str, int siz, int y, bool measure) { int last = 0; int lastspace = 0; int xs, xo; if(current_display->sidescreen) xs = dwidth - vid.fsize*2, xo = vid.yres + vid.fsize; else xs = vid.xres * 618/1000, xo = vid.xres * 186/1000; for(int i=0; i<=isize(str); i++) { int ls = 0; int prev = last; if(str[i] == ' ') lastspace = i; if(textwidth(siz, str.substr(last, i-last)) > xs) { if(lastspace == last) ls = i-1, last = i-1; else ls = lastspace, last = ls+1; } if(str[i] == 10 || i == isize(str)) ls = i, last = i+1; if(ls) { if(!measure) displayfr(xo, y, 2, siz, str.substr(prev, ls-prev), dialogcolor, 0); if(ls == prev) y += siz/2; else y += siz; lastspace = last; } } y += siz/2; return y; } EX int tothei, dialogwidth, dfsize, dfspace, leftwidth, rightwidth, innerwidth, itemx, keyx, valuex; EX string highlight_text; EX void measure() { tothei = 0; dialogwidth = 0; innerwidth = 0; int N = items.size(); for(int i=0; i key_queue; EX void queue_key(int key) { key_queue.push_back(key); } EX void display() { callhooks(hooks_display_dialog); int N = items.size(); dfsize = vid.fsize; #if ISMOBILE || ISPANDORA dfsize *= 3; #endif dfspace = dfsize * 5/4; dcenter = vid.xres/2; dwidth = vid.xres; if(current_display->sidescreen) { dwidth = vid.xres - vid.yres; dcenter = vid.xres - dwidth / 2; } measure(); while(tothei > vid.yres - 5 * vid.fsize) { int adfsize = int(dfsize * sqrt((vid.yres - 5. * vid.fsize) / tothei)); if(adfsize < dfsize-1) dfsize = adfsize + 1; else dfsize--; dfspace = dfsize * 5/4; measure(); } while(dialogwidth > dwidth) { int adfsize = int(dfsize * sqrt(vid.xres * 1. / dialogwidth)); if(adfsize < dfsize-1) dfsize = adfsize + 1; else dfsize--; // keep dfspace measure(); } tothei = (vid.yres - tothei) / 2; for(int i=0; i= top && mousey < tothei && I.key); if(cmode & sm::DIALOG_STRICT_X) xthis = xthis && (mousex >= dcenter - dialogwidth/2 && mousex <= dcenter + dialogwidth/2); displayfr(dcenter, mid, 2, dfsize * I.scale/100, I.body, I.color, 8); if(xthis) getcstat = I.key; } else if(I.type == diItem || I.type == diBigItem) { bool xthis = (mousey >= top && mousey < tothei); if(cmode & sm::DIALOG_STRICT_X) xthis = xthis && (mousex >= dcenter - dialogwidth/2 && mousex <= dcenter + dialogwidth/2); #if ISMOBILE if(xthis && mousepressed) I.color = I.colorc; #else if(xthis && mousemoved) { highlight_text = I.body; mousemoved = false; } if(highlight_text == I.body) { I.color = (xthis&&mousepressed&&actonrelease) ? I.colorc : I.colors; } #endif if(I.type == diBigItem) { displayfr(dcenter, mid, 2, dfsize * I.scale/100, I.body, I.color, 8); } else { if(!mousing) displayfr(keyx, mid, 2, dfsize * I.scale/100, keyname(I.key), I.colork, 16); displayfr(itemx, mid, 2, dfsize * I.scale/100, I.body, I.color, 0); int siz = dfsize * I.scale/100; while(siz > 6 && textwidth(siz, I.value) >= vid.xres - valuex) siz--; displayfr(valuex, mid, 2, siz, I.value, I.colorv, 0); } if(xthis) getcstat = I.key; } else if(among(I.type, diSlider, diIntSlider)) { bool xthis = (mousey >= top && mousey < tothei); int sl, sr; if(current_display->sidescreen) sl = vid.yres + vid.fsize*2, sr = vid.xres - vid.fsize*2; else sl = vid.xres/4, sr = vid.xres*3/4; int sw = sr-sl; if(I.type == diSlider) { displayfr(sl, mid, 2, dfsize * I.scale/100, "(", I.color, 16); displayfr(sl + double(sw * I.param), mid, 2, dfsize * I.scale/100, "#", I.color, 8); displayfr(sr, mid, 2, dfsize * I.scale/100, ")", I.color, 0); } else { displayfr(sl, mid, 2, dfsize * I.scale/100, "{", I.color, 16); if(I.p2 < sw / 4) for(int a=0; a<=I.p2; a++) if(a != I.p1) displayfr(sl + double(sw * a / I.p2), mid, 2, dfsize * I.scale/100, a == I.p1 ? "#" : ".", I.color, 8); displayfr(sl + double(sw * I.p1 / I.p2), mid, 2, dfsize * I.scale/100, "#", I.color, 8); displayfr(sr, mid, 2, dfsize * I.scale/100, "}", I.color, 0); } if(xthis) getcstat = I.key, inslider = true, slider_x = mousex; } else if(I.type == diKeyboard) { int len = 0; for(char c: I.body) if(c == ' ' || c == '\t') len += 3; else len++; int sl, sr; if(current_display->sidescreen) sl = vid.yres + vid.fsize*2, sr = vid.xres - vid.fsize*2; else sl = vid.xres/4, sr = vid.xres*3/4; int pos = 0; for(char c: I.body) { string s = ""; s += c; int nlen = 1; if(c == ' ') s = "SPACE", nlen = 3; if(c == '\b') s = "⌫", nlen = 1; if(c == '\r' || c == '\n') s = "⏎", nlen = 1; if(c == 1) s = "←", nlen = 1; if(c == 2) s = "→", nlen = 1; if(c == 3) s = "π"; if(c == '\t') s = "CLEAR", nlen = 3; int left = sl + (sr-sl) * pos / len; pos += nlen; int right = sl + (sr-sl) * pos / len; bool in = (mousex >= left && mousex <= right && mousey >= top && mousey < tothei); int xpos = (left + right) / 2; if(in) { if(c == 1) getcstat = SDLK_LEFT; else if(c == 2) getcstat = SDLK_RIGHT; else getcstat = c; } displayfr(xpos, mid, 2, dfsize * I.scale/100, s, in ? I.colors : I.color, 8); } } } } bool isitem(item& it) { return it.type == diItem || it.type == diBigItem; } EX void handle_actions(int &sym, int &uni) { if(key_actions.count(uni)) { key_actions[uni](); sym = uni = 0; return; } if(key_actions.count(sym)) { key_actions[sym](); sym = uni = 0; return; } } EX void handleNavigation(int &sym, int &uni) { if(uni == '\n' || uni == '\r' || DIRECTIONKEY == SDLK_KP5) { for(int i=0; i= 'A' && uni <= 'D') { int x = (slider_x - (dcenter-dwidth/4)) * 510 / dwidth; if(x < 0) x = 0; if(x > 255) x = 255; part(color, uni - 'A') = x; } else if(uni == ' ' || uni == '\n' || uni == '\r') { bool inHistory = false; for(int i=0; i<10; i++) if(colorhistory[i] == (color << shift)) inHistory = true; if(!inHistory) { colorhistory[lch] = (color << shift); lch++; lch %= 10; } popScreen(); if(reaction) reaction(); if(reaction_final) reaction_final(); } else if(uni >= '0' && uni <= '9') { color = colorhistory[uni - '0'] >> shift; } else if(palette && uni >= 'a' && uni < 'a'+(int) palette[0]) { color = palette[1 + uni - 'a'] >> shift; } else if(DKEY == SDLK_DOWN) { colorp = (colorp-1) & 3; } else if(DKEY == SDLK_UP) { colorp = (colorp+1) & 3; } else if(DKEY == SDLK_LEFT) { unsigned char* pts = (unsigned char*) &color; pts[colorp] -= abs(shiftmul) < .6 ? 1 : 17; } else if(DKEY == SDLK_RIGHT) { unsigned char* pts = (unsigned char*) &color; pts[colorp] += abs(shiftmul) < .6 ? 1 : 17; } else if(doexiton(sym, uni)) { popScreen(); if(reaction_final) reaction_final(); } } EX bool colorAlpha; EX void drawColorDialog() { cmode = sm::NUMBER | dialogflags; if(cmode & sm::SIDE) gamescreen(0); dcenter = vid.xres/2; dwidth = vid.xres; if(current_display->sidescreen) { dwidth = vid.xres - vid.yres; dcenter = vid.xres - dwidth / 2; } color_t color = *colorPointer; int ash = 8; for(int j=0; j<10; j++) { int x = dcenter + vid.fsize * 2 * (j-5); int y = vid.yres / 2- 5 * vid.fsize; string s0 = ""; s0 += ('0'+j); vid.fsize *= 2; displayColorButton(x, y, s0, '0'+j, 0, 0, colorhistory[j] >> ash); vid.fsize /= 2; } if(palette) { int q = palette[0]; for(int i=0; i> ash); vid.fsize /= 2; } } for(int i=0; i<4; i++) { int y = vid.yres / 2 + (2-i) * vid.fsize * 2; if(i == 3 && !colorAlpha) continue; color_t col = ((i==colorp) && !mousing) ? 0xFFD500 : dialogcolor; displayColorButton(dcenter - dwidth/4, y, "(", 0, 16, 0, col); string rgt = ") "; rgt += "ABGR" [i+(colorAlpha?0:1)]; displayColorButton(dcenter + dwidth/4, y, rgt, 0, 0, 0, col); displayColorButton(dcenter - dwidth/4 + dwidth * part(color, i) / 510, y, "#", 0, 8, 0, col); if(mousey >= y - vid.fsize && mousey < y + vid.fsize) getcstat = 'A' + i, inslider = true, slider_x = mousex; } displayColorButton(dcenter, vid.yres/2+vid.fsize * 6, XLAT("select this color") + " : " + format(colorAlpha ? "%08X" : "%06X", color), ' ', 8, 0, color >> (colorAlpha ? ash : 0)); if(extra_options) extra_options(); keyhandler = handleKeyColor; } EX void openColorDialog(unsigned int& col, unsigned int *pal IS(palette)) { colorPointer = &col; palette = pal; colorAlpha = true; dialogflags = 0; pushScreen(drawColorDialog); reaction = reaction_t(); extra_options = reaction_t(); } EX numberEditor ne; EX bool editingDetail() { return ne.editwhat == &vid.highdetail || ne.editwhat == &vid.middetail; } int ldtoint(ld x) { if(x > 0) return int(x+.5); else return int(x-.5); } EX string disp(ld x) { if(dialogflags & sm::HEXEDIT) return "0x" + itsh((unsigned long long)(x)); else if(ne.intval) return its(ldtoint(x)); else return fts(x); } EX reaction_t reaction; EX reaction_t reaction_final; EX reaction_t extra_options; EX void apply_slider() { if(ne.intval) *ne.intval = ldtoint(*ne.editwhat); if(reaction) reaction(); if(ne.intval) *ne.editwhat = *ne.intval; ne.s = disp(*ne.editwhat); #if CAP_ANIMATIONS anims::deanimate(*ne.editwhat); #endif } EX void use_hexeditor() { dialogflags |= sm::HEXEDIT; ne.s = disp(*ne.editwhat); } EX void apply_edit() { try { exp_parser ep; ep.s = ne.s; ld x = ep.rparse(); if(ne.sc.positive && x <= 0) return; *ne.editwhat = x; if(ne.intval) *ne.intval = ldtoint(*ne.editwhat); #if CAP_ANIMATIONS if(ne.animatable) anims::animate_parameter(*ne.editwhat, ne.s, reaction ? reaction : reaction_final); #endif if(reaction) reaction(); } catch(hr_parse_exception&) { } } EX void bound_low(ld val) { auto r = reaction; reaction = [r, val] () { if(*ne.editwhat < val) { *ne.editwhat = val; if(ne.intval) *ne.intval = ldtoint(*ne.editwhat); } if(r) r(); }; } EX void bound_up(ld val) { auto r = reaction; reaction = [r, val] () { if(*ne.editwhat > val) { *ne.editwhat = val; if(ne.intval) *ne.intval = ldtoint(*ne.editwhat); } if(r) r(); }; } EX int numberdark; EX void formula_keyboard(bool lr) { addKeyboardItem("1234567890"); addKeyboardItem("=+-*/^()\x3"); addKeyboardItem("qwertyuiop"); addKeyboardItem("asdfghjkl"); addKeyboardItem("zxcvbnm,.\b"); addKeyboardItem(lr ? " \t\x1\x2" : " \t"); } EX bool onscreen_keyboard = ISMOBILE; EX void number_dialog_help() { init("number dialog help"); dialog::addBreak(100); dialog::addHelp(XLAT("You can enter formulas in this dialog.")); dialog::addBreak(100); dialog::addHelp(XLAT("Functions available:")); addHelp(available_functions()); dialog::addBreak(100); dialog::addHelp(XLAT("Constants and variables available:")); addHelp(available_constants()); if(ne.animatable) { dialog::addBreak(100); dialog::addHelp(XLAT("Animations:")); dialog::addHelp(XLAT("a..b -- animate linearly from a to b")); dialog::addHelp(XLAT("a..b..|c..d -- animate from a to b, then from c to d")); dialog::addHelp(XLAT("a../x..b../y -- change smoothly, x and y are derivatives")); } /* "Most parameters can be animated simply by using '..' in their editing dialog. " "For example, the value of a parameter set to 0..1 will grow linearly from 0 to 1. " "You can also use functions (e.g. cos(0..2*pi)) and refer to other parameters." )); */ #if CAP_ANIMATIONS dialog::addBreak(50); auto f = find_edit(ne.intval ? (void*) ne.intval : (void*) ne.editwhat); if(f) dialog::addHelp(XLAT("Parameter names, e.g. '%1'", f->parameter_name)); else dialog::addHelp(XLAT("Parameter names")); dialog::addBreak(50); for(auto& ap: anims::aps) { string what = "?"; for(auto& p: params) if(p.second->affects(ap.value)) what = p.first; dialog::addInfo(what + " = " + ap.formula); } #endif dialog::addBreak(50); dialog::addHelp(XLAT("These can be combined, e.g. %1", "projection*sin(0..2*pi)")); display(); } EX void parser_help() { ne.editwhat = nullptr; ne.intval = nullptr; addItem("help", SDLK_F1); add_action_push(number_dialog_help); } EX void drawNumberDialog() { cmode = sm::NUMBER | dialogflags; if(numberdark < DONT_SHOW) gamescreen(numberdark); init(ne.title); addInfo(ne.s); if(ne.intval && ne.sc.direct == &identity_f) addIntSlider(int(ne.vmin), int(*ne.editwhat), int(ne.vmax), 500); else addSlider(ne.sc.direct(ne.vmin), ne.sc.direct(*ne.editwhat), ne.sc.direct(ne.vmax), 500); addBreak(100); #if !ISMOBILE addHelp(XLAT("You can scroll with arrow keys -- Ctrl to fine-tune")); addBreak(100); #endif dialog::addBack(); addSelItem(XLAT("default value"), disp(ne.dft), SDLK_HOME); add_edit(onscreen_keyboard); addItem("help", SDLK_F1); add_action_push(number_dialog_help); addBreak(100); if(ne.help != "") { addHelp(ne.help); // bool scal = !ISMOBILE && !ISPANDORA && isize(ne.help) > 160; // if(scal) lastItem().scale = 30; } if(extra_options) extra_options(); if(onscreen_keyboard) { addBreak(100); formula_keyboard(false); } display(); keyhandler = [] (int sym, int uni) { handleNavigation(sym, uni); if((uni >= '0' && uni <= '9') || among(uni, '.', '+', '-', '*', '/', '^', '(', ')', ',', '|', 3) || (uni >= 'a' && uni <= 'z')) { if(uni == 3) ne.s += "pi"; else ne.s += uni; apply_edit(); } else if(uni == '\b' || uni == '\t') { ne.s = ne.s. substr(0, isize(ne.s)-1); sscanf(ne.s.c_str(), LDF, ne.editwhat); apply_edit(); } #if !ISMOBILE else if(DKEY == SDLK_RIGHT) { if(ne.intval && abs(shiftmul) < .6) (*ne.editwhat)++; else *ne.editwhat = ne.sc.inverse(ne.sc.direct(*ne.editwhat) + shiftmul * ne.step); if(abs(*ne.editwhat) < ne.step * 1e-6 && !ne.intval) *ne.editwhat = 0; apply_slider(); } else if(DKEY == SDLK_LEFT) { if(ne.intval && abs(shiftmul) < .6) (*ne.editwhat)--; else *ne.editwhat = ne.sc.inverse(ne.sc.direct(*ne.editwhat) - shiftmul * ne.step); if(abs(*ne.editwhat) < ne.step * 1e-6 && !ne.intval) *ne.editwhat = 0; apply_slider(); } #endif else if(sym == SDLK_HOME) { *ne.editwhat = ne.dft; apply_slider(); } else if(uni == 500) { int sl, sr; if(current_display->sidescreen) sl = vid.yres + vid.fsize*2, sr = vid.xres - vid.fsize*2; else sl = vid.xres/4, sr = vid.xres*3/4; ld d = (slider_x - sl + .0) / (sr-sl); ld val = ne.sc.inverse(d * (ne.sc.direct(ne.vmax) - ne.sc.direct(ne.vmin)) + ne.sc.direct(ne.vmin)); 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)); ld dif = abs(val - nextval); if(dif > 1e-6) { ld mul = 1; while(dif < 10) dif *= 10, mul *= 10; val *= mul; val = floor(val + 0.5); val /= mul; } *ne.editwhat = val; apply_slider(); } else if(doexiton(sym, uni)) { popScreen(); if(reaction_final) reaction_final(); } }; } int nlpage = 1; int wheelshift = 0; EX int handlePage(int& nl, int& nlm, int perpage) { nlm = nl; int onl = nl; int ret = 0; if(nlpage) { nl = nlm = perpage; if(nlpage == 2) ret = nlm; int w = wheelshift; int realw = 0; while(w<0 && ret) { ret--; w++; realw--; } while(w>0 && ret+perpage < onl) { ret++; w--; realw++; } wheelshift = realw; if(ret+nl > onl) nl = onl-ret; } return ret; } EX void displayPageButtons(int i, bool pages) { int i0 = vid.yres - vid.fsize; int xr = vid.xres / 80; if(pages) if(displayfrZH(xr*8, i0, 1, vid.fsize, IFM("1 - ") + XLAT("page") + " 1", nlpage == 1 ? 0xD8D8C0 : dialogcolor, 8)) getcstat = '1'; if(pages) if(displayfrZH(xr*24, i0, 1, vid.fsize, IFM("2 - ") + XLAT("page") + " 2", nlpage == 1 ? 0xD8D8C0 : dialogcolor, 8)) getcstat = '2'; if(pages) if(displayfrZH(xr*40, i0, 1, vid.fsize, IFM("3 - ") + XLAT("all"), nlpage == 1 ? 0xD8D8C0 : dialogcolor, 8)) getcstat = '3'; if(i&1) if(displayfrZH(xr*56, i0, 1, vid.fsize, IFM(keyname(SDLK_ESCAPE) + " - ") + XLAT("go back"), dialogcolor, 8)) getcstat = '0'; if(i&2) if(displayfrZH(xr*72, i0, 1, vid.fsize, IFM("F1 - ") + XLAT("help"), dialogcolor, 8)) getcstat = SDLK_F1; } EX bool handlePageButtons(int uni) { if(uni == '1') nlpage = 1, wheelshift = 0; else if(uni == '2') nlpage = 2, wheelshift = 0; else if(uni == '3') nlpage = 0, wheelshift = 0; else if(uni == PSEUDOKEY_WHEELUP) wheelshift--; else if(uni == PSEUDOKEY_WHEELDOWN) wheelshift++; else return false; return true; } EX void editNumber(ld& x, ld vmin, ld vmax, ld step, ld dft, string title, string help) { ne.editwhat = &x; ne.s = disp(x); ne.vmin = vmin; ne.vmax = vmax; ne.step = step; ne.dft = dft; ne.title = title; ne.help = help; ne.sc = identity; ne.intval = NULL; dialogflags = 0; if(cmode & sm::SIDE) dialogflags |= sm::MAYDARK | sm::SIDE; cmode |= sm::NUMBER; pushScreen(drawNumberDialog); reaction = reaction_t(); reaction_final = reaction_t(); extra_options = reaction_t(); numberdark = 0; ne.animatable = true; #if CAP_ANIMATIONS anims::get_parameter_animation(x, ne.s); #endif } EX void editNumber(int& x, int vmin, int vmax, ld step, int dft, string title, string help) { editNumber(ne.intbuf, vmin, vmax, step, dft, title, help); ne.intbuf = x; ne.intval = &x; ne.s = its(x); ne.animatable = false; } EX void helpToEdit(int& x, int vmin, int vmax, int step, int dft) { popScreen(); string title = "help"; if(help[0] == '@') { int iv = help.find("\t"); int id = help.find("\n"); title = help.substr(iv+1, id-iv-1); help = help.substr(id+1); } editNumber(x, vmin, vmax, step, dft, title, help); } //-- choose file dialog-- #define CDIR dialogcolor #define CFILE forecolor bool filecmp(const pair &f1, const pair &f2) { if(f1.first == "../") return true; if(f2.first == "../") return false; if(f1.second != f2.second) return f1.second == CDIR; return f1.first < f2.first; } string filecaption, cfileext; string *cfileptr; bool editext = false; bool_reaction_t file_action; void handleKeyFile(int sym, int uni); EX void drawFileDialog() { displayfr(vid.xres/2, 30 + vid.fsize, 2, vid.fsize, filecaption, forecolor, 8); string& cfile = *cfileptr; displayfr(vid.xres/2, 34 + vid.fsize * 2, 2, vid.fsize, cfile, 0xFFFF00, 8); displayColorButton(vid.xres*1/4, 38+vid.fsize * 3, XLAT("F4 = extension"), SDLK_F4, 8, 0, editext ? 0xFF00FF : 0xFFFF00, editext ? 0x800080 : 0x808000); displayButton(vid.xres*2/4, 38+vid.fsize * 3, XLAT("Enter = choose"), SDLK_RETURN, 8); displayButton(vid.xres*3/4, 38+vid.fsize * 3, XLAT("Esc = cancel"), SDLK_ESCAPE, 8); v.clear(); DIR *d; struct dirent *dir; string where = "."; for(int i=0; id_name; if(s != ".." && s[0] == '.') ; else if(isize(s) > 4 && s.substr(isize(s)-4) == cfileext) v.push_back(make_pair(s, CFILE)); else if(dir->d_type & DT_DIR) v.push_back(make_pair(s+"/", CDIR)); } closedir(d); } sort(v.begin(), v.end(), filecmp); int q = v.size(); int percolumn = (vid.yres-38) / (vid.fsize+5) - 4; int columns = 1 + (q-1) / percolumn; for(int i=0; i= 32 && uni < 127) { s.insert(i, s0 + char(uni)); } else if(uni >= 1000 && uni <= 1000+isize(v)) { string where = "", what = s, whereparent = "../"; for(int i=0; i= 2 && s.substr(i-2,3) == "../") whereparent = s.substr(0, i+1) + "../"; else whereparent = where; where = s.substr(0, i+1), what = s.substr(i+1); } int i = uni - 1000; if(v[i].first == "../") { s = whereparent + what; } else if(v[i].second == CDIR) s = where + v[i].first + what; else s = where + v[i].first; } return; } EX void openFileDialog(string& filename, string fcap, string ext, bool_reaction_t action) { cfileptr = &filename; filecaption = fcap; cfileext = ext; file_action = action; pushScreen(dialog::drawFileDialog); } // infix/v/vpush EX string infix; EX bool hasInfix(const string &s) { if(infix == "") return true; string t = ""; for(int i=0; i= 'a' && c <= 'z') tt += c - 32; else if(c >= 'A' && c <= 'Z') tt += c; else if(c == '@') tt += c; if(tt) t += tt; } return t.find(infix) != string::npos; } EX bool editInfix(int uni) { if(uni >= 'A' && uni <= 'Z') infix += uni; else if(uni >= 'a' && uni <= 'z') infix += uni-32; else if(infix != "" && uni == 8) infix = infix.substr(0, isize(infix)-1); else if(infix != "" && uni != 0) infix = ""; else return false; return true; } EX vector > v; EX void vpush(color_t color, const char *name) { string s = XLATN(name); if(!hasInfix(s)) return; dialog::v.push_back(make_pair(s, color)); } int editpos = 0; EX string *edited_string; EX string view_edited_string() { string cs = *edited_string; if(editpos < 0) editpos = 0; if(editpos > isize(cs)) editpos = isize(cs); cs.insert(editpos, "°"); return cs; } EX void start_editing(string& s) { edited_string = &s; editpos = isize(s); } EX string editchecker(int sym, int uni) { if(uni >= 32 && uni < 127) return string("") + char(uni); return ""; } EX bool handle_edit_string(int sym, int uni, function checker IS(editchecker)) { auto& es = *edited_string; string u2; if(DKEY == SDLK_LEFT) editpos--; else if(DKEY == SDLK_RIGHT) editpos++; else if(uni == 8) { if(editpos == 0) return true; es.replace(editpos-1, 1, ""); editpos--; } else if((u2 = checker(sym, uni)) != "") { for(char c: u2) { es.insert(editpos, 1, c); editpos ++; } } else return false; return true; } EX void string_edit_dialog() { cmode = sm::NUMBER | dialogflags; if(numberdark < DONT_SHOW) gamescreen(numberdark); init(ne.title); addInfo(view_edited_string()); addBreak(100); formula_keyboard(true); addBreak(100); dialog::addBack(); addBreak(100); if(ne.help != "") { addHelp(ne.help); } if(extra_options) extra_options(); display(); keyhandler = [] (int sym, int uni) { handleNavigation(sym, uni); if(handle_edit_string(sym, uni)) ; else if(doexiton(sym, uni)) { popScreen(); if(reaction_final) reaction_final(); } }; } EX void edit_string(string& s, string title, string help) { start_editing(s); ne.title = title; ne.help = help; dialogflags = 0; if(cmode & sm::SIDE) dialogflags |= sm::MAYDARK | sm::SIDE; cmode |= sm::NUMBER; pushScreen(string_edit_dialog); reaction = reaction_t(); extra_options = reaction_t(); numberdark = 0; } EX void confirm_dialog(const string& text, const reaction_t& act) { gamescreen(1); dialog::addBreak(250); dialog::init(XLAT("WARNING"), 0xFF0000, 150, 100); dialog::addHelp(text); dialog::addItem(XLAT("YES"), 'y'); auto yes = [act] () { popScreen(); act(); }; dialog::add_action(yes); dialog::add_key_action(SDLK_RETURN, yes); dialog::addItem(XLAT("NO"), 'n'); dialog::add_action([] () { popScreen(); }); dialog::display(); } EX void addBoolItem_action(const string& s, bool& b, char c) { dialog::addBoolItem(s, b, c); dialog::add_action([&b] { b = !b; }); } EX void addBoolItem_action_neg(const string& s, bool& b, char c) { dialog::addBoolItem(s, !b, c); dialog::add_action([&b] { b = !b; }); } EX bool cheat_forbidden() { if(tactic::on && !cheater) { addMessage(XLAT("Not available in the pure tactics mode!")); return true; } if(daily::on) { addMessage(XLAT("Not available in the daily challenge!")); return true; } return false; } EX void add_action_confirmed(const reaction_t& act) { dialog::add_action(dialog::add_confirmation(act)); } #if HDR template void addBoolItem_choice(const string& s, T& b, T val, char c) { addBoolItem(s, b == val, c); add_action([&b, val] { b = val; }); } inline void cheat_if_confirmed(const reaction_t& act) { if(cheat_forbidden()) return; if(needConfirmationEvenIfSaved()) pushScreen([act] () { confirm_dialog(XLAT("This will enable the cheat mode, making this game ineligible for scoring. Are you sure?"), act); }); else act(); } inline void do_if_confirmed(const reaction_t& act) { if(needConfirmationEvenIfSaved()) pushScreen([act] () { confirm_dialog(XLAT("This will end your current game and start a new one. Are you sure?"), act); }); else act(); } inline void push_confirm_dialog(const reaction_t& act, const string& s) { pushScreen([act, s] () { confirm_dialog(s, act); }); } inline reaction_t add_confirmation(const reaction_t& act) { return [act] { do_if_confirmed(act); }; } #endif } }