feat: add parameter ranges
This commit is contained in:
@@ -66,6 +66,11 @@ enzo::bt::String enzo::prm::Parameter::evalString(unsigned int index) const
|
||||
return stringValues_[index];
|
||||
}
|
||||
|
||||
const enzo::prm::Template& enzo::prm::Parameter::getTemplate()
|
||||
{
|
||||
return template_;
|
||||
}
|
||||
|
||||
|
||||
|
||||
enzo::prm::Type enzo::prm::Parameter::getType() const
|
||||
|
||||
@@ -20,6 +20,8 @@ public:
|
||||
void setFloat(bt::floatT value, unsigned int index=0);
|
||||
void setString(bt::String value, unsigned int index=0);
|
||||
|
||||
const Template& getTemplate();
|
||||
|
||||
boost::signals2::signal<void ()> valueChanged;
|
||||
private:
|
||||
Template template_;
|
||||
|
||||
8
src/Engine/Parameter/Range.cpp
Normal file
8
src/Engine/Parameter/Range.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "Engine/Parameter/Range.h"
|
||||
|
||||
|
||||
enzo::prm::Range::Range(bt::floatT minValue, RangeFlag minFlag, bt::floatT maxValue, RangeFlag maxFlag)
|
||||
: minValue_{minValue}, minFlag_{minFlag}, maxValue_{maxValue}, maxFlag_{maxFlag}
|
||||
{
|
||||
|
||||
}
|
||||
29
src/Engine/Parameter/Range.h
Normal file
29
src/Engine/Parameter/Range.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Types.h"
|
||||
namespace enzo::prm
|
||||
{
|
||||
enum class RangeFlag
|
||||
{
|
||||
UNLOCKED,
|
||||
LOCKED
|
||||
|
||||
};
|
||||
|
||||
class Range
|
||||
{
|
||||
public:
|
||||
Range(bt::floatT minValue=0, RangeFlag minFlag=RangeFlag::UNLOCKED, bt::floatT maxValue=10, RangeFlag maxFlag=RangeFlag::UNLOCKED);
|
||||
|
||||
bt::floatT getMin() const { return minValue_; }
|
||||
bt::floatT getMax() const { return maxValue_; }
|
||||
RangeFlag getMinFlag() const { return minFlag_; }
|
||||
RangeFlag getMaxFlag() const { return maxFlag_; }
|
||||
|
||||
private:
|
||||
bt::floatT minValue_;
|
||||
bt::floatT maxValue_;
|
||||
RangeFlag minFlag_;
|
||||
RangeFlag maxFlag_;
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "Engine/Parameter/Template.h"
|
||||
#include "Engine/Parameter/Default.h"
|
||||
#include "Engine/Parameter/Range.h"
|
||||
#include "Engine/Parameter/Type.h"
|
||||
|
||||
enzo::prm::Template::Template(enzo::prm::Type type, const char* name)
|
||||
@@ -14,17 +15,18 @@ enzo::prm::Template::Template(enzo::prm::Type type, const char* name, unsigned i
|
||||
|
||||
}
|
||||
|
||||
enzo::prm::Template::Template(enzo::prm::Type type, const char* name, std::vector<prm::Default> defaults, unsigned int vectorSize)
|
||||
enzo::prm::Template::Template(enzo::prm::Type type, const char* name, std::vector<prm::Default> defaults, unsigned int vectorSize, std::vector<prm::Range> ranges)
|
||||
: type_{type}, name_{name}, defaults_{defaults}, vectorSize_(vectorSize)
|
||||
{
|
||||
|
||||
ranges.resize(vectorSize);
|
||||
ranges_ = ranges;
|
||||
}
|
||||
|
||||
enzo::prm::Template::Template(enzo::prm::Type type, const char* name, prm::Default theDefault, unsigned int vectorSize)
|
||||
enzo::prm::Template::Template(enzo::prm::Type type, const char* name, prm::Default theDefault, unsigned int vectorSize, Range range)
|
||||
: type_{type}, name_{name}, vectorSize_(vectorSize)
|
||||
{
|
||||
defaults_.push_back(theDefault);
|
||||
|
||||
ranges_.push_back(range);
|
||||
}
|
||||
|
||||
enzo::prm::Template::Template()
|
||||
@@ -55,6 +57,11 @@ const enzo::prm::Default enzo::prm::Template::getDefault(unsigned int index) con
|
||||
return defaults_.at(index);
|
||||
}
|
||||
|
||||
const enzo::prm::Range& enzo::prm::Template::getRange(unsigned int index) const
|
||||
{
|
||||
return ranges_.at(index);
|
||||
}
|
||||
|
||||
const unsigned int enzo::prm::Template::getNumDefaults() const
|
||||
{
|
||||
return defaults_.size();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "Engine/Parameter/Default.h"
|
||||
#include "Engine/Parameter/Range.h"
|
||||
#include "Engine/Parameter/Type.h"
|
||||
#include "Engine/Types.h"
|
||||
|
||||
@@ -22,17 +23,20 @@ public:
|
||||
enzo::prm::Type type,
|
||||
const char* name,
|
||||
std::vector<prm::Default> defaults,
|
||||
unsigned int vectorSize = 1
|
||||
unsigned int vectorSize = 1,
|
||||
std::vector<prm::Range> ranges=std::vector<prm::Range>()
|
||||
);
|
||||
Template(
|
||||
enzo::prm::Type type,
|
||||
const char* name,
|
||||
prm::Default theDefault,
|
||||
unsigned int vectorSize = 1
|
||||
unsigned int vectorSize = 1,
|
||||
Range range=Range()
|
||||
);
|
||||
Template();
|
||||
const char* getName() const;
|
||||
const prm::Default getDefault(unsigned int index=0) const;
|
||||
const prm::Range& getRange(unsigned int index=0) const;
|
||||
const prm::Type getType() const;
|
||||
const unsigned int getSize() const;
|
||||
const unsigned int getNumDefaults() const;
|
||||
@@ -40,6 +44,7 @@ public:
|
||||
private:
|
||||
enzo::prm::Type type_;
|
||||
std::vector<prm::Default> defaults_;
|
||||
std::vector<prm::Range> ranges_;
|
||||
// TODO: make a class that holds token and name
|
||||
const char* name_;
|
||||
unsigned int vectorSize_;
|
||||
|
||||
@@ -6,17 +6,19 @@
|
||||
#include <iostream>
|
||||
#include <qboxlayout.h>
|
||||
#include <qnamespace.h>
|
||||
#include <icecream.hpp>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
|
||||
enzo::ui::FloatSliderParm::FloatSliderParm(bt::floatT value, QWidget *parent, Qt::WindowFlags f)
|
||||
enzo::ui::FloatSliderParm::FloatSliderParm(std::weak_ptr<prm::Parameter> parameter, unsigned int vectorIndex, QWidget *parent, Qt::WindowFlags f)
|
||||
: QWidget(parent, f)
|
||||
{
|
||||
// tells qt to style the widget even though it's a Q_OBJECT
|
||||
setAttribute(Qt::WA_StyledBackground, true);
|
||||
setFixedHeight(24);
|
||||
|
||||
parameter_ = parameter;
|
||||
|
||||
mainLayout_ = new QVBoxLayout();
|
||||
setLayout(mainLayout_);
|
||||
@@ -34,7 +36,19 @@ enzo::ui::FloatSliderParm::FloatSliderParm(bt::floatT value, QWidget *parent, Qt
|
||||
)");
|
||||
mainLayout_->addWidget(valueLabel_);
|
||||
|
||||
setValueImpl(value);
|
||||
if(auto parameterShared=parameter_.lock())
|
||||
{
|
||||
auto range = parameterShared->getTemplate().getRange();
|
||||
minValue_=range.getMin();
|
||||
maxValue_=range.getMax();
|
||||
clampMin_=range.getMinFlag()==prm::RangeFlag::LOCKED;
|
||||
clampMax_=range.getMaxFlag()==prm::RangeFlag::LOCKED;
|
||||
setValueImpl(parameterShared->evalFloat(vectorIndex));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::bad_weak_ptr();
|
||||
}
|
||||
}
|
||||
|
||||
void enzo::ui::FloatSliderParm::paintEvent(QPaintEvent *event)
|
||||
@@ -45,7 +59,7 @@ void enzo::ui::FloatSliderParm::paintEvent(QPaintEvent *event)
|
||||
painter.setBrush(QColor("#383838"));
|
||||
|
||||
const int valueRange = maxValue_-minValue_;
|
||||
float fillPercent = static_cast<float>(value_-minValue_)/valueRange;
|
||||
float fillPercent = std::min<float>(static_cast<float>(value_-minValue_)/valueRange, 1);
|
||||
|
||||
constexpr float margin = 3;
|
||||
float fillWidth = rect().width()-margin*2;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <QWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
#include "Engine/Parameter/Parameter.h"
|
||||
|
||||
namespace enzo::ui
|
||||
{
|
||||
@@ -12,7 +13,7 @@ class FloatSliderParm
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FloatSliderParm(bt::floatT value, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
|
||||
FloatSliderParm(std::weak_ptr<prm::Parameter> parameter, unsigned int vectorIndex=0, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
|
||||
void setValue(bt::floatT value);
|
||||
|
||||
Q_SIGNALS:
|
||||
@@ -23,10 +24,11 @@ private:
|
||||
QVBoxLayout* mainLayout_;
|
||||
QLabel* valueLabel_;
|
||||
bt::floatT value_;
|
||||
bool clampMin_ = true;
|
||||
bool clampMax_ = true;
|
||||
bt::floatT minValue_=-5;
|
||||
bt::floatT maxValue_=10;
|
||||
bool clampMin_;
|
||||
bool clampMax_;
|
||||
bt::floatT minValue_;
|
||||
bt::floatT maxValue_;
|
||||
std::weak_ptr<prm::Parameter> parameter_;
|
||||
|
||||
void setValueImpl(bt::floatT value);
|
||||
|
||||
|
||||
@@ -28,23 +28,23 @@ enzo::ui::FormParm::FormParm(std::weak_ptr<prm::Parameter> parameter)
|
||||
{
|
||||
case prm::Type::FLOAT:
|
||||
{
|
||||
FloatSliderParm* slider = new FloatSliderParm(sharedParameter->evalFloat());
|
||||
FloatSliderParm* slider = new FloatSliderParm(parameter);
|
||||
mainLayout_->addWidget(slider);
|
||||
connect(slider, &FloatSliderParm::valueChanged, this, [this](bt::floatT value){this->changeValue(value, 0);});
|
||||
break;
|
||||
}
|
||||
case prm::Type::INT:
|
||||
{
|
||||
IntSliderParm* slider = new IntSliderParm(sharedParameter->evalInt());
|
||||
IntSliderParm* slider = new IntSliderParm(parameter_);
|
||||
mainLayout_->addWidget(slider);
|
||||
connect(slider, &IntSliderParm::valueChanged, this, [this](bt::intT value){this->changeValue(value, 0);});
|
||||
break;
|
||||
}
|
||||
case prm::Type::XYZ:
|
||||
{
|
||||
FloatSliderParm* slider1 = new FloatSliderParm(sharedParameter->evalFloat(0));
|
||||
FloatSliderParm* slider2 = new FloatSliderParm(sharedParameter->evalFloat(1));
|
||||
FloatSliderParm* slider3 = new FloatSliderParm(sharedParameter->evalFloat(2));
|
||||
FloatSliderParm* slider1 = new FloatSliderParm(parameter, 0);
|
||||
FloatSliderParm* slider2 = new FloatSliderParm(parameter, 0);
|
||||
FloatSliderParm* slider3 = new FloatSliderParm(parameter, 0);
|
||||
QHBoxLayout* vectorLayout = new QHBoxLayout();
|
||||
vectorLayout->addWidget(slider1);
|
||||
vectorLayout->addWidget(slider2);
|
||||
@@ -57,7 +57,7 @@ enzo::ui::FormParm::FormParm(std::weak_ptr<prm::Parameter> parameter)
|
||||
}
|
||||
case prm::Type::STRING:
|
||||
{
|
||||
StringParm* stringParm = new StringParm(sharedParameter->evalString());
|
||||
StringParm* stringParm = new StringParm(parameter);
|
||||
|
||||
connect(stringParm, &StringParm::valueChanged, this, [this](bt::String value){this->changeValue(value, 0);});
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "Gui/Parameters/IntSliderParm.h"
|
||||
#include "Engine/Parameter/Range.h"
|
||||
#include "Engine/Types.h"
|
||||
#include <QPainter>
|
||||
#include <QPaintEvent>
|
||||
@@ -12,7 +13,7 @@
|
||||
#include <icecream.hpp>
|
||||
|
||||
|
||||
enzo::ui::IntSliderParm::IntSliderParm(bt::intT value, QWidget *parent, Qt::WindowFlags f)
|
||||
enzo::ui::IntSliderParm::IntSliderParm(std::weak_ptr<prm::Parameter> parameter, QWidget *parent, Qt::WindowFlags f)
|
||||
: QWidget(parent, f)
|
||||
{
|
||||
// tells qt to style the widget even though it's a Q_OBJECT
|
||||
@@ -21,8 +22,8 @@ enzo::ui::IntSliderParm::IntSliderParm(bt::intT value, QWidget *parent, Qt::Wind
|
||||
|
||||
notchPen_ = QPen(QColor("#383838"), notchWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
|
||||
|
||||
parameter_=parameter;
|
||||
|
||||
|
||||
mainLayout_ = new QVBoxLayout();
|
||||
setLayout(mainLayout_);
|
||||
|
||||
@@ -39,7 +40,19 @@ enzo::ui::IntSliderParm::IntSliderParm(bt::intT value, QWidget *parent, Qt::Wind
|
||||
)");
|
||||
mainLayout_->addWidget(valueLabel_);
|
||||
|
||||
setValueImpl(value);
|
||||
if(auto parameterShared=parameter_.lock())
|
||||
{
|
||||
auto range = parameterShared->getTemplate().getRange();
|
||||
minValue_=range.getMin();
|
||||
maxValue_=range.getMax();
|
||||
clampMin_=range.getMinFlag()==prm::RangeFlag::LOCKED;
|
||||
clampMax_=range.getMaxFlag()==prm::RangeFlag::LOCKED;
|
||||
setValueImpl(parameterShared->evalInt());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::bad_weak_ptr();
|
||||
}
|
||||
}
|
||||
|
||||
void enzo::ui::IntSliderParm::paintEvent(QPaintEvent *event)
|
||||
@@ -48,7 +61,7 @@ void enzo::ui::IntSliderParm::paintEvent(QPaintEvent *event)
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
const int valueRange = maxValue_-minValue_;
|
||||
float fillPercent = static_cast<float>(value_-minValue_)/valueRange;
|
||||
float fillPercent = std::min<float>(static_cast<float>(value_-minValue_)/valueRange, 1);
|
||||
IC(value_, fillPercent);
|
||||
float margin = 3;
|
||||
float fillWidth = rect().width()-margin*2;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPen>
|
||||
#include "Engine/Parameter/Parameter.h"
|
||||
|
||||
namespace enzo::ui
|
||||
{
|
||||
@@ -13,7 +14,7 @@ class IntSliderParm
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
IntSliderParm(bt::intT value, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
|
||||
IntSliderParm(std::weak_ptr<enzo::prm::Parameter> parameter, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
|
||||
void setValue(bt::intT value);
|
||||
|
||||
Q_SIGNALS:
|
||||
@@ -24,10 +25,12 @@ private:
|
||||
QVBoxLayout* mainLayout_;
|
||||
QLabel* valueLabel_;
|
||||
bt::intT value_;
|
||||
bool clampMin_ = true;
|
||||
bool clampMax_ = true;
|
||||
bt::intT minValue_=-5;
|
||||
bt::intT maxValue_=10;
|
||||
bool clampMin_;
|
||||
bool clampMax_;
|
||||
bt::intT minValue_;
|
||||
bt::intT maxValue_;
|
||||
|
||||
std::weak_ptr<prm::Parameter> parameter_;
|
||||
|
||||
QPen notchPen_;
|
||||
static constexpr int notchWidth = 2;
|
||||
|
||||
@@ -9,16 +9,25 @@
|
||||
#include <algorithm>
|
||||
#include <QLineEdit>
|
||||
#include <string>
|
||||
#include <icecream.hpp>
|
||||
|
||||
|
||||
enzo::ui::StringParm::StringParm(bt::String value, QWidget *parent)
|
||||
enzo::ui::StringParm::StringParm(std::weak_ptr<prm::Parameter> parameter, QWidget *parent)
|
||||
: QLineEdit(parent)
|
||||
{
|
||||
// tells qt to style the widget even though it's a Q_OBJECT
|
||||
setAttribute(Qt::WA_StyledBackground, true);
|
||||
setFixedHeight(24);
|
||||
|
||||
value_ = value;
|
||||
parameter_ = parameter;
|
||||
if(auto parameterShared=parameter_.lock())
|
||||
{
|
||||
value_ = parameterShared->evalString();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::bad_weak_ptr();
|
||||
}
|
||||
setText(QString::fromStdString(value_));
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include "Engine/Parameter/Parameter.h"
|
||||
|
||||
namespace enzo::ui
|
||||
{
|
||||
@@ -13,9 +14,10 @@ class StringParm
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
StringParm(bt::String value, QWidget *parent = nullptr);
|
||||
StringParm(std::weak_ptr<prm::Parameter>, QWidget *parent = nullptr);
|
||||
void setValue(bt::String value);
|
||||
void setValueQString(QString value);
|
||||
std::weak_ptr<prm::Parameter> parameter_;
|
||||
|
||||
Q_SIGNALS:
|
||||
void valueChanged(bt::String value);
|
||||
|
||||
@@ -20,6 +20,7 @@ add_library(${libName} SHARED
|
||||
../Engine/Parameter/Template.cpp
|
||||
../Engine/Parameter/Parameter.cpp
|
||||
../Engine/Parameter/Default.cpp
|
||||
../Engine/Parameter/Range.cpp
|
||||
GopTransform.cpp
|
||||
GopHouse.cpp
|
||||
GopTestCube.cpp
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "OpDefs/GopGrid.h"
|
||||
#include "Engine/Parameter/Range.h"
|
||||
#include "Engine/Types.h"
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
@@ -25,30 +26,46 @@ void GopGrid::cookOp(enzo::op::Context context)
|
||||
|
||||
const bt::intT columns = context.evalFloatParm("columns");
|
||||
const bt::intT rows = context.evalFloatParm("rows");
|
||||
if(columns==0 || rows==0)
|
||||
if(columns<=0 || rows<=0)
|
||||
{
|
||||
setOutputGeometry(0, geo);
|
||||
return;
|
||||
}
|
||||
|
||||
const bt::floatT centerOffsetX = (columns-1)*width/2.0;
|
||||
const bt::floatT centerOffsetY = (rows-1)*height/2.0;
|
||||
const bt::floatT centerOffsetX = width/2.0;
|
||||
const bt::floatT centerOffsetY = height/2.0;
|
||||
|
||||
// add points
|
||||
for(int i=0;i<columns;i++)
|
||||
{
|
||||
for(int j=0;j<rows;++j)
|
||||
{
|
||||
geo.addPoint(bt::Vector3(i*width-centerOffsetX, sin(i+j), j*height-centerOffsetY));
|
||||
const bt::floatT x = static_cast<bt::floatT>(i)/columns*width-centerOffsetX;
|
||||
const bt::floatT z = static_cast<bt::floatT>(j)/rows*height-centerOffsetY;
|
||||
geo.addPoint(bt::Vector3(x, sin(i+j)/10, z));
|
||||
}
|
||||
}
|
||||
|
||||
for(int i=0;i<std::floor((columns-1)*(rows)-1);i++)
|
||||
if(columns > 1 && rows > 1)
|
||||
{
|
||||
const int endOffset = (i+1)%rows==0;
|
||||
const ga::Offset startPt = i+endOffset;
|
||||
geo.addFace(startPt,startPt+rows,startPt+rows+1,startPt+1);
|
||||
// add faces
|
||||
for(int i=0;i<std::floor((columns-1)*(rows)-1);i++)
|
||||
{
|
||||
const int endOffset = (i+1)%rows==0;
|
||||
const ga::Offset startPt = i+endOffset;
|
||||
geo.addFace({startPt,startPt+rows,startPt+rows+1,startPt+1});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// add lines
|
||||
const size_t iterationLimit = std::max(columns, rows)-1;
|
||||
for(int i=0;i<iterationLimit;i++)
|
||||
{
|
||||
const ga::Offset startPt = i;
|
||||
geo.addFace({startPt,startPt+1}, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setOutputGeometry(0, geo);
|
||||
}
|
||||
@@ -57,9 +74,21 @@ void GopGrid::cookOp(enzo::op::Context context)
|
||||
|
||||
enzo::prm::Template GopGrid::parameterList[] =
|
||||
{
|
||||
enzo::prm::Template(enzo::prm::Type::FLOAT, "width", enzo::prm::Default(1)),
|
||||
enzo::prm::Template(enzo::prm::Type::FLOAT, "height", enzo::prm::Default(1)),
|
||||
enzo::prm::Template(enzo::prm::Type::INT, "rows", enzo::prm::Default(10)),
|
||||
enzo::prm::Template(enzo::prm::Type::INT, "columns", enzo::prm::Default(10)),
|
||||
enzo::prm::Template(enzo::prm::Type::FLOAT, "width", enzo::prm::Default(10), 1, enzo::prm::Range(0, enzo::prm::RangeFlag::UNLOCKED, 100, enzo::prm::RangeFlag::UNLOCKED)),
|
||||
enzo::prm::Template(enzo::prm::Type::FLOAT, "height", enzo::prm::Default(10), 1, enzo::prm::Range(0, enzo::prm::RangeFlag::UNLOCKED, 100, enzo::prm::RangeFlag::UNLOCKED)),
|
||||
enzo::prm::Template(
|
||||
enzo::prm::Type::INT,
|
||||
"rows",
|
||||
enzo::prm::Default(10),
|
||||
1,
|
||||
enzo::prm::Range(0, enzo::prm::RangeFlag::LOCKED, 100, enzo::prm::RangeFlag::UNLOCKED)
|
||||
),
|
||||
enzo::prm::Template(
|
||||
enzo::prm::Type::INT,
|
||||
"columns",
|
||||
enzo::prm::Default(10),
|
||||
1,
|
||||
enzo::prm::Range(0, enzo::prm::RangeFlag::LOCKED, 100, enzo::prm::RangeFlag::UNLOCKED)
|
||||
),
|
||||
enzo::prm::Terminator
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user