mirror of
https://github.com/gnif/LookingGlass.git
synced 2024-11-10 08:38:20 +00:00
[client] rework the configuration overlay to allow for tabs
This commit is contained in:
parent
fe6339fc77
commit
6387bf2d2e
@ -105,7 +105,10 @@ GraphHandle app_registerGraph(const char * name, RingBuffer buffer, float min, f
|
|||||||
void app_unregisterGraph(GraphHandle handle);
|
void app_unregisterGraph(GraphHandle handle);
|
||||||
|
|
||||||
void app_overlayConfigRegister(const char * title,
|
void app_overlayConfigRegister(const char * title,
|
||||||
void (*callback)(void * udata), void * udata);
|
void (*callback)(void * udata, int * id), void * udata);
|
||||||
|
|
||||||
|
void app_overlayConfigRegisterTab(const char * title,
|
||||||
|
void (*callback)(void * udata, int * id), void * udata);
|
||||||
|
|
||||||
void app_clipboardRelease(void);
|
void app_clipboardRelease(void);
|
||||||
void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count);
|
void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count);
|
||||||
|
@ -251,12 +251,6 @@ void egl_desktopConfigUI(EGL_Desktop * desktop)
|
|||||||
}
|
}
|
||||||
igSliderInt("##nvgain", &desktop->nvGain, 0, desktop->nvMax, format, 0);
|
igSliderInt("##nvgain", &desktop->nvGain, 0, desktop->nvMax, format, 0);
|
||||||
igPopItemWidth();
|
igPopItemWidth();
|
||||||
|
|
||||||
if (egl_postProcessImgui(desktop->pp))
|
|
||||||
{
|
|
||||||
atomic_store(&desktop->processFrame, true);
|
|
||||||
app_invalidateWindow(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool egl_desktopSetup(EGL_Desktop * desktop, const LG_RendererFormat format)
|
bool egl_desktopSetup(EGL_Desktop * desktop, const LG_RendererFormat format)
|
||||||
@ -366,7 +360,8 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
|
|||||||
|
|
||||||
int scaleAlgo = EGL_SCALE_NEAREST;
|
int scaleAlgo = EGL_SCALE_NEAREST;
|
||||||
|
|
||||||
if (atomic_exchange(&desktop->processFrame, false))
|
if (atomic_exchange(&desktop->processFrame, false) ||
|
||||||
|
egl_postProcessConfigModified(desktop->pp))
|
||||||
egl_postProcessRun(desktop->pp, desktop->texture, outputWidth, outputHeight);
|
egl_postProcessRun(desktop->pp, desktop->texture, outputWidth, outputHeight);
|
||||||
|
|
||||||
unsigned int finalSizeX, finalSizeY;
|
unsigned int finalSizeX, finalSizeY;
|
||||||
|
@ -626,7 +626,7 @@ static void debugCallback(GLenum source, GLenum type, GLuint id,
|
|||||||
DEBUG_PRINT(level, "GL message (source: %s, type: %s): %s", sourceName, typeName, message);
|
DEBUG_PRINT(level, "GL message (source: %s, type: %s): %s", sourceName, typeName, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void egl_config_ui(void * opaque)
|
static void egl_configUI(void * opaque, int * id)
|
||||||
{
|
{
|
||||||
struct Inst * this = opaque;
|
struct Inst * this = opaque;
|
||||||
egl_damageConfigUI(this->damage);
|
egl_damageConfigUI(this->damage);
|
||||||
@ -822,7 +822,7 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
app_overlayConfigRegister("EGL", egl_config_ui, this);
|
app_overlayConfigRegister("EGL", egl_configUI, this);
|
||||||
|
|
||||||
this->imgui = true;
|
this->imgui = true;
|
||||||
return true;
|
return true;
|
||||||
|
@ -164,7 +164,7 @@ static bool egl_filterFFXCASImguiConfig(EGL_Filter * filter)
|
|||||||
bool cas = this->enable;
|
bool cas = this->enable;
|
||||||
float casSharpness = this->sharpness;
|
float casSharpness = this->sharpness;
|
||||||
|
|
||||||
igCheckbox("AMD FidelityFX CAS", &cas);
|
igCheckbox("Enabled", &cas);
|
||||||
if (cas != this->enable)
|
if (cas != this->enable)
|
||||||
{
|
{
|
||||||
this->enable = cas;
|
this->enable = cas;
|
||||||
|
@ -203,7 +203,7 @@ static bool egl_filterFFXFSR1ImguiConfig(EGL_Filter * filter)
|
|||||||
bool enable = this->enable;
|
bool enable = this->enable;
|
||||||
float sharpness = this->sharpness;
|
float sharpness = this->sharpness;
|
||||||
|
|
||||||
igCheckbox("AMD FidelityFX FSR", &enable);
|
igCheckbox("Enabled", &enable);
|
||||||
if (enable != this->enable)
|
if (enable != this->enable)
|
||||||
{
|
{
|
||||||
this->enable = enable;
|
this->enable = enable;
|
||||||
|
@ -20,6 +20,10 @@
|
|||||||
|
|
||||||
#include "postprocess.h"
|
#include "postprocess.h"
|
||||||
#include "filters.h"
|
#include "filters.h"
|
||||||
|
#include "app.h"
|
||||||
|
#include "cimgui.h"
|
||||||
|
|
||||||
|
#include <stdatomic.h>
|
||||||
|
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
#include "common/array.h"
|
#include "common/array.h"
|
||||||
@ -37,6 +41,7 @@ struct EGL_PostProcess
|
|||||||
struct ll * filters;
|
struct ll * filters;
|
||||||
GLuint output;
|
GLuint output;
|
||||||
unsigned int outputX, outputY;
|
unsigned int outputX, outputY;
|
||||||
|
_Atomic(bool) modified;
|
||||||
|
|
||||||
EGL_Model * model;
|
EGL_Model * model;
|
||||||
};
|
};
|
||||||
@ -47,6 +52,27 @@ void egl_postProcessEarlyInit(void)
|
|||||||
EGL_Filters[i]->earlyInit();
|
EGL_Filters[i]->earlyInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void configUI(void * opaque, int * id)
|
||||||
|
{
|
||||||
|
struct EGL_PostProcess * this = opaque;
|
||||||
|
|
||||||
|
bool redraw = false;
|
||||||
|
EGL_Filter * filter;
|
||||||
|
for(ll_reset(this->filters); ll_walk(this->filters, (void **)&filter); )
|
||||||
|
{
|
||||||
|
igPushIDInt(++*id);
|
||||||
|
if (igCollapsingHeaderBoolPtr(filter->ops.name, NULL, 0))
|
||||||
|
redraw |= egl_filterImguiConfig(filter);
|
||||||
|
igPopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redraw)
|
||||||
|
{
|
||||||
|
atomic_store(&this->modified, true);
|
||||||
|
app_invalidateWindow(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool egl_postProcessInit(EGL_PostProcess ** pp)
|
bool egl_postProcessInit(EGL_PostProcess ** pp)
|
||||||
{
|
{
|
||||||
EGL_PostProcess * this = calloc(1, sizeof(*this));
|
EGL_PostProcess * this = calloc(1, sizeof(*this));
|
||||||
@ -70,6 +96,8 @@ bool egl_postProcessInit(EGL_PostProcess ** pp)
|
|||||||
}
|
}
|
||||||
egl_modelSetDefault(this->model, false);
|
egl_modelSetDefault(this->model, false);
|
||||||
|
|
||||||
|
app_overlayConfigRegisterTab("EGL Filters", configUI, this);
|
||||||
|
|
||||||
*pp = this;
|
*pp = this;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@ -111,14 +139,9 @@ bool egl_postProcessAdd(EGL_PostProcess * this, const EGL_FilterOps * ops)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool egl_postProcessImgui(EGL_PostProcess * this)
|
bool egl_postProcessConfigModified(EGL_PostProcess * this)
|
||||||
{
|
{
|
||||||
bool redraw = false;
|
return atomic_load(&this->modified);
|
||||||
EGL_Filter * filter;
|
|
||||||
for(ll_reset(this->filters); ll_walk(this->filters, (void **)&filter); )
|
|
||||||
redraw |= egl_filterImguiConfig(filter);
|
|
||||||
|
|
||||||
return redraw;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex,
|
bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex,
|
||||||
@ -131,6 +154,7 @@ bool egl_postProcessRun(EGL_PostProcess * this, EGL_Texture * tex,
|
|||||||
if (egl_textureGet(tex, &texture, &sizeX, &sizeY) != EGL_TEX_STATUS_OK)
|
if (egl_textureGet(tex, &texture, &sizeX, &sizeY) != EGL_TEX_STATUS_OK)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
atomic_store(&this->modified, false);
|
||||||
for(ll_reset(this->filters); ll_walk(this->filters, (void **)&filter); )
|
for(ll_reset(this->filters); ll_walk(this->filters, (void **)&filter); )
|
||||||
{
|
{
|
||||||
egl_filterSetOutputResHint(filter, targetX, targetY);
|
egl_filterSetOutputResHint(filter, targetX, targetY);
|
||||||
|
@ -33,9 +33,8 @@ void egl_postProcessFree(EGL_PostProcess ** pp);
|
|||||||
/* create and add a filter to this processor */
|
/* create and add a filter to this processor */
|
||||||
bool egl_postProcessAdd(EGL_PostProcess * this, const EGL_FilterOps * ops);
|
bool egl_postProcessAdd(EGL_PostProcess * this, const EGL_FilterOps * ops);
|
||||||
|
|
||||||
/* render the imgui options
|
/* returns true if the configuration was modified since the last run */
|
||||||
* returns true if the filter needs to be re-run */
|
bool egl_postProcessConfigModified(EGL_PostProcess * this);
|
||||||
bool egl_postProcessImgui(EGL_PostProcess * this);
|
|
||||||
|
|
||||||
/* apply the filters to the supplied texture
|
/* apply the filters to the supplied texture
|
||||||
* targetX/Y is the final target output dimension hint if scalers are present */
|
* targetX/Y is the final target output dimension hint if scalers are present */
|
||||||
|
@ -883,7 +883,13 @@ void app_setOverlay(bool enable)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void app_overlayConfigRegister(const char * title,
|
void app_overlayConfigRegister(const char * title,
|
||||||
void (*callback)(void * udata), void * udata)
|
void (*callback)(void * udata, int * id), void * udata)
|
||||||
{
|
{
|
||||||
overlayConfig_register(title, callback, udata);
|
overlayConfig_register(title, callback, udata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void app_overlayConfigRegisterTab(const char * title,
|
||||||
|
void (*callback)(void * udata, int * id), void * udata)
|
||||||
|
{
|
||||||
|
overlayConfig_registerTab(title, callback, udata);
|
||||||
|
}
|
||||||
|
@ -34,13 +34,14 @@ typedef struct ConfigCallback
|
|||||||
{
|
{
|
||||||
const char * title;
|
const char * title;
|
||||||
void * udata;
|
void * udata;
|
||||||
void (*callback)(void * udata);
|
void (*callback)(void * udata, int * id);
|
||||||
}
|
}
|
||||||
ConfigCallback;
|
ConfigCallback;
|
||||||
|
|
||||||
typedef struct OverlayConfig
|
typedef struct OverlayConfig
|
||||||
{
|
{
|
||||||
struct ll * callbacks;
|
struct ll * callbacks;
|
||||||
|
struct ll * tabCallbacks;
|
||||||
}
|
}
|
||||||
OverlayConfig;
|
OverlayConfig;
|
||||||
|
|
||||||
@ -48,7 +49,8 @@ static OverlayConfig cfg = { 0 };
|
|||||||
|
|
||||||
static bool config_init(void ** udata, const void * params)
|
static bool config_init(void ** udata, const void * params)
|
||||||
{
|
{
|
||||||
cfg.callbacks = ll_new();
|
cfg.callbacks = ll_new();
|
||||||
|
cfg.tabCallbacks = ll_new();
|
||||||
if (!cfg.callbacks)
|
if (!cfg.callbacks)
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("failed to allocate ram");
|
DEBUG_ERROR("failed to allocate ram");
|
||||||
@ -58,47 +60,26 @@ static bool config_init(void ** udata, const void * params)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void config_free(void * udata)
|
static void config_freeList(struct ll * list)
|
||||||
{
|
{
|
||||||
ConfigCallback * cb;
|
ConfigCallback * cb;
|
||||||
while(ll_shift(cfg.callbacks, (void **)&cb))
|
while(ll_shift(list, (void **)&cb))
|
||||||
free(cb);
|
free(cb);
|
||||||
|
ll_free(list);
|
||||||
ll_free(cfg.callbacks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int config_render(void * udata, bool interactive, struct Rect * windowRects,
|
static void config_free(void * udata)
|
||||||
int maxRects)
|
|
||||||
{
|
{
|
||||||
if (!interactive)
|
config_freeList(cfg.callbacks);
|
||||||
return 0;
|
config_freeList(cfg.tabCallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void config_renderLGTab(void)
|
||||||
|
{
|
||||||
const float fontSize = igGetFontSize();
|
const float fontSize = igGetFontSize();
|
||||||
const ImGuiViewport * viewport = igGetMainViewport();
|
|
||||||
igSetNextWindowPos(
|
|
||||||
(ImVec2){
|
|
||||||
viewport->WorkPos.x + 100,
|
|
||||||
viewport->WorkPos.y + 30
|
|
||||||
},
|
|
||||||
ImGuiCond_FirstUseEver,
|
|
||||||
(ImVec2){}
|
|
||||||
);
|
|
||||||
|
|
||||||
igSetNextWindowSize(
|
if (igCollapsingHeaderBoolPtr("About", NULL,
|
||||||
(ImVec2){550, 680},
|
ImGuiTreeNodeFlags_DefaultOpen))
|
||||||
ImGuiCond_FirstUseEver);
|
|
||||||
|
|
||||||
if (!igBegin("Configuration", NULL, ImGuiWindowFlags_MenuBar))
|
|
||||||
{
|
|
||||||
overlayGetImGuiRect(windowRects);
|
|
||||||
igEnd();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
igBeginMenuBar();
|
|
||||||
igEndMenuBar();
|
|
||||||
|
|
||||||
if (igCollapsingHeaderBoolPtr("About Looking Glass", NULL, 0))
|
|
||||||
{
|
{
|
||||||
igText(LG_COPYRIGHT_STR);
|
igText(LG_COPYRIGHT_STR);
|
||||||
overlayTextURL(LG_WEBSITE_STR, NULL);
|
overlayTextURL(LG_WEBSITE_STR, NULL);
|
||||||
@ -107,7 +88,8 @@ static int config_render(void * udata, bool interactive, struct Rect * windowRec
|
|||||||
igTextWrapped(LG_LICENSE_STR);
|
igTextWrapped(LG_LICENSE_STR);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (igCollapsingHeaderBoolPtr("Help & Support", NULL, 0))
|
if (igCollapsingHeaderBoolPtr("Help & Support", NULL,
|
||||||
|
ImGuiTreeNodeFlags_DefaultOpen))
|
||||||
{
|
{
|
||||||
igBeginTable("split", 2, 0, (ImVec2){}, 0.0f);
|
igBeginTable("split", 2, 0, (ImVec2){}, 0.0f);
|
||||||
igTableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, fontSize * 9.0f, 0);
|
igTableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, fontSize * 9.0f, 0);
|
||||||
@ -121,7 +103,8 @@ static int config_render(void * udata, bool interactive, struct Rect * windowRec
|
|||||||
igEndTable();
|
igEndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (igCollapsingHeaderBoolPtr("The Looking Glass Team / Donations", NULL, 0))
|
if (igCollapsingHeaderBoolPtr("The Looking Glass Team / Donations", NULL,
|
||||||
|
ImGuiTreeNodeFlags_DefaultOpen))
|
||||||
{
|
{
|
||||||
for(const struct LGTeamMember * member = LG_TEAM; member->name; ++member)
|
for(const struct LGTeamMember * member = LG_TEAM; member->name; ++member)
|
||||||
{
|
{
|
||||||
@ -154,17 +137,82 @@ static int config_render(void * udata, bool interactive, struct Rect * windowRec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int config_render(void * udata, bool interactive, struct Rect * windowRects,
|
||||||
|
int maxRects)
|
||||||
|
{
|
||||||
|
if (!interactive)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int id = 1000;
|
||||||
|
|
||||||
|
const ImGuiViewport * viewport = igGetMainViewport();
|
||||||
|
igSetNextWindowPos(
|
||||||
|
(ImVec2){
|
||||||
|
viewport->WorkPos.x + 100,
|
||||||
|
viewport->WorkPos.y + 30
|
||||||
|
},
|
||||||
|
ImGuiCond_FirstUseEver,
|
||||||
|
(ImVec2){}
|
||||||
|
);
|
||||||
|
|
||||||
|
igSetNextWindowSize(
|
||||||
|
(ImVec2){550, 680},
|
||||||
|
ImGuiCond_FirstUseEver);
|
||||||
|
|
||||||
|
igPushIDInt(id++);
|
||||||
|
if (!igBegin("Configuration", NULL, ImGuiWindowFlags_MenuBar))
|
||||||
|
{
|
||||||
|
overlayGetImGuiRect(windowRects);
|
||||||
|
igEnd();
|
||||||
|
igPopID();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
igBeginMenuBar();
|
||||||
|
igEndMenuBar();
|
||||||
|
|
||||||
|
igBeginTabBar("Configuration#tabs", 0);
|
||||||
|
|
||||||
|
if (igBeginTabItem("Looking Glass", NULL, 0))
|
||||||
|
{
|
||||||
|
config_renderLGTab();
|
||||||
|
igEndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
ConfigCallback * cb;
|
ConfigCallback * cb;
|
||||||
for (ll_reset(cfg.callbacks); ll_walk(cfg.callbacks, (void **)&cb); )
|
|
||||||
|
if (igBeginTabItem("Settings", NULL, 0))
|
||||||
{
|
{
|
||||||
if (!igCollapsingHeaderBoolPtr(cb->title, NULL, 0))
|
for (ll_reset(cfg.callbacks); ll_walk(cfg.callbacks, (void **)&cb); )
|
||||||
continue;
|
{
|
||||||
cb->callback(cb->udata);
|
if (!igCollapsingHeaderBoolPtr(cb->title, NULL, 0))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
igPushIDInt(id++);
|
||||||
|
cb->callback(cb->udata, &id);
|
||||||
|
igPopID();
|
||||||
|
}
|
||||||
|
igEndTabItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (ll_reset(cfg.tabCallbacks); ll_walk(cfg.tabCallbacks, (void **)&cb); )
|
||||||
|
{
|
||||||
|
if (!igBeginTabItem(cb->title, NULL, 0))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
igPushIDInt(id++);
|
||||||
|
cb->callback(cb->udata, &id);
|
||||||
|
igPopID();
|
||||||
|
igEndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
igEndTabBar();
|
||||||
|
|
||||||
overlayGetImGuiRect(windowRects);
|
overlayGetImGuiRect(windowRects);
|
||||||
igEnd();
|
igEnd();
|
||||||
|
igPopID();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,8 +224,8 @@ struct LG_OverlayOps LGOverlayConfig =
|
|||||||
.render = config_render
|
.render = config_render
|
||||||
};
|
};
|
||||||
|
|
||||||
void overlayConfig_register(const char * title, void (*callback)(void * udata),
|
static void config_addToList(struct ll * list, const char * title,
|
||||||
void * udata)
|
void(*callback)(void * udata, int * id), void * udata)
|
||||||
{
|
{
|
||||||
ConfigCallback * cb = calloc(1, sizeof(*cb));
|
ConfigCallback * cb = calloc(1, sizeof(*cb));
|
||||||
if (!cb)
|
if (!cb)
|
||||||
@ -189,5 +237,17 @@ void overlayConfig_register(const char * title, void (*callback)(void * udata),
|
|||||||
cb->title = title;
|
cb->title = title;
|
||||||
cb->udata = udata;
|
cb->udata = udata;
|
||||||
cb->callback = callback;
|
cb->callback = callback;
|
||||||
ll_push(cfg.callbacks, cb);
|
ll_push(list, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void overlayConfig_register(const char * title,
|
||||||
|
void (*callback)(void * udata, int * id), void * udata)
|
||||||
|
{
|
||||||
|
config_addToList(cfg.callbacks, title, callback, udata);
|
||||||
|
};
|
||||||
|
|
||||||
|
void overlayConfig_registerTab(const char * title,
|
||||||
|
void (*callback)(void * udata, int * id), void * udata)
|
||||||
|
{
|
||||||
|
config_addToList(cfg.tabCallbacks, title, callback, udata);
|
||||||
};
|
};
|
||||||
|
@ -45,7 +45,7 @@ struct OverlayGraph
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
static void configCallback(void * udata)
|
static void configCallback(void * udata, int * id)
|
||||||
{
|
{
|
||||||
igCheckbox("Show Timing Graphs", &gs.show);
|
igCheckbox("Show Timing Graphs", &gs.show);
|
||||||
igSeparator();
|
igSeparator();
|
||||||
|
@ -35,7 +35,10 @@ void overlayGraph_unregister();
|
|||||||
void overlayGraph_iterate(void (*callback)(GraphHandle handle, const char * name,
|
void overlayGraph_iterate(void (*callback)(GraphHandle handle, const char * name,
|
||||||
bool * enabled, void * udata), void * udata);
|
bool * enabled, void * udata), void * udata);
|
||||||
|
|
||||||
void overlayConfig_register(const char * title, void (*callback)(void * udata),
|
void overlayConfig_register(const char * title,
|
||||||
void * udata);
|
void (*callback)(void * udata, int * id), void * udata);
|
||||||
|
|
||||||
|
void overlayConfig_registerTab(const char * title,
|
||||||
|
void (*callback)(void * udata, int * id), void * udata);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user