dev/gtk4 fusionné

This commit is contained in:
Adrien Bourmault 2023-01-26 01:14:24 +01:00
commit fbea0f51a9
No known key found for this signature in database
GPG Key ID: 6EB408FE0ACEC664
39 changed files with 2358 additions and 740 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
**/tags.*
bin/*
build/*

View File

@ -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

View File

@ -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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) 2021 Adrien Bourmault
Copyright (C) <year> <name of author>
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,

126
Makefile Normal file
View File

@ -0,0 +1,126 @@
##
## Gem-graph OpenGL experiments
##
## Desc: Makefile
##
## Copyright (C) 2023 Arthur Menges <arthur.menges@a-lec.org>
## Copyright (C) 2023 Adrien Bourmault <neox@a-lec.org>
##
## 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 <http://www.gnu.org/licenses/>.
##
.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}

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# Gem-graph Client

View File

@ -1,194 +0,0 @@
# Gem-graph Client
## Procédure de développement (Bonnes pratiques)
### Une seule branche par fonctionnalité : dev/<fonctionnalité>
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/<fonctionnalité>
git switch dev/<fonctionnalité>
git push --set-upstream origin dev/<fonctionnalité> // 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: <message>"
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.
---

9
data/shader.frag Executable file
View File

@ -0,0 +1,9 @@
#version 330 core
in vec4 color;
out vec4 out_frag_color;
void main(void)
{
out_frag_color = color;
}

16
data/shader.vert Executable file
View File

@ -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);
}

32
description_modes.txt Normal file
View File

@ -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)

0
help/.gitkeep Normal file
View File

34
include/base.h Normal file
View File

@ -0,0 +1,34 @@
/*
* Gem-graph OpenGL experiments
*
* Desc: Base header
*
* Copyright (C) 2023 Arthur Menges <arthur.menges@a-lec.org>
* Copyright (C) 2023 Adrien Bourmault <neox@a-lec.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
enum
{
X_AXIS,
Y_AXIS,
Z_AXIS,
N_AXIS
};

43
include/graphics.h Normal file
View File

@ -0,0 +1,43 @@
/*
* Gem-graph OpenGL experiments
*
* Desc: OpenGL utils header
*
* Copyright (C) 2023 Arthur Menges <arthur.menges@a-lec.org>
* Copyright (C) 2023 Adrien Bourmault <neox@a-lec.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <unistd.h>
#include <GL/glu.h>
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);

149
include/ui.h Normal file
View File

@ -0,0 +1,149 @@
/*
* Gem-graph OpenGL experiments
*
* Desc: User interface header
*
* Copyright (C) 2023 Arthur Menges <arthur.menges@a-lec.org>
* Copyright (C) 2023 Adrien Bourmault <neox@a-lec.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <unistd.h>
#include <gtk-4.0/gtk/gtk.h>
#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();

27
manifest.scm Normal file
View File

@ -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"
)
)

View File

@ -1,83 +0,0 @@
<gem-graph-model version="0.2">
<!-- Model identity informations -->
<identity>
<name>Modèle de test</name>
<owner>Gaston Lagaffe</owner>
<owner_id>2</owner_id>
<date>1629830000</date>
<version>1.0</version>
<g_ref id="texte" date="1629830000" author="Jean" lang="en">Ref</g_ref>
</identity>
<!-- Model parameters -->
<parameters id="texte" date="0" author="Gaston Lagaffe">
<simulation>
<max_thread>0</max_thread>
<max_cycles>9</max_cycles>
</simulation>
<space-param>
<dimension>
<!-- loop_on_zero="true" anything moved before zero reenters at the end of space-->
<!-- loop_on_zero="false" anything moved before zero is lost-->
<!-- loop_on_max="true" anything moved beyond max reenters at the zero of space-->
<!-- loop_on_max="false" anything moved beyond max is lost-->
<axis name="x" size="29"/>
<axis name="y" size="0"/>
<axis name="z" size="0"/>
</dimension>
<!-- Site_multiplicity = number of sites in a space unit. -->
<!-- Each site points towards a neighbouring space unit. -->
<!-- Several arrows can be stacked in the same site. -->
<site_multiplicity>3</site_multiplicity>
</space-param>
</parameters>
<!-- Model objects definition -->
<objects id="texte" date="1629830000" author="Jean Sirmai">
<object id="dimer 1-1" date="1629830000" author="Jean Sirmai">
<arrow x="0" y="0" z="0" site="1" weight="1"/>
<arrow x="1" y="0" z="0" site="2" weight="1"/>
</object>
</objects>
<!-- Saved space description (initial space is sequence 0) -->
<savestates id="texte" date="1629830000" author="THE MATRIX">
<space id="initial" date="1629830000" author="Jean Sirmai" sequence="0">
<arrow x="0" y="0" z="0" site="1" weight="1"/>
<arrow x="1" y="0" z="0" site="2" weight="1"/>
<arrow x="10" y="0" z="0" site="1" weight="1"/>
<arrow x="11" y="0" z="0" site="2" weight="1"/>
<arrow x="20" y="0" z="0" site="1" weight="1"/>
<arrow x="21" y="0" z="0" site="2" weight="1"/>
<!-- Three dimers 1-1 are drawn in the global space
at locations (0,1) (10,11) and (20,21) -->
</space>
</savestates>
<!-- Model transitions definition (rules) -->
<!-- In this version : <xs:sequence minOccurs="0" maxOccurs="unbounded">-->
<!-- Transitions should be edited by hand and written in a 'human-readable' format -->
<conditions id="random walk of dimers" date="1629822222" author="Jean">
<condition x="0" y="0" z="0" site="1" weight="1" node_id="1" parent="0"/>
<condition x="1" y="0" z="0" site="2" weight="1" node_id="2" parent="1"/>
<!-- as soon as conditions 1 and 2 are satisfied, a dimer is identified at location (0,1). -->
<condition x="2" y="0" z="0" site="1" weight="0" node_id="3" parent="2"/>
<!-- as soon as condition 3 is satisfied,
the neighbouring space unit to East of the dimer is empty
and the dimer identified by conditions (1,2) can be moved to East. -->
<condition x="-1" y="0" z="0" site="2" weight="0" node_id="4" parent="2"/>
<!-- as soon as condition 4 is satisfied,
the neighbouring space unit to West of the dimer is empty
and the dimer identified by conditions (1,2) can be moved to West. -->
</conditions>
<transitions>
<transition id="move_a_dimer_to_east" date="1629811111" author="Jean" parent="3" probability="1">
<arrow x="0" y="0" z="0" site="1" weight="0"/>
<arrow x="1" y="0" z="0" site="2" weight="0"/>
<arrow x="1" y="0" z="0" site="1" weight="1"/>
<arrow x="2" y="0" z="0" site="2" weight="1"/>
</transition>
<transition id="move_a_dimer_to_west" date="1629811112" author="Jean" parent="4" probability="1">
<arrow x="0" y="0" z="0" site="1" weight="0"/>
<arrow x="1" y="0" z="0" site="2" weight="0"/>
<arrow x="0" y="0" z="0" site="2" weight="1"/>
<arrow x="-1" y="0" z="0" site="1" weight="1"/>
</transition>
</transitions>
</gem-graph-model>

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

47
old_python_code/gemgraph.py Executable file
View File

@ -0,0 +1,47 @@
#!/bin/python3
# Copyright (C) 2022 Libre en Communs <contact@a-lec.org>
# Copyright (C) 2022 Adrien Bourmault <neox@a-lec.org>
# Copyright (C) 2022 Jean Sirmai <jean@a-lec.org>
#
# 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 <http://www.gnu.org/licenses/>.
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))

207
old_python_code/main.py Normal file
View File

@ -0,0 +1,207 @@
# main.py
#
# Copyright (C) 2022 Libre en Communs <contact@a-lec.org>
# Copyright (C) 2022 Adrien Bourmault <neox@a-lec.org>
# Copyright (C) 2022 Jean Sirmai <jean@a-lec.org>
#
# 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 <http://www.gnu.org/licenses/>.
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, ['<primary>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,
['<primary>e'])
self.runmode_action = \
self.create_action(
'runmode',
self.on_runmode_action,
DISABLE,
['<primary>r'])
self.presentmode_action = \
self.create_action(
'presentmode',
self.on_presentmode_action,
DISABLE,
['<primary>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,
['<primary>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)

82
old_python_code/window.py Normal file
View File

@ -0,0 +1,82 @@
# window.py
#
# Copyright (C) 2022 Libre en Communs <contact@a-lec.org>
# Copyright (C) 2022 Adrien Bourmault <neox@a-lec.org>
# Copyright (C) 2022 Jean Sirmai <jean@a-lec.org>
#
# 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 <http://www.gnu.org/licenses/>.
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)

0
po/.gitkeep Normal file
View File

509
src/graphics/graphics.c Normal file
View File

@ -0,0 +1,509 @@
/*
* Gem-graph OpenGL experiments
*
* Desc: GL functions
*
* Copyright (C) 2023 Arthur Menges <arthur.menges@a-lec.org>
* Copyright (C) 2023 Adrien Bourmault <neox@a-lec.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <epoxy/gl.h>
#include <GL/glu.h>
#include <GL/glext.h>
#include <glib-2.0/glib.h>
#include <math.h>
#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);
}

48
src/main.c Normal file
View File

@ -0,0 +1,48 @@
/*
* Gem-graph OpenGL experiments
*
* Desc: Main file
*
* Copyright (C) 2023 Arthur Menges <arthur.menges@a-lec.org>
* Copyright (C) 2023 Adrien Bourmault <neox@a-lec.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <unistd.h>
#include <gtk-4.0/gtk/gtk.h>
#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;
}

View File

@ -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 <https:#www.gnu.org/licenses/>. #
# ------------------------------------------------------------------------- #
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()

View File

@ -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)

0
src/ui/.gitkeep Normal file
View File

248
src/ui/actions.c Normal file
View File

@ -0,0 +1,248 @@
/*
* Gem-graph OpenGL experiments
*
* Desc: User interface functions
*
* Copyright (C) 2023 Arthur Menges <arthur.menges@a-lec.org>
* Copyright (C) 2023 Adrien Bourmault <neox@a-lec.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#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");
}

149
src/ui/application.c Normal file
View File

@ -0,0 +1,149 @@
/*
* Gem-graph OpenGL experiments
*
* Desc: User interface functions
*
* Copyright (C) 2023 Arthur Menges <arthur.menges@a-lec.org>
* Copyright (C) 2023 Adrien Bourmault <neox@a-lec.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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 *[]) { "<primary>q", NULL });
gtk_application_set_accels_for_action(GTK_APPLICATION(self),
"app.editmode",
(const char *[]) { "<primary>e", NULL });
gtk_application_set_accels_for_action(GTK_APPLICATION(self),
"app.runmode",
(const char *[]) { "<primary>r", NULL });
gtk_application_set_accels_for_action(GTK_APPLICATION(self),
"app.presentmode",
(const char *[]) { "<primary>p", NULL });
gtk_application_set_accels_for_action(GTK_APPLICATION(self),
"app.savefile",
(const char *[]) { "<primary>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);
}

30
src/ui/gemgraph.cmb Normal file
View File

@ -0,0 +1,30 @@
<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd">
<cambalache-project version="0.10.2" target_tk="gtk-4.0">
<ui>
(1,1,"gem-graph.ui","gemgraph.ui",None,None,None,None,None,None)
</ui>
<object>
(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)
</object>
<object_property>
(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)
</object_property>
</cambalache-project>

255
src/ui/gemgraph.ui Normal file
View File

@ -0,0 +1,255 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.10.2 -->
<interface>
<!-- interface-name gem-graph.ui -->
<requires lib="gtk" version="4.6"/>
<template class="GemGraphClientWindow" parent="GtkApplicationWindow">
<property name="default-height">720</property>
<property name="default-width">1080</property>
<property name="icon-name">application-x-executable</property>
<property name="title">GemGraph 0.1.0</property>
<style>
<class name="devel"/>
</style>
<child type="titlebar">
<object class="GtkHeaderBar" id="main_titlebar">
<child type="end">
<object class="GtkMenuButton" id="main_button_menu">
<property name="direction">none</property>
<property name="menu-model">main_menu</property>
</object>
</child>
<child type="start">
<object class="GtkMenuButton" id="main_button_mode">
<property name="always-show-arrow">True</property>
<property name="use-underline">True</property>
<property name="tooltip-text" translatable="yes">Change session mode</property>
<property name="icon-name">document-edit-symbolic</property>
<property name="menu-model">mode_menu</property>
</object>
</child>
<child>
<object class="GtkToggleButton" id="main_button_sidebar">
<property name="icon-name">sidebar-show-symbolic</property>
<property name="tooltip-text" translatable="yes">Display/hide sidebar</property>
<property name="action-name">app.togglesidebar</property>
</object>
<!-- <object class="GtkLabel" id="is_modified"> -->
<!-- <property name="halign">end</property> -->
<!-- <property name="hexpand">true</property> -->
<!-- <property name="label">•</property> -->
<!-- <property name="margin-end">6</property> -->
<!-- <property name="visible">true</property> -->
<!-- </object> -->
</child>
</object>
</child>
<child>
<object class="GtkOverlay" id="toast_overlay">
<child>
<object class="GtkPaned" id="main_paned">
<property name="position">300</property>
<child>
<object class="GtkStack" id="side_stack">
<child>
<object class="GtkStackPage">
<property name="name">run</property>
<property name="child">
<object class="GtkLabel" id="labelsiderun">
<property name="justify">center</property>
<property name="label" translatable="yes">&lt;b&gt;Sidebar: run mode&lt;/b&gt;</property>
<property name="use-markup">True</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">home</property>
<property name="child">
<object class="GtkLabel" id="labelhome">
<property name="justify">center</property>
<property name="label" translatable="yes">&lt;b&gt;Sidebar: home&lt;/b&gt;</property>
<property name="use-markup">True</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">presentation</property>
<property name="child">
<object class="GtkLabel" id="labelsidepresentation">
<property name="justify">center</property>
<property name="label" translatable="yes">&lt;b&gt;Sidebar: presentation mode&lt;/b&gt;</property>
<property name="use-markup">True</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">edition</property>
<property name="child">
<object class="GtkLabel" id="labelsideedit">
<property name="justify">center</property>
<property name="label" translatable="yes">&lt;b&gt;Sidebar: edition mode&lt;/b&gt;</property>
<property name="use-markup">True</property>
</object>
</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkStack" id="main_stack">
<child>
<object class="GtkStackPage">
<property name="name">run</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkGlArea" id="run_glarea">
</object>
</child>
<child>
<object class="GtkBox" id="run_controls">
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">home</property>
<property name="child">
<object class="GtkLabel">
<property name="justify">center</property>
<property name="label" translatable="yes">&lt;b&gt;Main zone: home&lt;/b&gt;</property>
<property name="use-markup">True</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">presentation</property>
<property name="child">
<object class="GtkLabel">
<property name="halign">center</property>
<property name="label" translatable="yes">&lt;b&gt;Main zone: presentation mode&lt;/b&gt;</property>
<property name="use-markup">True</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<property name="name">edition</property>
<property name="child">
<object class="GtkLabel">
<property name="halign">center</property>
<property name="label" translatable="yes">&lt;b&gt;Main zone: edition mode&lt;/b&gt;</property>
<property name="use-markup">True</property>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child type="overlay">
<object class="GtkRevealer" id="toast_revealer">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">start</property>
<child>
<object class="GtkBox" id="toast_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">start</property>
<property name="spacing">20</property>
<child>
<object class="GtkLabel" id="toast_text">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">(null)</property>
</object>
</child>
<child>
<object class="GtkToggleButton" id="toast_close_button">
<property name="icon-name">window-close-symbolic</property>
<property name="action-name">app.toastclose</property>
</object>
</child>
<style>
<class name="app-notification"/>
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
<menu id="main_menu">
<section>
<item>
<attribute name="label" translatable="yes">_Open...</attribute>
<attribute name="action">app.openfile</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Close</attribute>
<attribute name="action">app.closefile</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Save</attribute>
<attribute name="action">app.savefile</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">_Preferences</attribute>
<attribute name="action">app.preferences</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Keyboard Shortcuts</attribute>
<attribute name="action">win.show-help-overlay</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_About GemGraph</attribute>
<attribute name="action">app.about</attribute>
</item>
</section>
</menu>
<menu id="mode_menu">
<section>
<attribute name="label" translatable="yes">Switch session mode</attribute>
<item>
<attribute name="icon">document-edit-symbolic</attribute>
<attribute name="label" translatable="yes">_Edition</attribute>
<attribute name="action">app.editmode</attribute>
<attribute name="use-icon">True</attribute>
</item>
<item>
<attribute name="icon">system-run-symbolic</attribute>
<attribute name="label" translatable="yes">_Run</attribute>
<attribute name="action">app.runmode</attribute>
<attribute name="use-icon">True</attribute>
</item>
<item>
<attribute name="icon">video-display-symbolic</attribute>
<attribute name="label" translatable="yes">_Presentation</attribute>
<attribute name="action">app.presentmode</attribute>
<attribute name="use-icon">True</attribute>
</item>
</section>
</menu>
</interface>

339
src/ui/window.c Normal file
View File

@ -0,0 +1,339 @@
/*
* Gem-graph OpenGL experiments
*
* Desc: User interface functions
*
* Copyright (C) 2023 Arthur Menges <arthur.menges@a-lec.org>
* Copyright (C) 2023 Adrien Bourmault <neox@a-lec.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <unistd.h>
#include <gtk-4.0/gtk/gtk.h>
#include <glib-2.0/glib.h>
#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);
}