mirror of
https://github.com/gnif/LookingGlass.git
synced 2024-11-12 20:27:53 +00:00
[client] overlay: add modal message dialog support
This commit is contained in:
parent
0080e5f1b9
commit
780cf5f362
11 changed files with 267 additions and 52 deletions
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
153
client/src/overlay/msg.c
Normal file
153
client/src/overlay/msg.c
Normal 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);
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue