Text align (#10804)
* Text/ShapeText marker text alignment * Allow `draw print` to use a dynamic align * Highlight currently selected value when possible * Fix alignment with newlines * Add `lineAlign` --------- Co-authored-by: Anuken <arnukren@gmail.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import mindustry.game.MapObjectives.*;
|
|||||||
import mindustry.gen.*;
|
import mindustry.gen.*;
|
||||||
import mindustry.graphics.*;
|
import mindustry.graphics.*;
|
||||||
import mindustry.io.*;
|
import mindustry.io.*;
|
||||||
|
import mindustry.logic.*;
|
||||||
import mindustry.type.*;
|
import mindustry.type.*;
|
||||||
import mindustry.ui.*;
|
import mindustry.ui.*;
|
||||||
import mindustry.ui.dialogs.*;
|
import mindustry.ui.dialogs.*;
|
||||||
@@ -280,6 +281,15 @@ public class MapObjectivesDialog extends BaseDialog{
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
setInterpreter(Alignment.class, int.class, (cont, name, type, field, remover, indexer, get, set) -> {
|
||||||
|
Alignment align = field.getAnnotation(Alignment.class);
|
||||||
|
name(cont, name, remover, indexer);
|
||||||
|
cont.button(b -> {
|
||||||
|
b.label(() -> LStatement.alignToName.get(get.get(), "center"));
|
||||||
|
b.clicked(() -> LStatement.showAlignSelect(b, get.get(), set::get, align.hor(), align.ver()));
|
||||||
|
}, () -> {});
|
||||||
|
});
|
||||||
|
|
||||||
// Types that use the default interpreter. It would be nice if all types could use it, but I don't know how to reliably prevent classes like [? extends Content] from using it.
|
// Types that use the default interpreter. It would be nice if all types could use it, but I don't know how to reliably prevent classes like [? extends Content] from using it.
|
||||||
for(var obj : MapObjectives.allObjectiveTypes) setInterpreter(obj.get().getClass(), defaultInterpreter());
|
for(var obj : MapObjectives.allObjectiveTypes) setInterpreter(obj.get().getClass(), defaultInterpreter());
|
||||||
for(var mark : MapObjectives.allMarkerTypes) setInterpreter(mark.get().getClass(), defaultInterpreter());
|
for(var mark : MapObjectives.allMarkerTypes) setInterpreter(mark.get().getClass(), defaultInterpreter());
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ public abstract class WorldLabelComp implements Posc, Drawc, Syncc{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(){
|
public void draw(){
|
||||||
drawAt(text, x, y, z, flags, fontSize);
|
drawAt(text, x, y, z, flags, fontSize, Align.center, Align.center);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void drawAt(String text, float x, float y, float layer, int flags, float fontSize){
|
public static void drawAt(String text, float x, float y, float layer, int flags, float fontSize, int align, int lineAlign){
|
||||||
Draw.z(layer);
|
Draw.z(layer);
|
||||||
float z = Drawf.text();
|
float z = Drawf.text();
|
||||||
|
|
||||||
@@ -46,14 +46,32 @@ public abstract class WorldLabelComp implements Posc, Drawc, Syncc{
|
|||||||
font.getData().setScale(0.25f / Scl.scl(1f) * fontSize);
|
font.getData().setScale(0.25f / Scl.scl(1f) * fontSize);
|
||||||
layout.setText(font, text);
|
layout.setText(font, text);
|
||||||
|
|
||||||
|
int border = (flags & flagBackground) != 0 ? 1 : 0;
|
||||||
|
|
||||||
|
if(Align.isBottom(align)){
|
||||||
|
y += layout.height + border * 1.5f;
|
||||||
|
}else if(Align.isTop(align)){
|
||||||
|
y -= border * 1.5f;
|
||||||
|
}else{
|
||||||
|
y += layout.height / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Align.isLeft(align)){
|
||||||
|
x += layout.width / 2 + border;
|
||||||
|
}else if(Align.isRight(align)){
|
||||||
|
x -= layout.width / 2 + border;
|
||||||
|
}
|
||||||
|
|
||||||
if((flags & flagBackground) != 0){
|
if((flags & flagBackground) != 0){
|
||||||
Draw.color(0f, 0f, 0f, 0.3f);
|
Draw.color(0f, 0f, 0f, 0.3f);
|
||||||
Fill.rect(x, y - layout.height / 2, layout.width + 2, layout.height + 3);
|
Fill.rect(x, y - layout.height / 2, layout.width + 2, layout.height + 3);
|
||||||
Draw.color();
|
Draw.color();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float tx = Align.isLeft(lineAlign) ? -layout.width * 0.5f : Align.isRight(lineAlign) ? layout.width * 0.5f : 0;
|
||||||
|
|
||||||
font.setColor(Color.white);
|
font.setColor(Color.white);
|
||||||
font.draw(text, x, y, 0, Align.center, false);
|
font.draw(text, x + tx, y, 0, lineAlign, false);
|
||||||
|
|
||||||
Draw.reset();
|
Draw.reset();
|
||||||
Pools.free(layout);
|
Pools.free(layout);
|
||||||
|
|||||||
@@ -757,6 +757,8 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
|||||||
public @Multiline String text = "frog";
|
public @Multiline String text = "frog";
|
||||||
public float fontSize = 1f, textHeight = 7f;
|
public float fontSize = 1f, textHeight = 7f;
|
||||||
public @LabelFlag byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
|
public @LabelFlag byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
|
||||||
|
public @Alignment int textAlign = Align.center;
|
||||||
|
public @Alignment(ver = false) int lineAlign = Align.center;
|
||||||
|
|
||||||
public float radius = 6f, rotation = 0f;
|
public float radius = 6f, rotation = 0f;
|
||||||
public int sides = 4;
|
public int sides = 4;
|
||||||
@@ -791,6 +793,15 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
|||||||
this.textHeight = textHeight;
|
this.textHeight = textHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ShapeTextMarker(String text, float x, float y, float radius, float rotation, float textHeight, int textAlign){
|
||||||
|
this.text = text;
|
||||||
|
this.pos.set(x, y);
|
||||||
|
this.radius = radius;
|
||||||
|
this.rotation = rotation;
|
||||||
|
this.textHeight = textHeight;
|
||||||
|
this.textAlign = textAlign;
|
||||||
|
}
|
||||||
|
|
||||||
public ShapeTextMarker(){}
|
public ShapeTextMarker(){}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -812,7 +823,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
|||||||
// font size cannot be 0
|
// font size cannot be 0
|
||||||
if(Mathf.equal(fontSize, 0f)) return;
|
if(Mathf.equal(fontSize, 0f)) return;
|
||||||
|
|
||||||
WorldLabel.drawAt(fetchedText, pos.x, pos.y + radius * scaleFactor + textHeight * scaleFactor, drawLayer, flags, fontSize * scaleFactor);
|
WorldLabel.drawAt(fetchedText, pos.x, pos.y + radius * scaleFactor + textHeight * scaleFactor, drawLayer, flags, fontSize * scaleFactor, textAlign, lineAlign);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -823,6 +834,8 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
|||||||
switch(type){
|
switch(type){
|
||||||
case fontSize -> fontSize = (float)p1;
|
case fontSize -> fontSize = (float)p1;
|
||||||
case textHeight -> textHeight = (float)p1;
|
case textHeight -> textHeight = (float)p1;
|
||||||
|
case textAlign -> textAlign = (int)p1;
|
||||||
|
case lineAlign -> lineAlign = (int)p1;
|
||||||
case outline -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p1, 0f));
|
case outline -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p1, 0f));
|
||||||
case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagBackground, !Mathf.equal((float)p1, 0f));
|
case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagBackground, !Mathf.equal((float)p1, 0f));
|
||||||
case radius -> radius = (float)p1;
|
case radius -> radius = (float)p1;
|
||||||
@@ -980,6 +993,9 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
|||||||
public @Multiline String text = "uwu";
|
public @Multiline String text = "uwu";
|
||||||
public float fontSize = 1f;
|
public float fontSize = 1f;
|
||||||
public @LabelFlag byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
|
public @LabelFlag byte flags = WorldLabel.flagBackground | WorldLabel.flagOutline;
|
||||||
|
public @Alignment int textAlign = Align.center;
|
||||||
|
public @Alignment(ver = false) int lineAlign = Align.center;
|
||||||
|
|
||||||
// Cached localized text.
|
// Cached localized text.
|
||||||
private transient String fetchedText;
|
private transient String fetchedText;
|
||||||
|
|
||||||
@@ -1006,7 +1022,7 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
|||||||
fetchedText = fetchText(text);
|
fetchedText = fetchText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
WorldLabel.drawAt(fetchedText, pos.x, pos.y, drawLayer, flags, fontSize * scaleFactor);
|
WorldLabel.drawAt(fetchedText, pos.x, pos.y, drawLayer, flags, fontSize * scaleFactor, textAlign, lineAlign);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1016,6 +1032,8 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
|||||||
if(!Double.isNaN(p1)){
|
if(!Double.isNaN(p1)){
|
||||||
switch(type){
|
switch(type){
|
||||||
case fontSize -> fontSize = (float)p1;
|
case fontSize -> fontSize = (float)p1;
|
||||||
|
case textAlign -> textAlign = (int)p1;
|
||||||
|
case lineAlign -> lineAlign = (int)p1;
|
||||||
case outline -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p1, 0f));
|
case outline -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagOutline, !Mathf.equal((float)p1, 0f));
|
||||||
case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagBackground, !Mathf.equal((float)p1, 0f));
|
case labelFlags -> flags = (byte)Pack.bitmask(flags, WorldLabel.flagBackground, !Mathf.equal((float)p1, 0f));
|
||||||
}
|
}
|
||||||
@@ -1317,6 +1335,14 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
|||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
public @interface LabelFlag{}
|
public @interface LabelFlag{}
|
||||||
|
|
||||||
|
/** For {@code int}; treats it as an alignment from {@link Align} */
|
||||||
|
@Target(FIELD)
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
public @interface Alignment{
|
||||||
|
boolean hor() default true;
|
||||||
|
boolean ver() default true;
|
||||||
|
}
|
||||||
|
|
||||||
/** For {@link UnlockableContent}; filters all un-researchable content. */
|
/** For {@link UnlockableContent}; filters all un-researchable content. */
|
||||||
@Target(FIELD)
|
@Target(FIELD)
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
@@ -1341,4 +1367,5 @@ public class MapObjectives implements Iterable<MapObjective>, Eachable<MapObject
|
|||||||
@Target(FIELD)
|
@Target(FIELD)
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
public @interface TilePos{}
|
public @interface TilePos{}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,6 +147,8 @@ public class GlobalVars{
|
|||||||
put("@" + sensor.name(), sensor);
|
put("@" + sensor.name(), sensor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LStatement.nameToAlign.each((name, align) -> put("@" + name, align));
|
||||||
|
|
||||||
logicIdToContent = new UnlockableContent[ContentType.all.length][];
|
logicIdToContent = new UnlockableContent[ContentType.all.length][];
|
||||||
contentIdToLogicId = new int[ContentType.all.length][];
|
contentIdToLogicId = new int[ContentType.all.length][];
|
||||||
|
|
||||||
|
|||||||
@@ -897,7 +897,7 @@ public class LExecutor{
|
|||||||
int advance = (int)data.spaceXadvance, lineHeight = (int)data.lineHeight;
|
int advance = (int)data.spaceXadvance, lineHeight = (int)data.lineHeight;
|
||||||
|
|
||||||
int xOffset, yOffset;
|
int xOffset, yOffset;
|
||||||
int align = p1.id; //p1 is not a variable, it's a raw align value. what a massive hack
|
int align = p1.numi();
|
||||||
|
|
||||||
int maxWidth = 0, lines = 1, lineWidth = 0;
|
int maxWidth = 0, lines = 1, lineWidth = 0;
|
||||||
for(int i = 0; i < str.length(); i++){
|
for(int i = 0; i < str.length(); i++){
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ public enum LMarkerControl{
|
|||||||
flushText("fetch"),
|
flushText("fetch"),
|
||||||
fontSize("size"),
|
fontSize("size"),
|
||||||
textHeight("height"),
|
textHeight("height"),
|
||||||
|
textAlign("align"),
|
||||||
|
lineAlign("align"),
|
||||||
labelFlags("background", "outline"),
|
labelFlags("background", "outline"),
|
||||||
texture("printFlush", "name"),
|
texture("printFlush", "name"),
|
||||||
textureSize("width", "height"),
|
textureSize("width", "height"),
|
||||||
|
|||||||
@@ -23,6 +23,25 @@ import static mindustry.logic.LCanvas.*;
|
|||||||
* A statement is an intermediate representation of an instruction, to be used mostly in UI.
|
* A statement is an intermediate representation of an instruction, to be used mostly in UI.
|
||||||
* Contains all relevant variable information. */
|
* Contains all relevant variable information. */
|
||||||
public abstract class LStatement{
|
public abstract class LStatement{
|
||||||
|
|
||||||
|
private static final String[] aligns = {"topLeft", "top", "topRight", "left", "center", "right", "bottomLeft", "bottom", "bottomRight"};
|
||||||
|
public static final ObjectMap<String, Integer> nameToAlign = ObjectMap.of(
|
||||||
|
"center", Align.center,
|
||||||
|
"top", Align.top,
|
||||||
|
"bottom", Align.bottom,
|
||||||
|
"left", Align.left,
|
||||||
|
"right", Align.right,
|
||||||
|
"topLeft", Align.topLeft,
|
||||||
|
"topRight", Align.topRight,
|
||||||
|
"bottomLeft", Align.bottomLeft,
|
||||||
|
"bottomRight", Align.bottomRight
|
||||||
|
);
|
||||||
|
public static final IntMap<String> alignToName = new IntMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
nameToAlign.each((k, v) -> alignToName.put(v, k));
|
||||||
|
}
|
||||||
|
|
||||||
public transient @Nullable StatementElem elem;
|
public transient @Nullable StatementElem elem;
|
||||||
|
|
||||||
public abstract void build(Table table);
|
public abstract void build(Table table);
|
||||||
@@ -175,7 +194,36 @@ public abstract class LStatement{
|
|||||||
showSelect(b, values, current, getter, 4, c -> {});
|
showSelect(b, values, current, getter, 4, c -> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void showSelectTable(Button b, Cons2<Table, Runnable> hideCons){
|
protected void fieldAlignSelect(Table t, Prov<String> get, Cons<String> set, boolean hor, boolean ver) {
|
||||||
|
t.button(b -> {
|
||||||
|
b.image(Icon.pencilSmall);
|
||||||
|
b.clicked(() -> {
|
||||||
|
var current = get.get();
|
||||||
|
showAlignSelect(b, current.startsWith("@") ? nameToAlign.get(current.substring(1), -1) : -1, align -> set.get("@" + alignToName.get(align)), hor, ver);
|
||||||
|
});
|
||||||
|
}, Styles.logict, () -> {}).size(40f).color(t.color).left().padLeft(-10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showAlignSelect(Button b, int current, Intc setter, boolean hor, boolean ver) {
|
||||||
|
showSelectTable(b, (t, hide) -> {
|
||||||
|
t.defaults().size(150f, 40f);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for(String align : aligns){
|
||||||
|
int val = nameToAlign.get(align);
|
||||||
|
if(!hor && !Align.isCenterHorizontal(val)) continue;
|
||||||
|
if(!ver && !Align.isCenterVertical(val)) continue;
|
||||||
|
t.button(align, Styles.logicTogglet, () -> {
|
||||||
|
setter.get(val);
|
||||||
|
hide.run();
|
||||||
|
}).checked(current == nameToAlign.get(align)).grow();
|
||||||
|
|
||||||
|
if (++i % 3 == 0) t.row();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void showSelectTable(Button b, Cons2<Table, Runnable> hideCons){
|
||||||
Table t = new Table(Tex.paneSolid){
|
Table t = new Table(Tex.paneSolid){
|
||||||
@Override
|
@Override
|
||||||
public float getPrefHeight(){
|
public float getPrefHeight(){
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import arc.graphics.*;
|
|||||||
import arc.scene.style.*;
|
import arc.scene.style.*;
|
||||||
import arc.scene.ui.*;
|
import arc.scene.ui.*;
|
||||||
import arc.scene.ui.layout.*;
|
import arc.scene.ui.layout.*;
|
||||||
import arc.struct.*;
|
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.*;
|
import mindustry.*;
|
||||||
import mindustry.annotations.Annotations.*;
|
import mindustry.annotations.Annotations.*;
|
||||||
@@ -123,19 +122,6 @@ public class LStatements{
|
|||||||
|
|
||||||
@RegisterStatement("draw")
|
@RegisterStatement("draw")
|
||||||
public static class DrawStatement extends LStatement{
|
public static class DrawStatement extends LStatement{
|
||||||
static final String[] aligns = {"center", "top", "bottom", "left", "right", "topLeft", "topRight", "bottomLeft", "bottomRight"};
|
|
||||||
//yes, boxing Integer is gross but this is easier to construct and Integers <128 don't allocate anyway
|
|
||||||
static final ObjectMap<String, Integer> nameToAlign = ObjectMap.of(
|
|
||||||
"center", Align.center,
|
|
||||||
"top", Align.top,
|
|
||||||
"bottom", Align.bottom,
|
|
||||||
"left", Align.left,
|
|
||||||
"right", Align.right,
|
|
||||||
"topLeft", Align.topLeft,
|
|
||||||
"topRight", Align.topRight,
|
|
||||||
"bottomLeft", Align.bottomLeft,
|
|
||||||
"bottomRight", Align.bottomRight
|
|
||||||
);
|
|
||||||
|
|
||||||
public GraphicsType type = GraphicsType.clear;
|
public GraphicsType type = GraphicsType.clear;
|
||||||
public String x = "0", y = "0", p1 = "0", p2 = "0", p3 = "0", p4 = "0";
|
public String x = "0", y = "0", p1 = "0", p2 = "0", p3 = "0", p4 = "0";
|
||||||
@@ -165,7 +151,7 @@ public class LStatements{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(type == GraphicsType.print){
|
if(type == GraphicsType.print){
|
||||||
p1 = "bottomLeft";
|
p1 = "@bottomLeft";
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuild(table);
|
rebuild(table);
|
||||||
@@ -253,13 +239,11 @@ public class LStatements{
|
|||||||
row(s);
|
row(s);
|
||||||
|
|
||||||
s.add("align ");
|
s.add("align ");
|
||||||
|
fields(s, "align", p1, v -> p1 = v);
|
||||||
s.button(b -> {
|
fieldAlignSelect(s, () -> p1, v -> {
|
||||||
b.label(() -> nameToAlign.containsKey(p1) ? p1 : "bottomLeft");
|
p1 = v;
|
||||||
b.clicked(() -> showSelect(b, aligns, p1, t -> {
|
rebuild(table);
|
||||||
p1 = t;
|
}, true, true);
|
||||||
}, 2, cell -> cell.size(165, 50)));
|
|
||||||
}, Styles.logict, () -> {}).size(165, 40).color(s.color).left().padLeft(2);
|
|
||||||
}
|
}
|
||||||
case translate, scale -> {
|
case translate, scale -> {
|
||||||
fields(s, "x", x, v -> x = v);
|
fields(s, "x", x, v -> x = v);
|
||||||
@@ -278,12 +262,15 @@ public class LStatements{
|
|||||||
if(type == GraphicsType.color && p2.equals("0")){
|
if(type == GraphicsType.color && p2.equals("0")){
|
||||||
p2 = "255";
|
p2 = "255";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(type == GraphicsType.print && nameToAlign.get(p1) != null){
|
||||||
|
p1 = "@" + p1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LInstruction build(LAssembler builder){
|
public LInstruction build(LAssembler builder){
|
||||||
return new DrawI((byte)type.ordinal(), builder.var(x), builder.var(y),
|
return new DrawI((byte)type.ordinal(), builder.var(x), builder.var(y), builder.var(p1), builder.var(p2), builder.var(p3), builder.var(p4));
|
||||||
type == GraphicsType.print ? new LVar(p1, nameToAlign.get(p1, Align.bottomLeft), true) : builder.var(p1), builder.var(p2), builder.var(p3), builder.var(p4));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -2399,6 +2386,11 @@ public class LStatements{
|
|||||||
}).width(240f).left();
|
}).width(240f).left();
|
||||||
}));
|
}));
|
||||||
}, Styles.logict, () -> {}).size(40f).padLeft(-11).color(table.color);
|
}, Styles.logict, () -> {}).size(40f).padLeft(-11).color(table.color);
|
||||||
|
}else if(type == LMarkerControl.textAlign || type == LMarkerControl.lineAlign){
|
||||||
|
fieldAlignSelect(t, () -> p1, v -> {
|
||||||
|
p1 = v;
|
||||||
|
rebuild(table);
|
||||||
|
}, true, type != LMarkerControl.lineAlign);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user