From 27c8d0f3cef19241d395c512dd4ae108c7475739 Mon Sep 17 00:00:00 2001 From: m1ngsama Date: Wed, 19 Nov 2025 17:27:46 +0800 Subject: [PATCH] feat: Add ASCII art and ANSI shadow effects to TUI --- src/main.cpp | 2 + src/tui_view.cpp | 189 ++++++++++++++++++++++++++++++++++++++++++++--- src/tui_view.h | 3 + 3 files changed, 182 insertions(+), 12 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 9457209..842c2d0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,8 @@ #include "calendar.h" int main() { + display_splash_screen(); // Display splash screen at startup + while (true) { int choice = run_portal_tui(); diff --git a/src/tui_view.cpp b/src/tui_view.cpp index c3729d2..296b31f 100644 --- a/src/tui_view.cpp +++ b/src/tui_view.cpp @@ -7,9 +7,29 @@ #include #include #include +#include // Added this line namespace { +// Define color pairs +enum ColorPairs { + NORMAL_TEXT = 1, + SHADOW_TEXT, + BANNER_TEXT, + SELECTED_ITEM +}; + +void init_colors() { + if (has_colors()) { + start_color(); + use_default_colors(); // Use terminal's default background + init_pair(NORMAL_TEXT, COLOR_WHITE, -1); // White foreground, default background + init_pair(SHADOW_TEXT, COLOR_BLACK, -1); // Black foreground, default background (for shadow effect) + init_pair(BANNER_TEXT, COLOR_CYAN, -1); // Cyan foreground, default background + init_pair(SELECTED_ITEM, COLOR_YELLOW, -1); // Yellow foreground, default background + } +} + std::string format_date(const std::chrono::system_clock::time_point &tp) { auto tt = std::chrono::system_clock::to_time_t(tp); std::tm tm{}; @@ -30,6 +50,7 @@ void run_tui(const std::vector &events) { setlocale(LC_ALL, ""); initscr(); + init_colors(); // Initialize colors cbreak(); noecho(); keypad(stdscr, TRUE); @@ -41,7 +62,9 @@ void run_tui(const std::vector &events) { // 如果没有任何事件,给出提示信息 if (events.empty()) { clear(); + attron(COLOR_PAIR(BANNER_TEXT)); mvprintw(0, 0, "NBTCA 未来一个月活动"); + attroff(COLOR_PAIR(BANNER_TEXT)); mvprintw(2, 0, "未来一个月内暂无活动。"); mvprintw(4, 0, "按任意键退出..."); refresh(); @@ -55,9 +78,44 @@ void run_tui(const std::vector &events) { while (true) { clear(); - mvprintw(0, 0, "NBTCA 未来一个月活动 (q 退出, ↑↓ 滚动)"); - int visibleLines = height - 2; + // ASCII art banner for "CALENDAR" + std::string calendar_banner[] = { + " ██████╗ █████╗ ██╗ ███████╗███╗ ██╗██████╗ █████╗ ██████╗ ", + "██╔════╝██╔══██╗██║ ██╔════╝████╗ ██║██╔══██╗██╔══██╗██╔══██╗", + "██║ ███████║██║ █████╗ ██╔██╗ ██║██║ ██║███████║██████╔╝", + "██║ ██╔══██║██║ ██╔══╝ ██║╚██╗██║██║ ██║██╔══██║██╔══██╗", + "╚██████╗██║ ██║███████╗███████╗██║ ╚████║██████╔╝██║ ██║██║ ██║", + " ╚═════╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═══╝╚═════╝ ╚═╝ ╚═╝╚╝ ╚═╝" + }; + + int banner_height = sizeof(calendar_banner) / sizeof(calendar_banner[0]); + int banner_width = calendar_banner[0].length(); + + int start_col_banner = (width - banner_width) / 2; + if (start_col_banner < 0) start_col_banner = 0; + + // Draw shadow + attron(COLOR_PAIR(SHADOW_TEXT)); + for (int i = 0; i < banner_height; ++i) { + mvprintw(i + 1, start_col_banner + 1, "%s", calendar_banner[i].c_str()); + } + attroff(COLOR_PAIR(SHADOW_TEXT)); + + // Draw main banner + attron(COLOR_PAIR(BANNER_TEXT)); + for (int i = 0; i < banner_height; ++i) { + mvprintw(i, start_col_banner, "%s", calendar_banner[i].c_str()); + } + attroff(COLOR_PAIR(BANNER_TEXT)); + + attron(COLOR_PAIR(NORMAL_TEXT)); + std::string instruction_msg = "(q 退出, ↑↓ 滚动)"; + mvprintw(banner_height + 1, (width - instruction_msg.length()) / 2, "%s", instruction_msg.c_str()); + attroff(COLOR_PAIR(NORMAL_TEXT)); + + int start_event_row = banner_height + 3; // Start events below the banner and instruction + int visibleLines = height - start_event_row - 1; // Adjust visible lines if (selected < top) { top = selected; @@ -80,11 +138,19 @@ void run_tui(const std::vector &events) { } if (idx == selected) { - attron(A_REVERSE); - mvprintw(i + 1, 0, "%s", line.c_str()); - attroff(A_REVERSE); + attron(A_REVERSE | COLOR_PAIR(SELECTED_ITEM)); + mvprintw(start_event_row + i, 0, "%s", line.c_str()); + attroff(A_REVERSE | COLOR_PAIR(SELECTED_ITEM)); } else { - mvprintw(i + 1, 0, "%s", line.c_str()); + attron(COLOR_PAIR(NORMAL_TEXT)); + mvprintw(start_event_row + i, 0, "%s", line.c_str()); + attroff(COLOR_PAIR(NORMAL_TEXT)); + } + // Add a separator after each event + if (i < visibleLines - 1 && idx + 1 < static_cast(events.size())) { + attron(COLOR_PAIR(SHADOW_TEXT)); + mvprintw(start_event_row + i + 1, 0, "%s", std::string(width, '-').c_str()); // Dynamic separator + attroff(COLOR_PAIR(SHADOW_TEXT)); } } @@ -107,27 +173,68 @@ int run_portal_tui() { setlocale(LC_ALL, ""); initscr(); + init_colors(); // Initialize colors cbreak(); noecho(); keypad(stdscr, TRUE); curs_set(0); + int height, width; + getmaxyx(stdscr, height, width); + std::vector menu_items = {"Calendar", "Exit"}; int selected = 0; int choice = -1; while (choice == -1) { clear(); - mvprintw(0, 0, "TUT - Feature Portal (q or Enter to select)"); + + // ASCII art banner for "NBTCA Tools" + std::string banner_art[] = { + "███╗ ██╗██████╗ ████████╗ ██████╗ █████╗ ████████╗ ██████╗ ██████╗ ██╗ ███████╗", + "████╗ ██║██╔══██╗╚══██╔══╝██╔════╝██╔══██╗ ╚══██╔══╝██╔═══██╗██╔═══██╗██║ ██╔════╝", + "██╔██╗ ██║██████╔╝ ██║ ██║ ███████║ ██║ ██║ ██║██║ ██║██║ ███████╗", + "██║╚██╗██║██╔══██╗ ██║ ██║ ██╔══██║ ██║ ██║ ██║██║ ██║██║ ╚════██║", + "██║ ╚████║██████╔╝ ██║ ╚██████╗██║ ██║ ██║ ╚██████╔╝╚██████╔╝███████╗███████║", + "╚═╝ ╚═══╝╚═════╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝" + }; + + int banner_height = sizeof(banner_art) / sizeof(banner_art[0]); + int banner_width = banner_art[0].length(); + + int start_col_banner = (width - banner_width) / 2; + if (start_col_banner < 0) start_col_banner = 0; + + // Draw shadow + attron(COLOR_PAIR(SHADOW_TEXT)); + for (int i = 0; i < banner_height; ++i) { + mvprintw(i + 1, start_col_banner + 1, "%s", banner_art[i].c_str()); + } + attroff(COLOR_PAIR(SHADOW_TEXT)); + + // Draw main banner + attron(COLOR_PAIR(BANNER_TEXT)); + for (int i = 0; i < banner_height; ++i) { + mvprintw(i, start_col_banner, "%s", banner_art[i].c_str()); + } + attroff(COLOR_PAIR(BANNER_TEXT)); + + attron(COLOR_PAIR(NORMAL_TEXT)); + // Feature Portal message below the banner + std::string portal_msg = "Feature Portal (q or Enter to select)"; + mvprintw(banner_height + 1, (width - portal_msg.length()) / 2, "%s", portal_msg.c_str()); + attroff(COLOR_PAIR(NORMAL_TEXT)); for (size_t i = 0; i < menu_items.size(); ++i) { if ((int)i == selected) { - attron(A_REVERSE); - } - mvprintw(i + 2, 2, "%s", menu_items[i].c_str()); - if ((int)i == selected) { - attroff(A_REVERSE); + attron(A_REVERSE | COLOR_PAIR(SELECTED_ITEM)); + } else { + attron(COLOR_PAIR(NORMAL_TEXT)); } + // Adjust vertical position based on banner height and portal message + mvprintw(banner_height + 3 + i, 2, "%s", menu_items[i].c_str()); + attroff(A_REVERSE | COLOR_PAIR(SELECTED_ITEM)); + attroff(COLOR_PAIR(NORMAL_TEXT)); } refresh(); @@ -160,4 +267,62 @@ int run_portal_tui() { return choice; } +// 显示启动画面 +void display_splash_screen() { + setlocale(LC_ALL, ""); + initscr(); + init_colors(); // Initialize colors + cbreak(); + noecho(); + curs_set(0); + + int height, width; + getmaxyx(stdscr, height, width); + + clear(); + + // Simple ASCII art for "NBTCA Tools" + std::string splash_art[] = { + "███╗ ██╗██████╗ ████████╗ ██████╗ █████╗ ████████╗ ██████╗ ██████╗ ██╗ ███████╗", + "████╗ ██║██╔══██╗╚══██╔══╝██╔════╝██╔══██╗ ╚══██╔══╝██╔═══██╗██╔═══██╗██║ ██╔════╝", + "██╔██╗ ██║██████╔╝ ██║ ██║ ███████║ ██║ ██║ ██║██║ ██║██║ ███████╗", + "██║╚██╗██║██╔══██╗ ██║ ██║ ██╔══██║ ██║ ██║ ██║██║ ██║██║ ╚════██║", + "██║ ╚████║██████╔╝ ██║ ╚██████╗██║ ██║ ██║ ╚██████╔╝╚██████╔╝███████╗███████║", + "╚═╝ ╚═══╝╚═════╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝" + }; + + int art_height = sizeof(splash_art) / sizeof(splash_art[0]); + int art_width = splash_art[0].length(); + + int start_row = (height - art_height) / 2; + int start_col = (width - art_width) / 2; + + if (start_row < 0) start_row = 0; + if (start_col < 0) start_col = 0; + + // Draw shadow + attron(COLOR_PAIR(SHADOW_TEXT)); + for (int i = 0; i < art_height; ++i) { + mvprintw(start_row + i + 1, start_col + 1, "%s", splash_art[i].c_str()); + } + attroff(COLOR_PAIR(SHADOW_TEXT)); + + // Draw main art + attron(COLOR_PAIR(BANNER_TEXT)); + for (int i = 0; i < art_height; ++i) { + mvprintw(start_row + i, start_col, "%s", splash_art[i].c_str()); + } + attroff(COLOR_PAIR(BANNER_TEXT)); + + attron(COLOR_PAIR(NORMAL_TEXT)); + std::string loading_msg = "Loading..."; + mvprintw(start_row + art_height + 2, (width - loading_msg.length()) / 2, "%s", loading_msg.c_str()); + attroff(COLOR_PAIR(NORMAL_TEXT)); + + refresh(); + std::this_thread::sleep_for(std::chrono::seconds(2)); // Display for 2 seconds + + endwin(); +} + diff --git a/src/tui_view.h b/src/tui_view.h index 306b1fe..ec95b6f 100644 --- a/src/tui_view.h +++ b/src/tui_view.h @@ -10,3 +10,6 @@ void run_tui(const std::vector &events); // 运行 ncurses TUI for the portal int run_portal_tui(); +// 显示启动画面 +void display_splash_screen(); +