Create World Screen + Scrolling Text Boxes!

This commit is contained in:
TheBrokenRail 2024-02-02 04:20:34 -05:00
parent 4f32cfab45
commit 99b43ddb5a
23 changed files with 414 additions and 311 deletions

@ -1 +1 @@
Subproject commit 95e24d13fa223ff524f8920314c4984686c73928 Subproject commit fbdd1e27983eeb1329d51ee3264b2974c4901fbe

View File

@ -219,7 +219,7 @@ static void glfw_char(__attribute__((unused)) GLFWwindow *window, unsigned int c
memset(str, 0, str_size); memset(str, 0, str_size);
codepoint_to_utf8((unsigned char *) str, codepoint); codepoint_to_utf8((unsigned char *) str, codepoint);
char *cp437_str = to_cp437(str); char *cp437_str = to_cp437(str);
// Send Event· // Send Event
for (int i = 0; cp437_str[i] != '\0'; i++) { for (int i = 0; cp437_str[i] != '\0'; i++) {
character_event(cp437_str[i]); character_event(cp437_str[i]);
} }

View File

@ -3,22 +3,9 @@
#include <symbols/minecraft.h> #include <symbols/minecraft.h>
struct TextInputBox { struct TextInputBox {
GuiComponent super; static TextInputBox *create(const std::string &placeholder = "", const std::string &text = "");
int m_ID; GuiComponent super;
int m_xPos;
int m_yPos;
int m_width;
int m_height;
std::string m_placeholder;
std::string m_text;
bool m_bFocused;
bool m_bEnabled;
bool m_bCursorOn;
int m_insertHead;
int m_lastFlashed;
Font *m_pFont;
int m_maxLength;
void setSize(int x, int y, int width = 200, int height = 12); void setSize(int x, int y, int width = 200, int height = 12);
void init(Font *pFont); void init(Font *pFont);
@ -30,6 +17,25 @@ struct TextInputBox {
void setFocused(bool b); void setFocused(bool b);
void onClick(int x, int y); void onClick(int x, int y);
bool clicked(int x, int y); bool clicked(int x, int y);
std::string getText();
bool isFocused();
void setMaxLength(int max_length);
static TextInputBox *create(int id, const std::string &placeholder = "", const std::string &text = ""); private:
void recalculateScroll();
std::string m_text;
bool m_bFocused;
int m_xPos;
int m_yPos;
int m_width;
int m_height;
std::string m_placeholder;
bool m_bEnabled;
bool m_bCursorOn;
int m_insertHead;
int m_lastFlashed;
Font *m_pFont;
int m_maxLength;
int m_scrollPos;
}; };

View File

@ -0,0 +1,5 @@
#pragma once
#include <symbols/minecraft.h>
Button *touch_create_button(int id, std::string text);

View File

@ -1,19 +1,21 @@
#pragma once #pragma once
#include <string>
#include <libreborn/libreborn.h> #include <libreborn/libreborn.h>
#ifdef __cplusplus // Message Limitations
extern "C" { #define MAX_CHAT_MESSAGE_LENGTH 256
#endif
// Message Prefix
__attribute__((visibility("internal"))) std::string _chat_get_prefix(char *username);
// Queue Message For Sending
#ifndef MCPI_SERVER_MODE #ifndef MCPI_SERVER_MODE
__attribute__((visibility("internal"))) void _chat_queue_message(const char *message); __attribute__((visibility("internal"))) void _chat_queue_message(const char *message);
#endif #endif
// Init Chat UI
#ifndef MCPI_HEADLESS_MODE #ifndef MCPI_HEADLESS_MODE
__attribute__((visibility("internal"))) void _init_chat_ui(); __attribute__((visibility("internal"))) void _init_chat_ui();
#endif #endif
#ifdef __cplusplus
}
#endif

View File

@ -19,9 +19,6 @@
#include "chat-internal.h" #include "chat-internal.h"
#include <mods/chat/chat.h> #include <mods/chat/chat.h>
// Message Limitations
#define MAX_CHAT_MESSAGE_LENGTH 512
// Send API Command // Send API Command
std::string chat_send_api_command(Minecraft *minecraft, std::string str) { std::string chat_send_api_command(Minecraft *minecraft, std::string str) {
struct ConnectedClient client; struct ConnectedClient client;
@ -47,9 +44,12 @@ static void send_api_chat_command(Minecraft *minecraft, char *str) {
#endif #endif
// Send Message To Players // Send Message To Players
std::string _chat_get_prefix(char *username) {
return std::string("<") + username + "> ";
}
void chat_send_message(ServerSideNetworkHandler *server_side_network_handler, char *username, char *message) { void chat_send_message(ServerSideNetworkHandler *server_side_network_handler, char *username, char *message) {
char *full_message = NULL; char *full_message = NULL;
safe_asprintf(&full_message, "<%s> %s", username, message); safe_asprintf(&full_message, "%s%s", _chat_get_prefix(username).c_str(), message);
sanitize_string(&full_message, MAX_CHAT_MESSAGE_LENGTH, 0); sanitize_string(&full_message, MAX_CHAT_MESSAGE_LENGTH, 0);
std::string cpp_string = full_message; std::string cpp_string = full_message;
free(full_message); free(full_message);
@ -123,5 +123,8 @@ void init_chat() {
// Init UI // Init UI
_init_chat_ui(); _init_chat_ui();
#endif #endif
// Disable Built-In Chat Message Limiting
unsigned char message_limit_patch[4] = {0x03, 0x00, 0x53, 0xe1}; // "cmp r4, r4"
patch((void *) 0x6b4c0, message_limit_patch);
} }
} }

View File

@ -8,6 +8,7 @@
#include <mods/chat/chat.h> #include <mods/chat/chat.h>
#include <mods/text-input-box/TextInputScreen.h> #include <mods/text-input-box/TextInputScreen.h>
#include <mods/misc/misc.h> #include <mods/misc/misc.h>
#include <mods/touch/touch.h>
// Structure // Structure
struct ChatScreen { struct ChatScreen {
@ -23,24 +24,16 @@ CUSTOM_VTABLE(chat_screen, Screen) {
original_init(super); original_init(super);
ChatScreen *self = (ChatScreen *) super; ChatScreen *self = (ChatScreen *) super;
// Text Input // Text Input
self->chat = TextInputBox::create(1); self->chat = TextInputBox::create();
self->super.m_textInputs->push_back(self->chat); self->super.m_textInputs->push_back(self->chat);
self->chat->init(super->font); self->chat->init(super->font);
self->chat->setFocused(true); self->chat->setFocused(true);
// Determien Max Length
std::string prefix = _chat_get_prefix(Strings_default_username);
int max_length = MAX_CHAT_MESSAGE_LENGTH - prefix.length();
self->chat->setMaxLength(max_length);
// Send Button // Send Button
if (Minecraft_isTouchscreen(super->minecraft)) { self->send = touch_create_button(1, "Send");
self->send = (Button *) new Touch_TButton;
} else {
self->send = new Button;
}
ALLOC_CHECK(self->send);
int send_id = 2;
std::string send_text = "Send";
if (Minecraft_isTouchscreen(super->minecraft)) {
Touch_TButton_constructor((Touch_TButton *) self->send, send_id, &send_text);
} else {
Button_constructor(self->send, send_id, &send_text);
}
super->rendered_buttons.push_back(self->send); super->rendered_buttons.push_back(self->send);
super->selectable_buttons.push_back(self->send); super->selectable_buttons.push_back(self->send);
// Hide Chat Messages // Hide Chat Messages
@ -69,7 +62,7 @@ CUSTOM_VTABLE(chat_screen, Screen) {
vtable->setupPositions = [](Screen *super) { vtable->setupPositions = [](Screen *super) {
Screen_setupPositions_non_virtual(super); Screen_setupPositions_non_virtual(super);
ChatScreen *self = (ChatScreen *) super; ChatScreen *self = (ChatScreen *) super;
self->send->height = 20; self->send->height = 24;
self->send->width = 40; self->send->width = 40;
int x = 0; int x = 0;
int y = super->height - self->send->height; int y = super->height - self->send->height;
@ -83,9 +76,9 @@ CUSTOM_VTABLE(chat_screen, Screen) {
vtable->keyPressed = [](Screen *super, int key) { vtable->keyPressed = [](Screen *super, int key) {
// Handle Enter // Handle Enter
ChatScreen *self = (ChatScreen *) super; ChatScreen *self = (ChatScreen *) super;
if (key == 0x0d && self->chat->m_bFocused) { if (key == 0x0d && self->chat->isFocused()) {
if (self->chat->m_text.length() > 0) { if (self->chat->getText().length() > 0) {
_chat_queue_message(self->chat->m_text.c_str()); _chat_queue_message(self->chat->getText().c_str());
} }
Minecraft_setScreen(super->minecraft, NULL); Minecraft_setScreen(super->minecraft, NULL);
} }

View File

@ -4,230 +4,223 @@
// Game Mode UI Code Is Useless In Headless Mode // Game Mode UI Code Is Useless In Headless Mode
#ifndef MCPI_SERVER_MODE #ifndef MCPI_SERVER_MODE
#include <pthread.h>
#include <cstring>
#include <ctime>
#include <string> #include <string>
#include <stdexcept> #include <set>
#include <symbols/minecraft.h> #include <symbols/minecraft.h>
#include <media-layer/core.h>
#include <mods/text-input-box/TextInputScreen.h>
#include <mods/touch/touch.h>
#include "game-mode-internal.h" #include "game-mode-internal.h"
// Run Command // Strings
static char *run_command_proper(const char *command[], bool allow_empty) { #define GAME_MODE_STR(mode) ("Game Mode: " mode)
// Run #define SURVIVAL_STR GAME_MODE_STR("Survival")
int return_code; #define CREATIVE_STR GAME_MODE_STR("Creative")
char *output = run_command(command, &return_code, NULL);
// Handle Message // Structure
if (output != NULL) { struct CreateWorldScreen {
// Check Return Code TextInputScreen super;
if (is_exit_status_success(return_code)) { TextInputBox *name;
// Remove Ending Newline TextInputBox *seed;
int length = strlen(output); Button *game_mode;
if (output[length - 1] == '\n') { Button *create;
output[length - 1] = '\0'; Button *back;
}
length = strlen(output);
// Don't Allow Empty Strings
if (allow_empty || length > 0) {
// Return
return output;
}
}
// Free Output
free(output);
}
// Return
return !is_exit_status_success(return_code) ? NULL : run_command_proper(command, allow_empty);
}
// Track Create World State
static pthread_mutex_t create_world_state_lock = PTHREAD_MUTEX_INITIALIZER;
typedef enum {
DIALOG_CLOSED,
DIALOG_OPEN,
DIALOG_SUCCESS
} create_world_state_dialog_t;
struct create_world_state_t {
volatile create_world_state_dialog_t dialog_state = DIALOG_CLOSED;
volatile char *name = NULL;
volatile int32_t game_mode = 0;
volatile int32_t seed = 0;
}; };
static create_world_state_t create_world_state; static void create_world(Minecraft *minecraft, std::string name, bool is_creative, std::string seed);
// Destructor CUSTOM_VTABLE(create_world_screen, Screen) {
__attribute__((destructor)) static void _free_create_world_state_name() { TextInputScreen::setup(vtable);
free((void *) create_world_state.name); // Constants
} static int line_height = 8;
static int bottom_padding = 4;
// Reset State (Assume Lock) static int inner_padding = 4;
static void reset_create_world_state() { static int description_padding = 4;
create_world_state.dialog_state = DIALOG_CLOSED; static int title_padding = 8;
if (create_world_state.name != NULL) { // Init
free((void *) create_world_state.name); static Screen_init_t original_init = vtable->init;
} vtable->init = [](Screen *super) {
create_world_state.name = NULL; original_init(super);
create_world_state.game_mode = 0; CreateWorldScreen *self = (CreateWorldScreen *) super;
create_world_state.seed = 0; // Name
} self->name = TextInputBox::create("World Name", "Unnamed world");
self->super.m_textInputs->push_back(self->name);
// Chat Thread self->name->init(super->font);
#define DEFAULT_WORLD_NAME "Unnamed world" self->name->setFocused(true);
#define DIALOG_TITLE "Create World"
#define GAME_MODE_DIALOG_SIZE "200"
static void *create_world_thread(__attribute__((unused)) void *nop) {
// Run Dialogs
char *world_name = NULL;
{
// World Name
{
// Open
const char *command[] = {
"zenity",
"--title", DIALOG_TITLE,
"--name", MCPI_APP_ID,
"--entry",
"--text", "Enter World Name:",
"--entry-text", DEFAULT_WORLD_NAME,
NULL
};
char *output = run_command_proper(command, false);
// Handle Message
if (output != NULL) {
// Store
world_name = strdup(output);
ALLOC_CHECK(world_name);
// Free
free(output);
} else {
// Fail
goto fail;
}
}
// Game Mode
int game_mode = 0;
{
// Open
const char *command[] = {
"zenity",
"--title", DIALOG_TITLE,
"--name", MCPI_APP_ID,
"--list",
"--radiolist",
"--width", GAME_MODE_DIALOG_SIZE,
"--height", GAME_MODE_DIALOG_SIZE,
"--text", "Select Game Mode:",
"--column","Selected",
"--column", "Name",
"TRUE", "Creative",
"FALSE", "Survival",
NULL
};
char *output = run_command_proper(command, false);
// Handle Message
if (output != NULL) {
// Store
game_mode = strcmp(output, "Creative") == 0;
// Free
free(output);
} else {
// Fail
goto fail;
}
}
// Seed // Seed
int32_t seed = 0; self->seed = TextInputBox::create("Seed");
get_seed: self->super.m_textInputs->push_back(self->seed);
{ self->seed->init(super->font);
// Open self->seed->setFocused(false);
const char *command[] = { // Game Mode
"zenity", self->game_mode = touch_create_button(1, CREATIVE_STR);
"--title", DIALOG_TITLE, super->rendered_buttons.push_back(self->game_mode);
"--name", MCPI_APP_ID, super->selectable_buttons.push_back(self->game_mode);
"--entry", // Create
"--only-numerical", self->create = touch_create_button(2, "Create");
"--text", "Enter Seed (Leave Blank For Random):", super->rendered_buttons.push_back(self->create);
NULL super->selectable_buttons.push_back(self->create);
}; // Back
char *output = run_command_proper(command, true); self->back = touch_create_button(3, "Back");
// Handle Message super->rendered_buttons.push_back(self->back);
if (output != NULL) { super->selectable_buttons.push_back(self->back);
// Store };
bool valid = true; // Removal
try { static Screen_removed_t original_removed = vtable->removed;
seed = strlen(output) == 0 ? time(NULL) : std::stoi(output); vtable->removed = [](Screen *super) {
} catch (std::invalid_argument &e) { original_removed(super);
// Invalid Seed CreateWorldScreen *self = (CreateWorldScreen *) super;
WARN("Invalid Seed: %s", output); delete self->name;
valid = false; delete self->seed;
} catch (std::out_of_range &e) { self->game_mode->vtable->destructor_deleting(self->game_mode);
// Out-Of-Range Seed self->back->vtable->destructor_deleting(self->back);
WARN("Seed Out-Of-Range: %s", output); self->create->vtable->destructor_deleting(self->create);
valid = false; };
} // Rendering
// Free static Screen_render_t original_render = vtable->render;
free(output); vtable->render = [](Screen *super, int x, int y, float param_1) {
// Retry If Invalid // Background
if (!valid) { super->vtable->renderBackground(super);
goto get_seed; // Call Original Method
} original_render(super, x, y, param_1);
} else { // Title
// Fail std::string title = "Create world";
goto fail; Screen_drawCenteredString(super, super->font, &title, super->width / 2, title_padding, 0xffffffff);
} // Game Mode Description
CreateWorldScreen *self = (CreateWorldScreen *) super;
bool is_creative = self->game_mode->text == CREATIVE_STR;
std::string description = is_creative ? Strings_creative_mode_description : Strings_survival_mode_description;
Screen_drawString(super, super->font, &description, self->game_mode->x, self->game_mode->y + self->game_mode->height + description_padding, 0xa0a0a0);
};
// Positioning
vtable->setupPositions = [](Screen *super) {
Screen_setupPositions_non_virtual(super);
CreateWorldScreen *self = (CreateWorldScreen *) super;
// Height/Width
int width = 120;
int height = 24;
self->create->width = self->back->width = self->game_mode->width = width;
int seed_width = self->game_mode->width;
int name_width = width * 1.5f;
self->create->height = self->back->height = self->game_mode->height = height;
int text_box_height = self->game_mode->height;
// Find Center Y
int top = (title_padding * 2) + line_height;
int bottom = super->height - self->create->height - (bottom_padding * 2);
int center_y = ((bottom - top) / 2) + top;
center_y -= (description_padding + line_height) / 2;
// X/Y
self->create->y = self->back->y = super->height - bottom_padding - height;
self->create->x = self->game_mode->x = (super->width / 2) - inner_padding - width;
self->back->x = (super->width / 2) + inner_padding;
int seed_x = self->back->x;
int name_x = (super->width / 2) - (name_width / 2);
int name_y = center_y - inner_padding - height;
self->game_mode->y = center_y + inner_padding;
int seed_y = self->game_mode->y;
// Update Text Boxes
self->name->setSize(name_x, name_y, name_width, text_box_height);
self->seed->setSize(seed_x, seed_y, seed_width, text_box_height);
};
// ESC
vtable->handleBackEvent = [](Screen *super, bool do_nothing) {
if (!do_nothing) {
ScreenChooser_setScreen(&super->minecraft->screen_chooser, 5);
} }
return true;
};
// Button Click
vtable->buttonClicked = [](Screen *super, Button *button) {
CreateWorldScreen *self = (CreateWorldScreen *) super;
bool is_creative = self->game_mode->text == CREATIVE_STR;
if (button == self->game_mode) {
// Toggle Game Mode
self->game_mode->text = is_creative ? SURVIVAL_STR : CREATIVE_STR;
} else if (button == self->back) {
// Back
super->vtable->handleBackEvent(super, false);
} else if (button == self->create) {
// Create
create_world(super->minecraft, self->name->getText(), is_creative, self->seed->getText());
}
};
}
static Screen *create_create_world_screen() {
// Construct
CreateWorldScreen *screen = new CreateWorldScreen;
ALLOC_CHECK(screen);
Screen_constructor(&screen->super.super);
// Update State // Set VTable
pthread_mutex_lock(&create_world_state_lock); screen->super.super.vtable = get_create_world_screen_vtable();
reset_create_world_state();
create_world_state.dialog_state = DIALOG_SUCCESS;
char *safe_name = to_cp437(world_name);
create_world_state.name = safe_name;
free(world_name);
create_world_state.game_mode = game_mode;
create_world_state.seed = seed;
pthread_mutex_unlock(&create_world_state_lock);
// Return
return NULL;
}
fail:
// Update State
pthread_mutex_lock(&create_world_state_lock);
reset_create_world_state();
pthread_mutex_unlock(&create_world_state_lock);
free(world_name);
// Return // Return
return NULL; return (Screen *) screen;
} }
// Create Chat Thead // Unique Level Name (https://github.com/ReMinecraftPE/mcpe/blob/d7a8b6baecf8b3b050538abdbc976f690312aa2d/source/client/gui/screens/CreateWorldScreen.cpp#L65-L83)
static void open_create_world() { static std::string getUniqueLevelName(LevelStorageSource *source, const std::string &in) {
// Update State (Assume Lock) std::set<std::string> maps;
create_world_state.dialog_state = DIALOG_OPEN; std::vector<LevelSummary> vls;
// Start Thread source->vtable->getLevelList(source, &vls);
pthread_t thread; for (int i = 0; i < int(vls.size()); i++) {
pthread_create(&thread, NULL, create_world_thread, NULL); const LevelSummary &ls = vls[i];
maps.insert(ls.folder);
}
std::string out = in;
while (maps.find(out) != maps.end()) {
out += "-";
}
return out;
} }
// Create World // Create World
static void create_world(Screen *host_screen, std::string folder_name) { static void create_world(Minecraft *minecraft, std::string name, bool is_creative, std::string seed_str) {
// Get Minecraft // Get Seed
Minecraft *minecraft = host_screen->minecraft; int seed;
seed_str = Util_stringTrim(&seed_str);
if (!seed_str.empty()) {
int num;
if (sscanf(seed_str.c_str(), "%d", &num) > 0) {
seed = num;
} else {
seed = Util_hashCode(&seed_str);
}
} else {
seed = Common_getEpochTimeS();
}
// Get Folder Name
name = Util_stringTrim(&name);
std::string folder = "";
for (char c : name) {
if (
c >= ' ' && c <= '~' &&
c != '/' &&
c != '\\' &&
c != '`' &&
c != '?' &&
c != '*' &&
c != '<' &&
c != '>' &&
c != '|' &&
c != '"' &&
c != ':'
) {
folder += c;
}
}
if (folder.empty()) {
folder = "World";
}
folder = getUniqueLevelName(Minecraft_getLevelSource(minecraft), folder);
// Settings // Settings
LevelSettings settings; LevelSettings settings;
settings.game_type = create_world_state.game_mode; settings.game_type = is_creative;
settings.seed = create_world_state.seed; settings.seed = seed;
// Create World // Create World
std::string world_name = (char *) create_world_state.name; minecraft->vtable->selectLevel(minecraft, &folder, &name, &settings);
minecraft->vtable->selectLevel(minecraft, &folder_name, &world_name, &settings);
// Multiplayer // Multiplayer
Minecraft_hostMultiplayer(minecraft, 19132); Minecraft_hostMultiplayer(minecraft, 19132);
@ -237,55 +230,20 @@ static void create_world(Screen *host_screen, std::string folder_name) {
ALLOC_CHECK(screen); ALLOC_CHECK(screen);
screen = ProgressScreen_constructor(screen); screen = ProgressScreen_constructor(screen);
Minecraft_setScreen(minecraft, (Screen *) screen); Minecraft_setScreen(minecraft, (Screen *) screen);
// Reset
reset_create_world_state();
} }
// Redirect Create World Button // Redirect Create World Button
#define create_SelectWorldScreen_tick_injection(prefix) \ #define create_SelectWorldScreen_tick_injection(prefix) \
static void prefix##SelectWorldScreen_tick_injection(prefix##SelectWorldScreen *screen) { \ static void prefix##SelectWorldScreen_tick_injection(prefix##SelectWorldScreen *screen) { \
/* Lock */ \ if (screen->should_create_world) { \
pthread_mutex_lock(&create_world_state_lock); \ /* Open Screen */ \
\ Minecraft_setScreen(screen->minecraft, create_create_world_screen()); \
bool *should_create_world = &screen->should_create_world; \
if (*should_create_world) { \
/* Check State */ \
if (create_world_state.dialog_state == DIALOG_CLOSED) { \
/* Open Dialog */ \
open_create_world(); \
} \
\
/* Finish */ \ /* Finish */ \
*should_create_world = false; \ screen->should_create_world = false; \
} else { \ } else { \
/* Call Original Method */ \ /* Call Original Method */ \
prefix##SelectWorldScreen_tick_non_virtual(screen); \ prefix##SelectWorldScreen_tick_non_virtual(screen); \
} \ } \
\
/* Create World If Dialog Succeeded */ \
if (create_world_state.dialog_state == DIALOG_SUCCESS) { \
/* Create World Dialog Finished */ \
\
/* Get New World Name */ \
std::string name = (char *) create_world_state.name; \
std::string new_name = prefix##SelectWorldScreen_getUniqueLevelName(screen, &name); \
\
/* Create World */ \
create_world((Screen *) screen, new_name); \
} \
\
/* Lock/Unlock UI */ \
if (create_world_state.dialog_state != DIALOG_OPEN) { \
/* Dialog Closed, Unlock UI */ \
media_set_interactable(1); \
} else { \
/* Dialog Open, Lock UI */ \
media_set_interactable(0); \
} \
\
/* Unlock */ \
pthread_mutex_unlock(&create_world_state_lock); \
} }
create_SelectWorldScreen_tick_injection() create_SelectWorldScreen_tick_injection()
create_SelectWorldScreen_tick_injection(Touch_) create_SelectWorldScreen_tick_injection(Touch_)

View File

@ -35,7 +35,7 @@ static void _handle_back(Minecraft *minecraft) {
} }
// Fix OptionsScreen Ignoring The Back Button // Fix OptionsScreen Ignoring The Back Button
static int32_t OptionsScreen_handleBackEvent_injection(OptionsScreen *screen, bool do_nothing) { static bool OptionsScreen_handleBackEvent_injection(OptionsScreen *screen, bool do_nothing) {
if (!do_nothing) { if (!do_nothing) {
Minecraft *minecraft = screen->minecraft; Minecraft *minecraft = screen->minecraft;
Minecraft_setScreen(minecraft, NULL); Minecraft_setScreen(minecraft, NULL);

View File

@ -2,13 +2,12 @@
#include <mods/text-input-box/TextInputBox.h> #include <mods/text-input-box/TextInputBox.h>
TextInputBox *TextInputBox::create(int id, const std::string &placeholder, const std::string &text) { TextInputBox *TextInputBox::create(const std::string &placeholder, const std::string &text) {
// Construct // Construct
TextInputBox *self = new TextInputBox; TextInputBox *self = new TextInputBox;
GuiComponent_constructor(&self->super); GuiComponent_constructor(&self->super);
// Setup // Setup
self->m_ID = id;
self->m_xPos = 0; self->m_xPos = 0;
self->m_yPos = 0; self->m_yPos = 0;
self->m_width = 0; self->m_width = 0;
@ -22,6 +21,7 @@ TextInputBox *TextInputBox::create(int id, const std::string &placeholder, const
self->m_lastFlashed = 0; self->m_lastFlashed = 0;
self->m_pFont = nullptr; self->m_pFont = nullptr;
self->m_maxLength = -1; self->m_maxLength = -1;
self->m_scrollPos = 0;
// Return // Return
return self; return self;
@ -32,6 +32,7 @@ void TextInputBox::setSize(int x, int y, int width, int height) {
m_yPos = y; m_yPos = y;
m_width = width; m_width = width;
m_height = height; m_height = height;
recalculateScroll();
} }
void TextInputBox::init(Font *pFont) { void TextInputBox::init(Font *pFont) {
@ -61,6 +62,7 @@ void TextInputBox::keyPressed(int key) {
} }
m_text.erase(m_text.begin() + m_insertHead - 1, m_text.begin() + m_insertHead); m_text.erase(m_text.begin() + m_insertHead - 1, m_text.begin() + m_insertHead);
m_insertHead--; m_insertHead--;
recalculateScroll();
break; break;
} }
case 0x2e: { case 0x2e: {
@ -83,6 +85,7 @@ void TextInputBox::keyPressed(int key) {
if (m_insertHead < 0) { if (m_insertHead < 0) {
m_insertHead = 0; m_insertHead = 0;
} }
recalculateScroll();
break; break;
} }
case 0x27: { case 0x27: {
@ -95,6 +98,7 @@ void TextInputBox::keyPressed(int key) {
} else { } else {
m_insertHead = 0; m_insertHead = 0;
} }
recalculateScroll();
break; break;
} }
case 0x0d: { case 0x0d: {
@ -130,6 +134,7 @@ void TextInputBox::setFocused(bool b) {
m_lastFlashed = Common_getTimeMs(); m_lastFlashed = Common_getTimeMs();
m_bCursorOn = true; m_bCursorOn = true;
m_insertHead = int(m_text.size()); m_insertHead = int(m_text.size());
recalculateScroll();
} }
} }
@ -143,38 +148,64 @@ void TextInputBox::charPressed(int k) {
return; return;
} }
// note: the width will increase by the same amount no matter where K is appended // Ignore Newlines
std::string test_str = m_text + char(k); if (k == '\n') {
if (m_maxLength != -1 && int(test_str.length()) > m_maxLength) {
return; return;
} }
int width = Font_width(m_pFont, &test_str);
if (width < (m_width - PADDING)) { // Check Max Length
m_text.insert(m_text.begin() + m_insertHead, k); if (m_maxLength != -1 && int(m_text.length()) >= m_maxLength) {
m_insertHead++; return;
} }
// Insert
m_text.insert(m_text.begin() + m_insertHead, k);
m_insertHead++;
recalculateScroll();
} }
static std::string get_rendered_text(Font *font, int width, int scroll_pos, std::string text) {
std::string rendered_text = text.substr(scroll_pos);
int max_width = width - (PADDING * 2);
while (Font_width(font, &rendered_text) > max_width) {
rendered_text.pop_back();
}
return rendered_text;
}
static char CURSOR_CHAR = '_';
void TextInputBox::render() { void TextInputBox::render() {
GuiComponent_fill(&super, m_xPos, m_yPos, m_xPos + m_width, m_yPos + m_height, 0xFFAAAAAA); GuiComponent_fill(&super, m_xPos, m_yPos, m_xPos + m_width, m_yPos + m_height, 0xFFAAAAAA);
GuiComponent_fill(&super, m_xPos + 1, m_yPos + 1, m_xPos + m_width - 1, m_yPos + m_height - 1, 0xFF000000); GuiComponent_fill(&super, m_xPos + 1, m_yPos + 1, m_xPos + m_width - 1, m_yPos + m_height - 1, 0xFF000000);
int textYPos = (m_height - 8) / 2; int text_color;
int scroll_pos;
std::string rendered_text;
if (m_text.empty()) { if (m_text.empty()) {
GuiComponent_drawString(&super, m_pFont, &m_placeholder, m_xPos + PADDING, m_yPos + textYPos, 0x404040); rendered_text = m_placeholder;
text_color = 0x404040;
scroll_pos = 0;
} else { } else {
GuiComponent_drawString(&super, m_pFont, &m_text, m_xPos + PADDING, m_yPos + textYPos, 0xFFFFFF); rendered_text = m_text;
text_color = 0xffffff;
scroll_pos = m_scrollPos;
} }
rendered_text = get_rendered_text(m_pFont, m_width, scroll_pos, rendered_text);
int textYPos = (m_height - 8) / 2;
GuiComponent_drawString(&super, m_pFont, &rendered_text, m_xPos + PADDING, m_yPos + textYPos, text_color);
if (m_bCursorOn) { if (m_bCursorOn) {
int xPos = 5; int cursor_pos = m_insertHead - m_scrollPos;
if (cursor_pos >= 0 && cursor_pos <= int(rendered_text.length())) {
std::string substr = rendered_text.substr(0, cursor_pos);
int xPos = PADDING + Font_width(m_pFont, &substr);
std::string substr = m_text.substr(0, m_insertHead); std::string str;
xPos += Font_width(m_pFont, &substr); str += CURSOR_CHAR;
GuiComponent_drawString(&super, m_pFont, &str, m_xPos + xPos, m_yPos + textYPos + 2, 0xffffff);
std::string str = "_"; }
GuiComponent_drawString(&super, m_pFont, &str, m_xPos + xPos, m_yPos + textYPos + 2, 0xFFFFFF);
} }
} }
@ -198,3 +229,66 @@ bool TextInputBox::clicked(int xPos, int yPos) {
return true; return true;
} }
void TextInputBox::recalculateScroll() {
// Skip If Size Unset
if (m_width == 0) {
return;
}
// Ensure Cursor Is Visible
bool is_cursor_at_end = m_insertHead == int(m_text.length());
if (m_scrollPos >= m_insertHead && m_scrollPos > 0) {
// Cursor Is At Scroll Position
// Move Back Scroll As Far As Possible
while (true) {
int test_scroll_pos = m_scrollPos - 1;
std::string rendered_text = m_text;
if (is_cursor_at_end) {
rendered_text += CURSOR_CHAR;
}
rendered_text = get_rendered_text(m_pFont, m_width, test_scroll_pos, rendered_text);
int cursor_pos = m_insertHead - test_scroll_pos;
if (cursor_pos >= int(rendered_text.length())) {
break;
} else {
m_scrollPos = test_scroll_pos;
if (m_scrollPos == 0) {
break;
}
}
}
} else {
// Cursor After Scroll Area
// Increase Scroll So Cursor Is Visible
while (true) {
std::string rendered_text = m_text;
if (is_cursor_at_end) {
rendered_text += CURSOR_CHAR;
}
rendered_text = get_rendered_text(m_pFont, m_width, m_scrollPos, rendered_text);
int cursor_pos = m_insertHead - m_scrollPos;
if (cursor_pos < int(rendered_text.length())) {
break;
} else {
if (m_scrollPos == int(m_text.length())) {
WARN("Text Box Is Too Small");
break;
} else {
m_scrollPos++;
}
}
}
}
}
std::string TextInputBox::getText() {
return m_text;
}
bool TextInputBox::isFocused() {
return m_bFocused;
}
void TextInputBox::setMaxLength(int max_length) {
m_maxLength = max_length;
}

View File

@ -2,6 +2,7 @@
#include <mods/feature/feature.h> #include <mods/feature/feature.h>
#include <mods/init/init.h> #include <mods/init/init.h>
#include <mods/touch/touch.h>
#include <symbols/minecraft.h> #include <symbols/minecraft.h>
@ -40,9 +41,27 @@ static void LargeImageButton_render_GuiComponent_drawCenteredString_injection(Gu
GuiComponent_drawCenteredString(component, font, text, x, y, color); GuiComponent_drawCenteredString(component, font, text, x, y, color);
} }
// Create Button
static int touch_gui = 0;
Button *touch_create_button(int id, std::string text) {
Button *button = nullptr;
if (touch_gui) {
button = (Button *) new Touch_TButton;
} else {
button = new Button;
}
ALLOC_CHECK(button);
if (touch_gui) {
Touch_TButton_constructor((Touch_TButton *) button, id, &text);
} else {
Button_constructor(button, id, &text);
}
return button;
}
// Init // Init
void init_touch() { void init_touch() {
int touch_gui = feature_has("Full Touch GUI", server_disabled); touch_gui = feature_has("Full Touch GUI", server_disabled);
int touch_buttons = touch_gui; int touch_buttons = touch_gui;
if (touch_gui) { if (touch_gui) {
// Main UI // Main UI

View File

@ -76,6 +76,7 @@ set(SRC
src/level/ServerLevel.def src/level/ServerLevel.def
src/level/Dimension.def src/level/Dimension.def
src/level/MultiPlayerLevel.def src/level/MultiPlayerLevel.def
src/level/LevelSummary.def
src/item/ItemRenderer.def src/item/ItemRenderer.def
src/item/ItemInHandRenderer.def src/item/ItemInHandRenderer.def
src/item/AuxDataTileItem.def src/item/AuxDataTileItem.def
@ -100,6 +101,7 @@ set(SRC
src/gui/screens/StartMenuScreen.def src/gui/screens/StartMenuScreen.def
src/gui/screens/ProgressScreen.def src/gui/screens/ProgressScreen.def
src/gui/screens/Touch_SelectWorldScreen.def src/gui/screens/Touch_SelectWorldScreen.def
src/gui/screens/ScreenChooser.def
src/gui/Font.def src/gui/Font.def
src/gui/components/ImageButton.def src/gui/components/ImageButton.def
src/gui/components/OptionButton.def src/gui/components/OptionButton.def
@ -142,6 +144,7 @@ set(SRC
src/misc/Config.def src/misc/Config.def
src/misc/Random.def src/misc/Random.def
src/misc/Mth.def src/misc/Mth.def
src/misc/Util.def
src/input/IMoveInput.def src/input/IMoveInput.def
src/input/IBuildInput.def src/input/IBuildInput.def
src/input/MouseBuildInput.def src/input/MouseBuildInput.def

View File

@ -41,6 +41,7 @@ property int progress = 0xc60;
property PerfRenderer *perf_renderer = 0xcbc; property PerfRenderer *perf_renderer = 0xcbc;
property CommandServer *command_server = 0xcc0; property CommandServer *command_server = 0xcc0;
property Font *font = 0x16c; property Font *font = 0x16c;
property ScreenChooser screen_chooser = 0x168;
// Smooth Lighting // Smooth Lighting
static-property bool useAmbientOcclusion = 0x136b90; static-property bool useAmbientOcclusion = 0x136b90;

View File

@ -9,3 +9,4 @@ property int width = 0x14;
property int height = 0x18; property int height = 0x18;
property int x = 0xc; property int x = 0xc;
property int y = 0x10; property int y = 0x10;
property std::string text = 0x1c;

View File

@ -10,7 +10,7 @@ virtual-method void updateEvents() = 0x14;
virtual-method void keyboardNewChar(char key) = 0x70; virtual-method void keyboardNewChar(char key) = 0x70;
virtual-method void keyPressed(int key) = 0x6c; virtual-method void keyPressed(int key) = 0x6c;
virtual-method void render(int x, int y, float param_1) = 0x8; virtual-method void render(int x, int y, float param_1) = 0x8;
virtual-method bool handleBackEvent(bool param_1) = 0x24; virtual-method bool handleBackEvent(bool do_nothing) = 0x24;
virtual-method void tick() = 0x28; virtual-method void tick() = 0x28;
virtual-method void buttonClicked(Button *button) = 0x60; virtual-method void buttonClicked(Button *button) = 0x60;
virtual-method void init() = 0xc; virtual-method void init() = 0xc;

View File

@ -0,0 +1 @@
method void setScreen(uint id) = 0x29490;

View File

@ -1 +1,2 @@
virtual-method void deleteLevel(std::string *level_name) = 0x20; virtual-method void deleteLevel(std::string *level_name) = 0x20;
virtual-method void getLevelList(std::vector<LevelSummary> *level_list) = 0xc;

View File

@ -0,0 +1,7 @@
size 0x14;
property std::string folder = 0x0;
property std::string name = 0x4;
property int seed = 0x8;
property int game_mode = 0xc;
property int param_5 = 0x10;

View File

@ -6,3 +6,4 @@ static-method void sleepMs(int x) = 0x13cf4;
static-method int sdl_key_to_minecraft_key(int sdl_key) = 0x1243c; static-method int sdl_key_to_minecraft_key(int sdl_key) = 0x1243c;
static-method void anGenBuffers(int count, uint *buffers) = 0x5f28c; static-method void anGenBuffers(int count, uint *buffers) = 0x5f28c;
static-method int getTimeMs() = 0x13cd4; static-method int getTimeMs() = 0x13cd4;
static-method int getEpochTimeS() = 0x13d00;

View File

@ -1 +1,2 @@
method void debugFpsMeterKeyPress(int key) = 0x79118; method void debugFpsMeterKeyPress(int key) = 0x79118;
method void renderFpsMeter(float param_1) = 0x79280;

View File

@ -7,4 +7,6 @@ static-property char *options_txt_fopen_mode_when_loading = 0x19d24; // w
static-property char **feedback_vibration_options_txt_name_1 = 0x198a0; // feedback_vibration static-property char **feedback_vibration_options_txt_name_1 = 0x198a0; // feedback_vibration
static-property char **feedback_vibration_options_txt_name_2 = 0x194bc; // feedback_vibration static-property char **feedback_vibration_options_txt_name_2 = 0x194bc; // feedback_vibration
static-property char **gfx_lowquality_options_txt_name = 0x194c4; // gfx_lowquality static-property char **gfx_lowquality_options_txt_name = 0x194c4; // gfx_lowquality
static-property char *classic_create_button_text = 0x39bec; // Create static-property char *classic_create_button_text = 0x39bec; // Create
static-property-array char creative_mode_description = 0x104492; // Unlimited resources and flying
static-property-array char survival_mode_description = 0x104470; // Mobs, health and gather resources

View File

@ -0,0 +1,3 @@
static-method std::string stringTrim(std::string *str) = 0x77c40;
static-method int hashCode(std::string *str) = 0x77a50;
static-method std::string *stringReplace(std::string *str, std::string *what, std::string *with, int param_1) = 0x779f0;

View File

@ -1,3 +1,5 @@
extends Packet; extends Packet;
vtable 0x108a98;
property char *message = 0xc; property char *message = 0xc;