From 4e585a4939b45822b8578601cbca887e78a7a122 Mon Sep 17 00:00:00 2001 From: Doyle Thai Date: Sun, 21 Aug 2016 15:19:11 +1000 Subject: [PATCH] Move ui element rendering to window rendering --- src/UserInterface.c | 229 +++++++++++++++++---------- src/WorldTraveller.c | 218 ++++++++++++------------- src/include/Dengine/UserInterface.h | 36 +++-- src/include/Dengine/WorldTraveller.h | 15 ++ 4 files changed, 298 insertions(+), 200 deletions(-) diff --git a/src/UserInterface.c b/src/UserInterface.c index a7525e1..3a92996 100644 --- a/src/UserInterface.c +++ b/src/UserInterface.c @@ -4,70 +4,6 @@ #include "Dengine/Renderer.h" #include "Dengine/Debug.h" -i32 userInterface_window(UiState *const uiState, MemoryArena *const arena, - AssetManager *const assetManager, - Renderer *const renderer, Font *const font, - const KeyInput input, WindowState *window) -{ - if (math_pointInRect(window->rect, input.mouseP)) - { - uiState->hotItem = window->id; - if (uiState->activeItem == 0 && input.keys[keycode_mouseLeft].endedDown) - uiState->activeItem = window->id; - } - - Rect rect = window->rect; - RenderTex nullRenderTex = renderer_createNullRenderTex(assetManager); - renderer_staticRect(renderer, rect.pos, rect.size, V2(0, 0), 0, - nullRenderTex, V4(0.25f, 0.25f, 0.5f, 0.5f)); - - v2 menuTitleP = v2_add(rect.pos, V2(0, rect.size.h - 10)); - renderer_staticString(renderer, arena, font, window->title, menuTitleP, - V2(0, 0), 0, V4(0, 0, 0, 1)); - - // NOTE(doyle): activeItem captures mouse click within the UI bounds, but if - // the user drags the mouse outside the bounds quicker than the game updates - // then we use a second flag which only "unclicks" when the mouse is let go - if (uiState->activeItem == window->id) window->windowHeld = TRUE; - - if (window->windowHeld) - { - if (!input.keys[keycode_mouseLeft].endedDown) - { - window->windowHeld = FALSE; - window->prevFrameWindowHeld = FALSE; - } - } - - if (window->windowHeld) - { - // NOTE(doyle): If this is the first window click we don't process - // movement and store the current position to delta from next cycle - if (window->prevFrameWindowHeld) - { - // NOTE(doyle): Window clicked and held - v2 deltaP = v2_sub(input.mouseP, window->prevMouseP); - DEBUG_PUSH_VAR("Delta Pos %4.2f, %4.2f", deltaP, "v2"); - window->rect.pos = v2_add(deltaP, window->rect.pos); - } - else - { - window->prevFrameWindowHeld = TRUE; - } - - window->prevMouseP = input.mouseP; - } - - if (!input.keys[keycode_mouseLeft].endedDown && - uiState->hotItem == window->id && - uiState->activeItem == window->id) - { - return 1; - } - - return 0; -} - i32 userInterface_button(UiState *const uiState, MemoryArena *const arena, AssetManager *const assetManager, @@ -194,13 +130,13 @@ i32 userInterface_button(UiState *const uiState, uiState->hotItem == id && uiState->activeItem == id) { - return 1; + return id; } return 0; } -i32 userInterface_scrollBar(UiState *const uiState, +i32 userInterface_scrollbar(UiState *const uiState, AssetManager *const assetManager, Renderer *const renderer, const KeyInput input, const i32 id, const Rect scrollBarRect, @@ -277,13 +213,13 @@ i32 userInterface_scrollBar(UiState *const uiState, if (*value < maxValue) { (*value)++; - return 1; + return id; } case keycode_down: if (*value > 0) { (*value)--; - return 1; + return id; } default: break; @@ -309,7 +245,7 @@ i32 userInterface_scrollBar(UiState *const uiState, if (newValue != *value) { *value = newValue; - return 1; + return id; } } @@ -319,17 +255,13 @@ i32 userInterface_scrollBar(UiState *const uiState, i32 userInterface_textField(UiState *const uiState, MemoryArena *const arena, AssetManager *const assetManager, Renderer *const renderer, Font *const font, - KeyInput input, const i32 id, v2 pos, + KeyInput input, const i32 id, const Rect rect, char *const string) { i32 strLen = common_strlen(string); b32 changed = FALSE; - Rect textRect = {0}; - textRect.pos = pos; - textRect.size = V2(30 * font->maxSize.w, font->maxSize.h); - - if (math_pointInRect(textRect, input.mouseP)) + if (math_pointInRect(rect, input.mouseP)) { uiState->hotItem = id; if (uiState->activeItem == uiState->statWindow.id || @@ -352,28 +284,27 @@ i32 userInterface_textField(UiState *const uiState, MemoryArena *const arena, if (uiState->kbdItem == id) { // Draw outline - renderer_staticRect(renderer, v2_add(V2(-2, -2), textRect.pos), - v2_add(V2(4, 4), textRect.size), V2(0, 0), 0, + renderer_staticRect(renderer, v2_add(V2(-2, -2), rect.pos), + v2_add(V2(4, 4), rect.size), V2(0, 0), 0, renderTex, V4(1.0f, 0, 0, 1)); } // Render text field - renderer_staticRect(renderer, textRect.pos, textRect.size, V2(0, 0), 0, + renderer_staticRect(renderer, rect.pos, rect.size, V2(0, 0), 0, renderTex, V4(0.75f, 0.5f, 0.5f, 1)); if (uiState->activeItem == id || uiState->hotItem == id) { - renderer_staticRect(renderer, textRect.pos, textRect.size, V2(0, 0), 0, + renderer_staticRect(renderer, rect.pos, rect.size, V2(0, 0), 0, renderTex, V4(0.75f, 0.75f, 0.0f, 1)); } else { - renderer_staticRect(renderer, textRect.pos, textRect.size, V2(0, 0), 0, + renderer_staticRect(renderer, rect.pos, rect.size, V2(0, 0), 0, renderTex, V4(0.5f, 0.5f, 0.5f, 1)); } - v2 strPos = textRect.pos; - + v2 strPos = rect.pos; renderer_staticString(renderer, arena, font, string, strPos, V2(0, 0), 0, V4(0, 0, 0, 1)); @@ -417,6 +348,138 @@ i32 userInterface_textField(UiState *const uiState, MemoryArena *const arena, } uiState->lastWidget = id; - return changed; + + if (changed) return id; + return 0; +} + +i32 userInterface_window(UiState *const uiState, MemoryArena *const arena, + AssetManager *const assetManager, + Renderer *const renderer, Font *const font, + const KeyInput input, WindowState *window) +{ + if (math_pointInRect(window->rect, input.mouseP)) + { + uiState->hotItem = window->id; + if (uiState->activeItem == 0 && input.keys[keycode_mouseLeft].endedDown) + uiState->activeItem = window->id; + } + + Rect rect = window->rect; + RenderTex nullRenderTex = renderer_createNullRenderTex(assetManager); + renderer_staticRect(renderer, rect.pos, rect.size, V2(0, 0), 0, + nullRenderTex, V4(0.25f, 0.25f, 0.5f, 0.5f)); + + v2 menuTitleP = v2_add(rect.pos, V2(0, rect.size.h - 10)); + renderer_staticString(renderer, arena, font, window->title, menuTitleP, + V2(0, 0), 0, V4(0, 0, 0, 1)); + + // NOTE(doyle): activeItem captures mouse click within the UI bounds, but if + // the user drags the mouse outside the bounds quicker than the game updates + // then we use a second flag which only "unclicks" when the mouse is let go + if (uiState->activeItem == window->id) window->windowHeld = TRUE; + +#if 0 + for (i32 i = 0; i < window->numChildItems; i++) + { + if (uiState->activeItem == window->childUiId[i]) + { + window->windowHeld = FALSE; + window->prevFrameWindowHeld = FALSE; + break; + } + } +#endif + + if (window->windowHeld) + { + if (!input.keys[keycode_mouseLeft].endedDown) + { + window->windowHeld = FALSE; + window->prevFrameWindowHeld = FALSE; + } + } + + if (window->windowHeld) + { + // NOTE(doyle): If this is the first window click we don't process + // movement and store the current position to delta from next cycle + if (window->prevFrameWindowHeld) + { + // NOTE(doyle): Window clicked and held + v2 deltaP = v2_sub(input.mouseP, window->prevMouseP); + + for (i32 i = 0; i < window->numChildUiItems; i++) + { + UiItem *childUi = &window->childUiItems[i]; + childUi->rect.pos = v2_add(deltaP, childUi->rect.pos); + } + + DEBUG_PUSH_VAR("Delta Pos %4.2f, %4.2f", deltaP, "v2"); + window->rect.pos = v2_add(deltaP, window->rect.pos); + } + else + { + window->prevFrameWindowHeld = TRUE; + } + + window->prevMouseP = input.mouseP; + } + + /* Draw window elements */ + i32 firstActiveChildId = -1; + for (i32 i = 0; i < window->numChildUiItems; i++) + { + UiItem *childUi = &window->childUiItems[i]; + + // TODO(doyle): Redundant? If we can only have 1 active child at a time + // What about overlapping elements? + i32 getChildActiveState = -1; + switch(childUi->type) + { + case uitype_button: + // TODO(doyle): Bug in font rendering once button reaches 700-800+ + // pixels + getChildActiveState = userInterface_button( + uiState, arena, assetManager, renderer, font, input, + childUi->id, childUi->rect, childUi->label); + break; + + case uitype_scrollbar: + // TODO(doyle): window steals scrollbar focus + getChildActiveState = userInterface_scrollbar( + uiState, assetManager, renderer, input, childUi->id, + childUi->rect, &childUi->value, childUi->maxValue); + break; + + case uitype_textField: + getChildActiveState = userInterface_textField( + uiState, arena, assetManager, renderer, font, input, + childUi->id, childUi->rect, childUi->string); + break; + + default: + DEBUG_LOG( + "userInterface_window() warning: Enum uitype unrecognised"); + break; + } + + // NOTE(doyle): Capture only the first active id, but keep loop going to + // render the rest of the window ui + if (firstActiveChildId == -1 && getChildActiveState > 0) + firstActiveChildId = getChildActiveState; + } + + if (firstActiveChildId != -1) + return firstActiveChildId; + + if (!input.keys[keycode_mouseLeft].endedDown && + uiState->hotItem == window->id && + uiState->activeItem == window->id) + { + return window->id; + } + + return 0; } diff --git a/src/WorldTraveller.c b/src/WorldTraveller.c index 30610c9..4c9aa9c 100644 --- a/src/WorldTraveller.c +++ b/src/WorldTraveller.c @@ -323,6 +323,56 @@ INTERNAL void entityInit(GameState *state, v2 windowSize) #endif } +INTERNAL v2 getPosRelativeToRect(Rect rect, v2 offset, + enum RectBaseline baseline) +{ +#ifdef DENGINE_DEBUG + ASSERT(baseline < rectbaseline_count); +#endif + v2 result = {0}; + + v2 posToOffsetFrom = rect.pos; + switch (baseline) + { + case rectbaseline_top: + posToOffsetFrom.y += (rect.size.h); + posToOffsetFrom.x += (rect.size.w * 0.5f); + break; + case rectbaseline_topLeft: + posToOffsetFrom.y += (rect.size.h); + break; + case rectbaseline_topRight: + posToOffsetFrom.y += (rect.size.h); + posToOffsetFrom.x += (rect.size.w); + break; + case rectbaseline_bottom: + posToOffsetFrom.x += (rect.size.w * 0.5f); + break; + case rectbaseline_bottomRight: + posToOffsetFrom.x += (rect.size.w); + break; + case rectbaseline_left: + posToOffsetFrom.y += (rect.size.h * 0.5f); + break; + case rectbaseline_right: + posToOffsetFrom.x += (rect.size.w); + posToOffsetFrom.y += (rect.size.h * 0.5f); + break; + + case rectbaseline_bottomLeft: + break; + default: +#ifdef DENGINE_DEBUG + DEBUG_LOG( + "getPosRelativeToRect() warning: baseline enum not recognised"); +#endif + break; + } + + result = v2_add(posToOffsetFrom, offset); + return result; +} + // TODO(doyle): Remove and implement own random generator! #include #include @@ -339,6 +389,8 @@ void worldTraveller_gameInit(GameState *state, v2 windowSize) state->state = state_active; state->currWorldIndex = 0; state->tileSize = 64; + + state->uiState.uniqueId = 1; state->uiState.keyEntered = keycode_null; state->uiState.keyMod = keycode_null; state->uiState.keyChar = keycode_null; @@ -351,18 +403,63 @@ void worldTraveller_gameInit(GameState *state, v2 windowSize) state->uiState.statWindow.prevFrameWindowHeld = FALSE; state->uiState.statWindow.windowHeld = FALSE; - common_strncpy(state->uiState.debugWindow.title, "Debug Menu", + WindowState *debugWindow = &state->uiState.debugWindow; + common_strncpy(debugWindow->title, "Debug Menu", common_strlen("Debug Menu")); - state->uiState.debugWindow.id = 98; - state->uiState.debugWindow.rect.pos = V2(800, 400); - state->uiState.debugWindow.rect.size = V2(750, 400); - state->uiState.debugWindow.prevFrameWindowHeld = FALSE; - state->uiState.debugWindow.windowHeld = FALSE; + debugWindow->id = 98; + debugWindow->numChildUiItems = 0; + debugWindow->rect.pos = V2(800, 400); + debugWindow->rect.size = V2(750, 400); + debugWindow->prevFrameWindowHeld = FALSE; + debugWindow->windowHeld = FALSE; + + UiItem *audioBtn = + (debugWindow->childUiItems) + debugWindow->numChildUiItems++; + common_strncpy(audioBtn->label, "Toggle Music", + common_strlen("Toggle Music")); + audioBtn->id = userInterface_generateId(&state->uiState); + audioBtn->rect.size = V2(100, 50); + audioBtn->rect.pos = getPosRelativeToRect(debugWindow->rect, V2(10, -65.0f), + rectbaseline_topLeft); + audioBtn->type = uitype_button; + + UiItem *debugBtn = + (debugWindow->childUiItems) + debugWindow->numChildUiItems++; + common_strncpy(debugBtn->label, "Toggle Debug", + common_strlen("Toggle Debug")); + debugBtn->id = userInterface_generateId(&state->uiState); + debugBtn->rect.size = V2(100, 50); + debugBtn->rect.pos = getPosRelativeToRect(audioBtn->rect, V2(25, 0), + rectbaseline_bottomRight); + debugBtn->type = uitype_button; + + UiItem *scrollbar = + (debugWindow->childUiItems) + debugWindow->numChildUiItems++; + scrollbar->id = userInterface_generateId(&state->uiState); + + scrollbar->rect.size = V2(16, debugWindow->rect.size.h); + scrollbar->rect.pos = + getPosRelativeToRect(debugWindow->rect, V2(-scrollbar->rect.size.w, 0), + rectbaseline_bottomRight); + scrollbar->value = 0; + scrollbar->maxValue = 160; + scrollbar->type = uitype_scrollbar; + + UiItem *textField = + (debugWindow->childUiItems) + debugWindow->numChildUiItems++; + textField->id = userInterface_generateId(&state->uiState); + textField->rect.size = V2(200, 20); + textField->rect.pos = getPosRelativeToRect( + audioBtn->rect, V2(0, -textField->rect.size.h - 10), + rectbaseline_bottomLeft); + + common_strncpy(textField->string, "Hello world", + common_strlen("Hello world")); + textField->type = uitype_textField; state->config.playWorldAudio = FALSE; state->config.showDebugDisplay = TRUE; - assetInit(state); rendererInit(state, windowSize); entityInit(state, windowSize); @@ -1039,70 +1136,6 @@ typedef struct BattleState GLOBAL_VAR BattleState battleState = {0}; -enum RectBaseline -{ - rectbaseline_top, - rectbaseline_topLeft, - rectbaseline_topRight, - rectbaseline_bottom, - rectbaseline_bottomRight, - rectbaseline_bottomLeft, - rectbaseline_left, - rectbaseline_right, - rectbaseline_center, - rectbaseline_count, - -}; - -INTERNAL v2 getPosRelativeToRect(Rect rect, v2 offset, - enum RectBaseline baseline) -{ -#ifdef DENGINE_DEBUG - ASSERT(baseline < rectbaseline_count); -#endif - v2 result = {0}; - - v2 posToOffsetFrom = rect.pos; - switch (baseline) - { - case rectbaseline_top: - posToOffsetFrom.y += (rect.size.h); - posToOffsetFrom.x += (rect.size.w * 0.5f); - break; - case rectbaseline_topLeft: - posToOffsetFrom.y += (rect.size.h); - break; - case rectbaseline_topRight: - posToOffsetFrom.y += (rect.size.h); - posToOffsetFrom.x += (rect.size.w); - break; - case rectbaseline_bottom: - posToOffsetFrom.x += (rect.size.w * 0.5f); - break; - case rectbaseline_bottomRight: - posToOffsetFrom.x += (rect.size.w); - break; - case rectbaseline_left: - posToOffsetFrom.y += (rect.size.h * 0.5f); - break; - case rectbaseline_right: - posToOffsetFrom.x += (rect.size.w); - posToOffsetFrom.y += (rect.size.h * 0.5f); - break; - - case rectbaseline_bottomLeft: - break; - default: -#ifdef DENGINE_DEBUG - DEBUG_LOG( - "getPosRelativeToRect() warning: baseline enum not recognised"); -#endif - break; - } - - result = v2_add(posToOffsetFrom, offset); - return result; -} void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt) { @@ -1412,53 +1445,22 @@ void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt) /* Draw debug window */ WindowState *debugWindow = &state->uiState.debugWindow; - userInterface_window(&state->uiState, &state->arena, assetManager, renderer, - font, state->input, debugWindow); + i32 activeId = + userInterface_window(&state->uiState, &state->arena, assetManager, + renderer, font, state->input, debugWindow); - // TODO(doyle): Bug in font rendering once button reaches 700-800+ pixels - Rect toggleAudioButtonRect = {0}; - toggleAudioButtonRect.size = V2(100, 50); - toggleAudioButtonRect.pos = getPosRelativeToRect( - debugWindow->rect, V2(10, -65.0f), rectbaseline_topLeft); - - b32 toggleAudioClicked = userInterface_button( - &state->uiState, &state->arena, assetManager, renderer, font, - state->input, 1, toggleAudioButtonRect, "Toggle Music"); - - if (toggleAudioClicked) + // TODO(doyle): Name lookup to user interface id + if (activeId == 1) { state->config.playWorldAudio = (state->config.playWorldAudio == TRUE) ? FALSE : TRUE; } - - Rect toggleDebugButtonRect = {0}; - toggleDebugButtonRect.size = V2(100, 50); - toggleDebugButtonRect.pos = getPosRelativeToRect( - toggleAudioButtonRect, V2(25, 0), rectbaseline_bottomRight); - b32 toggleDebugClicked = userInterface_button( - &state->uiState, &state->arena, assetManager, renderer, font, - state->input, 2, toggleDebugButtonRect, "Toggle Debug Display"); - - if (toggleDebugClicked) + else if (activeId == 2) { state->config.showDebugDisplay = (state->config.showDebugDisplay == TRUE) ? FALSE : TRUE; } - LOCAL_PERSIST i32 scrollValue = 30; - Rect scrollRectA = {V2(0, 0), V2(16, 255)}; - scrollRectA.pos = getPosRelativeToRect(debugWindow->rect, V2(-20, -260), - rectbaseline_topRight); - userInterface_scrollBar(&state->uiState, assetManager, renderer, - state->input, 3, scrollRectA, &scrollValue, 160); - - LOCAL_PERSIST char fieldString[80] = "Hello world"; - v2 textFieldP = getPosRelativeToRect(toggleAudioButtonRect, V2(0, -20), - rectbaseline_bottomLeft); - userInterface_textField(&state->uiState, &state->arena, assetManager, - renderer, font, state->input, 4, textFieldP, - fieldString); - // RESET IMGUI if (!state->input.keys[keycode_mouseLeft].endedDown) state->uiState.activeItem = 0; diff --git a/src/include/Dengine/UserInterface.h b/src/include/Dengine/UserInterface.h index 086ec0a..7fe36a7 100644 --- a/src/include/Dengine/UserInterface.h +++ b/src/include/Dengine/UserInterface.h @@ -15,7 +15,7 @@ enum UiType { uitype_button, uitype_scrollbar, - uitype_textfield, + uitype_textField, uitype_string, uitype_count, }; @@ -23,14 +23,24 @@ enum UiType typedef struct UiItem { i32 id; - Rect rect; + char label[64]; enum UiType type; + + Rect rect; + + // TODO(doyle): ECS this? Not all elements need + i32 value; + i32 maxValue; + char string[80]; } UiItem; typedef struct WindowState { - i32 id; char title[64]; + i32 id; + + UiItem childUiItems[16]; + i32 numChildUiItems; Rect rect; // TODO(doyle): Store this in the input data not window? @@ -42,6 +52,8 @@ typedef struct WindowState typedef struct UiState { + i32 uniqueId; + UiItem uiList[128]; i32 numItems; @@ -58,10 +70,11 @@ typedef struct UiState WindowState debugWindow; } UiState; -i32 userInterface_window(UiState *const uiState, MemoryArena *const arena, - AssetManager *const assetManager, - Renderer *const renderer, Font *const font, - const KeyInput input, WindowState *window); +inline i32 userInterface_generateId(UiState *const uiState) +{ + i32 result = uiState->uniqueId++; + return result; +} i32 userInterface_button(UiState *const uiState, MemoryArena *const arena, @@ -76,12 +89,17 @@ i32 userInterface_textField(UiState *const uiState, MemoryArena *const arena, AssetManager *const assetManager, Renderer *const renderer, Font *const font, - KeyInput input, const i32 id, v2 pos, + KeyInput input, const i32 id, const Rect rect, char *const string); -i32 userInterface_scrollBar(UiState *const uiState, +i32 userInterface_scrollbar(UiState *const uiState, AssetManager *const assetManager, Renderer *const renderer, const KeyInput input, const i32 id, const Rect scrollBarRect, i32 *const value, const i32 maxValue); + +i32 userInterface_window(UiState *const uiState, MemoryArena *const arena, + AssetManager *const assetManager, + Renderer *const renderer, Font *const font, + const KeyInput input, WindowState *window); #endif diff --git a/src/include/Dengine/WorldTraveller.h b/src/include/Dengine/WorldTraveller.h index c0161f1..d5a0ed9 100644 --- a/src/include/Dengine/WorldTraveller.h +++ b/src/include/Dengine/WorldTraveller.h @@ -16,6 +16,21 @@ /* Forward declaration */ typedef struct Entity Entity; +enum RectBaseline +{ + rectbaseline_top, + rectbaseline_topLeft, + rectbaseline_topRight, + rectbaseline_bottom, + rectbaseline_bottomRight, + rectbaseline_bottomLeft, + rectbaseline_left, + rectbaseline_right, + rectbaseline_center, + rectbaseline_count, + +}; + typedef struct Config { b32 playWorldAudio;