diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 9d8f65f9..e8fa605c 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -59,7 +59,8 @@ jobs: libglew-dev \ libglm-dev \ libomp-17-dev \ - libpng-dev + libpng-dev \ + libfreetype-dev - name: Build diff --git a/CMakeLists.txt b/CMakeLists.txt index a9851725..a1d3fb8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,13 +69,14 @@ find_package(GLEW REQUIRED) find_package(glfw3 REQUIRED) find_package(glm REQUIRED) find_package(PNG REQUIRED) +find_package(Freetype REQUIRED) # find_package(OpenMP REQUIRED) target_link_libraries(FunGame PRIVATE glfw) target_link_libraries(FunGame PRIVATE OpenGL::GL) target_link_libraries(FunGame PRIVATE GLEW::GLEW) target_link_libraries(FunGame PRIVATE PNG::PNG) -# target_link_libraries(FunGame PRIVATE OpenMP::OpenMP_CXX) +target_link_libraries(FunGame PRIVATE Freetype::Freetype) if(WIN32) target_link_libraries(FunGame PRIVATE glm) @@ -138,8 +139,17 @@ else() WORKING_DIRECTORY ${CMAKE_BINARY_DIR} ) + add_custom_target(fonts + # maybe vendor is already made by something else? + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/vendor/fonts + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/vendor/fonts ${CMAKE_BINARY_DIR}/vendor/fonts + COMMENT " copying ${CMAKE_SOURCE_DIR}/vendor/fonts to ${CMAKE_BINARY_DIR}/vendor/fonts " + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + add_dependencies(FunGame resources) add_dependencies(FunGame data) + add_dependencies(FunGame fonts) endif() configure_file(src/config.h.in config.h) diff --git a/README.md b/README.md index 87c05d41..073528ae 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ pacman -Su \ mingw-w64-x86_64-ninja \ mingw-w64-x86_64-glew \ mingw-w64-x86_64-glfw \ - mingw-w64-x86_64-glm + mingw-w64-x86_64-glm \ + mingw-w64-x86_64-freetype ``` ## Building diff --git a/resources/shaders/overlay/FramedWindow.frag b/resources/shaders/overlay/FramedWindow.frag new file mode 100644 index 00000000..073c88a7 --- /dev/null +++ b/resources/shaders/overlay/FramedWindow.frag @@ -0,0 +1,87 @@ +#version 450 core + +// Ouput data +layout(location = 0) out vec3 color; + +in vec2 UV; // in pixels +uniform ivec2 frame_size; +uniform usampler2D window_texture; +uniform int ui_scale; + +// uniform +uniform ivec4 border_size; +uniform ivec4 side_lengths; +uniform ivec2 inner_pattern_size; +uniform ivec2 positions[9]; + +void +main(){ + int width_2 = side_lengths[0]; + int height_4 = side_lengths[1]; + int width_5 = inner_pattern_size[0]; + int height_5 = inner_pattern_size[1]; + int height_6 = side_lengths[2]; + int width_8 = side_lengths[3]; + + ivec2 pixel_position = ivec2(int(UV.x), int(UV.y)); + ivec2 ui_position = pixel_position / ui_scale; + ivec2 frame_size_px = frame_size / ui_scale; + + ivec2 texture_offset; + +// The window is arranged like this +// 0 | 1 | 2 +// ---+---+--- +// 3 | 4 | 5 +// ---+---+--- +// 6 | 7 | 8 +// + if (ui_position.y < border_size[1] && ui_position.x < border_size[0]) { // 0 + ivec2 local_position = ivec2(ui_position.x, ui_position.y); + texture_offset = local_position + positions[0]; + } + else if (ui_position.y < border_size[1] && frame_size_px.x - ui_position.x - 1 < border_size[2]) { // 2 + ivec2 local_position = ivec2(ui_position.x - frame_size_px.x + border_size[2], ui_position.y); + texture_offset = local_position + positions[2]; + } + else if ((frame_size_px.y - ui_position.y - 1 < border_size[3]) && ui_position.x < border_size[0]) { // 6 + ivec2 local_position = ivec2(ui_position.x, ui_position.y - frame_size_px.y + border_size[3]); + texture_offset = local_position + positions[6]; + } + else if ((frame_size_px.y - ui_position.y - 1 < border_size[3]) && (frame_size_px.x - ui_position.x - 1 < border_size[2])) { // 8 + ivec2 local_position = ivec2(ui_position.x - frame_size_px.x + border_size[2], ui_position.y - frame_size_px.y + border_size[3]); + texture_offset = local_position + positions[8]; + } + else if (ui_position.x < border_size[0]) { // 3 + ivec2 local_position = ivec2(ui_position.x, ui_position.y - border_size[1]); + local_position.y = local_position.y % height_4; + texture_offset = local_position + positions[3]; + } + else if (ui_position.y < border_size[1]) { // 1 + ivec2 local_position = ivec2(ui_position.x, ui_position.y); + local_position.x = local_position.x % width_2; + texture_offset = local_position + positions[1]; + } + else if ((frame_size_px.x - ui_position.x) <= border_size[3]) { // 5 + ivec2 local_position = ivec2(border_size[2] - frame_size_px.x + ui_position.x, ui_position.y - border_size[3]); + local_position.y = local_position.y % height_6; + texture_offset = local_position + positions[5]; + } + else if ((frame_size_px.y - ui_position.y) <= border_size[3]) { // 7 + ivec2 local_position = ivec2(ui_position.x - border_size[2], ui_position.y - frame_size_px.y + border_size[3]); + local_position.x = local_position.x % width_8; + texture_offset = local_position + positions[7]; + } else { // 4 + ivec2 local_position = ivec2(ui_position.x - border_size[0], ui_position.y - border_size[1]); + local_position.x = local_position.x % width_5; + local_position.y = local_position.y % height_5; + texture_offset = positions[4] + local_position; + } + + uvec4 color_int = texelFetch(window_texture, texture_offset, 0); + if (color_int.a == 0) { + discard; + } + + color = vec3(color_int.rgb)/255.0; +} diff --git a/resources/shaders/overlay/TextWindow.frag b/resources/shaders/overlay/TextWindow.frag new file mode 100644 index 00000000..30a92085 --- /dev/null +++ b/resources/shaders/overlay/TextWindow.frag @@ -0,0 +1,23 @@ +#version 450 core + + +// Ouput data +layout(location = 0) out vec3 color; + +uniform usampler2D font_texture; +// right now only using the rgb components +// may in the future use the alpha component +uniform vec4 font_color; + +in vec2 UV; + +void +main() { + uint alpha = texelFetch(font_texture, ivec2(UV), 0).r; + + if (alpha > 0) { + color = font_color.rgb; + } else { + discard; + } +} diff --git a/resources/shaders/overlay/TextWindow.vert b/resources/shaders/overlay/TextWindow.vert new file mode 100644 index 00000000..ce732887 --- /dev/null +++ b/resources/shaders/overlay/TextWindow.vert @@ -0,0 +1,21 @@ +#version 450 core + +// Screen and texture position of letter +layout(location = 0) in ivec4 pos; + +// this is the size of the view +// then switch to division +uniform ivec2 frame_size; +uniform int ui_scale; + +// Output data ; will be interpolated for each fragment. +out vec2 UV; + +void +main() { + + gl_Position = vec4(ui_scale * (vec2(pos.xy) / vec2(frame_size)) - 1, 1, 1); + UV = vec2(pos.w, pos.z); + //UV = vec2((pos.z - 304) * 20, pos.w * 2); +} + diff --git a/resources/shaders/overlay/Widget.vert b/resources/shaders/overlay/Widget.vert new file mode 100644 index 00000000..a01e75ce --- /dev/null +++ b/resources/shaders/overlay/Widget.vert @@ -0,0 +1,15 @@ +#version 450 core + +// Input vertex data, different for all executions of this shader. +layout(location = 0) in vec3 pos; +layout(location = 1) in vec2 vertex_position_screenspace; + +// Output data ; will be interpolated for each fragment. +out vec2 UV; + +void +main() { + gl_Position = vec4(pos, 1); + UV = vertex_position_screenspace; +} + diff --git a/resources/textures/GenericBorder.json b/resources/textures/GenericBorder.json new file mode 100644 index 00000000..3e789fff --- /dev/null +++ b/resources/textures/GenericBorder.json @@ -0,0 +1,57 @@ +{ + "texture_file": "./GenericBorder.png", + "border_size": [ + 5, + 5, + 5, + 5 + ], + "side_lengths": [ + 1, + 1, + 1, + 1 + ], + "inner_pattern_size": [ + 1, + 1 + ], + "texture_regions": [ + [ + 0, + 0 + ], + [ + 5, + 0 + ], + [ + 6, + 0 + ], + [ + 0, + 5 + ], + [ + 5, + 5 + ], + [ + 6, + 5 + ], + [ + 0, + 6 + ], + [ + 5, + 6 + ], + [ + 6, + 6 + ] + ] +} \ No newline at end of file diff --git a/resources/textures/GenericBorder.png b/resources/textures/GenericBorder.png new file mode 100644 index 00000000..ab539552 Binary files /dev/null and b/resources/textures/GenericBorder.png differ diff --git a/resources/textures/GenericBorder_2.json b/resources/textures/GenericBorder_2.json new file mode 100644 index 00000000..1a5f8fb9 --- /dev/null +++ b/resources/textures/GenericBorder_2.json @@ -0,0 +1,57 @@ +{ + "texture_file": "./GenericBorder_2.png", + "border_size": [ + 7, + 7, + 7, + 7 + ], + "side_lengths": [ + 10, + 10, + 10, + 10 + ], + "inner_pattern_size": [ + 10, + 10 + ], + "texture_regions": [ + [ + 0, + 0 + ], + [ + 6, + 0 + ], + [ + 15, + 0 + ], + [ + 0, + 6 + ], + [ + 6, + 6 + ], + [ + 15, + 6 + ], + [ + 0, + 15 + ], + [ + 6, + 15 + ], + [ + 15, + 15 + ] + ] +} \ No newline at end of file diff --git a/resources/textures/GenericBorder_2.png b/resources/textures/GenericBorder_2.png new file mode 100644 index 00000000..13a59cf6 Binary files /dev/null and b/resources/textures/GenericBorder_2.png differ diff --git a/resources/textures/GenericButton.json b/resources/textures/GenericButton.json new file mode 100644 index 00000000..b6609fce --- /dev/null +++ b/resources/textures/GenericButton.json @@ -0,0 +1,57 @@ +{ + "texture_file": "./GenericButton.png", + "border_size": [ + 5, + 5, + 5, + 5 + ], + "side_lengths": [ + 1, + 1, + 1, + 1 + ], + "inner_pattern_size": [ + 1, + 1 + ], + "texture_regions": [ + [ + 0, + 0 + ], + [ + 5, + 0 + ], + [ + 6, + 0 + ], + [ + 0, + 5 + ], + [ + 5, + 5 + ], + [ + 6, + 5 + ], + [ + 0, + 6 + ], + [ + 5, + 6 + ], + [ + 6, + 6 + ] + ] +} \ No newline at end of file diff --git a/resources/textures/GenericButton.png b/resources/textures/GenericButton.png new file mode 100644 index 00000000..4c686400 Binary files /dev/null and b/resources/textures/GenericButton.png differ diff --git a/src/exceptions.hpp b/src/exceptions.hpp index 1d1e217e..d47557c4 100644 --- a/src/exceptions.hpp +++ b/src/exceptions.hpp @@ -8,13 +8,14 @@ namespace exc { class file_not_found_error : public std::runtime_error { - constexpr static std::string ERROR_PREFIX_ = "Could not open "; + constexpr static std::string_view ERROR_PREFIX_ = "Could not open "; std::filesystem::path path_; public: file_not_found_error(std::filesystem::path path) : - runtime_error(ERROR_PREFIX_ + path.string()), path_(std::move(path)) {} + runtime_error(std::move(std::string(ERROR_PREFIX_) + path.string())), + path_(std::move(path)) {} file_not_found_error(const file_not_found_error& other) noexcept = default; diff --git a/src/graphics_main.cpp b/src/graphics_main.cpp index ef57b8a6..1938b105 100644 --- a/src/graphics_main.cpp +++ b/src/graphics_main.cpp @@ -1,10 +1,15 @@ #include "graphics_main.hpp" +#include "chrono" #include "config.h" #include "gui/handler.hpp" +#include "gui/render/structures/screen_data.hpp" +#include "gui/scene/input.hpp" +#include "gui/the_buttons/user_interface.hpp" #include "gui/ui/imgui_gui.hpp" #include "gui/ui/opengl_gui.hpp" #include "gui/ui/opengl_setup.hpp" +#include "gui/ui/user_interface_setup.hpp" #include "logging.hpp" #include "types.hpp" #include "world/climate.hpp" @@ -14,22 +19,52 @@ #include #include -// create structure to pass game start data either from command line or from gui -/* -struct Options { - std::optional biome; +#include - std::optional save_file; -};*/ +// result from loading +// will be modified to handel all events that must be returned from the loading +// screen. +struct background_result { + int result; + std::unique_ptr world; + std::unique_ptr climate; +}; +// read settings from command line int graphics_main(const argh::parser& cmdl) { - // add window width and height? + size_t seed; + cmdl("seed", SEED) >> seed; + size_t size; + cmdl("size", STRESS_TEST_SIZE) >> size; + std::string biome_name; + cmdl("biome-name", BIOME_BASE_NAME) >> biome_name; + bool imgui_debug = cmdl[{"-g", "--imgui"}]; + + intro_scene::result new_game_settings = intro_scene::NewGame{ + .biome = biome_name, .seed = seed, .size = size, .DearIMGUI = imgui_debug + }; + + return graphics_main(new_game_settings); +} + +int +graphics_main(intro_scene::result result) { + LOG_WARNING(logging::main_logger, "Only kinda implemented"); + + // global context and logging are already initalized. + + // read settings from file + // Things like screen size + // full screen + // all graphics/audio settings + // not that bad, make a settings header and define structures in there. screen_size_t window_width = 1280; screen_size_t window_height = 800; // init graphics + // TODO stream line this part and make sure to clean things up correctly std::optional opt_window = gui::setup_opengl(window_width, window_height); if (!opt_window) { @@ -43,52 +78,201 @@ graphics_main(const argh::parser& cmdl) { glGenVertexArrays(1, &VertexArrayID); gui::VertexBufferHandler::instance().bind_vertex_buffer(VertexArrayID); - std::string run_function = cmdl(1).str(); + // This is the top level game loop. This will handle switching between + // games, and starting new games. Should also have a settings page + // etc. + while (true) { + switch (result.index()) { + case 0: // exiting + goto exit; + break; + case 1: // intro page + result = intro_window(window); + break; + case 2: // new world with settings + { + result = start_game(result, window); + } + break; + case 3: // from save with file path + { + result = start_game(result, window); + } + break; + default: + LOG_WARNING( + logging::main_logger, "NOT IMPLEMENTED/Something went wrong" + ); + break; + } + } +exit: + gui::scene::InputHandler::forward_inputs_to( + nullptr + ); // this removes an object from static storage + GlobalContext& context = GlobalContext::instance(); + context.run_opengl_queue(); // this should clean up anything left in the queue + + glDeleteVertexArrays(1, &VertexArrayID); + return std::get(result).status; +} + +intro_scene::result +start_game(intro_scene::result result, GLFWwindow* window) { + // add window width and height? + + screen_size_t display_w, display_h; + + // Start Splash screen + gui::shader::ShaderHandler temp_handler; + gui::shader::Program& splash_screen_program = temp_handler.load_program( + "Splash Screen", files::get_resources_path() / "shaders" / "Passthrough.vert", + files::get_resources_path() / "shaders" / "Green.frag" + ); + + std::function splash_screen_setup = []() { + // Draw over everything + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + }; - // Think about this later - // need to read biomes from manifest - // then in background need to mesh things - // then send those things back to main thread - // the main thread loads meshes onto gpu and renders to screen - // Read manifest - // util::load_manifest(); + auto splash_screen_pipeline = std::make_shared( + splash_screen_program, splash_screen_setup + ); + // TODO allow to do all these without differed + // TODO assert in at the lowest level that opengl things are run on the main thread + auto screen_data = std::make_shared(); + + splash_screen_pipeline->data.push_back(screen_data.get()); + + // background task + GlobalContext& global_context = GlobalContext::instance(); + // don't forget ot load ScreenData onto gpu + global_context.run_opengl_queue(); manifest::ObjectHandler object_handler; - object_handler.load_all_manifests(); - // generate options either from command line inputs - // or from gui - // struct Options + std::future load_manifests_future; + bool imgui_debug; - size_t seed; - cmdl("seed", SEED) >> seed; - size_t size; - cmdl("size", STRESS_TEST_SIZE) >> size; - std::string biome_name; - cmdl("biome-name", BIOME_BASE_NAME) >> biome_name; + if (result.index() == 2) { // new game + std::string biome_name = std::get(result).biome; + size_t size = std::get(result).size; + size_t seed = std::get(result).seed; + imgui_debug = std::get(result).DearIMGUI; - world::World world(&object_handler, biome_name, size, size, seed); - world.generate_plants(); + load_manifests_future = + global_context.submit_task([&object_handler, biome_name, size, seed]() { + background_result result; - world::Climate climate; + int manifest_result = object_handler.load_all_manifests(); + result.result = manifest_result; + if (result.result == 1) { + return result; + } + + result.world = std::make_unique( + &object_handler, biome_name, size, size, seed + ); + result.world->generate_plants(); + result.climate = std::make_unique(); + + return result; + }); + + } else if (result.index() == 2) { // load game + LOG_ERROR(logging::main_logger, "Loading World Not Implemented (yet*)"); + return intro_scene::IntroPage(); + // not implemented + } + + // can't cancel a task after it has been started run. + + while (load_manifests_future.wait_for(std::chrono::seconds(0)) + != std::future_status::ready) { + glfwGetFramebufferSize(window, &display_w, &display_h); + + splash_screen_pipeline->render(display_w, display_h, 0); + + glfwSwapBuffers(window); + glfwPollEvents(); + } + + auto future_value = load_manifests_future.get(); + + if (future_value.result == 1) { + LOG_ERROR(logging::main_logger, "Error Loading. Exiting."); + return intro_scene::Exit(1); + } + + world::World& world = *future_value.world; + world::Climate& climate = *future_value.climate; // if need gui start gui - bool imgui_debug = cmdl[{"-g", "--imgui"}]; if (imgui_debug) { - return gui::imgui_entry(window, world, climate); + int gui_result = gui::imgui_entry(window, world, climate); + return intro_scene::Exit(gui_result); } else { // if don't then strate to opengl - return gui::opengl_entry(window, world, climate); + int gui_result = gui::opengl_entry(window, world, climate); + return intro_scene::Exit(gui_result); } - glDeleteVertexArrays(1, &VertexArrayID); - - return 0; + return intro_scene::Exit(0); } -int -graphics_main() { - LOG_CRITICAL(logging::main_logger, "NOT IMPLEMENTED"); +// TODO move to separate file +intro_scene::result +intro_window(GLFWwindow* window) { + screen_size_t display_w, display_h; + + // Start Splash screen + gui::shader::ShaderHandler temp_handler; + gui::shader::Program& splash_screen_program = temp_handler.load_program( + "Splash Screen", files::get_resources_path() / "shaders" / "Passthrough.vert", + files::get_resources_path() / "shaders" / "Blue.frag" + ); + + std::function splash_screen_setup = []() { + // Draw over everything + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + }; + + auto splash_screen_pipeline = std::make_shared( + splash_screen_program, splash_screen_setup + ); + + auto screen_data = std::make_shared(); + + splash_screen_pipeline->data.push_back(screen_data.get()); + + GlobalContext& global_context = GlobalContext::instance(); + // don't forget ot load ScreenData onto gpu + + auto main_interface = + std::make_shared(temp_handler, 4); + gui::setup(*main_interface); - return 1; -} \ No newline at end of file + gui::scene::InputHandler::imgui_active = false; + gui::scene::InputHandler::set_window(window); + gui::scene::InputHandler::forward_inputs_to( + std::static_pointer_cast(main_interface) + ); + global_context.run_opengl_queue(); + + while (!glfwWindowShouldClose(window)) { + glfwGetFramebufferSize(window, &display_w, &display_h); + + splash_screen_pipeline->render(display_w, display_h, 0); + + main_interface->update(display_w, display_h); + + glfwSwapBuffers(window); + glfwPollEvents(); + } + + return intro_scene::Exit(0); +} diff --git a/src/graphics_main.hpp b/src/graphics_main.hpp index f691382a..eb110b23 100644 --- a/src/graphics_main.hpp +++ b/src/graphics_main.hpp @@ -23,6 +23,12 @@ #include +#include +#include + +#include +#include + /** * @brief Start Graphics window * @@ -30,9 +36,51 @@ */ int graphics_main(const argh::parser& cmdl); +namespace intro_scene { + +enum return_to { + EXIT, + INTRO_SCENE, + NEW_GAME, + LOAD_GAME, +}; + +struct Exit { + int status = 0; +}; + +struct IntroPage {}; + +struct NewGame { + std::string biome; + size_t seed; + size_t size; + // map location; + // starting something + // difficulty etc + // int difficulty; + bool DearIMGUI; +}; + +struct LoadGame { + std::filesystem::path game_file_path; + bool DearIMGUI; +}; + +using result = std::variant; + +} // namespace intro_scene + +intro_scene::result intro_window(GLFWwindow* window); + +// should this be templated who knows? +intro_scene::result start_game(intro_scene::result, GLFWwindow* window); + +// intro_scene::result graphics_main(intro_scene::LoadGame); + /** * @brief Start Graphics window * - * @warning NOT IMPLEMENTED + * */ -int graphics_main(); +int graphics_main(intro_scene::result result = intro_scene::IntroPage()); diff --git a/src/gui/render/gl_enums.hpp b/src/gui/render/gl_enums.hpp index cdb3fdea..9f6d8356 100644 --- a/src/gui/render/gl_enums.hpp +++ b/src/gui/render/gl_enums.hpp @@ -150,18 +150,25 @@ enum class GPUArayType : GLenum { enum class GPUPixelType : GLenum { FLOAT = GL_FLOAT, // float (32) HALF_FLOAT = GL_HALF_FLOAT, // float16 - NONE = 0, // render buffers have no internal type + UNSIGNED_BYTE = GL_UNSIGNED_BYTE, + NONE = 0, // render buffers have no internal type + }; enum class GPUPixelStorageFormat : GLenum { - R = GL_R, + RED = GL_RED, DEPTH = GL_DEPTH_COMPONENT, DEPTH_16 = GL_DEPTH_COMPONENT16, DEPTH_24 = GL_DEPTH_COMPONENT24, DEPTH_32 = GL_DEPTH_COMPONENT32, RGB = GL_RGB, RGB8 = GL_RGB8, - RGBA = GL_RGBA + RGBA = GL_RGBA, + RGBA8 = GL_RGBA8, + + RGBA8I = GL_RGBA8I, + RGBA8UI = GL_RGBA8UI, + // RGBA8I = GL_RGBA8I }; enum class GPUPixelReadFormat : GLenum { @@ -176,6 +183,7 @@ enum class GPUPixelReadFormat : GLenum { RGBA = GL_RGBA, BGRA = GL_BGRA, + RGBA_INTEGER = GL_RGBA_INTEGER, }; /** @@ -614,7 +622,7 @@ get_size(const GPUPixelReadFormat& data_format) { constexpr inline size_t get_size(const GPUPixelStorageFormat& data_format) { switch (data_format) { - case GPUPixelStorageFormat::R: + case GPUPixelStorageFormat::RED: case GPUPixelStorageFormat::DEPTH: return 1; case GPUPixelStorageFormat::RGB: @@ -640,6 +648,8 @@ get_size(const GPUPixelType type) { return 4; case GPUPixelType::HALF_FLOAT: return 2; + case GPUPixelType::UNSIGNED_BYTE: + return 1; default: LOG_CRITICAL( logging::opengl_logger, diff --git a/src/gui/render/gpu_data/frame_buffer.cpp b/src/gui/render/gpu_data/frame_buffer.cpp index 0c80292d..19a9867b 100644 --- a/src/gui/render/gpu_data/frame_buffer.cpp +++ b/src/gui/render/gpu_data/frame_buffer.cpp @@ -100,7 +100,7 @@ FrameBufferBase::read_data( type = color_texture->get_type(); format = color_texture->get_format(); switch (format) { - case GPUPixelStorageFormat::R: + case GPUPixelStorageFormat::RED: read_format = GPUPixelReadFormat::RED; break; case GPUPixelStorageFormat::RGB: @@ -133,7 +133,7 @@ FrameBufferBase::read_data( } switch (format) { - case GPUPixelStorageFormat::R: + case GPUPixelStorageFormat::RED: case GPUPixelStorageFormat::DEPTH: // in this case format_size should be 1 return std::make_shared( diff --git a/src/gui/render/gpu_data/texture.cpp b/src/gui/render/gpu_data/texture.cpp index c2323d9f..6a4bf91e 100644 --- a/src/gui/render/gpu_data/texture.cpp +++ b/src/gui/render/gpu_data/texture.cpp @@ -1,7 +1,7 @@ #include "texture.hpp" #include "gui/handler.hpp" -#include "util/color.hpp" +#include "logging.hpp" namespace gui { @@ -163,7 +163,131 @@ Texture2D::load_data(std::shared_ptr image) { static_cast(settings_.read_format), static_cast(settings_.type), image->data() ); - glGenerateMipmap(GL_TEXTURE_2D); + if (settings_.type == GPUPixelType::FLOAT + || settings_.type == GPUPixelType::HALF_FLOAT) { + glGenerateMipmap(GL_TEXTURE_2D); + } +} + +std::shared_ptr +Texture2D::get_image() const { + // multiplying by 2 fixes the memory error, but that can't possibly be correct. + // TODO GL_PACK_ALIGNMENT + size_t data_size = width_ * height_ * get_size(settings_.type) + * get_size(settings_.read_format) * 2; + + std::shared_ptr data = std::make_shared(data_size); + + if (settings_.multisample) { + LOG_ERROR(logging::opengl_logger, "Cannot load multisample texture to image."); + return nullptr; + } + glBindTexture(GL_TEXTURE_2D, texture_ID_); + +#if DEBUG() + int width; + int height; + int opengl_type; + + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); + glGetTexLevelParameteriv( + GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &opengl_type + ); + + if (width != width_ || height != height_) { + LOG_WARNING( + logging::opengl_logger, + "Width or Height don't match correct value. Actual {}, {}, Saved {}, {}.", + width, height, width_, height_ + ); + } + + if (gui::gpu_data::GPUPixelStorageFormat(opengl_type) + != settings_.internal_format) { + LOG_WARNING( + logging::opengl_logger, "Type sizes don't match. Actual {}, Given {}.", + get_size(gui::gpu_data::GPUPixelStorageFormat(opengl_type)), + get_size(settings_.internal_format) + ); + } + +#endif + + glGetTexImage( + GL_TEXTURE_2D, 0, static_cast(settings_.read_format), + static_cast(settings_.type), data.get() + ); + + switch (settings_.type) { + case GPUPixelType::FLOAT: + case GPUPixelType::HALF_FLOAT: + switch (settings_.read_format) { + case GPUPixelReadFormat::DEPTH_COMPONENT: + case GPUPixelReadFormat::DEPTH_STENCIL: + case GPUPixelReadFormat::RED: + case GPUPixelReadFormat::GREEN: + case GPUPixelReadFormat::BLUE: + return std::make_shared( + data, width_, height_, get_size(settings_.type) + ); + case GPUPixelReadFormat::RGB: + case GPUPixelReadFormat::BGR: + return std::make_shared( + data, width_, height_, get_size(settings_.type) + ); + + case GPUPixelReadFormat::RGBA: + case GPUPixelReadFormat::BGRA: + return std::make_shared( + data, width_, height_, get_size(settings_.type) + ); + + default: + LOG_ERROR( + logging::opengl_logger, + "Cannot load image. Unknown read_format {}.", + static_cast(settings_.read_format) + ); + return nullptr; + } + case GPUPixelType::UNSIGNED_BYTE: + switch (settings_.read_format) { + case GPUPixelReadFormat::DEPTH_COMPONENT: + case GPUPixelReadFormat::DEPTH_STENCIL: + case GPUPixelReadFormat::RED: + case GPUPixelReadFormat::GREEN: + case GPUPixelReadFormat::BLUE: + return std::make_shared( + data, width_, height_, get_size(settings_.type) + ); + case GPUPixelReadFormat::RGB: + case GPUPixelReadFormat::BGR: + return std::make_shared( + data, width_, height_, get_size(settings_.type) + ); + + case GPUPixelReadFormat::RGBA: + case GPUPixelReadFormat::BGRA: + return std::make_shared( + data, width_, height_, get_size(settings_.type) + ); + + default: + LOG_ERROR( + logging::opengl_logger, + "Cannot load image. Unknown read_format {}.", + static_cast(settings_.read_format) + ); + return nullptr; + } + default: + LOG_ERROR( + logging::opengl_logger, "Cannot load image. Unknown type {}.", + static_cast(settings_.type) + ); + return nullptr; + } } } // namespace gpu_data diff --git a/src/gui/render/gpu_data/texture.hpp b/src/gui/render/gpu_data/texture.hpp index 1f0fa699..a7302ee4 100644 --- a/src/gui/render/gpu_data/texture.hpp +++ b/src/gui/render/gpu_data/texture.hpp @@ -2,10 +2,7 @@ #pragma once #include "data_types.hpp" -#include "global_context.hpp" -#include "logging.hpp" #include "types.hpp" -#include "util/color.hpp" #include "util/image.hpp" #include @@ -118,12 +115,12 @@ class Texture2D : virtual public GPUDataRenderBuffer { virtual void connect_depth_texture(GLuint framebuffer_ID) override; inline virtual GPUPixelType - get_type() const { + get_type() const override { return settings_.type; } inline virtual GPUPixelStorageFormat - get_format() const { + get_format() const override { return settings_.internal_format; } @@ -141,6 +138,8 @@ class Texture2D : virtual public GPUDataRenderBuffer { settings_.multisample ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; glBindTexture(target, texture_ID_); } + + [[nodiscard]] std::shared_ptr get_image() const; }; } // namespace gpu_data diff --git a/src/gui/render/gpu_data/vertex_array_object.hpp b/src/gui/render/gpu_data/vertex_array_object.hpp index 85e40f20..fb70650e 100644 --- a/src/gui/render/gpu_data/vertex_array_object.hpp +++ b/src/gui/render/gpu_data/vertex_array_object.hpp @@ -16,9 +16,15 @@ class VertexArrayObject { /** * @brief construct a new VertexArrayObject */ - inline VertexArrayObject() { - GlobalContext& context = GlobalContext::instance(); - context.push_opengl_task([this]() { glGenVertexArrays(1, &vertex_array_); }); + inline VertexArrayObject(bool differed = true) { + if (differed) { + GlobalContext& context = GlobalContext::instance(); + context.push_opengl_task([this]() { + glGenVertexArrays(1, &vertex_array_); + }); + } else { + glGenVertexArrays(1, &vertex_array_); + } }; // copy and move constructor diff --git a/src/gui/render/gpu_data/vertex_buffer_object.hpp b/src/gui/render/gpu_data/vertex_buffer_object.hpp index 0d717316..da0dff56 100644 --- a/src/gui/render/gpu_data/vertex_buffer_object.hpp +++ b/src/gui/render/gpu_data/vertex_buffer_object.hpp @@ -397,16 +397,28 @@ VertexBufferObject::private_insert_( // size_t size_ = alloc_size_; + // rewriting entire array if (start == 0 && end == size_) { // Tested - bind(); + if (alloc_size_ >= data_size) { + bind(); - alloc_size_ = data_size; + glBufferSubData( + static_cast(Buffer), start, alloc_size_ * element_size, + data_begin + ); + size_ = data_size; - glBufferData( - static_cast(Buffer), alloc_size_ * element_size, data_begin, - GL_DYNAMIC_DRAW - ); - size_ = alloc_size_; + } else { + bind(); + + alloc_size_ = data_size; + + glBufferData( + static_cast(Buffer), alloc_size_ * element_size, data_begin, + GL_DYNAMIC_DRAW + ); + size_ = alloc_size_; + } } else if (start + data_size + (size_ - end) > alloc_size_) { // Tested // the size of the new array is large than the allocated size @@ -549,6 +561,10 @@ VertexBufferObject::private_insert_( template void VertexBufferObject::bind() const { + GlobalContext& context = GlobalContext::instance(); + if (!context.is_main_thread()) { + LOG_ERROR(logging::opengl_logger, "Calling bind from nonmain thread."); + } constexpr GPUStructureType data_type = GPUStructureType::create(); LOG_BACKTRACE( diff --git a/src/gui/render/graphics_shaders/program_handler.cpp b/src/gui/render/graphics_shaders/program_handler.cpp index e8fc137d..e326a60d 100644 --- a/src/gui/render/graphics_shaders/program_handler.cpp +++ b/src/gui/render/graphics_shaders/program_handler.cpp @@ -232,7 +232,7 @@ Program::attach_uniforms() { LOG_INFO( logging::opengl_logger, "Uniform found with id: {}, name: {}, and type {}", - uid, name, gpu_data::to_string(enum_type) + uid, str_name, gpu_data::to_string(enum_type) ); if (length > buf_size - 4) { @@ -244,8 +244,8 @@ Program::attach_uniforms() { } uniforms_.emplace( - std::piecewise_construct, std::forward_as_tuple(name), - std::forward_as_tuple(name, enum_type, uid) + std::piecewise_construct, std::forward_as_tuple(str_name), + std::forward_as_tuple(str_name, enum_type, uid) ); } } @@ -273,6 +273,10 @@ Program::set_uniform(std::shared_ptr uex, std::string uniform_n "program \"{}\".", uniform_name, name_ ); + + // Add a "do you mean..." suggestion. + // Consider Levenshtein distance less than 5 + // https://en.wikipedia.org/wiki/Levenshtein_distance } } diff --git a/src/gui/render/graphics_shaders/render_types.hpp b/src/gui/render/graphics_shaders/render_types.hpp index 09d0c7ac..c8a1353b 100644 --- a/src/gui/render/graphics_shaders/render_types.hpp +++ b/src/gui/render/graphics_shaders/render_types.hpp @@ -43,7 +43,20 @@ class FrameBuffer { render(screen_size_t width, screen_size_t height, GLuint frame_buffer) = 0; }; -// blume +/** + * @brief Render only to a section of the window + * + * @details Renders to the given section of the window. This is used for things + * that get rendered few times per frame, and have a specific location like a + * window. + */ +class ScreenSection { + public: + virtual void render( + screen_size_t x_start, screen_size_t y_start, screen_size_t width, + screen_size_t height, GLuint frame_buffer, const gpu_data::GPUData* data + ) = 0; +}; /** * @brief Defines virtual classes for writing data to object diff --git a/src/gui/render/graphics_shaders/shader_program.cpp b/src/gui/render/graphics_shaders/shader_program.cpp index 41974494..d25e079d 100644 --- a/src/gui/render/graphics_shaders/shader_program.cpp +++ b/src/gui/render/graphics_shaders/shader_program.cpp @@ -31,13 +31,16 @@ namespace gui { namespace shader { void -Render_Base::render(screen_size_t width, screen_size_t height, GLuint framebuffer_ID) { +Render_Base::render( + screen_size_t width, screen_size_t height, GLuint framebuffer_ID, + screen_size_t x_start, screen_size_t y_start +) { // Render to the screen gui::FrameBufferHandler::instance().bind_fbo(framebuffer_ID); // Render on the whole framebuffer, complete // from the lower left corner to the upper right - glViewport(0, 0, width, height); + glViewport(x_start, y_start, width, height); opengl_program_.use_program(); @@ -46,6 +49,54 @@ Render_Base::render(screen_size_t width, screen_size_t height, GLuint framebuffe opengl_program_.bind_uniforms(); } +void +ShaderProgram_Windows::render( + screen_size_t x_start, screen_size_t y_start, screen_size_t width, + screen_size_t height, GLuint framebuffer_ID, const gpu_data::GPUData* data +) { + if (!data->do_render()) { + return; + } + Render_Base::render(width, height, framebuffer_ID, x_start, y_start); + + data->bind(); + + // Draw the triangles ! + glDrawArrays( + GL_TRIANGLE_STRIP, // mode + 0, // start + data->get_num_vertices() // number of vertices + ); + + data->release(); +} + +void +ShaderProgramElements_Windows::render( + screen_size_t x_start, screen_size_t y_start, screen_size_t width, + screen_size_t height, GLuint framebuffer_ID, const gpu_data::GPUDataElements* data +) { + Render_Base::render(width, height, framebuffer_ID, x_start, y_start); + + if (!data->do_render()) { + return; + } + data->bind(); + + auto num_vertices = data->get_num_vertices(); + auto element_type = data->get_element_type(); + + // Draw the triangles ! + glDrawElements( + GL_TRIANGLES, // mode + num_vertices, // count + static_cast(element_type), // type + (void*)0 // element array buffer offset + ); + + data->release(); +} + void ShaderProgram_Standard::render( screen_size_t width, screen_size_t height, GLuint framebuffer_ID diff --git a/src/gui/render/graphics_shaders/shader_program.hpp b/src/gui/render/graphics_shaders/shader_program.hpp index cb7ae074..bde74a34 100644 --- a/src/gui/render/graphics_shaders/shader_program.hpp +++ b/src/gui/render/graphics_shaders/shader_program.hpp @@ -118,17 +118,91 @@ class Render_Base { logging::opengl_logger, "Program ID: {}, Name: {}", opengl_program_.get_program_ID(), opengl_program_.get_name() ); - // LOG_DEBUG(logging::opengl_logger, "Uniforms ID: {}", uniforms_.get_names()); - // log_uniforms(shader_program.get_detected_uniforms(), uniforms.get_names()); } - void virtual render( - screen_size_t width, screen_size_t height, GLuint framebuffer_ID + void render( + screen_size_t width, screen_size_t height, GLuint framebuffer_ID, + screen_size_t x_start = 0, screen_size_t y_start = 0 ); inline virtual ~Render_Base() {} }; +/** + * @brief Program that renders UI windows + */ +class ShaderProgram_Windows : + public virtual Render_Base, + public virtual render_to::ScreenSection { + public: + /** + * @brief Construct a new ShaderProgram_Windows object + * + * @param gui::shader::Program& shader_program shader program to be run on data + * @param std::function setup_commands function to be run to initialize + * program + */ + inline ShaderProgram_Windows( + Program& shader_program, const std::function setup_commands + ) : Render_Base(shader_program, setup_commands) {} + + inline virtual ~ShaderProgram_Windows() {} + + /** + * @brief Render to given data to the given framebuffer at the given location + * + * @param screen_size_t x_start Distance from left of start of window + * @param screen_size_t y_start Distance from top of start of window + * @param screen_size_t width Width of window + * @param screen_size_t height Height of window + * @param GLuint framebuffer_ID Framebuffer to render to + * @param gui::gpu_data::GPUData* data OpenGL vertex array buffer data to be used by + * the program + */ + void virtual render( + screen_size_t, screen_size_t y_start, screen_size_t width, screen_size_t height, + GLuint framebuffer_ID, const gpu_data::GPUData* data + ) override; +}; + +/** + * @brief Program that renders UI many windows at the same time + * + * @details eg. Text + */ +class ShaderProgramElements_Windows : public virtual Render_Base { + public: + /** + * @brief Construct a new ShaderProgramElements_Windows object + * + * @param gui::shader::Program& shader_program shader program to be run on data + * @param std::function setup_commands function to be run to initialize + * program + */ + inline ShaderProgramElements_Windows( + shader::Program& shader_program, const std::function setup_commands + ) : Render_Base(shader_program, setup_commands) {} + + inline virtual ~ShaderProgramElements_Windows() {} + + /** + * @brief Render to given data to the given framebuffer at the given location + * + * @param screen_size_t x_start Distance from left of start of window + * @param screen_size_t y_start Distance from top of start of window + * @param screen_size_t width Width of window + * @param screen_size_t height Height of window + * @param GLuint framebuffer_ID Framebuffer to render to + * @param gui::gpu_data::GPUDataElements* data OpenGL vertex array buffer data to be + * used by the program + */ + void virtual render( + screen_size_t x_start, screen_size_t y_start, screen_size_t width, + screen_size_t height, GLuint framebuffer_ID, + const gpu_data::GPUDataElements* data + ); +}; + /** * @brief No elements No instancing */ diff --git a/src/gui/render/structures/font.cpp b/src/gui/render/structures/font.cpp new file mode 100644 index 00000000..b7c39030 --- /dev/null +++ b/src/gui/render/structures/font.cpp @@ -0,0 +1,127 @@ +#include "font.hpp" + +#include "logging.hpp" +#include "util/files.hpp" +#include "util/png_image.hpp" + +#include +#include FT_FREETYPE_H + +// ^ what type of mad man write this? + +namespace gui { + +namespace render { + +namespace structures { + +FontTexture::FontTexture(std::filesystem::path font_file) { + LOG_BACKTRACE(logging::main_logger, "Loading font from {}.", font_file); + + FT_Library ft; + + if (auto error = FT_Init_FreeType(&ft)) { + LOG_WARNING(logging::main_logger, "Could not initiate FreeType. {}", error); + return; + } + + FT_Face font_face; + if (auto error = FT_New_Face(ft, font_file.c_str(), 0, &font_face)) { + LOG_WARNING(logging::main_logger, "Could not load font. {}", error); + return; + } + + // 20 is the height in pixels + FT_Set_Pixel_Sizes(font_face, 0, 20); + + auto settings = gpu_data::TextureSettings{ + .internal_format = gpu_data::GPUPixelStorageFormat::RED, + .read_format = gpu_data::GPUPixelReadFormat::RED, + .type = gpu_data::GPUPixelType::UNSIGNED_BYTE, + .min_filter = GL_NEAREST, + .mag_filter = GL_NEAREST + }; + + unsigned int max_height = 0; + unsigned int total_width = 0; + + std::unordered_map images; + + descender_height_ = -font_face->descender; + ascender_height_ = font_face->ascender; + text_height_ = font_face->ascender + descender_height_; + + LOG_BACKTRACE(logging::main_logger, "Creating images for each character."); + for (unsigned char c = 0; c < 128; c++) { + if (FT_Load_Char(font_face, c, FT_LOAD_RENDER | FT_LOAD_MONOCHROME)) { + LOG_WARNING(logging::main_logger, "Failed to load \"{}\" from font.", c); + continue; + } + + auto char_size = + glm::uvec2(font_face->glyph->bitmap.width, font_face->glyph->bitmap.rows); + auto char_position = + glm::uvec2(font_face->glyph->bitmap_left, font_face->glyph->bitmap_top); + + std::vector data; + data.resize(char_size.x * char_size.y); + for (size_t i = 0; i < font_face->glyph->bitmap.width; i++) { + for (size_t j = 0; j < font_face->glyph->bitmap.rows; j++) { + png_byte font_bit = + font_face->glyph->bitmap + .buffer[j * font_face->glyph->bitmap.pitch + i / 8]; + uint8_t one = 1; + uint8_t value = font_bit >> (7 - i % 8); + data[i * font_face->glyph->bitmap.rows + j] = (value & one) * 255; + } + } + + images.emplace( + c, util::image::ByteMonochromeImage( + data.data(), char_size.x, char_size.y, sizeof(char) + ) + ); + + font_textures_.emplace( + c, + Character{ + .size = char_size, + .bearing = char_position, + .advance = static_cast(font_face->glyph->advance.x), + .position_in_texture = + glm::ivec4(total_width, 0, total_width + char_size.x, char_size.y) + } + ); + + total_width += char_size.x; + if (char_size.y > max_height) { + max_height = char_size.y; + } + } + + LOG_BACKTRACE(logging::main_logger, "Combining characters into single image."); + auto image = std::make_shared( + total_width, max_height, sizeof(char) + ); + + for (unsigned char c = 0; c < 128; c++) { + image->draw_at( + images.at(c), font_textures_[c].position_in_texture.x, + font_textures_[c].position_in_texture.y + ); + } + + LOG_BACKTRACE(logging::main_logger, "Loading font to texture."); + + image->transpose(); + texture_ = std::make_shared(image, settings); + + GlobalContext& global_context = GlobalContext::instance(); + global_context.run_opengl_queue(); +} + +} // namespace structures + +} // namespace render + +} // namespace gui diff --git a/src/gui/render/structures/font.hpp b/src/gui/render/structures/font.hpp new file mode 100644 index 00000000..9011018f --- /dev/null +++ b/src/gui/render/structures/font.hpp @@ -0,0 +1,144 @@ +// -*- lsst-c++ -*- +/* + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +/** + * @file font.hpp + * + * @author @AlemSnyder + * + * @brief Defines FontTexture Class + * + * @ingroup GUI RENDER STRUCTURES + * + */ + +#pragma once + +#include "../gpu_data/texture.hpp" + +#include +#include + +namespace gui { + +namespace render { + +namespace structures { + +/** + * @brief Data that defines size and location in texture of a character. + */ +struct Character { + glm::uvec2 size; + glm::ivec2 bearing; + unsigned int advance; + glm::ivec4 position_in_texture; +}; + +/** + * @brief Font data saved as an OpenGL texture. + */ +class FontTexture { + private: + std::shared_ptr texture_; + + // map of char to location (represented as a Character) in texture of the rasterred + // font + std::unordered_map font_textures_; + + // Maximum height of any character in pixels. + uint16_t text_height_; + // the distance from the baseline to the top of the character + uint16_t ascender_height_; + // Distance from baseline to bottom or character + uint16_t descender_height_; + + public: + /** + * @brief Construct a new FontTexture Object from file. + * + * @param std::filesystem::path font_file file to read font from. + */ + FontTexture(std::filesystem::path font_file); + + /** + * @brief Bind to texture index + * + * @details texture_index is the input in a shader program ie. GL_TEXTURE#. + * + * @param GLuint texture_index index of texture + */ + inline virtual void + bind(GLuint texture_index) const { + texture_->bind(texture_index); + } + + /** + * @brief Get pointer to Character for given char + * + * @param char character Character to quarry texture location of. + * + * @return const Character* pointer to location in texture of rasterred character or + * nullptr if not found. + */ + [[nodiscard]] inline const Character* + get_character(char character) const { + const auto data = font_textures_.find(character); + if (data != font_textures_.end()) { + return &data->second; + } else { + LOG_WARNING( + logging::main_logger, + "Could not find character \"{}\" with value \"{}\" " + "in font.", + std::string(1, character), static_cast(character) + ); + return nullptr; + } + } + + /** + * @brief Get the height of the font + * + * @return uint16_t height of text + */ + [[nodiscard]] inline auto + get_text_height() const { + return text_height_; + } + + /** + * @brief Get the ascender height + * + * @return uint16_t ascender height + */ + [[nodiscard]] inline auto + get_ascender_height() const { + return ascender_height_; + } + + /** + * @brief Get the descender height + * + * @return uint16_t descender height + */ + [[nodiscard]] inline auto + get_descender_height() const { + return descender_height_; + } +}; + +} // namespace structures + +} // namespace render + +} // namespace gui diff --git a/src/gui/render/structures/i_mesh.hpp b/src/gui/render/structures/i_mesh.hpp index 4a54e449..4d501072 100644 --- a/src/gui/render/structures/i_mesh.hpp +++ b/src/gui/render/structures/i_mesh.hpp @@ -85,15 +85,17 @@ class IMeshGPU : virtual public GPUDataElements { * @param util::Mesh& mesh to load * @param bool b set to false when calling this constructor when inherited */ - explicit inline IMeshGPU(const util::Mesh& mesh, bool b = true) : + explicit inline IMeshGPU(const util::Mesh& mesh, bool differed = true) : vertex_array_(mesh.get_indexed_vertices()), color_array_(mesh.get_indexed_color_ids()), normal_array_(mesh.get_indexed_normals()), element_array_(mesh.get_indices()), num_vertices_(mesh.get_indices().size()), do_render_(mesh.get_indices().size()) { - if (b) { + if (differed) { GlobalContext& context = GlobalContext::instance(); context.push_opengl_task([this]() { initialize(); }); + } else { + initialize(); } } diff --git a/src/gui/render/structures/uniform_types.hpp b/src/gui/render/structures/uniform_types.hpp index a1a006b0..9a32e1d9 100644 --- a/src/gui/render/structures/uniform_types.hpp +++ b/src/gui/render/structures/uniform_types.hpp @@ -105,7 +105,6 @@ class SpectralLight : public shader::UniformExecutor { sunlight_color.r, sunlight_color.g, sunlight_color.b ); - // here glUniform3f(uniform_ID, sunlight_color.r, sunlight_color.g, sunlight_color.b); } }; @@ -327,6 +326,180 @@ class StarRotationUniform : public shader::UniformExecutor { } }; +class FrameSizeUniform : public shader::UniformExecutor { + private: + glm::ivec2 frame_size_; + + public: + FrameSizeUniform() : + UniformExecutor(gpu_data::GPUArayType::INT_VEC2), frame_size_(0, 0) {} + + inline void + set_frame_size(glm::ivec2 frame_size) { + frame_size_ = frame_size; + } + + inline virtual ~FrameSizeUniform() {} + + inline virtual void + bind(GLint uniform_ID) const override { + LOG_BACKTRACE( + logging::opengl_logger, "Uniform {}, value {}, {} being initialized.", + uniform_ID, frame_size_.x, frame_size_.y + ); + + glUniform2i(uniform_ID, frame_size_.x, frame_size_.y); + } +}; + +class UIScaleUniform : public shader::UniformExecutor { + private: + uint8_t ui_scale_; + + public: + UIScaleUniform(uint8_t scale) : + UniformExecutor(gpu_data::GPUArayType::INT), ui_scale_(scale) {} + + inline void + set_ui_scale(uint8_t ui_scale) { + ui_scale_ = ui_scale; + } + + inline virtual ~UIScaleUniform() {} + + inline virtual void + bind(GLint uniform_ID) const override { + LOG_BACKTRACE( + logging::opengl_logger, "Uniform {}, value {} being initialized.", + uniform_ID, ui_scale_ + ); + + glUniform1i(uniform_ID, int(ui_scale_)); + } +}; + +class FrameBorderSizeUniform : public shader::UniformExecutor { + private: + glm::ivec4 border_size_; + + public: + FrameBorderSizeUniform() : UniformExecutor(gpu_data::GPUArayType::INT_VEC4) {} + + inline void + set_border_size(glm::ivec4 border_size) { + border_size_ = border_size; + } + + inline virtual ~FrameBorderSizeUniform() {} + + inline virtual void + bind(GLint uniform_ID) const override { + LOG_BACKTRACE( + logging::opengl_logger, "Uniform {} being initialized.", uniform_ID + ); + + glUniform4iv(uniform_ID, 1, &border_size_[0]); + } +}; + +class FrameSideLengthsUniform : public shader::UniformExecutor { + private: + glm::ivec4 side_length_; + + public: + FrameSideLengthsUniform() : UniformExecutor(gpu_data::GPUArayType::INT_VEC4) {} + + inline void + set_side_lengths(glm::ivec4 side_length) { + side_length_ = side_length; + } + + inline virtual ~FrameSideLengthsUniform() {} + + inline virtual void + bind(GLint uniform_ID) const override { + LOG_BACKTRACE( + logging::opengl_logger, "Uniform {} being initialized.", uniform_ID + ); + + glUniform4iv(uniform_ID, 1, &side_length_[0]); + } +}; + +class InnerPatternSizeUniform : public shader::UniformExecutor { + private: + glm::ivec2 pattern_size_; + + public: + InnerPatternSizeUniform() : UniformExecutor(gpu_data::GPUArayType::INT_VEC2) {} + + inline void + set_inner_pattern_size(glm::ivec2 pattern_size) { + pattern_size_ = pattern_size; + } + + inline virtual ~InnerPatternSizeUniform() {} + + inline virtual void + bind(GLint uniform_ID) const override { + LOG_BACKTRACE( + logging::opengl_logger, "Uniform {} being initialized.", uniform_ID + ); + + glUniform2iv(uniform_ID, 1, &pattern_size_[0]); + } +}; + +class TextureRegionsUniform : public shader::UniformExecutor { + private: + std::array texture_location_; + + public: + TextureRegionsUniform() : UniformExecutor(gpu_data::GPUArayType::INT_VEC2) {} + + inline void + set_texture_regions(std::array texture_location) { + texture_location_ = texture_location; + } + + inline virtual ~TextureRegionsUniform() {} + + inline virtual void + bind(GLint uniform_ID) const override { + LOG_BACKTRACE( + logging::opengl_logger, "Uniform {} being initialized.", uniform_ID + ); + + glUniform2iv(uniform_ID, 9, &(*texture_location_.data())[0]); + } +}; + +class FloatColorUniform : public shader::UniformExecutor { + private: + ColorFloat color_; + + public: + FloatColorUniform() : UniformExecutor(gpu_data::GPUArayType::FLOAT_VEC4) {} + + virtual ~FloatColorUniform() {}; + + inline virtual void + bind(GLint uniform_ID) const override { + LOG_BACKTRACE( + logging::opengl_logger, + "Uniform {}, value: ({}, {}, {}, {}), being initialized.", uniform_ID, + color_.r, color_.g, color_.b, color_.a + ); + + glUniform4f(uniform_ID, color_.r, color_.g, color_.b, color_.a); + } + + inline void + set_color(const ColorFloat&& color) { + color_ = color; + } +}; + } // namespace render } // namespace gui diff --git a/src/gui/render/structures/window_texture.cpp b/src/gui/render/structures/window_texture.cpp new file mode 100644 index 00000000..d54fa108 --- /dev/null +++ b/src/gui/render/structures/window_texture.cpp @@ -0,0 +1,66 @@ +#include "window_texture.hpp" + +#include "logging.hpp" +#include "util/files.hpp" +#include "util/png_image.hpp" + +#include + +#include + +namespace gui { + +namespace render { + +WindowTexture::WindowTexture( + std::shared_ptr image, glm::ivec4 border_size, + glm::ivec4 side_lengths, glm::ivec2 inner_pattern_size, + std::array texture_regions +) : + // clang-format off + gl_positions_{ + glm::vec3(-1, -1, 0), + glm::vec3(-1, 1, 0), + glm::vec3(1, -1, 0), + glm::vec3(1, 1, 0) + }, + screen_positions_{ + glm::vec2(-1, -1), + glm::vec2(-1, 1), + glm::vec2(1, -1), + glm::vec2(0, 0)}, + // clang-format on + num_vertices_(4), + border_texture_( + image, + gui::gpu_data::TextureSettings{ + .internal_format = gui::gpu_data::GPUPixelStorageFormat::RGBA8UI, + .read_format = gui::gpu_data::GPUPixelReadFormat::RGBA_INTEGER, + .type = gui::gpu_data::GPUPixelType::UNSIGNED_BYTE, + .min_filter = GL_NEAREST, + .mag_filter = GL_NEAREST + } + ), + border_size_(border_size), side_lengths_(side_lengths), + inner_pattern_size_(inner_pattern_size), texture_regions_(texture_regions) { + LOG_DEBUG(logging::main_logger, "Initializing a WindowTexture"); + GlobalContext& context = GlobalContext::instance(); + context.push_opengl_task([this]() { + vertex_array_object_.bind(); + gl_positions_.attach_to_vertex_attribute(0); + screen_positions_.attach_to_vertex_attribute(1); + vertex_array_object_.release(); + }); +} + +WindowTexture::WindowTexture( + std::shared_ptr image, const window_texture_data_t& texture_data +) : + WindowTexture( + image, texture_data.border_size, texture_data.side_lengths, + texture_data.inner_pattern_size, texture_data.texture_regions + ) {} + +} // namespace render + +} // namespace gui diff --git a/src/gui/render/structures/window_texture.hpp b/src/gui/render/structures/window_texture.hpp new file mode 100644 index 00000000..7860d26d --- /dev/null +++ b/src/gui/render/structures/window_texture.hpp @@ -0,0 +1,222 @@ +#pragma once + +#include "gui/render/gpu_data/data_types.hpp" +#include "gui/render/gpu_data/texture.hpp" +#include "gui/render/gpu_data/vertex_array_object.hpp" +#include "gui/render/gpu_data/vertex_buffer_object.hpp" +#include "screen_data.hpp" +#include "util/image.hpp" + +#include + +#include +#include + +namespace gui { + +namespace render { + +struct window_texture_data_t { + std::filesystem::path texture_file; + glm::ivec4 border_size; + glm::ivec4 side_lengths; + glm::ivec2 inner_pattern_size; + std::array texture_regions; + + inline std::array + border_size_write() { + return {border_size[0], border_size[1], border_size[2], border_size[3]}; + } + + inline void + border_size_read(std::array border_size_in) { + border_size = glm::ivec4( + border_size_in[0], border_size_in[1], border_size_in[2], border_size_in[3] + ); + } + + inline std::array + side_lengths_write() { + return {side_lengths[0], side_lengths[1], side_lengths[2], side_lengths[3]}; + } + + inline void + side_lengths_read(std::array side_lengths_in) { + side_lengths = glm::ivec4( + side_lengths_in[0], side_lengths_in[1], side_lengths_in[2], + side_lengths_in[3] + ); + } + + inline std::array + inner_pattern_size_write() { + return {inner_pattern_size[0], inner_pattern_size[1]}; + } + + inline void + inner_pattern_size_read(std::array inner_pattern_size_in) { + inner_pattern_size = + glm::ivec2(inner_pattern_size_in[0], inner_pattern_size_in[1]); + } + + inline std::array, 9> + texture_regions_write() { + return { + std::array({texture_regions[0].x, texture_regions[0].y}), + std::array({texture_regions[1].x, texture_regions[1].y}), + std::array({texture_regions[2].x, texture_regions[2].y}), + std::array({texture_regions[3].x, texture_regions[3].y}), + std::array({texture_regions[4].x, texture_regions[4].y}), + std::array({texture_regions[5].x, texture_regions[5].y}), + std::array({texture_regions[6].x, texture_regions[6].y}), + std::array({texture_regions[7].x, texture_regions[7].y}), + std::array({texture_regions[8].x, texture_regions[8].y}) + }; + } + + inline void + texture_regions_read(std::array, 9> texture_regions_in) { + texture_regions = std::array( + {glm::ivec2(texture_regions_in[0][0], texture_regions_in[0][1]), + glm::ivec2(texture_regions_in[1][0], texture_regions_in[1][1]), + glm::ivec2(texture_regions_in[2][0], texture_regions_in[2][1]), + glm::ivec2(texture_regions_in[3][0], texture_regions_in[3][1]), + glm::ivec2(texture_regions_in[4][0], texture_regions_in[4][1]), + glm::ivec2(texture_regions_in[5][0], texture_regions_in[5][1]), + glm::ivec2(texture_regions_in[6][0], texture_regions_in[6][1]), + glm::ivec2(texture_regions_in[7][0], texture_regions_in[7][1]), + glm::ivec2(texture_regions_in[8][0], texture_regions_in[8][1])} + ); + } +}; + +class WindowTexture : public virtual gpu_data::GPUData { + private: + // & to two things above + gpu_data::VertexArrayObject vertex_array_object_; + + gpu_data::VertexBufferObject gl_positions_; + gpu_data::VertexBufferObject screen_positions_; + unsigned int num_vertices_; + + gpu_data::Texture2D border_texture_; + + glm::ivec4 border_size_; + glm::ivec4 side_lengths_; + glm::ivec2 inner_pattern_size_; + std::array texture_regions_; + + public: + /** + * @brief Deleted copy constructor + */ + WindowTexture(const WindowTexture& obj) = delete; + + /** + * @brief Deleted copy operator + */ + WindowTexture& operator=(const WindowTexture& obj) = delete; + + /** + * @brief Default move constructor + */ + WindowTexture(WindowTexture&& obj) = default; + + /** + * @brief Default move constructor + */ + WindowTexture& operator=(WindowTexture&& obj) = default; + + /** + * @brief Construct a new Screen Data object, default constructor + * + */ + WindowTexture( + std::shared_ptr image, glm::ivec4 border_size, + glm::ivec4 side_lengths, glm::ivec2 inner_pattern_size, + std::array texture_regions + ); + + WindowTexture( + std::shared_ptr image, + const window_texture_data_t& texture_data + ); + + inline virtual ~WindowTexture() {} + + /** + * @brief Get the number of vertices + * + * @return unsigned int 4 (it is a rectangle) + */ + inline unsigned int + get_num_vertices() const { + return num_vertices_; + } + + [[nodiscard]] inline auto + get_border_size() const { + return border_size_; + } + + [[nodiscard]] inline auto + get_side_lengths() const { + return side_lengths_; + } + + [[nodiscard]] inline auto + get_inner_pattern_size() const { + return inner_pattern_size_; + } + + [[nodiscard]] inline auto + get_texture_regions() const { + return texture_regions_; + } + + inline virtual void + bind() const { + vertex_array_object_.bind(); + border_texture_.bind(0); + }; + + inline virtual void + release() const { + vertex_array_object_.release(); + } + + inline virtual bool + do_render() const { + return true; + }; + + inline void + update_position(std::array positions) { + screen_size_t x_diff = positions[2] - positions[0]; + screen_size_t y_diff = positions[3] - positions[1]; + + std::vector vertex_data( + {glm::vec2(x_diff, y_diff), glm::vec2(x_diff, 0), glm::vec2(0, y_diff), + glm::vec2(0, 0)} + ); + + screen_positions_.update(vertex_data); + } +}; + +} // namespace render + +} // namespace gui + +template <> +struct glz::meta { + using T = gui::render::window_texture_data_t; + + static constexpr auto value = object( + "texture_file", &T::texture_file, "border_size", + custom<&T::border_size_read, &T::border_size_write>, "side_lengths", + custom<&T::side_lengths_read, &T::side_lengths_write>, "inner_pattern_size", + custom<&T::inner_pattern_size_read, &T::inner_pattern_size_write>, + "texture_regions", custom<&T::texture_regions_read, &T::texture_regions_write> + ); +}; diff --git a/src/gui/scene/controls.cpp b/src/gui/scene/controls.cpp index 1c15c5ea..5bd0dde6 100644 --- a/src/gui/scene/controls.cpp +++ b/src/gui/scene/controls.cpp @@ -94,13 +94,12 @@ Controls::handle_pooled_inputs(GLFWwindow* window) { ); // display range max // Camera matrix view_matrix_ = glm::lookAt( - position_, // Camera is here - position_ - + direction, // and looks here : at the same position_, plus "direction" - screen_up // Head is up (set to 0,-1,0 to look upside-down) + position_, // Camera is here + position_ + direction, // look along direction + screen_up // set up direction ); - // For the next frame, the "last time" will be "now" + // Update time glfw_previous_time_ = currentTime; } @@ -118,15 +117,15 @@ Controls::handle_mouse_scroll_input( void Controls::handle_mouse_button_input( - GLFWwindow* window, int button, int action, int mods + GLFWwindow* window, int button, int action, [[maybe_unused]] int mods ) { if (static_cast(button) == Key::MOUSE_LEFT && action == GLFW_PRESS) { double xpos; double ypos; glfwGetCursorPos(window, &xpos, &ypos); LOG_DEBUG(logging::main_logger, "Mouse Position: [{}, {}]", xpos, ypos); - int xpos_int = floor(xpos); - int ypos_int = floor(ypos); + // int xpos_int = floor(xpos); + // int ypos_int = floor(ypos); } } diff --git a/src/gui/scene/input.cpp b/src/gui/scene/input.cpp index 60133bc7..01150a02 100644 --- a/src/gui/scene/input.cpp +++ b/src/gui/scene/input.cpp @@ -10,6 +10,7 @@ namespace scene { GLFWwindow* InputHandler::window_ = nullptr; std::shared_ptr InputHandler::forward_inputs_ = nullptr; bool InputHandler::escape_pressed_ = false; +bool InputHandler::imgui_active = false; void InputHandler::set_window(GLFWwindow* window) { @@ -37,7 +38,6 @@ InputHandler::forward_inputs_to(std::shared_ptr forward_to) { if (forward_to == nullptr) { // somehow prevent forwarding // this is ostensibly not a good idea - assert(forward_to != nullptr && "Must forward inputs to valid Inputs object."); } else { forward_inputs_->setup(window_); } @@ -45,6 +45,9 @@ InputHandler::forward_inputs_to(std::shared_ptr forward_to) { bool InputHandler::imgui_capture() { + if (!imgui_active) { + return false; + } ImGuiIO& io = ImGui::GetIO(); return (io.WantCaptureKeyboard || io.WantCaptureMouse || io.WantTextInput); } @@ -71,6 +74,7 @@ InputHandler::handle_key_event( escape_pressed_ = false; } } + assert(forward_inputs_ != nullptr && "Must forward inputs to valid Inputs object."); forward_inputs_->handle_key_event_input(window, key, scancode, action, mods); } @@ -79,6 +83,7 @@ InputHandler::handle_text_input(GLFWwindow* window, unsigned int codepoint) { if (imgui_capture()) { return; } + assert(forward_inputs_ != nullptr && "Must forward inputs to valid Inputs object."); forward_inputs_->handle_text_input_input(window, codepoint); } @@ -87,6 +92,7 @@ InputHandler::handle_mouse_event(GLFWwindow* window, double xpos, double ypos) { if (imgui_capture()) { return; } + assert(forward_inputs_ != nullptr && "Must forward inputs to valid Inputs object."); forward_inputs_->handle_mouse_event_input(window, xpos, ypos); } @@ -95,6 +101,7 @@ InputHandler::handle_mouse_enter(GLFWwindow* window, int enter) { if (imgui_capture()) { return; } + assert(forward_inputs_ != nullptr && "Must forward inputs to valid Inputs object."); forward_inputs_->handle_mouse_enter_input(window, enter); } @@ -105,6 +112,7 @@ InputHandler::handle_mouse_button( if (imgui_capture()) { return; } + assert(forward_inputs_ != nullptr && "Must forward inputs to valid Inputs object."); forward_inputs_->handle_mouse_button_input(window, button, action, mods); } @@ -113,6 +121,7 @@ InputHandler::handle_mouse_scroll(GLFWwindow* window, double xoffset, double yof if (imgui_capture()) { return; } + assert(forward_inputs_ != nullptr && "Must forward inputs to valid Inputs object."); forward_inputs_->handle_mouse_scroll_input(window, xoffset, yoffset); } @@ -121,6 +130,7 @@ InputHandler::handle_joystick(int jid, int event) { if (imgui_capture()) { return; } + assert(forward_inputs_ != nullptr && "Must forward inputs to valid Inputs object."); forward_inputs_->handle_joystick_input(jid, event); } @@ -129,6 +139,7 @@ InputHandler::handle_file_drop(GLFWwindow* window, int count, const char** paths if (imgui_capture()) { return; } + assert(forward_inputs_ != nullptr && "Must forward inputs to valid Inputs object."); forward_inputs_->handle_file_drop_input(window, count, paths); } @@ -137,6 +148,7 @@ InputHandler::handle_pooled_inputs(GLFWwindow* window) { if (imgui_capture()) { return; } + assert(forward_inputs_ != nullptr && "Must forward inputs to valid Inputs object."); forward_inputs_->handle_pooled_inputs(window); } diff --git a/src/gui/scene/input.hpp b/src/gui/scene/input.hpp index 37406e7e..78c08755 100644 --- a/src/gui/scene/input.hpp +++ b/src/gui/scene/input.hpp @@ -42,7 +42,7 @@ class Inputs { * @param int action GLFW action one of GLFW_PRESS, GLFW_RELEASE, GLFW_REPEAT * @param int mods GLFW mods enum */ - void + virtual void handle_key_event_input( [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int key, [[maybe_unused]] int scancode, [[maybe_unused]] int action, @@ -165,6 +165,9 @@ class Inputs { class InputHandler { // somehow the plan was to only let GlobalContext set the window + public: + static bool imgui_active; + protected: // so realistically we would have a unordered map that maps the window // but im not making multiple windows diff --git a/src/gui/scene/keymapping.hpp b/src/gui/scene/keymapping.hpp index c418e906..6b702c25 100644 --- a/src/gui/scene/keymapping.hpp +++ b/src/gui/scene/keymapping.hpp @@ -4,6 +4,7 @@ #include +#include #include #include @@ -424,6 +425,61 @@ to_string(const Key& key) { } } +enum KeyAction { PRESS = GLFW_PRESS, RELEASE = GLFW_RELEASE, REPEAT = GLFW_REPEAT }; + +constexpr inline std::string +to_string(const KeyAction& key) { + switch (key) { + case KeyAction::PRESS: + return "KEY_PRESS"; + case KeyAction::RELEASE: + return "KEY_RELEASE"; + case KeyAction::REPEAT: + return "KEY_REPEAT"; + default: + throw exc::not_implemented_error("Unknown key action"); + } +} + +enum KeyModifier { + SHIFT = GLFW_MOD_SHIFT, + CONTROL = GLFW_MOD_CONTROL, + ALT = GLFW_MOD_ALT, + SUPER = GLFW_MOD_SUPER, + CAPS_LOCK = GLFW_MOD_CAPS_LOCK, + NUM_LOCK = GLFW_MOD_NUM_LOCK +}; + +/* +// TODO want to make this constexpr +// ftm::join and fmt format are not cont expr +inline std::string +to_string(const KeyModifier& key) { + +std::vector mods; +// std::string mods = ""; +if (key | KeyModifier::SHIFT) { + mods.push_back("SHIFT"); +} if (key | KeyModifier::CONTROL) { + mods.push_back("CONTROL"); +} +if (key | KeyModifier::ALT) { + mods.push_back("ALT"); +} +if (key | KeyModifier::SUPER) { + mods.push_back("SUPER"); +} +if (key | KeyModifier::CAPS_LOCK) { + mods.push_back("CAPS_LOCK"); +} +if (key | KeyModifier::NUM_LOCK) { + mods.push_back("NUM_LOCK"); +} + +return fmtquill::format("{}", fmtquill::join(mods, " & ")); +} +*/ + namespace scene { enum Action : int { diff --git a/src/gui/scene/scene.hpp b/src/gui/scene/scene.hpp index 7700f90f..9f66eb48 100644 --- a/src/gui/scene/scene.hpp +++ b/src/gui/scene/scene.hpp @@ -28,6 +28,7 @@ #include "gui/render/gpu_data/shadow_map.hpp" #include "gui/render/graphics_shaders/render_types.hpp" #include "gui/render/structures/screen_data.hpp" +#include "gui/the_buttons/bordered_window.hpp" #include "helio.hpp" #include diff --git a/src/gui/the_buttons/README.md b/src/gui/the_buttons/README.md new file mode 100644 index 00000000..d0207825 --- /dev/null +++ b/src/gui/the_buttons/README.md @@ -0,0 +1,46 @@ +# Click + +Check between scene and ui + +if scene forward to scene, + +if ui forward to ui + +might want to run deselect + +# UI framework + +Splash screen / loading screen + +Intro screen + - New Game + - Load Game + - Settings + - Pedia / License / Copyright + + +# Child Parent Frame Relationship + +todo: + - do all TODOs in the buttons + - need to rename "the buttons" + - need to refactor all the file names and what not + + could make widow base that widget and frame inherit from. + + want to reduce the amount of code duplications + - want to start making buttons and other things + = buttons + - text + - radio buttons + - enums + - key select (for keybinding) + - toggle button + + > text rendering + - mouse click enums + + in fact the top level inputs should handle keybinding + + everything else should take enums, and maybe screen coordinates + > widget needs a get global position function. + - need a seconds inputs template that uses the types I created + + object has the frame + each interaction with the widgets get forwarded to the object. diff --git a/src/gui/the_buttons/bordered_widget.cpp b/src/gui/the_buttons/bordered_widget.cpp new file mode 100644 index 00000000..25f14e30 --- /dev/null +++ b/src/gui/the_buttons/bordered_widget.cpp @@ -0,0 +1,22 @@ +#include "bordered_widget.hpp" + +#include "user_interface.hpp" + +namespace gui { +namespace the_buttons { + +void +BorderedWidget::user_interface_render( + const UserInterface* user_interface, screen_size_t x_position, + screen_size_t y_position +) const { + user_interface->render_frame(this, x_position, y_position); + + for (const auto& child : children) { + child->user_interface_render( + user_interface, x_position + position_.x, y_position + position_.y + ); + } +} +} // namespace the_buttons +} // namespace gui \ No newline at end of file diff --git a/src/gui/the_buttons/bordered_widget.hpp b/src/gui/the_buttons/bordered_widget.hpp new file mode 100644 index 00000000..c5d31d95 --- /dev/null +++ b/src/gui/the_buttons/bordered_widget.hpp @@ -0,0 +1,150 @@ +// -*- lsst-c++ -*- +/* + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +/** + * @file bordered_widget.hpp + * + * @author @AlemSnyder + * + * @brief Defines BorderedWidget Class + * + * @ingroup GUI THE_BUTTONS + * + */ + +#pragma once + +#include "bordered_widget.hpp" +#include "frame_part.hpp" +#include "gui/render/structures/window_texture.hpp" +#include "types.hpp" +#include "widget.hpp" + +#include + +namespace gui { + +namespace the_buttons { + +/** + * @brief A widget with a border. + */ +class BorderedWidget : public virtual WidgetBase { + private: + std::shared_ptr data_; + + public: + BorderedWidget() = delete; + /** + * @brief Deleted copy constructor + */ + BorderedWidget(const BorderedWidget& obj) = delete; + + /** + * @brief Deleted copy operator + */ + BorderedWidget& operator=(const BorderedWidget& obj) = delete; + + /** + * @brief Default move constructor + */ + BorderedWidget(BorderedWidget&& obj) = delete; + + /** + * @brief Default move constructor + */ + BorderedWidget& operator=(BorderedWidget&& obj) = delete; + + inline BorderedWidget( + WidgetInterface* parent, std::shared_ptr data, + glm::ivec2 position, glm::ivec2 widget_size + ) : + WidgetBase( + parent, position, widget_size, + {position, glm::ivec2(position.x + widget_size.x, position.y), + position + widget_size, glm::ivec2(position.x, position.y + widget_size.y)} + ), + + data_(data) { + data_->update_position(get_bounding_box()); + } + + inline virtual ~BorderedWidget() {}; + + inline unsigned int + get_num_vertices() const { + return data_->get_num_vertices(); + } + + /** + * @brief Bind OpenGL data to use in a program + */ + inline virtual void + bind() const { + data_->bind(); + }; + + /** + * @brief Clears the OpenGL vertex array inputs + */ + inline virtual void + release() const { + data_->release(); + } + + /** + * @brief If the widget should be rendered + * + * @return bool true if the widget should be rendered, false otherwise. + */ + inline virtual bool + do_render() const { + return data_->do_render(); + }; + + /** + * @brief Called by the UI to render this widget to the screen + * + * @param UserInterface* user_interface The UserInterface that is rendering the + * widget + * @param screen_size_t x_position Position on the screen to render the widget + * @param screen_size_t y_position Position on the screen to render the widget + */ + void user_interface_render( + const UserInterface* user_interface, screen_size_t x_position, + screen_size_t y_position + ) const override; + + [[nodiscard]] inline virtual glm::ivec4 + get_border_size() const { + return data_->get_border_size(); + } + + [[nodiscard]] inline virtual glm::ivec4 + get_side_lengths() const { + return data_->get_side_lengths(); + } + + [[nodiscard]] inline virtual glm::ivec2 + get_inner_pattern_size() const { + return data_->get_inner_pattern_size(); + } + + [[nodiscard]] inline virtual std::array + get_texture_regions() const { + return data_->get_texture_regions(); + } +}; + +} // namespace the_buttons + +} // namespace gui \ No newline at end of file diff --git a/src/gui/the_buttons/bordered_window.cpp b/src/gui/the_buttons/bordered_window.cpp new file mode 100644 index 00000000..559620d6 --- /dev/null +++ b/src/gui/the_buttons/bordered_window.cpp @@ -0,0 +1,37 @@ +#include "bordered_window.hpp" + +#include "user_interface.hpp" + +namespace gui { + +namespace the_buttons { + +BorderedWindow::BorderedWindow( + std::shared_ptr data, glm::ivec2 position, + glm::ivec2 frame_size +) : + FrameBase( + position, frame_size, + {position, glm::ivec2(position.x + frame_size.x, position.y), + position + frame_size, glm::ivec2(position.x, position.y + frame_size.y)} + ), + data_(data) { + data_->update_position(get_bounding_box()); +} + +void +BorderedWindow::user_interface_render( + const UserInterface* user_interface, screen_size_t x_position, + screen_size_t y_position +) const { + user_interface->render_frame(this, x_position, y_position); + + for (const auto& child : children_) { + child->user_interface_render( + user_interface, x_position + position_.x, y_position + position_.y + ); + } +} + +} // namespace the_buttons +} // namespace gui diff --git a/src/gui/the_buttons/bordered_window.hpp b/src/gui/the_buttons/bordered_window.hpp new file mode 100644 index 00000000..95ec8748 --- /dev/null +++ b/src/gui/the_buttons/bordered_window.hpp @@ -0,0 +1,132 @@ +// -*- lsst-c++ -*- +/* + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +/** + * @file bordered_window.hpp + * + * @author @AlemSnyder + * + * @brief Defines BorderedWindow Class + * + * @ingroup GUI THE_BUTTONS + * + */ + +#pragma once + +#include "frame.hpp" +#include "gui/render/structures/window_texture.hpp" + +#include + +namespace gui { + +namespace the_buttons { + +class BorderedWindow : public virtual FrameBase { + private: + std::shared_ptr data_; + + public: + BorderedWindow() = delete; + /** + * @brief Deleted copy constructor + */ + BorderedWindow(const BorderedWindow& obj) = delete; + + /** + * @brief Deleted copy operator + */ + BorderedWindow& operator=(const BorderedWindow& obj) = delete; + + /** + * @brief Default move constructor + */ + BorderedWindow(BorderedWindow&& obj) = default; + + /** + * @brief Default move constructor + */ + BorderedWindow& operator=(BorderedWindow&& obj) = default; + + BorderedWindow( + std::shared_ptr data, glm::ivec2 position, + glm::ivec2 size + ); + + inline virtual ~BorderedWindow() {}; + + /** + * @brief Get number of vertices on path that surrounds this widget. + * @return unsigned int the number of vertices + */ + inline unsigned int + get_num_vertices() const { + return data_->get_num_vertices(); + } + + /** + * @brief Bind OpenGL data to use in a program + */ + inline virtual void + bind() const { + data_->bind(); + }; + + /** + * @brief Clears the OpenGL vertex array inputs + */ + inline virtual void + release() const { + data_->release(); + } + + /** + * @brief If the widget should be rendered + * + * @return bool true if the widget should be rendered, false otherwise. + */ + inline virtual bool + do_render() const { + return data_->do_render(); + }; + + /** + * @brief Get the box that bounds of the widget. + * + * @details This should be the smallest rectangle that completely contains the + * widget. + * + * @return std::array [x position, y position, width, height] + */ + inline virtual std::array + get_bounding_box() const { + return {position_.x, position_.y, frame_size_.x, frame_size_.y}; + } + + /** + * @brief Called by the UI to render this widget to the screen + * + * @param UserInterface* user_interface The UserInterface that is rendering the + * widget + * @param screen_size_t x_position Position on the screen to render the widget + * @param screen_size_t y_position Position on the screen to render the widget + */ + virtual void user_interface_render( + const UserInterface* user_interface, screen_size_t x_position, + screen_size_t y_position + ) const override; +}; + +} // namespace the_buttons + +} // namespace gui diff --git a/src/gui/the_buttons/button_widget.hpp b/src/gui/the_buttons/button_widget.hpp new file mode 100644 index 00000000..0116e9cd --- /dev/null +++ b/src/gui/the_buttons/button_widget.hpp @@ -0,0 +1,217 @@ +// -*- lsst-c++ -*- +/* + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +/** + * @file button_widget.hpp + * + * @author @AlemSnyder + * + * @brief Defines ButtonWidget Class + * + * @ingroup GUI THE_BUTTONS + * + */ + +#pragma once + +#include "bordered_widget.hpp" +#include "frame_part.hpp" +#include "gui/render/structures/window_texture.hpp" +#include "types.hpp" +#include "widget.hpp" + +#include +#include + +namespace gui { + +namespace the_buttons { + +/** + * @brief Widget with the ability to call a function when it is "pressed" + */ +class ButtonWidget : public virtual WidgetBase { + private: + std::shared_ptr data_; + + std::function button_function_; + + public: + ButtonWidget() = delete; + /** + * @brief Deleted copy constructor + */ + ButtonWidget(const ButtonWidget& obj) = delete; + + /** + * @brief Default copy operator + */ + ButtonWidget& operator=(const ButtonWidget& obj) = default; + + /** + * @brief Default move constructor + */ + ButtonWidget(ButtonWidget&& obj) = delete; + // TODO do this for all virtual classes + // ButtonWidget(ButtonWidget&& obj) { + // static_cast(*this) = std::move(static_cast(obj)); + // data_ = std::move(obj.data_); + // button_function_ = std::move(button_function_); + // } + + /** + * @brief Default move constructor + */ + ButtonWidget& operator=(ButtonWidget&& obj) = delete; + + /** + * @brief Construct a new ButtonWidget object. + * @param WidgetInterface* parent Parent Widget. Will be rendered on top of and + * relative to parent + * @param std::shared_ptr data OpenGL texture of the button + * @param glm::ivec2 position Position in parent widget + * @param glm::ivec2 widget_size size of widget + * @param std::function button_function Function called when button is + * pressed. + */ + ButtonWidget( + WidgetInterface* parent, std::shared_ptr data, + glm::ivec2 position, glm::ivec2 widget_size, + std::function button_function + ) : + WidgetBase( + parent, position, widget_size, + {position, glm::ivec2(position.x + widget_size.x, position.y), + position + widget_size, glm::ivec2(position.x, position.y + widget_size.y)} + ), + + data_(data), button_function_(button_function) { + data_->update_position(get_bounding_box()); + } + + inline virtual ~ButtonWidget() {}; + + /** + * @brief Get number of vertices on path that surrounds this widget. + * @return unsigned int the number of vertices + */ + inline unsigned int + get_num_vertices() const { + return data_->get_num_vertices(); + } + + /** + * @brief Bind OpenGL data to use in a program + */ + inline virtual void + bind() const { + data_->bind(); + }; + + /** + * @brief Clears the OpenGL vertex array inputs + */ + inline virtual void + release() const { + data_->release(); + } + + /** + * @brief If the widget should be rendered + * + * @return bool true if the widget should be rendered, false otherwise. + */ + inline virtual bool + do_render() const { + return data_->do_render(); + }; + + /** + * @brief Called by the UI to render this widget to the screen + * + * @param UserInterface* user_interface The UserInterface that is rendering the + * widget + * @param screen_size_t x_position Position on the screen to render the widget + * @param screen_size_t y_position Position on the screen to render the widget + */ + inline void + user_interface_render( + const UserInterface* user_interface, screen_size_t x_position, + screen_size_t y_position + ) const override { + user_interface->render_frame(this, x_position, y_position); + + for (const auto& child : children) { + child->user_interface_render( + user_interface, x_position + position_.x, y_position + position_.y + ); + } + } + + /** + * @brief Size of the four borders. Used in uniforms + */ + [[nodiscard]] inline virtual glm::ivec4 + get_border_size() const { + return data_->get_border_size(); + } + + /** + * @brief Size of the four side lengths. Used in uniforms + */ + [[nodiscard]] inline virtual glm::ivec4 + get_side_lengths() const { + return data_->get_side_lengths(); + } + + /** + * @brief Size of the four borders. Used in uniforms + */ + [[nodiscard]] inline virtual glm::ivec2 + get_inner_pattern_size() const { + return data_->get_inner_pattern_size(); + } + + /** + * @brief Size of the four borders. Used in uniforms + */ + [[nodiscard]] inline virtual std::array + get_texture_regions() const { + return data_->get_texture_regions(); + } + + // rn this may work, but... + // want to separate between has clickable and renderable children. + /** + * @brief Quarry if this widget has child widgets. + * + * @return true if there are child widgets, false otherwise. + */ + inline bool + has_children() const { + return false; + } + + inline virtual void + handle_mouse_button_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int button, + [[maybe_unused]] int action, [[maybe_unused]] int mods + ) { + if (action == GLFW_PRESS) { + button_function_(); + } + } +}; + +} // namespace the_buttons + +} // namespace gui \ No newline at end of file diff --git a/src/gui/the_buttons/frame.cpp b/src/gui/the_buttons/frame.cpp new file mode 100644 index 00000000..ff3e8862 --- /dev/null +++ b/src/gui/the_buttons/frame.cpp @@ -0,0 +1,90 @@ +#include "frame.hpp" + +#include "logging.hpp" +#include "types.hpp" +#include "user_interface.hpp" +#include "widget.hpp" + +#include + +#include + +namespace gui { + +namespace the_buttons { + +std::weak_ptr +FrameBase::get_child_at_position(screen_size_t x, screen_size_t y) const { + for (const auto& child : children_) { + if (child->is_interior(x - position_.x, y - position_.y)) { + if (child->has_children()) { + return child->get_child_at_position(x - position_.x, y - position_.y); + } else { + return child; + } + } + } + + LOG_WARNING(logging::main_logger, "Failed to find frame."); + return {}; +} + +bool +FrameBase::is_interior(screen_size_t x, screen_size_t y) const { + const auto bounding_box = get_bounding_box(); + if (x < bounding_box[0] || y < bounding_box[1] || x > bounding_box[2] + || y > bounding_box[3]) { + return false; + } + + //clang-format off + /* + * (previous_position) + * * + * * (current position) + * x + * (x, y) + * |----|--------------| + * w1 w2 + * + * w2 * previous_position.y + w1 * current_position.y + * y_line = ------------------------------------------------------ + * w1 + w2 + * + * If there are an odd number of lines above the position then it is on the + * interior. + */ + //clang-format on + + bool interior_flag = false; + glm::ivec2 previous_position = exterior_points_.back(); + + for (const auto& current_position : exterior_points_) { + int x_weight_1 = std::abs(previous_position.x - x); + int x_weight_2 = std::abs(current_position.x - x); + + if (y * (x_weight_1 + x_weight_2) + > previous_position.y * x_weight_2 + current_position.y * x_weight_1) { + interior_flag = !interior_flag; + } + + previous_position = current_position; + } + + return interior_flag; +} + +// void +// FrameBase::render_children( +// const UserInterface* user_interface, screen_size_t x_position, +// screen_size_t y_position +// ) const { +// for (const auto& child : children) { +// user_interface->render_frame( +// child, x_position + position_.x, y_position + position_.y +// ); +// } +// } + +} // namespace the_buttons +} // namespace gui diff --git a/src/gui/the_buttons/frame.hpp b/src/gui/the_buttons/frame.hpp new file mode 100644 index 00000000..7d8bf098 --- /dev/null +++ b/src/gui/the_buttons/frame.hpp @@ -0,0 +1,436 @@ +// -*- lsst-c++ -*- +/* + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +/** + * @file frame.hpp + * + * @author @AlemSnyder + * + * @brief Defines FrameInterface and FrameBase Classes + * + * @ingroup GUI THE_BUTTONS + * + */ + +#pragma once + +#include "../render/gpu_data/data_types.hpp" +#include "gui/scene/input.hpp" +#include "types.hpp" +#include "widget.hpp" + +#include +#include +#include + +namespace gui { + +namespace the_buttons { + +/** + * @brief Interface for Frame. Frames hare top level widgets in the UI system. They + * don't have parents and are rendered relative to the window. + */ +class FrameInterface : public virtual WidgetInterface { + public: + inline virtual ~FrameInterface() {}; + + /** + * @brief Check if the position of the frame fixed on the screen + * + * @return true if fixed false if not. + */ + virtual bool is_fixed() const = 0; + + /** + * @brief Check if the window should be rendered + * + * @return false if should not be rendered true if should be + */ + inline virtual bool is_visible() const = 0; + + /** + * @brief Function called when the Frame is selected + */ + virtual void on_select() = 0; + + /** + * @brief Function called when any other frame is selected + */ + virtual void on_end_select() = 0; + + /** + * @brief Get the box that bounds of the widget. + * + * @details This should be the smallest rectangle that completely contains the + * widget. + * + * @return std::array [x position, y position, width, height] + */ + virtual std::array get_bounding_box() const = 0; + + /** + * @brief Handle key input including mouse keys + * + * @param GLFWwindow* window window event came from + * @param int key GLFW key enum + * @param int scancode GLFW scancode enum + * @param int action GLFW action one of GLFW_PRESS, GLFW_RELEASE, GLFW_REPEAT + * @param int mods GLFW mods enum + */ + virtual void handle_key_event_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int key, + [[maybe_unused]] int scancode, [[maybe_unused]] int action, + [[maybe_unused]] int mods + ) = 0; + + /** + * @brief Handle text input + * + * @param GLFWwindow* window window to listen on + * @param unsigned int codepoint unicode 32 character. + */ + + virtual void handle_text_input_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] unsigned int codepoint + ) = 0; + + /** + * @brief Handle mouse movement events. + * + * @param GLFWwindow* window window to listen on + * @param double xpos x position in window coordinates + * @param double ypos y position in window coordinates. + */ + virtual void handle_mouse_event_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] double xpos, + [[maybe_unused]] double ypos + ) = 0; + + /** + * @brief Handle mouse enter window events + * + * @param GLFWwindow* window window to listen on + * @param int button + * @param int action + * @param int mods + */ + virtual void handle_mouse_button_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int button, + [[maybe_unused]] int action, [[maybe_unused]] int mods + ) = 0; + + /** + * @brief Handle mouse scroll events + * + * @param GLFWwindow* window window to listen on + * @param double xoffset x offset + * @param double yoffset y offset (usually 0) + */ + virtual void handle_mouse_scroll_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] double xoffset, + [[maybe_unused]] double yoffset + ) = 0; + + /** + * @brief Handle joystick event + * + * @param int jid joystick id + * @param int event + */ + virtual void + handle_joystick_input([[maybe_unused]] int jid, [[maybe_unused]] int event) = 0; + + /** + * @brief Handle file drop event + * + * @param GLFWwindow* window window to listen on + * @param int count number of files passed + * @param const char** paths file paths + */ + virtual void handle_file_drop_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int count, + [[maybe_unused]] const char** paths + ) = 0; + + /** + * @brief Handle all pooled inputs + * + * @param GLFWwindow* window window + */ + virtual void handle_pooled_inputs([[maybe_unused]] GLFWwindow* window) = 0; + + /** + * @brief Setup so this objects handles inputs correctly + * + * @param GLFWwindow* window window + */ + virtual void setup([[maybe_unused]] GLFWwindow* window) = 0; + + /** + * @brief Cleanup to original state + * + * @param GLFWwindow* window window + */ + virtual void cleanup([[maybe_unused]] GLFWwindow* window) = 0; +}; + +class FrameBase : public virtual FrameInterface { + protected: + glm::ivec2 position_; + glm::ivec2 frame_size_; + std::vector exterior_points_; + + bool fixed_; // fixed position in render queue + std::unordered_set> children_; + + bool is_selected; + + public: + FrameBase( + glm::ivec2 position, glm::ivec2 frame_size, + std::vector exterior_points, bool fixed = false + ) : + position_(position), frame_size_(frame_size), exterior_points_(exterior_points), + fixed_(fixed) {}; + + inline virtual ~FrameBase() {}; + + /** + * @brief Test if the given location is within the region of the widget + * + * @param screen_size_t x screen position x coordinate + * @param screen_size_t y screen position y coordinate + */ + bool is_interior(screen_size_t x, screen_size_t y) const; + + /** + * @brief Get child widget if it exists at the given position. + * + * @param screen_size_t x screen position x coordinate + * @param screen_size_t y screen position y coordinate + * + * @return std::weak_ptr a week pointer to the child widget. + * It will exist if the given location has a child widget. + */ + std::weak_ptr + get_child_at_position(screen_size_t x, screen_size_t y) const; + + /** + * @brief Quarry if this widget has child widgets. + * + * @return true if there are child widgets, false otherwise. + */ + [[nodiscard]] inline virtual bool + has_children() const override { + return !children_.empty(); + } + + // prevent overlapping children. + // bool check_children_positions(); + + // might want to make it such that after a child has been added + // nothing else can be added to it. + // This will prevent circular references. + // do this be checking if parent is set. + // could also do the opposite and require that initialized with parent. + + /** + * @brief Check if the position of the frame fixed on the screen + * + * @return true if fixed false if not. + */ + inline virtual bool + is_fixed() const override { + return fixed_; + } + + /** + * @brief Check if the window should be rendered + * + * @return false if should not be rendered true if should be + */ + inline virtual bool + is_visible() const override { + return true; + } + + /** + * @brief Function called when the Frame is selected + */ + inline virtual void on_select() override {}; + + /** + * @brief Function called when any other frame is selected + */ + inline virtual void on_end_select() override {}; + + /** + * @brief Get the box that bounds of the widget. + * + * @details This should be the smallest rectangle that completely contains the + * widget. + * + * @return std::array [x position, y position, width, height] + */ + inline virtual std::array + get_bounding_box() const { + return {position_.x, position_.y, frame_size_.x, frame_size_.y}; + } + + /** + * @brief Construct a child widget of given type with given arguments. + * @tparam widget_type T Child widget type + * @tparam ...Args arguments for child widget + * @param ...args arguments for child widget + * @return std::shared_ptr shared_ptr to child widget + */ + template + [[nodiscard]] inline std::shared_ptr + make(Args&&... args) { + auto new_widget = children_.emplace(std::make_shared(this, args...)); + if (new_widget.second) { + if (auto new_widget_ptr = std::dynamic_pointer_cast(*new_widget.first)) { + return new_widget_ptr; + } + } + return nullptr; + } + + [[nodiscard]] inline auto + begin() const { + return children_.begin(); + } + + [[nodiscard]] inline auto + end() const { + return children_.end(); + } + + /** + * @brief Handle key input including mouse keys + * + * @param GLFWwindow* window window event came from + * @param int key GLFW key enum + * @param int scancode GLFW scancode enum + * @param int action GLFW action one of GLFW_PRESS, GLFW_RELEASE, GLFW_REPEAT + * @param int mods GLFW mods enum + */ + virtual void + handle_key_event_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int key, + [[maybe_unused]] int scancode, [[maybe_unused]] int action, + [[maybe_unused]] int mods + ) {} + + /** + * @brief Handle text input + * + * @param GLFWwindow* window window to listen on + * @param unsigned int codepoint unicode 32 character. + */ + + virtual void + handle_text_input_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] unsigned int codepoint + ) {} + + /** + * @brief Handle mouse movement events. + * + * @param GLFWwindow* window window to listen on + * @param double xpos x position in window coordinates + * @param double ypos y position in window coordinates. + */ + virtual void + handle_mouse_event_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] double xpos, + [[maybe_unused]] double ypos + ) {} + + /** + * @brief Handle mouse enter window events + * + * @param GLFWwindow* window window to listen on + * @param int button + * @param int action + * @param int mods + */ + virtual void + handle_mouse_button_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int button, + [[maybe_unused]] int action, [[maybe_unused]] int mods + ) {} + + /** + * @brief Handle mouse scroll events + * + * @param GLFWwindow* window window to listen on + * @param double xoffset x offset + * @param double yoffset y offset (usually 0) + */ + virtual void + handle_mouse_scroll_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] double xoffset, + [[maybe_unused]] double yoffset + ) {} + + /** + * @brief Handle joystick event + * + * @param int jid joystick id + * @param int event + */ + virtual void + handle_joystick_input([[maybe_unused]] int jid, [[maybe_unused]] int event) {} + + /** + * @brief Handle file drop event + * + * @param GLFWwindow* window window to listen on + * @param int count number of files passed + * @param const char** paths file paths + */ + virtual void + handle_file_drop_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int count, + [[maybe_unused]] const char** paths + ) {} + + /** + * @brief Handle all pooled inputs + * + * @param GLFWwindow* window window + */ + virtual void + handle_pooled_inputs([[maybe_unused]] GLFWwindow* window) {} + + /** + * @brief Setup so this objects handles inputs correctly + * + * @param GLFWwindow* window window + */ + virtual void + setup([[maybe_unused]] GLFWwindow* window) {} + + /** + * @brief Cleanup to original state + * + * @param GLFWwindow* window window + */ + virtual void + cleanup([[maybe_unused]] GLFWwindow* window) {} +}; + +} // namespace the_buttons + +} // namespace gui diff --git a/src/gui/the_buttons/frame_part.cpp b/src/gui/the_buttons/frame_part.cpp new file mode 100644 index 00000000..2fd3328f --- /dev/null +++ b/src/gui/the_buttons/frame_part.cpp @@ -0,0 +1,77 @@ +#include "frame_part.hpp" + +#include "user_interface.hpp" + +namespace gui { + +namespace the_buttons { + +bool +WidgetBase::is_interior(screen_size_t x, screen_size_t y) const { + const auto bounding_box = get_bounding_box(); + if (x < bounding_box[0] || y < bounding_box[1] || x > bounding_box[2] + || y > bounding_box[3]) { + return false; + } + + //clang-format off + /* + * (previous_position) + * * + * * (current position) + * x + * (x, y) + * |----|--------------| + * w1 w2 + * + * w2 * previous_position.y + w1 * current_position.y + * y_line = ------------------------------------------------------ + * w1 + w2 + * + * If there are an odd number of lines above the position then it is on the + * interior. + */ + //clang-format on + + bool interior_flag = false; + glm::ivec2 previous_position = exterior_points_.back(); + + for (const auto& current_position : exterior_points_) { + int x_weight_1 = std::abs(previous_position.x - x); + int x_weight_2 = std::abs(current_position.x - x); + + if (y * (x_weight_1 + x_weight_2) + > previous_position.y * x_weight_2 + current_position.y * x_weight_1) { + interior_flag = !interior_flag; + } + + previous_position = current_position; + } + + return interior_flag; +} + +bool +WidgetBase::has_children() const { + return !children.empty(); +} + +std::weak_ptr +WidgetBase::get_child_at_position(screen_size_t x, screen_size_t y) const { + for (const auto& child : children) { + if (child->is_interior(x - position_.x, y - position_.y)) { + if (child->has_children()) { + return child->get_child_at_position(x - position_.x, y - position_.y); + } else { + return child; + } + } + } + + LOG_WARNING(logging::main_logger, "Failed to find frame."); + return {}; +} + +} // namespace the_buttons + +} // namespace gui diff --git a/src/gui/the_buttons/frame_part.hpp b/src/gui/the_buttons/frame_part.hpp new file mode 100644 index 00000000..6be5e48d --- /dev/null +++ b/src/gui/the_buttons/frame_part.hpp @@ -0,0 +1,239 @@ +// -*- lsst-c++ -*- +/* + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +/** + * @file frame_part.hpp + * + * @author @AlemSnyder + * + * @brief Defines WidgetBase Class + * + * @ingroup GUI THE_BUTTONS + * + */ + +#pragma once + +#include "types.hpp" +#include "widget.hpp" + +#include +#include + +namespace gui { + +namespace the_buttons { + +class WidgetBase : public virtual WidgetInterface { + protected: + WidgetInterface* parent_; + glm::ivec2 position_; + glm::ivec2 frame_size_; + std::vector exterior_points_; + + std::unordered_set> children; + + bool is_selected; + + void exterior_changed(); // need to change exterior for parent. + + public: + WidgetBase( + WidgetInterface* parent, glm::ivec2 position, glm::ivec2 frame_size, + std::vector exterior_points + ) : + parent_(parent), position_(position), frame_size_(frame_size), + exterior_points_(exterior_points) {}; + + WidgetBase() = delete; + /** + * @brief Deleted copy constructor + */ + WidgetBase(const WidgetBase& obj) = delete; + + /** + * @brief Deleted copy operator + */ + WidgetBase& operator=(const WidgetBase& obj) = delete; + + /** + * @brief Default move constructor + */ + WidgetBase(WidgetBase&& obj) = default; + + /** + * @brief Default move constructor + */ + WidgetBase& operator=(WidgetBase&& obj) = default; + + template + [[nodiscard]] inline std::shared_ptr + make(Args&&... args) { + auto new_widget = children.emplace(std::make_shared(this, args...)); + if (new_widget.second) { + if (auto new_widget_ptr = std::dynamic_pointer_cast(*new_widget.first)) { + return new_widget_ptr; + } + } + return nullptr; + } + + /** + * @brief Test if the given location is within the region of the widget + * + * @param screen_size_t x screen position x coordinate + * @param screen_size_t y screen position y coordinate + */ + virtual bool is_interior(screen_size_t x, screen_size_t y) const override; + + /** + * @brief Quarry if this widget has child widgets. + * + * @return true if there are child widgets, false otherwise. + */ + [[nodiscard]] virtual bool has_children() const override; + + /** + * @brief Get child widget if it exists at the given position. + * + * @param screen_size_t x screen position x coordinate + * @param screen_size_t y screen position y coordinate + * + * @return std::weak_ptr a week pointer to the child widget. + * It will exist if the given location has a child widget. + */ + virtual std::weak_ptr + get_child_at_position(screen_size_t x, screen_size_t y) const override; + + /** + * @brief Get the box that bounds of the widget. + * + * @details This should be the smallest rectangle that completely contains the + * widget. + * + * @return std::array [x position, y position, width, height] + */ + inline virtual std::array + get_bounding_box() const override { + return {position_.x, position_.y, frame_size_.x, frame_size_.y}; + } + + /** + * @brief Handle key input including mouse keys + * + * @param GLFWwindow* window window event came from + * @param int key GLFW key enum + * @param int scancode GLFW scancode enum + * @param int action GLFW action one of GLFW_PRESS, GLFW_RELEASE, GLFW_REPEAT + * @param int mods GLFW mods enum + */ + virtual void handle_key_event_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int key, + [[maybe_unused]] int scancode, [[maybe_unused]] int action, + [[maybe_unused]] int mods + ) override {}; + + /** + * @brief Handle text input + * + * @param GLFWwindow* window window to listen on + * @param unsigned int codepoint unicode 32 character. + */ + + virtual void handle_text_input_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] unsigned int codepoint + ) override {}; + + /** + * @brief Handle mouse movement events. + * + * @param GLFWwindow* window window to listen on + * @param double xpos x position in window coordinates + * @param double ypos y position in window coordinates. + */ + virtual void handle_mouse_event_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] double xpos, + [[maybe_unused]] double ypos + ) override {}; + + /** + * @brief Handle mouse enter window events + * + * @param GLFWwindow* window window to listen on + * @param int button + * @param int action + * @param int mods + */ + virtual void handle_mouse_button_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int button, + [[maybe_unused]] int action, [[maybe_unused]] int mods + ) override {}; + + /** + * @brief Handle mouse scroll events + * + * @param GLFWwindow* window window to listen on + * @param double xoffset x offset + * @param double yoffset y offset (usually 0) + */ + virtual void handle_mouse_scroll_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] double xoffset, + [[maybe_unused]] double yoffset + ) override {}; + + /** + * @brief Handle joystick event + * + * @param int jid joystick id + * @param int event + */ + virtual void handle_joystick_input( + [[maybe_unused]] int jid, [[maybe_unused]] int event + ) override {}; + + /** + * @brief Handle file drop event + * + * @param GLFWwindow* window window to listen on + * @param int count number of files passed + * @param const char** paths file paths + */ + virtual void handle_file_drop_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int count, + [[maybe_unused]] const char** paths + ) override {}; + + /** + * @brief Handle all pooled inputs + * + * @param GLFWwindow* window window + */ + virtual void handle_pooled_inputs([[maybe_unused]] GLFWwindow* window) override {}; + + /** + * @brief Setup so this objects handles inputs correctly + * + * @param GLFWwindow* window window + */ + virtual void setup([[maybe_unused]] GLFWwindow* window) override {}; + + /** + * @brief Cleanup to original state + * + * @param GLFWwindow* window window + */ + virtual void cleanup([[maybe_unused]] GLFWwindow* window) override {}; +}; + +} // namespace the_buttons + +} // namespace gui diff --git a/src/gui/the_buttons/text_widget.cpp b/src/gui/the_buttons/text_widget.cpp new file mode 100644 index 00000000..32cca076 --- /dev/null +++ b/src/gui/the_buttons/text_widget.cpp @@ -0,0 +1,234 @@ +#include "text_widget.hpp" + +#include "global_context.hpp" +#include "user_interface.hpp" + +namespace gui { +namespace the_buttons { +void +TextWidget::update_text_data() { + // just run this on a back end thread + std::vector data = generate_data(); + + // data.push_back(glm::ivec4(2, 2, 100, 15)); + // data.push_back(glm::ivec4(2, 10, 100, 0)); + // data.push_back(glm::ivec4(28, 2, 115, 15)); + // data.push_back(glm::ivec4(28, 10, 115, 0)); + + text_data_.insert(data, 0, text_data_.size()); + + std::vector elements_data; + for (size_t i = 0; i < num_characters_; i++) { + elements_data.push_back(4 * i + 0); + elements_data.push_back(4 * i + 1); + elements_data.push_back(4 * i + 2); + elements_data.push_back(4 * i + 2); + elements_data.push_back(4 * i + 1); + elements_data.push_back(4 * i + 3); + } + + element_array_.insert(elements_data, 0, element_array_.size()); +} + +void +TextWidget::user_interface_render( + const UserInterface* user_interface, screen_size_t x_position, + screen_size_t y_position +) const { + user_interface->render_frame(this, x_position, y_position); +} + +void +TextWidget::attach_all() { + text_data_.attach_to_vertex_attribute(0); +} + +void +TextWidget::initialize() { + vertex_array_object_.bind(); + attach_all(); + vertex_array_object_.release(); +} + +std::vector +TextWidget::generate_data() const { + std::vector data; + + // TODO I want to put all of this into a class. Some sort of text alignment + // iterator. + unsigned int text_scale = text_scale_; + bool wrap_text = wrap_text_; + + unsigned int line_spacing = font_->get_text_height(); + unsigned int baseline = font_->get_ascender_height(); + + unsigned int text_width = frame_size_.y / text_scale; + int text_height = frame_size_.y / text_scale; + + unsigned int character_advance = 0; + int line_advance = text_height - baseline; + bool previous_char = false; + bool white_space_after_char = false; + size_t words_checked_until = 0; + + LOG_BACKTRACE(logging::main_logger, "Generating data for string \"{}\".", text_); + + for (size_t character_position = 0; character_position < text_.size(); + character_position++) { + char character = text_.at(character_position); + if (character == '\n') { + character_advance = 0; + line_advance -= line_spacing; + previous_char = false; + white_space_after_char = false; + continue; + } else if (character == '\t') { + character_advance = character_advance + (40 - character_advance % 40); + if (previous_char) { + white_space_after_char = true; + } + continue; + } + // '' + else { + if (wrap_text) { + if (character_position >= words_checked_until) { + // check that the entirety of the word is safe + if (white_space_after_char) { + // this is not the first word on the line + // check next word + unsigned int local_advance = character_advance; + size_t check_word_position = character_position; + for (; check_word_position < text_.size(); + check_word_position++) { + char check_character = text_.at(check_word_position); + + // if the character is white space + if (check_character == ' ' || check_character == '\n' + || check_character == '\t') { + break; + } + const render::structures::Character* check_character_data = + font_->get_character(check_word_position); + if (!check_character_data) { + continue; + } + local_advance += check_character_data->size.x; + } + if (local_advance > text_width) { + character_advance = 0; + line_advance -= line_spacing; + previous_char = false; + white_space_after_char = false; + + if (local_advance < text_width * 2) { + words_checked_until = check_word_position; + } + } + } else { + const render::structures::Character* check_character_data = + font_->get_character(character); + if (!check_character_data) { + continue; + } + + if (character_advance + check_character_data->size.x + > text_width) { + if (check_character_data->size.x > text_width) { + LOG_WARNING( + logging::main_logger, + "Character \'{}\' has width {}, but TextWidth has " + "width {}.", + std::string(1, character), + check_character_data->size.x, text_width + ); + } else { + // newline + character_advance = 0; + line_advance -= line_spacing; + previous_char = false; + white_space_after_char = false; + } + } + unsigned int local_advance = character_advance; + size_t check_word_position = character_position; + for (; check_word_position < text_.size(); + check_word_position++) { + char check_character = text_.at(check_word_position); + + // if the character is white space + if (check_character == ' ' || check_character == '\n' + || check_character == '\t') { + break; + } + const render::structures::Character* check_character_data = + font_->get_character(check_word_position); + if (!check_character_data) { + continue; + } + local_advance += check_character_data->size.x; + if (local_advance > text_width) { + check_word_position -= 1; + break; + } + } + words_checked_until = check_word_position; + } + } + } + + // if (advance + character_data->advance > frame width or character is + // new_line) { advance = 0; line_advance += line_spacing} need to check for + // word then break on word but also need to break within a word of the world + // is too long + + // reading character data from font + const render::structures::Character* character_data = + font_->get_character(character); + if (!character_data) { + continue; + } + + glm::ivec4 position_in_texture = character_data->position_in_texture; + glm::ivec2 bearing = character_data->bearing; + glm::ivec2 size = character_data->size; + + data.push_back( + glm::ivec4( + character_advance + bearing.x, line_advance + bearing.y, + position_in_texture.x, position_in_texture.y + ) + ); + data.push_back( + glm::ivec4( + character_advance + bearing.x, line_advance + bearing.y - size.y, + position_in_texture.x, position_in_texture.w + ) + ); + data.push_back( + glm::ivec4( + character_advance + bearing.x + size.x, line_advance + bearing.y, + position_in_texture.z, position_in_texture.y + ) + ); + data.push_back( + glm::ivec4( + character_advance + bearing.x + size.x, + line_advance + bearing.y - size.y, position_in_texture.z, + position_in_texture.w + ) + ); + + character_advance += size.x; + } + } + // I might want to suppress this as it might not be a problem + if (line_advance > text_height) { + LOG_WARNING(logging::main_logger, "Text larger then allocated size."); + } + return data; +} + +} // namespace the_buttons + +} // namespace gui diff --git a/src/gui/the_buttons/text_widget.hpp b/src/gui/the_buttons/text_widget.hpp new file mode 100644 index 00000000..dac91fb1 --- /dev/null +++ b/src/gui/the_buttons/text_widget.hpp @@ -0,0 +1,227 @@ +// -*- lsst-c++ -*- +/* + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +/** + * @file text_widget.hpp + * + * @author @AlemSnyder + * + * @brief Defines TextWidget Class + * + * @ingroup GUI THE_BUTTONS + * + */ + +#pragma once + +#include "bordered_widget.hpp" +#include "frame_part.hpp" +#include "global_context.hpp" +#include "gui/render/gpu_data/vertex_buffer_object.hpp" +#include "gui/render/structures/font.hpp" +#include "types.hpp" +#include "util/color.hpp" +#include "widget.hpp" + +#include + +namespace gui { + +namespace the_buttons { + +/** + * @brief Widget for rendering text + */ +class TextWidget : public virtual WidgetBase, public virtual gpu_data::GPUDataElements { + private: + gpu_data::VertexArrayObject vertex_array_object_; + + std::shared_ptr font_; + gpu_data::VertexBufferObject text_data_; + gpu_data::VertexBufferObject< + uint16_t, gpu_data::BindingTarget::ELEMENT_ARRAY_BUFFER> + element_array_; + std::string text_; + uint32_t num_characters_; + + // I guess all the text needs to be one color + ColorFloat text_color_; + + bool wrap_text_; + uint8_t text_scale_; + + void update_text_data(); + + void initialize(); + + protected: + // attaches text_data_ to the vertex_array_object_ + void attach_all(); + + public: + TextWidget() = delete; + /** + * @brief Deleted copy constructor + */ + TextWidget(const TextWidget& obj) = delete; + + /** + * @brief Deleted copy operator + */ + TextWidget& operator=(const TextWidget& obj) = delete; + + /** + * @brief Default move constructor + */ + TextWidget(TextWidget&& obj) = delete; + + /** + * @brief Default move operator + */ + TextWidget& operator=(TextWidget&& obj) = delete; + + /** + * @brief Construct a new TextWidget object + * @param WidgetInterface* parent Widget the be rendered relative to + * @param std::shared_ptr font + * @param glm::ivec2 position in parent widget to place text + * @param glm::ivec2 widget_size Size of widget. Text will stay within this region + * @param std::string text = "" + * @param bool differed = true + * @param bool wrap_text = true + * @param uint8_t text_scale = 4 + * @param ColorFloat text_color = color::black + */ + inline TextWidget( + WidgetInterface* parent, std::shared_ptr font, + glm::ivec2 position, glm::ivec2 widget_size, std::string text = std::string(""), + bool differed = true, bool wrap_text = true, uint8_t text_scale = 4, + ColorFloat text_color = color::black + ) : + WidgetBase( + parent, position, widget_size, + {position, glm::ivec2(position.x + widget_size.x, position.y), + position + widget_size, glm::ivec2(position.x, position.y + widget_size.y)} + ), + vertex_array_object_(differed), + + font_(font), text_(text), num_characters_(text_.length()), + text_color_(text_color), wrap_text_(wrap_text), text_scale_(text_scale) { + if (differed) { + GlobalContext& context = GlobalContext::instance(); + context.push_opengl_task([this]() { initialize(); }); + } else { + initialize(); + } + update_text_data(); + } + + inline virtual ~TextWidget() {}; + + /** + * @brief Set the display text to the given string + * + * @param std::string&& `text` text to display in the widget. + */ + inline void + set_text(std::string&& text) { + text_ = text; + num_characters_ = text_.length(); + update_text_data(); + } + + /** + * @brief Get the number of vertices + * + * @return `unsigned int` the number of vertices. This is equal to six times the + * number of characters. + */ + inline unsigned int + get_num_vertices() const { + return num_characters_ * 6; // four vertices per character + } + + /** + * @brief Get the text color + * + * @return ColorFloat color of the text + */ + inline ColorFloat + get_text_color() const { + return text_color_; + } + + /** + * @brief Get the element type. In this case I think it's `uint16_t` + * + * @return `gpu_data::GPUArayType::UNSIGNED_SHORT` (to the best of my knownage) + */ + virtual gpu_data::GPUArayType + get_element_type() const { + return element_array_.get_opengl_numeric_type(); + } + + /** + * @brief Bind OpenGL data to use in a program + */ + inline virtual void + bind() const { + font_->bind(0); + vertex_array_object_.bind(); + element_array_.bind(); + }; + + /** + * @brief Clears the OpenGL vertex array inputs + */ + inline virtual void + release() const { + vertex_array_object_.release(); + } + + /** + * @brief If the widget should be rendered + * + * @return bool true if the widget should be rendered, false otherwise. + */ + inline virtual bool + do_render() const { + return true; + }; + + /** + * @brief Called by the UI to render this widget to the screen + * + * @param UserInterface* user_interface The UserInterface that is rendering the + * widget + * @param screen_size_t x_position Position on the screen to render the widget + * @param screen_size_t y_position Position on the screen to render the widget + */ + void user_interface_render( + const UserInterface* user_interface, screen_size_t x_position, + screen_size_t y_position + ) const override; + + /** + * @brief Calculates the texture location to window space position for the given + * text. + * + * @return std::vector A vector of positions. The first two values in + * the uvec4 are the position in the widget, and the last two are the position in + * the texture. + */ + [[nodiscard]] std::vector generate_data() const; +}; + +} // namespace the_buttons + +} // namespace gui \ No newline at end of file diff --git a/src/gui/the_buttons/user_interface.cpp b/src/gui/the_buttons/user_interface.cpp new file mode 100644 index 00000000..3eef96b1 --- /dev/null +++ b/src/gui/the_buttons/user_interface.cpp @@ -0,0 +1,359 @@ + + +#include "user_interface.hpp" + +#include "../render/graphics_shaders/program_handler.hpp" +#include "../render/graphics_shaders/shader_program.hpp" +#include "../render/structures/uniform_types.hpp" +#include "bordered_widget.hpp" +#include "bordered_window.hpp" +#include "button_widget.hpp" +#include "manifest/object_handler.hpp" +#include "text_widget.hpp" +#include "widget.hpp" + +namespace gui { + +namespace the_buttons { + +UserInterface::UserInterface(shader::ShaderHandler& shader_handler, uint8_t ui_scale) : + frame_size_uniform_(std::make_shared()), + ui_scale_uniform_(std::make_shared(ui_scale)), + frame_texture_uniform_( + std::make_shared( + gpu_data::GPUArayType::UNSIGNED_INT_SAMPLER_2D, 0 + ) + ), + border_sizes_(std::make_shared()), + side_lengths_(std::make_shared()), + inner_pattern_size_(std::make_shared()), + texture_regions_(std::make_shared()), + font_color_uniform_(std::make_shared()) { + shader::Program& window_render_program = shader_handler.load_program( + "Windows", files::get_resources_path() / "shaders" / "overlay" / "Widget.vert", + files::get_resources_path() / "shaders" / "overlay" / "FramedWindow.frag" + ); + + shader::Program& text_render_program = shader_handler.load_program( + "Windows", + files::get_resources_path() / "shaders" / "overlay" / "TextWindow.vert", + files::get_resources_path() / "shaders" / "overlay" / "TextWindow.frag" + ); + + // auto image_result = image::read_image(files::get_resources_path() / "textures" / + // "GenericBorder.png"); if (!image_result.has_value()) { + // LOG_ERROR(logging::file_io_logger, "Error Code {}", image_result.error()); + // return; + // } + // std::shared_ptr image = image_result.value(); + + // gpu_data::Texture2D border_texture(image, gui::gpu_data::TextureSettings{}, + // false); + + // Overwrites anything that was there before + std::function render_setup = []() { + // Draw over everything + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + }; + + // uniforms + // stars_program.set_uniform(matrix_view_projection_uniform, "MVP"); + window_render_program.set_uniform(frame_size_uniform_, "frame_size"); + window_render_program.set_uniform(ui_scale_uniform_, "ui_scale"); + window_render_program.set_uniform(frame_texture_uniform_, "window_texture"); + window_render_program.set_uniform(border_sizes_, "border_size"); + window_render_program.set_uniform(side_lengths_, "side_lengths"); + window_render_program.set_uniform(inner_pattern_size_, "inner_pattern_size"); + window_render_program.set_uniform(texture_regions_, "positions[0]"); + + text_render_program.set_uniform(frame_size_uniform_, "frame_size"); + text_render_program.set_uniform(ui_scale_uniform_, "ui_scale"); + text_render_program.set_uniform(frame_texture_uniform_, "font_texture"); + text_render_program.set_uniform(font_color_uniform_, "font_color"); + // vec3 font color_uniform + // vec2 position + + // windows + window_pipeline_ = std::make_shared( + window_render_program, render_setup + ); + + text_pipeline_ = std::make_shared( + text_render_program, render_setup + ); +} + +void +UserInterface::update( + [[maybe_unused]] screen_size_t width, [[maybe_unused]] screen_size_t height +) { + // cascade update frames using width and height + + FrameBufferHandler::instance().bind_fbo(0); // the screen + + glClear(GL_DEPTH_BUFFER_BIT); + + for (const auto& frame : frames_) { + frame->user_interface_render(this, 0, 0); + } +} + +// maybe visitor pattern +// double dispatch on render frames +// render_border +// render_text +// render image +// etc +void +UserInterface::render_frame( + const BorderedWindow* frame, screen_size_t x_frame_position, + screen_size_t y_frame_position +) const { + if (!frame->do_render()) { + return; + } + + border_sizes_->set_border_size(glm::ivec4(5, 5, 5, 5)); + side_lengths_->set_side_lengths(glm::ivec4(1, 1, 1, 1)); + inner_pattern_size_->set_inner_pattern_size(glm::ivec2(1, 1)); + texture_regions_->set_texture_regions( + {glm::ivec2(0, 0), glm::ivec2(5, 0), glm::ivec2(6, 0), glm::ivec2(0, 5), + glm::ivec2(5, 5), glm::ivec2(6, 5), glm::ivec2(0, 6), glm::ivec2(5, 6), + glm::ivec2(6, 6)} + ); + + const auto bounding_box = frame->get_bounding_box(); + // add offset + frame_size_uniform_->set_frame_size( + glm::ivec2(bounding_box[2] - bounding_box[0], bounding_box[3] - bounding_box[1]) + ); + window_pipeline_->render( + bounding_box[0] + x_frame_position, bounding_box[1] + y_frame_position, + bounding_box[2] - bounding_box[0], bounding_box[3] - bounding_box[1], 0, frame + ); + + // frame->render_children(this, x_frame_position, y_frame_position); +} + +void +UserInterface::render_frame( + const BorderedWidget* widget, screen_size_t x_frame_position, + screen_size_t y_frame_position +) const { + border_sizes_->set_border_size(widget->get_border_size()); + side_lengths_->set_side_lengths(widget->get_side_lengths()); + inner_pattern_size_->set_inner_pattern_size(widget->get_inner_pattern_size()); + texture_regions_->set_texture_regions(widget->get_texture_regions()); + + const auto bounding_box = widget->get_bounding_box(); + // add offset + frame_size_uniform_->set_frame_size( + glm::ivec2(bounding_box[2] - bounding_box[0], bounding_box[3] - bounding_box[1]) + ); + window_pipeline_->render( + bounding_box[0] + x_frame_position, bounding_box[1] + y_frame_position, + bounding_box[2] - bounding_box[0], bounding_box[3] - bounding_box[1], 0, widget + ); +} + +void +UserInterface::render_frame( + const ButtonWidget* widget, screen_size_t x_frame_position, + screen_size_t y_frame_position +) const { + border_sizes_->set_border_size(widget->get_border_size()); + side_lengths_->set_side_lengths(widget->get_side_lengths()); + inner_pattern_size_->set_inner_pattern_size(widget->get_inner_pattern_size()); + texture_regions_->set_texture_regions(widget->get_texture_regions()); + + const auto bounding_box = widget->get_bounding_box(); + // add offset + frame_size_uniform_->set_frame_size( + glm::ivec2(bounding_box[2] - bounding_box[0], bounding_box[3] - bounding_box[1]) + ); + window_pipeline_->render( + bounding_box[0] + x_frame_position, bounding_box[1] + y_frame_position, + bounding_box[2] - bounding_box[0], bounding_box[3] - bounding_box[1], 0, widget + ); +} + +void +UserInterface::render_frame( + const TextWidget* widget, screen_size_t x_frame_position, + screen_size_t y_frame_position +) const { + const auto bounding_box = widget->get_bounding_box(); + + frame_size_uniform_->set_frame_size( + glm::ivec2(bounding_box[2] - bounding_box[0], bounding_box[3] - bounding_box[1]) + ); + + // set uniforms + font_color_uniform_->set_color(widget->get_text_color()); + + text_pipeline_->render( + bounding_box[0] + x_frame_position, bounding_box[1] + y_frame_position, + bounding_box[2] - bounding_box[0], bounding_box[3] - bounding_box[1], 0, widget + ); +} + +std::pair, std::weak_ptr> +UserInterface::get_frame( + screen_size_t mouse_position_x, screen_size_t mouse_position_y +) const { + // iterate from back to front + auto frame_outer = frames_.end(); + // might be able to do this with control flow + bool found_frame_at_mouse_position = false; + do { + frame_outer--; + if ((*frame_outer)->is_interior(mouse_position_x, mouse_position_y)) { + found_frame_at_mouse_position = true; + break; + } + } while (frame_outer != frames_.begin()); + + if (!found_frame_at_mouse_position) { + return {}; + } + + std::weak_ptr new_frame_outer = *frame_outer; + std::weak_ptr frame_inner = + (*frame_outer)->get_child_at_position(mouse_position_x, mouse_position_y); + + // if there are no children then set to parent. + if (frame_inner.expired()) { + frame_inner = new_frame_outer; + } + + return std::make_pair< + std::weak_ptr, std::weak_ptr>( + std::move(new_frame_outer), std::move(frame_inner) + ); +} + +void +UserInterface::reselect_frame(GLFWwindow* window) { + // forward to + double xpos; + double ypos; + glfwGetCursorPos(window, &xpos, &ypos); + + screen_size_t height; + glfwGetFramebufferSize(window, nullptr, &height); + + auto selected_frames = + get_frame(screen_size_t(floor(xpos)), screen_size_t(height - floor(ypos))); + + if (const std::shared_ptr outer_frame = + selected_frames.first.lock()) { + // move to back + if (outer_frame != frames_.back() && !outer_frame->is_fixed()) { + frames_.remove(outer_frame); + frames_.push_back(outer_frame); + } + } + + if (std::shared_ptr inner_frame = selected_frames.second.lock()) { + selected_frame_ = inner_frame; + } else { + selected_frame_ = nullptr; + } +} + +void +UserInterface::handle_key_event_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int key, + [[maybe_unused]] int scancode, [[maybe_unused]] int action, + [[maybe_unused]] int mods +) { + if (selected_frame_) { + selected_frame_->handle_key_event_input(window, key, scancode, action, mods); + } +} + +void +UserInterface::handle_text_input_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] unsigned int codepoint +) { + if (selected_frame_) { + selected_frame_->handle_text_input_input(window, codepoint); + } +} + +void +UserInterface::handle_mouse_event_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] double xpos, + [[maybe_unused]] double ypos +) { + if (selected_frame_) { + selected_frame_->handle_mouse_event_input(window, xpos, ypos); + } +} + +void +UserInterface::handle_mouse_enter_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int entered +) {} // don't bother + +void +UserInterface::handle_mouse_button_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int button, + [[maybe_unused]] int action, [[maybe_unused]] int mods +) { + if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) { + reselect_frame(window); + } + if (selected_frame_) { + selected_frame_->handle_mouse_button_input(window, button, action, mods); + } +} + +void +UserInterface::handle_mouse_scroll_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] double xoffset, + [[maybe_unused]] double yoffset +) { + // possibly forward + if (selected_frame_) { + selected_frame_->handle_mouse_scroll_input(window, xoffset, yoffset); + } + // forward to top + // selected position then forward +} + +void +UserInterface::handle_joystick_input( + [[maybe_unused]] int jid, [[maybe_unused]] int event +) {} + +void +UserInterface::handle_file_drop_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int count, + [[maybe_unused]] const char** paths +) { + // drop onto mouse position + reselect_frame(window); + if (selected_frame_) { + selected_frame_->handle_file_drop_input(window, count, paths); + } +} + +void +UserInterface::handle_pooled_inputs([[maybe_unused]] GLFWwindow* window) { + if (selected_frame_) { + selected_frame_->handle_pooled_inputs(window); + } +} + +void +UserInterface::setup([[maybe_unused]] GLFWwindow* window) {} + +void +UserInterface::cleanup([[maybe_unused]] GLFWwindow* window) {} + +} // namespace the_buttons + +} // namespace gui diff --git a/src/gui/the_buttons/user_interface.hpp b/src/gui/the_buttons/user_interface.hpp new file mode 100644 index 00000000..f4b39c8e --- /dev/null +++ b/src/gui/the_buttons/user_interface.hpp @@ -0,0 +1,278 @@ +// -*- lsst-c++ -*- +/* + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +/** + * @file user_interface.hpp + * + * @author @AlemSnyder + * + * @brief Defines UserInterface Class + * + * @ingroup GUI THE_BUTTONS + * + */ + +#pragma once + +#include "../render/graphics_shaders/shader_program.hpp" +#include "../render/structures/uniform_types.hpp" +#include "frame.hpp" +#include "gui/render/structures/uniform_types.hpp" + +#include +#include + +namespace gui { + +namespace the_buttons { + +class BorderedWidget; +class BorderedWindow; +class ButtonWidget; +class TextWidget; + +/** + * @brief Top leven UI. Handles inputs and forwards inputs to widgets. + */ +class UserInterface : public virtual scene::Inputs { + private: + // uniform + std::shared_ptr frame_size_uniform_; + std::shared_ptr ui_scale_uniform_; + std::shared_ptr frame_texture_uniform_; + std::shared_ptr border_sizes_; + std::shared_ptr side_lengths_; + std::shared_ptr inner_pattern_size_; + std::shared_ptr texture_regions_; + std::shared_ptr font_color_uniform_; + + // widget renderer + std::shared_ptr window_pipeline_; + std::shared_ptr text_pipeline_; + + // set of all frames + std::list> frames_; + // selected frame + std::shared_ptr selected_frame_; + + // get frame at given location + [[nodiscard]] std::pair< + std::weak_ptr, std::weak_ptr> + get_frame(screen_size_t mouse_position_x, screen_size_t mouse_position_y) const; + + // get frame at given location + [[nodiscard]] inline std::pair< + std::weak_ptr, std::weak_ptr> + get_frame(screen_size_t mouse_position_x, screen_size_t mouse_position_y) { + auto got_frames = const_cast(this)->get_frame( + mouse_position_x, mouse_position_y + ); + return std::make_pair< + std::weak_ptr, std::weak_ptr>( + std::const_pointer_cast(got_frames.first.lock()), + std::const_pointer_cast(got_frames.second.lock()) + ); + } + + // render to window + void reselect_frame(GLFWwindow* window); + + public: + /** + * @brief Construct a new UserInterface object + * + * @param shader::ShaderHandler& shader_handler + * @param uint8_t ui_scale + */ + UserInterface(shader::ShaderHandler& shader_handler, uint8_t ui_scale); + + /** + * @brief Update the entire user interface using the given screen size. + * + * @param screen_size_t width width of screen + * @param screen_size_t height height of screen + */ + void update(screen_size_t width, screen_size_t height); + + /** + * @brief Set the ui scale + * + * @param uint8_t ui_scale Scale to set the ui. Number of screen pixels per UI pixel + */ + inline void + set_ui_scale(uint8_t ui_scale) { + ui_scale_uniform_->set_ui_scale(ui_scale); + } + + /** + * @brief Add new frame to the user interface + * + * @param std::shared_ptr frame frame to add + */ + inline void + add(std::shared_ptr frame) { + auto pos = frames_.begin(); + frames_.insert(pos, frame); + } + + // Each type of widget and frame needs a different method to render. Visitor pattern + /** + * @brief Render a BorderedWindow + * + * @param const BorderedWindow* frame + * @param screen_size_t x_frame_position + * @param screen_size_t y_frame_position + */ + void render_frame( + const BorderedWindow* frame, screen_size_t x_frame_position, + screen_size_t y_frame_position + ) const; + + /** + * @brief Render a BorderedWidget + * + * @param const BorderedWidget* frame + * @param screen_size_t x_frame_position + * @param screen_size_t y_frame_position + */ + void render_frame( + const BorderedWidget* widget, screen_size_t x_frame_position, + screen_size_t y_frame_position + ) const; + + /** + * @brief Render a ButtonWidget + * + * @param const ButtonWidget* frame + * @param screen_size_t x_frame_position + * @param screen_size_t y_frame_position + */ + void render_frame( + const ButtonWidget* widget, screen_size_t x_frame_position, + screen_size_t y_frame_position + ) const; + + /** + * @brief Render a TextWidget + * + * @param const TextWidget* frame + * @param screen_size_t x_frame_position + * @param screen_size_t y_frame_position + */ + void render_frame( + const TextWidget* widget, screen_size_t x_frame_position, + screen_size_t y_frame_position + ) const; + + /** + * @brief Handle key input including mouse keys + * + * @param GLFWwindow* window window event came from + * @param int key GLFW key enum + * @param int scancode GLFW scancode enum + * @param int action GLFW action one of GLFW_PRESS, GLFW_RELEASE, GLFW_REPEAT + * @param int mods GLFW mods enum + */ + virtual void handle_key_event_input( + GLFWwindow* window, int key, int scancode, int action, int mods + ); + + /** + * @brief Handle text input + * + * @param GLFWwindow* window window to listen on + * @param unsigned int codepoint unicode 32 character. + */ + + virtual void handle_text_input_input(GLFWwindow* window, unsigned int codepoint); + + /** + * @brief Handle mouse movement events. + * + * @param GLFWwindow* window window to listen on + * @param double xpos x position in window coordinates + * @param double ypos y position in window coordinates. + */ + virtual void handle_mouse_event_input(GLFWwindow* window, double xpos, double ypos); + + /** + * @brief Handle mouse enter window events + * + * @param GLFWwindow* window window to listen on + * @param int entered 1 if the curser entered the window, 0 if it exited. + */ + virtual void handle_mouse_enter_input(GLFWwindow* window, int entered); + + /** + * @brief Handle mouse enter window events + * + * @param GLFWwindow* window window to listen on + * @param int button + * @param int action + * @param int mods + */ + virtual void + handle_mouse_button_input(GLFWwindow* window, int button, int action, int mods); + + /** + * @brief Handle mouse scroll events + * + * @param GLFWwindow* window window to listen on + * @param double xoffset x offset + * @param double yoffset y offset (usually 0) + */ + virtual void + handle_mouse_scroll_input(GLFWwindow* window, double xoffset, double yoffset); + + /** + * @brief Handle joystick event + * + * @param int jid joystick id + * @param int event + */ + virtual void handle_joystick_input(int jid, int event); + + /** + * @brief Handle file drop event + * + * @param GLFWwindow* window window to listen on + * @param int count number of files passed + * @param const char** paths file paths + */ + virtual void + handle_file_drop_input(GLFWwindow* window, int count, const char** paths); + + /** + * @brief Handle all pooled inputs + * + * @param GLFWwindow* window window + */ + virtual void handle_pooled_inputs(GLFWwindow* window); + + /** + * @brief Setup so this objects handles inputs correctly + * + * @param GLFWwindow* window window + */ + virtual void setup(GLFWwindow* window); + + /** + * @brief Cleanup to original state + * + * @param GLFWwindow* window window + */ + virtual void cleanup(GLFWwindow* window); +}; + +} // namespace the_buttons + +} // namespace gui diff --git a/src/gui/the_buttons/widget.hpp b/src/gui/the_buttons/widget.hpp new file mode 100644 index 00000000..b7c44deb --- /dev/null +++ b/src/gui/the_buttons/widget.hpp @@ -0,0 +1,203 @@ +// -*- lsst-c++ -*- +/* + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +/** + * @file widget.hpp + * + * @author @AlemSnyder + * + * @brief Defines WidgetInterface Class + * + * @ingroup GUI THE_BUTTONS + * + */ + +#pragma once + +#include "../render/gpu_data/data_types.hpp" +#include "../scene/input.hpp" + +namespace gui { + +namespace the_buttons { + +class UserInterface; + +/** + * @brief Interface for widget in UI + */ +class WidgetInterface : public virtual scene::Inputs, public virtual gpu_data::GPUData { + public: + inline virtual ~WidgetInterface() {}; + + /** + * @brief Test if the given location is within the region of the widget + * + * @param screen_size_t x screen position x coordinate + * @param screen_size_t y screen position y coordinate + */ + virtual bool is_interior(screen_size_t x, screen_size_t y) const = 0; + + /** + * @brief Called by the UI to render this widget to the screen + * + * @param UserInterface* user_interface The UserInterface that is rendering the + * widget + * @param screen_size_t x_position Position on the screen to render the widget + * @param screen_size_t y_position Position on the screen to render the widget + */ + virtual void user_interface_render( + const UserInterface* user_interface, screen_size_t x_position, + screen_size_t y_position + ) const = 0; + + /** + * @brief Quarry if this widget has child widgets. + * + * @return true if there are child widgets, false otherwise. + */ + virtual bool has_children() const = 0; + + /** + * @brief Get child widget if it exists at the given position. + * + * @param screen_size_t x screen position x coordinate + * @param screen_size_t y screen position y coordinate + * + * @return std::weak_ptr a week pointer to the child widget. + * It will exist if the given location has a child widget. + */ + virtual std::weak_ptr + get_child_at_position(screen_size_t x, screen_size_t y) const = 0; + + /** + * @brief Get the box that bounds of the widget. + * + * @details This should be the smallest rectangle that completely contains the + * widget. + * + * @return std::array [x position, y position, width, height] + */ + virtual std::array get_bounding_box() const = 0; + + /** + * @brief Handle key input including mouse keys + * + * @param GLFWwindow* window window event came from + * @param int key GLFW key enum + * @param int scancode GLFW scancode enum + * @param int action GLFW action one of GLFW_PRESS, GLFW_RELEASE, GLFW_REPEAT + * @param int mods GLFW mods enum + */ + virtual void handle_key_event_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int key, + [[maybe_unused]] int scancode, [[maybe_unused]] int action, + [[maybe_unused]] int mods + ) = 0; + + /** + * @brief Handle text input + * + * @param GLFWwindow* window window to listen on + * @param unsigned int codepoint unicode 32 character. + */ + + virtual void handle_text_input_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] unsigned int codepoint + ) = 0; + + /** + * @brief Handle mouse movement events. + * + * @param GLFWwindow* window window to listen on + * @param double xpos x position in window coordinates + * @param double ypos y position in window coordinates. + */ + virtual void handle_mouse_event_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] double xpos, + [[maybe_unused]] double ypos + ) = 0; + + /** + * @brief Handle mouse enter window events + * + * @param GLFWwindow* window window to listen on + * @param int button + * @param int action + * @param int mods + */ + virtual void handle_mouse_button_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int button, + [[maybe_unused]] int action, [[maybe_unused]] int mods + ) = 0; + + /** + * @brief Handle mouse scroll events + * + * @param GLFWwindow* window window to listen on + * @param double xoffset x offset + * @param double yoffset y offset (usually 0) + */ + virtual void handle_mouse_scroll_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] double xoffset, + [[maybe_unused]] double yoffset + ) = 0; + + /** + * @brief Handle joystick event + * + * @param int jid joystick id + * @param int event + */ + virtual void + handle_joystick_input([[maybe_unused]] int jid, [[maybe_unused]] int event) = 0; + + /** + * @brief Handle file drop event + * + * @param GLFWwindow* window window to listen on + * @param int count number of files passed + * @param const char** paths file paths + */ + virtual void handle_file_drop_input( + [[maybe_unused]] GLFWwindow* window, [[maybe_unused]] int count, + [[maybe_unused]] const char** paths + ) = 0; + + /** + * @brief Handle all pooled inputs + * + * @param GLFWwindow* window window + */ + virtual void handle_pooled_inputs([[maybe_unused]] GLFWwindow* window) = 0; + + /** + * @brief Setup so this objects handles inputs correctly + * + * @param GLFWwindow* window window + */ + virtual void setup([[maybe_unused]] GLFWwindow* window) = 0; + + /** + * @brief Cleanup to original state + * + * @param GLFWwindow* window window + */ + virtual void cleanup([[maybe_unused]] GLFWwindow* window) = 0; +}; + +template +concept widget_type = std::is_base_of::value; + +} // namespace the_buttons + +} // namespace gui diff --git a/src/gui/ui/gui_entry.hpp b/src/gui/ui/gui_entry.hpp new file mode 100644 index 00000000..52c99f8a --- /dev/null +++ b/src/gui/ui/gui_entry.hpp @@ -0,0 +1,8 @@ +namespace gui { + +namespace ui { + +int main_gui_entry(); +} + +} // namespace gui diff --git a/src/gui/ui/imgui_gui.cpp b/src/gui/ui/imgui_gui.cpp index f9690f97..3d8acb8b 100644 --- a/src/gui/ui/imgui_gui.cpp +++ b/src/gui/ui/imgui_gui.cpp @@ -11,6 +11,7 @@ #include "../handler.hpp" #include "../scene/controls.hpp" #include "../scene/scene.hpp" +#include "../the_buttons/user_interface.hpp" #include "gui/scene/input.hpp" #include "imgui_style.hpp" #include "imgui_windows.hpp" @@ -18,6 +19,7 @@ #include "manifest/object_handler.hpp" #include "opengl_setup.hpp" #include "scene_setup.hpp" +#include "user_interface_setup.hpp" #include "util/mesh.hpp" #include "world/climate.hpp" #include "world/world.hpp" @@ -82,6 +84,7 @@ imgui_entry(GLFWwindow* window, world::World& world, world::Climate& climate) { : gui::scene::KeyMapping(); std::shared_ptr controller = std::make_shared(key_mapping); + scene::InputHandler::imgui_active = true; scene::InputHandler::set_window(window); scene::InputHandler::forward_inputs_to( static_pointer_cast(controller) @@ -101,6 +104,9 @@ imgui_entry(GLFWwindow* window, world::World& world, world::Climate& climate) { Scene main_scene(mode->width, mode->height, shadow_map_size, controller); setup(main_scene, shader_handler, world, climate); + the_buttons::UserInterface main_interface(shader_handler, 4); + setup(main_interface); + //! Main loop while (!glfwWindowShouldClose(window)) { @@ -137,6 +143,9 @@ imgui_entry(GLFWwindow* window, world::World& world, world::Climate& climate) { // "render" scene to the screen main_scene.copy_to_window(window_width, window_height); + // render interface to screen + main_interface.update(window_width, window_height); + // Start the Dear ImGui frame ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); diff --git a/src/gui/ui/imgui_style.cpp b/src/gui/ui/imgui_style.cpp index 40e62ff3..2095f8c1 100644 --- a/src/gui/ui/imgui_style.cpp +++ b/src/gui/ui/imgui_style.cpp @@ -76,8 +76,8 @@ set_imgui_style() { ImGuiIO& io = ImGui::GetIO(); - std::filesystem::path mono_font = - files::get_root_path() / "vendor" / "fonts" / "UbuntuMono-Regular.ttf"; + std::filesystem::path mono_font = files::get_root_path() / "vendor" / "fonts" + / "UbuntuMono" / "UbuntuMono-Regular.ttf"; // - If no fonts are loaded, dear imgui will use the default font. You can also load // multiple fonts and use ImGui::PushFont()/PopFont() to select them. diff --git a/src/gui/ui/opengl_gui.cpp b/src/gui/ui/opengl_gui.cpp index c59f7686..9a7e8708 100644 --- a/src/gui/ui/opengl_gui.cpp +++ b/src/gui/ui/opengl_gui.cpp @@ -1,6 +1,5 @@ #include "opengl_gui.hpp" -#include "../gui_logging.hpp" #include "../handler.hpp" #include "../render/graphics_shaders/program_handler.hpp" #include "../scene/controls.hpp" @@ -10,7 +9,6 @@ #include "opengl_setup.hpp" #include "scene_setup.hpp" #include "util/files.hpp" -#include "util/mesh.hpp" #include "world/climate.hpp" #include "world/world.hpp" @@ -20,7 +18,6 @@ #include #include -#include namespace gui { @@ -47,6 +44,7 @@ opengl_entry(GLFWwindow* window, world::World& world, world::Climate& climate) { : gui::scene::KeyMapping(); std::shared_ptr controller = std::make_shared(key_mapping); + scene::InputHandler::imgui_active = false; scene::InputHandler::set_window(window); scene::InputHandler::forward_inputs_to(controller); diff --git a/src/gui/ui/user_interface_setup.cpp b/src/gui/ui/user_interface_setup.cpp new file mode 100644 index 00000000..bd75ac87 --- /dev/null +++ b/src/gui/ui/user_interface_setup.cpp @@ -0,0 +1,117 @@ +#include "user_interface_setup.hpp" + +#include "../the_buttons/bordered_widget.hpp" +#include "../the_buttons/bordered_window.hpp" +#include "../the_buttons/button_widget.hpp" +#include "../the_buttons/text_widget.hpp" +#include "../the_buttons/user_interface.hpp" +#include "gui/render/structures/font.hpp" +#include "gui/render/structures/window_texture.hpp" +#include "util/files.hpp" +#include "util/png_image.hpp" + +namespace gui { + +void +setup(the_buttons::UserInterface& user_interface) { + auto texture_data_1 = files::read_json_from_file( + files::get_resources_path() / "textures" / "GenericBorder.json" + ); + + if (!texture_data_1) { + LOG_ERROR(logging::file_io_logger, "Failed to load texture"); + return; + } + + auto image_result = image::read_image( + files::get_resources_path() / "textures" / texture_data_1->texture_file + ); + if (!image_result.has_value()) { + LOG_ERROR(logging::file_io_logger, "Error Code {}", image_result.error()); + return; + } + std::shared_ptr image = image_result.value(); + + std::shared_ptr a_window = + std::make_shared( + std::make_shared(image, texture_data_1.value()), + + glm::ivec2(70, 70), glm::ivec2(230, 230) + ); + + std::shared_ptr a_second_window = + std::make_shared( + std::make_shared(image, texture_data_1.value()), + + glm::ivec2(200, 200), glm::ivec2(400, 600) + ); + + auto texture_data_2 = files::read_json_from_file( + files::get_resources_path() / "textures" / "GenericBorder_2.json" + ); + + if (!texture_data_2) { + LOG_ERROR(logging::file_io_logger, "Failed to load texture"); + return; + } + + auto image_result_2 = image::read_image( + files::get_resources_path() / "textures" / texture_data_2->texture_file + ); + if (!image_result_2.has_value()) { + LOG_ERROR(logging::file_io_logger, "Error Code {}", image_result_2.error()); + return; + } + std::shared_ptr image_2 = image_result_2.value(); + + std::shared_ptr a_widget = + a_second_window->make( + std::make_shared(image_2, texture_data_2.value()), + + glm::ivec2(20, 20), glm::ivec2(180, 300) + ); + + auto texture_data_3 = files::read_json_from_file( + files::get_resources_path() / "textures" / "GenericButton.json" + ); + + if (!texture_data_3) { + LOG_ERROR(logging::file_io_logger, "Failed to load texture"); + return; + } + + auto image_result_3 = image::read_image( + files::get_resources_path() / "textures" / texture_data_3->texture_file + ); + if (!image_result_3.has_value()) { + LOG_ERROR(logging::file_io_logger, "Error Code {}", image_result_3.error()); + return; + } + std::shared_ptr image_3 = image_result_3.value(); + + auto a_button = a_widget->make( + std::make_shared(image_3, texture_data_3.value()), + + glm::ivec2(20, 28), glm::ivec2(140, 80), std::function([]() { + LOG_INFO(logging::main_logger, "Button Was Pressed"); + }) + ); + + user_interface.add(a_window); + user_interface.add(a_second_window); + + // window_pipeline->data.push_back(scene.a_window.get()); + + auto my_font = std::make_shared( + files::get_root_path() / "vendor" / "fonts" / "pixel_font" + / "Pixelated_7_10.ttf" + ); + + auto text_widget = a_widget->make( + my_font, glm::ivec2(20, 100), glm::ivec2(140, 240), "Hello World", true + ); + + return; +} + +} // namespace gui diff --git a/src/gui/ui/user_interface_setup.hpp b/src/gui/ui/user_interface_setup.hpp new file mode 100644 index 00000000..b88f1d77 --- /dev/null +++ b/src/gui/ui/user_interface_setup.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "../the_buttons/user_interface.hpp" + +namespace gui { + +void setup(the_buttons::UserInterface& user_interface); + +} diff --git a/src/main.cpp b/src/main.cpp index 6edfe72c..9f2ba827 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -194,6 +194,35 @@ image_test(const argh::parser& cmdl) { return 0; } +int +color_image_text() { + auto image_result = image::read_image( + files::get_resources_path() / "textures" / "GenericBorder.png" + ); + if (!image_result.has_value()) { + LOG_ERROR(logging::file_io_logger, "Error Code {}", image_result.error()); + return 1; + } + std::shared_ptr image = image_result.value(); + + auto FPAimage = + std::dynamic_pointer_cast(image); + + auto result = image::write_image( + *FPAimage.get(), + files::get_resources_path() / "textures" / "GenericBorder_out_test.png" + ); + + if (!result == image::write_result_t::WR_OK) { + LOG_ERROR( + logging::file_io_logger, "Image write failed with result {}.", int(result) + ); + return 1; + } + + return 0; +} + // reimplement int ChunkDataTest() { @@ -457,6 +486,8 @@ tests(const argh::parser& cmdl) { return ChunkDataTest(); } else if (run_function == "imageTest") { return image_test(cmdl); + } else if (run_function == "ColorImageTest") { + return color_image_text(); } else if (run_function == "LoadManifest") { manifest::ObjectHandler object_handler; return object_handler.load_all_manifests(); diff --git a/src/types.hpp b/src/types.hpp index cfdb7b54..e1244b09 100644 --- a/src/types.hpp +++ b/src/types.hpp @@ -105,9 +105,11 @@ static_assert(sizeof(VoxelColorId) == sizeof(MatColorId)); // size in pixels of graphics objects using screen_size_t = int; +// screen position +using screen_position_t = glm::vec<2, screen_size_t>; // I'm so sorry, but this is needed because opengl uses ints to return from get -// window size +// window size, instead of unsigned ints template <> struct std::hash { diff --git a/src/util/image.cpp b/src/util/image.cpp index 66eab253..6dc35a59 100644 --- a/src/util/image.cpp +++ b/src/util/image.cpp @@ -2,10 +2,24 @@ #include "util/color.hpp" +#include + namespace util { namespace image { +Image::Image(void* data, size_t width, size_t height, size_t data_size) : + width_(width), height_(height), data_size_(data_size), + data_(new char[width * height * data_size]) { + std::memcpy(data_.get(), data, width * height * data_size); +}; + +Image::Image(size_t width, size_t height, size_t data_size) : + width_(width), height_(height), data_size_(data_size), + data_(new char[width * height * data_size]) { + std::memset(data_.get(), char(0), width * height * data_size); +}; + png_byte FloatMonochromeImage::get_color(size_t i, size_t j) const { assert(i < width_ && j < height_ && "Position must be within image."); @@ -98,6 +112,35 @@ HALFFloatPolychromeAlphaImage::get_color(size_t i, size_t j) const { } #endif +png_byte +ByteMonochromeImage::get_color(size_t i, size_t j) const { + assert(i < width_ && j < height_ && "Position must be within image."); + return read_data_byte(data_, i * height_ + j)[0]; +} + +std::array +BytePolychromeImage::get_color(size_t i, size_t j) const { + assert(i < width_ && j < height_ && "Position must be within image."); + return read_data_byte(data_, i * height_ + j); +} + +std::array +BytePolychromeAlphaImage::get_color(size_t i, size_t j) const { + assert(i < width_ && j < height_ && "Position must be within image."); + return read_data_byte(data_, i * height_ + j); +} + +void +ByteMonochromeImage::draw_at( + const ByteMonochromeImage& other, size_t position_x, size_t position_y +) { + for (size_t i = 0; i < other.get_width(); i++) { + for (size_t j = 0; j < other.get_height(); j++) { + set_color(other.get_color(i, j), i + position_x, j + position_y); + } + } +} + } // namespace image } // namespace util \ No newline at end of file diff --git a/src/util/image.hpp b/src/util/image.hpp index fcf6d6a4..99be9466 100644 --- a/src/util/image.hpp +++ b/src/util/image.hpp @@ -39,9 +39,11 @@ struct FloatPolychromeAlphaImage_data_t { screen_size_t height; }; +// t represents the underlying type of the data structure; template std::array read_data(std::shared_ptr data, size_t offset) { + // assert(sizeof(T) == data_size_) size_t bit_offset = offset * sizeof(T) * datum_number; char* data_ptr = &data[bit_offset]; T* pixel_data = reinterpret_cast(data_ptr); @@ -66,6 +68,21 @@ read_data_float(std::shared_ptr data, size_t offset) { return out; } +template +std::array +read_data_byte(std::shared_ptr data, size_t offset) { + // assert(sizeof(T) == data_size_) + size_t bit_offset = offset * sizeof(T) * datum_number; + char* data_ptr = &data[bit_offset]; + T* pixel_data = reinterpret_cast(data_ptr); + std::array out; + for (size_t i = 0; i < datum_number; i++) { + // normalize data to size of png_byte + out[i] = pixel_data[i]; + } + return out; +} + class Image { // some data protected: @@ -94,6 +111,10 @@ class Image { std::shared_ptr data, size_t width, size_t height, size_t data_size ) : width_(width), height_(height), data_size_(data_size), data_(data) {}; + Image(void* data, size_t width, size_t height, size_t data_size); + + Image(size_t width, size_t height, size_t data_size); + virtual ~Image() {} }; @@ -127,7 +148,9 @@ class FloatMonochromeImage : public virtual MonochromeImage { FloatMonochromeImage( std::shared_ptr data, size_t width, size_t height, size_t data_size - ) : Image(data, width, height, data_size) {} + ) : Image(data, width, height, data_size) { + assert(data_size == sizeof(float) && "data size must match expected size"); + } inline virtual size_t get_width() const { @@ -150,7 +173,9 @@ class FloatPolychromeImage : public virtual PolychromeImage { FloatPolychromeImage( std::shared_ptr data, size_t width, size_t height, size_t data_size - ) : Image(data, width, height, data_size) {} + ) : Image(data, width, height, data_size) { + assert(data_size == sizeof(float) && "data size must match expected size"); + } inline virtual size_t get_width() const { @@ -180,7 +205,9 @@ class FloatPolychromeAlphaImage : public virtual PolychromeAlphaImage { FloatPolychromeAlphaImage( std::shared_ptr data, size_t width, size_t height, size_t data_size - ) : Image(data, width, height, data_size) {} + ) : Image(data, width, height, data_size) { + assert(data_size == sizeof(float) && "data size must match expected size"); + } FloatPolychromeAlphaImage(std::vector> data) : FloatPolychromeAlphaImage(pad_color_data(data)) {} @@ -216,6 +243,158 @@ class HALFFloatPolychromeAlphaImage : public virtual PolychromeAlphaImage { }; #endif +// BYTE // 8 bit color channels 24 or 32 bit image +class ByteMonochromeImage : public virtual MonochromeImage { + public: + virtual png_byte get_color(size_t i, size_t j) const override; + + // virtual png_byte get_data(size_t i, size_t j) const; + + ByteMonochromeImage( + std::shared_ptr data, size_t width, size_t height, size_t data_size + ) : Image(data, width, height, data_size) { + assert( + data_size == sizeof(unsigned char) && "data size must match expected size" + ); + } + + ByteMonochromeImage(void* data, size_t width, size_t height, size_t data_size) : + Image(data, width, height, data_size) { + assert( + data_size == sizeof(unsigned char) && "data size must match expected size" + ); + } + + ByteMonochromeImage(size_t width, size_t height, size_t data_size) : + Image(width, height, data_size) { + assert( + data_size == sizeof(unsigned char) && "data size must match expected size" + ); + } + + void + draw_at(const ByteMonochromeImage& other, size_t position_x, size_t position_y); + + void + set_color(png_byte color, size_t i, size_t j) { + reinterpret_cast(data_.get())[i * height_ + j] = color; + } + + inline void + transpose() { + auto temp = width_; + width_ = height_; + height_ = temp; + } + + inline virtual size_t + get_width() const { + return width_; + } + + inline virtual size_t + get_height() const { + return height_; + } + + inline virtual ~ByteMonochromeImage() {} +}; + +class BytePolychromeImage : public virtual PolychromeImage { + public: + virtual std::array get_color(size_t i, size_t j) const override; + + // virtual std::array get_data(size_t i, size_t j) const; + // if needed make inline get color + BytePolychromeImage( + std::shared_ptr data, size_t width, size_t height, size_t data_size + ) : Image(data, width, height, data_size) { + assert( + data_size == sizeof(unsigned char) && "data size must match expected size" + ); + } + + BytePolychromeImage(void* data, size_t width, size_t height, size_t data_size) : + Image(data, width, height, data_size) { + assert( + data_size == sizeof(unsigned char) && "data size must match expected size" + ); + } + + BytePolychromeImage(size_t width, size_t height, size_t data_size) : + Image(width, height, data_size) { + assert( + data_size == sizeof(unsigned char) && "data size must match expected size" + ); + } + + inline virtual size_t + get_width() const { + return width_; + } + + inline virtual size_t + get_height() const { + return height_; + } + + inline virtual ~BytePolychromeImage() {} +}; + +// TODO +// Everything commented out is a todo +class BytePolychromeAlphaImage : public virtual PolychromeAlphaImage { + private: + // static BytePolychromeAlphaImage_data_t + // pad_color_data(const std::vector>& vector_data); + + // BytePolychromeAlphaImage(BytePolychromeAlphaImage_data_t data) : + // Image(data.data, data.width, data.height, sizeof(ColorByte)) {} + + public: + virtual std::array get_color(size_t i, size_t j) const override; + + // virtual std::array get_data(size_t i, size_t j) const; + + BytePolychromeAlphaImage( + std::shared_ptr data, size_t width, size_t height, size_t data_size + ) : Image(data, width, height, data_size) { + assert( + data_size == sizeof(unsigned char) && "data size must match expected size" + ); + } + + BytePolychromeAlphaImage( + void* data, size_t width, size_t height, size_t data_size + ) : Image(data, width, height, data_size) { + assert( + data_size == sizeof(unsigned char) && "data size must match expected size" + ); + } + + BytePolychromeAlphaImage(size_t width, size_t height, size_t data_size) : + Image(width, height, data_size) { + assert( + data_size == sizeof(unsigned char) && "data size must match expected size" + ); + } + + // BytePolychromeAlphaImage(std::vector> data) : + // BytePolychromeAlphaImage(pad_color_data(data)) {} + + inline virtual size_t + get_width() const { + return width_; + } + + inline virtual size_t + get_height() const { + return height_; + } + + inline virtual ~BytePolychromeAlphaImage() {} +}; + } // namespace image } // namespace util diff --git a/src/util/png_image.cpp b/src/util/png_image.cpp index 5f123e04..d4b3f389 100644 --- a/src/util/png_image.cpp +++ b/src/util/png_image.cpp @@ -1,6 +1,12 @@ #include "png_image.hpp" +#include "../exceptions.hpp" #include "../logging.hpp" +#include "image.hpp" + +#include + +#include namespace image { @@ -46,4 +52,149 @@ test_function() { #endif +[[nodiscard]] std::expected, int> +read_image(std::filesystem::path path) { + path = std::filesystem::absolute(path); + LOG_BACKTRACE(logging::file_io_logger, "Reading image from {}.", path.string()); + + // Read the tiles from the path specified, and save + // std::ifstream file(path, std::ios::in | std::ios::binary); + if (!std::filesystem::exists(path)) { + LOG_ERROR( + logging::file_io_logger, + "Could not open {}. Are you in the right directory?", path.string() + ); + throw exc::file_not_found_error(path); + } + + std::FILE* file = fopen(path.c_str(), "rb"); + + png_uint_32 width; + png_uint_32 height; + int bit_depth; + int color_type; + int interlace_method; + int compression_method; + int filter_method; + + unsigned char signal[8]; + + fread(signal, 1, 8, file); + + // file.read(reinterpret_cast(signal), 8); + + if (!png_check_sig(signal, 8)) { + LOG_ERROR(logging::file_io_logger, "Failed due to: Bad Signal"); + return std::unexpected(1); + } + + auto png_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (png_ptr == nullptr) { + LOG_ERROR(logging::file_io_logger, "Failed due to: Out of Memory"); + return std::unexpected(4); + } + + auto info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == nullptr) { + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + LOG_ERROR(logging::file_io_logger, "Failed due to: Out of Memory"); + return std::unexpected(4); + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + return std::unexpected(2); + } + + png_init_io(png_ptr, file); + png_set_sig_bytes(png_ptr, 8); + png_read_info(png_ptr, info_ptr); + + png_get_IHDR( + png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_method, + &compression_method, &filter_method + ); + + if (color_type != PNG_COLOR_TYPE_RGB_ALPHA) { + LOG_WARNING(logging::file_io_logger, "Color type not RGBA"); + } + + /* I don't really care about a background color; + if (!png_get_valid(png_ptr, info_ptr, PNG_INFO_bKGD)) { + LOG_ERROR(logging::file_io_logger, "Failed due to: Out of Memory"); + return std::unexpected(1); + }*/ + + // png_bytep [height]; + std::shared_ptr row_pointers(new png_bytep[height]); + + png_uint_32 row_bytes = png_get_rowbytes(png_ptr, info_ptr); + std::shared_ptr data(new char[row_bytes * height]); + // data.reserve(row_bytes * height); + + for (unsigned int i = 0; i < height; i++) { + row_pointers[i] = reinterpret_cast(data.get() + i * row_bytes); + } + + png_read_image(png_ptr, row_pointers.get()); + + png_read_end(png_ptr, nullptr); + + fclose(file); + + // todo do things for bit depth + auto image = + std::make_shared(data, width, height, 1); + +#if DEBUG() + for (unsigned int i = 0; i < image->get_width(); i++) { + for (unsigned int j = 0; j < image->get_height(); j++) { + auto raw_image_color0 = row_pointers[i][j * 4]; + auto raw_image_color1 = row_pointers[i][j * 4 + 1]; + auto raw_image_color2 = row_pointers[i][j * 4 + 2]; + auto raw_image_color3 = row_pointers[i][j * 4 + 3]; + + auto data_color0 = + reinterpret_cast(data.get())[i * row_bytes + j * 4]; + auto data_color1 = + reinterpret_cast(data.get())[i * row_bytes + j * 4 + 1]; + auto data_color2 = + reinterpret_cast(data.get())[i * row_bytes + j * 4 + 2]; + auto data_color3 = + reinterpret_cast(data.get())[i * row_bytes + j * 4 + 3]; + if (raw_image_color0 != data_color0 || raw_image_color1 != data_color1 + || raw_image_color2 != data_color2 || raw_image_color3 != data_color3) { + LOG_WARNING(logging::file_io_logger, "Pixel incorrect at {}{}", i, j); + LOG_WARNING( + logging::file_io_logger, "[{},{},{},{}]", data_color0, data_color1, + data_color2, data_color3 + ); + LOG_WARNING( + logging::file_io_logger, "[{},{},{},{}]", raw_image_color0, + raw_image_color1, raw_image_color2, raw_image_color3 + ); + } + + auto color = image->get_color(i, j); + + if (raw_image_color0 != color[0] || raw_image_color1 != color[1] + || raw_image_color2 != color[2] || raw_image_color3 != color[3]) { + LOG_WARNING(logging::file_io_logger, "Pixel incorrect at {}{}", i, j); + LOG_WARNING( + logging::file_io_logger, "[{},{},{},{}]", color[0], color[1], + color[2], color[3] + ); + LOG_WARNING( + logging::file_io_logger, "[{},{},{},{}]", raw_image_color0, + raw_image_color1, raw_image_color2, raw_image_color3 + ); + } + } + } +#endif + + return image; +} + } // namespace image diff --git a/src/util/png_image.hpp b/src/util/png_image.hpp index 31c3c968..4c824bee 100644 --- a/src/util/png_image.hpp +++ b/src/util/png_image.hpp @@ -28,9 +28,16 @@ #include #include +#include #include #include +namespace util { +namespace image { +class Image; +} +} // namespace util + namespace image { enum write_result_t { @@ -56,124 +63,12 @@ concept ImageColor = requires(T const img, size_t i, size_t j) { { img.get_color(i, j) } -> std::same_as>; }; -template -[[nodiscard]] write_result_t -write_image(T image, const std::filesystem::path& path) { - // Keep track of if we succeeded or not - write_result_t status = WR_OK; - - // Get image information - size_t WIDTH = image.get_width(); - size_t HEIGHT = image.get_height(); - - char meta_lang[] = "en"; - char meta_key[] = "An Image"; - char meta_text[] = "Some text"; - - // Create png variables - png_structp png_ptr = nullptr; - png_infop info_ptr = nullptr; - - // Open the file for writing - auto path_str = path.string(); // need to keep this from being free'd - std::FILE* file = fopen(path_str.c_str(), "wb"); - - if (!file) { - status = WR_FOPEN_FAILED; - goto fopen_failed; - } - - // Create our write struct - // TODO these nullptr should be function pointers - png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if (!png_ptr) { - status = WR_CREATE_WRITE_STRUCT_FAILED; - goto png_create_write_struct_failed; - } - - // Create our info struct for this png image - info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) { - status = WR_CREATE_INFO_STRUCT_FAILED; - goto png_create_info_struct_failed; - } - - // Set jump buffer for callbacks - if (setjmp(png_jmpbuf(png_ptr))) { - status = WR_SETJMP_PNG_JMPBUF_FAILED; - goto setjmp_png_jmpbuf_failed; - } - - // Set up IO for our file - png_init_io(png_ptr, file); - - // set information about our image - png_set_IHDR( - png_ptr, info_ptr, WIDTH, HEIGHT, 8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT - ); - - // Set metadata about the PNG file - png_text meta_data; - memset(&meta_data, 0, sizeof(meta_data)); // clear struct - - meta_data.compression = PNG_TEXT_COMPRESSION_NONE; // no compression - meta_data.lang_key = meta_lang; - meta_data.key = meta_key; - meta_data.text = meta_text; - - png_set_text(png_ptr, info_ptr, &meta_data, 1); - png_write_info(png_ptr, info_ptr); - - // multiple colors are written in the same row - // not sure how I want to implement this in a template safe way - // https://stackoverflow.com/questions/48757099/write-an-image-row-by-row-with-libpng-using-c - - /* - * write rows of image - */ - size_t i, j; - png_bytep row; - - // allocate data for row - row = new (std::nothrow) png_byte[WIDTH]; - if (!row) { - status = WR_ROW_MALLOC_FAILED; - goto row_malloc_failed; - } - - // write row data - for (i = 0; i < HEIGHT; i++) { - // set row data - for (j = 0; j < WIDTH; j++) - row[j] = static_cast(image.get_color(i, j)); - - // write the row - png_write_row(png_ptr, row); - } - - /* - * Cleanups - */ - // Free our row data - delete[] row; - -row_malloc_failed: - // Finish our write - png_write_end(png_ptr, info_ptr); - -setjmp_png_jmpbuf_failed: -png_create_info_struct_failed: - // Free our write struct - png_destroy_write_struct(&png_ptr, &info_ptr); - -png_create_write_struct_failed: - // Close our file - fclose(file); - -fopen_failed: - return status; -} +template +concept ImageRGBA = requires(T const img, size_t i, size_t j) { + { img.get_height() } -> std::convertible_to; + { img.get_width() } -> std::convertible_to; + { img.get_color(i, j) } -> std::same_as>; +}; void log_result(write_result_t result, const std::filesystem::path& path); @@ -199,9 +94,23 @@ class ImageTest { } }; -template +namespace { + +template +auto +to_array(std::array array) { + return array; +} + +template +auto +to_array(T value) { + return std::array({value}); +} + +template [[nodiscard]] write_result_t -write_image(T image, const std::filesystem::path& path) { +write_image_base(T image, const std::filesystem::path& path /*other settings*/) { // Keep track of if we succeeded or not write_result_t status = WR_OK; @@ -250,9 +159,21 @@ write_image(T image, const std::filesystem::path& path) { // Set up IO for our file png_init_io(png_ptr, file); + int color_type; + + if constexpr (n == 1) { + color_type = PNG_COLOR_TYPE_GRAY; + } else if constexpr (n == 3) { + color_type = PNG_COLOR_TYPE_RGB; + } else if constexpr (n == 4) { + color_type = PNG_COLOR_TYPE_RGB_ALPHA; + } else { + static_assert(n == 1 || n == 3 || n == 4, "Invalid number of color channels"); + } + // set information about our image png_set_IHDR( - png_ptr, info_ptr, WIDTH, HEIGHT, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, + png_ptr, info_ptr, WIDTH, HEIGHT, 8, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT ); @@ -279,7 +200,7 @@ write_image(T image, const std::filesystem::path& path) { png_bytep row; // allocate data for row - row = new (std::nothrow) png_byte[3 * WIDTH]; + row = new (std::nothrow) png_byte[n * WIDTH]; if (!row) { status = WR_ROW_MALLOC_FAILED; goto row_malloc_failed; @@ -289,10 +210,10 @@ write_image(T image, const std::filesystem::path& path) { for (i = 0; i < HEIGHT; i++) { // set row data for (j = 0; j < WIDTH; j++) { - const std::array pixel_color = image.get_color(i, j); - row[3 * j] = pixel_color[0]; - row[3 * j + 1] = pixel_color[1]; - row[3 * j + 2] = pixel_color[2]; + const std::array pixel_color = to_array(image.get_color(j, i)); + for (unsigned int channel = 0; channel < n; channel++) { + row[n * j + channel] = pixel_color[channel]; + } } // write the row @@ -322,6 +243,26 @@ write_image(T image, const std::filesystem::path& path) { return status; } +} // namespace + +template +[[nodiscard]] write_result_t +write_image(T image, const std::filesystem::path& path) { + return write_image_base(image, path); +} + +template +[[nodiscard]] write_result_t +write_image(T image, const std::filesystem::path& path) { + return write_image_base(image, path); +} + +template +[[nodiscard]] write_result_t +write_image(T image, const std::filesystem::path& path) { + return write_image_base(image, path); +} + class ColorImageTest { public: ColorImageTest() {}; @@ -346,4 +287,7 @@ class ColorImageTest { // #endif +[[nodiscard]] std::expected, int> +read_image(std::filesystem::path path); + } // namespace image diff --git a/src/world/object/entity/entity.cpp b/src/world/object/entity/entity.cpp index 14f64c39..95e4b2b6 100644 --- a/src/world/object/entity/entity.cpp +++ b/src/world/object/entity/entity.cpp @@ -96,7 +96,9 @@ EntityInstance::EntityInstance(std::shared_ptr entity_type) : EntityInstance::~EntityInstance() {} void -EntityInstance::operate(std::chrono::milliseconds delta_time, bool show) { +EntityInstance::operate( + [[maybe_unused]] std::chrono::milliseconds delta_time, bool show +) { if (std::shared_ptr entity_type = entity_type_.lock()) { glm::vec3 position = entity_type->decision(this); position_ = position; diff --git a/src/world/object/entity_controller.cpp b/src/world/object/entity_controller.cpp index 70a7f252..1198e00a 100644 --- a/src/world/object/entity_controller.cpp +++ b/src/world/object/entity_controller.cpp @@ -10,7 +10,7 @@ namespace object { // then check that update entities is correctly called. void -EntityController::update_entities(glm::mat4 transforms_matrix) { +EntityController::update_entities([[maybe_unused]] glm::mat4 transforms_matrix) { // maybe multi-processes for (auto& region : entity_instances_) { if (true) { diff --git a/vendor/fonts/UFL.txt b/vendor/fonts/UbuntuMono/UFL.txt similarity index 100% rename from vendor/fonts/UFL.txt rename to vendor/fonts/UbuntuMono/UFL.txt diff --git a/vendor/fonts/UbuntuMono-Bold.ttf b/vendor/fonts/UbuntuMono/UbuntuMono-Bold.ttf similarity index 100% rename from vendor/fonts/UbuntuMono-Bold.ttf rename to vendor/fonts/UbuntuMono/UbuntuMono-Bold.ttf diff --git a/vendor/fonts/UbuntuMono-BoldItalic.ttf b/vendor/fonts/UbuntuMono/UbuntuMono-BoldItalic.ttf similarity index 100% rename from vendor/fonts/UbuntuMono-BoldItalic.ttf rename to vendor/fonts/UbuntuMono/UbuntuMono-BoldItalic.ttf diff --git a/vendor/fonts/UbuntuMono-Italic.ttf b/vendor/fonts/UbuntuMono/UbuntuMono-Italic.ttf similarity index 100% rename from vendor/fonts/UbuntuMono-Italic.ttf rename to vendor/fonts/UbuntuMono/UbuntuMono-Italic.ttf diff --git a/vendor/fonts/UbuntuMono-Regular.ttf b/vendor/fonts/UbuntuMono/UbuntuMono-Regular.ttf similarity index 100% rename from vendor/fonts/UbuntuMono-Regular.ttf rename to vendor/fonts/UbuntuMono/UbuntuMono-Regular.ttf diff --git a/vendor/fonts/pixel_font/Pixelated_7_10.ttf b/vendor/fonts/pixel_font/Pixelated_7_10.ttf new file mode 100644 index 00000000..c38917d9 Binary files /dev/null and b/vendor/fonts/pixel_font/Pixelated_7_10.ttf differ