Move ui element rendering to window rendering

This commit is contained in:
Doyle Thai 2016-08-21 15:19:11 +10:00
parent 70a3032155
commit 4e585a4939
4 changed files with 298 additions and 200 deletions

View File

@ -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;
}

View File

@ -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 <time.h>
#include <stdlib.h>
@ -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;

View File

@ -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

View File

@ -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;