From 3b53952767802d390922d5a2a216d78c8cef76e6 Mon Sep 17 00:00:00 2001 From: PeachyPeach <72323920+PeachyPeachSM64@users.noreply.github.com> Date: Tue, 10 Mar 2026 23:38:24 +0100 Subject: [PATCH] DJUI Text improvements and bug fixes (#1131) - Drastically improve and optimize display list usage for text - Add text alignment with and without interpolation, and color codes, new lines and tabs are now handled properly - Restored alpha color code (`#RGBA` or `#RRGGBBAA`) for `djui_print_text` functions (regular DJUI text allows alpha color codes, but ignores the alpha component) - Add constants for common text alignment and rotation pivot values - Fix interpolation issues with all `djui_hud` elements - A few autogen fixes (missing `number` type for constants, missing return values for some functions) - Fix recolorable hud font offsets on `e`, `i` and `o` letters --- autogen/convert_constants.py | 2 + autogen/convert_functions.py | 2 +- autogen/lua_definitions/constants.lua | 54 +- autogen/lua_definitions/functions.lua | 27 +- autogen/lua_definitions/structs.lua | 8 - docs/lua/constants.md | 13 + docs/lua/functions-3.md | 89 ++- docs/lua/functions-4.md | 4 +- docs/lua/functions-5.md | 7 +- docs/lua/functions-6.md | 2 +- docs/lua/functions.md | 3 + docs/lua/structs.md | 16 - src/pc/crash_handler.c | 3 + src/pc/discord/discord_activity.c | 8 +- src/pc/djui/djui.c | 3 +- src/pc/djui/djui_font.c | 62 +- src/pc/djui/djui_font.h | 6 +- src/pc/djui/djui_gfx.c | 105 ++- src/pc/djui/djui_gfx.h | 10 +- src/pc/djui/djui_hud_utils.c | 704 +++++++++++------- src/pc/djui/djui_hud_utils.h | 40 +- src/pc/djui/djui_inputbox.c | 3 + src/pc/djui/djui_panel_mod_menu.c | 4 +- src/pc/djui/djui_text.c | 250 +++++-- src/pc/djui/djui_text.h | 5 + src/pc/djui/djui_unicode.c | 14 +- src/pc/djui/djui_unicode.h | 10 +- src/pc/lua/smlua_cobject_autogen.c | 14 +- src/pc/lua/smlua_cobject_autogen.h | 1 - src/pc/lua/smlua_constants_autogen.c | 13 + src/pc/lua/smlua_functions.c | 2 +- src/pc/lua/smlua_functions_autogen.c | 84 ++- src/pc/lua/smlua_hooks.c | 2 +- src/pc/mods/mod.c | 36 +- src/pc/mods/mod.h | 7 +- src/pc/mods/mods.c | 21 +- src/pc/network/packets/packet_download.c | 10 +- src/pc/network/packets/packet_mod_list.c | 7 +- src/pc/utils/misc.c | 15 - src/pc/utils/misc.h | 1 - .../custom_font_hud_recolor.rgba32.png | Bin 25778 -> 39601 bytes 41 files changed, 1133 insertions(+), 534 deletions(-) diff --git a/autogen/convert_constants.py b/autogen/convert_constants.py index 46b521710..409cd8427 100644 --- a/autogen/convert_constants.py +++ b/autogen/convert_constants.py @@ -535,6 +535,8 @@ def def_constant(fname, processed_constant, skip_constant): continue if '"' in c[1]: s += '\n--- @type string\n' + elif "." in c[1]: + s += '\n--- @type number\n' else: s += '\n--- @type integer\n' s += '%s = %s\n' % (c[0], c[1]) diff --git a/autogen/convert_functions.py b/autogen/convert_functions.py index 8846db56f..0ec22b12c 100644 --- a/autogen/convert_functions.py +++ b/autogen/convert_functions.py @@ -1342,7 +1342,7 @@ def doc_function(fname, function): s += '- None\n' s += '\n### Returns\n' - if rtype != None: + if len(rvalues) > 0: for _, ptype, plink in rvalues: if plink: s += '- [%s](%s)\n' % (ptype, plink) diff --git a/autogen/lua_definitions/constants.lua b/autogen/lua_definitions/constants.lua index fef72a27c..8e160c5d4 100644 --- a/autogen/lua_definitions/constants.lua +++ b/autogen/lua_definitions/constants.lua @@ -2760,6 +2760,42 @@ CONSOLE_MESSAGE_ERROR = 2 --- @type ConsoleMessageLevel --- | `CONSOLE_MESSAGE_WARNING` --- | `CONSOLE_MESSAGE_ERROR` +--- @type number +ROTATION_PIVOT_X_LEFT = 0.0 + +--- @type number +ROTATION_PIVOT_X_CENTER = 0.5 + +--- @type number +ROTATION_PIVOT_X_RIGHT = 1.0 + +--- @type number +ROTATION_PIVOT_Y_TOP = 0.0 + +--- @type number +ROTATION_PIVOT_Y_CENTER = 0.5 + +--- @type number +ROTATION_PIVOT_Y_BOTTOM = 1.0 + +--- @type number +TEXT_HALIGN_LEFT = 0.0 + +--- @type number +TEXT_HALIGN_CENTER = 0.5 + +--- @type number +TEXT_HALIGN_RIGHT = 1.0 + +--- @type number +TEXT_VALIGN_TOP = 0.0 + +--- @type number +TEXT_VALIGN_CENTER = 0.5 + +--- @type number +TEXT_VALIGN_BOTTOM = 1.0 + RESOLUTION_DJUI = 0 --- @type HudUtilsResolution RESOLUTION_N64 = 1 --- @type HudUtilsResolution RESOLUTION_COUNT = 2 --- @type HudUtilsResolution @@ -2778,16 +2814,18 @@ FILTER_COUNT = 2 --- @type HudUtilsFilter --- | `FILTER_LINEAR` --- | `FILTER_COUNT` -FONT_NORMAL = 0 --- @type DjuiFontType -FONT_MENU = 1 --- @type DjuiFontType -FONT_HUD = 2 --- @type DjuiFontType -FONT_ALIASED = 3 --- @type DjuiFontType -FONT_CUSTOM_HUD = 4 --- @type DjuiFontType -FONT_RECOLOR_HUD = 5 --- @type DjuiFontType -FONT_SPECIAL = 6 --- @type DjuiFontType -FONT_COUNT = 7 --- @type DjuiFontType +FONT_LEGACY = -1 --- @type DjuiFontType +FONT_NORMAL = 0 --- @type DjuiFontType +FONT_MENU = 1 --- @type DjuiFontType +FONT_HUD = 2 --- @type DjuiFontType +FONT_ALIASED = 3 --- @type DjuiFontType +FONT_CUSTOM_HUD = 4 --- @type DjuiFontType +FONT_RECOLOR_HUD = 5 --- @type DjuiFontType +FONT_SPECIAL = 6 --- @type DjuiFontType +FONT_COUNT = 7 --- @type DjuiFontType --- @alias DjuiFontType +--- | `FONT_LEGACY` --- | `FONT_NORMAL` --- | `FONT_MENU` --- | `FONT_HUD` diff --git a/autogen/lua_definitions/functions.lua b/autogen/lua_definitions/functions.lua index f7b0aa63d..746a8f4c4 100644 --- a/autogen/lua_definitions/functions.lua +++ b/autogen/lua_definitions/functions.lua @@ -3856,7 +3856,9 @@ function djui_hud_reset_color() -- ... end ---- @return HudUtilsRotation +--- @return integer rotation +--- @return number pivotX +--- @return number pivotY --- Gets the current DJUI HUD rotation function djui_hud_get_rotation() -- ... @@ -3881,6 +3883,29 @@ function djui_hud_set_rotation_interpolated(prevRotation, prevPivotX, prevPivotY -- ... end +--- @return number textHAlign +--- @return number textVAlign +--- Gets the current DJUI HUD text alignment +function djui_hud_get_text_alignment() + -- ... +end + +--- @param textHAlign number +--- @param textVAlign number +--- Sets the current DJUI HUD text alignment +function djui_hud_set_text_alignment(textHAlign, textVAlign) + -- ... +end + +--- @param prevTextHAlign number +--- @param prevTextVAlign number +--- @param textHAlign number +--- @param textVAlign number +--- Sets the current DJUI HUD text alignment interpolated +function djui_hud_set_text_alignment_interpolated(prevTextHAlign, prevTextVAlign, textHAlign, textVAlign) + -- ... +end + --- @return integer --- Gets the screen width in the current DJUI HUD resolution function djui_hud_get_screen_width() diff --git a/autogen/lua_definitions/structs.lua b/autogen/lua_definitions/structs.lua index c971ce2ff..5441e7424 100644 --- a/autogen/lua_definitions/structs.lua +++ b/autogen/lua_definitions/structs.lua @@ -955,14 +955,6 @@ --- @field public translation Vec3s --- @field public rotation Vec3s ---- @class HudUtilsRotation ---- @field public rotation number ---- @field public rotationDiff number ---- @field public prevPivotX number ---- @field public prevPivotY number ---- @field public pivotX number ---- @field public pivotY number - --- @class InstantWarp --- @field public id integer --- @field public area integer diff --git a/docs/lua/constants.md b/docs/lua/constants.md index 8f49768aa..0c92edac9 100644 --- a/docs/lua/constants.md +++ b/docs/lua/constants.md @@ -1149,6 +1149,18 @@
## [djui_hud_utils.h](#djui_hud_utils.h) +- ROTATION_PIVOT_X_LEFT +- ROTATION_PIVOT_X_CENTER +- ROTATION_PIVOT_X_RIGHT +- ROTATION_PIVOT_Y_TOP +- ROTATION_PIVOT_Y_CENTER +- ROTATION_PIVOT_Y_BOTTOM +- TEXT_HALIGN_LEFT +- TEXT_HALIGN_CENTER +- TEXT_HALIGN_RIGHT +- TEXT_VALIGN_TOP +- TEXT_VALIGN_CENTER +- TEXT_VALIGN_BOTTOM ### [enum HudUtilsResolution](#HudUtilsResolution) | Identifier | Value | @@ -1167,6 +1179,7 @@ ### [enum DjuiFontType](#DjuiFontType) | Identifier | Value | | :--------- | :---- | +| FONT_LEGACY | -1 | | FONT_NORMAL | 0 | | FONT_MENU | 1 | | FONT_HUD | 2 | diff --git a/docs/lua/functions-3.md b/docs/lua/functions-3.md index 7b1704e71..8e4f8c230 100644 --- a/docs/lua/functions-3.md +++ b/docs/lua/functions-3.md @@ -1312,7 +1312,8 @@ Calculates and returns the pitch and yaw angles from one 3D position (`from`) to | to | [Vec3f](structs.md#Vec3f) | ### Returns -- None +- `integer` +- `integer` ### C Prototype `void calculate_angles(Vec3f from, Vec3f to, RET s16 *pitch, RET s16 *yaw);` @@ -1585,7 +1586,7 @@ Applies a roll-based shake effect to the camera. Simulates rotational disturbanc | roll | `integer` | ### Returns -- None +- `integer` ### C Prototype `void shake_camera_roll(INOUT s16 *roll);` @@ -2825,7 +2826,7 @@ Gets the current DJUI HUD font - `integer` ### C Prototype -`u8 djui_hud_get_font(void);` +`s8 djui_hud_get_font(void);` [:arrow_up_small:](#) @@ -2928,16 +2929,18 @@ Resets the current DJUI HUD color Gets the current DJUI HUD rotation ### Lua Example -`local hudUtilsRotationValue = djui_hud_get_rotation()` +`local rotation, pivotX, pivotY = djui_hud_get_rotation()` ### Parameters - None ### Returns -- [HudUtilsRotation](structs.md#HudUtilsRotation) +- `integer` +- `number` +- `number` ### C Prototype -`struct HudUtilsRotation* djui_hud_get_rotation(void);` +`void djui_hud_get_rotation(RET s16 *rotation, RET f32 *pivotX, RET f32 *pivotY);` [:arrow_up_small:](#) @@ -2990,7 +2993,79 @@ Sets the current DJUI HUD rotation interpolated - None ### C Prototype -`void djui_hud_set_rotation_interpolated(s32 prevRotation, f32 prevPivotX, f32 prevPivotY, s32 rotation, f32 pivotX, f32 pivotY);` +`void djui_hud_set_rotation_interpolated(s16 prevRotation, f32 prevPivotX, f32 prevPivotY, s16 rotation, f32 pivotX, f32 pivotY);` + +[:arrow_up_small:](#) + +
+ +## [djui_hud_get_text_alignment](#djui_hud_get_text_alignment) + +### Description +Gets the current DJUI HUD text alignment + +### Lua Example +`local textHAlign, textVAlign = djui_hud_get_text_alignment()` + +### Parameters +- None + +### Returns +- `number` +- `number` + +### C Prototype +`void djui_hud_get_text_alignment(RET f32 *textHAlign, RET f32 *textVAlign);` + +[:arrow_up_small:](#) + +
+ +## [djui_hud_set_text_alignment](#djui_hud_set_text_alignment) + +### Description +Sets the current DJUI HUD text alignment + +### Lua Example +`djui_hud_set_text_alignment(textHAlign, textVAlign)` + +### Parameters +| Field | Type | +| ----- | ---- | +| textHAlign | `number` | +| textVAlign | `number` | + +### Returns +- None + +### C Prototype +`void djui_hud_set_text_alignment(f32 textHAlign, f32 textVAlign);` + +[:arrow_up_small:](#) + +
+ +## [djui_hud_set_text_alignment_interpolated](#djui_hud_set_text_alignment_interpolated) + +### Description +Sets the current DJUI HUD text alignment interpolated + +### Lua Example +`djui_hud_set_text_alignment_interpolated(prevTextHAlign, prevTextVAlign, textHAlign, textVAlign)` + +### Parameters +| Field | Type | +| ----- | ---- | +| prevTextHAlign | `number` | +| prevTextVAlign | `number` | +| textHAlign | `number` | +| textVAlign | `number` | + +### Returns +- None + +### C Prototype +`void djui_hud_set_text_alignment_interpolated(f32 prevTextHAlign, f32 prevTextVAlign, f32 textHAlign, f32 textVAlign);` [:arrow_up_small:](#) diff --git a/docs/lua/functions-4.md b/docs/lua/functions-4.md index 6c8cb8c8b..4bd956272 100644 --- a/docs/lua/functions-4.md +++ b/docs/lua/functions-4.md @@ -5134,7 +5134,9 @@ Calculates the distance between two points in 3D space (`from` and `to`), as wel | to | [Vec3f](structs.md#Vec3f) | ### Returns -- None +- `number` +- `integer` +- `integer` ### C Prototype `void vec3f_get_dist_and_angle(Vec3f from, Vec3f to, RET f32 *dist, RET s16 *pitch, RET s16 *yaw);` diff --git a/docs/lua/functions-5.md b/docs/lua/functions-5.md index 507dd7401..91636f399 100644 --- a/docs/lua/functions-5.md +++ b/docs/lua/functions-5.md @@ -2945,7 +2945,7 @@ Determines an object's forward speed multiplier. | floor_nY | `number` | ### Returns -- None +- `number` ### C Prototype `void calc_obj_friction(RET f32 *objFriction, f32 floor_nY);` @@ -4419,7 +4419,7 @@ Begin by increasing the current object's scale by `scaleVel`, and slowly decreas | blinkLength | `integer` | ### Returns -- None +- `integer` ### C Prototype `void obj_update_blinking(INOUT s32 *blinkTimer, s16 baseCycleLength, s16 cycleLengthRange, s16 blinkLength);` @@ -4743,7 +4743,8 @@ Treats far home as Mario. Returns the distance and angle to the nearest player | threshold | `number` | ### Returns -- None +- `integer` +- `integer` ### C Prototype `void treat_far_home_as_mario(f32 threshold, RET s32* distanceToPlayer, RET s32* angleToPlayer);` diff --git a/docs/lua/functions-6.md b/docs/lua/functions-6.md index afd96447e..41b220737 100644 --- a/docs/lua/functions-6.md +++ b/docs/lua/functions-6.md @@ -1963,7 +1963,7 @@ Marks an object to be unloaded at the end of the frame | dragStrength | `number` | ### Returns -- None +- `number` ### C Prototype `void apply_drag_to_value(INOUT f32 *value, f32 dragStrength);` diff --git a/docs/lua/functions.md b/docs/lua/functions.md index e2f94b46b..4076375df 100644 --- a/docs/lua/functions.md +++ b/docs/lua/functions.md @@ -763,6 +763,9 @@ - [djui_hud_get_rotation](functions-3.md#djui_hud_get_rotation) - [djui_hud_set_rotation](functions-3.md#djui_hud_set_rotation) - [djui_hud_set_rotation_interpolated](functions-3.md#djui_hud_set_rotation_interpolated) + - [djui_hud_get_text_alignment](functions-3.md#djui_hud_get_text_alignment) + - [djui_hud_set_text_alignment](functions-3.md#djui_hud_set_text_alignment) + - [djui_hud_set_text_alignment_interpolated](functions-3.md#djui_hud_set_text_alignment_interpolated) - [djui_hud_get_screen_width](functions-3.md#djui_hud_get_screen_width) - [djui_hud_get_screen_height](functions-3.md#djui_hud_get_screen_height) - [djui_hud_get_mouse_x](functions-3.md#djui_hud_get_mouse_x) diff --git a/docs/lua/structs.md b/docs/lua/structs.md index f189b286d..79c6d19ff 100644 --- a/docs/lua/structs.md +++ b/docs/lua/structs.md @@ -53,7 +53,6 @@ - [GraphNodeSwitchCase](#GraphNodeSwitchCase) - [GraphNodeTranslation](#GraphNodeTranslation) - [GraphNodeTranslationRotation](#GraphNodeTranslationRotation) -- [HudUtilsRotation](#HudUtilsRotation) - [InstantWarp](#InstantWarp) - [LakituState](#LakituState) - [LevelValues](#LevelValues) @@ -1427,21 +1426,6 @@
-## [HudUtilsRotation](#HudUtilsRotation) - -| Field | Type | Access | -| ----- | ---- | ------ | -| rotation | `number` | | -| rotationDiff | `number` | | -| prevPivotX | `number` | | -| prevPivotY | `number` | | -| pivotX | `number` | | -| pivotY | `number` | | - -[:arrow_up_small:](#) - -
- ## [InstantWarp](#InstantWarp) | Field | Type | Access | diff --git a/src/pc/crash_handler.c b/src/pc/crash_handler.c index 23301f0f2..470f4e2e6 100644 --- a/src/pc/crash_handler.c +++ b/src/pc/crash_handler.c @@ -235,6 +235,8 @@ static void crash_handler_produce_one_frame_callback(void) { // render the line f32 addX = 0; char* c = text->s; + + font->render_begin(); while (*c != '\0') { f32 charWidth = 0.4f; @@ -253,6 +255,7 @@ static void crash_handler_produce_one_frame_callback(void) { create_dl_translation_matrix(DJUI_MTX_NOPUSH, charWidth, 0, 0); c = djui_unicode_next_char(c); } + font->render_end(); // pop gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); diff --git a/src/pc/discord/discord_activity.c b/src/pc/discord/discord_activity.c index 510ac440e..36745701c 100644 --- a/src/pc/discord/discord_activity.c +++ b/src/pc/discord/discord_activity.c @@ -122,11 +122,9 @@ void discord_activity_update(void) { // HACK: give the detail population more space than the Discord details can fit so it gets truncated without cutting off the largest strings char details[512] = { 0 }; - discord_populate_details(details, 512); - char* detailsNoColor = str_remove_color_codes(details); - - snprintf(sCurActivity.details, 128, "%s", detailsNoColor); - free(detailsNoColor); + discord_populate_details(details, ARRAY_COUNT(details)); + djui_text_remove_colors(details); + snprintf(sCurActivity.details, 128, "%s", details); if (!app.activities) { LOG_INFO("no activities"); diff --git a/src/pc/djui/djui.c b/src/pc/djui/djui.c index 5c79ff3c6..3bd9fc8b1 100644 --- a/src/pc/djui/djui.c +++ b/src/pc/djui/djui.c @@ -168,7 +168,8 @@ void djui_lua_error_clear(void) { void djui_reset_hud_params(void) { djui_hud_set_resolution(RESOLUTION_DJUI); djui_hud_set_font(FONT_NORMAL); - djui_hud_set_rotation(0, 0, 0); + djui_hud_set_rotation(0, ROTATION_PIVOT_X_LEFT, ROTATION_PIVOT_Y_TOP); + djui_hud_set_text_alignment(TEXT_HALIGN_LEFT, TEXT_VALIGN_TOP); djui_hud_reset_color(); djui_hud_set_filter(FILTER_NEAREST); djui_hud_reset_viewport(); diff --git a/src/pc/djui/djui_font.c b/src/pc/djui/djui_font.c index f2d88cc88..4b649d784 100644 --- a/src/pc/djui/djui_font.c +++ b/src/pc/djui/djui_font.c @@ -7,7 +7,7 @@ // font 0 (built-in normal font) // /////////////////////////////////// -static void djui_font_normal_render_char(char* c) { +static void djui_font_normal_render_char(const char* c) { // replace undisplayable characters if (*c == ' ') { return; } @@ -18,16 +18,16 @@ static void djui_font_normal_render_char(char* c) { u32 tx = index % 64; u32 ty = index / 64; extern ALIGNED8 const Texture texture_font_jp[]; - djui_gfx_render_texture_tile(texture_font_jp, 512, 1024, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 8, ty * 16, 8, 16, false, true); + djui_gfx_render_texture_tile_font(texture_font_jp, 512, 1024, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 8, ty * 16, 8, 16); } else { u32 tx = index % 32; u32 ty = index / 32; extern ALIGNED8 const Texture texture_font_normal[]; - djui_gfx_render_texture_tile(texture_font_normal, 256, 128, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 8, ty * 16, 8, 16, false, true); + djui_gfx_render_texture_tile_font(texture_font_normal, 256, 128, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 8, ty * 16, 8, 16); } } -static f32 djui_font_normal_char_width(char* c) { +static f32 djui_font_normal_char_width(const char* c) { if (*c == ' ') { return configExCoopTheme ? 6 / 32.0f : 0.30f; } extern const f32 font_normal_widths[]; return djui_unicode_get_sprite_width(c, font_normal_widths, 32.0f); @@ -41,7 +41,9 @@ static const struct DjuiFont sDjuiFontNormal = { .yOffset = 0.0f, .defaultFontScale = 32.0f, .textBeginDisplayList = NULL, + .render_begin = djui_gfx_render_texture_tile_font_begin, .render_char = djui_font_normal_render_char, + .render_end = djui_gfx_render_texture_tile_font_end, .char_width = djui_font_normal_char_width, }; @@ -49,7 +51,7 @@ static const struct DjuiFont sDjuiFontNormal = { // font 1 (custom title font) // //////////////////////////////// -static void djui_font_title_render_char(char* c) { +static void djui_font_title_render_char(const char* c) { // replace undisplayable characters if (*c == ' ') { return; } @@ -64,10 +66,10 @@ static void djui_font_title_render_char(char* c) { u32 ty = index / 16; extern ALIGNED8 const Texture texture_font_title[]; - djui_gfx_render_texture_tile(texture_font_title, 1024, 512, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 64, ty * 64, 64, 64, false, true); + djui_gfx_render_texture_tile_font(texture_font_title, 1024, 512, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 64, ty * 64, 64, 64); } -static f32 djui_font_title_char_width(char* text) { +static f32 djui_font_title_char_width(const char* text) { char c = *text; if (c == ' ') { return 0.30f; } c = djui_unicode_get_base_char(text); @@ -83,7 +85,9 @@ static const struct DjuiFont sDjuiFontTitle = { .yOffset = 0.0f, .defaultFontScale = 64.0f, .textBeginDisplayList = NULL, + .render_begin = djui_gfx_render_texture_tile_font_begin, .render_char = djui_font_title_render_char, + .render_end = djui_gfx_render_texture_tile_font_end, .char_width = djui_font_title_char_width, }; @@ -124,27 +128,29 @@ static u8 djui_font_hud_index(char c) { return c; } -static void djui_font_hud_render_char(char* text) { +static void djui_font_hud_render_char(const char* text) { char c = *text; if (c == ' ') { return; } c = djui_unicode_get_base_char(text); u8 index = djui_font_hud_index(c); - djui_gfx_render_texture(main_hud_lut[index], 16, 16, G_IM_FMT_RGBA, G_IM_SIZ_16b, djui_hud_get_filter()); + djui_gfx_render_texture_font(main_hud_lut[index], 16, 16, G_IM_FMT_RGBA, G_IM_SIZ_16b); } -static f32 djui_font_hud_char_width(UNUSED char* text) { +static f32 djui_font_hud_char_width(UNUSED const char* text) { return 0.75f; } static const struct DjuiFont sDjuiFontHud = { .charWidth = 1.0f, .charHeight = 0.9f, - .lineHeight = 0.7f, + .lineHeight = 1.25f, .xOffset = 0.0f, .yOffset = 0.0f, .defaultFontScale = 16.0f, .textBeginDisplayList = NULL, + .render_begin = djui_gfx_render_texture_font_begin, .render_char = djui_font_hud_render_char, + .render_end = djui_gfx_render_texture_font_end, .char_width = djui_font_hud_char_width, }; @@ -152,7 +158,7 @@ static const struct DjuiFont sDjuiFontHud = { // font 3 (DJ's aliased font) // //////////////////////////////// -static void djui_font_aliased_render_char(char* c) { +static void djui_font_aliased_render_char(const char* c) { // replace undisplayable characters if (*c == ' ') { return; } @@ -163,16 +169,16 @@ static void djui_font_aliased_render_char(char* c) { u32 tx = index % 64; u32 ty = index / 64; extern ALIGNED8 const Texture texture_font_jp_aliased[]; - djui_gfx_render_texture_tile(texture_font_jp_aliased, 1024, 2048, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 16, ty * 32, 16, 32, false, true); + djui_gfx_render_texture_tile_font(texture_font_jp_aliased, 1024, 2048, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 16, ty * 32, 16, 32); } else { u32 tx = index % 32; u32 ty = index / 32; extern ALIGNED8 const Texture texture_font_aliased[]; - djui_gfx_render_texture_tile(texture_font_aliased, 512, 256, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 16, ty * 32, 16, 32, false, true); + djui_gfx_render_texture_tile_font(texture_font_aliased, 512, 256, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 16, ty * 32, 16, 32); } } -static f32 djui_font_aliased_char_width(char* c) { +static f32 djui_font_aliased_char_width(const char* c) { if (*c == ' ') { return 6 / 32.0f; } extern const f32 font_aliased_widths[]; return djui_unicode_get_sprite_width(c, font_aliased_widths, 1.0f) / 32.0f; @@ -186,7 +192,9 @@ static const struct DjuiFont sDjuiFontAliased = { .lineHeight = 0.8125f, .defaultFontScale = 32.0f, .textBeginDisplayList = NULL, + .render_begin = djui_gfx_render_texture_tile_font_begin, .render_char = djui_font_aliased_render_char, + .render_end = djui_gfx_render_texture_tile_font_end, .char_width = djui_font_aliased_char_width, }; @@ -194,7 +202,7 @@ static const struct DjuiFont sDjuiFontAliased = { // font 4/5 (custom hud font/recolor) // //////////////////////////////////////// -static void djui_font_custom_hud_render_char(char* c) { +static void djui_font_custom_hud_render_char(const char* c) { // replace undisplayable characters if (*c == ' ') { return; } @@ -204,10 +212,10 @@ static void djui_font_custom_hud_render_char(char* c) { u32 ty = index / 16; extern ALIGNED8 const Texture texture_font_hud[]; - djui_gfx_render_texture_tile(texture_font_hud, 512, 512, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 32, ty * 32, 32, 32, false, true); + djui_gfx_render_texture_tile_font(texture_font_hud, 512, 512, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 32, ty * 32, 32, 32); } -static void djui_font_custom_hud_recolor_render_char(char* c) { +static void djui_font_custom_hud_recolor_render_char(const char* c) { // replace undisplayable characters if (*c == ' ') { return; } @@ -217,10 +225,10 @@ static void djui_font_custom_hud_recolor_render_char(char* c) { u32 ty = index / 16; extern ALIGNED8 const Texture texture_font_hud_recolor[]; - djui_gfx_render_texture_tile(texture_font_hud_recolor, 512, 512, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 32, ty * 32, 32, 32, false, true); + djui_gfx_render_texture_tile_font(texture_font_hud_recolor, 512, 512, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 32, ty * 32, 32, 32); } -static f32 djui_font_custom_hud_char_width(char* text) { +static f32 djui_font_custom_hud_char_width(const char* text) { char c = *text; if (c == ' ') { return 0.3750f; } c = djui_unicode_get_base_char(text); @@ -236,7 +244,9 @@ static const struct DjuiFont sDjuiFontCustomHud = { .yOffset = -10.25f, .defaultFontScale = 32.0f, .textBeginDisplayList = NULL, + .render_begin = djui_gfx_render_texture_tile_font_begin, .render_char = djui_font_custom_hud_render_char, + .render_end = djui_gfx_render_texture_tile_font_end, .char_width = djui_font_custom_hud_char_width, }; @@ -248,7 +258,9 @@ static const struct DjuiFont sDjuiFontCustomHudRecolor = { .yOffset = -10.25f, .defaultFontScale = 32.0f, .textBeginDisplayList = NULL, + .render_begin = djui_gfx_render_texture_tile_font_begin, .render_char = djui_font_custom_hud_recolor_render_char, + .render_end = djui_gfx_render_texture_tile_font_end, .char_width = djui_font_custom_hud_char_width, }; @@ -256,7 +268,7 @@ static const struct DjuiFont sDjuiFontCustomHudRecolor = { // font 6 (special font) // /////////////////////////// -static void djui_font_special_render_char(char* c) { +static void djui_font_special_render_char(const char* c) { // replace undisplayable characters if (*c == ' ') { return; } @@ -266,17 +278,17 @@ static void djui_font_special_render_char(char* c) { u32 tx = index % 64; u32 ty = index / 64; extern ALIGNED8 const Texture texture_font_jp[]; - djui_gfx_render_texture_tile(texture_font_jp, 512, 1024, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 8, ty * 16, 8, 16, false, true); + djui_gfx_render_texture_tile_font(texture_font_jp, 512, 1024, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 8, ty * 16, 8, 16); } else { u32 tx = index % 32; u32 ty = index / 32; extern ALIGNED8 const Texture texture_font_special[]; - djui_gfx_render_texture_tile(texture_font_special, 256, 128, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 8, ty * 16, 8, 16, false, true); + djui_gfx_render_texture_tile_font(texture_font_special, 256, 128, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 8, ty * 16, 8, 16); } } -static f32 djui_font_special_char_width(char* c) { +static f32 djui_font_special_char_width(const char* c) { if (*c == ' ') { return 0.5f; } extern const f32 font_special_widths[]; return djui_unicode_get_sprite_width(c, font_special_widths, 32.0f); @@ -290,7 +302,9 @@ static const struct DjuiFont sDjuiFontSpecial = { .yOffset = 0.0f, .defaultFontScale = 32.0f, .textBeginDisplayList = NULL, + .render_begin = djui_gfx_render_texture_tile_font_begin, .render_char = djui_font_special_render_char, + .render_end = djui_gfx_render_texture_tile_font_end, .char_width = djui_font_special_char_width, }; diff --git a/src/pc/djui/djui_font.h b/src/pc/djui/djui_font.h index 02d3eef8a..7acf72a28 100644 --- a/src/pc/djui/djui_font.h +++ b/src/pc/djui/djui_font.h @@ -9,8 +9,10 @@ struct DjuiFont { f32 yOffset; f32 defaultFontScale; const Gfx* textBeginDisplayList; - void (*render_char)(char*); - f32 (*char_width)(char*); + void (*render_begin)(); + void (*render_char)(const char*); + void (*render_end)(); + f32 (*char_width)(const char*); }; extern const struct DjuiFont* gDjuiFonts[]; diff --git a/src/pc/djui/djui_gfx.c b/src/pc/djui/djui_gfx.c index 8ebd6c488..4319782c3 100644 --- a/src/pc/djui/djui_gfx.c +++ b/src/pc/djui/djui_gfx.c @@ -1,6 +1,7 @@ #include #include "sm64.h" #include "djui.h" +#include "djui_hud_utils.h" #include "game/ingame_menu.h" #include "game/segment2.h" #include "pc/pc_main.h" @@ -118,7 +119,93 @@ void djui_gfx_render_texture(const Texture* texture, u32 w, u32 h, u8 fmt, u8 si gSPDisplayList(gDisplayListHead++, dl_djui_image); } -void djui_gfx_render_texture_tile(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz, u32 tileX, u32 tileY, u32 tileW, u32 tileH, bool filter, bool font) { +void djui_gfx_render_texture_tile(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz, u32 tileX, u32 tileY, u32 tileW, u32 tileH, bool filter) { + if (!gDisplayListHead) { + LOG_ERROR("Retrieved a null displaylist head"); + return; + } + + if (!texture) { + LOG_ERROR("Attempted to render null texture"); + return; + } + + Vtx *vtx = alloc_display_list(sizeof(Vtx) * 4); + if (!vtx) { + LOG_ERROR("Failed to allocate vertices"); + return; + } + + f32 aspect = tileH ? ((f32)tileW / (f32)tileH) : 1; + + vtx[0] = (Vtx) {{{ 0, -1, 0 }, 0, { ( tileX * 2048.0f) / (f32)w + 1, ((tileY + tileH) * 2048.0f) / (f32)h + 1 }, { 0xff, 0xff, 0xff, 0xff }}}; + vtx[2] = (Vtx) {{{ 1 * aspect, 0, 0 }, 0, { ((tileX + tileW) * 2048.0f) / (f32)w + 1, ( tileY * 2048.0f) / (f32)h + 1 }, { 0xff, 0xff, 0xff, 0xff }}}; + vtx[1] = (Vtx) {{{ 1 * aspect, -1, 0 }, 0, { ((tileX + tileW) * 2048.0f) / (f32)w + 1, ((tileY + tileH) * 2048.0f) / (f32)h + 1 }, { 0xff, 0xff, 0xff, 0xff }}}; + vtx[3] = (Vtx) {{{ 0, 0, 0 }, 0, { ( tileX * 2048.0f) / (f32)w + 1, ( tileY * 2048.0f) / (f32)h + 1 }, { 0xff, 0xff, 0xff, 0xff }}}; + + gSPClearGeometryMode(gDisplayListHead++, G_LIGHTING | G_CULL_BOTH); + gDPSetCombineMode(gDisplayListHead++, G_CC_FADEA, G_CC_FADEA); + gDPSetRenderMode(gDisplayListHead++, G_RM_XLU_SURF, G_RM_XLU_SURF2); + gDPSetTextureFilter(gDisplayListHead++, filter ? G_TF_BILERP : G_TF_POINT); + + gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON); + + gDPSetTextureOverrideDjui(gDisplayListHead++, texture, djui_gfx_power_of_two(w), djui_gfx_power_of_two(h), fmt, siz); + gDPLoadTextureBlockWithoutTexture(gDisplayListHead++, NULL, G_IM_FMT_RGBA, G_IM_SIZ_16b, 64, 64, 0, G_TX_CLAMP, G_TX_CLAMP, 0, 0, 0, 0); + + *(gDisplayListHead++) = (Gfx) gsSPExecuteDjui(G_TEXOVERRIDE_DJUI); + + gSPVertexNonGlobal(gDisplayListHead++, vtx, 4, 0); + *(gDisplayListHead++) = (Gfx) gsSPExecuteDjui(G_TEXCLIP_DJUI); + gSP2TrianglesDjui(gDisplayListHead++, 0, 1, 2, 0x0, 0, 2, 3, 0x0); + + gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_OFF); + gDPSetCombineMode(gDisplayListHead++, G_CC_SHADE, G_CC_SHADE); + gSPSetGeometryMode(gDisplayListHead++, G_LIGHTING | G_CULL_BACK); +} + +void djui_gfx_render_texture_font_begin() { + gSPClearGeometryMode(gDisplayListHead++, G_LIGHTING | G_CULL_BOTH); + gDPSetCombineMode(gDisplayListHead++, G_CC_FADEA, G_CC_FADEA); + gDPSetRenderMode(gDisplayListHead++, G_RM_XLU_SURF, G_RM_XLU_SURF2); + gDPSetTextureFilter(gDisplayListHead++, djui_hud_get_filter() ? G_TF_BILERP : G_TF_POINT); + gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON); + gDPLoadTextureBlockWithoutTexture(gDisplayListHead++, NULL, G_IM_FMT_RGBA, G_IM_SIZ_16b, 64, 64, 0, G_TX_CLAMP, G_TX_CLAMP, 0, 0, 0, 0); +} + +void djui_gfx_render_texture_font(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz) { + if (!gDisplayListHead) { + LOG_ERROR("Retrieved a null displaylist head"); + return; + } + + if (!texture) { + LOG_ERROR("Attempted to render null texture"); + return; + } + + gDPSetTextureOverrideDjui(gDisplayListHead++, texture, djui_gfx_power_of_two(w), djui_gfx_power_of_two(h), fmt, siz); + *(gDisplayListHead++) = (Gfx) gsSPExecuteDjui(G_TEXOVERRIDE_DJUI); + gSPVertexNonGlobal(gDisplayListHead++, vertex_djui_image, 4, 0); + gSP2Triangles(gDisplayListHead++, 0, 1, 2, 0x0, 0, 2, 3, 0x0); +} + +void djui_gfx_render_texture_font_end() { + gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_OFF); + gDPSetCombineMode(gDisplayListHead++, G_CC_SHADE, G_CC_SHADE); + gSPSetGeometryMode(gDisplayListHead++, G_LIGHTING | G_CULL_BACK); +} + +void djui_gfx_render_texture_tile_font_begin() { + gSPClearGeometryMode(gDisplayListHead++, G_LIGHTING | G_CULL_BOTH); + gDPSetCombineMode(gDisplayListHead++, G_CC_FADEA, G_CC_FADEA); + gDPSetRenderMode(gDisplayListHead++, G_RM_XLU_SURF, G_RM_XLU_SURF2); + gDPSetTextureFilter(gDisplayListHead++, G_TF_POINT); + gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON); + gDPLoadTextureBlockWithoutTexture(gDisplayListHead++, NULL, G_IM_FMT_RGBA, G_IM_SIZ_16b, 64, 64, 0, G_TX_CLAMP, G_TX_CLAMP, 0, 0, 0, 0); +} + +void djui_gfx_render_texture_tile_font(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz, u32 tileX, u32 tileY, u32 tileW, u32 tileH) { if (!gDisplayListHead) { LOG_ERROR("Retrieved a null displaylist head"); return; @@ -139,29 +226,21 @@ void djui_gfx_render_texture_tile(const Texture* texture, u32 w, u32 h, u8 fmt, // I don't know why adding 1 to all of the UVs seems to fix rendering, but it does... // this should be tested carefully. it definitely fixes some stuff, but what does it break? - f32 offsetX = (font ? -1024.0f / (f32)w : 0) + 1; - f32 offsetY = (font ? -1024.0f / (f32)h : 0) + 1; + f32 offsetX = (-1024.0f / (f32)w) + 1; + f32 offsetY = (-1024.0f / (f32)h) + 1; vtx[0] = (Vtx) {{{ 0, -1, 0 }, 0, { ( tileX * 2048.0f) / (f32)w + offsetX, ((tileY + tileH) * 2048.0f) / (f32)h + offsetY }, { 0xff, 0xff, 0xff, 0xff }}}; vtx[2] = (Vtx) {{{ 1 * aspect, 0, 0 }, 0, { ((tileX + tileW) * 2048.0f) / (f32)w + offsetX, ( tileY * 2048.0f) / (f32)h + offsetY }, { 0xff, 0xff, 0xff, 0xff }}}; vtx[1] = (Vtx) {{{ 1 * aspect, -1, 0 }, 0, { ((tileX + tileW) * 2048.0f) / (f32)w + offsetX, ((tileY + tileH) * 2048.0f) / (f32)h + offsetY }, { 0xff, 0xff, 0xff, 0xff }}}; vtx[3] = (Vtx) {{{ 0, 0, 0 }, 0, { ( tileX * 2048.0f) / (f32)w + offsetX, ( tileY * 2048.0f) / (f32)h + offsetY }, { 0xff, 0xff, 0xff, 0xff }}}; - gSPClearGeometryMode(gDisplayListHead++, G_LIGHTING | G_CULL_BOTH); - gDPSetCombineMode(gDisplayListHead++, G_CC_FADEA, G_CC_FADEA); - gDPSetRenderMode(gDisplayListHead++, G_RM_XLU_SURF, G_RM_XLU_SURF2); - gDPSetTextureFilter(gDisplayListHead++, filter ? G_TF_BILERP : G_TF_POINT); - - gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON); - gDPSetTextureOverrideDjui(gDisplayListHead++, texture, djui_gfx_power_of_two(w), djui_gfx_power_of_two(h), fmt, siz); - gDPLoadTextureBlockWithoutTexture(gDisplayListHead++, NULL, G_IM_FMT_RGBA, G_IM_SIZ_16b, 64, 64, 0, G_TX_CLAMP, G_TX_CLAMP, 0, 0, 0, 0); - *(gDisplayListHead++) = (Gfx) gsSPExecuteDjui(G_TEXOVERRIDE_DJUI); - gSPVertexNonGlobal(gDisplayListHead++, vtx, 4, 0); *(gDisplayListHead++) = (Gfx) gsSPExecuteDjui(G_TEXCLIP_DJUI); gSP2TrianglesDjui(gDisplayListHead++, 0, 1, 2, 0x0, 0, 2, 3, 0x0); +} +void djui_gfx_render_texture_tile_font_end() { gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_OFF); gDPSetCombineMode(gDisplayListHead++, G_CC_SHADE, G_CC_SHADE); gSPSetGeometryMode(gDisplayListHead++, G_LIGHTING | G_CULL_BACK); diff --git a/src/pc/djui/djui_gfx.h b/src/pc/djui/djui_gfx.h index 8da361e3f..743ca4460 100644 --- a/src/pc/djui/djui_gfx.h +++ b/src/pc/djui/djui_gfx.h @@ -16,7 +16,15 @@ void djui_gfx_displaylist_end(void); f32 djui_gfx_get_scale(void); void djui_gfx_render_texture(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz, bool filter); -void djui_gfx_render_texture_tile(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz, u32 tileX, u32 tileY, u32 tileW, u32 tileH, bool filter, bool font); +void djui_gfx_render_texture_tile(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz, u32 tileX, u32 tileY, u32 tileW, u32 tileH, bool filter); + +void djui_gfx_render_texture_font_begin(); +void djui_gfx_render_texture_font(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz); +void djui_gfx_render_texture_font_end(); + +void djui_gfx_render_texture_tile_font_begin(); +void djui_gfx_render_texture_tile_font(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz, u32 tileX, u32 tileY, u32 tileW, u32 tileH); +void djui_gfx_render_texture_tile_font_end(); void gfx_get_dimensions(u32* width, u32* height); diff --git a/src/pc/djui/djui_hud_utils.c b/src/pc/djui/djui_hud_utils.c index 5d7a06d5b..8ad0a5b15 100644 --- a/src/pc/djui/djui_hud_utils.c +++ b/src/pc/djui/djui_hud_utils.c @@ -24,13 +24,45 @@ #include "engine/math_util.h" -static enum HudUtilsResolution sResolution = RESOLUTION_DJUI; -static enum HudUtilsFilter sFilter = FILTER_NEAREST; -static enum DjuiFontType sFont = FONT_NORMAL; -static struct HudUtilsRotation sRotation = { 0, 0, 0, 0, 0, 0 }; -static struct DjuiColor sColor = { 255, 255, 255, 255 }; +#define INTERP_INIT(v) {v, v} + +typedef struct { + f32 prev, curr; +} InterpFieldF32; + +struct HudUtilsState { + enum HudUtilsResolution resolution; + enum HudUtilsFilter filter; + enum DjuiFontType font; + struct DjuiColor color; + struct { + InterpFieldF32 degrees; + InterpFieldF32 pivotX; + InterpFieldF32 pivotY; + } rotation; + struct { + InterpFieldF32 h; + InterpFieldF32 v; + } textAlignment; +}; + +static struct HudUtilsState sHudUtilsState = { + .resolution = RESOLUTION_DJUI, + .filter = FILTER_NEAREST, + .font = FONT_NORMAL, + .color = { 255, 255, 255, 255 }, + .rotation = { + .degrees = INTERP_INIT(0), + .pivotX = INTERP_INIT(ROTATION_PIVOT_X_LEFT), + .pivotY = INTERP_INIT(ROTATION_PIVOT_Y_TOP), + }, + .textAlignment = { + .h = INTERP_INIT(TEXT_HALIGN_LEFT), + .v = INTERP_INIT(TEXT_VALIGN_TOP), + }, +}; + static struct DjuiColor sRefColor = { 255, 255, 255, 255 }; -static bool sLegacy = false; f32 gDjuiHudUtilsZ = 0; bool gDjuiHudLockMouse = false; @@ -67,8 +99,16 @@ struct GlobalTextures gGlobalTextures = { .wario_head = { .texture = texture_hud_char_wario_head, "texture_hud_char_wario_head", .width = 16, .height = 16, .format = G_IM_FMT_RGBA, .size = G_IM_SIZ_16b } }; +static inline const struct DjuiFont *djui_hud_get_text_font() { + return gDjuiFonts[sHudUtilsState.font < 0 ? FONT_NORMAL : sHudUtilsState.font]; +} + +static inline bool djui_hud_text_font_is_legacy() { + return sHudUtilsState.font < 0; +} + static void djui_hud_position_translate(f32* x, f32* y) { - if (sResolution == RESOLUTION_DJUI) { + if (sHudUtilsState.resolution == RESOLUTION_DJUI) { djui_gfx_position_translate(x, y); } else { *x = GFX_DIMENSIONS_FROM_LEFT_EDGE(0) + *x; @@ -77,7 +117,7 @@ static void djui_hud_position_translate(f32* x, f32* y) { } static void djui_hud_size_translate(f32* size) { - if (sResolution == RESOLUTION_DJUI) { + if (sHudUtilsState.resolution == RESOLUTION_DJUI) { djui_gfx_size_translate(size); } } @@ -90,7 +130,7 @@ static void djui_hud_translate_positions(f32 *outX, f32 *outY, f32 *outW, f32 *o *outY -= SCREEN_HEIGHT; // translate scale - if (sResolution == RESOLUTION_DJUI) { + if (sHudUtilsState.resolution == RESOLUTION_DJUI) { u32 windowWidth, windowHeight; gfx_get_dimensions(&windowWidth, &windowHeight); f32 screenWidth = (f32) windowWidth / djui_gfx_get_scale(); @@ -105,22 +145,31 @@ static void djui_hud_translate_positions(f32 *outX, f32 *outY, f32 *outW, f32 *o //////////// #define MAX_INTERP_HUD 512 -struct InterpHud { - Gfx* headPos; - f32 z; - f32 prevX; - f32 prevY; - f32 x; - f32 y; - f32 prevScaleW; - f32 prevScaleH; - f32 scaleW; - f32 scaleH; - f32 width; - f32 height; - enum HudUtilsResolution resolution; - struct HudUtilsRotation rotation; + +enum InterpHudType { + INTERP_HUD_TRANSLATION, + INTERP_HUD_ROTATION, + INTERP_HUD_SCALE, + INTERP_HUD_HALIGN, + INTERP_HUD_VALIGN, + INTERP_HUD_NEW_LINE, }; + +typedef struct { + enum InterpHudType type; + Gfx *pos; + f32 params[1]; // we don't need more for now +} InterpHudGfx; + +struct InterpHud { + f32 z; + InterpFieldF32 posX, posY; + InterpFieldF32 scaleX, scaleY; + f32 width, height; + struct HudUtilsState state; + struct GrowingArray *gfx; +}; + static struct InterpHud sInterpHuds[MAX_INTERP_HUD] = { 0 }; static u16 sInterpHudCount = 0; static u8 sColorAltered = FALSE; @@ -132,140 +181,211 @@ void patch_djui_hud_before(void) { void patch_djui_hud(f32 delta) { f32 savedZ = gDjuiHudUtilsZ; Gfx* savedHeadPos = gDisplayListHead; - enum HudUtilsResolution savedResolution = sResolution; - struct HudUtilsRotation savedRotation = sRotation; + struct HudUtilsState savedState = sHudUtilsState; + for (u16 i = 0; i < sInterpHudCount; i++) { struct InterpHud* interp = &sInterpHuds[i]; - f32 x = delta_interpolate_f32(interp->prevX, interp->x, delta); - f32 y = delta_interpolate_f32(interp->prevY, interp->y, delta); - f32 scaleW = delta_interpolate_f32(interp->prevScaleW, interp->scaleW, delta); - f32 scaleH = delta_interpolate_f32(interp->prevScaleH, interp->scaleH, delta); - sResolution = interp->resolution; - sRotation = interp->rotation; + f32 x = delta_interpolate_f32(interp->posX.prev, interp->posX.curr, delta); + f32 y = delta_interpolate_f32(interp->posY.prev, interp->posY.curr, delta); + f32 scaleW = delta_interpolate_f32(interp->scaleX.prev, interp->scaleX.curr, delta); + f32 scaleH = delta_interpolate_f32(interp->scaleY.prev, interp->scaleY.curr, delta); + sHudUtilsState = interp->state; gDjuiHudUtilsZ = interp->z; - gDisplayListHead = interp->headPos; - // translate position - f32 translatedX = x; - f32 translatedY = y; - djui_hud_position_translate(&translatedX, &translatedY); - create_dl_translation_matrix(DJUI_MTX_PUSH, translatedX, translatedY, gDjuiHudUtilsZ); + for (u32 j = 0; j != interp->gfx->count; ++j) { + const InterpHudGfx *gfx = interp->gfx->buffer[j]; + gDisplayListHead = gfx->pos; - // rotate - f32 translatedW = scaleW; - f32 translatedH = scaleH; - djui_hud_size_translate(&translatedW); - djui_hud_size_translate(&translatedH); - if (sRotation.rotationDiff != 0 || sRotation.rotation != 0) { - s32 rotation = delta_interpolate_s32(sRotation.rotation - sRotation.rotationDiff, sRotation.rotation, delta); - f32 pivotX = delta_interpolate_f32(sRotation.prevPivotX, sRotation.pivotX, delta); - f32 pivotY = delta_interpolate_f32(sRotation.prevPivotY, sRotation.pivotY, delta); - f32 pivotTranslationX = interp->width * translatedW * pivotX; - f32 pivotTranslationY = interp->height * translatedH * pivotY; - create_dl_translation_matrix(DJUI_MTX_NOPUSH, +pivotTranslationX, -pivotTranslationY, 0); - create_dl_rotation_matrix(DJUI_MTX_NOPUSH, rotation, 0, 0, 1); - create_dl_translation_matrix(DJUI_MTX_NOPUSH, -pivotTranslationX, +pivotTranslationY, 0); + switch (gfx->type) { + case INTERP_HUD_TRANSLATION: { + f32 translatedX = x; + f32 translatedY = y; + djui_hud_position_translate(&translatedX, &translatedY); + create_dl_translation_matrix(DJUI_MTX_PUSH, translatedX, translatedY, gDjuiHudUtilsZ); + } break; + + case INTERP_HUD_ROTATION: { + if (sHudUtilsState.rotation.degrees.prev != 0 || sHudUtilsState.rotation.degrees.curr != 0) { + f32 translatedW = scaleW; + f32 translatedH = scaleH; + djui_hud_size_translate(&translatedW); + djui_hud_size_translate(&translatedH); + s16 rotPrev = degrees_to_sm64(sHudUtilsState.rotation.degrees.prev); + s16 rotCurr = degrees_to_sm64(sHudUtilsState.rotation.degrees.curr); + s32 normalizedDiff = (((s32) rotCurr - (s32) rotPrev + 0x8000) & 0xFFFF) - 0x8000; // Fix modular overflow/underflow + s32 rotation = delta_interpolate_s32(rotCurr - normalizedDiff, rotCurr, delta); + f32 pivotX = delta_interpolate_f32(sHudUtilsState.rotation.pivotX.prev, sHudUtilsState.rotation.pivotX.curr, delta); + f32 pivotY = delta_interpolate_f32(sHudUtilsState.rotation.pivotY.prev, sHudUtilsState.rotation.pivotY.curr, delta); + f32 pivotTranslationX = interp->width * translatedW * pivotX; + f32 pivotTranslationY = interp->height * translatedH * pivotY; + create_dl_translation_matrix(DJUI_MTX_NOPUSH, +pivotTranslationX, -pivotTranslationY, 0); + create_dl_rotation_matrix(DJUI_MTX_NOPUSH, sm64_to_degrees(rotation), 0, 0, 1); + create_dl_translation_matrix(DJUI_MTX_NOPUSH, -pivotTranslationX, +pivotTranslationY, 0); + } + } break; + + case INTERP_HUD_SCALE: { + f32 translatedW = scaleW; + f32 translatedH = scaleH; + djui_hud_size_translate(&translatedW); + djui_hud_size_translate(&translatedH); + create_dl_scale_matrix(DJUI_MTX_NOPUSH, interp->width * translatedW, interp->height * translatedH, 1.0f); + } break; + + case INTERP_HUD_HALIGN: { + f32 textHAlign = delta_interpolate_f32(sHudUtilsState.textAlignment.h.prev, sHudUtilsState.textAlignment.h.curr, delta); + f32 lineWidth = gfx->params[0]; + create_dl_translation_matrix(DJUI_MTX_NOPUSH, -lineWidth * textHAlign, 0, 0); + } break; + + case INTERP_HUD_VALIGN: { + f32 textVAlign = delta_interpolate_f32(sHudUtilsState.textAlignment.v.prev, sHudUtilsState.textAlignment.v.curr, delta); + f32 textHeight = gfx->params[0]; + create_dl_translation_matrix(DJUI_MTX_NOPUSH, 0, textHeight * textVAlign, 0); + } break; + + case INTERP_HUD_NEW_LINE: { + const struct DjuiFont *font = djui_hud_get_text_font(); + f32 textHAlign = delta_interpolate_f32(sHudUtilsState.textAlignment.h.prev, sHudUtilsState.textAlignment.h.curr, delta); + f32 lineWidth = gfx->params[0]; + create_dl_translation_matrix(DJUI_MTX_NOPUSH, -lineWidth * (1.f - textHAlign), -font->lineHeight, 0); + } break; + } } - - // scale - create_dl_scale_matrix(DJUI_MTX_NOPUSH, interp->width * translatedW, interp->height * translatedH, 1.0f); } - sResolution = savedResolution; - sRotation = savedRotation; + + sHudUtilsState = savedState; gDisplayListHead = savedHeadPos; gDjuiHudUtilsZ = savedZ; } +struct InterpHud *djui_hud_create_interp() { + if (sInterpHudCount >= MAX_INTERP_HUD) { return NULL; } + + struct InterpHud *interp = &sInterpHuds[sInterpHudCount++]; + interp->z = gDjuiHudUtilsZ; + interp->state = sHudUtilsState; + if (!interp->gfx) { + interp->gfx = growing_array_init(NULL, 8, malloc, free); + } else { + interp->gfx->count = 0; + } + + return interp; +} + +InterpHudGfx *djui_hud_create_interp_gfx(struct InterpHud *interp, enum InterpHudType type) { + if (!interp) { return NULL; } + + InterpHudGfx *gfx = growing_array_alloc(interp->gfx, sizeof(InterpHudGfx)); + gfx->type = type; + gfx->pos = gDisplayListHead; + return gfx; +} + //////////// // others // //////////// u8 djui_hud_get_resolution(void) { - return sResolution; + return sHudUtilsState.resolution; } void djui_hud_set_resolution(enum HudUtilsResolution resolutionType) { if (resolutionType >= RESOLUTION_COUNT) { return; } - sResolution = resolutionType; + sHudUtilsState.resolution = resolutionType; } u8 djui_hud_get_filter(void) { - return sFilter; + return sHudUtilsState.filter; } void djui_hud_set_filter(enum HudUtilsFilter filterType) { if (filterType >= FILTER_COUNT) { return; } - sFilter = filterType; + sHudUtilsState.filter = filterType; } -u8 djui_hud_get_font(void) { - return sFont; +s8 djui_hud_get_font(void) { + return sHudUtilsState.font; } void djui_hud_set_font(s8 fontType) { if (fontType >= FONT_COUNT || fontType < -1) { return; } - sLegacy = fontType == -1; - if (sLegacy) { fontType = 0; } - sFont = fontType; + sHudUtilsState.font = fontType; } struct DjuiColor* djui_hud_get_color(void) { - sRefColor.r = sColor.r; - sRefColor.g = sColor.g; - sRefColor.b = sColor.b; - sRefColor.a = sColor.a; + sRefColor.r = sHudUtilsState.color.r; + sRefColor.g = sHudUtilsState.color.g; + sRefColor.b = sHudUtilsState.color.b; + sRefColor.a = sHudUtilsState.color.a; return &sRefColor; } void djui_hud_set_color(u8 r, u8 g, u8 b, u8 a) { - sColor.r = r; - sColor.g = g; - sColor.b = b; - sColor.a = a; + sHudUtilsState.color.r = r; + sHudUtilsState.color.g = g; + sHudUtilsState.color.b = b; + sHudUtilsState.color.a = a; sColorAltered = TRUE; gDPSetEnvColor(gDisplayListHead++, r, g, b, a); } void djui_hud_reset_color(void) { if (sColorAltered) { - sColor.r = 255; - sColor.g = 255; - sColor.b = 255; - sColor.a = 255; + sHudUtilsState.color.r = 255; + sHudUtilsState.color.g = 255; + sHudUtilsState.color.b = 255; + sHudUtilsState.color.a = 255; sColorAltered = FALSE; gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); } } -struct HudUtilsRotation* djui_hud_get_rotation(void) { - return &sRotation; +void djui_hud_get_rotation(RET s16 *rotation, RET f32 *pivotX, RET f32 *pivotY) { + *rotation = degrees_to_sm64(sHudUtilsState.rotation.degrees.curr); + *pivotX = sHudUtilsState.rotation.pivotX.curr; + *pivotY = sHudUtilsState.rotation.pivotY.curr; } void djui_hud_set_rotation(s16 rotation, f32 pivotX, f32 pivotY) { - sRotation.rotationDiff = 0; - sRotation.prevPivotX = pivotX; - sRotation.prevPivotY = pivotY; - sRotation.rotation = (rotation * 180.f) / 0x8000; - sRotation.pivotX = pivotX; - sRotation.pivotY = pivotY; + sHudUtilsState.rotation.degrees.prev = sHudUtilsState.rotation.degrees.curr = sm64_to_degrees(rotation); + sHudUtilsState.rotation.pivotX.prev = sHudUtilsState.rotation.pivotX.curr = pivotX; + sHudUtilsState.rotation.pivotY.prev = sHudUtilsState.rotation.pivotY.curr = pivotY; } -void djui_hud_set_rotation_interpolated(s32 prevRotation, f32 prevPivotX, f32 prevPivotY, s32 rotation, f32 pivotX, f32 pivotY) { - f32 normalizedDiff = ((rotation - prevRotation + 0x8000) & 0xFFFF) - 0x8000; // Fix modular overflow/underflow - sRotation.rotationDiff = (normalizedDiff * 180.f) / 0x8000; - sRotation.prevPivotX = prevPivotX; - sRotation.prevPivotY = prevPivotY; - sRotation.rotation = (rotation * 180.f) / 0x8000; - sRotation.pivotX = pivotX; - sRotation.pivotY = pivotY; +void djui_hud_set_rotation_interpolated(s16 prevRotation, f32 prevPivotX, f32 prevPivotY, s16 rotation, f32 pivotX, f32 pivotY) { + sHudUtilsState.rotation.degrees.prev = sm64_to_degrees(prevRotation); + sHudUtilsState.rotation.degrees.curr = sm64_to_degrees(rotation); + sHudUtilsState.rotation.pivotX.prev = prevPivotX; + sHudUtilsState.rotation.pivotX.curr = pivotX; + sHudUtilsState.rotation.pivotY.prev = prevPivotY; + sHudUtilsState.rotation.pivotY.curr = pivotY; +} + +void djui_hud_get_text_alignment(RET f32 *textHAlign, RET f32 *textVAlign) { + *textHAlign = sHudUtilsState.textAlignment.h.curr; + *textVAlign = sHudUtilsState.textAlignment.v.curr; +} + +void djui_hud_set_text_alignment(f32 textHAlign, f32 textVAlign) { + sHudUtilsState.textAlignment.h.prev = sHudUtilsState.textAlignment.h.curr = textHAlign; + sHudUtilsState.textAlignment.v.prev = sHudUtilsState.textAlignment.v.curr = textVAlign; +} + +void djui_hud_set_text_alignment_interpolated(f32 prevTextHAlign, f32 prevTextVAlign, f32 textHAlign, f32 textVAlign) { + sHudUtilsState.textAlignment.h.prev = prevTextHAlign; + sHudUtilsState.textAlignment.h.curr = textHAlign; + sHudUtilsState.textAlignment.v.prev = prevTextVAlign; + sHudUtilsState.textAlignment.v.curr = textVAlign; } u32 djui_hud_get_screen_width(void) { u32 windowWidth, windowHeight; gfx_get_dimensions(&windowWidth, &windowHeight); - return (sResolution == RESOLUTION_N64) + return (sHudUtilsState.resolution == RESOLUTION_N64) ? GFX_DIMENSIONS_ASPECT_RATIO * SCREEN_HEIGHT : (windowWidth / djui_gfx_get_scale()); } @@ -274,7 +394,7 @@ u32 djui_hud_get_screen_height(void) { u32 windowWidth, windowHeight; gfx_get_dimensions(&windowWidth, &windowHeight); - return (sResolution == RESOLUTION_N64) + return (sHudUtilsState.resolution == RESOLUTION_N64) ? SCREEN_HEIGHT : (windowHeight / djui_gfx_get_scale()); } @@ -364,23 +484,58 @@ void djui_hud_reset_scissor(void) { f32 djui_hud_measure_text(const char* message) { if (message == NULL) { return 0; } - const struct DjuiFont* font = gDjuiFonts[sFont]; - f32 width = 0; - const char* c = message; - while(*c != '\0') { - width += font->char_width((char*)c) * (sLegacy ? 0.5f : 1.0f); - c = djui_unicode_next_char((char*)c); + const struct DjuiFont* font = djui_hud_get_text_font(); + f32 width = 0, maxWidth = 0; + char *c = (char *) message; + const char *end = message + strlen(message); + while (*c != '\0') { + + // check color code + if (djui_text_parse_color(c, end, false, NULL, &c, NULL)) { + continue; + } + + // new line + if (*c == '\n') { + maxWidth = max(width, maxWidth); + width = 0; + } + + // tab: align to the next (4 x space width) + else if (*c == '\t') { + f32 tabWidth = 4 * font->char_width(" ") * (djui_hud_text_font_is_legacy() ? 0.5f : 1.0f); + width += tabWidth - fmodf(width, tabWidth); + } + + // unprintable chars + else if (!djui_text_is_printable(c)) { + // treat them as empty + } + + // regular chars + else { + width += font->char_width(c) * (djui_hud_text_font_is_legacy() ? 0.5f : 1.0f); + } + + c = djui_unicode_next_char(c); } - return width * font->defaultFontScale; + return max(width, maxWidth) * font->defaultFontScale; } -void djui_hud_print_text(const char* message, f32 x, f32 y, f32 scale) { +static Mtx *allocate_dl_translation_matrix() { + Mtx *matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); + if (matrix == NULL) { return NULL; } + gSPMatrix(gDisplayListHead++, matrix, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); + return matrix; +} + +static void djui_hud_print_text_internal(const char* message, f32 x, f32 y, f32 scale, struct InterpHud *interp) { if (message == NULL) { return; } gDjuiHudUtilsZ += 0.001f; - if (sLegacy) { scale *= 0.5f; } + if (djui_hud_text_font_is_legacy()) { scale *= 0.5f; } - const struct DjuiFont* font = gDjuiFonts[sFont]; + const struct DjuiFont* font = djui_hud_get_text_font(); f32 fontScale = font->defaultFontScale * scale; // setup display list @@ -389,117 +544,147 @@ void djui_hud_print_text(const char* message, f32 x, f32 y, f32 scale) { } // translate position + djui_hud_create_interp_gfx(interp, INTERP_HUD_TRANSLATION); f32 translatedX = x + (font->xOffset * scale); f32 translatedY = y + (font->yOffset * scale); djui_hud_position_translate(&translatedX, &translatedY); create_dl_translation_matrix(DJUI_MTX_PUSH, translatedX, translatedY, gDjuiHudUtilsZ); - // compute font size + // rotate f32 translatedFontSize = fontScale; djui_hud_size_translate(&translatedFontSize); + if (sHudUtilsState.rotation.degrees.prev != 0 || sHudUtilsState.rotation.degrees.curr != 0) { + djui_hud_create_interp_gfx(interp, INTERP_HUD_ROTATION); + f32 pivotTranslationX = font->defaultFontScale * translatedFontSize * sHudUtilsState.rotation.pivotX.curr; + f32 pivotTranslationY = font->defaultFontScale * translatedFontSize * sHudUtilsState.rotation.pivotY.curr; + create_dl_translation_matrix(DJUI_MTX_NOPUSH, +pivotTranslationX, -pivotTranslationY, 0); + create_dl_rotation_matrix(DJUI_MTX_NOPUSH, sHudUtilsState.rotation.degrees.curr, 0, 0, 1); + create_dl_translation_matrix(DJUI_MTX_NOPUSH, -pivotTranslationX, +pivotTranslationY, 0); + } + + // compute font size + djui_hud_create_interp_gfx(interp, INTERP_HUD_SCALE); create_dl_scale_matrix(DJUI_MTX_NOPUSH, translatedFontSize, translatedFontSize, 1.0f); - // render the line - f32 addX = 0; - char* c = (char*)message; - while (*c != '\0') { - f32 charWidth = font->char_width(c); + // allocate the translation matrix for the vertical alignment + InterpHudGfx *valignGfx = djui_hud_create_interp_gfx(interp, INTERP_HUD_VALIGN); + Mtx *valignMatrix = allocate_dl_translation_matrix(); + if (valignMatrix == NULL) { return; } - if (*c == '\n' && *c == ' ') { - addX += charWidth; - c++; + // allocate the translation matrix for the horizontal alignment + InterpHudGfx *halignGfx = djui_hud_create_interp_gfx(interp, INTERP_HUD_HALIGN); + Mtx *halignMatrix = allocate_dl_translation_matrix(); + if (halignMatrix == NULL) { return; } + + // render the line + char* c = (char*)message; + const char *end = message + strlen(message); + f32 lineWidth = 0; + f32 textHeight = font->lineHeight; + + font->render_begin(); + while (*c != '\0') { + + // check color code + struct DjuiColor parsedColor; + if (djui_text_parse_color(c, end, false, &sHudUtilsState.color, &c, &parsedColor)) { + gDPSetEnvColor(gDisplayListHead++, parsedColor.r, parsedColor.g, parsedColor.b, parsedColor.a); + continue; + } + + // new line + if (*c == '\n') { + + // compute the horizontal alignment matrix for the current line + guTranslate(halignMatrix, -lineWidth * sHudUtilsState.textAlignment.h.curr, 0, 0); + if (halignGfx) { halignGfx->params[0] = lineWidth; } + + // allocate a new translation matrix for the next line + halignGfx = djui_hud_create_interp_gfx(interp, INTERP_HUD_HALIGN); + halignMatrix = allocate_dl_translation_matrix(); + if (halignMatrix == NULL) { return; } + + // cancel out the line translation and move to the next line + // this is needed because otherwise the text would be rendered in a staircase way + InterpHudGfx *newlineGfx = djui_hud_create_interp_gfx(interp, INTERP_HUD_NEW_LINE); + create_dl_translation_matrix(DJUI_MTX_NOPUSH, -lineWidth * (1.f - sHudUtilsState.textAlignment.h.curr), -font->lineHeight, 0); + if (newlineGfx) { newlineGfx->params[0] = lineWidth; } + lineWidth = 0; + textHeight += font->lineHeight; + c = djui_unicode_next_char(c); + continue; + } + + // tab: align to the next (4 x space width) + else if (*c == '\t') { + f32 tabWidth = 4 * font->char_width(" "); + f32 newLineWidth = lineWidth + tabWidth - fmodf(lineWidth, tabWidth); + create_dl_translation_matrix(DJUI_MTX_NOPUSH, newLineWidth - lineWidth, 0, 0); + lineWidth = newLineWidth; + c = djui_unicode_next_char(c); + continue; + } + + // unprintable chars + if (!djui_text_is_printable(c)) { + c = djui_unicode_next_char(c); continue; } // render + f32 charWidth = font->char_width(c); font->render_char(c); - create_dl_translation_matrix(DJUI_MTX_NOPUSH, charWidth + addX, 0, 0); - addX = 0; + create_dl_translation_matrix(DJUI_MTX_NOPUSH, charWidth, 0, 0); + lineWidth += charWidth; c = djui_unicode_next_char(c); } + font->render_end(); + + // compute the horizontal alignment matrix for the last line + guTranslate(halignMatrix, -lineWidth * sHudUtilsState.textAlignment.h.curr, 0, 0); + if (halignGfx) { halignGfx->params[0] = lineWidth; } + + // compute the vertical alignment matrix + guTranslate(valignMatrix, 0, textHeight * sHudUtilsState.textAlignment.v.curr, 0); + if (valignGfx) { valignGfx->params[0] = textHeight; } // pop gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); } +void djui_hud_print_text(const char* message, f32 x, f32 y, f32 scale) { + djui_hud_print_text_internal(message, x, y, scale, NULL); +} + void djui_hud_print_text_interpolated(const char* message, f32 prevX, f32 prevY, f32 prevScale, f32 x, f32 y, f32 scale) { if (message == NULL) { return; } - f32 savedZ = gDjuiHudUtilsZ; - gDjuiHudUtilsZ += 0.001f; - if (sLegacy) { - prevScale *= 0.5f; - scale *= 0.5f; + if (djui_hud_text_font_is_legacy()) { prevScale *= 0.5f; } + + struct InterpHud *interp = djui_hud_create_interp(); + if (interp) { + const struct DjuiFont* font = djui_hud_get_text_font(); + interp->posX.prev = prevX; + interp->posY.prev = prevY; + interp->posX.curr = x; + interp->posY.curr = y; + interp->scaleX.prev = prevScale; + interp->scaleY.prev = prevScale; + interp->scaleX.curr = scale; + interp->scaleY.curr = scale; + interp->width = font->defaultFontScale; + interp->height = font->defaultFontScale; } - const struct DjuiFont* font = gDjuiFonts[sFont]; - f32 fontScale = font->defaultFontScale * scale; - - // setup display list - if (font->textBeginDisplayList != NULL) { - gSPDisplayList(gDisplayListHead++, font->textBeginDisplayList); - } - - Gfx* savedHeadPos = gDisplayListHead; - - // translate position - f32 translatedX = x + (font->xOffset * scale); - f32 translatedY = y + (font->yOffset * scale); - djui_hud_position_translate(&translatedX, &translatedY); - create_dl_translation_matrix(DJUI_MTX_PUSH, translatedX, translatedY, gDjuiHudUtilsZ); - - // compute font size - f32 translatedFontSize = fontScale; - djui_hud_size_translate(&translatedFontSize); - create_dl_scale_matrix(DJUI_MTX_NOPUSH, translatedFontSize, translatedFontSize, 1.0f); - - // render the line - f32 addX = 0; - char* c = (char*)message; - while (*c != '\0') { - f32 charWidth = font->char_width(c); - - if (*c == '\n' && *c == ' ') { - addX += charWidth; - c++; - continue; - } - - // render - font->render_char(c); - create_dl_translation_matrix(DJUI_MTX_NOPUSH, charWidth + addX, 0, 0); - addX = 0; - - c = djui_unicode_next_char(c); - } - - // pop - gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); - - if (sInterpHudCount >= MAX_INTERP_HUD) { return; } - struct InterpHud* interp = &sInterpHuds[sInterpHudCount++]; - interp->headPos = savedHeadPos; - interp->prevX = prevX; - interp->prevY = prevY; - interp->prevScaleW = prevScale; - interp->prevScaleH = prevScale; - interp->x = x; - interp->y = y; - interp->scaleW = scale; - interp->scaleH = scale; - interp->width = font->defaultFontScale; - interp->height = font->defaultFontScale; - interp->z = savedZ; - interp->resolution = sResolution; - interp->rotation = sRotation; + djui_hud_print_text_internal(message, x, y, scale, interp); } static inline bool is_power_of_two(u32 n) { return (n > 0) && ((n & (n - 1)) == 0); } -static void djui_hud_render_texture_raw(const Texture* texture, u32 width, u32 height, u8 fmt, u8 siz, f32 x, f32 y, f32 scaleW, f32 scaleH) { +static void djui_hud_render_texture_raw(const Texture* texture, u32 width, u32 height, u8 fmt, u8 siz, f32 x, f32 y, f32 scaleW, f32 scaleH, struct InterpHud *interp) { if (!is_power_of_two(width) || !is_power_of_two(height)) { LOG_LUA_LINE("Tried to render DJUI HUD texture with NPOT width or height"); return; @@ -510,6 +695,7 @@ static void djui_hud_render_texture_raw(const Texture* texture, u32 width, u32 h gDjuiHudUtilsZ += 0.001f; // translate position + djui_hud_create_interp_gfx(interp, INTERP_HUD_TRANSLATION); f32 translatedX = x; f32 translatedY = y; djui_hud_position_translate(&translatedX, &translatedY); @@ -520,25 +706,27 @@ static void djui_hud_render_texture_raw(const Texture* texture, u32 width, u32 h f32 translatedH = scaleH; djui_hud_size_translate(&translatedW); djui_hud_size_translate(&translatedH); - if (sRotation.rotation != 0) { - f32 pivotTranslationX = width * translatedW * sRotation.pivotX; - f32 pivotTranslationY = height * translatedH * sRotation.pivotY; + if (sHudUtilsState.rotation.degrees.prev != 0 || sHudUtilsState.rotation.degrees.curr != 0) { + djui_hud_create_interp_gfx(interp, INTERP_HUD_ROTATION); + f32 pivotTranslationX = width * translatedW * sHudUtilsState.rotation.pivotX.curr; + f32 pivotTranslationY = height * translatedH * sHudUtilsState.rotation.pivotY.curr; create_dl_translation_matrix(DJUI_MTX_NOPUSH, +pivotTranslationX, -pivotTranslationY, 0); - create_dl_rotation_matrix(DJUI_MTX_NOPUSH, sRotation.rotation, 0, 0, 1); + create_dl_rotation_matrix(DJUI_MTX_NOPUSH, sHudUtilsState.rotation.degrees.curr, 0, 0, 1); create_dl_translation_matrix(DJUI_MTX_NOPUSH, -pivotTranslationX, +pivotTranslationY, 0); } // translate scale + djui_hud_create_interp_gfx(interp, INTERP_HUD_SCALE); create_dl_scale_matrix(DJUI_MTX_NOPUSH, width * translatedW, height * translatedH, 1.0f); // render - djui_gfx_render_texture(texture, width, height, fmt, siz, sFilter); + djui_gfx_render_texture(texture, width, height, fmt, siz, sHudUtilsState.filter); // pop gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); } -static void djui_hud_render_texture_tile_raw(const Texture* texture, u32 width, u32 height, u8 fmt, u8 siz, f32 x, f32 y, f32 scaleW, f32 scaleH, u32 tileX, u32 tileY, u32 tileW, u32 tileH) { +static void djui_hud_render_texture_tile_raw(const Texture* texture, u32 width, u32 height, u8 fmt, u8 siz, f32 x, f32 y, f32 scaleW, f32 scaleH, u32 tileX, u32 tileY, u32 tileW, u32 tileH, struct InterpHud *interp) { if (!texture) { return; } gDjuiHudUtilsZ += 0.001f; @@ -546,6 +734,7 @@ static void djui_hud_render_texture_tile_raw(const Texture* texture, u32 width, if (height != 0) { scaleH *= (f32) tileH / (f32) height; } // translate position + djui_hud_create_interp_gfx(interp, INTERP_HUD_TRANSLATION); f32 translatedX = x; f32 translatedY = y; djui_hud_position_translate(&translatedX, &translatedY); @@ -556,20 +745,22 @@ static void djui_hud_render_texture_tile_raw(const Texture* texture, u32 width, f32 translatedH = scaleH; djui_hud_size_translate(&translatedW); djui_hud_size_translate(&translatedH); - if (sRotation.rotation != 0) { + if (sHudUtilsState.rotation.degrees.prev != 0 || sHudUtilsState.rotation.degrees.curr != 0) { + djui_hud_create_interp_gfx(interp, INTERP_HUD_ROTATION); f32 aspect = tileH ? ((f32) tileW / (f32) tileH) : 1.f; - f32 pivotTranslationX = width * translatedW * aspect * sRotation.pivotX; - f32 pivotTranslationY = height * translatedH * sRotation.pivotY; + f32 pivotTranslationX = width * translatedW * aspect * sHudUtilsState.rotation.pivotX.curr; + f32 pivotTranslationY = height * translatedH * sHudUtilsState.rotation.pivotY.curr; create_dl_translation_matrix(DJUI_MTX_NOPUSH, +pivotTranslationX, -pivotTranslationY, 0); - create_dl_rotation_matrix(DJUI_MTX_NOPUSH, sRotation.rotation, 0, 0, 1); + create_dl_rotation_matrix(DJUI_MTX_NOPUSH, sHudUtilsState.rotation.degrees.curr, 0, 0, 1); create_dl_translation_matrix(DJUI_MTX_NOPUSH, -pivotTranslationX, +pivotTranslationY, 0); } // translate scale + djui_hud_create_interp_gfx(interp, INTERP_HUD_SCALE); create_dl_scale_matrix(DJUI_MTX_NOPUSH, width * translatedW, height * translatedH, 1.0f); // render - djui_gfx_render_texture_tile(texture, width, height, fmt, siz, tileX, tileY, tileW, tileH, sFilter, false); + djui_gfx_render_texture_tile(texture, width, height, fmt, siz, tileX, tileY, tileW, tileH, sHudUtilsState.filter); // pop gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); @@ -577,44 +768,35 @@ static void djui_hud_render_texture_tile_raw(const Texture* texture, u32 width, void djui_hud_render_texture(struct TextureInfo* texInfo, f32 x, f32 y, f32 scaleW, f32 scaleH) { if (!texInfo) { return; } - djui_hud_render_texture_raw(texInfo->texture, texInfo->width, texInfo->height, texInfo->format, texInfo->size, x, y, scaleW, scaleH); + djui_hud_render_texture_raw(texInfo->texture, texInfo->width, texInfo->height, texInfo->format, texInfo->size, x, y, scaleW, scaleH, NULL); } void djui_hud_render_texture_tile(struct TextureInfo* texInfo, f32 x, f32 y, f32 scaleW, f32 scaleH, u32 tileX, u32 tileY, u32 tileW, u32 tileH) { if (!texInfo) { return; } - djui_hud_render_texture_tile_raw(texInfo->texture, texInfo->width, texInfo->height, texInfo->format, texInfo->size, x, y, scaleW, scaleH, tileX, tileY, tileW, tileH); + djui_hud_render_texture_tile_raw(texInfo->texture, texInfo->width, texInfo->height, texInfo->format, texInfo->size, x, y, scaleW, scaleH, tileX, tileY, tileW, tileH, NULL); } void djui_hud_render_texture_interpolated(struct TextureInfo* texInfo, f32 prevX, f32 prevY, f32 prevScaleW, f32 prevScaleH, f32 x, f32 y, f32 scaleW, f32 scaleH) { - Gfx* savedHeadPos = gDisplayListHead; - f32 savedZ = gDjuiHudUtilsZ; - if (!texInfo) { return; } - djui_hud_render_texture_raw(texInfo->texture, texInfo->width, texInfo->height, texInfo->format, texInfo->size, prevX, prevY, prevScaleW, prevScaleH); + struct InterpHud *interp = djui_hud_create_interp(); + if (interp) { + interp->posX.prev = prevX; + interp->posY.prev = prevY; + interp->posX.curr = x; + interp->posY.curr = y; + interp->scaleX.prev = prevScaleW; + interp->scaleY.prev = prevScaleH; + interp->scaleX.curr = scaleW; + interp->scaleY.curr = scaleH; + interp->width = texInfo->width; + interp->height = texInfo->height; + } - if (sInterpHudCount >= MAX_INTERP_HUD) { return; } - struct InterpHud* interp = &sInterpHuds[sInterpHudCount++]; - interp->headPos = savedHeadPos; - interp->prevX = prevX; - interp->prevY = prevY; - interp->prevScaleW = prevScaleW; - interp->prevScaleH = prevScaleH; - interp->x = x; - interp->y = y; - interp->scaleW = scaleW; - interp->scaleH = scaleH; - interp->width = texInfo->width; - interp->height = texInfo->height; - interp->z = savedZ; - interp->resolution = sResolution; - interp->rotation = sRotation; + djui_hud_render_texture_raw(texInfo->texture, texInfo->width, texInfo->height, texInfo->format, texInfo->size, prevX, prevY, prevScaleW, prevScaleH, interp); } void djui_hud_render_texture_tile_interpolated(struct TextureInfo* texInfo, f32 prevX, f32 prevY, f32 prevScaleW, f32 prevScaleH, f32 x, f32 y, f32 scaleW, f32 scaleH, u32 tileX, u32 tileY, u32 tileW, u32 tileH) { - Gfx* savedHeadPos = gDisplayListHead; - f32 savedZ = gDjuiHudUtilsZ; - if (!texInfo) { return; } // apply scale correction for tiles @@ -627,30 +809,28 @@ void djui_hud_render_texture_tile_interpolated(struct TextureInfo* texInfo, f32 prevScaleH *= ((f32)tileH / (f32)texInfo->height); } - djui_hud_render_texture_tile_raw(texInfo->texture, texInfo->width, texInfo->height, texInfo->format, texInfo->size, prevX, prevY, prevScaleW, prevScaleH, tileX, tileY, tileW, tileH); + struct InterpHud *interp = djui_hud_create_interp(); + if (interp) { + interp->posX.prev = prevX; + interp->posY.prev = prevY; + interp->posX.curr = x; + interp->posY.curr = y; + interp->scaleX.prev = prevScaleW; + interp->scaleY.prev = prevScaleH; + interp->scaleX.curr = scaleW; + interp->scaleY.curr = scaleH; + interp->width = texInfo->width; + interp->height = texInfo->height; + } - if (sInterpHudCount >= MAX_INTERP_HUD) { return; } - struct InterpHud* interp = &sInterpHuds[sInterpHudCount++]; - interp->headPos = savedHeadPos; - interp->prevX = prevX; - interp->prevY = prevY; - interp->prevScaleW = prevScaleW; - interp->prevScaleH = prevScaleH; - interp->x = x; - interp->y = y; - interp->scaleW = scaleW; - interp->scaleH = scaleH; - interp->width = texInfo->width; - interp->height = texInfo->height; - interp->z = savedZ; - interp->resolution = sResolution; - interp->rotation = sRotation; + djui_hud_render_texture_tile_raw(texInfo->texture, texInfo->width, texInfo->height, texInfo->format, texInfo->size, prevX, prevY, prevScaleW, prevScaleH, tileX, tileY, tileW, tileH, interp); } -void djui_hud_render_rect(f32 x, f32 y, f32 width, f32 height) { +static void djui_hud_render_rect_internal(f32 x, f32 y, f32 width, f32 height, struct InterpHud *interp) { gDjuiHudUtilsZ += 0.001f; // translate position + djui_hud_create_interp_gfx(interp, INTERP_HUD_TRANSLATION); f32 translatedX = x; f32 translatedY = y; djui_hud_position_translate(&translatedX, &translatedY); @@ -661,15 +841,17 @@ void djui_hud_render_rect(f32 x, f32 y, f32 width, f32 height) { f32 translatedH = height; djui_hud_size_translate(&translatedW); djui_hud_size_translate(&translatedH); - if (sRotation.rotation != 0) { - f32 pivotTranslationX = translatedW * sRotation.pivotX; - f32 pivotTranslationY = translatedH * sRotation.pivotY; + if (sHudUtilsState.rotation.degrees.prev != 0 || sHudUtilsState.rotation.degrees.curr != 0) { + djui_hud_create_interp_gfx(interp, INTERP_HUD_ROTATION); + f32 pivotTranslationX = translatedW * sHudUtilsState.rotation.pivotX.curr; + f32 pivotTranslationY = translatedH * sHudUtilsState.rotation.pivotY.curr; create_dl_translation_matrix(DJUI_MTX_NOPUSH, +pivotTranslationX, -pivotTranslationY, 0); - create_dl_rotation_matrix(DJUI_MTX_NOPUSH, sRotation.rotation, 0, 0, 1); + create_dl_rotation_matrix(DJUI_MTX_NOPUSH, sHudUtilsState.rotation.degrees.curr, 0, 0, 1); create_dl_translation_matrix(DJUI_MTX_NOPUSH, -pivotTranslationX, +pivotTranslationY, 0); } // translate scale + djui_hud_create_interp_gfx(interp, INTERP_HUD_SCALE); create_dl_scale_matrix(DJUI_MTX_NOPUSH, translatedW, translatedH, 1.0f); // render @@ -679,28 +861,26 @@ void djui_hud_render_rect(f32 x, f32 y, f32 width, f32 height) { gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); } +void djui_hud_render_rect(f32 x, f32 y, f32 width, f32 height) { + djui_hud_render_rect_internal(x, y, width, height, NULL); +} + void djui_hud_render_rect_interpolated(f32 prevX, f32 prevY, f32 prevWidth, f32 prevHeight, f32 x, f32 y, f32 width, f32 height) { - Gfx* savedHeadPos = gDisplayListHead; - f32 savedZ = gDjuiHudUtilsZ; + struct InterpHud *interp = djui_hud_create_interp(); + if (interp) { + interp->posX.prev = prevX; + interp->posY.prev = prevY; + interp->posX.curr = x; + interp->posY.curr = y; + interp->scaleX.prev = prevWidth; + interp->scaleY.prev = prevHeight; + interp->scaleX.curr = width; + interp->scaleY.curr = height; + interp->width = 1; + interp->height = 1; + } - djui_hud_render_rect(prevX, prevY, prevWidth, prevHeight); - - if (sInterpHudCount >= MAX_INTERP_HUD) { return; } - struct InterpHud* interp = &sInterpHuds[sInterpHudCount++]; - interp->headPos = savedHeadPos; - interp->prevX = prevX; - interp->prevY = prevY; - interp->prevScaleW = prevWidth; - interp->prevScaleH = prevHeight; - interp->x = x; - interp->y = y; - interp->scaleW = width; - interp->scaleH = height; - interp->width = 1; - interp->height = 1; - interp->z = savedZ; - interp->resolution = sResolution; - interp->rotation = sRotation; + djui_hud_render_rect_internal(prevX, prevY, prevWidth, prevHeight, interp); } void djui_hud_render_line(f32 p1X, f32 p1Y, f32 p2X, f32 p2Y, f32 size) { @@ -752,7 +932,7 @@ bool djui_hud_world_pos_to_screen_pos(Vec3f pos, VEC_OUT Vec3f out) { out[1] *= fovCoeff; f32 screenWidth, screenHeight; - if (sResolution == RESOLUTION_N64) { + if (sHudUtilsState.resolution == RESOLUTION_N64) { screenWidth = GFX_DIMENSIONS_ASPECT_RATIO * SCREEN_HEIGHT; screenHeight = SCREEN_HEIGHT; } else { diff --git a/src/pc/djui/djui_hud_utils.h b/src/pc/djui/djui_hud_utils.h index b4e1b9d08..353ee3511 100644 --- a/src/pc/djui/djui_hud_utils.h +++ b/src/pc/djui/djui_hud_utils.h @@ -1,6 +1,22 @@ #ifndef DJUI_HUD_UTILS_H #define DJUI_HUD_UTILS_H +// Common pivot values for rotation +#define ROTATION_PIVOT_X_LEFT 0.0 +#define ROTATION_PIVOT_X_CENTER 0.5 +#define ROTATION_PIVOT_X_RIGHT 1.0 +#define ROTATION_PIVOT_Y_TOP 0.0 +#define ROTATION_PIVOT_Y_CENTER 0.5 +#define ROTATION_PIVOT_Y_BOTTOM 1.0 + +// Common alignment values for text alignment +#define TEXT_HALIGN_LEFT 0.0 +#define TEXT_HALIGN_CENTER 0.5 +#define TEXT_HALIGN_RIGHT 1.0 +#define TEXT_VALIGN_TOP 0.0 +#define TEXT_VALIGN_CENTER 0.5 +#define TEXT_VALIGN_BOTTOM 1.0 + enum HudUtilsResolution { RESOLUTION_DJUI, RESOLUTION_N64, @@ -14,7 +30,8 @@ enum HudUtilsFilter { }; enum DjuiFontType { - FONT_NORMAL, + FONT_LEGACY = -1, + FONT_NORMAL = 0, FONT_MENU, FONT_HUD, FONT_ALIASED, @@ -24,15 +41,6 @@ enum DjuiFontType { FONT_COUNT, }; -struct HudUtilsRotation { - f32 rotation; - f32 rotationDiff; - f32 prevPivotX; - f32 prevPivotY; - f32 pivotX; - f32 pivotY; -}; - struct GlobalTextures { struct TextureInfo camera; struct TextureInfo lakitu; @@ -63,7 +71,7 @@ u8 djui_hud_get_filter(void); /* |description|Sets the current DJUI HUD texture filter|descriptionEnd| */ void djui_hud_set_filter(enum HudUtilsFilter filterType); /* |description|Gets the current DJUI HUD font|descriptionEnd| */ -u8 djui_hud_get_font(void); +s8 djui_hud_get_font(void); /* |description|Sets the current DJUI HUD font|descriptionEnd| */ void djui_hud_set_font(s8 fontType); /* |description|Gets the current DJUI HUD color|descriptionEnd| */ @@ -73,11 +81,17 @@ void djui_hud_set_color(u8 r, u8 g, u8 b, u8 a); /* |description|Resets the current DJUI HUD color|descriptionEnd| */ void djui_hud_reset_color(void); /* |description|Gets the current DJUI HUD rotation|descriptionEnd| */ -struct HudUtilsRotation* djui_hud_get_rotation(void); +void djui_hud_get_rotation(RET s16 *rotation, RET f32 *pivotX, RET f32 *pivotY); /* |description|Sets the current DJUI HUD rotation|descriptionEnd| */ void djui_hud_set_rotation(s16 rotation, f32 pivotX, f32 pivotY); /* |description|Sets the current DJUI HUD rotation interpolated|descriptionEnd| */ -void djui_hud_set_rotation_interpolated(s32 prevRotation, f32 prevPivotX, f32 prevPivotY, s32 rotation, f32 pivotX, f32 pivotY); +void djui_hud_set_rotation_interpolated(s16 prevRotation, f32 prevPivotX, f32 prevPivotY, s16 rotation, f32 pivotX, f32 pivotY); +/* |description|Gets the current DJUI HUD text alignment|descriptionEnd| */ +void djui_hud_get_text_alignment(RET f32 *textHAlign, RET f32 *textVAlign); +/* |description|Sets the current DJUI HUD text alignment|descriptionEnd| */ +void djui_hud_set_text_alignment(f32 textHAlign, f32 textVAlign); +/* |description|Sets the current DJUI HUD text alignment interpolated|descriptionEnd| */ +void djui_hud_set_text_alignment_interpolated(f32 prevTextHAlign, f32 prevTextVAlign, f32 textHAlign, f32 textVAlign); /* |description|Gets the screen width in the current DJUI HUD resolution|descriptionEnd| */ u32 djui_hud_get_screen_width(void); diff --git a/src/pc/djui/djui_inputbox.c b/src/pc/djui/djui_inputbox.c index 878a3a94d..c0a911130 100644 --- a/src/pc/djui/djui_inputbox.c +++ b/src/pc/djui/djui_inputbox.c @@ -589,6 +589,8 @@ static bool djui_inputbox_render(struct DjuiBase* base) { f32 drawX = inputbox->viewX; f32 additionalShift = 0; bool wasInsideSelection = false; + + font->render_begin(); for (u16 i = 0; i < inputbox->bufferSize; i++) { //render composition text @@ -617,6 +619,7 @@ static bool djui_inputbox_render(struct DjuiBase* base) { djui_inputbox_render_char(inputbox, c, &drawX, &additionalShift); c = djui_unicode_next_char(c); } + font->render_end(); gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); gSPDisplayList(gDisplayListHead++, dl_ia_text_end); diff --git a/src/pc/djui/djui_panel_mod_menu.c b/src/pc/djui/djui_panel_mod_menu.c index 782fcddee..36437b2c6 100644 --- a/src/pc/djui/djui_panel_mod_menu.c +++ b/src/pc/djui/djui_panel_mod_menu.c @@ -113,7 +113,8 @@ void djui_panel_mod_menu_mod_create(struct DjuiBase* caller) { } if (mod == NULL) { return; } - struct DjuiThreePanel* panel = djui_panel_menu_create(to_uppercase(mod->name), false); + char *modNameUppercase = to_uppercase(mod->name); + struct DjuiThreePanel* panel = djui_panel_menu_create(modNameUppercase, false); struct DjuiBase* body = djui_three_panel_get_body(panel); { struct DjuiPaginated* paginated = djui_paginated_create(body, 8); @@ -130,6 +131,7 @@ void djui_panel_mod_menu_mod_create(struct DjuiBase* caller) { } djui_panel_add(caller, panel, NULL); + free(modNameUppercase); } void djui_panel_mod_menu_create(struct DjuiBase* caller) { diff --git a/src/pc/djui/djui_text.c b/src/pc/djui/djui_text.c index 48f362aac..26fd4ea9c 100644 --- a/src/pc/djui/djui_text.c +++ b/src/pc/djui/djui_text.c @@ -4,10 +4,146 @@ #include "djui_hud_utils.h" #include "game/segment2.h" -static u8 sSavedR = 0; -static u8 sSavedG = 0; -static u8 sSavedB = 0; -static u8 sSavedA = 0; + /////////// + // color // +/////////// + +static const struct DjuiColor sDjuiTextDefaultColor = { 220, 220, 220, 255 }; +static struct DjuiColor sDjuiTextCurrentColor; + +bool djui_text_parse_color(char *begin, const char *end, bool ignoreAlpha, const struct DjuiColor *baseColor, char **nextChar, struct DjuiColor *parsedColor) { + char *c = begin; + + // Not an escape + if (*c != '\\') { + return false; + } + c = djui_unicode_next_char(c); + + // Not a color + if (*c != '#') { + return false; + } + c = djui_unicode_next_char(c); + + // Parse color + u32 color = 0; + u8 length = 0; + while (c < end) { + if (*c == '\\') { + break; + } + + u8 colorPiece = 0; + if (*c >= '0' && *c <= '9') { colorPiece = *c - '0'; } + else if (*c >= 'a' && *c <= 'f') { colorPiece = 10 + *c - 'a'; } + else if (*c >= 'A' && *c <= 'F') { colorPiece = 10 + *c - 'A'; } + else { // Not a valid color piece + return false; + } + color = (color << 4) | colorPiece; + length++; + + c = djui_unicode_next_char(c); + } + + // Unterminated color code + if (c == end) { + return false; + } + + switch (length) { + + // reset to base color + case 0: { + if (baseColor && parsedColor) { + *parsedColor = *baseColor; + } + } break; + + // #rgb + case 3: { + if (parsedColor) { + u32 r = (color >> 8) & 0xF; + u32 g = (color >> 4) & 0xF; + u32 b = (color >> 0) & 0xF; + parsedColor->r = (r << 4) | r; + parsedColor->g = (g << 4) | g; + parsedColor->b = (b << 4) | b; + parsedColor->a = 0xFF; + } + } break; + + // #rgba + case 4: { + if (parsedColor) { + u32 r = (color >> 12) & 0xF; + u32 g = (color >> 8) & 0xF; + u32 b = (color >> 4) & 0xF; + u32 a = (color >> 0) & 0xF; + parsedColor->r = (r << 4) | r; + parsedColor->g = (g << 4) | g; + parsedColor->b = (b << 4) | b; + parsedColor->a = ignoreAlpha ? 0xFF : ((a << 4) | a); + } + } break; + + // #rrggbb + case 6: { + if (parsedColor) { + parsedColor->r = ((color >> 16) & 0xFF); + parsedColor->g = ((color >> 8) & 0xFF); + parsedColor->b = ((color >> 0) & 0xFF); + parsedColor->a = 0xFF; + } + } break; + + // #rrggbbaa + case 8: { + if (parsedColor) { + parsedColor->r = ((color >> 24) & 0xFF); + parsedColor->g = ((color >> 16) & 0xFF); + parsedColor->b = ((color >> 8) & 0xFF); + parsedColor->a = ignoreAlpha ? 0xFF : ((color >> 0) & 0xFF); + } + } break; + + // Invalid color + default: return false; + } + + if (nextChar) { + *nextChar = djui_unicode_next_char(c); + } + return true; +} + +void djui_text_remove_colors(char *str) { + if (!str) { return; } + char *colorStart = str; + const char *strEnd = str + strlen(str); + while ((colorStart = strstr(colorStart, "\\#"))) { + char *colorEnd; + if (djui_text_parse_color(colorStart, strEnd, false, NULL, &colorEnd, NULL) && colorEnd > colorStart) { + memmove(colorStart, colorEnd, strlen(colorEnd) + 1); + } else { + colorStart++; + } + } +} + +char *djui_text_get_uncolored_string(char *dest, size_t length, const char *str) { + if (!dest) { + dest = malloc(length * sizeof(char)); + if (!dest) { + return NULL; + } + } + strncpy(dest, str, length - 1); + dest[length - 1] = 0; + djui_text_remove_colors(dest); + return dest; +} //////////////// // properties // @@ -59,6 +195,10 @@ static f32 sTextRenderY = 0; static f32 sTextRenderLastX = 0; static f32 sTextRenderLastY = 0; +bool djui_text_is_printable(const char *c) { + return c != NULL && (!iscntrl(*c) || *c == 0x7F); // the star +} + static void djui_text_translate(f32 x, f32 y) { sTextRenderX += x; sTextRenderY += y; @@ -90,7 +230,7 @@ static void djui_text_render_char(struct DjuiText* text, char* c) { sTextRenderY += 1.0f / text->fontScale; gDPSetEnvColor(gDisplayListHead++, text->dropShadow.r, text->dropShadow.g, text->dropShadow.b, text->dropShadow.a); djui_text_render_single_char(text, c); - gDPSetEnvColor(gDisplayListHead++, sSavedR, sSavedG, sSavedB, sSavedA); + gDPSetEnvColor(gDisplayListHead++, sDjuiTextCurrentColor.r, sDjuiTextCurrentColor.g, sDjuiTextCurrentColor.b, sDjuiTextCurrentColor.a); sTextRenderX -= 1.0f / text->fontScale; sTextRenderY -= 1.0f / text->fontScale; } @@ -99,16 +239,21 @@ static void djui_text_render_char(struct DjuiText* text, char* c) { static f32 djui_text_measure_word_width(struct DjuiText* text, char* message) { f32 width = 0; - bool skipping = false; char* c = message; + const char *end = message + strlen(message); while (*c != '\0') { - if (*c == ' ') { return width; } - if (*c == '\n') { return width; } - if (*c == '\0') { return width; } - if (*c == '\\') { skipping = !skipping; } - if (!skipping) { - width += text->font->char_width(c); + + // color code + if (djui_text_parse_color(c, end, true, NULL, &c, NULL)) { + continue; } + + // end of word due to unprintable chars or space + if (!djui_text_is_printable(c) || *c == ' ') { + return width; + } + + width += text->font->char_width(c); c = djui_unicode_next_char(c); } return width; @@ -122,17 +267,18 @@ static void djui_text_read_line(struct DjuiText* text, char** message, f32* line u16 lastSafeEllipsesIndex = *index; u16 lastSafeEllipsesLineWidth = *lineWidth + ellipsesWidth;*/ - bool skipping = false; char* c = *message; + const char *end = *message + strlen(*message); while (*c != '\0') { f32 charWidth = text->font->char_width(c); - // check for special escape sequences - if (*c == '\\') { skipping = !skipping; } - if (skipping || *c == '\\') { + // check for color code + if (*c == '\\') { lastC = c; - c = djui_unicode_next_char(c); - continue; + if (djui_text_parse_color(c, end, true, NULL, &c, NULL)) { + lastC = c; + continue; + } } // check for newline @@ -212,59 +358,6 @@ f32 djui_text_find_width(struct DjuiText* text, u16 maxLines) { return largestWidth * text->fontScale; } -static char* djui_text_render_line_parse_escape(char* c1, char* c2) { - bool parsingColor = (c1[1] == '#'); - char* c = parsingColor ? (c1 + 2) : (c1 + 1); - - u32 color = 0; - u8 colorPieces = 0; - while (c < c2) { - if (*c == '\\') { break; } - if (parsingColor) { - u8 colorPiece = 0; - if (*c >= '0' && *c <= '9') { colorPiece = *c - '0'; } - else if (*c >= 'a' && *c <= 'f') { colorPiece = 10 + *c - 'a'; } - else if (*c >= 'A' && *c <= 'F') { colorPiece = 10 + *c - 'A'; } - color = (color << 4) | colorPiece; - colorPieces++; - } - c = djui_unicode_next_char(c); - } - - if (parsingColor) { - if (colorPieces == 3) { - u32 r = (color >> 8) & 0xF; - u32 g = (color >> 4) & 0xF; - u32 b = (color >> 0) & 0xF; - sSavedR = (r << 4) | r; - sSavedG = (g << 4) | g; - sSavedB = (b << 4) | b; - /*} else if (colorPieces == 4) { - u32 r = (color >> 12) & 0xF; - u32 g = (color >> 8) & 0xF; - u32 b = (color >> 4) & 0xF; - u32 a = (color >> 0) & 0xF; - sSavedR = (r << 4) | r; - sSavedG = (g << 4) | g; - sSavedB = (b << 4) | b; - sSavedA = (a << 4) | a;*/ - } else if (colorPieces == 6) { - sSavedR = ((color >> 16) & 0xFF); - sSavedG = ((color >> 8) & 0xFF); - sSavedB = ((color >> 0) & 0xFF); - }/*else if (colorPieces == 8) { - sSavedR = ((color >> 24) & 0xFF); - sSavedG = ((color >> 16) & 0xFF); - sSavedB = ((color >> 8) & 0xFF); - sSavedA = ((color >> 0) & 0xFF); - }*/ - gDPSetEnvColor(gDisplayListHead++, sSavedR, sSavedG, sSavedB, sSavedA); - } - - c = djui_unicode_next_char(c); - return c; -} - static void djui_text_render_line(struct DjuiText* text, char* c1, char* c2, f32 lineWidth, bool ellipses) { struct DjuiBase* base = &text->base; struct DjuiBaseRect* comp = &base->comp; @@ -284,9 +377,13 @@ static void djui_text_render_line(struct DjuiText* text, char* c1, char* c2, f32 } // render the line + text->font->render_begin(); + for (char* c = c1; c < c2;) { - if (*c == '\\') { - c = djui_text_render_line_parse_escape(c, c2); + struct DjuiColor parsedColor; + if (djui_text_parse_color(c, c2, true, &sDjuiTextDefaultColor, &c, &parsedColor)) { + gDPSetEnvColor(gDisplayListHead++, parsedColor.r, parsedColor.g, parsedColor.b, parsedColor.a); + sDjuiTextCurrentColor = parsedColor; continue; } @@ -312,6 +409,8 @@ static void djui_text_render_line(struct DjuiText* text, char* c1, char* c2, f32 } } + text->font->render_end(); + // reset translation matrix djui_text_translate(-curWidth, text->font->lineHeight); } @@ -352,10 +451,7 @@ static bool djui_text_render(struct DjuiBase* base) { // set color gDPSetEnvColor(gDisplayListHead++, base->color.r, base->color.g, base->color.b, base->color.a); - sSavedR = base->color.r; - sSavedG = base->color.g; - sSavedB = base->color.b; - sSavedA = base->color.a; + sDjuiTextCurrentColor = base->color; // count lines u16 maxLines = comp->height / ((f32)text->font->lineHeight * text->fontScale); diff --git a/src/pc/djui/djui_text.h b/src/pc/djui/djui_text.h index 2d8f84ee5..89e6da0cb 100644 --- a/src/pc/djui/djui_text.h +++ b/src/pc/djui/djui_text.h @@ -11,12 +11,17 @@ struct DjuiText { enum DjuiVAlign textVAlign; }; +bool djui_text_parse_color(char *begin, const char *end, bool ignoreAlpha, const struct DjuiColor *baseColor, char **nextChar, struct DjuiColor *parsedColor); +void djui_text_remove_colors(char *str); +char *djui_text_get_uncolored_string(char *dest, size_t length, const char *str); + void djui_text_set_text(struct DjuiText* text, const char* message); void djui_text_set_font(struct DjuiText* text, const struct DjuiFont* font); void djui_text_set_font_scale(struct DjuiText* text, f32 fontScale); void djui_text_set_drop_shadow(struct DjuiText* text, f32 r, f32 g, f32 b, f32 a); void djui_text_set_alignment(struct DjuiText* text, enum DjuiHAlign hAlign, enum DjuiVAlign vAlign); +bool djui_text_is_printable(const char *c); int djui_text_count_lines(struct DjuiText* text, u16 maxLines); f32 djui_text_find_width(struct DjuiText* text, u16 maxLines); diff --git a/src/pc/djui/djui_unicode.c b/src/pc/djui/djui_unicode.c index e97cd8551..4714be653 100644 --- a/src/pc/djui/djui_unicode.c +++ b/src/pc/djui/djui_unicode.c @@ -204,7 +204,7 @@ struct SmCodeGlyph sSmCodeDuplicateGlyphs[] = { static void* sCharMap = NULL; -static s32 count_bytes_for_char(char* text) { +static s32 count_bytes_for_char(const char* text) { s32 bytes = 0; u8 mask = (1 << 7); while (*text & mask) { @@ -214,7 +214,7 @@ static s32 count_bytes_for_char(char* text) { return bytes ? bytes : 1; } -static u64 convert_unicode_char_to_u64(char* text) { +static u64 convert_unicode_char_to_u64(const char* text) { s32 bytes = count_bytes_for_char(text); u64 value = (u8)*text; @@ -273,7 +273,7 @@ void djui_unicode_init(void) { } } -u32 djui_unicode_get_sprite_index(char* text) { +u32 djui_unicode_get_sprite_index(const char* text) { // check for ASCI if ((u8)*text < 128) { // make sure it's in the valid range @@ -298,7 +298,7 @@ u32 djui_unicode_get_sprite_index(char* text) { return (u8)'?' - SPRITE_INDEX_START_CHAR; } -f32 djui_unicode_get_sprite_width(char* text, const f32 font_widths[], f32 unicodeScale) { +f32 djui_unicode_get_sprite_width(const char* text, const f32 font_widths[], f32 unicodeScale) { if (!text) { return 0; } // check for ASCII @@ -358,7 +358,7 @@ size_t djui_unicode_len(char* text) { return len; } -bool djui_unicode_valid_char(char* text) { +bool djui_unicode_valid_char(const char* text) { if ((u8)*text < 128) { return ((u8)*text >= ' '); } @@ -397,7 +397,7 @@ void djui_unicode_cleanup_end(char* text) { } } -char djui_unicode_get_base_char(char* text) { +char djui_unicode_get_base_char(const char* text) { if ((u8)*text < ' ') { return '?'; } if ((u8)*text < 128) { return *text; } if (!sCharMap) { return '?'; } @@ -406,7 +406,7 @@ char djui_unicode_get_base_char(char* text) { return (glyph == NULL) ? '?' : glyph->base; } -void djui_unicode_get_char(char* text, char* output) { +void djui_unicode_get_char(const char* text, char* output) { s32 bytes = count_bytes_for_char(text); while (bytes-- > 0) { *output = *text; diff --git a/src/pc/djui/djui_unicode.h b/src/pc/djui/djui_unicode.h index 59d138162..f99735e6f 100644 --- a/src/pc/djui/djui_unicode.h +++ b/src/pc/djui/djui_unicode.h @@ -4,12 +4,12 @@ #include void djui_unicode_init(void); -u32 djui_unicode_get_sprite_index(char* text); -f32 djui_unicode_get_sprite_width(char* text, const f32 font_widths[], f32 unicodeScale); +u32 djui_unicode_get_sprite_index(const char* text); +f32 djui_unicode_get_sprite_width(const char* text, const f32 font_widths[], f32 unicodeScale); char* djui_unicode_next_char(char* text); char* djui_unicode_at_index(char* text, s32 index); size_t djui_unicode_len(char* text); -bool djui_unicode_valid_char(char* text); +bool djui_unicode_valid_char(const char* text); void djui_unicode_cleanup_end(char* text); -char djui_unicode_get_base_char(char* text); -void djui_unicode_get_char(char* text, char* output); +char djui_unicode_get_base_char(const char* text); +void djui_unicode_get_char(const char* text, char* output); diff --git a/src/pc/lua/smlua_cobject_autogen.c b/src/pc/lua/smlua_cobject_autogen.c index e2cbf87ea..35a3ce52d 100644 --- a/src/pc/lua/smlua_cobject_autogen.c +++ b/src/pc/lua/smlua_cobject_autogen.c @@ -1228,16 +1228,6 @@ static struct LuaObjectField sGraphNodeTranslationRotationFields[LUA_GRAPH_NODE_ { "translation", LVT_COBJECT, offsetof(struct GraphNodeTranslationRotation, translation), true, LOT_VEC3S, 1, sizeof(Vec3s) }, }; -#define LUA_HUD_UTILS_ROTATION_FIELD_COUNT 6 -static struct LuaObjectField sHudUtilsRotationFields[LUA_HUD_UTILS_ROTATION_FIELD_COUNT] = { - { "pivotX", LVT_F32, offsetof(struct HudUtilsRotation, pivotX), false, LOT_NONE, 1, sizeof(f32) }, - { "pivotY", LVT_F32, offsetof(struct HudUtilsRotation, pivotY), false, LOT_NONE, 1, sizeof(f32) }, - { "prevPivotX", LVT_F32, offsetof(struct HudUtilsRotation, prevPivotX), false, LOT_NONE, 1, sizeof(f32) }, - { "prevPivotY", LVT_F32, offsetof(struct HudUtilsRotation, prevPivotY), false, LOT_NONE, 1, sizeof(f32) }, - { "rotation", LVT_F32, offsetof(struct HudUtilsRotation, rotation), false, LOT_NONE, 1, sizeof(f32) }, - { "rotationDiff", LVT_F32, offsetof(struct HudUtilsRotation, rotationDiff), false, LOT_NONE, 1, sizeof(f32) }, -}; - #define LUA_INSTANT_WARP_FIELD_COUNT 3 static struct LuaObjectField sInstantWarpFields[LUA_INSTANT_WARP_FIELD_COUNT] = { { "area", LVT_U8, offsetof(struct InstantWarp, area), false, LOT_NONE, 1, sizeof(u8) }, @@ -1484,7 +1474,7 @@ static struct LuaObjectField sModFields[LUA_MOD_FIELD_COUNT] = { { "incompatible", LVT_STRING_P, offsetof(struct Mod, incompatible), true, LOT_NONE, 1, sizeof(char*) }, { "index", LVT_S32, offsetof(struct Mod, index), true, LOT_NONE, 1, sizeof(s32) }, { "isDirectory", LVT_BOOL, offsetof(struct Mod, isDirectory), true, LOT_NONE, 1, sizeof(bool) }, - { "name", LVT_STRING_P, offsetof(struct Mod, name), true, LOT_NONE, 1, sizeof(char*) }, + { "name", LVT_STRING, offsetof(struct Mod, name), true, LOT_NONE, 1, sizeof(char) }, { "pausable", LVT_BOOL, offsetof(struct Mod, pausable), true, LOT_NONE, 1, sizeof(bool) }, { "relativePath", LVT_STRING, offsetof(struct Mod, relativePath), true, LOT_NONE, 1, sizeof(char) }, { "renderBehindHud", LVT_BOOL, offsetof(struct Mod, renderBehindHud), true, LOT_NONE, 1, sizeof(bool) }, @@ -2734,7 +2724,6 @@ struct LuaObjectTable sLuaObjectAutogenTable[LOT_AUTOGEN_MAX - LOT_AUTOGEN_MIN] { LOT_GRAPHNODESWITCHCASE, sGraphNodeSwitchCaseFields, LUA_GRAPH_NODE_SWITCH_CASE_FIELD_COUNT }, { LOT_GRAPHNODETRANSLATION, sGraphNodeTranslationFields, LUA_GRAPH_NODE_TRANSLATION_FIELD_COUNT }, { LOT_GRAPHNODETRANSLATIONROTATION, sGraphNodeTranslationRotationFields, LUA_GRAPH_NODE_TRANSLATION_ROTATION_FIELD_COUNT }, - { LOT_HUDUTILSROTATION, sHudUtilsRotationFields, LUA_HUD_UTILS_ROTATION_FIELD_COUNT }, { LOT_INSTANTWARP, sInstantWarpFields, LUA_INSTANT_WARP_FIELD_COUNT }, { LOT_LAKITUSTATE, sLakituStateFields, LUA_LAKITU_STATE_FIELD_COUNT }, { LOT_LEVELVALUES, sLevelValuesFields, LUA_LEVEL_VALUES_FIELD_COUNT }, @@ -2840,7 +2829,6 @@ const char *sLuaLotNames[] = { [LOT_GRAPHNODESWITCHCASE] = "GraphNodeSwitchCase", [LOT_GRAPHNODETRANSLATION] = "GraphNodeTranslation", [LOT_GRAPHNODETRANSLATIONROTATION] = "GraphNodeTranslationRotation", - [LOT_HUDUTILSROTATION] = "HudUtilsRotation", [LOT_INSTANTWARP] = "InstantWarp", [LOT_LAKITUSTATE] = "LakituState", [LOT_LEVELVALUES] = "LevelValues", diff --git a/src/pc/lua/smlua_cobject_autogen.h b/src/pc/lua/smlua_cobject_autogen.h index 291e0f3ee..d034ce9f7 100644 --- a/src/pc/lua/smlua_cobject_autogen.h +++ b/src/pc/lua/smlua_cobject_autogen.h @@ -74,7 +74,6 @@ enum LuaObjectAutogenType { LOT_GRAPHNODESWITCHCASE, LOT_GRAPHNODETRANSLATION, LOT_GRAPHNODETRANSLATIONROTATION, - LOT_HUDUTILSROTATION, LOT_INSTANTWARP, LOT_LAKITUSTATE, LOT_LEVELVALUES, diff --git a/src/pc/lua/smlua_constants_autogen.c b/src/pc/lua/smlua_constants_autogen.c index bc67f2ae0..d7ca5a882 100644 --- a/src/pc/lua/smlua_constants_autogen.c +++ b/src/pc/lua/smlua_constants_autogen.c @@ -1488,12 +1488,25 @@ char gSmluaConstants[] = "" "CONSOLE_MESSAGE_INFO=0\n" "CONSOLE_MESSAGE_WARNING=1\n" "CONSOLE_MESSAGE_ERROR=2\n" +"ROTATION_PIVOT_X_LEFT=0.0\n" +"ROTATION_PIVOT_X_CENTER=0.5\n" +"ROTATION_PIVOT_X_RIGHT=1.0\n" +"ROTATION_PIVOT_Y_TOP=0.0\n" +"ROTATION_PIVOT_Y_CENTER=0.5\n" +"ROTATION_PIVOT_Y_BOTTOM=1.0\n" +"TEXT_HALIGN_LEFT=0.0\n" +"TEXT_HALIGN_CENTER=0.5\n" +"TEXT_HALIGN_RIGHT=1.0\n" +"TEXT_VALIGN_TOP=0.0\n" +"TEXT_VALIGN_CENTER=0.5\n" +"TEXT_VALIGN_BOTTOM=1.0\n" "RESOLUTION_DJUI=0\n" "RESOLUTION_N64=1\n" "RESOLUTION_COUNT=2\n" "FILTER_NEAREST=0\n" "FILTER_LINEAR=1\n" "FILTER_COUNT=2\n" +"FONT_LEGACY=-1\n" "FONT_NORMAL=0\n" "FONT_MENU=1\n" "FONT_HUD=2\n" diff --git a/src/pc/lua/smlua_functions.c b/src/pc/lua/smlua_functions.c index cdbd868c0..4054a3ebd 100644 --- a/src/pc/lua/smlua_functions.c +++ b/src/pc/lua/smlua_functions.c @@ -945,7 +945,7 @@ int smlua_func_get_uncolored_string(lua_State* L) { const char *str = smlua_to_string(L, 1); if (!gSmLuaConvertSuccess) { LOG_LUA("get_uncolored_string: Failed to convert parameter 1"); return 0; } - char *strNoColor = str_remove_color_codes(str); + char *strNoColor = djui_text_get_uncolored_string(NULL, strlen(str) + 1, str); lua_pushstring(L, strNoColor); free(strNoColor); diff --git a/src/pc/lua/smlua_functions_autogen.c b/src/pc/lua/smlua_functions_autogen.c index c4b6b4f0e..e227a54aa 100644 --- a/src/pc/lua/smlua_functions_autogen.c +++ b/src/pc/lua/smlua_functions_autogen.c @@ -12318,7 +12318,7 @@ int smlua_func_djui_hud_reset_color(UNUSED lua_State* L) { return 1; } -int smlua_func_djui_hud_get_rotation(UNUSED lua_State* L) { +int smlua_func_djui_hud_get_rotation(lua_State* L) { if (L == NULL) { return 0; } int top = lua_gettop(L); @@ -12328,9 +12328,17 @@ int smlua_func_djui_hud_get_rotation(UNUSED lua_State* L) { } - smlua_push_object(L, LOT_HUDUTILSROTATION, djui_hud_get_rotation(), NULL); + s16 rotation; + f32 pivotX; + f32 pivotY; - return 1; + djui_hud_get_rotation(&rotation, &pivotX, &pivotY); + + lua_pushinteger(L, rotation); + lua_pushnumber(L, pivotX); + lua_pushnumber(L, pivotY); + + return 3; } int smlua_func_djui_hud_set_rotation(lua_State* L) { @@ -12363,13 +12371,13 @@ int smlua_func_djui_hud_set_rotation_interpolated(lua_State* L) { return 0; } - s32 prevRotation = smlua_to_integer(L, 1); + s16 prevRotation = smlua_to_integer(L, 1); if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "djui_hud_set_rotation_interpolated"); return 0; } f32 prevPivotX = smlua_to_number(L, 2); if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "djui_hud_set_rotation_interpolated"); return 0; } f32 prevPivotY = smlua_to_number(L, 3); if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 3, "djui_hud_set_rotation_interpolated"); return 0; } - s32 rotation = smlua_to_integer(L, 4); + s16 rotation = smlua_to_integer(L, 4); if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 4, "djui_hud_set_rotation_interpolated"); return 0; } f32 pivotX = smlua_to_number(L, 5); if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 5, "djui_hud_set_rotation_interpolated"); return 0; } @@ -12381,6 +12389,69 @@ int smlua_func_djui_hud_set_rotation_interpolated(lua_State* L) { return 1; } +int smlua_func_djui_hud_get_text_alignment(lua_State* L) { + if (L == NULL) { return 0; } + + int top = lua_gettop(L); + if (top != 0) { + LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "djui_hud_get_text_alignment", 0, top); + return 0; + } + + + f32 textHAlign; + f32 textVAlign; + + djui_hud_get_text_alignment(&textHAlign, &textVAlign); + + lua_pushnumber(L, textHAlign); + lua_pushnumber(L, textVAlign); + + return 2; +} + +int smlua_func_djui_hud_set_text_alignment(lua_State* L) { + if (L == NULL) { return 0; } + + int top = lua_gettop(L); + if (top != 2) { + LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "djui_hud_set_text_alignment", 2, top); + return 0; + } + + f32 textHAlign = smlua_to_number(L, 1); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "djui_hud_set_text_alignment"); return 0; } + f32 textVAlign = smlua_to_number(L, 2); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "djui_hud_set_text_alignment"); return 0; } + + djui_hud_set_text_alignment(textHAlign, textVAlign); + + return 1; +} + +int smlua_func_djui_hud_set_text_alignment_interpolated(lua_State* L) { + if (L == NULL) { return 0; } + + int top = lua_gettop(L); + if (top != 4) { + LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "djui_hud_set_text_alignment_interpolated", 4, top); + return 0; + } + + f32 prevTextHAlign = smlua_to_number(L, 1); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "djui_hud_set_text_alignment_interpolated"); return 0; } + f32 prevTextVAlign = smlua_to_number(L, 2); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "djui_hud_set_text_alignment_interpolated"); return 0; } + f32 textHAlign = smlua_to_number(L, 3); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 3, "djui_hud_set_text_alignment_interpolated"); return 0; } + f32 textVAlign = smlua_to_number(L, 4); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 4, "djui_hud_set_text_alignment_interpolated"); return 0; } + + djui_hud_set_text_alignment_interpolated(prevTextHAlign, prevTextVAlign, textHAlign, textVAlign); + + return 1; +} + int smlua_func_djui_hud_get_screen_width(UNUSED lua_State* L) { if (L == NULL) { return 0; } @@ -37389,6 +37460,9 @@ void smlua_bind_functions_autogen(void) { smlua_bind_function(L, "djui_hud_get_rotation", smlua_func_djui_hud_get_rotation); smlua_bind_function(L, "djui_hud_set_rotation", smlua_func_djui_hud_set_rotation); smlua_bind_function(L, "djui_hud_set_rotation_interpolated", smlua_func_djui_hud_set_rotation_interpolated); + smlua_bind_function(L, "djui_hud_get_text_alignment", smlua_func_djui_hud_get_text_alignment); + smlua_bind_function(L, "djui_hud_set_text_alignment", smlua_func_djui_hud_set_text_alignment); + smlua_bind_function(L, "djui_hud_set_text_alignment_interpolated", smlua_func_djui_hud_set_text_alignment_interpolated); smlua_bind_function(L, "djui_hud_get_screen_width", smlua_func_djui_hud_get_screen_width); smlua_bind_function(L, "djui_hud_get_screen_height", smlua_func_djui_hud_get_screen_height); smlua_bind_function(L, "djui_hud_get_mouse_x", smlua_func_djui_hud_get_mouse_x); diff --git a/src/pc/lua/smlua_hooks.c b/src/pc/lua/smlua_hooks.c index 9bfc6dceb..fc1dafcae 100644 --- a/src/pc/lua/smlua_hooks.c +++ b/src/pc/lua/smlua_hooks.c @@ -952,7 +952,7 @@ char** smlua_get_chat_subcommands_list(const char* maincommand) { for (s32 i = 0; i < sHookedChatCommandsCount; i++) { struct LuaHookedChatCommand* hook = &sHookedChatCommands[i]; if (strcmp(hook->command, maincommand) == 0) { - char* noColorsDesc = str_remove_color_codes(hook->description); + char* noColorsDesc = djui_text_get_uncolored_string(NULL, strlen(hook->description) + 1, hook->description); char* startSubcommands = strstr(noColorsDesc, "["); char* endSubcommands = strstr(noColorsDesc, "]"); diff --git a/src/pc/mods/mod.c b/src/pc/mods/mod.c index aca944c85..03f63ce39 100644 --- a/src/pc/mods/mod.c +++ b/src/pc/mods/mod.c @@ -197,11 +197,6 @@ void mod_clear(struct Mod* mod) { } } - if (mod->name != NULL) { - free(mod->name); - mod->name = NULL; - } - if (mod->incompatible != NULL) { free(mod->incompatible); mod->incompatible = NULL; @@ -449,7 +444,7 @@ static void mod_extract_fields(struct Mod* mod) { fseek(f, 0, SEEK_SET); // default to null - mod->name = NULL; + mod->name[0] = 0; mod->incompatible = NULL; mod->category = NULL; mod->description = NULL; @@ -457,7 +452,7 @@ static void mod_extract_fields(struct Mod* mod) { mod->ignoreScriptWarnings = false; // read line-by-line - #define BUFFER_SIZE MAX(MAX(MOD_NAME_MAX_LENGTH, MOD_INCOMPATIBLE_MAX_LENGTH), MOD_DESCRIPTION_MAX_LENGTH) + #define BUFFER_SIZE MAX(MAX(MOD_NAME_SIZE, MOD_INCOMPATIBLE_SIZE), MOD_DESCRIPTION_SIZE) char buffer[BUFFER_SIZE] = { 0 }; while (!feof(f)) { file_get_line(buffer, BUFFER_SIZE, f); @@ -470,24 +465,23 @@ static void mod_extract_fields(struct Mod* mod) { // extract the field char* extracted = NULL; - if (mod->name == NULL && (extracted = extract_lua_field("-- name:", buffer))) { - mod->name = calloc(MOD_NAME_MAX_LENGTH + 1, sizeof(char)); - if (snprintf(mod->name, MOD_NAME_MAX_LENGTH, "%s", extracted) < 0) { + if (!mod->name[0] && (extracted = extract_lua_field("-- name:", buffer))) { + if (snprintf(mod->name, MOD_NAME_SIZE, "%s", extracted) < 0) { LOG_INFO("Truncated mod name field '%s'", mod->name); } } else if (mod->incompatible == NULL && (extracted = extract_lua_field("-- incompatible:", buffer))) { - mod->incompatible = calloc(MOD_INCOMPATIBLE_MAX_LENGTH + 1, sizeof(char)); - if (snprintf(mod->incompatible, MOD_INCOMPATIBLE_MAX_LENGTH, "%s", extracted) < 0) { + mod->incompatible = calloc(MOD_INCOMPATIBLE_SIZE, sizeof(char)); + if (snprintf(mod->incompatible, MOD_INCOMPATIBLE_SIZE, "%s", extracted) < 0) { LOG_INFO("Truncated mod incompatible field '%s'", mod->incompatible); } } else if (mod->category == NULL && (extracted = extract_lua_field("-- category:", buffer))) { - mod->category = calloc(MOD_CATEGORY_MAX_LENGTH + 1, sizeof(char)); - if (snprintf(mod->category, MOD_CATEGORY_MAX_LENGTH, "%s", extracted) < 0) { + mod->category = calloc(MOD_CATEGORY_SIZE, sizeof(char)); + if (snprintf(mod->category, MOD_CATEGORY_SIZE, "%s", extracted) < 0) { LOG_INFO("Truncated mod category field '%s'", mod->category); } } else if (mod->description == NULL && (extracted = extract_lua_field("-- description:", buffer))) { - mod->description = calloc(MOD_DESCRIPTION_MAX_LENGTH + 1, sizeof(char)); - if (snprintf(mod->description, MOD_DESCRIPTION_MAX_LENGTH, "%s", extracted) < 0) { + mod->description = calloc(MOD_DESCRIPTION_SIZE, sizeof(char)); + if (snprintf(mod->description, MOD_DESCRIPTION_SIZE, "%s", extracted) < 0) { LOG_INFO("Truncated mod description field '%s'", mod->description); } } else if ((extracted = extract_lua_field("-- pausable:", buffer))) { @@ -632,17 +626,19 @@ bool mod_load(struct Mods* mods, char* basePath, char* modName) { mod_extract_fields(mod); // set name - if (mod->name == NULL) { - mod->name = strdup(modName); + if (!mod->name[0]) { + if (snprintf(mod->name, MOD_NAME_SIZE, "%s", modName) < 0) { + LOG_INFO("Truncated mod name field '%s'", mod->name); + } } // set category if (mod->category == NULL) { - char *modNameNoColor = str_remove_color_codes(mod->name); + char modNameNoColor[MOD_NAME_SIZE]; + djui_text_get_uncolored_string(modNameNoColor, MOD_NAME_SIZE, mod->name); if (strstr(modNameNoColor, "[CS]") == modNameNoColor) { mod->category = strdup("cs"); } - free(modNameNoColor); } // print diff --git a/src/pc/mods/mod.h b/src/pc/mods/mod.h index ea1e6c72a..9f14f361e 100644 --- a/src/pc/mods/mod.h +++ b/src/pc/mods/mod.h @@ -10,6 +10,11 @@ #define MOD_CATEGORY_MAX_LENGTH 64 #define MOD_DESCRIPTION_MAX_LENGTH 800 +#define MOD_NAME_SIZE (MOD_NAME_MAX_LENGTH + 1) +#define MOD_INCOMPATIBLE_SIZE (MOD_INCOMPATIBLE_MAX_LENGTH + 1) +#define MOD_CATEGORY_SIZE (MOD_CATEGORY_MAX_LENGTH + 1) +#define MOD_DESCRIPTION_SIZE (MOD_DESCRIPTION_MAX_LENGTH + 1) + struct Mods; struct ModFile { @@ -25,7 +30,7 @@ struct ModFile { }; struct Mod { - char* name; + char name[MOD_NAME_SIZE]; char* incompatible; char* category; char* description; diff --git a/src/pc/mods/mods.c b/src/pc/mods/mods.c index 9af7ab2b3..84b8fb497 100644 --- a/src/pc/mods/mods.c +++ b/src/pc/mods/mods.c @@ -184,19 +184,20 @@ static void mods_sort(struct Mods* mods) { } // By default, this is the alphabetical order on name + char modNameNoColor_i[MOD_NAME_SIZE]; + char modNameNoColor_j[MOD_NAME_SIZE]; for (s32 i = 1; i < mods->entryCount; ++i) { - struct Mod* mod = mods->entries[i]; + struct Mod* mod_i = mods->entries[i]; + djui_text_get_uncolored_string(modNameNoColor_i, MOD_NAME_SIZE, mod_i->name); for (s32 j = 0; j < i; ++j) { - struct Mod* mod2 = mods->entries[j]; - char* name = str_remove_color_codes(mod->name); - char* name2 = str_remove_color_codes(mod2->name); - if (strcmp(name, name2) < 0) { - mods->entries[i] = mod2; - mods->entries[j] = mod; - mod = mods->entries[i]; + struct Mod* mod_j = mods->entries[j]; + djui_text_get_uncolored_string(modNameNoColor_j, MOD_NAME_SIZE, mod_j->name); + if (strcmp(modNameNoColor_i, modNameNoColor_j) < 0) { + mods->entries[i] = mod_j; + mods->entries[j] = mod_i; + mod_i = mod_j; + memcpy(modNameNoColor_i, modNameNoColor_j, MOD_NAME_SIZE * sizeof(char)); } - free(name); - free(name2); } } } diff --git a/src/pc/network/packets/packet_download.c b/src/pc/network/packets/packet_download.c index ab5aa4917..3d26a7a30 100644 --- a/src/pc/network/packets/packet_download.c +++ b/src/pc/network/packets/packet_download.c @@ -318,13 +318,13 @@ after_filled:; // Cache any mod that doesn't have "(wip)" or "[wip]" in its name (case-insensitive) static bool should_cache_mod(struct Mod *mod) { - char *modName = sys_strdup(mod->name); - sys_strlwr(modName); + char modNameLowercase[MOD_NAME_SIZE]; + memcpy(modNameLowercase, mod->name, MOD_NAME_SIZE * sizeof(char)); + sys_strlwr(modNameLowercase); bool shouldCache = ( - !strstr(modName, "(wip)") && - !strstr(modName, "[wip]") + !strstr(modNameLowercase, "(wip)") && + !strstr(modNameLowercase, "[wip]") ); - free(modName); return shouldCache; } diff --git a/src/pc/network/packets/packet_mod_list.c b/src/pc/network/packets/packet_mod_list.c index 2a4a7baf8..23a4411d2 100644 --- a/src/pc/network/packets/packet_mod_list.c +++ b/src/pc/network/packets/packet_mod_list.c @@ -197,9 +197,8 @@ void network_receive_mod_list_entry(struct Packet* p) { } // get name - char name[MOD_NAME_MAX_LENGTH + 1] = { 0 }; - packet_read(p, name, nameLength * sizeof(u8)); - mod->name = strdup(name); + packet_read(p, mod->name, nameLength * sizeof(u8)); + mod->name[nameLength] = 0; // get incompatible length u16 incompatibleLength = 0; @@ -211,7 +210,7 @@ void network_receive_mod_list_entry(struct Packet* p) { // get incompatible if (incompatibleLength > 0) { - char incompatible[MOD_INCOMPATIBLE_MAX_LENGTH + 1] = { 0 }; + char incompatible[MOD_INCOMPATIBLE_SIZE] = { 0 }; packet_read(p, incompatible, incompatibleLength * sizeof(u8)); mod->incompatible = strdup(incompatible); } else { diff --git a/src/pc/utils/misc.c b/src/pc/utils/misc.c index 34e70241a..e358b942d 100644 --- a/src/pc/utils/misc.c +++ b/src/pc/utils/misc.c @@ -594,18 +594,3 @@ void str_seperator_concat(char *output_buffer, int buffer_size, char** strings, } } } - -char *str_remove_color_codes(const char *str) { - char *output = strdup(str); - char *startColor; - while ((startColor = strstr(output, "\\#"))) { - char *endColor = strchr(startColor + 2, '\\'); - if (endColor) { - memmove(startColor, endColor + 1, strlen(endColor + 1) + 1); - } else { - *startColor = 0; - break; - } - } - return output; -} diff --git a/src/pc/utils/misc.h b/src/pc/utils/misc.h index 0efabc869..01d813fa1 100644 --- a/src/pc/utils/misc.h +++ b/src/pc/utils/misc.h @@ -37,6 +37,5 @@ void delta_interpolate_mtx(Mtx* out, Mtx* a, Mtx* b, f32 delta); void detect_and_skip_mtx_interpolation(Mtx** mtxPrev, Mtx** mtx); void str_seperator_concat(char *output_buffer, int buffer_size, char** strings, int num_strings, char* seperator); -char *str_remove_color_codes(const char *str); #endif \ No newline at end of file diff --git a/textures/custom_font/custom_font_hud_recolor.rgba32.png b/textures/custom_font/custom_font_hud_recolor.rgba32.png index 1ad909523f230e5293f4d9e5f2862811c89d2455..3f1bf60bea8f5e84458b49d6da86fd27487862e5 100644 GIT binary patch literal 39601 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 DdY}p@