mirror of
https://github.com/zhenyan121/SporeBG-Conid.git
synced 2026-04-10 06:14:08 +08:00
Added UI class and UI rendering class
This commit is contained in:
@@ -18,12 +18,25 @@ bool GameApplication::initialize() {
|
||||
SDL_Log("无法初始化 SDL_ttf: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
// 输入管理
|
||||
m_inputManager = std::make_unique<InputManager>();
|
||||
// 窗口管理
|
||||
m_windowManager = std::make_unique<WindowManager>();
|
||||
|
||||
m_windowManager->Initialize(m_config);
|
||||
m_sceneManager = std::make_unique<SceneManager>(m_windowManager->GetRenderer());
|
||||
|
||||
|
||||
|
||||
// 字体管理
|
||||
m_fontManager = std::make_unique<FontManager>();
|
||||
// 文字渲染
|
||||
m_textRenderer = std::make_unique<TextRenderer>(m_windowManager->GetRenderer(), m_fontManager.get());
|
||||
|
||||
m_uiRenderer = std:: make_unique<UIRenderer>(m_windowManager->GetRenderer(), m_textRenderer.get());
|
||||
|
||||
// 场景管理
|
||||
m_sceneManager = std::make_unique<SceneManager>(m_windowManager->GetRenderer(), m_uiRenderer.get());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
#include "scenes/base/SceneManager.h"
|
||||
#include "input/InputManager.h"
|
||||
#include "utils/Config.h"
|
||||
#include <SDL3_ttf/SDL_ttf.h>
|
||||
|
||||
#include "graphics/font/TextRenderer.h"
|
||||
#include "graphics/font/FontManager.h"
|
||||
|
||||
|
||||
class GameApplication {
|
||||
private:
|
||||
@@ -14,6 +17,10 @@ private:
|
||||
std::unique_ptr<WindowManager> m_windowManager;
|
||||
std::unique_ptr<SceneManager> m_sceneManager;
|
||||
std::unique_ptr<InputManager> m_inputManager;
|
||||
std::unique_ptr<FontManager> m_fontManager;
|
||||
std::unique_ptr<TextRenderer> m_textRenderer;
|
||||
std::unique_ptr<UIRenderer> m_uiRenderer;
|
||||
|
||||
GameConfig m_config;
|
||||
|
||||
public:
|
||||
|
||||
106
src/graphics/ui/UIRenderer.cpp
Normal file
106
src/graphics/ui/UIRenderer.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
#include "UIRenderer.h"
|
||||
|
||||
UIRenderer::UIRenderer(SDL_Renderer* renderer, TextRenderer* textRenderer) :
|
||||
m_renderer(renderer),
|
||||
//m_fontManager(fontManager),
|
||||
m_textRenderer(textRenderer)
|
||||
{
|
||||
|
||||
}
|
||||
UIRenderer::~UIRenderer() {
|
||||
|
||||
}
|
||||
|
||||
void UIRenderer::renderUI(const UIRenderData& uiRenderData) {
|
||||
if (!m_renderer) return;
|
||||
// 控制颜色混合方式 用 NONE 可避免所有混合
|
||||
//SDL_SetRenderDrawBlendMode(m_renderer, SDL_BLENDMODE_NONE);
|
||||
|
||||
// 保存当前渲染状态
|
||||
SDL_Color previousDrawColor;
|
||||
SDL_GetRenderDrawColor(m_renderer,
|
||||
&previousDrawColor.r,
|
||||
&previousDrawColor.g,
|
||||
&previousDrawColor.b,
|
||||
&previousDrawColor.a);
|
||||
|
||||
// 设置UI渲染状态
|
||||
// 例如:SDL_SetRenderDrawBlendMode(m_renderer, SDL_BLENDMODE_BLEND);
|
||||
for (auto& button : uiRenderData.buttons) {
|
||||
renderButton(button);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 恢复之前的渲染状态
|
||||
SDL_SetRenderDrawColor(m_renderer,
|
||||
previousDrawColor.r,
|
||||
previousDrawColor.g,
|
||||
previousDrawColor.b,
|
||||
previousDrawColor.a);
|
||||
}
|
||||
|
||||
void UIRenderer::renderButton(const ButtonData& buttonData) {
|
||||
//SDL_Log("render button\n");
|
||||
renderButtonBackground(buttonData);
|
||||
|
||||
renderButtonBorder(buttonData);
|
||||
renderButtonText(buttonData);
|
||||
}
|
||||
|
||||
void UIRenderer::renderButtonBackground(const ButtonData& buttonData) {
|
||||
// 设置绘制颜色
|
||||
if (!m_renderer) {
|
||||
SDL_Log("renderer is null\n");
|
||||
}
|
||||
// SDL_Log("renderButtonBackground called for rect (%d,%d,%d,%d)",
|
||||
// buttonData.rect.x, buttonData.rect.y, buttonData.rect.w, buttonData.rect.h);
|
||||
//SDL_Log("start render background\n");
|
||||
auto m_backgroundColor = buttonData.backgroundColor;
|
||||
auto m_rect = buttonData.rect;
|
||||
|
||||
SDL_SetRenderDrawColor(m_renderer, m_backgroundColor.r, m_backgroundColor.g, m_backgroundColor.b, m_backgroundColor.a);
|
||||
|
||||
// 绘制普通矩形
|
||||
SDL_FRect rect = { static_cast<float>(m_rect.x),
|
||||
static_cast<float>(m_rect.y),
|
||||
static_cast<float>(m_rect.w),
|
||||
static_cast<float>(m_rect.h) };
|
||||
SDL_RenderRect(m_renderer, &rect);
|
||||
}
|
||||
|
||||
void UIRenderer::renderButtonBorder(const ButtonData& buttonData) {
|
||||
|
||||
}
|
||||
|
||||
void UIRenderer::renderButtonText(const ButtonData& buttonData) {
|
||||
auto m_text = buttonData.text;
|
||||
auto m_rect = buttonData.rect;
|
||||
auto m_textStyle = buttonData.textstytle;
|
||||
if (m_text.empty()) return;
|
||||
|
||||
// 计算文本位置(居中)
|
||||
// 这里需要TextRenderer的实际实现
|
||||
// 假设TextRenderer有一个renderText方法:
|
||||
// void renderText(SDL_Renderer* renderer, const std::string& text,
|
||||
// const TextStyle& style, int x, int y);
|
||||
|
||||
// 计算居中位置
|
||||
// 注意:这里需要获取文本的实际大小来计算居中
|
||||
// 由于不知道TextRenderer的具体实现,这里提供基本思路
|
||||
|
||||
// 假设可以获取文本尺寸
|
||||
// SDL_Point textSize = m_textRenderer.getTextSize(m_text, m_textStyle);
|
||||
|
||||
// 计算居中位置
|
||||
// int textX = static_cast<int>(m_rect.x + (m_rect.w - textSize.x) / 2);
|
||||
// int textY = static_cast<int>(m_rect.y + (m_rect.h - textSize.y) / 2);
|
||||
|
||||
// 临时实现:直接在中心位置渲染
|
||||
int centerX = static_cast<int>(m_rect.x + m_rect.w / 2);
|
||||
int centerY = static_cast<int>(m_rect.y + m_rect.h / 2);
|
||||
|
||||
// 根据你的TextRenderer实际API调整
|
||||
m_textRenderer->renderText(m_text, m_textStyle,
|
||||
centerX, centerY); // true表示居中
|
||||
}
|
||||
40
src/graphics/ui/UIRenderer.h
Normal file
40
src/graphics/ui/UIRenderer.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_ttf/SDL_ttf.h>
|
||||
#include "graphics/font/TextRenderer.h"
|
||||
#include "graphics/font/FontManager.h"
|
||||
#include "ui/base/UIRenderData.h"
|
||||
class UIRenderer {
|
||||
private:
|
||||
TextRenderer* m_textRenderer;
|
||||
|
||||
|
||||
SDL_Renderer* m_renderer;
|
||||
|
||||
|
||||
/**
|
||||
* @brief 渲染按钮背景
|
||||
* @param renderer SDL渲染器
|
||||
*/
|
||||
void renderButtonBackground(const ButtonData& buttonData);
|
||||
|
||||
/**
|
||||
* @brief 渲染按钮边框
|
||||
* @param renderer SDL渲染器
|
||||
*/
|
||||
void renderButtonBorder(const ButtonData& buttonData);
|
||||
|
||||
/**
|
||||
* @brief 渲染按钮文本
|
||||
* @param renderer SDL渲染器
|
||||
*/
|
||||
void renderButtonText(const ButtonData& buttonData);
|
||||
public:
|
||||
|
||||
UIRenderer(SDL_Renderer* m_renderer, TextRenderer* textRenderer);
|
||||
~UIRenderer();
|
||||
void renderUI(const UIRenderData& uiRenderData);
|
||||
|
||||
void renderButton(const ButtonData& buttonData);
|
||||
|
||||
};
|
||||
@@ -1,16 +1,17 @@
|
||||
// ui/Scene.h - 场景基类
|
||||
#pragma once
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "graphics/ui/UIRenderer.h"
|
||||
|
||||
|
||||
|
||||
class Scene {
|
||||
public:
|
||||
|
||||
virtual ~Scene() = default;
|
||||
|
||||
// 空实现,可选择性重写
|
||||
virtual void onEnter(SDL_Renderer* renderer, int width, int height){};
|
||||
virtual void onEnter(SDL_Renderer* renderer, int width, int height, UIRenderer* uiRenderer){};
|
||||
virtual void onExit(){};
|
||||
|
||||
virtual void update() = 0;
|
||||
@@ -18,6 +19,9 @@ public:
|
||||
virtual void render() = 0;
|
||||
// 处理点击
|
||||
virtual void handleClick(float x, float y) = 0;
|
||||
|
||||
protected:
|
||||
// UIRenderer应该由上层管理
|
||||
UIRenderer* m_uiRenderer;
|
||||
SDL_Renderer* m_renderer;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#include "SceneManager.h"
|
||||
|
||||
SceneManager::SceneManager(SDL_Renderer* renderer) {
|
||||
SceneManager::SceneManager(SDL_Renderer* renderer, UIRenderer* uiRenderer) :
|
||||
m_renderer(renderer),
|
||||
m_uiRenderer(uiRenderer)
|
||||
|
||||
{
|
||||
m_scene = std::make_unique<GameScene>();
|
||||
m_scene->onEnter(renderer, 1600, 900);
|
||||
m_scene->onEnter(renderer, 1600, 900, m_uiRenderer);
|
||||
}
|
||||
|
||||
SceneManager::~SceneManager() {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <unordered_map>
|
||||
class SceneManager {
|
||||
public:
|
||||
SceneManager(SDL_Renderer* renderer);
|
||||
SceneManager(SDL_Renderer* renderer, UIRenderer* uiRenderer);
|
||||
~SceneManager();
|
||||
|
||||
void PushScene(const std::string& sceneName, std::unique_ptr<Scene> scene);
|
||||
@@ -20,6 +20,8 @@ public:
|
||||
|
||||
|
||||
private:
|
||||
SDL_Renderer* m_renderer;
|
||||
UIRenderer* m_uiRenderer;
|
||||
std::unique_ptr<Scene> m_scene;
|
||||
// 用栈的形式来存储场景
|
||||
std::stack<std::unique_ptr<Scene>> m_scenes;
|
||||
|
||||
@@ -8,13 +8,21 @@ GameScene::~GameScene() {
|
||||
|
||||
}
|
||||
|
||||
void GameScene::onEnter(SDL_Renderer* renderer, int WIDTH, int HEIGHT) {
|
||||
m_renderer = std::make_unique<GameRenderer>(WIDTH, HEIGHT, renderer);
|
||||
void GameScene::onEnter(SDL_Renderer* renderer, int WIDTH, int HEIGHT, UIRenderer* uiRenderer){
|
||||
m_renderer = renderer;
|
||||
m_uiRenderer = uiRenderer;
|
||||
m_gameUIManager = std::make_unique<GameUIManager>();
|
||||
m_gameUIManager->init();
|
||||
m_gameRenderer = std::make_unique<GameRenderer>(WIDTH, HEIGHT, renderer);
|
||||
m_gameSession = std::make_unique<GameSession>();
|
||||
m_CoordinateConverter = std::make_unique<CoordinateConverter>(renderer);
|
||||
m_gameSession->initialize();
|
||||
|
||||
m_renderer->setBoard(m_gameSession->getBoard());
|
||||
m_gameRenderer->setBoard(m_gameSession->getBoard());
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
void GameScene::update() {
|
||||
@@ -22,15 +30,17 @@ void GameScene::update() {
|
||||
}
|
||||
|
||||
void GameScene::render() {
|
||||
m_renderer->beginFrame();
|
||||
m_renderer->drawBackground();
|
||||
m_renderer->drawBoard();
|
||||
m_renderer->drawPiece(m_gameSession->getSelectedPiece());
|
||||
m_renderer->endFrame();
|
||||
m_gameRenderer->beginFrame();
|
||||
m_gameRenderer->drawBackground();
|
||||
m_gameRenderer->drawBoard();
|
||||
|
||||
m_gameRenderer->drawPiece(m_gameSession->getSelectedPiece());
|
||||
m_uiRenderer->renderUI(m_gameUIManager->getUIRenderData());
|
||||
m_gameRenderer->endFrame();
|
||||
}
|
||||
|
||||
void GameScene::handleClick(float screenX, float screenY) {
|
||||
auto click = m_CoordinateConverter->ScreenToBoard(screenX, screenY, m_renderer->getBoardArea());
|
||||
auto click = m_CoordinateConverter->ScreenToBoard(screenX, screenY, m_gameRenderer->getBoardArea());
|
||||
if (click) {
|
||||
auto [row, col] = click.value();
|
||||
m_gameSession->handleCoordinateInput(row, col);
|
||||
|
||||
@@ -3,19 +3,27 @@
|
||||
#include "graphics/GameRenderer.h"
|
||||
#include "graphics/CoordinateConverter.h"
|
||||
#include "game/GameSession.h"
|
||||
#include "graphics/ui/UIRenderer.h"
|
||||
#include "ui/managers/GameUIManager.h"
|
||||
#include <memory>
|
||||
class GameScene : public Scene {
|
||||
public:
|
||||
GameScene();
|
||||
~GameScene();
|
||||
|
||||
void onEnter(SDL_Renderer* renderer, int WIDTH, int HEIGHT) override;
|
||||
void onEnter(SDL_Renderer* renderer, int WIDTH, int HEIGHT, UIRenderer* uiRenderer) override;
|
||||
|
||||
void update() override;
|
||||
void render() override;
|
||||
void handleClick(float screenX, float screenY) override;
|
||||
private:
|
||||
std::unique_ptr<GameRenderer> m_renderer;
|
||||
|
||||
std::unique_ptr<GameRenderer> m_gameRenderer;
|
||||
std::unique_ptr<CoordinateConverter> m_CoordinateConverter;
|
||||
std::unique_ptr<GameSession> m_gameSession;
|
||||
|
||||
std::unique_ptr<GameUIManager> m_gameUIManager;
|
||||
|
||||
|
||||
|
||||
};
|
||||
80
src/ui/base/UIComponent.h
Normal file
80
src/ui/base/UIComponent.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
/**
|
||||
* @brief UI组件抽象基类
|
||||
*
|
||||
* 所有UI组件(按钮、标签、面板等)都应继承此类
|
||||
* 提供统一的接口用于渲染、更新和事件处理
|
||||
*/
|
||||
|
||||
|
||||
|
||||
class UIComponent {
|
||||
public:
|
||||
virtual ~UIComponent() = default;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief 更新组件状态
|
||||
* @param deltaTime 距离上一帧的时间(秒),用于动画
|
||||
*/
|
||||
|
||||
virtual void update(float deltaTime) = 0;
|
||||
|
||||
/**
|
||||
* @brief 处理SDL事件
|
||||
* @param event SDL事件引用
|
||||
*/
|
||||
|
||||
//virtual void handleEvent
|
||||
|
||||
/**
|
||||
* @brief 设置组件位置
|
||||
* @param x X坐标(屏幕像素)
|
||||
* @param y Y坐标(屏幕像素)
|
||||
*/
|
||||
|
||||
virtual void setPosition(int x, int y) {
|
||||
m_rect.x = static_cast<float>(x);
|
||||
m_rect.y = static_cast<float>(y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取组件边界矩形
|
||||
* @return SDL_FRect 浮点矩形
|
||||
*/
|
||||
SDL_FRect getBounds() const { return m_rect; }
|
||||
|
||||
/**
|
||||
* @brief 设置是否可见
|
||||
* @param visible 可见性标志
|
||||
*/
|
||||
void setVisible(bool visible) { m_isVisible = visible; }
|
||||
|
||||
/**
|
||||
* @brief 检查是否可见
|
||||
* @return true 如果组件可见
|
||||
*/
|
||||
bool isVisible() const { return m_isVisible; }
|
||||
|
||||
/**
|
||||
* @brief 设置是否启用交互
|
||||
* @param enabled 启用标志
|
||||
*/
|
||||
void setEnabled(bool enabled) { m_isEnabled = enabled; }
|
||||
|
||||
/**
|
||||
* @brief 检查是否启用
|
||||
* @return true 如果组件启用
|
||||
*/
|
||||
bool isEnabled() const { return m_isEnabled; }
|
||||
|
||||
protected:
|
||||
UIComponent() = default;
|
||||
SDL_FRect m_rect = {0.0f, 0.0f, 0.0f, 0.0f}; ///< 组件位置和尺寸
|
||||
bool m_isVisible = true; ///< 可见性标志
|
||||
bool m_isEnabled = true; ///< 启用标志
|
||||
|
||||
};
|
||||
20
src/ui/base/UIRenderData.h
Normal file
20
src/ui/base/UIRenderData.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include "graphics/font/Textstyle.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
struct ButtonData
|
||||
{
|
||||
std::string text;
|
||||
TextStyle textstytle;
|
||||
SDL_FRect rect;
|
||||
SDL_Color backgroundColor;
|
||||
|
||||
int borderThickness;
|
||||
SDL_Color borderColor;
|
||||
};
|
||||
|
||||
// 可以通过索引方式实现,及直接传递Button指针, 在UIRender里面调用getData
|
||||
struct UIRenderData
|
||||
{
|
||||
std::vector<ButtonData> buttons;
|
||||
};
|
||||
33
src/ui/components/Button.cpp
Normal file
33
src/ui/components/Button.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "Button.h"
|
||||
|
||||
Button::Button()
|
||||
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Button::setText(const std::string& text, TextStyle style) {
|
||||
m_buttonData.text = text;
|
||||
m_buttonData.textstytle = style;
|
||||
}
|
||||
|
||||
void Button::setBackgroundColor(SDL_Color normal) {
|
||||
m_buttonData.backgroundColor = normal;
|
||||
}
|
||||
|
||||
void Button::setBorder(int thickness, SDL_Color color) {
|
||||
m_buttonData.borderThickness = thickness;
|
||||
m_buttonData.borderColor = color;
|
||||
}
|
||||
|
||||
|
||||
void Button::update(float deltaTime) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
48
src/ui/components/Button.h
Normal file
48
src/ui/components/Button.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
#include "ui/base/UIRenderData.h"
|
||||
#include "ui/base/UIComponent.h"
|
||||
class Button : public UIComponent{
|
||||
public:
|
||||
Button();
|
||||
~Button() override = default;
|
||||
|
||||
// 实现UIComponent接口
|
||||
|
||||
void update(float deltaTime) override;
|
||||
|
||||
/**
|
||||
* @brief 设置按钮文本
|
||||
* @param text 按钮文本
|
||||
* @param style 文本样式
|
||||
*/
|
||||
void setText(const std::string& text, TextStyle style);
|
||||
|
||||
/**
|
||||
* @brief 设置背景颜色
|
||||
* @param normal 正常状态颜色
|
||||
* @param hovered 悬停状态颜色(可选,默认透明)
|
||||
* @param pressed 按下状态颜色(可选,默认透明)
|
||||
*/
|
||||
void setBackgroundColor(SDL_Color normal);
|
||||
/**
|
||||
* @brief 设置边框
|
||||
* @param thickness 边框厚度(像素)
|
||||
* @param color 边框颜色
|
||||
*/
|
||||
void setBorder(int thickness, SDL_Color color);
|
||||
|
||||
|
||||
ButtonData& getButtonDate() {
|
||||
m_buttonData.rect = m_rect;
|
||||
return m_buttonData;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
|
||||
|
||||
|
||||
ButtonData m_buttonData;
|
||||
|
||||
|
||||
};
|
||||
43
src/ui/managers/GameUIManager.cpp
Normal file
43
src/ui/managers/GameUIManager.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "GameUIManager.h"
|
||||
|
||||
GameUIManager::GameUIManager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
GameUIManager::~GameUIManager() {
|
||||
|
||||
}
|
||||
|
||||
void GameUIManager::init() {
|
||||
auto button = std::make_unique<Button>();
|
||||
button->setBackgroundColor({255, 100, 0, 255});
|
||||
button->setBorder(2, {0, 0, 0, 255});
|
||||
button->setPosition(20, 20);
|
||||
button->setEnabled(true);
|
||||
button->setVisible(true);
|
||||
button->setText("hello,world!!", {"SourceHanSansSC-Regular.otf", 48, {0, 0, 0, 255}});
|
||||
m_buttons.push_back(std::move(button));
|
||||
}
|
||||
|
||||
|
||||
|
||||
const UIRenderData& GameUIManager::getUIRenderData() {
|
||||
CollectRenderData();
|
||||
return m_uiRenderData;
|
||||
}
|
||||
|
||||
void GameUIManager::CollectRenderData() {
|
||||
|
||||
//SDL_Log("CollectRenderData called. buttons count = %zu", m_buttons.size());
|
||||
// 清理上一帧的数据
|
||||
m_uiRenderData.buttons.clear();
|
||||
//SDL_Log("collect data\n");
|
||||
for (auto& button : m_buttons) {
|
||||
if(!button->isVisible()) {
|
||||
continue;
|
||||
|
||||
}
|
||||
m_uiRenderData.buttons.push_back(button->getButtonDate());
|
||||
}
|
||||
}
|
||||
22
src/ui/managers/GameUIManager.h
Normal file
22
src/ui/managers/GameUIManager.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_ttf/SDL_ttf.h>
|
||||
#include "ui/components/Button.h"
|
||||
#include "ui/base/UIRenderData.h"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
class GameUIManager {
|
||||
private:
|
||||
std::vector<std::unique_ptr<Button>> m_buttons;
|
||||
UIRenderData m_uiRenderData;
|
||||
|
||||
public:
|
||||
GameUIManager();
|
||||
~GameUIManager();
|
||||
|
||||
void init();
|
||||
|
||||
const UIRenderData& getUIRenderData();
|
||||
// 收集渲染数据
|
||||
void CollectRenderData();
|
||||
};
|
||||
Reference in New Issue
Block a user