diff --git a/Makefile b/Makefile index 40ecaa833..ba6c61f57 100644 --- a/Makefile +++ b/Makefile @@ -316,7 +316,7 @@ LEVEL_DIRS := $(patsubst levels/%,%,$(dir $(wildcard levels/*/header.h))) # Hi, I'm a PC SRC_DIRS := src src/engine src/game src/audio src/menu src/buffers actors levels bin data assets src/pc src/pc/gfx src/pc/audio src/pc/controller src/pc/fs src/pc/fs/packtypes -SRC_DIRS += src/pc/network src/pc/network/packets src/pc/network/socket src/pc/utils +SRC_DIRS += src/pc/network src/pc/network/packets src/pc/network/socket src/pc/utils src/pc/djui ASM_DIRS := #ifeq ($(DISCORDRPC),1) diff --git a/build-windows-visual-studio/sm64ex.vcxproj b/build-windows-visual-studio/sm64ex.vcxproj index 3cf256957..8c66ea60a 100644 --- a/build-windows-visual-studio/sm64ex.vcxproj +++ b/build-windows-visual-studio/sm64ex.vcxproj @@ -3940,6 +3940,14 @@ + + + + + + + + @@ -4351,6 +4359,16 @@ + + + + + + + + + + diff --git a/build-windows-visual-studio/sm64ex.vcxproj.filters b/build-windows-visual-studio/sm64ex.vcxproj.filters index 03d2e65b3..8ef0b86e2 100644 --- a/build-windows-visual-studio/sm64ex.vcxproj.filters +++ b/build-windows-visual-studio/sm64ex.vcxproj.filters @@ -3445,6 +3445,12 @@ {9ddfaa87-399e-4f61-aae3-f91af79e14cc} + + {0e1e4798-796e-4801-be6e-69d0c3b05ad7} + + + {471ac819-ed6b-4a33-8952-50a8e0d2d839} + @@ -15144,6 +15150,30 @@ Source Files\src\pc\network\packets + + Source Files\src\pc\djui + + + Source Files\src\pc\djui + + + Source Files\src\pc\djui\components + + + Source Files\src\pc\djui\components + + + Source Files\src\pc\djui\components + + + Source Files\src\pc\djui\components + + + Source Files\src\pc\djui\components + + + Source Files\src\pc\djui\components + @@ -16117,5 +16147,35 @@ Header Files\src\pc\network + + Source Files\src\pc\djui + + + Source Files\src\pc\djui + + + Source Files\src\pc\djui + + + Source Files\src\pc\djui + + + Source Files\src\pc\djui\components + + + Source Files\src\pc\djui\components + + + Source Files\src\pc\djui\components + + + Source Files\src\pc\djui\components + + + Source Files\src\pc\djui\components + + + Source Files\src\pc\djui\components + \ No newline at end of file diff --git a/developer/compile.sh b/developer/compile.sh index 7666dcce3..0704f7716 100644 --- a/developer/compile.sh +++ b/developer/compile.sh @@ -1,2 +1,4 @@ #!/bin/bash -make BETTERCAMERA=1 NODRAWINGDISTANCE=1 DEBUG=1 IMMEDIATELOAD=1 DEVELOPMENT=1 STRICT=1 && winpty cgdb ./build/us_pc/sm64.us.f3dex2e.exe -ex 'break debug_breakpoint_here' -ex 'run' -ex 'quit' \ No newline at end of file +#make BETTERCAMERA=1 NODRAWINGDISTANCE=1 DEBUG=1 IMMEDIATELOAD=1 DEVELOPMENT=1 STRICT=1 && winpty cgdb ./build/us_pc/sm64.us.f3dex2e.exe -ex 'break debug_breakpoint_here' -ex 'run' -ex 'quit' +#make BETTERCAMERA=1 NODRAWINGDISTANCE=1 DEBUG=1 IMMEDIATELOAD=1 DEVELOPMENT=1 STRICT=1 && winpty cgdb ./build/us_pc/sm64.us.f3dex2e.exe -ex 'break debug_breakpoint_here' -ex 'run --server 27015 --configfile sm64config_server.txt' -ex 'quit' +make BETTERCAMERA=1 NODRAWINGDISTANCE=1 DEBUG=1 IMMEDIATELOAD=1 DEVELOPMENT=1 STRICT=1 && ./build/us_pc/sm64.us.f3dex2e.exe --server 27015 --configfile sm64config_server.txt diff --git a/developer/debug.sh b/developer/debug.sh index 432f59290..9830bdc3d 100644 --- a/developer/debug.sh +++ b/developer/debug.sh @@ -1,2 +1,2 @@ #!/bin/bash -winpty cgdb ./build/us_pc/sm64.us.f3dex2e.exe -ex 'break debug_breakpoint_here' +make BETTERCAMERA=1 NODRAWINGDISTANCE=1 DEBUG=1 IMMEDIATELOAD=1 DEVELOPMENT=1 STRICT=1 && winpty cgdb ./build/us_pc/sm64.us.f3dex2e.exe -ex 'break debug_breakpoint_here' diff --git a/include/PR/gbi.h b/include/PR/gbi.h index dd1bb4400..d74be8ca1 100644 --- a/include/PR/gbi.h +++ b/include/PR/gbi.h @@ -21,6 +21,7 @@ #define _GBI_H_ #include +#include "src/pc/djui/djui_gbi.h" /* * To use the F3DEX ucodes, define F3DEX_GBI before include this file. diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c index bbf2c76dd..427466d34 100644 --- a/src/game/ingame_menu.c +++ b/src/game/ingame_menu.c @@ -26,6 +26,7 @@ #include "macros.h" #include "pc/cheats.h" #include "pc/network/network.h" +#include "pc/djui/djui.h" #ifdef BETTERCAMERA #include "bettercamera.h" #endif @@ -414,26 +415,31 @@ void render_multi_text_string(s16 *xPos, s16 *yPos, s8 multiTextID) } #endif +u8 str_ascii_char_to_dialog(char c) { + switch (c) { + case '\'': return 0x3E; + case '.': return 0x3F; + case ',': return DIALOG_CHAR_COMMA; + case '-': return 0x9F; + case '(': return 0xE1; + case ')': return 0xE3; + case '&': return 0xE5; + case '!': return 0xF2; + case '%': return 0xF3; + case '?': return 0xF4; + case '"': return 0xF6; // 0xF5 is opening quote + case '~': return 0xF7; + case '*': return 0xFB; + case ' ': return DIALOG_CHAR_SPACE; + case '\n': return DIALOG_CHAR_NEWLINE; + case '\0': return DIALOG_CHAR_TERMINATOR; + default: return ((u8)c < 0xF0) ? ASCII_TO_DIALOG(c) : c; + } +} + void str_ascii_to_dialog(const char* string, u8* dialog, u16 length) { for (int i = 0; i < length; i++) { - switch (string[i]) { - case '\'': dialog[i] = 0x3E; break; - case '.': dialog[i] = 0x3F; break; - case ',': dialog[i] = DIALOG_CHAR_COMMA; break; - case '-': dialog[i] = 0x9F; break; - case '(': dialog[i] = 0xE1; break; - case ')': dialog[i] = 0xE3; break; - case '&': dialog[i] = 0xE5; break; - case '!': dialog[i] = 0xF2; break; - case '%': dialog[i] = 0xF3; break; - case '?': dialog[i] = 0xF4; break; - case '"': dialog[i] = 0xF6; break; // 0xF5 is opening quote - case '~': dialog[i] = 0xF7; break; - case '*': dialog[i] = 0xFB; break; - case ' ': dialog[i] = DIALOG_CHAR_SPACE; break; - case '\n': dialog[i] = DIALOG_CHAR_NEWLINE; break; - default: dialog[i] = ((u8)string[i] < 0xF0) ? ASCII_TO_DIALOG(string[i]) : string[i]; - } + dialog[i] = str_ascii_char_to_dialog(string[i]); } dialog[length] = DIALOG_CHAR_TERMINATOR; } @@ -3196,6 +3202,7 @@ s16 render_menus_and_dialogs() { create_dl_ortho_matrix(); render_chat(); + djui_render(); if (gMenuMode != -1) { switch (gMenuMode) { diff --git a/src/game/ingame_menu.h b/src/game/ingame_menu.h index 73e136473..d3c218968 100644 --- a/src/game/ingame_menu.h +++ b/src/game/ingame_menu.h @@ -30,6 +30,10 @@ #define RENDER_COURSE_DONE_SCREEN 2 +#if !defined(VERSION_JP) && !defined(VERSION_SH) +extern u8 gDialogCharWidths[]; +#endif + extern s8 gDialogCourseActNum; extern s8 gHudFlash; @@ -117,6 +121,7 @@ void create_dl_identity_matrix(void); void create_dl_translation_matrix(s8 pushOp, f32 x, f32 y, f32 z); void create_dl_ortho_matrix(void); void render_generic_char(u8 c); +u8 str_ascii_char_to_dialog(char c); void str_ascii_to_dialog(const char* string, u8* dialog, u16 length); f32 get_generic_dialog_width(u8* dialog); f32 get_generic_ascii_string_width(const char* ascii); diff --git a/src/pc/djui/djui.c b/src/pc/djui/djui.c new file mode 100644 index 000000000..26dc1d84d --- /dev/null +++ b/src/pc/djui/djui.c @@ -0,0 +1,89 @@ +#include "djui.h" + +struct DjuiRoot* gDjuiRoot = NULL; + +ALIGNED8 static const u8 texture32x32[] = { +#include "actors/bubble/mr_i_bubble.rgba16.inc.c" +}; +ALIGNED8 static const u8 texture16x16[] = { +#include "textures/segment2/custom_luigi_head.rgba16.inc.c" +}; + +void djui_render(void) { + static struct DjuiRect* sDjuiRect = NULL; + static struct DjuiRect* sDjuiRect2 = NULL; + static struct DjuiText* sDjuiText = NULL; + static struct DjuiImage* sDjuiImage = NULL; + static struct DjuiButton* sDjuiButton = NULL; + + if (gDjuiRoot == NULL) { + gDjuiRoot = djui_root_create(); + + struct DjuiRect* imageContainer = djui_rect_create(&gDjuiRoot->base); + imageContainer->base.x = 32; + imageContainer->base.y = 32; + imageContainer->base.width = 64; + imageContainer->base.height = 64; + + sDjuiImage = djui_image_create(&imageContainer->base, texture16x16, 16, 16); + sDjuiImage->base.x = 16; + sDjuiImage->base.y = 16; + sDjuiImage->base.width = 32; + sDjuiImage->base.height = 32; + sDjuiImage->base.color.r = 255; + + ////////////// + + sDjuiRect = djui_rect_create(&gDjuiRoot->base); + sDjuiRect->base.x = 64; + sDjuiRect->base.y = 64; + sDjuiRect->base.width = 188; + sDjuiRect->base.height = 64; + sDjuiRect->base.color.a = 200; + sDjuiRect->base.hAlign = DJUI_HALIGN_LEFT; + sDjuiRect->base.vAlign = DJUI_VALIGN_BOTTOM; + + sDjuiRect2 = djui_rect_create(&sDjuiRect->base); + sDjuiRect2->base.x = 0; + sDjuiRect2->base.y = 0; + //sDjuiRect2->base.color.r = 0; + sDjuiRect2->base.hAlign = DJUI_HALIGN_CENTER; + sDjuiRect2->base.vAlign = DJUI_VALIGN_CENTER; + sDjuiRect2->base.color.a = 255; + sDjuiRect2->base.width = 188 - 8; + sDjuiRect2->base.height = 64 - 8; + + sDjuiText = djui_text_create(&sDjuiRect2->base, "Host"); + sDjuiText->base.color.r = 111; + sDjuiText->base.color.g = 111; + sDjuiText->base.color.b = 111; + sDjuiText->fontSize = 2; + sDjuiText->base.color.a = 255; + sDjuiText->base.width = 188 - 8; + sDjuiText->base.height = 64 - 8; + sDjuiText->base.x = 0; + sDjuiText->base.y = 0; + sDjuiText->textHAlign = DJUI_HALIGN_CENTER; + sDjuiText->textVAlign = DJUI_VALIGN_CENTER; + + sDjuiButton = djui_button_create(&gDjuiRoot->base, "button"); + } + + djui_base_render(&gDjuiRoot->base); + + if (sDjuiRect2 != NULL) { + static u32 sTimer = 0; + sTimer++; + sDjuiImage->base.x = 16.0f + cos((sTimer) / 10.0f) * 24.0f; + sDjuiImage->base.y = 16.0f + sin((sTimer) / 31.0f) * 24.0f; + sDjuiImage->base.color.r = 127.0 + sin((sTimer) / 13.0f) * 127.0f; + sDjuiImage->base.color.g = 127.0 + sin((sTimer) / 17.0f) * 127.0f; + sDjuiImage->base.color.b = 127.0 + sin((sTimer) / 23.0f) * 127.0f; + + sDjuiRect2->base.x = 32.0f + cos((sTimer) / 10.0f) * 64.0f; + sDjuiRect2->base.y = 32.0f + sin((sTimer) / 31.0f) * 64.0f; + + sDjuiButton->base.width = 200 + cos((sTimer) / 10.0f) * 64.0f; + sDjuiButton->base.height = 64 + sin((sTimer) / 10.0f) * 16.0f; + } +} \ No newline at end of file diff --git a/src/pc/djui/djui.h b/src/pc/djui/djui.h new file mode 100644 index 000000000..5d6c31480 --- /dev/null +++ b/src/pc/djui/djui.h @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "djui_types.h" +#include "djui_gfx.h" +#include "djui_base.h" + +#include "djui_root.h" +#include "djui_rect.h" +#include "djui_text.h" +#include "djui_image.h" + +#include "djui_button.h" + +#include "game/game_init.h" +#include "game/ingame_menu.h" + +extern struct DjuiRoot* gDjuiRoot; + +void djui_render(void); diff --git a/src/pc/djui/djui_base.c b/src/pc/djui/djui_base.c new file mode 100644 index 000000000..3a8f9b66a --- /dev/null +++ b/src/pc/djui/djui_base.c @@ -0,0 +1,209 @@ +#include "djui.h" + + //////////////// + // properties // +//////////////// + +void djui_base_set_location(struct DjuiBase* base, f32 x, f32 y) { + base->x = x; + base->y = y; +} + +void djui_base_set_size(struct DjuiBase* base, f32 width, f32 height) { + base->width = width; + base->height = height; + base->widthFill = false; + base->heightFill = false; +} + +void djui_base_set_size_fill(struct DjuiBase* base, f32 widthScale, f32 heightScale) { + base->width = widthScale; + base->height = heightScale; + base->widthFill = true; + base->heightFill = true; +} + +void djui_base_set_color(struct DjuiBase* base, u8 r, u8 g, u8 b, u8 a) { + base->color.r = r; + base->color.g = g; + base->color.b = b; + base->color.a = a; +} + +void djui_base_set_alignment(struct DjuiBase* base, enum DjuiHAlign hAlign, enum DjuiVAlign vAlign) { + base->hAlign = hAlign; + base->vAlign = vAlign; +} + + ///////////// + // utility // +///////////// + +static void djui_base_clip(struct DjuiBase* base) { + struct DjuiBase* parent = base->parent; + struct DjuiBaseRect* comp = &base->comp; + struct DjuiBaseRect* clip = &base->clip; + + clip->x = comp->x; + clip->y = comp->y; + clip->width = comp->width; + clip->height = comp->height; + + if (parent == NULL) { return; } + + clip->x = fmax(clip->x, parent->clip.x); + clip->y = fmax(clip->y, parent->clip.y); + + clip->width = (comp->x + comp->width) - clip->x; + clip->height = (comp->y + comp->height) - clip->y; + + clip->width = fmin(clip->width, (parent->clip.x + parent->clip.width) - clip->x); + clip->height = fmin(clip->height, (parent->clip.y + parent->clip.height) - clip->y); +} + +void djui_base_compute(struct DjuiBase* base) { + struct DjuiBase* parent = base->parent; + struct DjuiBaseRect* comp = &base->comp; + + f32 x = base->x; + f32 y = base->y; + f32 width = base->widthFill ? (parent->comp.width - x) : base->width; + f32 height = base->heightFill ? (parent->comp.height - y) : base->height; + + // horizontal alignment + if (base->hAlign == DJUI_HALIGN_CENTER) { + x += (parent->comp.width - width) / 2.0f; + } else if (base->hAlign == DJUI_HALIGN_RIGHT) { + x = parent->comp.width - width - x; + } + + // vertical alignment + if (base->vAlign == DJUI_VALIGN_CENTER) { + y += (parent->comp.height - height) / 2.0f; + } else if (base->vAlign == DJUI_VALIGN_BOTTOM) { + y = parent->comp.height - height - y; + } + + // offset comp on parent's location + if (base->parent != NULL) { + x += parent->comp.x; + y += parent->comp.y; + } + + // set computed values + comp->x = x; + comp->y = y; + comp->width = width; + comp->height = height; + + djui_base_clip(base); +} + +static void djui_base_add_child(struct DjuiBase* parent, struct DjuiBase* base) { + if (parent == NULL) { return; } + + // allocate linked list node + struct DjuiBaseChild* baseChild = malloc(sizeof(struct DjuiBaseChild)); + baseChild->base = base; + baseChild->next = NULL; + + // add it to the head + if (parent->child == NULL) { + parent->child = baseChild; + return; + } + + // add it to the tail + struct DjuiBaseChild* parentChild = parent->child; + while (parentChild != NULL) { + if (parentChild->next == NULL) { + parentChild->next = baseChild; + break; + } + parentChild = parentChild->next; + } +} + + //////////// + // events // +//////////// + +void djui_base_render(struct DjuiBase* base) { + if (!base->visible) { return; } + + struct DjuiBaseRect* comp = &base->comp; + djui_base_compute(base); + if (comp->width <= 0) { return; } + if (comp->height <= 0) { return; } + + if (base->render != NULL) { + base->render(base); + } + + // render all children + struct DjuiBaseChild* child = base->child; + while (child != NULL) { + djui_base_render(child->base); + child = child->next; + } +} + +void djui_base_destroy(struct DjuiBase* base) { + // remove myself from parent's linked list + if (base->parent != NULL) { + struct DjuiBaseChild* child = base->parent->child; + struct DjuiBaseChild* lastChild = NULL; + while (child != NULL) { + if (child->base == base) { + // adjust linked list + if (lastChild == NULL) { + base->parent->child = child->next; + } else { + lastChild->next = child->next; + } + // deallocate child node + free(child); + base->parent = NULL; + break; + } + + // iterate + lastChild = child; + child = child->next; + } + } + + // destroy all children and our linked list + struct DjuiBaseChild* child = base->child; + while (child != NULL) { + struct DjuiBaseChild* nextChild = child; + child->base->parent = NULL; + djui_base_destroy(child->base); + free(child); + child = nextChild->next; + } + + // destroy this + base->destroy(base); +} + +void djui_base_init(struct DjuiBase* parent, struct DjuiBase* base, void(*render)(struct DjuiBase*), void (*destroy)(struct DjuiBase*)) { + base->parent = parent; + base->child = NULL; + base->visible = true; + base->x = 0; + base->y = 0; + base->width = 64; + base->height = 64; + base->widthFill = false; + base->heightFill = false; + base->color.r = 255; + base->color.g = 255; + base->color.b = 255; + base->color.a = 255; + base->hAlign = DJUI_HALIGN_LEFT; + base->vAlign = DJUI_VALIGN_TOP; + base->render = render; + base->destroy = destroy; + djui_base_add_child(parent, base); +} diff --git a/src/pc/djui/djui_base.h b/src/pc/djui/djui_base.h new file mode 100644 index 000000000..4312465e2 --- /dev/null +++ b/src/pc/djui/djui_base.h @@ -0,0 +1,48 @@ +#pragma once +#include "djui.h" + +#pragma pack(1) +struct DjuiBaseRect { + f32 x; + f32 y; + f32 width; + f32 height; +}; + +#pragma pack(1) +struct DjuiBaseChild { + struct DjuiBase* base; + struct DjuiBaseChild* next; +}; + +#pragma pack(1) +struct DjuiBase { + struct DjuiBase* parent; + struct DjuiBaseChild* child; + bool visible; + f32 x; + f32 y; + f32 width; + f32 height; + bool widthFill; + bool heightFill; + struct DjuiColor color; + enum DjuiHAlign hAlign; + enum DjuiVAlign vAlign; + struct DjuiBaseRect comp; + struct DjuiBaseRect clip; + void (*render)(struct DjuiBase*); + void (*destroy)(struct DjuiBase*); +}; + +void djui_base_set_location(struct DjuiBase* base, f32 x, f32 y); +void djui_base_set_size(struct DjuiBase* base, f32 width, f32 height); +void djui_base_set_size_fill(struct DjuiBase* base, f32 widthScale, f32 heightScale); +void djui_base_set_color(struct DjuiBase* base, u8 r, u8 g, u8 b, u8 a); +void djui_base_set_alignment(struct DjuiBase* base, enum DjuiHAlign hAlign, enum DjuiVAlign vAlign); + +void djui_base_compute(struct DjuiBase* base); + +void djui_base_render(struct DjuiBase* base); +void djui_base_destroy(struct DjuiBase* base); +void djui_base_init(struct DjuiBase* parent, struct DjuiBase* base, void (*render)(struct DjuiBase*), void (*destroy)(struct DjuiBase*)); diff --git a/src/pc/djui/djui_button.c b/src/pc/djui/djui_button.c new file mode 100644 index 000000000..b9427a37e --- /dev/null +++ b/src/pc/djui/djui_button.c @@ -0,0 +1,27 @@ +#include "djui.h" + +static void djui_button_destroy(struct DjuiBase* base) { + struct DjuiButton* button = (struct DjuiButton*)base; + free(button); +} + +struct DjuiButton* djui_button_create(struct DjuiBase* parent, const char* message) { + struct DjuiButton* button = malloc(sizeof(struct DjuiButton)); + struct DjuiBase* base = &button->base; + + djui_base_init(parent, base, NULL, djui_button_destroy); + djui_base_set_size(base, 200, 64); + + struct DjuiRect* rect = djui_rect_create(&button->base); + djui_base_set_size_fill(&rect->base, 1.0f, 1.0f); + djui_base_set_color(&rect->base, 225, 225, 225, 255); + button->rect = rect; + + struct DjuiText* text = djui_text_create(&rect->base, message); + djui_base_set_size_fill(&text->base, 1.0f, 1.0f); + djui_base_set_color(&text->base, 11, 11, 11, 255); + djui_text_set_alignment(text, DJUI_HALIGN_CENTER, DJUI_VALIGN_CENTER); + button->text = text; + + return button; +} diff --git a/src/pc/djui/djui_button.h b/src/pc/djui/djui_button.h new file mode 100644 index 000000000..3e5fb4a2d --- /dev/null +++ b/src/pc/djui/djui_button.h @@ -0,0 +1,11 @@ +#pragma once +#include "djui.h" + +#pragma pack(1) +struct DjuiButton { + struct DjuiBase base; + struct DjuiRect* rect; + struct DjuiText* text; +}; + +struct DjuiButton* djui_button_create(struct DjuiBase* parent, const char* message); diff --git a/src/pc/djui/djui_gbi.h b/src/pc/djui/djui_gbi.h new file mode 100644 index 000000000..fa5c5e14c --- /dev/null +++ b/src/pc/djui/djui_gbi.h @@ -0,0 +1,29 @@ +#pragma once + +#define G_TEXCLIP_DJUI 0xe1 +#define G_TEXOVERRIDE_DJUI 0xe0 +#define G_EXECUTE_DJUI 0xdd + +#define gSetClippingDjui(pkt, cmd, rot, x1, y1, x2, y2) \ +{ \ + Gfx *_g = (Gfx *)(pkt); \ + _g->words.w0 = _SHIFTL(cmd, 24, 8) | _SHIFTL( x1, 16, 8) | \ + _SHIFTL( y1, 8, 8) | _SHIFTL(rot, 0, 8); \ + _g->words.w1 = _SHIFTL(x2, 16, 8) | _SHIFTL(y2, 8, 8); \ +} + +#define gSetOverrideDjui(pkt, cmd, texture, w, h) \ +{ \ + Gfx *_g = (Gfx *)(pkt); \ + _g->words.w0 = _SHIFTL(cmd, 24, 8) | _SHIFTL(w, 16, 8) | \ + _SHIFTL(h, 8, 8); \ + _g->words.w1 = (uintptr_t)(texture); \ +} + +#define gsSPExecuteDjui(word) \ +{{ \ + _SHIFTL(G_EXECUTE_DJUI, 24, 8), (unsigned int)(word) \ +}} + +#define gDPSetTextureClippingDjui(pkt, rot, x1, y1, x2, y2) gSetClippingDjui(pkt, G_TEXCLIP_DJUI, rot, x1, y1, x2, y2) +#define gDPSetTextureOverrideDjui(pkt, texture, w, h) gSetOverrideDjui(pkt, G_TEXOVERRIDE_DJUI, texture, w, h) diff --git a/src/pc/djui/djui_gfx.c b/src/pc/djui/djui_gfx.c new file mode 100644 index 000000000..4804f8eff --- /dev/null +++ b/src/pc/djui/djui_gfx.c @@ -0,0 +1,156 @@ +#include +#include "sm64.h" +#include "djui.h" +#include "game/ingame_menu.h" +#include "game/segment2.h" +#include "src/pc/pc_main.h" +#include "src/pc/gfx/gfx_window_manager_api.h" +#include "gfx_dimensions.h" + +static const Vtx vertex_djui_simple_rect[] = { + {{{ 0, -1, 0}, 0, { 0, 0 }, { 0xff, 0xff, 0xff, 0xff }}}, + {{{ 1, -1, 0}, 0, { 0, 0 }, { 0xff, 0xff, 0xff, 0xff }}}, + {{{ 1, 0, 0}, 0, { 0, 0 }, { 0xff, 0xff, 0xff, 0xff }}}, + {{{ 0, 0, 0}, 0, { 0, 0 }, { 0xff, 0xff, 0xff, 0xff }}}, +}; + +const Gfx dl_djui_simple_rect[] = { + gsDPPipeSync(), + gsSPClearGeometryMode(G_LIGHTING), + gsDPSetCombineMode(G_CC_FADE, G_CC_FADE), + gsDPSetRenderMode(G_RM_XLU_SURF, G_RM_XLU_SURF2), + gsSPVertex(vertex_djui_simple_rect, 4, 0), + gsSP2Triangles(0, 1, 2, 0x0, 0, 2, 3, 0x0), + gsSPEndDisplayList(), +}; + +///////////////////////////////////////////// + +static Vtx vertex_djui_ia_char[] = { + {{{ 0, -16, 0}, 0, { 0, 256}, { 0xff, 0xff, 0xff, 0xff }}}, + {{{ 8, -16, 0}, 0, { 0, 0}, { 0xff, 0xff, 0xff, 0xff }}}, + {{{ 8, 0, 0}, 0, { 512, 0}, { 0xff, 0xff, 0xff, 0xff }}}, + {{{ 0, 0, 0}, 0, { 512, 256}, { 0xff, 0xff, 0xff, 0xff }}}, +}; + +const Gfx dl_djui_ia_text_begin[] = { + gsDPPipeSync(), + gsSPClearGeometryMode(G_LIGHTING), + gsDPSetCombineMode(G_CC_FADEA, G_CC_FADEA), + gsDPSetEnvColor(255, 255, 255, 255), + gsDPSetRenderMode(G_RM_XLU_SURF, G_RM_XLU_SURF2), + gsDPSetTextureFilter(G_TF_POINT), + gsSPTexture(0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON), + gsSPEndDisplayList(), +}; + +const Gfx dl_djui_ia_text_settings[] = { + gsDPSetTile(G_IM_FMT_IA, G_IM_SIZ_16b, 0, 0, G_TX_LOADTILE, 0, G_TX_WRAP | G_TX_NOMIRROR, 3, G_TX_NOLOD, G_TX_WRAP | G_TX_NOMIRROR, 4, G_TX_NOLOD), + gsDPLoadSync(), + gsDPLoadBlock(G_TX_LOADTILE, 0, 0, ((16 * 8 + G_IM_SIZ_4b_INCR) >> G_IM_SIZ_4b_SHIFT) - 1, CALC_DXT(16, G_IM_SIZ_4b_BYTES)), + gsDPSetTile(G_IM_FMT_IA, G_IM_SIZ_4b, 1, 0, G_TX_RENDERTILE, 0, G_TX_WRAP | G_TX_NOMIRROR, 3, G_TX_NOLOD, G_TX_WRAP | G_TX_NOMIRROR, 4, G_TX_NOLOD), + gsDPSetTileSize(0, 0, 0, (16 - 1) << G_TEXTURE_IMAGE_FRAC, (8 - 1) << G_TEXTURE_IMAGE_FRAC), + gsSPVertex(vertex_djui_ia_char, 4, 0), + gsSPExecuteDjui(G_TEXCLIP_DJUI), + gsSP2Triangles(0, 1, 2, 0x0, 0, 2, 3, 0x0), + gsSPEndDisplayList(), +}; + +void djui_gfx_render_char(u8 c) { + void** fontLUT; + void* packedTexture; + + fontLUT = segmented_to_virtual(main_font_lut); + packedTexture = segmented_to_virtual(fontLUT[c]); + + gDPPipeSync(gDisplayListHead++); + gDPSetTextureImage(gDisplayListHead++, G_IM_FMT_IA, G_IM_SIZ_16b, 1, VIRTUAL_TO_PHYSICAL(packedTexture)); + gSPDisplayList(gDisplayListHead++, dl_djui_ia_text_settings); + +} +///////////////////////////////////////////// + +static const Vtx vertex_djui_image[] = { + {{{ 0, -1, 0 }, 0, { 0, 512 }, { 0xff, 0xff, 0xff, 0xff }}}, + {{{ 1, -1, 0 }, 0, { 512, 512 }, { 0xff, 0xff, 0xff, 0xff }}}, + {{{ 1, 0, 0 }, 0, { 512, 0 }, { 0xff, 0xff, 0xff, 0xff }}}, + {{{ 0, 0, 0 }, 0, { 0, 0 }, { 0xff, 0xff, 0xff, 0xff }}}, +}; + +const Gfx dl_djui_image[] = { + gsDPPipeSync(), + gsSPClearGeometryMode(G_LIGHTING), + gsDPSetCombineMode(G_CC_FADEA, G_CC_FADEA), + gsDPSetRenderMode(G_RM_XLU_SURF, G_RM_XLU_SURF2), + gsDPSetTextureFilter(G_TF_POINT), + gsSPTexture(0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON), + gsDPLoadTextureBlock(NULL, G_IM_FMT_RGBA, G_IM_SIZ_16b, 16, 16, 0, G_TX_CLAMP, G_TX_CLAMP, 5, 5, G_TX_NOLOD, G_TX_NOLOD), + gsSPExecuteDjui(G_TEXOVERRIDE_DJUI), + gsSPVertex(vertex_djui_image, 4, 0), + gsSPExecuteDjui(G_TEXCLIP_DJUI), + gsSP2Triangles(0, 1, 2, 0x0, 0, 2, 3, 0x0), + gsSPTexture(0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_OFF), + gsDPSetCombineMode(G_CC_SHADE, G_CC_SHADE), + gsSPEndDisplayList(), +}; + +void djui_gfx_render_texture(const u8* texture, u16 w, u16 h) { + gDPSetTextureOverrideDjui(gDisplayListHead++, texture, w, h); + gSPDisplayList(gDisplayListHead++, dl_djui_image); +} + +///////////////////////////////////////////// + +void djui_gfx_position_translate(f32* x, f32* y) { + u32 windowWidth, windowHeight; + wm_api->get_dimensions(&windowWidth, &windowHeight); + *x = GFX_DIMENSIONS_FROM_LEFT_EDGE(0) + *x * ((f32)SCREEN_HEIGHT / (f32)windowHeight); + *y = SCREEN_HEIGHT - *y * ((f32)SCREEN_HEIGHT / (f32)windowHeight); +} + +void djui_gfx_scale_translate(f32* width, f32* height) { + u32 windowWidth, windowHeight; + wm_api->get_dimensions(&windowWidth, &windowHeight); + + *width = *width * ((f32)SCREEN_HEIGHT / (f32)windowHeight); + *height = *height * ((f32)SCREEN_HEIGHT / (f32)windowHeight); +} + +void djui_gfx_size_translate(f32* size) { + u32 windowWidth, windowHeight; + wm_api->get_dimensions(&windowWidth, &windowHeight); + + *size = *size * ((f32)SCREEN_HEIGHT / (f32)windowHeight); +} + +bool djui_gfx_add_clipping_specific(struct DjuiBase* base, bool rotatedUV, f32 dX, f32 dY, f32 dW, f32 dH) { + struct DjuiBaseRect* clip = &base->clip; + + f32 clipX2 = clip->x + clip->width; + f32 clipY2 = clip->y + clip->height; + + f32 dX2 = dX + dW; + f32 dY2 = dY + dH; + + // completely clipped + if (dX2 < clip->x) { return true; } + if (dX > clipX2) { return true; } + if (dY2 < clip->y) { return true; } + if (dY > clipY2) { return true; } + + f32 dClipX1 = fmax((clip->x - dX) / dW, 0); + f32 dClipY1 = fmax((clip->y - dY) / dH, 0); + f32 dClipX2 = fmax((dX - (clipX2 - dW)) / dW, 0); + f32 dClipY2 = fmax((dY - (clipY2 - dH)) / dH, 0); + + if ((dClipX1 != 0) || (dClipY1 != 0) || (dClipX2 != 0) || (dClipY2 != 0)) { + gDPSetTextureClippingDjui(gDisplayListHead++, rotatedUV, (u8)(dClipX1 * 255), (u8)(dClipY1 * 255), (u8)(dClipX2 * 255), (u8)(dClipY2 * 255)); + } + + return false; +} + +bool djui_gfx_add_clipping(struct DjuiBase* base) { + struct DjuiBaseRect* comp = &base->comp; + return djui_gfx_add_clipping_specific(base, false, comp->x, comp->y, comp->width, comp->height); +} diff --git a/src/pc/djui/djui_gfx.h b/src/pc/djui/djui_gfx.h new file mode 100644 index 000000000..d6583fe0c --- /dev/null +++ b/src/pc/djui/djui_gfx.h @@ -0,0 +1,21 @@ +#pragma once +#include "djui.h" +#include "djui_base.h" + +#define DJUI_MTX_PUSH 1 +#define DJUI_MTX_NOPUSH 2 + +extern const Gfx dl_djui_simple_rect[]; +extern const Gfx dl_djui_ia_text_begin[]; +extern const Gfx dl_djui_img_begin[]; +extern const Gfx dl_djui_img_end[]; + +void djui_gfx_render_char(u8 c); +void djui_gfx_render_texture(const u8* texture, u16 w, u16 h); + +void djui_gfx_position_translate(f32* x, f32* y); +void djui_gfx_scale_translate(f32* width, f32* height); +void djui_gfx_size_translate(f32* size); + +bool djui_gfx_add_clipping_specific(struct DjuiBase* base, bool rotatedUV, f32 dX, f32 dY, f32 dW, f32 dH); +bool djui_gfx_add_clipping(struct DjuiBase* base); \ No newline at end of file diff --git a/src/pc/djui/djui_image.c b/src/pc/djui/djui_image.c new file mode 100644 index 000000000..cc43d4af2 --- /dev/null +++ b/src/pc/djui/djui_image.c @@ -0,0 +1,59 @@ +#include "djui.h" +#include "game/segment2.h" +#include "src/pc/network/network.h" + + //////////////// + // properties // +//////////////// + +void djui_image_set_image(struct DjuiImage* image, const u8* texture, u16 textureWidth, u16 textureHeight) { + image->texture = texture; + image->textureWidth = textureWidth; + image->textureHeight = textureHeight; +} + + //////////// + // events // +//////////// + +static void djui_image_render(struct DjuiBase* base) { + struct DjuiImage* image = (struct DjuiImage*)base; + struct DjuiBaseRect* comp = &base->comp; + struct DjuiBaseRect* clip = &base->clip; + + // translate position + f32 translatedX = comp->x; + f32 translatedY = comp->y; + djui_gfx_position_translate(&translatedX, &translatedY); + create_dl_translation_matrix(DJUI_MTX_PUSH, translatedX, translatedY, 0); + + // translate size + f32 translatedWidth = comp->width; + f32 translatedHeight = comp->height; + djui_gfx_scale_translate(&translatedWidth, &translatedHeight); + create_dl_scale_matrix(DJUI_MTX_NOPUSH, translatedWidth, translatedHeight, 1.0f); + + // render + if (!djui_gfx_add_clipping(base)) { + gDPSetEnvColor(gDisplayListHead++, base->color.r, base->color.g, base->color.b, base->color.a); + djui_gfx_render_texture(image->texture, image->textureWidth, image->textureHeight); + } + + gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); +} + +static void djui_image_destroy(struct DjuiBase* base) { + struct DjuiImage* image = (struct DjuiImage*)base; + free(image); +} + +struct DjuiImage* djui_image_create(struct DjuiBase* parent, const u8* texture, u16 textureWidth, u16 textureHeight) { + struct DjuiImage* image = malloc(sizeof(struct DjuiImage)); + struct DjuiBase* base = &image->base; + + djui_base_init(parent, base, djui_image_render, djui_image_destroy); + + djui_image_set_image(image, texture, textureWidth, textureHeight); + + return image; +} diff --git a/src/pc/djui/djui_image.h b/src/pc/djui/djui_image.h new file mode 100644 index 000000000..9bdecc197 --- /dev/null +++ b/src/pc/djui/djui_image.h @@ -0,0 +1,14 @@ +#pragma once +#include "djui.h" + +#pragma pack(1) +struct DjuiImage { + struct DjuiBase base; + const u8* texture; + u16 textureWidth; + u16 textureHeight; +}; + +void djui_image_set_image(struct DjuiImage* image, const u8* texture, u16 textureWidth, u16 textureHeight); + +struct DjuiImage* djui_image_create(struct DjuiBase* parent, const u8* texture, u16 textureWidth, u16 textureHeight); diff --git a/src/pc/djui/djui_rect.c b/src/pc/djui/djui_rect.c new file mode 100644 index 000000000..595c96ffd --- /dev/null +++ b/src/pc/djui/djui_rect.c @@ -0,0 +1,41 @@ +#include "djui.h" + + //////////// + // events // +//////////// + +static void djui_rect_render(struct DjuiBase* base) { + struct DjuiBaseRect* clip = &base->clip; + + // translate position + f32 translatedX = clip->x; + f32 translatedY = clip->y; + djui_gfx_position_translate(&translatedX, &translatedY); + create_dl_translation_matrix(DJUI_MTX_PUSH, translatedX, translatedY, 0); + + // translate size + f32 translatedWidth = clip->width; + f32 translatedHeight = clip->height; + djui_gfx_scale_translate(&translatedWidth, &translatedHeight); + create_dl_scale_matrix(DJUI_MTX_NOPUSH, translatedWidth, translatedHeight, 1.0f); + + // render + gDPSetEnvColor(gDisplayListHead++, base->color.r, base->color.g, base->color.b, base->color.a); + gSPDisplayList(gDisplayListHead++, dl_djui_simple_rect); + + gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); +} + +static void djui_rect_destroy(struct DjuiBase* base) { + struct DjuiRect* rect = (struct DjuiRect*)base; + free(rect); +} + +struct DjuiRect* djui_rect_create(struct DjuiBase* parent) { + struct DjuiRect* rect = malloc(sizeof(struct DjuiRect)); + struct DjuiBase* base = &rect->base; + + djui_base_init(parent, base, djui_rect_render, djui_rect_destroy); + + return rect; +} diff --git a/src/pc/djui/djui_rect.h b/src/pc/djui/djui_rect.h new file mode 100644 index 000000000..2d958567d --- /dev/null +++ b/src/pc/djui/djui_rect.h @@ -0,0 +1,9 @@ +#pragma once +#include "djui.h" + +#pragma pack(1) +struct DjuiRect { + struct DjuiBase base; +}; + +struct DjuiRect* djui_rect_create(struct DjuiBase* parent); diff --git a/src/pc/djui/djui_root.c b/src/pc/djui/djui_root.c new file mode 100644 index 000000000..a2d320484 --- /dev/null +++ b/src/pc/djui/djui_root.c @@ -0,0 +1,42 @@ +#include "djui.h" +#include "src/pc/pc_main.h" +#include "src/pc/gfx/gfx_window_manager_api.h" + +static void djui_root_render(struct DjuiBase* base) { + // grab window height + u32 windowWidth, windowHeight; + wm_api->get_dimensions(&windowWidth, &windowHeight); + + // fill the screen + base->x = 0; + base->y = 0; + base->width = windowWidth; + base->height = windowHeight; + + // compute base + djui_base_compute(base); +} + +static void djui_root_destroy(struct DjuiBase* base) { + struct DjuiRoot* root = (struct DjuiRoot*)base; + free(root); + gDjuiRoot = NULL; +} + +struct DjuiRoot* djui_root_create(void) { + struct DjuiRoot* root = malloc(sizeof(struct DjuiRoot)); + struct DjuiBase* base = &root->base; + + djui_base_init(NULL, base, djui_root_render, djui_root_destroy); + + u32 windowWidth, windowHeight; + wm_api->get_dimensions(&windowWidth, &windowHeight); + + base->x = 0; + base->y = 0; + base->width = windowWidth; + base->height = windowHeight; + base->color.a = 0; + + return root; +} diff --git a/src/pc/djui/djui_root.h b/src/pc/djui/djui_root.h new file mode 100644 index 000000000..b807d8db8 --- /dev/null +++ b/src/pc/djui/djui_root.h @@ -0,0 +1,9 @@ +#pragma once +#include "djui.h" + +#pragma pack(1) +struct DjuiRoot { + struct DjuiBase base; +}; + +struct DjuiRoot* djui_root_create(void); diff --git a/src/pc/djui/djui_text.c b/src/pc/djui/djui_text.c new file mode 100644 index 000000000..29838c920 --- /dev/null +++ b/src/pc/djui/djui_text.c @@ -0,0 +1,280 @@ +#include +#include "djui.h" +#include "game/segment2.h" + + //////////////// + // properties // +//////////////// + +void djui_text_set_text(struct DjuiText* text, const char* message) { + // deallocate old message + if (text->message != NULL) { + free(text->message); + } + + // allocate and set new message + u16 messageLen = strlen(message); + text->message = malloc(sizeof(char) * (messageLen + 1)); + memcpy(text->message, message, sizeof(char) * (messageLen + 1)); +} + +void djui_text_set_font_size(struct DjuiText* text, f32 fontSize) { + text->fontSize = fontSize; +} + +void djui_text_set_alignment(struct DjuiText* text, enum DjuiHAlign hAlign, enum DjuiVAlign vAlign) { + text->textHAlign = hAlign; + text->textVAlign = vAlign; +} + + /////////////// + // rendering // +/////////////// + +#define DJUI_TEXT_CHAR_HEIGHT 16 +#define DJUI_TEXT_CHAR_WIDTH 8 + +static f32 sTextRenderX = 0; +static f32 sTextRenderY = 0; +static f32 sTextRenderLastX = 0; +static f32 sTextRenderLastY = 0; + +static void djui_text_translate(f32 x, f32 y) { + sTextRenderX += x; + sTextRenderY += y; +} + +static void djui_text_render_char(struct DjuiText* text, u8 d) { + struct DjuiBaseRect* comp = &text->base.comp; + struct DjuiBaseRect* clip = &text->base.clip; + + f32 dX = comp->x + sTextRenderX * text->fontSize; + f32 dY = comp->y + sTextRenderY * text->fontSize; + f32 dW = DJUI_TEXT_CHAR_WIDTH * text->fontSize; + f32 dH = DJUI_TEXT_CHAR_HEIGHT * text->fontSize; + + if (djui_gfx_add_clipping_specific(&text->base, true, dX, dY, dW, dH)) { + return; + } + + create_dl_translation_matrix(DJUI_MTX_NOPUSH, sTextRenderX - sTextRenderLastX, (sTextRenderY - sTextRenderLastY) * -1.0f, 0); + djui_gfx_render_char(d); + + sTextRenderLastX = sTextRenderX; + sTextRenderLastY = sTextRenderY; +} + +static f32 djui_text_measure_word_width(char* message) { + f32 width = 0; + while (*message != '\0') { + u8 d = str_ascii_char_to_dialog(*message); + if (d == DIALOG_CHAR_SPACE) { return width; } + if (d == DIALOG_CHAR_NEWLINE) { return width; } + if (d == DIALOG_CHAR_TERMINATOR) { return width; } + width += gDialogCharWidths[d]; + message++; + } + return width; +} + +static void djui_text_read_line(char* message, u16* index, f32* lineWidth, f32 maxLineWidth, bool onLastLine, bool* ellipses) { + *lineWidth = 0; + u8 lastD = DIALOG_CHAR_TERMINATOR; + + f32 ellipsesWidth = gDialogCharWidths[0x3F] * 3.0f; + u16 lastSafeEllipsesIndex = *index; + u16 lastSafeEllipsesLineWidth = *lineWidth + ellipsesWidth; + + while (message[*index] != '\0') { + u8 d = str_ascii_char_to_dialog(message[*index]); + + // check for newline + if (d == DIALOG_CHAR_NEWLINE) { + *index = *index + 1; + break; + } + + // check to see if this character would exceed size + if (*lineWidth + gDialogCharWidths[d] >= maxLineWidth) { + break; + } + + // check to see if this word exceeds size + if (!onLastLine && lastD == DIALOG_CHAR_SPACE && d != DIALOG_CHAR_SPACE) { + f32 wordWidth = djui_text_measure_word_width(&message[*index]); + if (*lineWidth + wordWidth >= maxLineWidth) { + return; + } + } + + *lineWidth += gDialogCharWidths[d]; + + // check for safe ellipses index + if (onLastLine && ((*lineWidth + ellipsesWidth) < maxLineWidth)) { + lastSafeEllipsesIndex = *index; + lastSafeEllipsesLineWidth = *lineWidth + ellipsesWidth; + } + + *index = *index + 1; + lastD = d; + } + + // check to see if we should replace the end of the last line with ellipses + if (onLastLine && message[*index] != '\0') { + *index = lastSafeEllipsesIndex; + *lineWidth = lastSafeEllipsesLineWidth; + *ellipses = true; + } +} + +static void djui_text_render_line(struct DjuiText* text, u16 startIndex, u16 endIndex, f32 lineWidth, bool ellipses) { + struct DjuiBase* base = &text->base; + struct DjuiBaseRect* comp = &base->comp; + f32 curWidth = 0; + + // horizontal alignment + if (text->textHAlign == DJUI_HALIGN_CENTER) { + // center text + f32 offset = (comp->width / text->fontSize) / 2.0f - lineWidth / 2.0f; + djui_text_translate(offset, 0); + curWidth = offset; + } else if (text->textHAlign == DJUI_HALIGN_RIGHT) { + // right align text + f32 offset = (comp->width / text->fontSize) - lineWidth; + djui_text_translate(offset, 0); + curWidth = offset; + } + + // render the line + for (int i = startIndex; i < endIndex; i++) { + u8 d = str_ascii_char_to_dialog(text->message[i]); + switch (d) { + case DIALOG_CHAR_SPACE: + break; + case DIALOG_CHAR_NEWLINE: + break; + default: + djui_text_render_char(text, d); + break; + } + djui_text_translate(gDialogCharWidths[d], 0); + curWidth += gDialogCharWidths[d]; + } + + // render ellipses + if (ellipses) { + for (int i = 0; i < 3; i++) { + u8 d = str_ascii_char_to_dialog('.'); + djui_text_render_char(text, d); + djui_text_translate(gDialogCharWidths[d], 0); + curWidth += gDialogCharWidths[d]; + } + } + + // reset translation matrix + djui_text_translate(-curWidth, DJUI_TEXT_CHAR_HEIGHT); +} + + //////////// + // events // +//////////// + +static void djui_text_render(struct DjuiBase* base) { + gSPDisplayList(gDisplayListHead++, dl_djui_ia_text_begin); + + struct DjuiText* text = (struct DjuiText*)base; + struct DjuiBaseRect* comp = &base->comp; + + // reset render location + sTextRenderX = 0; + sTextRenderY = 0; + sTextRenderLastX = 0; + sTextRenderLastY = 0; + + // compute base + djui_base_compute(base); + + // translate position + f32 translatedX = comp->x; + f32 translatedY = comp->y; + djui_gfx_position_translate(&translatedX, &translatedY); + create_dl_translation_matrix(DJUI_MTX_PUSH, translatedX, translatedY, 0); + + // compute size + f32 translatedWidth = comp->width; + f32 translatedHeight = comp->height; + djui_gfx_scale_translate(&translatedWidth, &translatedHeight); + + // compute font size + f32 translatedFontSize = text->fontSize; + djui_gfx_size_translate(&translatedFontSize); + create_dl_scale_matrix(DJUI_MTX_NOPUSH, translatedFontSize, translatedFontSize, 1.0f); + + // set color + gDPSetEnvColor(gDisplayListHead++, base->color.r, base->color.g, base->color.b, base->color.a); + + // count lines + u16 startIndex = 0; + u16 endIndex = 0; + f32 maxLineWidth = comp->width / text->fontSize; + u16 lineCount = 0; + u16 maxLines = comp->height / ((f32)DJUI_TEXT_CHAR_HEIGHT * text->fontSize); + while (text->message[startIndex] != '\0') { + bool onLastLine = lineCount + 1 >= maxLines; + f32 lineWidth; + bool ellipses; + djui_text_read_line(text->message, &endIndex, &lineWidth, maxLineWidth, onLastLine, &ellipses); + startIndex = endIndex; + lineCount++; + if (onLastLine) { break; } + } + + // do vertical alignment + f32 vOffset = 0; + if (text->textVAlign == DJUI_VALIGN_CENTER) { + vOffset += (comp->height / text->fontSize) / 2.0f; + vOffset -= (lineCount * DJUI_TEXT_CHAR_HEIGHT) / 2.0f; + } else if (text->textVAlign == DJUI_VALIGN_BOTTOM) { + vOffset += (comp->height / text->fontSize); + vOffset -= (lineCount * DJUI_TEXT_CHAR_HEIGHT); + } + djui_text_translate(0, vOffset); + + // render lines + startIndex = 0; + endIndex = 0; + f32 lineWidth; + u16 lineIndex = 0; + bool ellipses = false; + while (text->message[startIndex] != '\0') { + bool onLastLine = lineIndex + 1 >= maxLines; + djui_text_read_line(text->message, &endIndex, &lineWidth, maxLineWidth, onLastLine, &ellipses); + djui_text_render_line(text, startIndex, endIndex, lineWidth, ellipses); + startIndex = endIndex; + lineIndex++; + if (onLastLine) { break; } + } + + gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); + gSPDisplayList(gDisplayListHead++, dl_ia_text_end); +} + +static void djui_text_destroy(struct DjuiBase* base) { + struct DjuiText* text = (struct DjuiText*)base; + free(text->message); + free(text); +} + +struct DjuiText* djui_text_create(struct DjuiBase* parent, const char* message) { + struct DjuiText* text = malloc(sizeof(struct DjuiText)); + struct DjuiBase* base = &text->base; + + djui_base_init(parent, base, djui_text_render, djui_text_destroy); + + text->message = NULL; + djui_text_set_text(text, message); + djui_text_set_font_size(text, 2.0f); + djui_text_set_alignment(text, DJUI_HALIGN_LEFT, DJUI_VALIGN_TOP); + + return text; +} diff --git a/src/pc/djui/djui_text.h b/src/pc/djui/djui_text.h new file mode 100644 index 000000000..8b1137f1c --- /dev/null +++ b/src/pc/djui/djui_text.h @@ -0,0 +1,17 @@ +#pragma once +#include "djui.h" + +#pragma pack(1) +struct DjuiText { + struct DjuiBase base; + char* message; + f32 fontSize; + enum DjuiHAlign textHAlign; + enum DjuiVAlign textVAlign; +}; + +void djui_text_set_text(struct DjuiText* text, const char* message); +void djui_text_set_font_size(struct DjuiText* text, f32 fontSize); +void djui_text_set_alignment(struct DjuiText* text, enum DjuiHAlign hAlign, enum DjuiVAlign vAlign); + +struct DjuiText* djui_text_create(struct DjuiBase* parent, const char* message); diff --git a/src/pc/djui/djui_types.h b/src/pc/djui/djui_types.h new file mode 100644 index 000000000..9d49717fc --- /dev/null +++ b/src/pc/djui/djui_types.h @@ -0,0 +1,21 @@ +#pragma once +#include "djui.h" + +#pragma pack(1) +struct DjuiColor { + u8 r; + u8 g; + u8 b; + u8 a; +}; + +enum DjuiScreenValueType { DJUI_SVT_ABSOLUTE, DJUI_SVT_RELATIVE }; + +#pragma pack(1) +struct DjuiScreenValue { + enum DjuiScreenValueType type; + f32 value; +}; + +enum DjuiHAlign { DJUI_HALIGN_LEFT, DJUI_HALIGN_CENTER, DJUI_HALIGN_RIGHT }; +enum DjuiVAlign { DJUI_VALIGN_TOP, DJUI_VALIGN_CENTER, DJUI_VALIGN_BOTTOM }; diff --git a/src/pc/gfx/gfx_pc.c b/src/pc/gfx/gfx_pc.c index 876d9f54c..7aece14b0 100644 --- a/src/pc/gfx/gfx_pc.c +++ b/src/pc/gfx/gfx_pc.c @@ -186,6 +186,12 @@ static const uint8_t missing_texture[MISSING_W * MISSING_H * 4] = { 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, }; +////////////////////////////////// +// forward declaration for djui // +////////////////////////////////// +void djui_gfx_run_dl(Gfx* cmd); +////////////////////////////////// + #ifdef EXTERNAL_DATA static inline size_t string_hash(const uint8_t *str) { size_t h = 0; @@ -1725,6 +1731,9 @@ static void gfx_run_dl(Gfx* cmd) { case G_SETCIMG: gfx_dp_set_color_image(C0(21, 3), C0(19, 2), C0(0, 11), seg_addr(cmd->words.w1)); break; + default: + djui_gfx_run_dl(cmd); + break; } ++cmd; } @@ -1839,3 +1848,144 @@ void gfx_shutdown(void) { gfx_wapi = NULL; } } + + ///////////////////////// + // v custom for djui v // +///////////////////////// + +static bool sDjuiClip = 0; +static bool sDjuiClipRotatedUV = 0; +static uint8_t sDjuiClipX1 = 0; +static uint8_t sDjuiClipY1 = 0; +static uint8_t sDjuiClipX2 = 0; +static uint8_t sDjuiClipY2 = 0; + +static bool sDjuiOverride = false; +static void* sDjuiOverrideTexture = NULL; +static uint8_t sDjuiOverrideW = 0; +static uint8_t sDjuiOverrideH = 0; + +static void djui_gfx_dp_set_clipping(bool rotatedUV, uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2) { + sDjuiClipRotatedUV = rotatedUV; + sDjuiClipX1 = x1; + sDjuiClipY1 = y1; + sDjuiClipX2 = x2; + sDjuiClipY2 = y2; + sDjuiClip = true; +} + +static void djui_gfx_dp_execute_clipping(void) { + if (!sDjuiClip) { return; } + sDjuiClip = 0; + + size_t start_index = 0; + size_t dest_index = 4; + + float minX = rsp.loaded_vertices[start_index].x; + float maxX = rsp.loaded_vertices[start_index].x; + float minY = rsp.loaded_vertices[start_index].y; + float maxY = rsp.loaded_vertices[start_index].y; + + float minU = rsp.loaded_vertices[start_index].u; + float maxU = rsp.loaded_vertices[start_index].u; + float minV = rsp.loaded_vertices[start_index].v; + float maxV = rsp.loaded_vertices[start_index].v; + + for (size_t i = start_index; i < dest_index; i++) { + struct LoadedVertex* d = &rsp.loaded_vertices[i]; + minX = fmin(minX, d->x); + maxX = fmax(maxX, d->x); + minY = fmin(minY, d->y); + maxY = fmax(maxY, d->y); + + minU = fmin(minU, d->u); + maxU = fmax(maxU, d->u); + minV = fmin(minV, d->v); + maxV = fmax(maxV, d->v); + } + + float midY = (minY + maxY) / 2.0f; + float midX = (minX + maxX) / 2.0f; + float midU = (minU + maxU) / 2.0f; + float midV = (minV + maxV) / 2.0f; + for (size_t i = start_index; i < dest_index; i++) { + struct LoadedVertex* d = &rsp.loaded_vertices[i]; + if (d->x <= midX) { + d->x += (maxX - minX) * (sDjuiClipX1 / 255.0f); + } else { + d->x -= (maxX - minX) * (sDjuiClipX2 / 255.0f); + } + if (d->y <= midY) { + d->y += (maxY - minY) * (sDjuiClipY2 / 255.0f); + } else { + d->y -= (maxY - minY) * (sDjuiClipY1 / 255.0f); + } + + if (sDjuiClipRotatedUV) { + if (d->u <= midU) { + d->u += (maxU - minU) * (sDjuiClipY2 / 255.0f); + } + else { + d->u -= (maxU - minU) * (sDjuiClipY1 / 255.0f); + } + if (d->v <= midV) { + d->v += (maxV - minV) * (sDjuiClipX2 / 255.0f); + } + else { + d->v -= (maxV - minV) * (sDjuiClipX1 / 255.0f); + } + } else { + if (d->u <= midU) { + d->u += (maxU - minU) * (sDjuiClipX1 / 255.0f); + } else { + d->u -= (maxU - minU) * (sDjuiClipX2 / 255.0f); + } + if (d->v <= midV) { + d->v += (maxV - minV) * (sDjuiClipY1 / 255.0f); + } else { + d->v -= (maxV - minV) * (sDjuiClipY2 / 255.0f); + } + } + } +} + +static void djui_gfx_dp_set_override(void* texture, uint8_t w, uint8_t h) { + sDjuiOverrideTexture = texture; + sDjuiOverrideW = w; + sDjuiOverrideH = h; + sDjuiOverride = (texture != NULL); +} + +static void djui_gfx_dp_execute_override(void) { + if (!sDjuiOverride) { return; } + sDjuiOverride = false; + + rdp.texture_to_load.addr = sDjuiOverrideTexture; + rdp.loaded_texture[rdp.texture_to_load.tile_number].addr = rdp.texture_to_load.addr; + uint32_t line = (((sDjuiOverrideW * 2) + 7) >> 3); + rdp.texture_tile.line_size_bytes = line * 8; + uint32_t lrs = (sDjuiOverrideW * sDjuiOverrideH) - 1; + rdp.loaded_texture[rdp.texture_to_load.tile_number].size_bytes = (lrs + 1) << 1; +} + +static void djui_gfx_dp_execute_djui(uint32_t opcode) { + switch (opcode) { + case G_TEXOVERRIDE_DJUI: djui_gfx_dp_execute_override(); break; + case G_TEXCLIP_DJUI: djui_gfx_dp_execute_clipping(); break; + } +} + +void djui_gfx_run_dl(Gfx* cmd) { + uint32_t opcode = cmd->words.w0 >> 24; + switch (opcode) { + case G_TEXCLIP_DJUI: + djui_gfx_dp_set_clipping(C0(0, 8), C0(16, 8), C0(8, 8), C1(16, 8), C1(8, 8)); + break; + case G_TEXOVERRIDE_DJUI: + djui_gfx_dp_set_override(seg_addr(cmd->words.w1), C0(16, 8), C0(8, 8)); + break; + case G_EXECUTE_DJUI: + djui_gfx_dp_execute_djui(cmd->words.w1); + break; + } +}