tui: framed welcome banner before username prompt (M7-1)

Replace the four lines of `==` rules + bilingual title with a centred
rounded box so the eye lands on a single visual element instead of two.

The new tui_render_welcome():
- centres horizontally and vertically (about a third of the way down)
- shows TNT + version, then 中文 subtitle, then English caption with
  decreasing visual weight (bold cyan / default / dim grey)
- falls back to a plain "TNT $version — anonymous chat over SSH" line
  when the terminal is too narrow for the frame
- declared in tui.h, called from input.c read_username()

read_username()'s prompt becomes a single short bilingual hint
("请输入用户名 (留空 anonymous): ") that lives below the box.
This commit is contained in:
m1ngsama 2026-05-17 10:48:27 +08:00
parent 7897d8820e
commit 159073ec49
3 changed files with 103 additions and 6 deletions

View file

@ -22,6 +22,11 @@ void tui_render_input(struct client *client, const char *input);
/* Clear the screen */
void tui_clear_screen(struct client *client);
/* Render the pre-login welcome banner. Centered, framed, shown once before
* the username prompt. Caller is responsible for printing the prompt
* itself afterwards. */
void tui_render_welcome(struct client *client);
/* Get help text based on language */
const char* tui_get_help_text(help_lang_t lang);

View file

@ -27,12 +27,8 @@ static int read_username(client_t *client) {
int pos = 0;
char buf[4];
tui_clear_screen(client);
client_printf(client, "================================\r\n");
client_printf(client, " 欢迎来到 TNT 匿名聊天室\r\n");
client_printf(client, " Welcome to TNT Anonymous Chat\r\n");
client_printf(client, "================================\r\n\r\n");
client_printf(client, "请输入用户名 (留空默认为 anonymous): ");
tui_render_welcome(client);
client_printf(client, " 请输入用户名 (留空 anonymous): ");
while (1) {
int n = ssh_channel_read_timeout(client->channel, buf, 1, 0, 60000); /* 60 sec timeout */

View file

@ -118,6 +118,102 @@ void tui_clear_screen(client_t *client) {
client_send(client, clear, strlen(clear));
}
/* Render the pre-login welcome banner.
*
* Centred horizontally; vertically positioned about a third of the way down
* the available height so the user's eye lands naturally on it before the
* prompt below. Uses light box-drawing characters (U+256D / U+2570) so the
* frame matches the rest of the TUI's aesthetic instead of the older ASCII
* `==` rules. */
void tui_render_welcome(client_t *client) {
if (!client || !client->connected) return;
int rw = client->width;
int rh = client->height;
if (rw < 10) rw = 10;
if (rh < 4) rh = 4;
/* Lines, in display order. Width is computed in display columns. */
const char *line1 = "TNT · " TNT_VERSION;
const char *line2 = "匿名聊天室 · SSH";
const char *line3 = "Anonymous chat over SSH";
int inner_w = utf8_string_width(line1);
int w2 = utf8_string_width(line2);
int w3 = utf8_string_width(line3);
if (w2 > inner_w) inner_w = w2;
if (w3 > inner_w) inner_w = w3;
inner_w += 4; /* 2 columns padding on each side */
/* Fall back to plain prompt if the terminal is too narrow for the frame. */
if (inner_w + 2 > rw) {
char fallback[128];
int n = snprintf(fallback, sizeof(fallback),
ANSI_CLEAR ANSI_HOME
"TNT %s — anonymous chat over SSH\r\n\r\n",
TNT_VERSION);
if (n > 0) client_send(client, fallback, (size_t)n);
return;
}
int top_pad = rh / 3;
if (top_pad < 1) top_pad = 1;
int left_pad = (rw - (inner_w + 2)) / 2;
if (left_pad < 0) left_pad = 0;
/* ~5 KiB is plenty for the framed banner even on the largest terminals. */
char buf[4096];
size_t pos = 0;
buffer_appendf(buf, sizeof(buf), &pos, ANSI_CLEAR ANSI_HOME);
for (int i = 0; i < top_pad; i++) {
buffer_appendf(buf, sizeof(buf), &pos, "\r\n");
}
/* Top border: ╭───…───╮ */
for (int i = 0; i < left_pad; i++) buffer_append_bytes(buf, sizeof(buf), &pos, " ", 1);
buffer_append_bytes(buf, sizeof(buf), &pos, "\033[36m", 5);
buffer_append_bytes(buf, sizeof(buf), &pos, "", strlen(""));
for (int i = 0; i < inner_w; i++) buffer_append_bytes(buf, sizeof(buf), &pos, "", strlen(""));
buffer_append_bytes(buf, sizeof(buf), &pos, "", strlen(""));
buffer_append_bytes(buf, sizeof(buf), &pos, "\033[0m", 4);
buffer_appendf(buf, sizeof(buf), &pos, "\r\n");
/* Three content lines with surrounding │ borders, centred inside the frame. */
const char *lines[3] = {line1, line2, line3};
int widths[3] = {utf8_string_width(line1), w2, w3};
const char *line_color[3] = {"\033[1;36m", "\033[0m", "\033[2;37m"};
for (int li = 0; li < 3; li++) {
for (int i = 0; i < left_pad; i++) buffer_append_bytes(buf, sizeof(buf), &pos, " ", 1);
buffer_append_bytes(buf, sizeof(buf), &pos, "\033[36m", 5);
buffer_append_bytes(buf, sizeof(buf), &pos, "", strlen(""));
buffer_append_bytes(buf, sizeof(buf), &pos, "\033[0m", 4);
int pad_total = inner_w - widths[li];
int pad_left = pad_total / 2;
int pad_right = pad_total - pad_left;
for (int i = 0; i < pad_left; i++) buffer_append_bytes(buf, sizeof(buf), &pos, " ", 1);
buffer_appendf(buf, sizeof(buf), &pos, "%s%s\033[0m", line_color[li], lines[li]);
for (int i = 0; i < pad_right; i++) buffer_append_bytes(buf, sizeof(buf), &pos, " ", 1);
buffer_append_bytes(buf, sizeof(buf), &pos, "\033[36m", 5);
buffer_append_bytes(buf, sizeof(buf), &pos, "", strlen(""));
buffer_append_bytes(buf, sizeof(buf), &pos, "\033[0m", 4);
buffer_appendf(buf, sizeof(buf), &pos, "\r\n");
}
/* Bottom border: ╰───…───╯ */
for (int i = 0; i < left_pad; i++) buffer_append_bytes(buf, sizeof(buf), &pos, " ", 1);
buffer_append_bytes(buf, sizeof(buf), &pos, "\033[36m", 5);
buffer_append_bytes(buf, sizeof(buf), &pos, "", strlen(""));
for (int i = 0; i < inner_w; i++) buffer_append_bytes(buf, sizeof(buf), &pos, "", strlen(""));
buffer_append_bytes(buf, sizeof(buf), &pos, "", strlen(""));
buffer_append_bytes(buf, sizeof(buf), &pos, "\033[0m", 4);
buffer_appendf(buf, sizeof(buf), &pos, "\r\n\r\n");
client_send(client, buf, pos);
}
/* Render the main screen */
void tui_render_screen(client_t *client) {
if (!client || !client->connected) return;