DJUI: Made inputbox usable

Added keyboard support to inputbox
Fixed how inputbox text is clipped
Added on_enter_press callback for inputbox
Added ability to set clipboard text
Adjusted how components can be focused, and when they lose focus
Erased most of the text input stuff in controller_keyboard
Disabled major parts of old chat system
Disabled major parts of old menu system
This commit is contained in:
MysterD 2021-07-01 20:03:54 -07:00
parent e424b9f9f3
commit b4418bbd4f
19 changed files with 445 additions and 339 deletions

18
developer/flags.sh Normal file
View File

@ -0,0 +1,18 @@
#!/bin/bash
set -e
function compiler() {
make clean
make RENDER_API=$1 WINDOW_API=$2 AUDIO_API=$3 CONTROLLER_API=$4
mv ./build/us_pc/sm64.us.f3dex2e.exe ./build/us_pc/$5.exe
}
compiler GL_LEGACY SDL1 SDL1 SDL1 legacy_1
compiler GL SDL1 SDL1 SDL1 gl_1
compiler D3D11 DXGI SDL1 SDL1 d3d11_1
compiler D3D12 DXGI SDL1 SDL1 d3d12_1
compiler GL_LEGACY SDL2 SDL2 SDL2 legacy_2
compiler GL SDL1 SDL2 SDL2 gl_2
compiler D3D11 DXGI SDL2 SDL2 d3d11_2
compiler D3D12 DXGI SDL2 SDL2 d3d12_2

View File

