ui sprite rendering
This commit is contained in:
parent
5f8e701ac1
commit
f9c69779ea
BIN
src/pp/pp_res/tile/Empty.ase
(Stored with Git LFS)
Normal file
BIN
src/pp/pp_res/tile/Empty.ase
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
src/pp/pp_res/tile/Wall.ase
(Stored with Git LFS)
Normal file
BIN
src/pp/pp_res/tile/Wall.ase
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -14,9 +14,9 @@
|
||||
|
||||
#define P_TilesXList(X) \
|
||||
X(Empty) \
|
||||
X(Wall) \
|
||||
X(Tile) \
|
||||
X(Carpet) \
|
||||
X(Wall) \
|
||||
/* -------------------- */
|
||||
|
||||
//- Tiles kinds enum
|
||||
|
||||
@ -2749,6 +2749,15 @@ void V_TickForever(WaveLaneCtx *lane)
|
||||
++panel->windows_count;
|
||||
++V.windows_count;
|
||||
}
|
||||
{
|
||||
V_Window *window = PushStruct(perm, V_Window);
|
||||
window->panel = panel;
|
||||
window->key = UI_RandKey(); // TODO: Don't use random keys
|
||||
window->is_spawn_window = 1;
|
||||
DllQueuePushNP(panel->first_window, panel->last_window, window, next_in_panel, prev_in_panel);
|
||||
++panel->windows_count;
|
||||
++V.windows_count;
|
||||
}
|
||||
panel->active_window_idx = 1;
|
||||
}
|
||||
|
||||
@ -3068,6 +3077,10 @@ void V_TickForever(WaveLaneCtx *lane)
|
||||
{
|
||||
tab_name = Lit("Tiles");
|
||||
}
|
||||
else if (window->is_spawn_window)
|
||||
{
|
||||
tab_name = Lit("Spawn");
|
||||
}
|
||||
else
|
||||
{
|
||||
tab_name = Lit("Unknown");
|
||||
@ -3224,12 +3237,59 @@ void V_TickForever(WaveLaneCtx *lane)
|
||||
UI_PushCP(UI_BuildColumn());
|
||||
{
|
||||
UI_Push(Tag, window->key.v);
|
||||
|
||||
//////////////////////////////
|
||||
//- Build tile window
|
||||
|
||||
// if (window->is_tile_window)
|
||||
// {
|
||||
// for (P_TileKind tile_kind = 0; tile_kind < P_TileKind_COUNT; ++tile_kind)
|
||||
// {
|
||||
// String name = P_NameFromTileKind(tile_kind);
|
||||
// UI_Key key = UI_KeyF("Tile %F", FmtString(name));
|
||||
// UI_BoxReport rep = UI_ReportsFromKey(key).draw;
|
||||
|
||||
// if (rep.m1.downs)
|
||||
// {
|
||||
// frame->equipped_tile = tile_kind;
|
||||
// }
|
||||
|
||||
// Vec4 bg_color = Zi;
|
||||
// bg_color = LerpSrgb(bg_color, theme.col.button_hot, rep.hot);
|
||||
// bg_color = LerpSrgb(bg_color, theme.col.button_active, rep.active);
|
||||
|
||||
// b32 is_selected = tile_kind == frame->equipped_tile;
|
||||
|
||||
// Vec4 border_color = Zi;
|
||||
// border_color = LerpSrgb(border_color, theme.col.button_selected, rep.misc);
|
||||
// border_color = LerpSrgb(border_color, theme.col.button_active, rep.hot);
|
||||
|
||||
// UI_SetNext(BackgroundColor, bg_color);
|
||||
// UI_SetNext(BorderColor, border_color);
|
||||
// UI_SetNext(BorderSize, 1);
|
||||
// UI_SetNext(Width, UI_GROW(1, 0));
|
||||
// UI_SetNext(Height, UI_SHRINK(0, 0));
|
||||
// UI_SetNext(Misc, is_selected);
|
||||
// UI_SetNext(Flags, UI_BoxFlag_CaptureMouse);
|
||||
// UI_PushCP(UI_BuildRowEx(key));
|
||||
// {
|
||||
// UI_SetNext(ChildAlignment, UI_Region_Center);
|
||||
// UI_SetNext(Text, name);
|
||||
// UI_SetNext(Flags, UI_BoxFlag_DrawText);
|
||||
// UI_SetNext(Width, UI_SHRINK(4, 0));
|
||||
// UI_SetNext(Height, UI_SHRINK(2, 0));
|
||||
// UI_BuildRow();
|
||||
// }
|
||||
// UI_PopCP(UI_TopCP());
|
||||
// }
|
||||
// }
|
||||
|
||||
if (window->is_tile_window)
|
||||
{
|
||||
for (P_TileKind tile_kind = 0; tile_kind < P_TileKind_COUNT; ++tile_kind)
|
||||
{
|
||||
String name = P_NameFromTileKind(tile_kind);
|
||||
UI_Key key = UI_KeyF("Tile %F", FmtString(name));
|
||||
String tile_name = P_NameFromTileKind(tile_kind);
|
||||
UI_Key key = UI_KeyF("Tile %F", FmtString(tile_name));
|
||||
UI_BoxReport rep = UI_ReportsFromKey(key).draw;
|
||||
|
||||
if (rep.m1.downs)
|
||||
@ -3254,18 +3314,41 @@ void V_TickForever(WaveLaneCtx *lane)
|
||||
UI_SetNext(Height, UI_SHRINK(0, 0));
|
||||
UI_SetNext(Misc, is_selected);
|
||||
UI_SetNext(Flags, UI_BoxFlag_CaptureMouse);
|
||||
UI_SetNext(ChildAlignment, UI_Region_Left);
|
||||
UI_PushCP(UI_BuildRowEx(key));
|
||||
{
|
||||
// Tile sprite
|
||||
{
|
||||
String sheet_name = StringF(frame->arena, "tile/%F.ase", FmtString(tile_name));
|
||||
ResourceKey sheet_resource = ResourceKeyFromStore(&P_Resources, sheet_name);
|
||||
SPR_SheetKey sheet = SPR_SheetKeyFromResource(sheet_resource);
|
||||
|
||||
UI_SetNext(ChildAlignment, UI_Region_Center);
|
||||
UI_SetNext(Text, name);
|
||||
UI_SetNext(Width, UI_SHRINK(4, 0));
|
||||
UI_SetNext(Height, UI_SHRINK(4, 0));
|
||||
UI_SetNext(SpriteSheet, sheet);
|
||||
UI_BuildRow();
|
||||
}
|
||||
// Tile name
|
||||
{
|
||||
UI_SetNext(ChildAlignment, UI_Region_Center);
|
||||
UI_SetNext(Text, tile_name);
|
||||
UI_SetNext(Flags, UI_BoxFlag_DrawText);
|
||||
UI_SetNext(Width, UI_SHRINK(4, 0));
|
||||
UI_SetNext(Height, UI_SHRINK(2, 0));
|
||||
UI_BuildRow();
|
||||
}
|
||||
}
|
||||
UI_PopCP(UI_TopCP());
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
//- Build spawn window
|
||||
|
||||
if (window->is_spawn_window)
|
||||
{
|
||||
}
|
||||
}
|
||||
UI_PopCP(UI_TopCP());
|
||||
}
|
||||
@ -3395,7 +3478,7 @@ void V_TickForever(WaveLaneCtx *lane)
|
||||
UI_Push(ChildLayoutAxis, Axis_Y);
|
||||
UI_Push(FloatingPos, palette->pos);
|
||||
UI_SetNext(Anchor, UI_Region_Center);
|
||||
UI_SetNext(Flags, UI_BoxFlag_Floating | UI_BoxFlag_CaptureMouse);
|
||||
UI_SetNext(Flags, UI_BoxFlag_Floating | (UI_BoxFlag_CaptureMouse * !!palette->is_showing));
|
||||
UI_PushCP(UI_BuildBoxEx(palette->key));
|
||||
{
|
||||
// Title bar
|
||||
@ -3407,7 +3490,7 @@ void V_TickForever(WaveLaneCtx *lane)
|
||||
UI_Push(ChildLayoutAxis, Axis_X);
|
||||
UI_Push(Width, UI_GROW(1, 0));
|
||||
UI_Push(Height, UI_FNT(2, 1));
|
||||
UI_SetNext(Flags, UI_BoxFlag_DrawText | UI_BoxFlag_CaptureMouse);
|
||||
UI_SetNext(Flags, UI_BoxFlag_DrawText | (UI_BoxFlag_CaptureMouse * !!palette->is_showing));
|
||||
UI_PushCP(UI_BuildBoxEx(titlebar_key));
|
||||
{
|
||||
UI_Push(Width, UI_GROW(1, 0));
|
||||
|
||||
@ -202,6 +202,7 @@ Struct(V_Window)
|
||||
UI_Key key;
|
||||
|
||||
b32 is_tile_window;
|
||||
b32 is_spawn_window;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
@ -27,6 +27,16 @@ SPR_SpanKey SPR_SpanKeyFromName(String name)
|
||||
return result;
|
||||
}
|
||||
|
||||
b32 SPR_IsSheetKeyNil(SPR_SheetKey key)
|
||||
{
|
||||
return key.r.v == 0;
|
||||
}
|
||||
|
||||
b32 SPR_IsSpanKeyNil(SPR_SpanKey key)
|
||||
{
|
||||
return key.v == 0;
|
||||
}
|
||||
|
||||
String SPR_NameFromRayKind(SPR_RayKind kind)
|
||||
{
|
||||
PERSIST Readonly String names[SPR_RayKind_COUNT] = {
|
||||
|
||||
@ -185,6 +185,9 @@ void SPR_Bootstrap(void);
|
||||
SPR_SheetKey SPR_SheetKeyFromResource(ResourceKey resource);
|
||||
SPR_SpanKey SPR_SpanKeyFromName(String name);
|
||||
|
||||
b32 SPR_IsSheetKeyNil(SPR_SheetKey key);
|
||||
b32 SPR_IsSpanKeyNil(SPR_SpanKey key);
|
||||
|
||||
String SPR_NameFromRayKind(SPR_RayKind kind);
|
||||
SPR_LayerKind SPR_LayerKindFromName(String name);
|
||||
|
||||
|
||||
116
src/ui/ui_core.c
116
src/ui/ui_core.c
@ -503,6 +503,9 @@ UI_Key UI_BuildBoxEx(UI_Key semantic_key)
|
||||
n->cmd.box.icon = UI_Top(Icon);
|
||||
n->cmd.box.anchor = UI_Top(Anchor);
|
||||
n->cmd.box.floating_pos = UI_Top(FloatingPos);
|
||||
n->cmd.box.sprite_sheet = UI_Top(SpriteSheet);
|
||||
n->cmd.box.sprite_span = UI_Top(SpriteSpan);
|
||||
n->cmd.box.sprite_seq = UI_Top(SpriteSeq);
|
||||
n->cmd.box.misc = UI_Top(Misc);
|
||||
}
|
||||
++frame->cmds_count;
|
||||
@ -865,7 +868,7 @@ Vec2 UI_CursorPos(void)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Text layout helpers
|
||||
//~ Layout helpers
|
||||
|
||||
GC_Run UI_ScaleRun(Arena *arena, GC_Run unscaled_run, Vec2 scale)
|
||||
{
|
||||
@ -1005,8 +1008,17 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
|
||||
{
|
||||
codepoints = String32FromString(scratch.arena, box->desc.text);
|
||||
}
|
||||
|
||||
if (AnyBit(box->desc.flags, UI_BoxFlag_DrawText) && codepoints.len > 0)
|
||||
{
|
||||
box->glyph_run = GC_RunFromString32(frame->arena, codepoints, box->desc.font, box->desc.font_size);
|
||||
}
|
||||
|
||||
if (!SPR_IsSheetKeyNil(box->desc.sprite_sheet))
|
||||
{
|
||||
box->sprite = SPR_SpriteFromSheet(box->desc.sprite_sheet, box->desc.sprite_span, box->desc.sprite_seq);
|
||||
}
|
||||
}
|
||||
box->last_build_tick = frame->tick;
|
||||
} break;
|
||||
|
||||
@ -1134,12 +1146,14 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
|
||||
UI_Box *box = boxes_pre[pre_index];
|
||||
for (Axis axis = 0; axis < Axis_COUNTXY; ++axis)
|
||||
{
|
||||
UI_Size sem_dims = box->desc.pref_semantic_dims[axis];
|
||||
if (sem_dims.kind == UI_SizeKind_Pixel)
|
||||
UI_Size size = box->desc.pref_semantic_dims[axis];
|
||||
if (size.kind == UI_SizeKind_Pixel)
|
||||
{
|
||||
box->solved_dims.v[axis] = sem_dims.v;
|
||||
box->solved_dims.v[axis] = size.v;
|
||||
}
|
||||
else if (sem_dims.kind == UI_SizeKind_Shrink && AnyBit(box->desc.flags, UI_BoxFlag_DrawText))
|
||||
else if (size.kind == UI_SizeKind_Shrink)
|
||||
{
|
||||
if (AnyBit(box->desc.flags, UI_BoxFlag_DrawText))
|
||||
{
|
||||
f32 text_size = 0;
|
||||
if (axis == Axis_X)
|
||||
@ -1150,7 +1164,12 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
|
||||
{
|
||||
text_size = box->glyph_run.font_ascent + box->glyph_run.font_descent;
|
||||
}
|
||||
box->solved_dims.v[axis] = text_size + (sem_dims.v * 2);
|
||||
box->solved_dims.v[axis] = text_size + (size.v * 2);
|
||||
}
|
||||
else if (!SPR_IsSheetKeyNil(box->desc.sprite_sheet))
|
||||
{
|
||||
box->solved_dims.v[axis] = box->sprite.tex_rect.p1.v[axis] - box->sprite.tex_rect.p0.v[axis] + (size.v * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1162,22 +1181,29 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
|
||||
if (box->parent)
|
||||
{
|
||||
Axis axis = box->parent->desc.child_layout_axis;
|
||||
UI_Size sem_dims = box->desc.pref_semantic_dims[axis];
|
||||
if (sem_dims.kind == UI_SizeKind_Grow)
|
||||
UI_Size size = box->desc.pref_semantic_dims[axis];
|
||||
if (size.kind == UI_SizeKind_Grow)
|
||||
{
|
||||
f32 match_size = 0;
|
||||
b32 found_match = 0;
|
||||
for (UI_Box *ancestor = box->parent; ancestor != 0 && !found_match; ancestor = ancestor->parent)
|
||||
{
|
||||
UI_Size ancestor_size = ancestor->desc.pref_semantic_dims[axis];
|
||||
if (ancestor_size.kind == UI_SizeKind_Pixel || (ancestor_size.kind == UI_SizeKind_Shrink && AnyBit(box->desc.flags, UI_BoxFlag_DrawText)))
|
||||
if (
|
||||
ancestor_size.kind == UI_SizeKind_Pixel || (
|
||||
ancestor_size.kind == UI_SizeKind_Shrink && (
|
||||
AnyBit(box->desc.flags, UI_BoxFlag_DrawText) ||
|
||||
!SPR_IsSheetKeyNil(box->desc.sprite_sheet)
|
||||
)
|
||||
)
|
||||
)
|
||||
{
|
||||
// Match independent ancestor
|
||||
match_size = ancestor->solved_dims.v[axis];
|
||||
found_match = 1;
|
||||
}
|
||||
}
|
||||
box->solved_dims.v[axis] = match_size * sem_dims.v;
|
||||
box->solved_dims.v[axis] = match_size * size.v;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1188,8 +1214,8 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
|
||||
UI_Box *box = boxes_post[post_index];
|
||||
for (Axis axis = 0; axis < Axis_COUNTXY; ++axis)
|
||||
{
|
||||
UI_Size sem_dims = box->desc.pref_semantic_dims[axis];
|
||||
if (sem_dims.kind == UI_SizeKind_Shrink && !AnyBit(box->desc.flags, UI_BoxFlag_DrawText))
|
||||
UI_Size size = box->desc.pref_semantic_dims[axis];
|
||||
if (size.kind == UI_SizeKind_Shrink && !(AnyBit(box->desc.flags, UI_BoxFlag_DrawText) || !SPR_IsSheetKeyNil(box->desc.sprite_sheet)))
|
||||
{
|
||||
f32 accum = 0;
|
||||
for (UI_Box *child = box->first; child; child = child->next)
|
||||
@ -1207,7 +1233,7 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
|
||||
}
|
||||
}
|
||||
}
|
||||
box->solved_dims.v[axis] = CeilF32(accum + (sem_dims.v * 2));
|
||||
box->solved_dims.v[axis] = CeilF32(accum + (size.v * 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1219,10 +1245,10 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
|
||||
if (box->parent)
|
||||
{
|
||||
Axis axis = !box->parent->desc.child_layout_axis;
|
||||
UI_Size sem_dims = box->desc.pref_semantic_dims[axis];
|
||||
if (sem_dims.kind == UI_SizeKind_Grow)
|
||||
UI_Size size = box->desc.pref_semantic_dims[axis];
|
||||
if (size.kind == UI_SizeKind_Grow)
|
||||
{
|
||||
box->solved_dims.v[axis] = box->parent->solved_dims.v[axis] * sem_dims.v;
|
||||
box->solved_dims.v[axis] = box->parent->solved_dims.v[axis] * size.v;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1508,7 +1534,6 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
|
||||
for (u64 pre_index = 0; pre_index < boxes_count; ++pre_index)
|
||||
{
|
||||
UI_Box *box = boxes_pre[pre_index];
|
||||
UI_RegionPair child_alignment = UI_PairFromRegion(box->desc.child_alignment);
|
||||
|
||||
b32 is_visible = 1;
|
||||
is_visible = is_visible && (box->desc.tint.w >= 0.0025);
|
||||
@ -1518,6 +1543,10 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
|
||||
{
|
||||
Vec4 debug_lin = is_visible ? LinearFromSrgb(box->desc.debug_color) : LinearFromSrgb(box->desc.invisible_debug_color);
|
||||
Vec4 tint_lin = LinearFromSrgb(box->desc.tint);
|
||||
Vec2 box_dims = DimsFromRng2(box->screen_rect);
|
||||
UI_RegionPair child_alignment = UI_PairFromRegion(box->desc.child_alignment);
|
||||
UI_AxisRegion x_alignment = child_alignment.v[Axis_X];
|
||||
UI_AxisRegion y_alignment = child_alignment.v[Axis_Y];
|
||||
|
||||
// Box rect
|
||||
{
|
||||
@ -1536,6 +1565,55 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
|
||||
rect->tex_slice_uv = box->raw_texture_slice_uv;
|
||||
}
|
||||
|
||||
// Sprite rect
|
||||
if (!SPR_IsSheetKeyNil(box->desc.sprite_sheet))
|
||||
{
|
||||
UI_GpuRect *rect = PushStruct(frame->rects_arena, UI_GpuRect);
|
||||
rect->debug_lin = debug_lin;
|
||||
rect->tint_lin = tint_lin;
|
||||
rect->tex = box->sprite.tex;
|
||||
rect->tex_slice_uv = DivRng2Vec2(box->sprite.tex_rect, box->sprite.tex_dims);
|
||||
|
||||
Vec2 dims = DimsFromRng2(box->sprite.tex_rect);
|
||||
Vec2 pos = Zi;
|
||||
switch (x_alignment)
|
||||
{
|
||||
case UI_AxisRegion_Start:
|
||||
{
|
||||
pos.x = box->screen_rect.p0.x;
|
||||
} break;
|
||||
case UI_AxisRegion_End:
|
||||
{
|
||||
pos.x = box->screen_rect.p1.x;
|
||||
pos.x -= dims.x;
|
||||
} break;
|
||||
case UI_AxisRegion_Center:
|
||||
{
|
||||
pos.x = box->screen_rect.p0.x;
|
||||
pos.x += (box_dims.x - dims.x) / 2;
|
||||
} break;
|
||||
}
|
||||
switch (y_alignment)
|
||||
{
|
||||
case UI_AxisRegion_Start:
|
||||
{
|
||||
pos.y = box->screen_rect.p0.y;
|
||||
} break;
|
||||
case UI_AxisRegion_End:
|
||||
{
|
||||
pos.y = box->screen_rect.p1.y;
|
||||
pos.y -= dims.y;
|
||||
} break;
|
||||
case UI_AxisRegion_Center:
|
||||
{
|
||||
pos.y = box->screen_rect.p0.y;
|
||||
pos.y += (box_dims.y - dims.y) / 2;
|
||||
} break;
|
||||
}
|
||||
rect->bounds.p0 = pos;
|
||||
rect->bounds.p1 = AddVec2(rect->bounds.p0, dims);
|
||||
}
|
||||
|
||||
// Text rects
|
||||
GC_Run raw_run_unscaled = box->glyph_run;
|
||||
GC_Run raw_run = UI_ScaleRun(frame->arena, raw_run_unscaled, box->solved_scale);
|
||||
@ -1590,15 +1668,11 @@ void UI_EndFrame(UI_Frame *frame, i32 vsync)
|
||||
final_baseline_length = raw_run.baseline_length;
|
||||
}
|
||||
|
||||
UI_AxisRegion x_alignment = child_alignment.v[Axis_X];
|
||||
UI_AxisRegion y_alignment = child_alignment.v[Axis_Y];
|
||||
|
||||
// Compute baseline
|
||||
f32 ascent = raw_run.font_ascent;
|
||||
f32 font_descent = raw_run.font_descent;
|
||||
f32 cap = raw_run.font_cap;
|
||||
f32 baseline_height = ascent + font_descent;
|
||||
Vec2 box_dims = DimsFromRng2(box->screen_rect);
|
||||
Vec2 baseline = Zi;
|
||||
switch (x_alignment)
|
||||
{
|
||||
|
||||
@ -121,6 +121,9 @@ Enum(UI_BoxFlag)
|
||||
X(TextColor, Vec4) \
|
||||
X(Text, String) \
|
||||
X(Icon, UI_Icon) \
|
||||
X(SpriteSheet, SPR_SheetKey) \
|
||||
X(SpriteSpan, SPR_SpanKey) \
|
||||
X(SpriteSeq, i64) \
|
||||
X(BackgroundTexture, G_Texture2DRef) \
|
||||
X(BackgroundTextureSliceUv, Rng2) \
|
||||
X(Misc, f64) \
|
||||
@ -255,6 +258,9 @@ Struct(UI_BoxDesc)
|
||||
UI_Region child_alignment;
|
||||
UI_Region anchor;
|
||||
Vec2 floating_pos;
|
||||
SPR_SheetKey sprite_sheet;
|
||||
SPR_SpanKey sprite_span;
|
||||
i64 sprite_seq;
|
||||
f64 misc;
|
||||
};
|
||||
|
||||
@ -308,6 +314,7 @@ Struct(UI_Box)
|
||||
G_Texture2DRef raw_texture;
|
||||
Rng2 raw_texture_slice_uv;
|
||||
GC_Run glyph_run;
|
||||
SPR_Sprite sprite;
|
||||
|
||||
//- Pre-layout data
|
||||
u64 pre_index;
|
||||
@ -527,7 +534,7 @@ Arena *UI_FrameArena(void);
|
||||
Vec2 UI_CursorPos(void);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//~ Text layout helpers
|
||||
//~ Layout helpers
|
||||
|
||||
GC_Run UI_ScaleRun(Arena *arena, GC_Run unscaled_run, Vec2 scale);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user