diff --git a/include/application.h b/include/application.h new file mode 100644 index 0000000..e099ab1 --- /dev/null +++ b/include/application.h @@ -0,0 +1,42 @@ +/* + * Gem-graph OpenGL experiments + * + * Desc: User interface header + * + * Copyright (C) 2023 Arthur Menges + * Copyright (C) 2023 Adrien Bourmault + * + * This file is part of Gem-graph. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define GEM_GRAPH_CLIENT_TYPE_APPLICATION (gem_graph_client_application_get_type()) + +G_DECLARE_FINAL_TYPE (GemGraphClientApplication, + gem_graph_client_application, + GEM_GRAPH_CLIENT, + APPLICATION, + GtkApplication) + +GemGraphClientApplication *gem_graph_client_application_new(const char *application_id, + GApplicationFlags flags); + +G_END_DECLS diff --git a/include/window.h b/include/window.h new file mode 100644 index 0000000..6552796 --- /dev/null +++ b/include/window.h @@ -0,0 +1,46 @@ +/* + * Gem-graph OpenGL experiments + * + * Desc: User interface header + * + * Copyright (C) 2023 Arthur Menges + * Copyright (C) 2023 Adrien Bourmault + * + * This file is part of Gem-graph. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +#include "../include/base.h" + +extern float rotation_angles[N_AXIS]; + +G_BEGIN_DECLS + +#define GEM_GRAPH_CLIENT_TYPE_WINDOW (gem_graph_client_window_get_type()) + +G_DECLARE_FINAL_TYPE (GemGraphClientWindow, + gem_graph_client_window, + GEM_GRAPH_CLIENT, + WINDOW, + GtkApplicationWindow) + +G_END_DECLS + +//void on_activate(GtkApplication *app, gpointer user_data); + diff --git a/src/application.c b/src/application.c new file mode 100644 index 0000000..77af316 --- /dev/null +++ b/src/application.c @@ -0,0 +1,120 @@ +/* + * Gem-graph OpenGL experiments + * + * Desc: User interface functions + * + * Copyright (C) 2023 Arthur Menges + * Copyright (C) 2023 Adrien Bourmault + * + * This file is part of Gem-graph. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include "../include/application.h" +#include "../include/window.h" + +struct _GemGraphClientApplication +{ + GtkApplication parent_instance; +}; + +G_DEFINE_TYPE (GemGraphClientApplication, + gem_graph_client_application, + GTK_TYPE_APPLICATION) + +GemGraphClientApplication *gem_graph_client_application_new( + const char *application_id, + GApplicationFlags flags) +{ + g_return_val_if_fail(application_id != NULL, NULL); + + return g_object_new(GEM_GRAPH_CLIENT_TYPE_APPLICATION, + "application-id", application_id, + "flags", flags, + NULL); +} + +static void gem_graph_client_application_activate(GApplication *app) +{ + GtkWindow *window; + + g_assert (GEM_GRAPH_CLIENT_IS_APPLICATION (app)); + + window = gtk_application_get_active_window(GTK_APPLICATION (app)); + if (window == NULL) + window = g_object_new(GEM_GRAPH_CLIENT_TYPE_WINDOW, + "application", app, + NULL); + + gtk_window_present (window); +} + +static void gem_graph_client_application_class_init( + GemGraphClientApplicationClass *klass) +{ + GApplicationClass *app_class = G_APPLICATION_CLASS(klass); + + app_class->activate = gem_graph_client_application_activate; +} + +static void +gem_graph_client_application_about_action (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + static const char *authors[] = {"neox", NULL}; + GemGraphClientApplication *self = user_data; + GtkWindow *window = NULL; + + g_assert (GEM_GRAPH_CLIENT_IS_APPLICATION(self)); + + window = gtk_application_get_active_window(GTK_APPLICATION (self)); + + gtk_show_about_dialog(window, + "program-name", "gem-graph-client", + "logo-icon-name", "org.alec.gemgraph", + "authors", authors, + "version", "0.1.0", + "copyright", "© 2023 neox", + NULL); +} + +static void gem_graph_client_application_quit_action (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GemGraphClientApplication *self = user_data; + + g_assert(GEM_GRAPH_CLIENT_IS_APPLICATION(self)); + + g_application_quit(G_APPLICATION(self)); +} + +static const GActionEntry app_actions[] = { + { "quit", gem_graph_client_application_quit_action }, + { "about", gem_graph_client_application_about_action }, +}; + +static void gem_graph_client_application_init(GemGraphClientApplication *self) +{ + g_action_map_add_action_entries(G_ACTION_MAP(self), + app_actions, + G_N_ELEMENTS(app_actions), + self); + gtk_application_set_accels_for_action(GTK_APPLICATION (self), + "app.quit", + (const char *[]) { "q", NULL }); +} + diff --git a/src/window.c b/src/window.c new file mode 100644 index 0000000..8b79d82 --- /dev/null +++ b/src/window.c @@ -0,0 +1,235 @@ +/* + * Gem-graph OpenGL experiments + * + * Desc: User interface functions + * + * Copyright (C) 2023 Arthur Menges + * Copyright (C) 2023 Adrien Bourmault + * + * This file is part of Gem-graph. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "../include/base.h" +#include "../include/graphics.h" +#include "../include/window.h" + +/* -------------------------------------------------------------------------- */ + +float rotation_angles[N_AXIS] = { 0.0 }; // Rotation angles on each axis + +static GtkWidget *gl_area = NULL; + +/* -------------------------------------------------------------------------- */ + +static void on_axis_value_change(GtkAdjustment *adjustment, gpointer data); + +static inline GtkWidget *create_axis_slider(int axis) +{ + GtkWidget *box, *label, *slider; + GtkAdjustment *adj; + const char *text; + + box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + + switch (axis) { + case X_AXIS: + text = "X"; + break; + + case Y_AXIS: + text = "Y"; + break; + + case Z_AXIS: + text = "Z"; + break; + + default: + g_assert_not_reached(); + } + + label = gtk_label_new(text); + gtk_box_append(GTK_BOX(box), label); + gtk_widget_show(label); + + adj = gtk_adjustment_new(0.0, 0.0, 360.0, 1.0, 12.0, 0.0); + g_signal_connect(adj, "value-changed", + G_CALLBACK(on_axis_value_change), + GINT_TO_POINTER(axis)); + slider = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, adj); + gtk_box_append(GTK_BOX(box), slider); + gtk_widget_set_hexpand(slider, TRUE); + gtk_widget_show(slider); + + gtk_widget_show(box); + + return box; +} + +/* -------------------------------------------------------------------------- */ + +static void on_axis_value_change(GtkAdjustment *adjustment, gpointer data) +{ + int axis = GPOINTER_TO_INT(data); + + g_assert(axis >= 0 && axis < N_AXIS); + + /* Update the rotation angle */ + rotation_angles[axis] = gtk_adjustment_get_value(adjustment); + + /* Update the contents of the GL drawing area */ + gtk_widget_queue_draw(gl_area); +} + +static gboolean on_render(GtkGLArea * area, GdkGLContext * context) +{ + if(gtk_gl_area_get_error(area) != NULL) + return FALSE; + + graphicsDraw(); + + return TRUE; +} + + +/* We need to set up our state when we realize the GtkGLArea widget */ +static void on_realize(GtkWidget *widget) +{ + gtk_gl_area_make_current(GTK_GL_AREA(widget)); + + if(gtk_gl_area_get_error(GTK_GL_AREA(widget)) != NULL) + return; + + graphicsInitGL(); +} + +/* We should tear down the state when unrealizing */ +static void on_unrealize(GtkWidget *widget) +{ + gtk_gl_area_make_current(GTK_GL_AREA(widget)); + + if(gtk_gl_area_get_error(GTK_GL_AREA(widget)) != NULL) + return; + + graphicsShutdownGL(); +} + +static void on_close_window(GtkWidget *widget) +{ + /* Reset the state */ + gl_area = NULL; + + rotation_angles[X_AXIS] = 0.0; + rotation_angles[Y_AXIS] = 0.0; + rotation_angles[Z_AXIS] = 0.0; +} + +void on_activate(GtkApplication *app, gpointer user_data) +{ + GtkWidget *window, *box, *button, *controls; + int i, minor=4, major=0; + + window = gtk_application_window_new(app); + gtk_window_set_default_size(GTK_WINDOW(window), 400, 600); + gtk_window_set_title(GTK_WINDOW(window), "GemGL (essais pour Gem-graph en OpenGL)"); + g_signal_connect(window, "destroy", G_CALLBACK(on_close_window), NULL); + + box = gtk_box_new(GTK_ORIENTATION_VERTICAL, FALSE); + gtk_widget_set_margin_start(box, 12); + gtk_widget_set_margin_end(box, 12); + gtk_widget_set_margin_top(box, 12); + gtk_widget_set_margin_bottom(box, 12); + gtk_box_set_spacing(GTK_BOX(box), 6); + gtk_window_set_child(GTK_WINDOW(window), box); + + gl_area = gtk_gl_area_new(); + gtk_gl_area_set_required_version( GTK_GL_AREA(gl_area), minor, major); + gtk_gl_area_get_required_version( GTK_GL_AREA(gl_area), &minor, &major); + gtk_widget_set_hexpand(gl_area, TRUE); + gtk_widget_set_vexpand(gl_area, TRUE); + gtk_widget_set_size_request(gl_area, 100, 200); + gtk_box_append(GTK_BOX(box), gl_area); + + // We need to initialize and free GL resources, so we use + // the realize and unrealize signals on the widget + // + g_signal_connect(gl_area, "realize", G_CALLBACK(on_realize), NULL); + g_signal_connect(gl_area, "unrealize", G_CALLBACK(on_unrealize), NULL); + + // The main "draw" call for GtkGLArea + g_signal_connect(gl_area, "render", G_CALLBACK(on_render), NULL); + + controls = gtk_box_new(GTK_ORIENTATION_VERTICAL, FALSE); + gtk_box_append(GTK_BOX(box), controls); + gtk_widget_set_hexpand(controls, TRUE); + + for(i = 0; i < N_AXIS; i++) + gtk_box_append(GTK_BOX(controls), create_axis_slider(i)); + + button = gtk_button_new_with_label("Fermer"); + gtk_widget_set_hexpand(button, TRUE); + gtk_box_append(GTK_BOX(box), button); + g_signal_connect_swapped(button, "clicked", G_CALLBACK(gtk_window_destroy), window); + + gtk_widget_show(window); +} + +/* -------------------------------------------------------------------------- */ + +struct _GemGraphClientWindow +{ + GtkApplicationWindow parent_instance; + + /* Template widgets */ + GtkHeaderBar *header_bar; + GtkLabel *label; +}; + +G_DEFINE_FINAL_TYPE (GemGraphClientWindow, + gem_graph_client_window, + GTK_TYPE_APPLICATION_WINDOW) + +static void gem_graph_client_window_class_init(GemGraphClientWindowClass *klass) +{ + gchar *contents; + gsize len; + GError *err; + GBytes *bytes; + + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + + if (g_file_get_contents("ui/gemgraph.ui", &contents, &len, &err) == FALSE) + g_error("error reading ui/gemgraph.ui: %s", err->message); + + bytes = g_bytes_new_take(contents, len); + gtk_widget_class_set_template(GTK_WIDGET_CLASS(klass), bytes); + + gtk_widget_class_bind_template_child(widget_class, + GemGraphClientWindow, + header_bar); + gtk_widget_class_bind_template_child(widget_class, + GemGraphClientWindow, + label); +} + +static void gem_graph_client_window_init(GemGraphClientWindow *self) +{ + gtk_widget_init_template(GTK_WIDGET (self)); +}