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