From f907dddd33c55a141da9b794127846b9c3d5ebe9 Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Sun, 9 Jul 2023 18:44:44 +0200 Subject: [PATCH] :video_game: Made WalkieTalkie/Resetmode keyhints clickable. Features: * InputEngine: added `setEventSimulatedValue()` allowing simulated input from UI or other programmatic source. * GUIUtils: Extended `ImDrawEventHighlighted()` global helper to draw key hints as buttons which simulate input. * GUIManager: added `RequestStaticMenusBlocking()` so that ad-hoc windows like the WalkieTalkie can block static menus like TopMenubar when hovered by mouse. --- source/main/gfx/GfxScene.cpp | 79 ++++++++++++++++++------------- source/main/gfx/GfxScene.h | 2 +- source/main/gui/GUIManager.cpp | 10 +++- source/main/gui/GUIManager.h | 6 ++- source/main/gui/GUIUtils.cpp | 17 ++++--- source/main/gui/GUIUtils.h | 2 +- source/main/main.cpp | 2 +- source/main/utils/InputEngine.cpp | 20 ++++++-- source/main/utils/InputEngine.h | 3 ++ 9 files changed, 92 insertions(+), 49 deletions(-) diff --git a/source/main/gfx/GfxScene.cpp b/source/main/gfx/GfxScene.cpp index 0fb1d47f6e..1067b5f34f 100644 --- a/source/main/gfx/GfxScene.cpp +++ b/source/main/gfx/GfxScene.cpp @@ -423,12 +423,12 @@ void GfxScene::CacheWalkietalkieCommandButtons(const ActorPtr& actor) { desc = actor->ar_command_key[i].description.c_str(); } - std::string text = fmt::format("{}: {} {}", - App::GetInputEngine()->getEventCommandTrimmed(eventID), + std::string text = fmt::format("{} {}", cmdbeam.cmb_is_contraction ? "Retract" : "Extend", desc); - auto inserted_pair = m_walkietalkie_commandkeys_cache.insert(std::make_pair(eventID, text)); + auto inserted_pair = m_walkietalkie_commandkeys_cache.insert( + std::make_pair(eventID, text)); // Only increase the size if we added a new commandkey - there may be multiple commandbeams for the same key. if (inserted_pair.second) { @@ -442,6 +442,8 @@ void GfxScene::CacheWalkietalkieCommandButtons(const ActorPtr& actor) void GfxScene::DrawWalkieTalkieLabel(Ogre::Vector3 scene_pos, float cam_dist, const ActorPtr& actor) { + // Draw a label with icon and usable command keys + // ---------------------------------------------- ImVec2 screen_size = ImGui::GetIO().DisplaySize; World2ScreenConverter world2screen( @@ -459,10 +461,11 @@ void GfxScene::DrawWalkieTalkieLabel(Ogre::Vector3 scene_pos, float cam_dist, co Ogre::TexturePtr icon = FetchIcon("walkie_talkie.png", "IconsRG"); ImVec2 icon_size(icon->getWidth(), icon->getHeight()); - const float ICON_PAD_RIGHT = 2.f; + const float ICON_PAD_RIGHT = 5.f; ImVec2 total_size = icon_size; std::string caption; + std::string id_str = "walkie talkie for character"; if (actor) { // Cache info @@ -473,45 +476,55 @@ void GfxScene::DrawWalkieTalkieLabel(Ogre::Vector3 scene_pos, float cam_dist, co ImVec2 namesize = ImGui::CalcTextSize(caption.c_str()); total_size.x += ICON_PAD_RIGHT + std::max(namesize.x, m_walkietalkie_commandkeys_screensize.x); total_size.y = std::max(total_size.y, m_walkietalkie_commandkeys_screensize.y); - } - ImDrawList* drawlist = GetImDummyFullscreenWindow(); - ImGuiContext* g = ImGui::GetCurrentContext(); - GUIManager::GuiTheme const& theme = App::GetGuiManager()->GetTheme(); + // Account for space between event-button and description + total_size + ImGui::GetStyle().ItemSpacing * ImVec2(2.f, 1.f); - // Draw background rectangle - const float PADDING = 4.f; - const ImVec2 PAD2(PADDING, PADDING); - ImVec2 rect_min = (pos - total_size/2) - PAD2; - ImVec2 rect_max = (pos + total_size/2) + PAD2; - drawlist->AddRectFilled( - rect_min, rect_max, - ImColor(theme.semitransparent_window_bg), - ImGui::GetStyle().WindowRounding); - ImVec2 cursor = rect_min + PAD2; - - // Draw icon - if (icon) - { - drawlist->AddImage(reinterpret_cast(icon->getHandle()), cursor, cursor + icon_size); - cursor.x += icon_size.x + ICON_PAD_RIGHT; + id_str = fmt::format("walkie talkie for actor {}", actor->ar_instance_id).c_str(); } + // Because we want buttons in the label, it must be a window, not a fullscreen dummy for drawing + const float PADDING = 4.f; + const ImVec2 BOXPAD2(PADDING, PADDING); + const ImVec2 BTNPAD2(2.f, 0.f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, BOXPAD2); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, BTNPAD2); + ImVec2 rect_min = (pos - total_size/2) - BOXPAD2; + ImGui::SetNextWindowPos(rect_min); + ImGui::SetNextWindowSize(total_size + BOXPAD2*2); + int window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar + | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus; + ImGui::Begin(id_str.c_str(), nullptr, window_flags); + GUIManager::GuiTheme const& theme = App::GetGuiManager()->GetTheme(); + if (actor) { - // Draw actor name (light text) - drawlist->AddText(g->Font, g->FontSize, cursor, ImColor(ImGui::GetStyle().Colors[ImGuiCol_Text]), caption.c_str()); - cursor.y += ImGui::GetTextLineHeight(); + // Draw actor name (dark text) + ImGui::SetCursorPosX(icon_size.x + ICON_PAD_RIGHT); + ImGui::TextDisabled(caption.c_str()); + ImGui::Separator(); - // Draw the commands (darker text, except where active) - const ImU32 color_lo = ImColor(ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]); - const ImU32 color_hi = ImColor(theme.highlight_text_color); + // Draw the commands (light text with activity highlight) + for (auto& pair: m_walkietalkie_commandkeys_cache) { - const ImU32 color = (actor->ar_command_key[pair.first].playerInputValue != 0) ? color_hi : color_lo; - drawlist->AddText(g->Font, g->FontSize, cursor, color, pair.second.c_str()); - cursor.y += ImGui::GetTextLineHeight(); + const bool force_active = (actor->ar_command_key[pair.first].playerInputValue != 0); + ImGui::SetCursorPosX(icon_size.x + ICON_PAD_RIGHT); + ImDrawEventHighlighted(events(pair.first), force_active); + ImGui::SameLine(); + ImGui::Text(pair.second.c_str()); } } + // Draw icon last + if (icon) + { + ImGui::SetCursorPos(BOXPAD2); + ImGui::Image(reinterpret_cast(icon->getHandle()), icon_size); + } + + App::GetGuiManager()->RequestStaticMenusBlocking(ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows)); + ImGui::End(); + ImGui::PopStyleVar(); //ImGuiStyleVar_WindowPadding, BOXPAD2); + ImGui::PopStyleVar(); //ImGuiStyleVar_FramePadding, BTNPAD2); } \ No newline at end of file diff --git a/source/main/gfx/GfxScene.h b/source/main/gfx/GfxScene.h index fdf65e1f24..b1b2b7a134 100644 --- a/source/main/gfx/GfxScene.h +++ b/source/main/gfx/GfxScene.h @@ -81,7 +81,7 @@ class GfxScene SkidmarkConfig m_skidmark_conf; // Walkie talkie icon (forwardcommands / importcommands) - std::unordered_map m_walkietalkie_commandkeys_cache; + std::unordered_map m_walkietalkie_commandkeys_cache; // eventID -> description ActorPtr m_walkietalkie_commandkeys_cache_actor; ImVec2 m_walkietalkie_commandkeys_screensize; }; diff --git a/source/main/gui/GUIManager.cpp b/source/main/gui/GUIManager.cpp index 0981f84e66..2b4a80aedd 100644 --- a/source/main/gui/GUIManager.cpp +++ b/source/main/gui/GUIManager.cpp @@ -105,14 +105,16 @@ void GUIManager::ShutdownMyGUI() } } -void GUIManager::ApplyGuiCaptureKeyboard() +void GUIManager::ApplyQueuedGuiRequests() { m_gui_kb_capture_requested = m_gui_kb_capture_queued; + m_staticmenus_blocking_requested = m_staticmenus_blocking_queued; }; bool GUIManager::AreStaticMenusAllowed() //!< i.e. top menubar / vehicle UI buttons { return (App::GetCameraManager()->GetCurrentBehavior() != CameraManager::CAMERA_BEHAVIOR_FREE && + !m_staticmenus_blocking_requested && // For ad-hoc windows like walkie-talkie label !this->ConsoleWindow.IsHovered() && !this->GameControls.IsHovered() && !this->FrictionSettings.IsHovered() && @@ -317,6 +319,7 @@ void GUIManager::NewImGuiFrame(float dt) // Reset state m_gui_kb_capture_queued = false; + m_staticmenus_blocking_queued = false; } void GUIManager::SetupImGui() @@ -450,6 +453,11 @@ void GUIManager::RequestGuiCaptureKeyboard(bool val) m_gui_kb_capture_queued = m_gui_kb_capture_queued || val; } +void GUIManager::RequestStaticMenusBlocking(bool val) +{ + m_staticmenus_blocking_queued = m_staticmenus_blocking_queued || val; +} + void GUIManager::WakeUpGUI() { m_last_mousemove_time.reset(); diff --git a/source/main/gui/GUIManager.h b/source/main/gui/GUIManager.h index 90f82ae0a7..218325257b 100644 --- a/source/main/gui/GUIManager.h +++ b/source/main/gui/GUIManager.h @@ -132,10 +132,10 @@ class GUIManager void ShowMessageBox(const char* title, const char* text, bool allow_close = true, const char* btn1_text = "OK", const char* btn2_text = nullptr); void ShowMessageBox(GUI::MessageBoxConfig const& conf); void RequestGuiCaptureKeyboard(bool val); //!< Pass true during frame to prevent input passing to application + void RequestStaticMenusBlocking(bool val); //!< Pass true during frame to prevent static menus (i.e. top menubar) from displaying bool IsGuiCaptureKeyboardRequested() const { return m_gui_kb_capture_requested; } - void ApplyGuiCaptureKeyboard(); //!< Call after rendered frame to apply queued value + void ApplyQueuedGuiRequests(); //!< Call after rendered frame to apply queued value bool AreStaticMenusAllowed(); //!< i.e. top menubar / vehicle UI buttons - void NewImGuiFrame(float dt); void DrawMainMenuGui(); void DrawSimulationGui(float dt); //!< Touches live data; must be called in sync with sim. thread @@ -173,6 +173,8 @@ class GUIManager GuiTheme m_theme; bool m_gui_kb_capture_queued = false; //!< Resets and accumulates every frame bool m_gui_kb_capture_requested = false; //!< Effective value, persistent + bool m_staticmenus_blocking_queued = false; //!< Resets and accumulates every frame + bool m_staticmenus_blocking_requested = false; //!< Effective value, persistent Ogre::Timer m_last_mousemove_time; bool m_is_cursor_supressed = false; //!< True if cursor was manually hidden. }; diff --git a/source/main/gui/GUIUtils.cpp b/source/main/gui/GUIUtils.cpp index ffbb4d1a9a..5d860f4d92 100644 --- a/source/main/gui/GUIUtils.cpp +++ b/source/main/gui/GUIUtils.cpp @@ -402,21 +402,24 @@ void RoR::ImTerminateComboboxString(std::string& target) target.resize(prev_size + 2, '\0'); } -void RoR::ImDrawEventHighlighted(events input_event) +void RoR::ImDrawEventHighlighted(events input_event, bool force_active /*=false*/) { ImVec4 col = ImGui::GetStyle().Colors[ImGuiCol_Text]; - if (App::GetInputEngine()->getEventValue(input_event)) + if (force_active || App::GetInputEngine()->getEventValue(input_event)) { col = App::GetGuiManager()->GetTheme().highlight_text_color; } std::string text = App::GetInputEngine()->getKeyForCommand(input_event); const ImVec2 PAD = ImVec2(2.f, 0.f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, PAD); - ImGui::BeginChildFrame(ImGuiID(input_event), ImGui::CalcTextSize(text.c_str()) + PAD*2); - ImGui::TextColored(col, "%s", text.c_str()); - ImGui::EndChildFrame(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, PAD); + ImGui::PushStyleColor(ImGuiCol_Text, col); + ImGui::PushID(ImGuiID(input_event)); + ImGui::Button(text.c_str()); + // `Button()` only returns `true` the moment when pressed, we need continuous input from `IsItemActive()` + App::GetInputEngine()->setEventSimulatedValue(input_event, static_cast(ImGui::IsItemActive())); + ImGui::PopID(); //ImGuiID(input_event) + ImGui::PopStyleColor(); // Text ImGui::PopStyleVar(); // FramePadding - } void RoR::ImDrawModifierKeyHighlighted(OIS::KeyCode key) diff --git a/source/main/gui/GUIUtils.h b/source/main/gui/GUIUtils.h index d9470bcf50..2a97861f9f 100644 --- a/source/main/gui/GUIUtils.h +++ b/source/main/gui/GUIUtils.h @@ -81,7 +81,7 @@ void ImAddItemToComboboxString(std::string& target, std::string const& item); void ImTerminateComboboxString(std::string& target); // Input engine helpers -void ImDrawEventHighlighted(events input_event); +void ImDrawEventHighlighted(events input_event, bool force_active=false); ///!< Draws button displaying configured key combo and simulating the event when pressed. void ImDrawModifierKeyHighlighted(OIS::KeyCode key); } // namespace RoR diff --git a/source/main/main.cpp b/source/main/main.cpp index bbc668a9a1..c0257dc0ea 100644 --- a/source/main/main.cpp +++ b/source/main/main.cpp @@ -1151,7 +1151,7 @@ int main(int argc, char *argv[]) } } // Render block - App::GetGuiManager()->ApplyGuiCaptureKeyboard(); + App::GetGuiManager()->ApplyQueuedGuiRequests(); } // End of main rendering/input loop diff --git a/source/main/utils/InputEngine.cpp b/source/main/utils/InputEngine.cpp index 413769e493..b75285df05 100644 --- a/source/main/utils/InputEngine.cpp +++ b/source/main/utils/InputEngine.cpp @@ -697,6 +697,11 @@ void InputEngine::resetKeys() } } +void InputEngine::setEventSimulatedValue(RoR::events eventID, float value) +{ + event_values_simulated[eventID] = value; +} + bool InputEngine::getEventBoolValue(int eventID) { return (getEventValue(eventID) > 0.5f); @@ -900,6 +905,10 @@ bool InputEngine::isEventAnalog(int eventID) float InputEngine::getEventValue(int eventID, bool pure, InputSourceType valueSource /*= InputSourceType::IST_ANY*/) { + const float simulatedValue = event_values_simulated[eventID]; + if (simulatedValue != 0.f) + return simulatedValue; + float returnValue = 0; std::vector t_vec = events[eventID]; float value = 0; @@ -1174,6 +1183,12 @@ const char* InputEngine::getEventTypeName(eventtypes type) } void InputEngine::addEvent(int eventID, event_trigger_t& t) +{ + this->addEvent(eventID); + events[eventID].push_back(t); +} + +void InputEngine::addEvent(int eventID) { uniqueCounter++; @@ -1183,9 +1198,8 @@ void InputEngine::addEvent(int eventID, event_trigger_t& t) if (events.find(eventID) == events.end()) { events[eventID] = std::vector(); - events[eventID].clear(); + event_values_simulated[eventID] = false; } - events[eventID].push_back(t); } void InputEngine::addEventDefault(int eventID, int deviceID /*= -1*/) @@ -1495,7 +1509,7 @@ bool InputEngine::processLine(const char* line, int deviceID) if (eventID == -1) return false; // Insert event with no trigger - events.insert(std::make_pair(eventID, std::vector())); + addEvent(eventID); return true; } case ET_MouseButton: diff --git a/source/main/utils/InputEngine.h b/source/main/utils/InputEngine.h index 7ec14c463a..61b2a68a66 100644 --- a/source/main/utils/InputEngine.h +++ b/source/main/utils/InputEngine.h @@ -484,6 +484,7 @@ class InputEngine void ProcessKeyRelease(const OIS::KeyEvent& arg); void ProcessJoystickEvent(const OIS::JoyStickEvent& arg); void resetKeys(); + void setEventSimulatedValue(events eventID, float value); // Event info @@ -513,6 +514,7 @@ class InputEngine // Event management void addEvent(int eventID, event_trigger_t& t); //!< Registers new trigger for this event. + void addEvent(int eventID); //!< Registers new event without trigger. void addEventDefault(int eventID, int deviceID = -1); //!< Adds a new trigger with builtin value for this event. void updateEvent(int eventID, const event_trigger_t& t); void eraseEvent(int eventID, const event_trigger_t* t); @@ -573,6 +575,7 @@ class InputEngine // define event aliases std::map> events; + std::map event_values_simulated; std::map event_times; std::string m_loaded_configs[MAX_JOYSTICKS]; bool loadMapping(Ogre::String fileName, int deviceID);