[client] overlay: add modal message dialog support

This commit is contained in:
Geoffrey McRae 2022-01-08 15:37:44 +11:00
parent 0080e5f1b9
commit 780cf5f362
11 changed files with 267 additions and 52 deletions

View File

@ -139,6 +139,7 @@ set(SOURCES
src/overlay/graphs.c src/overlay/graphs.c
src/overlay/help.c src/overlay/help.c
src/overlay/config.c src/overlay/config.c
src/overlay/msg.c
) )
# Force cimgui to build as a static library. # Force cimgui to build as a static library.

View File

@ -134,6 +134,8 @@ void app_clipboardRequest(const LG_ClipboardReplyFn replyFn, void * opaque);
*/ */
void app_alert(LG_MsgAlert type, const char * fmt, ...); void app_alert(LG_MsgAlert type, const char * fmt, ...);
void app_msgBox(const char * caption, const char * fmt, ...);
typedef struct KeybindHandle * KeybindHandle; typedef struct KeybindHandle * KeybindHandle;
typedef void (*KeybindFn)(int sc, void * opaque); typedef void (*KeybindFn)(int sc, void * opaque);

View File

@ -43,6 +43,10 @@ struct LG_OverlayOps
* optional, if omitted assumes false */ * optional, if omitted assumes false */
bool (*needs_render)(void * udata, bool interactive); 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 /* perform the actual drawing/rendering
* *
* `interactive` is true if the application is currently in overlay interaction * `interactive` is true if the application is currently in overlay interaction

View File

@ -63,7 +63,18 @@ bool app_isFormatValid(void)
bool app_isOverlayMode(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) 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.pos.y = y;
g_cursor.valid = true; g_cursor.valid = true;
if (g_state.overlayInput) if (app_isOverlayMode())
g_state.io->MousePos = (ImVec2) { x, y }; g_state.io->MousePos = (ImVec2) { x, y };
} }
@ -81,7 +92,7 @@ void app_handleFocusEvent(bool focused)
g_state.focused = focused; g_state.focused = focused;
// release any imgui buttons/keys if we lost focus // release any imgui buttons/keys if we lost focus
if (!focused && g_state.overlayInput) if (!focused && app_isOverlayMode())
core_resetOverlayInputState(); core_resetOverlayInputState();
if (!core_inputEnabled()) 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 // 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. // 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_Left ] = false;
g_state.io->MouseDown[ImGuiMouseButton_Right ] = false; g_state.io->MouseDown[ImGuiMouseButton_Right ] = false;
@ -243,7 +254,7 @@ void app_handleButtonPress(int button)
{ {
g_cursor.buttons |= (1U << button); g_cursor.buttons |= (1U << button);
if (g_state.overlayInput) if (app_isOverlayMode())
{ {
int igButton = mapSpiceToImGuiButton(button); int igButton = mapSpiceToImGuiButton(button);
if (igButton != -1) if (igButton != -1)
@ -262,7 +273,7 @@ void app_handleButtonRelease(int button)
{ {
g_cursor.buttons &= ~(1U << button); g_cursor.buttons &= ~(1U << button);
if (g_state.overlayInput) if (app_isOverlayMode())
{ {
int igButton = mapSpiceToImGuiButton(button); int igButton = mapSpiceToImGuiButton(button);
if (igButton != -1) if (igButton != -1)
@ -279,13 +290,13 @@ void app_handleButtonRelease(int button)
void app_handleWheelMotion(double motion) void app_handleWheelMotion(double motion)
{ {
if (g_state.overlayInput) if (app_isOverlayMode())
g_state.io->MouseWheel -= motion; g_state.io->MouseWheel -= motion;
} }
void app_handleKeyPress(int sc) 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) 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) if (sc == KEY_ESC)
app_setOverlay(false); app_setOverlay(false);
@ -339,7 +350,8 @@ void app_handleKeyRelease(int sc)
{ {
if (g_state.escapeAction == -1) 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); core_setGrab(!g_cursor.grab);
} }
else else
@ -356,7 +368,7 @@ void app_handleKeyRelease(int sc)
g_state.escapeActive = false; g_state.escapeActive = false;
} }
if (g_state.overlayInput) if (app_isOverlayMode())
{ {
g_state.io->KeysDown[sc] = false; g_state.io->KeysDown[sc] = false;
return; return;
@ -415,7 +427,7 @@ void app_handleKeyboardLEDs(bool numLock, bool capsLock, bool scrollLock)
void app_handleMouseRelative(double normx, double normy, void app_handleMouseRelative(double normx, double normy,
double rawx, double rawy) double rawx, double rawy)
{ {
if (g_state.overlayInput) if (app_isOverlayMode())
return; return;
if (g_cursor.grab) if (g_cursor.grab)
@ -437,7 +449,8 @@ void app_handleMouseRelative(double normx, double normy,
void app_handleMouseBasic() void app_handleMouseBasic()
{ {
/* do not pass mouse events to the guest if we do not have focus */ /* 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; return;
if (!core_inputEnabled()) if (!core_inputEnabled())
@ -624,6 +637,16 @@ void app_alert(LG_MsgAlert type, const char * fmt, ...)
va_end(args); 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) KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description)
{ {
// don't allow duplicate binds // don't allow duplicate binds
@ -746,7 +769,7 @@ bool app_overlayNeedsRender(void)
{ {
struct Overlay * overlay; struct Overlay * overlay;
if (g_state.overlayInput) if (app_isOverlayMode())
return true; return true;
for (ll_reset(g_state.overlays); for (ll_reset(g_state.overlays);
@ -755,7 +778,7 @@ bool app_overlayNeedsRender(void)
if (!overlay->ops->needs_render) if (!overlay->ops->needs_render)
continue; continue;
if (overlay->ops->needs_render(overlay->udata, g_state.overlayInput)) if (overlay->ops->needs_render(overlay->udata, false))
return true; return true;
} }
@ -782,7 +805,8 @@ render_again:
igNewFrame(); igNewFrame();
if (g_state.overlayInput) const bool overlayMode = app_isOverlayMode();
if (overlayMode)
{ {
totalDamage = true; totalDamage = true;
ImDrawList_AddRectFilled(igGetBackgroundDrawList_Nil(), (ImVec2) { 0.0f , 0.0f }, ImDrawList_AddRectFilled(igGetBackgroundDrawList_Nil(), (ImVec2) { 0.0f , 0.0f },
@ -794,12 +818,17 @@ render_again:
// igShowDemoWindow(&test); // igShowDemoWindow(&test);
} }
const bool msgModal = overlayMsg_modal();
// render the overlays // render the overlays
for (ll_reset(g_state.overlays); for (ll_reset(g_state.overlays);
ll_walk(g_state.overlays, (void **)&overlay); ) ll_walk(g_state.overlays, (void **)&overlay); )
{ {
if (msgModal && overlay->ops != &LGOverlayMsg)
continue;
const int written = const int written =
overlay->ops->render(overlay->udata, g_state.overlayInput, overlay->ops->render(overlay->udata, overlayMode,
buffer, MAX_OVERLAY_RECTS); buffer, MAX_OVERLAY_RECTS);
for (int i = 0; i < written; ++i) for (int i = 0; i < written; ++i)
@ -836,7 +865,7 @@ render_again:
overlay->lastRectCount = written; overlay->lastRectCount = written;
} }
if (g_state.overlayInput) if (overlayMode)
{ {
ImGuiMouseCursor cursor = igGetMouseCursor(); ImGuiMouseCursor cursor = igGetMouseCursor();
if (cursor != g_state.cursorLast) if (cursor != g_state.cursorLast)
@ -873,32 +902,11 @@ void app_freeOverlays(void)
void app_setOverlay(bool enable) void app_setOverlay(bool enable)
{ {
static bool wasGrabbed = false;
if (g_state.overlayInput == enable) if (g_state.overlayInput == enable)
return; return;
g_state.overlayInput = enable; g_state.overlayInput = enable;
g_state.cursorLast = -2; core_updateOverlayState();
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);
}
} }
void app_overlayConfigRegister(const char * title, void app_overlayConfigRegister(const char * title,

View File

@ -166,7 +166,7 @@ void core_setGrabQuiet(bool enable)
bool core_warpPointer(int x, int y, bool exiting) bool core_warpPointer(int x, int y, bool exiting)
{ {
if ((!g_cursor.inWindow && !exiting) || if ((!g_cursor.inWindow && !exiting) ||
g_state.overlayInput || app_isOverlayMode() ||
g_cursor.warpState == WARP_STATE_OFF) g_cursor.warpState == WARP_STATE_OFF)
return false; return false;
@ -376,7 +376,7 @@ void core_handleGuestMouseUpdate(void)
if (!util_guestCurToLocal(&localPos)) if (!util_guestCurToLocal(&localPos))
return; return;
if (g_state.overlayInput || !g_cursor.inView) if (app_isOverlayMode() || !g_cursor.inView)
return; return;
g_state.ds->guestPointerUpdated( g_state.ds->guestPointerUpdated(
@ -624,3 +624,34 @@ void core_resetOverlayInputState(void)
for(int key = 0; key < ARRAY_LENGTH(g_state.io->KeysDown); key++) for(int key = 0; key < ARRAY_LENGTH(g_state.io->KeysDown); key++)
g_state.io->KeysDown[key] = false; 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);
}
}

View File

@ -40,5 +40,6 @@ void core_handleGuestMouseUpdate(void);
void core_handleMouseGrabbed(double ex, double ey); void core_handleMouseGrabbed(double ex, double ey);
void core_handleMouseNormal(double ex, double ey); void core_handleMouseNormal(double ex, double ey);
void core_resetOverlayInputState(void); void core_resetOverlayInputState(void);
void core_updateOverlayState(void);
#endif #endif

View File

@ -238,7 +238,7 @@ static int renderThread(void * unused)
{ {
/* only update the time if we woke up early */ /* only update the time if we woke up early */
clock_gettime(CLOCK_MONOTONIC, &time); clock_gettime(CLOCK_MONOTONIC, &time);
tsAdd(&time, g_state.overlayInput ? tsAdd(&time, app_isOverlayMode() ?
g_state.overlayFrameTime : g_state.frameTime); 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_WARN("Recommend increase size to %d MiB", size);
DEBUG_BREAK(); DEBUG_BREAK();
app_alert(LG_ALERT_ERROR, app_msgBox(
"IVSHMEM too small, screen truncated\n" "IVSHMEM too small",
"Recommend increasing size to %d MiB", "IVSHMEM too small\n"
"Please increase the size to %d MiB",
size); size);
} }
@ -1512,6 +1513,8 @@ int main(int argc, char * argv[])
app_registerOverlay(&LGOverlayFPS , NULL); app_registerOverlay(&LGOverlayFPS , NULL);
app_registerOverlay(&LGOverlayGraphs, NULL); app_registerOverlay(&LGOverlayGraphs, NULL);
app_registerOverlay(&LGOverlayHelp , NULL); app_registerOverlay(&LGOverlayHelp , NULL);
app_registerOverlay(&LGOverlayMsg , NULL);
// early renderer setup for option registration // early renderer setup for option registration
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i) for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)

