223 lines
7.5 KiB
C
223 lines
7.5 KiB
C
#include "main_window.h"
|
||
#include <stdbool.h>
|
||
#include "util.h"
|
||
#include "gb_screen.h"
|
||
#include "check_menu_radio_group.h"
|
||
|
||
struct _MainWindow {
|
||
GtkApplicationWindow parent_instance;
|
||
GtkApplicationWindowClass parent_class;
|
||
|
||
GtkBox *container;
|
||
GbScreen *screen;
|
||
bool force_software_renderer;
|
||
GtkMenuBar *main_menu;
|
||
GtkSeparatorMenuItem *before_model_changer;
|
||
GtkMenu *link_menu;
|
||
};
|
||
|
||
G_DEFINE_TYPE(MainWindow, main_window, GTK_TYPE_APPLICATION_WINDOW);
|
||
|
||
typedef enum {
|
||
PROP_FORCE_SOFTWARE_RENDERER = 1,
|
||
|
||
N_PROPERTIES
|
||
} MainWindowProperty;
|
||
|
||
static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
|
||
|
||
static void main_window_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) {
|
||
MainWindow *self = (MainWindow *) object;
|
||
|
||
switch ((MainWindowProperty) property_id) {
|
||
case PROP_FORCE_SOFTWARE_RENDERER: self->force_software_renderer = g_value_get_boolean(value); break;
|
||
default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
|
||
}
|
||
}
|
||
|
||
static void main_window_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) {
|
||
MainWindow *self = (MainWindow *) object;
|
||
|
||
switch ((MainWindowProperty) property_id) {
|
||
case PROP_FORCE_SOFTWARE_RENDERER: g_value_set_boolean(value, self->force_software_renderer); break;
|
||
default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
|
||
}
|
||
}
|
||
|
||
static void main_window_constructed(GObject *object) {
|
||
MainWindow *self = (MainWindow *) object;
|
||
|
||
self->container = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
|
||
self->screen = gb_screen_new(self->force_software_renderer);
|
||
gtk_container_add(GTK_CONTAINER(self), GTK_WIDGET(self->container));
|
||
gtk_box_pack_end(GTK_BOX(self->container), GTK_WIDGET(self->screen), true, true, 0);
|
||
}
|
||
|
||
static void main_window_init(MainWindow *self) {
|
||
gtk_widget_init_template(GTK_WIDGET(self));
|
||
|
||
gtk_window_set_title(GTK_WINDOW(self), "SameBoy");
|
||
gtk_application_window_set_show_menubar(GTK_APPLICATION_WINDOW(self), false);
|
||
|
||
g_signal_new(
|
||
"break-debugger-keyboard", // signal name
|
||
G_TYPE_FROM_INSTANCE(self), // itype
|
||
G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_ACTION, // signal_flags
|
||
0, // class_offset
|
||
NULL, // accumulator
|
||
NULL, // accumulator_data
|
||
NULL, // c_marshaller,
|
||
G_TYPE_NONE, // return_type
|
||
0 // n_params
|
||
);
|
||
|
||
// Connect signal handlers
|
||
gtk_widget_add_events(GTK_WIDGET(self), GDK_KEY_PRESS_MASK);
|
||
gtk_widget_add_events(GTK_WIDGET(self), GDK_KEY_RELEASE_MASK);
|
||
|
||
GtkAccelGroup *accelGroup = gtk_accel_group_new();
|
||
gtk_window_add_accel_group(GTK_WINDOW(self), accelGroup);
|
||
gtk_widget_add_accelerator(GTK_WIDGET(self), "break-debugger-keyboard", accelGroup, GDK_KEY_C, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
|
||
}
|
||
|
||
static void main_window_finalize(GObject *object) {
|
||
MainWindow *self = (MainWindow *) object;
|
||
|
||
G_OBJECT_CLASS(main_window_parent_class)->finalize(object);
|
||
}
|
||
|
||
static void main_window_class_init(MainWindowClass *class) {
|
||
gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS(class), RESOURCE_PREFIX "ui/main_window.ui");
|
||
|
||
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), MainWindow, main_menu);
|
||
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), MainWindow, before_model_changer);
|
||
gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), MainWindow, link_menu);
|
||
|
||
G_OBJECT_CLASS(class)->finalize = main_window_finalize;
|
||
|
||
obj_properties[PROP_FORCE_SOFTWARE_RENDERER] = g_param_spec_boolean(
|
||
"force_software_renderer", "Software Renderer", "Forces the use of software rendering via Cairo",
|
||
false,
|
||
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE
|
||
);
|
||
|
||
G_OBJECT_CLASS(class)->set_property = main_window_set_property;
|
||
G_OBJECT_CLASS(class)->get_property = main_window_get_property;
|
||
G_OBJECT_CLASS(class)->constructed = main_window_constructed;
|
||
|
||
g_object_class_install_properties(G_OBJECT_CLASS(class), N_PROPERTIES, obj_properties);
|
||
}
|
||
|
||
MainWindow *main_window_new(GApplication *application, bool force_software_renderer) {
|
||
return g_object_new(MAIN_WINDOW_TYPE, "application", application, "force_software_renderer", force_software_renderer, NULL);
|
||
}
|
||
|
||
void main_window_fullscreen(MainWindow *self, bool make_fullscreen) {
|
||
if (make_fullscreen) {
|
||
gtk_window_unfullscreen(GTK_WINDOW(self));
|
||
}
|
||
else {
|
||
gtk_window_fullscreen(GTK_WINDOW(self));
|
||
}
|
||
}
|
||
|
||
// Creating these items in the UI defintion files was buggy in some desktop
|
||
// environments and the manual call of `g_signal_connect` was needed anyway
|
||
// because the UI definition can’t define string arguments for signal handlers.
|
||
static void create_model_menu_items(MainWindow *self, char *model_string) {
|
||
bool on_change_model(GtkWidget *, gpointer);
|
||
bool on_change_linked_device(GtkWidget *, gpointer);
|
||
|
||
static const char *const model_names[] = {
|
||
"Game Boy",
|
||
"Super Game Boy",
|
||
"Game Boy Color",
|
||
"Game Boy Advance",
|
||
NULL
|
||
};
|
||
|
||
static const char *const model_codes[] = {
|
||
"DMG",
|
||
"SGB",
|
||
"CGB",
|
||
"GBA",
|
||
NULL
|
||
};
|
||
|
||
// Find the menu item index of the previous sibling of the new menu items
|
||
GtkContainer *parent = GTK_CONTAINER(gtk_widget_get_parent(GTK_WIDGET(self->before_model_changer)));
|
||
g_autoptr(GList) list = gtk_container_get_children(parent);
|
||
gint position = g_list_index(list, self->before_model_changer);
|
||
|
||
CheckMenuItemGroup *model_group = check_menu_item_group_new((char **) model_names, (char **) model_codes);
|
||
check_menu_item_group_insert_into_menu_shell(model_group, GTK_MENU_SHELL(parent), position + 1);
|
||
check_menu_item_group_connect_toggle_signal(model_group, on_change_model);
|
||
check_menu_item_group_activate(model_group, model_string);
|
||
|
||
static const char *const peripheral_names[] = {
|
||
"None",
|
||
"Game Boy Printer",
|
||
NULL
|
||
};
|
||
|
||
static const char *const peripheral_codes[] = {
|
||
"NONE",
|
||
"PRINTER",
|
||
NULL,
|
||
};
|
||
|
||
CheckMenuItemGroup *link_group = check_menu_item_group_new((char **) peripheral_names, (char **) peripheral_codes);
|
||
check_menu_item_group_insert_into_menu_shell(link_group, GTK_MENU_SHELL(self->link_menu), 0);
|
||
check_menu_item_group_connect_toggle_signal(link_group, on_change_linked_device);
|
||
check_menu_item_group_activate(link_group, "NONE");
|
||
}
|
||
|
||
// Create our application’s menu.
|
||
//
|
||
// This function tries to stick to the desktop environment’s conventions.
|
||
// For the GNOME Shell it uses a hamburger menu, otherwise it either lets
|
||
// the desktop environment shell handle the menu if it signals support for it
|
||
// or uses a standard menubar inside the window.
|
||
void main_window_setup_menu(MainWindow *self, char *model_string) {
|
||
create_model_menu_items(self, model_string);
|
||
|
||
gtk_box_pack_start(GTK_BOX(self->container), GTK_WIDGET(self->main_menu), false, false, 0);
|
||
}
|
||
|
||
// GbScreen wrappers
|
||
void main_window_clear(MainWindow *self) {
|
||
return gb_screen_clear(self->screen);
|
||
}
|
||
|
||
uint32_t *main_window_get_pixels(MainWindow *self) {
|
||
return gb_screen_get_pixels(self->screen);
|
||
}
|
||
|
||
uint32_t *main_window_get_current_buffer(MainWindow *self) {
|
||
return gb_screen_get_current_buffer(self->screen);
|
||
}
|
||
|
||
uint32_t *main_window_get_previous_buffer(MainWindow *self) {
|
||
return gb_screen_get_previous_buffer(self->screen);
|
||
}
|
||
|
||
void main_window_flip(MainWindow *self) {
|
||
return gb_screen_flip(self->screen);
|
||
}
|
||
|
||
void main_window_set_resolution(MainWindow *self, unsigned width, unsigned height) {
|
||
return gb_screen_set_resolution(self->screen, width, height);
|
||
}
|
||
|
||
void main_window_set_blending_mode(MainWindow *self, GB_frame_blending_mode_t mode) {
|
||
return gb_screen_set_blending_mode(self->screen, mode);
|
||
}
|
||
|
||
void main_window_set_shader(MainWindow *self, const char *shader_name) {
|
||
return gb_screen_set_shader(self->screen, shader_name);
|
||
}
|
||
|
||
void main_window_queue_render(MainWindow *self) {
|
||
return gb_screen_queue_render(self->screen);
|
||
}
|