diff --git a/demo/src/common.c b/demo/src/common.c index cc306f0..f1bb965 100644 --- a/demo/src/common.c +++ b/demo/src/common.c @@ -75,7 +75,7 @@ void commonDraw() { textSize(16); text(16, 48, buffer); - textf(16, 16, "answer: %d, fps: %6.2f", answer, 1/worldGetFrameTime()); + textf(16, 16, "answer: %d, fps: %6.2f", answer, 1/windowGetFrameTime()); if (mouseWentDown("left")) soundPlay(beep, FALSE); diff --git a/demo/src/font.c b/demo/src/font.c index 747add9..69f46d0 100644 --- a/demo/src/font.c +++ b/demo/src/font.c @@ -22,7 +22,7 @@ void fontDraw() { double y = 256; fill(colorByName("1 0 0 0.5")); - textSize(64*(1+sin(worldGetSeconds()/4))); + textSize(64*(1+sin(windowGetSeconds()/4))); text(x, y, "Here is the\nleft aligned\ntext. VAW."); noFill(); diff --git a/demo/src/framebuffer.c b/demo/src/framebuffer.c index 0c5e085..b2b4804 100644 --- a/demo/src/framebuffer.c +++ b/demo/src/framebuffer.c @@ -23,7 +23,7 @@ void framebufferInit() { void framebufferDraw() { double step = 0.1; - t += worldGetFrameTime(); + t += windowGetFrameTime(); while(t > step) { saveState(); diff --git a/demo/src/main.c b/demo/src/main.c index ae75efb..8ce5282 100644 --- a/demo/src/main.c +++ b/demo/src/main.c @@ -40,15 +40,14 @@ void draw() { } int main() { - worldSetHeight(512); - worldSetWidth(1024); - worldSetResizable(TRUE); - worldSetFrameRateEx(1, 100); + windowSetSize(1024, 512); + windowSetResizable(TRUE); + windowSetVariableFrameRate(); - worldSetInit(&init); - worldSetDraw(&draw); + windowSetInit(&init); + windowSetDraw(&draw); - worldRun(); + windowRun(); return 0; } diff --git a/demo/src/phisics.c b/demo/src/phisics.c index dde503b..2a90724 100644 --- a/demo/src/phisics.c +++ b/demo/src/phisics.c @@ -49,7 +49,7 @@ void phisicsInit() { void phisicsDraw() { - double dt = worldGetFrameTime(); + double dt = windowGetFrameTime(); double accel = 100; double vx = spriteGetVelocityX(ball); diff --git a/demo/src/sprites.c b/demo/src/sprites.c index 1a65d9f..a2d0566 100644 --- a/demo/src/sprites.c +++ b/demo/src/sprites.c @@ -101,7 +101,7 @@ void spritesDraw() { const double rotatePeriod = 2; const double rotateAmplitude = 30; - double time = worldGetSeconds(); + double time = windowGetSeconds(); for(int i = 0; i < groupGetCount(pulse); ++i) { double scale = exp( scaleAmplitude * sin(shift*i + time/scalePeriod*2*PI) ); double rotation = rotateAmplitude * sin(shift*i + time/rotatePeriod*2*PI); diff --git a/doc/helianthus-doc-ru.odt b/doc/helianthus-doc-ru.odt index 7204845..62893e6 100644 Binary files a/doc/helianthus-doc-ru.odt and b/doc/helianthus-doc-ru.odt differ diff --git a/src/SConstruct b/src/SConstruct index 96a197d..5695493 100644 --- a/src/SConstruct +++ b/src/SConstruct @@ -49,7 +49,7 @@ headers = [ 'group.h', 'sound.h', 'sprite.h', - 'world.h' ] + 'window.h' ] root_headers = [ name + '.h' ] @@ -68,8 +68,8 @@ sources = [ 'test.c', 'sound.c', 'sprite.c', - 'world.c', - 'worldui.c', + 'window.c', + 'windowui.c', 'blob.S' ] diff --git a/src/animation.c b/src/animation.c index 190e5f0..21cf7fb 100644 --- a/src/animation.c +++ b/src/animation.c @@ -3,7 +3,7 @@ #include "private.h" #include "sprite.h" -#include "world.h" +#include "window.h" #include "animation.h" static HeliArray cache; @@ -403,8 +403,8 @@ Animation createAnimationEmpty() { animation->prev = last; *(animation->prev ? &animation->prev->next : &first) = last = animation; - double minFps = worldGetMinFrameRate(); - double maxFps = worldGetMaxFrameRate(); + double minFps = windowGetMinFrameRate(); + double maxFps = windowGetMaxFrameRate(); animation->fps = HELI_DEFAULT_FPS; if (animation->fps > maxFps) animation->fps = maxFps; if (animation->fps < minFps) animation->fps = minFps; diff --git a/src/drawing.c b/src/drawing.c index 33eb7b1..679011d 100644 --- a/src/drawing.c +++ b/src/drawing.c @@ -1,6 +1,6 @@ #include "private.h" -#include "world.h" +#include "window.h" #include "drawing.h" @@ -172,7 +172,7 @@ void viewportByFramebuffer(Framebuffer framebuffer) { if (framebuffer) glViewport(0, 0, framebufferGetWidth(framebuffer), framebufferGetHeight(framebuffer)); else - glViewport(0, 0, worldGetWidth(), worldGetHeight()); + glViewport(0, 0, windowGetWidth(), windowGetHeight()); } diff --git a/src/framebuffer.c b/src/framebuffer.c index ea0e518..67eba98 100644 --- a/src/framebuffer.c +++ b/src/framebuffer.c @@ -1,7 +1,6 @@ #include "private.h" -#include "world.h" #include "framebuffer.h" #include "drawing.h" diff --git a/src/group.c b/src/group.c index 32c0eac..089ea07 100644 --- a/src/group.c +++ b/src/group.c @@ -1,7 +1,7 @@ #include "private.h" #include "group.h" -#include "world.h" +#include "window.h" struct _Group { HeliArray sprites; @@ -38,7 +38,7 @@ Group createEdgesGroupEx(double x1, double y1, double x2, double y2, double bord } Group createEdgesGroup() - { return createEdgesGroupEx(0, 0, worldGetWidth(), worldGetHeight(), worldGetHeight(), 100); } + { return createEdgesGroupEx(0, 0, windowGetWidth(), windowGetHeight(), windowGetHeight(), 100); } void groupDestroy(Group group) { diff --git a/src/helianthus.h b/src/helianthus.h index 829454d..25af633 100644 --- a/src/helianthus.h +++ b/src/helianthus.h @@ -8,7 +8,7 @@ extern "C" { #include "helianthus/common.h" -#include "helianthus/world.h" +#include "helianthus/window.h" #include "helianthus/colors.h" #include "helianthus/drawing.h" #include "helianthus/font.h" diff --git a/src/private.h b/src/private.h index 79e8946..1c08c1b 100644 --- a/src/private.h +++ b/src/private.h @@ -282,7 +282,7 @@ void heliSoundUpdate(); void heliSoundFinish(); -// world +// window typedef struct _HeliDialog { int shown; diff --git a/src/sprite.c b/src/sprite.c index e95c120..da8ab78 100644 --- a/src/sprite.c +++ b/src/sprite.c @@ -2,7 +2,6 @@ #include "private.h" #include "sprite.h" -#include "world.h" #include "group.h" #include "drawing.h" #include "animation.h" diff --git a/src/window.c b/src/window.c new file mode 100644 index 0000000..77639a9 --- /dev/null +++ b/src/window.c @@ -0,0 +1,680 @@ + +#include + +#include + +#include "private.h" +#include "drawing.h" +#include "window.h" + + +static int sdlInitialized = 0; +static SDL_Window *window; +static SDL_GLContext context; + +static int started; +static int stopped; +static int firstFrame = TRUE; +static unsigned long long frameCount; +static unsigned long long elapsedTimeUs; +static unsigned long long elapsedTimeSinceLastFrameUs; +static unsigned int prevFrameTimeMs; + +HeliDialog dialog; + +static Callback initCallback; +static Callback drawCallback; +static Callback deinitCallback; + +static const int minWidth = 200; +static const int minHeight = 200; + +static int width = 512; +static int height = 512; +static int resizable; +static char title[1000]; +static int titleSize = (int)(sizeof(title)/sizeof(*title)); +static double minFPS = HELI_DEFAULT_FPS; +static double maxFPS = HELI_DEFAULT_FPS; +static double frameTime = 1.0/HELI_DEFAULT_FPS; + +static HeliArray keyEvents[6]; +static int keyEventsCount = (int)(sizeof(keyEvents)/sizeof(*keyEvents)); + +static int mouseMovedInFrame; +static double _mouseX; +static double _mouseY; + +static int _mouseScrolledX; +static int _mouseScrolledY; + + +static const char* keyAliases[][2] = { + // as user asked | as internally stored // + { "enter" , "return" }, + { "any enter" , "any return" }, + { "keypad return" , "keypad enter" } }; +static int keyAliasesCount = (int)(sizeof(keyAliases)/sizeof(*keyAliases)); +static const char* keyGroups[][2] = { + // as SDL passed | as internally stored // + { "left shift" , "any shift" }, + { "left ctrl" , "any ctrl" }, + { "left alt" , "any alt" }, + { "left gui" , "any gui" }, + { "right shift" , "any shift" }, + { "right ctrl" , "any ctrl" }, + { "right alt" , "any alt" }, + { "right gui" , "any gui" }, + { "0" , "any 0" }, + { "1" , "any 1" }, + { "2" , "any 2" }, + { "3" , "any 3" }, + { "4" , "any 4" }, + { "5" , "any 5" }, + { "6" , "any 6" }, + { "7" , "any 7" }, + { "8" , "any 8" }, + { "9" , "any 9" }, + { "/" , "any /" }, + { "*" , "any *" }, + { "-" , "any -" }, + { "+" , "any +" }, + { "return" , "any return" }, + { "keypad 0" , "any 0" }, + { "keypad 1" , "any 1" }, + { "keypad 2" , "any 2" }, + { "keypad 3" , "any 3" }, + { "keypad 4" , "any 4" }, + { "keypad 5" , "any 5" }, + { "keypad 6" , "any 6" }, + { "keypad 7" , "any 7" }, + { "keypad 8" , "any 8" }, + { "keypad 9" , "any 9" }, + { "keypad 9" , "any 9" }, + { "keypad /" , "any /" }, + { "keypad *" , "any *" }, + { "keypad -" , "any -" }, + { "keypad +" , "any +" }, + { "keypad enter" , "any return" } }; +static int keyGroupsCount = (int)(sizeof(keyGroups)/sizeof(*keyGroups)); + + + +int keyEventGetCount(KeyEvent mode) + { return (int)mode >= 0 && (int)mode <= keyEventsCount ? keyEvents[mode].count : 0; } +const char *keyEventGet(KeyEvent mode, int i) + { return (int)mode >= 0 && (int)mode <= keyEventsCount ? heliArrayGetValue(&keyEvents[mode], i) : NULL; } + +static int keyEventCheck(KeyEvent mode, const char *code) { + int count = keyEventGetCount(mode); + for(int i = 0; i < count; ++i) + if (heliStringCompareCi(keyEventGet(mode, i), code) == 0) + return TRUE; + return FALSE; +} + +static int keyEventAliasesCheck(KeyEvent mode, const char *code) { + if (keyEventCheck(mode, code)) return TRUE; + for(int i = 0; i < keyAliasesCount; ++i) + if (heliStringCompareCi(code, keyAliases[i][0]) == 0) + if (keyEventCheck(mode, keyAliases[i][1])) return TRUE; + return FALSE; +} + +static int keyEventAdd(KeyEvent mode, const char *code) { + if ((int)mode >= 0 && mode <= (int)keyEventsCount && !keyEventCheck(mode, code)) { + heliArrayInsert(&keyEvents[mode], -1, heliStringCopyLower(code), &free); + return TRUE; + } + return FALSE; +} + +static int keyEventRemove(KeyEvent mode, const char *code) { + int removed = FALSE; + if ((int)mode >= 0 && mode <= (int)keyEventsCount) + for(int i = keyEvents[mode].count-1; i >= 0; --i) + if (heliStringCompareCi(keyEvents[mode].items[i].value, code) == 0) + { heliArrayRemove(&keyEvents[mode], i); removed = TRUE; } + return removed; +} + +static void pressKey(int mouse, const char *code) { + keyEventAdd(mouse ? KEYEVENT_MOUSE_DOWN : KEYEVENT_KEY_DOWN, code); + keyEventAdd(mouse ? KEYEVENT_MOUSE_WENTDOWN : KEYEVENT_KEY_WENTDOWN, code); + if (!mouse) { + for(int i = 0; i < keyGroupsCount; ++i) { + if (heliStringCompareCi(code, keyGroups[i][0]) == 0) { + keyEventAdd(KEYEVENT_KEY_DOWN, keyGroups[i][1]); + keyEventAdd(KEYEVENT_KEY_WENTDOWN, keyGroups[i][1]); + } + } + } +} + +static void releaseKey(int mouse, const char *code) { + if (!keyEventRemove(mouse ? KEYEVENT_MOUSE_DOWN : KEYEVENT_KEY_DOWN, code)) + return; + keyEventAdd(mouse ? KEYEVENT_MOUSE_WENTUP : KEYEVENT_KEY_WENTUP, code); + if (mouse) + return; + for(int i = 0; i < keyGroupsCount; ++i) { + if (heliStringCompareCi(code, keyGroups[i][0]) != 0) continue; + int allRemoved = TRUE; + for(int j = 0; j < keyGroupsCount; ++j) + if ( heliStringCompareCi(keyGroups[i][1], keyGroups[j][1]) == 0 + && keyEventCheck(KEYEVENT_KEY_DOWN, keyGroups[j][0])) + { allRemoved = FALSE; break; } + if (allRemoved) + if (keyEventRemove(KEYEVENT_KEY_DOWN, keyGroups[i][1])) + keyEventAdd(KEYEVENT_KEY_WENTUP, keyGroups[i][1]); + } +} + +static void releaseAllKeys() { + int count = keyEventGetCount(KEYEVENT_KEY_DOWN); + for(int i = count-1; i >= 0; --i) + keyEventAdd(KEYEVENT_KEY_WENTUP, keyEventGet(KEYEVENT_KEY_DOWN, i)); + heliArrayClear(&keyEvents[KEYEVENT_KEY_DOWN]); + + count = keyEventGetCount(KEYEVENT_MOUSE_DOWN); + for(int i = count-1; i >= 0; --i) + keyEventAdd(KEYEVENT_MOUSE_WENTUP, keyEventGet(KEYEVENT_MOUSE_DOWN, i)); + heliArrayClear(&keyEvents[KEYEVENT_MOUSE_DOWN]); +} + + + +int keyDown(const char *code) + { return keyEventAliasesCheck(KEYEVENT_KEY_DOWN, code); } +int keyWentDown(const char *code) + { return keyEventAliasesCheck(KEYEVENT_KEY_WENTDOWN, code); } +int keyWentUp(const char *code) + { return keyEventAliasesCheck(KEYEVENT_KEY_WENTUP, code); } + +int mouseDidMove() + { return mouseMovedInFrame; } +int mouseDown(const char *code) + { return keyEventCheck(KEYEVENT_MOUSE_DOWN, code); } +int mouseWentDown(const char *code) + { return keyEventCheck(KEYEVENT_MOUSE_WENTDOWN, code); } +int mouseWentUp(const char *code) + { return keyEventCheck(KEYEVENT_MOUSE_WENTUP, code); } +int mouseScrolledX() + { return _mouseScrolledX; } +int mouseScrolledY() + { return _mouseScrolledY; } +double mouseX() + { return _mouseX; } +double mouseY() + { return _mouseY; } + +double mouseTransformedX() { + double x = mouseX(), y = mouseY(); + heliGLBackTransform(&x, &y); + return x; +} + +double mouseTransformedY() { + double x = mouseX(), y = mouseY(); + heliGLBackTransform(&x, &y); + return y; +} + +static void resize(int w, int h) { + w = w > minWidth ? w : minWidth; + h = h > minHeight ? h : minHeight; + if (width != w || height != h) { + width = w; + height = h; + if (started && !stopped && window) + SDL_SetWindowSize(window, width, height); + } +} + +int windowGetWidth() + { return width; } +void windowSetWidth(int w) + { resize(w, height); } + +int windowGetHeight() + { return height; } +void windowSetHeight(int h) + { resize(width, h); } + +void windowSetSize(int w, int h) + { resize(w, h); } + +int windowGetResizable() + { return resizable; } +void windowSetResizable(int r) { + if (resizable == r) return; + resizable = r ? TRUE : FALSE; + if (started && !stopped && window) + SDL_SetWindowResizable(window, resizable); +} + +const char* windowGetTitle() + { return title; } +void windowSetTitle(const char *t) { + int changed = FALSE; + for(int i = 0; i < titleSize-1; ++i) { + if (title[i] != t[i]) changed = TRUE; + title[i] = t[i]; + if (!t[i]) break; + } + if (changed && started && !stopped && window) + SDL_SetWindowTitle(window, title); +} + +double windowGetMinFrameRate() + { return minFPS; } +double windowGetMaxFrameRate() + { return minFPS; } +void windowSetFrameRateEx(double minFrameRate, double maxFrameRate) { + if (!(minFrameRate > HELI_MIN_FPS)) minFrameRate = HELI_MIN_FPS; + if (!(minFrameRate < HELI_MAX_FPS)) minFrameRate = HELI_MAX_FPS; + if (!(maxFrameRate > HELI_MIN_FPS)) maxFrameRate = HELI_MIN_FPS; + if (!(maxFrameRate < HELI_MAX_FPS)) maxFrameRate = HELI_MAX_FPS; + if (minFrameRate > maxFrameRate) minFrameRate = maxFrameRate; + minFPS = minFrameRate; + maxFPS = maxFrameRate; +} +void windowSetFrameRate(double frameRate) + { windowSetFrameRateEx(frameRate, frameRate); } +void windowSetVariableFrameRate() + { windowSetFrameRateEx(HELI_MIN_FPS, HELI_MAX_FPS); } + +double windowGetFrameTime() + { return frameTime; } + +int windowGetFrameCount() + { return (int)frameCount; } +double windowGetSeconds() + { return started ? elapsedTimeUs*1e-6 : 0.0; } + +void windowSetInit(Callback init) + { initCallback = init; } +void windowSetDraw(Callback draw) + { drawCallback = draw; } +void windowSetDeinit(Callback deinit) + { deinitCallback = deinit; } +void windowStop() + { if (started) stopped = TRUE; } + + +void messageBox(const char *message) { + SDL_ShowSimpleMessageBox( + SDL_MESSAGEBOX_INFORMATION, + title, + message, + window ); + prevFrameTimeMs = SDL_GetTicks(); +} + +int questionBox(const char *question, const char *answer0, const char *answer1) { + SDL_MessageBoxButtonData buttons[2] = {}; + + buttons[0].buttonid = 1; + buttons[0].flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT; + buttons[0].text = answer1; + + buttons[1].buttonid = 0; + buttons[1].flags = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT; + buttons[1].text = answer0; + + SDL_MessageBoxData data = {}; + data.flags = SDL_MESSAGEBOX_INFORMATION; + data.window = window; + data.title = title; + data.message = question; + data.buttons = buttons; + data.numbuttons = 2; + + int buttonid = 0;; + SDL_ShowMessageBox(&data, &buttonid); + prevFrameTimeMs = SDL_GetTicks(); + + return buttonid; +} + +int questionBox3(const char* question, const char* answer0, const char* answer1, const char* answer2) { + SDL_MessageBoxButtonData buttons[3] = {}; + + buttons[0].buttonid = 2; + buttons[0].flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT; + buttons[0].text = answer2; + + buttons[1].buttonid = 1; + buttons[1].text = answer1; + + buttons[2].buttonid = 0; + buttons[2].flags = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT; + buttons[2].text = answer0; + + SDL_MessageBoxData data = {}; + data.flags = SDL_MESSAGEBOX_INFORMATION; + data.window = window; + data.title = title; + data.message = question; + data.buttons = buttons; + data.numbuttons = 3; + + int buttonid = 0;; + SDL_ShowMessageBox(&data, &buttonid); + prevFrameTimeMs = SDL_GetTicks(); + + return buttonid; +} + +int askText(const char *question, char *answer, int maxAnswerSize) + { return askTextEx(question, answer, maxAnswerSize, FALSE, FALSE); } + + +static void resetEvents() { + dialog.newText[0] = 0; + _mouseScrolledX = _mouseScrolledY = 0; + heliArrayClear(&keyEvents[KEYEVENT_KEY_WENTDOWN]); + heliArrayClear(&keyEvents[KEYEVENT_KEY_WENTUP]); + heliArrayClear(&keyEvents[KEYEVENT_MOUSE_WENTDOWN]); + heliArrayClear(&keyEvents[KEYEVENT_MOUSE_WENTUP]); +} + + +static void draw() { + unsigned int currentFrameTimeMs = SDL_GetTicks(); + unsigned long long deltaUs = firstFrame ? 0 : (currentFrameTimeMs - prevFrameTimeMs)*1000ull; + prevFrameTimeMs = currentFrameTimeMs; + + double actualMinFPS = minFPS, actualMaxFPS = maxFPS; + if (dialog.shown) { actualMinFPS = 1, actualMaxFPS = 100; } + + unsigned long long minTimeStepUs = (unsigned long long)round(1e6/actualMaxFPS); + unsigned long long maxTimeStepUs = (unsigned long long)round(1e6/actualMinFPS); + elapsedTimeSinceLastFrameUs += deltaUs; + if (elapsedTimeSinceLastFrameUs > 2000000) + elapsedTimeSinceLastFrameUs = 2000000; + if (firstFrame || elapsedTimeSinceLastFrameUs >= minTimeStepUs) { + unsigned long long encountedTimeUs = elapsedTimeSinceLastFrameUs; + if (encountedTimeUs > maxTimeStepUs) encountedTimeUs = maxTimeStepUs; + double dt = encountedTimeUs*1e-6; + + if (!firstFrame) elapsedTimeSinceLastFrameUs -= encountedTimeUs; + + if (!dialog.shown) { + elapsedTimeUs += encountedTimeUs; + ++frameCount; + frameTime = firstFrame ? 1/maxFPS : dt; + heliAnimationUpdate(dt); + heliSpriteUpdate(dt); + } + heliSoundUpdate(); + + firstFrame = FALSE; + + viewportByWindow(); + projectionByViewport(); + + heliDrawingPrepareFrame(); + if (dialog.shown) { + heliDialogDraw(&dialog); + } else { + if (drawCallback) + drawCallback(); + } + + resetEvents(); + SDL_GL_SwapWindow(window); + } + + unsigned long long addUs = elapsedTimeSinceLastFrameUs + (SDL_GetTicks() - prevFrameTimeMs)*1000ull; + if (addUs < minTimeStepUs) { + unsigned long long waitUs = minTimeStepUs - addUs; + if (waitUs > 2000) + SDL_Delay( (unsigned int)((waitUs + 500)/1000ull) ); + } +} + + +static void deinit() { + heliGLStencilOpSeparatePtr = NULL; + + if (context) SDL_GL_DeleteContext(context); + context = NULL; + + if (window) SDL_DestroyWindow(window); + window = NULL; + + if (sdlInitialized) SDL_Quit(); + sdlInitialized = FALSE; +} + + +static int init() { + if (SDL_Init(SDL_INIT_EVERYTHING) < 0) { + fprintf(stderr, "helianthus: SDL_Init failed\n"); + deinit(); + return FALSE; + } + sdlInitialized = TRUE; + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetSwapInterval(1); + + unsigned int flags = SDL_WINDOW_OPENGL; + if (resizable) flags |= SDL_WINDOW_RESIZABLE; + + window = SDL_CreateWindow( + title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + width, height, flags ); + + if (!window) { + // try to create window without multisampling + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); + window = SDL_CreateWindow( + title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + width, height, flags ); + } + + + if (!window) { + fprintf(stderr, "helianthus: cannot create window: %s\n", SDL_GetError()); + SDL_ClearError(); + deinit(); + return FALSE; + } + + SDL_SetWindowMinimumSize(window, minWidth, minHeight); + + context = SDL_GL_CreateContext(window); + if (!context) { + fprintf(stderr, "helianthus: cannot create OpenGL context: %s\n", SDL_GetError()); + SDL_ClearError(); + deinit(); + return FALSE; + } + + heliGLBlendFuncSeparatePtr = SDL_GL_GetProcAddress("glBlendFuncSeparate"); + heliGLStencilOpSeparatePtr = SDL_GL_GetProcAddress("glStencilOpSeparate"); + heliGLTexImage2DMultisamplePtr = SDL_GL_GetProcAddress("glTexImage2DMultisample"); + heliGLGenFramebuffersPtr = SDL_GL_GetProcAddress("glGenFramebuffers"); + heliGLDeleteFramebuffersPtr = SDL_GL_GetProcAddress("glDeleteFramebuffers"); + heliGLBindFramebufferPtr = SDL_GL_GetProcAddress("glBindFramebuffer"); + heliGLBlitFramebufferPtr = SDL_GL_GetProcAddress("glBlitFramebuffer"); + heliGLFramebufferRenderbufferPtr = SDL_GL_GetProcAddress("glFramebufferRenderbuffer"); + heliGLFramebufferTexture2DPtr = SDL_GL_GetProcAddress("glFramebufferTexture2D"); + heliGLCheckFramebufferStatusPtr = SDL_GL_GetProcAddress("glCheckFramebufferStatus"); + heliGLGenRenderbuffersPtr = SDL_GL_GetProcAddress("glGenRenderbuffers"); + heliGLDeleteRenderbuffersPtr = SDL_GL_GetProcAddress("glDeleteRenderbuffers"); + heliGLBindRenderbufferPtr = SDL_GL_GetProcAddress("glBindRenderbuffer"); + heliGLRenderbufferStoragePtr = SDL_GL_GetProcAddress("glRenderbufferStorage"); + heliGLRenderbufferStorageMultisamplePtr = SDL_GL_GetProcAddress("glRenderbufferStorageMultisample"); + glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, (int*)&heliGLWindowFramebufferReadId); + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (int*)&heliGLWindowFramebufferDrawId); + + return TRUE; +} + + +static void handleEvent(SDL_Event *e) { + if (e->type == SDL_QUIT) { + stopped = TRUE; + } else + if (e->type == SDL_WINDOWEVENT) { + if (e->window.event == SDL_WINDOWEVENT_CLOSE) { + stopped = TRUE; + } else + if (e->window.event == SDL_WINDOWEVENT_RESIZED) { + width = e->window.data1; + height = e->window.data2; + } else + if (e->window.event == SDL_WINDOWEVENT_FOCUS_LOST) { + releaseAllKeys(); + } + } else + if (e->type == SDL_KEYDOWN || e->type == SDL_KEYUP) { + const char *keyname = SDL_GetKeyName(e->key.keysym.sym); + if (keyname && *keyname) { + if (e->type == SDL_KEYDOWN) + pressKey(FALSE, keyname); else releaseKey(FALSE, keyname); + } + } else + if (e->type == SDL_MOUSEBUTTONDOWN || e->type == SDL_MOUSEBUTTONUP) { + char *button = NULL; + switch(e->button.button) { + case SDL_BUTTON_LEFT: button = "left"; break; + case SDL_BUTTON_MIDDLE: button = "middle"; break; + case SDL_BUTTON_RIGHT: button = "right"; break; + default: break; + } + if (button) { + if (e->type == SDL_MOUSEBUTTONDOWN) + pressKey(TRUE, button); else releaseKey(TRUE, button); + } + _mouseX = e->button.x; + _mouseY = e->button.y; + } else + if (e->type == SDL_MOUSEMOTION) { + _mouseX = e->motion.x; + _mouseY = e->motion.y; + } else + if (e->type == SDL_MOUSEWHEEL) { + _mouseScrolledX += e->wheel.x; + _mouseScrolledY += e->wheel.y; + } else + if (e->type == SDL_TEXTINPUT) { + if (dialog.shown) { + int len = strlen(dialog.newText); + int newlen = strlen(e->text.text); + int dl = len + newlen + 1 - sizeof(dialog.newText); + if (dl > 0) newlen -= dl; + if (newlen > 0) { + memcpy(dialog.newText + len, e->text.text, newlen); + dialog.newText[len + newlen] = 0; + } + } + } +} + + +int askTextEx(const char *question, char *answer, int maxAnswerSize, int multiline, int password) { + if (maxAnswerSize < 0 || !answer) maxAnswerSize = 0; + + memset(&dialog, 0, sizeof(dialog)); + dialog.shown = TRUE; + dialog.question = question ? question : ""; + dialog.answer = calloc(1, maxAnswerSize + 1); + dialog.passwordText = calloc(1, maxAnswerSize*4 + 1); + dialog.maxAnswerSize = maxAnswerSize - 1; + if (maxAnswerSize > 0) memcpy(dialog.answer, answer, maxAnswerSize); + dialog.multiline = multiline != 0 && password == 0; + dialog.password = password != 0; + dialog.pos = dialog.selPos = strlen(dialog.answer); + dialog.success = FALSE; + + SDL_StartTextInput(); + while(dialog.shown && !stopped) { + SDL_Event event; + while (SDL_PollEvent(&event)) + handleEvent(&event); + draw(); + } + SDL_StopTextInput(); + + int success = dialog.success; + if (dialog.success && maxAnswerSize > 0) strcpy(answer, dialog.answer); + free(dialog.answer); + free(dialog.passwordText); + memset(&dialog, 0, sizeof(dialog)); + + resetEvents(); + heliArrayClear(&keyEvents[KEYEVENT_KEY_DOWN]); + heliArrayClear(&keyEvents[KEYEVENT_MOUSE_DOWN]); + + prevFrameTimeMs = SDL_GetTicks(); + heliDrawingPrepareFrame(); + + return success; +} + + +static void run() { + while(!stopped) { + SDL_Event event; + while (SDL_PollEvent(&event)) + handleEvent(&event); + draw(); + } +} + + +void windowRun() { + if (started) return; + started = TRUE; + stopped = FALSE; + + firstFrame = TRUE; + frameCount = 0; + elapsedTimeUs = 0; + elapsedTimeSinceLastFrameUs = 0; + srand(time(NULL)); + + if (init()) { + heliInitialized = TRUE; + + resetState(); + heliDoTests(); + + viewportByWindow(); + projectionByViewport(); + heliDrawingPrepareFrame(); + + if (initCallback) initCallback(); + run(); + if (deinitCallback) deinitCallback(); + + heliArrayClear(&heliObjectsSet); + heliSpriteFinish(); + heliDrawingFinish(); + heliFontFinish(); + heliAnimationFinish(); + heliSoundFinish(); + heliArrayDestroy(&heliObjectsSet); + + heliInitialized = FALSE; + + deinit(); + + _mouseScrolledX = _mouseScrolledY = 0; + for(int i = 0; i < keyEventsCount; ++i) + heliArrayDestroy(&keyEvents[i]); + } + + started = FALSE; +} diff --git a/src/window.h b/src/window.h new file mode 100644 index 0000000..c6baaa6 --- /dev/null +++ b/src/window.h @@ -0,0 +1,78 @@ +#ifndef HELI_WINDOW_H +#define HELI_WINDOW_H + + +#include "common.h" +#include "sprite.h" + +typedef void (*Callback)(); + +typedef enum _KeyEvent { + KEYEVENT_KEY_DOWN, + KEYEVENT_KEY_WENTDOWN, + KEYEVENT_KEY_WENTUP, + KEYEVENT_MOUSE_DOWN, + KEYEVENT_MOUSE_WENTDOWN, + KEYEVENT_MOUSE_WENTUP, +} KeyEvent; + + +void drawSprites(); + +int keyDown(const char *code); +int keyWentDown(const char *code); +int keyWentUp(const char *code); + +int keyEventGetCount(KeyEvent mode); +const char *keyEventGet(KeyEvent mode, int i); + +int mouseDidMove(); +int mouseDown(const char *code); +int mouseWentDown(const char *code); +int mouseWentUp(const char *code); +int mouseScrolledX(); +int mouseScrolledY(); +double mouseX(); +double mouseY(); + +double mouseTransformedX(); +double mouseTransformedY(); + +void messageBox(const char *message); +int questionBox(const char *question, const char *answer0, const char *answer1); +int questionBox3(const char *question, const char *answer0, const char *answer1, const char *answer2); +int askText(const char *question, char *answer, int maxAnswerSize); +int askTextEx(const char *question, char *answer, int maxAnswerSize, int multiline, int password); + +int windowGetWidth(); +void windowSetWidth(int width); + +int windowGetHeight(); +void windowSetHeight(int height); + +void windowSetSize(int width, int height); + +int windowGetResizable(); +void windowSetResizable(int resizable); + +const char* windowGetTitle(); +void windowSetTitle(const char *title); + +double windowGetMinFrameRate(); +double windowGetMaxFrameRate(); +void windowSetFrameRateEx(double minFrameRate, double maxFrameRate); +void windowSetFrameRate(double frameRate); +void windowSetVariableFrameRate(); +double windowGetFrameTime(); + +int windowGetFrameCount(); +double windowGetSeconds(); + +void windowSetInit(Callback init); +void windowSetDraw(Callback draw); +void windowSetDeinit(Callback deinit); +void windowRun(); +void windowStop(); + + +#endif diff --git a/src/windowui.c b/src/windowui.c new file mode 100644 index 0000000..1851720 --- /dev/null +++ b/src/windowui.c @@ -0,0 +1,375 @@ + +#include + +#include "private.h" + +#include "drawing.h" +#include "font.h" +#include "window.h" + + +static const char passwordPattern[] = "\u25CF"; + + +static int utf8charlen(const char *c) { + if (!*c) return 0; + if ( (c[0] & 0x80) == 0x00 ) + return 1; + if ( (c[0] & 0xE0) == 0xC0 + && (c[1] & 0xC0) == 0x80 ) + return 2; + if ( (c[0] & 0xF0) == 0xE0 + && (c[1] & 0xC0) == 0x80 + && (c[2] & 0xC0) == 0x80 ) + return 3; + if ( (c[0] & 0xF8) == 0xF0 + && (c[1] & 0xC0) == 0x80 + && (c[2] & 0xC0) == 0x80 + && (c[3] & 0xC0) == 0x80 ) + return 4; + return -1; +} + + +static int utf8len(const char *c) { + int len = 0; + while(*c) { + int l = utf8charlen(c); + c += l > 0 ? l : 1; + ++len; + } + return len; +} + + +static int utf8pos(const char *c, int pos) { + int p = 0; + const char *end = c + pos; + while(*c && c < end) { + int l = utf8charlen(c); + c += l > 0 ? l : 1; + ++p; + } + return p; +} + + +static int utf8shift(const char *s, int pos, int offset) { + int len = strlen(s); + if (pos > len) { offset += pos - len; pos = len; } + if (pos < 0) { offset += pos; pos = 0; } + + while(offset > 0 && pos < len) { + int l = utf8charlen(s + pos); + if (l > 0) { + pos += l; + } else { + while(pos < len && utf8charlen(s + pos) <= 0) ++pos; + } + --offset; + } + + while(offset < 0 && pos > 0) { + do --pos; while(pos > 0 && utf8charlen(s + pos) <= 0); + ++offset; + } + + return pos; +} + + +static void fixPos(HeliDialog *dialog) { + int answerLen = strlen(dialog->answer); + if (dialog->pos < 0) dialog->pos = 0; + if (dialog->pos > answerLen) dialog->pos = answerLen; + if (dialog->selPos < 0) dialog->selPos = 0; + if (dialog->selPos > answerLen) dialog->selPos = answerLen; +} + + +static void insert(HeliDialog *dialog, const char *text) { + int answerLen = strlen(dialog->answer); + fixPos(dialog); + + int pos0 = dialog->pos < dialog->selPos ? dialog->pos : dialog->selPos; + int pos1 = dialog->pos < dialog->selPos ? dialog->selPos : dialog->pos; + int selLen = pos1 - pos0; + + int textLen = strlen(text); + int dl = answerLen - selLen + textLen - dialog->maxAnswerSize; + if (dl > 0) textLen -= dl; + if (textLen < 0) textLen = 0; + + int tailPos = pos0 + selLen; + int offset = textLen - selLen; + int tailLen = answerLen - tailPos; + if (offset && tailLen > 0) + memmove( + dialog->answer + tailPos + offset, + dialog->answer + tailPos, + tailLen); + tailPos += offset; + dialog->answer[tailPos + tailLen] = 0; + + if (textLen > 0) + memcpy(dialog->answer + pos0, text, textLen); + + dialog->pos = dialog->selPos = tailPos; +} + + +static void copy(HeliDialog *dialog) { + if (!dialog->password && dialog->pos != dialog->selPos) { + int pos0 = dialog->pos < dialog->selPos ? dialog->pos : dialog->selPos; + int pos1 = dialog->pos < dialog->selPos ? dialog->selPos : dialog->pos; + char c = dialog->answer[pos1]; + dialog->answer[pos1] = 0; + SDL_SetClipboardText(dialog->answer + pos0); + dialog->answer[pos1] = c; + } +} + + +static void paste(HeliDialog *dialog) { + const char *text = SDL_GetClipboardText(); + if (text && text[0]) insert(dialog, text); +} + + +static void draw(HeliDialog *dialog) { + saveState(); + + const double time = SDL_GetTicks()*1e-3; + + const double w = windowGetWidth(); + const double h = windowGetHeight(); + const double border = 16; + + double title = 64; + double buttons = 32; + double l = border; + double t = border + title; + double r = w - border; + double b = h - border - buttons; + if (!dialog->multiline && b - t > buttons) { + t = round(0.5*(b + t - buttons)); + b = t + buttons; + title = t - border; + } + + const double cursorSrcollWidth = 64; + const double cursorSrcollHeight = 16; + + const double bt = h - buttons - border + 8; + const double bb = h - border; + const double bh = bb - bt; + double bw = (w - 2*border)/4; + if (bw < bh) bw = bh; + const double bl0 = border - 2; + const double br0 = bl0 + bw; + const double br1 = w - border + 2; + const double bl1 = br1 - bw; + + unsigned int strokeColor = COLOR_WHITE; + unsigned int fillColor = 0x4d4d4dff; + unsigned int selTextColor = COLOR_BLACK; + unsigned int selFillColor = COLOR_WHITE; + strokeWidth(1); + textFontDefault(); + textSize(16); + + noStroke(); + fill(fillColor); + rect(0, 0, w, h); + + + noFill(); + stroke(strokeColor); + textAlign(HALIGN_LEFT, VALIGN_TOP); + TextLayout layout = createTextLayout(dialog->password ? dialog->passwordText : dialog->answer); + + int shift = keyDown("any shift"); + if (keyWentDown("up")) { + dialog->pos = textLayoutCursorUp(layout, dialog->pos); + if (!shift) dialog->selPos = dialog->pos; + } + if (keyWentDown("down")) { + dialog->pos = textLayoutCursorDown(layout, dialog->pos); + if (!shift) dialog->selPos = dialog->pos; + } + + int pos = dialog->pos; + int selPos = dialog->selPos; + if (dialog->password) { + pos = utf8pos(dialog->answer, pos)*((int)sizeof(passwordPattern) - 1); + selPos = utf8pos(dialog->answer, selPos)*((int)sizeof(passwordPattern) - 1); + } + + cliprect(l-2, t-2, r-l+2, b-t+2); + double tx = l; + double ty = t; + double cx = textLayoutCursorGetX(layout, pos) + tx; + double cy = textLayoutCursorGetY(layout, pos) + ty; + double ch = textLayoutCursorGetHeight(layout, pos); + + double minScrollY = t + ch + cursorSrcollHeight - cy; + double maxScrollY = b - cursorSrcollHeight - cy; + if (maxScrollY > 0) maxScrollY = 0; + if (dialog->scrollY < minScrollY) dialog->scrollY = minScrollY; + if (dialog->scrollY > maxScrollY) dialog->scrollY = maxScrollY; + + double minScrollX = l + cursorSrcollWidth - cx; + double maxScrollX = r - cursorSrcollWidth - cx; + if (maxScrollX > 0) maxScrollX = 0; + if (dialog->scrollX < minScrollX) dialog->scrollX = minScrollX; + if (dialog->scrollX > maxScrollX) dialog->scrollX = maxScrollX; + + tx += dialog->scrollX; + ty += dialog->scrollY; + cx += dialog->scrollX; + cy += dialog->scrollY; + + if (pos == selPos) { + textLayoutDraw(layout, tx, ty); + } else { + int p0 = pos < selPos ? pos : selPos; + int p1 = pos < selPos ? selPos : pos; + fill(selFillColor); + stroke(selTextColor); + textLayoutDrawSubstr(layout, tx, ty, p0, p1 - p0); + noFill(); + stroke(strokeColor); + textLayoutDrawSubstr(layout, tx, ty, 0, p0); + textLayoutDrawFrom(layout, tx, ty, p1); + } + + stroke(colorByRGBA(1, 1, 1, 0.5 + 0.5*sin(time/0.5*2*PI))); + line(cx, cy, cx, cy - ch); + + textLayoutDestroy(layout); + noClip(); + + + noFill(); + stroke(strokeColor); + rect(l - 2, t - 2, r - l + 4, b - t + 4); + + textAlign(HALIGN_CENTER, VALIGN_CENTER); + text(w/2, border + title/2, dialog->question); + + rect(bl0, bt, bw, bh); + rect(bl1, bt, bw, bh); + text((bl0 + br0)/2, (bt + bb)/2, "\u2613"); + text((bl1 + br1)/2, (bt + bb)/2, "\u2713"); + + if (mouseWentDown("left")) { + double x = mouseX(); + double y = mouseY(); + if (y >= bt && y <= bb) { + if (x >= bl0 && x <= br0) dialog->shown = FALSE; + if (x >= bl1 && x <= br1) { dialog->success = TRUE; dialog->shown = FALSE; } + } + } + + restoreState(); +} + + +void heliDialogDraw(HeliDialog *dialog) { + if (dialog->newText[0]) insert(dialog, dialog->newText); + + int shift = keyDown("any shift"); + int ctrl = keyDown("any ctrl"); + + if (keyWentDown("backspace")) { + if (dialog->pos == dialog->selPos) { + dialog->selPos = dialog->pos; + dialog->pos = utf8shift(dialog->answer, dialog->pos, -1); + } + insert(dialog, ""); + } + + if (keyWentDown("delete")) { + if (dialog->pos == dialog->selPos) { + dialog->selPos = dialog->pos; + dialog->pos = utf8shift(dialog->answer, dialog->pos, 1); + } else + if (shift && !dialog->password) { + copy(dialog); + } + insert(dialog, ""); + } + + if (keyWentDown("left")) { + dialog->pos = utf8shift(dialog->answer, dialog->pos, -1); + if (!shift) dialog->selPos = dialog->pos; + } + + if (keyWentDown("right")) { + dialog->pos = utf8shift(dialog->answer, dialog->pos, 1); + if (!shift) dialog->selPos = dialog->pos; + } + + if (keyWentDown("home")) { + if (ctrl) { + dialog->pos = 0; + } else { + while( dialog->pos > 0 + && dialog->answer[dialog->pos-1] != '\r' + && dialog->answer[dialog->pos-1] != '\n' ) --dialog->pos; + } + if (!shift) dialog->selPos = dialog->pos; + } + + if (keyWentDown("end")) { + if (ctrl) { + dialog->pos = strlen(dialog->answer); + } else { + while( dialog->answer[dialog->pos] != 0 + && dialog->answer[dialog->pos] != '\r' + && dialog->answer[dialog->pos] != '\n' ) ++dialog->pos; + } + if (!shift) dialog->selPos = dialog->pos; + } + + if (keyWentDown("return")) { + if (!dialog->multiline || ctrl) { + dialog->success = TRUE; + dialog->shown = FALSE; + } else { + insert(dialog, "\n"); + } + } + + if (ctrl && keyWentDown("a")) + { dialog->selPos = 0; dialog->pos = strlen(dialog->answer); } + if (!dialog->password && ctrl && keyWentDown("c")) + copy(dialog); + if (!dialog->password && ctrl && keyWentDown("insert")) + copy(dialog); + if (ctrl && keyWentDown("v")) + paste(dialog); + if (shift && keyWentDown("insert")) + paste(dialog); + if (!dialog->password && ctrl && keyWentDown("x")) + { copy(dialog); insert(dialog, ""); } + + if (keyWentDown("escape")) dialog->shown = FALSE; + + if (!dialog->multiline) { + char *c = strpbrk(dialog->answer, "\n\r"); + if (c) *c = 0; + fixPos(dialog); + } + + if (dialog->password) { + int len = utf8len(dialog->answer); + char *c = dialog->passwordText; + for(int i = 0; i < len; ++i) + for(const char *p = passwordPattern; *p; ++p, ++c) + *c = *p; + *c = 0; + } + + draw(dialog); +} diff --git a/src/world.c b/src/world.c deleted file mode 100644 index 56c53ee..0000000 --- a/src/world.c +++ /dev/null @@ -1,680 +0,0 @@ - -#include - -#include - -#include "private.h" -#include "drawing.h" -#include "world.h" - - -static int sdlInitialized = 0; -static SDL_Window *window; -static SDL_GLContext context; - -static int started; -static int stopped; -static int firstFrame = TRUE; -static unsigned long long frameCount; -static unsigned long long elapsedTimeUs; -static unsigned long long elapsedTimeSinceLastFrameUs; -static unsigned int prevFrameTimeMs; - -HeliDialog dialog; - -static Callback initCallback; -static Callback drawCallback; -static Callback deinitCallback; - -static const int minWidth = 200; -static const int minHeight = 200; - -static int width = 512; -static int height = 512; -static int resizable; -static char title[1000]; -static int titleSize = (int)(sizeof(title)/sizeof(*title)); -static double minFPS = HELI_DEFAULT_FPS; -static double maxFPS = HELI_DEFAULT_FPS; -static double frameTime = 1.0/HELI_DEFAULT_FPS; - -static HeliArray keyEvents[6]; -static int keyEventsCount = (int)(sizeof(keyEvents)/sizeof(*keyEvents)); - -static int mouseMovedInFrame; -static double _mouseX; -static double _mouseY; - -static int _mouseScrolledX; -static int _mouseScrolledY; - - -static const char* keyAliases[][2] = { - // as user asked | as internally stored // - { "enter" , "return" }, - { "any enter" , "any return" }, - { "keypad return" , "keypad enter" } }; -static int keyAliasesCount = (int)(sizeof(keyAliases)/sizeof(*keyAliases)); -static const char* keyGroups[][2] = { - // as SDL passed | as internally stored // - { "left shift" , "any shift" }, - { "left ctrl" , "any ctrl" }, - { "left alt" , "any alt" }, - { "left gui" , "any gui" }, - { "right shift" , "any shift" }, - { "right ctrl" , "any ctrl" }, - { "right alt" , "any alt" }, - { "right gui" , "any gui" }, - { "0" , "any 0" }, - { "1" , "any 1" }, - { "2" , "any 2" }, - { "3" , "any 3" }, - { "4" , "any 4" }, - { "5" , "any 5" }, - { "6" , "any 6" }, - { "7" , "any 7" }, - { "8" , "any 8" }, - { "9" , "any 9" }, - { "/" , "any /" }, - { "*" , "any *" }, - { "-" , "any -" }, - { "+" , "any +" }, - { "return" , "any return" }, - { "keypad 0" , "any 0" }, - { "keypad 1" , "any 1" }, - { "keypad 2" , "any 2" }, - { "keypad 3" , "any 3" }, - { "keypad 4" , "any 4" }, - { "keypad 5" , "any 5" }, - { "keypad 6" , "any 6" }, - { "keypad 7" , "any 7" }, - { "keypad 8" , "any 8" }, - { "keypad 9" , "any 9" }, - { "keypad 9" , "any 9" }, - { "keypad /" , "any /" }, - { "keypad *" , "any *" }, - { "keypad -" , "any -" }, - { "keypad +" , "any +" }, - { "keypad enter" , "any return" } }; -static int keyGroupsCount = (int)(sizeof(keyGroups)/sizeof(*keyGroups)); - - - -int keyEventGetCount(KeyEvent mode) - { return (int)mode >= 0 && (int)mode <= keyEventsCount ? keyEvents[mode].count : 0; } -const char *keyEventGet(KeyEvent mode, int i) - { return (int)mode >= 0 && (int)mode <= keyEventsCount ? heliArrayGetValue(&keyEvents[mode], i) : NULL; } - -static int keyEventCheck(KeyEvent mode, const char *code) { - int count = keyEventGetCount(mode); - for(int i = 0; i < count; ++i) - if (heliStringCompareCi(keyEventGet(mode, i), code) == 0) - return TRUE; - return FALSE; -} - -static int keyEventAliasesCheck(KeyEvent mode, const char *code) { - if (keyEventCheck(mode, code)) return TRUE; - for(int i = 0; i < keyAliasesCount; ++i) - if (heliStringCompareCi(code, keyAliases[i][0]) == 0) - if (keyEventCheck(mode, keyAliases[i][1])) return TRUE; - return FALSE; -} - -static int keyEventAdd(KeyEvent mode, const char *code) { - if ((int)mode >= 0 && mode <= (int)keyEventsCount && !keyEventCheck(mode, code)) { - heliArrayInsert(&keyEvents[mode], -1, heliStringCopyLower(code), &free); - return TRUE; - } - return FALSE; -} - -static int keyEventRemove(KeyEvent mode, const char *code) { - int removed = FALSE; - if ((int)mode >= 0 && mode <= (int)keyEventsCount) - for(int i = keyEvents[mode].count-1; i >= 0; --i) - if (heliStringCompareCi(keyEvents[mode].items[i].value, code) == 0) - { heliArrayRemove(&keyEvents[mode], i); removed = TRUE; } - return removed; -} - -static void pressKey(int mouse, const char *code) { - keyEventAdd(mouse ? KEYEVENT_MOUSE_DOWN : KEYEVENT_KEY_DOWN, code); - keyEventAdd(mouse ? KEYEVENT_MOUSE_WENTDOWN : KEYEVENT_KEY_WENTDOWN, code); - if (!mouse) { - for(int i = 0; i < keyGroupsCount; ++i) { - if (heliStringCompareCi(code, keyGroups[i][0]) == 0) { - keyEventAdd(KEYEVENT_KEY_DOWN, keyGroups[i][1]); - keyEventAdd(KEYEVENT_KEY_WENTDOWN, keyGroups[i][1]); - } - } - } -} - -static void releaseKey(int mouse, const char *code) { - if (!keyEventRemove(mouse ? KEYEVENT_MOUSE_DOWN : KEYEVENT_KEY_DOWN, code)) - return; - keyEventAdd(mouse ? KEYEVENT_MOUSE_WENTUP : KEYEVENT_KEY_WENTUP, code); - if (mouse) - return; - for(int i = 0; i < keyGroupsCount; ++i) { - if (heliStringCompareCi(code, keyGroups[i][0]) != 0) continue; - int allRemoved = TRUE; - for(int j = 0; j < keyGroupsCount; ++j) - if ( heliStringCompareCi(keyGroups[i][1], keyGroups[j][1]) == 0 - && keyEventCheck(KEYEVENT_KEY_DOWN, keyGroups[j][0])) - { allRemoved = FALSE; break; } - if (allRemoved) - if (keyEventRemove(KEYEVENT_KEY_DOWN, keyGroups[i][1])) - keyEventAdd(KEYEVENT_KEY_WENTUP, keyGroups[i][1]); - } -} - -static void releaseAllKeys() { - int count = keyEventGetCount(KEYEVENT_KEY_DOWN); - for(int i = count-1; i >= 0; --i) - keyEventAdd(KEYEVENT_KEY_WENTUP, keyEventGet(KEYEVENT_KEY_DOWN, i)); - heliArrayClear(&keyEvents[KEYEVENT_KEY_DOWN]); - - count = keyEventGetCount(KEYEVENT_MOUSE_DOWN); - for(int i = count-1; i >= 0; --i) - keyEventAdd(KEYEVENT_MOUSE_WENTUP, keyEventGet(KEYEVENT_MOUSE_DOWN, i)); - heliArrayClear(&keyEvents[KEYEVENT_MOUSE_DOWN]); -} - - - -int keyDown(const char *code) - { return keyEventAliasesCheck(KEYEVENT_KEY_DOWN, code); } -int keyWentDown(const char *code) - { return keyEventAliasesCheck(KEYEVENT_KEY_WENTDOWN, code); } -int keyWentUp(const char *code) - { return keyEventAliasesCheck(KEYEVENT_KEY_WENTUP, code); } - -int mouseDidMove() - { return mouseMovedInFrame; } -int mouseDown(const char *code) - { return keyEventCheck(KEYEVENT_MOUSE_DOWN, code); } -int mouseWentDown(const char *code) - { return keyEventCheck(KEYEVENT_MOUSE_WENTDOWN, code); } -int mouseWentUp(const char *code) - { return keyEventCheck(KEYEVENT_MOUSE_WENTUP, code); } -int mouseScrolledX() - { return _mouseScrolledX; } -int mouseScrolledY() - { return _mouseScrolledY; } -double mouseX() - { return _mouseX; } -double mouseY() - { return _mouseY; } - -double transformedMouseX() { - double x = mouseX(), y = mouseY(); - heliGLBackTransform(&x, &y); - return x; -} - -double transformedMouseY() { - double x = mouseX(), y = mouseY(); - heliGLBackTransform(&x, &y); - return y; -} - -static void resize(int w, int h) { - w = w > minWidth ? w : minWidth; - h = h > minHeight ? h : minHeight; - if (width != w || height != h) { - width = w; - height = h; - if (started && !stopped && window) - SDL_SetWindowSize(window, width, height); - } -} - -int worldGetWidth() - { return width; } -void worldSetWidth(int w) - { resize(w, height); } - -int worldGetHeight() - { return height; } -void worldSetHeight(int h) - { resize(width, h); } - -void worldSetSize(int w, int h) - { resize(w, h); } - -int worldGetResizable() - { return resizable; } -void worldSetResizable(int r) { - if (resizable == r) return; - resizable = r ? TRUE : FALSE; - if (started && !stopped && window) - SDL_SetWindowResizable(window, resizable); -} - -const char* worldGetTitle() - { return title; } -void worldSetTitle(const char *t) { - int changed = FALSE; - for(int i = 0; i < titleSize-1; ++i) { - if (title[i] != t[i]) changed = TRUE; - title[i] = t[i]; - if (!t[i]) break; - } - if (changed && started && !stopped && window) - SDL_SetWindowTitle(window, title); -} - -double worldGetMinFrameRate() - { return minFPS; } -double worldGetMaxFrameRate() - { return minFPS; } -void worldSetFrameRateEx(double minFrameRate, double maxFrameRate) { - if (!(minFrameRate > HELI_MIN_FPS)) minFrameRate = HELI_MIN_FPS; - if (!(minFrameRate < HELI_MAX_FPS)) minFrameRate = HELI_MAX_FPS; - if (!(maxFrameRate > HELI_MIN_FPS)) maxFrameRate = HELI_MIN_FPS; - if (!(maxFrameRate < HELI_MAX_FPS)) maxFrameRate = HELI_MAX_FPS; - if (minFrameRate > maxFrameRate) minFrameRate = maxFrameRate; - minFPS = minFrameRate; - maxFPS = maxFrameRate; -} -void worldSetFrameRate(double frameRate) - { worldSetFrameRateEx(frameRate, frameRate); } -void worldSetVariableFrameRate() - { worldSetFrameRateEx(HELI_MIN_FPS, HELI_MAX_FPS); } - -double worldGetFrameTime() - { return frameTime; } - -int worldGetFrameCount() - { return (int)frameCount; } -double worldGetSeconds() - { return started ? elapsedTimeUs*1e-6 : 0.0; } - -void worldSetInit(Callback init) - { initCallback = init; } -void worldSetDraw(Callback draw) - { drawCallback = draw; } -void worldSetDeinit(Callback deinit) - { deinitCallback = deinit; } -void worldStop() - { if (started) stopped = TRUE; } - - -void messageBox(const char *message) { - SDL_ShowSimpleMessageBox( - SDL_MESSAGEBOX_INFORMATION, - title, - message, - window ); - prevFrameTimeMs = SDL_GetTicks(); -} - -int questionBox(const char *question, const char *answer0, const char *answer1) { - SDL_MessageBoxButtonData buttons[2] = {}; - - buttons[0].buttonid = 1; - buttons[0].flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT; - buttons[0].text = answer1; - - buttons[1].buttonid = 0; - buttons[1].flags = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT; - buttons[1].text = answer0; - - SDL_MessageBoxData data = {}; - data.flags = SDL_MESSAGEBOX_INFORMATION; - data.window = window; - data.title = title; - data.message = question; - data.buttons = buttons; - data.numbuttons = 2; - - int buttonid = 0;; - SDL_ShowMessageBox(&data, &buttonid); - prevFrameTimeMs = SDL_GetTicks(); - - return buttonid; -} - -int questionBox3(const char* question, const char* answer0, const char* answer1, const char* answer2) { - SDL_MessageBoxButtonData buttons[3] = {}; - - buttons[0].buttonid = 2; - buttons[0].flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT; - buttons[0].text = answer2; - - buttons[1].buttonid = 1; - buttons[1].text = answer1; - - buttons[2].buttonid = 0; - buttons[2].flags = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT; - buttons[2].text = answer0; - - SDL_MessageBoxData data = {}; - data.flags = SDL_MESSAGEBOX_INFORMATION; - data.window = window; - data.title = title; - data.message = question; - data.buttons = buttons; - data.numbuttons = 3; - - int buttonid = 0;; - SDL_ShowMessageBox(&data, &buttonid); - prevFrameTimeMs = SDL_GetTicks(); - - return buttonid; -} - -int askText(const char *question, char *answer, int maxAnswerSize) - { return askTextEx(question, answer, maxAnswerSize, FALSE, FALSE); } - - -static void resetEvents() { - dialog.newText[0] = 0; - _mouseScrolledX = _mouseScrolledY = 0; - heliArrayClear(&keyEvents[KEYEVENT_KEY_WENTDOWN]); - heliArrayClear(&keyEvents[KEYEVENT_KEY_WENTUP]); - heliArrayClear(&keyEvents[KEYEVENT_MOUSE_WENTDOWN]); - heliArrayClear(&keyEvents[KEYEVENT_MOUSE_WENTUP]); -} - - -static void draw() { - unsigned int currentFrameTimeMs = SDL_GetTicks(); - unsigned long long deltaUs = firstFrame ? 0 : (currentFrameTimeMs - prevFrameTimeMs)*1000ull; - prevFrameTimeMs = currentFrameTimeMs; - - double actualMinFPS = minFPS, actualMaxFPS = maxFPS; - if (dialog.shown) { actualMinFPS = 1, actualMaxFPS = 100; } - - unsigned long long minTimeStepUs = (unsigned long long)round(1e6/actualMaxFPS); - unsigned long long maxTimeStepUs = (unsigned long long)round(1e6/actualMinFPS); - elapsedTimeSinceLastFrameUs += deltaUs; - if (elapsedTimeSinceLastFrameUs > 2000000) - elapsedTimeSinceLastFrameUs = 2000000; - if (firstFrame || elapsedTimeSinceLastFrameUs >= minTimeStepUs) { - unsigned long long encountedTimeUs = elapsedTimeSinceLastFrameUs; - if (encountedTimeUs > maxTimeStepUs) encountedTimeUs = maxTimeStepUs; - double dt = encountedTimeUs*1e-6; - - if (!firstFrame) elapsedTimeSinceLastFrameUs -= encountedTimeUs; - - if (!dialog.shown) { - elapsedTimeUs += encountedTimeUs; - ++frameCount; - frameTime = firstFrame ? 1/maxFPS : dt; - heliAnimationUpdate(dt); - heliSpriteUpdate(dt); - } - heliSoundUpdate(); - - firstFrame = FALSE; - - viewportByWindow(); - projectionByViewport(); - - heliDrawingPrepareFrame(); - if (dialog.shown) { - heliDialogDraw(&dialog); - } else { - if (drawCallback) - drawCallback(); - } - - resetEvents(); - SDL_GL_SwapWindow(window); - } - - unsigned long long addUs = elapsedTimeSinceLastFrameUs + (SDL_GetTicks() - prevFrameTimeMs)*1000ull; - if (addUs < minTimeStepUs) { - unsigned long long waitUs = minTimeStepUs - addUs; - if (waitUs > 2000) - SDL_Delay( (unsigned int)((waitUs + 500)/1000ull) ); - } -} - - -static void deinit() { - heliGLStencilOpSeparatePtr = NULL; - - if (context) SDL_GL_DeleteContext(context); - context = NULL; - - if (window) SDL_DestroyWindow(window); - window = NULL; - - if (sdlInitialized) SDL_Quit(); - sdlInitialized = FALSE; -} - - -static int init() { - if (SDL_Init(SDL_INIT_EVERYTHING) < 0) { - fprintf(stderr, "helianthus: SDL_Init failed\n"); - deinit(); - return FALSE; - } - sdlInitialized = TRUE; - - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8); - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetSwapInterval(1); - - unsigned int flags = SDL_WINDOW_OPENGL; - if (resizable) flags |= SDL_WINDOW_RESIZABLE; - - window = SDL_CreateWindow( - title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, - width, height, flags ); - - if (!window) { - // try to create window without multisampling - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); - window = SDL_CreateWindow( - title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, - width, height, flags ); - } - - - if (!window) { - fprintf(stderr, "helianthus: cannot create window: %s\n", SDL_GetError()); - SDL_ClearError(); - deinit(); - return FALSE; - } - - SDL_SetWindowMinimumSize(window, minWidth, minHeight); - - context = SDL_GL_CreateContext(window); - if (!context) { - fprintf(stderr, "helianthus: cannot create OpenGL context: %s\n", SDL_GetError()); - SDL_ClearError(); - deinit(); - return FALSE; - } - - heliGLBlendFuncSeparatePtr = SDL_GL_GetProcAddress("glBlendFuncSeparate"); - heliGLStencilOpSeparatePtr = SDL_GL_GetProcAddress("glStencilOpSeparate"); - heliGLTexImage2DMultisamplePtr = SDL_GL_GetProcAddress("glTexImage2DMultisample"); - heliGLGenFramebuffersPtr = SDL_GL_GetProcAddress("glGenFramebuffers"); - heliGLDeleteFramebuffersPtr = SDL_GL_GetProcAddress("glDeleteFramebuffers"); - heliGLBindFramebufferPtr = SDL_GL_GetProcAddress("glBindFramebuffer"); - heliGLBlitFramebufferPtr = SDL_GL_GetProcAddress("glBlitFramebuffer"); - heliGLFramebufferRenderbufferPtr = SDL_GL_GetProcAddress("glFramebufferRenderbuffer"); - heliGLFramebufferTexture2DPtr = SDL_GL_GetProcAddress("glFramebufferTexture2D"); - heliGLCheckFramebufferStatusPtr = SDL_GL_GetProcAddress("glCheckFramebufferStatus"); - heliGLGenRenderbuffersPtr = SDL_GL_GetProcAddress("glGenRenderbuffers"); - heliGLDeleteRenderbuffersPtr = SDL_GL_GetProcAddress("glDeleteRenderbuffers"); - heliGLBindRenderbufferPtr = SDL_GL_GetProcAddress("glBindRenderbuffer"); - heliGLRenderbufferStoragePtr = SDL_GL_GetProcAddress("glRenderbufferStorage"); - heliGLRenderbufferStorageMultisamplePtr = SDL_GL_GetProcAddress("glRenderbufferStorageMultisample"); - glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, (int*)&heliGLWindowFramebufferReadId); - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (int*)&heliGLWindowFramebufferDrawId); - - return TRUE; -} - - -static void handleEvent(SDL_Event *e) { - if (e->type == SDL_QUIT) { - stopped = TRUE; - } else - if (e->type == SDL_WINDOWEVENT) { - if (e->window.event == SDL_WINDOWEVENT_CLOSE) { - stopped = TRUE; - } else - if (e->window.event == SDL_WINDOWEVENT_RESIZED) { - width = e->window.data1; - height = e->window.data2; - } else - if (e->window.event == SDL_WINDOWEVENT_FOCUS_LOST) { - releaseAllKeys(); - } - } else - if (e->type == SDL_KEYDOWN || e->type == SDL_KEYUP) { - const char *keyname = SDL_GetKeyName(e->key.keysym.sym); - if (keyname && *keyname) { - if (e->type == SDL_KEYDOWN) - pressKey(FALSE, keyname); else releaseKey(FALSE, keyname); - } - } else - if (e->type == SDL_MOUSEBUTTONDOWN || e->type == SDL_MOUSEBUTTONUP) { - char *button = NULL; - switch(e->button.button) { - case SDL_BUTTON_LEFT: button = "left"; break; - case SDL_BUTTON_MIDDLE: button = "middle"; break; - case SDL_BUTTON_RIGHT: button = "right"; break; - default: break; - } - if (button) { - if (e->type == SDL_MOUSEBUTTONDOWN) - pressKey(TRUE, button); else releaseKey(TRUE, button); - } - _mouseX = e->button.x; - _mouseY = e->button.y; - } else - if (e->type == SDL_MOUSEMOTION) { - _mouseX = e->motion.x; - _mouseY = e->motion.y; - } else - if (e->type == SDL_MOUSEWHEEL) { - _mouseScrolledX += e->wheel.x; - _mouseScrolledY += e->wheel.y; - } else - if (e->type == SDL_TEXTINPUT) { - if (dialog.shown) { - int len = strlen(dialog.newText); - int newlen = strlen(e->text.text); - int dl = len + newlen + 1 - sizeof(dialog.newText); - if (dl > 0) newlen -= dl; - if (newlen > 0) { - memcpy(dialog.newText + len, e->text.text, newlen); - dialog.newText[len + newlen] = 0; - } - } - } -} - - -int askTextEx(const char *question, char *answer, int maxAnswerSize, int multiline, int password) { - if (maxAnswerSize < 0 || !answer) maxAnswerSize = 0; - - memset(&dialog, 0, sizeof(dialog)); - dialog.shown = TRUE; - dialog.question = question ? question : ""; - dialog.answer = calloc(1, maxAnswerSize + 1); - dialog.passwordText = calloc(1, maxAnswerSize*4 + 1); - dialog.maxAnswerSize = maxAnswerSize - 1; - if (maxAnswerSize > 0) memcpy(dialog.answer, answer, maxAnswerSize); - dialog.multiline = multiline != 0 && password == 0; - dialog.password = password != 0; - dialog.pos = dialog.selPos = strlen(dialog.answer); - dialog.success = FALSE; - - SDL_StartTextInput(); - while(dialog.shown && !stopped) { - SDL_Event event; - while (SDL_PollEvent(&event)) - handleEvent(&event); - draw(); - } - SDL_StopTextInput(); - - int success = dialog.success; - if (dialog.success && maxAnswerSize > 0) strcpy(answer, dialog.answer); - free(dialog.answer); - free(dialog.passwordText); - memset(&dialog, 0, sizeof(dialog)); - - resetEvents(); - heliArrayClear(&keyEvents[KEYEVENT_KEY_DOWN]); - heliArrayClear(&keyEvents[KEYEVENT_MOUSE_DOWN]); - - prevFrameTimeMs = SDL_GetTicks(); - heliDrawingPrepareFrame(); - - return success; -} - - -static void run() { - while(!stopped) { - SDL_Event event; - while (SDL_PollEvent(&event)) - handleEvent(&event); - draw(); - } -} - - -void worldRun() { - if (started) return; - started = TRUE; - stopped = FALSE; - - firstFrame = TRUE; - frameCount = 0; - elapsedTimeUs = 0; - elapsedTimeSinceLastFrameUs = 0; - srand(time(NULL)); - - if (init()) { - heliInitialized = TRUE; - - resetState(); - heliDoTests(); - - viewportByWindow(); - projectionByViewport(); - heliDrawingPrepareFrame(); - - if (initCallback) initCallback(); - run(); - if (deinitCallback) deinitCallback(); - - heliArrayClear(&heliObjectsSet); - heliSpriteFinish(); - heliDrawingFinish(); - heliFontFinish(); - heliAnimationFinish(); - heliSoundFinish(); - heliArrayDestroy(&heliObjectsSet); - - heliInitialized = FALSE; - - deinit(); - - _mouseScrolledX = _mouseScrolledY = 0; - for(int i = 0; i < keyEventsCount; ++i) - heliArrayDestroy(&keyEvents[i]); - } - - started = FALSE; -} diff --git a/src/world.h b/src/world.h deleted file mode 100644 index 7d8d4ab..0000000 --- a/src/world.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef HELI_WORLD_H -#define HELI_WORLD_H - - -#include "common.h" -#include "sprite.h" - -typedef void (*Callback)(); - -typedef enum _KeyEvent { - KEYEVENT_KEY_DOWN, - KEYEVENT_KEY_WENTDOWN, - KEYEVENT_KEY_WENTUP, - KEYEVENT_MOUSE_DOWN, - KEYEVENT_MOUSE_WENTDOWN, - KEYEVENT_MOUSE_WENTUP, -} KeyEvent; - - -void drawSprites(); - -int keyDown(const char *code); -int keyWentDown(const char *code); -int keyWentUp(const char *code); - -int keyEventGetCount(KeyEvent mode); -const char *keyEventGet(KeyEvent mode, int i); - -int mouseDidMove(); -int mouseDown(const char *code); -int mouseWentDown(const char *code); -int mouseWentUp(const char *code); -int mouseScrolledX(); -int mouseScrolledY(); -double mouseX(); -double mouseY(); - -double transformedMouseX(); -double transformedMouseY(); - -void messageBox(const char *message); -int questionBox(const char *question, const char *answer0, const char *answer1); -int questionBox3(const char *question, const char *answer0, const char *answer1, const char *answer2); -int askText(const char *question, char *answer, int maxAnswerSize); -int askTextEx(const char *question, char *answer, int maxAnswerSize, int multiline, int password); - -int worldGetWidth(); -void worldSetWidth(int width); - -int worldGetHeight(); -void worldSetHeight(int height); - -void worldSetSize(int width, int height); - -int worldGetResizable(); -void worldSetResizable(int resizable); - -const char* worldGetTitle(); -void worldSetTitle(const char *title); - -double worldGetMinFrameRate(); -double worldGetMaxFrameRate(); -void worldSetFrameRateEx(double minFrameRate, double maxFrameRate); -void worldSetFrameRate(double frameRate); -void worldSetVariableFrameRate(); -double worldGetFrameTime(); - -int worldGetFrameCount(); -double worldGetSeconds(); - -void worldSetInit(Callback init); -void worldSetDraw(Callback draw); -void worldSetDeinit(Callback deinit); -void worldRun(); -void worldStop(); - - -#endif diff --git a/src/worldui.c b/src/worldui.c deleted file mode 100644 index 1aab31c..0000000 --- a/src/worldui.c +++ /dev/null @@ -1,375 +0,0 @@ - -#include - -#include "private.h" - -#include "drawing.h" -#include "font.h" -#include "world.h" - - -static const char passwordPattern[] = "\u25CF"; - - -static int utf8charlen(const char *c) { - if (!*c) return 0; - if ( (c[0] & 0x80) == 0x00 ) - return 1; - if ( (c[0] & 0xE0) == 0xC0 - && (c[1] & 0xC0) == 0x80 ) - return 2; - if ( (c[0] & 0xF0) == 0xE0 - && (c[1] & 0xC0) == 0x80 - && (c[2] & 0xC0) == 0x80 ) - return 3; - if ( (c[0] & 0xF8) == 0xF0 - && (c[1] & 0xC0) == 0x80 - && (c[2] & 0xC0) == 0x80 - && (c[3] & 0xC0) == 0x80 ) - return 4; - return -1; -} - - -static int utf8len(const char *c) { - int len = 0; - while(*c) { - int l = utf8charlen(c); - c += l > 0 ? l : 1; - ++len; - } - return len; -} - - -static int utf8pos(const char *c, int pos) { - int p = 0; - const char *end = c + pos; - while(*c && c < end) { - int l = utf8charlen(c); - c += l > 0 ? l : 1; - ++p; - } - return p; -} - - -static int utf8shift(const char *s, int pos, int offset) { - int len = strlen(s); - if (pos > len) { offset += pos - len; pos = len; } - if (pos < 0) { offset += pos; pos = 0; } - - while(offset > 0 && pos < len) { - int l = utf8charlen(s + pos); - if (l > 0) { - pos += l; - } else { - while(pos < len && utf8charlen(s + pos) <= 0) ++pos; - } - --offset; - } - - while(offset < 0 && pos > 0) { - do --pos; while(pos > 0 && utf8charlen(s + pos) <= 0); - ++offset; - } - - return pos; -} - - -static void fixPos(HeliDialog *dialog) { - int answerLen = strlen(dialog->answer); - if (dialog->pos < 0) dialog->pos = 0; - if (dialog->pos > answerLen) dialog->pos = answerLen; - if (dialog->selPos < 0) dialog->selPos = 0; - if (dialog->selPos > answerLen) dialog->selPos = answerLen; -} - - -static void insert(HeliDialog *dialog, const char *text) { - int answerLen = strlen(dialog->answer); - fixPos(dialog); - - int pos0 = dialog->pos < dialog->selPos ? dialog->pos : dialog->selPos; - int pos1 = dialog->pos < dialog->selPos ? dialog->selPos : dialog->pos; - int selLen = pos1 - pos0; - - int textLen = strlen(text); - int dl = answerLen - selLen + textLen - dialog->maxAnswerSize; - if (dl > 0) textLen -= dl; - if (textLen < 0) textLen = 0; - - int tailPos = pos0 + selLen; - int offset = textLen - selLen; - int tailLen = answerLen - tailPos; - if (offset && tailLen > 0) - memmove( - dialog->answer + tailPos + offset, - dialog->answer + tailPos, - tailLen); - tailPos += offset; - dialog->answer[tailPos + tailLen] = 0; - - if (textLen > 0) - memcpy(dialog->answer + pos0, text, textLen); - - dialog->pos = dialog->selPos = tailPos; -} - - -static void copy(HeliDialog *dialog) { - if (!dialog->password && dialog->pos != dialog->selPos) { - int pos0 = dialog->pos < dialog->selPos ? dialog->pos : dialog->selPos; - int pos1 = dialog->pos < dialog->selPos ? dialog->selPos : dialog->pos; - char c = dialog->answer[pos1]; - dialog->answer[pos1] = 0; - SDL_SetClipboardText(dialog->answer + pos0); - dialog->answer[pos1] = c; - } -} - - -static void paste(HeliDialog *dialog) { - const char *text = SDL_GetClipboardText(); - if (text && text[0]) insert(dialog, text); -} - - -static void draw(HeliDialog *dialog) { - saveState(); - - const double time = SDL_GetTicks()*1e-3; - - const double w = worldGetWidth(); - const double h = worldGetHeight(); - const double border = 16; - - double title = 64; - double buttons = 32; - double l = border; - double t = border + title; - double r = w - border; - double b = h - border - buttons; - if (!dialog->multiline && b - t > buttons) { - t = round(0.5*(b + t - buttons)); - b = t + buttons; - title = t - border; - } - - const double cursorSrcollWidth = 64; - const double cursorSrcollHeight = 16; - - const double bt = h - buttons - border + 8; - const double bb = h - border; - const double bh = bb - bt; - double bw = (w - 2*border)/4; - if (bw < bh) bw = bh; - const double bl0 = border - 2; - const double br0 = bl0 + bw; - const double br1 = w - border + 2; - const double bl1 = br1 - bw; - - unsigned int strokeColor = COLOR_WHITE; - unsigned int fillColor = 0x4d4d4dff; - unsigned int selTextColor = COLOR_BLACK; - unsigned int selFillColor = COLOR_WHITE; - strokeWidth(1); - textFontDefault(); - textSize(16); - - noStroke(); - fill(fillColor); - rect(0, 0, w, h); - - - noFill(); - stroke(strokeColor); - textAlign(HALIGN_LEFT, VALIGN_TOP); - TextLayout layout = createTextLayout(dialog->password ? dialog->passwordText : dialog->answer); - - int shift = keyDown("any shift"); - if (keyWentDown("up")) { - dialog->pos = textLayoutCursorUp(layout, dialog->pos); - if (!shift) dialog->selPos = dialog->pos; - } - if (keyWentDown("down")) { - dialog->pos = textLayoutCursorDown(layout, dialog->pos); - if (!shift) dialog->selPos = dialog->pos; - } - - int pos = dialog->pos; - int selPos = dialog->selPos; - if (dialog->password) { - pos = utf8pos(dialog->answer, pos)*((int)sizeof(passwordPattern) - 1); - selPos = utf8pos(dialog->answer, selPos)*((int)sizeof(passwordPattern) - 1); - } - - cliprect(l-2, t-2, r-l+2, b-t+2); - double tx = l; - double ty = t; - double cx = textLayoutCursorGetX(layout, pos) + tx; - double cy = textLayoutCursorGetY(layout, pos) + ty; - double ch = textLayoutCursorGetHeight(layout, pos); - - double minScrollY = t + ch + cursorSrcollHeight - cy; - double maxScrollY = b - cursorSrcollHeight - cy; - if (maxScrollY > 0) maxScrollY = 0; - if (dialog->scrollY < minScrollY) dialog->scrollY = minScrollY; - if (dialog->scrollY > maxScrollY) dialog->scrollY = maxScrollY; - - double minScrollX = l + cursorSrcollWidth - cx; - double maxScrollX = r - cursorSrcollWidth - cx; - if (maxScrollX > 0) maxScrollX = 0; - if (dialog->scrollX < minScrollX) dialog->scrollX = minScrollX; - if (dialog->scrollX > maxScrollX) dialog->scrollX = maxScrollX; - - tx += dialog->scrollX; - ty += dialog->scrollY; - cx += dialog->scrollX; - cy += dialog->scrollY; - - if (pos == selPos) { - textLayoutDraw(layout, tx, ty); - } else { - int p0 = pos < selPos ? pos : selPos; - int p1 = pos < selPos ? selPos : pos; - fill(selFillColor); - stroke(selTextColor); - textLayoutDrawSubstr(layout, tx, ty, p0, p1 - p0); - noFill(); - stroke(strokeColor); - textLayoutDrawSubstr(layout, tx, ty, 0, p0); - textLayoutDrawFrom(layout, tx, ty, p1); - } - - stroke(colorByRGBA(1, 1, 1, 0.5 + 0.5*sin(time/0.5*2*PI))); - line(cx, cy, cx, cy - ch); - - textLayoutDestroy(layout); - noClip(); - - - noFill(); - stroke(strokeColor); - rect(l - 2, t - 2, r - l + 4, b - t + 4); - - textAlign(HALIGN_CENTER, VALIGN_CENTER); - text(w/2, border + title/2, dialog->question); - - rect(bl0, bt, bw, bh); - rect(bl1, bt, bw, bh); - text((bl0 + br0)/2, (bt + bb)/2, "\u2613"); - text((bl1 + br1)/2, (bt + bb)/2, "\u2713"); - - if (mouseWentDown("left")) { - double x = mouseX(); - double y = mouseY(); - if (y >= bt && y <= bb) { - if (x >= bl0 && x <= br0) dialog->shown = FALSE; - if (x >= bl1 && x <= br1) { dialog->success = TRUE; dialog->shown = FALSE; } - } - } - - restoreState(); -} - - -void heliDialogDraw(HeliDialog *dialog) { - if (dialog->newText[0]) insert(dialog, dialog->newText); - - int shift = keyDown("any shift"); - int ctrl = keyDown("any ctrl"); - - if (keyWentDown("backspace")) { - if (dialog->pos == dialog->selPos) { - dialog->selPos = dialog->pos; - dialog->pos = utf8shift(dialog->answer, dialog->pos, -1); - } - insert(dialog, ""); - } - - if (keyWentDown("delete")) { - if (dialog->pos == dialog->selPos) { - dialog->selPos = dialog->pos; - dialog->pos = utf8shift(dialog->answer, dialog->pos, 1); - } else - if (shift && !dialog->password) { - copy(dialog); - } - insert(dialog, ""); - } - - if (keyWentDown("left")) { - dialog->pos = utf8shift(dialog->answer, dialog->pos, -1); - if (!shift) dialog->selPos = dialog->pos; - } - - if (keyWentDown("right")) { - dialog->pos = utf8shift(dialog->answer, dialog->pos, 1); - if (!shift) dialog->selPos = dialog->pos; - } - - if (keyWentDown("home")) { - if (ctrl) { - dialog->pos = 0; - } else { - while( dialog->pos > 0 - && dialog->answer[dialog->pos-1] != '\r' - && dialog->answer[dialog->pos-1] != '\n' ) --dialog->pos; - } - if (!shift) dialog->selPos = dialog->pos; - } - - if (keyWentDown("end")) { - if (ctrl) { - dialog->pos = strlen(dialog->answer); - } else { - while( dialog->answer[dialog->pos] != 0 - && dialog->answer[dialog->pos] != '\r' - && dialog->answer[dialog->pos] != '\n' ) ++dialog->pos; - } - if (!shift) dialog->selPos = dialog->pos; - } - - if (keyWentDown("return")) { - if (!dialog->multiline || ctrl) { - dialog->success = TRUE; - dialog->shown = FALSE; - } else { - insert(dialog, "\n"); - } - } - - if (ctrl && keyWentDown("a")) - { dialog->selPos = 0; dialog->pos = strlen(dialog->answer); } - if (!dialog->password && ctrl && keyWentDown("c")) - copy(dialog); - if (!dialog->password && ctrl && keyWentDown("insert")) - copy(dialog); - if (ctrl && keyWentDown("v")) - paste(dialog); - if (shift && keyWentDown("insert")) - paste(dialog); - if (!dialog->password && ctrl && keyWentDown("x")) - { copy(dialog); insert(dialog, ""); } - - if (keyWentDown("escape")) dialog->shown = FALSE; - - if (!dialog->multiline) { - char *c = strpbrk(dialog->answer, "\n\r"); - if (c) *c = 0; - fixPos(dialog); - } - - if (dialog->password) { - int len = utf8len(dialog->answer); - char *c = dialog->passwordText; - for(int i = 0; i < len; ++i) - for(const char *p = passwordPattern; *p; ++p, ++c) - *c = *p; - *c = 0; - } - - draw(dialog); -}