Update ds_pof.c

Substantial code clean up.
Enforce digit input on dice roll entry
Added some spacing between main grid rows and columns
This commit is contained in:
Rob
2025-07-07 08:44:39 +01:00
parent 147d588a45
commit d1704d51ed

389
ds_pof.c
View File

@ -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 <gtk/gtk.h>
#include <ctype.h>
#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 );
// }
}