diff --git a/build-windows-visual-studio/sm64ex.vcxproj b/build-windows-visual-studio/sm64ex.vcxproj index b4a724eab..f1982d5c9 100644 --- a/build-windows-visual-studio/sm64ex.vcxproj +++ b/build-windows-visual-studio/sm64ex.vcxproj @@ -3946,6 +3946,7 @@ + @@ -4387,6 +4388,7 @@ + diff --git a/build-windows-visual-studio/sm64ex.vcxproj.filters b/build-windows-visual-studio/sm64ex.vcxproj.filters index 9c3a48da6..8b64f8821 100644 --- a/build-windows-visual-studio/sm64ex.vcxproj.filters +++ b/build-windows-visual-studio/sm64ex.vcxproj.filters @@ -15249,6 +15249,9 @@ Source Files\src\pc\djui\panel + + Source Files\src\pc\djui\component\compound + @@ -16318,5 +16321,8 @@ Source Files\src\pc\djui\panel + + Source Files\src\pc\djui\component\compound + \ No newline at end of file diff --git a/src/pc/djui/djui.h b/src/pc/djui/djui.h index 8c3e26f66..4cf5227ba 100644 --- a/src/pc/djui/djui.h +++ b/src/pc/djui/djui.h @@ -21,9 +21,10 @@ #include "djui_three_panel.h" #include "djui_button.h" -#include "djui_flow_layout.h" +#include "djui_inputbox.h" #include "djui_slider.h" #include "djui_checkbox.h" +#include "djui_flow_layout.h" #include "djui_selectionbox.h" #include "djui_bind.h" diff --git a/src/pc/djui/djui_inputbox.c b/src/pc/djui/djui_inputbox.c new file mode 100644 index 000000000..c4008f60a --- /dev/null +++ b/src/pc/djui/djui_inputbox.c @@ -0,0 +1,258 @@ +#include +#include +#include "djui.h" +#include "game/segment2.h" + +#define DJUI_INPUTBOX_YOFF (-2) +#define DJUI_INPUTBOX_MAX_BLINK 50 +#define DJUI_INPUTBOX_MID_BLINK (DJUI_INPUTBOX_MAX_BLINK / 2) +#define DJUI_INPUTBOX_CURSOR_WIDTH (2.0f / 32.0f) + +static u8 sCursorBlink = 0; + +static void djui_inputbox_set_default_style(struct DjuiBase* base) { + struct DjuiInputbox* inputbox = (struct DjuiInputbox*)base; + djui_base_set_border_color(base, 150, 150, 150, 255); + djui_base_set_color(&inputbox->base, 240, 240, 240, 255); +} + +static void djui_inputbox_on_hover(struct DjuiBase* base) { + struct DjuiInputbox* inputbox = (struct DjuiInputbox*)base; + djui_base_set_border_color(base, 0, 120, 215, 255); + djui_base_set_color(&inputbox->base, 255, 255, 255, 255); +} + +static void djui_inputbox_on_hover_end(struct DjuiBase* base) { + djui_inputbox_set_default_style(base); +} + +static u16 djui_inputbox_get_cursor_index(struct DjuiInputbox* inputbox) { + struct DjuiBaseRect* comp = &inputbox->base.comp; + struct DjuiFont* font = &gDjuiFonts[0]; + + f32 cX = (gCursorX - (comp->x + inputbox->viewX)) / font->defaultFontScale; + f32 x = 0; + u16 index = 0; + for (u16 i = 0; i < inputbox->bufferSize; i++) { + char c = inputbox->buffer[i]; + if (x < cX) { + index = i; + } + if (c == '\0') { break; } + x += font->char_width(c); + } + + return index; +} + +static void djui_inputbox_on_cursor_down_begin(struct DjuiBase* base, bool inputCursor) { + struct DjuiInputbox* inputbox = (struct DjuiInputbox*)base; + u16 index = djui_inputbox_get_cursor_index(inputbox); + inputbox->selection[0] = index; + inputbox->selection[1] = index; + sCursorBlink = 0; +} + +static void djui_inputbox_on_cursor_down(struct DjuiBase* base) { + struct DjuiInputbox* inputbox = (struct DjuiInputbox*)base; + u16 index = djui_inputbox_get_cursor_index(inputbox); + inputbox->selection[0] = index; +} + +static void djui_inputbox_on_cursor_down_end(struct DjuiBase* base) { + djui_inputbox_set_default_style(base); +} + +static void djui_inputbox_render_char(struct DjuiInputbox* inputbox, char c, f32* drawX, f32* additionalShift) { + struct DjuiBaseRect* comp = &inputbox->base.comp; + struct DjuiFont* font = &gDjuiFonts[0]; + f32 dX = comp->x + *drawX; + f32 dY = comp->y + DJUI_INPUTBOX_YOFF; + f32 dW = font->charWidth * font->defaultFontScale; + f32 dH = font->charHeight * font->defaultFontScale; + + f32 charWidth = font->char_width(c); + *drawX += charWidth * font->defaultFontScale; + + if (c != ' ') { + if (djui_gfx_add_clipping_specific(&inputbox->base, font->rotatedUV, dX, dY, dW, dH)) { + *additionalShift += charWidth; + return; + } + font->render_char(c); + } + + create_dl_translation_matrix(DJUI_MTX_NOPUSH, charWidth + *additionalShift, 0, 0); + *additionalShift = 0; +} + +static void djui_inputbox_render_selection(struct DjuiInputbox* inputbox) { + struct DjuiFont* font = &gDjuiFonts[0]; + + // make selection well formed + u16 selection[2] = { 0 }; + selection[0] = fmin(inputbox->selection[0], inputbox->selection[1]); + selection[1] = fmax(inputbox->selection[0], inputbox->selection[1]); + + char* msg = inputbox->buffer; + f32 x = 0; + f32 width = 0; + for (u16 i = 0; i < selection[1]; i++) { + if (i < selection[0]) { + x += font->char_width(msg[i]); + } else { + width += font->char_width(msg[i]); + } + } + + sCursorBlink = (sCursorBlink + 1) % DJUI_INPUTBOX_MAX_BLINK; + + // render only cursor when there is no selection width + if (selection[0] == selection[1]) { + if (sCursorBlink < DJUI_INPUTBOX_MID_BLINK) { + create_dl_translation_matrix(DJUI_MTX_PUSH, x - DJUI_INPUTBOX_CURSOR_WIDTH / 2.0f, -0.1f, 0); + create_dl_scale_matrix(DJUI_MTX_NOPUSH, DJUI_INPUTBOX_CURSOR_WIDTH, 0.8f, 1.0f); + gDPSetEnvColor(gDisplayListHead++, 0, 0, 0, 255); + gSPDisplayList(gDisplayListHead++, dl_djui_simple_rect); + gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); + } + return; + } + + // clip selection box + // note: this is incredibly confusing due to being in font-space instead of screen-space + f32 clipLow = -inputbox->viewX / font->defaultFontScale; + if (x < clipLow) { + width -= clipLow - x; + x = clipLow; + } + f32 clipHigh = (inputbox->base.clip.width / font->defaultFontScale) - x + clipLow; + if (width > clipHigh) { + width = clipHigh; + } + + // render selection box + create_dl_translation_matrix(DJUI_MTX_PUSH, x, -0.1f, 0); + create_dl_scale_matrix(DJUI_MTX_NOPUSH, width, 0.8f, 1.0f); + gDPSetEnvColor(gDisplayListHead++, 0, 120, 215, 255); + gSPDisplayList(gDisplayListHead++, dl_djui_simple_rect); + gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); + + // render selection cursor + if (sCursorBlink < DJUI_INPUTBOX_MID_BLINK) { + f32 cX = (inputbox->selection[0] < inputbox->selection[1]) ? x : (x + width); + create_dl_translation_matrix(DJUI_MTX_PUSH, cX - DJUI_INPUTBOX_CURSOR_WIDTH / 2.0f, -0.1f, 0); + create_dl_scale_matrix(DJUI_MTX_NOPUSH, DJUI_INPUTBOX_CURSOR_WIDTH, 0.8f, 1.0f); + gDPSetEnvColor(gDisplayListHead++, 255, 127, 0, 255); + gSPDisplayList(gDisplayListHead++, dl_djui_simple_rect); + gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); + } +} + +static void djui_inputbox_keep_selection_in_view(struct DjuiInputbox* inputbox) { + struct DjuiFont* font = &gDjuiFonts[0]; + + // calculate where our cursor is + f32 cursorX = inputbox->viewX; + char* msg = inputbox->buffer; + for (u16 i = 0; i < inputbox->selection[0]; i++) { + cursorX += font->char_width(msg[i]) * font->defaultFontScale; + } + + // shift viewing window + if (cursorX > inputbox->base.comp.width) { + inputbox->viewX -= cursorX - inputbox->base.comp.width; + } else if (cursorX < 0) { + inputbox->viewX -= cursorX; + } +} + +static void djui_inputbox_render(struct DjuiBase* base) { + struct DjuiInputbox* inputbox = (struct DjuiInputbox*)base; + struct DjuiBaseRect* comp = &base->comp; + struct DjuiFont* font = &gDjuiFonts[0]; + djui_rect_render(base); + + // shift the viewing window to keep the selection in view + djui_inputbox_keep_selection_in_view(inputbox); + + // translate position + f32 translatedX = comp->x + inputbox->viewX; + f32 translatedY = comp->y + DJUI_INPUTBOX_YOFF; + djui_gfx_position_translate(&translatedX, &translatedY); + create_dl_translation_matrix(DJUI_MTX_PUSH, translatedX, translatedY, 0); + + // compute font size + f32 translatedFontSize = font->defaultFontScale; + djui_gfx_size_translate(&translatedFontSize); + create_dl_scale_matrix(DJUI_MTX_NOPUSH, translatedFontSize, translatedFontSize, 1.0f); + + // render selection + djui_inputbox_render_selection(inputbox); + + // begin font + gSPDisplayList(gDisplayListHead++, font->textBeginDisplayList); + + // set color + gDPSetEnvColor(gDisplayListHead++, 0, 0, 0, 255); + + // make selection well formed + u16 selection[2] = { 0 }; + selection[0] = fmin(inputbox->selection[0], inputbox->selection[1]); + selection[1] = fmax(inputbox->selection[0], inputbox->selection[1]); + + // render text + char* msg = inputbox->buffer; + f32 drawX = inputbox->viewX; + f32 additionalShift = 0; + bool wasInsideSelection = false; + for (u16 i = 0; i < inputbox->bufferSize; i++) { + if (msg[i] == '\0') { break; } + + // deal with seleciton color + if (selection[0] != selection[1]) { + bool insideSelection = (i >= selection[0]) && (i < selection[1]); + if (insideSelection && !wasInsideSelection) { + gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); + } else if (!insideSelection && wasInsideSelection) { + gDPSetEnvColor(gDisplayListHead++, 0, 0, 0, 255); + } + wasInsideSelection = insideSelection; + } + + // render character + djui_inputbox_render_char(inputbox, msg[i], &drawX, &additionalShift); + } + + gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); + gSPDisplayList(gDisplayListHead++, dl_ia_text_end); +} + +static void djui_inputbox_destroy(struct DjuiBase* base) { + struct DjuiInputbox* inputbox = (struct DjuiInputbox*)base; + free(inputbox->buffer); + free(inputbox); +} + +struct DjuiInputbox* djui_inputbox_create(struct DjuiBase* parent, u16 bufferSize) { + struct DjuiInputbox* inputbox = malloc(sizeof(struct DjuiInputbox)); + struct DjuiBase* base = &inputbox->base; + inputbox->viewX = 0; + inputbox->selection[0] = 0; + inputbox->selection[1] = 0; + inputbox->bufferSize = bufferSize; + inputbox->buffer = malloc(sizeof(char) * bufferSize); + memset(inputbox->buffer, 0, sizeof(char) * bufferSize); + sprintf(inputbox->buffer, "testing string hello world there it is"); + + djui_base_init(parent, base, djui_inputbox_render, djui_inputbox_destroy); + djui_base_set_size(base, 200, 32); + djui_base_set_border_width(base, 2); + djui_interactable_create(base); + djui_interactable_hook_hover(base, djui_inputbox_on_hover, djui_inputbox_on_hover_end); + djui_interactable_hook_cursor_down(base, djui_inputbox_on_cursor_down_begin, djui_inputbox_on_cursor_down, djui_inputbox_on_cursor_down_end); + + djui_inputbox_set_default_style(base); + + return inputbox; +} diff --git a/src/pc/djui/djui_inputbox.h b/src/pc/djui/djui_inputbox.h new file mode 100644 index 000000000..d9557f994 --- /dev/null +++ b/src/pc/djui/djui_inputbox.h @@ -0,0 +1,13 @@ +#pragma once +#include "djui.h" + +#pragma pack(1) +struct DjuiInputbox { + struct DjuiBase base; + char* buffer; + u16 bufferSize; + u16 selection[2]; + f32 viewX; +}; + +struct DjuiInputbox* djui_inputbox_create(struct DjuiBase* parent, u16 bufferSize); diff --git a/src/pc/djui/djui_panel_main.c b/src/pc/djui/djui_panel_main.c index ce5819c67..41b4c22ce 100644 --- a/src/pc/djui/djui_panel_main.c +++ b/src/pc/djui/djui_panel_main.c @@ -46,6 +46,9 @@ void djui_panel_main_create(struct DjuiBase* caller) { djui_text_set_alignment(footer, DJUI_HALIGN_CENTER, DJUI_VALIGN_BOTTOM); } + struct DjuiInputbox* inputbox = djui_inputbox_create(&gDjuiRoot->base, 256); + djui_base_set_location(&inputbox->base, 400, 400); + djui_panel_add(caller, &panel->base, defaultBase); gInteractableOverridePad = true; } diff --git a/src/pc/gfx/gfx_pc.c b/src/pc/gfx/gfx_pc.c index 5e83bee26..4191d314f 100644 --- a/src/pc/gfx/gfx_pc.c +++ b/src/pc/gfx/gfx_pc.c @@ -1868,7 +1868,7 @@ static uint32_t sDjuiOverrideB = 0; static void djui_gfx_dp_execute_clipping(void) { if (!sDjuiClip) { return; } - sDjuiClip = 0; + sDjuiClip = false; size_t start_index = 0; size_t dest_index = 4;