rework UI
This commit is contained in:
parent
a6a66d99fd
commit
877f8034c7
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
.zig-cache
|
.zig-cache
|
||||||
zig-out
|
zig-out
|
||||||
.vscode
|
.vscode
|
||||||
|
profile.json
|
||||||
|
@ -90,6 +90,11 @@ pub fn build(b: *std.Build) !void {
|
|||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const profiler_dep = b.dependency("profiler.zig", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
const stb_image_lib = buildStbImage(b);
|
const stb_image_lib = buildStbImage(b);
|
||||||
const cute_aseprite_lib = buildCuteAseprite(b, raylib_dep);
|
const cute_aseprite_lib = buildCuteAseprite(b, raylib_dep);
|
||||||
|
|
||||||
@ -114,6 +119,9 @@ pub fn build(b: *std.Build) !void {
|
|||||||
exe.root_module.addImport("known-folders", known_folders);
|
exe.root_module.addImport("known-folders", known_folders);
|
||||||
exe.root_module.addImport("ini", ini);
|
exe.root_module.addImport("ini", ini);
|
||||||
|
|
||||||
|
// TODO: Add flag to disable in release
|
||||||
|
exe.root_module.addImport("profiler", profiler_dep.module("profiler"));
|
||||||
|
|
||||||
const external_compiler_support_dir = try std.process.getEnvVarOwned(b.allocator, "NIEXTCCOMPILERSUPP");
|
const external_compiler_support_dir = try std.process.getEnvVarOwned(b.allocator, "NIEXTCCOMPILERSUPP");
|
||||||
exe.addSystemIncludePath(.{ .cwd_relative = try std.fs.path.join(b.allocator, &.{ external_compiler_support_dir, "include" }) });
|
exe.addSystemIncludePath(.{ .cwd_relative = try std.fs.path.join(b.allocator, &.{ external_compiler_support_dir, "include" }) });
|
||||||
|
|
||||||
|
@ -2,19 +2,16 @@
|
|||||||
.name = "Baigiamasis projektas",
|
.name = "Baigiamasis projektas",
|
||||||
.version = "0.1.0",
|
.version = "0.1.0",
|
||||||
|
|
||||||
.dependencies = .{
|
.dependencies = .{ .@"raylib-zig" = .{ .url = "https://github.com/Not-Nik/raylib-zig/archive/43d15b05c2b97cab30103fa2b46cff26e91619ec.tar.gz", .hash = "12204a223b19043e17b79300413d02f60fc8004c0d9629b8d8072831e352a78bf212" }, .@"known-folders" = .{
|
||||||
.@"raylib-zig" = .{
|
|
||||||
.url = "https://github.com/Not-Nik/raylib-zig/archive/43d15b05c2b97cab30103fa2b46cff26e91619ec.tar.gz",
|
|
||||||
.hash = "12204a223b19043e17b79300413d02f60fc8004c0d9629b8d8072831e352a78bf212"
|
|
||||||
},
|
|
||||||
.@"known-folders" = .{
|
|
||||||
.url = "git+https://github.com/ziglibs/known-folders.git#1cceeb70e77dec941a4178160ff6c8d05a74de6f",
|
.url = "git+https://github.com/ziglibs/known-folders.git#1cceeb70e77dec941a4178160ff6c8d05a74de6f",
|
||||||
.hash = "12205f5e7505c96573f6fc5144592ec38942fb0a326d692f9cddc0c7dd38f9028f29",
|
.hash = "12205f5e7505c96573f6fc5144592ec38942fb0a326d692f9cddc0c7dd38f9028f29",
|
||||||
},
|
}, .ini = .{
|
||||||
.@"ini" = .{
|
|
||||||
.url = "https://github.com/ziglibs/ini/archive/e18d36665905c1e7ba0c1ce3e8780076b33e3002.tar.gz",
|
.url = "https://github.com/ziglibs/ini/archive/e18d36665905c1e7ba0c1ce3e8780076b33e3002.tar.gz",
|
||||||
.hash = "1220b0979ea9891fa4aeb85748fc42bc4b24039d9c99a4d65d893fb1c83e921efad8",
|
.hash = "1220b0979ea9891fa4aeb85748fc42bc4b24039d9c99a4d65d893fb1c83e921efad8",
|
||||||
}
|
}, .@"profiler.zig" = .{
|
||||||
|
.url = "git+https://github.com/lassade/profiler.zig.git#d066d066c36c4eebd494babf15c1cdbd2d512b12",
|
||||||
|
.hash = "122097461acc2064f5f89b85d76d2a02232579864b17604617a333789c892f2d262f",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
.paths = .{
|
.paths = .{
|
||||||
|
1399
src/app.zig
1399
src/app.zig
File diff suppressed because it is too large
Load Diff
@ -5,15 +5,38 @@ const FontFace = @import("./font-face.zig");
|
|||||||
const Aseprite = @import("cute_aseprite");
|
const Aseprite = @import("cute_aseprite");
|
||||||
|
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const log = std.log.scoped(.assets);
|
||||||
|
|
||||||
pub const FontId = enum {
|
pub const FontVariant = enum {
|
||||||
text
|
regular,
|
||||||
|
regular_italic,
|
||||||
|
bold,
|
||||||
|
bold_italic,
|
||||||
|
thin,
|
||||||
|
thin_italic
|
||||||
};
|
};
|
||||||
|
|
||||||
var loaded_fonts: std.BoundedArray(rl.Font, 32) = .{};
|
pub const FontId = struct {
|
||||||
|
variant: FontVariant,
|
||||||
|
size: f32,
|
||||||
|
|
||||||
const FontArray = std.EnumArray(FontId, FontFace);
|
pub fn eql(self: FontId, other: FontId) bool {
|
||||||
var fonts: FontArray = FontArray.initUndefined();
|
return self.variant == other.variant and self.size == other.size;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const LoadedFont = struct {
|
||||||
|
id: FontId,
|
||||||
|
font: rl.Font,
|
||||||
|
generation: u64,
|
||||||
|
};
|
||||||
|
|
||||||
|
const FontTTFArray = std.EnumArray(FontVariant, []const u8);
|
||||||
|
var font_ttfs: FontTTFArray = undefined;
|
||||||
|
|
||||||
|
const LoadedFontsArray = std.BoundedArray(LoadedFont, std.meta.fields(FontVariant).len * 8);
|
||||||
|
var current_font_generation: u64 = 0;
|
||||||
|
var loaded_fonts: LoadedFontsArray = .{ };
|
||||||
|
|
||||||
pub var grab_texture: struct {
|
pub var grab_texture: struct {
|
||||||
normal: rl.Texture2D,
|
normal: rl.Texture2D,
|
||||||
@ -24,16 +47,47 @@ pub var grab_texture: struct {
|
|||||||
pub var dropdown_arrow: rl.Texture2D = undefined;
|
pub var dropdown_arrow: rl.Texture2D = undefined;
|
||||||
|
|
||||||
pub fn font(font_id: FontId) FontFace {
|
pub fn font(font_id: FontId) FontFace {
|
||||||
return fonts.get(font_id);
|
var found_font: ?LoadedFont = null;
|
||||||
|
for (loaded_fonts.slice()) |*loaded_font| {
|
||||||
|
if (font_id.eql(loaded_font.id)) {
|
||||||
|
loaded_font.generation = current_font_generation;
|
||||||
|
found_font = loaded_font.*;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found_font == null) {
|
||||||
|
const raylib_font = loadFont(
|
||||||
|
font_ttfs.get(font_id.variant),
|
||||||
|
@intFromFloat(@round(font_id.size))
|
||||||
|
) catch rl.getFontDefault();
|
||||||
|
|
||||||
|
found_font = LoadedFont{
|
||||||
|
.id = font_id,
|
||||||
|
.font = raylib_font,
|
||||||
|
.generation = current_font_generation
|
||||||
|
};
|
||||||
|
|
||||||
|
loaded_fonts.append(found_font.?) catch {
|
||||||
|
log.warn("Failed to append font, font cache is full", .{});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return FontFace{
|
||||||
|
.line_height = 1.2,
|
||||||
|
.font = found_font.?.font,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) !void {
|
pub fn init(allocator: std.mem.Allocator) !void {
|
||||||
const roboto_regular = @embedFile("./assets/fonts/roboto/Roboto-Regular.ttf");
|
font_ttfs = FontTTFArray.init(.{
|
||||||
|
.regular = @embedFile("./assets/fonts/roboto/Roboto-Regular.ttf"),
|
||||||
|
.regular_italic = @embedFile("./assets/fonts/roboto/Roboto-Italic.ttf"),
|
||||||
|
|
||||||
const default_font = try loadFont(roboto_regular, 16);
|
.bold = @embedFile("./assets/fonts/roboto/Roboto-Bold.ttf"),
|
||||||
|
.bold_italic = @embedFile("./assets/fonts/roboto/Roboto-BoldItalic.ttf"),
|
||||||
|
|
||||||
fonts = FontArray.init(.{
|
.thin = @embedFile("./assets/fonts/roboto/Roboto-Thin.ttf"),
|
||||||
.text = FontFace{ .font = default_font, .line_height = 1.2 }
|
.thin_italic = @embedFile("./assets/fonts/roboto/Roboto-ThinItalic.ttf")
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -86,22 +140,36 @@ fn loadFont(ttf_data: []const u8, font_size: u32) !rl.Font {
|
|||||||
codepoints.appendAssumeCapacity(codepoint);
|
codepoints.appendAssumeCapacity(codepoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
const loaded_font = rl.loadFontFromMemory(".ttf", ttf_data, @intCast(font_size), codepoints.slice());
|
const raylib_font = rl.loadFontFromMemory(".ttf", ttf_data, @intCast(font_size), codepoints.slice());
|
||||||
if (!loaded_font.isReady()) {
|
if (!raylib_font.isReady()) {
|
||||||
return error.LoadFontFromMemory;
|
return error.LoadFontFromMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
loaded_fonts.appendAssumeCapacity(loaded_font);
|
return raylib_font;
|
||||||
|
}
|
||||||
|
|
||||||
return loaded_font;
|
pub fn deinitUnusedFonts() void {
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < loaded_fonts.len) {
|
||||||
|
const loaded_font = loaded_fonts.buffer[i];
|
||||||
|
if (loaded_font.generation < current_font_generation) {
|
||||||
|
loaded_font.font.unload();
|
||||||
|
_ = loaded_fonts.swapRemove(i);
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current_font_generation += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(allocator: std.mem.Allocator) void {
|
pub fn deinit(allocator: std.mem.Allocator) void {
|
||||||
_ = allocator;
|
_ = allocator;
|
||||||
|
|
||||||
for (loaded_fonts.slice()) |loaded_font| {
|
for (loaded_fonts.slice()) |loaded_font| {
|
||||||
loaded_font.unload();
|
loaded_font.font.unload();
|
||||||
}
|
}
|
||||||
|
loaded_fonts.len = 0;
|
||||||
|
|
||||||
grab_texture.active.unload();
|
grab_texture.active.unload();
|
||||||
grab_texture.hot.unload();
|
grab_texture.hot.unload();
|
||||||
|
BIN
src/assets/fonts/roboto/Roboto-Bold.ttf
Normal file
BIN
src/assets/fonts/roboto/Roboto-Bold.ttf
Normal file
Binary file not shown.
BIN
src/assets/fonts/roboto/Roboto-BoldItalic.ttf
Normal file
BIN
src/assets/fonts/roboto/Roboto-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
src/assets/fonts/roboto/Roboto-Italic.ttf
Normal file
BIN
src/assets/fonts/roboto/Roboto-Italic.ttf
Normal file
Binary file not shown.
BIN
src/assets/fonts/roboto/Roboto-Thin.ttf
Normal file
BIN
src/assets/fonts/roboto/Roboto-Thin.ttf
Normal file
Binary file not shown.
BIN
src/assets/fonts/roboto/Roboto-ThinItalic.ttf
Normal file
BIN
src/assets/fonts/roboto/Roboto-ThinItalic.ttf
Normal file
Binary file not shown.
@ -60,7 +60,6 @@ pub fn drawTextEx(self: @This(), text: []const u8, position: rl.Vector2, tint: r
|
|||||||
|
|
||||||
var iter = std.unicode.Utf8Iterator{ .bytes = text, .i = 0 };
|
var iter = std.unicode.Utf8Iterator{ .bytes = text, .i = 0 };
|
||||||
while (iter.nextCodepoint()) |codepoint| {
|
while (iter.nextCodepoint()) |codepoint| {
|
||||||
|
|
||||||
if (codepoint == '\n') {
|
if (codepoint == '\n') {
|
||||||
offset.x = 0;
|
offset.x = 0;
|
||||||
offset.y += font_size * self.line_height;
|
offset.y += font_size * self.line_height;
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
#version 330
|
|
||||||
|
|
||||||
// Input vertex attributes (from vertex shader)
|
|
||||||
in vec2 fragTexCoord;
|
|
||||||
in vec4 fragColor;
|
|
||||||
|
|
||||||
// Input uniform values
|
|
||||||
uniform sampler2D texture0;
|
|
||||||
uniform vec4 colDiffuse;
|
|
||||||
|
|
||||||
// Output fragment color
|
|
||||||
out vec4 finalColor;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
vec4 texelColor = texture(texture0, fragTexCoord)*colDiffuse*fragColor;
|
|
||||||
float luminance = dot(texelColor.rgb, vec3(0.2126, 0.7152, 0.0722));
|
|
||||||
gl_FragColor = vec4(luminance, luminance, luminance, texelColor.a);
|
|
||||||
}
|
|
40
src/main.zig
40
src/main.zig
@ -3,12 +3,13 @@ const rl = @import("raylib");
|
|||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const Application = @import("./app.zig");
|
const Application = @import("./app.zig");
|
||||||
const Assets = @import("./assets.zig");
|
const Assets = @import("./assets.zig");
|
||||||
const Profiler = @import("./profiler.zig");
|
const Profiler = @import("./my-profiler.zig");
|
||||||
const Platform = @import("./platform.zig");
|
const Platform = @import("./platform.zig");
|
||||||
const raylib_h = @cImport({
|
const raylib_h = @cImport({
|
||||||
@cInclude("stdio.h");
|
@cInclude("stdio.h");
|
||||||
@cInclude("raylib.h");
|
@cInclude("raylib.h");
|
||||||
});
|
});
|
||||||
|
const P = @import("profiler");
|
||||||
|
|
||||||
const log = std.log;
|
const log = std.log;
|
||||||
const profiler_enabled = builtin.mode == .Debug;
|
const profiler_enabled = builtin.mode == .Debug;
|
||||||
@ -65,6 +66,12 @@ fn raylibTraceLogCallback(logType: c_int, text: [*c]const u8, args: raylib_h.va_
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
|
try P.init(.{});
|
||||||
|
defer {
|
||||||
|
P.dump("profile.json") catch |err| std.log.err("profile dump failed: {}", .{err});
|
||||||
|
P.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
Platform.init();
|
Platform.init();
|
||||||
|
|
||||||
// TODO: Setup logging to a file
|
// TODO: Setup logging to a file
|
||||||
@ -156,8 +163,8 @@ pub fn main() !void {
|
|||||||
defer app.deinit();
|
defer app.deinit();
|
||||||
|
|
||||||
if (builtin.mode == .Debug) {
|
if (builtin.mode == .Debug) {
|
||||||
// try app.appendChannelFromDevice("Dev1/ai0");
|
try app.appendChannelFromDevice("Dev1/ai0");
|
||||||
try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_I.bin");
|
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_I.bin");
|
||||||
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_IjStim.bin");
|
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_IjStim.bin");
|
||||||
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_IjStim.bin");
|
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_IjStim.bin");
|
||||||
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_IjStim.bin");
|
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_IjStim.bin");
|
||||||
@ -168,15 +175,23 @@ pub fn main() !void {
|
|||||||
|
|
||||||
var profiler_shown = false;
|
var profiler_shown = false;
|
||||||
if (profiler_enabled) {
|
if (profiler_enabled) {
|
||||||
const font_face = Assets.font(.text);
|
profiler = try Profiler.init(
|
||||||
profiler = try Profiler.init(allocator, 10 * target_fps, @divFloor(std.time.ns_per_s, target_fps), font_face);
|
allocator,
|
||||||
|
10 * target_fps,
|
||||||
|
@divFloor(std.time.ns_per_s, target_fps),
|
||||||
|
Assets.FontId{ .variant = .regular, .size = 16 }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.setExitKey(rl.KeyboardKey.key_null);
|
rl.setExitKey(rl.KeyboardKey.key_null);
|
||||||
|
|
||||||
|
var last_font_cleanup_at = rl.getTime();
|
||||||
while (!rl.windowShouldClose() and !app.should_close) {
|
while (!rl.windowShouldClose() and !app.should_close) {
|
||||||
rl.beginDrawing();
|
rl.beginDrawing();
|
||||||
defer rl.endDrawing();
|
|
||||||
|
{
|
||||||
|
const zone = P.begin(@src(), "tick");
|
||||||
|
defer zone.end();
|
||||||
|
|
||||||
if (profiler) |*p| {
|
if (profiler) |*p| {
|
||||||
p.start();
|
p.start();
|
||||||
@ -194,6 +209,19 @@ pub fn main() !void {
|
|||||||
try p.showResults();
|
try p.showResults();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const now = rl.getTime();
|
||||||
|
if (now - last_font_cleanup_at > 10) {
|
||||||
|
Assets.deinitUnusedFonts();
|
||||||
|
last_font_cleanup_at = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const zone = P.begin(@src(), "end draw");
|
||||||
|
defer zone.end();
|
||||||
|
rl.endDrawing();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const rl = @import("raylib");
|
const rl = @import("raylib");
|
||||||
|
const Assets = @import("./assets.zig");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const FontId = Assets.FontId;
|
||||||
|
|
||||||
const FontFace = @import("./font-face.zig");
|
|
||||||
const srcery = @import("./srcery.zig");
|
const srcery = @import("./srcery.zig");
|
||||||
const rect_utils = @import("./rect-utils.zig");
|
const rect_utils = @import("./rect-utils.zig");
|
||||||
|
|
||||||
@ -14,9 +15,9 @@ history_head: usize,
|
|||||||
|
|
||||||
started_at: i128,
|
started_at: i128,
|
||||||
ns_budget: u128,
|
ns_budget: u128,
|
||||||
font_face: FontFace,
|
font: FontId,
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, data_points: usize, ns_budget: u128, font_face: FontFace) !@This() {
|
pub fn init(allocator: Allocator, data_points: usize, ns_budget: u128, font: FontId) !@This() {
|
||||||
return @This(){
|
return @This(){
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.history = try allocator.alloc(u128, data_points),
|
.history = try allocator.alloc(u128, data_points),
|
||||||
@ -24,7 +25,7 @@ pub fn init(allocator: Allocator, data_points: usize, ns_budget: u128, font_face
|
|||||||
.history_head = 0,
|
.history_head = 0,
|
||||||
.started_at = 0,
|
.started_at = 0,
|
||||||
.ns_budget = ns_budget,
|
.ns_budget = ns_budget,
|
||||||
.font_face = font_face,
|
.font = font,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +91,8 @@ pub fn showResults(self: *const @This()) !void {
|
|||||||
var layout_offset: f32 = 0;
|
var layout_offset: f32 = 0;
|
||||||
|
|
||||||
const allocator = self.allocator;
|
const allocator = self.allocator;
|
||||||
const font_size = self.font_face.getSize();
|
const font_face = Assets.font(self.font);
|
||||||
|
const font_size = font_face.getSize();
|
||||||
|
|
||||||
const labels = .{
|
const labels = .{
|
||||||
.{ "Min", @as(f32, @floatFromInt(min_time_taken)) },
|
.{ "Min", @as(f32, @floatFromInt(min_time_taken)) },
|
||||||
@ -104,7 +106,7 @@ pub fn showResults(self: *const @This()) !void {
|
|||||||
const min_time_str = try std.fmt.allocPrintZ(allocator, "{s}: {d:10.0}us ({d:.3}%)", .{ label_name, time_taken / std.time.ns_per_us, time_taken / ns_budget * 100 });
|
const min_time_str = try std.fmt.allocPrintZ(allocator, "{s}: {d:10.0}us ({d:.3}%)", .{ label_name, time_taken / std.time.ns_per_us, time_taken / ns_budget * 100 });
|
||||||
defer allocator.free(min_time_str);
|
defer allocator.free(min_time_str);
|
||||||
|
|
||||||
self.font_face.drawText(min_time_str, .{ .x = content_rect.x, .y = content_rect.y + layout_offset }, color);
|
font_face.drawText(min_time_str, .{ .x = content_rect.x, .y = content_rect.y + layout_offset }, color);
|
||||||
layout_offset += font_size;
|
layout_offset += font_size;
|
||||||
}
|
}
|
||||||
}
|
}
|
2259
src/ui.zig
2259
src/ui.zig
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user