From d1704d51edc12ae160628f19d808b536491f552f Mon Sep 17 00:00:00 2001 From: Rob Date: Mon, 7 Jul 2025 08:44:39 +0100 Subject: [PATCH] Update ds_pof.c Substantial code clean up. Enforce digit input on dice roll entry Added some spacing between main grid rows and columns --- ds_pof.c | 389 ++++++++++++++++++++++--------------------------------- 1 file changed, 158 insertions(+), 231 deletions(-) diff --git a/ds_pof.c b/ds_pof.c index a9b63a7..6c80d24 100644 --- a/ds_pof.c +++ b/ds_pof.c @@ -1,69 +1,99 @@ -// gcc $(pkg-config --cflags gtk4) -o ds_pof ds_pof.c $(pkg-config --libs gtk4) +/*============================================================================*/ +// +// Title: ds_pof.c +// Author: RH +// +// Description: +// GUI application to host character details in the TTRPG game +// 'Dark Souls: Phases of Fire' and automatically calculate the outcome of a +// dice roll based on current stats and conditions. +// +// TODO: +// - Need header file for types and defines +// - menu bar should be seperate element +// - window controls +// - Would be nice to seperate GUI source file from actual calculations and +// operations typical of a CLI c application +// - CSS styling and parsing +// - Look at XML UI building +// - Implement Loading/Saving of character details -> csv for now, sqlite3 in +// the future +// - Implement full character stats, feats, class, and status conditions +// - this will take some considerable effort +// - Make the app look pretty - the hardest of all challenges +// - Resist the urge to learn JS and build in electron +/*============================================================================*/ #include +#include + +#define NUM_CORE_STATS 6 // --------------------------------------------------------- -// global widgets and buffers needed across various -// callback functons //TODO: make this local to app and pass as struct +// this struct pointer will contain pointers to various +// elements all needed in attack/damage callback functions +// +// a struct is neccessary when passing multiple elements +// in a g_signal_connect() +// +// it is created in main and passed into activate(), +// where it is then poulated as elements are created // --------------------------------------------------------- -static GtkWidget *str_button; -static GtkWidget *str_modifier; +struct atk_dmg_object +{ + GtkEntryBuffer *dice_roll_buffer; + GtkWidget *stat_modifiers[NUM_CORE_STATS]; + //GtkWidget *str_modifier; + GtkWidget *prof_button; + GtkTextBuffer *calc_result_buffer; + // dmg only: + GtkWidget *cinder_wrath_check; +}; -static GtkWidget *dex_button; -static GtkWidget *dex_modifier; +// definitions for indexes of atk_dmg_object.stat_modifiers +#define STR_MODIFIER 0 +#define DEX_MODIFIER 1 +#define CON_MODIFIER 2 +#define INT_MODIFIER 3 +#define WIS_MODIFIER 4 +#define CHA_MODIFIER 5 -static GtkWidget *int_button; -static GtkWidget *int_modifier; - -static GtkWidget *con_button; -static GtkWidget *con_modifier; - -static GtkWidget *wis_button; -static GtkWidget *wis_modifier; - -static GtkWidget *cha_button; -static GtkWidget *cha_modifier; - -static char modifier_buf[20] = {'\0'}; static char result_buf[200] = {'\0'}; -static GtkWidget *prof_button; -static GtkEntryBuffer *dice_roll_buffer; -static GtkTextBuffer *calc_result_buffer; -static GtkWidget* calc_result_diag; - -static GtkWidget *cinder_wrath_check; - // --------------------------------------------------------- // callback function to calculate attack roll whenever the // attack button is pressed // --------------------------------------------------------- -static void attack_calc(GtkWidget *widget) +static void attack_calc(GtkWidget *widget, gpointer data) { - //TODO: need error handling or some kind of integer only buffer (maybe spin button without buttons?) - int dice_roll_value = atoi(gtk_entry_buffer_get_text(dice_roll_buffer)); - int str_mod_value = atoi(gtk_label_get_text((GtkLabel *) str_modifier)); - int prof_value = gtk_spin_button_get_value_as_int((GtkSpinButton *) prof_button); + struct atk_dmg_object *p_atk_dmg_struct = (struct atk_dmg_object *) data; + + int dice_roll_value = atoi(gtk_entry_buffer_get_text(p_atk_dmg_struct->dice_roll_buffer)); + int str_mod_value = atoi(gtk_label_get_text((GtkLabel *) p_atk_dmg_struct->stat_modifiers[STR_MODIFIER])); + int prof_value = gtk_spin_button_get_value_as_int((GtkSpinButton *) p_atk_dmg_struct->prof_button); + int atk_result = dice_roll_value + prof_value + str_mod_value; sprintf(result_buf, "Attack result: %d [Dice: %d] [Proficiency: %d] [Strength: %d]", atk_result, dice_roll_value, prof_value, str_mod_value); - gtk_text_buffer_set_text(calc_result_buffer, result_buf, -1); + gtk_text_buffer_set_text(p_atk_dmg_struct->calc_result_buffer, result_buf, -1); // clear the dice roll field for convenience - gtk_entry_buffer_delete_text(dice_roll_buffer, 0, -1 /*delete all*/); + gtk_entry_buffer_delete_text(p_atk_dmg_struct->dice_roll_buffer, 0, -1 /*delete all*/); } // --------------------------------------------------------- // callback function to calculate damage roll whenever the // damage button is pressed // --------------------------------------------------------- -static void damage_calc(GtkWidget *widget) +static void damage_calc(GtkWidget *widget, gpointer data) { - int dice_roll_value = atoi(gtk_entry_buffer_get_text(dice_roll_buffer)); - int cinder_wrath = gtk_check_button_get_active((GtkCheckButton *) cinder_wrath_check); - int str_mod_value = atoi(gtk_label_get_text((GtkLabel *) str_modifier)); - int prof_value = (cinder_wrath == 1) ? gtk_spin_button_get_value_as_int((GtkSpinButton *) prof_button) : 0; + struct atk_dmg_object *p_atk_dmg_struct = (struct atk_dmg_object *) data; + + int dice_roll_value = atoi(gtk_entry_buffer_get_text(p_atk_dmg_struct->dice_roll_buffer)); + int cinder_wrath = gtk_check_button_get_active((GtkCheckButton *) p_atk_dmg_struct->cinder_wrath_check); + int str_mod_value = atoi(gtk_label_get_text((GtkLabel *) p_atk_dmg_struct->stat_modifiers[STR_MODIFIER])); + int prof_value = (cinder_wrath == 1) ? gtk_spin_button_get_value_as_int((GtkSpinButton *) p_atk_dmg_struct->prof_button) : 0; int additional_buff = 2; //TODO: this is for duelling, need a way to parameteris this/configure int dmg_result = dice_roll_value + prof_value + str_mod_value + additional_buff; @@ -71,13 +101,12 @@ static void damage_calc(GtkWidget *widget) dmg_result, dice_roll_value, prof_value, (cinder_wrath == 1) ? "Enabled" : "Disabled", str_mod_value, additional_buff); - gtk_text_buffer_set_text(calc_result_buffer, result_buf, -1); + gtk_text_buffer_set_text(p_atk_dmg_struct->calc_result_buffer, result_buf, -1); // clear the dice roll field for convenience - gtk_entry_buffer_delete_text(dice_roll_buffer, 0, -1 /*delete all*/); + gtk_entry_buffer_delete_text(p_atk_dmg_struct->dice_roll_buffer, 0, -1 /*delete all*/); } - // --------------------------------------------------------- // callback function used by all core stat spin-button // to update a stat modifier whenever the raw value in @@ -118,11 +147,28 @@ static void quit_activated(GSimpleAction *action, GVariant *parameter, gpointer g_application_quit(G_APPLICATION (app)); } +// --------------------------------------------------------- +// callback function to enforce numeric only entry on the +// dice roll input buffer +// --------------------------------------------------------- +void numeric_only_text_check(GtkEditable *editable, + const gchar *text, + gint length, + gint *position, gpointer data) +{ + if (!isdigit(text[0])) + { + gtk_editable_delete_text(editable, *position-1, -1); + } +} + // --------------------------------------------------------- // activation function - configures the application UI // --------------------------------------------------------- static void activate (GtkApplication *app, gpointer user_data) { + struct atk_dmg_object *data = (struct atk_dmg_object *) user_data; + GtkWidget *window; GtkWidget *grid; GtkWidget *button; @@ -135,6 +181,11 @@ static void activate (GtkApplication *app, gpointer user_data) /* Here we construct the container that is going pack our core elements (buttons etc) */ grid = gtk_grid_new (); + // set grid padding and row/col spacing + gtk_grid_set_column_spacing((GtkGrid*) grid, 2); + gtk_grid_set_row_spacing((GtkGrid*) grid, 2); + gtk_widget_set_margin_start(grid, 10); + gtk_widget_set_margin_end(grid, 10); // create two stacks and a switcher so we can have two tabs // one tab is for the basic attack/damage roll function using the 6 cores stats @@ -165,116 +216,35 @@ static void activate (GtkApplication *app, gpointer user_data) // When the buttons are modified, the callback function // reads the button value and calculates the stat modifier // to then display (and use for atk/dmg calculations) - // TODO: try to condense this block // --------------------------------------------------------- - // label and increment/decrement button for strength - const char *strength = "STR"; + // The below for-loop creates a label to display the stat type, + // a spin-button to set the stat value, and a widget for displaying + // the stat modifier for each of the 6 core stats + GtkWidget *label = gtk_label_new (NULL); - gtk_label_set_markup(GTK_LABEL (label), strength); - gtk_grid_attach(GTK_GRID (grid), label, 0, 1, 1, 1); //TODO: we can raise these up by one + const char *stat_names[] = {"STR", "DEX", "CON", "INT", "WIS", "CHA"}; + GtkWidget *str_butt, *dex_button, *con_button, *int_button, *wis_button, *cha_button; + GtkWidget *stat_buttons[] = {str_butt, dex_button, con_button, int_button, wis_button, cha_button}; - adjustment = gtk_adjustment_new(10.0, 0.0, 100.0, 1.0, 5.0, 0.0); - str_button = gtk_spin_button_new(adjustment, 1.0, 0); - gtk_grid_attach(GTK_GRID (grid), str_button, 0, 2, 1, 1); + for(int i = 0; i < NUM_CORE_STATS; i++) + { + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (label), stat_names[i]); + gtk_grid_attach (GTK_GRID (grid), label, i, 1, 1, 1); - str_modifier = gtk_label_new (NULL); - gtk_grid_attach(GTK_GRID (grid), str_modifier, 0, 3, 1, 1); + adjustment = gtk_adjustment_new (10.0, 0.0, 100.0, 1.0, 5.0, 0.0); + stat_buttons[i] = gtk_spin_button_new (adjustment, 1.0, 0); + gtk_grid_attach (GTK_GRID (grid), stat_buttons[i] , i, 2, 1, 1); - g_signal_connect(str_button, "value-changed", G_CALLBACK(stat_calc), str_modifier); + data->stat_modifiers[i] = gtk_label_new (NULL); + gtk_grid_attach(GTK_GRID (grid), data->stat_modifiers[i], i, 3, 1, 1); - // perform first-time stat calculation so something exists in the modifier fields - stat_calc(str_button, str_modifier); + g_signal_connect(stat_buttons[i], "value-changed", G_CALLBACK(stat_calc), data->stat_modifiers[i]); - // label and increment/decrement button for dexterity - const char *dexterity = "DEX"; - label = gtk_label_new (NULL); - gtk_label_set_markup (GTK_LABEL (label), dexterity); - gtk_grid_attach (GTK_GRID (grid), label, 1, 1, 1, 1); - - adjustment = gtk_adjustment_new (10.0, 0.0, 100.0, 1.0, 5.0, 0.0); - dex_button = gtk_spin_button_new (adjustment, 1.0, 0); - gtk_grid_attach (GTK_GRID (grid), dex_button, 1, 2, 1, 1); - - dex_modifier = gtk_label_new (NULL); - gtk_grid_attach(GTK_GRID (grid), dex_modifier, 1, 3, 1, 1); - - g_signal_connect(dex_button, "value-changed", G_CALLBACK(stat_calc), dex_modifier); - - // perform first-time stat calculation so something exists in the modifier fields - stat_calc(dex_button, dex_modifier); - - // label and increment/decrement button for constitution - const char *constitution = "CON"; - label = gtk_label_new (NULL); - gtk_label_set_markup (GTK_LABEL (label), constitution); - gtk_grid_attach (GTK_GRID (grid), label, 2, 1, 1, 1); - - adjustment = gtk_adjustment_new (10.0, 0.0, 100.0, 1.0, 5.0, 0.0); - con_button = gtk_spin_button_new (adjustment, 1.0, 0); - gtk_grid_attach (GTK_GRID (grid), con_button, 2, 2, 1, 1); - - con_modifier = gtk_label_new (NULL); - gtk_grid_attach(GTK_GRID (grid), con_modifier, 2, 3, 1, 1); - - g_signal_connect(con_button, "value-changed", G_CALLBACK(stat_calc), con_modifier); - - // perform first-time stat calculation so something exists in the modifier fields - stat_calc(con_button, con_modifier); - - // label and increment/decrement button for intelligence - const char *intelligence = "INT"; - label = gtk_label_new (NULL); - gtk_label_set_markup (GTK_LABEL (label), intelligence); - gtk_grid_attach (GTK_GRID (grid), label, 3, 1, 1, 1); - - adjustment = gtk_adjustment_new (10.0, 0.0, 100.0, 1.0, 5.0, 0.0); - int_button = gtk_spin_button_new (adjustment, 1.0, 0); - gtk_grid_attach (GTK_GRID (grid), int_button, 3, 2, 1, 1); - - int_modifier = gtk_label_new (NULL); - gtk_grid_attach(GTK_GRID (grid), int_modifier, 3, 3, 1, 1); - - g_signal_connect(int_button, "value-changed", G_CALLBACK(stat_calc), int_modifier); - - // perform first-time stat calculation so something exists in the modifier fields - stat_calc(int_button, int_modifier); - - // label and increment/decrement button for wisdom - const char *wisdom = "WIS"; - label = gtk_label_new (NULL); - gtk_label_set_markup (GTK_LABEL (label), wisdom); - gtk_grid_attach (GTK_GRID (grid), label, 4, 1, 1, 1); - - adjustment = gtk_adjustment_new (10.0, 0.0, 100.0, 1.0, 5.0, 0.0); - wis_button = gtk_spin_button_new (adjustment, 1.0, 0); - gtk_grid_attach (GTK_GRID (grid), wis_button, 4, 2, 1, 1); - - wis_modifier = gtk_label_new (NULL); - gtk_grid_attach(GTK_GRID (grid), wis_modifier, 4, 3, 1, 1); - - g_signal_connect(wis_button, "value-changed", G_CALLBACK(stat_calc), wis_modifier); - - // perform first-time stat calculation so something exists in the modifier fields - stat_calc(wis_button, wis_modifier); - - // label and increment/decrement button for charisma - const char *charisma = "CHA"; - label = gtk_label_new (NULL); - gtk_label_set_markup (GTK_LABEL (label), charisma); - gtk_grid_attach (GTK_GRID (grid), label, 5, 1, 1, 1); - - adjustment = gtk_adjustment_new (10.0, 0.0, 100.0, 1.0, 5.0, 0.0); - cha_button = gtk_spin_button_new (adjustment, 1.0, 0); - gtk_grid_attach (GTK_GRID (grid), cha_button, 5, 2, 1, 1); - - cha_modifier = gtk_label_new(NULL); - gtk_grid_attach(GTK_GRID (grid), cha_modifier, 5, 3, 1, 1); - - g_signal_connect(cha_button, "value-changed", G_CALLBACK(stat_calc), cha_modifier); - - // perform first-time stat calculation so something exists in the modifier fields - stat_calc(cha_button, cha_modifier); + // perform first-time stat calculation so something exists in the modifier fields + stat_calc(stat_buttons[i], data->stat_modifiers[i]); + } // --------------------------------------------------------- // spin button for proficiency @@ -287,8 +257,11 @@ static void activate (GtkApplication *app, gpointer user_data) gtk_grid_attach (GTK_GRID (grid), label, 6, 1, 1, 1); adjustment = gtk_adjustment_new (0.0, 0.0, 100.0, 1.0, 5.0, 0.0); - prof_button = gtk_spin_button_new (adjustment, 1.0, 0); + GtkWidget *prof_button = gtk_spin_button_new (adjustment, 1.0, 0); gtk_grid_attach (GTK_GRID (grid), prof_button, 6, 2, 1, 1); + //TODO: the below is not quite what I want, maybe implement a custom version using a box, entry and 2 small buttos with icons? + //gtk_orientable_set_orientation((GtkOrientable*) prof_button, GTK_ORIENTATION_VERTICAL); + data->prof_button = prof_button; // --------------------------------------------------------- // text entry field for the user to input their dice roll @@ -299,9 +272,16 @@ static void activate (GtkApplication *app, gpointer user_data) gtk_label_set_markup (GTK_LABEL (label), dice_roll_label); gtk_grid_attach (GTK_GRID (grid), label, 0, 4, 1, 1); - dice_roll_buffer = gtk_entry_buffer_new(NULL, -1); + GtkEntryBuffer *dice_roll_buffer = gtk_entry_buffer_new(NULL, -1); GtkWidget *dice_roll_input = gtk_entry_new_with_buffer(dice_roll_buffer); gtk_grid_attach (GTK_GRID (grid), dice_roll_input, 1, 4, 1, 1); + data->dice_roll_buffer = dice_roll_buffer; + + // assign callback function to run whenever text is entered + // which will check if the entered text is a digit and remove + // it if it is any other character + g_signal_connect_after( gtk_editable_get_delegate(GTK_EDITABLE(dice_roll_input)), + "insert-text", G_CALLBACK(numeric_only_text_check), NULL ); // --------------------------------------------------------- // action buttons and options @@ -312,30 +292,32 @@ static void activate (GtkApplication *app, gpointer user_data) // --------------------------------------------------------- button = gtk_button_new_with_label ("Attack"); - g_signal_connect (button, "clicked", G_CALLBACK (attack_calc), NULL); - gtk_grid_attach (GTK_GRID (grid), button, 0, 5, 1, 1); + g_signal_connect(button, "clicked", G_CALLBACK (attack_calc), data); + gtk_grid_attach(GTK_GRID (grid), button, 0, 5, 1, 1); button = gtk_button_new_with_label ("Damage"); - g_signal_connect (button, "clicked", G_CALLBACK (damage_calc), NULL); - gtk_grid_attach (GTK_GRID (grid), button, 1, 5, 1, 1); + g_signal_connect(button, "clicked", G_CALLBACK (damage_calc), data); + gtk_grid_attach(GTK_GRID (grid), button, 1, 5, 1, 1); - // checkbox to enable if raging (cinder wrath) - adds proficiency to dmg - cinder_wrath_check = gtk_check_button_new_with_label("Cinder Wrath"); - //g_signal_connect (cinder_wrath_check, "toggled", G_CALLBACK (print_hello), NULL); + // checkbox to enable if raging (cinder wrath) + GtkWidget *cinder_wrath_check = gtk_check_button_new_with_label("Cinder Wrath"); gtk_grid_attach (GTK_GRID (grid), cinder_wrath_check, 2, 5, 3, 1); + data->cinder_wrath_check = cinder_wrath_check; // --------------------------------------------------------- // Text buffer for displaying the result of the calculation // and ensure the buffer cannot be modified by the user // --------------------------------------------------------- - calc_result_buffer = gtk_text_buffer_new(NULL); - calc_result_diag = gtk_text_view_new_with_buffer(calc_result_buffer); + GtkTextBuffer *calc_result_buffer = gtk_text_buffer_new(NULL); + GtkWidget *calc_result_diag = gtk_text_view_new_with_buffer(calc_result_buffer); // make it so the field cannot be edited by the user (makes the text non-input) gtk_text_view_set_editable(GTK_TEXT_VIEW(calc_result_diag), FALSE); gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(calc_result_diag), FALSE); gtk_grid_attach (GTK_GRID (grid), calc_result_diag, 0, 6, 4, 2); + data->calc_result_buffer = calc_result_buffer; + // --------------------------------------------------------- // Create a menu button that will allow for loading/saving // character configuration, as well as exiting the app @@ -406,6 +388,14 @@ static void activate (GtkApplication *app, gpointer user_data) // --------------------------------------------------------- gtk_window_present (GTK_WINDOW (window)); + + // --------------------------------------------------------- + // clean up + // --------------------------------------------------------- + g_object_unref(load_file); + g_object_unref(save_file); + g_object_unref(quit_app); + g_object_unref(settings_menu); } int main (int argc, char **argv) @@ -413,81 +403,18 @@ int main (int argc, char **argv) GtkApplication *app; int status; - app = gtk_application_new ("org.gtk.example", 0); - g_signal_connect (app, "activate", G_CALLBACK (activate), NULL); - status = g_application_run (G_APPLICATION (app), argc, argv); - g_object_unref (app); + // this struct pointer will contain pointers to various + // elements all needed in attack/damage calculations + // a struct is neccessary when passing multiple elements + // in a g_signal_connect() + struct atk_dmg_object *data = malloc(sizeof(*data)); + + app = gtk_application_new("org.gtk.example", 0); + g_signal_connect(app, "activate", G_CALLBACK(activate), data); + status = g_application_run (G_APPLICATION(app), argc, argv); + g_object_unref(app); + + free(data); return status; -} - - - - - - - - -// static void activate ( GApplication *app, G_GNUC_UNUSED gpointer *data ) -// { -// GtkWidget *win; -// GSimpleAction *act_connect; -// GSimpleAction *act_disconnect; - -// /// *** -// GMenu *menu_bar; -// GMenu *network_menu; -// GMenu *server_menu; - -// /// *** -// GMenuItem *menu_item_connect; -// GMenuItem *menu_item_disconnect; - -// /// *** Menu Bar -// menu_bar = g_menu_new(); - -// /// *** Network_Menu -// network_menu = g_menu_new(); -// g_menu_append_submenu ( menu_bar, "Network", G_MENU_MODEL ( network_menu ) ); - -// /// *** Server_Menu -// server_menu = g_menu_new(); -// g_menu_append_submenu ( network_menu, "Server", G_MENU_MODEL ( server_menu ) ); -// /// *** - -// /// *** Create Connect and Disconnect Actions -// act_connect = g_simple_action_new ( "connect", NULL ); -// act_disconnect = g_simple_action_new ( "disconnect", NULL ); - -// /// *** Add them to the ActionMap -// g_action_map_add_action ( G_ACTION_MAP ( app ), G_ACTION ( act_connect ) ); -// g_action_map_add_action ( G_ACTION_MAP ( app ), G_ACTION ( act_disconnect ) ); - -// /// *** Connect them to the activate Signal -// g_signal_connect ( act_connect, "activate", G_CALLBACK ( action_clbk ), NULL ); -// g_signal_connect ( act_disconnect, "activate", G_CALLBACK ( action_clbk ), NULL ); - -// /// *** Create the Connect Item -// menu_item_connect = g_menu_item_new ( "Connect", "app.connect" ); -// g_menu_append_item ( server_menu, menu_item_connect ); - -// /// *** Create the Disconnect Item -// menu_item_disconnect = g_menu_item_new ( "Disconnect", "app.disconnect" ); -// g_menu_append_item ( server_menu, menu_item_disconnect ); - -// /// *** -// gtk_application_set_menubar ( GTK_APPLICATION ( app ), G_MENU_MODEL ( menu_bar ) ); -// gtk_application_window_set_show_menubar ( GTK_APPLICATION_WINDOW ( win ), TRUE ); - -// /// *** -// gtk_window_present ( GTK_WINDOW ( win ) ); - -// /// *** Clean -// g_object_unref ( act_connect ); -// g_object_unref ( act_disconnect ); -// g_object_unref ( menu_item_connect ); -// g_object_unref ( menu_item_disconnect ); -// g_object_unref ( server_menu ); -// g_object_unref ( network_menu ); -// g_object_unref ( menu_bar ); -// } \ No newline at end of file +} \ No newline at end of file