mirror of
https://github.com/Armored-Dragon/overte.git
synced 2025-03-11 16:13:16 +01:00
clean up reticle scripting to be exposed through Reticle object (implemented int ApplicationCompositor)
This commit is contained in:
parent
0d873c7732
commit
a25581c656
12 changed files with 92 additions and 141 deletions
|
@ -724,7 +724,7 @@ function MyController(hand) {
|
|||
}
|
||||
this.searchSphereOff();
|
||||
|
||||
Controller.setReticleVisible(true);
|
||||
Reticle.setVisible(true);
|
||||
|
||||
};
|
||||
|
||||
|
@ -1023,7 +1023,7 @@ function MyController(hand) {
|
|||
(this.triggerSmoothedGrab() || this.bumperSqueezed()) ? INTERSECT_COLOR : NO_INTERSECT_COLOR);
|
||||
}
|
||||
|
||||
Controller.setReticleVisible(false);
|
||||
Reticle.setVisible(false);
|
||||
|
||||
};
|
||||
|
||||
|
@ -1886,7 +1886,7 @@ function cleanup() {
|
|||
rightController.cleanup();
|
||||
leftController.cleanup();
|
||||
Controller.disableMapping(MAPPING_NAME);
|
||||
Controller.setReticleVisible(true);
|
||||
Reticle.setVisible(true);
|
||||
}
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
Script.update.connect(update);
|
||||
|
|
|
@ -24,9 +24,9 @@ var PITCH_SCALING = 10.0;
|
|||
var YAW_SCALING = 10.0;
|
||||
|
||||
var EXPECTED_CHANGE = 50;
|
||||
var lastPos = Controller.getReticlePosition();
|
||||
var lastPos = Reticle.getPosition();
|
||||
function moveReticle(dY, dX) {
|
||||
var globalPos = Controller.getReticlePosition();
|
||||
var globalPos = Reticle.getPosition();
|
||||
|
||||
// some debugging to see if position is jumping around on us...
|
||||
var distanceSinceLastMove = length(lastPos, globalPos);
|
||||
|
@ -45,7 +45,7 @@ function moveReticle(dY, dX) {
|
|||
|
||||
globalPos.x += dX;
|
||||
globalPos.y += dY;
|
||||
Controller.setReticlePosition(globalPos);
|
||||
Reticle.setPosition(globalPos);
|
||||
lastPos = globalPos;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,9 +31,9 @@ function length(posA, posB) {
|
|||
}
|
||||
|
||||
var EXPECTED_CHANGE = 50;
|
||||
var lastPos = Controller.getReticlePosition();
|
||||
var lastPos = Reticle.getPosition();
|
||||
function moveReticle(dX, dY) {
|
||||
var globalPos = Controller.getReticlePosition();
|
||||
var globalPos = Reticle.getPosition();
|
||||
|
||||
// some debugging to see if position is jumping around on us...
|
||||
var distanceSinceLastMove = length(lastPos, globalPos);
|
||||
|
@ -52,7 +52,7 @@ function moveReticle(dX, dY) {
|
|||
|
||||
globalPos.x += dX;
|
||||
globalPos.y += dY;
|
||||
Controller.setReticlePosition(globalPos);
|
||||
Reticle.setPosition(globalPos);
|
||||
lastPos = globalPos;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,10 +23,10 @@ function length(posA, posB) {
|
|||
}
|
||||
|
||||
function moveReticleAbsolute(x, y) {
|
||||
var globalPos = Controller.getReticlePosition();
|
||||
var globalPos = Reticle.getPosition();
|
||||
globalPos.x = x;
|
||||
globalPos.y = y;
|
||||
Controller.setReticlePosition(globalPos);
|
||||
Reticle.setPosition(globalPos);
|
||||
}
|
||||
|
||||
var MAPPING_NAME = "com.highfidelity.testing.reticleWithHandRotation";
|
||||
|
|
|
@ -806,14 +806,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
} else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) {
|
||||
cycleCamera();
|
||||
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
|
||||
auto reticlePosition = _controllerScriptingInterface->getReticlePosition();
|
||||
auto reticlePosition = _compositor.getReticlePosition();
|
||||
offscreenUi->toggleMenu(_glWidget->mapFromGlobal(QPoint(reticlePosition.x, reticlePosition.y)));
|
||||
} else if (action == controller::toInt(controller::Action::RETICLE_X)) {
|
||||
auto oldPos = _controllerScriptingInterface->getReticlePosition();
|
||||
_controllerScriptingInterface->setReticlePosition({ oldPos.x + state, oldPos.y });
|
||||
auto oldPos = _compositor.getReticlePosition();
|
||||
_compositor.setReticlePosition({ oldPos.x + state, oldPos.y });
|
||||
} else if (action == controller::toInt(controller::Action::RETICLE_Y)) {
|
||||
auto oldPos = _controllerScriptingInterface->getReticlePosition();
|
||||
_controllerScriptingInterface->setReticlePosition({ oldPos.x, oldPos.y + state });
|
||||
auto oldPos = _compositor.getReticlePosition();
|
||||
_compositor.setReticlePosition({ oldPos.x, oldPos.y + state });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1254,6 +1254,7 @@ void Application::initializeUi() {
|
|||
rootContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
|
||||
rootContext->setContextProperty("Scene", DependencyManager::get<SceneScriptingInterface>().data());
|
||||
rootContext->setContextProperty("Render", _renderEngine->getConfiguration().get());
|
||||
rootContext->setContextProperty("Reticle", _compositor.getReticleInterface());
|
||||
|
||||
_glWidget->installEventFilter(offscreenUi.data());
|
||||
offscreenUi->setMouseTranslator([=](const QPointF& pt) {
|
||||
|
@ -2085,7 +2086,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
void Application::keyReleaseEvent(QKeyEvent* event) {
|
||||
if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto reticlePosition = _controllerScriptingInterface->getReticlePosition();
|
||||
auto reticlePosition = _compositor.getReticlePosition();
|
||||
offscreenUi->toggleMenu(_glWidget->mapFromGlobal(QPoint(reticlePosition.x, reticlePosition.y)));
|
||||
}
|
||||
|
||||
|
@ -2542,7 +2543,7 @@ void Application::setLowVelocityFilter(bool lowVelocityFilter) {
|
|||
controller::InputDevice::setLowVelocityFilter(lowVelocityFilter);
|
||||
}
|
||||
|
||||
ivec2 Application::getMouse() const {
|
||||
ivec2 Application::getMouse() {
|
||||
if (isHMDMode()) {
|
||||
return _compositor.screenToOverlay(getTrueMouse());
|
||||
}
|
||||
|
@ -3566,7 +3567,7 @@ glm::vec3 Application::getSunDirection() {
|
|||
// FIXME, preprocessor guard this check to occur only in DEBUG builds
|
||||
static QThread * activeRenderingThread = nullptr;
|
||||
|
||||
PickRay Application::computePickRay(float x, float y) const {
|
||||
PickRay Application::computePickRay(float x, float y) {
|
||||
vec2 pickPoint { x, y };
|
||||
PickRay result;
|
||||
if (isHMDMode()) {
|
||||
|
@ -4216,6 +4217,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
scriptEngine->registerGlobalObject("Render", _renderEngine->getConfiguration().get());
|
||||
|
||||
scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
|
||||
scriptEngine->registerGlobalObject("Reticle", _compositor.getReticleInterface());
|
||||
}
|
||||
|
||||
bool Application::canAcceptURL(const QString& urlString) const {
|
||||
|
@ -4695,7 +4697,7 @@ QSize Application::getDeviceSize() const {
|
|||
return fromGlm(getActiveDisplayPlugin()->getRecommendedRenderSize());
|
||||
}
|
||||
|
||||
PickRay Application::computePickRay() const {
|
||||
PickRay Application::computePickRay() {
|
||||
return computePickRay(getTrueMouse().x, getTrueMouse().y);
|
||||
}
|
||||
|
||||
|
@ -4703,9 +4705,9 @@ bool Application::isThrottleRendering() const {
|
|||
return getActiveDisplayPlugin()->isThrottled();
|
||||
}
|
||||
|
||||
// FIXME -- consolidate users of getTrueMouse() controllerScriptingInterface->getReticlePosition()
|
||||
ivec2 Application::getTrueMouse() const {
|
||||
auto reticlePosition = _controllerScriptingInterface->getReticlePosition();
|
||||
// FIXME -- consolidate users of getTrueMouse() _compositor.getReticlePosition()
|
||||
ivec2 Application::getTrueMouse() {
|
||||
auto reticlePosition = _compositor.getReticlePosition();
|
||||
return toGlm(_glWidget->mapFromGlobal(QPoint(reticlePosition.x, reticlePosition.y)));
|
||||
}
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@ public:
|
|||
glm::uvec2 getUiSize() const;
|
||||
QSize getDeviceSize() const;
|
||||
bool hasFocus() const;
|
||||
PickRay computePickRay() const;
|
||||
PickRay computePickRay();
|
||||
|
||||
bool isThrottleRendering() const;
|
||||
|
||||
|
@ -139,8 +139,8 @@ public:
|
|||
EntityTreeRenderer* getEntityClipboardRenderer() { return &_entityClipboardRenderer; }
|
||||
EntityEditPacketSender* getEntityEditPacketSender() { return &_entityEditSender; }
|
||||
|
||||
ivec2 getMouse() const;
|
||||
ivec2 getTrueMouse() const;
|
||||
ivec2 getMouse();
|
||||
ivec2 getTrueMouse();
|
||||
|
||||
FaceTracker* getActiveFaceTracker();
|
||||
FaceTracker* getSelectedFaceTracker();
|
||||
|
@ -169,7 +169,7 @@ public:
|
|||
|
||||
virtual ViewFrustum* getCurrentViewFrustum() override { return getDisplayViewFrustum(); }
|
||||
virtual QThread* getMainThread() override { return thread(); }
|
||||
virtual PickRay computePickRay(float x, float y) const override;
|
||||
virtual PickRay computePickRay(float x, float y) override;
|
||||
virtual glm::vec3 getAvatarPosition() const override;
|
||||
virtual qreal getDevicePixelRatio() override;
|
||||
|
||||
|
|
|
@ -112,7 +112,9 @@ bool raySphereIntersect(const glm::vec3 &dir, const glm::vec3 &origin, float r,
|
|||
}
|
||||
|
||||
ApplicationCompositor::ApplicationCompositor() :
|
||||
_alphaPropertyAnimation(new QPropertyAnimation(this, "alpha"))
|
||||
_alphaPropertyAnimation(new QPropertyAnimation(this, "alpha")),
|
||||
_reticleInterface(new ReticleInterface(this))
|
||||
|
||||
{
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
|
||||
|
@ -282,15 +284,13 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int
|
|||
bindCursorTexture(batch);
|
||||
|
||||
//Mouse Pointer
|
||||
auto controllerScriptingInterface = DependencyManager::get<controller::ScriptingInterface>();
|
||||
bool reticleVisible = controllerScriptingInterface->getReticleVisible();
|
||||
if (reticleVisible) {
|
||||
if (getReticleVisible()) {
|
||||
glm::mat4 overlayXfm;
|
||||
_modelTransform.getMatrix(overlayXfm);
|
||||
|
||||
glm::vec2 projection = screenToSpherical(qApp->getTrueMouse());
|
||||
|
||||
float cursorDepth = controllerScriptingInterface->getReticleDepth();
|
||||
float cursorDepth = getReticleDepth();
|
||||
mat4 pointerXfm = glm::scale(mat4(), vec3(cursorDepth)) * glm::mat4_cast(quat(vec3(-projection.y, projection.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1));
|
||||
mat4 reticleXfm = overlayXfm * pointerXfm;
|
||||
reticleXfm = glm::scale(reticleXfm, reticleScale);
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#ifndef hifi_ApplicationCompositor_h
|
||||
#define hifi_ApplicationCompositor_h
|
||||
|
||||
#include <QCursor>
|
||||
#include <QObject>
|
||||
#include <QPropertyAnimation>
|
||||
#include <cstdint>
|
||||
|
@ -22,6 +23,7 @@
|
|||
class Camera;
|
||||
class PalmData;
|
||||
class RenderArgs;
|
||||
class ReticleInterface;
|
||||
|
||||
const float MAGNIFY_WIDTH = 220.0f;
|
||||
const float MAGNIFY_HEIGHT = 100.0f;
|
||||
|
@ -80,6 +82,31 @@ public:
|
|||
static glm::vec2 screenToSpherical(const glm::vec2 & screenPos);
|
||||
static glm::vec2 sphericalToScreen(const glm::vec2 & sphericalPos);
|
||||
|
||||
Q_INVOKABLE bool getReticleVisible() { return _reticleVisible; }
|
||||
Q_INVOKABLE void setReticleVisible(bool visible) { _reticleVisible = visible; }
|
||||
|
||||
Q_INVOKABLE float getReticleDepth() { return _reticleDepth; }
|
||||
Q_INVOKABLE void setReticleDepth(float depth) { _reticleDepth = depth; }
|
||||
|
||||
Q_INVOKABLE glm::vec2 getReticlePosition() {
|
||||
return toGlm(QCursor::pos());
|
||||
}
|
||||
Q_INVOKABLE void setReticlePosition(glm::vec2 position) {
|
||||
// NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,
|
||||
// remove it after we're done
|
||||
const float REASONABLE_CHANGE = 50.0f;
|
||||
glm::vec2 oldPos = toGlm(QCursor::pos());
|
||||
auto distance = glm::distance(oldPos, position);
|
||||
if (distance > REASONABLE_CHANGE) {
|
||||
qDebug() << "Contrller::ScriptingInterface ---- UNREASONABLE CHANGE! distance:" << distance << " oldPos:" << oldPos << " newPos:" << position;
|
||||
}
|
||||
|
||||
QCursor::setPos(position.x, position.y);
|
||||
}
|
||||
|
||||
ReticleInterface* getReticleInterface() { return _reticleInterface; }
|
||||
|
||||
|
||||
private:
|
||||
void displayOverlayTextureStereo(RenderArgs* renderArgs, float aspectRatio, float fov);
|
||||
void bindCursorTexture(gpu::Batch& batch, uint8_t cursorId = 0);
|
||||
|
@ -115,6 +142,35 @@ private:
|
|||
Transform _cameraBaseTransform;
|
||||
|
||||
std::unique_ptr<QPropertyAnimation> _alphaPropertyAnimation;
|
||||
|
||||
bool _reticleVisible { true };
|
||||
float _reticleDepth { 1.0f };
|
||||
|
||||
ReticleInterface* _reticleInterface;
|
||||
|
||||
};
|
||||
|
||||
// Scripting interface available to control the Reticle
|
||||
class ReticleInterface : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition)
|
||||
Q_PROPERTY(bool visible READ getVisible WRITE setVisible)
|
||||
Q_PROPERTY(float depth READ getDepth WRITE setDepth)
|
||||
public:
|
||||
ReticleInterface(ApplicationCompositor* outer) : _compositor(outer), QObject(outer) { }
|
||||
|
||||
Q_INVOKABLE bool getVisible() { return _compositor->getReticleVisible(); }
|
||||
Q_INVOKABLE void setVisible(bool visible) { _compositor->setReticleVisible(visible); }
|
||||
|
||||
Q_INVOKABLE float getDepth() { return _compositor->getReticleDepth(); }
|
||||
Q_INVOKABLE void setDepth(float depth) { _compositor->setReticleDepth(depth); }
|
||||
|
||||
Q_INVOKABLE glm::vec2 getPosition() { return _compositor->getReticlePosition(); }
|
||||
Q_INVOKABLE void setPosition(glm::vec2 position) { _compositor->setReticlePosition(position); }
|
||||
private:
|
||||
ApplicationCompositor* _compositor;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // hifi_ApplicationCompositor_h
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <gpu/Texture.h>
|
||||
|
||||
|
||||
// Handles the drawing of the overlays to the screen
|
||||
// TODO, move divide up the rendering, displaying and input handling
|
||||
// facilities of this class
|
||||
|
|
|
@ -106,71 +106,6 @@ namespace controller {
|
|||
return getPoseValue(Input(device, source, ChannelType::POSE).getID());
|
||||
}
|
||||
|
||||
//bool ScriptingInterface::isPrimaryButtonPressed() const {
|
||||
// return isButtonPressed(StandardButtonChannel::A);
|
||||
//}
|
||||
//
|
||||
//glm::vec2 ScriptingInterface::getPrimaryJoystickPosition() const {
|
||||
// return getJoystickPosition(0);
|
||||
//}
|
||||
|
||||
//int ScriptingInterface::getNumberOfButtons() const {
|
||||
// return StandardButtonChannel::NUM_STANDARD_BUTTONS;
|
||||
//}
|
||||
|
||||
//bool ScriptingInterface::isButtonPressed(int buttonIndex) const {
|
||||
// return getButtonValue((StandardButtonChannel)buttonIndex) == 0.0 ? false : true;
|
||||
//}
|
||||
|
||||
//int ScriptingInterface::getNumberOfTriggers() const {
|
||||
// return StandardCounts::TRIGGERS;
|
||||
//}
|
||||
|
||||
//float ScriptingInterface::getTriggerValue(int triggerIndex) const {
|
||||
// return getAxisValue(triggerIndex == 0 ? StandardAxisChannel::LT : StandardAxisChannel::RT);
|
||||
//}
|
||||
|
||||
//int ScriptingInterface::getNumberOfJoysticks() const {
|
||||
// return StandardCounts::ANALOG_STICKS;
|
||||
//}
|
||||
|
||||
//glm::vec2 ScriptingInterface::getJoystickPosition(int joystickIndex) const {
|
||||
// StandardAxisChannel xid = StandardAxisChannel::LX;
|
||||
// StandardAxisChannel yid = StandardAxisChannel::LY;
|
||||
// if (joystickIndex != 0) {
|
||||
// xid = StandardAxisChannel::RX;
|
||||
// yid = StandardAxisChannel::RY;
|
||||
// }
|
||||
// vec2 result;
|
||||
// result.x = getAxisValue(xid);
|
||||
// result.y = getAxisValue(yid);
|
||||
// return result;
|
||||
//}
|
||||
|
||||
//int ScriptingInterface::getNumberOfSpatialControls() const {
|
||||
// return StandardCounts::POSES;
|
||||
//}
|
||||
|
||||
//glm::vec3 ScriptingInterface::getSpatialControlPosition(int controlIndex) const {
|
||||
// // FIXME extract the position from the standard pose
|
||||
// return vec3();
|
||||
//}
|
||||
|
||||
//glm::vec3 ScriptingInterface::getSpatialControlVelocity(int controlIndex) const {
|
||||
// // FIXME extract the velocity from the standard pose
|
||||
// return vec3();
|
||||
//}
|
||||
|
||||
//glm::vec3 ScriptingInterface::getSpatialControlNormal(int controlIndex) const {
|
||||
// // FIXME extract the normal from the standard pose
|
||||
// return vec3();
|
||||
//}
|
||||
//
|
||||
//glm::quat ScriptingInterface::getSpatialControlRawRotation(int controlIndex) const {
|
||||
// // FIXME extract the rotation from the standard pose
|
||||
// return quat();
|
||||
//}
|
||||
|
||||
QVector<Action> ScriptingInterface::getAllActions() {
|
||||
return DependencyManager::get<UserInputMapper>()->getAllActions();
|
||||
}
|
||||
|
|
|
@ -90,46 +90,6 @@ namespace controller {
|
|||
Q_INVOKABLE QObject* parseMapping(const QString& json);
|
||||
Q_INVOKABLE QObject* loadMapping(const QString& jsonUrl);
|
||||
|
||||
Q_INVOKABLE bool getReticleVisible() { return _reticleVisible; }
|
||||
Q_INVOKABLE void setReticleVisible(bool visible) { _reticleVisible = visible; }
|
||||
|
||||
Q_INVOKABLE float getReticleDepth() { return _reticleDepth; }
|
||||
Q_INVOKABLE void setReticleDepth(float depth) { _reticleDepth = depth; }
|
||||
|
||||
Q_INVOKABLE glm::vec2 getReticlePosition() {
|
||||
return toGlm(QCursor::pos());
|
||||
}
|
||||
Q_INVOKABLE void setReticlePosition(glm::vec2 position) {
|
||||
// NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,
|
||||
// remove it after we're done
|
||||
const float REASONABLE_CHANGE = 50.0f;
|
||||
glm::vec2 oldPos = toGlm(QCursor::pos());
|
||||
auto distance = glm::distance(oldPos, position);
|
||||
if (distance > REASONABLE_CHANGE) {
|
||||
qDebug() << "Contrller::ScriptingInterface ---- UNREASONABLE CHANGE! distance:" << distance << " oldPos:" << oldPos << " newPos:" << position;
|
||||
}
|
||||
|
||||
QCursor::setPos(position.x, position.y);
|
||||
}
|
||||
|
||||
//Q_INVOKABLE bool isPrimaryButtonPressed() const;
|
||||
//Q_INVOKABLE glm::vec2 getPrimaryJoystickPosition() const;
|
||||
|
||||
//Q_INVOKABLE int getNumberOfButtons() const;
|
||||
//Q_INVOKABLE bool isButtonPressed(int buttonIndex) const;
|
||||
|
||||
//Q_INVOKABLE int getNumberOfTriggers() const;
|
||||
//Q_INVOKABLE float getTriggerValue(int triggerIndex) const;
|
||||
|
||||
//Q_INVOKABLE int getNumberOfJoysticks() const;
|
||||
//Q_INVOKABLE glm::vec2 getJoystickPosition(int joystickIndex) const;
|
||||
|
||||
//Q_INVOKABLE int getNumberOfSpatialControls() const;
|
||||
//Q_INVOKABLE glm::vec3 getSpatialControlPosition(int controlIndex) const;
|
||||
//Q_INVOKABLE glm::vec3 getSpatialControlVelocity(int controlIndex) const;
|
||||
//Q_INVOKABLE glm::vec3 getSpatialControlNormal(int controlIndex) const;
|
||||
//Q_INVOKABLE glm::quat getSpatialControlRawRotation(int controlIndex) const;
|
||||
|
||||
Q_INVOKABLE const QVariantMap& getHardware() { return _hardware; }
|
||||
Q_INVOKABLE const QVariantMap& getActions() { return _actions; }
|
||||
Q_INVOKABLE const QVariantMap& getStandard() { return _standard; }
|
||||
|
@ -170,9 +130,6 @@ namespace controller {
|
|||
bool _touchCaptured{ false };
|
||||
bool _wheelCaptured{ false };
|
||||
bool _actionsCaptured{ false };
|
||||
|
||||
bool _reticleVisible{ true };
|
||||
float _reticleDepth { 1.0f };
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ public:
|
|||
|
||||
virtual QThread* getMainThread() = 0;
|
||||
|
||||
virtual PickRay computePickRay(float x, float y) const = 0;
|
||||
virtual PickRay computePickRay(float x, float y) = 0;
|
||||
|
||||
virtual glm::vec3 getAvatarPosition() const = 0;
|
||||
|
||||
|
|
Loading…
Reference in a new issue