diff --git a/.gitmodules b/.gitmodules index 5ea5e69cb30c7..906f56129933b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,9 @@ [submodule "external/VulkanMemoryAllocator"] path = external/VulkanMemoryAllocator url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator +[submodule "external/imgui"] + path = external/imgui + url = https://github.com/ocornut/imgui.git +[submodule "external/glm"] + path = external/glm + url = https://github.com/g-truc/glm.git diff --git a/cmake/TaichiCore.cmake b/cmake/TaichiCore.cmake index 13ee4b209a1d5..9bf87a62be134 100644 --- a/cmake/TaichiCore.cmake +++ b/cmake/TaichiCore.cmake @@ -36,11 +36,19 @@ if (WIN32) endif() endif() +set(TI_WITH_GGUI OFF) +if(TI_WITH_CUDA AND TI_WITH_VULKAN) + set(TI_WITH_GGUI ON) +endif() + + if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/glad/src/glad.c") set(TI_WITH_OPENGL OFF) message(WARNING "external/glad submodule not detected. Settings TI_WITH_OPENGL to OFF.") endif() + + file(GLOB TAICHI_CORE_SOURCE "taichi/*/*/*/*.cpp" "taichi/*/*/*.cpp" "taichi/*/*.cpp" "taichi/*.cpp" "taichi/*/*/*/*.h" "taichi/*/*/*.h" "taichi/*/*.h" "taichi/*.h" "tests/cpp/task/*.cpp") @@ -54,6 +62,30 @@ file(GLOB TAICHI_METAL_SOURCE "taichi/backends/metal/*.h" "taichi/backends/metal file(GLOB TAICHI_OPENGL_SOURCE "taichi/backends/opengl/*.h" "taichi/backends/opengl/*.cpp" "taichi/backends/opengl/shaders/*") file(GLOB TAICHI_CC_SOURCE "taichi/backends/cc/*.h" "taichi/backends/cc/*.cpp") file(GLOB TAICHI_VULKAN_SOURCE "taichi/backends/vulkan/*.h" "taichi/backends/vulkan/*.cpp" "taichi/backends/vulkan/shaders/*") + +file(GLOB TAICHI_GGUI_SOURCE + "taichi/ui/*.cpp" "taichi/ui/*/*.cpp" "taichi/ui/*/*/*.cpp" "taichi/ui/*/*/*/*.cpp" "taichi/ui/*/*/*/*/*.cpp" + "taichi/ui/*.h" "taichi/ui/*/*.h" "taichi/ui/*/*/*.h" "taichi/ui/*/*/*/*.h" "taichi/ui/*/*/*/*/*.h" + "taichi/ui/backends/vulkan/renderables/kernels.cu" +) +list(REMOVE_ITEM TAICHI_CORE_SOURCE ${TAICHI_GGUI_SOURCE}) + + +if(TI_WITH_GGUI) + add_definitions(-DTI_WITH_GGUI) + + enable_language(CUDA) + set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -use_fast_math -std=c++17" ) + list(APPEND TAICHI_CORE_SOURCE ${TAICHI_GGUI_SOURCE}) + + include_directories(SYSTEM external/glm) + +endif() + + + + + # These are required, regardless of whether Vulkan is enabled or not # TODO(#2298): Clean up the Vulkan code structure, all Vulkan API related things should be # guarded by TI_WITH_VULKAN macro at the source code level. @@ -295,3 +327,16 @@ if (WIN32) set_target_properties(${CORE_WITH_PYBIND_LIBRARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/runtimes") endif () + + +if(TI_WITH_GGUI) + + # Dear ImGui + add_definitions(-DIMGUI_IMPL_VULKAN_NO_PROTOTYPES) + set(IMGUI_DIR external/imgui) + include_directories(external/glfw/include) + include_directories(SYSTEM ${IMGUI_DIR} ${IMGUI_DIR}/backends ..) + add_library(imgui ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp ${IMGUI_DIR}/backends/imgui_impl_vulkan.cpp ${IMGUI_DIR}/imgui.cpp ${IMGUI_DIR}/imgui_draw.cpp ${IMGUI_DIR}/imgui_tables.cpp ${IMGUI_DIR}/imgui_widgets.cpp) + target_link_libraries(${CORE_LIBRARY_NAME} imgui) + +endif() diff --git a/external/glm b/external/glm new file mode 160000 index 0000000000000..06ed280db4e27 --- /dev/null +++ b/external/glm @@ -0,0 +1 @@ +Subproject commit 06ed280db4e274fa5e1f36d5ea4f7dfd654ff9b0 diff --git a/external/imgui b/external/imgui new file mode 160000 index 0000000000000..c7529c8ea8ef3 --- /dev/null +++ b/external/imgui @@ -0,0 +1 @@ +Subproject commit c7529c8ea8ef36e344d00cb38e1493b465ce6090 diff --git a/taichi/ui/common/app_config.h b/taichi/ui/common/app_config.h new file mode 100644 index 0000000000000..4728a7ff4cc4e --- /dev/null +++ b/taichi/ui/common/app_config.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include "taichi/ui/utils/utils.h" +#include "taichi/program/arch.h" + +TI_UI_NAMESPACE_BEGIN + +struct AppConfig { + std::string name; + int width{0}; + int height{0}; + bool vsync{false}; + std::string package_path; + taichi::lang::Arch ti_arch; +}; + +TI_UI_NAMESPACE_END diff --git a/taichi/ui/common/camera.h b/taichi/ui/common/camera.h new file mode 100644 index 0000000000000..58fa47821db44 --- /dev/null +++ b/taichi/ui/common/camera.h @@ -0,0 +1,38 @@ +#pragma once + +#include "taichi/ui/utils/utils.h" + +TI_UI_NAMESPACE_BEGIN + +enum class ProjectionMode : int { Perspective = 0, Orthogonal = 1 }; + +struct Camera { + glm::vec3 position; + glm::vec3 lookat; + glm::vec3 up; + ProjectionMode projection_mode = ProjectionMode::Perspective; + + float fov{45}; + + float left{-1}; + float right{1}; + float top{-1}; + float bottom{1}; + float z_near{0.1}; + float z_far{1000}; + + glm::mat4 get_view_matrix() { + return glm::lookAt(position, lookat, up); + } + glm::mat4 get_projection_matrix(float aspect_ratio) { + if (projection_mode == ProjectionMode::Perspective) { + return glm::perspective(fov, aspect_ratio, z_far, z_near); + } else if (projection_mode == ProjectionMode::Orthogonal) { + return glm::ortho(left, right, top, bottom, z_far, z_near); + } else { + throw std::runtime_error("invalid camera projection mode"); + } + } +}; + +TI_UI_NAMESPACE_END diff --git a/taichi/ui/common/canvas_base.h b/taichi/ui/common/canvas_base.h new file mode 100644 index 0000000000000..44198b9e83d31 --- /dev/null +++ b/taichi/ui/common/canvas_base.h @@ -0,0 +1,41 @@ +#pragma once +#include "taichi/ui/common/field_info.h" +#include "taichi/ui/common/scene_base.h" +#include "taichi/ui/common/renderable_info.h" +#include "taichi/ui/utils/utils.h" + +TI_UI_NAMESPACE_BEGIN + +struct SetImageInfo { + FieldInfo img; +}; + +struct TrianglesInfo { + RenderableInfo renderable_info; + glm::vec3 color; +}; + +struct CirclesInfo { + RenderableInfo renderable_info; + glm::vec3 color; + float radius; +}; + +struct LinesInfo { + RenderableInfo renderable_info; + glm::vec3 color; + float width; +}; + +class CanvasBase { + public: + virtual void set_background_color(const glm::vec3 &color) = 0; + virtual void set_image(const SetImageInfo &info) = 0; + virtual void triangles(const TrianglesInfo &info) = 0; + virtual void circles(const CirclesInfo &info) = 0; + virtual void lines(const LinesInfo &info) = 0; + virtual void scene(SceneBase *scene) = 0; + virtual ~CanvasBase() = default; +}; + +TI_UI_NAMESPACE_END diff --git a/taichi/ui/common/event.h b/taichi/ui/common/event.h new file mode 100644 index 0000000000000..b3b5d0f074a1e --- /dev/null +++ b/taichi/ui/common/event.h @@ -0,0 +1,14 @@ +#pragma once +#include "taichi/ui/utils/utils.h" + +TI_UI_NAMESPACE_BEGIN + +enum class EventType : int { Any = 0, Press = 1, Release = 2 }; + +struct Event { + EventType tag; + + DEFINE_PROPERTY(std::string, key); +}; + +TI_UI_NAMESPACE_END diff --git a/taichi/ui/common/field_info.h b/taichi/ui/common/field_info.h new file mode 100644 index 0000000000000..1de960db86336 --- /dev/null +++ b/taichi/ui/common/field_info.h @@ -0,0 +1,33 @@ +#pragma once +#include "taichi/ui/utils/utils.h" + +#include "taichi/ir/type_utils.h" + +TI_UI_NAMESPACE_BEGIN + +enum class FieldSource : int { + TaichiCuda = 0, + TaichiX64 = 1, + TaichiVulkan = 2, + TaichiOpenGL = 3 + // support np array / torch tensor in the future? +}; + +enum class FieldType : int { Scalar = 0, Matrix = 1 }; + +struct FieldInfo { + DEFINE_PROPERTY(bool, valid) + DEFINE_PROPERTY(FieldType, field_type); + DEFINE_PROPERTY(int, matrix_rows); + DEFINE_PROPERTY(int, matrix_cols); + DEFINE_PROPERTY(std::vector, shape); + DEFINE_PROPERTY(FieldSource, field_source); + DEFINE_PROPERTY(taichi::lang::DataType, dtype); + DEFINE_PROPERTY(uint64_t, data); + + FieldInfo() { + valid = false; + } +}; + +TI_UI_NAMESPACE_END diff --git a/taichi/ui/common/gui_base.h b/taichi/ui/common/gui_base.h new file mode 100644 index 0000000000000..6aae3d11aba63 --- /dev/null +++ b/taichi/ui/common/gui_base.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include "taichi/ui/utils/utils.h" + +TI_UI_NAMESPACE_BEGIN + +class GuiBase { + public: + virtual void begin(std::string name, + float x, + float y, + float width, + float height) = 0; + virtual void end() = 0; + virtual void text(std::string text) = 0; + virtual bool checkbox(std::string name, bool old_value) = 0; + virtual float slider_float(std::string name, + float old_value, + float minimum, + float maximum) = 0; + virtual glm::vec3 color_edit_3(std::string name, glm::vec3 old_value) = 0; + virtual bool button(std::string text) = 0; + virtual ~GuiBase() = default; +}; + +TI_UI_NAMESPACE_END diff --git a/taichi/ui/common/input_handler.h b/taichi/ui/common/input_handler.h new file mode 100644 index 0000000000000..6842146586057 --- /dev/null +++ b/taichi/ui/common/input_handler.h @@ -0,0 +1,104 @@ +#pragma once +#include +#include +#include +#include "taichi/ui/utils/utils.h" + +TI_UI_NAMESPACE_BEGIN + +class InputHandler { + public: + void key_callback(GLFWwindow *window, + int key, + int scancode, + int action, + int mode) { + if (action == GLFW_PRESS) { + keys_[key] = true; + } else if (action == GLFW_RELEASE) { + keys_[key] = false; + } + for (auto f : user_key_callbacks_) { + f(key, action); + } + } + + void mouse_pos_callback(GLFWwindow *window, double xpos, double ypos) { + if (first_mouse_) { + last_x_ = xpos; + last_y_ = ypos; + first_mouse_ = false; + } + + last_x_ = xpos; + last_y_ = ypos; + + for (auto f : user_mouse_pos_callbacks_) { + f(xpos, ypos); + } + } + + void mouse_button_callback(GLFWwindow *window, + int button, + int action, + int modifier) { + if (button == GLFW_MOUSE_BUTTON_LEFT) { + if (action == GLFW_PRESS) { + left_mouse_down_ = true; + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + } + if (action == GLFW_RELEASE) { + left_mouse_down_ = false; + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + } + if (action == GLFW_PRESS) { + keys_[button] = true; + } else if (action == GLFW_RELEASE) { + keys_[button] = false; + } + for (auto f : user_mouse_button_callbacks_) { + f(button, action); + } + } + + bool is_pressed(int key) { + return keys_[key]; + } + + float last_x() { + return last_x_; + } + + float last_y() { + return last_y_; + } + + void add_key_callback(std::function f) { + user_key_callbacks_.push_back(f); + } + void add_mouse_pos_callback(std::function f) { + user_mouse_pos_callbacks_.push_back(f); + } + void add_mouse_button_callback(std::function f) { + user_mouse_button_callbacks_.push_back(f); + } + + InputHandler() : keys_(1024, false) { + } + + private: + bool first_mouse_ = true; + + bool left_mouse_down_ = false; + + std::vector keys_; + float last_x_ = 0; + float last_y_ = 0; + + std::vector> user_key_callbacks_; + std::vector> user_mouse_pos_callbacks_; + std::vector> user_mouse_button_callbacks_; +}; + +TI_UI_NAMESPACE_END diff --git a/taichi/ui/common/renderable_info.h b/taichi/ui/common/renderable_info.h new file mode 100644 index 0000000000000..2489ede86a789 --- /dev/null +++ b/taichi/ui/common/renderable_info.h @@ -0,0 +1,15 @@ +#pragma once +#include "taichi/ui/common/field_info.h" +#include "taichi/ui/utils/utils.h" + +TI_UI_NAMESPACE_BEGIN + +struct RenderableInfo { + FieldInfo vertices; + FieldInfo normals; + FieldInfo tex_coords; + FieldInfo per_vertex_color; + FieldInfo indices; +}; + +TI_UI_NAMESPACE_END diff --git a/taichi/ui/common/scene_base.h b/taichi/ui/common/scene_base.h new file mode 100644 index 0000000000000..388e1a8f3fdbc --- /dev/null +++ b/taichi/ui/common/scene_base.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include "taichi/ui/common/field_info.h" +#include "taichi/ui/common/renderable_info.h" +#include "taichi/ui/common/camera.h" +#include "taichi/ui/utils/utils.h" + +TI_UI_NAMESPACE_BEGIN + +struct PointLight { + glm::vec3 pos; + glm::vec3 color; +}; + +struct MeshInfo { + RenderableInfo renderable_info; + glm::vec3 color; +}; + +struct ParticlesInfo { + RenderableInfo renderable_info; + glm::vec3 color; + float radius; +}; + +class SceneBase { + public: + static constexpr int kMaxPointLights = 16; + + void set_camera(const Camera &camera) { + camera_ = camera; + } + + void mesh(const MeshInfo &info) { + mesh_infos_.push_back(info); + } + void particles(const ParticlesInfo &info) { + particles_infos_.push_back(info); + } + void point_light(glm::vec3 pos, glm::vec3 color) { + if (point_lights_.size() >= kMaxPointLights) { + throw std::runtime_error("point light count exceeds kMaxPointLights"); + } + point_lights_.push_back({pos, color}); + } + void ambient_light(glm::vec3 color) { + ambient_light_color_ = color; + } + virtual ~SceneBase() = default; + + protected: + Camera camera_; + glm::vec3 ambient_light_color_ = glm::vec3(0.1, 0.1, 0.1); + std::vector point_lights_; + std::vector mesh_infos_; + std::vector particles_infos_; +}; + +TI_UI_NAMESPACE_END diff --git a/taichi/ui/common/window_base.cpp b/taichi/ui/common/window_base.cpp new file mode 100644 index 0000000000000..449f524f3012b --- /dev/null +++ b/taichi/ui/common/window_base.cpp @@ -0,0 +1,160 @@ +#include "taichi/ui/common/window_base.h" + +TI_UI_NAMESPACE_BEGIN + +WindowBase ::WindowBase(AppConfig config) : config_(config) { + glfw_window_ = create_glfw_window_(config_.name, config_.width, + config_.height, config_.vsync); + glfwSetWindowUserPointer(glfw_window_, this); + set_callbacks(); + last_record_time_ = glfwGetTime(); +} + +void WindowBase::set_callbacks() { + glfwSetKeyCallback(glfw_window_, key_callback); + glfwSetCursorPosCallback(glfw_window_, mouse_pos_callback); + glfwSetMouseButtonCallback(glfw_window_, mouse_button_callback); + + input_handler_.add_key_callback([&](int key, int action) { + if (action == GLFW_PRESS) { + events_.push_back({EventType::Press, button_id_to_name(key)}); + } else if (action == GLFW_RELEASE) { + events_.push_back({EventType::Release, button_id_to_name(key)}); + } + }); + input_handler_.add_mouse_button_callback([&](int key, int action) { + if (action == GLFW_PRESS) { + events_.push_back({EventType::Press, button_id_to_name(key)}); + } else if (action == GLFW_RELEASE) { + events_.push_back({EventType::Release, button_id_to_name(key)}); + } + }); +} + +CanvasBase *WindowBase::get_canvas() { + return nullptr; +} + +void WindowBase::show() { + ++frames_since_last_record_; + + double current_time = glfwGetTime(); + + if (current_time - last_record_time_ >= 1) { + double FPS = + (double)frames_since_last_record_ / (current_time - last_record_time_); + std::string glfw_window_text = + config_.name + " " + std::to_string(FPS) + " FPS"; + + glfwSetWindowTitle(glfw_window_, glfw_window_text.c_str()); + last_record_time_ = current_time; + frames_since_last_record_ = 0; + } + + glfwPollEvents(); +} + +bool WindowBase::is_pressed(std::string button) { + int button_id = buttom_name_to_id(button); + return input_handler_.is_pressed(button_id) > 0; +} + +bool WindowBase::is_running() { + return !glfwWindowShouldClose(glfw_window_); +} + +void WindowBase::set_is_running(bool value) { + glfwSetWindowShouldClose(glfw_window_, !value); +} + +std::pair WindowBase::get_cursor_pos() { + float x = input_handler_.last_x(); + float y = input_handler_.last_y(); + + x = x / (float)config_.width; + y = (config_.height - y) / (float)config_.height; + return std::make_pair(x, y); +} + +std::vector WindowBase::get_events(EventType tag) { + glfwPollEvents(); + std::vector result; + std::list::iterator i = events_.begin(); + while (i != events_.end()) { + if (i->tag == tag || tag == EventType::Any) { + result.push_back(*i); + i = events_.erase(i); + } else { + ++i; + } + } + return result; +} + +bool WindowBase::get_event(EventType tag) { + glfwPollEvents(); + if (events_.size() == 0) { + return false; + } + if (tag == EventType::Any) { + current_event_ = events_.front(); + events_.pop_front(); + return true; + } else { + std::list::iterator it; + for (it = events_.begin(); it != events_.end(); ++it) { + if (it->tag == tag) { + current_event_ = *it; + events_.erase(it); + return true; + } + } + return false; + } +} + +// these 2 are used to export the `current_event` field to python +Event WindowBase::get_current_event() { + return current_event_; +} +void WindowBase::set_current_event(const Event &event) { + current_event_ = event; +} + +WindowBase::~WindowBase() { + glfwDestroyWindow(glfw_window_); +} + +GuiBase *WindowBase::GUI() { + return nullptr; +} + +void WindowBase::key_callback(GLFWwindow *glfw_window, + int key, + int scancode, + int action, + int mode) { + auto window = + reinterpret_cast(glfwGetWindowUserPointer(glfw_window)); + window->input_handler_.key_callback(glfw_window, key, scancode, action, mode); +} + +void WindowBase::mouse_pos_callback(GLFWwindow *glfw_window, + double xpos, + double ypos) { + auto window = + reinterpret_cast(glfwGetWindowUserPointer(glfw_window)); + window->input_handler_.mouse_pos_callback(glfw_window, xpos, ypos); +} + +void WindowBase::mouse_button_callback(GLFWwindow *glfw_window, + int button, + int action, + int modifier) { + auto window = + reinterpret_cast(glfwGetWindowUserPointer(glfw_window)); + window->input_handler_.mouse_button_callback(glfw_window, button, action, + modifier); +} + +TI_UI_NAMESPACE_END diff --git a/taichi/ui/common/window_base.h b/taichi/ui/common/window_base.h new file mode 100644 index 0000000000000..d960bffbb59c1 --- /dev/null +++ b/taichi/ui/common/window_base.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include "taichi/ui/utils/utils.h" +#include "input_handler.h" + +#include +#include +#include +#include +#include + +#include "taichi/ui/common/canvas_base.h" +#include "taichi/ui/common/event.h" +#include "taichi/ui/common/gui_base.h" +#include "taichi/ui/common/app_config.h" + +TI_UI_NAMESPACE_BEGIN + +class WindowBase { + public: + bool is_pressed(std::string button); + + bool is_running(); + + void set_is_running(bool value); + + std::pair get_cursor_pos(); + + std::vector get_events(EventType tag); + + bool get_event(EventType tag); + + Event get_current_event(); + + void set_current_event(const Event &event); + + virtual CanvasBase *get_canvas(); + + virtual void show(); + + virtual GuiBase *GUI(); + + virtual ~WindowBase(); + + protected: + AppConfig config_; + GLFWwindow *glfw_window_{nullptr}; + InputHandler input_handler_; + + // used for FPS counting + double last_record_time_{0.0}; + int frames_since_last_record_{0}; + + std::list events_; + Event current_event_{EventType::Any, ""}; + + protected: + WindowBase(AppConfig config); + + void set_callbacks(); + + static void key_callback(GLFWwindow *glfw_window, + int key, + int scancode, + int action, + int mode); + + static void mouse_pos_callback(GLFWwindow *glfw_window, + double xpos, + double ypos); + + static void mouse_button_callback(GLFWwindow *glfw_window, + int button, + int action, + int modifier); +}; + +TI_UI_NAMESPACE_END diff --git a/taichi/ui/utils/utils.h b/taichi/ui/utils/utils.h new file mode 100644 index 0000000000000..f898a11ca1844 --- /dev/null +++ b/taichi/ui/utils/utils.h @@ -0,0 +1,218 @@ + + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN64 +#include +#include +#include +#include +#define _USE_MATH_DEFINES +#endif + +#ifdef _WIN64 +#define VK_USE_PLATFORM_WIN32_KHR 1 +#endif +#include +#include + +#include + +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include +#include + +#define TI_UI_NAMESPACE_BEGIN \ + namespace taichi { \ + namespace ui { + +#define TI_UI_NAMESPACE_END \ + } \ + } + +TI_UI_NAMESPACE_BEGIN + +inline void initGLFW() { + if (!glfwInit()) { + printf("cannot initialize GLFW\n"); + exit(EXIT_FAILURE); + } +} + +inline GLFWwindow *create_glfw_window_(const std::string &name, + int screenWidth, + int screenHeight, + bool vsync) { + initGLFW(); + GLFWwindow *window; + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + + window = glfwCreateWindow(screenWidth, screenHeight, name.c_str(), nullptr, + nullptr); + + if (!window) { + glfwTerminate(); + exit(EXIT_FAILURE); + } + + if (vsync) { + glfwSwapInterval(1); + } else { + glfwSwapInterval(0); + } + return window; +} + +struct Keys { +#define DEFINE_KEY(name) static inline const std::string name = #name + + DEFINE_KEY(Shift); + DEFINE_KEY(Alt); + DEFINE_KEY(Control); + DEFINE_KEY(Escape); + DEFINE_KEY(Return); + DEFINE_KEY(Tab); + DEFINE_KEY(BackSpace); + DEFINE_KEY(Space); + DEFINE_KEY(Up); + DEFINE_KEY(Down); + DEFINE_KEY(Left); + DEFINE_KEY(Right); + DEFINE_KEY(CapsLock); + DEFINE_KEY(LMB); + DEFINE_KEY(MMB); + DEFINE_KEY(RMB); +#undef DEFINE_KEY +}; + +inline std::unordered_map get_keys_map() { + static std::unordered_map keys = { + {Keys::Shift, GLFW_KEY_LEFT_SHIFT}, + {Keys::Alt, GLFW_KEY_LEFT_ALT}, + {Keys::Control, GLFW_KEY_LEFT_CONTROL}, + {Keys::Escape, GLFW_KEY_ESCAPE}, + {Keys::Return, GLFW_KEY_ENTER}, + {Keys::Tab, GLFW_KEY_TAB}, + {Keys::BackSpace, GLFW_KEY_BACKSPACE}, + {Keys::Space, GLFW_KEY_SPACE}, + {" ", GLFW_KEY_SPACE}, + {Keys::Up, GLFW_KEY_UP}, + {Keys::Down, GLFW_KEY_DOWN}, + {Keys::Left, GLFW_KEY_LEFT}, + {Keys::Right, GLFW_KEY_RIGHT}, + {Keys::CapsLock, GLFW_KEY_CAPS_LOCK}, + {Keys::LMB, GLFW_MOUSE_BUTTON_LEFT}, + {Keys::MMB, GLFW_MOUSE_BUTTON_MIDDLE}, + {Keys::RMB, GLFW_MOUSE_BUTTON_RIGHT}}; + return keys; +} + +inline std::unordered_map get_inv_keys_map() { + auto keys = get_keys_map(); + std::unordered_map keys_inv; + for (auto kv : keys) { + keys_inv[kv.second] = kv.first; + } + keys_inv[GLFW_KEY_RIGHT_SHIFT] = Keys::Shift; + keys_inv[GLFW_KEY_RIGHT_CONTROL] = Keys::Control; + keys_inv[GLFW_KEY_RIGHT_ALT] = Keys::Alt; + return keys_inv; +} + +inline int buttom_name_to_id(const std::string &name) { + if (name.size() == 1) { + char c = name[0]; + if (c >= 'a' && c <= 'z') { + c = c - ('a' - 'A'); + return (int)c; + } + } + + auto keys = get_keys_map(); + + if (keys.find(name) != keys.end()) { + return keys.at(name); + } else { + throw std::runtime_error(std::string("unrecognized name: ") + name); + } +} + +inline std::string button_id_to_name(int id) { + if (id >= 'A' && id <= 'Z') { + char c = id + ('a' - 'A'); + std::string name; + name += c; + return name; + } + auto keys = get_inv_keys_map(); + + if (keys.find(id) != keys.end()) { + return keys.at(id); + } else { + throw std::runtime_error(std::string("unrecognized id: \n") + + std::to_string(id)); + } +} + +inline int next_power_of_2(int n) { + int count = 0; + + if (n && !(n & (n - 1))) + return n; + + while (n != 0) { + n >>= 1; + count += 1; + } + + return 1 << count; +} + +#define DEFINE_PROPERTY(Type, name) \ + Type name; \ + void set_##name(const Type &new_name) { \ + name = new_name; \ + } \ + Type get_##name() { \ + return name; \ + } + +inline std::vector read_file(const std::string &filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error(filename + " failed to open file!"); + } + + size_t fileSize = (size_t)file.tellg(); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(buffer.data(), fileSize); + + file.close(); + + return buffer; +} + +TI_UI_NAMESPACE_END