// gcc $(pkg-config --cflags gtk4) -o ds_pof ds_pof.c $(pkg-config --libs gtk4) #include // --------------------------------------------------------- // global widgets and buffers needed across various // callback functons //TODO: make this local to app and pass as struct // --------------------------------------------------------- static GtkWidget *str_button; static GtkWidget *str_modifier; static GtkWidget *dex_button; static GtkWidget *dex_modifier; 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) { //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); 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); // clear the dice roll field for convenience gtk_entry_buffer_delete_text(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) { 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; 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; sprintf(result_buf, "Damage result: %d [Dice: %d] [Proficiency: %d | Cinder Wrath %s] [Strength: %d] [Duelling Buff: %d]", 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); // clear the dice roll field for convenience gtk_entry_buffer_delete_text(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 // the spin-button is changed // --------------------------------------------------------- static int stat_calc(GtkWidget *spin_button, GtkWidget *mod_label) { int raw_value = gtk_spin_button_get_value_as_int((GtkSpinButton *) spin_button); int mod_value = (int) ((raw_value - 10) / 2); char mod_buf[20] = {'\0'}; sprintf(mod_buf, "%d", mod_value); gtk_label_set_markup(GTK_LABEL(mod_label), mod_buf); } // --------------------------------------------------------- // callback function to set the character stats to those // parsed from an input file // --------------------------------------------------------- static void load_from_file_activated(GSimpleAction *action, GVariant *parameter, gpointer app) { g_print("load\n"); } // --------------------------------------------------------- // callback function to save current character stats to // a file // --------------------------------------------------------- static void save_to_file_activated(GSimpleAction *action, GVariant *parameter, gpointer app) { g_print("save\n"); } // --------------------------------------------------------- // callback function to exit the app // --------------------------------------------------------- static void quit_activated(GSimpleAction *action, GVariant *parameter, gpointer app) { g_application_quit(G_APPLICATION (app)); } // --------------------------------------------------------- // activation function - configures the application UI // --------------------------------------------------------- static void activate (GtkApplication *app, gpointer user_data) { GtkWidget *window; GtkWidget *grid; GtkWidget *button; GtkWidget *inscription; GtkAdjustment *adjustment; /* create a new window */ window = gtk_application_window_new(app); gtk_window_set_default_size (GTK_WINDOW (window), 600, 400); /* Here we construct the container that is going pack our core elements (buttons etc) */ grid = gtk_grid_new (); // 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 // the second tab is for full character customisation and can affect attack/damage roll options //TODO: implement the contents of second tab GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6); GtkWidget *stack = gtk_stack_new (); GtkWidget *switcher = gtk_stack_switcher_new (); GtkWidget *label2 = gtk_label_new("Stack Page 2"); gtk_stack_add_titled((GtkStack *) stack, GTK_WIDGET(grid), "Calculator", "Calculator"); gtk_stack_add_titled((GtkStack *) stack, GTK_WIDGET(label2), "Character Details", "Character Details"); gtk_stack_switcher_set_stack((GtkStackSwitcher *) switcher, (GtkStack *) stack); //order matters, this way around gives tabs at top, invert for tabs at bottom gtk_box_prepend((GtkBox *) box, stack); gtk_box_prepend((GtkBox *) box, switcher); gtk_window_set_child(GTK_WINDOW(window), box); // --------------------------------------------------------- // Setup the spin buttons (increment/decrement buttons) // for the 6 core stats (STR, DEX, CON, INT, WIS, CHA) // The buttons start with a default of 10, can be manually // editied like a text box, have specified limits, and set // step sizes. // 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"; 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 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); str_modifier = gtk_label_new (NULL); gtk_grid_attach(GTK_GRID (grid), str_modifier, 0, 3, 1, 1); g_signal_connect(str_button, "value-changed", G_CALLBACK(stat_calc), str_modifier); // perform first-time stat calculation so something exists in the modifier fields stat_calc(str_button, str_modifier); // 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); // --------------------------------------------------------- // spin button for proficiency // --------------------------------------------------------- // label and increment/decrement button for proficiency const char *proficiency = "PROF"; label = gtk_label_new (NULL); gtk_label_set_markup (GTK_LABEL (label), proficiency); 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); gtk_grid_attach (GTK_GRID (grid), prof_button, 6, 2, 1, 1); // --------------------------------------------------------- // text entry field for the user to input their dice roll // --------------------------------------------------------- const char *dice_roll_label = "Dice Roll:"; label = gtk_label_new (NULL); 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); 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); // --------------------------------------------------------- // action buttons and options // provide buttons to calculate attack rolls and damage // rolls based on the supplied input dice roll // also provide option to indicate if Cinder Wrath is // active or not, as this adds proficiency to the dmg value // --------------------------------------------------------- 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); 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); // 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); gtk_grid_attach (GTK_GRID (grid), cinder_wrath_check, 2, 5, 3, 1); // --------------------------------------------------------- // 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); // 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); // --------------------------------------------------------- // Create a menu button that will allow for loading/saving // character configuration, as well as exiting the app // The menu button sits within a header bar, and uses // a Menu Model for the menu structure and contents // --------------------------------------------------------- // the menu button that will sit in the header bar GtkWidget *menubutton = gtk_menu_button_new(); gtk_menu_button_set_direction((GtkMenuButton*) menubutton, GTK_ARROW_DOWN); //DOWN //gtk_widget_set_valign (GtkWidget* widget, GtkAlign align); gtk_widget_set_halign(menubutton, GTK_ALIGN_END); // the top-level menu GMenu *settings_menu = g_menu_new(); // the different options we have in the top-level menu GMenuItem *load_file = g_menu_item_new( "Load from file", "app.load_from_file" ); g_menu_append_item(settings_menu, load_file); GMenuItem *save_file = g_menu_item_new( "Save to file", "app.save_to_file" ); g_menu_append_item(settings_menu, save_file); GMenuItem *quit_app = g_menu_item_new ( "Quit", "app.quit" ); g_menu_append_item(settings_menu, quit_app); // define short-cuts that we can associate with the menu items // + O for opening a file, + S for saving a file // + Q for exiting the application const char *load_accels[2] = { "O", NULL }; const char *save_accels[2] = { "S", NULL }; const char *quit_accels[2] = { "Q", NULL }; // set up the app actions (callbacks) that are taken when an item // in the menu is pressed static GActionEntry app_entries[] = { { "load_from_file", load_from_file_activated, NULL, NULL, NULL }, { "save_to_file", save_to_file_activated, NULL, NULL, NULL }, { "quit", quit_activated, NULL, NULL, NULL } }; g_action_map_add_action_entries(G_ACTION_MAP (app), app_entries, G_N_ELEMENTS(app_entries), app); // apply the short-cuts that are defined above gtk_application_set_accels_for_action(GTK_APPLICATION (app), "app.load_from_file", load_accels); gtk_application_set_accels_for_action(GTK_APPLICATION (app), "app.save_to_file", save_accels); gtk_application_set_accels_for_action(GTK_APPLICATION (app), "app.quit", quit_accels); gtk_menu_button_set_menu_model((GtkMenuButton*) menubutton, G_MENU_MODEL(settings_menu)); // create headerbar to contain the menu button GtkWidget *headerbar = gtk_header_bar_new(); gtk_header_bar_pack_end((GtkHeaderBar*) headerbar, menubutton); // set the title of the headerbar const char *window_title_label = "DS:PoF Attack and Damage Roll Calculator"; label = gtk_label_new (NULL); gtk_label_set_markup (GTK_LABEL (label), window_title_label); gtk_header_bar_set_title_widget((GtkHeaderBar*) headerbar, label); // pass the headerbar to the window gtk_window_set_titlebar(GTK_WINDOW (window), headerbar); // --------------------------------------------------------- // Open the application window // --------------------------------------------------------- gtk_window_present (GTK_WINDOW (window)); } 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); 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 ); // }