window_message: provant el redimensionat i les animacions
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
"[DEFINE_BUTTONS] TITLE": "define buttons title",
|
||||
"[DEFINE_BUTTONS] FIRE_LEFT": "Disparar cap a l'esquerra",
|
||||
"[DEFINE_BUTTONS] FIRE_UP": "Disparar cap amunt",
|
||||
"[DEFINE_BUTTONS] FIRE_UP": "Disparar cap amunt amunt amunt amunt",
|
||||
"[DEFINE_BUTTONS] FIRE_RIGHT": "Disparar cap a la dreta",
|
||||
"[DEFINE_BUTTONS] START": "Start",
|
||||
"[DEFINE_BUTTONS] SERVICE_MENU": "Menu de servei",
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
DefineButtons::DefineButtons()
|
||||
: input_(Input::get()) {
|
||||
|
||||
clearButtons();
|
||||
|
||||
auto gamepads = input_->getGamepads();
|
||||
@@ -24,18 +23,26 @@ DefineButtons::DefineButtons()
|
||||
}
|
||||
|
||||
// Crear la ventana de mensaje
|
||||
WindowMessage::Config config;
|
||||
config.bg_color = Color{20, 30, 50, 224}; // Fondo azul oscuro semi-transparente
|
||||
config.border_color = Color{100, 150, 200, 255}; // Borde azul claro
|
||||
config.title_color = Color{100, 150, 200, 255}; // Titulo azul claro
|
||||
config.text_color = Color{220, 220, 220, 255}; // Texto gris claro
|
||||
config.padding = 15.0f;
|
||||
config.line_spacing = 5.0f;
|
||||
config.title_separator_spacing = 15;
|
||||
config.min_width = 250.0f;
|
||||
config.text_safety_margin = 15.0f;
|
||||
config.min_width = 100;
|
||||
config.min_height = 32;
|
||||
|
||||
auto text_renderer = Resource::get()->getText("04b_25_flat");
|
||||
window_message_ = std::make_unique<WindowMessage>(
|
||||
text_renderer,
|
||||
Lang::getText("[DEFINE_BUTTONS] TITLE"),
|
||||
Color{20, 30, 50, 208}, // Fondo azul oscuro semi-transparente
|
||||
Color{100, 150, 200, 255}, // Borde azul claro
|
||||
Color{100, 150, 200, 255}, // Titulo azul claro
|
||||
Color{220, 220, 220, 255} // Texto gris claro
|
||||
config
|
||||
);
|
||||
|
||||
window_message_->setPadding(0.0f);
|
||||
window_message_->setLineSpacing(0.0f);
|
||||
window_message_->setPosition(param.game.game_area.center_x, param.game.game_area.center_y, WindowMessage::PositionMode::CENTERED);
|
||||
}
|
||||
|
||||
void DefineButtons::render() {
|
||||
@@ -76,7 +83,6 @@ auto DefineButtons::enable(Options::Gamepad *options_gamepad) -> bool {
|
||||
|
||||
if (window_message_) {
|
||||
window_message_->autoSize();
|
||||
window_message_->centerOnScreen();
|
||||
window_message_->show();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,18 +10,12 @@
|
||||
WindowMessage::WindowMessage(
|
||||
std::shared_ptr<Text> text_renderer,
|
||||
const std::string& title,
|
||||
const Color& bg_color,
|
||||
const Color& border_color,
|
||||
const Color& title_color,
|
||||
const Color& text_color)
|
||||
const Config& config)
|
||||
: text_renderer_(std::move(text_renderer)),
|
||||
config_(config),
|
||||
title_(title),
|
||||
bg_color_(bg_color),
|
||||
border_color_(border_color),
|
||||
title_color_(title_color),
|
||||
text_color_(text_color),
|
||||
title_style_(Text::CENTER | Text::COLOR, title_color, title_color, 0, -2),
|
||||
text_style_(Text::CENTER | Text::COLOR, text_color, text_color, 0, -2) {
|
||||
title_style_(Text::CENTER | Text::COLOR, config_.title_color, config_.title_color, 0, -2),
|
||||
text_style_(Text::CENTER | Text::COLOR, config_.text_color, config_.text_color, 0, -2) {
|
||||
}
|
||||
|
||||
void WindowMessage::render() {
|
||||
@@ -32,14 +26,16 @@ void WindowMessage::render() {
|
||||
SDL_Renderer* renderer = Screen::get()->getRenderer();
|
||||
|
||||
// Dibujar fondo con transparencia
|
||||
SDL_SetRenderDrawColor(renderer, bg_color_.r, bg_color_.g, bg_color_.b, bg_color_.a);
|
||||
SDL_SetRenderDrawColor(renderer, config_.bg_color.r, config_.bg_color.g,
|
||||
config_.bg_color.b, config_.bg_color.a);
|
||||
SDL_RenderFillRect(renderer, &rect_);
|
||||
|
||||
// Dibujar borde
|
||||
SDL_SetRenderDrawColor(renderer, border_color_.r, border_color_.g, border_color_.b, border_color_.a);
|
||||
SDL_SetRenderDrawColor(renderer, config_.border_color.r, config_.border_color.g,
|
||||
config_.border_color.b, config_.border_color.a);
|
||||
SDL_RenderRect(renderer, &rect_);
|
||||
|
||||
float current_y = rect_.y + padding_;
|
||||
float current_y = rect_.y + config_.padding;
|
||||
|
||||
// Dibujar título si existe
|
||||
if (!title_.empty()) {
|
||||
@@ -48,15 +44,16 @@ void WindowMessage::render() {
|
||||
current_y,
|
||||
title_,
|
||||
title_style_);
|
||||
current_y += text_renderer_->getCharacterSize() + line_spacing_ * 2;
|
||||
current_y += text_renderer_->getCharacterSize() + config_.title_separator_spacing;
|
||||
|
||||
// Línea separadora debajo del título
|
||||
SDL_SetRenderDrawColor(renderer, border_color_.r, border_color_.g, border_color_.b, border_color_.a);
|
||||
SDL_SetRenderDrawColor(renderer, config_.border_color.r, config_.border_color.g,
|
||||
config_.border_color.b, config_.border_color.a);
|
||||
SDL_RenderLine(renderer,
|
||||
rect_.x + padding_,
|
||||
current_y - line_spacing_,
|
||||
rect_.x + rect_.w - padding_,
|
||||
current_y - line_spacing_);
|
||||
rect_.x + config_.padding,
|
||||
current_y - config_.title_separator_spacing / 2.0f,
|
||||
rect_.x + rect_.w - config_.padding,
|
||||
current_y - config_.title_separator_spacing / 2.0f);
|
||||
}
|
||||
|
||||
// Dibujar textos
|
||||
@@ -66,15 +63,22 @@ void WindowMessage::render() {
|
||||
current_y,
|
||||
text,
|
||||
text_style_);
|
||||
current_y += text_renderer_->getCharacterSize() + line_spacing_;
|
||||
current_y += text_renderer_->getCharacterSize() + config_.line_spacing;
|
||||
}
|
||||
}
|
||||
|
||||
void WindowMessage::update() {
|
||||
// Por ahora no hay animaciones, pero se puede extender
|
||||
// Actualizar animación de redimensionado
|
||||
if (resize_animation_.active) {
|
||||
// Aquí necesitarías el delta_time del game loop
|
||||
// Por ahora usamos un valor fijo, pero idealmente se pasaría como parámetro
|
||||
float delta_time = 1.0f / 60.0f; // Asumiendo 60 FPS
|
||||
updateAnimation(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowMessage::show() {
|
||||
ensureTextFits();
|
||||
visible_ = true;
|
||||
}
|
||||
|
||||
@@ -84,58 +88,120 @@ void WindowMessage::hide() {
|
||||
|
||||
void WindowMessage::setTitle(const std::string& title) {
|
||||
title_ = title;
|
||||
triggerAutoResize();
|
||||
}
|
||||
|
||||
void WindowMessage::setText(const std::string& text) {
|
||||
texts_.clear();
|
||||
texts_.push_back(text);
|
||||
triggerAutoResize();
|
||||
}
|
||||
|
||||
void WindowMessage::setTexts(const std::vector<std::string>& texts) {
|
||||
texts_ = texts;
|
||||
triggerAutoResize();
|
||||
}
|
||||
|
||||
void WindowMessage::addText(const std::string& text) {
|
||||
texts_.push_back(text);
|
||||
triggerAutoResize();
|
||||
}
|
||||
|
||||
void WindowMessage::clearTexts() {
|
||||
texts_.clear();
|
||||
triggerAutoResize();
|
||||
}
|
||||
|
||||
void WindowMessage::setPosition(float x, float y) {
|
||||
rect_.x = x;
|
||||
rect_.y = y;
|
||||
void WindowMessage::setPosition(float x, float y, PositionMode mode) {
|
||||
anchor_x_ = x;
|
||||
anchor_y_ = y;
|
||||
position_mode_ = mode;
|
||||
updatePosition();
|
||||
}
|
||||
|
||||
void WindowMessage::setSize(float width, float height) {
|
||||
rect_.w = width;
|
||||
rect_.h = height;
|
||||
updatePosition(); // Reposicionar después de cambiar el tamaño
|
||||
}
|
||||
|
||||
void WindowMessage::centerOnScreen() {
|
||||
rect_.x = (param.game.width - rect_.w) / 2.0f;
|
||||
rect_.y = (param.game.height - rect_.h) / 2.0f;
|
||||
setPosition(getScreenWidth() / 2.0f, getScreenHeight() / 2.0f, PositionMode::CENTERED);
|
||||
}
|
||||
|
||||
void WindowMessage::autoSize() {
|
||||
if (resize_animation_.active) {
|
||||
resize_animation_.stop(); // Detener animación anterior
|
||||
}
|
||||
|
||||
float old_width = rect_.w;
|
||||
float old_height = rect_.h;
|
||||
|
||||
calculateAutoSize();
|
||||
|
||||
// Solo animar si hay cambio en el tamaño y la ventana está visible
|
||||
if (visible_ && (old_width != rect_.w || old_height != rect_.h)) {
|
||||
resize_animation_.start(old_width, old_height, rect_.w, rect_.h);
|
||||
// Restaurar el tamaño anterior para que la animación funcione
|
||||
rect_.w = old_width;
|
||||
rect_.h = old_height;
|
||||
} else {
|
||||
updatePosition(); // Reposicionar después de ajustar el tamaño
|
||||
}
|
||||
}
|
||||
|
||||
void WindowMessage::updateStyles() {
|
||||
title_style_ = Text::Style(Text::CENTER | Text::COLOR, config_.title_color,
|
||||
config_.title_color, 0, -2);
|
||||
text_style_ = Text::Style(Text::CENTER | Text::COLOR, config_.text_color,
|
||||
config_.text_color, 0, -2);
|
||||
}
|
||||
|
||||
void WindowMessage::updatePosition() {
|
||||
switch (position_mode_) {
|
||||
case PositionMode::CENTERED:
|
||||
rect_.x = anchor_x_ - rect_.w / 2.0f;
|
||||
rect_.y = anchor_y_ - rect_.h / 2.0f;
|
||||
break;
|
||||
case PositionMode::FIXED:
|
||||
rect_.x = anchor_x_;
|
||||
rect_.y = anchor_y_;
|
||||
break;
|
||||
}
|
||||
|
||||
// Asegurar que la ventana esté dentro de los límites de la pantalla
|
||||
rect_.x = std::max(0.0f, std::min(rect_.x, getScreenWidth() - rect_.w));
|
||||
rect_.y = std::max(0.0f, std::min(rect_.y, getScreenHeight() - rect_.h));
|
||||
}
|
||||
|
||||
void WindowMessage::ensureTextFits() {
|
||||
float required_width = calculateContentWidth() + (config_.padding * 2) + config_.text_safety_margin;
|
||||
float required_height = calculateContentHeight() + (config_.padding * 2) + config_.text_safety_margin;
|
||||
|
||||
// Verificar si el tamaño actual es suficiente
|
||||
if (rect_.w < required_width || rect_.h < required_height) {
|
||||
autoSize(); // Recalcular tamaño automáticamente
|
||||
}
|
||||
}
|
||||
|
||||
void WindowMessage::calculateAutoSize() {
|
||||
float content_width = calculateContentWidth();
|
||||
float content_height = calculateContentHeight();
|
||||
|
||||
rect_.w = content_width + (padding_ * 2);
|
||||
rect_.h = content_height + (padding_ * 2);
|
||||
// Calcular dimensiones con padding y margen de seguridad
|
||||
rect_.w = content_width + (config_.padding * 2) + config_.text_safety_margin;
|
||||
rect_.h = content_height + (config_.padding * 2);
|
||||
|
||||
// Aplicar límites mínimos y máximos
|
||||
rect_.w = std::max(rect_.w, 200.0f);
|
||||
rect_.h = std::max(rect_.h, 100.0f);
|
||||
// Aplicar límites mínimos
|
||||
rect_.w = std::max(rect_.w, config_.min_width);
|
||||
rect_.h = std::max(rect_.h, config_.min_height);
|
||||
|
||||
// No exceder el 80% de la pantalla
|
||||
rect_.w = std::min(rect_.w, param.game.width * 0.8f);
|
||||
rect_.h = std::min(rect_.h, param.game.height * 0.8f);
|
||||
// Aplicar límites máximos basados en el tamaño de pantalla
|
||||
float max_width = getScreenWidth() * config_.max_width_ratio;
|
||||
float max_height = getScreenHeight() * config_.max_height_ratio;
|
||||
|
||||
rect_.w = std::min(rect_.w, max_width);
|
||||
rect_.h = std::min(rect_.h, max_height);
|
||||
}
|
||||
|
||||
auto WindowMessage::calculateContentHeight() const -> float {
|
||||
@@ -143,20 +209,22 @@ auto WindowMessage::calculateContentHeight() const -> float {
|
||||
|
||||
// Altura del título
|
||||
if (!title_.empty()) {
|
||||
height += text_renderer_->getCharacterSize() + line_spacing_ * 2; // Espacio extra para separador
|
||||
height += text_renderer_->getCharacterSize() + config_.title_separator_spacing;
|
||||
}
|
||||
|
||||
// Altura de los textos
|
||||
if (!texts_.empty()) {
|
||||
height += (texts_.size() * text_renderer_->getCharacterSize());
|
||||
height += ((texts_.size() - 1) * line_spacing_); // Espaciado entre líneas
|
||||
if (texts_.size() > 1) {
|
||||
height += ((texts_.size() - 1) * config_.line_spacing);
|
||||
}
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
auto WindowMessage::calculateContentWidth() const -> float {
|
||||
float max_width = 200.0f; // Ancho mínimo
|
||||
float max_width = config_.min_width - (config_.padding * 2); // Ancho mínimo sin padding
|
||||
|
||||
// Ancho del título
|
||||
if (!title_.empty()) {
|
||||
@@ -172,3 +240,48 @@ auto WindowMessage::calculateContentWidth() const -> float {
|
||||
|
||||
return max_width;
|
||||
}
|
||||
|
||||
auto WindowMessage::getScreenWidth() const -> float {
|
||||
return static_cast<float>(param.game.width);
|
||||
}
|
||||
|
||||
auto WindowMessage::getScreenHeight() const -> float {
|
||||
return static_cast<float>(param.game.height);
|
||||
}
|
||||
|
||||
void WindowMessage::triggerAutoResize() {
|
||||
if (auto_resize_enabled_) {
|
||||
autoSize();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowMessage::updateAnimation(float delta_time) {
|
||||
if (!resize_animation_.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
resize_animation_.elapsed += delta_time;
|
||||
|
||||
if (resize_animation_.isFinished()) {
|
||||
// Animación terminada
|
||||
rect_.w = resize_animation_.target_width;
|
||||
rect_.h = resize_animation_.target_height;
|
||||
resize_animation_.stop();
|
||||
updatePosition();
|
||||
} else {
|
||||
// Interpolar el tamaño
|
||||
float progress = easeOut(resize_animation_.getProgress());
|
||||
|
||||
rect_.w = resize_animation_.start_width +
|
||||
(resize_animation_.target_width - resize_animation_.start_width) * progress;
|
||||
rect_.h = resize_animation_.start_height +
|
||||
(resize_animation_.target_height - resize_animation_.start_height) * progress;
|
||||
|
||||
updatePosition(); // Mantener la posición centrada durante la animación
|
||||
}
|
||||
}
|
||||
|
||||
auto WindowMessage::easeOut(float t) const -> float {
|
||||
// Función de suavizado ease-out cuadrática
|
||||
return 1.0f - (1.0f - t) * (1.0f - t);
|
||||
}
|
||||
@@ -11,13 +11,53 @@
|
||||
|
||||
class WindowMessage {
|
||||
public:
|
||||
enum class PositionMode {
|
||||
CENTERED, // La ventana se centra en el punto especificado
|
||||
FIXED // La esquina superior izquierda coincide con el punto
|
||||
};
|
||||
|
||||
struct Config {
|
||||
// Colores
|
||||
Color bg_color;
|
||||
Color border_color;
|
||||
Color title_color;
|
||||
Color text_color;
|
||||
|
||||
// Espaciado y dimensiones
|
||||
float padding;
|
||||
float line_spacing;
|
||||
float title_separator_spacing; // Espacio extra para separador del título
|
||||
|
||||
// Límites de tamaño
|
||||
float min_width;
|
||||
float min_height;
|
||||
float max_width_ratio; // % máximo de ancho de pantalla
|
||||
float max_height_ratio; // % máximo de alto de pantalla
|
||||
|
||||
// Margen de seguridad para texto
|
||||
float text_safety_margin; // Margen extra para evitar texto cortado
|
||||
|
||||
// Constructor con valores por defecto
|
||||
Config()
|
||||
: bg_color{40, 40, 60, 220}
|
||||
, border_color{100, 100, 120, 255}
|
||||
, title_color{255, 255, 255, 255}
|
||||
, text_color{200, 200, 200, 255}
|
||||
, padding{15.0f}
|
||||
, line_spacing{5.0f}
|
||||
, title_separator_spacing{10.0f}
|
||||
, min_width{200.0f}
|
||||
, min_height{100.0f}
|
||||
, max_width_ratio{0.8f}
|
||||
, max_height_ratio{0.8f}
|
||||
, text_safety_margin{20.0f}
|
||||
{}
|
||||
};
|
||||
|
||||
WindowMessage(
|
||||
std::shared_ptr<Text> text_renderer,
|
||||
const std::string& title = "",
|
||||
const Color& bg_color = Color{40, 40, 60, 220},
|
||||
const Color& border_color = Color{100, 100, 120, 255},
|
||||
const Color& title_color = Color{255, 255, 255, 255},
|
||||
const Color& text_color = Color{200, 200, 200, 255}
|
||||
const Config& config = Config{}
|
||||
);
|
||||
|
||||
// Métodos principales
|
||||
@@ -36,30 +76,42 @@ class WindowMessage {
|
||||
void addText(const std::string& text);
|
||||
void clearTexts();
|
||||
|
||||
// Control de redimensionado automático
|
||||
void enableAutoResize(bool enabled) { auto_resize_enabled_ = enabled; }
|
||||
[[nodiscard]] auto isAutoResizeEnabled() const -> bool { return auto_resize_enabled_; }
|
||||
|
||||
// Configuración de posición y tamaño
|
||||
void setPosition(float x, float y);
|
||||
void setPosition(float x, float y, PositionMode mode = PositionMode::CENTERED);
|
||||
void setSize(float width, float height);
|
||||
void centerOnScreen();
|
||||
void autoSize(); // Ajusta automáticamente al contenido
|
||||
void autoSize(); // Ajusta automáticamente al contenido y reposiciona si es necesario
|
||||
|
||||
// Configuración de colores
|
||||
void setBackgroundColor(const Color& color) { bg_color_ = color; }
|
||||
void setBorderColor(const Color& color) { border_color_ = color; }
|
||||
void setTitleColor(const Color& color) { title_color_ = color; }
|
||||
void setTextColor(const Color& color) { text_color_ = color; }
|
||||
void setBackgroundColor(const Color& color) { config_.bg_color = color; }
|
||||
void setBorderColor(const Color& color) { config_.border_color = color; }
|
||||
void setTitleColor(const Color& color) { config_.title_color = color; updateStyles(); }
|
||||
void setTextColor(const Color& color) { config_.text_color = color; updateStyles(); }
|
||||
|
||||
// Configuración de espaciado
|
||||
void setPadding(float padding) { padding_ = padding; }
|
||||
void setLineSpacing(float spacing) { line_spacing_ = spacing; }
|
||||
void setPadding(float padding) { config_.padding = padding; }
|
||||
void setLineSpacing(float spacing) { config_.line_spacing = spacing; }
|
||||
|
||||
// Configuración avanzada
|
||||
void setConfig(const Config& config) { config_ = config; updateStyles(); }
|
||||
[[nodiscard]] auto getConfig() const -> const Config& { return config_; }
|
||||
|
||||
// Getters
|
||||
[[nodiscard]] auto getRect() const -> const SDL_FRect& { return rect_; }
|
||||
[[nodiscard]] auto getPositionMode() const -> PositionMode { return position_mode_; }
|
||||
[[nodiscard]] auto getAnchorPoint() const -> std::pair<float, float> { return {anchor_x_, anchor_y_}; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<Text> text_renderer_;
|
||||
Config config_;
|
||||
|
||||
// Estado de visibilidad
|
||||
// Estado de visibilidad y redimensionado
|
||||
bool visible_ = false;
|
||||
bool auto_resize_enabled_ = true; // Por defecto habilitado
|
||||
|
||||
// Contenido
|
||||
std::string title_;
|
||||
@@ -67,14 +119,40 @@ class WindowMessage {
|
||||
|
||||
// Posición y tamaño
|
||||
SDL_FRect rect_{0, 0, 300, 200};
|
||||
float padding_ = 15.0f;
|
||||
float line_spacing_ = 5.0f;
|
||||
PositionMode position_mode_ = PositionMode::CENTERED;
|
||||
float anchor_x_ = 0.0f;
|
||||
float anchor_y_ = 0.0f;
|
||||
|
||||
// Colores
|
||||
Color bg_color_;
|
||||
Color border_color_;
|
||||
Color title_color_;
|
||||
Color text_color_;
|
||||
// Animación de redimensionado
|
||||
struct ResizeAnimation {
|
||||
bool active = false;
|
||||
float start_width, start_height;
|
||||
float target_width, target_height;
|
||||
float duration = 0.3f; // segundos
|
||||
float elapsed = 0.0f;
|
||||
|
||||
void start(float from_w, float from_h, float to_w, float to_h) {
|
||||
start_width = from_w;
|
||||
start_height = from_h;
|
||||
target_width = to_w;
|
||||
target_height = to_h;
|
||||
elapsed = 0.0f;
|
||||
active = true;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
active = false;
|
||||
elapsed = 0.0f;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto isFinished() const -> bool {
|
||||
return elapsed >= duration;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto getProgress() const -> float {
|
||||
return std::min(elapsed / duration, 1.0f);
|
||||
}
|
||||
} resize_animation_;
|
||||
|
||||
// Estilos
|
||||
Text::Style title_style_;
|
||||
@@ -82,6 +160,17 @@ class WindowMessage {
|
||||
|
||||
// Métodos privados
|
||||
void calculateAutoSize();
|
||||
void updatePosition(); // Actualiza la posición según el modo y punto de anclaje
|
||||
void updateStyles(); // Actualiza los estilos de texto cuando cambian los colores
|
||||
void ensureTextFits(); // Verifica y ajusta para que todo el texto sea visible
|
||||
void triggerAutoResize(); // Inicia redimensionado automático si está habilitado
|
||||
void updateAnimation(float delta_time); // Actualiza la animación de redimensionado
|
||||
|
||||
// Función de suavizado (ease-out)
|
||||
[[nodiscard]] auto easeOut(float t) const -> float;
|
||||
|
||||
[[nodiscard]] auto calculateContentHeight() const -> float;
|
||||
[[nodiscard]] auto calculateContentWidth() const -> float;
|
||||
[[nodiscard]] auto getScreenWidth() const -> float;
|
||||
[[nodiscard]] auto getScreenHeight() const -> float;
|
||||
};
|
||||
Reference in New Issue
Block a user