multiline labels
This commit is contained in:
parent
fcc5d11814
commit
d189bdc107
@ -20,7 +20,8 @@ namespace gui {
|
||||
using onnumberchange = std::function<void(GUI*, double)>;
|
||||
|
||||
enum class Align {
|
||||
left, center, right
|
||||
left, center, right,
|
||||
top=left, bottom=right,
|
||||
};
|
||||
|
||||
class UINode {
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
#include "controls.h"
|
||||
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "../../window/Events.h"
|
||||
@ -30,9 +32,16 @@ Label::Label(std::wstring text, std::string fontName)
|
||||
|
||||
void Label::setText(std::wstring text) {
|
||||
this->text = text;
|
||||
lines = 1;
|
||||
for (size_t i = 0; i < text.length(); i++) {
|
||||
if (text[i] == L'\n') {
|
||||
lines++;
|
||||
}
|
||||
}
|
||||
lines = std::max(lines, 1U);
|
||||
}
|
||||
|
||||
std::wstring Label::getText() const {
|
||||
const std::wstring& Label::getText() const {
|
||||
return text;
|
||||
}
|
||||
|
||||
@ -44,18 +53,45 @@ const std::string& Label::getFontName() const {
|
||||
return fontName;
|
||||
}
|
||||
|
||||
void Label::setVerticalAlign(Align align) {
|
||||
this->valign = align;
|
||||
}
|
||||
|
||||
Align Label::getVerticalAlign() const {
|
||||
return valign;
|
||||
}
|
||||
|
||||
float Label::getLineInterval() const {
|
||||
return lineInterval;
|
||||
}
|
||||
|
||||
void Label::setLineInterval(float interval) {
|
||||
lineInterval = interval;
|
||||
}
|
||||
|
||||
int Label::getTextYOffset() const {
|
||||
return textYOffset;
|
||||
}
|
||||
|
||||
int Label::getLineYOffset(uint line) const {
|
||||
return line * totalLineHeight + textYOffset;
|
||||
}
|
||||
|
||||
void Label::draw(const GfxContext* pctx, Assets* assets) {
|
||||
if (supplier) {
|
||||
setText(supplier());
|
||||
}
|
||||
|
||||
auto batch = pctx->getBatch2D();
|
||||
auto font = assets->getFont(fontName);
|
||||
|
||||
batch->color = getColor();
|
||||
Font* font = assets->getFont(fontName);
|
||||
|
||||
uint lineHeight = font->getLineHeight();
|
||||
glm::vec2 size = getSize();
|
||||
glm::vec2 newsize (
|
||||
font->calcWidth(text),
|
||||
font->getLineHeight()+font->getYOffset()
|
||||
(lines == 1 ? lineHeight : lineHeight*lineInterval)*lines + font->getYOffset()
|
||||
);
|
||||
|
||||
glm::vec2 coord = calcCoord();
|
||||
@ -69,14 +105,48 @@ void Label::draw(const GfxContext* pctx, Assets* assets) {
|
||||
coord.x += size.x-newsize.x;
|
||||
break;
|
||||
}
|
||||
coord.y += (size.y-newsize.y)*0.5f;
|
||||
font->draw(batch, text, coord.x, coord.y);
|
||||
switch (valign) {
|
||||
case Align::top:
|
||||
break;
|
||||
case Align::center:
|
||||
coord.y += (size.y-newsize.y)*0.5f;
|
||||
break;
|
||||
case Align::bottom:
|
||||
coord.y += size.y-newsize.y;
|
||||
break;
|
||||
}
|
||||
textYOffset = coord.y;
|
||||
totalLineHeight = lineHeight * lineInterval;
|
||||
|
||||
if (multiline) {
|
||||
size_t offset = 0;
|
||||
for (uint i = 0; i < lines; i++) {
|
||||
std::wstring_view view(text.c_str()+offset, text.length()-offset);
|
||||
size_t end = view.find(L'\n');
|
||||
if (end != std::wstring::npos) {
|
||||
view = std::wstring_view(text.c_str()+offset, end);
|
||||
offset += end + 1;
|
||||
}
|
||||
font->draw(batch, view, coord.x, coord.y + i * totalLineHeight, FontStyle::none);
|
||||
}
|
||||
} else {
|
||||
font->draw(batch, text, coord.x, coord.y, FontStyle::none);
|
||||
}
|
||||
}
|
||||
|
||||
void Label::textSupplier(wstringsupplier supplier) {
|
||||
this->supplier = supplier;
|
||||
}
|
||||
|
||||
|
||||
void Label::setMultiline(bool multiline) {
|
||||
this->multiline = multiline;
|
||||
}
|
||||
|
||||
bool Label::isMultiline() const {
|
||||
return multiline;
|
||||
}
|
||||
|
||||
// ================================= Image ====================================
|
||||
Image::Image(std::string texture, glm::vec2 size) : UINode(glm::vec2(), size), texture(texture) {
|
||||
setInteractive(false);
|
||||
@ -264,7 +334,7 @@ void TextBox::draw(const GfxContext* pctx, Assets* assets) {
|
||||
if (!isFocused())
|
||||
return;
|
||||
|
||||
const int yoffset = 2;
|
||||
const int yoffset = 0;
|
||||
const int lineHeight = font->getLineHeight();
|
||||
glm::vec2 lcoord = label->calcCoord();
|
||||
auto batch = pctx->getBatch2D();
|
||||
@ -390,6 +460,16 @@ bool TextBox::isValid() const {
|
||||
return valid;
|
||||
}
|
||||
|
||||
void TextBox::setMultiline(bool multiline) {
|
||||
this->multiline = multiline;
|
||||
label->setMultiline(multiline);
|
||||
label->setVerticalAlign(multiline ? Align::top : Align::center);
|
||||
}
|
||||
|
||||
bool TextBox::isMultiline() const {
|
||||
return multiline;
|
||||
}
|
||||
|
||||
void TextBox::setOnEditStart(runnable oneditstart) {
|
||||
onEditStart = oneditstart;
|
||||
}
|
||||
|
||||
@ -24,19 +24,40 @@ namespace gui {
|
||||
std::wstring text;
|
||||
std::string fontName;
|
||||
wstringsupplier supplier = nullptr;
|
||||
uint lines = 1;
|
||||
float lineInterval = 1.5f;
|
||||
Align valign = Align::center;
|
||||
|
||||
bool multiline = false;
|
||||
|
||||
// runtime values
|
||||
int textYOffset = 0;
|
||||
int totalLineHeight = 1;
|
||||
public:
|
||||
Label(std::string text, std::string fontName="normal");
|
||||
Label(std::wstring text, std::string fontName="normal");
|
||||
|
||||
virtual void setText(std::wstring text);
|
||||
std::wstring getText() const;
|
||||
const std::wstring& getText() const;
|
||||
|
||||
virtual void setFontName(std::string name);
|
||||
const std::string& getFontName() const;
|
||||
virtual const std::string& getFontName() const;
|
||||
|
||||
virtual void setVerticalAlign(Align align);
|
||||
virtual Align getVerticalAlign() const;
|
||||
|
||||
virtual float getLineInterval() const;
|
||||
virtual void setLineInterval(float interval);
|
||||
|
||||
virtual int getTextYOffset() const;
|
||||
virtual int getLineYOffset(uint line) const;
|
||||
|
||||
virtual void draw(const GfxContext* pctx, Assets* assets) override;
|
||||
|
||||
virtual void textSupplier(wstringsupplier supplier);
|
||||
|
||||
virtual void setMultiline(bool multiline);
|
||||
virtual bool isMultiline() const;
|
||||
};
|
||||
|
||||
class Image : public UINode {
|
||||
@ -121,6 +142,8 @@ namespace gui {
|
||||
size_t selectionEnd = 0;
|
||||
size_t selectionOrigin = 0;
|
||||
|
||||
bool multiline = false;
|
||||
|
||||
size_t normalizeIndex(int index);
|
||||
|
||||
int calcIndexAt(int x) const;
|
||||
@ -134,16 +157,9 @@ namespace gui {
|
||||
TextBox(std::wstring placeholder,
|
||||
glm::vec4 padding=glm::vec4(4.0f));
|
||||
|
||||
virtual std::shared_ptr<UINode> getAt(glm::vec2 pos, std::shared_ptr<UINode> self) override;
|
||||
|
||||
virtual void draw(const GfxContext* pctx, Assets* assets) override;
|
||||
virtual void drawBackground(const GfxContext* pctx, Assets* assets) override;
|
||||
virtual void typed(unsigned int codepoint) override;
|
||||
virtual void keyPressed(keycode key) override;
|
||||
virtual void setTextSupplier(wstringsupplier supplier);
|
||||
virtual void setTextConsumer(wstringconsumer consumer);
|
||||
virtual void setTextValidator(wstringchecker validator);
|
||||
virtual bool isFocuskeeper() const override {return true;}
|
||||
virtual void setFocusedColor(glm::vec4 color);
|
||||
virtual glm::vec4 getFocusedColor() const;
|
||||
virtual void setErrorColor(glm::vec4 color);
|
||||
@ -161,11 +177,22 @@ namespace gui {
|
||||
virtual bool validate();
|
||||
virtual void setValid(bool valid);
|
||||
virtual bool isValid() const;
|
||||
|
||||
// multiline mode is in development
|
||||
virtual void setMultiline(bool multiline);
|
||||
virtual bool isMultiline() const;
|
||||
virtual void setOnEditStart(runnable oneditstart);
|
||||
|
||||
virtual void focus(GUI*) override;
|
||||
virtual void refresh() override;
|
||||
virtual void click(GUI*, int, int) override;
|
||||
virtual void mouseMove(GUI*, int x, int y) override;
|
||||
virtual bool isFocuskeeper() const override {return true;}
|
||||
virtual void draw(const GfxContext* pctx, Assets* assets) override;
|
||||
virtual void drawBackground(const GfxContext* pctx, Assets* assets) override;
|
||||
virtual void typed(unsigned int codepoint) override;
|
||||
virtual void keyPressed(keycode key) override;
|
||||
virtual std::shared_ptr<UINode> getAt(glm::vec2 pos, std::shared_ptr<UINode> self) override;
|
||||
};
|
||||
|
||||
class InputBindBox : public Panel {
|
||||
|
||||
@ -17,6 +17,8 @@ static Align align_from_string(const std::string& str, Align def) {
|
||||
if (str == "left") return Align::left;
|
||||
if (str == "center") return Align::center;
|
||||
if (str == "right") return Align::right;
|
||||
if (str == "top") return Align::top;
|
||||
if (str == "bottom") return Align::bottom;
|
||||
return def;
|
||||
}
|
||||
|
||||
@ -138,6 +140,11 @@ static std::shared_ptr<UINode> readLabel(UiXmlReader& reader, xml::xmlelement el
|
||||
std::wstring text = readAndProcessInnerText(element);
|
||||
auto label = std::make_shared<Label>(text);
|
||||
_readUINode(reader, element, *label);
|
||||
if (element->has("valign")) {
|
||||
label->setVerticalAlign(
|
||||
align_from_string(element->attr("valign").getText(), label->getVerticalAlign())
|
||||
);
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
@ -163,7 +170,7 @@ static std::shared_ptr<UINode> readButton(UiXmlReader& reader, xml::xmlelement e
|
||||
auto callback = scripting::create_runnable(
|
||||
reader.getEnvironment().getId(),
|
||||
element->attr("onclick").getText(),
|
||||
reader.getFilename()+".lua"
|
||||
reader.getFilename()
|
||||
);
|
||||
button->listenAction([callback](GUI*) {
|
||||
callback();
|
||||
@ -188,7 +195,7 @@ static std::shared_ptr<UINode> readCheckBox(UiXmlReader& reader, xml::xmlelement
|
||||
auto consumer = scripting::create_bool_consumer(
|
||||
reader.getEnvironment().getId(),
|
||||
element->attr("consumer").getText(),
|
||||
reader.getFilename()+".lua"
|
||||
reader.getFilename()
|
||||
);
|
||||
checkbox->setConsumer(consumer);
|
||||
}
|
||||
@ -197,7 +204,7 @@ static std::shared_ptr<UINode> readCheckBox(UiXmlReader& reader, xml::xmlelement
|
||||
auto supplier = scripting::create_bool_supplier(
|
||||
reader.getEnvironment().getId(),
|
||||
element->attr("supplier").getText(),
|
||||
reader.getFilename()+".lua"
|
||||
reader.getFilename()
|
||||
);
|
||||
checkbox->setSupplier(supplier);
|
||||
}
|
||||
@ -210,12 +217,16 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement
|
||||
auto textbox = std::make_shared<TextBox>(placeholder, glm::vec4(0.0f));
|
||||
_readPanel(reader, element, *textbox);
|
||||
textbox->setText(text);
|
||||
|
||||
if (element->has("multiline")) {
|
||||
textbox->setMultiline(element->attr("multiline").asBool());
|
||||
}
|
||||
|
||||
if (element->has("consumer")) {
|
||||
auto consumer = scripting::create_wstring_consumer(
|
||||
reader.getEnvironment().getId(),
|
||||
element->attr("consumer").getText(),
|
||||
reader.getFilename()+".lua"
|
||||
reader.getFilename()
|
||||
);
|
||||
textbox->setTextConsumer(consumer);
|
||||
}
|
||||
@ -224,7 +235,7 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement
|
||||
auto supplier = scripting::create_wstring_supplier(
|
||||
reader.getEnvironment().getId(),
|
||||
element->attr("consumer").getText(),
|
||||
reader.getFilename()+".lua"
|
||||
reader.getFilename()
|
||||
);
|
||||
textbox->setTextSupplier(supplier);
|
||||
}
|
||||
@ -238,7 +249,7 @@ static std::shared_ptr<UINode> readTextBox(UiXmlReader& reader, xml::xmlelement
|
||||
auto validator = scripting::create_wstring_validator(
|
||||
reader.getEnvironment().getId(),
|
||||
element->attr("validator").getText(),
|
||||
reader.getFilename()+".lua"
|
||||
reader.getFilename()
|
||||
);
|
||||
textbox->setTextValidator(validator);
|
||||
}
|
||||
@ -265,7 +276,7 @@ static std::shared_ptr<UINode> readTrackBar(UiXmlReader& reader, xml::xmlelement
|
||||
auto consumer = scripting::create_number_consumer(
|
||||
reader.getEnvironment().getId(),
|
||||
element->attr("consumer").getText(),
|
||||
reader.getFilename()+".lua"
|
||||
reader.getFilename()
|
||||
);
|
||||
bar->setConsumer(consumer);
|
||||
}
|
||||
@ -273,7 +284,7 @@ static std::shared_ptr<UINode> readTrackBar(UiXmlReader& reader, xml::xmlelement
|
||||
auto supplier = scripting::create_number_supplier(
|
||||
reader.getEnvironment().getId(),
|
||||
element->attr("supplier").getText(),
|
||||
reader.getFilename()+".lua"
|
||||
reader.getFilename()
|
||||
);
|
||||
bar->setSupplier(supplier);
|
||||
}
|
||||
|
||||
@ -2,7 +2,9 @@
|
||||
#include "Texture.h"
|
||||
#include "Batch2D.h"
|
||||
|
||||
using glm::vec4;
|
||||
inline constexpr uint GLYPH_SIZE = 16;
|
||||
inline constexpr uint MAX_CODEPAGES = 10000; // idk ho many codepages unicode has
|
||||
inline constexpr glm::vec4 SHADOW_TINT(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
Font::Font(std::vector<std::unique_ptr<Texture>> pages, int lineHeight, int yoffset)
|
||||
: lineHeight(lineHeight), yoffset(yoffset), pages(std::move(pages)) {
|
||||
@ -16,71 +18,78 @@ int Font::getYOffset() const {
|
||||
}
|
||||
|
||||
int Font::getLineHeight() const {
|
||||
return lineHeight;
|
||||
return lineHeight;
|
||||
}
|
||||
|
||||
bool Font::isPrintableChar(int c) {
|
||||
switch (c){
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\f':
|
||||
case '\r':
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
bool Font::isPrintableChar(uint codepoint) const {
|
||||
switch (codepoint){
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\f':
|
||||
case '\r':
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const int RES = 16;
|
||||
|
||||
int Font::calcWidth(std::wstring text, size_t length) {
|
||||
return std::min(text.length(), length) * 8;
|
||||
return std::min(text.length(), length) * 8;
|
||||
}
|
||||
|
||||
void Font::draw(Batch2D* batch, std::wstring text, int x, int y) {
|
||||
draw(batch, text, x, y, STYLE_NONE);
|
||||
draw(batch, text, x, y, FontStyle::none);
|
||||
}
|
||||
|
||||
void Font::draw(Batch2D* batch, std::wstring text, int x, int y, int style) {
|
||||
int page = 0;
|
||||
int next = 10000;
|
||||
int init_x = x;
|
||||
do {
|
||||
for (unsigned c : text){
|
||||
if (isPrintableChar(c)){
|
||||
int charpage = c >> 8;
|
||||
if (charpage == page){
|
||||
Texture* texture = pages[charpage].get();
|
||||
if (texture == nullptr){
|
||||
texture = pages[0].get();
|
||||
}
|
||||
batch->texture(texture);
|
||||
|
||||
switch (style){
|
||||
case STYLE_SHADOW:
|
||||
batch->sprite(x+1, y+1, RES, RES, 16, c, vec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
break;
|
||||
case STYLE_OUTLINE:
|
||||
for (int oy = -1; oy <= 1; oy++){
|
||||
for (int ox = -1; ox <= 1; ox++){
|
||||
if (ox || oy)
|
||||
batch->sprite(x+ox, y+oy, RES, RES, 16, c, vec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
batch->sprite(x, y, RES, RES, 16, c, batch->color);
|
||||
}
|
||||
else if (charpage > page && charpage < next){
|
||||
next = charpage;
|
||||
}
|
||||
}
|
||||
x += 8;//getGlyphWidth(c);
|
||||
}
|
||||
page = next;
|
||||
next = 10000;
|
||||
x = init_x;
|
||||
} while (page < 10000);
|
||||
static inline void drawGlyph(Batch2D* batch, int x, int y, uint c, FontStyle style) {
|
||||
switch (style){
|
||||
case FontStyle::shadow:
|
||||
batch->sprite(x+1, y+1, GLYPH_SIZE, GLYPH_SIZE, 16, c, SHADOW_TINT);
|
||||
break;
|
||||
case FontStyle::outline:
|
||||
for (int oy = -1; oy <= 1; oy++){
|
||||
for (int ox = -1; ox <= 1; ox++){
|
||||
if (ox || oy) {
|
||||
batch->sprite(x+ox, y+oy, GLYPH_SIZE, GLYPH_SIZE, 16, c, SHADOW_TINT);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
batch->sprite(x, y, GLYPH_SIZE, GLYPH_SIZE, 16, c, batch->color);
|
||||
}
|
||||
|
||||
void Font::draw(Batch2D* batch, std::wstring text, int x, int y, FontStyle style) {
|
||||
draw(batch, std::wstring_view(text.c_str(), text.length()), x, y, style);
|
||||
}
|
||||
|
||||
void Font::draw(Batch2D* batch, std::wstring_view text, int x, int y, FontStyle style) {
|
||||
uint page = 0;
|
||||
uint next = MAX_CODEPAGES;
|
||||
int init_x = x;
|
||||
do {
|
||||
for (uint c : text){
|
||||
if (!isPrintableChar(c)) {
|
||||
x += 8;
|
||||
continue;
|
||||
}
|
||||
int charpage = c >> 8;
|
||||
if (charpage == page){
|
||||
Texture* texture = pages[charpage].get();
|
||||
if (texture == nullptr){
|
||||
texture = pages[0].get();
|
||||
}
|
||||
batch->texture(texture);
|
||||
drawGlyph(batch, x, y, c, style);
|
||||
}
|
||||
else if (charpage > page && charpage < next){
|
||||
next = charpage;
|
||||
}
|
||||
x += 8;//getGlyphWidth(c);
|
||||
}
|
||||
page = next;
|
||||
next = MAX_CODEPAGES;
|
||||
x = init_x;
|
||||
} while (page < MAX_CODEPAGES);
|
||||
}
|
||||
|
||||
@ -9,25 +9,27 @@
|
||||
class Texture;
|
||||
class Batch2D;
|
||||
|
||||
const uint STYLE_NONE = 0;
|
||||
const uint STYLE_SHADOW = 1;
|
||||
const uint STYLE_OUTLINE = 2;
|
||||
enum class FontStyle {
|
||||
none,
|
||||
shadow,
|
||||
outline
|
||||
};
|
||||
|
||||
class Font {
|
||||
int lineHeight;
|
||||
int lineHeight;
|
||||
int yoffset;
|
||||
public:
|
||||
std::vector<std::unique_ptr<Texture>> pages;
|
||||
Font(std::vector<std::unique_ptr<Texture>> pages, int lineHeight, int yoffset);
|
||||
~Font();
|
||||
std::vector<std::unique_ptr<Texture>> pages;
|
||||
Font(std::vector<std::unique_ptr<Texture>> pages, int lineHeight, int yoffset);
|
||||
~Font();
|
||||
|
||||
int getLineHeight() const;
|
||||
int getLineHeight() const;
|
||||
int getYOffset() const;
|
||||
int calcWidth(std::wstring text, size_t length=-1);
|
||||
// int getGlyphWidth(char c);
|
||||
bool isPrintableChar(int c);
|
||||
void draw(Batch2D* batch, std::wstring text, int x, int y);
|
||||
void draw(Batch2D* batch, std::wstring text, int x, int y, int style);
|
||||
int calcWidth(std::wstring text, size_t length=-1);
|
||||
bool isPrintableChar(uint codepoint) const;
|
||||
void draw(Batch2D* batch, std::wstring text, int x, int y);
|
||||
void draw(Batch2D* batch, std::wstring text, int x, int y, FontStyle style);
|
||||
void draw(Batch2D* batch, std::wstring_view text, int x, int y, FontStyle style);
|
||||
};
|
||||
|
||||
#endif /* GRAPHICS_FONT_H_ */
|
||||
|
||||
@ -292,6 +292,10 @@ double util::parse_double(const std::string& str, size_t offset, size_t len) {
|
||||
return parse_double(str.substr(offset, len));
|
||||
}
|
||||
|
||||
/// @brief Split string by delimiter
|
||||
/// @param str source string
|
||||
/// @param delimiter split delimiter size
|
||||
/// @return vector of string parts, containing at least one string
|
||||
std::vector<std::string> util::split(const std::string& str, char delimiter) {
|
||||
std::vector<std::string> result;
|
||||
std::stringstream ss(str);
|
||||
@ -299,9 +303,16 @@ std::vector<std::string> util::split(const std::string& str, char delimiter) {
|
||||
while (std::getline(ss, tmp, delimiter)) {
|
||||
result.push_back(tmp);
|
||||
}
|
||||
if (result.empty()) {
|
||||
result.push_back("");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief Split wstring by delimiter
|
||||
/// @param str source string
|
||||
/// @param delimiter split delimiter size
|
||||
/// @return vector of string parts, containing at least one string
|
||||
std::vector<std::wstring> util::split(const std::wstring& str, char delimiter) {
|
||||
std::vector<std::wstring> result;
|
||||
std::wstringstream ss(str);
|
||||
@ -309,5 +320,8 @@ std::vector<std::wstring> util::split(const std::wstring& str, char delimiter) {
|
||||
while (std::getline(ss, tmp, static_cast<wchar_t>(delimiter))) {
|
||||
result.push_back(tmp);
|
||||
}
|
||||
if (result.empty()) {
|
||||
result.push_back(L"");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user