From 780cf5f362bfc35a832ee438d15db05f77958489 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Sat, 8 Jan 2022 15:37:44 +1100 Subject: [PATCH] [client] overlay: add modal message dialog support --- client/CMakeLists.txt | 1 + client/include/app.h | 2 + client/include/interface/overlay.h | 4 + client/src/app.c | 88 +++++++++-------- client/src/core.c | 35 ++++++- client/src/core.h | 1 + client/src/main.c | 11 ++- client/src/overlay/msg.c | 153 +++++++++++++++++++++++++++++ client/src/overlays.h | 4 + common/include/common/stringlist.h | 1 + common/src/stringlist.c | 19 ++-- 11 files changed, 267 insertions(+), 52 deletions(-) create mode 100644 client/src/overlay/msg.c diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 4c1b257e..647e940a 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -139,6 +139,7 @@ set(SOURCES src/overlay/graphs.c src/overlay/help.c src/overlay/config.c + src/overlay/msg.c ) # Force cimgui to build as a static library. diff --git a/client/include/app.h b/client/include/app.h index 965874e9..1c6db185 100644 --- a/client/include/app.h +++ b/client/include/app.h @@ -134,6 +134,8 @@ void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque); */ void app_alert(LG_MsgAlert type, const char * fmt, ...); +void app_msgBox(const char * caption, const char * fmt, ...); + typedef struct KeybindHandle * KeybindHandle; typedef void (*KeybindFn)(int sc, void * opaque); diff --git a/client/include/interface/overlay.h b/client/include/interface/overlay.h index 2a6c6986..39cbb2b5 100644 --- a/client/include/interface/overlay.h +++ b/client/include/interface/overlay.h @@ -43,6 +43,10 @@ struct LG_OverlayOps * optional, if omitted assumes false */ bool (*needs_render)(void * udata, bool interactive); + /* return true if the overlay currently requires overlay mode + * optional, if omitted assumes false */ + bool (*needs_overlay)(void * udata); + /* perform the actual drawing/rendering * * `interactive` is true if the application is currently in overlay interaction diff --git a/client/src/app.c b/client/src/app.c index 84e8f768..e8de8f84 100644 --- a/client/src/app.c +++ b/client/src/app.c @@ -63,7 +63,18 @@ bool app_isFormatValid(void) bool app_isOverlayMode(void) { - return g_state.overlayInput; + if (g_state.overlayInput) + return true; + + struct Overlay * overlay; + for (ll_reset(g_state.overlays); + ll_walk(g_state.overlays, (void **)&overlay); ) + { + if (overlay->ops->needs_overlay && overlay->ops->needs_overlay(overlay)) + return true; + } + + return false; } void app_updateCursorPos(double x, double y) @@ -72,7 +83,7 @@ void app_updateCursorPos(double x, double y) g_cursor.pos.y = y; g_cursor.valid = true; - if (g_state.overlayInput) + if (app_isOverlayMode()) g_state.io->MousePos = (ImVec2) { x, y }; } @@ -81,7 +92,7 @@ void app_handleFocusEvent(bool focused) g_state.focused = focused; // release any imgui buttons/keys if we lost focus - if (!focused && g_state.overlayInput) + if (!focused && app_isOverlayMode()) core_resetOverlayInputState(); if (!core_inputEnabled()) @@ -131,7 +142,7 @@ void app_handleEnterEvent(bool entered) // stop the user being able to drag windows off the screen and work around // the mouse button release being missed due to not being in capture mode. - if (g_state.overlayInput) + if (app_isOverlayMode()) { g_state.io->MouseDown[ImGuiMouseButton_Left ] = false; g_state.io->MouseDown[ImGuiMouseButton_Right ] = false; @@ -243,7 +254,7 @@ void app_handleButtonPress(int button) { g_cursor.buttons |= (1U << button); - if (g_state.overlayInput) + if (app_isOverlayMode()) { int igButton = mapSpiceToImGuiButton(button); if (igButton != -1) @@ -262,7 +273,7 @@ void app_handleButtonRelease(int button) { g_cursor.buttons &= ~(1U << button); - if (g_state.overlayInput) + if (app_isOverlayMode()) { int igButton = mapSpiceToImGuiButton(button); if (igButton != -1) @@ -279,13 +290,13 @@ void app_handleButtonRelease(int button) void app_handleWheelMotion(double motion) { - if (g_state.overlayInput) + if (app_isOverlayMode()) g_state.io->MouseWheel -= motion; } void app_handleKeyPress(int sc) { - if (!g_state.overlayInput || !g_state.io->WantCaptureKeyboard) + if (!app_isOverlayMode() || !g_state.io->WantCaptureKeyboard) { if (sc == g_params.escapeKey && !g_state.escapeActive) { @@ -302,7 +313,7 @@ void app_handleKeyPress(int sc) } } - if (g_state.overlayInput) + if (app_isOverlayMode()) { if (sc == KEY_ESC) app_setOverlay(false); @@ -339,7 +350,8 @@ void app_handleKeyRelease(int sc) { if (g_state.escapeAction == -1) { - if (!g_state.escapeHelp && g_params.useSpiceInput && !g_state.overlayInput) + if (!g_state.escapeHelp && g_params.useSpiceInput && + !app_isOverlayMode()) core_setGrab(!g_cursor.grab); } else @@ -356,7 +368,7 @@ void app_handleKeyRelease(int sc) g_state.escapeActive = false; } - if (g_state.overlayInput) + if (app_isOverlayMode()) { g_state.io->KeysDown[sc] = false; return; @@ -415,7 +427,7 @@ void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock) void app_handleMouseRelative(double normx, double normy, double rawx, double rawy) { - if (g_state.overlayInput) + if (app_isOverlayMode()) return; if (g_cursor.grab) @@ -437,7 +449,8 @@ void app_handleMouseRelative(double normx, double normy, void app_handleMouseBasic() { /* do not pass mouse events to the guest if we do not have focus */ - if (!g_cursor.guest.valid || !g_state.haveSrcSize || !g_state.focused || g_state.overlayInput) + if (!g_cursor.guest.valid || !g_state.haveSrcSize || !g_state.focused || + app_isOverlayMode()) return; if (!core_inputEnabled()) @@ -624,6 +637,16 @@ void app_alert(LG_MsgAlert type, const char * fmt, ...) va_end(args); } +void app_msgBox(const char * caption, const char * fmt, ...) +{ + va_list args; + va_start(args, fmt); + overlayMsg_show(caption, fmt, args); + va_end(args); + + core_updateOverlayState(); +} + KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description) { // don't allow duplicate binds @@ -746,7 +769,7 @@ bool app_overlayNeedsRender(void) { struct Overlay * overlay; - if (g_state.overlayInput) + if (app_isOverlayMode()) return true; for (ll_reset(g_state.overlays); @@ -755,7 +778,7 @@ bool app_overlayNeedsRender(void) if (!overlay->ops->needs_render) continue; - if (overlay->ops->needs_render(overlay->udata, g_state.overlayInput)) + if (overlay->ops->needs_render(overlay->udata, false)) return true; } @@ -782,7 +805,8 @@ render_again: igNewFrame(); - if (g_state.overlayInput) + const bool overlayMode = app_isOverlayMode(); + if (overlayMode) { totalDamage = true; ImDrawList_AddRectFilled(igGetBackgroundDrawList_Nil(), (ImVec2) { 0.0f , 0.0f }, @@ -794,12 +818,17 @@ render_again: // igShowDemoWindow(&test); } + const bool msgModal = overlayMsg_modal(); + // render the overlays for (ll_reset(g_state.overlays); ll_walk(g_state.overlays, (void **)&overlay); ) { + if (msgModal && overlay->ops != &LGOverlayMsg) + continue; + const int written = - overlay->ops->render(overlay->udata, g_state.overlayInput, + overlay->ops->render(overlay->udata, overlayMode, buffer, MAX_OVERLAY_RECTS); for (int i = 0; i < written; ++i) @@ -836,7 +865,7 @@ render_again: overlay->lastRectCount = written; } - if (g_state.overlayInput) + if (overlayMode) { ImGuiMouseCursor cursor = igGetMouseCursor(); if (cursor != g_state.cursorLast) @@ -873,32 +902,11 @@ void app_freeOverlays(void) void app_setOverlay(bool enable) { - static bool wasGrabbed = false; - if (g_state.overlayInput == enable) return; g_state.overlayInput = enable; - g_state.cursorLast = -2; - - if (g_state.overlayInput) - { - wasGrabbed = g_cursor.grab; - - g_state.io->ConfigFlags &= ~ImGuiConfigFlags_NoMouse; - g_state.io->MousePos = (ImVec2) { g_cursor.pos.x, g_cursor.pos.y }; - - core_setGrabQuiet(false); - core_setCursorInView(false); - } - else - { - g_state.io->ConfigFlags |= ImGuiConfigFlags_NoMouse; - core_resetOverlayInputState(); - core_setGrabQuiet(wasGrabbed); - core_invalidatePointer(true); - app_invalidateWindow(false); - } + core_updateOverlayState(); } void app_overlayConfigRegister(const char * title, diff --git a/client/src/core.c b/client/src/core.c index ae098a86..5deda96a 100644 --- a/client/src/core.c +++ b/client/src/core.c @@ -166,7 +166,7 @@ void core_setGrabQuiet(bool enable) bool core_warpPointer(int x, int y, bool exiting) { if ((!g_cursor.inWindow && !exiting) || - g_state.overlayInput || + app_isOverlayMode() || g_cursor.warpState == WARP_STATE_OFF) return false; @@ -376,7 +376,7 @@ void core_handleGuestMouseUpdate(void) if (!util_guestCurToLocal(&localPos)) return; - if (g_state.overlayInput || !g_cursor.inView) + if (app_isOverlayMode() || !g_cursor.inView) return; g_state.ds->guestPointerUpdated( @@ -624,3 +624,34 @@ void core_resetOverlayInputState(void) for(int key = 0; key < ARRAY_LENGTH(g_state.io->KeysDown); key++) g_state.io->KeysDown[key] = false; } + +void core_updateOverlayState(void) +{ + static bool lastState = false; + bool currentState = app_isOverlayMode(); + if (lastState == currentState) + return; + + lastState = currentState; + g_state.cursorLast = -2; + + static bool wasGrabbed = false; + if (app_isOverlayMode()) + { + wasGrabbed = g_cursor.grab; + + g_state.io->ConfigFlags &= ~ImGuiConfigFlags_NoMouse; + g_state.io->MousePos = (ImVec2) { g_cursor.pos.x, g_cursor.pos.y }; + + core_setGrabQuiet(false); + core_setCursorInView(false); + } + else + { + g_state.io->ConfigFlags |= ImGuiConfigFlags_NoMouse; + core_resetOverlayInputState(); + core_setGrabQuiet(wasGrabbed); + core_invalidatePointer(true); + app_invalidateWindow(false); + } +} diff --git a/client/src/core.h b/client/src/core.h index 9eadb439..98fd19fd 100644 --- a/client/src/core.h +++ b/client/src/core.h @@ -40,5 +40,6 @@ void core_handleGuestMouseUpdate(void); void core_handleMouseGrabbed(double ex, double ey); void core_handleMouseNormal(double ex, double ey); void core_resetOverlayInputState(void); +void core_updateOverlayState(void); #endif diff --git a/client/src/main.c b/client/src/main.c index 1d2741d2..335050d0 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -238,7 +238,7 @@ static int renderThread(void * unused) { /* only update the time if we woke up early */ clock_gettime(CLOCK_MONOTONIC, &time); - tsAdd(&time, g_state.overlayInput ? + tsAdd(&time, app_isOverlayMode() ? g_state.overlayFrameTime : g_state.frameTime); } } @@ -622,9 +622,10 @@ int main_frameThread(void * unused) DEBUG_WARN("Recommend increase size to %d MiB", size); DEBUG_BREAK(); - app_alert(LG_ALERT_ERROR, - "IVSHMEM too small, screen truncated\n" - "Recommend increasing size to %d MiB", + app_msgBox( + "IVSHMEM too small", + "IVSHMEM too small\n" + "Please increase the size to %d MiB", size); } @@ -1512,6 +1513,8 @@ int main(int argc, char * argv[]) app_registerOverlay(&LGOverlayFPS , NULL); app_registerOverlay(&LGOverlayGraphs, NULL); app_registerOverlay(&LGOverlayHelp , NULL); + app_registerOverlay(&LGOverlayMsg , NULL); + // early renderer setup for option registration for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i) diff --git a/client/src/overlay/msg.c b/client/src/overlay/msg.c new file mode 100644 index 00000000..a8bff1a5 --- /dev/null +++ b/client/src/overlay/msg.c @@ -0,0 +1,153 @@ +/** + * Looking Glass + * Copyright © 2017-2022 The Looking Glass Authors + * https://looking-glass.io + * + * 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; either 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "interface/overlay.h" +#include "cimgui.h" +#include "overlay_utils.h" + +#include "common/stringutils.h" +#include "common/stringlist.h" +#include "ll.h" + +#include "../main.h" + +#include + +struct Msg +{ + char * caption; + char * message; + StringList lines; +}; + +struct MsgState +{ + struct ll * messages; +}; + +struct MsgState l_msg = { 0 }; + +static bool msg_init(void ** udata, const void * params) +{ + l_msg.messages = ll_new(); + return true; +} + +static void freeMsg(struct Msg * msg) +{ + free(msg->caption); + free(msg->message); + stringlist_free(&msg->lines); + free(msg); +} + +static void msg_free(void * udata) +{ + struct Msg * msg; + while(ll_shift(l_msg.messages, (void **)&msg)) + freeMsg(msg); + ll_free(l_msg.messages); +} + +static bool msg_needsOverlay(void * udata) +{ + return ll_count(l_msg.messages) > 0; +} + +static int msg_render(void * udata, bool interactive, struct Rect * windowRects, + int maxRects) +{ + struct Msg * msg; + if (!ll_peek_head(l_msg.messages, (void **)&msg)) + return 0; + + ImVec2 * screen = overlayGetScreenSize(); + igSetNextWindowBgAlpha(0.8f); + igSetNextWindowPos((ImVec2) { screen->x * 0.5f, screen->y * 0.5f }, 0, + (ImVec2) { 0.5f, 0.5f }); + + igBegin( + msg->caption, + NULL, + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoBringToFrontOnFocus | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse + ); + + ImVec2 textSize; + + const int lines = stringlist_count(msg->lines); + for(int i = 0; i < lines; ++i) + { + const char * line = stringlist_at(msg->lines, i); + igCalcTextSize(&textSize, line, NULL, false, 0.0); + igSetCursorPosX((igGetWindowWidth() * 0.5f) - (textSize.x * 0.5f)); + igText("%s", stringlist_at(msg->lines, i)); + } + + igCalcTextSize(&textSize, "OK", NULL, false, 0.0); + ImGuiStyle * style = igGetStyle(); + textSize.x += (style->FramePadding.x * 2.0f) * 8.0f; + textSize.y += (style->FramePadding.y * 2.0f) * 1.5f; + igSetCursorPosX((igGetWindowWidth() * 0.5f) - (textSize.x * 0.5f)); + + if (igButton("OK", textSize)) + { + ll_shift(l_msg.messages, NULL); + freeMsg(msg); + app_invalidateOverlay(false); + } + + overlayGetImGuiRect(windowRects); + igEnd(); + + return 1; +} + +struct LG_OverlayOps LGOverlayMsg = +{ + .name = "msg", + .init = msg_init, + .free = msg_free, + .needs_overlay = msg_needsOverlay, + .render = msg_render +}; + +bool overlayMsg_modal(void) +{ + return ll_count(l_msg.messages) > 0; +} + +void overlayMsg_show(const char * caption, const char * fmt, va_list args) +{ + struct Msg * msg = malloc(sizeof(*msg)); + msg->caption = strdup(caption); + msg->lines = stringlist_new(false); + valloc_sprintf(&msg->message, fmt, args); + + char * rest = msg->message; + char * token; + stringlist_clear(msg->lines); + while((token = strtok_r(rest, "\n", &rest))) + stringlist_push(msg->lines, token); + + ll_push(l_msg.messages, msg); + app_invalidateOverlay(false); +} diff --git a/client/src/overlays.h b/client/src/overlays.h index 9f6bcecb..e8f6a242 100644 --- a/client/src/overlays.h +++ b/client/src/overlays.h @@ -37,9 +37,13 @@ extern struct LG_OverlayOps LGOverlayFPS; extern struct LG_OverlayOps LGOverlayGraphs; extern struct LG_OverlayOps LGOverlayHelp; extern struct LG_OverlayOps LGOverlayConfig; +extern struct LG_OverlayOps LGOverlayMsg; void overlayAlert_show(LG_MsgAlert type, const char * fmt, va_list args); +bool overlayMsg_modal(void); +void overlayMsg_show(const char * caption, const char * fmt, va_list args); + GraphHandle overlayGraph_register(const char * name, RingBuffer buffer, float min, float max); void overlayGraph_unregister(); diff --git a/common/include/common/stringlist.h b/common/include/common/stringlist.h index 805786c2..668ac4de 100644 --- a/common/include/common/stringlist.h +++ b/common/include/common/stringlist.h @@ -31,5 +31,6 @@ int stringlist_push (StringList sl, char * str); void stringlist_remove(StringList sl, unsigned int index); unsigned int stringlist_count (StringList sl); char * stringlist_at (StringList sl, unsigned int index); +void stringlist_clear (StringList sl); #endif diff --git a/common/src/stringlist.c b/common/src/stringlist.c index 4f5d6610..739b63f3 100644 --- a/common/src/stringlist.c +++ b/common/src/stringlist.c @@ -44,12 +44,7 @@ StringList stringlist_new(bool owns_strings) void stringlist_free(StringList * sl) { - if ((*sl)->owns_strings) - { - char * ptr; - vector_forEach(ptr, &(*sl)->vector) - free(ptr); - } + stringlist_clear(*sl); vector_destroy(&(*sl)->vector); free((*sl)); @@ -82,3 +77,15 @@ char * stringlist_at(StringList sl, unsigned int index) vector_at(&sl->vector, index, &ptr); return ptr; } + +void stringlist_clear(StringList sl) +{ + if (sl->owns_strings) + { + char * ptr; + vector_forEach(ptr, &sl->vector) + free(ptr); + } + + vector_clear(&sl->vector); +}