153
client/src/overlay/msg.c Normal file
View File

@ -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 <string.h>
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);
}

View File

@ -37,9 +37,13 @@ extern struct LG_OverlayOps LGOverlayFPS;
extern struct LG_OverlayOps LGOverlayGraphs; extern struct LG_OverlayOps LGOverlayGraphs;
extern struct LG_OverlayOps LGOverlayHelp; extern struct LG_OverlayOps LGOverlayHelp;
extern struct LG_OverlayOps LGOverlayConfig; extern struct LG_OverlayOps LGOverlayConfig;
extern struct LG_OverlayOps LGOverlayMsg;
void overlayAlert_show(LG_MsgAlert type, const char * fmt, va_list args); 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, GraphHandle overlayGraph_register(const char * name, RingBuffer buffer,
float min, float max); float min, float max);
void overlayGraph_unregister(); void overlayGraph_unregister();

View File

@ -31,5 +31,6 @@ int stringlist_push (StringList sl, char * str);
void stringlist_remove(StringList sl, unsigned int index); void stringlist_remove(StringList sl, unsigned int index);
unsigned int stringlist_count (StringList sl); unsigned int stringlist_count (StringList sl);
char * stringlist_at (StringList sl, unsigned int index); char * stringlist_at (StringList sl, unsigned int index);
void stringlist_clear (StringList sl);
#endif #endif

View File

@ -44,12 +44,7 @@ StringList stringlist_new(bool owns_strings)
void stringlist_free(StringList * sl) void stringlist_free(StringList * sl)
{ {
if ((*sl)->owns_strings) stringlist_clear(*sl);
{
char * ptr;
vector_forEach(ptr, &(*sl)->vector)
free(ptr);
}
vector_destroy(&(*sl)->vector); vector_destroy(&(*sl)->vector);
free((*sl)); free((*sl));
@ -82,3 +77,15 @@ char * stringlist_at(StringList sl, unsigned int index)
vector_at(&sl->vector, index, &ptr); vector_at(&sl->vector, index, &ptr);
return 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);
}