ui: introduce UI v2

This commit is contained in:
Marcin Kurczewski 2025-04-13 12:04:25 +02:00
parent 454d133db7
commit 548c5ac653
No known key found for this signature in database
GPG key ID: CC65E6FD28CAE42A
49 changed files with 1590 additions and 722 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View 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 });
}

View 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);
}
}

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

View 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);

View 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();
}

View 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();
}

View 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();
}

View 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();
}

View 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);
}

View 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();
}

View 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);
}

View 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);
}

View 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();
}

View file

@ -0,0 +1,4 @@
#pragma once
#include "console/common.h"
#include "console/history.h"

View file

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

View file

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

View file

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

View 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"

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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