diff --git a/dependencies/tim b/dependencies/tim index d417b5b..3fb220f 160000 --- a/dependencies/tim +++ b/dependencies/tim @@ -1 +1 @@ -Subproject commit d417b5bbd5dc387b25a115e247c9c3a451d830f9 +Subproject commit 3fb220ff5448e04b5ea24f80d940ed95b710afce diff --git a/src/cli/ClientCLI/ClientCLI.c b/src/cli/ClientCLI/ClientCLI.c index 18d778d..3c46761 100644 --- a/src/cli/ClientCLI/ClientCLI.c +++ b/src/cli/ClientCLI/ClientCLI.c @@ -18,11 +18,11 @@ static const str farewell_art = STR( "\\(_,J J L l`,)/\n" ); -#define FPS 30 #define is_alias(LITERAL) str_equals(command, STR(LITERAL)) -static Result(void) ClientCLI_askUserNameAndPassword(str* username_out, str* password_out); +Result(bool) start_screen(Array(char) username_buf, Array(char) password_buf, + str* out_username, str* out_password); static Result(void) ClientCLI_openUserDB(ClientCLI* self); static Result(void) ClientCLI_execCommand(ClientCLI* self, str command, bool* stop); static Result(SavedServer*) ClientCLI_joinNewServer(ClientCLI* self); @@ -48,48 +48,29 @@ void ClientCLI_construct(ClientCLI* self){ } Result(void) ClientCLI_run(ClientCLI* self) { - Deferral(FPS); + Deferral(32); - TimEditState e; - TimEditState_init(&e, 32, "Greetings!"); - bool edit_enabled = false; - while(tim_run(30)){ - TimStyle c = { .brd = TimColor16_White, .bg = TimColor16_DarkBlue, .fg = TimColor16_White }; - tim_frame(0, 0, ~0, ~0, c); - - tim_label(e.s, A, 2, A, A, c); - - if(edit_enabled){ - TimKey key = tim_edit(&e, A, 5, tim->scopes[tim->scope].w - 4, c); - - if(key == TimKey_Escape || key == TimKey_Enter) - edit_enabled = false; - } - else { - if(tim_button("[Enter] Edit text", A, 5, tim->scopes[tim->scope].w - 4, A, c) || tim_is_key_press(TimKey_Enter)){ - edit_enabled = true; - tim->focus = &e; - tim->event.type = TimEvent_Void; // consume key event - } - } - - if(tim_button("[Q] Quit", A, ~1, 10, A, c) || tim_is_key_press('q')) - exit(0); + // ask username and password + Array(char) username_buf = Array_char_alloc(USERNAME_SIZE_MAX + 1); + Array(char) password_buf = Array_char_alloc(PASSWORD_SIZE_MAX + 1); + Defer( + Array_char_destroy(&username_buf); + Array_char_destroy(&password_buf); + ); + str username = str_null, password = str_null; + try(bool start, i, start_screen(username_buf, password_buf, &username, &password)); + if(!start){ + Return RESULT_VOID; } + tim_reset_terminal(); term_clear(); printf(FMT_str"\n", greeting_art.len, greeting_art.data); - // create Client - str username = str_null, password = str_null; - try_void(ClientCLI_askUserNameAndPassword(&username, &password)); - Defer( - str_destroy(username); - str_destroy(password); - ); - Client_free(self->client); + // create client try(self->client, p, Client_create(username, password)); - memset(password.data, 0, password.len); + // erase password from memory + Array_char_memset(&password_buf, 0); // init db try_void(ClientCLI_openUserDB(self)); @@ -120,49 +101,6 @@ Result(void) ClientCLI_run(ClientCLI* self) { Return RESULT_VOID; } -static Result(void) ClientCLI_askUserNameAndPassword(str* username_out, str* password_out){ - Deferral(8); - bool success = false; - - // ask username - Array(char) username_buf = Array_char_alloc(128); - Defer(if(!success) Array_char_destroy(&username_buf)); - str username = str_null; - while(true) { - printf("username: "); - try_void(term_readLine(username_buf.data, username_buf.len)); - username = str_from_cstr(username_buf.data); - str_trim(&username, true); - str name_error_str = validateUsername_str(username); - if(name_error_str.data){ - printf("ERROR: "FMT_str"\n", - name_error_str.len, name_error_str.data); - } - else break; - } - - // ask password - Array(char) password_buf = Array_char_alloc(128); - Defer(if(!success) Array_char_destroy(&password_buf)); - str password = str_null; - while(true) { - printf("password: "); - // TODO: hide password - try_void(term_readLineHidden(password_buf.data, password_buf.len)); - password = str_from_cstr(password_buf.data); - str_trim(&password, true); - if(password.len < PASSWORD_SIZE_MIN || password.len > PASSWORD_SIZE_MAX){ - printf("ERROR: password length (in bytes) must be >= %i and <= %i\n", - PASSWORD_SIZE_MIN, PASSWORD_SIZE_MAX); - } - else break; - } - - *username_out = username; - *password_out = password; - success = true; - Return RESULT_VOID; -} static Result(void) ClientCLI_openUserDB(ClientCLI* self){ Deferral(8); diff --git a/src/cli/ClientCLI/ClientCLI.h b/src/cli/ClientCLI/ClientCLI.h index c8c0e7f..682bac1 100644 --- a/src/cli/ClientCLI/ClientCLI.h +++ b/src/cli/ClientCLI/ClientCLI.h @@ -5,6 +5,8 @@ #include "tlibc/collections/List.h" #include "db/client_db.h" +#define FPS 30 + typedef struct ClientCLI { Client* client; tsqlite_connection* db; diff --git a/src/cli/ClientCLI/start_screen.c b/src/cli/ClientCLI/start_screen.c new file mode 100644 index 0000000..f418ba6 --- /dev/null +++ b/src/cli/ClientCLI/start_screen.c @@ -0,0 +1,138 @@ +#include "ClientCLI.h" +#include "network/tcp-chat-protocol/v1.h" +#include "tim.h" + + +typedef struct TextInputState { + TimEditState edit; + cstr label; + TimStyle style_default; + TimStyle style_focused; + TimKey result_key; +} TextInputState; + +void draw_item_text_input(bool is_selected, TimRect place, void* data){ + TextInputState* ctx = data; + TimStyle style = is_selected ? ctx->style_focused : ctx->style_default; + ctx->result_key = tim_edit(&ctx->edit, place.x, place.y, place.w, style); + tim_label(ctx->label, place.x + 3, place.y, A, 1, style); +} + +typedef struct StartScreenContext { + TimStyle style_default; + TimStyle style_focused; + TimStyle style_error; + TextInputState input_username; + TextInputState input_password; + bool exit; + bool start; + char* err_msg; // heap only +} StartScreenContext; + +void draw_start_screen_buttons(bool is_selected, TimRect place, void* data){ + StartScreenContext* ctx = data; + TimStyle style = is_selected ? ctx->style_focused : ctx->style_default; + i32 start_w = place.w / 2; + i32 dist = 1; + i32 quit_x = place.x + start_w + dist; + i32 quit_w = place.w - start_w - dist; + if(tim_button("[Enter] Start", place.x, place.y, start_w, A, style) + || tim_is_key_press(TimKey_Enter)) + { + ctx->start = true; + } + else if(tim_button("[Esc/Q] Quit", quit_x, place.y, quit_w, A, ctx->style_default) + || tim_is_key_press('q')) + { + ctx->exit = true; + } +} + +Result(bool) start_screen(Array(char) username_buf, Array(char) password_buf, + str* out_username, str* out_password) +{ + Deferral(16); + + bool result = false; + + StartScreenContext ctx = { + .style_default = { .brd = TimColor16_Gray, .bg = TimColor16_DarkBlue, .fg = TimColor16_Gray }, + .style_focused = { .brd = TimColor16_White, .bg = TimColor16_DarkCyan, .fg = TimColor16_White }, + .style_error = { .brd = TimColor16_Gray, .bg = TimColor16_DarkRed, .fg = TimColor16_White }, + }; + + TimEditState_construct(&ctx.input_username.edit, username_buf.data, username_buf.len, NULL); + ctx.input_username.label = "[username]"; + ctx.input_username.style_default = ctx.style_default; + ctx.input_username.style_focused = ctx.style_focused; + + TimEditState_construct(&ctx.input_password.edit, password_buf.data, password_buf.len, NULL); + ctx.input_password.edit.masked = true; + ctx.input_password.label = "[password]"; + ctx.input_password.style_default = ctx.style_default; + ctx.input_password.style_focused = ctx.style_focused; + + TimScrollItem items[] = { + // username input + { .data = &ctx.input_username, .focus_target = &ctx.input_username.edit, .h = 3, .draw = draw_item_text_input }, + // password input + { .data = &ctx.input_password, .focus_target = &ctx.input_password.edit, .h = 3, .draw = draw_item_text_input }, + // buttons + { .data = &ctx, .focus_target = NULL, .h = 3, .draw = draw_start_screen_buttons }, + }; + + TimScrollState scroll_list = { .items = items, .len = ARRAY_LEN(items) }; + + while(tim_run(FPS)){ + tim_fill(tim_cell(" ", ctx.style_default.fg, ctx.style_default.bg), 0, 0, A, A); + tim_scope(A, A, 40, 14) { + tim_scroll(&scroll_list, 1, 1, ~1, ~1, ctx.style_default); + } + + if(ctx.err_msg){ + i32 below_scroll = tim->scopes[tim->scope].h/2 + 7; + tim_label("ERROR: ", A, below_scroll, A, A, ctx.style_error); + tim_label(ctx.err_msg, A, below_scroll + 1, A, A, ctx.style_error); + } + + if(ctx.exit + || ctx.input_username.result_key == TimKey_Escape + || ctx.input_password.result_key == TimKey_Escape) + { + result = false; + break; + } + if(ctx.start){ + ctx.start = false; + free(ctx.err_msg); + ctx.err_msg = NULL; + + // check username + str username = str_from_cstr(username_buf.data); + str_trim(&username, true); + str name_error_str = validateUsername_str(username); + if(name_error_str.data){ + ctx.err_msg = name_error_str.data; + continue; + } + + // check password + str password = str_from_cstr(password_buf.data); + str_trim(&password, true); + if(password.len < PASSWORD_SIZE_MIN || password.len > PASSWORD_SIZE_MAX){ + ctx.err_msg = sprintf_malloc( + "password length (in bytes) must be >= %i and <= %i", + PASSWORD_SIZE_MIN, PASSWORD_SIZE_MAX + ); + continue; + } + + *out_username = username; + *out_password = password; + result = true; + break; + } + } + + Return RESULT_VALUE(i, result); +} \ No newline at end of file