2024-02-19 00:08:03 +01:00
|
|
|
#include "PasswordInputField.hpp"
|
|
|
|
#include "../Renderer.hpp"
|
|
|
|
#include "../../core/hyprlock.hpp"
|
|
|
|
#include <algorithm>
|
|
|
|
|
2024-02-20 15:42:04 +01:00
|
|
|
CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::unordered_map<std::string, std::any>& props) {
|
|
|
|
size = std::any_cast<Hyprlang::VEC2>(props.at("size"));
|
|
|
|
inner = std::any_cast<Hyprlang::INT>(props.at("inner_color"));
|
|
|
|
outer = std::any_cast<Hyprlang::INT>(props.at("outer_color"));
|
|
|
|
out_thick = std::any_cast<Hyprlang::INT>(props.at("outline_thickness"));
|
2024-02-20 18:12:43 +01:00
|
|
|
dt_size = std::any_cast<Hyprlang::FLOAT>(props.at("dots_size"));
|
|
|
|
dt_space = std::any_cast<Hyprlang::FLOAT>(props.at("dots_spacing"));
|
2024-02-21 12:42:18 +01:00
|
|
|
dots.center = std::any_cast<Hyprlang::INT>(props.at("dots_center"));
|
2024-02-20 15:42:04 +01:00
|
|
|
fadeOnEmpty = std::any_cast<Hyprlang::INT>(props.at("fade_on_empty"));
|
|
|
|
font = std::any_cast<Hyprlang::INT>(props.at("font_color"));
|
|
|
|
pos = std::any_cast<Hyprlang::VEC2>(props.at("position"));
|
|
|
|
hiddenInputState.enabled = std::any_cast<Hyprlang::INT>(props.at("hide_input"));
|
|
|
|
viewport = viewport_;
|
2024-02-19 23:45:59 +01:00
|
|
|
|
2024-02-20 18:12:43 +01:00
|
|
|
pos = posFromHVAlign(viewport, size, pos, std::any_cast<Hyprlang::STRING>(props.at("halign")), std::any_cast<Hyprlang::STRING>(props.at("valign")));
|
|
|
|
dt_size = std::clamp(dt_size, 0.2f, 0.8f);
|
|
|
|
dt_space = std::clamp(dt_space, 0.f, 1.f);
|
2024-02-19 23:58:59 +01:00
|
|
|
|
|
|
|
std::string placeholderText = std::any_cast<Hyprlang::STRING>(props.at("placeholder_text"));
|
|
|
|
if (!placeholderText.empty()) {
|
|
|
|
placeholder.resourceID = "placeholder:" + std::to_string((uintptr_t)this);
|
|
|
|
CAsyncResourceGatherer::SPreloadRequest request;
|
|
|
|
request.id = placeholder.resourceID;
|
|
|
|
request.asset = placeholderText;
|
|
|
|
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
|
|
|
|
request.props["font_family"] = std::string{"Sans"};
|
|
|
|
request.props["color"] = CColor{1.0 - font.r, 1.0 - font.g, 1.0 - font.b, 0.5};
|
2024-02-20 01:53:49 +01:00
|
|
|
request.props["font_size"] = (int)size.y / 4;
|
2024-02-19 23:58:59 +01:00
|
|
|
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
|
|
|
|
}
|
2024-02-19 02:22:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CPasswordInputField::updateFade() {
|
|
|
|
const auto PASSLEN = g_pHyprlock->getPasswordBufferLen();
|
|
|
|
|
|
|
|
if (!fadeOnEmpty) {
|
|
|
|
fade.a = 1.0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (PASSLEN == 0 && fade.a != 0.0 && (!fade.animated || fade.appearing)) {
|
|
|
|
fade.a = 1.0;
|
|
|
|
fade.animated = true;
|
|
|
|
fade.appearing = false;
|
|
|
|
fade.start = std::chrono::system_clock::now();
|
|
|
|
} else if (PASSLEN > 0 && fade.a != 1.0 && (!fade.animated || !fade.appearing)) {
|
|
|
|
fade.a = 0.0;
|
|
|
|
fade.animated = true;
|
|
|
|
fade.appearing = true;
|
|
|
|
fade.start = std::chrono::system_clock::now();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fade.animated) {
|
|
|
|
if (fade.appearing)
|
|
|
|
fade.a = std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - fade.start).count() / 100000.0, 0.0, 1.0);
|
|
|
|
else
|
|
|
|
fade.a = std::clamp(1.0 - std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - fade.start).count() / 100000.0, 0.0, 1.0);
|
|
|
|
|
|
|
|
if ((fade.appearing && fade.a == 1.0) || (!fade.appearing && fade.a == 0.0))
|
|
|
|
fade.animated = false;
|
|
|
|
}
|
2024-02-19 00:08:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CPasswordInputField::updateDots() {
|
|
|
|
const auto PASSLEN = g_pHyprlock->getPasswordBufferLen();
|
|
|
|
|
2024-02-19 22:09:00 +01:00
|
|
|
if (PASSLEN == dots.currentAmount)
|
|
|
|
return;
|
|
|
|
|
2024-02-20 03:47:03 +01:00
|
|
|
if (std::abs(PASSLEN - dots.currentAmount) > 1) {
|
|
|
|
dots.currentAmount = std::clamp(dots.currentAmount, PASSLEN - 1.f, PASSLEN + 1.f);
|
|
|
|
dots.lastFrame = std::chrono::system_clock::now();
|
|
|
|
}
|
|
|
|
|
2024-02-29 15:34:27 +01:00
|
|
|
if (PASSLEN == 0 && !placeholder.failID.empty()) {
|
|
|
|
dots.currentAmount = PASSLEN;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-02-20 03:47:03 +01:00
|
|
|
const auto DELTA = std::clamp((int)std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - dots.lastFrame).count(), 0, 20000);
|
2024-02-19 22:09:00 +01:00
|
|
|
|
2024-02-20 03:47:03 +01:00
|
|
|
const float TOADD = DELTA / 1000000.0 * dots.speedPerSecond;
|
2024-02-19 00:08:03 +01:00
|
|
|
|
2024-02-19 22:09:00 +01:00
|
|
|
if (PASSLEN > dots.currentAmount) {
|
|
|
|
dots.currentAmount += TOADD;
|
|
|
|
if (dots.currentAmount > PASSLEN)
|
|
|
|
dots.currentAmount = PASSLEN;
|
|
|
|
} else if (PASSLEN < dots.currentAmount) {
|
|
|
|
dots.currentAmount -= TOADD;
|
|
|
|
if (dots.currentAmount < PASSLEN)
|
|
|
|
dots.currentAmount = PASSLEN;
|
2024-02-19 00:08:03 +01:00
|
|
|
}
|
|
|
|
|
2024-02-19 22:09:00 +01:00
|
|
|
dots.lastFrame = std::chrono::system_clock::now();
|
2024-02-19 00:08:03 +01:00
|
|
|
}
|
|
|
|
|
2024-02-19 17:26:08 +01:00
|
|
|
bool CPasswordInputField::draw(const SRenderData& data) {
|
2024-02-19 00:08:03 +01:00
|
|
|
CBox inputFieldBox = {pos, size};
|
|
|
|
CBox outerBox = {pos - Vector2D{out_thick, out_thick}, size + Vector2D{out_thick * 2, out_thick * 2}};
|
|
|
|
|
2024-02-20 01:53:49 +01:00
|
|
|
bool forceReload = false;
|
|
|
|
|
2024-02-19 02:22:22 +01:00
|
|
|
updateFade();
|
2024-02-19 00:08:03 +01:00
|
|
|
updateDots();
|
2024-02-20 01:53:49 +01:00
|
|
|
updateFailTex();
|
2024-02-20 15:42:04 +01:00
|
|
|
updateHiddenInputState();
|
2024-02-20 01:53:49 +01:00
|
|
|
|
|
|
|
float passAlpha = g_pHyprlock->passwordCheckWaiting() ? 0.5 : 1.0;
|
2024-02-19 00:08:03 +01:00
|
|
|
|
2024-02-19 02:22:22 +01:00
|
|
|
CColor outerCol = outer;
|
2024-02-20 17:34:59 +01:00
|
|
|
outerCol.a *= fade.a * data.opacity;
|
2024-02-19 02:22:22 +01:00
|
|
|
CColor innerCol = inner;
|
2024-02-20 17:29:23 +01:00
|
|
|
innerCol.a *= fade.a * data.opacity;
|
|
|
|
CColor fontCol = font;
|
2024-02-20 01:53:49 +01:00
|
|
|
fontCol.a *= fade.a * data.opacity * passAlpha;
|
2024-02-19 02:22:22 +01:00
|
|
|
|
|
|
|
g_pRenderer->renderRect(outerBox, outerCol, outerBox.h / 2.0);
|
2024-02-20 15:42:04 +01:00
|
|
|
|
|
|
|
const auto PASSLEN = g_pHyprlock->getPasswordBufferLen();
|
|
|
|
|
|
|
|
if (PASSLEN != 0 && hiddenInputState.enabled) {
|
|
|
|
CBox outerBoxScaled = outerBox;
|
|
|
|
Vector2D p = outerBox.pos();
|
|
|
|
outerBoxScaled.translate(-p).scale(0.5).translate(p);
|
|
|
|
if (hiddenInputState.lastQuadrant > 1)
|
|
|
|
outerBoxScaled.y += outerBoxScaled.h;
|
|
|
|
if (hiddenInputState.lastQuadrant % 2 == 1)
|
|
|
|
outerBoxScaled.x += outerBoxScaled.w;
|
|
|
|
glEnable(GL_SCISSOR_TEST);
|
|
|
|
glScissor(outerBoxScaled.x, outerBoxScaled.y, outerBoxScaled.w, outerBoxScaled.h);
|
|
|
|
g_pRenderer->renderRect(outerBox, hiddenInputState.lastColor, outerBox.h / 2.0);
|
|
|
|
glScissor(0, 0, viewport.x, viewport.y);
|
|
|
|
glDisable(GL_SCISSOR_TEST);
|
|
|
|
}
|
|
|
|
|
2024-02-19 02:22:22 +01:00
|
|
|
g_pRenderer->renderRect(inputFieldBox, innerCol, inputFieldBox.h / 2.0);
|
2024-02-19 00:08:03 +01:00
|
|
|
|
2024-02-26 01:33:19 +01:00
|
|
|
const int PASS_SIZE = std::nearbyint(inputFieldBox.h * dt_size * 0.5f) * 2.f;
|
|
|
|
const int PASS_SPACING = std::floor(PASS_SIZE * dt_space);
|
|
|
|
const int DOT_PAD = (inputFieldBox.h - PASS_SIZE) / 2;
|
|
|
|
const int DOT_AREA_WIDTH = inputFieldBox.w - DOT_PAD * 2; // avail width for dots
|
|
|
|
const int MAX_DOTS = std::round(DOT_AREA_WIDTH * 1.0 / (PASS_SIZE + PASS_SPACING)); // max amount of dots that can fit in the area
|
|
|
|
const int DOT_FLOORED = std::floor(dots.currentAmount);
|
|
|
|
const float DOT_ALPHA = fontCol.a;
|
2024-02-24 15:14:10 +01:00
|
|
|
// Calculate the total width required for all dots including spaces between them
|
|
|
|
const int TOTAL_DOTS_WIDTH = (PASS_SIZE + PASS_SPACING) * dots.currentAmount - PASS_SPACING;
|
2024-02-19 00:08:03 +01:00
|
|
|
|
2024-02-20 15:42:04 +01:00
|
|
|
if (!hiddenInputState.enabled) {
|
2024-02-24 15:14:10 +01:00
|
|
|
// Calculate starting x-position to ensure dots stay centered within the input field
|
2024-02-26 01:33:19 +01:00
|
|
|
int xstart = dots.center ? (DOT_AREA_WIDTH - TOTAL_DOTS_WIDTH) / 2 + DOT_PAD : DOT_PAD;
|
2024-02-21 12:42:18 +01:00
|
|
|
|
2024-02-26 01:33:19 +01:00
|
|
|
if (dots.currentAmount > MAX_DOTS)
|
|
|
|
xstart = (inputFieldBox.w + MAX_DOTS * (PASS_SIZE + PASS_SPACING) - PASS_SPACING - 2 * TOTAL_DOTS_WIDTH) / 2;
|
|
|
|
|
|
|
|
for (int i = 0; i < dots.currentAmount; ++i) {
|
|
|
|
if (i < DOT_FLOORED - MAX_DOTS)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (dots.currentAmount != DOT_FLOORED) {
|
|
|
|
if (i == DOT_FLOORED)
|
|
|
|
fontCol.a *= (dots.currentAmount - DOT_FLOORED) * data.opacity;
|
|
|
|
else if (i == DOT_FLOORED - MAX_DOTS)
|
|
|
|
fontCol.a *= (1 - dots.currentAmount + DOT_FLOORED) * data.opacity;
|
|
|
|
}
|
2024-02-19 00:08:03 +01:00
|
|
|
|
2024-02-24 15:14:10 +01:00
|
|
|
Vector2D dotPosition = inputFieldBox.pos() + Vector2D{xstart + i * (PASS_SIZE + PASS_SPACING), inputFieldBox.h / 2.f - PASS_SIZE / 2.f};
|
|
|
|
CBox box{dotPosition, Vector2D{PASS_SIZE, PASS_SIZE}};
|
2024-02-20 15:42:04 +01:00
|
|
|
g_pRenderer->renderRect(box, fontCol, PASS_SIZE / 2.0);
|
2024-02-26 01:33:19 +01:00
|
|
|
fontCol.a = DOT_ALPHA;
|
2024-02-20 15:42:04 +01:00
|
|
|
}
|
2024-02-19 22:09:00 +01:00
|
|
|
}
|
|
|
|
|
2024-02-19 23:58:59 +01:00
|
|
|
if (PASSLEN == 0 && !placeholder.resourceID.empty()) {
|
2024-02-20 01:53:49 +01:00
|
|
|
SPreloadedAsset* currAsset = nullptr;
|
|
|
|
|
|
|
|
if (!placeholder.failID.empty()) {
|
|
|
|
if (!placeholder.failAsset)
|
|
|
|
placeholder.failAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.failID);
|
|
|
|
|
|
|
|
currAsset = placeholder.failAsset;
|
|
|
|
} else {
|
|
|
|
if (!placeholder.asset)
|
|
|
|
placeholder.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.resourceID);
|
2024-02-19 23:58:59 +01:00
|
|
|
|
2024-02-20 01:53:49 +01:00
|
|
|
currAsset = placeholder.asset;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (currAsset) {
|
2024-02-19 23:58:59 +01:00
|
|
|
Vector2D pos = outerBox.pos() + outerBox.size() / 2.f;
|
2024-02-20 01:53:49 +01:00
|
|
|
pos = pos - currAsset->texture.m_vSize / 2.f;
|
|
|
|
CBox textbox{pos, currAsset->texture.m_vSize};
|
|
|
|
g_pRenderer->renderTexture(textbox, currAsset->texture, data.opacity * fade.a, 0);
|
|
|
|
} else
|
|
|
|
forceReload = true;
|
|
|
|
}
|
|
|
|
|
2024-02-26 18:37:19 +01:00
|
|
|
return dots.currentAmount != PASSLEN || data.opacity < 1.0 || forceReload;
|
2024-02-20 01:53:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void CPasswordInputField::updateFailTex() {
|
|
|
|
const auto FAIL = g_pHyprlock->passwordLastFailReason();
|
|
|
|
|
|
|
|
if (g_pHyprlock->passwordCheckWaiting())
|
|
|
|
placeholder.canGetNewFail = true;
|
|
|
|
|
|
|
|
if (g_pHyprlock->getPasswordBufferLen() != 0) {
|
|
|
|
if (placeholder.failAsset) {
|
|
|
|
g_pRenderer->asyncResourceGatherer->unloadAsset(placeholder.failAsset);
|
|
|
|
placeholder.failAsset = nullptr;
|
|
|
|
placeholder.failID = "";
|
2024-02-19 23:58:59 +01:00
|
|
|
}
|
2024-02-20 01:53:49 +01:00
|
|
|
return;
|
2024-02-19 23:58:59 +01:00
|
|
|
}
|
|
|
|
|
2024-02-20 01:53:49 +01:00
|
|
|
if (!FAIL.has_value() || !placeholder.canGetNewFail)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// query
|
|
|
|
CAsyncResourceGatherer::SPreloadRequest request;
|
|
|
|
request.id = "input-error:" + std::to_string((uintptr_t)this) + ",time:" + std::to_string(time(nullptr));
|
|
|
|
placeholder.failID = request.id;
|
|
|
|
request.asset = "<span style=\"italic\">" + FAIL.value() + "</span>";
|
|
|
|
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
|
|
|
|
request.props["font_family"] = std::string{"Sans"};
|
|
|
|
request.props["color"] = CColor{1.0 - font.r, 1.0 - font.g, 1.0 - font.b, 0.5};
|
|
|
|
request.props["font_size"] = (int)size.y / 4;
|
|
|
|
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
|
|
|
|
|
|
|
|
placeholder.canGetNewFail = false;
|
|
|
|
}
|
2024-02-20 15:42:04 +01:00
|
|
|
|
|
|
|
void CPasswordInputField::updateHiddenInputState() {
|
|
|
|
if (!hiddenInputState.enabled || (size_t)hiddenInputState.lastPasswordLength == g_pHyprlock->getPasswordBufferLen())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// randomize new thang
|
|
|
|
hiddenInputState.lastPasswordLength = g_pHyprlock->getPasswordBufferLen();
|
|
|
|
|
|
|
|
float r1 = (rand() % 100) / 255.0;
|
|
|
|
float r2 = (rand() % 100) / 255.0;
|
|
|
|
int r3 = rand() % 3;
|
|
|
|
int r4 = rand() % 2;
|
|
|
|
int r5 = rand() % 2;
|
|
|
|
|
|
|
|
((float*)&hiddenInputState.lastColor.r)[r3] = r1 + 155 / 255.0;
|
|
|
|
((float*)&hiddenInputState.lastColor.r)[(r3 + r4) % 3] = r2 + 155 / 255.0;
|
|
|
|
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
|
|
if (i != r3 && i != ((r3 + r4) % 3)) {
|
|
|
|
((float*)&hiddenInputState.lastColor.r)[i] = 1.0 - ((float*)&hiddenInputState.lastColor.r)[r5 ? r3 : ((r3 + r4) % 3)];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hiddenInputState.lastColor.a = 1.0;
|
|
|
|
hiddenInputState.lastQuadrant = (hiddenInputState.lastQuadrant + rand() % 3 + 1) % 4;
|
|
|
|
}
|