├── LICENSE.txt ├── Makefile ├── README.md ├── example └── main.cpp ├── extra ├── CMakeLists.txt ├── iwyu.imp ├── screenshot.png └── shader_graph_converter.cpp ├── font ├── OFL.txt └── SourceSansPro-Regular.ttf ├── nanovg ├── LICENSE.txt ├── README.md └── src │ ├── fontstash.h │ ├── nanovg.c │ ├── nanovg.h │ ├── nanovg_gl.h │ ├── nanovg_gl_utils.h │ ├── stb_image.h │ └── stb_truetype.h └── src ├── button_category.cpp ├── button_category.h ├── buttons_nodes.cpp ├── buttons_nodes.h ├── config.h ├── curve.cpp ├── curve.h ├── drawing.cpp ├── drawing.h ├── editable_graph.cpp ├── editable_graph.h ├── glfw_callbacks.cpp ├── glfw_callbacks.h ├── graph_decoder.cpp ├── graph_decoder.h ├── graph_editor.cpp ├── graph_editor.h ├── gui_colors.h ├── gui_sizes.h ├── input_box.cpp ├── input_box.h ├── main_window.cpp ├── main_window.h ├── node_base.cpp ├── node_base.h ├── node_colors.cpp ├── node_colors.h ├── node_converter.cpp ├── node_converter.h ├── node_inputs.cpp ├── node_inputs.h ├── node_interop_max.cpp ├── node_interop_max.h ├── node_outputs.cpp ├── node_outputs.h ├── node_shaders.cpp ├── node_shaders.h ├── node_textures.cpp ├── node_textures.h ├── node_vector.cpp ├── node_vector.h ├── output.h ├── panel_edit.cpp ├── panel_edit.h ├── panel_edit_bool.cpp ├── panel_edit_bool.h ├── panel_edit_color.cpp ├── panel_edit_color.h ├── panel_edit_color_ramp.cpp ├── panel_edit_color_ramp.h ├── panel_edit_curve.cpp ├── panel_edit_curve.h ├── panel_edit_enum.cpp ├── panel_edit_enum.h ├── panel_edit_multi_input.cpp ├── panel_edit_multi_input.h ├── selection.cpp ├── selection.h ├── serialize.cpp ├── serialize.h ├── sockets.cpp ├── sockets.h ├── statusbar.cpp ├── statusbar.h ├── subwindow.cpp ├── subwindow.h ├── subwindow_node_list.cpp ├── subwindow_node_list.h ├── subwindow_param_editor.cpp ├── subwindow_param_editor.h ├── toolbar.cpp ├── toolbar.h ├── ui_requests.cpp ├── ui_requests.h ├── undo.cpp ├── undo.h ├── util_area.cpp ├── util_area.h ├── util_color_ramp.cpp ├── util_color_ramp.h ├── util_enum.h ├── util_hermite_spline.cpp ├── util_hermite_spline.h ├── util_parse.cpp ├── util_parse.h ├── util_platform.cpp ├── util_platform.h ├── util_typedef.h ├── util_vector.cpp ├── util_vector.h ├── view.cpp ├── view.h ├── widget.cpp ├── widget.h ├── widget_multi_input.cpp ├── widget_multi_input.h ├── widget_radio_list.cpp ├── widget_radio_list.h ├── wrapper_glfw_func.h ├── wrapper_glfw_window.cpp ├── wrapper_glfw_window.h ├── wrapper_nvg_context.cpp ├── wrapper_nvg_context.h ├── wrapper_nvg_func.h ├── zoom.cpp └── zoom.h /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Jeffrey Witthuhn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | CXX ?= g++ 3 | AR ?= ar 4 | 5 | BINARY_NAME = shader_editor 6 | LIB_NAME = libshadereditor.a 7 | 8 | SRC_DIR = ./src 9 | INC_DIR = ./include 10 | OBJ_DIR = ./obj 11 | LIB_DIR = ./lib 12 | 13 | LIB_PATH := $(LIB_DIR)/$(LIB_NAME) 14 | 15 | CPP_PATHS := $(shell find $(SRC_DIR) -name *.cpp) 16 | CPP_FILES := $(notdir ${CPP_PATHS}) 17 | OBJ_PATHS := $(CPP_FILES:%=$(OBJ_DIR)/%.o) 18 | GCC_MAKEFILES := $(OBJ_PATHS:.o=.d) 19 | 20 | # Uncomment these and point them to your libraries if 21 | # their includes and libs are not on the default paths 22 | #DEP_CXXFLAGS := -I../lib/glfw-3.2.1/dist/include/ -I../lib/glew-2.1.0/dist/include/ 23 | #DEP_LDFLAGS := -L../lib/glfw-3.2.1/dist/lib/ -L../lib/glew-2.1.0/dist/lib/ 24 | 25 | OS := $(shell uname) 26 | ifeq ($(OS),Darwin) 27 | GL_LDFLAGS = -framework OpenGL 28 | MORE_LDFLAGS = -framework Cocoa -framework CoreVideo -framework IOKit 29 | else 30 | GL_LDFLAGS = -lGL 31 | endif 32 | 33 | CPPFLAGS_NVG := -MMD -MP 34 | CPPFLAGS := -MMD -MP -Inanovg/src/ 35 | CXXFLAGS := -Wall -Wextra -std=c++14 $(DEP_CXXFLAGS) 36 | LDFLAGS := -lstdc++ -lm -lGLEW -lglfw $(GL_LDFLAGS) $(DEP_LDFLAGS) $(MORE_LDFLAGS) 37 | 38 | MKDIR_P = mkdir -p 39 | 40 | PUBLIC_INCLUDES = graph_decoder.h graph_editor.h output.h util_platform.h 41 | PUBLIC_INCLUDE_DST := $(addprefix $(INC_DIR)/,$(PUBLIC_INCLUDES)) 42 | 43 | $(BINARY_NAME): $(LIB_PATH) $(PUBLIC_INCLUDE_DST) 44 | $(MKDIR_P) $(dir $@) 45 | $(CXX) ./example/main.cpp $(LIB_DIR)/$(LIB_NAME) $(CXXFLAGS) $(LDFLAGS) -I$(INC_DIR) -o $@ 46 | 47 | $(OBJ_DIR)/%.cpp.o: $(SRC_DIR)/%.cpp 48 | $(MKDIR_P) $(dir $@) 49 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ 50 | 51 | $(OBJ_DIR)/nanovg.o: nanovg/src/nanovg.c 52 | $(MKDIR_P) $(dir $@) 53 | $(CC) $(CPPFLAGS_NVG) -c $< -o $@ 54 | 55 | $(INC_DIR)/%.h: $(SRC_DIR)/%.h 56 | $(MKDIR_P) $(dir $@) 57 | cp $< $@ 58 | 59 | $(LIB_PATH): $(OBJ_PATHS) $(OBJ_DIR)/nanovg.o 60 | $(MKDIR_P) $(dir $@) 61 | $(AR) rcs $(LIB_DIR)/$(LIB_NAME) $^ 62 | 63 | iwyu: CXX := include-what-you-use 64 | iwyu: CXXFLAGS += -Xiwyu --mapping_file=./extra/iwyu.imp 65 | iwyu: $(BINARY_NAME) 66 | 67 | -include $(GCC_MAKEFILES) 68 | 69 | clean: 70 | rm -rf $(OBJ_DIR) 71 | rm -rf $(INC_DIR) 72 | rm -rf $(LIB_DIR) 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Notice: This repository is no longer being actively developed or maintained. I have abandoned this version in favor of a ground up rewrite that is available [here](https://github.com/jlwitthuhn/cycles-shader-editor-imgui/). 2 | 3 | # Cycles Shader Editor 4 | 5 | Cycles Shader Editor is a cross-platform C++ library that provides a graphical editor for creating [Cycles](https://www.cycles-renderer.org/) shader graphs with a simple drag and drop interface. It is intended to be used as part of a plugin that integrates Cycles with a DCC app and was created as part of [my own plugin for 3ds Max](https://cyclesformax.net/). 6 | 7 | ![Screenshot](/extra/screenshot.png) 8 | 9 | ## Running the Example Program 10 | 11 | Included in this repository is a simple program that creates a node graph editor window and, when the user saves, writes the serialized node graph to stdout. 12 | 13 | ### Prerequisites 14 | 15 | This should build on any plaform that GLFW builds on, but I have only tested it with Visual Studio 2015, GCC 9.2.1 on Fedora 31, Clang 9.0.0 on Fedora 31, and Apple Clang 9.0.0 on macOS 10.13 16 | 17 | Libraries you will need are: 18 | - GLFW 3.x 19 | - GLEW 20 | - NanoVG 21 | - A compatible version is bundled in this repository, but any moderately recent version should work. 22 | 23 | A note about NanoVG: The bundled version of NanoVG has an extra function defined, nvgCreateFontW. This works the same as the normal nvgCreateFont but with wchar_t paths rather than char paths. This extra function is required only for building the Windows version of the shader editor because Windows likes to use wchar_t for unicode strings. 24 | 25 | OpenGL 2.0 support is required to run the editor. 26 | 27 | This project does not depend on any Cycles code or headers. 28 | 29 | ### Building 30 | 31 | On unixy systems, you can simply run `make` to build the example program. It will produce a binary named `shader_editor` in the top-level directory. It will also produce a `lib/libshadereditor.a` static library. 32 | 33 | On Windows I haven't provided a visual studio project, but it should build fine if you put all the source files together in a C++ project with default settings. 34 | 35 | ### Using the Editor 36 | 37 | * To create a node, drag and drop from a button in the 'Nodes' window onto the grid. 38 | * To create a connection, click and drag from the output circle of one node to the input of another. 39 | * To change a parameter, click the parameter's name on the node. 40 | * To pan the view, use the arrow keys on your keyboard or middle-click and drag anywhere on the grid. 41 | * To write the serialized version of your graph to stdout, click the save button or press ctrl+s. 42 | 43 | ## Calling the Shader Editor from Your Code 44 | 45 | You will need just a few headers to be able to use this library's functionality. With the makefile included here, these headers will automatically get copied to the `include` top-level directory during the build. For those not using the makefile, these headers are: 46 | 47 | * graph_decoder.h 48 | * graph_editor.h 49 | * output.h 50 | * util_platform.h 51 | 52 | All types defined in these headers are a part of the `cse` namespace. `graph_decoder.h` and `graph_editor.h` are the only two you will need to #include directly, the others define types used by graph_decoder and graph_editor. 53 | 54 | ### Creating a Window 55 | 56 | The included example program is close to the smallest program possible to make with the library. It is reproduced below. 57 | 58 | ```C++ 59 | #include 60 | 61 | #include 62 | #include 63 | 64 | int main() 65 | { 66 | cse::GraphEditor node_window; 67 | 68 | node_window.create_window(); 69 | 70 | while (node_window.run_window_loop_iteration()) { 71 | // Check if data is ready 72 | std::string graph; 73 | if (node_window.get_serialized_graph(graph)) { 74 | std::cout << "Graph saved:\n" << graph << std::endl; 75 | } 76 | } 77 | return 0; 78 | } 79 | ``` 80 | 81 | You will use largely the same basic flow when using this library. The key points here are: 82 | 83 | * cse::GraphEditor is the main class used to represent a Node Graph Editor window. 84 | * Calling GraphEditor::create_window() will cause the window to appear. 85 | * Once a window exists, loop calling GraphEditor::run_window_loop_iteration() until it returns false. 86 | * This method is responsible for handling user input and drawing. 87 | * It will return false once the window has been closed. 88 | * Call GraphEditor::get_serialized_graph to get the latest serialized graph from the window. 89 | * This function will return true if the graph has been updated since the last time get_serialized_graph was called. 90 | 91 | ### Decoding the Graph String 92 | 93 | Now you can create a window and get a serialized graph from it, but that string is not very useful on its own. 94 | 95 | To help with this, you can use the cse::CyclesNodeGraph class defined in `graph_decoder.h`. This class has a single constructor that takes a serialized graph string as an argument. Once the object construction is complete, the 'nodes' and 'connections' member variables will be populated with relevant information. 96 | 97 | ### Constructing a ccl::ShaderGraph 98 | 99 | The file [extra/shader_graph_converter.cpp](extra/shader_graph_converter.cpp) contains some functions that can be used to create a ccl::ShaderGraph from a serialized graph string. These are not included in the main project to avoid requiring Cycles as a dependency for building the editor. 100 | 101 | ## The Serialized Graph Format 102 | 103 | The format for serialized graph strings is very simple. It is a series of pipe-delimited strings. The basic format is: 104 | ``` 105 | cycles_shader|1|section_nodes|[NODES]|section_connections|[CONNECTIONS]| 106 | ^ ^ ^ ^ ^ ^ 107 | 1 2 3 4 5 6 108 | ``` 109 | 1. special string to identify shader graph files 110 | 2. serialization format version 111 | 3. nodes follow this marker 112 | 4. list of nodes 113 | 5. connections follow this marker 114 | 6. list of connections 115 | 116 | The format of each node is: 117 | ``` 118 | TYPE|NAME|X_POS|Y_POS|INPUT_NAME|INPUT_VALUE|...|node_end 119 | ``` 120 | 121 | * TYPE is the type of the node, such as diffuse_bsdf 122 | * NAME is a unique identifier for the node 123 | * X_POS and Y_POS are for the position of the node inside the graph editor 124 | * INPUT_NAME and INPUT_VALUE can be repeated any number of times to cover all of a node's inputs 125 | 126 | The format of each connection is: 127 | ``` 128 | SRC_NODE|SRC_SLOT|DST_NODE|DST_SLOT 129 | ``` 130 | * SRC_NODE and DST_NODE are node names, as described above 131 | * SRC_SLOT and DST_SLOT are socket names 132 | 133 | Bringing this all together, a simple diffuse shader will have a serialized graph that resembles this: 134 | ``` 135 | cycles_shader|1|section_nodes|diffuse_bsdf|node1|0|0|roughness|0|color|0,1,1|node_end|out_material|output|200|0|node_end|section_connections|node1|BSDF|output|Surface| 136 | ``` 137 | 138 | ## License 139 | 140 | This project is available under the MIT license. The full text of the license is available in [LICENSE.txt](LICENSE.txt) 141 | -------------------------------------------------------------------------------- /example/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | cse::GraphEditor node_window; 9 | 10 | node_window.create_window(); 11 | 12 | while (node_window.run_window_loop_iteration()) { 13 | // Check if data is ready 14 | std::string graph; 15 | if (node_window.get_serialized_graph(graph)) { 16 | std::cout << "Graph saved:\n" << graph << std::endl; 17 | } 18 | } 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /extra/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copy this to the project's top level to make it work 2 | # Builds a static library of cycles-shader-graph_editor 3 | 4 | cmake_minimum_required(VERSION 3.14) 5 | 6 | include_directories("${NANOVG_INCLUDE_DIR}") 7 | include_directories("${GLFW_INCLUDE_DIR}") 8 | include_directories("${GLEW_INCLUDE_DIR}") 9 | 10 | file(GLOB LibSources ./src/*.cpp) 11 | add_library(neditor STATIC ${LibSources}) 12 | set_target_properties(neditor PROPERTIES PUBLIC_HEADER "./src/graph_decoder.h;./src/graph_editor.h;./src/output.h;./src/util_platform.h;./src/util_vector.h") 13 | 14 | add_definitions(-DGLEW_STATIC) 15 | 16 | install(TARGETS neditor 17 | LIBRARY DESTINATION ./lib 18 | PUBLIC_HEADER DESTINATION ./include/neditor 19 | ) 20 | -------------------------------------------------------------------------------- /extra/iwyu.imp: -------------------------------------------------------------------------------- 1 | [ 2 | { include: ["", "private", "", "public"] }, 3 | { symbol: ["std::ceil", "private", "", "public"] }, 4 | { symbol: ["std::floor", "private", "", "public"] } 5 | ] 6 | -------------------------------------------------------------------------------- /extra/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jlwitthuhn/cycles-shader-editor/0d6771801402ca7ecff006f399be90b1beea2884/extra/screenshot.png -------------------------------------------------------------------------------- /font/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010, 2012, 2014 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name ‘Source’. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /font/SourceSansPro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jlwitthuhn/cycles-shader-editor/0d6771801402ca7ecff006f399be90b1beea2884/font/SourceSansPro-Regular.ttf -------------------------------------------------------------------------------- /nanovg/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Mikko Mononen memon@inside.org 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | 19 | -------------------------------------------------------------------------------- /nanovg/README.md: -------------------------------------------------------------------------------- 1 | NanoVG 2 | ========== 3 | 4 | NanoVG is small antialiased vector graphics rendering library for OpenGL. It has lean API modeled after HTML5 canvas API. It is aimed to be a practical and fun toolset for building scalable user interfaces and visualizations. 5 | 6 | ## Screenshot 7 | 8 | ![screenshot of some text rendered witht the sample program](/example/screenshot-01.png?raw=true) 9 | 10 | Usage 11 | ===== 12 | 13 | The NanoVG API is modeled loosely on HTML5 canvas API. If you know canvas, you're up to speed with NanoVG in no time. 14 | 15 | ## Creating drawing context 16 | 17 | The drawing context is created using platform specific constructor function. If you're using the OpenGL 2.0 back-end the context is created as follows: 18 | ```C 19 | #define NANOVG_GL2_IMPLEMENTATION // Use GL2 implementation. 20 | #include "nanovg_gl.h" 21 | ... 22 | struct NVGcontext* vg = nvgCreateGL2(NVG_ANTIALIAS | NVG_STENCIL_STROKES); 23 | ``` 24 | 25 | The first parameter defines flags for creating the renderer. 26 | 27 | - `NVG_ANTIALIAS` means that the renderer adjusts the geometry to include anti-aliasing. If you're using MSAA, you can omit this flags. 28 | - `NVG_STENCIL_STROKES` means that the render uses better quality rendering for (overlapping) strokes. The quality is mostly visible on wider strokes. If you want speed, you can omit this flag. 29 | 30 | Currently there is an OpenGL back-end for NanoVG: [nanovg_gl.h](/src/nanovg_gl.h) for OpenGL 2.0, OpenGL ES 2.0, OpenGL 3.2 core profile and OpenGL ES 3. The implementation can be chosen using a define as in above example. See the header file and examples for further info. 31 | 32 | *NOTE:* The render target you're rendering to must have stencil buffer. 33 | 34 | ## Drawing shapes with NanoVG 35 | 36 | Drawing a simple shape using NanoVG consists of four steps: 1) begin a new shape, 2) define the path to draw, 3) set fill or stroke, 4) and finally fill or stroke the path. 37 | 38 | ```C 39 | nvgBeginPath(vg); 40 | nvgRect(vg, 100,100, 120,30); 41 | nvgFillColor(vg, nvgRGBA(255,192,0,255)); 42 | nvgFill(vg); 43 | ``` 44 | 45 | Calling `nvgBeginPath()` will clear any existing paths and start drawing from blank slate. There are number of number of functions to define the path to draw, such as rectangle, rounded rectangle and ellipse, or you can use the common moveTo, lineTo, bezierTo and arcTo API to compose the paths step by step. 46 | 47 | ## Understanding Composite Paths 48 | 49 | Because of the way the rendering backend is build in NanoVG, drawing a composite path, that is path consisting from multiple paths defining holes and fills, is a bit more involved. NanoVG uses even-odd filling rule and by default the paths are wound in counter clockwise order. Keep that in mind when drawing using the low level draw API. In order to wind one of the predefined shapes as a hole, you should call `nvgPathWinding(vg, NVG_HOLE)`, or `nvgPathWinding(vg, NVG_CW)` _after_ defining the path. 50 | 51 | ``` C 52 | nvgBeginPath(vg); 53 | nvgRect(vg, 100,100, 120,30); 54 | nvgCircle(vg, 120,120, 5); 55 | nvgPathWinding(vg, NVG_HOLE); // Mark circle as a hole. 56 | nvgFillColor(vg, nvgRGBA(255,192,0,255)); 57 | nvgFill(vg); 58 | ``` 59 | 60 | ## Rendering is wrong, what to do? 61 | 62 | - make sure you have created NanoVG context using one of the `nvgCreatexxx()` calls 63 | - make sure you have initialised OpenGL with *stencil buffer* 64 | - make sure you have cleared stencil buffer 65 | - make sure all rendering calls happen between `nvgBeginFrame()` and `nvgEndFrame()` 66 | - to enable more checks for OpenGL errors, add `NVG_DEBUG` flag to `nvgCreatexxx()` 67 | - if the problem still persists, please report an issue! 68 | 69 | ## OpenGL state touched by the backend 70 | 71 | The OpenGL back-end touches following states: 72 | 73 | When textures are uploaded or updated, the following pixel store is set to defaults: `GL_UNPACK_ALIGNMENT`, `GL_UNPACK_ROW_LENGTH`, `GL_UNPACK_SKIP_PIXELS`, `GL_UNPACK_SKIP_ROWS`. Texture binding is also affected. Texture updates can happen when the user loads images, or when new font glyphs are added. Glyphs are added as needed between calls to `nvgBeginFrame()` and `nvgEndFrame()`. 74 | 75 | The data for the whole frame is buffered and flushed in `nvgEndFrame()`. The following code illustrates the OpenGL state touched by the rendering code: 76 | ```C 77 | glUseProgram(prog); 78 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 79 | glEnable(GL_CULL_FACE); 80 | glCullFace(GL_BACK); 81 | glFrontFace(GL_CCW); 82 | glEnable(GL_BLEND); 83 | glDisable(GL_DEPTH_TEST); 84 | glDisable(GL_SCISSOR_TEST); 85 | glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 86 | glStencilMask(0xffffffff); 87 | glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 88 | glStencilFunc(GL_ALWAYS, 0, 0xffffffff); 89 | glActiveTexture(GL_TEXTURE0); 90 | glBindBuffer(GL_UNIFORM_BUFFER, buf); 91 | glBindVertexArray(arr); 92 | glBindBuffer(GL_ARRAY_BUFFER, buf); 93 | glBindTexture(GL_TEXTURE_2D, tex); 94 | glUniformBlockBinding(... , GLNVG_FRAG_BINDING); 95 | ``` 96 | 97 | ## API Reference 98 | 99 | See the header file [nanovg.h](/src/nanovg.h) for API reference. 100 | 101 | ## Ports 102 | 103 | - [DX11 port](https://github.com/cmaughan/nanovg) by [Chris Maughan](https://github.com/cmaughan) 104 | - [Metal port](https://github.com/ollix/MetalNanoVG) by [Olli Wang](https://github.com/olliwang) 105 | - [bgfx port](https://github.com/bkaradzic/bgfx/tree/master/examples/20-nanovg) by [Branimir Karadžić](https://github.com/bkaradzic) 106 | 107 | ## Projects using NanoVG 108 | 109 | - [Processing API simulation by vinjn](https://github.com/vinjn/island/blob/master/examples/01-processing/sketch2d.h) 110 | 111 | ## License 112 | The library is licensed under [zlib license](LICENSE.txt) 113 | 114 | ## Discussions 115 | [NanoVG mailing list](https://groups.google.com/forum/#!forum/nanovg) 116 | 117 | ## Links 118 | Uses [stb_truetype](http://nothings.org) (or, optionally, [freetype](http://freetype.org)) for font rendering. 119 | Uses [stb_image](http://nothings.org) for image loading. 120 | -------------------------------------------------------------------------------- /nanovg/src/nanovg_gl_utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2009-2013 Mikko Mononen memon@inside.org 3 | // 4 | // This software is provided 'as-is', without any express or implied 5 | // warranty. In no event will the authors be held liable for any damages 6 | // arising from the use of this software. 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 1. The origin of this software must not be misrepresented; you must not 11 | // claim that you wrote the original software. If you use this software 12 | // in a product, an acknowledgment in the product documentation would be 13 | // appreciated but is not required. 14 | // 2. Altered source versions must be plainly marked as such, and must not be 15 | // misrepresented as being the original software. 16 | // 3. This notice may not be removed or altered from any source distribution. 17 | // 18 | #ifndef NANOVG_GL_UTILS_H 19 | #define NANOVG_GL_UTILS_H 20 | 21 | struct NVGLUframebuffer { 22 | NVGcontext* ctx; 23 | GLuint fbo; 24 | GLuint rbo; 25 | GLuint texture; 26 | int image; 27 | }; 28 | typedef struct NVGLUframebuffer NVGLUframebuffer; 29 | 30 | // Helper function to create GL frame buffer to render to. 31 | void nvgluBindFramebuffer(NVGLUframebuffer* fb); 32 | NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags); 33 | void nvgluDeleteFramebuffer(NVGLUframebuffer* fb); 34 | 35 | #endif // NANOVG_GL_UTILS_H 36 | 37 | #ifdef NANOVG_GL_IMPLEMENTATION 38 | 39 | #if defined(NANOVG_GL3) || defined(NANOVG_GLES2) || defined(NANOVG_GLES3) 40 | // FBO is core in OpenGL 3>. 41 | # define NANOVG_FBO_VALID 1 42 | #elif defined(NANOVG_GL2) 43 | // On OS X including glext defines FBO on GL2 too. 44 | # ifdef __APPLE__ 45 | # include 46 | # define NANOVG_FBO_VALID 1 47 | # endif 48 | #endif 49 | 50 | static GLint defaultFBO = -1; 51 | 52 | NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags) 53 | { 54 | #ifdef NANOVG_FBO_VALID 55 | GLint defaultFBO; 56 | GLint defaultRBO; 57 | NVGLUframebuffer* fb = NULL; 58 | 59 | glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO); 60 | glGetIntegerv(GL_RENDERBUFFER_BINDING, &defaultRBO); 61 | 62 | fb = (NVGLUframebuffer*)malloc(sizeof(NVGLUframebuffer)); 63 | if (fb == NULL) goto error; 64 | memset(fb, 0, sizeof(NVGLUframebuffer)); 65 | 66 | fb->image = nvgCreateImageRGBA(ctx, w, h, imageFlags | NVG_IMAGE_FLIPY | NVG_IMAGE_PREMULTIPLIED, NULL); 67 | 68 | #if defined NANOVG_GL2 69 | fb->texture = nvglImageHandleGL2(ctx, fb->image); 70 | #elif defined NANOVG_GL3 71 | fb->texture = nvglImageHandleGL3(ctx, fb->image); 72 | #elif defined NANOVG_GLES2 73 | fb->texture = nvglImageHandleGLES2(ctx, fb->image); 74 | #elif defined NANOVG_GLES3 75 | fb->texture = nvglImageHandleGLES3(ctx, fb->image); 76 | #endif 77 | 78 | fb->ctx = ctx; 79 | 80 | // frame buffer object 81 | glGenFramebuffers(1, &fb->fbo); 82 | glBindFramebuffer(GL_FRAMEBUFFER, fb->fbo); 83 | 84 | // render buffer object 85 | glGenRenderbuffers(1, &fb->rbo); 86 | glBindRenderbuffer(GL_RENDERBUFFER, fb->rbo); 87 | glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, w, h); 88 | 89 | // combine all 90 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->texture, 0); 91 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->rbo); 92 | 93 | if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { 94 | #ifdef GL_DEPTH24_STENCIL8 95 | // If GL_STENCIL_INDEX8 is not supported, try GL_DEPTH24_STENCIL8 as a fallback. 96 | // Some graphics cards require a depth buffer along with a stencil. 97 | glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h); 98 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->texture, 0); 99 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->rbo); 100 | 101 | if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) 102 | #endif // GL_DEPTH24_STENCIL8 103 | goto error; 104 | } 105 | 106 | glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); 107 | glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO); 108 | return fb; 109 | error: 110 | glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); 111 | glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO); 112 | nvgluDeleteFramebuffer(fb); 113 | return NULL; 114 | #else 115 | NVG_NOTUSED(ctx); 116 | NVG_NOTUSED(w); 117 | NVG_NOTUSED(h); 118 | NVG_NOTUSED(imageFlags); 119 | return NULL; 120 | #endif 121 | } 122 | 123 | void nvgluBindFramebuffer(NVGLUframebuffer* fb) 124 | { 125 | #ifdef NANOVG_FBO_VALID 126 | if (defaultFBO == -1) glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO); 127 | glBindFramebuffer(GL_FRAMEBUFFER, fb != NULL ? fb->fbo : defaultFBO); 128 | #else 129 | NVG_NOTUSED(fb); 130 | #endif 131 | } 132 | 133 | void nvgluDeleteFramebuffer(NVGLUframebuffer* fb) 134 | { 135 | #ifdef NANOVG_FBO_VALID 136 | if (fb == NULL) return; 137 | if (fb->fbo != 0) 138 | glDeleteFramebuffers(1, &fb->fbo); 139 | if (fb->rbo != 0) 140 | glDeleteRenderbuffers(1, &fb->rbo); 141 | if (fb->image >= 0) 142 | nvgDeleteImage(fb->ctx, fb->image); 143 | fb->ctx = NULL; 144 | fb->fbo = 0; 145 | fb->rbo = 0; 146 | fb->texture = 0; 147 | fb->image = -1; 148 | free(fb); 149 | #else 150 | NVG_NOTUSED(fb); 151 | #endif 152 | } 153 | 154 | #endif // NANOVG_GL_IMPLEMENTATION 155 | -------------------------------------------------------------------------------- /src/button_category.cpp: -------------------------------------------------------------------------------- 1 | #include "button_category.h" 2 | 3 | #include 4 | 5 | #include "drawing.h" 6 | 7 | cse::NodeCategoryButtonPlacer::NodeCategoryButtonPlacer(const Float2 draw_origin, const float parent_width, const float vertical_padding) 8 | { 9 | this->draw_origin = draw_origin; 10 | this->parent_width = parent_width; 11 | this->vertical_padding = vertical_padding; 12 | 13 | this->button_width = NodeCategoryButton::get_button_width(); 14 | this->button_height = NodeCategoryButton::get_button_height(); 15 | 16 | positions_made = 0; 17 | } 18 | 19 | cse::Float2 cse::NodeCategoryButtonPlacer::next_button_position() 20 | { 21 | // Find horizontal position 22 | float x; 23 | const float total_content_width = button_width * 2 + UI_SUBWIN_NODE_LIST_CAT_BUTTON_HGAP; 24 | const float hpadding = std::floor((parent_width - total_content_width) / 2.0f); 25 | // Left button 26 | if (positions_made % 2 == 0) { 27 | x = hpadding; 28 | } 29 | // Right button 30 | else { 31 | x = parent_width - hpadding - button_width; 32 | } 33 | 34 | const int rows_complete = positions_made / 2; 35 | const float y = rows_complete * (2 * vertical_padding + button_height) + vertical_padding; 36 | 37 | ++positions_made; 38 | 39 | const Float2 result(x, y); 40 | return draw_origin + result; 41 | } 42 | 43 | float cse::NodeCategoryButtonPlacer::get_draw_height() 44 | { 45 | const int row_count = (positions_made + 1) / 2; 46 | 47 | return row_count * (2 * vertical_padding + button_height); 48 | } 49 | 50 | cse::NodeCategoryButton::NodeCategoryButton(const std::string& label) 51 | { 52 | this->label = label; 53 | } 54 | 55 | void cse::NodeCategoryButton::draw(const Float2 draw_position, NVGcontext* const draw_context) 56 | { 57 | constexpr bool BUTTON_ENABLED = true; 58 | Drawing::draw_button(draw_context, draw_position, get_button_width(), get_button_height(), label, BUTTON_ENABLED, selected); 59 | } 60 | 61 | void cse::NodeCategoryButton::update_mouse_position(const Float2 local_position) 62 | { 63 | mouse_local_pos = local_position; 64 | } 65 | 66 | bool cse::NodeCategoryButton::is_mouse_over_button() const 67 | { 68 | return ( 69 | mouse_local_pos.x > 0.0f && 70 | mouse_local_pos.x < get_button_width() && 71 | mouse_local_pos.y > 0.0f && 72 | mouse_local_pos.y < get_button_height() 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /src/button_category.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "buttons_nodes.h" 8 | #include "gui_sizes.h" 9 | #include "util_vector.h" 10 | 11 | struct NVGcontext; 12 | 13 | namespace cse { 14 | 15 | class NodeCategoryButtonPlacer { 16 | public: 17 | NodeCategoryButtonPlacer(Float2 draw_origin, float parent_width, float vertical_padding); 18 | Float2 next_button_position(); 19 | float get_draw_height(); 20 | 21 | private: 22 | Float2 draw_origin; 23 | float button_width; 24 | float button_height; 25 | float parent_width; 26 | float vertical_padding; 27 | 28 | int positions_made; 29 | }; 30 | 31 | class NodeCategoryButton { 32 | public: 33 | static float get_button_width() { return UI_SUBWIN_NODE_LIST_CAT_BUTTON_WIDTH; } 34 | static float get_button_height() { return UI_SUBWIN_NODE_LIST_CAT_BUTTON_HEIGHT; } 35 | 36 | NodeCategoryButton(const std::string& label); 37 | 38 | void draw(Float2 draw_position, NVGcontext* draw_context); 39 | void update_mouse_position(Float2 local_position); 40 | 41 | bool is_mouse_over_button() const; 42 | 43 | bool selected = false; 44 | 45 | std::list> node_buttons; 46 | 47 | private: 48 | std::string label; 49 | 50 | Float2 mouse_local_pos; 51 | 52 | }; 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/buttons_nodes.cpp: -------------------------------------------------------------------------------- 1 | #include "buttons_nodes.h" 2 | 3 | #include "drawing.h" 4 | #include "gui_sizes.h" 5 | 6 | float cse::NodeCreationButton::draw(NVGcontext* const draw_context, const Float2 draw_origin, const Float2 parent_local_mouse_pos, const float parent_width) 7 | { 8 | // Coordinates to draw the actual button at 9 | // This is the input draw_origin adjusted to account for padding 10 | draw_pos = draw_origin + cse::Float2(UI_SUBWIN_NODE_LIST_NODE_BUTTON_HPADDING, UI_SUBWIN_NODE_LIST_BUTTON_VPADDING); 11 | 12 | this->mouse_parent_pos = parent_local_mouse_pos; 13 | 14 | button_width = parent_width - 2 * UI_SUBWIN_NODE_LIST_NODE_BUTTON_HPADDING; 15 | if (button_width < 0.0f) { 16 | return 0.0f; 17 | } 18 | 19 | Drawing::draw_button(draw_context, draw_pos, button_width, UI_SUBWIN_NODE_LIST_NODE_BUTTON_HEIGHT, label.c_str(), true, pressed); 20 | 21 | return UI_SUBWIN_NODE_LIST_NODE_BUTTON_HEIGHT + UI_SUBWIN_NODE_LIST_BUTTON_VPADDING * 2; 22 | } 23 | 24 | bool cse::NodeCreationButton::is_mouse_over_button() 25 | { 26 | if (mouse_parent_pos.x > draw_pos.x && 27 | mouse_parent_pos.x < draw_pos.x + button_width && 28 | mouse_parent_pos.y > draw_pos.y && 29 | mouse_parent_pos.y < draw_pos.y + UI_SUBWIN_NODE_LIST_NODE_BUTTON_HEIGHT) 30 | { 31 | return true; 32 | } 33 | 34 | return false; 35 | } 36 | -------------------------------------------------------------------------------- /src/buttons_nodes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "util_vector.h" 7 | 8 | struct NVGcontext; 9 | 10 | namespace cse { 11 | 12 | class EditableNode; 13 | 14 | class NodeCreationButton { 15 | public: 16 | virtual ~NodeCreationButton() {} 17 | 18 | virtual float draw(NVGcontext* draw_context, Float2 draw_origin, Float2 parent_local_mouse_pos, float parent_width); 19 | virtual bool is_mouse_over_button(); 20 | 21 | virtual std::shared_ptr create_node() = 0; 22 | 23 | bool pressed = false; 24 | 25 | protected: 26 | std::string label; 27 | 28 | Float2 mouse_parent_pos; // Mouse position, local within parent subwindow 29 | Float2 draw_pos; 30 | 31 | float button_width; 32 | }; 33 | 34 | // Generic 35 | template 36 | class GenericNodeButton : public NodeCreationButton { 37 | public: 38 | GenericNodeButton() { 39 | const Float2 irrelevant_position(0.0f, 0.0f); 40 | const T tmp_node(irrelevant_position); 41 | this->label = tmp_node.get_title(); 42 | } 43 | 44 | virtual std::shared_ptr create_node() override { 45 | const Float2 irrelevant_position(0.0f, 0.0f); 46 | return std::make_shared(irrelevant_position); 47 | } 48 | }; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define INCLUDE_MAX_INTEGRATION 4 | 5 | // this should match RAMP_TABLE_SIZE defined in cycles kernel_types.h 6 | #define CURVE_TABLE_SIZE 256 7 | -------------------------------------------------------------------------------- /src/curve.cpp: -------------------------------------------------------------------------------- 1 | #include "curve.h" 2 | 3 | #include 4 | 5 | #include "sockets.h" 6 | #include "util_enum.h" 7 | #include "util_hermite_spline.h" 8 | 9 | cse::CurveEvaluator::CurveEvaluator(CurveSocketValue* const curve_socket_val, const int segments) 10 | { 11 | const std::vector& curve_control_points = curve_socket_val->curve_points; 12 | 13 | // No points, in this case treat as output = input 14 | if (curve_control_points.size() == 0) { 15 | const Float2 point_0(0.0f, 0.0f); 16 | const Float2 point_1(1.0f, 1.0f); 17 | sampled_points.push_back(point_0); 18 | sampled_points.push_back(point_1); 19 | return; 20 | } 21 | // One point, this represents constant output 22 | else if (curve_control_points.size() == 1) { 23 | const float const_output = curve_control_points[0].y; 24 | const Float2 point_0(0.0f, const_output); 25 | const Float2 point_1(1.0f, const_output); 26 | sampled_points.push_back(point_0); 27 | sampled_points.push_back(point_1); 28 | return; 29 | } 30 | 31 | if (curve_socket_val->curve_interp == CurveInterpolation::CUBIC_HERMITE) { 32 | // Sample as a series of cubic hermite splines 33 | constexpr float FIRST_SAMPLE_X = 0.0f; 34 | constexpr float LAST_SAMPLE_X = 1.0f; 35 | 36 | typedef std::vector::size_type vec_fp_size_t; 37 | 38 | const float x_increment = (LAST_SAMPLE_X - FIRST_SAMPLE_X) / segments; 39 | float next_sample_x = FIRST_SAMPLE_X; 40 | const vec_fp_size_t final_segment_begin_index = curve_control_points.size() - 2; 41 | for (vec_fp_size_t segment_begin_index = 0; segment_begin_index <= final_segment_begin_index; segment_begin_index++) { 42 | const Float2 point_b = curve_control_points[segment_begin_index]; 43 | const Float2 point_c = curve_control_points[segment_begin_index + 1]; 44 | Float2 point_a, point_d; 45 | if (segment_begin_index == 0) { 46 | point_a = point_b + point_b - point_c; 47 | } 48 | else { 49 | point_a = curve_control_points[segment_begin_index - 1]; 50 | } 51 | if (segment_begin_index == final_segment_begin_index) { 52 | point_d = point_c + point_c - point_b; 53 | } 54 | else { 55 | point_d = curve_control_points[segment_begin_index + 2]; 56 | } 57 | CurveEvaluator hermite_spline(point_a, point_b, point_c, point_d); 58 | 59 | // If the sample is before the first control point(b), use the value of the first control point 60 | while (next_sample_x <= LAST_SAMPLE_X && hermite_spline.compare_to_range(next_sample_x) == -1) { 61 | const Float2 new_point(next_sample_x, point_b.y); 62 | sampled_points.push_back(new_point); 63 | next_sample_x += x_increment; 64 | } 65 | 66 | // If the sample is in this range, evaluate normally 67 | while (next_sample_x <= LAST_SAMPLE_X && hermite_spline.compare_to_range(next_sample_x) == 0) { 68 | const float sampled_y = hermite_spline.eval(next_sample_x); 69 | const Float2 new_point(next_sample_x, sampled_y); 70 | sampled_points.push_back(new_point); 71 | next_sample_x += x_increment; 72 | } 73 | 74 | // If the sample is past this range and we are in the last segment, use the value of the last control point 75 | while (next_sample_x <= LAST_SAMPLE_X && hermite_spline.compare_to_range(next_sample_x) == 1 && segment_begin_index == final_segment_begin_index) { 76 | const Float2 new_point(next_sample_x, point_c.y); 77 | sampled_points.push_back(new_point); 78 | next_sample_x += x_increment; 79 | } 80 | } 81 | } 82 | else { 83 | // Default is linear 84 | // In this case, copy points directly rather than sampling 85 | for (const Float2 this_point : curve_control_points) { 86 | sampled_points.push_back(this_point); 87 | } 88 | } 89 | } 90 | 91 | cse::CurveEvaluator::CurveEvaluator(const std::shared_ptr curve_socket_val, const int segments) : CurveEvaluator(curve_socket_val.get(), segments) 92 | { 93 | 94 | } 95 | 96 | cse::CurveEvaluator::CurveEvaluator(const Float2 a, const Float2 b, const Float2 c, const Float2 d, const int segments) 97 | { 98 | const CubicHermiteSplineInterpolator x_solver(a.x, b.x, c.x, d.x); 99 | const CubicHermiteSplineInterpolator y_solver(a.y, b.y, c.y, d.y); 100 | 101 | const float segment_size = 1.0f / segments; 102 | for (int i = 0; i <= segments; i++) { 103 | const float this_t = segment_size * i; 104 | const float this_x = x_solver.eval(this_t); 105 | const float this_y = y_solver.eval(this_t); 106 | const Float2 this_point(this_x, this_y); 107 | sampled_points.push_back(this_point); 108 | } 109 | } 110 | 111 | int cse::CurveEvaluator::compare_to_range(const float in_value) const 112 | { 113 | if (sampled_points.size() < 1) { 114 | return 0; 115 | } 116 | 117 | const float first = sampled_points[0].x; 118 | if (in_value < first) { 119 | return -1; 120 | } 121 | 122 | const float last = sampled_points[sampled_points.size() - 1].x; 123 | if (in_value > last) { 124 | return 1; 125 | } 126 | 127 | return 0; 128 | } 129 | 130 | float cse::CurveEvaluator::eval(const float in_value) const 131 | { 132 | if (sampled_points.size() < 2) { 133 | return in_value; 134 | } 135 | 136 | typedef std::vector::size_type vec_fp_size_t; 137 | 138 | // sampled_points is ordered by x value 139 | // do a binary search to find which two values surround the input value 140 | vec_fp_size_t min_index = 0; 141 | vec_fp_size_t max_index = sampled_points.size() - 1; 142 | while ((max_index - min_index) > 1) { 143 | const vec_fp_size_t delta = max_index - min_index; 144 | const vec_fp_size_t index_to_check = min_index + delta / 2; 145 | const float value_to_check = sampled_points[index_to_check].x; 146 | if (in_value >= value_to_check) { 147 | min_index = index_to_check; 148 | } 149 | else { 150 | max_index = index_to_check; 151 | } 152 | } 153 | 154 | const Float2 point_a = sampled_points[min_index]; 155 | const Float2 point_b = sampled_points[max_index]; 156 | const float x_min = point_a.x; 157 | const float x_max = point_b.x; 158 | const float x_delta = x_max - x_min; 159 | 160 | if (in_value < x_min) { 161 | return point_a.y; 162 | } 163 | if (in_value > x_max) { 164 | return point_b.y; 165 | } 166 | 167 | const float weight_factor = (in_value - x_min) / (x_delta); 168 | const float result = (1.0f - weight_factor) * point_a.y + weight_factor * point_b.y; 169 | 170 | return result; 171 | } 172 | -------------------------------------------------------------------------------- /src/curve.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "util_vector.h" 7 | 8 | namespace cse { 9 | class CurveSocketValue; 10 | 11 | // This class is used to store the output of some curve in a format that allows for quick evaluation 12 | // It will linearly interpolate between sampled values 13 | // The curve may not have multiple points with equal X values 14 | class CurveEvaluator { 15 | public: 16 | CurveEvaluator(CurveSocketValue* curve_socket_val, int segments = 512); 17 | CurveEvaluator(std::shared_ptr curve_socket_val, int segments = 512); 18 | CurveEvaluator(Float2 a, Float2 b, Float2 c, Float2 d, int segments = 128); 19 | 20 | int compare_to_range(float in_value) const; 21 | float eval(float in_value) const; 22 | 23 | private: 24 | std::vector sampled_points; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/drawing.h: -------------------------------------------------------------------------------- 1 | // Common drawing functions go here 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "util_area.h" 9 | 10 | struct NVGcontext; 11 | 12 | namespace cse { 13 | 14 | class Float2; 15 | class Float3; 16 | class NodeSocket; 17 | 18 | namespace Drawing { 19 | 20 | // UI Elements 21 | void draw_button(NVGcontext* draw_context, Float2 pos, float width, float height, const std::string& label, bool enabled, bool pressed); 22 | void draw_color_swatch(NVGcontext* draw_context, Float2 pos, float width, float height, Float3 color, bool selected); 23 | void draw_color_pick_cursor(NVGcontext* draw_context, Float2 pos); 24 | 25 | // Node Graph View 26 | float draw_node( 27 | NVGcontext* draw_context, 28 | const char* title, 29 | Float3 header_color, 30 | float node_width, 31 | Float2 world_pos, 32 | bool selected, 33 | const std::vector>& socket_vec, 34 | const std::shared_ptr& selected_socket, 35 | std::vector>>& socket_targets, 36 | std::vector>>& label_targets 37 | ); 38 | void draw_node_connection_curve(NVGcontext* draw_context, Float2 begin_pos, Float2 end_pos, float width); 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/editable_graph.cpp: -------------------------------------------------------------------------------- 1 | #include "editable_graph.h" 2 | 3 | #include "node_outputs.h" 4 | #include "sockets.h" 5 | #include "util_vector.h" 6 | 7 | cse::EditableGraph::EditableGraph(const ShaderGraphType type) 8 | { 9 | reset(type); 10 | } 11 | 12 | void cse::EditableGraph::add_node(std::shared_ptr& node, const Float2 world_pos) 13 | { 14 | if (node.use_count() == 0) { 15 | return; 16 | } 17 | 18 | node->world_pos = world_pos; 19 | nodes.push_front(node); 20 | should_push_undo_state = true; 21 | } 22 | 23 | void cse::EditableGraph::add_connection(const std::weak_ptr socket_begin, const std::weak_ptr socket_end) 24 | { 25 | EditableNode* source_node = nullptr; 26 | if (const auto socket_begin_ptr = socket_begin.lock()) { 27 | if (socket_begin_ptr->io_type != SocketIOType::OUTPUT) { 28 | return; 29 | } 30 | source_node = socket_begin_ptr->parent; 31 | } 32 | else { 33 | // pointer is invalid 34 | return; 35 | } 36 | if (const auto socket_end_ptr = socket_end.lock()) { 37 | if (socket_end_ptr->io_type != SocketIOType::INPUT) { 38 | return; 39 | } 40 | if (socket_end_ptr->parent == source_node) { 41 | // Do not allow a node to connect to itself 42 | return; 43 | } 44 | } 45 | else { 46 | // pointer is invalid 47 | return; 48 | } 49 | // Remove any existing connection with this endpoint 50 | remove_connection_with_end(socket_end); 51 | should_push_undo_state = true; 52 | NodeConnection new_connection(socket_begin, socket_end); 53 | connections.push_back(new_connection); 54 | } 55 | 56 | cse::NodeConnection cse::EditableGraph::remove_connection_with_end(const std::weak_ptr socket_end) 57 | { 58 | const NodeConnection default_result = NodeConnection(std::weak_ptr(), std::weak_ptr()); 59 | if (socket_end.expired()) { 60 | return default_result; 61 | } 62 | std::list::iterator iter; 63 | for (iter = connections.begin(); iter != connections.end(); iter++) { 64 | if (iter->end_socket.lock() == socket_end.lock()) { 65 | should_push_undo_state = true; 66 | NodeConnection result = *iter; 67 | connections.erase(iter); 68 | return result; 69 | } 70 | } 71 | return default_result; 72 | } 73 | 74 | void cse::EditableGraph::remove_node_set(const cse::WeakNodeSet& weak_nodes_to_remove) 75 | { 76 | // Create a set of shared_ptrs from the weak_ptrs 77 | SharedNodeSet shared_nodes_to_remove; 78 | for (auto weak_node : weak_nodes_to_remove) { 79 | shared_nodes_to_remove.insert(weak_node.lock()); 80 | } 81 | 82 | // Remove all nodes in the set 83 | auto node_iter = nodes.begin(); 84 | while (node_iter != nodes.end()) { 85 | auto this_node = *node_iter; 86 | if (this_node->can_be_deleted() && shared_nodes_to_remove.count(this_node) == 1) { 87 | node_iter = nodes.erase(node_iter); 88 | should_push_undo_state = true; 89 | } 90 | else { 91 | node_iter++; 92 | } 93 | } 94 | 95 | remove_invalid_connections(); 96 | } 97 | 98 | bool cse::EditableGraph::is_node_under_point(const Float2 world_pos) const 99 | { 100 | for (const auto this_node : nodes) { 101 | if (this_node->contains_point(world_pos)) { 102 | return true; 103 | } 104 | } 105 | return false; 106 | } 107 | 108 | std::weak_ptr cse::EditableGraph::get_node_under_point(const Float2 world_pos) const 109 | { 110 | for (const auto this_node : nodes) { 111 | if (this_node->contains_point(world_pos)) { 112 | return this_node; 113 | } 114 | } 115 | return std::weak_ptr(); 116 | } 117 | 118 | std::weak_ptr cse::EditableGraph::get_socket_under_point(const Float2 world_pos) const 119 | { 120 | for (const auto this_node : nodes) { 121 | auto maybe_result = this_node->get_socket_label_under_point(world_pos); 122 | if (maybe_result.expired() == false) { 123 | return maybe_result; 124 | } 125 | } 126 | return std::weak_ptr(); 127 | } 128 | 129 | std::weak_ptr cse::EditableGraph::get_connector_under_point(const Float2 world_pos, const SocketIOType io_type) const 130 | { 131 | for (const auto this_node : nodes) { 132 | auto maybe_result = this_node->get_socket_connector_under_point(world_pos); 133 | if (auto maybe_result_ptr = maybe_result.lock()) { 134 | if (maybe_result_ptr->io_type == io_type) { 135 | return maybe_result; 136 | } 137 | } 138 | } 139 | return std::weak_ptr(); 140 | } 141 | 142 | void cse::EditableGraph::raise_node(const std::weak_ptr weak_node) 143 | { 144 | if (weak_node.expired()) { 145 | return; 146 | } 147 | 148 | const std::shared_ptr node_to_raise = weak_node.lock(); 149 | 150 | // Exit early if this is already the top node 151 | if (nodes.front() == node_to_raise) { 152 | return; 153 | } 154 | 155 | for (auto iter = nodes.begin(); iter != nodes.end(); iter++) { 156 | const std::shared_ptr this_node = *iter; 157 | if (this_node == node_to_raise) { 158 | nodes.erase(iter); 159 | nodes.push_front(this_node); 160 | return; 161 | } 162 | } 163 | } 164 | 165 | bool cse::EditableGraph::needs_undo_push() 166 | { 167 | bool result = false; 168 | for (const auto this_node : nodes) { 169 | if (this_node->changed) { 170 | result = true; 171 | this_node->changed = false; 172 | } 173 | } 174 | if (should_push_undo_state) { 175 | result = true; 176 | should_push_undo_state = false; 177 | } 178 | return result; 179 | } 180 | 181 | void cse::EditableGraph::reset(const ShaderGraphType type) 182 | { 183 | connections.clear(); 184 | nodes.clear(); 185 | 186 | switch (type) { 187 | case ShaderGraphType::EMPTY: 188 | // Do nothing 189 | break; 190 | case ShaderGraphType::MATERIAL: 191 | { 192 | nodes.push_back(std::make_shared(Float2(0.0f, 0.0f))); 193 | } 194 | } 195 | } 196 | 197 | void cse::EditableGraph::remove_invalid_connections() 198 | { 199 | std::list::iterator iter = connections.begin(); 200 | while (iter != connections.end()) { 201 | if (iter->is_valid()) { 202 | iter++; 203 | } 204 | else { 205 | iter = connections.erase(iter); 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/editable_graph.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "node_base.h" 7 | #include "util_enum.h" 8 | #include "util_typedef.h" 9 | 10 | namespace cse { 11 | 12 | class Float2; 13 | class NodeSocket; 14 | 15 | // This class is used to store all data for a single graph that can be edited interactively 16 | class EditableGraph { 17 | public: 18 | EditableGraph(ShaderGraphType type); 19 | 20 | void add_node(std::shared_ptr& node, Float2 world_pos); 21 | void add_connection(std::weak_ptr socket_begin, std::weak_ptr socket_end); 22 | 23 | NodeConnection remove_connection_with_end(std::weak_ptr socket_end); 24 | void remove_node_set(const WeakNodeSet& nodes_to_remove); 25 | 26 | bool is_node_under_point(Float2 world_pos) const; 27 | std::weak_ptr get_node_under_point(Float2 world_pos) const; 28 | 29 | std::weak_ptr get_socket_under_point(Float2 world_pos) const; 30 | std::weak_ptr get_connector_under_point(Float2 world_pos, SocketIOType io_type) const; 31 | 32 | void raise_node(std::weak_ptr node); 33 | 34 | bool needs_undo_push(); 35 | 36 | std::list> nodes; 37 | std::list connections; 38 | 39 | private: 40 | void reset(ShaderGraphType type); 41 | 42 | void remove_invalid_connections(); 43 | 44 | bool should_push_undo_state = false; 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/glfw_callbacks.cpp: -------------------------------------------------------------------------------- 1 | #include "glfw_callbacks.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "main_window.h" 9 | 10 | std::mutex window_map_mutex; 11 | std::map window_map; 12 | 13 | static cse::EditorMainWindow* get_main_window(GLFWwindow* const glfw_window) 14 | { 15 | std::lock_guard lock(window_map_mutex); 16 | if (window_map.count(glfw_window) > 0) { 17 | return window_map[glfw_window]; 18 | } 19 | else { 20 | return nullptr; 21 | } 22 | } 23 | 24 | void cse::register_window_pair_for_callbacks(GLFWwindow* const glfw_window, EditorMainWindow* const main_window) 25 | { 26 | std::lock_guard lock(window_map_mutex); 27 | window_map[glfw_window] = main_window; 28 | } 29 | 30 | void cse::unregister_window_for_callbacks(GLFWwindow* const glfw_window) 31 | { 32 | std::lock_guard lock(window_map_mutex); 33 | window_map.erase(glfw_window); 34 | } 35 | 36 | void cse::mouse_button_callback(GLFWwindow* const window, const int button, const int action, const int mods) 37 | { 38 | EditorMainWindow* const main_window = get_main_window(window); 39 | if (main_window != nullptr) { 40 | main_window->handle_mouse_button(button, action, mods); 41 | } 42 | } 43 | 44 | void cse::key_callback(GLFWwindow* const window, const int key, const int scancode, const int action, const int mods) 45 | { 46 | EditorMainWindow* const main_window = get_main_window(window); 47 | if (main_window != nullptr) { 48 | main_window->handle_key(key, scancode, action, mods); 49 | } 50 | } 51 | 52 | void cse::character_callback(GLFWwindow* const window, const unsigned int codepoint) 53 | { 54 | EditorMainWindow* const main_window = get_main_window(window); 55 | if (main_window != nullptr) { 56 | main_window->handle_character(codepoint); 57 | } 58 | } 59 | 60 | void cse::scroll_callback(GLFWwindow* const window, const double xoffset, const double yoffset) 61 | { 62 | EditorMainWindow* const main_window = get_main_window(window); 63 | if (main_window != nullptr) { 64 | main_window->handle_scroll(xoffset, yoffset); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/glfw_callbacks.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct GLFWwindow; 4 | 5 | namespace cse { 6 | 7 | class EditorMainWindow; 8 | 9 | void register_window_pair_for_callbacks(GLFWwindow* glfw_window, EditorMainWindow* main_window); 10 | void unregister_window_for_callbacks(GLFWwindow* glfw_window); 11 | 12 | void mouse_button_callback(GLFWwindow* window, int button, int action, int mods); 13 | void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods); 14 | void character_callback(GLFWwindow* window, unsigned int codepoint); 15 | void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/graph_decoder.cpp: -------------------------------------------------------------------------------- 1 | #include "graph_decoder.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "node_base.h" 7 | #include "serialize.h" 8 | 9 | cse::CyclesNodeGraph::CyclesNodeGraph(const std::string& encoded_graph) 10 | { 11 | // Temporary storage to fill during deserialization 12 | std::list> tmp_nodes; 13 | std::list tmp_connections; 14 | 15 | deserialize_graph(encoded_graph, tmp_nodes, tmp_connections); 16 | generate_output_lists(tmp_nodes, tmp_connections, nodes, connections); 17 | } 18 | -------------------------------------------------------------------------------- /src/graph_decoder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "output.h" 7 | 8 | namespace cse { 9 | 10 | // Description of a Cycles shader graph 11 | class CyclesNodeGraph { 12 | public: 13 | CyclesNodeGraph(const std::string& encoded_graph); 14 | 15 | std::vector nodes; 16 | std::vector connections; 17 | }; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/graph_editor.cpp: -------------------------------------------------------------------------------- 1 | #include "graph_editor.h" 2 | 3 | #include "main_window.h" 4 | 5 | cse::GraphEditor::GraphEditor() 6 | { 7 | main_window = std::make_unique(); 8 | const PathString default_font_path = Platform::get_pathstring("font"); 9 | set_font_search_path(default_font_path); 10 | } 11 | 12 | cse::GraphEditor::~GraphEditor() 13 | { 14 | // Empty destructor is needed so incomplete EditorMainWindow type can be used in the header 15 | } 16 | 17 | void cse::GraphEditor::set_font_search_path(const PathString& font_path) 18 | { 19 | main_window->set_font_search_path(font_path); 20 | } 21 | 22 | bool cse::GraphEditor::create_window() 23 | { 24 | return main_window->create_window(); 25 | } 26 | 27 | bool cse::GraphEditor::run_window_loop_iteration() 28 | { 29 | return main_window->run_window_loop_iteration(); 30 | } 31 | 32 | void cse::GraphEditor::set_target_frame_rate(const double fps) 33 | { 34 | main_window->set_target_frame_rate(fps); 35 | } 36 | 37 | void cse::GraphEditor::load_serialized_graph(const std::string& graph) 38 | { 39 | main_window->load_serialized_graph(graph); 40 | } 41 | 42 | bool cse::GraphEditor::get_serialized_graph(std::string& graph) 43 | { 44 | return main_window->get_serialized_output(graph); 45 | } 46 | -------------------------------------------------------------------------------- /src/graph_editor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "util_platform.h" 7 | 8 | namespace cse { 9 | 10 | class EditorMainWindow; 11 | 12 | class GraphEditor { 13 | public: 14 | GraphEditor(); 15 | ~GraphEditor(); 16 | 17 | void set_font_search_path(const PathString& font_path); 18 | 19 | bool create_window(); 20 | bool run_window_loop_iteration(); 21 | 22 | void set_target_frame_rate(double fps); 23 | 24 | void load_serialized_graph(const std::string& graph); 25 | 26 | bool get_serialized_graph(std::string& graph); 27 | 28 | private: 29 | std::unique_ptr main_window; 30 | }; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/gui_colors.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "util_vector.h" 4 | 5 | static const cse::Float3 COLOR_NODE_HEADER_DEFAULT (0.80f, 0.80f, 0.80f); 6 | static const cse::Float3 COLOR_NODE_HEADER_OUTPUT (0.80f, 0.44f, 0.44f); 7 | static const cse::Float3 COLOR_NODE_HEADER_COLOR (0.80f, 0.65f, 0.20f); 8 | static const cse::Float3 COLOR_NODE_HEADER_CONVERTER(0.40f, 0.72f, 0.80f); 9 | static const cse::Float3 COLOR_NODE_HEADER_INPUT (0.80f, 0.56f, 0.56f); 10 | static const cse::Float3 COLOR_NODE_HEADER_SHADER (0.40f, 0.75f, 0.55f); 11 | static const cse::Float3 COLOR_NODE_HEADER_TEXTURE (0.80f, 0.55f, 0.40f); 12 | static const cse::Float3 COLOR_NODE_HEADER_VECTOR (0.55f, 0.40f, 0.80f); 13 | -------------------------------------------------------------------------------- /src/gui_sizes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | static constexpr int UI_WINDOW_WIDTH = 1280; 4 | static constexpr int UI_WINDOW_HEIGHT = 720; 5 | 6 | static constexpr float UI_FONT_SIZE_NORMAL = 18.0f; 7 | 8 | static constexpr float UI_BUTTON_CORNER_RADIUS = 2.0f; 9 | 10 | static constexpr float UI_TOOLBAR_HEIGHT = 28.0f; 11 | static constexpr float UI_TOOLBAR_BUTTON_HPAD = 6.0f; 12 | static constexpr float UI_TOOLBAR_BUTTON_VPAD = 4.0f; 13 | static constexpr float UI_TOOLBAR_BUTTON_WIDTH = 74.0f; 14 | static constexpr float UI_TOOLBAR_SPACER_VPAD = 4.0f; 15 | static constexpr float UI_TOOLBAR_SPACER_WIDTH = 10.0f; 16 | 17 | static constexpr float UI_STATUSBAR_HEIGHT = 22.0f; 18 | 19 | static constexpr float UI_SUBWIN_HEADER_HEIGHT = 22.0f; 20 | static constexpr float UI_SUBWIN_CORNER_RADIUS = 4.0f; 21 | 22 | static constexpr float UI_SUBWIN_NODE_LIST_WIDTH = 180.0f; 23 | static constexpr float UI_SUBWIN_NODE_LIST_BUTTON_VPADDING = 2.0f; 24 | static constexpr float UI_SUBWIN_NODE_LIST_CAT_BUTTON_WIDTH = 74.0f; 25 | static constexpr float UI_SUBWIN_NODE_LIST_CAT_BUTTON_HEIGHT = 20.0f; 26 | static constexpr float UI_SUBWIN_NODE_LIST_CAT_BUTTON_HGAP = 6.0f; 27 | static constexpr float UI_SUBWIN_NODE_LIST_NODE_BUTTON_HPADDING = 6.0f; 28 | static constexpr float UI_SUBWIN_NODE_LIST_NODE_BUTTON_HEIGHT = 20.0f; 29 | 30 | static constexpr float UI_SUBWIN_PARAM_EDIT_WIDTH = 268.0f; 31 | static constexpr float UI_SUBWIN_PARAM_EDIT_LAYOUT_ROW_HEIGHT = 20.0f; 32 | static constexpr float UI_SUBWIN_PARAM_EDIT_LAYOUT_SPACER_HEIGHT = 8.0f; 33 | static constexpr float UI_SUBWIN_PARAM_EDIT_TEXT_INPUT_WIDTH = 80.0f; 34 | static constexpr float UI_SUBWIN_PARAM_EDIT_TEXT_INPUT_HEIGHT = 16.0f; 35 | static constexpr float UI_SUBWIN_PARAM_EDIT_TEXT_INPUT_HPAD = 12.0f; 36 | static constexpr float UI_SUBWIN_PARAM_EDIT_SEPARATOR_HPAD = 8.0f; 37 | static constexpr float UI_SUBWIN_PARAM_EDIT_SEPARATOR_VPAD = 4.0f; 38 | static constexpr float UI_SUBWIN_PARAM_EDIT_SLIDER_HPAD = 8.0f; 39 | static constexpr float UI_SUBWIN_PARAM_EDIT_SLIDER_VPAD = 4.0f; 40 | static constexpr float UI_SUBWIN_PARAM_EDIT_RECT_HEIGHT = 180.0f; 41 | static constexpr float UI_SUBWIN_PARAM_EDIT_RECT_HPAD = 8.0f; 42 | static constexpr float UI_SUBWIN_PARAM_EDIT_RECT_VPAD = 4.0f; 43 | static constexpr float UI_SUBWIN_PARAM_EDIT_CURVE_POINT_RADIUS = 4.0f; 44 | 45 | static constexpr float UI_SUBWIN_PARAM_EDIT_COLOR_RAMP_ROW_HEIGHT = 32.0f; 46 | static constexpr float UI_SUBWIN_PARAM_EDIT_COLOR_RAMP_TEXT_INPUT_WIDTH = 54.0f; 47 | static constexpr float UI_SUBWIN_PARAM_EDIT_ROW_HIGHLIGHT_VPAD = 2.0f; 48 | static constexpr float UI_SUBWIN_PARAM_EDIT_ROW_HIGHLIGHT_HPAD = 8.0f; 49 | static constexpr float UI_SUBWIN_PARAM_EDIT_ROW_HIGHLIGHT_CORNER_RADIUS = 6.0f; 50 | 51 | static constexpr float UI_NODE_HEADER_HEIGHT = 24.0f; 52 | static constexpr float UI_NODE_CORNER_RADIUS = 6.0f; 53 | static constexpr float UI_NODE_SOCKET_ROW_HEIGHT = 22.0f; 54 | static constexpr float UI_NODE_SOCKET_RADIUS = 5.5f; 55 | static constexpr float UI_NODE_BOTTOM_PADDING = 6.0f; 56 | 57 | static constexpr float UI_CHECKBOX_RADIUS = 5.0f; 58 | static constexpr float UI_CHECKBOX_SPACING = 4.0f; 59 | 60 | static constexpr float UI_WIDGET_RADIO_LIST_ROW_HEIGHT = 16.0f; -------------------------------------------------------------------------------- /src/input_box.cpp: -------------------------------------------------------------------------------- 1 | #include "input_box.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "gui_sizes.h" 11 | #include "sockets.h" 12 | #include "util_parse.h" 13 | 14 | const float INPUT_CORNER_RADIUS = 1.0f; 15 | 16 | cse::BaseInputBox::BaseInputBox(const float width, const float height) 17 | { 18 | this->width = width; 19 | this->height = height; 20 | } 21 | 22 | void cse::BaseInputBox::draw(NVGcontext* const draw_context, const bool hightlight) 23 | { 24 | // Back fill 25 | nvgBeginPath(draw_context); 26 | nvgRoundedRect(draw_context, position.x, position.y, width, height, INPUT_CORNER_RADIUS); 27 | if (hightlight) { 28 | nvgFillColor(draw_context, nvgRGBA(230, 230, 230, 255)); 29 | } 30 | else { 31 | nvgFillColor(draw_context, nvgRGBA(205, 205, 205, 255)); 32 | } 33 | nvgFill(draw_context); 34 | 35 | // Font settings 36 | nvgFontSize(draw_context, UI_FONT_SIZE_NORMAL); 37 | nvgFontFace(draw_context, "sans"); 38 | nvgTextAlign(draw_context, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); 39 | nvgFontBlur(draw_context, 0.0f); 40 | nvgFillColor(draw_context, nvgRGBA(0, 0, 0, 255)); 41 | 42 | // Socket value 43 | const std::string value_text = selected ? 44 | input_string : 45 | get_value_as_string(); 46 | nvgText(draw_context, position.x + width / 2, position.y + height / 2, value_text.c_str(), nullptr); 47 | 48 | // Outline 49 | nvgBeginPath(draw_context); 50 | nvgRoundedRect(draw_context, position.x, position.y, width, height, INPUT_CORNER_RADIUS); 51 | if (selected) { 52 | nvgStrokeColor(draw_context, nvgRGBA(255, 255, 255, 255)); 53 | } 54 | else { 55 | nvgStrokeColor(draw_context, nvgRGBA(20, 20, 20, 255)); 56 | } 57 | nvgStrokeWidth(draw_context, 1.0f); 58 | nvgStroke(draw_context); 59 | } 60 | 61 | void cse::BaseInputBox::set_position(const Float2 parent_position) 62 | { 63 | this->position = parent_position; 64 | } 65 | 66 | bool cse::BaseInputBox::contains_point(const Float2 parent_local_pos) 67 | { 68 | const Float2 local_pos = parent_local_pos - position; 69 | 70 | return (local_pos.x > 0 && 71 | local_pos.x < width && 72 | local_pos.y > 0 && 73 | local_pos.y < height); 74 | } 75 | 76 | void cse::BaseInputBox::handle_character(const unsigned int codepoint) 77 | { 78 | const char as_char = static_cast(codepoint); 79 | const unsigned char as_uchar = static_cast(as_char); 80 | const int is_num = isdigit(as_uchar); 81 | const int is_punctuation = (as_char == '-' || as_char == '.'); 82 | 83 | if (is_num || is_punctuation) { 84 | input_string += static_cast(codepoint); 85 | } 86 | } 87 | 88 | void cse::BaseInputBox::backspace() 89 | { 90 | if (input_string.size() == 0) { 91 | return; 92 | } 93 | 94 | input_string = input_string.substr(0, input_string.size() - 1); 95 | } 96 | 97 | void cse::BaseInputBox::begin_edit() 98 | { 99 | cancel_edit(); 100 | selected = true; 101 | } 102 | 103 | bool cse::BaseInputBox::complete_edit() 104 | { 105 | selected = false; 106 | if (input_string == "") { 107 | return false; 108 | } 109 | 110 | const bool result = set_value_from_input_string(); 111 | input_string.clear(); 112 | return result; 113 | } 114 | 115 | void cse::BaseInputBox::cancel_edit() 116 | { 117 | input_string.clear(); 118 | selected = false; 119 | } 120 | 121 | bool cse::BaseInputBox::has_input_focus() const 122 | { 123 | return selected; 124 | } 125 | 126 | cse::IntInputBox::IntInputBox(const float width, const float height) : BaseInputBox(width, height) 127 | { 128 | 129 | } 130 | 131 | void cse::IntInputBox::attach_int_value(const std::weak_ptr socket_value_in) 132 | { 133 | socket_value = socket_value_in; 134 | } 135 | 136 | std::string cse::IntInputBox::get_value_as_string() 137 | { 138 | if (auto socket_value_ptr = socket_value.lock()) { 139 | std::stringstream value_stream; 140 | value_stream << socket_value_ptr->get_value(); 141 | return value_stream.str(); 142 | } 143 | return std::string(); 144 | } 145 | 146 | bool cse::IntInputBox::set_value_from_input_string() 147 | { 148 | if (auto socket_value_ptr = socket_value.lock()) { 149 | try { 150 | const int old_val = socket_value_ptr->get_value(); 151 | const int new_val = std::stoi(input_string); 152 | if (old_val != new_val) { 153 | socket_value_ptr->set_value(new_val); 154 | return true; 155 | } 156 | } 157 | catch (std::invalid_argument&) {} 158 | catch (std::out_of_range&) {} 159 | } 160 | return false; 161 | } 162 | 163 | cse::FloatInputBox::FloatInputBox(const float width, const float height) : BaseInputBox(width, height) 164 | { 165 | 166 | } 167 | 168 | void cse::FloatInputBox::attach_float_value(const std::weak_ptr socket_value_in) 169 | { 170 | socket_value = socket_value_in; 171 | } 172 | 173 | void cse::FloatInputBox::set_float_value(const float value) 174 | { 175 | if (auto socket_value_ptr = socket_value.lock()) { 176 | socket_value_ptr->set_value(value); 177 | } 178 | } 179 | 180 | float cse::FloatInputBox::get_float_value() 181 | { 182 | if (auto socket_value_ptr = socket_value.lock()) { 183 | return socket_value_ptr->get_value(); 184 | } 185 | return 0.0f; 186 | } 187 | 188 | std::string cse::FloatInputBox::get_value_as_string() 189 | { 190 | if (auto socket_value_ptr = socket_value.lock()) { 191 | std::stringstream value_stream; 192 | value_stream << std::fixed << std::setprecision(3) << socket_value_ptr->get_value(); 193 | return value_stream.str(); 194 | } 195 | return std::string(); 196 | } 197 | 198 | bool cse::FloatInputBox::set_value_from_input_string() 199 | { 200 | if (auto socket_value_ptr = socket_value.lock()) { 201 | try { 202 | const float old_val = socket_value_ptr->get_value(); 203 | const float new_val = locale_safe_stof(input_string); 204 | socket_value_ptr->set_value(new_val); 205 | if (old_val != new_val) { 206 | socket_value_ptr->set_value(new_val); 207 | return true; 208 | } 209 | } 210 | catch (std::invalid_argument&) {} 211 | catch (std::out_of_range&) {} 212 | } 213 | return false; 214 | } 215 | -------------------------------------------------------------------------------- /src/input_box.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "util_vector.h" 7 | 8 | struct NVGcontext; 9 | 10 | namespace cse { 11 | 12 | class FloatSocketValue; 13 | class IntSocketValue; 14 | 15 | class BaseInputBox { 16 | public: 17 | BaseInputBox(float width, float height); 18 | virtual ~BaseInputBox() {} 19 | 20 | void draw(NVGcontext* draw_context, bool highlight); 21 | void set_position(Float2 parent_position); 22 | bool contains_point(Float2 parent_local_pos); 23 | 24 | void handle_character(unsigned int codepoint); 25 | void backspace(); 26 | 27 | void begin_edit(); 28 | bool complete_edit(); 29 | void cancel_edit(); 30 | 31 | bool has_input_focus() const; 32 | 33 | float width; 34 | float height; 35 | 36 | protected: 37 | virtual std::string get_value_as_string() = 0; 38 | virtual bool set_value_from_input_string() = 0; 39 | 40 | // Position of this UI element, relative to parent component 41 | Float2 position; 42 | 43 | bool selected = false; 44 | 45 | std::string input_string; 46 | }; 47 | 48 | class IntInputBox : public BaseInputBox { 49 | public: 50 | IntInputBox(float width, float height); 51 | virtual ~IntInputBox() override {} 52 | 53 | void attach_int_value(std::weak_ptr socket_value); 54 | 55 | private: 56 | virtual std::string get_value_as_string() override; 57 | virtual bool set_value_from_input_string() override; 58 | 59 | std::weak_ptr socket_value; 60 | }; 61 | 62 | class FloatInputBox : public BaseInputBox { 63 | public: 64 | FloatInputBox(float width, float height); 65 | virtual ~FloatInputBox() override {} 66 | 67 | void attach_float_value(std::weak_ptr socket_value); 68 | void set_float_value(float value); 69 | float get_float_value(); 70 | 71 | private: 72 | virtual std::string get_value_as_string() override; 73 | virtual bool set_value_from_input_string() override; 74 | 75 | std::weak_ptr socket_value; 76 | }; 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "ui_requests.h" 9 | #include "util_platform.h" 10 | #include "util_vector.h" 11 | #include "undo.h" 12 | 13 | namespace cse { 14 | 15 | class EditableGraph; 16 | class EditGraphView; 17 | class GlfwWindow; 18 | class NodeCreationHelper; 19 | class NodeEditorStatusBar; 20 | class NodeEditorSubwindow; 21 | class NodeEditorToolbar; 22 | class NvgContext; 23 | 24 | class EditorMainWindow { 25 | public: 26 | EditorMainWindow(); 27 | ~EditorMainWindow(); 28 | 29 | void set_font_search_path(const PathString& font_path); 30 | 31 | bool create_window(); 32 | bool run_window_loop_iteration(); 33 | 34 | void set_target_frame_rate(double fps); 35 | 36 | void handle_mouse_button(int button, int action, int mods); 37 | void handle_key(int key, int scancode, int action, int mods); 38 | void handle_character(unsigned int codepoint); 39 | void handle_scroll(double xoffset, double yoffset); 40 | 41 | void load_serialized_graph(const std::string& graph_str); 42 | 43 | bool get_serialized_output(std::string& graph); 44 | 45 | private: 46 | void pre_draw(); 47 | void draw(); 48 | 49 | void swap_buffers(); 50 | 51 | void service_requests(); 52 | 53 | void update_mouse_position(Float2 screen_position); 54 | 55 | // Forwards input to any subwindow that wants to grab input 56 | // Returns true if a subwindow accepted the input, false otherwise 57 | bool forward_mouse_to_subwindow(int button, int action, int mods); 58 | bool forward_key_to_subwindow(int key, int scancode, int action, int mods); 59 | bool forward_character_to_subwindow(unsigned int codepoint); 60 | 61 | void update_serialized_state(); 62 | void push_undo_state(); 63 | 64 | void undo(); 65 | void redo(); 66 | 67 | void clear_graph(bool reset_undo); 68 | 69 | void do_output(); 70 | 71 | void release_resources(); 72 | 73 | std::shared_ptr main_graph; 74 | 75 | Float2 mouse_screen_pos; 76 | int window_width, window_height; 77 | 78 | std::string serialized_state; 79 | UndoStack undo_stack; 80 | 81 | std::shared_ptr node_creation_helper; 82 | 83 | std::list> subwindows; 84 | 85 | std::unique_ptr toolbar; 86 | std::unique_ptr status_bar; 87 | std::unique_ptr view; 88 | 89 | UIRequests requests; 90 | 91 | std::unique_ptr glfw_window; 92 | std::unique_ptr nvg_context; 93 | 94 | std::string serialized_output; 95 | bool serialized_output_updated = false; 96 | 97 | double target_frame_rate = 60.0; 98 | std::chrono::time_point last_buffer_swap_time; 99 | 100 | PathString font_search_path; 101 | }; 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/node_base.cpp: -------------------------------------------------------------------------------- 1 | #include "node_base.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "config.h" 7 | #include "curve.h" 8 | #include "drawing.h" 9 | #include "gui_colors.h" 10 | #include "gui_sizes.h" 11 | #include "sockets.h" 12 | #include "util_color_ramp.h" 13 | #include "util_enum.h" 14 | #include "util_vector.h" 15 | 16 | static cse::Float3 get_color_for_category(const cse::NodeCategory category) 17 | { 18 | using cse::NodeCategory; 19 | 20 | switch(category) { 21 | case NodeCategory::OUTPUT: 22 | return COLOR_NODE_HEADER_OUTPUT; 23 | case NodeCategory::COLOR: 24 | return COLOR_NODE_HEADER_COLOR; 25 | case NodeCategory::CONVERTER: 26 | return COLOR_NODE_HEADER_CONVERTER; 27 | case NodeCategory::INPUT: 28 | return COLOR_NODE_HEADER_INPUT; 29 | case NodeCategory::SHADER: 30 | return COLOR_NODE_HEADER_SHADER; 31 | case NodeCategory::TEXTURE: 32 | return COLOR_NODE_HEADER_TEXTURE; 33 | case NodeCategory::VECTOR: 34 | return COLOR_NODE_HEADER_VECTOR; 35 | default: 36 | return COLOR_NODE_HEADER_DEFAULT; 37 | } 38 | } 39 | 40 | cse::NodeConnection::NodeConnection(const std::weak_ptr begin_socket, const std::weak_ptr end_socket) : 41 | begin_socket(begin_socket), 42 | end_socket(end_socket) 43 | { 44 | 45 | } 46 | 47 | bool cse::NodeConnection::is_valid() const 48 | { 49 | return !(begin_socket.expired() || end_socket.expired()); 50 | } 51 | 52 | bool cse::NodeConnection::includes_node(EditableNode* const node) const 53 | { 54 | if (auto begin_socket_ptr = begin_socket.lock()) { 55 | if (begin_socket_ptr->parent == node) { 56 | return true; 57 | } 58 | } 59 | if (auto end_socket_ptr = end_socket.lock()) { 60 | if (end_socket_ptr->parent == node) { 61 | return true; 62 | } 63 | } 64 | return false; 65 | } 66 | 67 | cse::EditableNode::EditableNode(NodeCategory category, CyclesNodeType type, const std::string& title) : 68 | category(category), 69 | type(type), 70 | title(title) 71 | { 72 | 73 | } 74 | 75 | std::string cse::EditableNode::get_title() const 76 | { 77 | return title; 78 | } 79 | 80 | void cse::EditableNode::draw_node(NVGcontext* const draw_context, const bool selected, const std::shared_ptr selected_socket) 81 | { 82 | const Float3 header_color = get_color_for_category(category); 83 | 84 | content_height = Drawing::draw_node( 85 | draw_context, 86 | title.c_str(), 87 | header_color, 88 | content_width, 89 | world_pos, 90 | selected, 91 | sockets, 92 | selected_socket, 93 | socket_targets, 94 | label_targets 95 | ); 96 | } 97 | 98 | bool cse::EditableNode::contains_point(const Float2 world_pos_in) const 99 | { 100 | const Float2 local_pos = get_local_pos(world_pos_in); 101 | return ( 102 | local_pos.x >= 0.0f && 103 | local_pos.x <= content_width && 104 | local_pos.y >= 0.0f && 105 | local_pos.y <= UI_NODE_HEADER_HEIGHT + content_height 106 | ); 107 | } 108 | 109 | std::weak_ptr cse::EditableNode::get_socket_connector_under_point(const Float2 check_world_pos) const 110 | { 111 | const Float2 local_pos = get_local_pos(check_world_pos); 112 | for (const auto click_target : socket_targets) { 113 | if (click_target.contains_point(local_pos)) { 114 | return click_target.get_value(); 115 | } 116 | } 117 | return std::weak_ptr(); 118 | } 119 | 120 | std::weak_ptr cse::EditableNode::get_socket_label_under_point(const Float2 check_world_pos) const 121 | { 122 | if (contains_point(check_world_pos) == false) { 123 | // Nothing will match if the node is not under the given point 124 | return std::weak_ptr(); 125 | } 126 | const Float2 local_pos = get_local_pos(check_world_pos); 127 | for (const auto click_target : label_targets) { 128 | if (click_target.contains_point(local_pos)) { 129 | return click_target.get_value(); 130 | } 131 | } 132 | return std::weak_ptr(); 133 | } 134 | 135 | std::weak_ptr cse::EditableNode::get_socket_by_display_name(const SocketIOType in_out, const std::string& socket_name) 136 | { 137 | for (const auto socket : sockets) { 138 | if (socket->display_name == socket_name && socket->io_type == in_out) { 139 | return socket; 140 | } 141 | } 142 | return std::weak_ptr(); 143 | } 144 | 145 | std::weak_ptr cse::EditableNode::get_socket_by_internal_name(const SocketIOType in_out, const std::string& socket_name) 146 | { 147 | for (const auto socket : sockets) { 148 | if (socket->internal_name == socket_name && socket->io_type == in_out) { 149 | return socket; 150 | } 151 | } 152 | return std::weak_ptr(); 153 | } 154 | 155 | cse::Float2 cse::EditableNode::get_dimensions() 156 | { 157 | return cse::Float2(content_width, content_height + UI_NODE_HEADER_HEIGHT); 158 | } 159 | 160 | bool cse::EditableNode::can_be_deleted() 161 | { 162 | return true; 163 | } 164 | 165 | void cse::EditableNode::update_output_node(OutputNode& output) 166 | { 167 | output.type = type; 168 | 169 | output.world_x = std::floor(world_pos.x); 170 | output.world_y = std::floor(world_pos.y); 171 | 172 | if (type == CyclesNodeType::MaterialOutput) { 173 | output.name = std::string("output"); 174 | } 175 | 176 | for (const auto this_socket : sockets) { 177 | if (this_socket->io_type != SocketIOType::INPUT) { 178 | continue; 179 | } 180 | 181 | if (this_socket->socket_type == SocketType::FLOAT) { 182 | const std::shared_ptr float_val = std::dynamic_pointer_cast(this_socket->value); 183 | if (float_val) { 184 | output.float_values[this_socket->internal_name] = float_val->get_value(); 185 | } 186 | } 187 | else if (this_socket->socket_type == SocketType::COLOR) { 188 | const std::shared_ptr color_val = std::dynamic_pointer_cast(this_socket->value); 189 | if (color_val) { 190 | const float x = color_val->r_socket_val->get_value(); 191 | const float y = color_val->g_socket_val->get_value(); 192 | const float z = color_val->b_socket_val->get_value(); 193 | const Float3 float3_val(x, y, z); 194 | output.float3_values[this_socket->internal_name] = float3_val; 195 | } 196 | } 197 | else if (this_socket->socket_type == SocketType::VECTOR) { 198 | const std::shared_ptr float3_socket_val = std::dynamic_pointer_cast(this_socket->value); 199 | if (float3_socket_val) { 200 | const Float3 float3_val = float3_socket_val->get_value(); 201 | output.float3_values[this_socket->internal_name] = float3_val; 202 | } 203 | } 204 | else if (this_socket->socket_type == SocketType::STRING_ENUM) { 205 | const std::shared_ptr string_val = std::dynamic_pointer_cast(this_socket->value); 206 | if (string_val) { 207 | output.string_values[this_socket->internal_name] = string_val->value.internal_value; 208 | } 209 | } 210 | else if (this_socket->socket_type == SocketType::INT) { 211 | const std::shared_ptr int_val = std::dynamic_pointer_cast(this_socket->value); 212 | if (int_val) { 213 | output.int_values[this_socket->internal_name] = int_val->get_value(); 214 | } 215 | } 216 | else if (this_socket->socket_type == SocketType::BOOLEAN) { 217 | const std::shared_ptr bool_val = std::dynamic_pointer_cast(this_socket->value); 218 | if (bool_val) { 219 | output.bool_values[this_socket->internal_name] = bool_val->value; 220 | } 221 | } 222 | else if (this_socket->socket_type == SocketType::CURVE) { 223 | const std::shared_ptr curve_val = std::dynamic_pointer_cast(this_socket->value); 224 | if (curve_val) { 225 | OutputCurve out_curve; 226 | typedef std::vector::size_type vec_index; 227 | for (vec_index i = 0; i < curve_val->curve_points.size(); i++) { 228 | const Float2 this_point = curve_val->curve_points[i]; 229 | out_curve.control_points.push_back(Float2(this_point.x, this_point.y)); 230 | } 231 | out_curve.enum_curve_interp = static_cast(curve_val->curve_interp); 232 | const CurveEvaluator curve(curve_val); 233 | for (vec_index i = 0; i < CURVE_TABLE_SIZE; i++) { 234 | const float x = static_cast(i) / (CURVE_TABLE_SIZE - 1.0f); 235 | out_curve.samples.push_back(curve.eval(x)); 236 | } 237 | output.curve_values[this_socket->internal_name] = out_curve; 238 | } 239 | } 240 | else if (this_socket->socket_type == SocketType::COLOR_RAMP) { 241 | const std::shared_ptr ramp_val = std::dynamic_pointer_cast(this_socket->value); 242 | if (ramp_val) { 243 | OutputColorRamp out_ramp; 244 | 245 | // Copy over control points 246 | for (const auto& this_point : ramp_val->ramp_points) { 247 | OutputColorRampPoint new_point; 248 | new_point.pos = this_point.position; 249 | new_point.color = this_point.color; 250 | new_point.alpha = this_point.alpha; 251 | out_ramp.points.push_back(new_point); 252 | } 253 | 254 | // Copy evaluated samples 255 | std::vector samples = ramp_val->evaluate_samples(); 256 | for (const auto& this_sample : samples) { 257 | out_ramp.samples_color.push_back(Float3(this_sample.x, this_sample.y, this_sample.z)); 258 | out_ramp.samples_alpha.push_back(this_sample.w); 259 | } 260 | 261 | output.ramp_values[this_socket->internal_name] = out_ramp; 262 | } 263 | } 264 | } 265 | } 266 | 267 | cse::Float2 cse::EditableNode::get_local_pos(const Float2 world_pos_in) const 268 | { 269 | return world_pos_in - world_pos; 270 | } 271 | -------------------------------------------------------------------------------- /src/node_base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "output.h" 8 | #include "util_area.h" 9 | #include "util_enum.h" 10 | #include "util_vector.h" 11 | 12 | struct NVGcontext; 13 | 14 | namespace cse { 15 | 16 | class NodeSocket; 17 | class EditableNode; 18 | 19 | class NodeConnection { 20 | public: 21 | NodeConnection(std::weak_ptr begin_socket, std::weak_ptr end_socket); 22 | 23 | bool is_valid() const; 24 | bool includes_node(EditableNode* node) const; 25 | 26 | const std::weak_ptr begin_socket; 27 | const std::weak_ptr end_socket; 28 | }; 29 | 30 | class EditableNode { 31 | public: 32 | EditableNode(NodeCategory category, CyclesNodeType type, const std::string& title); 33 | virtual ~EditableNode() {} 34 | 35 | virtual std::string get_title() const; 36 | 37 | virtual void draw_node(NVGcontext* draw_context, bool selected, std::shared_ptr selected_socket); 38 | 39 | virtual bool contains_point(Float2 world_pos_in) const; 40 | virtual std::weak_ptr get_socket_connector_under_point(Float2 check_world_pos) const; 41 | virtual std::weak_ptr get_socket_label_under_point(Float2 check_world_pos) const; 42 | 43 | virtual std::weak_ptr get_socket_by_display_name(SocketIOType in_out, const std::string& socket_name); 44 | virtual std::weak_ptr get_socket_by_internal_name(SocketIOType in_out, const std::string& socket_name); 45 | 46 | virtual Float2 get_dimensions(); 47 | 48 | virtual bool can_be_deleted(); 49 | 50 | virtual void update_output_node(OutputNode& output); 51 | 52 | bool changed = true; 53 | 54 | Float2 world_pos; 55 | 56 | protected: 57 | Float2 get_local_pos(Float2 world_pos_in) const; 58 | 59 | const NodeCategory category; 60 | const CyclesNodeType type; 61 | 62 | const std::string title; 63 | 64 | float content_width = 150.0f; 65 | float content_height = 0.0f; 66 | 67 | std::vector> sockets; 68 | std::vector>> socket_targets; 69 | std::vector>> label_targets; 70 | }; 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/node_colors.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "node_base.h" 4 | 5 | namespace cse { 6 | 7 | class Float2; 8 | 9 | struct OutputNode; 10 | 11 | class MixRGBNode : public EditableNode { 12 | public: 13 | MixRGBNode(Float2 position); 14 | }; 15 | 16 | class InvertNode : public EditableNode { 17 | public: 18 | InvertNode(Float2 position); 19 | }; 20 | 21 | class LightFalloffNode : public EditableNode { 22 | public: 23 | LightFalloffNode(Float2 position); 24 | }; 25 | 26 | class HSVNode : public EditableNode { 27 | public: 28 | HSVNode(Float2 position); 29 | }; 30 | 31 | class GammaNode : public EditableNode { 32 | public: 33 | GammaNode(Float2 position); 34 | }; 35 | 36 | class BrightnessContrastNode : public EditableNode { 37 | public: 38 | BrightnessContrastNode(Float2 position); 39 | }; 40 | 41 | class RGBCurvesNode : public EditableNode { 42 | public: 43 | RGBCurvesNode(Float2 position); 44 | 45 | virtual void update_output_node(OutputNode& output) override; 46 | }; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/node_converter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "node_base.h" 4 | 5 | namespace cse { 6 | 7 | class Float2; 8 | 9 | struct OutputNode; 10 | 11 | class BlackbodyNode : public EditableNode { 12 | public: 13 | BlackbodyNode(Float2 position); 14 | }; 15 | 16 | class ColorRampNode : public EditableNode { 17 | public: 18 | ColorRampNode(Float2 position); 19 | 20 | virtual void update_output_node(OutputNode& output) override; 21 | }; 22 | 23 | class CombineHSVNode : public EditableNode { 24 | public: 25 | CombineHSVNode(Float2 position); 26 | }; 27 | 28 | class CombineRGBNode : public EditableNode { 29 | public: 30 | CombineRGBNode(Float2 position); 31 | }; 32 | 33 | class CombineXYZNode : public EditableNode { 34 | public: 35 | CombineXYZNode(Float2 position); 36 | }; 37 | 38 | class MathNode : public EditableNode { 39 | public: 40 | MathNode(Float2 position); 41 | }; 42 | 43 | class RGBToBWNode : public EditableNode { 44 | public: 45 | RGBToBWNode(Float2 position); 46 | }; 47 | 48 | class SeparateHSVNode : public EditableNode { 49 | public: 50 | SeparateHSVNode(Float2 position); 51 | }; 52 | 53 | class SeparateRGBNode : public EditableNode { 54 | public: 55 | SeparateRGBNode(Float2 position); 56 | }; 57 | 58 | class SeparateXYZNode : public EditableNode { 59 | public: 60 | SeparateXYZNode(Float2 position); 61 | }; 62 | 63 | class VectorMathNode : public EditableNode { 64 | public: 65 | VectorMathNode(Float2 position); 66 | }; 67 | 68 | class WavelengthNode : public EditableNode { 69 | public: 70 | WavelengthNode(Float2 position); 71 | }; 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/node_inputs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "node_base.h" 4 | 5 | namespace cse { 6 | 7 | class Float2; 8 | 9 | class AmbientOcculsionNode : public EditableNode { 10 | public: 11 | AmbientOcculsionNode(Float2 position); 12 | }; 13 | 14 | class BevelNode : public EditableNode { 15 | public: 16 | BevelNode(Float2 position); 17 | }; 18 | 19 | class CameraDataNode : public EditableNode { 20 | public: 21 | CameraDataNode(Float2 position); 22 | }; 23 | 24 | class FresnelNode : public EditableNode { 25 | public: 26 | FresnelNode(Float2 position); 27 | }; 28 | 29 | class GeometryNode : public EditableNode { 30 | public: 31 | GeometryNode(Float2 position); 32 | }; 33 | 34 | class LayerWeightNode : public EditableNode { 35 | public: 36 | LayerWeightNode(Float2 position); 37 | }; 38 | 39 | class LightPathNode : public EditableNode { 40 | public: 41 | LightPathNode(Float2 position); 42 | }; 43 | 44 | class ObjectInfoNode : public EditableNode { 45 | public: 46 | ObjectInfoNode(Float2 position); 47 | }; 48 | 49 | class RGBNode : public EditableNode { 50 | public: 51 | RGBNode(Float2 position); 52 | }; 53 | 54 | class TangentNode : public EditableNode { 55 | public: 56 | TangentNode(Float2 position); 57 | }; 58 | 59 | class TextureCoordinateNode : public EditableNode { 60 | public: 61 | TextureCoordinateNode(Float2 position); 62 | }; 63 | 64 | class ValueNode : public EditableNode { 65 | public: 66 | ValueNode(Float2 position); 67 | }; 68 | 69 | class WireframeNode : public EditableNode { 70 | public: 71 | WireframeNode(Float2 position); 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /src/node_interop_max.cpp: -------------------------------------------------------------------------------- 1 | #include "node_interop_max.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "output.h" 7 | #include "sockets.h" 8 | #include "util_enum.h" 9 | #include "util_vector.h" 10 | 11 | cse::MaxTexmapShaderNode::MaxTexmapShaderNode(const Float2 position) : EditableNode(NodeCategory::TEXTURE, CyclesNodeType::MaxTex, "3ds Max Texmap") 12 | { 13 | world_pos = position; 14 | 15 | const auto color_output = std::make_shared(this, SocketIOType::OUTPUT, SocketType::COLOR, "Color", "color"); 16 | const auto alpha_output = std::make_shared(this, SocketIOType::OUTPUT, SocketType::FLOAT, "Alpha", "alpha"); 17 | 18 | sockets.push_back(color_output); 19 | sockets.push_back(alpha_output); 20 | 21 | const auto slot_input = std::make_shared(this, SocketIOType::INPUT, SocketType::INT, "Slot", "slot"); 22 | slot_input->value = std::make_shared(1, 1, 32); 23 | const auto autosize_input = std::make_shared(this, SocketIOType::INPUT, SocketType::BOOLEAN, "Auto-size", "autosize"); 24 | autosize_input->value = std::make_shared(true); 25 | const auto width_input = std::make_shared(this, SocketIOType::INPUT, SocketType::INT, "Width", "width"); 26 | width_input->value = std::make_shared(512, 1, 32768); 27 | const auto height_input = std::make_shared(this, SocketIOType::INPUT, SocketType::INT, "Height", "height"); 28 | height_input->value = std::make_shared(512, 1, 32768); 29 | const auto precision_input = std::make_shared(this, SocketIOType::INPUT, SocketType::STRING_ENUM, "Precision", "precision"); 30 | const auto precision_value = std::make_shared(); 31 | precision_value->enum_values.push_back(StringEnumPair("8-bit/Channel Int", "uchar")); 32 | precision_value->enum_values.push_back(StringEnumPair("32-bit/Channel Float", "float")); 33 | precision_value->set_from_internal_name("uchar"); 34 | precision_input->value = precision_value; 35 | const auto vector_input = std::make_shared(this, SocketIOType::INPUT, SocketType::VECTOR, "Vector", "vector"); 36 | 37 | sockets.push_back(slot_input); 38 | sockets.push_back(autosize_input); 39 | sockets.push_back(width_input); 40 | sockets.push_back(height_input); 41 | sockets.push_back(precision_input); 42 | sockets.push_back(vector_input); 43 | } 44 | -------------------------------------------------------------------------------- /src/node_interop_max.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "node_base.h" 4 | 5 | namespace cse { 6 | 7 | class Float2; 8 | 9 | class MaxTexmapShaderNode : public EditableNode { 10 | public: 11 | MaxTexmapShaderNode(Float2 position); 12 | }; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/node_outputs.cpp: -------------------------------------------------------------------------------- 1 | #include "node_outputs.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "output.h" 7 | #include "sockets.h" 8 | #include "util_enum.h" 9 | #include "util_vector.h" 10 | 11 | cse::MaterialOutputNode::MaterialOutputNode(const Float2 position) : EditableNode(NodeCategory::OUTPUT, CyclesNodeType::MaterialOutput, "Material Output") 12 | { 13 | world_pos = position; 14 | 15 | const auto surface_input = std::make_shared(this, SocketIOType::INPUT, SocketType::CLOSURE, "Surface", "surface"); 16 | const auto volume_input = std::make_shared(this, SocketIOType::INPUT, SocketType::CLOSURE, "Volume", "volume"); 17 | const auto displacement_input = std::make_shared(this, SocketIOType::INPUT, SocketType::VECTOR, "Displacement", "displacement"); 18 | 19 | sockets.push_back(surface_input); 20 | sockets.push_back(volume_input); 21 | sockets.push_back(displacement_input); 22 | 23 | // This node being added should not trigger an update to undo state 24 | changed = false; 25 | } 26 | 27 | bool cse::MaterialOutputNode::can_be_deleted() 28 | { 29 | return false; 30 | } 31 | -------------------------------------------------------------------------------- /src/node_outputs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "node_base.h" 4 | 5 | namespace cse { 6 | 7 | class Float2; 8 | 9 | class MaterialOutputNode : public EditableNode { 10 | public: 11 | MaterialOutputNode(Float2 position); 12 | 13 | virtual bool can_be_deleted() override; 14 | }; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/node_shaders.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "node_base.h" 4 | 5 | namespace cse { 6 | 7 | class Float2; 8 | 9 | class PrincipledBSDFNode : public EditableNode { 10 | public: 11 | PrincipledBSDFNode(Float2 position); 12 | }; 13 | 14 | class PrincipledVolumeNode : public EditableNode { 15 | public: 16 | PrincipledVolumeNode(Float2 position); 17 | }; 18 | 19 | class PrincipledHairNode : public EditableNode { 20 | public: 21 | PrincipledHairNode(Float2 position); 22 | }; 23 | 24 | class MixShaderNode : public EditableNode { 25 | public: 26 | MixShaderNode(Float2 position); 27 | }; 28 | 29 | class AddShaderNode : public EditableNode { 30 | public: 31 | AddShaderNode(Float2 position); 32 | }; 33 | 34 | class DiffuseBSDFNode : public EditableNode { 35 | public: 36 | DiffuseBSDFNode(Float2 position); 37 | }; 38 | 39 | class GlossyBSDFNode : public EditableNode { 40 | public: 41 | GlossyBSDFNode(Float2 position); 42 | }; 43 | 44 | class TransparentBSDFNode : public EditableNode { 45 | public: 46 | TransparentBSDFNode(Float2 position); 47 | }; 48 | 49 | class RefractionBSDFNode : public EditableNode { 50 | public: 51 | RefractionBSDFNode(Float2 position); 52 | }; 53 | 54 | class GlassBSDFNode : public EditableNode { 55 | public: 56 | GlassBSDFNode(Float2 position); 57 | }; 58 | 59 | class TranslucentBSDFNode : public EditableNode { 60 | public: 61 | TranslucentBSDFNode(Float2 position); 62 | }; 63 | 64 | class AnisotropicBSDFNode : public EditableNode { 65 | public: 66 | AnisotropicBSDFNode(Float2 position); 67 | }; 68 | 69 | class VelvetBSDFNode : public EditableNode { 70 | public: 71 | VelvetBSDFNode(Float2 position); 72 | }; 73 | 74 | class ToonBSDFNode : public EditableNode { 75 | public: 76 | ToonBSDFNode(Float2 position); 77 | }; 78 | 79 | class SubsurfaceScatteringNode : public EditableNode { 80 | public: 81 | SubsurfaceScatteringNode(Float2 position); 82 | }; 83 | 84 | class EmissionNode : public EditableNode { 85 | public: 86 | EmissionNode(Float2 position); 87 | }; 88 | 89 | class HairBSDFNode : public EditableNode { 90 | public: 91 | HairBSDFNode(Float2 position); 92 | }; 93 | 94 | class HoldoutNode : public EditableNode { 95 | public: 96 | HoldoutNode(Float2 position); 97 | }; 98 | 99 | class VolumeAbsorptionNode : public EditableNode { 100 | public: 101 | VolumeAbsorptionNode(Float2 position); 102 | }; 103 | 104 | class VolumeScatterNode : public EditableNode { 105 | public: 106 | VolumeScatterNode(Float2 position); 107 | }; 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/node_textures.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "node_base.h" 4 | 5 | namespace cse { 6 | 7 | class Float2; 8 | 9 | class BrickTextureNode : public EditableNode { 10 | public: 11 | BrickTextureNode(Float2 position); 12 | }; 13 | 14 | class NoiseTextureNode : public EditableNode { 15 | public: 16 | NoiseTextureNode(Float2 position); 17 | }; 18 | 19 | class WaveTextureNode : public EditableNode { 20 | public: 21 | WaveTextureNode(Float2 position); 22 | }; 23 | 24 | class VoronoiTextureNode : public EditableNode { 25 | public: 26 | VoronoiTextureNode(Float2 position); 27 | }; 28 | 29 | class MusgraveTextureNode : public EditableNode { 30 | public: 31 | MusgraveTextureNode(Float2 position); 32 | }; 33 | 34 | class GradientTextureNode : public EditableNode { 35 | public: 36 | GradientTextureNode(Float2 position); 37 | }; 38 | 39 | class MagicTextureNode : public EditableNode { 40 | public: 41 | MagicTextureNode(Float2 position); 42 | }; 43 | 44 | class CheckerTextureNode : public EditableNode { 45 | public: 46 | CheckerTextureNode(Float2 position); 47 | }; 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/node_vector.cpp: -------------------------------------------------------------------------------- 1 | #include "node_vector.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "output.h" 7 | #include "sockets.h" 8 | #include "util_enum.h" 9 | #include "util_vector.h" 10 | 11 | cse::BumpNode::BumpNode(const Float2 position) : EditableNode(NodeCategory::VECTOR, CyclesNodeType::Bump, "Bump") 12 | { 13 | world_pos = position; 14 | 15 | const auto normal_output = std::make_shared(this, SocketIOType::OUTPUT, SocketType::NORMAL, "Normal", "normal"); 16 | 17 | sockets.push_back(normal_output); 18 | 19 | const auto invert_input = std::make_shared(this, SocketIOType::INPUT, SocketType::BOOLEAN, "Invert", "invert"); 20 | invert_input->value = std::make_shared(false); 21 | const auto strength_input = std::make_shared(this, SocketIOType::INPUT, SocketType::FLOAT, "Strength", "strength"); 22 | strength_input->value = std::make_shared(1.0f, 0.0f, 1.0f); 23 | const auto distance_input = std::make_shared(this, SocketIOType::INPUT, SocketType::FLOAT, "Distance", "distance"); 24 | distance_input->value = std::make_shared(0.1f, 0.0f, 1000.0f); 25 | const auto height_input = std::make_shared(this, SocketIOType::INPUT, SocketType::FLOAT, "Height", "height"); 26 | height_input->selectable = false; 27 | const auto normal_input = std::make_shared(this, SocketIOType::INPUT, SocketType::NORMAL, "Normal", "normal"); 28 | 29 | sockets.push_back(invert_input); 30 | sockets.push_back(strength_input); 31 | sockets.push_back(distance_input); 32 | sockets.push_back(height_input); 33 | sockets.push_back(normal_input); 34 | } 35 | 36 | cse::DisplacementNode::DisplacementNode(const Float2 position) : EditableNode(NodeCategory::VECTOR, CyclesNodeType::Displacement, "Displacement") 37 | { 38 | world_pos = position; 39 | 40 | const auto displacement_output = std::make_shared (this, SocketIOType::OUTPUT, SocketType::VECTOR, "Displacement", "displacement"); 41 | 42 | sockets.push_back(displacement_output); 43 | 44 | const auto space_input = std::make_shared(this, SocketIOType::INPUT, SocketType::STRING_ENUM, "Space", "space"); 45 | const auto space_value = std::make_shared(); 46 | space_value->enum_values.push_back(StringEnumPair("Object Space", "object")); 47 | space_value->enum_values.push_back(StringEnumPair("World Space", "world")); 48 | space_value->set_from_internal_name("object"); 49 | space_input->value = space_value; 50 | const auto height_input = std::make_shared(this, SocketIOType::INPUT, SocketType::FLOAT, "Height", "height"); 51 | height_input->value = std::make_shared(0.0f, 0.0f, 1000.0f); 52 | const auto midlevel_input = std::make_shared(this, SocketIOType::INPUT, SocketType::FLOAT, "Midlevel", "midlevel"); 53 | midlevel_input->value = std::make_shared(0.5f, 0.0f, 1000.0f); 54 | const auto scale_input = std::make_shared(this, SocketIOType::INPUT, SocketType::FLOAT, "Scale", "scale"); 55 | scale_input->value = std::make_shared(1.0f, 0.0f, 1000.0f); 56 | const auto normal_input = std::make_shared(this, SocketIOType::INPUT, SocketType::NORMAL, "Normal", "normal"); 57 | 58 | sockets.push_back(space_input); 59 | sockets.push_back(height_input); 60 | sockets.push_back(midlevel_input); 61 | sockets.push_back(scale_input); 62 | sockets.push_back(normal_input); 63 | } 64 | 65 | cse::NormalMapNode::NormalMapNode(const Float2 position) : EditableNode(NodeCategory::VECTOR, CyclesNodeType::NormalMap, "Normal Map") 66 | { 67 | world_pos = position; 68 | 69 | const auto normal_output = std::make_shared(this, SocketIOType::OUTPUT, SocketType::NORMAL, "Normal", "normal"); 70 | 71 | sockets.push_back(normal_output); 72 | 73 | const auto space_input = std::make_shared(this, SocketIOType::INPUT, SocketType::STRING_ENUM, "Space", "space"); 74 | const auto space_value = std::make_shared(); 75 | space_value->enum_values.push_back(StringEnumPair("Tangent", "tangent")); 76 | space_value->enum_values.push_back(StringEnumPair("Object", "object")); 77 | space_value->enum_values.push_back(StringEnumPair("World", "world")); 78 | space_value->set_from_internal_name("tangent"); 79 | space_input->value = space_value; 80 | const auto strength_input = std::make_shared(this, SocketIOType::INPUT, SocketType::FLOAT, "Strength", "strength"); 81 | strength_input->value = std::make_shared(1.0f, 0.0f, 10.0f); 82 | const auto color_input = std::make_shared(this, SocketIOType::INPUT, SocketType::COLOR, "Color", "color"); 83 | color_input->value = std::make_shared(0.5f, 0.5f, 1.0f); 84 | 85 | sockets.push_back(space_input); 86 | sockets.push_back(strength_input); 87 | sockets.push_back(color_input); 88 | } 89 | 90 | cse::VectorDisplacementNode::VectorDisplacementNode(const Float2 position) : EditableNode(NodeCategory::VECTOR, CyclesNodeType::VectorDisplacement, "Vector Displacement") 91 | { 92 | world_pos = position; 93 | 94 | const auto displacement_output = std::make_shared (this, SocketIOType::OUTPUT, SocketType::VECTOR, "Displacement", "displacement"); 95 | 96 | sockets.push_back(displacement_output); 97 | 98 | const auto space_input = std::make_shared(this, SocketIOType::INPUT, SocketType::STRING_ENUM, "Space", "space"); 99 | const auto space_value = std::make_shared(); 100 | space_value->enum_values.push_back(StringEnumPair("Tangent Space", "tangent")); 101 | space_value->enum_values.push_back(StringEnumPair("Object Space", "object")); 102 | space_value->enum_values.push_back(StringEnumPair("World Space", "world")); 103 | space_value->set_from_internal_name("tangent"); 104 | space_input->value = space_value; 105 | const auto vector_input = std::make_shared(this, SocketIOType::INPUT, SocketType::COLOR, "Vector", "vector"); 106 | vector_input->value = std::make_shared(0.0f, 0.0f, 0.0f); 107 | const auto midlevel_input = std::make_shared(this, SocketIOType::INPUT, SocketType::FLOAT, "Midlevel", "midlevel"); 108 | midlevel_input->value = std::make_shared(0.5f, 0.0f, 1000.0f); 109 | const auto scale_input = std::make_shared(this, SocketIOType::INPUT, SocketType::FLOAT, "Scale", "scale"); 110 | scale_input->value = std::make_shared(1.0f, 0.0f, 1000.0f); 111 | 112 | sockets.push_back(space_input); 113 | sockets.push_back(vector_input); 114 | sockets.push_back(midlevel_input); 115 | sockets.push_back(scale_input); 116 | } 117 | 118 | cse::VectorTransformNode::VectorTransformNode(const Float2 position) : EditableNode(NodeCategory::VECTOR, CyclesNodeType::VectorTransform, "Vector Transform") 119 | { 120 | world_pos = position; 121 | 122 | const auto vector_output = std::make_shared(this, SocketIOType::OUTPUT, SocketType::VECTOR, "Vector", "vector"); 123 | 124 | sockets.push_back(vector_output); 125 | 126 | const auto type_input = std::make_shared(this, SocketIOType::INPUT, SocketType::STRING_ENUM, "Type", "type"); 127 | const auto type_value = std::make_shared(); 128 | type_value->enum_values.push_back(StringEnumPair("Vector", "vector")); 129 | type_value->enum_values.push_back(StringEnumPair("Point", "point")); 130 | type_value->enum_values.push_back(StringEnumPair("Normal", "normal")); 131 | type_value->set_from_internal_name("vector"); 132 | type_input->value = type_value; 133 | 134 | const auto convert_from_input = std::make_shared(this, SocketIOType::INPUT, SocketType::STRING_ENUM, "Convert From", "convert_from"); 135 | const auto convert_from_value = std::make_shared(); 136 | convert_from_value->enum_values.push_back(StringEnumPair("Camera", "camera")); 137 | convert_from_value->enum_values.push_back(StringEnumPair("Object", "object")); 138 | convert_from_value->enum_values.push_back(StringEnumPair("World", "world")); 139 | convert_from_value->set_from_internal_name("world"); 140 | convert_from_input->value = convert_from_value; 141 | 142 | const auto convert_to_input = std::make_shared(this, SocketIOType::INPUT, SocketType::STRING_ENUM, "Convert To", "convert_to"); 143 | const auto convert_to_value = std::make_shared(); 144 | convert_to_value->enum_values.push_back(StringEnumPair("Camera", "camera")); 145 | convert_to_value->enum_values.push_back(StringEnumPair("Object", "object")); 146 | convert_to_value->enum_values.push_back(StringEnumPair("World", "world")); 147 | convert_to_value->set_from_internal_name("object"); 148 | convert_to_input->value = convert_to_value; 149 | 150 | const auto vector_input = std::make_shared(this, SocketIOType::INPUT, SocketType::VECTOR, "Vector", "vector"); 151 | vector_input->value = std::make_shared(0.0f, -100000.0f, 100000.0f, 0.0f, -100000.0f, 100000.0f, 0.0f, -100000.0f, 100000.0f); 152 | vector_input->selectable = true; 153 | 154 | sockets.push_back(type_input); 155 | sockets.push_back(convert_from_input); 156 | sockets.push_back(convert_to_input); 157 | sockets.push_back(vector_input); 158 | } 159 | -------------------------------------------------------------------------------- /src/node_vector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "node_base.h" 4 | 5 | namespace cse { 6 | 7 | class Float2; 8 | 9 | class BumpNode : public EditableNode { 10 | public: 11 | BumpNode(Float2 position); 12 | }; 13 | 14 | class DisplacementNode : public EditableNode { 15 | public: 16 | DisplacementNode(Float2 position); 17 | }; 18 | 19 | class NormalMapNode : public EditableNode { 20 | public: 21 | NormalMapNode(Float2 position); 22 | }; 23 | 24 | class VectorDisplacementNode : public EditableNode { 25 | public: 26 | VectorDisplacementNode(Float2 position); 27 | }; 28 | 29 | class VectorTransformNode : public EditableNode { 30 | public: 31 | VectorTransformNode(Float2 position); 32 | }; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/output.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "util_vector.h" 8 | 9 | namespace cse { 10 | 11 | enum class CyclesNodeType { 12 | // Shader 13 | PrincipledBSDF, 14 | PrincipledVolume, 15 | PrincipledHair, 16 | MixShader, 17 | AddShader, 18 | DiffuseBSDF, 19 | GlossyBSDF, 20 | TransparentBSDF, 21 | RefractionBSDF, 22 | GlassBSDF, 23 | TranslucentBSDF, 24 | AnisotropicBSDF, 25 | VelvetBSDF, 26 | ToonBSDF, 27 | SubsurfaceScattering, 28 | Emission, 29 | HairBSDF, 30 | Holdout, 31 | VolAbsorption, 32 | VolScatter, 33 | // Texture 34 | MaxTex, 35 | BrickTex, 36 | CheckerTex, 37 | GradientTex, 38 | MagicTex, 39 | MusgraveTex, 40 | NoiseTex, 41 | VoronoiTex, 42 | WaveTex, 43 | // Input 44 | AmbientOcclusion, 45 | Bevel, 46 | CameraData, 47 | Fresnel, 48 | Geometry, 49 | LayerWeight, 50 | LightPath, 51 | ObjectInfo, 52 | RGB, 53 | Tangent, 54 | TextureCoordinate, 55 | Value, 56 | Wireframe, 57 | // Color 58 | MixRGB, 59 | Invert, 60 | LightFalloff, 61 | HSV, 62 | Gamma, 63 | BrightnessContrast, 64 | RGBCurves, 65 | // Vector 66 | Bump, 67 | Displacement, 68 | NormalMap, 69 | VectorDisplacement, 70 | VectorTransform, 71 | // Converter 72 | Blackbody, 73 | ColorRamp, 74 | CombineHSV, 75 | CombineRGB, 76 | CombineXYZ, 77 | Math, 78 | RGBtoBW, 79 | SeparateHSV, 80 | SeparateRGB, 81 | SeparateXYZ, 82 | VectorMath, 83 | Wavelength, 84 | // Output 85 | MaterialOutput, 86 | Unknown, 87 | // Other 88 | Count, 89 | }; 90 | 91 | struct OutputCurve { 92 | std::vector control_points; 93 | int enum_curve_interp = 0; 94 | std::vector samples; 95 | }; 96 | 97 | struct OutputColorRampPoint { 98 | float pos; 99 | Float3 color; 100 | float alpha; 101 | }; 102 | 103 | struct OutputColorRamp { 104 | std::vector points; 105 | std::vector samples_color; 106 | std::vector samples_alpha; 107 | }; 108 | 109 | struct OutputNode { 110 | CyclesNodeType type; 111 | 112 | std::string name; 113 | 114 | float world_x; 115 | float world_y; 116 | 117 | std::map float_values; 118 | std::map float3_values; 119 | std::map string_values; 120 | std::map int_values; 121 | std::map bool_values; 122 | std::map curve_values; 123 | std::map ramp_values; 124 | }; 125 | 126 | struct OutputConnection { 127 | std::string source_node; 128 | std::string source_socket; 129 | std::string dest_node; 130 | std::string dest_socket; 131 | }; 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/panel_edit.cpp: -------------------------------------------------------------------------------- 1 | #include "panel_edit.h" 2 | 3 | cse::EditParamPanel::EditParamPanel(const float width) : 4 | panel_width(width) 5 | { 6 | 7 | } 8 | 9 | float cse::EditParamPanel::get_width() const 10 | { 11 | return panel_width; 12 | } 13 | 14 | float cse::EditParamPanel::get_height() const 15 | { 16 | return panel_height; 17 | } 18 | 19 | void cse::EditParamPanel::pre_draw() 20 | { 21 | // Stub so subclasses can optionally override 22 | } 23 | 24 | void cse::EditParamPanel::set_mouse_local_position(const Float2 local_pos) 25 | { 26 | mouse_local_pos = local_pos; 27 | } 28 | 29 | bool cse::EditParamPanel::is_mouse_over() const 30 | { 31 | if (is_active() == false) { 32 | return false; 33 | } 34 | 35 | const float min_x = 0.0f; 36 | const float max_x = panel_width; 37 | const float min_y = 0.0f; 38 | const float max_y = panel_height; 39 | 40 | if (mouse_local_pos.x > min_x && 41 | mouse_local_pos.x < max_x && 42 | mouse_local_pos.y > min_y && 43 | mouse_local_pos.y < max_y) 44 | { 45 | return true; 46 | } 47 | 48 | return false; 49 | } 50 | 51 | bool cse::EditParamPanel::has_input_focus() const 52 | { 53 | return false; 54 | } 55 | 56 | void cse::EditParamPanel::handle_mouse_button(int, int, int) 57 | { 58 | // Stub 59 | } 60 | 61 | void cse::EditParamPanel::handle_key(int, int, int, int) 62 | { 63 | // Stub 64 | } 65 | 66 | void cse::EditParamPanel::handle_character(unsigned int) 67 | { 68 | // Stub 69 | } 70 | 71 | void cse::EditParamPanel::deselect_input_box() 72 | { 73 | // Stub 74 | } 75 | 76 | 77 | bool cse::EditParamPanel::should_push_undo_state() 78 | { 79 | bool result = request_undo_push; 80 | request_undo_push = false; 81 | return result; 82 | } 83 | 84 | void cse::EditParamPanel::tab() 85 | { 86 | // Stub 87 | } 88 | 89 | void cse::EditParamPanel::reset() 90 | { 91 | // Stub 92 | } 93 | -------------------------------------------------------------------------------- /src/panel_edit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "util_vector.h" 6 | 7 | struct NVGcontext; 8 | 9 | namespace cse { 10 | class SocketValue; 11 | 12 | class EditParamPanel { 13 | public: 14 | EditParamPanel(float width); 15 | virtual ~EditParamPanel() {} 16 | 17 | float get_width() const; 18 | float get_height() const; 19 | 20 | virtual bool is_active() const = 0; 21 | 22 | virtual void pre_draw(); 23 | virtual float draw(NVGcontext* draw_context) = 0; 24 | 25 | virtual void set_mouse_local_position(Float2 local_pos); 26 | virtual bool is_mouse_over() const; 27 | 28 | virtual bool has_input_focus() const; 29 | virtual void handle_mouse_button(int button, int action, int mods); 30 | virtual void handle_key(int key, int scancode, int action, int mods); 31 | virtual void handle_character(unsigned int codepoint); 32 | 33 | virtual void set_attached_value(std::weak_ptr socket_value) = 0; 34 | virtual void deselect_input_box(); 35 | 36 | virtual bool should_push_undo_state(); 37 | 38 | virtual void tab(); 39 | 40 | protected: 41 | virtual void reset(); 42 | 43 | const float panel_width; 44 | float panel_height = 1.0f; 45 | 46 | Float2 mouse_local_pos; 47 | 48 | bool request_undo_push = false; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /src/panel_edit_bool.cpp: -------------------------------------------------------------------------------- 1 | #include "panel_edit_bool.h" 2 | 3 | #include "sockets.h" 4 | #include "util_enum.h" 5 | #include "util_vector.h" 6 | 7 | cse::EditBoolPanel::EditBoolPanel(const float width) : 8 | EditParamPanel(width), 9 | radio_widget(width) 10 | { 11 | 12 | } 13 | 14 | bool cse::EditBoolPanel::is_active() const 15 | { 16 | return (attached_bool.expired() == false); 17 | } 18 | 19 | void cse::EditBoolPanel::pre_draw() 20 | { 21 | radio_widget.pre_draw(); 22 | } 23 | 24 | float cse::EditBoolPanel::draw(NVGcontext* const draw_context) 25 | { 26 | float height_drawn = 0.0f; 27 | 28 | radio_widget_pos = height_drawn; 29 | height_drawn += radio_widget.draw(draw_context); 30 | 31 | panel_height = height_drawn; 32 | return panel_height; 33 | } 34 | 35 | void cse::EditBoolPanel::set_mouse_local_position(const Float2 local_pos) 36 | { 37 | EditParamPanel::set_mouse_local_position(local_pos); 38 | 39 | const Float2 radio_widget_relative = local_pos - Float2(0.0f, radio_widget_pos); 40 | radio_widget.set_mouse_local_position(radio_widget_relative); 41 | } 42 | 43 | void cse::EditBoolPanel::handle_mouse_button(const int button, const int action, const int mods) 44 | { 45 | radio_widget.handle_mouse_button(button, action, mods); 46 | } 47 | 48 | void cse::EditBoolPanel::set_attached_value(const std::weak_ptr socket_value) 49 | { 50 | if (auto socket_value_ptr = socket_value.lock()) { 51 | if (socket_value_ptr->get_type() == SocketType::BOOLEAN) { 52 | const auto bool_value_ptr = std::dynamic_pointer_cast(socket_value_ptr); 53 | if (attached_bool.lock() != bool_value_ptr) { 54 | attached_bool = bool_value_ptr; 55 | radio_widget.attach_value(attached_bool); 56 | } 57 | return; 58 | } 59 | } 60 | attached_bool = std::weak_ptr(); 61 | radio_widget.attach_value(attached_bool); 62 | } 63 | 64 | bool cse::EditBoolPanel::should_push_undo_state() 65 | { 66 | bool result = false; 67 | result = EditParamPanel::should_push_undo_state() || result; 68 | result = radio_widget.should_push_undo_state() || result; 69 | return result; 70 | } 71 | -------------------------------------------------------------------------------- /src/panel_edit_bool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "panel_edit.h" 6 | #include "widget_radio_list.h" 7 | 8 | struct NVGcontext; 9 | 10 | namespace cse { 11 | 12 | class BoolSocketValue; 13 | class Float2; 14 | class SocketValue; 15 | 16 | class EditBoolPanel : public EditParamPanel { 17 | public: 18 | EditBoolPanel(float width); 19 | 20 | virtual bool is_active() const override; 21 | 22 | virtual void pre_draw() override; 23 | virtual float draw(NVGcontext* draw_context) override; 24 | 25 | virtual void set_mouse_local_position(Float2 local_pos) override; 26 | 27 | virtual void handle_mouse_button(int button, int action, int mods) override; 28 | 29 | virtual void set_attached_value(std::weak_ptr socket_value) override; 30 | 31 | virtual bool should_push_undo_state() override; 32 | 33 | private: 34 | std::weak_ptr attached_bool; 35 | 36 | float radio_widget_pos = 0.0f; 37 | RadioListWidget radio_widget; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/panel_edit_color.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "panel_edit.h" 6 | #include "util_area.h" 7 | #include "widget_multi_input.h" 8 | 9 | struct NVGcontext; 10 | 11 | namespace cse { 12 | 13 | class ColorSocketValue; 14 | class Float2; 15 | class Float3; 16 | class SocketValue; 17 | 18 | class EditColorPanel : public EditParamPanel { 19 | public: 20 | EditColorPanel(float width); 21 | 22 | virtual bool is_active() const override; 23 | 24 | virtual void pre_draw() override; 25 | virtual float draw(NVGcontext* draw_context) override; 26 | 27 | virtual void set_mouse_local_position(Float2 local_pos) override; 28 | 29 | virtual bool has_input_focus() const override; 30 | virtual void handle_mouse_button(int button, int action, int mods) override; 31 | virtual void handle_key(int key, int scancode, int action, int mods) override; 32 | virtual void handle_character(unsigned int codepoint) override; 33 | 34 | virtual void set_attached_value(std::weak_ptr socket_value) override; 35 | virtual void deselect_input_box() override; 36 | 37 | virtual bool should_push_undo_state() override; 38 | 39 | virtual void tab() override; 40 | 41 | private: 42 | Float3 get_hsv(); 43 | void set_hsv(Float3 hsv); 44 | 45 | void set_hue_from_mouse(); 46 | void set_sat_val_from_mouse(); 47 | 48 | bool mouse_sat_val_selection_active = false; 49 | bool mouse_hue_selection_active = false; 50 | 51 | Area color_rect_click_target; 52 | Area hue_bar_click_target; 53 | 54 | std::weak_ptr attached_color; 55 | 56 | float input_widget_pos = 0.0f; 57 | MultiInputWidget input_widget; 58 | 59 | float last_hue = 0.0f; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /src/panel_edit_color_ramp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "input_box.h" 7 | #include "panel_edit.h" 8 | #include "util_area.h" 9 | 10 | struct NVGcontext; 11 | 12 | namespace cse { 13 | 14 | class ColorSocketValue; 15 | class ColorRampSocketValue; 16 | class EditColorPanel; 17 | class Float2; 18 | class FloatSocketValue; 19 | class SocketValue; 20 | 21 | struct ColorRampPoint; 22 | 23 | class EditColorRampPanel : public EditParamPanel { 24 | public: 25 | EditColorRampPanel(float width); 26 | virtual ~EditColorRampPanel() override; 27 | 28 | virtual bool is_active() const override; 29 | 30 | virtual void pre_draw() override; 31 | virtual float draw(NVGcontext* draw_context) override; 32 | 33 | virtual void set_mouse_local_position(Float2 local_pos) override; 34 | 35 | virtual bool has_input_focus() const override; 36 | virtual void handle_mouse_button(int button, int action, int mods) override; 37 | virtual void handle_key(int key, int scancode, int action, int mods) override; 38 | virtual void handle_character(unsigned int codepoint) override; 39 | 40 | virtual void set_attached_value(std::weak_ptr socket_value) override; 41 | virtual void deselect_input_box() override; 42 | 43 | virtual bool should_push_undo_state() override; 44 | 45 | virtual void tab() override; 46 | 47 | private: 48 | void update_preview(); 49 | bool something_is_selected() const; 50 | 51 | // Everything needed to support one row in the UI 52 | class ColorRampRow { 53 | public: 54 | ColorRampRow(cse::ColorRampPoint point); 55 | 56 | bool has_input_focus() const; 57 | void handle_character(unsigned int codepoint); 58 | 59 | FloatInputBox box_pos; 60 | FloatInputBox box_alpha; 61 | Area color_target; 62 | bool color_selected = false; 63 | 64 | Area delete_target; 65 | 66 | std::shared_ptr value_pos; 67 | std::shared_ptr value_alpha; 68 | std::shared_ptr value_color; 69 | 70 | bool flagged_for_deletion = false; 71 | }; 72 | 73 | std::weak_ptr attached_ramp; 74 | 75 | std::vector ramp_rows; 76 | Area new_point_button; 77 | bool new_point_button_pressed = false; 78 | 79 | float edit_color_panel_pos = 0.0f; 80 | std::shared_ptr edit_color_panel; 81 | 82 | static constexpr int PREVIEW_WIDTH_PX = 256; 83 | static constexpr int PREVIEW_CHANNELS = 4; 84 | unsigned char preview_tex_color[PREVIEW_WIDTH_PX * PREVIEW_CHANNELS]; 85 | unsigned char preview_tex_alpha[PREVIEW_WIDTH_PX * PREVIEW_CHANNELS]; 86 | 87 | NVGcontext* nvg_context = nullptr; 88 | int nvg_handle_preview_color = -1; 89 | int nvg_handle_preview_alpha = -1; 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /src/panel_edit_curve.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "panel_edit.h" 8 | #include "util_area.h" 9 | #include "util_enum.h" 10 | #include "util_vector.h" 11 | 12 | struct NVGcontext; 13 | 14 | namespace cse { 15 | class CurveSocketValue; 16 | class SocketValue; 17 | 18 | class EditCurvePanel : public EditParamPanel { 19 | public: 20 | EditCurvePanel(float width); 21 | 22 | virtual bool is_active() const override; 23 | 24 | virtual void pre_draw() override; 25 | virtual float draw(NVGcontext* draw_context) override; 26 | virtual void handle_mouse_button(int button, int action, int mods) override; 27 | 28 | virtual void set_attached_value(std::weak_ptr socket_value) override; 29 | 30 | protected: 31 | virtual void reset() override; 32 | 33 | private: 34 | void move_selected_point(Float2 new_pos); 35 | 36 | std::weak_ptr attached_curve; 37 | EditCurveMode edit_mode = EditCurveMode::MOVE; 38 | 39 | Area target_view; 40 | std::vector> edit_mode_click_areas; 41 | std::vector> interp_click_areas; 42 | 43 | std::size_t selected_point_index = 0; 44 | bool selected_point_valid = false; 45 | 46 | bool moving_selected_point = false; 47 | bool mouse_has_moved = false; 48 | Float2 move_selected_point_begin_mouse_pos; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /src/panel_edit_enum.cpp: -------------------------------------------------------------------------------- 1 | #include "panel_edit_enum.h" 2 | 3 | #include "sockets.h" 4 | #include "util_enum.h" 5 | #include "util_vector.h" 6 | 7 | cse::EditEnumPanel::EditEnumPanel(const float width) : 8 | EditParamPanel(width), 9 | radio_widget(width) 10 | { 11 | 12 | } 13 | 14 | bool cse::EditEnumPanel::is_active() const 15 | { 16 | return attached_enum.use_count() > 0; 17 | } 18 | 19 | void cse::EditEnumPanel::pre_draw() 20 | { 21 | radio_widget.pre_draw(); 22 | } 23 | 24 | float cse::EditEnumPanel::draw(NVGcontext* const draw_context) 25 | { 26 | float height_drawn = 0.0f; 27 | 28 | radio_widget_pos = height_drawn; 29 | height_drawn += radio_widget.draw(draw_context); 30 | 31 | panel_height = height_drawn; 32 | return panel_height; 33 | } 34 | 35 | void cse::EditEnumPanel::set_mouse_local_position(const Float2 local_pos) 36 | { 37 | EditParamPanel::set_mouse_local_position(local_pos); 38 | 39 | const Float2 radio_widget_relative = local_pos - Float2(0.0f, radio_widget_pos); 40 | radio_widget.set_mouse_local_position(radio_widget_relative); 41 | } 42 | 43 | void cse::EditEnumPanel::handle_mouse_button(const int button, const int action, const int mods) 44 | { 45 | radio_widget.handle_mouse_button(button, action, mods); 46 | } 47 | 48 | void cse::EditEnumPanel::set_attached_value(const std::weak_ptr socket_value) 49 | { 50 | if (auto socket_value_ptr = socket_value.lock()) { 51 | if (socket_value_ptr->get_type() == SocketType::STRING_ENUM) { 52 | const auto enum_value_ptr = std::dynamic_pointer_cast(socket_value_ptr); 53 | if (attached_enum.lock() != enum_value_ptr) { 54 | attached_enum = enum_value_ptr; 55 | radio_widget.attach_value(attached_enum); 56 | } 57 | return; 58 | } 59 | } 60 | attached_enum = std::weak_ptr(); 61 | radio_widget.attach_value(attached_enum); 62 | } 63 | 64 | bool cse::EditEnumPanel::should_push_undo_state() 65 | { 66 | bool result = false; 67 | result = EditParamPanel::should_push_undo_state() || result; 68 | result = radio_widget.should_push_undo_state() || result; 69 | return result; 70 | } 71 | -------------------------------------------------------------------------------- /src/panel_edit_enum.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "panel_edit.h" 6 | #include "widget_radio_list.h" 7 | 8 | struct NVGcontext; 9 | 10 | namespace cse { 11 | 12 | class Float2; 13 | class SocketValue; 14 | class StringEnumSocketValue; 15 | 16 | class EditEnumPanel : public EditParamPanel { 17 | public: 18 | EditEnumPanel(float width); 19 | 20 | virtual bool is_active() const override; 21 | 22 | virtual void pre_draw() override; 23 | virtual float draw(NVGcontext* draw_context) override; 24 | 25 | virtual void set_mouse_local_position(Float2 local_pos) override; 26 | 27 | virtual void handle_mouse_button(int button, int action, int mods) override; 28 | 29 | virtual void set_attached_value(std::weak_ptr socket_value) override; 30 | 31 | virtual bool should_push_undo_state() override; 32 | 33 | private: 34 | std::weak_ptr attached_enum; 35 | 36 | float radio_widget_pos = 0.0f; 37 | RadioListWidget radio_widget; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/panel_edit_multi_input.cpp: -------------------------------------------------------------------------------- 1 | #include "panel_edit_multi_input.h" 2 | 3 | #include 4 | 5 | #include "sockets.h" 6 | #include "util_enum.h" 7 | #include "util_vector.h" 8 | 9 | cse::EditMultiInputPanel::EditMultiInputPanel(const float width) : 10 | EditParamPanel(width), 11 | input_widget(width) 12 | { 13 | 14 | } 15 | 16 | bool cse::EditMultiInputPanel::is_active() const 17 | { 18 | bool result = false; 19 | result = (attached_int.use_count() > 0) || result; 20 | result = (attached_float.use_count() > 0) || result; 21 | result = (attached_vec.use_count() > 0) || result; 22 | return result; 23 | } 24 | 25 | float cse::EditMultiInputPanel::draw(NVGcontext* const draw_context) 26 | { 27 | float height_drawn = 0.0f; 28 | 29 | if (is_active() == false) { 30 | return height_drawn; 31 | } 32 | 33 | nvgSave(draw_context); 34 | nvgTranslate(draw_context, 0.0f, height_drawn); 35 | input_widget_pos = height_drawn; 36 | height_drawn += input_widget.draw(draw_context); 37 | nvgRestore(draw_context); 38 | 39 | panel_height = height_drawn; 40 | return panel_height; 41 | } 42 | 43 | void cse::EditMultiInputPanel::set_mouse_local_position(const Float2 local_pos) 44 | { 45 | EditParamPanel::set_mouse_local_position(local_pos); 46 | 47 | const Float2 input_widget_offset = local_pos - Float2(0.0f, input_widget_pos); 48 | input_widget.set_mouse_local_pos(input_widget_offset); 49 | } 50 | 51 | bool cse::EditMultiInputPanel::has_input_focus() const 52 | { 53 | return input_widget.has_input_focus(); 54 | } 55 | 56 | void cse::EditMultiInputPanel::handle_mouse_button(const int button, const int action, const int mods) 57 | { 58 | input_widget.handle_mouse_button(button, action, mods); 59 | } 60 | 61 | void cse::EditMultiInputPanel::handle_key(const int key, int scancode, const int action, const int mods) 62 | { 63 | if (input_widget.has_input_focus()) { 64 | input_widget.handle_key(key, scancode, action, mods); 65 | } 66 | } 67 | 68 | void cse::EditMultiInputPanel::handle_character(const unsigned int codepoint) 69 | { 70 | if (input_widget.has_input_focus()) { 71 | input_widget.handle_character(codepoint); 72 | } 73 | } 74 | 75 | void cse::EditMultiInputPanel::set_attached_value(const std::weak_ptr socket_value) 76 | { 77 | if (auto socket_value_ptr = socket_value.lock()) { 78 | switch (socket_value_ptr->get_type()) { 79 | case SocketType::INT: 80 | { 81 | const auto int_value_ptr = std::dynamic_pointer_cast(socket_value_ptr); 82 | if (attached_int.lock() != int_value_ptr) { 83 | attached_int = int_value_ptr; 84 | input_widget.clear_sockets(); 85 | input_widget.add_socket_input("Value:", attached_int); 86 | } 87 | attached_float = std::weak_ptr(); 88 | attached_vec = std::weak_ptr(); 89 | break; 90 | } 91 | case SocketType::FLOAT: 92 | { 93 | const auto float_value_ptr = std::dynamic_pointer_cast(socket_value_ptr); 94 | if (attached_float.lock() != float_value_ptr) { 95 | attached_float = float_value_ptr; 96 | input_widget.clear_sockets(); 97 | input_widget.add_socket_input("Value:", attached_float); 98 | } 99 | attached_int = std::weak_ptr(); 100 | attached_vec = std::weak_ptr(); 101 | break; 102 | } 103 | case SocketType::VECTOR: 104 | { 105 | const auto vec_value_ptr = std::dynamic_pointer_cast(socket_value_ptr); 106 | if (attached_vec.lock() != vec_value_ptr) { 107 | attached_vec = vec_value_ptr; 108 | input_widget.clear_sockets(); 109 | input_widget.add_socket_input("X:", vec_value_ptr->x_socket_val); 110 | input_widget.add_socket_input("Y:", vec_value_ptr->y_socket_val); 111 | input_widget.add_socket_input("Z:", vec_value_ptr->z_socket_val); 112 | } 113 | attached_int = std::weak_ptr(); 114 | attached_float = std::weak_ptr(); 115 | break; 116 | } 117 | default: 118 | attached_int = std::weak_ptr(); 119 | attached_float = std::weak_ptr(); 120 | attached_vec = std::weak_ptr(); 121 | } 122 | return; 123 | } 124 | attached_int = std::weak_ptr(); 125 | attached_float = std::weak_ptr(); 126 | attached_vec = std::weak_ptr(); 127 | input_widget.clear_sockets(); 128 | } 129 | 130 | void cse::EditMultiInputPanel::deselect_input_box() 131 | { 132 | request_undo_push = input_widget.complete_input() || request_undo_push; 133 | } 134 | 135 | bool cse::EditMultiInputPanel::should_push_undo_state() 136 | { 137 | bool result = false; 138 | result = EditParamPanel::should_push_undo_state() || result; 139 | result = input_widget.should_push_undo_state() || result; 140 | return result; 141 | } 142 | 143 | void cse::EditMultiInputPanel::tab() 144 | { 145 | input_widget.tab(); 146 | } 147 | -------------------------------------------------------------------------------- /src/panel_edit_multi_input.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "panel_edit.h" 6 | #include "widget_multi_input.h" 7 | 8 | struct NVGcontext; 9 | 10 | namespace cse { 11 | 12 | class Float2; 13 | class FloatSocketValue; 14 | class Float3SocketValue; 15 | class IntSocketValue; 16 | class SocketValue; 17 | 18 | class EditMultiInputPanel : public EditParamPanel { 19 | public: 20 | EditMultiInputPanel(float width); 21 | 22 | virtual bool is_active() const override; 23 | 24 | virtual float draw(NVGcontext* draw_context) override; 25 | 26 | virtual void set_mouse_local_position(Float2 local_pos) override; 27 | 28 | virtual bool has_input_focus() const override; 29 | virtual void handle_mouse_button(int button, int action, int mods) override; 30 | virtual void handle_key(int key, int scancode, int action, int mods) override; 31 | virtual void handle_character(unsigned int codepoint) override; 32 | 33 | virtual void set_attached_value(std::weak_ptr socket_value) override; 34 | virtual void deselect_input_box() override; 35 | 36 | virtual bool should_push_undo_state() override; 37 | 38 | virtual void tab() override; 39 | 40 | private: 41 | std::weak_ptr attached_int; 42 | std::weak_ptr attached_float; 43 | std::weak_ptr attached_vec; 44 | 45 | float input_widget_pos = 0.0f; 46 | MultiInputWidget input_widget; 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /src/selection.cpp: -------------------------------------------------------------------------------- 1 | #include "selection.h" 2 | 3 | #include "node_base.h" 4 | #include "util_vector.h" 5 | 6 | void cse::Selection::move_nodes(const Float2 delta) 7 | { 8 | for (const auto weak_node : nodes) { 9 | if (const auto this_node = weak_node.lock()) { 10 | this_node->world_pos += delta; 11 | } 12 | } 13 | } 14 | 15 | void cse::Selection::modify_selection(SelectMode mode, std::weak_ptr node) 16 | { 17 | if (node.use_count() == 0) { 18 | return; 19 | } 20 | 21 | switch (mode) { 22 | case SelectMode::NORMAL: 23 | { 24 | if (nodes.count(node) > 0) { 25 | // This node is already selected, do not change selection 26 | } 27 | else { 28 | // This node is not selected, now select this node exclusively 29 | nodes.clear(); 30 | nodes.insert(node); 31 | } 32 | break; 33 | } 34 | case SelectMode::ADD: 35 | { 36 | nodes.insert(node); 37 | break; 38 | } 39 | case SelectMode::TOGGLE: 40 | { 41 | if (nodes.count(node) > 0) { 42 | nodes.erase(node); 43 | } 44 | else { 45 | nodes.insert(node); 46 | } 47 | break; 48 | } 49 | } 50 | } 51 | 52 | void cse::Selection::clear() 53 | { 54 | nodes.clear(); 55 | socket = std::weak_ptr(); 56 | } -------------------------------------------------------------------------------- /src/selection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "util_typedef.h" 6 | 7 | namespace cse { 8 | 9 | class EditableNode; 10 | class Float2; 11 | class NodeSocket; 12 | 13 | enum class SelectMode { 14 | NORMAL, 15 | ADD, 16 | TOGGLE, 17 | }; 18 | 19 | // This class is used to hold references to the objects currently selected by the user 20 | class Selection { 21 | public: 22 | void move_nodes(Float2 delta); 23 | 24 | void modify_selection(SelectMode mode, std::weak_ptr node); 25 | 26 | void clear(); 27 | 28 | WeakNodeSet nodes; 29 | std::weak_ptr socket; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/serialize.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace cse { 9 | 10 | struct OutputConnection; 11 | struct OutputNode; 12 | 13 | class EditableNode; 14 | class NodeConnection; 15 | 16 | void generate_output_lists( 17 | const std::list>& node_list, 18 | const std::list& connection_list, 19 | std::vector& out_node_list, 20 | std::vector& out_connection_list 21 | ); 22 | 23 | std::string serialize_graph(std::vector& nodes, std::vector& connections); 24 | void deserialize_graph( 25 | const std::string& graph, 26 | std::list>& nodes, 27 | std::list& connections 28 | ); 29 | 30 | } -------------------------------------------------------------------------------- /src/sockets.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "util_color_ramp.h" 9 | #include "util_enum.h" 10 | #include "util_vector.h" 11 | 12 | namespace cse { 13 | 14 | class EditableNode; 15 | 16 | class SocketValue { 17 | public: 18 | virtual ~SocketValue() {} 19 | virtual SocketType get_type() const = 0; 20 | }; 21 | 22 | class IntSocketValue : public SocketValue { 23 | public: 24 | IntSocketValue(int default_val, int min, int max); 25 | 26 | virtual SocketType get_type() const override; 27 | 28 | int get_value(); 29 | void set_value(int value_in); 30 | 31 | private: 32 | int value; 33 | 34 | int default_val; 35 | int min; 36 | int max; 37 | }; 38 | 39 | class FloatSocketValue : public SocketValue { 40 | public: 41 | FloatSocketValue(float default_val, float min, float max); 42 | 43 | virtual SocketType get_type() const override; 44 | 45 | float get_value(); 46 | void set_value(float value_in); 47 | 48 | private: 49 | float value; 50 | 51 | float default_val; 52 | float min; 53 | float max; 54 | }; 55 | 56 | class Float3SocketValue : public SocketValue { 57 | public: 58 | Float3SocketValue(float default_x, float min_x, float max_x, 59 | float default_y, float min_y, float max_y, 60 | float default_z, float min_z, float max_z); 61 | 62 | virtual SocketType get_type() const override; 63 | 64 | Float3 get_value(); 65 | void set_x(float x_in); 66 | void set_y(float y_in); 67 | void set_z(float z_in); 68 | 69 | std::shared_ptr x_socket_val; 70 | std::shared_ptr y_socket_val; 71 | std::shared_ptr z_socket_val; 72 | }; 73 | 74 | class ColorSocketValue : public SocketValue { 75 | public: 76 | ColorSocketValue(float default_r, float default_g, float default_b); 77 | 78 | virtual SocketType get_type() const override; 79 | 80 | Float3 get_value(); 81 | 82 | std::shared_ptr r_socket_val; 83 | std::shared_ptr g_socket_val; 84 | std::shared_ptr b_socket_val; 85 | 86 | float last_hue = 0.0f; 87 | }; 88 | 89 | class StringEnumPair { 90 | public: 91 | static StringEnumPair make_spacer(); 92 | 93 | StringEnumPair(const std::string& display_value, const std::string& internal_value); 94 | 95 | bool is_spacer() const; 96 | 97 | std::string display_value; 98 | std::string internal_value; 99 | 100 | protected: 101 | bool this_is_spacer = false; 102 | }; 103 | 104 | class StringEnumSocketValue : public SocketValue { 105 | public: 106 | StringEnumSocketValue(); 107 | 108 | virtual SocketType get_type() const override; 109 | 110 | bool set_from_internal_name(std::string internal_name); 111 | 112 | StringEnumPair value; 113 | std::vector enum_values; 114 | }; 115 | 116 | class BoolSocketValue : public SocketValue { 117 | public: 118 | BoolSocketValue(bool default_val); 119 | 120 | virtual SocketType get_type() const override; 121 | 122 | bool value; 123 | bool default_value; 124 | }; 125 | 126 | class CurveSocketValue : public SocketValue { 127 | public: 128 | CurveSocketValue(); 129 | 130 | virtual SocketType get_type() const override; 131 | 132 | void reset_value(); 133 | void create_point(float x); 134 | void delete_point(const Float2& target); 135 | // Gets the index, if any, of the closest point within selection range 136 | bool get_target_index(const Float2& target, std::size_t& index); 137 | // Move a point and return its new index 138 | std::size_t move_point(std::size_t index, const Float2& new_point); 139 | 140 | void sort_curve_points(); 141 | 142 | std::vector curve_points; 143 | CurveInterpolation curve_interp = CurveInterpolation::CUBIC_HERMITE; 144 | }; 145 | 146 | class ColorRampSocketValue : public SocketValue { 147 | public: 148 | ColorRampSocketValue(); 149 | 150 | virtual SocketType get_type() const override; 151 | 152 | std::vector ramp_points; 153 | 154 | std::vector evaluate_samples(unsigned int count = 256) const; 155 | }; 156 | 157 | class NodeSocket { 158 | public: 159 | NodeSocket(EditableNode* parent, SocketIOType io_type, SocketType socket_type, std::string display_name, std::string internal_name); 160 | 161 | void set_float_val(float float_in); 162 | void set_float3_val(float x_in, float y_in, float z_in); 163 | void set_string_val(StringEnumPair string_in); 164 | 165 | EditableNode* const parent; 166 | 167 | SocketIOType io_type; 168 | SocketType socket_type; 169 | std::string display_name; 170 | std::string internal_name; 171 | 172 | bool draw_socket = true; 173 | 174 | bool selectable = false; 175 | 176 | // World position where this socket was drawn last frame 177 | Float2 world_draw_position; 178 | 179 | std::shared_ptr value; 180 | 181 | // This variable is used to track when a value should be crossed out in the UI 182 | // It should be set to true prior to calling draw() when this socket's input has a connection 183 | // It will be reset to false when the parent node's draw() is called 184 | bool input_connected_this_frame = false; 185 | }; 186 | 187 | } 188 | -------------------------------------------------------------------------------- /src/statusbar.cpp: -------------------------------------------------------------------------------- 1 | #include "statusbar.h" 2 | 3 | #include 4 | 5 | 6 | cse::NodeEditorStatusBar::NodeEditorStatusBar() 7 | { 8 | status_text = "Node graph editor loaded"; 9 | } 10 | 11 | void cse::NodeEditorStatusBar::draw(NVGcontext* const draw_context, const float width) 12 | { 13 | // Panel 14 | nvgBeginPath(draw_context); 15 | nvgRoundedRect(draw_context, 0.0f, 0.0f, width, get_status_bar_height(), 0.0f); 16 | nvgFillColor(draw_context, nvgRGBA(180, 180, 180, 255)); 17 | nvgFill(draw_context); 18 | 19 | // Status text 20 | nvgFontSize(draw_context, UI_FONT_SIZE_NORMAL); 21 | nvgFontFace(draw_context, "sans"); 22 | nvgTextAlign(draw_context, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); 23 | nvgFontBlur(draw_context, 0.0f); 24 | nvgFillColor(draw_context, nvgRGBA(0, 0, 0, 255)); 25 | nvgText(draw_context, 4.0f, get_status_bar_height() / 2.0f, status_text.c_str(), nullptr); 26 | 27 | // Zoom text 28 | nvgFontSize(draw_context, UI_FONT_SIZE_NORMAL); 29 | nvgFontFace(draw_context, "sans"); 30 | nvgTextAlign(draw_context, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE); 31 | nvgFontBlur(draw_context, 0.0f); 32 | nvgFillColor(draw_context, nvgRGBA(0, 0, 0, 255)); 33 | nvgText(draw_context, width - 4.0f, get_status_bar_height() / 2.0f, zoom_text.c_str(), nullptr); 34 | } 35 | 36 | void cse::NodeEditorStatusBar::set_status_text(const std::string& text) 37 | { 38 | status_text = text; 39 | } 40 | 41 | void cse::NodeEditorStatusBar::set_zoom_text(const std::string& text) 42 | { 43 | zoom_text = text; 44 | } -------------------------------------------------------------------------------- /src/statusbar.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "gui_sizes.h" 6 | 7 | struct NVGcontext; 8 | 9 | namespace cse { 10 | 11 | class NodeEditorStatusBar { 12 | public: 13 | static float get_status_bar_height() { return UI_STATUSBAR_HEIGHT; } 14 | 15 | NodeEditorStatusBar(); 16 | 17 | void draw(NVGcontext* draw_context, float width); 18 | 19 | void set_status_text(const std::string& text); 20 | void set_zoom_text(const std::string& text); 21 | 22 | private: 23 | std::string status_text; 24 | std::string zoom_text; 25 | }; 26 | 27 | } -------------------------------------------------------------------------------- /src/subwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "subwindow.h" 2 | 3 | #include 4 | 5 | #include "gui_sizes.h" 6 | 7 | cse::NodeEditorSubwindow::NodeEditorSubwindow(Float2 screen_position, std::string title) 8 | { 9 | subwindow_screen_pos = screen_position; 10 | this->title = title; 11 | } 12 | 13 | cse::Float2 cse::NodeEditorSubwindow::get_screen_pos() const 14 | { 15 | return subwindow_screen_pos; 16 | } 17 | 18 | float cse::NodeEditorSubwindow::get_width() const 19 | { 20 | return subwindow_width; 21 | } 22 | 23 | float cse::NodeEditorSubwindow::get_height() const 24 | { 25 | return content_height + UI_SUBWIN_HEADER_HEIGHT + 3.0f; 26 | } 27 | 28 | void cse::NodeEditorSubwindow::pre_draw() 29 | { 30 | // Do nothing by default 31 | } 32 | 33 | void cse::NodeEditorSubwindow::draw(NVGcontext* draw_context) 34 | { 35 | if (is_active() == false) { 36 | return; 37 | } 38 | 39 | // Draw window 40 | nvgBeginPath(draw_context); 41 | nvgRoundedRect(draw_context, 0.0f, 0.0f, get_width(), get_height(), UI_SUBWIN_CORNER_RADIUS); 42 | nvgFillColor(draw_context, nvgRGBA(180, 180, 180, 255)); 43 | nvgFill(draw_context); 44 | 45 | // Header 46 | nvgBeginPath(draw_context); 47 | nvgRoundedRect(draw_context, 0.0f, 0.0f, subwindow_width, UI_SUBWIN_HEADER_HEIGHT, UI_SUBWIN_CORNER_RADIUS); 48 | nvgRect(draw_context, 0.0f, UI_SUBWIN_HEADER_HEIGHT / 2, subwindow_width, UI_SUBWIN_HEADER_HEIGHT / 2); 49 | if (is_mouse_over_header()) { 50 | nvgFillColor(draw_context, nvgRGBA(225, 225, 225, 255)); 51 | } 52 | else { 53 | nvgFillColor(draw_context, nvgRGBA(210, 210, 210, 255)); 54 | } 55 | nvgFill(draw_context); 56 | 57 | nvgStrokeColor(draw_context, nvgRGBA(0, 0, 0, 255)); 58 | nvgStrokeWidth(draw_context, 0.8f); 59 | 60 | nvgBeginPath(draw_context); 61 | nvgMoveTo(draw_context, 0.0f, UI_SUBWIN_HEADER_HEIGHT); 62 | nvgLineTo(draw_context, subwindow_width, UI_SUBWIN_HEADER_HEIGHT); 63 | nvgStroke(draw_context); 64 | 65 | // Outline 66 | nvgBeginPath(draw_context); 67 | nvgRoundedRect(draw_context, 0.0f, 0.0f, subwindow_width, get_height(), UI_SUBWIN_CORNER_RADIUS); 68 | nvgStroke(draw_context); 69 | 70 | // Title 71 | nvgFontSize(draw_context, UI_FONT_SIZE_NORMAL); 72 | nvgFontFace(draw_context, "sans"); 73 | nvgTextAlign(draw_context, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); 74 | nvgFontBlur(draw_context, 0.0f); 75 | nvgFillColor(draw_context, nvgRGBA(0, 0, 0, 255)); 76 | nvgText(draw_context, subwindow_width / 2, UI_SUBWIN_HEADER_HEIGHT / 2, title.c_str(), nullptr); 77 | 78 | // Set up context and call draw_content 79 | nvgSave(draw_context); 80 | nvgTranslate(draw_context, 0.0f, UI_SUBWIN_HEADER_HEIGHT + 3.0f); 81 | nvgScissor(draw_context, 0.0f, 0.0f, get_width(), content_height); 82 | draw_content(draw_context); 83 | nvgRestore(draw_context); 84 | } 85 | 86 | bool cse::NodeEditorSubwindow::is_mouse_over() const 87 | { 88 | if (subwindow_moving) { 89 | return true; 90 | } 91 | if (is_active() == false) { 92 | return false; 93 | } 94 | return ( 95 | mouse_local_pos.x > 0.0f && 96 | mouse_local_pos.y > 0.0f && 97 | mouse_local_pos.x < get_width() && 98 | mouse_local_pos.y < get_height() 99 | ); 100 | } 101 | 102 | bool cse::NodeEditorSubwindow::is_mouse_over_header() const 103 | { 104 | if (subwindow_moving) { 105 | return true; 106 | } 107 | if (is_active() == false) { 108 | return false; 109 | } 110 | return (mouse_local_pos.x > 0 && 111 | mouse_local_pos.x < subwindow_width && 112 | mouse_local_pos.y > 0 && 113 | mouse_local_pos.y < UI_SUBWIN_HEADER_HEIGHT); 114 | } 115 | 116 | void cse::NodeEditorSubwindow::set_mouse_position(Float2 local_position, float max_safe_pos_y) 117 | { 118 | if (subwindow_moving) { 119 | subwindow_screen_pos = subwindow_screen_pos + (local_position - mouse_local_begin_move_pos); 120 | if (subwindow_screen_pos.y + UI_SUBWIN_HEADER_HEIGHT > max_safe_pos_y) { 121 | subwindow_screen_pos = Float2(subwindow_screen_pos.x, max_safe_pos_y - UI_SUBWIN_HEADER_HEIGHT); 122 | } 123 | mouse_local_pos = mouse_local_begin_move_pos; 124 | } 125 | else { 126 | mouse_local_pos = local_position; 127 | } 128 | mouse_content_pos = mouse_local_pos - Float2(0.0f, UI_SUBWIN_HEADER_HEIGHT + 3.0f); 129 | } 130 | 131 | bool cse::NodeEditorSubwindow::has_input_focus() const 132 | { 133 | return false; 134 | } 135 | 136 | void cse::NodeEditorSubwindow::deselect_input_box() 137 | { 138 | 139 | } 140 | 141 | void cse::NodeEditorSubwindow::handle_mouse_button(int /*button*/, int /*action*/, int /*mods*/) 142 | { 143 | 144 | } 145 | 146 | void cse::NodeEditorSubwindow::handle_key(int /*key*/, int /*scancode*/, int /*action*/, int /*mods*/) 147 | { 148 | 149 | } 150 | 151 | void cse::NodeEditorSubwindow::handle_character(unsigned int /*codepoint*/) 152 | { 153 | 154 | } 155 | 156 | void cse::NodeEditorSubwindow::move_window_begin() 157 | { 158 | subwindow_moving = true; 159 | mouse_local_begin_move_pos = mouse_local_pos; 160 | } 161 | 162 | void cse::NodeEditorSubwindow::move_window_end() 163 | { 164 | subwindow_moving = false; 165 | } 166 | 167 | bool cse::NodeEditorSubwindow::needs_undo_push() 168 | { 169 | return false; 170 | } 171 | 172 | void cse::NodeEditorSubwindow::update_selection(std::weak_ptr) 173 | { 174 | // Do nothing, this is a virtual function for child classes 175 | } 176 | 177 | void cse::NodeEditorSubwindow::tab() 178 | { 179 | // Do nothing, this is a virtual function for child classes 180 | } 181 | -------------------------------------------------------------------------------- /src/subwindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "util_vector.h" 7 | 8 | struct NVGcontext; 9 | 10 | namespace cse { 11 | 12 | class Selection; 13 | 14 | class NodeEditorSubwindow { 15 | public: 16 | NodeEditorSubwindow(Float2 screen_position, std::string title); 17 | virtual ~NodeEditorSubwindow() {} 18 | 19 | virtual Float2 get_screen_pos() const; 20 | virtual float get_width() const; 21 | virtual float get_height() const; 22 | 23 | virtual void pre_draw(); 24 | // Draws the window at (0, 0). Can freely modify nanovg state without nvgSave/nvgRestore. 25 | virtual void draw(NVGcontext* draw_context); 26 | 27 | virtual bool is_mouse_over() const; 28 | virtual bool is_mouse_over_header() const; 29 | virtual void set_mouse_position(Float2 local_position, float max_pos_y); 30 | 31 | // Returns true if this subwindow wants to capture keyboard input 32 | virtual bool has_input_focus() const; 33 | virtual void deselect_input_box(); 34 | 35 | virtual void handle_mouse_button(int button, int action, int mods); 36 | virtual void handle_key(int key, int scancode, int action, int mods); 37 | virtual void handle_character(unsigned int codepoint); 38 | 39 | virtual void move_window_begin(); 40 | virtual void move_window_end(); 41 | 42 | // Returns true if changes made by this window require an undo stack push 43 | // Currently only used by the param editor subwindow 44 | virtual bool needs_undo_push(); 45 | virtual void update_selection(std::weak_ptr selection); 46 | 47 | virtual void tab(); 48 | 49 | virtual bool is_active() const { return true; } 50 | 51 | protected: 52 | virtual void draw_content(NVGcontext* draw_context) = 0; 53 | 54 | std::string title = "Subwindow"; 55 | Float2 subwindow_screen_pos; 56 | 57 | Float2 mouse_local_pos; // Mouse position relative to the top-left corner of the window, prefer using mouse_content_pos over this 58 | Float2 mouse_local_begin_move_pos; 59 | Float2 mouse_content_pos; // Local position not including window header 60 | 61 | float subwindow_width = 100.0f; 62 | float content_height = 1.0f; 63 | 64 | bool subwindow_moving = false; 65 | }; 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/subwindow_node_list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "button_category.h" 7 | #include "buttons_nodes.h" 8 | #include "subwindow.h" 9 | 10 | struct NVGcontext; 11 | 12 | namespace cse { 13 | 14 | class EditableNode; 15 | class Float2; 16 | 17 | class NodeCreationHelper { 18 | public: 19 | NodeCreationHelper(); 20 | 21 | bool is_ready() const; 22 | 23 | void set_node(const std::shared_ptr& new_node); 24 | void clear(); 25 | std::shared_ptr take(); 26 | 27 | private: 28 | std::shared_ptr current_node; 29 | }; 30 | 31 | class NodeListSubwindow : public NodeEditorSubwindow { 32 | public: 33 | NodeListSubwindow(std::weak_ptr node_creation_helper, Float2 screen_position); 34 | 35 | virtual void handle_mouse_button(int button, int action, int mods) override; 36 | 37 | protected: 38 | virtual void draw_content(NVGcontext* draw_context) override; 39 | 40 | const std::list>& get_active_creation_buttons() const; 41 | bool is_category_button_under_mouse() const; 42 | bool is_creation_button_under_mouse() const; 43 | void select_category_button_under_mouse(); 44 | void select_creation_button_under_mouse(); 45 | 46 | void reset_creation_buttons(); 47 | 48 | std::list> category_buttons; 49 | 50 | std::weak_ptr node_creation_helper; 51 | 52 | // this is here so get_active_creation_buttons() can return an empty list in the case where no button is selected 53 | const std::list> empty_creation_button_list; 54 | }; 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/subwindow_param_editor.cpp: -------------------------------------------------------------------------------- 1 | #include "subwindow_param_editor.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "gui_sizes.h" 10 | #include "panel_edit.h" 11 | #include "panel_edit_bool.h" 12 | #include "panel_edit_color.h" 13 | #include "panel_edit_color_ramp.h" 14 | #include "panel_edit_curve.h" 15 | #include "panel_edit_enum.h" 16 | #include "panel_edit_multi_input.h" 17 | #include "selection.h" 18 | #include "sockets.h" 19 | #include "util_enum.h" 20 | #include "util_vector.h" 21 | 22 | cse::ParamEditorSubwindow::ParamEditorSubwindow(const Float2 screen_position) : 23 | NodeEditorSubwindow(screen_position, "Parameter Editor") 24 | { 25 | subwindow_width = UI_SUBWIN_PARAM_EDIT_WIDTH; 26 | panels.push_back(std::make_shared(UI_SUBWIN_PARAM_EDIT_WIDTH)); 27 | panels.push_back(std::make_shared(UI_SUBWIN_PARAM_EDIT_WIDTH)); 28 | panels.push_back(std::make_shared(UI_SUBWIN_PARAM_EDIT_WIDTH)); 29 | panels.push_back(std::make_shared(UI_SUBWIN_PARAM_EDIT_WIDTH)); 30 | panels.push_back(std::make_shared(UI_SUBWIN_PARAM_EDIT_WIDTH)); 31 | panels.push_back(std::make_shared(UI_SUBWIN_PARAM_EDIT_WIDTH)); 32 | } 33 | 34 | void cse::ParamEditorSubwindow::pre_draw() 35 | { 36 | if (auto selected_param_ptr = selected_param.lock()) { 37 | for (const auto& this_panel : panels) { 38 | this_panel->set_attached_value(selected_param_ptr->value); 39 | } 40 | } 41 | for (const auto& this_panel : panels) { 42 | this_panel->pre_draw(); 43 | } 44 | } 45 | 46 | void cse::ParamEditorSubwindow::set_mouse_position(const Float2 local_position, const float max_pos_y) 47 | { 48 | NodeEditorSubwindow::set_mouse_position(local_position, max_pos_y); 49 | for (const auto& this_panel : panels) { 50 | if (this_panel->is_active()) { 51 | this_panel->set_mouse_local_position(mouse_content_pos - Float2(0.0f, panel_start_y)); 52 | } 53 | } 54 | } 55 | 56 | void cse::ParamEditorSubwindow::handle_mouse_button(const int button, const int action, const int mods) 57 | { 58 | bool mouse_is_over_panel = false; 59 | for (const auto& this_panel : panels) { 60 | if (this_panel->is_active() && this_panel->is_mouse_over()) { 61 | mouse_is_over_panel = true; 62 | } 63 | } 64 | 65 | if (is_mouse_over_header()) { 66 | if (button == GLFW_MOUSE_BUTTON_LEFT) { 67 | if (action == GLFW_PRESS) { 68 | move_window_begin(); 69 | } 70 | else if (action == GLFW_RELEASE) { 71 | move_window_end(); 72 | } 73 | } 74 | } 75 | else if (mouse_is_over_panel) { 76 | for (const auto& this_panel : panels) { 77 | if (this_panel->is_active() && this_panel->is_mouse_over()) { 78 | this_panel->handle_mouse_button(button, action, mods); 79 | } 80 | } 81 | } 82 | else { 83 | if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) { 84 | deselect_input_box(); 85 | } 86 | } 87 | 88 | if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_RELEASE) { 89 | for (const auto& this_panel : panels) { 90 | if (this_panel->is_active()) { 91 | this_panel->handle_mouse_button(button, action, mods); 92 | } 93 | } 94 | } 95 | } 96 | 97 | void cse::ParamEditorSubwindow::handle_key(const int key, const int scancode, const int action, const int mods) 98 | { 99 | for (const auto& this_panel : panels) { 100 | if (this_panel->has_input_focus()) { 101 | this_panel->handle_key(key, scancode, action, mods); 102 | } 103 | } 104 | } 105 | 106 | void cse::ParamEditorSubwindow::handle_character(const unsigned int codepoint) 107 | { 108 | for (const auto& this_panel : panels) { 109 | if (this_panel->has_input_focus()) { 110 | this_panel->handle_character(codepoint); 111 | } 112 | } 113 | } 114 | 115 | bool cse::ParamEditorSubwindow::has_input_focus() const 116 | { 117 | for (const auto& this_panel : panels) { 118 | if (this_panel->has_input_focus()) { 119 | return true; 120 | } 121 | } 122 | return false; 123 | } 124 | 125 | void cse::ParamEditorSubwindow::deselect_input_box() 126 | { 127 | for (const auto& this_panel : panels) { 128 | this_panel->deselect_input_box(); 129 | } 130 | } 131 | 132 | bool cse::ParamEditorSubwindow::is_active() const 133 | { 134 | return (selected_param.use_count() > 0); 135 | } 136 | 137 | bool cse::ParamEditorSubwindow::needs_undo_push() 138 | { 139 | bool result = request_undo_stack_push; 140 | request_undo_stack_push = false; 141 | 142 | for (const auto& this_panel : panels) { 143 | result = this_panel->should_push_undo_state() || result; 144 | } 145 | return result; 146 | } 147 | 148 | void cse::ParamEditorSubwindow::update_selection(const std::weak_ptr selection) 149 | { 150 | const auto selection_ptr = selection.lock(); 151 | if (selection_ptr) { 152 | if (selected_param.lock() != selection_ptr->socket.lock()) { 153 | deselect_input_box(); 154 | selected_param = selection_ptr->socket; 155 | content_height = 0.0f; 156 | } 157 | } 158 | } 159 | 160 | void cse::ParamEditorSubwindow::tab() 161 | { 162 | for (const auto& this_panel : panels) { 163 | if (this_panel->is_active()) { 164 | this_panel->tab(); 165 | } 166 | } 167 | } 168 | 169 | void cse::ParamEditorSubwindow::draw_content(NVGcontext* const draw_context) 170 | { 171 | float height_drawn = 0.0f; 172 | 173 | auto selected_param_ptr = selected_param.lock(); 174 | if (selected_param_ptr && selected_param_ptr->io_type == SocketIOType::INPUT) { 175 | // Draw generic part 176 | nvgFontSize(draw_context, UI_FONT_SIZE_NORMAL); 177 | nvgFontFace(draw_context, "sans"); 178 | nvgTextAlign(draw_context, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); 179 | nvgFontBlur(draw_context, 0.0f); 180 | nvgFillColor(draw_context, nvgRGBA(0, 0, 0, 255)); 181 | 182 | std::string parameter_name_text = "Parameter: " + selected_param_ptr->display_name; 183 | nvgText(draw_context, subwindow_width / 2, height_drawn + UI_SUBWIN_PARAM_EDIT_LAYOUT_ROW_HEIGHT / 2, parameter_name_text.c_str(), nullptr); 184 | height_drawn += UI_SUBWIN_PARAM_EDIT_LAYOUT_ROW_HEIGHT; 185 | 186 | std::string parameter_type_text; 187 | switch (selected_param_ptr->socket_type) { 188 | case SocketType::FLOAT: 189 | parameter_type_text = "Type: Float"; 190 | break; 191 | case SocketType::INT: 192 | parameter_type_text = "Type: Integer"; 193 | break; 194 | case SocketType::COLOR: 195 | parameter_type_text = "Type: Color"; 196 | break; 197 | case SocketType::VECTOR: 198 | parameter_type_text = "Type: Vector"; 199 | break; 200 | case SocketType::NORMAL: 201 | parameter_type_text = "Type: Normal"; 202 | break; 203 | case SocketType::CLOSURE: 204 | parameter_type_text = "Type: Closure"; 205 | break; 206 | case SocketType::STRING: 207 | parameter_type_text = "Type: String"; 208 | break; 209 | case SocketType::STRING_ENUM: 210 | parameter_type_text = "Type: Enumeration"; 211 | break; 212 | case SocketType::BOOLEAN: 213 | parameter_type_text = "Type: Boolean"; 214 | break; 215 | case SocketType::CURVE: 216 | parameter_type_text = "Type: Curve"; 217 | break; 218 | case SocketType::COLOR_RAMP: 219 | parameter_type_text = "Type: Color Ramp"; 220 | break; 221 | default: 222 | parameter_type_text = "Type: Error"; 223 | break; 224 | } 225 | nvgText(draw_context, subwindow_width / 2, height_drawn + UI_SUBWIN_PARAM_EDIT_LAYOUT_ROW_HEIGHT / 2, parameter_type_text.c_str(), nullptr); 226 | height_drawn += UI_SUBWIN_PARAM_EDIT_LAYOUT_ROW_HEIGHT; 227 | 228 | 229 | // Draw separator 230 | height_drawn += UI_SUBWIN_PARAM_EDIT_SEPARATOR_VPAD; 231 | nvgBeginPath(draw_context); 232 | nvgMoveTo(draw_context, UI_SUBWIN_PARAM_EDIT_SEPARATOR_HPAD, height_drawn); 233 | nvgLineTo(draw_context, subwindow_width - UI_SUBWIN_PARAM_EDIT_SEPARATOR_HPAD, height_drawn); 234 | nvgStrokeColor(draw_context, nvgRGBf(0.0f, 0.0f, 0.0f)); 235 | nvgStrokeWidth(draw_context, 1.0f); 236 | nvgStroke(draw_context); 237 | height_drawn += UI_SUBWIN_PARAM_EDIT_SEPARATOR_VPAD; 238 | 239 | panel_start_y = height_drawn; 240 | 241 | bool ui_drawn = false; 242 | for (const auto& this_panel : panels) { 243 | if (this_panel->is_active()) { 244 | nvgSave(draw_context); 245 | nvgTranslate(draw_context, 0.0f, panel_start_y); 246 | nvgScissor(draw_context, 0.0f, 0.0f, this_panel->get_width(), this_panel->get_height()); 247 | height_drawn += this_panel->draw(draw_context); 248 | nvgRestore(draw_context); 249 | ui_drawn = true; 250 | break; 251 | } 252 | } 253 | if (ui_drawn == false) { 254 | nvgFontSize(draw_context, UI_FONT_SIZE_NORMAL); 255 | nvgFontFace(draw_context, "sans"); 256 | nvgTextAlign(draw_context, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); 257 | nvgFontBlur(draw_context, 0.0f); 258 | nvgFillColor(draw_context, nvgRGBA(0, 0, 0, 255)); 259 | nvgText(draw_context, subwindow_width / 2, height_drawn + UI_SUBWIN_PARAM_EDIT_LAYOUT_ROW_HEIGHT / 2, "Uneditable type selected", nullptr); 260 | height_drawn += UI_SUBWIN_PARAM_EDIT_LAYOUT_ROW_HEIGHT; 261 | } 262 | } 263 | 264 | content_height = height_drawn + 4.0f; 265 | } 266 | -------------------------------------------------------------------------------- /src/subwindow_param_editor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "subwindow.h" 7 | 8 | struct NVGcontext; 9 | 10 | namespace cse { 11 | 12 | class NodeSocket; 13 | class EditParamPanel; 14 | class Float2; 15 | class Selection; 16 | 17 | class ParamEditorSubwindow : public NodeEditorSubwindow { 18 | public: 19 | ParamEditorSubwindow(Float2 screen_position); 20 | 21 | virtual void pre_draw() override; 22 | 23 | virtual void set_mouse_position(Float2 local_position, float max_pos_y) override; 24 | 25 | virtual void handle_mouse_button(int button, int action, int mods) override; 26 | virtual void handle_key(int key, int scancode, int action, int mods) override; 27 | virtual void handle_character(unsigned int codepoint) override; 28 | 29 | virtual bool has_input_focus() const override; 30 | virtual void deselect_input_box() override; 31 | 32 | virtual bool is_active() const override; 33 | 34 | virtual bool needs_undo_push() override; 35 | virtual void update_selection(std::weak_ptr selection) override; 36 | 37 | virtual void tab() override; 38 | 39 | protected: 40 | virtual void draw_content(NVGcontext* draw_context) override; 41 | 42 | std::weak_ptr selected_param; 43 | 44 | float panel_start_y = 0; 45 | 46 | // Panels 47 | std::vector> panels; 48 | 49 | // Undo state stuff 50 | bool request_undo_stack_push = false; 51 | }; 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/toolbar.cpp: -------------------------------------------------------------------------------- 1 | #include "toolbar.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "drawing.h" 7 | #include "gui_sizes.h" 8 | #include "ui_requests.h" 9 | 10 | cse::ToolbarButton::ToolbarButton(const ToolbarButtonType type) 11 | { 12 | this->type = type; 13 | } 14 | 15 | std::string cse::ToolbarButton::get_label() const 16 | { 17 | switch (type) { 18 | case ToolbarButtonType::SAVE: 19 | return "Save"; 20 | case ToolbarButtonType::UNDO: 21 | return "Undo"; 22 | case ToolbarButtonType::REDO: 23 | return "Redo"; 24 | case ToolbarButtonType::ZOOM_IN: 25 | return "Zoom In"; 26 | case ToolbarButtonType::ZOOM_OUT: 27 | return "Zoom Out"; 28 | default: 29 | return "Error"; 30 | } 31 | } 32 | 33 | void cse::ToolbarButton::set_geometry(const Float2 pos_in, const float width_in, const float height_in) 34 | { 35 | pos = pos_in; 36 | width = width_in; 37 | height = height_in; 38 | } 39 | 40 | bool cse::ToolbarButton::contains_point(const Float2 point) const 41 | { 42 | const float min_x = pos.x; 43 | const float max_x = pos.x + width; 44 | const float min_y = pos.y; 45 | const float max_y = pos.y + height; 46 | return ( 47 | point.x >= min_x && 48 | point.x <= max_x && 49 | point.y >= min_y && 50 | point.y <= max_y 51 | ); 52 | } 53 | 54 | cse::NodeEditorToolbar::NodeEditorToolbar() 55 | { 56 | buttons.push_back(ToolbarButton(ToolbarButtonType::SAVE)); 57 | buttons.push_back(ToolbarButton(ToolbarButtonType::SPACER)); 58 | buttons.push_back(ToolbarButton(ToolbarButtonType::UNDO)); 59 | buttons.push_back(ToolbarButton(ToolbarButtonType::REDO)); 60 | buttons.push_back(ToolbarButton(ToolbarButtonType::SPACER)); 61 | buttons.push_back(ToolbarButton(ToolbarButtonType::ZOOM_IN)); 62 | buttons.push_back(ToolbarButton(ToolbarButtonType::ZOOM_OUT)); 63 | } 64 | 65 | void cse::NodeEditorToolbar::draw(NVGcontext* const draw_context, const float toolbar_width) 66 | { 67 | nvgSave(draw_context); 68 | 69 | // Draw panel 70 | nvgBeginPath(draw_context); 71 | nvgRoundedRect(draw_context, 0.0f, 0.0f, toolbar_width, get_toolbar_height(), 0.0f); 72 | nvgFillColor(draw_context, nvgRGBA(180, 180, 180, 255)); 73 | nvgFill(draw_context); 74 | 75 | // Draw buttons 76 | float section_begin_x = 0; 77 | for (ToolbarButton& this_button: buttons) { 78 | if (this_button.type == ToolbarButtonType::SPACER) { 79 | 80 | const float x_pos = section_begin_x + UI_TOOLBAR_SPACER_WIDTH / 2.0f; 81 | 82 | nvgBeginPath(draw_context); 83 | nvgStrokeWidth(draw_context, 0.75f); 84 | nvgStrokeColor(draw_context, nvgRGBf(0.1f, 0.1f, 0.1f)); 85 | 86 | nvgMoveTo(draw_context, x_pos, UI_TOOLBAR_SPACER_VPAD); 87 | nvgLineTo(draw_context, x_pos, get_toolbar_height() - UI_TOOLBAR_SPACER_VPAD); 88 | 89 | nvgStroke(draw_context); 90 | 91 | section_begin_x += UI_TOOLBAR_SPACER_WIDTH; 92 | continue; 93 | } 94 | 95 | const Float2 button_pos(section_begin_x + UI_TOOLBAR_BUTTON_HPAD, UI_TOOLBAR_BUTTON_VPAD); 96 | const float button_height = get_toolbar_height() - 2 * UI_TOOLBAR_BUTTON_VPAD; 97 | Drawing::draw_button(draw_context, button_pos, UI_TOOLBAR_BUTTON_WIDTH, button_height, this_button.get_label(), this_button.enabled, this_button.pressed); 98 | this_button.set_geometry(button_pos, UI_TOOLBAR_BUTTON_WIDTH, button_height); 99 | 100 | section_begin_x += UI_TOOLBAR_BUTTON_WIDTH + UI_TOOLBAR_BUTTON_HPAD * 2; 101 | } 102 | 103 | nvgRestore(draw_context); 104 | } 105 | 106 | void cse::NodeEditorToolbar::set_button_enabled(const ToolbarButtonType type, const bool enabled) 107 | { 108 | for (ToolbarButton& this_button : buttons) { 109 | if (this_button.type == type) { 110 | this_button.enabled = enabled; 111 | return; 112 | } 113 | } 114 | } 115 | 116 | bool cse::NodeEditorToolbar::is_mouse_over() 117 | { 118 | return ( 119 | mouse_screen_pos.y > 0.0f && 120 | mouse_screen_pos.y < get_toolbar_height() 121 | ); 122 | } 123 | 124 | void cse::NodeEditorToolbar::set_mouse_position(Float2 screen_position) 125 | { 126 | mouse_screen_pos = screen_position; 127 | } 128 | 129 | void cse::NodeEditorToolbar::handle_mouse_button(int button, int action, int /*mods*/) 130 | { 131 | if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) { 132 | press_button_under_mouse(); 133 | } 134 | else if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_RELEASE) { 135 | release_button_under_mouse(); 136 | for (ToolbarButton& this_button : buttons) { 137 | this_button.pressed = false; 138 | } 139 | } 140 | } 141 | 142 | cse::UIRequests cse::NodeEditorToolbar::consume_ui_requests() 143 | { 144 | const UIRequests result = requests; 145 | requests.clear(); 146 | return result; 147 | } 148 | 149 | void cse::NodeEditorToolbar::press_button_under_mouse() 150 | { 151 | for (ToolbarButton& button : buttons) { 152 | if (button.contains_point(mouse_screen_pos)) { 153 | button.pressed = true; 154 | } 155 | } 156 | } 157 | 158 | void cse::NodeEditorToolbar::release_button_under_mouse() 159 | { 160 | for (ToolbarButton& button : buttons) { 161 | if (button.contains_point(mouse_screen_pos)) { 162 | if (button.pressed) { 163 | set_request(button.type); 164 | } 165 | button.pressed = false; 166 | } 167 | } 168 | } 169 | 170 | void cse::NodeEditorToolbar::set_request(ToolbarButtonType button_type) 171 | { 172 | switch (button_type) { 173 | case ToolbarButtonType::SAVE: 174 | requests.save = true; 175 | break; 176 | case ToolbarButtonType::UNDO: 177 | requests.undo = true; 178 | break; 179 | case ToolbarButtonType::REDO: 180 | requests.redo = true; 181 | break; 182 | case ToolbarButtonType::ZOOM_IN: 183 | requests.view.zoom_in = true; 184 | break; 185 | case ToolbarButtonType::ZOOM_OUT: 186 | requests.view.zoom_out = true; 187 | break; 188 | default: 189 | break; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/toolbar.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "gui_sizes.h" 7 | #include "ui_requests.h" 8 | #include "util_vector.h" 9 | 10 | struct NVGcontext; 11 | 12 | namespace cse { 13 | 14 | enum class ToolbarButtonType { 15 | SAVE, 16 | UNDO, 17 | REDO, 18 | ZOOM_IN, 19 | ZOOM_OUT, 20 | SPACER, 21 | }; 22 | 23 | class ToolbarButton { 24 | public: 25 | ToolbarButton(ToolbarButtonType type); 26 | 27 | std::string get_label() const; 28 | 29 | // Called from NodeEditorToolbar::draw to store the geometry of this button every draw 30 | void set_geometry(Float2 pos, float width, float height); 31 | bool contains_point(Float2 point) const; 32 | 33 | ToolbarButtonType type = ToolbarButtonType::SPACER; 34 | bool pressed = false; 35 | bool enabled = true; 36 | 37 | private: 38 | Float2 pos; 39 | float width; 40 | float height; 41 | }; 42 | 43 | class NodeEditorToolbar { 44 | public: 45 | static float get_toolbar_height() { return UI_TOOLBAR_HEIGHT; } 46 | 47 | NodeEditorToolbar(); 48 | 49 | void draw(NVGcontext* draw_context, float toolbar_width); 50 | 51 | void set_button_enabled(ToolbarButtonType type, bool enabled); 52 | 53 | bool is_mouse_over(); 54 | void set_mouse_position(Float2 screen_position); 55 | void handle_mouse_button(int button, int action, int mods); 56 | 57 | UIRequests consume_ui_requests(); 58 | 59 | private: 60 | void press_button_under_mouse(); 61 | void release_button_under_mouse(); 62 | void set_request(ToolbarButtonType button_type); 63 | 64 | Float2 mouse_screen_pos; 65 | 66 | std::list buttons; 67 | 68 | UIRequests requests; 69 | }; 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/ui_requests.cpp: -------------------------------------------------------------------------------- 1 | #include "ui_requests.h" 2 | 3 | cse::ViewUIRequests::ViewUIRequests() 4 | { 5 | clear(); 6 | } 7 | 8 | void cse::ViewUIRequests::clear() 9 | { 10 | pan_left = false; 11 | pan_right = false; 12 | pan_up = false; 13 | pan_down = false; 14 | 15 | zoom_in = false; 16 | zoom_out = false; 17 | } 18 | 19 | void cse::ViewUIRequests::operator|=(const ViewUIRequests& other) 20 | { 21 | pan_left = pan_left || other.pan_left; 22 | pan_right = pan_right || other.pan_right; 23 | pan_up = pan_up || other.pan_up; 24 | pan_down = pan_down || other.pan_down; 25 | 26 | zoom_in = zoom_in || other.zoom_in; 27 | zoom_out = zoom_out || other.zoom_out; 28 | } 29 | 30 | cse::UIRequests::UIRequests() 31 | { 32 | clear(); 33 | } 34 | 35 | void cse::UIRequests::clear() 36 | { 37 | save = false; 38 | undo = false; 39 | redo = false; 40 | 41 | view.clear(); 42 | } 43 | 44 | void cse::UIRequests::operator|=(const UIRequests& other) 45 | { 46 | save = save || other.save; 47 | undo = undo || other.undo; 48 | redo = redo || other.redo; 49 | 50 | view |= other.view; 51 | } 52 | -------------------------------------------------------------------------------- /src/ui_requests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cse { 4 | 5 | // Every frame, requests from several UI components will be combined with operator|= 6 | // For each flag that is true, the main window will take some action 7 | class ViewUIRequests { 8 | public: 9 | ViewUIRequests(); 10 | 11 | void clear(); 12 | 13 | void operator|=(const ViewUIRequests& other); 14 | 15 | bool pan_left = false; 16 | bool pan_right = false; 17 | bool pan_up = false; 18 | bool pan_down = false; 19 | 20 | bool zoom_in = false; 21 | bool zoom_out = false; 22 | }; 23 | 24 | class UIRequests { 25 | public: 26 | UIRequests(); 27 | 28 | void clear(); 29 | 30 | void operator|=(const UIRequests& other); 31 | 32 | bool save = false; 33 | bool undo = false; 34 | bool redo = false; 35 | 36 | ViewUIRequests view; 37 | }; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/undo.cpp: -------------------------------------------------------------------------------- 1 | #include "undo.h" 2 | 3 | static const int UNDO_LIMIT = 50; 4 | 5 | void cse::UndoStack::push_undo_state(std::string state) 6 | { 7 | redo_state.clear(); 8 | undo_state.push_front(state); 9 | while (undo_state.size() > UNDO_LIMIT) { 10 | undo_state.pop_back(); 11 | } 12 | } 13 | 14 | std::string cse::UndoStack::pop_undo_state(std::string current_state) 15 | { 16 | if (undo_state.size() == 0) { 17 | return current_state; 18 | } 19 | redo_state.push_front(current_state); 20 | std::string result_state = undo_state.front(); 21 | undo_state.pop_front(); 22 | return result_state; 23 | } 24 | 25 | std::string cse::UndoStack::pop_redo_state(std::string current_state) 26 | { 27 | if (redo_state.size() == 0) { 28 | return current_state; 29 | } 30 | undo_state.push_front(current_state); 31 | std::string result_state = redo_state.front(); 32 | redo_state.pop_front(); 33 | return result_state; 34 | } 35 | 36 | bool cse::UndoStack::undo_available() 37 | { 38 | return (undo_state.size() > 0); 39 | } 40 | 41 | bool cse::UndoStack::redo_available() 42 | { 43 | return (redo_state.size() > 0); 44 | } 45 | 46 | void cse::UndoStack::clear() 47 | { 48 | undo_state.clear(); 49 | redo_state.clear(); 50 | } 51 | -------------------------------------------------------------------------------- /src/undo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace cse { 7 | 8 | class UndoStack { 9 | public: 10 | void push_undo_state(std::string state); 11 | 12 | std::string pop_undo_state(std::string current_state); 13 | std::string pop_redo_state(std::string current_state); 14 | 15 | bool undo_available(); 16 | bool redo_available(); 17 | 18 | void clear(); 19 | 20 | private: 21 | std::list undo_state; 22 | std::list redo_state; 23 | }; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/util_area.cpp: -------------------------------------------------------------------------------- 1 | #include "util_area.h" 2 | 3 | #include 4 | 5 | cse::Area::Area(const Float2 a, const Float2 b) 6 | { 7 | const float min_x = std::min(a.x, b.x); 8 | const float min_y = std::min(a.y, b.y); 9 | const float max_x = std::max(a.x, b.x); 10 | const float max_y = std::max(a.y, b.y); 11 | 12 | lo = Float2(min_x, min_y); 13 | hi = Float2(max_x, max_y); 14 | } 15 | 16 | bool cse::Area::contains_point(const Float2 pos) const 17 | { 18 | return (pos.x > lo.x && 19 | pos.x < hi.x && 20 | pos.y > lo.y && 21 | pos.y < hi.y); 22 | } 23 | 24 | bool cse::Area::overlaps(const Area other) const 25 | { 26 | // Return false if there is no horizontal overlap 27 | if (lo.x > other.hi.x || other.lo.x > hi.x) { 28 | return false; 29 | } 30 | 31 | // Return false if there is no vertical overlap 32 | if (lo.y > other.hi.y || other.lo.y > hi.y) { 33 | return false; 34 | } 35 | 36 | return true; 37 | } 38 | 39 | cse::Float2 cse::Area::get_normalized_pos(const Float2 pos) const 40 | { 41 | const Float2 size = hi - lo; 42 | const Float2 offset = pos - lo; 43 | 44 | const float pos_x = offset.x / size.x; 45 | const float pos_y = offset.y / size.y; 46 | 47 | Float2 result(pos_x, pos_y); 48 | return result.clamp_to(Float2(0.0f, 0.0f), Float2(1.0f, 1.0f)); 49 | } 50 | -------------------------------------------------------------------------------- /src/util_area.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "util_vector.h" 4 | 5 | namespace cse { 6 | 7 | class Area { 8 | public: 9 | Area(Float2 a, Float2 b); 10 | 11 | bool contains_point(Float2 pos) const; 12 | bool overlaps(Area other) const; 13 | 14 | Float2 get_normalized_pos(Float2 pos) const; 15 | 16 | protected: 17 | Float2 lo; 18 | Float2 hi; 19 | }; 20 | 21 | template 22 | class HolderArea : public Area { 23 | public: 24 | HolderArea(const Float2 begin_pos, const Float2 end_pos, T hold_value) : 25 | Area(begin_pos, end_pos), 26 | value(hold_value) 27 | {} 28 | 29 | T get_value() const { return value; } 30 | 31 | protected: 32 | T value; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/util_color_ramp.cpp: -------------------------------------------------------------------------------- 1 | #include "util_color_ramp.h" 2 | 3 | cse::ColorRampPoint::ColorRampPoint(const float position, const Float3 color, const float alpha) : 4 | position(position), 5 | color(color), 6 | alpha(alpha) 7 | { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/util_color_ramp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "util_vector.h" 4 | 5 | namespace cse { 6 | struct ColorRampPoint { 7 | ColorRampPoint(float position, Float3 color, float alpha); 8 | 9 | float position; 10 | Float3 color; 11 | float alpha; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/util_enum.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cse { 4 | 5 | enum class NodeCategory { 6 | OUTPUT, 7 | COLOR, 8 | CONVERTER, 9 | INPUT, 10 | SHADER, 11 | TEXTURE, 12 | VECTOR, 13 | }; 14 | 15 | enum class ShaderGraphType { 16 | EMPTY, 17 | MATERIAL, 18 | }; 19 | 20 | enum class EditCurveMode { 21 | MOVE, 22 | CREATE, 23 | DELETE, 24 | }; 25 | 26 | enum class CurveInterpolation { 27 | LINEAR, 28 | CUBIC_HERMITE, 29 | }; 30 | 31 | enum class SocketIOType { 32 | INPUT, 33 | OUTPUT, 34 | }; 35 | 36 | enum class SocketType { 37 | FLOAT, 38 | INT, 39 | COLOR, 40 | VECTOR, 41 | NORMAL, 42 | CLOSURE, 43 | STRING, 44 | STRING_ENUM, 45 | BOOLEAN, 46 | CURVE, 47 | COLOR_RAMP, 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /src/util_hermite_spline.cpp: -------------------------------------------------------------------------------- 1 | #include "util_hermite_spline.h" 2 | 3 | // Cubic hermit spline interpolation 4 | // Code taken from https://blog.demofox.org/2015/08/08/cubic-hermite-interpolation/ 5 | 6 | cse::CubicHermiteSplineInterpolator::CubicHermiteSplineInterpolator(float A, float B, float C, float D) : 7 | a( -A / 2.0f + (3.0f*B) / 2.0f - (3.0f*C) / 2.0f + D / 2.0f ), 8 | b( A - (5.0f*B) / 2.0f + 2.0f*C - D / 2.0f ), 9 | c( -A / 2.0f + C / 2.0f ), 10 | d( B ) 11 | { 12 | 13 | } 14 | 15 | float cse::CubicHermiteSplineInterpolator::eval(float t) const 16 | { 17 | return a*t*t*t + b*t*t + c*t + d; 18 | } 19 | -------------------------------------------------------------------------------- /src/util_hermite_spline.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cse { 4 | class CubicHermiteSplineInterpolator { 5 | public: 6 | CubicHermiteSplineInterpolator(float A, float B, float C, float D); 7 | 8 | float eval(float t) const; 9 | 10 | private: 11 | const float a; 12 | const float b; 13 | const float c; 14 | const float d; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/util_parse.cpp: -------------------------------------------------------------------------------- 1 | #include "util_parse.h" 2 | 3 | #include 4 | 5 | float cse::locale_safe_stof(std::string input) 6 | { 7 | std::stringstream stream(input); 8 | float result = 0.0f; 9 | stream >> result; 10 | return result; 11 | } 12 | -------------------------------------------------------------------------------- /src/util_parse.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cse { 6 | 7 | // This will parse a string using the default locale rather than the application locale 8 | // It should be used for loading files 9 | float locale_safe_stof(std::string input); 10 | } 11 | -------------------------------------------------------------------------------- /src/util_platform.cpp: -------------------------------------------------------------------------------- 1 | #include "util_platform.h" 2 | 3 | #ifdef _WIN32 4 | #include 5 | #else 6 | #include 7 | #endif 8 | 9 | #include 10 | #include 11 | 12 | #include "wrapper_nvg_context.h" 13 | 14 | cse::PathString cse::Platform::get_pathstring(const std::string& input) 15 | { 16 | #ifdef _WIN32 17 | return std::wstring(input.begin(), input.end()); 18 | #else 19 | return input; 20 | #endif 21 | } 22 | 23 | cse::PathString cse::Platform::get_font_path(const PathString& search_path, const std::string& filename) 24 | { 25 | #ifdef _WIN32 26 | wchar_t PATH_SEPARATOR = L'\\'; 27 | #else 28 | char PATH_SEPARATOR = '/'; 29 | #endif 30 | return search_path + PATH_SEPARATOR + get_pathstring(filename); 31 | } 32 | 33 | void cse::Platform::nvg_create_font(const PathString& path, const std::string& name, const std::unique_ptr& nvg_context) 34 | { 35 | #ifdef _WIN32 36 | nvgCreateFontW(nvg_context->context_ptr, name.c_str(), path.c_str()); 37 | #else 38 | nvgCreateFont(nvg_context->context_ptr, name.c_str(), path.c_str()); 39 | #endif 40 | } 41 | 42 | int cse::Platform::get_delete_key() 43 | { 44 | #ifdef __APPLE__ 45 | return GLFW_KEY_BACKSPACE; 46 | #else 47 | return GLFW_KEY_DELETE; 48 | #endif 49 | } 50 | 51 | void cse::Platform::thread_usleep(const int us) 52 | { 53 | #ifdef _WIN32 54 | Sleep(static_cast(us / 1000)); 55 | #else 56 | usleep(us); 57 | #endif 58 | } 59 | -------------------------------------------------------------------------------- /src/util_platform.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Platform-specific things go in this header 4 | 5 | #include 6 | #include 7 | 8 | namespace cse { 9 | #ifdef _WIN32 10 | typedef std::wstring PathString; 11 | #else 12 | typedef std::string PathString; 13 | #endif 14 | 15 | class NvgContext; 16 | 17 | namespace Platform { 18 | PathString get_pathstring(const std::string& input); 19 | PathString get_font_path(const PathString& search_path, const std::string& filename); 20 | 21 | void nvg_create_font(const PathString& path, const std::string& name, const std::unique_ptr& nvg_context); 22 | 23 | int get_delete_key(); 24 | 25 | void thread_usleep(int us); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/util_typedef.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // Header to contain typedefs for template abominations 3 | 4 | #include 5 | #include 6 | 7 | namespace cse { 8 | class EditableNode; 9 | 10 | typedef std::set, std::owner_less>> WeakNodeSet; 11 | typedef std::set, std::owner_less>> SharedNodeSet; 12 | } 13 | -------------------------------------------------------------------------------- /src/util_vector.cpp: -------------------------------------------------------------------------------- 1 | #include "util_vector.h" 2 | 3 | #include 4 | 5 | float cse::Float2::magnitude_squared() const 6 | { 7 | return x * x + y * y; 8 | } 9 | 10 | cse::Float2 cse::Float2::clamp_to(const Float2& a, const Float2& b) const 11 | { 12 | const float lo_x_in = std::min(a.x, b.x); 13 | const float lo_y_in = std::min(a.y, b.y); 14 | const float hi_x_in = std::max(a.x, b.x); 15 | const float hi_y_in = std::max(a.y, b.y); 16 | 17 | const float new_x = std::max(lo_x_in, std::min(hi_x_in, x)); 18 | const float new_y = std::max(lo_y_in, std::min(hi_y_in, y)); 19 | return Float2(new_x, new_y); 20 | } 21 | 22 | bool cse::Float2::is_nonzero() const 23 | { 24 | return x != 0.0f || y != 0.0f; 25 | } 26 | 27 | cse::Float2 cse::Float2::operator+(const Float2& other) const 28 | { 29 | return Float2(x + other.x, y + other.y); 30 | } 31 | 32 | cse::Float2 cse::Float2::operator-(const Float2& other) const 33 | { 34 | return Float2(x - other.x, y - other.y); 35 | } 36 | 37 | void cse::Float2::operator+=(const Float2& other) 38 | { 39 | x += other.x; 40 | y += other.y; 41 | } 42 | 43 | bool cse::Float2::operator==(const Float2& other) const 44 | { 45 | return x == other.x && y == other.y; 46 | } 47 | 48 | bool cse::Float2::operator!=(const Float2& other) const 49 | { 50 | return !(operator==(other)); 51 | } 52 | 53 | cse::Float3 cse::Float3::rgb_as_hsv() const 54 | { 55 | // Calculate Val 56 | const float max = std::max(x, std::max(y, z)); 57 | const float val = max; 58 | 59 | // Calculate Sat 60 | const float min = std::min(x, std::min(y, z)); 61 | const float range = max - min; 62 | if (range <= 0.0f || max <= 0.0f) { 63 | const float hue = -1.0f; 64 | const float sat = 0.0f; 65 | return Float3(hue, sat, val); 66 | } 67 | const float sat = range / max; 68 | 69 | // Calculate Hue 70 | auto calculate_hue = [](const Float3 rgb, const float min, const float max, const float range) -> double 71 | { 72 | constexpr double ONE_THIRD = 0.3333333333333333333333; 73 | constexpr double OFFSET_MULTIPLIER = ONE_THIRD / 2.0; 74 | 75 | if (rgb.x == max) { 76 | // Red is the top color, so the hue must be between magenta and yellow 77 | // offset is how far the hue is from straight red 78 | // It has a range of [-1, 1] with -1 meaning magenta and 1 meaning yellow 79 | constexpr double RED_BASE_HUE_LO = 0.0; 80 | constexpr double RED_BASE_HUE_HI = 1.0; 81 | if (rgb.y == min && rgb.z == min) { 82 | return 0.0f; 83 | } 84 | else { 85 | const double offset = (rgb.y - rgb.z) / range; 86 | if (offset < 0.0f) { 87 | return RED_BASE_HUE_HI + offset * OFFSET_MULTIPLIER; 88 | } 89 | else { 90 | return RED_BASE_HUE_LO + offset * OFFSET_MULTIPLIER; 91 | } 92 | } 93 | } 94 | else if (rgb.y == max) { 95 | constexpr double GREEN_BASE_HUE = ONE_THIRD; 96 | const double offset = (rgb.z - rgb.x) / range; 97 | return GREEN_BASE_HUE + offset * OFFSET_MULTIPLIER; 98 | } 99 | else { 100 | constexpr double BLUE_BASE_HUE = ONE_THIRD * 2.0; 101 | const double offset = (rgb.x - rgb.y) / range; 102 | return BLUE_BASE_HUE + offset * OFFSET_MULTIPLIER; 103 | } 104 | }; 105 | 106 | const float hue = static_cast(calculate_hue(*this, min, max, range)); 107 | 108 | return Float3(hue, sat, val); 109 | } 110 | 111 | cse::Float3 cse::Float3::hsv_as_rgb() const 112 | { 113 | 114 | const float hue = x; 115 | const float sat = y; 116 | const float val = z; 117 | 118 | // Special fast case for monochromatic colors 119 | if (sat <= 0.0) { 120 | return Float3(val, val, val); 121 | } 122 | 123 | const float base_hue = std::min(1.0f, std::max(0.0f, hue)); 124 | const float scaled_hue = base_hue * 6.0f; 125 | const int hue_bucket = static_cast(scaled_hue); 126 | const float hue_offset = scaled_hue - hue_bucket; 127 | const float p = val * (1.0f - sat); 128 | const float q = val * (1.0f - (sat * hue_offset)); 129 | const float t = val * (1.0f - (sat * (1.0f - hue_offset))); 130 | 131 | cse::Float3 result; 132 | switch (hue_bucket) { 133 | case 0: 134 | result.x = val; 135 | result.y = t; 136 | result.z = p; 137 | break; 138 | case 1: 139 | result.x = q; 140 | result.y = val; 141 | result.z = p; 142 | break; 143 | case 2: 144 | result.x = p; 145 | result.y = val; 146 | result.z = t; 147 | break; 148 | case 3: 149 | result.x = p; 150 | result.y = q; 151 | result.z = val; 152 | break; 153 | case 4: 154 | result.x = t; 155 | result.y = p; 156 | result.z = val; 157 | break; 158 | case 5: 159 | default: 160 | result.x = val; 161 | result.y = p; 162 | result.z = q; 163 | break; 164 | } 165 | return result; 166 | } 167 | -------------------------------------------------------------------------------- /src/util_vector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cse { 4 | 5 | class Float2 { 6 | public: 7 | Float2() : x(0.0f), y(0.0f) {} 8 | Float2(const float x, const float y) : x(x), y(y) {} 9 | 10 | float magnitude_squared() const; 11 | 12 | Float2 clamp_to(const Float2& a, const Float2& b) const; 13 | 14 | bool is_nonzero() const; 15 | 16 | Float2 operator+(const Float2& other) const; 17 | Float2 operator-(const Float2& other) const; 18 | 19 | void operator+=(const Float2& other); 20 | 21 | bool operator==(const Float2& other) const; 22 | bool operator!=(const Float2& other) const; 23 | 24 | float x; 25 | float y; 26 | }; 27 | 28 | class Float3 { 29 | public: 30 | Float3() : x(0.0f), y(0.0f), z(0.0f) {} 31 | Float3(const float x, const float y, const float z) : x(x), y(y), z(z) {} 32 | 33 | Float3 rgb_as_hsv() const; 34 | Float3 hsv_as_rgb() const; 35 | 36 | float x; 37 | float y; 38 | float z; 39 | }; 40 | 41 | class Float4 { 42 | public: 43 | Float4(const float x, const float y, const float z, const float w) : x(x), y(y), z(z), w(w) {} 44 | 45 | float x; 46 | float y; 47 | float z; 48 | float w; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /src/view.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "selection.h" 7 | #include "util_typedef.h" 8 | #include "util_vector.h" 9 | #include "zoom.h" 10 | 11 | struct NVGcontext; 12 | 13 | namespace cse { 14 | 15 | class EditableGraph; 16 | class NodeCreationHelper; 17 | class NodeSocket; 18 | class ViewUIRequests; 19 | 20 | class EditGraphView { 21 | private: 22 | class ViewBorders { 23 | public: 24 | ViewBorders(Float2 view_center, int viewport_width, int viewport_height, float zoom_scale); 25 | 26 | const float left; 27 | const float right; 28 | const float top; 29 | const float bottom; 30 | }; 31 | 32 | public: 33 | EditGraphView( 34 | std::shared_ptr graph, 35 | std::weak_ptr node_creation_helper 36 | ); 37 | 38 | // Main 3 functions called from the main loop 39 | void set_mouse_position(Float2 view_local_mouse_pos, int viewport_width, int viewport_height); 40 | void pre_draw(); 41 | void draw(NVGcontext* draw_context); 42 | 43 | // Input 44 | void handle_mouse_button(int button, int action, int mods); 45 | void handle_key(int key, int scancode, int action, int mods); 46 | void handle_requests(ViewUIRequests& requests); 47 | 48 | // Undo 49 | bool needs_undo_push(); 50 | 51 | // Selection 52 | std::weak_ptr get_const_selection() const; 53 | void clear_selection(); 54 | 55 | // Misc 56 | std::string get_zoom_string() const; 57 | 58 | private: 59 | void begin_connection(std::weak_ptr socket_begin); 60 | void complete_connection(std::weak_ptr socket_end); 61 | void reroute_connection(std::weak_ptr socket_end); 62 | void cancel_connection(); 63 | 64 | void select_label(std::weak_ptr label); 65 | 66 | WeakNodeSet get_boxed_nodes(); 67 | 68 | void node_move_begin(); 69 | void node_move_end(); 70 | 71 | void pan(int horizontal_ticks, int vertical_ticks); 72 | void snap_view_center(); 73 | 74 | void box_select_begin(); 75 | void box_select_end(SelectMode mode); 76 | 77 | void select_label_under_mouse(); 78 | void deselect_label(); 79 | 80 | const std::shared_ptr graph; 81 | const std::weak_ptr node_creation_helper; 82 | 83 | std::weak_ptr connection_in_progress_start; 84 | 85 | bool box_select_active = false; 86 | Float2 world_box_select_begin; 87 | Float2 world_box_select_end; 88 | 89 | std::shared_ptr selection; 90 | ZoomManager zoom_level; 91 | 92 | Float2 view_center; 93 | 94 | Float2 mouse_world_position; 95 | bool mouse_pan_active = false; 96 | 97 | bool node_move_active = false; 98 | bool node_move_did_something = false; 99 | 100 | bool should_push_undo_state = false; 101 | 102 | // Width and height of this view widget, in pixels on screen 103 | int widget_width; 104 | int widget_height; 105 | }; 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/widget.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | 3 | cse::Widget::Widget(const float width) : width(width) 4 | { 5 | 6 | } 7 | 8 | cse::Widget::~Widget() 9 | { 10 | 11 | } 12 | 13 | float cse::Widget::draw(NVGcontext*) 14 | { 15 | return 0.0f; 16 | } 17 | 18 | void cse::Widget::set_mouse_local_pos(const Float2 local_pos) 19 | { 20 | mouse_pos = local_pos; 21 | } 22 | 23 | bool cse::Widget::has_input_focus() const 24 | { 25 | return false; 26 | } 27 | 28 | void cse::Widget::handle_mouse_button(int, int, int) 29 | { 30 | 31 | } 32 | 33 | void cse::Widget::handle_key(int, int, int, int) 34 | { 35 | 36 | } 37 | 38 | void cse::Widget::handle_character(unsigned int) 39 | { 40 | 41 | } 42 | 43 | bool cse::Widget::should_push_undo_state() 44 | { 45 | const bool result = request_undo_push; 46 | request_undo_push = false; 47 | return result; 48 | } 49 | -------------------------------------------------------------------------------- /src/widget.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "util_vector.h" 4 | 5 | struct NVGcontext; 6 | 7 | namespace cse { 8 | 9 | class Widget { 10 | public: 11 | Widget(float width); 12 | virtual ~Widget(); 13 | 14 | virtual float draw(NVGcontext* draw_context); 15 | 16 | void set_mouse_local_pos(Float2 local_pos); 17 | 18 | virtual bool has_input_focus() const; 19 | virtual void handle_mouse_button(int button, int action, int mods); 20 | virtual void handle_key(int key, int scancode, int action, int mods); 21 | virtual void handle_character(unsigned int codepoint); 22 | 23 | virtual bool should_push_undo_state(); 24 | 25 | protected: 26 | const float width; 27 | 28 | Float2 mouse_pos; 29 | 30 | bool request_undo_push = false; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/widget_multi_input.cpp: -------------------------------------------------------------------------------- 1 | #include "widget_multi_input.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "gui_sizes.h" 7 | #include "input_box.h" 8 | #include "sockets.h" 9 | 10 | static std::shared_ptr get_input_box_for_value(const std::shared_ptr& socket_value) 11 | { 12 | if (const auto as_int = std::dynamic_pointer_cast(socket_value)) { 13 | const auto result = std::make_shared(UI_SUBWIN_PARAM_EDIT_TEXT_INPUT_WIDTH, UI_SUBWIN_PARAM_EDIT_TEXT_INPUT_HEIGHT); 14 | result->attach_int_value(as_int); 15 | return result; 16 | } 17 | else if (const auto as_float = std::dynamic_pointer_cast(socket_value)) { 18 | const auto result = std::make_shared(UI_SUBWIN_PARAM_EDIT_TEXT_INPUT_WIDTH, UI_SUBWIN_PARAM_EDIT_TEXT_INPUT_HEIGHT); 19 | result->attach_float_value(as_float); 20 | return result; 21 | } 22 | else { 23 | // Unsupported type, return empty ptr 24 | return std::shared_ptr(); 25 | } 26 | } 27 | 28 | cse::MultiInputWidget::MultiInputWidget(const float width) : Widget(width) 29 | { 30 | 31 | } 32 | 33 | bool cse::MultiInputWidget::tab() 34 | { 35 | const bool something_selected{ has_input_focus() }; 36 | if (something_selected) { 37 | // Advance to the next box 38 | bool select_next{ false }; 39 | for (const InputRow& socket : sockets) { 40 | if (select_next) { 41 | socket.input_box->begin_edit(); 42 | return has_input_focus(); 43 | } 44 | if (socket.input_box->has_input_focus()) { 45 | socket.input_box->complete_edit(); 46 | select_next = true; 47 | } 48 | } 49 | // If we reach this line, this means the selected box was last 50 | // In that case, keep nothing selected 51 | } 52 | else { 53 | // Select the first box 54 | for (const InputRow& socket : sockets) { 55 | socket.input_box->begin_edit(); 56 | break; 57 | } 58 | } 59 | return has_input_focus(); 60 | } 61 | 62 | bool cse::MultiInputWidget::add_socket_input(const std::string label, const std::weak_ptr socket_value) 63 | { 64 | if (const auto locked = socket_value.lock()) { 65 | const std::shared_ptr input_box = get_input_box_for_value(locked); 66 | if (input_box) { 67 | sockets.push_back(InputRow(label, socket_value, input_box)); 68 | return true; 69 | } 70 | } 71 | return false; 72 | } 73 | 74 | void cse::MultiInputWidget::clear_sockets() 75 | { 76 | sockets.clear(); 77 | } 78 | 79 | bool cse::MultiInputWidget::complete_input() 80 | { 81 | bool value_has_changed = false; 82 | for (const auto& this_socket : sockets) { 83 | value_has_changed = this_socket.input_box->complete_edit() || value_has_changed; 84 | } 85 | return value_has_changed; 86 | } 87 | 88 | float cse::MultiInputWidget::draw(NVGcontext* const draw_context) 89 | { 90 | float height_drawn = 0.0f; 91 | 92 | if (sockets.size() == 0) { 93 | // No sockets exist, show error 94 | nvgFontSize(draw_context, UI_FONT_SIZE_NORMAL); 95 | nvgFontFace(draw_context, "sans"); 96 | nvgTextAlign(draw_context, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); 97 | nvgFontBlur(draw_context, 0.0f); 98 | nvgFillColor(draw_context, nvgRGBA(0, 0, 0, 255)); 99 | 100 | const std::string value_text = "Error: No sockets to display"; 101 | nvgText(draw_context, width / 2, height_drawn + UI_SUBWIN_PARAM_EDIT_LAYOUT_ROW_HEIGHT / 2, value_text.c_str(), nullptr); 102 | 103 | height_drawn += UI_SUBWIN_PARAM_EDIT_LAYOUT_ROW_HEIGHT; 104 | } 105 | else { 106 | // Iterate through and draw all sockets 107 | for (auto this_socket : sockets) { 108 | if (this_socket.socket_value.expired()) { 109 | continue; 110 | } 111 | 112 | const std::shared_ptr this_val = this_socket.socket_value.lock(); 113 | 114 | nvgFontSize(draw_context, UI_FONT_SIZE_NORMAL); 115 | nvgFontFace(draw_context, "sans"); 116 | nvgTextAlign(draw_context, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE); 117 | nvgFontBlur(draw_context, 0.0f); 118 | nvgFillColor(draw_context, nvgRGBA(0, 0, 0, 255)); 119 | 120 | const float text_x_draw = width - this_socket.input_box->width - 3 * UI_SUBWIN_PARAM_EDIT_TEXT_INPUT_HPAD; 121 | const float text_y_draw = height_drawn + UI_SUBWIN_PARAM_EDIT_LAYOUT_ROW_HEIGHT / 2; 122 | nvgText(draw_context, text_x_draw, text_y_draw, this_socket.label.c_str(), nullptr); 123 | 124 | const float input_x_draw = width - this_socket.input_box->width - 2 * UI_SUBWIN_PARAM_EDIT_TEXT_INPUT_HPAD; 125 | const float input_y_draw = height_drawn + (UI_SUBWIN_PARAM_EDIT_LAYOUT_ROW_HEIGHT - this_socket.input_box->height) / 2; 126 | const bool highlight = this_socket.input_box->contains_point(mouse_pos); 127 | this_socket.input_box->set_position(Float2(input_x_draw, input_y_draw)); 128 | this_socket.input_box->draw(draw_context, highlight); 129 | 130 | height_drawn += UI_SUBWIN_PARAM_EDIT_LAYOUT_ROW_HEIGHT; 131 | } 132 | } 133 | 134 | return height_drawn; 135 | } 136 | 137 | bool cse::MultiInputWidget::has_input_focus() const 138 | { 139 | bool result = false; 140 | for (const auto& this_socket : sockets) { 141 | result = this_socket.input_box->has_input_focus() || result; 142 | } 143 | return result; 144 | } 145 | 146 | void cse::MultiInputWidget::handle_mouse_button(const int button, const int action, int) 147 | { 148 | if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) { 149 | for (const auto& this_socket : sockets) { 150 | if (this_socket.input_box->contains_point(mouse_pos)) { 151 | this_socket.input_box->begin_edit(); 152 | } 153 | else { 154 | request_undo_push = this_socket.input_box->complete_edit() || request_undo_push; 155 | } 156 | } 157 | } 158 | } 159 | 160 | void cse::MultiInputWidget::handle_key(const int key, int, const int action, int) 161 | { 162 | for (const auto& this_socket : sockets) { 163 | if (this_socket.input_box->has_input_focus()) { 164 | if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { 165 | this_socket.input_box->cancel_edit(); 166 | } 167 | else if (key == GLFW_KEY_ENTER && action == GLFW_PRESS) { 168 | request_undo_push = this_socket.input_box->complete_edit() || request_undo_push; 169 | } 170 | else if (key == GLFW_KEY_BACKSPACE && action == GLFW_PRESS) { 171 | this_socket.input_box->backspace(); 172 | } 173 | } 174 | } 175 | } 176 | 177 | void cse::MultiInputWidget::handle_character(const unsigned int codepoint) 178 | { 179 | for (const auto& this_socket : sockets) { 180 | if (this_socket.input_box->has_input_focus()) { 181 | this_socket.input_box->handle_character(codepoint); 182 | } 183 | } 184 | } 185 | 186 | cse::MultiInputWidget::InputRow::InputRow(const std::string label, const std::weak_ptr socket_value, const std::shared_ptr input_box) : 187 | label(label), 188 | socket_value(socket_value), 189 | input_box(input_box) 190 | { 191 | 192 | } 193 | -------------------------------------------------------------------------------- /src/widget_multi_input.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "util_vector.h" 8 | #include "widget.h" 9 | 10 | struct NVGcontext; 11 | 12 | namespace cse { 13 | 14 | class BaseInputBox; 15 | class SocketValue; 16 | 17 | // Widget that can accept input for multiple socket values 18 | class MultiInputWidget : public Widget { 19 | public: 20 | MultiInputWidget(float width); 21 | 22 | // Returns whether a box is currently selected 23 | bool tab(); 24 | 25 | bool add_socket_input(std::string label, std::weak_ptr socket_value); 26 | void clear_sockets(); 27 | 28 | bool complete_input(); 29 | 30 | virtual float draw(NVGcontext* draw_context) override; 31 | 32 | virtual bool has_input_focus() const override; 33 | virtual void handle_mouse_button(int button, int action, int mods) override; 34 | virtual void handle_key(int key, int scancode, int action, int mods) override; 35 | virtual void handle_character(unsigned int codepoint) override; 36 | 37 | private: 38 | class InputRow { 39 | public: 40 | InputRow(std::string label, std::weak_ptr socket_value, std::shared_ptr input_box); 41 | 42 | const std::string label; 43 | const std::weak_ptr socket_value; 44 | const std::shared_ptr input_box; 45 | }; 46 | 47 | std::vector sockets; 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /src/widget_radio_list.cpp: -------------------------------------------------------------------------------- 1 | #include "widget_radio_list.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "gui_sizes.h" 9 | #include "sockets.h" 10 | #include "wrapper_nvg_func.h" 11 | 12 | cse::RadioListWidget::RadioListWidget(const float width) : width(width) 13 | { 14 | 15 | } 16 | 17 | void cse::RadioListWidget::attach_value(const std::weak_ptr bool_socket_value) 18 | { 19 | string_pairs.clear(); 20 | this->attached_enum = std::weak_ptr(); 21 | if (bool_socket_value.expired() == false) { 22 | this->attached_bool = bool_socket_value; 23 | string_pairs.push_back(StringEnumPair("True", "true")); 24 | string_pairs.push_back(StringEnumPair("False", "tralse")); 25 | } 26 | } 27 | 28 | void cse::RadioListWidget::attach_value(const std::weak_ptr enum_socket_value) 29 | { 30 | string_pairs.clear(); 31 | this->attached_bool = std::weak_ptr(); 32 | if (auto locked = enum_socket_value.lock()) { 33 | this->attached_enum = locked; 34 | for (const StringEnumPair& this_pair : locked->enum_values) { 35 | string_pairs.push_back(this_pair); 36 | } 37 | } 38 | } 39 | 40 | void cse::RadioListWidget::pre_draw() 41 | { 42 | click_areas.clear(); 43 | } 44 | 45 | float cse::RadioListWidget::draw(NVGcontext* const draw_context) 46 | { 47 | float height_drawn = 0.0f; 48 | 49 | if (attached_enum.expired() == false && attached_bool.expired() == false) { 50 | return height_drawn; 51 | } 52 | 53 | nvgFontSize(draw_context, UI_FONT_SIZE_NORMAL); 54 | nvgFontFace(draw_context, "sans"); 55 | nvgFontBlur(draw_context, 0.0f); 56 | nvgFillColor(draw_context, nvgRGBA(0, 0, 0, 255)); 57 | nvgTextAlign(draw_context, NVG_ALIGN_LEFT | NVG_ALIGN_BASELINE); 58 | 59 | float max_label_width = 0; 60 | for (StringEnumPair this_enum_value : string_pairs) { 61 | float bounds[4]; 62 | nvgTextBounds(draw_context, 0, 0, this_enum_value.display_value.c_str(), nullptr, bounds); 63 | float this_width = std::ceil(bounds[2]); 64 | if (this_width > max_label_width) { 65 | max_label_width = this_width; 66 | } 67 | } 68 | const float max_draw_width = max_label_width + UI_CHECKBOX_SPACING + UI_CHECKBOX_RADIUS * 2; 69 | 70 | const auto enum_ptr = attached_enum.lock(); 71 | const auto bool_ptr = attached_bool.lock(); 72 | for (StringEnumPair this_enum_value : string_pairs) { 73 | if (this_enum_value.is_spacer()) { 74 | const Float2 spacer_left((width - max_draw_width) / 2.0f, height_drawn + UI_SUBWIN_PARAM_EDIT_LAYOUT_SPACER_HEIGHT / 2.0f); 75 | const Float2 spacer_right(spacer_left.x + max_draw_width, spacer_left.y); 76 | 77 | nvgBeginPath(draw_context); 78 | nvgMoveTo(draw_context, spacer_left); 79 | nvgLineTo(draw_context, spacer_right); 80 | 81 | nvgStrokeWidth(draw_context, 1.0f); 82 | nvgStrokeColor(draw_context, nvgRGBf(0.15f, 0.15f, 0.15f)); 83 | nvgStroke(draw_context); 84 | 85 | height_drawn += UI_SUBWIN_PARAM_EDIT_LAYOUT_SPACER_HEIGHT; 86 | } 87 | else { 88 | bool this_value_selected = false; 89 | if (enum_ptr) { 90 | this_value_selected = (enum_ptr->value.internal_value == this_enum_value.internal_value); 91 | } 92 | else if (bool_ptr) { 93 | const bool this_bool = this_enum_value.internal_value == "true"; 94 | this_value_selected = (this_bool == bool_ptr->value); 95 | } 96 | 97 | float circle_pos_x = width / 2.0f - max_draw_width / 2 + UI_CHECKBOX_RADIUS; 98 | float circle_pos_y = height_drawn + UI_WIDGET_RADIO_LIST_ROW_HEIGHT * 0.5f; 99 | nvgBeginPath(draw_context); 100 | nvgCircle(draw_context, circle_pos_x, circle_pos_y, UI_CHECKBOX_RADIUS); 101 | nvgFillColor(draw_context, nvgRGBAf(0.1f, 0.1f, 0.1f, 1.0f)); 102 | nvgFill(draw_context); 103 | if (this_value_selected) { 104 | nvgBeginPath(draw_context); 105 | nvgCircle(draw_context, circle_pos_x, circle_pos_y, UI_CHECKBOX_RADIUS * 0.666f); 106 | nvgFillColor(draw_context, nvgRGBAf(0.9f, 0.9f, 0.9f, 1.0f)); 107 | nvgFill(draw_context); 108 | } 109 | 110 | float text_pos_x = width / 2.0f - max_draw_width / 2 + UI_CHECKBOX_SPACING + UI_CHECKBOX_RADIUS * 2; 111 | float text_pos_y = height_drawn + UI_WIDGET_RADIO_LIST_ROW_HEIGHT * 0.8f; 112 | nvgFillColor(draw_context, nvgRGBA(0, 0, 0, 255)); 113 | nvgText(draw_context, text_pos_x, text_pos_y, this_enum_value.display_value.c_str(), nullptr); 114 | 115 | Float2 click_area_begin(0.0f, height_drawn); 116 | Float2 click_area_end(width, height_drawn + UI_WIDGET_RADIO_LIST_ROW_HEIGHT); 117 | HolderArea click_area(click_area_begin, click_area_end, this_enum_value.internal_value); 118 | 119 | click_areas.push_back(click_area); 120 | 121 | height_drawn += UI_WIDGET_RADIO_LIST_ROW_HEIGHT; 122 | } 123 | } 124 | 125 | return height_drawn; 126 | } 127 | 128 | void cse::RadioListWidget::set_mouse_local_position(const Float2 local_pos) 129 | { 130 | mouse_local_pos = local_pos; 131 | } 132 | 133 | void cse::RadioListWidget::handle_mouse_button(const int button, const int action, const int /*mods*/) 134 | { 135 | if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) { 136 | for (const HolderArea& this_area : click_areas) { 137 | if (this_area.contains_point(mouse_local_pos)) { 138 | if (auto enum_value = attached_enum.lock()) { 139 | if (enum_value->value.internal_value != this_area.get_value()) { 140 | enum_value->set_from_internal_name(this_area.get_value()); 141 | request_undo_push = true; 142 | } 143 | } 144 | else if (auto bool_value = attached_bool.lock()) { 145 | const bool new_value = (this_area.get_value() == "true") ? true : false; 146 | if (bool_value->value != new_value) { 147 | bool_value->value = new_value; 148 | request_undo_push = true; 149 | } 150 | } 151 | } 152 | } 153 | } 154 | } 155 | 156 | bool cse::RadioListWidget::should_push_undo_state() 157 | { 158 | const bool result = request_undo_push; 159 | request_undo_push = false; 160 | return result; 161 | } 162 | -------------------------------------------------------------------------------- /src/widget_radio_list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "sockets.h" 8 | #include "util_area.h" 9 | #include "util_vector.h" 10 | 11 | struct NVGcontext; 12 | 13 | namespace cse { 14 | 15 | // Widget that creates a radio button input panel for an enum 16 | class RadioListWidget { 17 | public: 18 | RadioListWidget(float width); 19 | 20 | void attach_value(std::weak_ptr bool_socket_value); 21 | void attach_value(std::weak_ptr enum_socket_value); 22 | 23 | void pre_draw(); 24 | float draw(NVGcontext* draw_context); 25 | 26 | void set_mouse_local_position(Float2 local_pos); 27 | 28 | void handle_mouse_button(int button, int action, int mods); 29 | 30 | bool should_push_undo_state(); 31 | 32 | private: 33 | const float width; 34 | 35 | std::weak_ptr attached_bool; 36 | std::weak_ptr attached_enum; 37 | std::vector> click_areas; 38 | 39 | Float2 mouse_local_pos; 40 | 41 | bool request_undo_push = false; 42 | 43 | std::vector string_pairs; 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/wrapper_glfw_func.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // This header defines small inline-able functions to make standard GLFW functions work with 4 | // this project's GlfwWindow wrapper class as well as smart pointers to GlfwWindow 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include "wrapper_glfw_window.h" 11 | 12 | namespace cse { 13 | 14 | // Context setup 15 | inline void glfwMakeContextCurrent(const std::unique_ptr& glfw_window) { 16 | glfwMakeContextCurrent(glfw_window->window_ptr); 17 | } 18 | 19 | // Input callback setup 20 | inline GLFWkeyfun glfwSetKeyCallback(const std::unique_ptr& glfw_window, const GLFWkeyfun cbfun) { 21 | return glfwSetKeyCallback(glfw_window->window_ptr, cbfun); 22 | } 23 | inline GLFWmousebuttonfun glfwSetMouseButtonCallback(const std::unique_ptr& glfw_window, const GLFWmousebuttonfun cbfun) { 24 | return glfwSetMouseButtonCallback(glfw_window->window_ptr, cbfun); 25 | } 26 | inline GLFWcharfun glfwSetCharCallback(const std::unique_ptr& glfw_window, const GLFWcharfun cbfun) { 27 | return glfwSetCharCallback(glfw_window->window_ptr, cbfun); 28 | } 29 | inline GLFWscrollfun glfwSetScrollCallback(const std::unique_ptr& glfw_window, const GLFWscrollfun cbfun) { 30 | return glfwSetScrollCallback(glfw_window->window_ptr, cbfun); 31 | } 32 | 33 | // Window status 34 | inline int glfwWindowShouldClose(const std::unique_ptr& glfw_window) { 35 | return glfwWindowShouldClose(glfw_window->window_ptr); 36 | } 37 | inline void glfwGetFramebufferSize(const std::unique_ptr& glfw_window, int* const width, int* const height) { 38 | glfwGetFramebufferSize(glfw_window->window_ptr, width, height); 39 | } 40 | inline void glfwGetCursorPos(const std::unique_ptr& glfw_window, double* const xpos, double* const ypos) { 41 | glfwGetCursorPos(glfw_window->window_ptr, xpos, ypos); 42 | } 43 | inline void glfwGetWindowSize(const std::unique_ptr& glfw_window, int* const width, int* const height) { 44 | glfwGetWindowSize(glfw_window->window_ptr, width, height); 45 | } 46 | 47 | // OpenGL stuff 48 | inline void glfwSwapBuffers(const std::unique_ptr& glfw_window) { 49 | glfwSwapBuffers(glfw_window->window_ptr); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/wrapper_glfw_window.cpp: -------------------------------------------------------------------------------- 1 | #include "wrapper_glfw_window.h" 2 | 3 | #include "glfw_callbacks.h" 4 | 5 | #include 6 | 7 | cse::GlfwWindow::GlfwWindow(const int width, const int height, const char* const title) : 8 | window_ptr(glfwCreateWindow(width, height, title, nullptr, nullptr)) 9 | { 10 | 11 | } 12 | 13 | cse::GlfwWindow::~GlfwWindow() 14 | { 15 | if (window_ptr != nullptr) { 16 | unregister_window_for_callbacks(window_ptr); 17 | glfwDestroyWindow(window_ptr); 18 | } 19 | } 20 | 21 | bool cse::GlfwWindow::is_valid() const 22 | { 23 | return window_ptr != nullptr; 24 | } -------------------------------------------------------------------------------- /src/wrapper_glfw_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct GLFWwindow; 4 | 5 | namespace cse { 6 | 7 | // Class to safely wrap glfwCreateWindow and glfwDestroyWindow 8 | class GlfwWindow { 9 | public: 10 | GlfwWindow(int width, int height, const char* title); 11 | ~GlfwWindow(); 12 | 13 | bool is_valid() const; 14 | 15 | GLFWwindow* const window_ptr; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/wrapper_nvg_context.cpp: -------------------------------------------------------------------------------- 1 | #include "wrapper_nvg_context.h" 2 | 3 | #define NANOVG_GL2_IMPLEMENTATION 4 | 5 | #include 6 | #include 7 | 8 | cse::NvgContext::NvgContext() : 9 | context_ptr(nvgCreateGL2(NVG_ANTIALIAS | NVG_STENCIL_STROKES | NVG_DEBUG)) 10 | { 11 | 12 | } 13 | 14 | cse::NvgContext::~NvgContext() 15 | { 16 | if (context_ptr != nullptr) { 17 | nvgDeleteGL2(context_ptr); 18 | } 19 | } 20 | 21 | bool cse::NvgContext::is_valid() const { 22 | return (context_ptr != nullptr); 23 | } 24 | -------------------------------------------------------------------------------- /src/wrapper_nvg_context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct NVGcontext; 4 | 5 | namespace cse { 6 | 7 | // Class to safely wrap nvgCreate and nvgDelete 8 | class NvgContext { 9 | public: 10 | NvgContext(); 11 | ~NvgContext(); 12 | 13 | bool is_valid() const; 14 | 15 | NVGcontext* const context_ptr; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/wrapper_nvg_func.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // This header defines small inline-able functions to make standard nanovg functions work with 4 | // this project's internal types 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include "util_vector.h" 11 | #include "wrapper_nvg_context.h" 12 | 13 | namespace cse { 14 | 15 | // Frame begin/end control 16 | inline void nvgBeginFrame(const std::unique_ptr& ctx, const float windowWidth, const float windowHeight, const float devicePixelRatio) 17 | { 18 | nvgBeginFrame(ctx->context_ptr, windowWidth, windowHeight, devicePixelRatio); 19 | } 20 | 21 | inline void nvgEndFrame(const std::unique_ptr& ctx) 22 | { 23 | nvgEndFrame(ctx->context_ptr); 24 | } 25 | 26 | // Path 27 | inline void nvgMoveTo(NVGcontext* const ctx, const Float2 point) 28 | { 29 | nvgMoveTo(ctx, point.x, point.y); 30 | } 31 | 32 | inline void nvgLineTo(NVGcontext* const ctx, const Float2 point) 33 | { 34 | nvgLineTo(ctx, point.x, point.y); 35 | } 36 | 37 | inline void nvgRect(NVGcontext* const ctx, const Float2 begin, const Float2 size) 38 | { 39 | nvgRect(ctx, begin.x, begin.y, size.x, size.y); 40 | } 41 | 42 | // Color 43 | inline void nvgFillColor(NVGcontext* const ctx, const Float3 color) 44 | { 45 | nvgFillColor(ctx, nvgRGBf(color.x, color.y, color.z)); 46 | } 47 | 48 | inline NVGpaint nvgLinearGradient( 49 | NVGcontext* const ctx, 50 | const float sx, 51 | const float sy, 52 | const float ex, 53 | const float ey, 54 | const Float3 icol, 55 | const Float3 ocol) 56 | { 57 | return nvgLinearGradient(ctx, sx, sy, ex, ey, nvgRGBf(icol.x, icol.y, icol.z), nvgRGBf(ocol.x, ocol.y, ocol.z)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/zoom.cpp: -------------------------------------------------------------------------------- 1 | #include "zoom.h" 2 | 3 | #include 4 | 5 | cse::ZoomManager::ZoomManager() 6 | { 7 | zooms.push_back(0.25f); 8 | zooms.push_back(0.325f); 9 | zooms.push_back(0.40f); 10 | zooms.push_back(0.50f); 11 | zooms.push_back(0.666667f); 12 | zooms.push_back(0.833333f); 13 | zooms.push_back(1.00f); 14 | zooms.push_back(1.25f); 15 | zooms.push_back(1.50f); 16 | zooms.push_back(1.75f); 17 | zooms.push_back(2.00f); 18 | zooms.push_back(2.333333f); 19 | zooms.push_back(2.666667f); 20 | zooms.push_back(3.00f); 21 | zooms.push_back(3.50f); 22 | zooms.push_back(4.00f); 23 | 24 | zoom_level = -1; 25 | for (std::vector::size_type i = 0; i < zooms.size(); i++) { 26 | if (zooms[i] == 1.0f) { 27 | zoom_level = static_cast(i); 28 | } 29 | } 30 | 31 | if (zoom_level < 0) { 32 | assert(false); 33 | } 34 | } 35 | 36 | float cse::ZoomManager::get_world_scale() const 37 | { 38 | return zooms[zoom_level]; 39 | } 40 | 41 | void cse::ZoomManager::zoom_in() 42 | { 43 | if (zoom_level == static_cast(zooms.size() - 1)) { 44 | return; 45 | } 46 | zoom_level++; 47 | } 48 | 49 | void cse::ZoomManager::zoom_out() 50 | { 51 | if (zoom_level == 0) { 52 | return; 53 | } 54 | zoom_level--; 55 | } 56 | -------------------------------------------------------------------------------- /src/zoom.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cse { 6 | 7 | class ZoomManager { 8 | public: 9 | ZoomManager(); 10 | 11 | float get_world_scale() const; 12 | 13 | void zoom_in(); 14 | void zoom_out(); 15 | 16 | private: 17 | int zoom_level; 18 | std::vector zooms; 19 | }; 20 | 21 | } 22 | --------------------------------------------------------------------------------