Rework application to support GTK4 GUI by default and TUI via -t flag
Some checks failed
Build / build (push) Failing after 4s
Some checks failed
Build / build (push) Failing after 4s
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
|
||||
482
gui.c
Normal file
482
gui.c
Normal file
@@ -0,0 +1,482 @@
|
||||
#include <gtk/gtk.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#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;
|
||||
}
|
||||
6
gui.h
Normal file
6
gui.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef GUI_H_
|
||||
#define GUI_H_
|
||||
|
||||
int run_gui(int argc, char* argv[], const char* initial_text);
|
||||
|
||||
#endif
|
||||
183
main.c
183
main.c
@@ -3,15 +3,25 @@
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user