Bit if UI cleanup
This commit is contained in:
@@ -16,7 +16,6 @@
|
||||
#include "cutscene/cutscene.h"
|
||||
#include "asset/asset.h"
|
||||
#include "ui/ui.h"
|
||||
#include "ui/uitextbox.h"
|
||||
#include "assert/assert.h"
|
||||
#include "network/network.h"
|
||||
#include "system/system.h"
|
||||
@@ -43,7 +42,6 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
errorChain(localeManagerInit());
|
||||
errorChain(displayInit());
|
||||
errorChain(uiInit());
|
||||
errorChain(uiTextboxInit());
|
||||
errorChain(cutsceneInit());
|
||||
errorChain(rpgInit());
|
||||
errorChain(networkInit());
|
||||
@@ -63,8 +61,7 @@ errorret_t engineUpdate(void) {
|
||||
inputUpdate();
|
||||
consoleUpdate();
|
||||
errorChain(rpgUpdate());
|
||||
uiUpdate();
|
||||
errorChain(uiTextboxUpdate());
|
||||
errorChain(uiUpdate());
|
||||
errorChain(cutsceneUpdate());
|
||||
errorChain(sceneUpdate());
|
||||
errorChain(assetUpdate());
|
||||
@@ -80,13 +77,12 @@ void engineExit(void) {
|
||||
}
|
||||
|
||||
errorret_t engineDispose(void) {
|
||||
uiTextboxDispose();
|
||||
cutsceneDispose();
|
||||
errorChain(sceneDispose());
|
||||
errorChain(networkDispose());
|
||||
errorChain(rpgDispose());
|
||||
localeManagerDispose();
|
||||
uiDispose();
|
||||
errorChain(uiDispose());
|
||||
consoleDispose();
|
||||
errorChain(displayDispose());
|
||||
// errorChain(saveDispose());
|
||||
|
||||
@@ -139,6 +139,62 @@ float_t inputGetLastValue(const inputaction_t action) {
|
||||
assertTrue(action < INPUT_ACTION_COUNT, "Input action out of bounds");
|
||||
return INPUT.actions[action].lastDynamicValue;
|
||||
}
|
||||
|
||||
bool_t inputIsDownDynamic(const inputaction_t action) {
|
||||
return inputGetCurrentValueDynamic(action) > 0.0f;
|
||||
}
|
||||
|
||||
bool_t inputWasDownDynamic(const inputaction_t action) {
|
||||
return inputGetLastValueDynamic(action) > 0.0f;
|
||||
}
|
||||
|
||||
bool_t inputPressedDynamic(const inputaction_t action) {
|
||||
return inputIsDownDynamic(action) && !inputWasDownDynamic(action);
|
||||
}
|
||||
|
||||
bool_t inputReleasedDynamic(const inputaction_t action) {
|
||||
return !inputIsDownDynamic(action) && inputWasDownDynamic(action);
|
||||
}
|
||||
|
||||
float_t inputAxisDynamic(
|
||||
const inputaction_t neg,
|
||||
const inputaction_t pos
|
||||
) {
|
||||
return inputGetCurrentValueDynamic(pos) -
|
||||
inputGetCurrentValueDynamic(neg);
|
||||
}
|
||||
|
||||
void inputAxis2DDynamic(
|
||||
const inputaction_t negX, const inputaction_t posX,
|
||||
const inputaction_t negY, const inputaction_t posY,
|
||||
vec2 result
|
||||
) {
|
||||
assertNotNull(result, "Result vector cannot be null");
|
||||
result[0] = inputAxisDynamic(negX, posX);
|
||||
result[1] = inputAxisDynamic(negY, posY);
|
||||
}
|
||||
|
||||
void inputAngle2DDynamic(
|
||||
const inputaction_t negX, const inputaction_t posX,
|
||||
const inputaction_t negY, const inputaction_t posY,
|
||||
vec2 result
|
||||
) {
|
||||
assertNotNull(result, "Result vector cannot be null");
|
||||
float_t x = inputAxisDynamic(negX, posX);
|
||||
float_t y = inputAxisDynamic(negY, posY);
|
||||
float_t mag = sqrtf(x * x + y * y);
|
||||
if(mag <= 0.0f) {
|
||||
result[0] = 0.0f;
|
||||
result[1] = 0.0f;
|
||||
return;
|
||||
}
|
||||
if(mag > 1.0f) {
|
||||
x /= mag;
|
||||
y /= mag;
|
||||
}
|
||||
result[0] = x;
|
||||
result[1] = y;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool_t inputIsDown(const inputaction_t action) {
|
||||
|
||||
@@ -5,6 +5,7 @@ LEFT,
|
||||
RIGHT,
|
||||
ACCEPT,
|
||||
CANCEL,
|
||||
PAUSE,
|
||||
RAGEQUIT,
|
||||
CONSOLE,
|
||||
POINTERX,
|
||||
|
||||
|
+76
-2
@@ -52,7 +52,7 @@ float_t inputGetLastValue(const inputaction_t action);
|
||||
#ifdef DUSK_TIME_DYNAMIC
|
||||
/**
|
||||
* Gets the current value of a specific input action (dynamic timestep).
|
||||
*
|
||||
*
|
||||
* @param action The input action to get the value for.
|
||||
* @return The current value of the action (0.0f to 1.0f).
|
||||
*/
|
||||
@@ -60,11 +60,85 @@ float_t inputGetLastValue(const inputaction_t action);
|
||||
|
||||
/**
|
||||
* Gets the last value of a specific input action (dynamic timestep).
|
||||
*
|
||||
*
|
||||
* @param action The input action to get the value for.
|
||||
* @return The last value of the action (0.0f to 1.0f).
|
||||
*/
|
||||
float_t inputGetLastValueDynamic(const inputaction_t action);
|
||||
|
||||
/**
|
||||
* Checks if an action is currently down on the dynamic timestep.
|
||||
*
|
||||
* @param action The input action to check.
|
||||
* @return true if the action is currently down.
|
||||
*/
|
||||
bool_t inputIsDownDynamic(const inputaction_t action);
|
||||
|
||||
/**
|
||||
* Checks if an action was down on the previous dynamic frame.
|
||||
*
|
||||
* @param action The input action to check.
|
||||
* @return true if the action was down last dynamic frame.
|
||||
*/
|
||||
bool_t inputWasDownDynamic(const inputaction_t action);
|
||||
|
||||
/**
|
||||
* Checks if an action was pressed this dynamic frame (down now, not before).
|
||||
*
|
||||
* @param action The input action to check.
|
||||
* @return true if the action was just pressed this dynamic frame.
|
||||
*/
|
||||
bool_t inputPressedDynamic(const inputaction_t action);
|
||||
|
||||
/**
|
||||
* Checks if an action was released this dynamic frame (up now, down before).
|
||||
*
|
||||
* @param action The input action to check.
|
||||
* @return true if the action was just released this dynamic frame.
|
||||
*/
|
||||
bool_t inputReleasedDynamic(const inputaction_t action);
|
||||
|
||||
/**
|
||||
* Gets the value of an input axis on the dynamic timestep.
|
||||
*
|
||||
* @param neg The action representing the negative direction.
|
||||
* @param pos The action representing the positive direction.
|
||||
* @return The current axis value (-1.0f to 1.0f).
|
||||
*/
|
||||
float_t inputAxisDynamic(
|
||||
const inputaction_t neg,
|
||||
const inputaction_t pos
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the values of a 2D input axis on the dynamic timestep.
|
||||
*
|
||||
* @param negX Negative X action.
|
||||
* @param posX Positive X action.
|
||||
* @param negY Negative Y action.
|
||||
* @param posY Positive Y action.
|
||||
* @param result vec2 to store the result (-1.0f to 1.0f per axis).
|
||||
*/
|
||||
void inputAxis2DDynamic(
|
||||
const inputaction_t negX, const inputaction_t posX,
|
||||
const inputaction_t negY, const inputaction_t posY,
|
||||
vec2 result
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets an angled 2D unit vector from four actions on the dynamic timestep.
|
||||
*
|
||||
* @param negX Negative X action.
|
||||
* @param posX Positive X action.
|
||||
* @param negY Negative Y action.
|
||||
* @param posY Positive Y action.
|
||||
* @param result vec2 to store the result.
|
||||
*/
|
||||
void inputAngle2DDynamic(
|
||||
const inputaction_t negX, const inputaction_t posX,
|
||||
const inputaction_t negY, const inputaction_t posY,
|
||||
vec2 result
|
||||
);
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
add_subdirectory(debug)
|
||||
add_subdirectory(uifocus)
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
ui.c
|
||||
uiconsole.c
|
||||
uicrop.c
|
||||
uifps.c
|
||||
uielement.c
|
||||
uiframe.c
|
||||
uifullbox.c
|
||||
uiloading.c
|
||||
uitextbox.c
|
||||
uiplayerpos.c
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
uiconsole.c
|
||||
uifps.c
|
||||
uiplayerpos.c
|
||||
)
|
||||
+21
-15
@@ -11,23 +11,17 @@
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "ui/uielement.h"
|
||||
#include "ui/uifullbox.h"
|
||||
#include "ui/uiloading.h"
|
||||
#include "ui/uicrop.h"
|
||||
#include "time/time.h"
|
||||
#include "ui/uifocus/uifocus.h"
|
||||
#include "log/log.h"
|
||||
|
||||
ui_t UI;
|
||||
|
||||
errorret_t uiInit(void) {
|
||||
memoryZero(&UI, sizeof(ui_t));
|
||||
uiCropInit();
|
||||
uiFullboxInit(&UI_FULLBOX_UNDER);
|
||||
uiFullboxInit(&UI_FULLBOX_OVER);
|
||||
uiLoadingInit();
|
||||
uiFocusInit();
|
||||
|
||||
uielement_t *element = &UI_ELEMENTS[0];
|
||||
while(element->type != UI_ELEMENT_TYPE_NULL) {
|
||||
while(element->draw != NULL) {
|
||||
errorChain(uiElementInit(element));
|
||||
element++;
|
||||
}
|
||||
@@ -35,15 +29,21 @@ errorret_t uiInit(void) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void uiUpdate(void) {
|
||||
uiFullboxUpdate(&UI_FULLBOX_UNDER, TIME.delta);
|
||||
uiFullboxUpdate(&UI_FULLBOX_OVER, TIME.delta);
|
||||
uiLoadingUpdate(TIME.delta);
|
||||
errorret_t uiUpdate(void) {
|
||||
uiFocusUpdate();
|
||||
|
||||
uielement_t *element = &UI_ELEMENTS[0];
|
||||
while(element->draw != NULL) {
|
||||
errorChain(uiElementUpdate(element));
|
||||
element++;
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t uiRender(void) {
|
||||
const uielement_t *element = &UI_ELEMENTS[0];
|
||||
while(element->type != UI_ELEMENT_TYPE_NULL) {
|
||||
while(element->draw != NULL) {
|
||||
errorChain(uiElementDraw(element));
|
||||
|
||||
if(SPRITEBATCH.spriteCount > 0) {
|
||||
@@ -56,5 +56,11 @@ errorret_t uiRender(void) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void uiDispose(void) {
|
||||
errorret_t uiDispose(void) {
|
||||
uielement_t *element = &UI_ELEMENTS[0];
|
||||
while(element->draw != NULL) {
|
||||
errorChain(uiElementDispose(element));
|
||||
element++;
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
+6
-2
@@ -21,8 +21,10 @@ errorret_t uiInit(void);
|
||||
|
||||
/**
|
||||
* Updates the UI system.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
void uiUpdate(void);
|
||||
errorret_t uiUpdate(void);
|
||||
|
||||
/**
|
||||
* Renders the UI system.
|
||||
@@ -33,5 +35,7 @@ errorret_t uiRender(void);
|
||||
|
||||
/**
|
||||
* Disposes of the UI system.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
void uiDispose(void);
|
||||
errorret_t uiDispose(void);
|
||||
@@ -13,8 +13,9 @@
|
||||
|
||||
uicrop_t UI_CROP;
|
||||
|
||||
void uiCropInit(void) {
|
||||
errorret_t uiCropInit(void) {
|
||||
UI_CROP.color = COLOR_BLACK;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t uiCropDraw(void) {
|
||||
|
||||
@@ -17,8 +17,10 @@ extern uicrop_t UI_CROP;
|
||||
|
||||
/**
|
||||
* Initializes the crop bars to opaque black.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
void uiCropInit(void);
|
||||
errorret_t uiCropInit(void);
|
||||
|
||||
/**
|
||||
* Renders solid-color bars covering every area outside the
|
||||
|
||||
+48
-23
@@ -7,54 +7,79 @@
|
||||
|
||||
#include "uielement.h"
|
||||
#include "assert/assert.h"
|
||||
#include "ui/uifps.h"
|
||||
#include "ui/debug/uifps.h"
|
||||
#include "engine/engine.h"
|
||||
#include "ui/uitextbox.h"
|
||||
#include "ui/uifullbox.h"
|
||||
#include "ui/uiloading.h"
|
||||
#include "ui/uiplayerpos.h"
|
||||
#include "ui/debug/uiplayerpos.h"
|
||||
#include "ui/uicrop.h"
|
||||
#include "ui/uiconsole.h"
|
||||
#include "ui/debug/uiconsole.h"
|
||||
|
||||
uielement_t UI_ELEMENTS[] = {
|
||||
// Crop bars: black bars outside the scan-safe area.
|
||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiCropDraw },
|
||||
|
||||
// Fullbox under: above scene, below system UI.
|
||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiFullboxUnderDraw },
|
||||
{
|
||||
.init = uiFullboxUnderInit,
|
||||
.update = uiFullboxUnderUpdate,
|
||||
.draw = uiFullboxUnderDraw
|
||||
},
|
||||
|
||||
// { .type = UI_ELEMENT_TYPE_SCRIPT, .script = { .script = "ui/test.js" } },
|
||||
|
||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiTextboxDraw },
|
||||
{
|
||||
.init = uiTextboxInit,
|
||||
.update = uiTextboxUpdate,
|
||||
.draw = uiTextboxDraw,
|
||||
.dispose = uiTextboxDispose
|
||||
},
|
||||
|
||||
// Fullbox over: above absolutely everything.
|
||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiFullboxOverDraw },
|
||||
{
|
||||
.init = uiFullboxOverInit,
|
||||
.update = uiFullboxOverUpdate,
|
||||
.draw = uiFullboxOverDraw
|
||||
},
|
||||
|
||||
{
|
||||
.init = uiLoadingInit,
|
||||
.update = uiLoadingUpdate,
|
||||
.draw = uiLoadingDraw
|
||||
},
|
||||
|
||||
// These render above the fullbox overlay.
|
||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiConsoleDraw },
|
||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiFPSDraw },
|
||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiPlayerPosDraw },
|
||||
{ .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiLoadingDraw },
|
||||
{
|
||||
.init = uiCropInit,
|
||||
.draw = uiCropDraw
|
||||
},
|
||||
|
||||
|
||||
{ .type = UI_ELEMENT_TYPE_NULL },
|
||||
// Debug items
|
||||
{ .draw = uiConsoleDraw },
|
||||
{ .draw = uiFPSDraw },
|
||||
{ .draw = uiPlayerPosDraw },
|
||||
|
||||
{ 0 } // Null terminator
|
||||
};
|
||||
|
||||
errorret_t uiElementInit(uielement_t *element) {
|
||||
assertNotNull(element, "element must not be NULL");
|
||||
if(element->init != NULL) errorChain(element->init());
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t uiElementUpdate(uielement_t *element) {
|
||||
assertNotNull(element, "element must not be NULL");
|
||||
if(element->update != NULL) errorChain(element->update());
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t uiElementDraw(const uielement_t *element) {
|
||||
switch(element->type) {
|
||||
case UI_ELEMENT_TYPE_NATIVE:
|
||||
errorChain(element->draw());
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Invalid UI element type");
|
||||
}
|
||||
assertNotNull(element, "element must not be NULL");
|
||||
assertNotNull(element->draw, "element draw callback must not be NULL");
|
||||
return element->draw();
|
||||
}
|
||||
|
||||
errorret_t uiElementDispose(uielement_t *element) {
|
||||
assertNotNull(element, "element must not be NULL");
|
||||
if(element->dispose != NULL) errorChain(element->dispose());
|
||||
errorOk();
|
||||
}
|
||||
+26
-11
@@ -8,28 +8,43 @@
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
|
||||
typedef enum {
|
||||
UI_ELEMENT_TYPE_NULL,
|
||||
UI_ELEMENT_TYPE_NATIVE,
|
||||
UI_ELEMENT_TYPE_COUNT
|
||||
} uielementtype_t;
|
||||
|
||||
typedef struct {
|
||||
uielementtype_t type;
|
||||
errorret_t (*init)();
|
||||
errorret_t (*update)();
|
||||
errorret_t (*draw)();
|
||||
errorret_t (*dispose)();
|
||||
} uielement_t;
|
||||
|
||||
extern uielement_t UI_ELEMENTS[];
|
||||
|
||||
/**
|
||||
* Initializes a UI element.
|
||||
* Initializes a UI element, calling its init callback if set.
|
||||
*
|
||||
* @param element The element to initialize.
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiElementInit(uielement_t *element);
|
||||
|
||||
/**
|
||||
* Draws a UI element.
|
||||
*
|
||||
* Updates a UI element, calling its update callback if set.
|
||||
*
|
||||
* @param element The element to update.
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiElementUpdate(uielement_t *element);
|
||||
|
||||
/**
|
||||
* Draws a UI element, calling its draw callback if set.
|
||||
*
|
||||
* @param element The element to render.
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiElementDraw(const uielement_t *element);
|
||||
errorret_t uiElementDraw(const uielement_t *element);
|
||||
|
||||
/**
|
||||
* Disposes of a UI element, calling its dispose callback if set.
|
||||
*
|
||||
* @param element The element to dispose.
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiElementDispose(uielement_t *element);
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
uifocus.c
|
||||
)
|
||||
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "uifocus.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "input/input.h"
|
||||
#include "time/time.h"
|
||||
|
||||
const uifocusdirmap_t UI_FOCUS_DIR_MAP[] = {
|
||||
{ INPUT_ACTION_UP, UI_FOCUS_DIRECTION_UP, 0, -1 },
|
||||
{ INPUT_ACTION_DOWN, UI_FOCUS_DIRECTION_DOWN, 0, 1 },
|
||||
{ INPUT_ACTION_LEFT, UI_FOCUS_DIRECTION_LEFT, -1, 0 },
|
||||
{ INPUT_ACTION_RIGHT, UI_FOCUS_DIRECTION_RIGHT, 1, 0 },
|
||||
{ INPUT_ACTION_NULL, UI_FOCUS_DIRECTION_NONE, 0, 0 },
|
||||
};
|
||||
|
||||
uifocus_t UI_FOCUS;
|
||||
|
||||
void uiFocusInit(void) {
|
||||
memoryZero(&UI_FOCUS, sizeof(uifocus_t));
|
||||
}
|
||||
|
||||
void uiFocusPush(
|
||||
const uint8_t cols,
|
||||
const uint8_t rows,
|
||||
uifocusitemcallback_t selected,
|
||||
uifocusitemcallback_t changed,
|
||||
uifocusitemcallback_t closed
|
||||
) {
|
||||
assertTrue(
|
||||
UI_FOCUS.count < UI_FOCUS_STACK_MAX,
|
||||
"UI focus stack overflow"
|
||||
);
|
||||
|
||||
uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count];
|
||||
memoryZero(item, sizeof(uifocusitem_t));
|
||||
item->cols = cols;
|
||||
item->rows = rows;
|
||||
item->selected = selected;
|
||||
item->changed = changed;
|
||||
item->closed = closed;
|
||||
UI_FOCUS.count++;
|
||||
}
|
||||
|
||||
void uiFocusPop(void) {
|
||||
assertTrue(UI_FOCUS.count > 0, "UI focus stack underflow");
|
||||
|
||||
uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count - 1];
|
||||
if(item->closed != NULL) item->closed(item);
|
||||
UI_FOCUS.count--;
|
||||
}
|
||||
|
||||
void uiFocusPopTo(const int32_t depth) {
|
||||
assertTrue(depth >= 0, "Focus depth must be >= 0");
|
||||
assertTrue(depth <= UI_FOCUS.count, "Focus depth exceeds current depth");
|
||||
while(UI_FOCUS.count > depth) uiFocusPop();
|
||||
}
|
||||
|
||||
void uiFocusSelect(const uint8_t x, const uint8_t y) {
|
||||
assertTrue(UI_FOCUS.count > 0, "No active focus item");
|
||||
uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count - 1];
|
||||
assertTrue(item->cols > 0, "Focus item cols must be > 0");
|
||||
assertTrue(item->rows > 0, "Focus item rows must be > 0");
|
||||
uint8_t newX = x % item->cols;
|
||||
uint8_t newY = y % item->rows;
|
||||
|
||||
if(item->changed != NULL) {
|
||||
uint8_t oldX = item->x;
|
||||
uint8_t oldY = item->y;
|
||||
item->x = newX;
|
||||
item->y = newY;
|
||||
if(!item->changed(item)) {
|
||||
item->x = oldX;
|
||||
item->y = oldY;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
item->x = newX;
|
||||
item->y = newY;
|
||||
}
|
||||
|
||||
void uiFocusMoveDirection(
|
||||
uifocusitem_t *item,
|
||||
const uifocusdirection_t dir
|
||||
) {
|
||||
const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP;
|
||||
while(m->action != INPUT_ACTION_NULL) {
|
||||
if(m->direction == dir) {
|
||||
uint8_t x = (uint8_t)(
|
||||
(item->x + item->cols + m->dx) % item->cols
|
||||
);
|
||||
uint8_t y = (uint8_t)(
|
||||
(item->y + item->rows + m->dy) % item->rows
|
||||
);
|
||||
uiFocusSelect(x, y);
|
||||
return;
|
||||
}
|
||||
m++;
|
||||
}
|
||||
}
|
||||
|
||||
void uiFocusUpdate(void) {
|
||||
if(UI_FOCUS.count == 0) return;
|
||||
uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count - 1];
|
||||
|
||||
#ifdef DUSK_TIME_DYNAMIC
|
||||
#define PRESSED(a) inputPressedDynamic(a)
|
||||
#define IS_DOWN(a) inputIsDownDynamic(a)
|
||||
#else
|
||||
#define PRESSED(a) inputPressed(a)
|
||||
#define IS_DOWN(a) inputIsDown(a)
|
||||
#endif
|
||||
|
||||
if(PRESSED(INPUT_ACTION_ACCEPT)) {
|
||||
if(item->selected != NULL) item->selected(item);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if(PRESSED(INPUT_ACTION_CANCEL)) {
|
||||
if(item->closed != NULL && item->closed(item)) {
|
||||
UI_FOCUS.count--;
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
|
||||
{
|
||||
const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP;
|
||||
while(m->action != INPUT_ACTION_NULL) {
|
||||
if(PRESSED(m->action)) {
|
||||
UI_FOCUS.direction = m->direction;
|
||||
UI_FOCUS.timeHeld = 0.0f;
|
||||
uiFocusMoveDirection(item, m->direction);
|
||||
goto done;
|
||||
}
|
||||
m++;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
bool_t held = false;
|
||||
const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP;
|
||||
while(m->action != INPUT_ACTION_NULL) {
|
||||
if(m->direction == UI_FOCUS.direction) {
|
||||
held = IS_DOWN(m->action);
|
||||
break;
|
||||
}
|
||||
m++;
|
||||
}
|
||||
|
||||
if(!held) {
|
||||
UI_FOCUS.direction = UI_FOCUS_DIRECTION_NONE;
|
||||
UI_FOCUS.timeHeld = 0.0f;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DUSK_TIME_DYNAMIC
|
||||
UI_FOCUS.timeHeld += TIME.dynamicDelta;
|
||||
#else
|
||||
UI_FOCUS.timeHeld += TIME.delta;
|
||||
#endif
|
||||
|
||||
if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY) goto done;
|
||||
|
||||
if(UI_FOCUS.timeHeld >= UI_FOCUS_HOLD_DELAY + UI_FOCUS_HOLD_REPEAT) {
|
||||
UI_FOCUS.timeHeld = UI_FOCUS_HOLD_DELAY;
|
||||
uiFocusMoveDirection(item, UI_FOCUS.direction);
|
||||
}
|
||||
|
||||
done:
|
||||
#undef PRESSED
|
||||
#undef IS_DOWN
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "uifocusitem.h"
|
||||
#include "input/inputaction.h"
|
||||
|
||||
/** Maximum depth of the focus stack. */
|
||||
#define UI_FOCUS_STACK_MAX 8
|
||||
|
||||
/**
|
||||
* How long a direction must be held before repeating begins, in seconds.
|
||||
*/
|
||||
#define UI_FOCUS_HOLD_DELAY 0.5f
|
||||
|
||||
/**
|
||||
* Interval between repeated moves while a direction is held, in seconds.
|
||||
*/
|
||||
#define UI_FOCUS_HOLD_REPEAT 0.1f
|
||||
|
||||
typedef enum {
|
||||
UI_FOCUS_DIRECTION_NONE,
|
||||
UI_FOCUS_DIRECTION_UP,
|
||||
UI_FOCUS_DIRECTION_DOWN,
|
||||
UI_FOCUS_DIRECTION_LEFT,
|
||||
UI_FOCUS_DIRECTION_RIGHT
|
||||
} uifocusdirection_t;
|
||||
|
||||
typedef struct {
|
||||
inputaction_t action;
|
||||
uifocusdirection_t direction;
|
||||
int8_t dx;
|
||||
int8_t dy;
|
||||
} uifocusdirmap_t;
|
||||
|
||||
/**
|
||||
* Mapping of input actions to focus directions, terminated by an
|
||||
* entry with action == INPUT_ACTION_NULL.
|
||||
*/
|
||||
extern const uifocusdirmap_t UI_FOCUS_DIR_MAP[];
|
||||
|
||||
/**
|
||||
* A stack of focused UI items. Push an item when a widget captures
|
||||
* focus; pop it when focus is released. The topmost item is always
|
||||
* the active focus context.
|
||||
*/
|
||||
typedef struct {
|
||||
uifocusitem_t items[UI_FOCUS_STACK_MAX];
|
||||
int32_t count;
|
||||
uifocusdirection_t direction;
|
||||
float_t timeHeld;
|
||||
} uifocus_t;
|
||||
|
||||
extern uifocus_t UI_FOCUS;
|
||||
|
||||
/**
|
||||
* Initializes the focus system, zeroing all state.
|
||||
*/
|
||||
void uiFocusInit(void);
|
||||
|
||||
/**
|
||||
* Pushes a new focus item onto the stack with the given grid dimensions
|
||||
* and callbacks. x and y are initialized to 0.
|
||||
*
|
||||
* @param cols Number of columns in the focus grid.
|
||||
* @param rows Number of rows in the focus grid.
|
||||
* @param selected Called when the user selects the focused cell.
|
||||
* @param changed Called when the focused cell position changes.
|
||||
* @param closed Called when this focus item is popped.
|
||||
*/
|
||||
void uiFocusPush(
|
||||
const uint8_t cols,
|
||||
const uint8_t rows,
|
||||
uifocusitemcallback_t selected,
|
||||
uifocusitemcallback_t changed,
|
||||
uifocusitemcallback_t closed
|
||||
);
|
||||
|
||||
/**
|
||||
* Pops the topmost focus item from the stack, invoking its closed
|
||||
* callback if one is set.
|
||||
*/
|
||||
void uiFocusPop(void);
|
||||
|
||||
/**
|
||||
* Pops focus items until the stack depth reaches the given value.
|
||||
* Each popped item has its closed callback invoked as normal.
|
||||
*
|
||||
* @param depth Target stack depth to pop back to.
|
||||
*/
|
||||
void uiFocusPopTo(const int32_t depth);
|
||||
|
||||
/**
|
||||
* Manually sets the cursor position of the topmost focus item and
|
||||
* fires its changed callback.
|
||||
*
|
||||
* @param x Column to move to.
|
||||
* @param y Row to move to.
|
||||
*/
|
||||
void uiFocusSelect(const uint8_t x, const uint8_t y);
|
||||
|
||||
/**
|
||||
* Moves the topmost focus item one step in the given direction,
|
||||
* wrapping at the grid edges, and fires its changed callback.
|
||||
*
|
||||
* @param item The focus item to move.
|
||||
* @param dir Direction to move.
|
||||
*/
|
||||
void uiFocusMoveDirection(
|
||||
uifocusitem_t *item,
|
||||
const uifocusdirection_t dir
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates the focus system. Handles first-press movement and
|
||||
* held-direction repeating. Called once per game tick.
|
||||
*/
|
||||
void uiFocusUpdate(void);
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct uifocusitem_s uifocusitem_t;
|
||||
|
||||
/**
|
||||
* Callback invoked when a focus item's selected cell changes.
|
||||
*
|
||||
* @param item The focus item that changed.
|
||||
*/
|
||||
typedef bool_t (*uifocusitemcallback_t)(const uifocusitem_t *item);
|
||||
|
||||
/**
|
||||
* A single entry on the UI focus stack. Tracks the focused cell
|
||||
* within a grid of cols x rows, and the current x/y position.
|
||||
*/
|
||||
struct uifocusitem_s {
|
||||
uint8_t cols;
|
||||
uint8_t rows;
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uifocusitemcallback_t selected;
|
||||
uifocusitemcallback_t changed;
|
||||
uifocusitemcallback_t closed;
|
||||
};
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "uifullbox.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "time/time.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "display/texture/texture.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
@@ -92,10 +93,30 @@ void uiFullboxTransition(
|
||||
fullbox->easing = easing;
|
||||
}
|
||||
|
||||
errorret_t uiFullboxUnderInit(void) {
|
||||
uiFullboxInit(&UI_FULLBOX_UNDER);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t uiFullboxUnderUpdate(void) {
|
||||
uiFullboxUpdate(&UI_FULLBOX_UNDER, TIME.delta);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t uiFullboxUnderDraw(void) {
|
||||
return uiFullboxDraw(&UI_FULLBOX_UNDER);
|
||||
}
|
||||
|
||||
errorret_t uiFullboxOverInit(void) {
|
||||
uiFullboxInit(&UI_FULLBOX_OVER);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t uiFullboxOverUpdate(void) {
|
||||
uiFullboxUpdate(&UI_FULLBOX_OVER, TIME.delta);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t uiFullboxOverDraw(void) {
|
||||
return uiFullboxDraw(&UI_FULLBOX_OVER);
|
||||
}
|
||||
|
||||
@@ -66,12 +66,44 @@ void uiFullboxTransition(
|
||||
easingtype_t easing
|
||||
);
|
||||
|
||||
/**
|
||||
* Init function for UI_FULLBOX_UNDER (registered in UI_ELEMENTS).
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiFullboxUnderInit(void);
|
||||
|
||||
/**
|
||||
* Update function for UI_FULLBOX_UNDER (registered in UI_ELEMENTS).
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiFullboxUnderUpdate(void);
|
||||
|
||||
/**
|
||||
* Draw function for UI_FULLBOX_UNDER (registered in UI_ELEMENTS).
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiFullboxUnderDraw(void);
|
||||
|
||||
/**
|
||||
* Init function for UI_FULLBOX_OVER (registered in UI_ELEMENTS).
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiFullboxOverInit(void);
|
||||
|
||||
/**
|
||||
* Update function for UI_FULLBOX_OVER (registered in UI_ELEMENTS).
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiFullboxOverUpdate(void);
|
||||
|
||||
/**
|
||||
* Draw function for UI_FULLBOX_OVER (registered in UI_ELEMENTS).
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
errorret_t uiFullboxOverDraw(void);
|
||||
|
||||
+11
-8
@@ -8,6 +8,7 @@
|
||||
#include "uiloading.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "time/time.h"
|
||||
#include "display/text/text.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
@@ -17,7 +18,7 @@
|
||||
|
||||
uiloading_t UI_LOADING;
|
||||
|
||||
void uiLoadingInit(void) {
|
||||
errorret_t uiLoadingInit(void) {
|
||||
memoryZero(&UI_LOADING, sizeof(uiloading_t));
|
||||
eventInit(
|
||||
&UI_LOADING.onTransitionEnd,
|
||||
@@ -25,16 +26,18 @@ void uiLoadingInit(void) {
|
||||
UI_LOADING.onTransitionEndUsers,
|
||||
4
|
||||
);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void uiLoadingUpdate(float_t delta) {
|
||||
if(UI_LOADING.duration <= 0.0f || UI_LOADING.time >= UI_LOADING.duration)
|
||||
return;
|
||||
UI_LOADING.time += delta;
|
||||
if(UI_LOADING.time >= UI_LOADING.duration) {
|
||||
UI_LOADING.time = UI_LOADING.duration;
|
||||
eventInvoke(&UI_LOADING.onTransitionEnd, &UI_LOADING);
|
||||
errorret_t uiLoadingUpdate(void) {
|
||||
if(UI_LOADING.duration > 0.0f && UI_LOADING.time < UI_LOADING.duration) {
|
||||
UI_LOADING.time += TIME.delta;
|
||||
if(UI_LOADING.time >= UI_LOADING.duration) {
|
||||
UI_LOADING.time = UI_LOADING.duration;
|
||||
eventInvoke(&UI_LOADING.onTransitionEnd, &UI_LOADING);
|
||||
}
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t uiLoadingDraw(void) {
|
||||
|
||||
@@ -26,16 +26,18 @@ extern uiloading_t UI_LOADING;
|
||||
|
||||
/**
|
||||
* Initializes the loading indicator.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
void uiLoadingInit(void);
|
||||
errorret_t uiLoadingInit(void);
|
||||
|
||||
/**
|
||||
* Advances the loading indicator fade transition. Fires onTransitionEnd once
|
||||
* when the transition completes.
|
||||
*
|
||||
* @param delta Seconds elapsed since last update.
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
void uiLoadingUpdate(float_t delta);
|
||||
errorret_t uiLoadingUpdate(void);
|
||||
|
||||
/**
|
||||
* Draws the loading indicator. No-op when fully transparent.
|
||||
|
||||
@@ -271,7 +271,8 @@ errorret_t uiTextboxDraw(void) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void uiTextboxDispose(void) {
|
||||
errorret_t uiTextboxDispose(void) {
|
||||
uiFrameDispose(&UI_TEXTBOX.frame);
|
||||
UI_TEXTBOX.font = NULL;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -115,5 +115,7 @@ void uiTextboxNextPage(void);
|
||||
|
||||
/**
|
||||
* Disposes of UI_TEXTBOX, nulling out texture pointers.
|
||||
*
|
||||
* @return Any error that occurs.
|
||||
*/
|
||||
void uiTextboxDispose(void);
|
||||
errorret_t uiTextboxDispose(void);
|
||||
|
||||
@@ -115,6 +115,7 @@ errorret_t inputInitDolphin(void) {
|
||||
X("b", INPUT_ACTION_CANCEL);
|
||||
X("z", INPUT_ACTION_CONSOLE);
|
||||
X("start", INPUT_ACTION_RAGEQUIT);
|
||||
X("start", INPUT_ACTION_PAUSE);
|
||||
|
||||
#elif defined(DUSK_WII)
|
||||
X("up", INPUT_ACTION_UP);
|
||||
@@ -130,6 +131,7 @@ errorret_t inputInitDolphin(void) {
|
||||
X("b", INPUT_ACTION_CANCEL);
|
||||
X("z", INPUT_ACTION_CONSOLE);
|
||||
X("start", INPUT_ACTION_RAGEQUIT);
|
||||
X("start", INPUT_ACTION_PAUSE);
|
||||
|
||||
// TODO: Wiimote, USB Keyboard, probably more.
|
||||
|
||||
|
||||
@@ -517,6 +517,7 @@ errorret_t inputInitLinux(void) {
|
||||
X("tab", INPUT_ACTION_CANCEL);
|
||||
X("q", INPUT_ACTION_CANCEL);
|
||||
X("escape", INPUT_ACTION_RAGEQUIT);
|
||||
X("enter", INPUT_ACTION_PAUSE);
|
||||
X("`", INPUT_ACTION_CONSOLE);
|
||||
#endif
|
||||
|
||||
@@ -528,6 +529,7 @@ errorret_t inputInitLinux(void) {
|
||||
X("gamepad_a", INPUT_ACTION_ACCEPT);
|
||||
X("gamepad_b", INPUT_ACTION_CANCEL);
|
||||
X("gamepad_back", INPUT_ACTION_RAGEQUIT);
|
||||
X("gamepad_start", INPUT_ACTION_PAUSE);
|
||||
X("gamepad_lstick_up", INPUT_ACTION_UP);
|
||||
X("gamepad_lstick_down", INPUT_ACTION_DOWN);
|
||||
X("gamepad_lstick_left", INPUT_ACTION_LEFT);
|
||||
|
||||
@@ -83,6 +83,7 @@ errorret_t inputInitPSP(void) {
|
||||
X("cancel", INPUT_ACTION_CANCEL);
|
||||
X("triangle", INPUT_ACTION_CONSOLE);
|
||||
X("select", INPUT_ACTION_RAGEQUIT);
|
||||
X("start", INPUT_ACTION_PAUSE);
|
||||
X("lstick_up", INPUT_ACTION_UP);
|
||||
X("lstick_down", INPUT_ACTION_DOWN);
|
||||
X("lstick_left", INPUT_ACTION_LEFT);
|
||||
|
||||
Reference in New Issue
Block a user