From 4aeabcf11316850337755d6ea860486172c2b5e7 Mon Sep 17 00:00:00 2001 From: rock88 Date: Thu, 28 May 2020 22:11:55 +0300 Subject: [PATCH] Basic gamepad input --- src/InputController.cpp | 25 ++++++ src/ui/Application.cpp | 20 ++++- src/ui/Application.hpp | 3 + src/ui/windows/ContentWindow.cpp | 137 +++++++++++++++++++++++++++++++ src/ui/windows/ContentWindow.hpp | 2 + third_party/nanogui | 2 +- 6 files changed, 184 insertions(+), 5 deletions(-) diff --git a/src/InputController.cpp b/src/InputController.cpp index ffc3806..bfbeb94 100644 --- a/src/InputController.cpp +++ b/src/InputController.cpp @@ -22,6 +22,7 @@ struct MouseState { struct MouseState mouse_state; struct GamePadState game_pad_state; +static GLFWgamepadstate last_glfw_gamepad_state; static int m_width = 1, m_height = 1; @@ -130,6 +131,30 @@ void InputController::handle_gamepad_event(GLFWgamepadstate* gamepad) { SET_GAME_PAD_STATE(X_FLAG, GLFW_GAMEPAD_BUTTON_X); SET_GAME_PAD_STATE(Y_FLAG, GLFW_GAMEPAD_BUTTON_Y); } + + // Send to nanogui + if (gamepad->buttons != last_glfw_gamepad_state.buttons) { + for (int j = 0; j <= GLFW_GAMEPAD_BUTTON_LAST; j++) { + if (gamepad->buttons[j] == last_glfw_gamepad_state.buttons[j]) { + continue; + } + + nanogui::gamepad_button_callback_event(0, j, gamepad->buttons[j]); + } + } + + if (gamepad->axes != last_glfw_gamepad_state.axes) { + for (int j = 0; j <= GLFW_GAMEPAD_AXIS_LAST; j++) { + if (gamepad->axes[j] == last_glfw_gamepad_state.axes[j]) { + continue; + } + + nanogui::gamepad_analog_callback_event(0, j, gamepad->axes[j]); + } + } + + last_glfw_gamepad_state = *gamepad; + } void InputController::handle_rumple(unsigned short low_freq_motor, unsigned short high_freq_motor) { diff --git a/src/ui/Application.cpp b/src/ui/Application.cpp index e2c497f..d53f63a 100644 --- a/src/ui/Application.cpp +++ b/src/ui/Application.cpp @@ -62,9 +62,21 @@ void Application::pop_window() { m_windows.back()->set_visible(true); update_focus(m_windows.back()); perform_layout(); - } - - if (auto w = static_cast(m_windows.front())) { - w->window_appear(); + + if (auto w = static_cast(m_windows.front())) { + w->window_appear(); + } + } +} + +void Application::gamepad_button_callback_event(int jid, int button, int action) { + if (!m_windows.empty()) { + m_windows.back()->gamepad_button_event(jid, button, action); + } +} + +void Application::gamepad_analog_callback_event(int jid, int axis, float value) { + if (!m_windows.empty()) { + m_windows.back()->gamepad_analog_event(jid, axis, value); } } diff --git a/src/ui/Application.hpp b/src/ui/Application.hpp index 1aa7e4d..7678a1e 100644 --- a/src/ui/Application.hpp +++ b/src/ui/Application.hpp @@ -18,6 +18,9 @@ public: void pop_window(); + void gamepad_button_callback_event(int jid, int button, int action) override; + void gamepad_analog_callback_event(int jid, int axis, float value) override; + private: std::vector m_windows; }; diff --git a/src/ui/windows/ContentWindow.cpp b/src/ui/windows/ContentWindow.cpp index a708250..314ca7b 100644 --- a/src/ui/windows/ContentWindow.cpp +++ b/src/ui/windows/ContentWindow.cpp @@ -1,6 +1,8 @@ #include "ContentWindow.hpp" #include "Application.hpp" +#include "LoadingOverlay.hpp" #include "nanovg.h" +#include using namespace nanogui; @@ -74,3 +76,138 @@ void ContentWindow::set_right_title_button(int icon, const std::function }); perform_layout(); } + +static inline std::vector selectables_child_recursive(Widget *widget) { + std::vector selectables; + + for (auto child: widget->children()) { + if (child->selectable()) { + selectables.push_back(child); + } + auto child_selectables = selectables_child_recursive(child); + selectables.insert(selectables.end(), child_selectables.begin(), child_selectables.end()); + } + return selectables; +} + +static inline Widget *most_closed_widget(Widget *target, std::vector widgets, bool left, bool right, bool up, bool down) { + if (widgets.size() < 2) { + return nullptr; + } + + int index = -1; + int distanse = std::numeric_limits::max(); + + for (int i = 0; i < widgets.size(); i++) { + auto widget = widgets[i]; + + if (widget == target) { + continue; + } + + if (left && widget->center().y() == target->center().y() && widget->center().x() < target->center().x()) { + int new_distanse = target->center().x() - widget->center().x(); + if (new_distanse < distanse) { + distanse = new_distanse; + index = i; + } + } else if (right && widget->center().y() == target->center().y() && widget->center().x() > target->center().x()) { + int new_distanse = widget->center().x() - target->center().x(); + if (new_distanse < distanse) { + distanse = new_distanse; + index = i; + } + } else if (up && widget->center().x() == target->center().x() && widget->center().y() < target->center().y()) { + int new_distanse = target->center().y() - widget->center().y(); + if (new_distanse < distanse) { + distanse = new_distanse; + index = i; + } + } else if (down && widget->center().x() == target->center().x() && widget->center().y() > target->center().y()) { + int new_distanse = widget->center().y() - target->center().y(); + if (new_distanse < distanse) { + distanse = new_distanse; + index = i; + } + } + } + + if (index != -1) { + return widgets[index]; + } + return nullptr; +} + +bool ContentWindow::gamepad_button_event(int jid, int button, int action) { + if (!action) { + return false; + } + + std::vector messages; + std::vector loaders; + + for (auto child: screen()->children()) { + if (auto message = dynamic_cast(child)) { + messages.push_back(message); + } + if (auto loader = dynamic_cast(child)) { + loaders.push_back(loader); + } + } + + bool handle_button = messages.empty() && loaders.empty(); + + if (button == NANOGUI_GAMEPAD_BUTTON_B) { + if (!messages.empty()) { + for (auto message: messages) { + message->dispose(); + } + return false; + } + + if (!loaders.empty()) { + return false; + } + + pop(); + return false; + } else if (handle_button && button == NANOGUI_GAMEPAD_BUTTON_A) { + auto selectables = selectables_child_recursive(m_container); + auto current = std::find_if(selectables.begin(), selectables.end(), [](auto c) { return c->selected(); }); + + if (current != selectables.end()) { + auto widget = *current; + widget->mouse_button_event(widget->position() + widget->size() / 2, NANOGUI_MOUSE_BUTTON_1, 1, 0); + widget->mouse_button_event(widget->position() + widget->size() / 2, NANOGUI_MOUSE_BUTTON_1, 0, 0); + } + return false; + } + + auto selectables = selectables_child_recursive(m_container); + auto current = std::find_if(selectables.begin(), selectables.end(), [](auto c) { return c->selected(); }); + + if (handle_button && button >= NANOGUI_GAMEPAD_BUTTON_DPAD_UP && button <= NANOGUI_GAMEPAD_BUTTON_DPAD_LEFT) { + if (current == selectables.end()) { + selectables.front()->set_selected(true); + return false; + } + + auto current_selectable = *current; + auto new_selectable = most_closed_widget( + current_selectable, + selectables, + button == NANOGUI_GAMEPAD_BUTTON_DPAD_LEFT, + button == NANOGUI_GAMEPAD_BUTTON_DPAD_RIGHT, + button == NANOGUI_GAMEPAD_BUTTON_DPAD_UP, + button == NANOGUI_GAMEPAD_BUTTON_DPAD_DOWN + ); + + if (new_selectable) { + current_selectable->set_selected(false); + new_selectable->set_selected(true); + + m_scroll->set_scroll((new_selectable->position().y() + new_selectable->height()) / m_scroll->height()); + } + } + return false; +} diff --git a/src/ui/windows/ContentWindow.hpp b/src/ui/windows/ContentWindow.hpp index 4d31f33..1ef5a75 100644 --- a/src/ui/windows/ContentWindow.hpp +++ b/src/ui/windows/ContentWindow.hpp @@ -53,6 +53,8 @@ public: return true; }; + bool gamepad_button_event(int jid, int button, int action) override; + private: Application* application() { auto application = static_cast(screen()); diff --git a/third_party/nanogui b/third_party/nanogui index ec772a9..45d1e75 160000 --- a/third_party/nanogui +++ b/third_party/nanogui @@ -1 +1 @@ -Subproject commit ec772a923595c1d4b08156fcbf8f4631871dd642 +Subproject commit 45d1e7539dcade92040ec406f063c73b3cdfae13