diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2ba72a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +**/tags.* +bin/* +build/* \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index d27c286..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,11 +0,0 @@ -before_script: - - python --version - - pip install -r requirements.txt - -stages: - - Static Analysis - -flake8: - stage: Static Analysis - script: - - flake8 --max-line-length=120 --ignore=E402,E266, src/*.py \ No newline at end of file diff --git a/LICENSE b/LICENSE index 15c6e41..be3f7b2 100644 --- a/LICENSE +++ b/LICENSE @@ -630,11 +630,11 @@ state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - Copyright (C) 2021 Adrien Bourmault + Copyright (C) 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 + 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, diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a16f2f8 --- /dev/null +++ b/Makefile @@ -0,0 +1,126 @@ +## +## Gem-graph OpenGL experiments +## +## Desc: Makefile +## +## 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 . +## + +.PHONY: all clean install run build_system +.DELETE_ON_ERROR: $(BINDIR)/gem-graph-client +.DEFAULT_GOAL := all + +# +# Color codes +# +CL='\033[0;32m' +CL2='\033[1;36m' +CL3='\033[0m' +NC='\033[1;37m' + +# +# Variables & constants +# +NTHREADS= $(shell nproc) + +CC=gcc +WARNINGS= -Wall +DEBUG= -ggdb -fno-omit-frame-pointer -fdiagnostics-color=always +OPTIMIZE= -O2 +INCLUDE= $(shell pkg-config --cflags glib-2.0 libxml-2.0 gtk4) +LIBS= $(shell pkg-config --libs glib-2.0 libxml-2.0 gtk4) -lGL -lGLU -lm -lepoxy -lX11 -lGLEW + +BINDIR=bin +BUILDDIR=build +SRCDIR=src +#vpath %.c $(SRCDIR) + +SOURCES= $(shell find $(SRCDIR) -type f -name "*.c") +BUILDBINS=$(patsubst %.c,$(BUILDDIR)/%.o,$(SOURCES)) +BUILDDEPS=$(patsubst %.c,$(BUILDDIR)/%.d,$(SOURCES)) + +-include /etc/os-release + +# +# Directories +# +$(BUILDDIR): + @mkdir -p $@ + @echo -e ${CL2}[$@] ${CL}folder generated.${CL3} + +$(BINDIR): + @mkdir -p $@ + @echo -e ${CL2}[$@] ${CL}folder generated.${CL3} + +# +# Dependencies +# +-include $(BUILDDEPS) + +$(BUILDDIR)/%.d: %.c | $(BUILDDIR) + @mkdir -p $(shell dirname $@) + @$(CC) -MM -MT $(@:%.d=%.o) -MF $@ $< + @echo -e ${CL2}[$@] ${CL}dependencies generated.${CL3} + +# +# Compilation +# +$(BINDIR)/gem-graph-client: $(BUILDBINS) | $(BINDIR) + @$(CC) -o $@ $(WARNINGS) $(DEBUG) $(OPTIMIZE) $^ $(INCLUDE) $(LIBS) + @echo -e ${CL2}[$@] ${CL}built.${CL3} + + +$(BUILDDIR)/%.o: %.c | $(BUILDDIR) + @mkdir -p $(shell dirname $@) + @$(CC) $(WARNINGS) $(DEBUG) $(OPTIMIZE) $(INCLUDE) -c $< -o $@ + @echo -e ${CL2}[$@] ${CL}compiled.${CL3} + + +# +# Virtual recipes +# +clean: + @rm -rf $(BINDIR) + @rm -rf $(BUILDDIR) + @echo -e ${CL2}[$@] ${CL}done.${CL3} + +install: + echo "Installing is not supported" + + +ifeq ($(ID), guix) +build_system: + @echo -e ${CL2}[$@] ${CL}building through containerized manifest...${CL3} + @guix shell --container -m ./manifest.scm -- make $(BINDIR)/gem-graph-client -j $(NTHREADS) + @echo -e ${CL2}[$@] ${CL}done.${CL3} +else +build_system: + @echo -e ${CL2}[$@] ${CL}building...${CL3} + @make $(BINDIR)/gem-graph-client -j $(NTHREADS) + @echo -e ${CL2}[$@] ${CL}done.${CL3} +endif + +run: build_system + @echo -e ${CL2}[$@] ${CL}executing...${CL3} + @$(BINDIR)/gem-graph-client + @echo -e ${CL2}[$@] ${CL}done.${CL3} + +all: build_system + @echo -e ${CL2}[$@] ${CL}done.${CL3} + diff --git a/README.md b/README.md new file mode 100644 index 0000000..c06f6d1 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Gem-graph Client + diff --git a/contributing.md b/contributing.md deleted file mode 100644 index 9eb3ab9..0000000 --- a/contributing.md +++ /dev/null @@ -1,194 +0,0 @@ -# Gem-graph Client - -## Procédure de développement (Bonnes pratiques) - -### Une seule branche par fonctionnalité : dev/ - - git fetch origin // récupère les branches sur le remote (git.a-lec.org) - git switch devel // car on part toujours de la branche devel - git branch dev/ - git switch dev/ - git push --set-upstream origin dev/ // dit au serveur que la branche existe - - [...] - -Une fois fonctionnalité terminée et push accompli, ouvrir une merge request à -l'aide du lien fourni par git push - - -### Lors du travail sur une branche (les [...] du premier point) - -Pour chaque session de travail : - - git pull - [...] - git commit -am "WIP: " - git push - -Une fois terminée la fonctionnalité et s'il y a un objectif de version : - - git tag -a vM.m.p -m "Fonctionnalité terminée" - git commit -am "Fonctionnalité terminée" - git push - -## Objectifs de développement - -A gem-graph client can be used to: - -- create or edit a gem-graph model -- control a run of a gem-graph model by a gem-graph server -- get and study the results of such a run - ---- - -In order to execute these functions, it must be able to manage: - -inputs / outputs - - - load and save models - - use xml schemas to check the validity of models - - connect to an instance of gem-graph server - - dialog with this instance - -a GUI able to: - - - render and edit the model - - states - - objects types - - tags and references - - transitions (edition 'de novo' or starting from a state copy) - - transitions trees - - users trees - - - control a run using commands: - - run / stop - - speed up / slow down - - do / undo / redo - - predefine run duration, sampling frequency and nature - - predefine interruption parameters - (according to server performances and/or model results) - - preselect the search algorithm in the conditions tree - - - monitor performances of the server - - - view and analyse the results of a run - - history - - phase diagrams (structures / fluxes) - - statistics - - errors - - - facilitate usage of the software - - preferences - - context info - - help - ---- - -Definitions (see also: gem graph README and gem graph server README) - -1) XML Schema - -- According to the model xml schema, - a gem-graph model must include at least the following data groups: - - + identity (name, owner, creation date, version, references,...) - + parameters (about simulation and space: dimension, size x y z,...) - + objects: each object is a connex graph drawn as a set of arrows - + savestates: each state (objects, situations, tags) is drawn as a set of arrows - + transitions: each transition is defined as two sets of arrows - + conditions: that are specified as nodes in a tree (see below) - - -2) Automaton rewriting a geometric graph - -- A gem-graph is a geometric graph = a graph whose nodes have coordinates in a space. -- A gem-graph model describes an automaton that can rewrite a gem-graph. -- An automaton that can rewrite a gem-graph has to define states and transitions. - -- States belong to a unique global space in which all the transitions occur. -- Each transition rewrites a part of the global space. This part is called the local space. - -- States are drawn in spaces (global or local) using arrows. -- States can be combinations of objects, situations and/or tags. -- Objects and tags are connex graphs (i.e. graphs whose nodes are all connected). -- A situation describes the relative positions of several objects in the global space. -- A tag is a part of an object or situation which encodes a human readable text (annotation, ref, value,...). - -- Several connex graphs can be used to describe the same object. -- An instance of the class object groups all these graphs plus some comments or values. - -- Transitions are defined by an initial and a final local states. -- The initial state of a transition can be evaluated by a set of conditions. -- Each condition is defined as the association of: - - + a location (space unit (x, y, z) and site number) - + an arrow number (or weight) as several arrows can be stacked at the same location - - -- All the conditions can be ordered in a single tree: the 'tree of conditions' (see below). -- In the XML model file, in order to write and read this tree, - each condition has a node identifier (node_id) and a parent. - - -3) Data structures - -- Arrows are represented (encoded) by numbers in sites. -- In each space units is a finite number of sites. -- The site 0 always points towards its own space unit. -- Each other site points towards a neighbouring (not necessarily adjacent) state unit. -- Each site of each space unit can 'contain' 0 or n arrows. -- The set of states of all the sites of all the space units defines a space state. - -- A condition is satisfied if both following quantities are equal: - + the number of arrows (or weights) specified by the condition - + the number of arrows present in the space at the same location specified by the condition. - - -- When conditions are evaluated by the automaton, these two values are compared. -- These evaluations are done by threads (workers) managed by the scheduler of the server. - -- A transition rule associates several conditions to several assignments. -- An assignment instruction assigns a number (n) of arrows to a site of a space unit of the global space. - -- The conditions of a transition rule must all be satisfied for this rule to be applied. - -- Several transition rules can have conditions that read the same site. -- As all the transition rules share at least one common conditions on the local space origin, - all the conditions of all the transition rules can be ordered as a single conditions tree. -- In this conditions tree, each condition is a node and each set of assignments is a leave. -- The root of this tree is the condition on the local space origin. -- The other conditions are met by following a predefined path through all the locations of the local space. -- This path is determined by the user according to the design and performances of the model. -- In the XML model file, in order to write and read the conditions tree, each condition has a node identifier and a parent. - - -4) Algorithms - -- The search of a transition rule in the conditions tree is done by the threads of the scheduler (see gem-graph-server README) -- The result of these searches can be: - + success (the founded rule is applied) - + failure (there is no rule for this situation; the local situation is returned to the client) - + error - -- The rules and the conditions tree are designed by the client and executed by the server scheduler threads. - It is the responsability of the user to manage: - + conditions deficit, conflicts and redundancy - + rules deficit, conflicts and redundancy - + optimisation of the conditions tree by design of an adequate path through all the locations of the local space - + approximations of objects, situations and phenomena - - -5) Meta-edition (future) - -- As mentionned above, several connex graphs can be used to describe the same object. -- An instance of the class object, called a meta-object, then groups all these graphs plus some comments or values. - -- In future versions, the client should provide tools in order to help model designers to edit - + objects from meta-objects. (vectorisation) - + transitions from meta-transitions. (AI ?) - - -NB Adequate data structures should describe these meta-transitions and meta-objets. - - ---- diff --git a/requirements.txt b/data/.gitkeep similarity index 100% rename from requirements.txt rename to data/.gitkeep diff --git a/models/dimers random walk.xml b/data/models/dimers random walk.xml similarity index 100% rename from models/dimers random walk.xml rename to data/models/dimers random walk.xml diff --git a/schemas/models_0.2.1.xmls b/data/schemas/models_0.2.1.xmls similarity index 100% rename from schemas/models_0.2.1.xmls rename to data/schemas/models_0.2.1.xmls diff --git a/data/shader.frag b/data/shader.frag new file mode 100755 index 0000000..fb8a76c --- /dev/null +++ b/data/shader.frag @@ -0,0 +1,9 @@ +#version 330 core + +in vec4 color; +out vec4 out_frag_color; + +void main(void) +{ + out_frag_color = color; +} \ No newline at end of file diff --git a/data/shader.vert b/data/shader.vert new file mode 100755 index 0000000..588e6fa --- /dev/null +++ b/data/shader.vert @@ -0,0 +1,16 @@ +#version 330 core + +uniform mat4 projection_matrix; +uniform mat4 model_matrix; +uniform mat4 view_matrix; + +layout(location=0) in vec3 in_position; +layout(location=1) in vec3 in_color; + +out vec4 color; + +void main(void) +{ + gl_Position = projection_matrix * view_matrix * model_matrix * vec4(in_position, 1); + color = vec4(1 * in_color.rgb, 1); +} \ No newline at end of file diff --git a/description_modes.txt b/description_modes.txt new file mode 100644 index 0000000..e379246 --- /dev/null +++ b/description_modes.txt @@ -0,0 +1,32 @@ +Home: + - Pas de sidebar (inutile) + - Récemment ouverts + - Permettre d'ouvrir directement sans validation (par un "ne pas redemander") + - Logo en grand au centre + +Edit: + - Sidebar présente + - Bibliothèques (par stack) + - objets + - conditions/transitions (liées) + - états sauvegardés + - Zone principale + - vue état globale 3D (en haut ou en bas) avec zoom sur localité + - vue édition (avec split) -> vue 3D aussi ? vue schématique ? + - conditions/transitions + - objets + - états + +Run: + - Sidebar + - Contrôles + - Vitesse + - Pas à pas + - Avance/Recul/Run/Pause (avec vue buffer) + - Bibliothèques (par stack) + - objets + - conditions/transitions (liées) + - états sauvegardés + - Zone principale + - Vue globale 3D (en haut ou bas) + - Mesures (stats par exemple) diff --git a/help/.gitkeep b/help/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/include/base.h b/include/base.h new file mode 100644 index 0000000..493eca0 --- /dev/null +++ b/include/base.h @@ -0,0 +1,34 @@ +/* + * Gem-graph OpenGL experiments + * + * Desc: Base 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 + +enum +{ + X_AXIS, + Y_AXIS, + Z_AXIS, + + N_AXIS +}; diff --git a/include/graphics.h b/include/graphics.h new file mode 100644 index 0000000..c324ef1 --- /dev/null +++ b/include/graphics.h @@ -0,0 +1,43 @@ +/* + * Gem-graph OpenGL experiments + * + * Desc: OpenGL utils 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 + +void graphics_draw(void); + +void graphics_init_buffers( GLuint *vao_out, GLuint *buffer_out, + GLuint *color_buffer_out ); + +void graphics_init_shaders( GLuint *program_out, GLuint *m_out, + GLuint *v_out, GLuint *p_out ); + +void graphics_debug_callback( GLenum source, GLenum type, GLuint id, + GLenum severity, GLsizei length, const GLchar *msg, + const void *data ); + +void graphics_init(void); + +void graphics_shutdown(void); diff --git a/include/ui.h b/include/ui.h new file mode 100644 index 0000000..d91abbb --- /dev/null +++ b/include/ui.h @@ -0,0 +1,149 @@ +/* + * 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 + +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 + + +// +// Actions +// +void on_about_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data); + +void on_quit_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data); + +void on_preferences_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data); + +void on_togglesidebar_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data); + +void on_editmode_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data); + +void on_runmode_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data); + +void on_presentmode_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data); + +void on_openfile_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data); + +void on_closefile_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data); + +void on_savefile_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data); + +void on_toast_close_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data); + + +static const GActionEntry app_actions[] = { + { "quit", on_quit_action, NULL, NULL, NULL }, + { "about", on_about_action, NULL, NULL, NULL }, + { "preferences", on_preferences_action, NULL, NULL, NULL }, + { "togglesidebar", on_togglesidebar_action, NULL, NULL, NULL }, + { "editmode", on_editmode_action, NULL, NULL, NULL }, + { "runmode", on_runmode_action, NULL, NULL, NULL }, + { "presentmode", on_presentmode_action, NULL, NULL, NULL }, + { "openfile", on_openfile_action, NULL, NULL, NULL }, + { "closefile", on_closefile_action, NULL, NULL, NULL }, + { "savefile", on_savefile_action, NULL, NULL, NULL }, + { "toastclose", on_toast_close_action, NULL, NULL, NULL }, +}; + +void ui_enable_action(const char *name); + +void ui_disable_action(const char *name); + +// +// Actions responses +// +void on_openfile_response(GtkNativeDialog *native, + int response, + GemGraphClientWindow *self); + +void on_openfile_response_complete(GObject *source_object, + GAsyncResult *result, + GemGraphClientWindow *self); + +/* -------------------------------------------------------------------------- */ + +// +// Window primitives +// + +void ui_set_stack(const char *mode); + +void ui_send_notification(const char *message); +void ui_send_internal_notification(const char *message); +void ui_close_internal_notification(void); +void ui_setup_glarea(); diff --git a/manifest.scm b/manifest.scm new file mode 100644 index 0000000..d77a78b --- /dev/null +++ b/manifest.scm @@ -0,0 +1,27 @@ +;; +;; Dépendances sous GNU Guix +;; + + +(specifications->manifest + (list + "bash" + "coreutils" + "gcc-toolchain" + "pkg-config" + "findutils" + "make" + "gtk" + "libxml2" + "glu" + "glew" + "glfw" + "libepoxy" + "pango@1.90.0" + "xorgproto" + "glib" + "mesa-headers" + "mesa" + "libadwaita" + ) +) \ No newline at end of file diff --git a/models/testing.xml b/models/testing.xml deleted file mode 100644 index 2a30294..0000000 --- a/models/testing.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - Modèle de test - Gaston Lagaffe - 2 - 1629830000 - 1.0 - Ref - - - - - 0 - 9 - - - - - - - - - - - - - - - 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/old_python_code/__init__.py b/old_python_code/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/old_python_code/__pycache__/__init__.cpython-310.pyc b/old_python_code/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..49346e5 Binary files /dev/null and b/old_python_code/__pycache__/__init__.cpython-310.pyc differ diff --git a/old_python_code/__pycache__/__init__.cpython-39.pyc b/old_python_code/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..3ef758d Binary files /dev/null and b/old_python_code/__pycache__/__init__.cpython-39.pyc differ diff --git a/old_python_code/__pycache__/main.cpython-310.pyc b/old_python_code/__pycache__/main.cpython-310.pyc new file mode 100644 index 0000000..b1bd1e2 Binary files /dev/null and b/old_python_code/__pycache__/main.cpython-310.pyc differ diff --git a/old_python_code/__pycache__/main.cpython-39.pyc b/old_python_code/__pycache__/main.cpython-39.pyc new file mode 100644 index 0000000..d8e78b1 Binary files /dev/null and b/old_python_code/__pycache__/main.cpython-39.pyc differ diff --git a/old_python_code/__pycache__/window.cpython-310.pyc b/old_python_code/__pycache__/window.cpython-310.pyc new file mode 100644 index 0000000..650c61e Binary files /dev/null and b/old_python_code/__pycache__/window.cpython-310.pyc differ diff --git a/old_python_code/__pycache__/window.cpython-39.pyc b/old_python_code/__pycache__/window.cpython-39.pyc new file mode 100644 index 0000000..e97dd49 Binary files /dev/null and b/old_python_code/__pycache__/window.cpython-39.pyc differ diff --git a/old_python_code/gemgraph.py b/old_python_code/gemgraph.py new file mode 100755 index 0000000..df3dfbc --- /dev/null +++ b/old_python_code/gemgraph.py @@ -0,0 +1,47 @@ +#!/bin/python3 + +# Copyright (C) 2022 Libre en Communs +# Copyright (C) 2022 Adrien Bourmault +# Copyright (C) 2022 Jean Sirmai +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import sys +import signal +import locale +import gettext + +VERSION = '0.0.1' +#pkgdatadir = '@pkgdatadir@' +#localedir = '@localedir@' + +#sys.path.insert(1, pkgdatadir) +signal.signal(signal.SIGINT, signal.SIG_DFL) +#locale.bindtextdomain('gemgraph', localedir) +#locale.textdomain('gemgraph') +#gettext.install('gemgraph', localedir) + +if __name__ == "__main__" and __package__ is None: + __package__ = "gemgraph" + +if __name__ == '__main__': + import gi + + from gi.repository import Gio + #resource = Gio.Resource.load(os.path.join(pkgdatadir, 'gemgraph.gresource')) + #resource._register() + + from gemgraph import main + sys.exit(main.main(VERSION)) diff --git a/old_python_code/main.py b/old_python_code/main.py new file mode 100644 index 0000000..c3c9031 --- /dev/null +++ b/old_python_code/main.py @@ -0,0 +1,207 @@ +# main.py +# +# Copyright (C) 2022 Libre en Communs +# Copyright (C) 2022 Adrien Bourmault +# Copyright (C) 2022 Jean Sirmai +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import sys +import gi + +gi.require_version('Gtk', '4.0') +gi.require_version('Adw', '1') + +from gi.repository import Gtk, Gio, Adw +from .window import GemGraphWindow, AboutDialog + + +ENABLE = True +DISABLE = False + + +class GemGraphApplication(Adw.Application): + """The main application singleton class.""" + + def __init__(self): + super().__init__(application_id='org.example.App', + flags=Gio.ApplicationFlags.FLAGS_NONE) + + # Create base actions + self.create_action('quit', self.quit, ENABLE, ['q']) + self.create_action('about', self.on_about_action, ENABLE) + self.create_action('preferences', self.on_preferences_action, ENABLE) + self.create_action('togglesidebar', self.on_togglesidebar_action, ENABLE) + + # Create later actions (mode) + self.editmode_action = \ + self.create_action( + 'editmode', + self.on_editmode_action, + DISABLE, + ['e']) + + self.runmode_action = \ + self.create_action( + 'runmode', + self.on_runmode_action, + DISABLE, + ['r']) + + self.presentmode_action = \ + self.create_action( + 'presentmode', + self.on_presentmode_action, + DISABLE, + ['p']) + + # Create file actions + self.create_action('openfile', self.do_open, ENABLE) + + self.close_action = \ + self.create_action( + 'closefile', + self.on_close_action, + DISABLE) + + self.save_action = \ + self.create_action( + 'savefile', + self.on_save_action, + DISABLE, + ['s']) + + def do_activate(self): + """Called when the application is activated. + + We raise the application's main window, creating it if + necessary. + """ + print("app.do_activate called") + win = self.props.active_window + if not win: + win = GemGraphWindow(application=self) + + # Display home mode by default + win.stack_switch_mode("home") + win.present() + + def do_open(self, widget, _): + """Called when the application is activated with a file to open. + + We raise the application's main window, creating it if + necessary. + + Also a callback for the app.openfile action + """ + print("app.do_open called") + win = self.props.active_window + if not win: + win = GemGraphWindow(application=self) + + # Open file + pass + + # Activate file actions + self.close_action.set_enabled(True) + self.save_action.set_enabled(True) + + # Activate mode actions + self.runmode_action.set_enabled(True) + self.editmode_action.set_enabled(True) + self.presentmode_action.set_enabled(True) + + # Display edit mode + win.stack_switch_mode("edit") + win.present() + + def on_close_action(self, widget, _): + """Callback for the app.closefile action.""" + print('app.closefile action activated') + win = self.props.active_window + + # Disable file actions + self.close_action.set_enabled(False) + self.save_action.set_enabled(False) + + # Disable mode actions + self.runmode_action.set_enabled(False) + self.editmode_action.set_enabled(False) + self.presentmode_action.set_enabled(False) + + # Get home + win.stack_switch_mode("home") + + def on_save_action(self, widget, _): + """Callback for the app.savefile action.""" + print('app.savefile action activated') + win = self.props.active_window + + def on_about_action(self, widget, _): + """Callback for the app.about action.""" + about = AboutDialog(self.props.active_window) + about.present() + + def on_preferences_action(self, widget, _): + """Callback for the app.preferences action.""" + print('app.preferences action activated') + win = self.props.active_window + win.send_toast("Not implemented at this time (sorry!)") + + def on_editmode_action(self, widget, _): + """Callback for the app.editmode action.""" + print('app.editmode action activated') + win = self.props.active_window + win.stack_switch_mode("edit") + + def on_runmode_action(self, widget, _): + """Callback for the app.runmode action.""" + print('app.runmode action activated') + win = self.props.active_window + win.stack_switch_mode("run") + + def on_presentmode_action(self, widget, _): + """Callback for the app.presentmode action.""" + print('app.presentmode action activated') + win = self.props.active_window + win.stack_switch_mode("presentation") + + def on_togglesidebar_action(self, widget, _): + """Callback for the app.togglesidebar action.""" + print('app.togglesidebar action activated') + win = self.props.active_window + win.toggle_sidebar() + + def create_action(self, name, callback, is_enabled, shortcuts=None): + """Add an application action. + + Args: + name: the name of the action + callback: the function to be called when the action is + activated + shortcuts: an optional list of accelerators + """ + action = Gio.SimpleAction.new(name, None) + action.connect("activate", callback) + action.set_enabled(is_enabled) + self.add_action(action) + if shortcuts: + self.set_accels_for_action(f"app.{name}", shortcuts) + return action + + +def main(version): + """The application's entry point.""" + app = GemGraphApplication() + return app.run(sys.argv) diff --git a/old_python_code/window.py b/old_python_code/window.py new file mode 100644 index 0000000..1b0be65 --- /dev/null +++ b/old_python_code/window.py @@ -0,0 +1,82 @@ +# window.py +# +# Copyright (C) 2022 Libre en Communs +# Copyright (C) 2022 Adrien Bourmault +# Copyright (C) 2022 Jean Sirmai +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from gi.repository import Gtk, Adw + + +@Gtk.Template(filename='ui/gemgraph.ui') +class GemGraphWindow(Gtk.ApplicationWindow): + """ + Window class + """ + __gtype_name__ = 'main' + + main_stack = Gtk.Template.Child() + side_stack = Gtk.Template.Child() + main_paned = Gtk.Template.Child() + main_button_mode = Gtk.Template.Child() + toast_overlay = Gtk.Template.Child() + main_button_sidebar = Gtk.Template.Child() + + __mode_icon = { + "mode_home":"user-home-symbolic" , + "mode_edit":"document-edit-symbolic" , + "mode_run":"system-run-symbolic" , + "mode_presentation":"x-office-presentation-symbolic" + } + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def toggle_sidebar(self): + """ + Sets the sidebar position to open or closed + """ + position = self.main_paned.get_position() + + if position != 0: + self.main_paned.set_position(0) + else: + self.main_paned.set_position(300) + + self.main_button_sidebar.toggled() + + def stack_switch_mode(self, mode): + """ + Sets the active mode from stack. Mode is the name of the mode (string) + """ + self.main_stack.set_visible_child_full("main_"+mode, Gtk.StackTransitionType.CROSSFADE) + self.side_stack.set_visible_child_full("side_"+mode, Gtk.StackTransitionType.CROSSFADE) + self.main_button_mode.props.icon_name = self.__mode_icon["mode_"+mode] + + def send_toast(self, message): + self.toast_overlay.add_toast(Adw.Toast(title=message)) + +class AboutDialog(Gtk.AboutDialog): + + def __init__(self, parent): + Gtk.AboutDialog.__init__(self) + self.props.program_name = 'GemGraph' + self.props.version = "0.0.0" + self.props.authors = ['Adrien Bourmault', 'Jean Sirmai'] + self.props.copyright = 'Copyright © 2022 Libre en Communs' + self.props.logo_icon_name = 'application-x-executable' + self.props.modal = True + self.set_transient_for(parent) + diff --git a/po/.gitkeep b/po/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/graphics/graphics.c b/src/graphics/graphics.c new file mode 100644 index 0000000..af77a1c --- /dev/null +++ b/src/graphics/graphics.c @@ -0,0 +1,509 @@ +/* + * Gem-graph OpenGL experiments + * + * Desc: GL 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 +#include +#include +#include +#include + +#include "../../include/base.h" +#include "../../include/ui.h" + +#define VERTEX_SHADER_FILE "data/shader.vert" +#define FRAG_SHADER_FILE "data/shader.frag" + +/* -------------------------------------------------------------------------- */ + +static GLuint position_buffer; +static GLuint color_buffer; +static GLuint program; +static GLuint m_location; +static GLuint v_location; +static GLuint p_location; +static GLuint indices_nb; + +// v4----- v5 +// /| /| +// v1------v0| +// | | | | +// | |v7---|-|v6 +// |/ |/ +// v2------v3 +// +static GLfloat vertex_base[] = { + 0.5, 0.5, 0.5, // v0 + -0.5, 0.5, 0.5, // v1 + -0.5,-0.5, 0.5, // v2 + 0.5,-0.5, 0.5, // v3 + 0.5, 0.5,-0.5, // v4 + -0.5, 0.5,-0.5, // v5 + -0.5,-0.5,-0.5, // v6 + 0.5,-0.5,-0.5, // v7 +}; + +static GLubyte indices[] = { + 0,1, + 1,2, + 2,3, + 3,0, + + 4,5, + 5,6, + 6,7, + 7,4, + + 0,4, + 1,5, + 2,6, + 3,7, +}; + + +static GLfloat color_base[] = { + 0.8, 0.8, 0.8, // blanc + 0.8, 0.8, 0.2, // jaune + 0.8, 0.2, 0.2, // rouge + 0.2, 0.2, 0.2, // noir + 0.2, 0.2, 0.2, // gris + 0.2, 0.8, 0.8, // cyan + 0.2, 0.8, 0.2, // vert + 0.8, 0.2, 0.8, // magenta +}; + +/* -------------------------------------------------------------------------- */ + +void graphics_debug_callback( GLenum source, GLenum type, GLuint id, GLenum severity, + GLsizei length, const GLchar *msg, const void *data ); + +static inline void compute_i(float *res) +{ + /* initialize to the identity matrix */ + res[0] = 1.f; res[4] = 0.f; res[8] = 0.f; res[12] = 0.f; + res[1] = 0.f; res[5] = 1.f; res[9] = 0.f; res[13] = 0.f; + res[2] = 0.f; res[6] = 0.f; res[10] = 1.f; res[14] = 0.f; + res[3] = 0.f; res[7] = 0.f; res[11] = 0.f; res[15] = 1.f; +} + +static inline void compute_mvp(float *res, float phi, float theta, float psi) +{ + float x = phi *(G_PI / 180.f); + float y = theta *(G_PI / 180.f); + float z = psi *(G_PI / 180.f); + float c1 = cosf(x), s1 = sinf(x); + float c2 = cosf(y), s2 = sinf(y); + float c3 = cosf(z), s3 = sinf(z); + float c3c2 = c3 * c2; + float s3c1 = s3 * c1; + float c3s2s1 = c3 * s2 * s1; + float s3s1 = s3 * s1; + float c3s2c1 = c3 * s2 * c1; + float s3c2 = s3 * c2; + float c3c1 = c3 * c1; + float s3s2s1 = s3 * s2 * s1; + float c3s1 = c3 * s1; + float s3s2c1 = s3 * s2 * c1; + float c2s1 = c2 * s1; + float c2c1 = c2 * c1; + + compute_i(res); + + /* apply all three rotations using the three matrices: + * + * ⎡ c3 s3 0 ⎤ ⎡ c2 0 -s2 ⎤ ⎡ 1 0 0 ⎤ + * ⎢ -s3 c3 0 ⎥ ⎢ 0 1 0 ⎥ ⎢ 0 c1 s1 ⎥ + * ⎣ 0 0 1 ⎦ ⎣ s2 0 c2 ⎦ ⎣ 0 -s1 c1 ⎦ + */ + res[0] = c3c2; res[4] = s3c1 + c3s2s1; res[8] = s3s1 - c3s2c1; res[12] = 0.f; + res[1] = -s3c2; res[5] = c3c1 - s3s2s1; res[9] = c3s1 + s3s2c1; res[13] = 0.f; + res[2] = s2; res[6] = -c2s1; res[10] = c2c1; res[14] = 0.f; + res[3] = 0.f; res[7] = 0.f; res[11] = 0.f; res[15] = 1.f; + +} + +/* Create and compile a shader */ +static GLuint create_shader(int type, const char *src) +{ + GLuint shader; + int status; + + shader = glCreateShader(type); + glShaderSource(shader, 1, &src, NULL); + glCompileShader(shader); + + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if(status == GL_FALSE) { + int log_len; + char *buffer; + + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len); + + buffer = g_malloc(log_len + 1); + glGetShaderInfoLog(shader, log_len, NULL, buffer); + + g_warning("Compile failure in %s shader:\n%s", + type == GL_VERTEX_SHADER ? "vertex" : "fragment", + buffer); + + g_free(buffer); + + glDeleteShader(shader); + + return 0; + } + + return shader; +} + +static inline char *read_shader_file(char *filename) +{ + int fd = open(filename, O_RDONLY); + + if(fd < 0) { + printf("Couldn't read shader: %s\n",filename); + return NULL; + } + + int shader_size = lseek(fd, 0, SEEK_END) +1 ; + char *shader_contents = malloc(sizeof(char) * shader_size); + + lseek(fd, 0, SEEK_SET); + read(fd,shader_contents,shader_size); + + shader_contents[shader_size-1]='\0'; + + //printf("Shader %s content is :\n%s\n", filename, shader_contents); + + close(fd); + + return shader_contents; +} + +/* -------------------------------------------------------------------------- */ + +void graphics_draw(void) +{ + float m[16]; + float v[16]; + float p[16]; + + + /* Compute the model view projection matrix using the + * rotation angles specified through the GtkRange widgets + */ + compute_mvp(p, + rotation_angles[X_AXIS], + rotation_angles[Y_AXIS], + rotation_angles[Z_AXIS]); + compute_i(m); + compute_i(v); + + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + /* Use our shaders */ + glUseProgram(program); + + /* Update the "mvp" matrix we use in the shader */ + glUniformMatrix4fv(m_location, 1, GL_FALSE, &m[0]); + glUniformMatrix4fv(v_location, 1, GL_FALSE, &v[0]); + glUniformMatrix4fv(p_location, 1, GL_FALSE, &p[0]); + + /* Use the vertices in our buffer */ + glEnableVertexAttribArray(0); + glBindBuffer(GL_ARRAY_BUFFER, position_buffer); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0,(void*)0); + + // couleurs + glEnableVertexAttribArray(1); + glBindBuffer(GL_ARRAY_BUFFER, color_buffer); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0,(void*)0); + + glEnable(GL_DEPTH_TEST); + + glDrawElements(GL_LINES, indices_nb, GL_UNSIGNED_BYTE, indices); + + /* We finished using the buffers and program */ + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glUseProgram(0); + + glFlush(); +} + + +/* Initialize the GL buffers */ +void graphics_init_buffers( GLuint *vao_out, GLuint *buffer_out, + GLuint *color_buffer_out) +{ + GLuint vao, vertex_buffer, color_buffer; + + indices_nb = sizeof(indices) / sizeof(indices[0]); + + int vertex_nb = indices_nb * 3; + + + printf("Initialization of buffers with %u indices (=%u vertex)\n", + indices_nb, + vertex_nb); + + printf("Real vertex number is %lu long\n", + sizeof(vertex_base) / sizeof(vertex_base[0])); + + // We only use one VAO, so we always keep it bound + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + // vertices + glGenBuffers(1, &vertex_buffer); + glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_base), vertex_base, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + // colors + glGenBuffers(1, &color_buffer); + glBindBuffer(GL_ARRAY_BUFFER, color_buffer); + glBufferData(GL_ARRAY_BUFFER, sizeof(color_base), color_base, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + if(vao_out != NULL) + *vao_out = vao; + + if(buffer_out != NULL) + *buffer_out = vertex_buffer; + + if(color_buffer_out != NULL) + *color_buffer_out = color_buffer; +} + + + +/* Initialize the shaders and link them into a program */ +void graphics_init_shaders( GLuint *program_out, GLuint *m_out, + GLuint *v_out, GLuint *p_out) +{ + GLuint vertex, fragment; + GLuint program = 0; + GLuint m = 0; + GLuint v = 0; + GLuint p = 0; + int status; + + vertex = create_shader(GL_VERTEX_SHADER, read_shader_file(VERTEX_SHADER_FILE)); + + if(vertex == 0) { + *program_out = 0; + return; + } + + fragment = create_shader(GL_FRAGMENT_SHADER, read_shader_file(FRAG_SHADER_FILE)); + + if(fragment == 0) { + glDeleteShader(vertex); + *program_out = 0; + return; + } + + program = glCreateProgram(); + glAttachShader(program, vertex); + glAttachShader(program, fragment); + + glLinkProgram(program); + + glGetProgramiv(program, GL_LINK_STATUS, &status); + + if(status == GL_FALSE) { + int log_len; + char *buffer; + + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len); + + buffer = g_malloc(log_len + 1); + glGetProgramInfoLog(program, log_len, NULL, buffer); + + g_warning("Linking failure:\n%s", buffer); + + g_free(buffer); + + glDeleteProgram(program); + program = 0; + + goto out; + } + + /* Get the location of the "mvp" uniform */ + m = glGetUniformLocation(program, "model_matrix"); + v = glGetUniformLocation(program, "view_matrix"); + p = glGetUniformLocation(program, "projection_matrix"); + + glDetachShader(program, vertex); + glDetachShader(program, fragment); + +out: + glDeleteShader(vertex); + glDeleteShader(fragment); + + if(program_out != NULL) + *program_out = program; + + if(m_out != NULL) + *m_out = m; + + if(v_out != NULL) + *v_out = v; + + if(p_out != NULL) + *p_out = p; +} + +void graphics_init(void) +{ + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glEnable(GL_MULTISAMPLE); + //glEnable(GL_POLYGON_SMOOTH); + //glEnable(GL_PROGRAM_POINT_SIZE); + + graphics_init_buffers(NULL, &position_buffer, &color_buffer); + graphics_init_shaders(&program, &m_location, &v_location, &p_location); + glDebugMessageCallback(graphics_debug_callback, NULL); +} + +void graphics_shutdown(void) +{ + glDeleteBuffers(1, &position_buffer); + glDeleteBuffers(1, &color_buffer); + glDeleteProgram(program); +} + + +/* -------------------------------------------------------------------------- */ + +void graphics_debug_callback( GLenum source, GLenum type, GLuint id, GLenum severity, + GLsizei length, const GLchar *msg, const void *data ) +{ + const char *errsource; + const char *errtype; + const char *errseverity; + const GLubyte *string; + GLenum code; + + switch (source) { + case GL_DEBUG_SOURCE_API: + errsource = "API"; + break; + + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: + errsource = "WINDOW SYSTEM"; + break; + + case GL_DEBUG_SOURCE_SHADER_COMPILER: + errsource = "SHADER COMPILER"; + break; + + case GL_DEBUG_SOURCE_THIRD_PARTY: + errsource = "THIRD PARTY"; + break; + + case GL_DEBUG_SOURCE_APPLICATION: + errsource = "APPLICATION"; + break; + + case GL_DEBUG_SOURCE_OTHER: + errsource = "UNKNOWN"; + break; + + default: + errsource = "UNKNOWN"; + break; + } + + switch (type) { + case GL_DEBUG_TYPE_ERROR: + errtype = "ERROR"; + break; + + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: + errtype = "DEPRECATED BEHAVIOR"; + break; + + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: + errtype = "UNDEFINED BEHAVIOR"; + break; + + case GL_DEBUG_TYPE_PORTABILITY: + errtype = "PORTABILITY"; + break; + + case GL_DEBUG_TYPE_PERFORMANCE: + errtype = "PERFORMANCE"; + break; + + case GL_DEBUG_TYPE_OTHER: + errtype = "OTHER"; + break; + + case GL_DEBUG_TYPE_MARKER: + errtype = "MARKER"; + break; + + default: + errtype = "UNKNOWN"; + break; + } + + switch (severity) { + case GL_DEBUG_SEVERITY_HIGH: + errseverity = "CRITICAL"; + break; + + case GL_DEBUG_SEVERITY_MEDIUM: + errseverity = "ERROR"; + break; + + case GL_DEBUG_SEVERITY_LOW: + errseverity = "WARNING"; + break; + + case GL_DEBUG_SEVERITY_NOTIFICATION: + errseverity = "INFO"; + break; + + default: + errseverity = "UNKNOWN"; + break; + } + + code = glGetError(); + string = gluErrorString(code); + + printf("[%s] %s (%s) from %s: %s\n", + errseverity, string, errtype, errsource, msg); +} + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..235a97e --- /dev/null +++ b/src/main.c @@ -0,0 +1,48 @@ +/* + * Gem-graph OpenGL experiments + * + * Desc: Main file + * + * 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/base.h" +#include "../include/ui.h" + +/* -------------------------------------------------------------------------- */ + +int main(int argc, char **argv) +{ + g_autoptr(GemGraphClientApplication) app = NULL; + int res; + + // bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + // bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + // textdomain (GETTEXT_PACKAGE); + + app = gem_graph_client_application_new("org.alec.gemgraph", + G_APPLICATION_DEFAULT_FLAGS); + + res = g_application_run(G_APPLICATION(app), argc, argv); + return res; +} + diff --git a/src/main.py b/src/main.py deleted file mode 100644 index f2a95fb..0000000 --- a/src/main.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/python3 -# ------------------------------------------------------------------------- # -# Client main file # -# # -# Copyright © 2021 The Gem-graph Project # -# # -# 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 . # -# ------------------------------------------------------------------------- # - -import gi -gi.require_version("Gtk", "3.0") -from gi.repository import Gtk - -glade_path = "../ui/GLArea.glade" - -## Main window - -builder = Gtk.Builder() -builder.add_from_file(glade_path) - -win = builder.get_object("Appwindow") -win.show_all() diff --git a/src/model.py b/src/model.py deleted file mode 100644 index cabf07c..0000000 --- a/src/model.py +++ /dev/null @@ -1,414 +0,0 @@ -from lxml import etree -import datetime as DT - - -## Identity - - -class G_ref: - def __init__(self, node): - self.node = node - - def get_id(self): - return self.node.get("id") - - def set_id(self, id): - self.node.attrib["id"] = id - - def get_author(self): - return self.node.get("author") - - def set_author(self, author): - self.node.attrib["author"] = author - - def get_date(self): - return DT.datetime.fromtimestamp(int(self.node.get("date"))) - - def set_date(self, date): - self.node.attrib["date"] = str(int(date.timestamp())) - - def get_lang(self): - return self.node.get("lang") - - def set_lang(self, lang): - if(len(lang) > 2): - print("lang must be 2 characters") - return - self.node.attrib["lang"] = lang - - def get_text(self): - return self.node.text - - def set_text(self, text): - self.node.text = text - - -class Identity: - def __init__(self, node): - self.node = node - for child in node: - if(child.tag == "name"): - self.__name = child - if(child.tag == "owner"): - self.__owner = child - if(child.tag == "owner_id"): - self.__owner_id = child - if(child.tag == "date"): - self.__date = child - if(child.tag == "version"): - self.__version = child - if(child.tag == "g_ref"): - self.__g_ref = child - - def get_g_ref(self): - return G_ref(self.__g_ref) - - def get_name(self): - return self.__name.text - - def get_owner(self): - return self.__owner.text - - def get_owner_id(self): - return self.__owner_id.text - - def get_date(self): - return DT.datetime.fromtimestamp(int(self.__date.text)) - - def get_version(self): - return self.__version.text - - def set_name(self, text): - self.__name.text = text - - def set_owner(self, text): - self.__owner.text = text - - def set_owner_id(self, text): - self.__owner_id.text = text - - def set_date(self, date): - self.__date.text = str(int(date.timestamp())) - - def set_version(self, text): - self.__version.text = text - -## Parameters - - -class Simulation: - - def __init__(self, node): - self.node = node - for child in node: - if(child.tag == "max_thread"): - self.__max_thread = child - if(child.tag == "max_cycles"): - self.__max_cycles = child - - def get_max_thread(self): - return int(self.__max_thread.text) - - def set_max_thread(self, value): - if(not isinstance(value, int)): - print("max_thread must be an int") - return - self.__max_thread.text = str(value) - - def get_max_cycles(self): - return int(self.__max_cycles.text) - - def set_max_cycles(self, value): - if(not isinstance(value, int)): - print("max_cycles must be an int") - return - self.__max_cycles.text = str(value) - - -class Spaceparam: - def __init__(self, node): - self.node = node - for child in node: - if(child.tag == "dimension"): - self.__dimension = Dimension(child) - if(child.tag == "site_multiplicity"): - self.__site_multiplicity = child - - def get_site_multiplicity(self): - return self.__site_multiplicity.text - - def set_site_multiplicity(self, value): - self.__site_multiplicity.text = str(value) - - def get_dimension(self): - return self.__dimension - - -class Axis: - def __init__(self, node): - self.node = node - - def get_name(self): - return self.node.attrib["name"] - - def get_size(self): - return int(self.node.attrib["size"]) - - -class Dimension: - __axis = [] - - def __init__(self, node): - self.node = node - for child in node: - if(child.tag == "axis"): - self.__axis.append(Axis(child)) - - def get_axis(self, i): - return self.__axis[i] - - -class Parameters: - - def __init__(self, node): - self.node = node - for child in node: - if(child.tag == "simulation"): - self.simulation = Simulation(child) - if(child.tag == "space-param"): - self.spaceparam = Spaceparam(child) - - def get_id(self): - return self.node.attrib["id"] - - def set_id(self, text): - self.node.attrib["id"] = text - - def get_author(self): - return self.node.attrib["author"] - - def get_date(self): - return self.node.attrib["date"] - - def set_author(self, text): - self.node.attrib["author"] = text - - def set_date(self, text): - self.node.attrib["date"] = text - -## Objects - - -class Object: - - __arrows = [] - - def __init__(self, node): - self.node = node - for child in node: - if(child.tag == "arrow"): - self.__arrows.append(Arrow(child)) - - def get_arrow(self, i): - return self.__arrows[i] - - -class Objects: - __objects = [] - - def __init__(self, node): - self.node = node - for child in node: - if(child.tag == "object"): - self.__objects.append(Object(child)) - - def get_object(self, i): - return self.__objects[i] - -## Savestates - - -class Space: - __arrows = [] - - def __init__(self, node): - self.node = node - for child in node: - if(child.tag == "arrow"): - self.__arrows.append(Arrow(child)) - - def get_arrow(self, i): - return self.__arrows[i] - - -class Savestates: - __space = [] - - def __init__(self, node): - self.node = node - for child in node: - if(child.tag == "space"): - self.__space.append(Space(child)) - - def get_space(self, i): - return self.__space[i] - -## Conditions - - -class Condition: - def __init__(self, node): - self.node = node - - def get_coord(self, axis): - return int(self.node.get(axis)) - - def set_coord(self, axis, value): - self.node.attrib[axis] = str(value) - - def get_site(self): - return int(self.node.get("site")) - - def set_site(self, site): - self.node.attrib["site"] = str(site) - - def get_weight(self): - return int(self.node.get("weight")) - - def set_weight(self, weight): - self.node.attrib["weight"] = str(weight) - - def get_node_id(self): - return int(self.node.get("node_id")) - - def set_node_id(self, node_id): - self.node.attrib["node_id"] = str(node_id) - - def get_parent(self): - return int(self.node.get("parent")) - - def set_parent(self, parent): - self.node.attrib["parent"] = str(parent) - - -class Conditions: - __conditions = [] - - def __init__(self, node): - self.node = node - for child in node: - if(child.tag == "condition"): - self.__conditions.append(Condition(child)) - - -class Transition: - __arrows = [] - - def __init__(self, node): - self.node = node - for child in node: - if(child.tag == "arrow"): - self.__arrows.append(Arrow(child)) - - def get_arrow(self, i): - return self.__arrows[i] - - -class Transitions: - __transitions = [] - - def __init__(self, node): - self.node = node - for child in node: - if(child.tag == "transition"): - self.__transition.append(Transition(child)) - - def get_transition(self, i): - return self.__transitions[i] - - def get_transition_array(self, i): - return self.__transitions - -## Model - - -class Arrow: - - def __init__(self, node): - self.node = node - - def get_coord(self, axis): - return int(self.node.get(axis)) - - def set_coord(self, axis, value): - self.node.attrib[axis] = str(value) - - def get_site(self): - return int(self.node.get("site")) - - def set_site(self, site): - self.node.attrib["site"] = str(site) - - def get_weight(self): - return int(self.node.get("weight")) - - def set_weight(self, weight): - self.node.attrib["weight"] = str(weight) - - -class Model: - - def __init__(self, modelpath, schemapath): - schema = etree.parse(schemapath) - xmlschema = etree.XMLSchema(schema) - self.parser = etree.XMLParser( - remove_blank_text=True, - remove_pis=True, - compact=True, - schema=xmlschema) - self.tree = etree.parse(modelpath, self.parser) - self.node = self.tree.getroot() - self.create_all_leaves() - - def create_all_leaves(self): - for child in self.node: - if(isinstance(child.tag, str)): - if(child.tag == "identity"): - self.__identity = child - if(child.tag == "parameters"): - self.__parameters = child - if(child.tag == "objects"): - self.__objects = child - if(child.tag == "savestates"): - self.__savestates = child - if(child.tag == "conditions"): - self.__conditions = child - if(child.tag == "transition"): - self.__transitions = child - - def get_identity(self): - return Identity(self.__identity) - - def get_parameters(self): - return Parameters(self.__parameters) - - def get_objects(self): - return Objects(self.__objects) - - def get_savestates(self): - return Savestates(self.__savestates) - - def get_conditions(self): - return Conditions(self.__conditions) - - def get_transitions(self): - return Transitions(self.__transitions) - - def saveas(self, name): - self.tree.write("../models/" + name + ".xml", encoding="utf-8") - - -modelPath = "../models/dimers random walk.xml" -schemaPath = "../schemas/models_0.2.xmls" - -testmodel = Model(modelPath, schemaPath) diff --git a/src/ui/.gitkeep b/src/ui/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/ui/actions.c b/src/ui/actions.c new file mode 100644 index 0000000..5ef3441 --- /dev/null +++ b/src/ui/actions.c @@ -0,0 +1,248 @@ +/* + * 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/base.h" +#include "../../include/ui.h" + +void on_about_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + static const char *authors[] = { "Adrien Bourmault (neox@a-lec.org)", + "Jean Sirmai (jean@a-lec.org)", + "Arthur Menges (arthur.menges@a-lec.org)", + 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", + "logo-icon-name", "application-x-executable", + "authors", authors, + "version", "0.1.0", + "copyright", "Copyright © 2023 Libre en Communs", + NULL); +} + +void on_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)); +} + +void on_preferences_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GemGraphClientApplication *self = user_data; + + g_assert(GEM_GRAPH_CLIENT_IS_APPLICATION(self)); + + ui_send_internal_notification("Not implemented !"); +} + +void on_togglesidebar_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GemGraphClientApplication *self = user_data; + + g_assert(GEM_GRAPH_CLIENT_IS_APPLICATION(self)); + + ui_send_internal_notification("Not implemented !"); +} + +void on_editmode_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GemGraphClientApplication *self = user_data; + + g_assert(GEM_GRAPH_CLIENT_IS_APPLICATION(self)); + + ui_set_stack("edition"); +} + +void on_runmode_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GemGraphClientApplication *self = user_data; + + g_assert(GEM_GRAPH_CLIENT_IS_APPLICATION(self)); + + ui_set_stack("run"); +} + +void on_presentmode_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GemGraphClientApplication *self = user_data; + + g_assert(GEM_GRAPH_CLIENT_IS_APPLICATION(self)); + + ui_set_stack("presentation"); +} + +void on_openfile_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GemGraphClientApplication *self = user_data; + + g_assert(GEM_GRAPH_CLIENT_IS_APPLICATION(self)); + + // Create a new file selection dialog, using the "open" mode + GtkFileChooserNative *native = + gtk_file_chooser_native_new("Open File", + GTK_WINDOW(self), + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Open", + "_Cancel"); + + // Connect the "response" signal of the file selection dialog; + // this signal is emitted when the user selects a file, or when + // they cancel the operation + g_signal_connect (native, + "response", + G_CALLBACK(on_openfile_response), + self); + + // Present the dialog to the user + gtk_native_dialog_show (GTK_NATIVE_DIALOG (native)); +} + +void on_closefile_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GemGraphClientApplication *self = user_data; + + g_assert(GEM_GRAPH_CLIENT_IS_APPLICATION(self)); + + ui_disable_action("closefile"); + ui_disable_action("savefile"); + ui_disable_action("runmode"); + ui_disable_action("editmode"); + ui_disable_action("presentmode"); + ui_set_stack("home"); +} + +void on_savefile_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GemGraphClientApplication *self = user_data; + + g_assert(GEM_GRAPH_CLIENT_IS_APPLICATION(self)); + +} + +void on_toast_close_action(GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GemGraphClientApplication *self = user_data; + + g_assert(GEM_GRAPH_CLIENT_IS_APPLICATION(self)); + + ui_close_internal_notification(); +} + +/* -------------------------------------------------------------------------- */ + +/* + * Responses + */ +void on_openfile_response(GtkNativeDialog *native, + int response, + GemGraphClientWindow *self) +{ + g_autoptr (GFile) file; + GtkFileChooser *chooser; + + if (response == GTK_RESPONSE_ACCEPT) { + chooser = GTK_FILE_CHOOSER(native); + file = gtk_file_chooser_get_file(chooser); + + // Load asynchroneously not to block control flow + g_file_load_contents_async (file, + NULL, + (GAsyncReadyCallback)on_openfile_response_complete, + self); + } + + g_object_unref (native); +} + +void on_openfile_response_complete(GObject *source_object, + GAsyncResult *result, + GemGraphClientWindow *self) +{ + GFile *file = G_FILE(source_object); + + g_autofree char *contents = NULL; + gsize length = 0; + + g_autoptr(GError) error = NULL; + + // Gives you the contents of the file as a byte array, or + // set the error argument + g_file_load_contents_finish(file, + result, + &contents, + &length, + NULL, + &error); + + // In case of error, print a warning to the standard error output + if (error != NULL) + { + g_printerr("Unable to open “%s”: %s\n", + g_file_peek_path(file), + error->message); + return; + } + + g_print("File content is :\n%s\n", contents); + + ui_enable_action("closefile"); + ui_enable_action("savefile"); + ui_enable_action("runmode"); + ui_enable_action("editmode"); + ui_enable_action("presentmode"); + ui_set_stack("run"); +} diff --git a/src/ui/application.c b/src/ui/application.c new file mode 100644 index 0000000..a051d19 --- /dev/null +++ b/src/ui/application.c @@ -0,0 +1,149 @@ +/* + * 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/base.h" +#include "../../include/ui.h" + +struct _GemGraphClientApplication +{ + GtkApplication parent_instance; +}; + +G_DEFINE_TYPE (GemGraphClientApplication, + gem_graph_client_application, + GTK_TYPE_APPLICATION) + + +static GemGraphClientApplication *application; + +/* -------------------------------------------------------------------------- */ + +void ui_enable_action(const char *name) { + g_simple_action_set_enabled( + (GSimpleAction *)g_action_map_lookup_action( + G_ACTION_MAP(application), + name), + true); +} + +void ui_disable_action(const char *name) { + g_simple_action_set_enabled( + (GSimpleAction *)g_action_map_lookup_action( + G_ACTION_MAP(application), + name), + false); +} + +/* + * Window actual presentation on screen + * + */ +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); + + ui_set_stack("home"); + + gtk_window_present(window); +} + +/* + * Action records are registered here + * + */ +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); + + // Setup shortcuts + gtk_application_set_accels_for_action(GTK_APPLICATION(self), + "app.quit", + (const char *[]) { "q", NULL }); + gtk_application_set_accels_for_action(GTK_APPLICATION(self), + "app.editmode", + (const char *[]) { "e", NULL }); + gtk_application_set_accels_for_action(GTK_APPLICATION(self), + "app.runmode", + (const char *[]) { "r", NULL }); + gtk_application_set_accels_for_action(GTK_APPLICATION(self), + "app.presentmode", + (const char *[]) { "p", NULL }); + gtk_application_set_accels_for_action(GTK_APPLICATION(self), + "app.savefile", + (const char *[]) { "s", NULL }); + + application = self; + + // + // Disable unneeded/inoperant actions + // + ui_disable_action("savefile"); + ui_disable_action("closefile"); + ui_disable_action("editmode"); + ui_disable_action("runmode"); + ui_disable_action("presentmode"); +} + +void ui_send_notification(const char *message) +{ + g_print("NOTIFICATION: %s\n", message); + + g_application_send_notification(G_APPLICATION(application), + "notification", + g_notification_new(message) + ); +} + +/* -------------------------------------------------------------------------- */ + +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; +} + +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); +} diff --git a/src/ui/gemgraph.cmb b/src/ui/gemgraph.cmb new file mode 100644 index 0000000..f32aa7b --- /dev/null +++ b/src/ui/gemgraph.cmb @@ -0,0 +1,30 @@ + + + + + (1,1,"gem-graph.ui","gemgraph.ui",None,None,None,None,None,None) + + + (1,1,"GtkApplicationWindow","main",None,None,None,None,None), + (1,2,"AdwHeaderBar","main_titlebar",1,None,"titlebar",None,None), + (1,3,"GtkMenuButton","main_button_menu",2,None,"end",None,None), + (1,6,"GtkToggleButton","main_button_sidebar",2,None,None,None,2), + (1,7,"GtkPaned","main_paned",1,None,None,None,1), + (1,8,"GtkFrame","main_sideframe",7,None,None,None,None), + (1,9,"GtkFrame","main_frame",7,None,None,None,1), + (1,10,"GtkMenuButton","main_button_mode",2,None,"start",None,None) + + + (1,1,"GtkWindow","default-height","720",None,None,None,None,None), + (1,1,"GtkWindow","default-width","1080",None,None,None,None,None), + (1,1,"GtkWindow","icon-name","application-x-executable",None,None,None,None,None), + (1,1,"GtkWindow","title","Gem-graph 0.0.1",None,None,None,None,None), + (1,3,"GtkMenuButton","direction","none",None,None,None,None,None), + (1,6,"GtkButton","icon-name","sidebar-show-symbolic",None,None,None,None,None), + (1,7,"GtkPaned","position","250",None,None,None,None,None), + (1,8,"GtkWidget","opacity","0.0",None,None,None,None,None), + (1,9,"GtkWidget","opacity","0.0",None,None,None,None,None), + (1,10,"GtkMenuButton","always-show-arrow","True",None,None,None,None,None), + (1,10,"GtkMenuButton","icon-name","document-edit-symbolic",None,None,None,None,None) + + diff --git a/src/ui/gemgraph.ui b/src/ui/gemgraph.ui new file mode 100644 index 0000000..93bbd17 --- /dev/null +++ b/src/ui/gemgraph.ui @@ -0,0 +1,255 @@ + + + + + + + + +
+ + _Open... + app.openfile + + + _Close + app.closefile + + + _Save + app.savefile + +
+
+ + _Preferences + app.preferences + + + _Keyboard Shortcuts + win.show-help-overlay + + + _About GemGraph + app.about + +
+
+ +
+ + Switch session mode + + + document-edit-symbolic + _Edition + app.editmode + True + + + system-run-symbolic + _Run + app.runmode + True + + + video-display-symbolic + _Presentation + app.presentmode + True + +
+
+
diff --git a/src/ui/window.c b/src/ui/window.c new file mode 100644 index 0000000..7bb3827 --- /dev/null +++ b/src/ui/window.c @@ -0,0 +1,339 @@ +/* + * 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/ui.h" + +/* -------------------------------------------------------------------------- */ + +float rotation_angles[N_AXIS] = { 0.0 }; // Rotation angles on each axis + +static GtkWidget *gl_area = NULL; + +static GemGraphClientWindow *window; + +/* -------------------------------------------------------------------------- */ + +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; + + graphics_draw(); + + 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; + + graphics_init(); +} + +/* 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; + + graphics_shutdown(); +} + +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; +} + +/* -------------------------------------------------------------------------- */ + +struct _GemGraphClientWindow +{ + GtkApplicationWindow parent_instance; + + /* Template widgets */ + GtkHeaderBar *main_titlebar; + GtkStack *main_stack; + GtkStack *side_stack; + GtkPaned *main_paned; + GtkMenuButton *main_button_mode; + GtkToggleButton *main_button_sidebar; + GtkRevealer *toast_revealer; + GtkToggleButton *toast_close_button; + GtkLabel *toast_text; + GtkGLArea *run_glarea; + GtkBox *run_controls; +}; + +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("src/ui/gemgraph.ui", &contents, &len, &err) == FALSE) + g_error("error reading 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, + main_titlebar); + gtk_widget_class_bind_template_child(widget_class, + GemGraphClientWindow, + main_stack); + gtk_widget_class_bind_template_child(widget_class, + GemGraphClientWindow, + side_stack); + gtk_widget_class_bind_template_child(widget_class, + GemGraphClientWindow, + main_paned); + gtk_widget_class_bind_template_child(widget_class, + GemGraphClientWindow, + main_button_mode); + gtk_widget_class_bind_template_child(widget_class, + GemGraphClientWindow, + main_button_sidebar); + gtk_widget_class_bind_template_child(widget_class, + GemGraphClientWindow, + toast_revealer); + gtk_widget_class_bind_template_child(widget_class, + GemGraphClientWindow, + toast_close_button); + gtk_widget_class_bind_template_child(widget_class, + GemGraphClientWindow, + toast_text); + gtk_widget_class_bind_template_child(widget_class, + GemGraphClientWindow, + run_glarea); + gtk_widget_class_bind_template_child(widget_class, + GemGraphClientWindow, + run_controls); +} + +static void gem_graph_client_window_init(GemGraphClientWindow *self) +{ + gtk_widget_init_template(GTK_WIDGET(self)); + + window = self; + + ui_setup_glarea(); +} + +/* -------------------------------------------------------------------------- */ + +void ui_set_stack(const char *mode) +{ + + if (window->main_stack == NULL) { + g_printerr("Can't find self->main_stack !\n"); + return; + } + if (window->side_stack == NULL) { + g_printerr("Can't find self->side_stack !\n"); + return; + } + gtk_stack_set_visible_child_full(window->main_stack, + mode, + GTK_STACK_TRANSITION_TYPE_CROSSFADE); + gtk_stack_set_visible_child_full(window->side_stack, + mode, + GTK_STACK_TRANSITION_TYPE_CROSSFADE); + + // Switch on the first letter of the mode, because switch is soooo simple :) + switch(mode[0]) { + case 'e': + gtk_menu_button_set_icon_name(window->main_button_mode, + "document-edit-symbolic"); + break; + case 'r': + gtk_menu_button_set_icon_name(window->main_button_mode, + "system-run-symbolic"); + break; + case 'p': + gtk_menu_button_set_icon_name(window->main_button_mode, + "x-office-presentation-symbolic"); + break; + case 'h': + gtk_menu_button_set_icon_name(window->main_button_mode, + "user-home-symbolic"); + break; + default: + break; + } +} + +void ui_send_internal_notification(const char *message) +{ + if (window->toast_revealer == NULL) { + g_printerr("Can't find self->toast_overlay !\n"); + return; + } + + if (window->toast_text == NULL) { + g_printerr("Can't find self->toast_overlay !\n"); + return; + } + + gtk_label_set_label(window->toast_text, message); + gtk_revealer_set_reveal_child(window->toast_revealer, true); +} + +void ui_close_internal_notification(void) +{ + if (window->toast_revealer == NULL) { + g_printerr("Can't find self->toast_overlay !\n"); + return; + } + + if (window->toast_text == NULL) { + g_printerr("Can't find self->toast_overlay !\n"); + return; + } + + gtk_revealer_set_reveal_child(window->toast_revealer, false); +} + +void ui_setup_glarea() +{ + int i, minor=4, major=0; + GtkWidget * controls; + gint height; + gint width; + + gl_area = GTK_WIDGET(window->run_glarea); + controls = GTK_WIDGET(window->run_controls); + gtk_widget_set_hexpand(gl_area, TRUE); + gtk_widget_set_vexpand(gl_area, TRUE); + gtk_widget_set_halign(gl_area, GTK_ALIGN_CENTER); + gtk_widget_set_valign(gl_area, GTK_ALIGN_CENTER); + + gtk_widget_set_size_request(gl_area, 500, 500); + + // 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); + + gtk_widget_set_hexpand(controls, TRUE); + + for(i = 0; i < N_AXIS; i++) + gtk_box_append(GTK_BOX(controls), create_axis_slider(i)); + + gtk_gl_area_set_auto_render(window->run_glarea, true); + + gtk_widget_show(GTK_WIDGET(gl_area)); + 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); +} +