diff --git a/Makefile b/Makefile index 7c162d8..fe214ae 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,12 @@ DESCRIPTION = Timed Petri Net Editor STANDARD = --std=c++14 BUILD_TYPE = debug +################################################### +# Select backend for dear im gui: RayLib or GLFW3 +# +BACKEND ?= RayLib +# BACKEND ?= GLFW3 + ################################################### # Location of the project directory and Makefiles # @@ -15,20 +21,16 @@ M := $(P)/.makefile include $(M)/Makefile.header ################################################### -# Linkage +# Inform Makefile where to find *.cpp files # -LINKER_FLAGS += -ldl -lpthread +VPATH += $(P)/src $(P)/src/Utils $(P)/src/Net +VPATH += $(P)/src/Net/Formats $(P)/src/Renderer $(P)/include ################################################### # Inform Makefile where to find header files # INCLUDES += -I$(P)/include -I$(P)/src -I$(P)/src/Net/Formats -I$(P)/src/Renderer -################################################### -# Inform Makefile where to find *.cpp and *.o files -# -VPATH += $(P)/src $(P)/src/Net $(P)/src/Net/Formats $(P)/src/Renderer $(P)/include - ################################################### # Project defines # @@ -41,18 +43,14 @@ CCFLAGS += -Wno-sign-conversion -Wno-float-equal CXXFLAGS += -Wno-undef -Wno-switch-enum -Wno-enum-compare ################################################### -# Embed assets for web version. Assets shall be -# present inside $(BUILD) folder. +# Linkage # -ifeq ($(ARCHI),Emscripten) -LINKER_FLAGS += --preload-file imgui.ini -LINKER_FLAGS += --preload-file examples -LINKER_FLAGS += -s FORCE_FILESYSTEM=1 -endif +LINKER_FLAGS += -ldl -lpthread ################################################### # Set thirdpart Raylib # +ifeq ($(BACKEND),RayLib) INCLUDES += -I$(THIRDPART)/raylib/src THIRDPART_LIBS += $(abspath $(THIRDPART)/raylib/src/$(ARCHI)/libraylib.a) @@ -68,6 +66,27 @@ LINKER_FLAGS += -s ASYNCIFY # our own. LINKER_FLAGS += --shell-file $(THIRDPART)/raylib/src/shell.html endif +endif + +################################################### +# Dear ImGui backends: Raylib +# +ifeq ($(BACKEND),RayLib) +VPATH += $(THIRDPART)/rlImGui +VPATH += $(P)/src/Renderer/Backends/RayLib +INCLUDES += -I$(THIRDPART)/rlImGui +INCLUDES += -I$(P)/src/Renderer/Backends/RayLib +DEARIMGUI_BACKEND_OBJS += rlImGui.o +endif + +################################################### +# Dear ImGui backends: OpenGL/GLFW3 +# +ifeq ($(BACKEND),GLFW3) +VPATH += $(P)/src/Renderer/Backends/GLFW3 +INCLUDES += -I$(P)/src/Renderer/Backends/GLFW3 +DEARIMGUI_BACKEND_OBJS += imgui_impl_glfw.o imgui_impl_opengl3.o +endif ################################################### # Set thirdpart Dear ImGui @@ -75,28 +94,21 @@ endif INCLUDES += -I$(THIRDPART)/imgui -I$(THIRDPART)/imgui/backends -I$(THIRDPART)/imgui/misc/cpp VPATH += $(THIRDPART)/imgui $(THIRDPART)/imgui/backends $(THIRDPART)/imgui/misc/cpp DEARIMGUI_OBJS += imgui_widgets.o imgui_draw.o imgui_tables.o imgui.o imgui_stdlib.o -# DEARIMGUI_OBJS += imgui_impl_glfw.o imgui_impl_opengl3.o +DEARIMGUI_OBJS += imgui_demo.o ################################################### # Set thirdpart Dear ImGui Plot # -INCLUDES += -I$(THIRDPART)/implot VPATH += $(THIRDPART)/implot +INCLUDES += -I$(THIRDPART)/implot DEARIMGUI_OBJS += implot_items.o implot.o ################################################### # Set thirdpart file dialog -INCLUDES += -I$(THIRDPART)/ImGuiFileDialog VPATH += $(THIRDPART)/ImGuiFileDialog +INCLUDES += -I$(THIRDPART)/ImGuiFileDialog DEARIMGUI_OBJS += ImGuiFileDialog.o -################################################### -# Set thirdpart Raylib with Dear ImGui -# -INCLUDES += -I$(THIRDPART)/rlImGui -VPATH += $(THIRDPART)/rlImGui -DEARIMGUI_OBJS += rlImGui.o - ################################################### # Set MQTT Library. # @@ -120,17 +132,30 @@ LINKER_FLAGS += -framework OpenGL -framework Cocoa LINKER_FLAGS += -framework IOKit -framework CoreVideo LINKER_FLAGS += -L/usr/local/lib -L/opt/local/lib LINKER_FLAGS += -lGLEW -lglfw +else ifeq ($(ARCHI),Linux) +LINKER_FLAGS += -lGL +PKG_LIBS += --static glfw3 +else ifneq ($(ARCHI),Emscripten) +$(error Unknown architecture $(ARCHI) for OpenGL) endif ################################################### -# Make the list of compiled files for the application +# Check if Dear im gui backend has been set # -#DEARIMGUI_OBJS += imgui_demo.o -OBJS += $(DEARIMGUI_OBJS) -OBJS += Application.o PetriNet.o PetriEditor.o Howard.o Algorithms.o main.o -OBJS += ImportJSON.o ExportJSON.o ExportSymfony.o ExportPnEditor.o -OBJS += ExportPetriLaTeX.o ExportJulia.o ExportGraphviz.o ExportDrawIO.o -OBJS += ExportGrafcetCpp.o +ifeq ($(DEARIMGUI_BACKEND_OBJS),) +$(error "Define BACKEND either as RayLib or GLFW3") +endif + +################################################### +# Embed assets for web version. Assets shall be +# present inside $(BUILD) folder. +# +ifeq ($(ARCHI),Emscripten) +LINKER_FLAGS += --preload-file imgui.ini +LINKER_FLAGS += --preload-file examples +LINKER_FLAGS += --preload-file data +LINKER_FLAGS += -s FORCE_FILESYSTEM=1 +endif ################################################### # MacOS X @@ -142,6 +167,15 @@ MACOS_BUNDLE_ICON = data/TimedPetriNetEditor.icns LINKER_FLAGS += -framework CoreFoundation endif +################################################### +# Make the list of compiled files for the application +# +OBJS += $(DEARIMGUI_BACKEND_OBJS) $(DEARIMGUI_OBJS) +OBJS += Path.o Application.o PetriNet.o PetriEditor.o Howard.o Algorithms.o main.o +OBJS += ImportJSON.o ExportJSON.o ExportSymfony.o ExportPnEditor.o +OBJS += ExportPetriLaTeX.o ExportJulia.o ExportGraphviz.o ExportDrawIO.o +OBJS += ExportGrafcetCpp.o + ################################################### # Compile the project, the static and shared libraries .PHONY: all @@ -153,9 +187,11 @@ all: copy-emscripten-assets $(TARGET) copy-emscripten-assets: | $(BUILD) ifeq ($(ARCHI),Emscripten) @$(call print-to,"Copying assets","$(TARGET)","$(BUILD)","") - @cp -r $(P)/data/examples $(BUILD) + @mkdir -p $(BUILD)/data + @mkdir -p $(BUILD)/examples + @cp $(P)/data/examples/*.json $(BUILD)/examples + @cp $(P)/data/font.ttf $(BUILD)/data @cp $(P)/imgui.ini $(BUILD) - @rm -fr $(BUILD)/examples/pics endif ################################################### diff --git a/data/font.ttf b/data/font.ttf index bd62e60..8f25a64 100644 Binary files a/data/font.ttf and b/data/font.ttf differ diff --git a/imgui.ini b/imgui.ini index be70a2b..ac60063 100644 --- a/imgui.ini +++ b/imgui.ini @@ -4,18 +4,18 @@ Size=664,636 Collapsed=0 [Window][Dear ImGui Demo] -Pos=0,19 -Size=780,671 +Pos=0,20 +Size=693,670 Collapsed=0 -DockId=0x00000008,2 +DockId=0x00000002,2 [Window][DockSpace] Size=1920,1080 Collapsed=0 [Window][DockSpaceViewport_11111111] -Pos=0,19 -Size=1024,749 +Pos=0,20 +Size=1024,748 Collapsed=0 [Window][Delete?] @@ -53,44 +53,41 @@ Size=500,440 Collapsed=0 [Window][Example: Custom rendering] +Pos=1,2 Size=1920,1080 Collapsed=0 -DockId=0x00000003,0 [Window][Petri net: foo bar] -Pos=0,19 -Size=780,671 +Pos=0,20 +Size=693,670 Collapsed=0 -DockId=0x00000008,0 +DockId=0x00000002,0 [Window][Example: Custom Node Graph] Size=1920,1080 Collapsed=0 -DockId=0x00000003,0 [Window][Help] Pos=505,19 Size=295,581 Collapsed=0 -DockId=0x00000003,2 [Window][About] Pos=505,19 Size=295,581 Collapsed=0 -DockId=0x00000003,2 [Window][Console] -Pos=0,19 -Size=780,671 +Pos=0,20 +Size=693,670 Collapsed=0 -DockId=0x00000008,1 +DockId=0x00000002,1 [Window][Message] Pos=0,692 Size=1024,76 Collapsed=0 -DockId=0x0000000E,0 +DockId=0x00000004,0 [Window][Choose the Petri file to load##ChooseFileDlgKey] Pos=62,98 @@ -138,22 +135,22 @@ Size=353,144 Collapsed=0 [Window][Arcs] -Pos=782,19 -Size=242,671 +Pos=695,20 +Size=329,670 Collapsed=0 -DockId=0x0000000C,2 +DockId=0x00000001,2 [Window][Places] -Pos=782,19 -Size=242,671 +Pos=695,20 +Size=329,670 Collapsed=0 -DockId=0x0000000C,0 +DockId=0x00000001,0 [Window][Transitions] -Pos=782,19 -Size=242,671 +Pos=695,20 +Size=329,670 Collapsed=0 -DockId=0x0000000C,1 +DockId=0x00000001,1 [Table][0x5CB1E615,4] RefScale=13 @@ -276,19 +273,9 @@ RefScale=13 Column 0 Sort=0v [Docking][Data] -DockSpace ID=0x8B93E3BD Window=0xA787BDB4 Pos=0,19 Size=1024,749 Split=Y Selected=0x313AEB1D - DockNode ID=0x0000000D Parent=0x8B93E3BD SizeRef=1920,932 Split=X - DockNode ID=0x0000000B Parent=0x0000000D SizeRef=838,1010 Split=Y - DockNode ID=0x00000007 Parent=0x0000000B SizeRef=1920,935 Split=X - DockNode ID=0x00000005 Parent=0x00000007 SizeRef=1732,1010 Split=X - DockNode ID=0x00000001 Parent=0x00000005 SizeRef=1732,1010 Split=Y - DockNode ID=0x00000003 Parent=0x00000001 SizeRef=0,0 Split=Y Selected=0x313AEB1D - DockNode ID=0x00000008 Parent=0x00000003 SizeRef=1732,957 CentralNode=1 Selected=0xE87781F4 - DockNode ID=0x00000009 Parent=0x00000003 SizeRef=1732,51 Selected=0x6B041B99 - DockNode ID=0x00000004 Parent=0x00000001 SizeRef=800,32 HiddenTabBar=1 Selected=0x6B041B99 - DockNode ID=0x00000002 Parent=0x00000005 SizeRef=186,1010 Selected=0x081FB661 - DockNode ID=0x00000006 Parent=0x00000007 SizeRef=186,1010 Selected=0x081FB661 - DockNode ID=0x0000000A Parent=0x0000000B SizeRef=1920,73 Selected=0x6B041B99 - DockNode ID=0x0000000C Parent=0x0000000D SizeRef=242,1010 Selected=0x6372E3B7 - DockNode ID=0x0000000E Parent=0x8B93E3BD SizeRef=1920,76 Selected=0x6B041B99 +DockSpace ID=0x8B93E3BD Window=0xA787BDB4 Pos=0,20 Size=1024,748 Split=Y + DockNode ID=0x00000003 Parent=0x8B93E3BD SizeRef=1920,931 Split=X + DockNode ID=0x00000002 Parent=0x00000003 SizeRef=1589,1009 CentralNode=1 Selected=0x313AEB1D + DockNode ID=0x00000001 Parent=0x00000003 SizeRef=329,1009 Selected=0x081FB661 + DockNode ID=0x00000004 Parent=0x8B93E3BD SizeRef=1920,76 Selected=0x6B041B99 diff --git a/src/Renderer/Application.hpp b/src/Renderer/Application.hpp deleted file mode 100644 index 97cc7d3..0000000 --- a/src/Renderer/Application.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef APPLICATION_HPP -# define APPLICATION_HPP - -#include "raylib.h" -#include "imgui_impl_raylib.h" -#include "imgui/misc/cpp/imgui_stdlib.h" -#include "imgui.h" -#include "implot.h" -#include "ImGuiFileDialog.h" - -#include - -class Application -{ -public: - - Application(size_t const width, size_t const height, std::string const& title); - virtual ~Application(); - void run(); - -private: - - virtual void onStartUp() = 0; - virtual void onDraw() = 0; - -private: - - Rectangle m_screen_resolution; - Vector2 m_mouse_position = { 0, 0 }; - bool m_exit_window = false; - size_t m_framerate = 60u; -}; - -#endif diff --git a/src/Renderer/Backends/GLFW3/Application.cpp b/src/Renderer/Backends/GLFW3/Application.cpp new file mode 100644 index 0000000..1eb6246 --- /dev/null +++ b/src/Renderer/Backends/GLFW3/Application.cpp @@ -0,0 +1,121 @@ +//============================================================================= +// TimedPetriNetEditor: A timed Petri net editor. +// Copyright 2021 -- 2023 Quentin Quadrat +// +// This file is part of TimedPetriNetEditor. +// +// TimedPetriNetEditor 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 GNU Emacs. If not, see . +//============================================================================= + +#include "Backends/GLFW3/Application.hpp" + +#include +#include +#include +#include + +static void error_callback(int error, const char* description) +{ + fprintf(stderr, "Glfw Error %d: %s\n", error, description); +} + +Application::Application(size_t const width, size_t const height, std::string const& title) + : m_clear_color(ImVec4(0.1058, 0.1137f, 0.1255f, 1.00f)) +{ + glfwSetErrorCallback(error_callback); + + if (!glfwInit()) + std::exit(1); + + const char* glsl_version = "#version 130"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + + // Create window with graphics context + m_window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr); + if (m_window == NULL) + std::exit(1); + + glfwSetWindowSize(m_window, width, height); + glfwMakeContextCurrent(m_window); + glfwSwapInterval(1); + + // Add window based callbacks to the underlying app + //glfwSetMouseButtonCallback(m_window, &Derived::MouseButtonCallback); + //glfwSetCursorPosCallback(m_window, &Derived::CursorPosCallback); + //glfwSetKeyCallback(m_window, &Derived::KeyCallback); + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImPlot::CreateContext(); + + // Setup Platform/Renderer backends + ImGui_ImplGlfw_InitForOpenGL(m_window, true); + ImGui_ImplOpenGL3_Init(glsl_version); + + // Enable controls + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking + + // Add custom fonts + io.Fonts->AddFontFromFileTTF("/home/qq/MyGitHub/TimedPetriNetEditor-dev/data/Ruda.ttf", 13.0f); +} + +Application::~Application() +{ + // Cleanup + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImPlot::DestroyContext(); + ImGui::DestroyContext(); + + glfwDestroyWindow(m_window); + glfwTerminate(); +} + +void Application::run() +{ + // Initialize the underlying app + onStartUp(); + + while (!glfwWindowShouldClose(m_window)) + { + // Poll events like key presses, mouse movements etc. + glfwPollEvents(); + + // Start the Dear ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + // Main loop of the underlying app + onDraw(); + + // Rendering + ImGui::Render(); + int display_w, display_h; + glfwGetFramebufferSize(m_window, &display_w, &display_h); + glViewport(0, 0, display_w, display_h); + glClearColor(m_clear_color.x * m_clear_color.w, + m_clear_color.y * m_clear_color.w, + m_clear_color.z * m_clear_color.w, + m_clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + glfwSwapBuffers(m_window); + } +} diff --git a/src/Renderer/Backends/GLFW3/Application.hpp b/src/Renderer/Backends/GLFW3/Application.hpp new file mode 100644 index 0000000..71a224d --- /dev/null +++ b/src/Renderer/Backends/GLFW3/Application.hpp @@ -0,0 +1,54 @@ +//============================================================================= +// TimedPetriNetEditor: A timed Petri net editor. +// Copyright 2021 -- 2023 Quentin Quadrat +// +// This file is part of TimedPetriNetEditor. +// +// TimedPetriNetEditor 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 GNU Emacs. If not, see . +//============================================================================= + +#ifndef APPLICATION_HPP +# define APPLICATION_HPP + +#include "imgui.h" +#include "imgui_impl_glfw.h" +#include "imgui_impl_opengl3.h" +#include "imgui/misc/cpp/imgui_stdlib.h" +#include "implot.h" +#include "ImGuiFileDialog.h" +#include + +// ***************************************************************************** +//! \brief +// ***************************************************************************** +class Application +{ +public: + + Application(size_t const width, size_t const height, std::string const& title); + virtual ~Application(); + void run(); + +private: + + virtual void onStartUp() = 0; + virtual void onDraw() = 0; + +private: + + GLFWwindow* m_window = nullptr; + ImVec4 m_clear_color; +}; + +#endif diff --git a/src/Renderer/Application.cpp b/src/Renderer/Backends/RayLib/Application.cpp similarity index 50% rename from src/Renderer/Application.cpp rename to src/Renderer/Backends/RayLib/Application.cpp index 7cd50fa..19bfc6f 100644 --- a/src/Renderer/Application.cpp +++ b/src/Renderer/Backends/RayLib/Application.cpp @@ -1,11 +1,40 @@ -#include "Application.hpp" +//============================================================================= +// TimedPetriNetEditor: A timed Petri net editor. +// Copyright 2021 -- 2023 Quentin Quadrat +// +// This file is part of TimedPetriNetEditor. +// +// TimedPetriNetEditor 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 GNU Emacs. If not, see . +//============================================================================= + +#include "Backends/RayLib/Application.hpp" +#include "project_info.hpp" // generated by MyMakefile #include #include #include #include +#ifdef __APPLE__ +# define GET_DATA_PATH osx_get_resources_dir("data") +#else +# define GET_DATA_PATH project::info::data_path +#endif + +//------------------------------------------------------------------------------ Application::Application(size_t const width, size_t const height, std::string const& title) + : m_path(GET_DATA_PATH) { m_screen_resolution.x = 0.0f; m_screen_resolution.y = 0.0f; @@ -26,6 +55,9 @@ Application::Application(size_t const width, size_t const height, std::string co io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking + // Setup fonts + io.Fonts->AddFontFromFileTTF(m_path.expand("font.ttf").c_str(), 14.0f); + // Setup Dear ImGui style ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); @@ -34,6 +66,7 @@ Application::Application(size_t const width, size_t const height, std::string co ImGui_ImplRaylib_Init(); } +//------------------------------------------------------------------------------ Application::~Application() { ImGui_ImplRaylib_Shutdown(); @@ -43,6 +76,7 @@ Application::~Application() CloseWindow(); // Stop raylib } +//------------------------------------------------------------------------------ void Application::run() { // Initialize the underlying app @@ -69,3 +103,10 @@ void Application::run() EndDrawing(); // Stop raylib content } } + +//------------------------------------------------------------------------------ +void Application::setFramerate(size_t const framerate) +{ + m_framerate = framerate; + SetTargetFPS(m_framerate); +} \ No newline at end of file diff --git a/src/Renderer/Backends/RayLib/Application.hpp b/src/Renderer/Backends/RayLib/Application.hpp new file mode 100644 index 0000000..e936150 --- /dev/null +++ b/src/Renderer/Backends/RayLib/Application.hpp @@ -0,0 +1,64 @@ +//============================================================================= +// TimedPetriNetEditor: A timed Petri net editor. +// Copyright 2021 -- 2023 Quentin Quadrat +// +// This file is part of TimedPetriNetEditor. +// +// TimedPetriNetEditor 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 GNU Emacs. If not, see . +//============================================================================= + +#ifndef APPLICATION_HPP +# define APPLICATION_HPP + +#include "raylib.h" +#include "imgui_impl_raylib.h" +#include "imgui/misc/cpp/imgui_stdlib.h" +#include "imgui.h" +#include "implot.h" +#include "ImGuiFileDialog.h" + +#include "Utils/Path.hpp" + +#include + +// ***************************************************************************** +//! \brief +// ***************************************************************************** +class Application +{ +public: + + Application(size_t const width, size_t const height, std::string const& title); + virtual ~Application(); + void run(); + void setFramerate(size_t const framerate); + +private: + + virtual void onStartUp() = 0; + virtual void onDraw() = 0; + +protected: + + Path m_path; + +private: + + Rectangle m_screen_resolution; + Vector2 m_mouse_position = { 0, 0 }; + bool m_exit_window = false; + size_t m_framerate = 60u; +}; + +#endif diff --git a/src/Renderer/KeyBindings.hpp b/src/Renderer/KeyBindings.hpp new file mode 100644 index 0000000..3514462 --- /dev/null +++ b/src/Renderer/KeyBindings.hpp @@ -0,0 +1,32 @@ +//============================================================================= +// TimedPetriNetEditor: A timed Petri net editor. +// Copyright 2021 -- 2023 Quentin Quadrat +// +// This file is part of TimedPetriNetEditor. +// +// TimedPetriNetEditor 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 GNU Emacs. If not, see . +//============================================================================= + +#ifndef KEY_BINDINGS_HPP +# define KEY_BINDINGS_HPP + +// ----------------------------------------------------------------------------- +# define KEY_BINDING_QUIT_APPLICATION ImGuiKey_Escape +# define KEY_BINDING_RUN_SIMULATION ImGuiKey_Space +# define KEY_BINDING_RUN_SIMULATION_ALT ImGuiKey_R +# define KEY_BINDING_ROTATE_CW ImGuiKey_PageUp +# define KEY_BINDING_ROTATE_CCW ImGuiKey_PageDown +# define KEY_BINDING_MOVE_PETRI_NODE ImGuiKey_Semicolon + +#endif \ No newline at end of file diff --git a/src/Renderer/PetriEditor.cpp b/src/Renderer/PetriEditor.cpp index 2c3b274..d2c49ed 100644 --- a/src/Renderer/PetriEditor.cpp +++ b/src/Renderer/PetriEditor.cpp @@ -19,6 +19,7 @@ //============================================================================= #include "Renderer/PetriEditor.hpp" +#include "Renderer/KeyBindings.hpp" #include "TimedPetriNetEditor/Algorithms.hpp" #include "Net/Formats/Exports.hpp" #include "Net/Formats/Imports.hpp" @@ -59,16 +60,20 @@ static std::vector const exporters = { //------------------------------------------------------------------------------ // Config for rendering the Petri net -const float TRANS_WIDTH = 25.0f; // Rectangle width for rendering Transitions -const float TRANS_HEIGHT = TRANS_WIDTH / 2.0f; // Rectangle height for rendering Transitions -const float PLACE_RADIUS = TRANS_WIDTH / 2.0f; // Circle radius for rendering Places -const float TOKEN_RADIUS = 2.0f; // Circle radius for rendering tokens -const float CAPTION_FONT_SIZE = 12; +static float thickness = 1.0f; +static float sz = 36.0f; +static const float TRANS_WIDTH = 36.0f; // Rectangle width for rendering Transitions +static const float TRANS_HEIGHT = TRANS_WIDTH / 2.0f; // Rectangle height for rendering Transitions +static const float PLACE_RADIUS = TRANS_WIDTH / 2.0f; // Circle radius for rendering Places +static const float TOKEN_RADIUS = 2.0f; // Circle radius for rendering tokens +static const float CAPTION_FONT_SIZE = 12; #define FILL_COLOR(a) IM_COL32(255, 165, 0, (a)) #define OUTLINE_COLOR IM_COL32(165, 42, 42, 255) // Arcs, Places, Transitions #define CRITICAL_COLOR IM_COL32(255, 0, 0, 255) -const uint8_t alpha = 255; // FIXME +static const uint8_t alpha = 255; // FIXME +static float font_size_pixels = 16.0f; +static ImFont* font1 = nullptr; //------------------------------------------------------------------------------ // helper functions @@ -143,7 +148,7 @@ static void help() << "Right button pressed: add a transition" << std::endl << "Middle button pressed: add an arc with the selected place or transition as origin" << std::endl << "Middle button release: end the arc with the selected place or transition as destination" << std::endl - << "Middle button pressed: move the view" << std::endl; + << "Middle button pressed: move the view is no place or transitions are selected" << std::endl; ImGui::Text("%s", help.str().c_str()); ImGui::EndTabItem(); @@ -281,6 +286,8 @@ static void inspector(Editor& editor) ImGui::Begin("Transitions"); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); ImGui::Checkbox(show_transition_captions ? "Show transition identifiers" : "Show transition captions", &show_transition_captions); + ImGui::DragFloat("Thickness", &thickness, 0.05f, 1.0f, 8.0f, "%.02f"); + ImGui::DragFloat("Size", &sz, 0.2f, 2.0f, 100.0f, "%.0f"); ImGui::PopStyleVar(); ImGui::Separator(); //if (!editor.m_simulating) @@ -675,16 +682,16 @@ Editor::Editor(size_t const width, size_t const height, //------------------------------------------------------------------------------ void Editor::onStartUp() { - if (m_filename.empty()) - return ; - - if (m_net.load(m_filename)) - { - m_messages.setInfo("loaded with success " + m_filename); - } - else + if (!m_filename.empty()) { - m_messages.setError(m_net.error()); + if (m_net.load(m_filename)) + { + m_messages.setInfo("loaded with success " + m_filename); + } + else + { + m_messages.setError(m_net.error()); + } } } @@ -708,7 +715,8 @@ void Editor::drawGrill() { draw_list->ChannelsSetCurrent(0); // Background draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(50, 50, 50, 255)); - draw_list->AddRect(canvas_p0, canvas_p1, IM_COL32(255, 255, 255, 255)); + ImU32 color = m_simulating ? IM_COL32(0, 255, 0, 255) : IM_COL32(255, 255, 255, 255); + draw_list->AddRect(canvas_p0, canvas_p1, color); if (!m_layout_config.grid.enable) return ; @@ -749,22 +757,22 @@ void Editor::drawPetriNet() drawGrill(); - // Update the node the user is moving + // Update node positions the user is moving for (auto& it: m_selected_modes) { it->x = m_mouse.x; it->y = m_mouse.y; } + for (auto const& it: m_net.arcs()) + drawArc(it); + for (auto const& it: m_net.places()) drawPlace(it); for (auto const& it: m_net.transitions()) drawTransition(it); - for (auto const& it: m_net.arcs()) - drawArc(it); - draw_list->PopClipRect(); ImGui::End(); @@ -779,6 +787,7 @@ void Editor::onDraw() ::tpne::console(*this); ::tpne::messagebox(*this); ::tpne::inspector(*this); + ImGui::ShowDemoWindow(); drawPetriNet(); } @@ -976,7 +985,8 @@ void Editor::drawTransition(Transition const& transition) const ImVec2 pmin(p.x - TRANS_WIDTH / 2.0f, p.y - TRANS_HEIGHT / 2.0f); const ImVec2 pmax(p.x + TRANS_WIDTH / 2.0f, p.y + TRANS_HEIGHT / 2.0f); draw_list->AddRectFilled(pmin, pmax, color); - draw_list->AddRect(pmin, pmax + ImVec2(0.0f, 1.0f), OUTLINE_COLOR, 0, 5.0f); + const float rounding = sz / 5.0f; + draw_list->AddRect(pmin, pmax, OUTLINE_COLOR, rounding, ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBottomRight, thickness); // Draw the caption ImVec2 dim = ImGui::CalcTextSize(transition.key.c_str()); @@ -1220,7 +1230,6 @@ bool Editor::IsMouseReleased(ImGuiMouseButton& key) //------------------------------------------------------------------------------ void Editor::handleMoveNode() { - std::cout << "handleMoveNode\n"; if (m_selected_modes.size() == 0u) { Node* node = getNode(m_mouse.x, m_mouse.y); @@ -1281,13 +1290,16 @@ void Editor::onHandleInput() { is_dragging = disable_dragging = false; - // The 'M' key was pressed. Reset the state but do not add new node! + // The 'M' key was pressed for moving selected nodes. + // Reset the state but do not add new node! if (m_selected_modes.size() != 0u) { m_node_from = m_node_to = nullptr; m_selected_modes.clear(); if (button == ImGuiMouseButton_Middle) + { return; + } } if (button == ImGuiMouseButton_Middle) @@ -1298,10 +1310,27 @@ void Editor::onHandleInput() if (ImGui::IsItemHovered()) { - if (ImGui::IsKeyPressed(ImGuiKey_Semicolon, false)) + if (ImGui::IsKeyPressed(KEY_BINDING_MOVE_PETRI_NODE, false)) { handleMoveNode(); } + // Run the animation of the Petri net + else if (ImGui::IsKeyPressed(KEY_BINDING_RUN_SIMULATION) || + ImGui::IsKeyPressed(KEY_BINDING_RUN_SIMULATION_ALT)) + { + m_simulating = m_simulating ^ true; + + // Note: in GUI.cpp in the Application constructor, I set + // the window to have slower framerate in the aim to have a + // bigger discrete time and therefore AnimatedToken moving + // with a bigger step range and avoid them to overlap when + // i.e. two of them, carying 1 token, are arriving at almost + // the same moment but separated by one call of this method + // update() producing two AnimatedToken carying 1 token that + // will be displayed at the same position instead of a + // single AnimatedToken carying 2 tokens. + setFramerate(m_simulating ? 30 : 60); // FPS + } } } diff --git a/src/Renderer/PetriEditor.hpp b/src/Renderer/PetriEditor.hpp index ac68f12..a1cd04f 100644 --- a/src/Renderer/PetriEditor.hpp +++ b/src/Renderer/PetriEditor.hpp @@ -1,9 +1,29 @@ +//============================================================================= +// TimedPetriNetEditor: A timed Petri net editor. +// Copyright 2021 -- 2023 Quentin Quadrat +// +// This file is part of TimedPetriNetEditor. +// +// TimedPetriNetEditor 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 GNU Emacs. If not, see . +//============================================================================= + #ifndef PETRIEDITOR_HPP # define PETRIEDITOR_HPP # include "TimedPetriNetEditor/PetriNet.hpp" -# include "Renderer/Application.hpp" -# include "Renderer/Messages.hpp" +# include "Application.hpp" +# include "Utils/Messages.hpp" namespace tpne { diff --git a/src/Renderer/Messages.hpp b/src/Utils/Messages.hpp similarity index 100% rename from src/Renderer/Messages.hpp rename to src/Utils/Messages.hpp diff --git a/src/Utils/Path.cpp b/src/Utils/Path.cpp new file mode 100644 index 0000000..a527bc7 --- /dev/null +++ b/src/Utils/Path.cpp @@ -0,0 +1,255 @@ +//===================================================================== +// MyLogger: A basic logger. +// Copyright 2018 Quentin Quadrat +// +// This file is part of MyLogger. +// +// MyLogger 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 MyLogger. If not, see . +//===================================================================== + +#include "Utils/Path.hpp" +#include +#include +#ifdef __APPLE__ +# include +#endif + +//------------------------------------------------------------------------------ +#ifdef __APPLE__ +std::string osx_get_resources_dir(std::string const& file) +{ + struct stat exists; // folder exists ? + std::string path; + + CFURLRef resourceURL = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle()); + char resourcePath[PATH_MAX]; + if (CFURLGetFileSystemRepresentation(resourceURL, true, + reinterpret_cast(resourcePath), + PATH_MAX)) + { + if (resourceURL != NULL) + { + CFRelease(resourceURL); + } + + path = std::string(resourcePath) + "/" + file; + if (stat(path.c_str(), &exists) == 0) + { + return path; + } + } + +#ifdef DATADIR + path = std::string(DATADIR) + "/" + file; + if (stat(path.c_str(), &exists) == 0) + { + return path; + } +#endif + + path = "data/" + file; + if (stat(path.c_str(), &exists) == 0) + { + return path; + } + + return file; +} +#endif + +//------------------------------------------------------------------------------ +Path::Path(std::string const& path, char const delimiter) + : m_delimiter(delimiter) +{ + add(path); +} + +//------------------------------------------------------------------------------ +void Path::add(std::string const& path) +{ + if (!path.empty()) + { + split(path); + } +} + +//------------------------------------------------------------------------------ +void Path::reset(std::string const& path) +{ + m_search_paths.clear(); + split(path); +} + +//------------------------------------------------------------------------------ +void Path::clear() +{ + m_search_paths.clear(); + m_string_path.clear(); + m_dirty = false; +} + +//------------------------------------------------------------------------------ +void Path::remove(std::string const& path) +{ + m_search_paths.remove(path); + m_dirty = true; +} + +//------------------------------------------------------------------------------ +bool Path::exist(std::string const& path) const +{ + struct stat buffer; + return stat(path.c_str(), &buffer) == 0; +} + +//------------------------------------------------------------------------------ +std::pair Path::find(std::string const& filename) const +{ + if (Path::exist(filename)) + return std::make_pair(filename, true); + + for (auto const& it: m_search_paths) + { + std::string file(it + filename); + if (Path::exist(file)) + return std::make_pair(file, true); + } + + // Not found + return std::make_pair(std::string(), false); +} + +//------------------------------------------------------------------------------ +std::string Path::expand(std::string const& filename) const +{ + for (auto const& it: m_search_paths) + { + std::string file(it + filename); + if (Path::exist(file)) + return file; + } + + return filename; +} + +//------------------------------------------------------------------------------ +bool Path::open(std::string& filename, std::ifstream& ifs, std::ios_base::openmode mode) const +{ + ifs.open(filename.c_str(), mode); + if (ifs) + return true; + + for (auto const& it: m_search_paths) + { + std::string file(it + filename); + ifs.open(file.c_str(), mode); + if (ifs) + { + filename = file; + return true; + } + } + + // Not found + return false; +} + +//------------------------------------------------------------------------------ +bool Path::open(std::string& filename, std::ofstream& ofs, std::ios_base::openmode mode) const +{ + ofs.open(filename.c_str(), mode); + if (ofs) + return true; + + for (auto const& it: m_search_paths) + { + std::string file(it + filename); + ofs.open(file.c_str(), mode); + if (ofs) + { + filename = file; + return true; + } + } + + // Not found + return false; +} + +//------------------------------------------------------------------------------ +bool Path::open(std::string& filename, std::fstream& fs, std::ios_base::openmode mode) const +{ + fs.open(filename.c_str(), mode); + if (fs) + return true; + + for (auto const& it: m_search_paths) + { + std::string file(it + filename); + fs.open(filename.c_str(), mode); + if (fs) + { + filename = file; + return true; + } + } + + // Not found + return false; +} + +//------------------------------------------------------------------------------ +std::string const& Path::toString() +{ + update(); + return m_string_path; +} + +//------------------------------------------------------------------------------ +void Path::update() +{ + if (m_dirty) + { + m_string_path.clear(); + m_string_path += "."; + m_string_path += m_delimiter; + + for (auto const& it: m_search_paths) + { + m_string_path += it; + m_string_path.pop_back(); // Remove the '/' char + m_string_path += m_delimiter; + } + m_dirty = false; + } +} + +//------------------------------------------------------------------------------ +void Path::split(std::string const& path) +{ + std::stringstream ss(path); + std::string directory; + + while (std::getline(ss, directory, m_delimiter)) + { + if (directory.empty()) + continue ; + + if ((*directory.rbegin() == '\\') || (*directory.rbegin() == '/')) + m_search_paths.push_back(directory); + else + m_search_paths.push_back(directory + "/"); + } + m_dirty = true; +} diff --git a/src/Utils/Path.hpp b/src/Utils/Path.hpp new file mode 100644 index 0000000..0b90676 --- /dev/null +++ b/src/Utils/Path.hpp @@ -0,0 +1,141 @@ +//===================================================================== +// MyLogger: A basic logger. +// Copyright 2018 Quentin Quadrat +// +// This file is part of MyLogger. +// +// MyLogger 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 MyLogger. If not, see . +//===================================================================== + +#ifndef MYLOGGER_PATH_HPP +# define MYLOGGER_PATH_HPP + +# include +# include +# include +# include + +// ***************************************************************************** +//! \brief Class manipulating a set of paths for searching files in the same +//! idea of the Unix environment variable $PATH. Paths are separated by ':' and +//! search is made left to right. Ie. "/foo/bar:/usr/lib/" +// ***************************************************************************** +class Path +{ +public: + + //-------------------------------------------------------------------------- + //! \brief Empty constructor. No path is defined. + //-------------------------------------------------------------------------- + Path() = default; + + //-------------------------------------------------------------------------- + //! \brief Constructor with a given path. Directories shall be separated + //! by the character given by the param delimiter. + //! Example: Path("/foo/bar:/usr/lib/", ':'). + //-------------------------------------------------------------------------- + Path(std::string const& path, char const delimiter = ':'); + + //-------------------------------------------------------------------------- + //! \brief Destructor. + //-------------------------------------------------------------------------- + ~Path() = default; + + //-------------------------------------------------------------------------- + //! \brief Append a new path. Directories are separated by the delimiter char + //! (by default ':'). Example: add("/foo/bar:/usr/lib/"). + //-------------------------------------------------------------------------- + void add(std::string const& path); + + //-------------------------------------------------------------------------- + //! \brief Replace the path state by a new one. Directories are separated by + //! the delimiter char (by default ':'). Example: reset("/foo/bar:/usr/lib/"). + //-------------------------------------------------------------------------- + void reset(std::string const& path); + + //-------------------------------------------------------------------------- + //! \brief Erase the path. + //-------------------------------------------------------------------------- + void clear(); + + //-------------------------------------------------------------------------- + //! \brief Erase the given directory from the path if found. + //-------------------------------------------------------------------------- + void remove(std::string const& path); + + //-------------------------------------------------------------------------- + //! \brief Find if a file exists in the search path. Note that you have to + //! check again the existence of this file when opening it (with functions + //! such as iofstream, fopen, open ...). Indeed the file may have been + //! suppress since this method have bee called. + //! + //! \return the full path (if found) and the existence of this path. + //! + //-------------------------------------------------------------------------- + std::pair find(std::string const& filename) const; + + //-------------------------------------------------------------------------- + //! \brief Return the full path for the file (if found) else return itself. + //! Beware of race condition: even if found the file may have suppress after + //! this function has been called. + //-------------------------------------------------------------------------- + std::string expand(std::string const& filename) const; + + //-------------------------------------------------------------------------- + //! \brief Return the path as string. The first path is always ".:" + //-------------------------------------------------------------------------- + std::string const& toString(); + + bool open(std::string& filename, std::ifstream& ifs, + std::ios_base::openmode mode = std::ios_base::in) const; + bool open(std::string& filename, std::ofstream& ifs, + std::ios_base::openmode mode = std::ios_base::out) const; + bool open(std::string& filename, std::fstream& ifs, + std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) const; +protected: + + //-------------------------------------------------------------------------- + //! \brief From m_search_paths rebuild m_string_path + //-------------------------------------------------------------------------- + void update(); + + //-------------------------------------------------------------------------- + //! \brief Slipt paths separated by delimiter char into std::list + //-------------------------------------------------------------------------- + void split(std::string const& path); + + //-------------------------------------------------------------------------- + //! \brief Return true if the path exists. be careful the file may not + //! exist after the function ends. + //-------------------------------------------------------------------------- + bool exist(std::string const& path) const; + +protected: + + //! \brief Path separator when several pathes are given as a single string. + const char m_delimiter = ':'; + //! \brief the list of pathes. + std::list m_search_paths; + //! \brief the list of pathes converted as a string. Pathes are separated by + //! the m_delimiter char. + std::string m_string_path; + //! \brief redo update() when dirty + bool m_dirty = false; +}; + +# ifdef __APPLE__ +std::string osx_get_resources_dir(std::string const& file); +# endif + +#endif // MYLOGGER_PATH_HPP diff --git a/src/Renderer/Utils.hpp b/src/Utils/Utils.hpp similarity index 100% rename from src/Renderer/Utils.hpp rename to src/Utils/Utils.hpp