mirror of
https://github.com/LostArtefacts/TRX.git
synced 2025-04-28 12:47:58 +03:00
ui: introduce UI v2
This commit is contained in:
parent
454d133db7
commit
548c5ac653
49 changed files with 1590 additions and 722 deletions
|
@ -4,7 +4,7 @@
|
|||
#include "debug.h"
|
||||
#include "game/console/registry.h"
|
||||
#include "game/game_string.h"
|
||||
#include "game/ui/widgets/console.h"
|
||||
#include "game/ui2.h"
|
||||
#include "log.h"
|
||||
#include "memory.h"
|
||||
#include "strings.h"
|
||||
|
@ -14,20 +14,18 @@
|
|||
#include <string.h>
|
||||
|
||||
static bool m_IsOpened = false;
|
||||
static UI_WIDGET *m_Console;
|
||||
static UI2_CONSOLE_STATE m_UIState = {};
|
||||
|
||||
void Console_Init(void)
|
||||
{
|
||||
m_Console = UI_Console_Create();
|
||||
UI2_Console_Init(&m_UIState);
|
||||
|
||||
Console_History_Init();
|
||||
}
|
||||
|
||||
void Console_Shutdown(void)
|
||||
{
|
||||
if (m_Console != nullptr) {
|
||||
m_Console->free(m_Console);
|
||||
m_Console = nullptr;
|
||||
}
|
||||
UI2_Console_Free(&m_UIState);
|
||||
|
||||
Console_History_Shutdown();
|
||||
Console_Registry_Shutdown();
|
||||
|
@ -38,16 +36,21 @@ void Console_Shutdown(void)
|
|||
void Console_Open(void)
|
||||
{
|
||||
if (m_IsOpened) {
|
||||
UI_Console_HandleClose(m_Console);
|
||||
return;
|
||||
}
|
||||
m_IsOpened = true;
|
||||
UI_Console_HandleOpen(m_Console);
|
||||
UI2_FireEvent(
|
||||
(EVENT) { .name = "console_open", .sender = nullptr, .data = nullptr });
|
||||
}
|
||||
|
||||
void Console_Close(void)
|
||||
{
|
||||
UI_Console_HandleClose(m_Console);
|
||||
if (!m_IsOpened) {
|
||||
return;
|
||||
}
|
||||
m_IsOpened = false;
|
||||
UI2_FireEvent((EVENT) {
|
||||
.name = "console_close", .sender = nullptr, .data = nullptr });
|
||||
}
|
||||
|
||||
bool Console_IsOpened(void)
|
||||
|
@ -55,21 +58,6 @@ bool Console_IsOpened(void)
|
|||
return m_IsOpened;
|
||||
}
|
||||
|
||||
void Console_ScrollLogs(void)
|
||||
{
|
||||
UI_Console_ScrollLogs(m_Console);
|
||||
}
|
||||
|
||||
int32_t Console_GetVisibleLogCount(void)
|
||||
{
|
||||
return UI_Console_GetVisibleLogCount(m_Console);
|
||||
}
|
||||
|
||||
int32_t Console_GetMaxLogCount(void)
|
||||
{
|
||||
return UI_Console_GetMaxLogCount(m_Console);
|
||||
}
|
||||
|
||||
void Console_Log(const char *fmt, ...)
|
||||
{
|
||||
ASSERT(fmt != nullptr);
|
||||
|
@ -86,7 +74,12 @@ void Console_Log(const char *fmt, ...)
|
|||
va_end(va);
|
||||
|
||||
LOG_INFO("%s", text);
|
||||
UI_Console_HandleLog(m_Console, text);
|
||||
|
||||
UI2_FireEvent((EVENT) {
|
||||
.name = "console_log",
|
||||
.sender = nullptr,
|
||||
.data = text,
|
||||
});
|
||||
Memory_FreePointer(&text);
|
||||
}
|
||||
|
||||
|
@ -137,15 +130,5 @@ COMMAND_RESULT Console_Eval(const char *const cmdline)
|
|||
|
||||
void Console_Draw(void)
|
||||
{
|
||||
if (m_Console == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
Console_ScrollLogs();
|
||||
|
||||
if (Console_IsOpened() || Console_GetVisibleLogCount() > 0) {
|
||||
Console_DrawBackdrop();
|
||||
}
|
||||
|
||||
m_Console->draw(m_Console);
|
||||
UI2_Console(&m_UIState);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "game/savegame.h"
|
||||
#include "game/shell.h"
|
||||
#include "game/text.h"
|
||||
#include "game/ui2.h"
|
||||
#include "gfx/gl/track.h"
|
||||
|
||||
#define DEBUG_OPTIM 0
|
||||
|
@ -64,6 +65,7 @@ static PHASE_CONTROL M_Control(PHASE *const phase, const int32_t nframes)
|
|||
static void M_Draw(PHASE *const phase)
|
||||
{
|
||||
Output_BeginScene();
|
||||
UI2_BeginScene();
|
||||
#if DEBUG_OPTIM
|
||||
BENCHMARK benchmark = Benchmark_Start();
|
||||
#endif
|
||||
|
@ -73,6 +75,7 @@ static void M_Draw(PHASE *const phase)
|
|||
|
||||
Console_Draw();
|
||||
Text_Draw();
|
||||
UI2_EndScene();
|
||||
Output_DrawPolyList();
|
||||
Fader_Draw(&m_ExitFader);
|
||||
|
||||
|
|
|
@ -395,5 +395,5 @@ int32_t Text_GetHeight(const TEXTSTRING *const text)
|
|||
height += TEXT_HEIGHT_FIXED;
|
||||
}
|
||||
}
|
||||
return height * text->scale.v / TEXT_BASE_SCALE;
|
||||
return height * text->scale.v / (float)TEXT_BASE_SCALE;
|
||||
}
|
||||
|
|
|
@ -1,310 +0,0 @@
|
|||
#include "game/ui/widgets/console.h"
|
||||
|
||||
#include "game/clock.h"
|
||||
#include "game/console/common.h"
|
||||
#include "game/console/history.h"
|
||||
#include "game/text.h"
|
||||
#include "game/ui/common.h"
|
||||
#include "game/ui/events.h"
|
||||
#include "game/ui/widgets/label.h"
|
||||
#include "game/ui/widgets/prompt.h"
|
||||
#include "game/ui/widgets/spacer.h"
|
||||
#include "game/ui/widgets/stack.h"
|
||||
#include "memory.h"
|
||||
#include "strings.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define WINDOW_MARGIN 5
|
||||
#define MAX_LOG_LINES 20
|
||||
#define MAX_LISTENERS 4
|
||||
#define LOG_MARGIN 10
|
||||
#define LOG_SCALE 0.8
|
||||
#define DELAY_PER_CHAR 0.2
|
||||
|
||||
typedef struct {
|
||||
UI_WIDGET_VTABLE vtable;
|
||||
UI_WIDGET *container;
|
||||
UI_WIDGET *prompt;
|
||||
UI_WIDGET *spacer;
|
||||
char *log_lines;
|
||||
int32_t logs_on_screen;
|
||||
int32_t history_idx;
|
||||
|
||||
int32_t listeners[MAX_LISTENERS];
|
||||
struct {
|
||||
double expire_at;
|
||||
UI_WIDGET *label;
|
||||
} logs[MAX_LOG_LINES];
|
||||
} UI_CONSOLE;
|
||||
|
||||
static void M_MoveHistoryUp(UI_CONSOLE *self);
|
||||
static void M_MoveHistoryDown(UI_CONSOLE *self);
|
||||
static void M_DoLayout(UI_CONSOLE *self);
|
||||
static void M_HandlePromptCancel(const EVENT *event, void *data);
|
||||
static void M_HandlePromptConfirm(const EVENT *event, void *data);
|
||||
static void M_HandleCanvasResize(const EVENT *event, void *data);
|
||||
static void M_UpdateLogCount(UI_CONSOLE *self);
|
||||
|
||||
static int32_t M_GetWidth(const UI_CONSOLE *self);
|
||||
static int32_t M_GetHeight(const UI_CONSOLE *self);
|
||||
static void M_SetPosition(UI_CONSOLE *self, int32_t x, int32_t y);
|
||||
static void M_Control(UI_CONSOLE *self);
|
||||
static void M_Draw(UI_CONSOLE *self);
|
||||
static void M_Free(UI_CONSOLE *self);
|
||||
|
||||
static void M_MoveHistoryUp(UI_CONSOLE *const self)
|
||||
{
|
||||
self->history_idx--;
|
||||
CLAMP(self->history_idx, 0, Console_History_GetLength());
|
||||
const char *const new_prompt = Console_History_Get(self->history_idx);
|
||||
if (new_prompt == nullptr) {
|
||||
UI_Prompt_ChangeText(self->prompt, "");
|
||||
} else {
|
||||
UI_Prompt_ChangeText(self->prompt, new_prompt);
|
||||
}
|
||||
}
|
||||
|
||||
static void M_MoveHistoryDown(UI_CONSOLE *const self)
|
||||
{
|
||||
self->history_idx++;
|
||||
CLAMP(self->history_idx, 0, Console_History_GetLength());
|
||||
const char *const new_prompt = Console_History_Get(self->history_idx);
|
||||
if (new_prompt == nullptr) {
|
||||
UI_Prompt_ChangeText(self->prompt, "");
|
||||
} else {
|
||||
UI_Prompt_ChangeText(self->prompt, new_prompt);
|
||||
}
|
||||
}
|
||||
|
||||
static void M_DoLayout(UI_CONSOLE *const self)
|
||||
{
|
||||
UI_Stack_SetSize(self->container, M_GetWidth(self), M_GetHeight(self));
|
||||
M_SetPosition(self, WINDOW_MARGIN, WINDOW_MARGIN);
|
||||
}
|
||||
|
||||
static void M_HandlePromptCancel(const EVENT *const event, void *const data)
|
||||
{
|
||||
Console_Close();
|
||||
}
|
||||
|
||||
static void M_HandlePromptConfirm(const EVENT *const event, void *const data)
|
||||
{
|
||||
UI_CONSOLE *const self = (UI_CONSOLE *)data;
|
||||
const char *text = event->data;
|
||||
Console_History_Append(text);
|
||||
Console_Eval(text);
|
||||
Console_Close();
|
||||
|
||||
self->history_idx = Console_History_GetLength();
|
||||
}
|
||||
|
||||
static void M_HandleCanvasResize(const EVENT *event, void *data)
|
||||
{
|
||||
UI_CONSOLE *const self = (UI_CONSOLE *)data;
|
||||
M_DoLayout(self);
|
||||
}
|
||||
|
||||
static void M_HandleKeyDown(const EVENT *const event, void *const user_data)
|
||||
{
|
||||
if (!Console_IsOpened()) {
|
||||
return;
|
||||
}
|
||||
|
||||
UI_CONSOLE *const self = user_data;
|
||||
const UI_INPUT key = (UI_INPUT)(uintptr_t)event->data;
|
||||
|
||||
// clang-format off
|
||||
switch (key) {
|
||||
case UI_KEY_UP: M_MoveHistoryUp(self); break;
|
||||
case UI_KEY_DOWN: M_MoveHistoryDown(self); break;
|
||||
default: break;
|
||||
}
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
static void M_UpdateLogCount(UI_CONSOLE *const self)
|
||||
{
|
||||
self->logs_on_screen = 0;
|
||||
for (int32_t i = MAX_LOG_LINES - 1; i >= 0; i--) {
|
||||
if (self->logs[i].expire_at != 0.0) {
|
||||
self->logs_on_screen = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t M_GetWidth(const UI_CONSOLE *const self)
|
||||
{
|
||||
if (self->vtable.is_hidden) {
|
||||
return 0;
|
||||
}
|
||||
return UI_GetCanvasWidth() - 2 * WINDOW_MARGIN;
|
||||
}
|
||||
|
||||
static int32_t M_GetHeight(const UI_CONSOLE *const self)
|
||||
{
|
||||
if (self->vtable.is_hidden) {
|
||||
return 0;
|
||||
}
|
||||
return UI_GetCanvasHeight() - 2 * WINDOW_MARGIN;
|
||||
}
|
||||
|
||||
static void M_SetPosition(UI_CONSOLE *const self, int32_t x, int32_t y)
|
||||
{
|
||||
self->container->set_position(self->container, x, y);
|
||||
}
|
||||
|
||||
static void M_Control(UI_CONSOLE *const self)
|
||||
{
|
||||
if (self->container->control != nullptr) {
|
||||
self->container->control(self->container);
|
||||
}
|
||||
}
|
||||
|
||||
static void M_Draw(UI_CONSOLE *const self)
|
||||
{
|
||||
if (self->vtable.is_hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->container->draw != nullptr) {
|
||||
self->container->draw(self->container);
|
||||
}
|
||||
}
|
||||
|
||||
static void M_Free(UI_CONSOLE *const self)
|
||||
{
|
||||
self->spacer->free(self->spacer);
|
||||
self->prompt->free(self->prompt);
|
||||
self->container->free(self->container);
|
||||
for (int32_t i = 0; i < MAX_LOG_LINES; i++) {
|
||||
self->logs[i].label->free(self->logs[i].label);
|
||||
}
|
||||
for (int32_t i = 0; i < MAX_LISTENERS; i++) {
|
||||
UI_Events_Unsubscribe(self->listeners[i]);
|
||||
}
|
||||
Memory_Free(self);
|
||||
}
|
||||
|
||||
UI_WIDGET *UI_Console_Create(void)
|
||||
{
|
||||
UI_CONSOLE *const self = Memory_Alloc(sizeof(UI_CONSOLE));
|
||||
self->vtable = (UI_WIDGET_VTABLE) {
|
||||
.control = (UI_WIDGET_CONTROL)M_Control,
|
||||
.draw = (UI_WIDGET_DRAW)M_Draw,
|
||||
.get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth,
|
||||
.get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight,
|
||||
.set_position = (UI_WIDGET_SET_POSITION)M_SetPosition,
|
||||
.free = (UI_WIDGET_FREE)M_Free,
|
||||
};
|
||||
|
||||
self->container = UI_Stack_Create(
|
||||
UI_STACK_LAYOUT_VERTICAL, M_GetWidth(self), M_GetHeight(self));
|
||||
UI_Stack_SetVAlign(self->container, UI_STACK_V_ALIGN_BOTTOM);
|
||||
|
||||
for (int32_t i = MAX_LOG_LINES - 1; i >= 0; i--) {
|
||||
self->logs[i].label =
|
||||
UI_Label_Create("", UI_LABEL_AUTO_SIZE, UI_LABEL_AUTO_SIZE);
|
||||
UI_Label_SetScale(self->logs[i].label, LOG_SCALE);
|
||||
UI_Stack_AddChild(self->container, self->logs[i].label);
|
||||
}
|
||||
|
||||
self->spacer = UI_Spacer_Create(LOG_MARGIN, LOG_MARGIN);
|
||||
UI_Stack_AddChild(self->container, self->spacer);
|
||||
|
||||
self->prompt = UI_Prompt_Create(UI_LABEL_AUTO_SIZE, UI_LABEL_AUTO_SIZE);
|
||||
UI_Stack_AddChild(self->container, self->prompt);
|
||||
|
||||
int32_t i = 0;
|
||||
self->listeners[i++] = UI_Events_Subscribe(
|
||||
"confirm", self->prompt, M_HandlePromptConfirm, self);
|
||||
self->listeners[i++] = UI_Events_Subscribe(
|
||||
"cancel", self->prompt, M_HandlePromptCancel, nullptr);
|
||||
self->listeners[i++] = UI_Events_Subscribe(
|
||||
"canvas_resize", nullptr, M_HandleCanvasResize, self);
|
||||
self->listeners[i++] =
|
||||
UI_Events_Subscribe("key_down", nullptr, M_HandleKeyDown, self);
|
||||
|
||||
M_DoLayout(self);
|
||||
return (UI_WIDGET *)self;
|
||||
}
|
||||
|
||||
void UI_Console_HandleOpen(UI_WIDGET *const widget)
|
||||
{
|
||||
UI_CONSOLE *const self = (UI_CONSOLE *)widget;
|
||||
UI_Prompt_SetFocus(self->prompt, true);
|
||||
self->history_idx = Console_History_GetLength();
|
||||
}
|
||||
|
||||
void UI_Console_HandleClose(UI_WIDGET *const widget)
|
||||
{
|
||||
UI_CONSOLE *const self = (UI_CONSOLE *)widget;
|
||||
UI_Prompt_SetFocus(self->prompt, false);
|
||||
UI_Prompt_Clear(self->prompt);
|
||||
}
|
||||
|
||||
void UI_Console_HandleLog(UI_WIDGET *const widget, const char *const text)
|
||||
{
|
||||
UI_CONSOLE *const self = (UI_CONSOLE *)widget;
|
||||
|
||||
int32_t dst_idx = -1;
|
||||
for (int32_t i = MAX_LOG_LINES - 1; i > 0; i--) {
|
||||
if (self->logs[i].label == nullptr) {
|
||||
continue;
|
||||
}
|
||||
UI_Label_ChangeText(
|
||||
self->logs[i].label, UI_Label_GetText(self->logs[i - 1].label));
|
||||
self->logs[i].expire_at = self->logs[i - 1].expire_at;
|
||||
}
|
||||
|
||||
if (self->logs[0].label == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->logs[0].expire_at =
|
||||
Clock_GetRealTime() + strlen(text) * DELAY_PER_CHAR;
|
||||
|
||||
char *wrapped = String_WordWrap(text, Text_GetMaxLineLength());
|
||||
UI_Label_ChangeText(self->logs[0].label, wrapped);
|
||||
Memory_FreePointer(&wrapped);
|
||||
|
||||
M_DoLayout(self);
|
||||
M_UpdateLogCount(self);
|
||||
}
|
||||
|
||||
void UI_Console_ScrollLogs(UI_WIDGET *const widget)
|
||||
{
|
||||
UI_CONSOLE *const self = (UI_CONSOLE *)widget;
|
||||
|
||||
int32_t i = MAX_LOG_LINES - 1;
|
||||
while (i >= 0 && !self->logs[i].expire_at) {
|
||||
i--;
|
||||
}
|
||||
|
||||
bool need_layout = false;
|
||||
while (i >= 0 && self->logs[i].expire_at
|
||||
&& Clock_GetRealTime() >= self->logs[i].expire_at) {
|
||||
self->logs[i].expire_at = 0.0;
|
||||
UI_Label_ChangeText(self->logs[i].label, "");
|
||||
need_layout = true;
|
||||
i--;
|
||||
}
|
||||
|
||||
if (need_layout) {
|
||||
M_UpdateLogCount(self);
|
||||
M_DoLayout(self);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t UI_Console_GetVisibleLogCount(UI_WIDGET *const widget)
|
||||
{
|
||||
UI_CONSOLE *const self = (UI_CONSOLE *)widget;
|
||||
return self->logs_on_screen;
|
||||
}
|
||||
|
||||
int32_t UI_Console_GetMaxLogCount(UI_WIDGET *const widget)
|
||||
{
|
||||
return MAX_LOG_LINES;
|
||||
}
|
|
@ -1,327 +0,0 @@
|
|||
#include "game/ui/widgets/prompt.h"
|
||||
|
||||
#include "game/const.h"
|
||||
#include "game/input.h"
|
||||
#include "game/ui/common.h"
|
||||
#include "game/ui/events.h"
|
||||
#include "game/ui/widgets/label.h"
|
||||
#include "memory.h"
|
||||
#include "strings.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static const char m_ValidPromptChars[] =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-: ";
|
||||
|
||||
typedef struct {
|
||||
UI_WIDGET_VTABLE vtable;
|
||||
UI_WIDGET *label;
|
||||
UI_WIDGET *caret;
|
||||
|
||||
int32_t listener1;
|
||||
int32_t listener2;
|
||||
|
||||
struct {
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
} pos;
|
||||
bool is_focused;
|
||||
int32_t current_text_capacity;
|
||||
char *current_text;
|
||||
int32_t caret_pos;
|
||||
} UI_PROMPT;
|
||||
|
||||
static void M_UpdatePromptLabel(UI_PROMPT *self);
|
||||
static void M_UpdateCaretLabel(UI_PROMPT *self);
|
||||
static void M_MoveCaretLeft(UI_PROMPT *self);
|
||||
static void M_MoveCaretRight(UI_PROMPT *self);
|
||||
static void M_MoveCaretStart(UI_PROMPT *self);
|
||||
static void M_MoveCaretEnd(UI_PROMPT *self);
|
||||
static void M_DeleteCharBack(UI_PROMPT *self);
|
||||
static void M_Confirm(UI_PROMPT *self);
|
||||
static void M_Cancel(UI_PROMPT *self);
|
||||
static void M_Clear(UI_PROMPT *self);
|
||||
|
||||
static int32_t M_GetWidth(const UI_PROMPT *self);
|
||||
static int32_t M_GetHeight(const UI_PROMPT *self);
|
||||
static void M_SetPosition(UI_PROMPT *self, int32_t x, int32_t y);
|
||||
static void M_Control(UI_PROMPT *self);
|
||||
static void M_Draw(UI_PROMPT *self);
|
||||
static void M_Free(UI_PROMPT *self);
|
||||
static void M_HandleKeyDown(const EVENT *event, void *user_data);
|
||||
static void M_HandleTextEdit(const EVENT *event, void *user_data);
|
||||
|
||||
static void M_UpdatePromptLabel(UI_PROMPT *const self)
|
||||
{
|
||||
UI_Label_ChangeText(self->label, self->current_text);
|
||||
}
|
||||
|
||||
static void M_UpdateCaretLabel(UI_PROMPT *const self)
|
||||
{
|
||||
const char old = self->current_text[self->caret_pos];
|
||||
self->current_text[self->caret_pos] = '\0';
|
||||
UI_Label_ChangeText(self->label, self->current_text);
|
||||
const int32_t width = UI_Label_MeasureTextWidth(self->label);
|
||||
self->current_text[self->caret_pos] = old;
|
||||
UI_Label_ChangeText(self->label, self->current_text);
|
||||
|
||||
self->caret->set_position(self->caret, self->pos.x + width, self->pos.y);
|
||||
}
|
||||
|
||||
static int32_t M_GetWidth(const UI_PROMPT *const self)
|
||||
{
|
||||
if (self->vtable.is_hidden) {
|
||||
return 0;
|
||||
}
|
||||
return self->label->get_width(self->label);
|
||||
}
|
||||
|
||||
static int32_t M_GetHeight(const UI_PROMPT *const self)
|
||||
{
|
||||
if (self->vtable.is_hidden) {
|
||||
return 0;
|
||||
}
|
||||
return self->label->get_height(self->label);
|
||||
}
|
||||
|
||||
static void M_SetPosition(
|
||||
UI_PROMPT *const self, const int32_t x, const int32_t y)
|
||||
{
|
||||
self->pos.x = x;
|
||||
self->pos.y = y;
|
||||
self->label->set_position(self->label, x, y);
|
||||
M_UpdateCaretLabel(self);
|
||||
}
|
||||
|
||||
static void M_Control(UI_PROMPT *const self)
|
||||
{
|
||||
if (self->label->control != nullptr) {
|
||||
self->label->control(self->label);
|
||||
}
|
||||
if (self->caret->control != nullptr) {
|
||||
self->caret->control(self->caret);
|
||||
}
|
||||
}
|
||||
|
||||
static void M_Draw(UI_PROMPT *const self)
|
||||
{
|
||||
if (self->vtable.is_hidden) {
|
||||
return;
|
||||
}
|
||||
if (self->label->draw != nullptr) {
|
||||
self->label->draw(self->label);
|
||||
}
|
||||
if (self->caret->draw != nullptr) {
|
||||
self->caret->draw(self->caret);
|
||||
}
|
||||
}
|
||||
|
||||
static void M_Free(UI_PROMPT *const self)
|
||||
{
|
||||
self->label->free(self->label);
|
||||
self->caret->free(self->caret);
|
||||
UI_Events_Unsubscribe(self->listener1);
|
||||
UI_Events_Unsubscribe(self->listener2);
|
||||
Memory_FreePointer(&self->current_text);
|
||||
Memory_Free(self);
|
||||
}
|
||||
|
||||
static void M_MoveCaretLeft(UI_PROMPT *const self)
|
||||
{
|
||||
if (self->caret_pos > 0) {
|
||||
self->caret_pos--;
|
||||
M_UpdateCaretLabel(self);
|
||||
}
|
||||
}
|
||||
|
||||
static void M_MoveCaretRight(UI_PROMPT *const self)
|
||||
{
|
||||
if (self->caret_pos < (int32_t)strlen(self->current_text)) {
|
||||
self->caret_pos++;
|
||||
M_UpdateCaretLabel(self);
|
||||
}
|
||||
}
|
||||
|
||||
static void M_MoveCaretStart(UI_PROMPT *const self)
|
||||
{
|
||||
self->caret_pos = 0;
|
||||
M_UpdateCaretLabel(self);
|
||||
}
|
||||
|
||||
static void M_MoveCaretEnd(UI_PROMPT *const self)
|
||||
{
|
||||
self->caret_pos = strlen(self->current_text);
|
||||
M_UpdateCaretLabel(self);
|
||||
}
|
||||
|
||||
static void M_DeleteCharBack(UI_PROMPT *const self)
|
||||
{
|
||||
if (self->caret_pos <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
memmove(
|
||||
self->current_text + self->caret_pos - 1,
|
||||
self->current_text + self->caret_pos,
|
||||
strlen(self->current_text) + 1 - self->caret_pos);
|
||||
|
||||
self->caret_pos--;
|
||||
M_UpdatePromptLabel(self);
|
||||
M_UpdateCaretLabel(self);
|
||||
}
|
||||
|
||||
static void M_Confirm(UI_PROMPT *const self)
|
||||
{
|
||||
if (String_IsEmpty(self->current_text)) {
|
||||
M_Cancel(self);
|
||||
return;
|
||||
}
|
||||
const EVENT event = {
|
||||
.name = "confirm",
|
||||
.sender = self,
|
||||
.data = self->current_text,
|
||||
};
|
||||
UI_Events_Fire(&event);
|
||||
M_Clear(self);
|
||||
M_UpdateCaretLabel(self);
|
||||
}
|
||||
|
||||
static void M_Cancel(UI_PROMPT *const self)
|
||||
{
|
||||
const EVENT event = {
|
||||
.name = "cancel",
|
||||
.sender = self,
|
||||
.data = self->current_text,
|
||||
};
|
||||
UI_Events_Fire(&event);
|
||||
M_Clear(self);
|
||||
}
|
||||
|
||||
static void M_Clear(UI_PROMPT *const self)
|
||||
{
|
||||
strcpy(self->current_text, "");
|
||||
self->caret_pos = 0;
|
||||
M_UpdatePromptLabel(self);
|
||||
M_UpdateCaretLabel(self);
|
||||
}
|
||||
|
||||
static void M_HandleKeyDown(const EVENT *const event, void *const user_data)
|
||||
{
|
||||
const UI_INPUT key = (UI_INPUT)(uintptr_t)event->data;
|
||||
UI_PROMPT *const self = user_data;
|
||||
|
||||
if (!self->is_focused) {
|
||||
return;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
switch (key) {
|
||||
case UI_KEY_LEFT: M_MoveCaretLeft(self); break;
|
||||
case UI_KEY_RIGHT: M_MoveCaretRight(self); break;
|
||||
case UI_KEY_HOME: M_MoveCaretStart(self); break;
|
||||
case UI_KEY_END: M_MoveCaretEnd(self); break;
|
||||
case UI_KEY_BACK: M_DeleteCharBack(self); break;
|
||||
case UI_KEY_RETURN: M_Confirm(self); break;
|
||||
case UI_KEY_ESCAPE: M_Cancel(self); break;
|
||||
default: break;
|
||||
}
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
static void M_HandleTextEdit(const EVENT *const event, void *const user_data)
|
||||
{
|
||||
const char *insert_string = event->data;
|
||||
const size_t insert_length = strlen(insert_string);
|
||||
UI_PROMPT *const self = user_data;
|
||||
|
||||
if (!self->is_focused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(insert_string) != 1
|
||||
|| !strstr(m_ValidPromptChars, insert_string)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t available_space =
|
||||
self->current_text_capacity - strlen(self->current_text);
|
||||
if (insert_length >= available_space) {
|
||||
self->current_text_capacity *= 2;
|
||||
self->current_text =
|
||||
Memory_Realloc(self->current_text, self->current_text_capacity);
|
||||
}
|
||||
|
||||
memmove(
|
||||
self->current_text + self->caret_pos + insert_length,
|
||||
self->current_text + self->caret_pos,
|
||||
strlen(self->current_text) + 1 - self->caret_pos);
|
||||
memcpy(self->current_text + self->caret_pos, insert_string, insert_length);
|
||||
|
||||
self->caret_pos += insert_length;
|
||||
M_UpdatePromptLabel(self);
|
||||
M_UpdateCaretLabel(self);
|
||||
}
|
||||
|
||||
UI_WIDGET *UI_Prompt_Create(const int32_t width, const int32_t height)
|
||||
{
|
||||
UI_PROMPT *const self = Memory_Alloc(sizeof(UI_PROMPT));
|
||||
self->vtable = (UI_WIDGET_VTABLE) {
|
||||
.control = (UI_WIDGET_CONTROL)M_Control,
|
||||
.draw = (UI_WIDGET_DRAW)M_Draw,
|
||||
.get_width = (UI_WIDGET_GET_WIDTH)M_GetWidth,
|
||||
.get_height = (UI_WIDGET_GET_HEIGHT)M_GetHeight,
|
||||
.set_position = (UI_WIDGET_SET_POSITION)M_SetPosition,
|
||||
.free = (UI_WIDGET_FREE)M_Free,
|
||||
};
|
||||
|
||||
self->current_text_capacity = 1;
|
||||
self->current_text = Memory_Alloc(self->current_text_capacity);
|
||||
self->label = UI_Label_Create(self->current_text, width, height);
|
||||
self->caret = UI_Label_Create("", width, height);
|
||||
UI_Label_SetZIndex(self->label, 16);
|
||||
UI_Label_SetZIndex(self->caret, 8);
|
||||
self->is_focused = false;
|
||||
|
||||
self->listener1 =
|
||||
UI_Events_Subscribe("key_down", nullptr, M_HandleKeyDown, self);
|
||||
self->listener2 =
|
||||
UI_Events_Subscribe("text_edit", nullptr, M_HandleTextEdit, self);
|
||||
|
||||
return (UI_WIDGET *)self;
|
||||
}
|
||||
|
||||
void UI_Prompt_SetSize(
|
||||
UI_WIDGET *const widget, const int32_t width, const int32_t height)
|
||||
{
|
||||
UI_PROMPT *const self = (UI_PROMPT *)widget;
|
||||
UI_Label_SetSize(self->label, width, height);
|
||||
}
|
||||
|
||||
void UI_Prompt_SetFocus(UI_WIDGET *const widget, const bool is_focused)
|
||||
{
|
||||
UI_PROMPT *const self = (UI_PROMPT *)widget;
|
||||
self->is_focused = is_focused;
|
||||
if (is_focused) {
|
||||
Input_EnterListenMode();
|
||||
UI_Label_ChangeText(self->caret, "\\{button left}");
|
||||
UI_Label_Flash(self->caret, 1, LOGIC_FPS * 2 / 3);
|
||||
} else {
|
||||
Input_ExitListenMode();
|
||||
UI_Label_ChangeText(self->caret, "");
|
||||
}
|
||||
}
|
||||
|
||||
void UI_Prompt_Clear(UI_WIDGET *const widget)
|
||||
{
|
||||
UI_PROMPT *const self = (UI_PROMPT *)widget;
|
||||
M_Clear(self);
|
||||
}
|
||||
|
||||
void UI_Prompt_ChangeText(UI_WIDGET *widget, const char *new_text)
|
||||
{
|
||||
UI_PROMPT *const self = (UI_PROMPT *)widget;
|
||||
Memory_FreePointer(&self->current_text);
|
||||
self->current_text = Memory_DupStr(new_text);
|
||||
self->caret_pos = strlen(new_text);
|
||||
M_UpdateCaretLabel(self);
|
||||
}
|
202
src/libtrx/game/ui2/common.c
Normal file
202
src/libtrx/game/ui2/common.c
Normal file
|
@ -0,0 +1,202 @@
|
|||
#include "game/ui2/common.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "game/console/common.h"
|
||||
#include "game/game_string.h"
|
||||
#include "game/ui2/events.h"
|
||||
#include "game/ui2/widgets/stack.h"
|
||||
#include "memory.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static struct {
|
||||
MEMORY_ARENA_ALLOCATOR alloc;
|
||||
|
||||
int32_t node_count;
|
||||
int32_t node_capacity;
|
||||
UI2_NODE *nodes;
|
||||
|
||||
UI2_NODE *root; // The top-level container
|
||||
UI2_NODE *current; // The current container into which we attach nodes
|
||||
} m_Priv = {
|
||||
.alloc = {
|
||||
.default_chunk_size = 1024 * 4,
|
||||
},
|
||||
};
|
||||
|
||||
static UI2_INPUT M_TranslateInput(uint32_t system_keycode);
|
||||
static void M_MeasureNode(UI2_NODE *node);
|
||||
static void M_LayoutNode(UI2_NODE *node, float x, float y, float w, float h);
|
||||
static void M_DrawNode(const UI2_NODE *node);
|
||||
|
||||
static UI2_INPUT M_TranslateInput(uint32_t system_keycode)
|
||||
{
|
||||
return (UI2_INPUT)UI_TranslateInput(system_keycode);
|
||||
}
|
||||
|
||||
// Depth-first measure pass
|
||||
static void M_MeasureNode(UI2_NODE *const node)
|
||||
{
|
||||
if (node == nullptr || node->ops == nullptr
|
||||
|| node->ops->measure == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Recurse to children
|
||||
UI2_NODE *child = node->first_child;
|
||||
while (child != nullptr) {
|
||||
M_MeasureNode(child);
|
||||
child = child->next_sibling;
|
||||
}
|
||||
|
||||
node->ops->measure(node);
|
||||
}
|
||||
|
||||
// Depth-first layout pass
|
||||
static void M_LayoutNode(
|
||||
UI2_NODE *const node, const float x, const float y, const float w,
|
||||
const float h)
|
||||
{
|
||||
if (node == nullptr || node->ops == nullptr
|
||||
|| node->ops->layout == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
node->ops->layout(node, x, y, w, h);
|
||||
// Recursing to children is a responsibility of the layout function.
|
||||
}
|
||||
|
||||
// Depth-first draw pass
|
||||
static void M_DrawNode(const UI2_NODE *const node)
|
||||
{
|
||||
if (node == nullptr || node->ops == nullptr || node->ops->draw == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw self
|
||||
const bool draw_children = node->ops->draw(node);
|
||||
if (!draw_children) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw children
|
||||
const UI2_NODE *child = node->first_child;
|
||||
while (child != nullptr) {
|
||||
M_DrawNode(child);
|
||||
child = child->next_sibling;
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate a new node
|
||||
UI2_NODE *UI2_AllocNode(
|
||||
const UI2_WIDGET_OPS *const ops, const size_t additional_size)
|
||||
{
|
||||
const size_t size = sizeof(UI2_NODE) + additional_size;
|
||||
UI2_NODE *const node = Memory_ArenaAlloc(&m_Priv.alloc, size);
|
||||
memset(node, 0, size);
|
||||
node->ops = ops;
|
||||
node->data = (char *)node + sizeof(UI2_NODE);
|
||||
m_Priv.node_count++;
|
||||
return node;
|
||||
}
|
||||
|
||||
// Attach child to parent's child list
|
||||
void UI2_AddChild(UI2_NODE *const child)
|
||||
{
|
||||
UI2_NODE *const parent = m_Priv.current;
|
||||
if (parent == nullptr || child == nullptr) {
|
||||
return;
|
||||
}
|
||||
child->parent = parent;
|
||||
if (parent->first_child == nullptr) {
|
||||
parent->first_child = child;
|
||||
parent->last_child = child;
|
||||
} else {
|
||||
parent->last_child->next_sibling = child;
|
||||
parent->last_child = child;
|
||||
}
|
||||
}
|
||||
|
||||
void UI2_PushCurrent(UI2_NODE *const child)
|
||||
{
|
||||
m_Priv.current = child;
|
||||
}
|
||||
|
||||
void UI2_PopCurrent(void)
|
||||
{
|
||||
if (m_Priv.current != nullptr && m_Priv.current->parent != nullptr) {
|
||||
m_Priv.current = m_Priv.current->parent;
|
||||
}
|
||||
}
|
||||
|
||||
// Scene management
|
||||
void UI2_BeginScene(void)
|
||||
{
|
||||
Memory_ArenaReset(&m_Priv.alloc);
|
||||
m_Priv.node_count = 0;
|
||||
|
||||
// Make a root node. We'll treat it like a vertical stack by default
|
||||
m_Priv.root = UI2_CreateStack((UI2_STACK_SETTINGS) {
|
||||
.orientation = UI2_STACK_VERTICAL,
|
||||
.align = {
|
||||
.h = UI2_STACK_H_ALIGN_CENTER,
|
||||
.v = UI2_STACK_V_ALIGN_CENTER,
|
||||
},
|
||||
});
|
||||
m_Priv.current = m_Priv.root;
|
||||
}
|
||||
|
||||
void UI2_EndScene(void)
|
||||
{
|
||||
if (m_Priv.root == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
M_MeasureNode(m_Priv.root);
|
||||
M_LayoutNode(
|
||||
m_Priv.root, 0, 0, UI2_GetCanvasWidth(), UI2_GetCanvasHeight());
|
||||
M_DrawNode(m_Priv.root);
|
||||
}
|
||||
|
||||
void UI2_Init(void)
|
||||
{
|
||||
UI2_InitEvents();
|
||||
}
|
||||
|
||||
void UI2_Shutdown(void)
|
||||
{
|
||||
Memory_ArenaFree(&m_Priv.alloc);
|
||||
UI2_ShutdownEvents();
|
||||
}
|
||||
|
||||
void UI2_ToggleState(bool *const config_setting)
|
||||
{
|
||||
// NOTE: handled in the legacy UI module
|
||||
// *config_setting ^= true;
|
||||
// Config_Write();
|
||||
// Console_Log(*config_setting ? GS(OSD_UI_ON) : GS(OSD_UI_OFF));
|
||||
}
|
||||
|
||||
void UI2_HandleKeyDown(const uint32_t key)
|
||||
{
|
||||
UI2_FireEvent((EVENT) {
|
||||
.name = "key_down",
|
||||
.sender = nullptr,
|
||||
.data = (void *)M_TranslateInput(key),
|
||||
});
|
||||
}
|
||||
|
||||
void UI2_HandleKeyUp(const uint32_t key)
|
||||
{
|
||||
UI2_FireEvent((EVENT) {
|
||||
.name = "key_up",
|
||||
.sender = nullptr,
|
||||
.data = (void *)M_TranslateInput(key),
|
||||
});
|
||||
}
|
||||
|
||||
void UI2_HandleTextEdit(const char *const text)
|
||||
{
|
||||
UI2_FireEvent((EVENT) {
|
||||
.name = "text_edit", .sender = nullptr, .data = (void *)text });
|
||||
}
|
41
src/libtrx/game/ui2/events.c
Normal file
41
src/libtrx/game/ui2/events.c
Normal file
|
@ -0,0 +1,41 @@
|
|||
#include "game/ui2/events.h"
|
||||
|
||||
#include "config/common.h"
|
||||
#include "debug.h"
|
||||
#include "game/ui2/common.h"
|
||||
|
||||
static EVENT_MANAGER *m_EventManager = nullptr;
|
||||
|
||||
void UI2_InitEvents(void)
|
||||
{
|
||||
m_EventManager = EventManager_Create();
|
||||
}
|
||||
|
||||
void UI2_ShutdownEvents(void)
|
||||
{
|
||||
EventManager_Free(m_EventManager);
|
||||
m_EventManager = nullptr;
|
||||
}
|
||||
|
||||
int32_t UI2_Subscribe(
|
||||
const char *const event_name, const void *const sender,
|
||||
const EVENT_LISTENER listener, void *const user_data)
|
||||
{
|
||||
ASSERT(m_EventManager != nullptr);
|
||||
return EventManager_Subscribe(
|
||||
m_EventManager, event_name, sender, listener, user_data);
|
||||
}
|
||||
|
||||
void UI2_Unsubscribe(const int32_t listener_id)
|
||||
{
|
||||
if (m_EventManager != nullptr) {
|
||||
EventManager_Unsubscribe(m_EventManager, listener_id);
|
||||
}
|
||||
}
|
||||
|
||||
void UI2_FireEvent(const EVENT event)
|
||||
{
|
||||
if (m_EventManager != nullptr) {
|
||||
EventManager_Fire(m_EventManager, &event);
|
||||
}
|
||||
}
|
42
src/libtrx/game/ui2/helpers.c
Normal file
42
src/libtrx/game/ui2/helpers.c
Normal file
|
@ -0,0 +1,42 @@
|
|||
#include "game/ui2/helpers.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
void UI2_MeasureWrapper(UI2_NODE *const node)
|
||||
{
|
||||
node->measure_w = 0;
|
||||
node->measure_h = 0;
|
||||
const UI2_NODE *child = node->first_child;
|
||||
while (child != nullptr) {
|
||||
node->measure_w = MAX(node->measure_w, child->measure_w);
|
||||
node->measure_h = MAX(node->measure_h, child->measure_h);
|
||||
child = child->next_sibling;
|
||||
}
|
||||
}
|
||||
|
||||
void UI2_LayoutBasic(
|
||||
UI2_NODE *const node, const float x, const float y, const float w,
|
||||
const float h)
|
||||
{
|
||||
node->x = x;
|
||||
node->y = y;
|
||||
node->w = w;
|
||||
node->h = h;
|
||||
}
|
||||
|
||||
void UI2_LayoutWrapper(
|
||||
UI2_NODE *const node, const float x, const float y, const float w,
|
||||
const float h)
|
||||
{
|
||||
UI2_LayoutBasic(node, x, y, w, h);
|
||||
UI2_NODE *child = node->first_child;
|
||||
while (child != nullptr) {
|
||||
child->ops->layout(child, x, y, w, h);
|
||||
child = child->next_sibling;
|
||||
}
|
||||
}
|
||||
|
||||
bool UI2_DrawWrapper(const UI2_NODE *const node)
|
||||
{
|
||||
return true;
|
||||
}
|
9
src/libtrx/game/ui2/helpers.h
Normal file
9
src/libtrx/game/ui2/helpers.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "game/ui2/common.h"
|
||||
|
||||
// Repetitive widget ops strategies
|
||||
void UI2_MeasureWrapper(UI2_NODE *node);
|
||||
void UI2_LayoutBasic(UI2_NODE *node, float x, float y, float w, float h);
|
||||
void UI2_LayoutWrapper(UI2_NODE *node, float x, float y, float w, float h);
|
||||
bool UI2_DrawWrapper(const UI2_NODE *node);
|
56
src/libtrx/game/ui2/widgets/anchor.c
Normal file
56
src/libtrx/game/ui2/widgets/anchor.c
Normal file
|
@ -0,0 +1,56 @@
|
|||
#include "game/ui2/widgets/anchor.h"
|
||||
|
||||
#include "game/ui2/common.h"
|
||||
#include "game/ui2/helpers.h"
|
||||
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
} M_DATA;
|
||||
|
||||
static void M_Measure(UI2_NODE *node);
|
||||
static void M_Layout(UI2_NODE *node, float x, float y, float w, float h);
|
||||
|
||||
static const UI2_WIDGET_OPS m_Ops = {
|
||||
.measure = M_Measure,
|
||||
.layout = M_Layout,
|
||||
.draw = UI2_DrawWrapper,
|
||||
};
|
||||
|
||||
static void M_Measure(UI2_NODE *const node)
|
||||
{
|
||||
node->measure_w = UI2_GetCanvasWidth();
|
||||
node->measure_h = UI2_GetCanvasHeight();
|
||||
}
|
||||
|
||||
static void M_Layout(
|
||||
UI2_NODE *const node, const float x, const float y, const float w,
|
||||
const float h)
|
||||
{
|
||||
UI2_LayoutBasic(node, x, y, w, h);
|
||||
const M_DATA *const data = node->data;
|
||||
UI2_NODE *child = node->first_child;
|
||||
while (child != nullptr) {
|
||||
const float cw = child->measure_w;
|
||||
const float ch = child->measure_h;
|
||||
const float cx = x + (w - cw) * data->x;
|
||||
const float cy = y + (h - ch) * data->y;
|
||||
child->ops->layout(child, cx, cy, cw, ch);
|
||||
child = child->next_sibling;
|
||||
}
|
||||
}
|
||||
|
||||
void UI2_BeginAnchor(const float x, const float y)
|
||||
{
|
||||
UI2_NODE *const node = UI2_AllocNode(&m_Ops, sizeof(M_DATA));
|
||||
M_DATA *const data = node->data;
|
||||
data->x = x;
|
||||
data->y = y;
|
||||
UI2_AddChild(node);
|
||||
UI2_PushCurrent(node);
|
||||
}
|
||||
|
||||
void UI2_EndAnchor(void)
|
||||
{
|
||||
UI2_PopCurrent();
|
||||
}
|
166
src/libtrx/game/ui2/widgets/console.c
Normal file
166
src/libtrx/game/ui2/widgets/console.c
Normal file
|
@ -0,0 +1,166 @@
|
|||
#include "game/ui2/widgets/console.h"
|
||||
|
||||
#include "game/console.h"
|
||||
#include "game/text.h"
|
||||
#include "game/ui2/events.h"
|
||||
#include "game/ui2/helpers.h"
|
||||
#include "game/ui2/widgets/anchor.h"
|
||||
#include "game/ui2/widgets/console_logs.h"
|
||||
#include "game/ui2/widgets/pad.h"
|
||||
#include "game/ui2/widgets/prompt.h"
|
||||
#include "game/ui2/widgets/spacer.h"
|
||||
#include "game/ui2/widgets/stack.h"
|
||||
#include "utils.h"
|
||||
|
||||
static bool M_Draw(const UI2_NODE *node);
|
||||
|
||||
static const UI2_WIDGET_OPS m_Ops = {
|
||||
.measure = UI2_MeasureWrapper,
|
||||
.layout = UI2_LayoutWrapper,
|
||||
.draw = M_Draw,
|
||||
};
|
||||
|
||||
static void M_MoveHistoryUp(UI2_CONSOLE_STATE *state);
|
||||
static void M_MoveHistoryDown(UI2_CONSOLE_STATE *state);
|
||||
static void M_HandleOpen(const EVENT *event, void *user_data);
|
||||
static void M_HandleClose(const EVENT *event, void *user_data);
|
||||
static void M_HandleCancel(const EVENT *event, void *user_data);
|
||||
static void M_HandleConfirm(const EVENT *event, void *user_data);
|
||||
|
||||
static void M_MoveHistoryUp(UI2_CONSOLE_STATE *const state)
|
||||
{
|
||||
state->history_idx--;
|
||||
CLAMP(state->history_idx, 0, Console_History_GetLength());
|
||||
const char *const new_prompt = Console_History_Get(state->history_idx);
|
||||
UI2_Prompt_ChangeText(
|
||||
&state->prompt, new_prompt == nullptr ? "" : new_prompt);
|
||||
}
|
||||
|
||||
static void M_MoveHistoryDown(UI2_CONSOLE_STATE *const state)
|
||||
{
|
||||
state->history_idx++;
|
||||
CLAMP(state->history_idx, 0, Console_History_GetLength());
|
||||
const char *const new_prompt = Console_History_Get(state->history_idx);
|
||||
UI2_Prompt_ChangeText(
|
||||
&state->prompt, new_prompt == nullptr ? "" : new_prompt);
|
||||
}
|
||||
|
||||
static void M_HandleKeyDown(const EVENT *const event, void *const user_data)
|
||||
{
|
||||
if (!Console_IsOpened()) {
|
||||
return;
|
||||
}
|
||||
|
||||
UI2_CONSOLE_STATE *const state = user_data;
|
||||
const UI2_INPUT key = (UI2_INPUT)(uintptr_t)event->data;
|
||||
|
||||
// clang-format off
|
||||
switch (key) {
|
||||
case UI_KEY_UP: M_MoveHistoryUp(state); break;
|
||||
case UI_KEY_DOWN: M_MoveHistoryDown(state); break;
|
||||
default: break;
|
||||
}
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
static void M_HandleOpen(const EVENT *event, void *user_data)
|
||||
{
|
||||
UI2_CONSOLE_STATE *const state = user_data;
|
||||
UI2_Prompt_SetFocus(&state->prompt, true);
|
||||
state->history_idx = Console_History_GetLength();
|
||||
}
|
||||
|
||||
static void M_HandleClose(const EVENT *event, void *user_data)
|
||||
{
|
||||
UI2_CONSOLE_STATE *const state = user_data;
|
||||
UI2_Prompt_SetFocus(&state->prompt, false);
|
||||
UI2_Prompt_Clear(&state->prompt);
|
||||
}
|
||||
|
||||
static void M_HandleCancel(const EVENT *const event, void *const data)
|
||||
{
|
||||
Console_Close();
|
||||
}
|
||||
|
||||
static void M_HandleConfirm(const EVENT *event, void *user_data)
|
||||
{
|
||||
UI2_CONSOLE_STATE *const state = user_data;
|
||||
const char *text = event->data;
|
||||
Console_History_Append(text);
|
||||
Console_Eval(text);
|
||||
Console_Close();
|
||||
state->history_idx = Console_History_GetLength();
|
||||
}
|
||||
|
||||
static bool M_Draw(const UI2_NODE *node)
|
||||
{
|
||||
UI2_CONSOLE_STATE *const state = *(UI2_CONSOLE_STATE **)node->data;
|
||||
if (Console_IsOpened() || state->logs.vis_lines > 0) {
|
||||
Console_DrawBackdrop();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void UI2_Console_Init(UI2_CONSOLE_STATE *const state)
|
||||
{
|
||||
UI2_Prompt_Init(&state->prompt);
|
||||
UI2_ConsoleLogs_Init(&state->logs);
|
||||
|
||||
struct {
|
||||
const char *event_name;
|
||||
const void *sender;
|
||||
EVENT_LISTENER handler;
|
||||
} listeners[] = {
|
||||
{ "console_open", nullptr, M_HandleOpen },
|
||||
{ "console_close", nullptr, M_HandleClose },
|
||||
{ "cancel", &state->prompt, M_HandleCancel },
|
||||
{ "confirm", &state->prompt, M_HandleConfirm },
|
||||
{ "key_down", nullptr, M_HandleKeyDown },
|
||||
{ 0 },
|
||||
};
|
||||
for (int32_t i = 0; listeners[i].event_name != nullptr; i++) {
|
||||
state->listeners[i] = UI2_Subscribe(
|
||||
listeners[i].event_name, listeners[i].sender, listeners[i].handler,
|
||||
state);
|
||||
}
|
||||
|
||||
state->history_idx = -1;
|
||||
}
|
||||
|
||||
void UI2_Console_Free(UI2_CONSOLE_STATE *const state)
|
||||
{
|
||||
UI2_ConsoleLogs_Free(&state->logs);
|
||||
UI2_Prompt_Free(&state->prompt);
|
||||
|
||||
for (int32_t i = 0; i < 5; i++) {
|
||||
UI2_Unsubscribe(state->listeners[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void UI2_Console(UI2_CONSOLE_STATE *const state)
|
||||
{
|
||||
UI2_Prompt_SetFocus(&state->prompt, Console_IsOpened());
|
||||
|
||||
UI2_NODE *const node = UI2_AllocNode(&m_Ops, sizeof(UI2_CONSOLE_STATE *));
|
||||
*(UI2_CONSOLE_STATE **)node->data = state;
|
||||
UI2_AddChild(node);
|
||||
UI2_PushCurrent(node);
|
||||
|
||||
UI2_BeginAnchor(0.0f, 1.0f);
|
||||
UI2_BeginPad(5.0f, 5.0f);
|
||||
UI2_BeginStack(UI2_STACK_VERTICAL);
|
||||
|
||||
UI2_ConsoleLogs(&state->logs);
|
||||
UI2_Spacer(0.0f, 4.0f);
|
||||
if (Console_IsOpened()) {
|
||||
UI2_Prompt(&state->prompt);
|
||||
} else {
|
||||
UI2_Spacer(0.0f, TEXT_HEIGHT_FIXED);
|
||||
}
|
||||
|
||||
UI2_EndStack();
|
||||
UI2_EndAnchor();
|
||||
UI2_EndPad();
|
||||
|
||||
UI2_PopCurrent();
|
||||
}
|
101
src/libtrx/game/ui2/widgets/console_logs.c
Normal file
101
src/libtrx/game/ui2/widgets/console_logs.c
Normal file
|
@ -0,0 +1,101 @@
|
|||
#include "game/ui2/widgets/console_logs.h"
|
||||
|
||||
#include "debug.h"
|
||||
#include "game/clock.h"
|
||||
#include "game/text.h"
|
||||
#include "game/ui2/events.h"
|
||||
#include "game/ui2/widgets/label.h"
|
||||
#include "game/ui2/widgets/stack.h"
|
||||
#include "memory.h"
|
||||
#include "strings.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define M_LOG_SCALE 0.8f
|
||||
#define M_MAX_LOG_LINES 20
|
||||
#define M_DELAY_PER_CHAR 0.2
|
||||
|
||||
static void M_ScrollLogs(UI2_CONSOLE_LOGS *state);
|
||||
static void M_UpdateLogCount(UI2_CONSOLE_LOGS *state);
|
||||
static void M_HandleLog(const EVENT *event, void *user_data);
|
||||
|
||||
static void M_ScrollLogs(UI2_CONSOLE_LOGS *state)
|
||||
{
|
||||
int32_t i = state->max_lines - 1;
|
||||
while (i >= 0 && !state->logs[i].expire_at) {
|
||||
i--;
|
||||
}
|
||||
|
||||
bool need_layout = false;
|
||||
while (i >= 0 && state->logs[i].expire_at
|
||||
&& Clock_GetRealTime() >= state->logs[i].expire_at) {
|
||||
state->logs[i].expire_at = 0.0;
|
||||
Memory_FreePointer(&state->logs[i].text);
|
||||
need_layout = true;
|
||||
i--;
|
||||
}
|
||||
|
||||
if (need_layout) {
|
||||
M_UpdateLogCount(state);
|
||||
}
|
||||
}
|
||||
static void M_UpdateLogCount(UI2_CONSOLE_LOGS *const state)
|
||||
{
|
||||
state->vis_lines = 0;
|
||||
for (int32_t i = state->max_lines - 1; i >= 0; i--) {
|
||||
if (state->logs[i].expire_at != 0.0) {
|
||||
state->vis_lines = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void M_HandleLog(const EVENT *const event, void *const user_data)
|
||||
{
|
||||
const char *text = event->data;
|
||||
UI2_CONSOLE_LOGS *const state = user_data;
|
||||
Memory_FreePointer(&state->logs[state->max_lines - 1].text);
|
||||
for (int32_t i = state->max_lines - 1; i > 0; i--) {
|
||||
state->logs[i] = state->logs[i - 1];
|
||||
}
|
||||
|
||||
state->logs[0].expire_at =
|
||||
Clock_GetRealTime() + strlen(text) * M_DELAY_PER_CHAR;
|
||||
state->logs[0].text = String_WordWrap(text, Text_GetMaxLineLength());
|
||||
M_UpdateLogCount(state);
|
||||
}
|
||||
|
||||
void UI2_ConsoleLogs_Init(UI2_CONSOLE_LOGS *state)
|
||||
{
|
||||
if (state->max_lines <= 0) {
|
||||
state->max_lines = M_MAX_LOG_LINES;
|
||||
}
|
||||
state->logs = Memory_Alloc(state->max_lines * sizeof(UI2_CONSOLE_LOG_LINE));
|
||||
state->vis_lines = 0;
|
||||
state->listener_id =
|
||||
UI2_Subscribe("console_log", nullptr, M_HandleLog, state);
|
||||
}
|
||||
|
||||
void UI2_ConsoleLogs_Free(UI2_CONSOLE_LOGS *state)
|
||||
{
|
||||
Memory_FreePointer(&state->logs);
|
||||
UI2_Unsubscribe(state->listener_id);
|
||||
}
|
||||
|
||||
void UI2_ConsoleLogs(UI2_CONSOLE_LOGS *const state)
|
||||
{
|
||||
ASSERT(state != nullptr);
|
||||
M_ScrollLogs(state);
|
||||
UI2_BeginStackEx((UI2_STACK_SETTINGS) {
|
||||
.orientation = UI2_STACK_VERTICAL,
|
||||
.align = {
|
||||
.h = UI2_STACK_H_ALIGN_LEFT,
|
||||
.v = UI2_STACK_V_ALIGN_CENTER,
|
||||
},
|
||||
});
|
||||
for (int32_t i = state->vis_lines - 1; i >= 0; i--) {
|
||||
UI2_LabelEx(
|
||||
state->logs[i].text, (UI2_LABEL_SETTINGS) { .scale = M_LOG_SCALE });
|
||||
}
|
||||
UI2_EndStack();
|
||||
}
|
37
src/libtrx/game/ui2/widgets/flash.c
Normal file
37
src/libtrx/game/ui2/widgets/flash.c
Normal file
|
@ -0,0 +1,37 @@
|
|||
#include "game/ui2/widgets/flash.h"
|
||||
|
||||
#include "game/clock.h"
|
||||
#include "game/ui2/helpers.h"
|
||||
|
||||
static bool M_Draw(const UI2_NODE *node);
|
||||
|
||||
static const UI2_WIDGET_OPS m_Ops = {
|
||||
.measure = UI2_MeasureWrapper,
|
||||
.layout = UI2_LayoutWrapper,
|
||||
.draw = M_Draw,
|
||||
};
|
||||
|
||||
static bool M_Draw(const UI2_NODE *const node)
|
||||
{
|
||||
UI2_FLASH_STATE *const state = *(UI2_FLASH_STATE **)node->data;
|
||||
const bool draw_children = state->count < 0;
|
||||
state->count -= Clock_GetFrameAdvance();
|
||||
if (state->count < -state->rate) {
|
||||
state->count = state->rate;
|
||||
}
|
||||
return draw_children;
|
||||
}
|
||||
|
||||
void UI2_BeginFlash(UI2_FLASH_STATE *const state)
|
||||
{
|
||||
UI2_NODE *const node = UI2_AllocNode(&m_Ops, sizeof(UI2_FLASH_STATE *));
|
||||
UI2_FLASH_STATE **const data = node->data;
|
||||
*data = state;
|
||||
UI2_AddChild(node);
|
||||
UI2_PushCurrent(node);
|
||||
}
|
||||
|
||||
void UI2_EndFlash(void)
|
||||
{
|
||||
UI2_PopCurrent();
|
||||
}
|
96
src/libtrx/game/ui2/widgets/label.c
Normal file
96
src/libtrx/game/ui2/widgets/label.c
Normal file
|
@ -0,0 +1,96 @@
|
|||
#include "game/ui2/widgets/label.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "game/text.h"
|
||||
#include "game/ui2/helpers.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
UI2_LABEL_SETTINGS settings;
|
||||
char *text;
|
||||
} M_DATA;
|
||||
|
||||
static void M_Measure(UI2_NODE *node);
|
||||
static bool M_Draw(const UI2_NODE *node);
|
||||
|
||||
static UI2_LABEL_SETTINGS m_DefaultSettings = { .scale = 1.0f };
|
||||
static const UI2_WIDGET_OPS m_Ops = {
|
||||
.measure = M_Measure,
|
||||
.layout = UI2_LayoutBasic,
|
||||
.draw = M_Draw,
|
||||
};
|
||||
|
||||
static TEXTSTRING *M_CreateText(
|
||||
const float x, const float y, const char *text,
|
||||
const UI2_LABEL_SETTINGS settings);
|
||||
|
||||
static TEXTSTRING *M_CreateText(
|
||||
const float x, const float y, const char *text,
|
||||
const UI2_LABEL_SETTINGS settings)
|
||||
{
|
||||
TEXTSTRING *const textstring = Text_Create(x, y, text);
|
||||
Text_SetPos(
|
||||
textstring, x / g_Config.ui.text_scale, y / g_Config.ui.text_scale);
|
||||
Text_SetMultiline(textstring, true);
|
||||
Text_SetScale(
|
||||
textstring, settings.scale * TEXT_BASE_SCALE,
|
||||
settings.scale * TEXT_BASE_SCALE);
|
||||
return textstring;
|
||||
}
|
||||
|
||||
static void M_Measure(UI2_NODE *const node)
|
||||
{
|
||||
M_DATA *const data = node->data;
|
||||
UI2_Label_MeasureEx(
|
||||
data->text, &node->measure_w, &node->measure_h, data->settings);
|
||||
}
|
||||
|
||||
static bool M_Draw(const UI2_NODE *const node)
|
||||
{
|
||||
M_DATA *const data = node->data;
|
||||
TEXTSTRING *const textstring =
|
||||
M_CreateText(node->x, node->y, data->text, data->settings);
|
||||
if (data->settings.z != 0) {
|
||||
textstring->pos.z = data->settings.z;
|
||||
}
|
||||
Text_DrawText(textstring);
|
||||
Text_Remove(textstring);
|
||||
return true;
|
||||
}
|
||||
|
||||
void UI2_Label(const char *const text)
|
||||
{
|
||||
UI2_LabelEx(text, m_DefaultSettings);
|
||||
}
|
||||
|
||||
void UI2_LabelEx(const char *const text, const UI2_LABEL_SETTINGS settings)
|
||||
{
|
||||
UI2_NODE *const node =
|
||||
UI2_AllocNode(&m_Ops, sizeof(M_DATA) + strlen(text) + 1);
|
||||
M_DATA *const data = node->data;
|
||||
data->settings = settings;
|
||||
data->text = (char *)node->data + sizeof(M_DATA);
|
||||
strcpy(data->text, text);
|
||||
UI2_AddChild(node);
|
||||
}
|
||||
|
||||
void UI2_Label_Measure(
|
||||
const char *const text, float *const out_w, float *const out_h)
|
||||
{
|
||||
UI2_Label_MeasureEx(text, out_w, out_h, m_DefaultSettings);
|
||||
}
|
||||
|
||||
void UI2_Label_MeasureEx(
|
||||
const char *const text, float *const out_w, float *const out_h,
|
||||
const UI2_LABEL_SETTINGS settings)
|
||||
{
|
||||
TEXTSTRING *const textstring = M_CreateText(0, 0, text, settings);
|
||||
if (out_w != nullptr) {
|
||||
*out_w = Text_GetWidth(textstring) * g_Config.ui.text_scale;
|
||||
}
|
||||
if (out_h != nullptr) {
|
||||
*out_h = Text_GetHeight(textstring) * g_Config.ui.text_scale;
|
||||
}
|
||||
Text_Remove(textstring);
|
||||
}
|
56
src/libtrx/game/ui2/widgets/pad.c
Normal file
56
src/libtrx/game/ui2/widgets/pad.c
Normal file
|
@ -0,0 +1,56 @@
|
|||
#include "game/ui2/widgets/pad.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "game/ui2/helpers.h"
|
||||
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
} M_DATA;
|
||||
|
||||
static void M_Measure(UI2_NODE *node);
|
||||
static void M_Layout(UI2_NODE *node, float x, float y, float w, float h);
|
||||
|
||||
static const UI2_WIDGET_OPS m_Ops = {
|
||||
.measure = M_Measure,
|
||||
.layout = M_Layout,
|
||||
.draw = UI2_DrawWrapper,
|
||||
};
|
||||
|
||||
static void M_Measure(UI2_NODE *const node)
|
||||
{
|
||||
UI2_MeasureWrapper(node);
|
||||
const M_DATA *const data = node->data;
|
||||
node->measure_w += data->x;
|
||||
node->measure_h += data->y;
|
||||
}
|
||||
|
||||
static void M_Layout(
|
||||
UI2_NODE *const node, const float x, const float y, const float w,
|
||||
const float h)
|
||||
{
|
||||
UI2_LayoutBasic(node, x, y, w, h);
|
||||
const M_DATA *const data = node->data;
|
||||
const float px = data->x;
|
||||
const float py = data->y;
|
||||
UI2_NODE *child = node->first_child;
|
||||
while (child != nullptr) {
|
||||
child->ops->layout(child, x + px, y + py, w - px * 2, h - py * 2);
|
||||
child = child->next_sibling;
|
||||
}
|
||||
}
|
||||
|
||||
void UI2_BeginPad(const float x, const float y)
|
||||
{
|
||||
UI2_NODE *const node = UI2_AllocNode(&m_Ops, sizeof(M_DATA));
|
||||
M_DATA *const data = node->data;
|
||||
data->x = x * g_Config.ui.text_scale;
|
||||
data->y = y * g_Config.ui.text_scale;
|
||||
UI2_AddChild(node);
|
||||
UI2_PushCurrent(node);
|
||||
}
|
||||
|
||||
void UI2_EndPad(void)
|
||||
{
|
||||
UI2_PopCurrent();
|
||||
}
|
252
src/libtrx/game/ui2/widgets/prompt.c
Normal file
252
src/libtrx/game/ui2/widgets/prompt.c
Normal file
|
@ -0,0 +1,252 @@
|
|||
#include "game/ui2/widgets/prompt.h"
|
||||
|
||||
#include "game/const.h"
|
||||
#include "game/input.h"
|
||||
#include "game/ui2/common.h"
|
||||
#include "game/ui2/events.h"
|
||||
#include "game/ui2/helpers.h"
|
||||
#include "game/ui2/widgets/flash.h"
|
||||
#include "game/ui2/widgets/label.h"
|
||||
#include "log.h"
|
||||
#include "memory.h"
|
||||
#include "strings.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
UI2_PROMPT_STATE *state;
|
||||
} M_DATA;
|
||||
|
||||
static const char m_ValidPromptChars[] =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-: ";
|
||||
|
||||
static void M_Layout(UI2_NODE *node, float x, float y, float w, float h);
|
||||
|
||||
static const UI2_WIDGET_OPS m_Ops = {
|
||||
.measure = UI2_MeasureWrapper,
|
||||
.layout = M_Layout,
|
||||
.draw = UI2_DrawWrapper,
|
||||
};
|
||||
|
||||
static void M_MoveCaretLeft(UI2_PROMPT_STATE *state);
|
||||
static void M_MoveCaretRight(UI2_PROMPT_STATE *state);
|
||||
static void M_MoveCaretStart(UI2_PROMPT_STATE *state);
|
||||
static void M_MoveCaretEnd(UI2_PROMPT_STATE *state);
|
||||
static void M_DeleteCharBack(UI2_PROMPT_STATE *state);
|
||||
static void M_Confirm(UI2_PROMPT_STATE *state);
|
||||
static void M_Cancel(UI2_PROMPT_STATE *state);
|
||||
static void M_Clear(UI2_PROMPT_STATE *state);
|
||||
|
||||
static void M_HandleKeyDown(const EVENT *event, void *user_data);
|
||||
static void M_HandleTextEdit(const EVENT *event, void *user_data);
|
||||
|
||||
static void M_Layout(
|
||||
UI2_NODE *const node, const float x, const float y, const float w,
|
||||
const float h)
|
||||
{
|
||||
UI2_LayoutBasic(node, x, y, w, h);
|
||||
const M_DATA *const data = node->data;
|
||||
UI2_NODE *const prompt = node->first_child;
|
||||
UI2_NODE *const caret = prompt->next_sibling;
|
||||
prompt->ops->layout(prompt, x, y, w, h);
|
||||
|
||||
const char old = data->state->current_text[data->state->caret_pos];
|
||||
data->state->current_text[data->state->caret_pos] = '\0';
|
||||
float caret_pos;
|
||||
UI2_Label_Measure(data->state->current_text, &caret_pos, nullptr);
|
||||
data->state->current_text[data->state->caret_pos] = old;
|
||||
|
||||
caret->ops->layout(caret, x + caret_pos, y, w, h);
|
||||
}
|
||||
|
||||
static void M_MoveCaretLeft(UI2_PROMPT_STATE *const state)
|
||||
{
|
||||
if (state->caret_pos > 0) {
|
||||
state->caret_pos--;
|
||||
}
|
||||
}
|
||||
|
||||
static void M_MoveCaretRight(UI2_PROMPT_STATE *const state)
|
||||
{
|
||||
if (state->caret_pos < (int32_t)strlen(state->current_text)) {
|
||||
state->caret_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
static void M_MoveCaretStart(UI2_PROMPT_STATE *const state)
|
||||
{
|
||||
state->caret_pos = 0;
|
||||
}
|
||||
|
||||
static void M_MoveCaretEnd(UI2_PROMPT_STATE *const state)
|
||||
{
|
||||
state->caret_pos = strlen(state->current_text);
|
||||
}
|
||||
|
||||
static void M_DeleteCharBack(UI2_PROMPT_STATE *const state)
|
||||
{
|
||||
if (state->caret_pos <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
memmove(
|
||||
state->current_text + state->caret_pos - 1,
|
||||
state->current_text + state->caret_pos,
|
||||
strlen(state->current_text) + 1 - state->caret_pos);
|
||||
state->caret_pos--;
|
||||
}
|
||||
|
||||
static void M_Confirm(UI2_PROMPT_STATE *const state)
|
||||
{
|
||||
if (String_IsEmpty(state->current_text)) {
|
||||
M_Cancel(state);
|
||||
return;
|
||||
}
|
||||
UI2_FireEvent((EVENT) {
|
||||
.name = "confirm",
|
||||
.sender = state,
|
||||
.data = state->current_text,
|
||||
});
|
||||
M_Clear(state);
|
||||
}
|
||||
|
||||
static void M_Cancel(UI2_PROMPT_STATE *const state)
|
||||
{
|
||||
UI2_FireEvent((EVENT) {
|
||||
.name = "cancel",
|
||||
.sender = state,
|
||||
.data = state->current_text,
|
||||
});
|
||||
M_Clear(state);
|
||||
}
|
||||
|
||||
static void M_Clear(UI2_PROMPT_STATE *const state)
|
||||
{
|
||||
strcpy(state->current_text, "");
|
||||
state->caret_pos = 0;
|
||||
}
|
||||
|
||||
static void M_HandleKeyDown(const EVENT *const event, void *const user_data)
|
||||
{
|
||||
const UI2_INPUT key = (UI2_INPUT)(uintptr_t)event->data;
|
||||
UI2_PROMPT_STATE *const state = user_data;
|
||||
|
||||
if (!state->is_focused) {
|
||||
return;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
switch (key) {
|
||||
case UI_KEY_LEFT: M_MoveCaretLeft(state); break;
|
||||
case UI_KEY_RIGHT: M_MoveCaretRight(state); break;
|
||||
case UI_KEY_HOME: M_MoveCaretStart(state); break;
|
||||
case UI_KEY_END: M_MoveCaretEnd(state); break;
|
||||
case UI_KEY_BACK: M_DeleteCharBack(state); break;
|
||||
case UI_KEY_RETURN: M_Confirm(state); break;
|
||||
case UI_KEY_ESCAPE: M_Cancel(state); break;
|
||||
default: break;
|
||||
}
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
static void M_HandleTextEdit(const EVENT *const event, void *const user_data)
|
||||
{
|
||||
const char *insert_string = event->data;
|
||||
const size_t insert_length = strlen(insert_string);
|
||||
UI2_PROMPT_STATE *const state = user_data;
|
||||
|
||||
if (!state->is_focused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(insert_string) != 1
|
||||
|| strstr(m_ValidPromptChars, insert_string) == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t available_space =
|
||||
state->current_text_capacity - strlen(state->current_text);
|
||||
if (insert_length >= available_space) {
|
||||
state->current_text_capacity *= 2;
|
||||
state->current_text =
|
||||
Memory_Realloc(state->current_text, state->current_text_capacity);
|
||||
}
|
||||
|
||||
memmove(
|
||||
state->current_text + state->caret_pos + insert_length,
|
||||
state->current_text + state->caret_pos,
|
||||
strlen(state->current_text) + 1 - state->caret_pos);
|
||||
memcpy(
|
||||
state->current_text + state->caret_pos, insert_string, insert_length);
|
||||
|
||||
state->caret_pos += insert_length;
|
||||
}
|
||||
|
||||
void UI2_Prompt_Init(UI2_PROMPT_STATE *state)
|
||||
{
|
||||
state->is_focused = false;
|
||||
state->current_text_capacity = 30;
|
||||
state->current_text = Memory_Alloc(state->current_text_capacity);
|
||||
state->listener1 =
|
||||
UI2_Subscribe("key_down", nullptr, M_HandleKeyDown, state);
|
||||
state->listener2 =
|
||||
UI2_Subscribe("text_edit", nullptr, M_HandleTextEdit, state);
|
||||
state->flash.count = 0;
|
||||
state->flash.rate = LOGIC_FPS * 2 / 3;
|
||||
}
|
||||
|
||||
void UI2_Prompt_Free(UI2_PROMPT_STATE *state)
|
||||
{
|
||||
UI2_Unsubscribe(state->listener1);
|
||||
UI2_Unsubscribe(state->listener2);
|
||||
Memory_FreePointer(&state->current_text);
|
||||
}
|
||||
|
||||
void UI2_Prompt(UI2_PROMPT_STATE *const state)
|
||||
{
|
||||
UI2_NODE *const node = UI2_AllocNode(&m_Ops, sizeof(M_DATA));
|
||||
M_DATA *const data = node->data;
|
||||
data->state = state;
|
||||
UI2_AddChild(node);
|
||||
UI2_PushCurrent(node);
|
||||
UI2_LabelEx(
|
||||
state->current_text != nullptr ? state->current_text : "",
|
||||
(UI2_LABEL_SETTINGS) { .scale = 1.0f, .z = 16 });
|
||||
if (state->is_focused) {
|
||||
UI2_BeginFlash(&state->flash);
|
||||
}
|
||||
UI2_LabelEx(
|
||||
"\\{button left}", (UI2_LABEL_SETTINGS) { .scale = 1.0f, .z = 8 });
|
||||
if (state->is_focused) {
|
||||
UI2_EndFlash();
|
||||
}
|
||||
UI2_PopCurrent();
|
||||
}
|
||||
|
||||
void UI2_Prompt_SetFocus(UI2_PROMPT_STATE *const state, const bool is_focused)
|
||||
{
|
||||
if (state->is_focused == is_focused) {
|
||||
return;
|
||||
}
|
||||
state->is_focused = is_focused;
|
||||
state->flash.count = 0;
|
||||
if (is_focused) {
|
||||
Input_EnterListenMode();
|
||||
} else {
|
||||
Input_ExitListenMode();
|
||||
}
|
||||
}
|
||||
|
||||
void UI2_Prompt_Clear(UI2_PROMPT_STATE *state)
|
||||
{
|
||||
M_Clear(state);
|
||||
}
|
||||
|
||||
void UI2_Prompt_ChangeText(
|
||||
UI2_PROMPT_STATE *const state, const char *const new_text)
|
||||
{
|
||||
Memory_FreePointer(&state->current_text);
|
||||
state->current_text = Memory_DupStr(new_text);
|
||||
state->caret_pos = strlen(new_text);
|
||||
}
|
25
src/libtrx/game/ui2/widgets/spacer.c
Normal file
25
src/libtrx/game/ui2/widgets/spacer.c
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include "game/ui2/widgets/spacer.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "game/ui2/helpers.h"
|
||||
|
||||
static void M_Measure(UI2_NODE *node);
|
||||
|
||||
static const UI2_WIDGET_OPS m_Ops = {
|
||||
.measure = M_Measure,
|
||||
.layout = UI2_LayoutBasic,
|
||||
.draw = UI2_DrawWrapper,
|
||||
};
|
||||
|
||||
static void M_Measure(UI2_NODE *const node)
|
||||
{
|
||||
// already done in the constructor
|
||||
}
|
||||
|
||||
void UI2_Spacer(const float w, const float h)
|
||||
{
|
||||
UI2_NODE *const node = UI2_AllocNode(&m_Ops, 0);
|
||||
node->measure_w = w * g_Config.ui.text_scale;
|
||||
node->measure_h = h * g_Config.ui.text_scale;
|
||||
UI2_AddChild(node);
|
||||
}
|
150
src/libtrx/game/ui2/widgets/stack.c
Normal file
150
src/libtrx/game/ui2/widgets/stack.c
Normal file
|
@ -0,0 +1,150 @@
|
|||
#include "game/ui2/widgets/stack.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "game/ui2/helpers.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
UI2_STACK_SETTINGS settings;
|
||||
} M_DATA;
|
||||
|
||||
static float M_CalcStartX(const UI2_NODE *node, const UI2_NODE *child);
|
||||
static float M_CalcStartY(const UI2_NODE *node, const UI2_NODE *child);
|
||||
static void M_Measure(UI2_NODE *node);
|
||||
static void M_Layout(UI2_NODE *node, float x, float y, float w, float h);
|
||||
|
||||
static const UI2_WIDGET_OPS m_Ops = {
|
||||
.measure = M_Measure,
|
||||
.layout = M_Layout,
|
||||
.draw = UI2_DrawWrapper,
|
||||
};
|
||||
|
||||
static float M_CalcStartX(
|
||||
const UI2_NODE *const node, const UI2_NODE *const child)
|
||||
{
|
||||
M_DATA *const data = node->data;
|
||||
switch (data->settings.align.h) {
|
||||
case UI2_STACK_H_ALIGN_LEFT:
|
||||
return node->x;
|
||||
case UI2_STACK_H_ALIGN_CENTER:
|
||||
return node->x + (node->w - child->measure_w) * 0.5f;
|
||||
case UI2_STACK_H_ALIGN_RIGHT:
|
||||
return node->x + node->w - child->measure_w;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
static float M_CalcStartY(
|
||||
const UI2_NODE *const node, const UI2_NODE *const child)
|
||||
{
|
||||
M_DATA *const data = node->data;
|
||||
switch (data->settings.align.v) {
|
||||
case UI2_STACK_V_ALIGN_TOP:
|
||||
return node->y;
|
||||
break;
|
||||
case UI2_STACK_V_ALIGN_CENTER:
|
||||
return node->y + (node->h - child->measure_h) * 0.5f;
|
||||
break;
|
||||
case UI2_STACK_V_ALIGN_BOTTOM:
|
||||
return node->y + node->h - child->measure_h;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
static void M_Measure(UI2_NODE *const node)
|
||||
{
|
||||
node->measure_w = 0;
|
||||
node->measure_h = 0;
|
||||
|
||||
UI2_NODE *child = node->first_child;
|
||||
M_DATA *const data = node->data;
|
||||
while (child != nullptr) {
|
||||
if (data->settings.orientation == UI2_STACK_VERTICAL) {
|
||||
node->measure_w = MAX(node->measure_w, child->measure_w);
|
||||
node->measure_h += child->measure_h;
|
||||
} else {
|
||||
node->measure_w += child->measure_w;
|
||||
node->measure_h = MAX(node->measure_h, child->measure_h);
|
||||
}
|
||||
child = child->next_sibling;
|
||||
}
|
||||
}
|
||||
|
||||
static void M_Layout(
|
||||
UI2_NODE *const node, const float x, const float y, const float w,
|
||||
const float h)
|
||||
{
|
||||
UI2_LayoutBasic(node, x, y, w, h);
|
||||
M_DATA *const data = node->data;
|
||||
|
||||
float cx = x;
|
||||
float cy = y;
|
||||
|
||||
// Lay out children
|
||||
UI2_NODE *child = node->first_child;
|
||||
while (child != nullptr) {
|
||||
const float cw = child->measure_w;
|
||||
const float ch = child->measure_h;
|
||||
|
||||
// Calculate other axis placement
|
||||
switch (data->settings.orientation) {
|
||||
case UI2_STACK_HORIZONTAL:
|
||||
cy = M_CalcStartY(node, child);
|
||||
break;
|
||||
case UI2_STACK_VERTICAL:
|
||||
cx = M_CalcStartX(node, child);
|
||||
break;
|
||||
}
|
||||
|
||||
if (data->settings.orientation == UI2_STACK_VERTICAL) {
|
||||
child->ops->layout(child, cx, cy, cw, ch);
|
||||
cy += ch + data->settings.spacing.v;
|
||||
} else {
|
||||
child->ops->layout(child, cx, cy, cw, ch);
|
||||
cx += cw + data->settings.spacing.h;
|
||||
}
|
||||
|
||||
child = child->next_sibling;
|
||||
}
|
||||
}
|
||||
|
||||
UI2_NODE *UI2_CreateStack(const UI2_STACK_SETTINGS settings)
|
||||
{
|
||||
UI2_NODE *const node = UI2_AllocNode(&m_Ops, sizeof(M_DATA));
|
||||
if (node == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
M_DATA *const data = node->data;
|
||||
data->settings = settings;
|
||||
return node;
|
||||
}
|
||||
|
||||
void UI2_BeginStack(const UI2_STACK_ORIENTATION orientation)
|
||||
{
|
||||
UI2_BeginStackEx((UI2_STACK_SETTINGS) {
|
||||
.orientation = orientation,
|
||||
.align = {
|
||||
.h = UI2_STACK_H_ALIGN_LEFT,
|
||||
.v = UI2_STACK_V_ALIGN_CENTER,
|
||||
},
|
||||
.spacing = {
|
||||
.h = 4.0f * g_Config.ui.text_scale,
|
||||
.v = 4.0f * g_Config.ui.text_scale,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
void UI2_BeginStackEx(const UI2_STACK_SETTINGS settings)
|
||||
{
|
||||
UI2_NODE *const child = UI2_CreateStack(settings);
|
||||
UI2_AddChild(child);
|
||||
UI2_PushCurrent(child);
|
||||
}
|
||||
|
||||
void UI2_EndStack(void)
|
||||
{
|
||||
UI2_PopCurrent();
|
||||
}
|
4
src/libtrx/include/libtrx/game/console.h
Normal file
4
src/libtrx/include/libtrx/game/console.h
Normal file
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
|
||||
#include "console/common.h"
|
||||
#include "console/history.h"
|
|
@ -11,10 +11,6 @@ void Console_Open(void);
|
|||
void Console_Close(void);
|
||||
bool Console_IsOpened(void);
|
||||
|
||||
void Console_ScrollLogs(void);
|
||||
int32_t Console_GetVisibleLogCount(void);
|
||||
int32_t Console_GetMaxLogCount(void);
|
||||
|
||||
void Console_Log(const char *fmt, ...);
|
||||
COMMAND_RESULT Console_Eval(const char *cmdline);
|
||||
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "./base.h"
|
||||
|
||||
UI_WIDGET *UI_Console_Create(void);
|
||||
|
||||
void UI_Console_HandleOpen(UI_WIDGET *widget);
|
||||
void UI_Console_HandleClose(UI_WIDGET *widget);
|
||||
void UI_Console_HandleLog(UI_WIDGET *widget, const char *text);
|
||||
void UI_Console_ScrollLogs(UI_WIDGET *widget);
|
||||
int32_t UI_Console_GetVisibleLogCount(UI_WIDGET *widget);
|
||||
int32_t UI_Console_GetMaxLogCount(UI_WIDGET *widget);
|
|
@ -1,10 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "./base.h"
|
||||
|
||||
UI_WIDGET *UI_Prompt_Create(int32_t width, int32_t height);
|
||||
void UI_Prompt_SetSize(UI_WIDGET *widget, int32_t width, int32_t height);
|
||||
|
||||
void UI_Prompt_SetFocus(UI_WIDGET *widget, bool is_focused);
|
||||
void UI_Prompt_Clear(UI_WIDGET *widget);
|
||||
void UI_Prompt_ChangeText(UI_WIDGET *widget, const char *new_text);
|
12
src/libtrx/include/libtrx/game/ui2.h
Normal file
12
src/libtrx/include/libtrx/game/ui2.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "./ui2/common.h"
|
||||
#include "./ui2/events.h"
|
||||
#include "./ui2/widgets/anchor.h"
|
||||
#include "./ui2/widgets/console.h"
|
||||
#include "./ui2/widgets/console_logs.h"
|
||||
#include "./ui2/widgets/flash.h"
|
||||
#include "./ui2/widgets/label.h"
|
||||
#include "./ui2/widgets/pad.h"
|
||||
#include "./ui2/widgets/spacer.h"
|
||||
#include "./ui2/widgets/stack.h"
|
63
src/libtrx/include/libtrx/game/ui2/common.h
Normal file
63
src/libtrx/include/libtrx/game/ui2/common.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
#pragma once
|
||||
|
||||
#include "../ui/common.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef UI_INPUT UI2_INPUT;
|
||||
|
||||
// Forward declaration of the node and its vtable.
|
||||
struct UI2_NODE;
|
||||
typedef struct {
|
||||
void (*measure)(struct UI2_NODE *node);
|
||||
void (*layout)(struct UI2_NODE *node, float x, float y, float w, float h);
|
||||
bool (*draw)(const struct UI2_NODE *node);
|
||||
} UI2_WIDGET_OPS;
|
||||
|
||||
// Node structure that forms the UI tree
|
||||
typedef struct UI2_NODE {
|
||||
// Common operations on a widget
|
||||
const UI2_WIDGET_OPS *ops;
|
||||
|
||||
// Final layout rectangle
|
||||
float x;
|
||||
float y;
|
||||
float w;
|
||||
float h;
|
||||
|
||||
// Needed size from measure pass
|
||||
float measure_w;
|
||||
float measure_h;
|
||||
|
||||
// Link to parent and siblings to form a tree
|
||||
struct UI2_NODE *parent;
|
||||
struct UI2_NODE *first_child;
|
||||
struct UI2_NODE *last_child;
|
||||
struct UI2_NODE *next_sibling;
|
||||
|
||||
// Widget-specific data
|
||||
void *data;
|
||||
} UI2_NODE;
|
||||
|
||||
// Dimensions in virtual pixels of the screen area
|
||||
// (640x480 for any 4:3 resolution on 1.00 text scaling)
|
||||
extern int32_t UI2_GetCanvasWidth(void);
|
||||
extern int32_t UI2_GetCanvasHeight(void);
|
||||
|
||||
// Public API for scene management
|
||||
void UI2_BeginScene(void);
|
||||
void UI2_EndScene(void);
|
||||
|
||||
// Helpers to add children, etc.
|
||||
UI2_NODE *UI2_AllocNode(const UI2_WIDGET_OPS *ops, size_t additional_size);
|
||||
void UI2_AddChild(UI2_NODE *child);
|
||||
void UI2_PushCurrent(UI2_NODE *child);
|
||||
void UI2_PopCurrent(void);
|
||||
|
||||
void UI2_Init(void);
|
||||
void UI2_Shutdown(void);
|
||||
void UI2_ToggleState(bool *config_setting);
|
||||
|
||||
void UI2_HandleKeyDown(uint32_t key);
|
||||
void UI2_HandleKeyUp(uint32_t key);
|
||||
void UI2_HandleTextEdit(const char *text);
|
16
src/libtrx/include/libtrx/game/ui2/events.h
Normal file
16
src/libtrx/include/libtrx/game/ui2/events.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include "../../event_manager.h"
|
||||
|
||||
typedef void (*EVENT_LISTENER)(const EVENT *, void *user_data);
|
||||
|
||||
void UI2_InitEvents(void);
|
||||
void UI2_ShutdownEvents(void);
|
||||
|
||||
int32_t UI2_Subscribe(
|
||||
const char *event_name, const void *sender, EVENT_LISTENER listener,
|
||||
void *user_data);
|
||||
|
||||
void UI2_Unsubscribe(int32_t listener_id);
|
||||
|
||||
void UI2_FireEvent(EVENT event);
|
9
src/libtrx/include/libtrx/game/ui2/widgets/anchor.h
Normal file
9
src/libtrx/include/libtrx/game/ui2/widgets/anchor.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
// Used to align a top-level widget to the screen center or to the screen edges.
|
||||
// Uses ratio inputs.
|
||||
|
||||
void UI2_BeginAnchor(const float x, const float y);
|
||||
void UI2_EndAnchor(void);
|
18
src/libtrx/include/libtrx/game/ui2/widgets/console.h
Normal file
18
src/libtrx/include/libtrx/game/ui2/widgets/console.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include "./console_logs.h"
|
||||
#include "./prompt.h"
|
||||
|
||||
// Dev console display widget.
|
||||
|
||||
typedef struct {
|
||||
UI2_CONSOLE_LOGS logs;
|
||||
UI2_PROMPT_STATE prompt;
|
||||
|
||||
int32_t listeners[5];
|
||||
int32_t history_idx;
|
||||
} UI2_CONSOLE_STATE;
|
||||
|
||||
void UI2_Console_Init(UI2_CONSOLE_STATE *state);
|
||||
void UI2_Console_Free(UI2_CONSOLE_STATE *state);
|
||||
void UI2_Console(UI2_CONSOLE_STATE *state);
|
22
src/libtrx/include/libtrx/game/ui2/widgets/console_logs.h
Normal file
22
src/libtrx/include/libtrx/game/ui2/widgets/console_logs.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Scrollback for the dev console.
|
||||
|
||||
typedef struct {
|
||||
char *text;
|
||||
double expire_at;
|
||||
} UI2_CONSOLE_LOG_LINE;
|
||||
|
||||
typedef struct {
|
||||
size_t max_lines;
|
||||
size_t vis_lines;
|
||||
UI2_CONSOLE_LOG_LINE *logs;
|
||||
int32_t listener_id;
|
||||
} UI2_CONSOLE_LOGS;
|
||||
|
||||
void UI2_ConsoleLogs_Init(UI2_CONSOLE_LOGS *state);
|
||||
void UI2_ConsoleLogs_Free(UI2_CONSOLE_LOGS *state);
|
||||
void UI2_ConsoleLogs(UI2_CONSOLE_LOGS *state);
|
15
src/libtrx/include/libtrx/game/ui2/widgets/flash.h
Normal file
15
src/libtrx/include/libtrx/game/ui2/widgets/flash.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Make the child widget invisible in the specified interval.
|
||||
|
||||
typedef struct {
|
||||
int32_t rate;
|
||||
int32_t count;
|
||||
} UI2_FLASH_STATE;
|
||||
|
||||
void UI2_BeginFlash(UI2_FLASH_STATE *state);
|
||||
void UI2_EndFlash(void);
|
19
src/libtrx/include/libtrx/game/ui2/widgets/label.h
Normal file
19
src/libtrx/include/libtrx/game/ui2/widgets/label.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Basic text widget.
|
||||
|
||||
typedef struct {
|
||||
float scale;
|
||||
int32_t z;
|
||||
} UI2_LABEL_SETTINGS;
|
||||
|
||||
void UI2_Label(const char *text);
|
||||
void UI2_LabelEx(const char *text, UI2_LABEL_SETTINGS settings);
|
||||
|
||||
void UI2_Label_Measure(const char *text, float *out_w, float *out_h);
|
||||
void UI2_Label_MeasureEx(
|
||||
const char *text, float *out_w, float *out_h, UI2_LABEL_SETTINGS settings);
|
8
src/libtrx/include/libtrx/game/ui2/widgets/pad.h
Normal file
8
src/libtrx/include/libtrx/game/ui2/widgets/pad.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
// An invisible border in pixel units around the child widget.
|
||||
|
||||
void UI2_BeginPad(float x, float y);
|
||||
void UI2_EndPad(void);
|
29
src/libtrx/include/libtrx/game/ui2/widgets/prompt.h
Normal file
29
src/libtrx/include/libtrx/game/ui2/widgets/prompt.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include "../common.h"
|
||||
#include "./flash.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// A text edit widget that collects text input from the player.
|
||||
// Needs to be in focus to work, otherwise is inactive.
|
||||
|
||||
typedef struct {
|
||||
bool is_focused;
|
||||
int32_t caret_pos;
|
||||
int32_t current_text_capacity;
|
||||
char *current_text;
|
||||
|
||||
int32_t listener1;
|
||||
int32_t listener2;
|
||||
|
||||
UI2_FLASH_STATE flash;
|
||||
} UI2_PROMPT_STATE;
|
||||
|
||||
void UI2_Prompt_Init(UI2_PROMPT_STATE *state);
|
||||
void UI2_Prompt_Free(UI2_PROMPT_STATE *state);
|
||||
|
||||
void UI2_Prompt(UI2_PROMPT_STATE *state);
|
||||
void UI2_Prompt_Clear(UI2_PROMPT_STATE *state);
|
||||
void UI2_Prompt_SetFocus(UI2_PROMPT_STATE *state, bool is_focused);
|
||||
void UI2_Prompt_ChangeText(UI2_PROMPT_STATE *state, const char *new_text);
|
7
src/libtrx/include/libtrx/game/ui2/widgets/spacer.h
Normal file
7
src/libtrx/include/libtrx/game/ui2/widgets/spacer.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
// An invisible widget that occupies certain space in pixels.
|
||||
|
||||
void UI2_Spacer(float w, float h);
|
43
src/libtrx/include/libtrx/game/ui2/widgets/stack.h
Normal file
43
src/libtrx/include/libtrx/game/ui2/widgets/stack.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Stack several widgets vertically or horizontally.
|
||||
|
||||
typedef enum {
|
||||
UI2_STACK_VERTICAL,
|
||||
UI2_STACK_HORIZONTAL,
|
||||
} UI2_STACK_ORIENTATION;
|
||||
|
||||
typedef enum {
|
||||
UI2_STACK_H_ALIGN_LEFT,
|
||||
UI2_STACK_H_ALIGN_CENTER,
|
||||
UI2_STACK_H_ALIGN_RIGHT,
|
||||
} UI2_STACK_H_ALIGN;
|
||||
|
||||
typedef enum {
|
||||
UI2_STACK_V_ALIGN_TOP,
|
||||
UI2_STACK_V_ALIGN_CENTER,
|
||||
UI2_STACK_V_ALIGN_BOTTOM,
|
||||
} UI2_STACK_V_ALIGN;
|
||||
|
||||
typedef struct {
|
||||
UI2_STACK_ORIENTATION orientation;
|
||||
struct {
|
||||
UI2_STACK_H_ALIGN h;
|
||||
UI2_STACK_V_ALIGN v;
|
||||
} align;
|
||||
struct {
|
||||
float h;
|
||||
float v;
|
||||
} spacing;
|
||||
} UI2_STACK_SETTINGS;
|
||||
|
||||
// Private constructor
|
||||
UI2_NODE *UI2_CreateStack(UI2_STACK_SETTINGS settings);
|
||||
|
||||
void UI2_BeginStack(UI2_STACK_ORIENTATION orientation);
|
||||
void UI2_BeginStackEx(UI2_STACK_SETTINGS settings);
|
||||
void UI2_EndStack(void);
|
|
@ -197,15 +197,25 @@ sources = [
|
|||
'game/text.c',
|
||||
'game/ui/common.c',
|
||||
'game/ui/events.c',
|
||||
'game/ui/widgets/console.c',
|
||||
'game/ui/widgets/frame.c',
|
||||
'game/ui/widgets/label.c',
|
||||
'game/ui/widgets/photo_mode.c',
|
||||
'game/ui/widgets/prompt.c',
|
||||
'game/ui/widgets/requester.c',
|
||||
'game/ui/widgets/spacer.c',
|
||||
'game/ui/widgets/stack.c',
|
||||
'game/ui/widgets/window.c',
|
||||
'game/ui2/common.c',
|
||||
'game/ui2/events.c',
|
||||
'game/ui2/helpers.c',
|
||||
'game/ui2/widgets/anchor.c',
|
||||
'game/ui2/widgets/console.c',
|
||||
'game/ui2/widgets/console_logs.c',
|
||||
'game/ui2/widgets/flash.c',
|
||||
'game/ui2/widgets/label.c',
|
||||
'game/ui2/widgets/pad.c',
|
||||
'game/ui2/widgets/prompt.c',
|
||||
'game/ui2/widgets/spacer.c',
|
||||
'game/ui2/widgets/stack.c',
|
||||
'gfx/2d/2d_renderer.c',
|
||||
'gfx/2d/2d_surface.c',
|
||||
'gfx/3d/3d_renderer.c',
|
||||
|
|
|
@ -11,7 +11,7 @@ void Console_DrawBackdrop(void)
|
|||
int32_t sw = Viewport_GetWidth();
|
||||
int32_t sh = Screen_GetRenderScale(
|
||||
// not entirely accurate, but good enough
|
||||
TEXT_HEIGHT * 1.0 + 10 * TEXT_HEIGHT * 0.8, RSR_TEXT);
|
||||
TEXT_HEIGHT_FIXED * 1.0 + 7 * TEXT_HEIGHT_FIXED * 0.8, RSR_TEXT);
|
||||
int32_t sy = Viewport_GetHeight() - sh;
|
||||
|
||||
RGBA_8888 top = { 0, 0, 0, 0 };
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <libtrx/debug.h>
|
||||
#include <libtrx/game/interpolation.h>
|
||||
#include <libtrx/game/ui/common.h>
|
||||
#include <libtrx/game/ui2/common.h>
|
||||
|
||||
#define FRAME_BUFFER(key) \
|
||||
do { \
|
||||
|
@ -67,6 +68,7 @@ void Game_ProcessInput(void)
|
|||
|
||||
if (g_InputDB.toggle_ui) {
|
||||
UI_ToggleState(&g_Config.ui.enable_game_ui);
|
||||
UI2_ToggleState(&g_Config.ui.enable_game_ui);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -507,10 +507,10 @@ static void M_ProgressBar(
|
|||
const TEXTSTRING *const txt, BAR_INFO *const bar, const int32_t timer)
|
||||
{
|
||||
int32_t width = Text_GetWidth(txt);
|
||||
int32_t height = TEXT_HEIGHT;
|
||||
int32_t height = TEXT_HEIGHT_FIXED * 2 / 3;
|
||||
|
||||
int32_t x = txt->pos.x;
|
||||
int32_t y = txt->pos.y - TEXT_HEIGHT;
|
||||
int32_t y = txt->pos.y - height;
|
||||
|
||||
if (txt->flags.centre_h) {
|
||||
x += (Screen_GetResWidthDownscaled(RSR_TEXT) - width) / 2;
|
||||
|
|
|
@ -71,7 +71,7 @@ static REQUEST_INFO m_NewGameRequester = {
|
|||
.line_offset = 0,
|
||||
.line_old_offset = 0,
|
||||
.pix_width = 162,
|
||||
.line_height = TEXT_HEIGHT + 7,
|
||||
.line_height = TEXT_HEIGHT_FIXED + 1,
|
||||
.is_blockable = false,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
|
@ -87,7 +87,7 @@ static REQUEST_INFO m_SelectLevelRequester = {
|
|||
.line_offset = 0,
|
||||
.line_old_offset = 0,
|
||||
.pix_width = 292,
|
||||
.line_height = TEXT_HEIGHT + 7,
|
||||
.line_height = TEXT_HEIGHT_FIXED + 3,
|
||||
.is_blockable = false,
|
||||
.x = 0,
|
||||
.y = -32,
|
||||
|
@ -103,7 +103,7 @@ REQUEST_INFO g_SavegameRequester = {
|
|||
.line_offset = 0,
|
||||
.line_old_offset = 0,
|
||||
.pix_width = 292,
|
||||
.line_height = TEXT_HEIGHT + 7,
|
||||
.line_height = TEXT_HEIGHT_FIXED + 3,
|
||||
.is_blockable = false,
|
||||
.x = 0,
|
||||
.y = -32,
|
||||
|
|
|
@ -242,14 +242,14 @@ static void M_BarGetLocation(
|
|||
}
|
||||
|
||||
if (g_GameInfo.showing_demo && bar_info->location == BL_BOTTOM_CENTER) {
|
||||
*y -= M_GetBarToTextScale() * (TEXT_HEIGHT + bar_spacing);
|
||||
*y -= M_GetBarToTextScale() * (TEXT_HEIGHT_FIXED + bar_spacing);
|
||||
} else if (
|
||||
g_GameInfo.inv_ring_shown && GF_GetCurrentLevel() != nullptr
|
||||
&& GF_GetCurrentLevel()->type == GFL_TITLE
|
||||
&& (bar_info->location == BL_TOP_CENTER
|
||||
|| bar_info->location == BL_BOTTOM_CENTER)) {
|
||||
*y = screen_margin_v + m_BarOffsetY[bar_info->location]
|
||||
+ M_GetBarToTextScale() * (TEXT_HEIGHT + bar_spacing);
|
||||
+ M_GetBarToTextScale() * (TEXT_HEIGHT_FIXED + bar_spacing);
|
||||
}
|
||||
|
||||
m_BarOffsetY[bar_info->location] += *height + bar_spacing;
|
||||
|
|
|
@ -131,7 +131,7 @@ int32_t Screen_GetRenderScale(int32_t unit, RENDER_SCALE_REF ref)
|
|||
} else if (ref == RSR_BAR) {
|
||||
return M_GetRenderScaleBase(unit, 640, 480, g_Config.ui.bar_scale);
|
||||
} else {
|
||||
return M_GetRenderScaleBase(unit, 640, 480, 0);
|
||||
return M_GetRenderScaleBase(unit, 640, 480, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
RSR_TEXT = 0,
|
||||
RSR_BAR = 1,
|
||||
RSR_TEXT,
|
||||
RSR_BAR,
|
||||
RSR_GENERIC,
|
||||
} RENDER_SCALE_REF;
|
||||
|
||||
void Screen_Init(void);
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include <libtrx/game/game_buf.h>
|
||||
#include <libtrx/game/game_string_table.h>
|
||||
#include <libtrx/game/ui/common.h>
|
||||
#include <libtrx/game/ui2.h>
|
||||
#include <libtrx/memory.h>
|
||||
#include <libtrx/strings.h>
|
||||
|
||||
|
@ -160,6 +161,7 @@ void Shell_Shutdown(void)
|
|||
Music_Shutdown();
|
||||
Sound_Shutdown();
|
||||
UI_Shutdown();
|
||||
UI2_Shutdown();
|
||||
Text_Shutdown();
|
||||
Config_Shutdown();
|
||||
Log_Shutdown();
|
||||
|
@ -185,6 +187,7 @@ void Shell_Main(void)
|
|||
|
||||
Text_Init();
|
||||
UI_Init();
|
||||
UI2_Init();
|
||||
|
||||
Input_Init();
|
||||
Sound_Init();
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
|
||||
#include <libtrx/config.h>
|
||||
|
||||
#define TEXT_BOX_OFFSET 2
|
||||
#define TEXT_BOX_OFFSET_X 2
|
||||
#define TEXT_BOX_OFFSET_Y1 0
|
||||
#define TEXT_BOX_OFFSET_Y2 3
|
||||
|
||||
static RGBA_8888 m_MenuColorMap[MC_NUMBER_OF] = {
|
||||
{ 70, 30, 107, 230 }, // MC_PURPLE_C
|
||||
|
@ -154,9 +156,9 @@ void Text_DrawText(TEXTSTRING *const text)
|
|||
y += Screen_GetResHeightDownscaled(RSR_TEXT);
|
||||
}
|
||||
|
||||
int32_t bxpos = text->background.offset.x + x - TEXT_BOX_OFFSET;
|
||||
int32_t bxpos = text->background.offset.x + x - TEXT_BOX_OFFSET_X;
|
||||
int32_t bypos =
|
||||
text->background.offset.y + y - TEXT_BOX_OFFSET * 2 - TEXT_HEIGHT;
|
||||
text->background.offset.y + y + TEXT_BOX_OFFSET_Y1 - TEXT_HEIGHT_FIXED;
|
||||
|
||||
int32_t sx;
|
||||
int32_t sy;
|
||||
|
@ -216,14 +218,14 @@ void Text_DrawText(TEXTSTRING *const text)
|
|||
if (text->background.size.x) {
|
||||
bxpos += text_width / 2;
|
||||
bxpos -= text->background.size.x / 2;
|
||||
bwidth = text->background.size.x + TEXT_BOX_OFFSET * 2;
|
||||
bwidth = text->background.size.x + TEXT_BOX_OFFSET_X * 2;
|
||||
} else {
|
||||
bwidth = text_width + TEXT_BOX_OFFSET * 2;
|
||||
bwidth = text_width + TEXT_BOX_OFFSET_X * 2;
|
||||
}
|
||||
if (text->background.size.y) {
|
||||
bheight = text->background.size.y;
|
||||
} else {
|
||||
bheight = TEXT_HEIGHT + 7;
|
||||
bheight = TEXT_HEIGHT_FIXED + TEXT_BOX_OFFSET_Y2;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,5 +252,5 @@ void Text_DrawText(TEXTSTRING *const text)
|
|||
|
||||
int32_t Text_GetMaxLineLength(void)
|
||||
{
|
||||
return Screen_GetResWidthDownscaled(RSR_TEXT) / (TEXT_HEIGHT * 0.75);
|
||||
return Screen_GetResWidthDownscaled(RSR_TEXT) / (TEXT_HEIGHT_FIXED * 0.6);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
#define TEXT_HEIGHT 11 // TODO: Get rid of this
|
||||
#define TEXT_OUTLINE_THICKNESS 2
|
||||
|
||||
RGBA_8888 Text_GetMenuColor(MENU_COLOR color);
|
||||
|
|
|
@ -15,6 +15,16 @@ int32_t UI_GetCanvasHeight(void)
|
|||
return Screen_GetResHeightDownscaled(RSR_TEXT);
|
||||
}
|
||||
|
||||
int32_t UI2_GetCanvasWidth(void)
|
||||
{
|
||||
return Screen_GetResHeightDownscaled(RSR_GENERIC) * 16 / 9;
|
||||
}
|
||||
|
||||
int32_t UI2_GetCanvasHeight(void)
|
||||
{
|
||||
return Screen_GetResHeightDownscaled(RSR_GENERIC);
|
||||
}
|
||||
|
||||
UI_INPUT UI_TranslateInput(uint32_t system_keycode)
|
||||
{
|
||||
// clang-format off
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <libtrx/debug.h>
|
||||
#include <libtrx/filesystem.h>
|
||||
#include <libtrx/game/ui/common.h>
|
||||
#include <libtrx/game/ui2.h>
|
||||
#include <libtrx/gfx/common.h>
|
||||
#include <libtrx/gfx/context.h>
|
||||
#include <libtrx/log.h>
|
||||
|
@ -189,6 +190,7 @@ void Shell_ProcessEvents(void)
|
|||
Console_Open();
|
||||
} else {
|
||||
UI_HandleKeyDown(event.key.keysym.sym);
|
||||
UI2_HandleKeyDown(event.key.keysym.sym);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -203,10 +205,12 @@ void Shell_ProcessEvents(void)
|
|||
|
||||
case SDL_TEXTEDITING:
|
||||
UI_HandleTextEdit(event.text.text);
|
||||
UI2_HandleTextEdit(event.text.text);
|
||||
break;
|
||||
|
||||
case SDL_TEXTINPUT:
|
||||
UI_HandleTextEdit(event.text.text);
|
||||
UI2_HandleTextEdit(event.text.text);
|
||||
break;
|
||||
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include <libtrx/game/objects/creatures/wolf.h>
|
||||
#include <libtrx/game/shell.h>
|
||||
#include <libtrx/game/ui/common.h>
|
||||
#include <libtrx/game/ui2.h>
|
||||
#include <libtrx/memory.h>
|
||||
#include <libtrx/strings.h>
|
||||
|
||||
|
@ -245,6 +246,7 @@ static void M_HandleKeyDown(const SDL_Event *const event)
|
|||
Console_Open();
|
||||
} else {
|
||||
UI_HandleKeyDown(event->key.keysym.sym);
|
||||
UI2_HandleKeyDown(event->key.keysym.sym);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -408,6 +410,7 @@ void Shell_Main(void)
|
|||
Config_Init();
|
||||
Text_Init();
|
||||
UI_Init();
|
||||
UI2_Init();
|
||||
Console_Init();
|
||||
|
||||
Input_Init();
|
||||
|
@ -544,6 +547,7 @@ void Shell_Shutdown(void)
|
|||
Render_Shutdown();
|
||||
Text_Shutdown();
|
||||
UI_Shutdown();
|
||||
UI2_Shutdown();
|
||||
GameBuf_Shutdown();
|
||||
Config_Shutdown();
|
||||
EnumMap_Shutdown();
|
||||
|
@ -597,10 +601,12 @@ void Shell_ProcessEvents(void)
|
|||
|
||||
case SDL_TEXTEDITING:
|
||||
UI_HandleTextEdit(event.text.text);
|
||||
UI2_HandleTextEdit(event.text.text);
|
||||
break;
|
||||
|
||||
case SDL_TEXTINPUT:
|
||||
UI_HandleTextEdit(event.text.text);
|
||||
UI2_HandleTextEdit(event.text.text);
|
||||
break;
|
||||
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
|
|
|
@ -16,6 +16,16 @@ int32_t UI_GetCanvasHeight(void)
|
|||
return Scaler_CalcInverse(g_PhdWinHeight, SCALER_TARGET_TEXT);
|
||||
}
|
||||
|
||||
int32_t UI2_GetCanvasWidth(void)
|
||||
{
|
||||
return Scaler_CalcInverse(g_PhdWinHeight, SCALER_TARGET_GENERIC) * 16 / 9;
|
||||
}
|
||||
|
||||
int32_t UI2_GetCanvasHeight(void)
|
||||
{
|
||||
return Scaler_CalcInverse(g_PhdWinHeight, SCALER_TARGET_GENERIC);
|
||||
}
|
||||
|
||||
UI_INPUT UI_TranslateInput(uint32_t system_keycode)
|
||||
{
|
||||
// clang-format off
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue