|
|
a20939 |
|
|
|
a20939 |
#include <sdl.h></sdl.h>
|
|
|
a20939 |
|
|
|
a20939 |
#include "private.h"
|
|
|
a20939 |
|
|
|
534343 |
#include "drawing.h"
|
|
|
534343 |
#include "font.h"
|
|
|
d7d433 |
#include "window.h"
|
|
|
a20939 |
|
|
|
a20939 |
|
|
|
1d9c50 |
static const char passwordPattern[] = "*";
|
|
|
97fe4e |
|
|
|
97fe4e |
|
|
|
534343 |
static int utf8charlen(const char *c) {
|
|
|
534343 |
if (!*c) return 0;
|
|
|
534343 |
if ( (c[0] & 0x80) == 0x00 )
|
|
|
534343 |
return 1;
|
|
|
534343 |
if ( (c[0] & 0xE0) == 0xC0
|
|
|
534343 |
&& (c[1] & 0xC0) == 0x80 )
|
|
|
534343 |
return 2;
|
|
|
534343 |
if ( (c[0] & 0xF0) == 0xE0
|
|
|
534343 |
&& (c[1] & 0xC0) == 0x80
|
|
|
534343 |
&& (c[2] & 0xC0) == 0x80 )
|
|
|
534343 |
return 3;
|
|
|
534343 |
if ( (c[0] & 0xF8) == 0xF0
|
|
|
534343 |
&& (c[1] & 0xC0) == 0x80
|
|
|
534343 |
&& (c[2] & 0xC0) == 0x80
|
|
|
534343 |
&& (c[3] & 0xC0) == 0x80 )
|
|
|
534343 |
return 4;
|
|
|
534343 |
return -1;
|
|
|
a20939 |
}
|
|
|
a20939 |
|
|
|
a20939 |
|
|
|
97fe4e |
static int utf8len(const char *c) {
|
|
|
97fe4e |
int len = 0;
|
|
|
97fe4e |
while(*c) {
|
|
|
97fe4e |
int l = utf8charlen(c);
|
|
|
97fe4e |
c += l > 0 ? l : 1;
|
|
|
97fe4e |
++len;
|
|
|
97fe4e |
}
|
|
|
97fe4e |
return len;
|
|
|
97fe4e |
}
|
|
|
97fe4e |
|
|
|
97fe4e |
|
|
|
97fe4e |
static int utf8pos(const char *c, int pos) {
|
|
|
97fe4e |
int p = 0;
|
|
|
97fe4e |
const char *end = c + pos;
|
|
|
97fe4e |
while(*c && c < end) {
|
|
|
97fe4e |
int l = utf8charlen(c);
|
|
|
97fe4e |
c += l > 0 ? l : 1;
|
|
|
97fe4e |
++p;
|
|
|
97fe4e |
}
|
|
|
97fe4e |
return p;
|
|
|
97fe4e |
}
|
|
|
97fe4e |
|
|
|
97fe4e |
|
|
|
534343 |
static int utf8shift(const char *s, int pos, int offset) {
|
|
|
534343 |
int len = strlen(s);
|
|
|
534343 |
if (pos > len) { offset += pos - len; pos = len; }
|
|
|
534343 |
if (pos < 0) { offset += pos; pos = 0; }
|
|
|
534343 |
|
|
|
534343 |
while(offset > 0 && pos < len) {
|
|
|
534343 |
int l = utf8charlen(s + pos);
|
|
|
534343 |
if (l > 0) {
|
|
|
534343 |
pos += l;
|
|
|
534343 |
} else {
|
|
|
534343 |
while(pos < len && utf8charlen(s + pos) <= 0) ++pos;
|
|
|
534343 |
}
|
|
|
534343 |
--offset;
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
534343 |
while(offset < 0 && pos > 0) {
|
|
|
534343 |
do --pos; while(pos > 0 && utf8charlen(s + pos) <= 0);
|
|
|
534343 |
++offset;
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
534343 |
return pos;
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
534343 |
|
|
|
534343 |
static void fixPos(HeliDialog *dialog) {
|
|
|
534343 |
int answerLen = strlen(dialog->answer);
|
|
|
534343 |
if (dialog->pos < 0) dialog->pos = 0;
|
|
|
534343 |
if (dialog->pos > answerLen) dialog->pos = answerLen;
|
|
|
534343 |
if (dialog->selPos < 0) dialog->selPos = 0;
|
|
|
534343 |
if (dialog->selPos > answerLen) dialog->selPos = answerLen;
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
534343 |
|
|
|
534343 |
static void insert(HeliDialog *dialog, const char *text) {
|
|
|
534343 |
int answerLen = strlen(dialog->answer);
|
|
|
534343 |
fixPos(dialog);
|
|
|
534343 |
|
|
|
534343 |
int pos0 = dialog->pos < dialog->selPos ? dialog->pos : dialog->selPos;
|
|
|
534343 |
int pos1 = dialog->pos < dialog->selPos ? dialog->selPos : dialog->pos;
|
|
|
534343 |
int selLen = pos1 - pos0;
|
|
|
534343 |
|
|
|
534343 |
int textLen = strlen(text);
|
|
|
534343 |
int dl = answerLen - selLen + textLen - dialog->maxAnswerSize;
|
|
|
534343 |
if (dl > 0) textLen -= dl;
|
|
|
534343 |
if (textLen < 0) textLen = 0;
|
|
|
534343 |
|
|
|
534343 |
int tailPos = pos0 + selLen;
|
|
|
534343 |
int offset = textLen - selLen;
|
|
|
534343 |
int tailLen = answerLen - tailPos;
|
|
|
534343 |
if (offset && tailLen > 0)
|
|
|
534343 |
memmove(
|
|
|
534343 |
dialog->answer + tailPos + offset,
|
|
|
534343 |
dialog->answer + tailPos,
|
|
|
534343 |
tailLen);
|
|
|
534343 |
tailPos += offset;
|
|
|
534343 |
dialog->answer[tailPos + tailLen] = 0;
|
|
|
534343 |
|
|
|
534343 |
if (textLen > 0)
|
|
|
534343 |
memcpy(dialog->answer + pos0, text, textLen);
|
|
|
534343 |
|
|
|
534343 |
dialog->pos = dialog->selPos = tailPos;
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
534343 |
|
|
|
534343 |
static void copy(HeliDialog *dialog) {
|
|
|
97fe4e |
if (!dialog->password && dialog->pos != dialog->selPos) {
|
|
|
534343 |
int pos0 = dialog->pos < dialog->selPos ? dialog->pos : dialog->selPos;
|
|
|
534343 |
int pos1 = dialog->pos < dialog->selPos ? dialog->selPos : dialog->pos;
|
|
|
534343 |
char c = dialog->answer[pos1];
|
|
|
534343 |
dialog->answer[pos1] = 0;
|
|
|
534343 |
SDL_SetClipboardText(dialog->answer + pos0);
|
|
|
534343 |
dialog->answer[pos1] = c;
|
|
|
534343 |
}
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
534343 |
|
|
|
534343 |
static void paste(HeliDialog *dialog) {
|
|
|
534343 |
const char *text = SDL_GetClipboardText();
|
|
|
534343 |
if (text && text[0]) insert(dialog, text);
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
534343 |
|
|
|
534343 |
static void draw(HeliDialog *dialog) {
|
|
|
deef1d |
saveState();
|
|
|
534343 |
|
|
|
da4619 |
const double time = SDL_GetTicks()*1e-3;
|
|
|
da4619 |
|
|
|
d7d433 |
const double w = windowGetWidth();
|
|
|
d7d433 |
const double h = windowGetHeight();
|
|
|
534343 |
const double border = 16;
|
|
|
97fe4e |
|
|
|
97fe4e |
double title = 64;
|
|
|
97fe4e |
double buttons = 32;
|
|
|
97fe4e |
double l = border;
|
|
|
97fe4e |
double t = border + title;
|
|
|
97fe4e |
double r = w - border;
|
|
|
97fe4e |
double b = h - border - buttons;
|
|
|
97fe4e |
if (!dialog->multiline && b - t > buttons) {
|
|
|
97fe4e |
t = round(0.5*(b + t - buttons));
|
|
|
97fe4e |
b = t + buttons;
|
|
|
97fe4e |
title = t - border;
|
|
|
97fe4e |
}
|
|
|
97fe4e |
|
|
|
97fe4e |
const double cursorSrcollWidth = 64;
|
|
|
97fe4e |
const double cursorSrcollHeight = 16;
|
|
|
534343 |
|
|
|
534343 |
const double bt = h - buttons - border + 8;
|
|
|
534343 |
const double bb = h - border;
|
|
|
534343 |
const double bh = bb - bt;
|
|
|
534343 |
double bw = (w - 2*border)/4;
|
|
|
534343 |
if (bw < bh) bw = bh;
|
|
|
534343 |
const double bl0 = border - 2;
|
|
|
534343 |
const double br0 = bl0 + bw;
|
|
|
534343 |
const double br1 = w - border + 2;
|
|
|
534343 |
const double bl1 = br1 - bw;
|
|
|
534343 |
|
|
|
7ac038 |
unsigned int strokeColor = COLOR_WHITE;
|
|
|
7ac038 |
unsigned int fillColor = 0x4d4d4dff;
|
|
|
7ac038 |
unsigned int selTextColor = COLOR_BLACK;
|
|
|
7ac038 |
unsigned int selFillColor = COLOR_WHITE;
|
|
|
deef1d |
strokeWidth(1);
|
|
|
534343 |
textFontDefault();
|
|
|
534343 |
textSize(16);
|
|
|
534343 |
|
|
|
534343 |
noStroke();
|
|
|
534343 |
fill(fillColor);
|
|
|
534343 |
rect(0, 0, w, h);
|
|
|
534343 |
|
|
|
97fe4e |
|
|
|
534343 |
noFill();
|
|
|
534343 |
stroke(strokeColor);
|
|
|
534343 |
textAlign(HALIGN_LEFT, VALIGN_TOP);
|
|
|
97fe4e |
TextLayout layout = createTextLayout(dialog->password ? dialog->passwordText : dialog->answer);
|
|
|
97fe4e |
|
|
|
519bc5 |
int shift = keyDown("any shift");
|
|
|
97fe4e |
if (keyWentDown("up")) {
|
|
|
97fe4e |
dialog->pos = textLayoutCursorUp(layout, dialog->pos);
|
|
|
97fe4e |
if (!shift) dialog->selPos = dialog->pos;
|
|
|
97fe4e |
}
|
|
|
97fe4e |
if (keyWentDown("down")) {
|
|
|
97fe4e |
dialog->pos = textLayoutCursorDown(layout, dialog->pos);
|
|
|
97fe4e |
if (!shift) dialog->selPos = dialog->pos;
|
|
|
97fe4e |
}
|
|
|
97fe4e |
|
|
|
97fe4e |
int pos = dialog->pos;
|
|
|
97fe4e |
int selPos = dialog->selPos;
|
|
|
97fe4e |
if (dialog->password) {
|
|
|
97fe4e |
pos = utf8pos(dialog->answer, pos)*((int)sizeof(passwordPattern) - 1);
|
|
|
97fe4e |
selPos = utf8pos(dialog->answer, selPos)*((int)sizeof(passwordPattern) - 1);
|
|
|
97fe4e |
}
|
|
|
97fe4e |
|
|
|
f8dca4 |
cliprect(l-2, t-2, r-l+2, b-t+2);
|
|
|
97fe4e |
double tx = l;
|
|
|
97fe4e |
double ty = t;
|
|
|
97fe4e |
double cx = textLayoutCursorGetX(layout, pos) + tx;
|
|
|
97fe4e |
double cy = textLayoutCursorGetY(layout, pos) + ty;
|
|
|
97fe4e |
double ch = textLayoutCursorGetHeight(layout, pos);
|
|
|
97fe4e |
|
|
|
97fe4e |
double minScrollY = t + ch + cursorSrcollHeight - cy;
|
|
|
97fe4e |
double maxScrollY = b - cursorSrcollHeight - cy;
|
|
|
97fe4e |
if (maxScrollY > 0) maxScrollY = 0;
|
|
|
97fe4e |
if (dialog->scrollY < minScrollY) dialog->scrollY = minScrollY;
|
|
|
97fe4e |
if (dialog->scrollY > maxScrollY) dialog->scrollY = maxScrollY;
|
|
|
97fe4e |
|
|
|
97fe4e |
double minScrollX = l + cursorSrcollWidth - cx;
|
|
|
97fe4e |
double maxScrollX = r - cursorSrcollWidth - cx;
|
|
|
97fe4e |
if (maxScrollX > 0) maxScrollX = 0;
|
|
|
97fe4e |
if (dialog->scrollX < minScrollX) dialog->scrollX = minScrollX;
|
|
|
97fe4e |
if (dialog->scrollX > maxScrollX) dialog->scrollX = maxScrollX;
|
|
|
97fe4e |
|
|
|
97fe4e |
tx += dialog->scrollX;
|
|
|
97fe4e |
ty += dialog->scrollY;
|
|
|
97fe4e |
cx += dialog->scrollX;
|
|
|
97fe4e |
cy += dialog->scrollY;
|
|
|
97fe4e |
|
|
|
97fe4e |
if (pos == selPos) {
|
|
|
da4619 |
textLayoutDraw(layout, tx, ty);
|
|
|
da4619 |
} else {
|
|
|
97fe4e |
int p0 = pos < selPos ? pos : selPos;
|
|
|
97fe4e |
int p1 = pos < selPos ? selPos : pos;
|
|
|
da4619 |
fill(selFillColor);
|
|
|
da4619 |
stroke(selTextColor);
|
|
|
da4619 |
textLayoutDrawSubstr(layout, tx, ty, p0, p1 - p0);
|
|
|
da4619 |
noFill();
|
|
|
da4619 |
stroke(strokeColor);
|
|
|
da4619 |
textLayoutDrawSubstr(layout, tx, ty, 0, p0);
|
|
|
da4619 |
textLayoutDrawFrom(layout, tx, ty, p1);
|
|
|
da4619 |
}
|
|
|
97fe4e |
|
|
|
d4e89f |
stroke(colorByRGBA(1, 1, 1, 0.5 + 0.5*sin(time/0.5*2*PI)));
|
|
|
da4619 |
line(cx, cy, cx, cy - ch);
|
|
|
97fe4e |
|
|
|
da4619 |
textLayoutDestroy(layout);
|
|
|
8bc1f1 |
noClip();
|
|
|
534343 |
|
|
|
97fe4e |
|
|
|
534343 |
noFill();
|
|
|
534343 |
stroke(strokeColor);
|
|
|
534343 |
rect(l - 2, t - 2, r - l + 4, b - t + 4);
|
|
|
534343 |
|
|
|
534343 |
textAlign(HALIGN_CENTER, VALIGN_CENTER);
|
|
|
5d2371 |
text(w/2, border + title/2, dialog->question);
|
|
|
534343 |
|
|
|
534343 |
rect(bl0, bt, bw, bh);
|
|
|
534343 |
rect(bl1, bt, bw, bh);
|
|
|
5d2371 |
text((bl0 + br0)/2, (bt + bb)/2, "\u2613");
|
|
|
5d2371 |
text((bl1 + br1)/2, (bt + bb)/2, "\u2713");
|
|
|
534343 |
|
|
|
534343 |
if (mouseWentDown("left")) {
|
|
|
534343 |
double x = mouseX();
|
|
|
534343 |
double y = mouseY();
|
|
|
534343 |
if (y >= bt && y <= bb) {
|
|
|
534343 |
if (x >= bl0 && x <= br0) dialog->shown = FALSE;
|
|
|
534343 |
if (x >= bl1 && x <= br1) { dialog->success = TRUE; dialog->shown = FALSE; }
|
|
|
534343 |
}
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
deef1d |
restoreState();
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
534343 |
|
|
|
534343 |
void heliDialogDraw(HeliDialog *dialog) {
|
|
|
534343 |
if (dialog->newText[0]) insert(dialog, dialog->newText);
|
|
|
534343 |
|
|
|
519bc5 |
int shift = keyDown("any shift");
|
|
|
519bc5 |
int ctrl = keyDown("any ctrl");
|
|
|
534343 |
|
|
|
534343 |
if (keyWentDown("backspace")) {
|
|
|
534343 |
if (dialog->pos == dialog->selPos) {
|
|
|
534343 |
dialog->selPos = dialog->pos;
|
|
|
534343 |
dialog->pos = utf8shift(dialog->answer, dialog->pos, -1);
|
|
|
534343 |
}
|
|
|
534343 |
insert(dialog, "");
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
534343 |
if (keyWentDown("delete")) {
|
|
|
534343 |
if (dialog->pos == dialog->selPos) {
|
|
|
534343 |
dialog->selPos = dialog->pos;
|
|
|
534343 |
dialog->pos = utf8shift(dialog->answer, dialog->pos, 1);
|
|
|
534343 |
} else
|
|
|
97fe4e |
if (shift && !dialog->password) {
|
|
|
534343 |
copy(dialog);
|
|
|
534343 |
}
|
|
|
534343 |
insert(dialog, "");
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
534343 |
if (keyWentDown("left")) {
|
|
|
534343 |
dialog->pos = utf8shift(dialog->answer, dialog->pos, -1);
|
|
|
534343 |
if (!shift) dialog->selPos = dialog->pos;
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
534343 |
if (keyWentDown("right")) {
|
|
|
534343 |
dialog->pos = utf8shift(dialog->answer, dialog->pos, 1);
|
|
|
534343 |
if (!shift) dialog->selPos = dialog->pos;
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
da4619 |
if (keyWentDown("home")) {
|
|
|
da4619 |
if (ctrl) {
|
|
|
da4619 |
dialog->pos = 0;
|
|
|
da4619 |
} else {
|
|
|
da4619 |
while( dialog->pos > 0
|
|
|
da4619 |
&& dialog->answer[dialog->pos-1] != '\r'
|
|
|
da4619 |
&& dialog->answer[dialog->pos-1] != '\n' ) --dialog->pos;
|
|
|
da4619 |
}
|
|
|
534343 |
if (!shift) dialog->selPos = dialog->pos;
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
da4619 |
if (keyWentDown("end")) {
|
|
|
da4619 |
if (ctrl) {
|
|
|
da4619 |
dialog->pos = strlen(dialog->answer);
|
|
|
da4619 |
} else {
|
|
|
da4619 |
while( dialog->answer[dialog->pos] != 0
|
|
|
da4619 |
&& dialog->answer[dialog->pos] != '\r'
|
|
|
da4619 |
&& dialog->answer[dialog->pos] != '\n' ) ++dialog->pos;
|
|
|
da4619 |
}
|
|
|
534343 |
if (!shift) dialog->selPos = dialog->pos;
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
534343 |
if (keyWentDown("return")) {
|
|
|
534343 |
if (!dialog->multiline || ctrl) {
|
|
|
534343 |
dialog->success = TRUE;
|
|
|
534343 |
dialog->shown = FALSE;
|
|
|
534343 |
} else {
|
|
|
534343 |
insert(dialog, "\n");
|
|
|
534343 |
}
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
da4619 |
if (ctrl && keyWentDown("a"))
|
|
|
da4619 |
{ dialog->selPos = 0; dialog->pos = strlen(dialog->answer); }
|
|
|
97fe4e |
if (!dialog->password && ctrl && keyWentDown("c"))
|
|
|
534343 |
copy(dialog);
|
|
|
97fe4e |
if (!dialog->password && ctrl && keyWentDown("insert"))
|
|
|
534343 |
copy(dialog);
|
|
|
534343 |
if (ctrl && keyWentDown("v"))
|
|
|
534343 |
paste(dialog);
|
|
|
534343 |
if (shift && keyWentDown("insert"))
|
|
|
534343 |
paste(dialog);
|
|
|
97fe4e |
if (!dialog->password && ctrl && keyWentDown("x"))
|
|
|
534343 |
{ copy(dialog); insert(dialog, ""); }
|
|
|
534343 |
|
|
|
534343 |
if (keyWentDown("escape")) dialog->shown = FALSE;
|
|
|
534343 |
|
|
|
534343 |
if (!dialog->multiline) {
|
|
|
534343 |
char *c = strpbrk(dialog->answer, "\n\r");
|
|
|
534343 |
if (c) *c = 0;
|
|
|
534343 |
fixPos(dialog);
|
|
|
534343 |
}
|
|
|
534343 |
|
|
|
97fe4e |
if (dialog->password) {
|
|
|
97fe4e |
int len = utf8len(dialog->answer);
|
|
|
97fe4e |
char *c = dialog->passwordText;
|
|
|
97fe4e |
for(int i = 0; i < len; ++i)
|
|
|
97fe4e |
for(const char *p = passwordPattern; *p; ++p, ++c)
|
|
|
97fe4e |
*c = *p;
|
|
|
97fe4e |
*c = 0;
|
|
|
97fe4e |
}
|
|
|
97fe4e |
|
|
|
534343 |
draw(dialog);
|
|
|
534343 |
}
|