@ -127,33 +127,17 @@ void chat_add_message(char* ascii, enum ChatMessageType chatMessageType) {
static void chat_stop_input(void) {
sInChatInput = FALSE;
keyboard_stop_text_input();
}
static void chat_send_input(void) {
sInChatInput = FALSE;
keyboard_stop_text_input();
if (strlen(gTextInput) == 0) { return; }
chat_add_message(gTextInput, CMT_LOCAL);
// our message has the same color as our shirt
network_send_chat(gTextInput, get_player_color(gNetworkPlayerLocal->globalIndex, 0));
}
void chat_start_input(void) {
sInChatInput = TRUE;
keyboard_start_text_input(TIM_SINGLE_LINE, CHAT_DIALOG_MAX - 3, chat_stop_input, chat_send_input);
}
void render_chat(void) {
u8 count = 0;
if (sInChatInput) {
struct ChatMessage inputMessage = { 0 };
inputMessage.type = CMT_INPUT;
inputMessage.dialog[0] = 0xFD;
inputMessage.dialog[1] = 0x9E;
str_ascii_to_dialog(gTextInput, &inputMessage.dialog[2], MIN(strlen(gTextInput), CHAT_DIALOG_MAX - 3));
inputMessage.life = CHAT_LIFE_MAX;
render_chat_message(&inputMessage, count++);
}
u8 index = onMessageIndex;

View File

@ -227,7 +227,7 @@ static Gfx gd_texture1_dummy_aligner1[] = { // @ 801A8728
gsSPEndDisplayList(),
};
ALIGNED8 static u8 gd_texture_hand_open[] = {
ALIGNED8 u8 gd_texture_hand_open[] = {
#include "textures/intro_raw/hand_open.rgba16.inc.c"
};
@ -235,7 +235,7 @@ static Gfx gd_texture2_dummy_aligner1[] = {
gsSPEndDisplayList()
};
ALIGNED8 static u8 gd_texture_hand_closed[] = {
ALIGNED8 u8 gd_texture_hand_closed[] = {
#include "textures/intro_raw/hand_closed.rgba16.inc.c"
};

View File

@ -178,75 +178,15 @@ static void connect_menu_draw_strings(void) {
print_generic_ascii_string(30, 175, "Type in or paste the host's IP.");
print_generic_ascii_string(30, 160, "Note - the host must forward a port on their router.");
if (keyboard_in_text_input()) {
if (strlen(gTextInput) >= 7) {
print_generic_ascii_string(30, 100, "Press (ENTER) to connect.");
} else {
print_generic_ascii_string(30, 100, "Press (ESC) to cancel.");
}
}
gDPSetEnvColor(gDisplayListHead++, 130, 222, 140, gMenuStringAlpha);
print_generic_ascii_string(30, 130, gTextInput);
}
static void connect_menu_on_connection_attempt(void) {
play_sound(SOUND_GENERAL_COIN, gDefaultSoundArgs);
keyboard_stop_text_input();
if (gNetworkType != NT_NONE) { return; }
char delims[2] = " ";
// copy input
char buffer[MAX_TEXT_INPUT] = { 0 };
strncpy(buffer, gTextInput, MAX_TEXT_INPUT);
char* text = buffer;
// trim whitespace
while (*text == ' ') { text++; }
// grab IP
char* ip = strtok(text, delims);
if (ip == NULL) { custom_menu_close(); return; }
strncpy(configJoinIp, ip, MAX_CONFIG_STRING);
// grab port
char* port = strtok(NULL, delims);
if (port != NULL) {
unsigned int intPort = atoi(port);
if (intPort == 0 || intPort > 65535) { configJoinPort = DEFAULT_PORT; custom_menu_close(); return; }
configJoinPort = intPort;
}
else {
configJoinPort = DEFAULT_PORT;
}
network_set_system(NS_SOCKET);
network_init(NT_CLIENT);
}
static void connect_menu_on_click(void) {
sConnectionJoinError[0] = '\0';
keyboard_start_text_input(TIM_IP, MAX_TEXT_INPUT, custom_menu_close, connect_menu_on_connection_attempt);
// fill in our last attempt
if (configJoinPort == 0 || configJoinPort > 65535) { configJoinPort = DEFAULT_PORT; }
// only print custom port
if (configJoinPort == DEFAULT_PORT) {
sprintf(gTextInput, "%s", configJoinIp);
}
else {
sprintf(gTextInput, "%s %d", configJoinIp, configJoinPort);
}
}
static void connect_menu_on_close(void) {
keyboard_stop_text_input();
network_shutdown();
}

View File

@ -20,19 +20,6 @@
#include "game/chat.h"
#include "src/pc/djui/djui.h"
// TODO: use some common lookup header
#define SCANCODE_BACKSPACE 0x0E
#define SCANCODE_ESCAPE 0x01
#define SCANCODE_ENTER 0x1C
#define SCANCODE_V 0x2F
#define SCANCODE_INSERT 0x152
#define SCANCODE_CTRL1 0x1D
#define SCANCODE_CTRL2 0x11D
#define SCANCODE_SHIFT1 0x2A
#define SCANCODE_SHIFT2 0x36
#define SCANCODE_ALT1 0x38
#define SCANCODE_ALT2 0x138
static int keyboard_buttons_down;
#define MAX_KEYBINDS 64
@ -41,16 +28,6 @@ static int num_keybinds = 0;
static u32 keyboard_lastkey = VK_INVALID;
char gTextInput[MAX_TEXT_INPUT];
static u8 sInTextInput = false;
static u8 sMaxTextInput = 0;
static clock_t sIgnoreTextInput = 0;
u8 held_ctrl, held_shift, held_alt;
static enum TextInputMode sTextInputMode;
void (*textInputOnEscape)(void) = NULL;
void (*textInputOnEnter)(void) = NULL;
static int keyboard_map_scancode(int scancode) {
int ret = 0;
for (int i = 0; i < num_keybinds; i++) {
@ -61,70 +38,16 @@ static int keyboard_map_scancode(int scancode) {
return ret;
}
static void keyboard_alter_modifier(int scancode, bool down) {
if (down) {
switch (scancode) {
case SCANCODE_CTRL1: held_ctrl |= (1 << 0); break;
case SCANCODE_CTRL2: held_ctrl |= (1 << 1); break;
case SCANCODE_SHIFT1: held_shift |= (1 << 0); break;
case SCANCODE_SHIFT2: held_shift |= (1 << 1); break;
case SCANCODE_ALT1: held_alt |= (1 << 0); break;
case SCANCODE_ALT2: held_alt |= (1 << 1); break;
}
} else {
switch (scancode) {
case SCANCODE_CTRL1: held_ctrl &= ~(1 << 0); break;
case SCANCODE_CTRL2: held_ctrl &= ~(1 << 1); break;
case SCANCODE_SHIFT1: held_shift &= ~(1 << 0); break;
case SCANCODE_SHIFT2: held_shift &= ~(1 << 1); break;
case SCANCODE_ALT1: held_alt &= ~(1 << 0); break;
case SCANCODE_ALT2: held_alt &= ~(1 << 1); break;
}
}
}
bool keyboard_on_key_down(int scancode) {
// alter the held value of modifier keys
keyboard_alter_modifier(scancode, true);
djui_interactable_on_key_down(scancode);
#ifdef DEBUG
if (!sInTextInput) {
debug_keyboard_on_key_down(scancode);
}
debug_keyboard_on_key_down(scancode);
#endif
if (sInTextInput) {
// perform text-input-specific actions
switch (scancode & 0xFF) {
case SCANCODE_BACKSPACE:
gTextInput[max(strlen(gTextInput) - 1, 0)] = '\0';
break;
case SCANCODE_ESCAPE:
if (textInputOnEscape != NULL) { textInputOnEscape(); }
break;
case SCANCODE_ENTER:
if (textInputOnEnter != NULL) { textInputOnEnter(); }
break;
case SCANCODE_V:
if (held_ctrl) { keyboard_on_text_input(wm_api->get_clipboard_text()); }
break;
case SCANCODE_INSERT:
if (held_shift) { keyboard_on_text_input(wm_api->get_clipboard_text()); }
break;
}
// ignore any normal key down event if we're in text-input mode
// see if interactable captures this scancode
if (djui_interactable_on_key_down(scancode)) {
return FALSE;
}
if (!held_alt && (scancode == (int)configKeyChat[0])) {
if (sSelectedFileNum != 0) {
sIgnoreTextInput = clock() + CLOCKS_PER_SEC * 0.01f;
chat_start_input();
}
}
int mapped = keyboard_map_scancode(scancode);
keyboard_buttons_down |= mapped;
keyboard_lastkey = scancode;
@ -132,16 +55,8 @@ bool keyboard_on_key_down(int scancode) {
}
bool keyboard_on_key_up(int scancode) {
// alter the held value of modifier keys
keyboard_alter_modifier(scancode, false);
djui_interactable_on_key_up(scancode);
if (sInTextInput) {
// ignore any key up event if we're in text-input mode
return FALSE;
}
int mapped = keyboard_map_scancode(scancode);
keyboard_buttons_down &= ~mapped;
if (keyboard_lastkey == (u32) scancode)
@ -153,87 +68,8 @@ void keyboard_on_all_keys_up(void) {
keyboard_buttons_down = 0;
}
char* keyboard_start_text_input(enum TextInputMode inInputMode, u8 inMaxTextInput, void (*onEscape)(void), void (*onEnter)(void)) {
// set text-input events
textInputOnEscape = onEscape;
textInputOnEnter = onEnter;
sMaxTextInput = inMaxTextInput;
// clear buffer
for (int i = 0; i < MAX_TEXT_INPUT; i++) { gTextInput[i] = '\0'; }
// clear held-value for modifiers
held_ctrl = 0;
held_shift = 0;
held_alt = 0;
// start allowing text input
wm_api->start_text_input();
sTextInputMode = inInputMode;
sInTextInput = true;
}
void keyboard_stop_text_input(void) {
// stop allowing text input
sInTextInput = false;
wm_api->stop_text_input();
}
bool keyboard_in_text_input(void) { return sInTextInput; }
static bool keyboard_allow_character_input(char c) {
switch (sTextInputMode) {
case TIM_IP:
// IP only allows numbers, periods, and spaces
return (c >= '0' && c <= '9')
|| (c == '.')
|| (c == ' ');
case TIM_MULTI_LINE:
// multi-line allows new-line character
if (c == '\n') { return true; }
// intentional fall-through
case TIM_SINGLE_LINE:
// allow all characters that we can display in-game
return (c >= '0' && c <= '9')
|| (c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c == '\'') || (c == '.')
|| (c == ',') || (c == '-')
|| (c == '(') || (c == ')')
|| (c == '&') || (c == '!')
|| (c == '%') || (c == '?')
|| (c == '"') || (c == '~')
|| (c == '*') || (c == ' ');
}
return false;
}
void keyboard_on_text_input(char* text) {
if (sIgnoreTextInput != 0 && clock() <= sIgnoreTextInput) {
sIgnoreTextInput = 0;
return;
}
if (!sInTextInput) { return; }
// sanity check input
if (text == NULL) { return; }
int i = strlen(gTextInput);
while (*text != '\0') {
// make sure we don't overrun the buffer
if (i >= MAX_TEXT_INPUT) { break; }
if (i >= sMaxTextInput) { break; }
// copy over character if we're allowed to input it
if (keyboard_allow_character_input(*text)) {
gTextInput[i++] = *text;
}
text++;
}
djui_interactable_on_text_input(text);
}
static void keyboard_add_binds(int mask, unsigned int *scancode) {

View File

@ -6,26 +6,32 @@
# define VK_BASE_KEYBOARD 0x0000
#define SCANCODE_ESCAPE 1
#define SCANCODE_BACKSPACE 14
#define SCANCODE_ENTER 28
#define SCANCODE_CONTROL_LEFT 29
#define SCANCODE_SHIFT_LEFT 42
#define SCANCODE_A 30
#define SCANCODE_X 45
#define SCANCODE_C 46
#define SCANCODE_V 47
#define SCANCODE_SHIFT_RIGHT 54
#define SCANCODE_CONTROL_RIGHT 285
#define SCANCODE_HOME 327
#define SCANCODE_LEFT 331
#define SCANCODE_RIGHT 333
#define SCANCODE_END 335
#define SCANCODE_INSERT 338
#define SCANCODE_DELETE 339
#ifdef __cplusplus
extern "C" {
#endif
#define MAX_TEXT_INPUT 255
extern char gTextInput[];
enum TextInputMode {
TIM_IP,
TIM_MULTI_LINE,
TIM_SINGLE_LINE,
};
bool keyboard_on_key_down(int scancode);
bool keyboard_on_key_up(int scancode);
void keyboard_on_all_keys_up(void);
void keyboard_on_text_input(char* text);
char* keyboard_start_text_input(enum TextInputMode, u8 inMaxTextInput, void (*onEscape)(void), void (*onEnter)(void));
void keyboard_stop_text_input(void);
bool keyboard_in_text_input(void);
#ifdef __cplusplus
}

View File

@ -159,9 +159,11 @@ static void controller_sdl_read(OSContPad *pad) {
u32 mouse = SDL_GetRelativeMouseState(&mouse_x, &mouse_y);
for (u32 i = 0; i < num_mouse_binds; ++i)
if (mouse & SDL_BUTTON(mouse_binds[i][0]))
pad->button |= mouse_binds[i][1];
if (!gInteractableOverridePad) {
for (u32 i = 0; i < num_mouse_binds; ++i)
if (mouse & SDL_BUTTON(mouse_binds[i][0]))
pad->button |= mouse_binds[i][1];
}
// remember buttons that changed from 0 to 1
last_mouse = (mouse_buttons ^ mouse) & mouse;

View File

@ -20,6 +20,8 @@
#include "game/level_update.h"
#include "pc/djui/djui.h"
// mouse buttons are also in the controller namespace (why), just offset 0x100
#define VK_OFS_SDL_MOUSE 0x0100
#define VK_BASE_SDL_MOUSE (VK_BASE_SDL_GAMEPAD + VK_OFS_SDL_MOUSE)
@ -163,10 +165,11 @@ static void controller_sdl_read(OSContPad *pad) {
u32 mouse = SDL_GetRelativeMouseState(&mouse_x, &mouse_y);
for (u32 i = 0; i < num_mouse_binds; ++i)
if (mouse & SDL_BUTTON(mouse_binds[i][0]))
pad->button |= mouse_binds[i][1];
if (!gInteractableOverridePad) {
for (u32 i = 0; i < num_mouse_binds; ++i)
if (mouse & SDL_BUTTON(mouse_binds[i][0]))
pad->button |= mouse_binds[i][1];
}
// remember buttons that changed from 0 to 1
last_mouse = (mouse_buttons ^ mouse) & mouse;
mouse_buttons = mouse;

View File

@ -37,14 +37,6 @@ static void djui_checkbox_on_cursor_down_end(struct DjuiBase* base) {
djui_checkbox_set_default_style(base);
}
static void djui_checkbox_on_focus_begin(struct DjuiBase* base) {
struct DjuiCheckbox* checkbox = (struct DjuiCheckbox*)base;
djui_base_set_border_color(&checkbox->rect->base, 20, 170, 255, 255);
djui_base_set_color(&checkbox->rect->base, 255, 255, 255, 32);
djui_base_set_color(&checkbox->text->base, 229, 241, 251, 255);
djui_base_set_color(&checkbox->rectValue->base, 255, 255, 255, 255);
}
static void djui_checkbox_destroy(struct DjuiBase* base) {
struct DjuiCheckbox* checkbox = (struct DjuiCheckbox*)base;
free(checkbox);

View File

@ -1,15 +1,9 @@
#include "djui.h"
#include "pc/controller/controller_mouse.h"
#include "pc/controller/controller_sdl.h"
#include "src/pc/controller/controller_sdl.h"
#include "src/pc/controller/controller_mouse.h"
ALIGNED8 static u8 texture_hand_open[] = {
#include "textures/intro_raw/hand_open.rgba16.inc.c"
};
ALIGNED8 static u8 texture_hand_closed[] = {
#include "textures/intro_raw/hand_closed.rgba16.inc.c"
};
extern ALIGNED8 u8 gd_texture_hand_open[];
extern ALIGNED8 u8 gd_texture_hand_closed[];
static struct DjuiImage* sMouseCursor = NULL;
@ -22,7 +16,11 @@ f32 gCursorX = 0;
f32 gCursorY = 0;
void djui_cursor_set_visible(bool visible) {
djui_base_set_visible(&sMouseCursor->base, visible);
if (sMouseCursor) {
djui_base_set_visible(&sMouseCursor->base, visible);
}
sSavedMouseX = mouse_window_x;
sSavedMouseY = mouse_window_y;
}
bool djui_cursor_inside_base(struct DjuiBase* base) {
@ -42,11 +40,7 @@ static void djui_cursor_base_hover_location(struct DjuiBase* base, f32* x, f32*
void djui_cursor_input_controlled_center(struct DjuiBase* base) {
if (!sCursorMouseControlled) {
sInputControlledBase = base;
if (sMouseCursor != NULL) {
djui_base_set_visible(&sMouseCursor->base, (base != NULL));
}
sSavedMouseX = mouse_window_x;
sSavedMouseY = mouse_window_y;
djui_cursor_set_visible(base != NULL);
}
}
@ -109,12 +103,11 @@ void djui_cursor_update(void) {
controller_sdl_read_mouse_window();
// check if mouse is in control again
if (!sCursorMouseControlled) {
if (!sCursorMouseControlled || (sMouseCursor && !sMouseCursor->base.visible)) {
f32 dist = sqrtf(powf(mouse_window_x - sSavedMouseX, 2) + powf(mouse_window_y - sSavedMouseY, 2));
if (dist > 5) {
sCursorMouseControlled = true;
djui_interactable_set_input_focus(NULL);
djui_base_set_visible(&sMouseCursor->base, true);
djui_cursor_set_visible(true);
}
}
@ -131,16 +124,16 @@ void djui_cursor_update(void) {
// set cursor sprite
if ((gInteractablePad.button & PAD_BUTTON_A) || (mouse_window_buttons & MOUSE_BUTTON_1)) {
djui_image_set_image(sMouseCursor, texture_hand_closed, 32, 32, 16);
djui_image_set_image(sMouseCursor, gd_texture_hand_closed, 32, 32, 16);
} else {
djui_image_set_image(sMouseCursor, texture_hand_open, 32, 32, 16);
djui_image_set_image(sMouseCursor, gd_texture_hand_open, 32, 32, 16);
}
#endif
djui_base_render(&sMouseCursor->base);
}
void djui_cursor_create(void) {
sMouseCursor = djui_image_create(NULL, texture_hand_open, 32, 32, 16);
sMouseCursor = djui_image_create(NULL, gd_texture_hand_open, 32, 32, 16);
djui_base_set_location(&sMouseCursor->base, 0, 0);
djui_base_set_size(&sMouseCursor->base, 64, 64);
}

View File

@ -1,15 +1,24 @@
#include <string.h>
#include <stdio.h>
#include "djui.h"
#include "pc/gfx/gfx_window_manager_api.h"
#include "pc/pc_main.h"
#include "game/segment2.h"
#include "pc/controller/controller_keyboard.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 sHeldShift = 0;
static u8 sHeldControl = 0;
static u8 sCursorBlink = 0;
void djui_inputbox_hook_enter_press(struct DjuiInputbox* inputbox, void (*on_enter_press)(void)) {
inputbox->on_enter_press = on_enter_press;
}
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);
@ -51,6 +60,7 @@ static void djui_inputbox_on_cursor_down_begin(struct DjuiBase* base, bool input
inputbox->selection[0] = index;
inputbox->selection[1] = index;
sCursorBlink = 0;
djui_interactable_set_input_focus(base);
}
static void djui_inputbox_on_cursor_down(struct DjuiBase* base) {
@ -63,6 +73,236 @@ static void djui_inputbox_on_cursor_down_end(struct DjuiBase* base) {
djui_inputbox_set_default_style(base);
}
static u16 djui_inputbox_jump_word_left(char* msg, u16 len, u16 i) {
if (i == 0) { return i; }
s32 lastI = i;
bool seenNonSpace = false;
while (true) {
if (msg[i] == ' ' && seenNonSpace) { i = lastI; break; }
lastI = i;
i--;
if (i <= 0) { i = 0; break; }
if (msg[i] != ' ') { seenNonSpace = true; }
}
return i;
}
static u16 djui_inputbox_jump_word_right(char *msg, u16 len, u16 i) {
if (i >= len) { return len; }
bool seenSpace = false;
while (true) {
i++;
if (i >= len) { i = len; break; }
if (msg[i] != ' ' && seenSpace) { break; }
if (msg[i] == ' ') { seenSpace = true; }
};
return i;
}
static void djui_inputbox_delete_selection(struct DjuiInputbox *inputbox) {
u16 *sel = inputbox->selection;
char *msg = inputbox->buffer;
u16 len = strlen(msg);
if (sel[0] != sel[1]) {
u16 s1 = fmin(sel[0], sel[1]);
u16 s2 = fmax(sel[0], sel[1]);
memmove(&msg[s1], &msg[s2], (len + 1) - s2);
sel[0] = s1;
sel[1] = s1;
}
}
static bool djui_inputbox_on_key_down(struct DjuiBase *base, int scancode) {
struct DjuiInputbox *inputbox = (struct DjuiInputbox *) base;
u16 *sel = inputbox->selection;
char *msg = inputbox->buffer;
u16 len = strlen(msg);
u16 s1 = fmin(sel[0], sel[1]);
u16 s2 = fmax(sel[0], sel[1]);
switch (scancode) {
case SCANCODE_CONTROL_LEFT: sHeldControl |= (1 << 0); return true;
case SCANCODE_CONTROL_RIGHT: sHeldControl |= (1 << 1); return true;
case SCANCODE_SHIFT_LEFT: sHeldShift |= (1 << 0); return true;
case SCANCODE_SHIFT_RIGHT: sHeldShift |= (1 << 1); return true;
}
if (scancode == SCANCODE_LEFT) {
if (sHeldControl) {
sel[0] = djui_inputbox_jump_word_left(msg, len, sel[0]);
} else if (sel[0] > 0) {
sel[0]--;
}
if (!sHeldShift) { sel[1] = sel[0]; }
sCursorBlink = 0;
return true;
}
if (scancode == SCANCODE_RIGHT) {
if (sHeldControl) {
sel[0] = djui_inputbox_jump_word_right(msg, len, sel[0]);
} else if (sel[0] < len) {
sel[0]++;
}
if (!sHeldShift) { sel[1] = sel[0]; }
sCursorBlink = 0;
return true;
}
if (scancode == SCANCODE_HOME) {
sel[0] = 0;
if (!sHeldShift) { sel[1] = sel[0]; }
sCursorBlink = 0;
return true;
}
if (scancode == SCANCODE_END) {
sel[0] = len;
if (!sHeldShift) { sel[1] = sel[0]; }
sCursorBlink = 0;
return true;
}
if (scancode == SCANCODE_BACKSPACE) {
if (sel[0] == sel[1]) {
if (sHeldControl) {
sel[0] = djui_inputbox_jump_word_left(msg, len, sel[0]);
} else if (sel[0] > 0) {
sel[0]--;
}
}
if (sel[0] != sel[1]) {
djui_inputbox_delete_selection(inputbox);
}
sCursorBlink = 0;
return true;
}
if (scancode == SCANCODE_DELETE) {
if (sel[0] == sel[1]) {
if (sHeldControl) {
sel[1] = djui_inputbox_jump_word_right(msg, len, sel[1]);
} else if (sel[1] < len) {
sel[1]++;
}
}
if (sel[0] != sel[1]) {
djui_inputbox_delete_selection(inputbox);
}
sCursorBlink = 0;
return true;
}
if ((sHeldControl && scancode == SCANCODE_V) || (sHeldShift && scancode == SCANCODE_INSERT)) {
djui_interactable_on_text_input(wm_api->get_clipboard_text());
sCursorBlink = 0;
return true;
}
if (sHeldControl && (scancode == SCANCODE_C || scancode == SCANCODE_X)) {
if (sel[0] != sel[1]) {
char clipboardText[256] = { 0 };
snprintf(clipboardText, fmin(256, 1 + s2 - s1), "%s", &msg[s1]);
wm_api->set_clipboard_text(clipboardText);
if (scancode == SCANCODE_X) {
djui_inputbox_delete_selection(inputbox);
sCursorBlink = 0;
}
}
return true;
}
if (sHeldControl && scancode == SCANCODE_A) {
inputbox->selection[0] = len;
inputbox->selection[1] = 0;
sCursorBlink = 0;
return true;
}
if (scancode == SCANCODE_ESCAPE) {
djui_interactable_set_input_focus(NULL);
return true;
}
if (scancode == SCANCODE_ENTER) {
djui_interactable_set_input_focus(NULL);
if (inputbox->on_enter_press) {
inputbox->on_enter_press();
}
return true;
}
return true;
}
static void djui_inputbox_on_key_up(struct DjuiBase *base, int scancode) {
switch (scancode) {
case SCANCODE_CONTROL_LEFT: sHeldControl &= ~(1 << 0); break;
case SCANCODE_CONTROL_RIGHT: sHeldControl &= ~(1 << 1); break;
case SCANCODE_SHIFT_LEFT: sHeldShift &= ~(1 << 0); break;
case SCANCODE_SHIFT_RIGHT: sHeldShift &= ~(1 << 1); break;
}
}
static void djui_inputbox_on_focus_begin(struct DjuiBase* base) {
sHeldControl = 0;
sHeldShift = 0;
wm_api->start_text_input();
}
static void djui_inputbox_on_focus_end(struct DjuiBase* base) {
wm_api->stop_text_input();
}
static void djui_inputbox_on_text_input(struct DjuiBase *base, char* text) {
struct DjuiInputbox *inputbox = (struct DjuiInputbox *) base;
char* msg = inputbox->buffer;
int msgLen = strlen(msg);
int textLen = strlen(text);
// truncate
if (textLen + msgLen >= inputbox->bufferSize) {
int space = (inputbox->bufferSize - msgLen);
if (space <= 1) { return; }
text[space - 1] = '\0';
textLen = space - 1;
}
// erase selection
if (inputbox->selection[0] != inputbox->selection[1]) {
djui_inputbox_delete_selection(inputbox);
}
// sanitize
char *t = text;
while (*t != '\0') {
if (*t == '\n') { *t = ' '; }
else if (*t == '\r') { *t = ' '; }
else if (*t == ' ') { ; }
else if (*t < '!' || *t > '~') { *t = '?'; }
t++;
}
// back up current message
char* sMsg = malloc(sizeof(char) * (inputbox->bufferSize));
memcpy(sMsg, msg, inputbox->bufferSize);
// insert text
u16 sel = inputbox->selection[0];
snprintf(&msg[sel], (inputbox->bufferSize - sel), "%s%s", text, &sMsg[sel]);
free(sMsg);
// adjust cursor
inputbox->selection[0] += strlen(text);
inputbox->selection[1] = inputbox->selection[0];
sCursorBlink = 0;
}
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];
@ -74,16 +314,14 @@ static void djui_inputbox_render_char(struct DjuiInputbox* inputbox, char c, f32
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;
if (c != ' ' && !djui_gfx_add_clipping_specific(&inputbox->base, font->rotatedUV, dX, dY, dW, dH)) {
if (*additionalShift > 0) {
create_dl_translation_matrix(DJUI_MTX_NOPUSH, *additionalShift, 0, 0);
*additionalShift = 0;
}
font->render_char(c);
}
create_dl_translation_matrix(DJUI_MTX_NOPUSH, charWidth + *additionalShift, 0, 0);
*additionalShift = 0;
*additionalShift += charWidth;
}
static void djui_inputbox_render_selection(struct DjuiInputbox* inputbox) {
@ -109,7 +347,7 @@ static void djui_inputbox_render_selection(struct DjuiInputbox* inputbox) {
// render only cursor when there is no selection width
if (selection[0] == selection[1]) {
if (sCursorBlink < DJUI_INPUTBOX_MID_BLINK) {
if (sCursorBlink < DJUI_INPUTBOX_MID_BLINK && djui_interactable_is_input_focus(&inputbox->base)) {
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);
@ -139,7 +377,7 @@ static void djui_inputbox_render_selection(struct DjuiInputbox* inputbox) {
gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
// render selection cursor
if (sCursorBlink < DJUI_INPUTBOX_MID_BLINK) {
if (sCursorBlink < DJUI_INPUTBOX_MID_BLINK && djui_interactable_is_input_focus(&inputbox->base)) {
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);
@ -237,13 +475,10 @@ static void djui_inputbox_destroy(struct DjuiBase* base) {
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;
memset(inputbox, 0, sizeof(struct DjuiInputbox));
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);
@ -251,6 +486,9 @@ struct DjuiInputbox* djui_inputbox_create(struct DjuiBase* parent, u16 bufferSiz
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_interactable_hook_key(base, djui_inputbox_on_key_down, djui_inputbox_on_key_up);
djui_interactable_hook_focus(base, djui_inputbox_on_focus_begin, NULL, djui_inputbox_on_focus_end);
djui_interactable_hook_text_input(base, djui_inputbox_on_text_input);
djui_inputbox_set_default_style(base);

View File

@ -8,6 +8,9 @@ struct DjuiInputbox {
u16 bufferSize;
u16 selection[2];
f32 viewX;
void (*on_enter_press)(void);
};
void djui_inputbox_hook_enter_press(struct DjuiInputbox* inputbox, void (*on_enter_press)(void));
struct DjuiInputbox* djui_inputbox_create(struct DjuiBase* parent, u16 bufferSize);

View File

@ -26,11 +26,12 @@ static bool sIgnoreInteractableUntilCursorReleased = false;
static struct DjuiBase* sInteractableFocus = NULL;
static struct DjuiBase* sInteractableBinding = NULL;
static struct DjuiBase* sHovered = NULL;
static struct DjuiBase* sMouseDown = NULL;
bool gInteractableOverridePad = false;
OSContPad gInteractablePad = { 0 };
OSContPad sLastInteractablePad = { 0 };
static struct DjuiBase* sHovered = NULL;
static struct DjuiBase* sMouseDown = NULL;
bool gInteractableOverridePad = false;
OSContPad gInteractablePad = { 0 };
static OSContPad sLastInteractablePad = { 0 };
static int sLastMouseButtons = 0;
static void djui_interactable_on_click(struct DjuiBase* base) {
if (base == NULL) { return; }
@ -166,17 +167,46 @@ void djui_interactable_set_input_focus(struct DjuiBase* base) {
djui_cursor_set_visible(base == NULL);
}
void djui_interactable_on_key_down(int scancode) {
switch (scancode) {
case SCANCODE_UP: sKeyboardHoldDirection = PAD_HOLD_DIR_UP; break;
case SCANCODE_DOWN: sKeyboardHoldDirection = PAD_HOLD_DIR_DOWN; break;
case SCANCODE_LEFT: sKeyboardHoldDirection = PAD_HOLD_DIR_LEFT; break;
case SCANCODE_RIGHT: sKeyboardHoldDirection = PAD_HOLD_DIR_RIGHT; break;
case SCANCODE_ENTER: sKeyboardButtons |= PAD_BUTTON_A; break;
bool djui_interactable_is_input_focus(struct DjuiBase* base) {
return sInteractableFocus == base;
}
bool djui_interactable_on_key_down(int scancode) {
bool keyFocused = (sInteractableFocus != NULL)
&& (sInteractableFocus->interactable != NULL)
&& (sInteractableFocus->interactable->on_key_down != NULL);
if (keyFocused) {
bool consume = sInteractableFocus->interactable->on_key_down(sInteractableFocus, scancode);
sKeyboardHoldDirection = PAD_HOLD_DIR_NONE;
sKeyboardButtons = 0;
return consume;
}
switch (scancode) {
case SCANCODE_UP: sKeyboardHoldDirection = PAD_HOLD_DIR_UP; return true;
case SCANCODE_DOWN: sKeyboardHoldDirection = PAD_HOLD_DIR_DOWN; return true;
case SCANCODE_LEFT: sKeyboardHoldDirection = PAD_HOLD_DIR_LEFT; return true;
case SCANCODE_RIGHT: sKeyboardHoldDirection = PAD_HOLD_DIR_RIGHT; return true;
case SCANCODE_ENTER: sKeyboardButtons |= PAD_BUTTON_A; return true;
}
return false;
}
void djui_interactable_on_key_up(int scancode) {
bool keyFocused = (sInteractableFocus != NULL)
&& (sInteractableFocus->interactable != NULL)
&& (sInteractableFocus->interactable->on_key_up != NULL);
if (keyFocused) {
sInteractableFocus->interactable->on_key_up(sInteractableFocus, scancode);
sKeyboardHoldDirection = PAD_HOLD_DIR_NONE;
sKeyboardButtons = 0;
return;
}
OSContPad* pad = &gInteractablePad;
switch (scancode) {
case SCANCODE_UP: if (sKeyboardHoldDirection == PAD_HOLD_DIR_UP) { sKeyboardHoldDirection = PAD_HOLD_DIR_NONE; pad->stick_y = 0; } break;
@ -187,6 +217,13 @@ void djui_interactable_on_key_up(int scancode) {
}
}
void djui_interactable_on_text_input(char* text) {
if (sInteractableFocus == NULL) { return; }
if (sInteractableFocus->interactable == NULL) { return; }
if (sInteractableFocus->interactable->on_text_input == NULL) { return; }
sInteractableFocus->interactable->on_text_input(sInteractableFocus, text);
}
void djui_interactable_update_pad(void) {
OSContPad* pad = &gInteractablePad;
@ -252,16 +289,22 @@ void djui_interactable_update(void) {
}
}
if (sInteractableBinding != NULL) {
djui_interactable_on_bind(sInteractableBinding);
} else if (sInteractableFocus != NULL) {
// escape focus
u16 buttons = PAD_BUTTON_A | PAD_BUTTON_B;
if ((padButtons & buttons) && !(sLastInteractablePad.button & buttons)) {
// update focused
if (sInteractableFocus) {
u16 mainButtons = PAD_BUTTON_A | PAD_BUTTON_B;
if ((mouseButtons & MOUSE_BUTTON_1) && !(sLastMouseButtons && MOUSE_BUTTON_1) && !djui_cursor_inside_base(sInteractableFocus)) {
// clicked outside of focused
djui_interactable_set_input_focus(NULL);
} else if ((padButtons & mainButtons) && !(sLastInteractablePad.button & mainButtons)) {
// pressed main face button
djui_interactable_set_input_focus(NULL);
} else {
djui_interactable_on_focus(sInteractableFocus);
}
}
if (sInteractableBinding != NULL) {
djui_interactable_on_bind(sInteractableBinding);
} else if ((padButtons & PAD_BUTTON_A) || (mouseButtons & MOUSE_BUTTON_1)) {
// cursor down events
if (sHovered != NULL) {
@ -288,6 +331,7 @@ void djui_interactable_update(void) {
}
sLastInteractablePad = gInteractablePad;
sLastMouseButtons = mouseButtons;
}
void djui_interactable_hook_hover(struct DjuiBase* base,
@ -336,6 +380,21 @@ void djui_interactable_hook_bind(struct DjuiBase* base,
interactable->on_bind = on_bind;
}
void djui_interactable_hook_key(struct DjuiBase* base,
bool (*on_key_down)(struct DjuiBase*, int),
void (*on_key_up)(struct DjuiBase*, int)) {
struct DjuiInteractable *interactable = base->interactable;
interactable->on_key_down = on_key_down;
interactable->on_key_up = on_key_up;
}
void djui_interactable_hook_text_input(struct DjuiBase *base,
void (*on_text_input)(struct DjuiBase*, char*)) {
struct DjuiInteractable *interactable = base->interactable;
interactable->on_text_input = on_text_input;
}
void djui_interactable_create(struct DjuiBase* base) {
if (base->interactable != NULL) {

View File

@ -21,6 +21,9 @@ struct DjuiInteractable {
void (*on_click)(struct DjuiBase*);
void (*on_value_change)(struct DjuiBase*);
void (*on_bind)(struct DjuiBase*);
bool (*on_key_down)(struct DjuiBase*, int scancode);
void (*on_key_up)(struct DjuiBase*, int scancode);
void (*on_text_input)(struct DjuiBase*, char* text);
};
extern bool gInteractableOverridePad;
@ -29,8 +32,11 @@ extern OSContPad gInteractablePad;
bool djui_interactable_is_binding(void);
void djui_interactable_set_binding(struct DjuiBase* base);
void djui_interactable_set_input_focus(struct DjuiBase* base);
void djui_interactable_on_key_down(int scancode);
bool djui_interactable_is_input_focus(struct DjuiBase* base);
bool djui_interactable_on_key_down(int scancode);
void djui_interactable_on_key_up(int scancode);
void djui_interactable_on_text_input(char *text);
void djui_interactable_update(void);
void djui_interactable_hook_hover(struct DjuiBase* base,
@ -55,4 +61,12 @@ void djui_interactable_hook_value_change(struct DjuiBase* base,
void djui_interactable_hook_bind(struct DjuiBase* base,
void (*on_bind)(struct DjuiBase*));
void djui_interactable_hook_key(struct DjuiBase* base,
bool (*on_key_down)(struct DjuiBase*, int),
void (*on_key_up)(struct DjuiBase*, int));
void djui_interactable_hook_text_input(struct DjuiBase* base,
void (*on_text_input)(struct DjuiBase*, char*));
void djui_interactable_create(struct DjuiBase* base);

View File

@ -16,17 +16,17 @@ void djui_panel_controls_create(struct DjuiBase* caller) {
djui_base_set_color(&bindBody->base, 0, 0, 0, 0);
djui_flow_layout_set_margin(bindBody, 1);
{
struct DjuiBind* bind1 = djui_bind_create(&bindBody->base, "A", configKeyA);
struct DjuiBind* bind2 = djui_bind_create(&bindBody->base, "B", configKeyB);
struct DjuiBind* bind3 = djui_bind_create(&bindBody->base, "Start", configKeyStart);
struct DjuiBind* bind4 = djui_bind_create(&bindBody->base, "L", configKeyL);
struct DjuiBind* bind5 = djui_bind_create(&bindBody->base, "R", configKeyR);
struct DjuiBind* bind6 = djui_bind_create(&bindBody->base, "Z", configKeyZ);
struct DjuiBind* bind7 = djui_bind_create(&bindBody->base, "C Up", configKeyCUp);
struct DjuiBind* bind8 = djui_bind_create(&bindBody->base, "C Down", configKeyCDown);
struct DjuiBind* bind9 = djui_bind_create(&bindBody->base, "C Left", configKeyCLeft);
struct DjuiBind* bind10 = djui_bind_create(&bindBody->base, "C Right", configKeyCRight);
struct DjuiBind* bind11 = djui_bind_create(&bindBody->base, "Chat", configKeyChat);
struct DjuiBind* bind1 = djui_bind_create(&bindBody->base, "A", configKeyA);
djui_bind_create(&bindBody->base, "B", configKeyB);
djui_bind_create(&bindBody->base, "Start", configKeyStart);
djui_bind_create(&bindBody->base, "L", configKeyL);
djui_bind_create(&bindBody->base, "R", configKeyR);
djui_bind_create(&bindBody->base, "Z", configKeyZ);
djui_bind_create(&bindBody->base, "C Up", configKeyCUp);
djui_bind_create(&bindBody->base, "C Down", configKeyCDown);
djui_bind_create(&bindBody->base, "C Left", configKeyCLeft);
djui_bind_create(&bindBody->base, "C Right", configKeyCRight);
djui_bind_create(&bindBody->base, "Chat", configKeyChat);
defaultBase = &bind1->buttons[0]->base;
}

View File

@ -47,7 +47,7 @@ void djui_panel_main_create(struct DjuiBase* caller) {
}
struct DjuiInputbox* inputbox = djui_inputbox_create(&gDjuiRoot->base, 256);
djui_base_set_location(&inputbox->base, 400, 400);
djui_base_set_location(&inputbox->base, 400, 100);
djui_panel_add(caller, &panel->base, defaultBase);
gInteractableOverridePad = true;

View File

@ -634,6 +634,20 @@ static char* gfx_dxgi_get_clipboard_text(void) {
return NULL;
}
void gfx_dxgi_set_clipboard_text(char* text) {
if (OpenClipboard(NULL)) {
HGLOBAL clipbuffer;
char *buffer;
EmptyClipboard();
clipbuffer = GlobalAlloc(GMEM_DDESHARE, strlen(text) + 1);
buffer = (char *) GlobalLock(clipbuffer);
strcpy(buffer, LPCSTR(source));
GlobalUnlock(clipbuffer);
SetClipboardData(CF_TEXT, clipbuffer);
CloseClipboard();
}
}
void ThrowIfFailed(HRESULT res) {
if (FAILED(res)) {
fprintf(stderr, "Error: 0x%08X\n", res);
@ -665,6 +679,7 @@ struct GfxWindowManagerAPI gfx_dxgi = {
gfx_dxgi_start_text_input,
gfx_dxgi_stop_text_input,
gfx_dxgi_get_clipboard_text,
gfx_dxgi_set_clipboard_text,
};
#endif

View File

@ -369,6 +369,7 @@ static void gfx_sdl_shutdown(void) {
static void gfx_sdl_start_text_input(void) { SDL_StartTextInput(); }
static void gfx_sdl_stop_text_input(void) { SDL_StopTextInput(); }
static char* gfx_sdl_get_clipboard_text(void) { return SDL_GetClipboardText(); }
static void gfx_sdl_set_clipboard_text(char* text) { SDL_SetClipboardText(text); }
struct GfxWindowManagerAPI gfx_sdl = {
gfx_sdl_init,
@ -384,6 +385,7 @@ struct GfxWindowManagerAPI gfx_sdl = {
gfx_sdl_start_text_input,
gfx_sdl_stop_text_input,
gfx_sdl_get_clipboard_text,
gfx_sdl_set_clipboard_text,
};
#endif // BACKEND_WM

View File

@ -23,6 +23,7 @@ struct GfxWindowManagerAPI {
void (*start_text_input)(void);
void (*stop_text_input)(void);
char* (*get_clipboard_text)(void);
void (*set_clipboard_text)(char*);
};
#endif