From d7ff90d0724eada5671759f37a5b1104ae21688d Mon Sep 17 00:00:00 2001 From: Jannis Date: Sun, 7 Jun 2026 18:02:33 +0200 Subject: [PATCH] Rework application to support GTK4 GUI by default and TUI via -t flag --- CMakeLists.txt | 10 +- gui.c | 482 +++++++++++++++++++++++++++++++++++++++++++++++++ gui.h | 6 + main.c | 183 +++++++++++-------- 4 files changed, 599 insertions(+), 82 deletions(-) create mode 100644 gui.c create mode 100644 gui.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ce2473..45fe6cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,12 @@ cmake_minimum_required(VERSION 3.10.0) project(ScreamIntoTheVoid VERSION 0.1.0 LANGUAGES C) -add_executable(ScreamIntoTheVoid draw.c main.c - main.h) +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK4 REQUIRED IMPORTED_TARGET gtk4) + +add_executable(ScreamIntoTheVoid draw.c main.c gui.c + main.h draw.h gui.h) + +target_link_libraries(ScreamIntoTheVoid PRIVATE PkgConfig::GTK4) + diff --git a/gui.c b/gui.c new file mode 100644 index 0000000..c5569cb --- /dev/null +++ b/gui.c @@ -0,0 +1,482 @@ +#include +#include +#include +#include +#include "gui.h" + +// Define CSS data for the application's look and feel +static const char* css_data = +"window {\n" +" background-color: #0b090a;\n" +" color: #f5f3f4;\n" +" font-family: 'Inter', 'Segoe UI', sans-serif;\n" +"}\n" +"\n" +".title-label {\n" +" font-size: 28px;\n" +" font-weight: 800;\n" +" color: #f5f3f4;\n" +" margin-top: 15px;\n" +" margin-bottom: 5px;\n" +" letter-spacing: 2px;\n" +"}\n" +"\n" +".subtitle-label {\n" +" font-size: 14px;\n" +" color: #b1a7a6;\n" +" margin-bottom: 15px;\n" +"}\n" +"\n" +".worry-text-view {\n" +" font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;\n" +" font-size: 16px;\n" +" background-color: #161a1d;\n" +" color: #f5f3f4;\n" +" border: 2px solid #3f3f46;\n" +" border-radius: 8px;\n" +" padding: 15px;\n" +"}\n" +"\n" +".worry-text-view:focus {\n" +" border-color: #7209b7;\n" +"}\n" +"\n" +".action-button {\n" +" font-size: 14px;\n" +" font-weight: 600;\n" +" padding: 12px 24px;\n" +" border-radius: 8px;\n" +" border: none;\n" +" color: white;\n" +" margin-bottom: 10px;\n" +"}\n" +"\n" +".btn-scream {\n" +" background: #7209b7;\n" +" box-shadow: 0 4px 12px rgba(114, 9, 183, 0.3);\n" +"}\n" +"\n" +".btn-scream:hover {\n" +" background: #8f20d8;\n" +"}\n" +"\n" +".btn-burn {\n" +" background: #d90429;\n" +" box-shadow: 0 4px 12px rgba(217, 4, 41, 0.3);\n" +"}\n" +"\n" +".btn-burn:hover {\n" +" background: #ef233c;\n" +"}\n" +"\n" +".btn-dissolve {\n" +" background: #06d6a0;\n" +" color: #0b090a;\n" +" box-shadow: 0 4px 12px rgba(6, 214, 160, 0.3);\n" +"}\n" +"\n" +".btn-dissolve:hover {\n" +" background: #1ae8b2;\n" +"}\n" +"\n" +".text-scream {\n" +" color: #b388ff;\n" +"}\n" +"\n" +".text-burn {\n" +" color: #ff9100;\n" +"}\n" +"\n" +".text-dissolve {\n" +" color: #00e5ff;\n" +"}\n" +"\n" +".consumed-label {\n" +" font-size: 24px;\n" +" font-weight: 700;\n" +" color: #f5f3f4;\n" +" margin-bottom: 25px;\n" +"}\n" +"\n" +".btn-reset {\n" +" background-color: #3f3f46;\n" +" color: white;\n" +" padding: 10px 20px;\n" +" border-radius: 6px;\n" +" border: 1px solid #52525b;\n" +"}\n" +"\n" +".btn-reset:hover {\n" +" background-color: #52525b;\n" +"}\n" +; + +typedef enum { + ANIM_SCREAM, + ANIM_BURN, + ANIM_DISSOLVE +} AnimType; + +typedef struct { + GtkWidget *stack; + GtkWidget *text_view; // Input text view + GtkWidget *anim_text_view; // Animation text view (read-only) + GtkWidget *anim_title; // Title during animation + GtkWidget *consumed_label; // Label on the consumed page + + AnimType type; + char *original_text; + char *animated_text; + int step; + int max_steps; + guint timer_id; +} AppState; + +static AppState app_state = {NULL, NULL, NULL, NULL, NULL, ANIM_SCREAM, NULL, NULL, 0, 0, 0}; + +// Forward declarations +static gboolean anim_timer_tick(gpointer user_data); +static void start_animation(AnimType type, const char* title, int max_steps, int interval_ms); +static void on_btn_scream_clicked(GtkButton *btn, gpointer user_data); +static void on_btn_burn_clicked(GtkButton *btn, gpointer user_data); +static void on_btn_dissolve_clicked(GtkButton *btn, gpointer user_data); +static void on_btn_reset_clicked(GtkButton *btn, gpointer user_data); + +static gboolean anim_timer_tick(gpointer user_data) { + AppState *state = (AppState*)user_data; + if (state->animated_text == NULL) { + state->timer_id = 0; + return FALSE; + } + + int len = strlen(state->animated_text); + + if (state->type == ANIM_SCREAM) { + for (int i = 0; i < len; i++) { + if (state->animated_text[i] != ' ' && state->animated_text[i] != '\n' && state->animated_text[i] != '.' && (rand() % 100) < (state->step * 7)) { + state->animated_text[i] = (rand() % 2 == 0) ? '.' : ' '; + } + } + } else if (state->type == ANIM_BURN) { + const char* fire_chars = "@#%*+=-:. "; + int fire_len = strlen(fire_chars); + for (int i = 0; i < len; i++) { + if (state->animated_text[i] != ' ' && state->animated_text[i] != '\n') { + int r = rand() % 100; + if (r < (state->step * 10)) { + int char_idx = (state->step / 3) + (rand() % 3); + if (char_idx >= fire_len) state->animated_text[i] = ' '; + else state->animated_text[i] = fire_chars[char_idx]; + } + } + } + } else if (state->type == ANIM_DISSOLVE) { + for (int i = 0; i < len; i++) { + if (state->animated_text[i] != ' ' && state->animated_text[i] != '\n') { + if ((rand() % 100) < (state->step * 5)) { + state->animated_text[i] = (rand() % 5 == 0) ? '~' : (rand() % 2 == 0 ? ',' : ' '); + } + } + } + } + + // Update the animation buffer + GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(state->anim_text_view)); + gtk_text_buffer_set_text(buf, state->animated_text, -1); + + state->step++; + if (state->step > state->max_steps) { + state->timer_id = 0; + + // Transition to consumed page + gtk_stack_set_visible_child_name(GTK_STACK(state->stack), "consumed"); + + if (state->type == ANIM_SCREAM) { + gtk_label_set_text(GTK_LABEL(state->consumed_label), "The void has consumed your worries."); + } else if (state->type == ANIM_BURN) { + gtk_label_set_text(GTK_LABEL(state->consumed_label), "Ashes to ashes. Dust to dust."); + } else { + gtk_label_set_text(GTK_LABEL(state->consumed_label), "Dissolved into nothingness."); + } + + if (state->original_text) { + g_free(state->original_text); + state->original_text = NULL; + } + if (state->animated_text) { + g_free(state->animated_text); + state->animated_text = NULL; + } + + return FALSE; // Stop the timer + } + + return TRUE; // Continue the timer +} + +static void start_animation(AnimType type, const char* title, int max_steps, int interval_ms) { + GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(app_state.text_view)); + GtkTextIter start, end; + gtk_text_buffer_get_bounds(buffer, &start, &end); + char *text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); + + if (text == NULL || strlen(text) == 0) { + g_free(text); + return; + } + + // Clean up any old timer or strings + if (app_state.timer_id != 0) { + g_source_remove(app_state.timer_id); + app_state.timer_id = 0; + } + if (app_state.original_text) { + g_free(app_state.original_text); + app_state.original_text = NULL; + } + if (app_state.animated_text) { + g_free(app_state.animated_text); + app_state.animated_text = NULL; + } + + app_state.type = type; + app_state.original_text = g_strdup(text); + app_state.animated_text = g_strdup(text); + app_state.step = 0; + app_state.max_steps = max_steps; + + g_free(text); + + // Set up css classes for anim text view + gtk_widget_remove_css_class(app_state.anim_text_view, "text-scream"); + gtk_widget_remove_css_class(app_state.anim_text_view, "text-burn"); + gtk_widget_remove_css_class(app_state.anim_text_view, "text-dissolve"); + + if (type == ANIM_SCREAM) { + gtk_widget_add_css_class(app_state.anim_text_view, "text-scream"); + } else if (type == ANIM_BURN) { + gtk_widget_add_css_class(app_state.anim_text_view, "text-burn"); + } else { + gtk_widget_add_css_class(app_state.anim_text_view, "text-dissolve"); + } + + gtk_label_set_text(GTK_LABEL(app_state.anim_title), title); + + // Set initial text on anim view + GtkTextBuffer *anim_buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(app_state.anim_text_view)); + gtk_text_buffer_set_text(anim_buf, app_state.animated_text, -1); + + // Transition to animation screen + gtk_stack_set_visible_child_name(GTK_STACK(app_state.stack), "anim"); + + // Start timer + app_state.timer_id = g_timeout_add(interval_ms, anim_timer_tick, &app_state); +} + +static void on_btn_scream_clicked(GtkButton *btn, gpointer user_data) { + (void)btn; + (void)user_data; + start_animation(ANIM_SCREAM, "SCREAMING INTO THE VOID...", 20, 150); +} + +static void on_btn_burn_clicked(GtkButton *btn, gpointer user_data) { + (void)btn; + (void)user_data; + start_animation(ANIM_BURN, "LETTING IT BURN...", 25, 100); +} + +static void on_btn_dissolve_clicked(GtkButton *btn, gpointer user_data) { + (void)btn; + (void)user_data; + start_animation(ANIM_DISSOLVE, "LETTING IT DISSOLVE...", 30, 100); +} + +static void on_btn_reset_clicked(GtkButton *btn, gpointer user_data) { + (void)btn; + (void)user_data; + GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(app_state.text_view)); + gtk_text_buffer_set_text(buf, "", -1); + gtk_stack_set_visible_child_name(GTK_STACK(app_state.stack), "input"); + gtk_widget_grab_focus(app_state.text_view); +} + +static void activate(GtkApplication* app, gpointer user_data) { + const char* initial_text = (const char*)user_data; + + // Create main window + GtkWidget *window = gtk_application_window_new(app); + gtk_window_set_title(GTK_WINDOW(window), "Scream Into The Void"); + gtk_window_set_default_size(GTK_WINDOW(window), 800, 600); + + // Apply CSS styling + GtkCssProvider *provider = gtk_css_provider_new(); + gtk_css_provider_load_from_string(provider, css_data); + gtk_style_context_add_provider_for_display( + gdk_display_get_default(), + GTK_STYLE_PROVIDER(provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + ); + g_object_unref(provider); + + // Main layout container: Stack + app_state.stack = gtk_stack_new(); + gtk_stack_set_transition_type(GTK_STACK(app_state.stack), GTK_STACK_TRANSITION_TYPE_CROSSFADE); + gtk_stack_set_transition_duration(GTK_STACK(app_state.stack), 400); + gtk_window_set_child(GTK_WINDOW(window), app_state.stack); + + // ---------------------------------------------------- + // PAGE 1: Input Page + // ---------------------------------------------------- + GtkWidget *input_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); + gtk_widget_set_margin_start(input_page, 40); + gtk_widget_set_margin_end(input_page, 40); + gtk_widget_set_margin_top(input_page, 20); + gtk_widget_set_margin_bottom(input_page, 30); + + GtkWidget *title_label = gtk_label_new("SCREAM INTO THE VOID"); + gtk_widget_add_css_class(title_label, "title-label"); + gtk_box_append(GTK_BOX(input_page), title_label); + + GtkWidget *subtitle_label = gtk_label_new("What's weighing on your soul?"); + gtk_widget_add_css_class(subtitle_label, "subtitle-label"); + gtk_box_append(GTK_BOX(input_page), subtitle_label); + + // Scrolled text area for input + GtkWidget *scrolled_window = gtk_scrolled_window_new(); + gtk_widget_set_vexpand(scrolled_window, TRUE); + gtk_widget_set_hexpand(scrolled_window, TRUE); + gtk_widget_set_margin_top(scrolled_window, 10); + gtk_widget_set_margin_bottom(scrolled_window, 15); + + app_state.text_view = gtk_text_view_new(); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(app_state.text_view), GTK_WRAP_WORD_CHAR); + gtk_widget_add_css_class(app_state.text_view, "worry-text-view"); + + // Set initial text if any (e.g. read from file) + if (initial_text && strlen(initial_text) > 0) { + GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(app_state.text_view)); + gtk_text_buffer_set_text(buf, initial_text, -1); + } + + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window), app_state.text_view); + gtk_box_append(GTK_BOX(input_page), scrolled_window); + + // Action buttons container + GtkWidget *btn_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 20); + gtk_widget_set_halign(btn_box, GTK_ALIGN_CENTER); + + GtkWidget *btn_scream = gtk_button_new_with_label("Scream it into the void"); + gtk_widget_add_css_class(btn_scream, "action-button"); + gtk_widget_add_css_class(btn_scream, "btn-scream"); + g_signal_connect(btn_scream, "clicked", G_CALLBACK(on_btn_scream_clicked), NULL); + + GtkWidget *btn_burn = gtk_button_new_with_label("Let it burn"); + gtk_widget_add_css_class(btn_burn, "action-button"); + gtk_widget_add_css_class(btn_burn, "btn-burn"); + g_signal_connect(btn_burn, "clicked", G_CALLBACK(on_btn_burn_clicked), NULL); + + GtkWidget *btn_dissolve = gtk_button_new_with_label("Let it dissolve"); + gtk_widget_add_css_class(btn_dissolve, "action-button"); + gtk_widget_add_css_class(btn_dissolve, "btn-dissolve"); + g_signal_connect(btn_dissolve, "clicked", G_CALLBACK(on_btn_dissolve_clicked), NULL); + + gtk_box_append(GTK_BOX(btn_box), btn_scream); + gtk_box_append(GTK_BOX(btn_box), btn_burn); + gtk_box_append(GTK_BOX(btn_box), btn_dissolve); + + gtk_box_append(GTK_BOX(input_page), btn_box); + gtk_stack_add_named(GTK_STACK(app_state.stack), input_page, "input"); + + // ---------------------------------------------------- + // PAGE 2: Animation Page + // ---------------------------------------------------- + GtkWidget *anim_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); + gtk_widget_set_margin_start(anim_page, 40); + gtk_widget_set_margin_end(anim_page, 40); + gtk_widget_set_margin_top(anim_page, 20); + gtk_widget_set_margin_bottom(anim_page, 30); + + app_state.anim_title = gtk_label_new("THE VOID IS CONSUMING..."); + gtk_widget_add_css_class(app_state.anim_title, "title-label"); + gtk_box_append(GTK_BOX(anim_page), app_state.anim_title); + + // Subtitle placeholder to keep consistent alignment + GtkWidget *anim_subtitle = gtk_label_new("Release your hold on these thoughts."); + gtk_widget_add_css_class(anim_subtitle, "subtitle-label"); + gtk_box_append(GTK_BOX(anim_page), anim_subtitle); + + GtkWidget *anim_scrolled = gtk_scrolled_window_new(); + gtk_widget_set_vexpand(anim_scrolled, TRUE); + gtk_widget_set_hexpand(anim_scrolled, TRUE); + gtk_widget_set_margin_top(anim_scrolled, 10); + gtk_widget_set_margin_bottom(anim_scrolled, 15); + + app_state.anim_text_view = gtk_text_view_new(); + gtk_text_view_set_editable(GTK_TEXT_VIEW(app_state.anim_text_view), FALSE); + gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(app_state.anim_text_view), FALSE); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(app_state.anim_text_view), GTK_WRAP_WORD_CHAR); + gtk_widget_add_css_class(app_state.anim_text_view, "worry-text-view"); + + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(anim_scrolled), app_state.anim_text_view); + gtk_box_append(GTK_BOX(anim_page), anim_scrolled); + + // Add dummy box to maintain layout height equivalent to input page + GtkWidget *dummy_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + GtkWidget *dummy_lbl = gtk_label_new(""); + gtk_widget_set_size_request(dummy_lbl, -1, 48); // Match button container height approximately + gtk_box_append(GTK_BOX(dummy_box), dummy_lbl); + gtk_box_append(GTK_BOX(anim_page), dummy_box); + + gtk_stack_add_named(GTK_STACK(app_state.stack), anim_page, "anim"); + + // ---------------------------------------------------- + // PAGE 3: Consumed Page + // ---------------------------------------------------- + GtkWidget *consumed_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 20); + gtk_widget_set_valign(consumed_page, GTK_ALIGN_CENTER); + gtk_widget_set_halign(consumed_page, GTK_ALIGN_CENTER); + gtk_widget_set_vexpand(consumed_page, TRUE); + gtk_widget_set_hexpand(consumed_page, TRUE); + + app_state.consumed_label = gtk_label_new("Consumed."); + gtk_widget_add_css_class(app_state.consumed_label, "consumed-label"); + gtk_box_append(GTK_BOX(consumed_page), app_state.consumed_label); + + GtkWidget *btn_reset = gtk_button_new_with_label("Return to the Void"); + gtk_widget_add_css_class(btn_reset, "action-button"); + gtk_widget_add_css_class(btn_reset, "btn-reset"); + g_signal_connect(btn_reset, "clicked", G_CALLBACK(on_btn_reset_clicked), NULL); + gtk_box_append(GTK_BOX(consumed_page), btn_reset); + + gtk_stack_add_named(GTK_STACK(app_state.stack), consumed_page, "consumed"); + + // Present window + gtk_window_present(GTK_WINDOW(window)); +} + +int run_gui(int argc, char* argv[], const char* initial_text) { + srand(time(NULL)); + GtkApplication *app = gtk_application_new("org.antigravity.ScreamIntoTheVoid", G_APPLICATION_DEFAULT_FLAGS); + g_signal_connect(app, "activate", G_CALLBACK(activate), (gpointer)initial_text); + + // We construct a clean arguments list for GTK to avoid standard GTK warnings + // about custom command line arguments. + int dummy_argc = 1; + char* dummy_argv[] = { argv[0], NULL }; + + int status = g_application_run(G_APPLICATION(app), dummy_argc, dummy_argv); + g_object_unref(app); + + // Cleanup any lingering app state references + if (app_state.timer_id != 0) { + g_source_remove(app_state.timer_id); + } + if (app_state.original_text) { + g_free(app_state.original_text); + } + if (app_state.animated_text) { + g_free(app_state.animated_text); + } + + return status; +} diff --git a/gui.h b/gui.h new file mode 100644 index 0000000..d9507ae --- /dev/null +++ b/gui.h @@ -0,0 +1,6 @@ +#ifndef GUI_H_ +#define GUI_H_ + +int run_gui(int argc, char* argv[], const char* initial_text); + +#endif diff --git a/main.c b/main.c index e60e6c1..b8f17c2 100644 --- a/main.c +++ b/main.c @@ -3,15 +3,25 @@ #include #include "draw.h" #include "main.h" +#include "gui.h" int main(int argc, char *argv[]) { - char input[4096]; + int use_tui = 0; + char* filename = NULL; + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-t") == 0) { + use_tui = 1; + } else if (filename == NULL && argv[i][0] != '-') { + filename = argv[i]; + } + } + char* data = NULL; size_t data_len = 0; - if (argc > 1) { + if (filename != NULL) { // Read from file - FILE *f = fopen(argv[1], "rb"); + FILE *f = fopen(filename, "rb"); if (f == NULL) { perror("Error opening file"); return 1; @@ -26,86 +36,99 @@ int main(int argc, char *argv[]) { fclose(f); return 1; } - fread(data, 1, data_len, f); - data[data_len] = '\0'; + size_t read_bytes = fread(data, 1, data_len, f); + data[read_bytes] = '\0'; + data_len = read_bytes; fclose(f); - } else { - // Get input from user - drawEdges(); - drawEmptyLines(1); - drawTitle(TITLE); - drawEmptyLines(1); - drawEdges(); - - getUserInput(input, "What's weighing on your soul?"); - data = strdup(input); - data_len = strlen(data); } - if (data == NULL || strlen(data) == 0) { - printf("The void remains silent. (No input provided)\n"); + if (use_tui) { + char input[4096]; + if (data == NULL) { + // Get input from user + drawEdges(); + drawEmptyLines(1); + drawTitle(TITLE); + drawEmptyLines(1); + drawEdges(); + + getUserInput(input, "What's weighing on your soul?"); + data = strdup(input); + data_len = strlen(data); + } + + if (data == NULL || strlen(data) == 0) { + printf("The void remains silent. (No input provided)\n"); + free(data); + return 0; + } + + // Main interaction loop + int choice = -1; + while (choice != 0) { + printf("\033[H\033[J"); // Clear screen + drawEdges(); + drawEmptyLines(1); + drawTitle("THE VOID AWAITS"); + drawEmptyLines(1); + + if (filename != NULL) { + char file_info[256]; + snprintf(file_info, sizeof(file_info), "File: %s (%zu bytes)", filename, data_len); + drawTitle(file_info); + } else { + // Show a preview of the string + char preview[100]; + strncpy(preview, data, 96); + preview[96] = '\0'; + if (strlen(data) > 96) strcat(preview, "..."); + drawTitle(preview); + } + + drawEmptyLines(1); + drawEdges(); + drawEmptyLines(1); + drawTitle("1. Scream it into the void (Standard)"); + drawTitle("2. Let it burn (Fire)"); + drawTitle("3. Let it dissolve (Acid)"); + drawTitle("0. Keep it to yourself (Exit)"); + drawEmptyLines(1); + drawEdges(); + + printf("Choose your release: "); + if (scanf("%d", &choice) != 1) { + while (getchar() != '\n'); // clear buffer + continue; + } + + switch (choice) { + case 1: + screamIntoVoid(data); + choice = 0; // Exit after screaming + break; + case 2: + burnText(data); + choice = 0; + break; + case 3: + dissolveText(data); + choice = 0; + break; + case 0: + break; + default: + printf("Invalid choice.\n"); + sleep_ms(1000); + break; + } + } + + free(data); return 0; + } else { + // Run GUI + int status = run_gui(argc, argv, data ? data : ""); + free(data); + return status; } - - // Main interaction loop - int choice = -1; - while (choice != 0) { - printf("\033[H\033[J"); // Clear screen - drawEdges(); - drawEmptyLines(1); - drawTitle("THE VOID AWAITS"); - drawEmptyLines(1); - - if (argc > 1) { - char file_info[256]; - snprintf(file_info, sizeof(file_info), "File: %s (%zu bytes)", argv[1], data_len); - drawTitle(file_info); - } else { - // Show a preview of the string - char preview[100]; - strncpy(preview, data, 96); - if (strlen(data) > 96) strcat(preview, "..."); - drawTitle(preview); - } - - drawEmptyLines(1); - drawEdges(); - drawEmptyLines(1); - drawTitle("1. Scream it into the void (Standard)"); - drawTitle("2. Let it burn (Fire)"); - drawTitle("3. Let it dissolve (Acid)"); - drawTitle("0. Keep it to yourself (Exit)"); - drawEmptyLines(1); - drawEdges(); - - printf("Choose your release: "); - if (scanf("%d", &choice) != 1) { - while (getchar() != '\n'); // clear buffer - continue; - } - - switch (choice) { - case 1: - screamIntoVoid(data); - choice = 0; // Exit after screaming - break; - case 2: - burnText(data); - choice = 0; - break; - case 3: - dissolveText(data); - choice = 0; - break; - case 0: - break; - default: - printf("Invalid choice.\n"); - sleep_ms(1000); - break; - } - } - - free(data); - return 0; }